作为无监督学习算法的基础,学好聚类算法很关键,我之前介绍过kmeans聚类算法,现在系统的介绍一下聚类算法

1. 什么是分类

日常生活中我们会经常见到分类的情况,如家里大扫除时给物品归类,超市货架上商品分类等。分类就是先打标签后归类的行为。

2. 什么是聚类

聚类,顾名思义,是聚集不同类别的方式。和一般的分类不同,分类需要手动打标签,也就是所谓的有监督学习,聚类则无需打标签,会自动根据标签(样本属性)对样本进行区分,所以聚类属于无监督学习。举个例子,线下超市货架上的商品按类别摆放就属于分类而不属于聚类,原因是放置一个新的商品我们需要手动打上标签后摆放到货架;相反的,在线上购物平台,一个用户进行了点击浏览操作后系统自动将其划分为某一类别用户,这个操作就是聚类,因为无需对用户进行打标签就将其归类。聚类的流程如下图所示:在这里插入图片描述

3. 聚类有什么用

聚类在数据挖掘与分析中扮演着重要的角色,通过聚类分析我们可以了解到某一类簇(类别)的共有特性,也可以根据已有或者新的样本创建新的特征标签。此外,聚类在人工智能中也应用广泛,如:聚类可以根据不同用户的市场行为,将客户分成不同类型的群体,方便进行市场分析和后续的精准营销;聚类可以进行文本分析,将类似的语句进行划分,用于实现话术分类、话题发现等任务。

4. 聚类有哪几种

聚类根据不同的方式划分为以下三种:(先主要介绍前两种)

  1. 原型聚类

原型聚类是最简单而且最常用的聚类,通俗的说,原型聚类由一组初始的样本组成(也叫做初始簇),之后通过一系列的迭代划分,形成不同的簇(样本集合)。根据不同的原型表示、不同的迭代方法,可以产生不同的聚类方法。常见的原型聚类有K-means聚类。
优势:原型聚类算法原理简单,容易实现,适用于大规模数据集‌。
劣势:

  1. 需要预先指定簇的数量‌:K-means算法需要事先指定簇的数量K,这在实际应用中可能难以确定‌。
  2. ‌对初始值敏感‌:原型聚类的结果对初始质心的选择非常敏感,不同的初始值可能导致不同的聚类结果。
  3. 容易陷入局部最优‌:由于采用迭代优化方法,原型聚类可能陷入局部最优解,而不是全局最优解。
  4. 对噪声和异常点敏感‌:原型聚类对噪声和异常点较为敏感,可能会影响聚类效果。
  1. 密度聚类

密度聚类是根据数据点密度分布的无监督学习方法,它通过定义密度相连区域形成簇,能识别任意形状的簇并有效处理噪声,常见的密度聚类是DBSCAN算法。
密度聚类中常见的术语:

  1. 邻域半径(ε)‌:划定密度计算的范围。也就是规定距离不超过这个值的为邻域对象。
  2. 最小点数:核心对象邻域半径内所包含对象的最小个数,只有邻域半径内的对象数量超过了这个值才能认定当前对象为核心对象,也就是判定核心对象的阈值。
  3. 核心对象:只有邻域半径内的对象数量超过了这个值才能认定当前对象为核心对象,
  4. 密度直达:对象a的邻域中包含了对象b,那就说对象a和对象b是密度直达。
  5. 密度可达:如果对象a的邻域中不包含对象b,a和b都和某一对象c是密度直达的,那就说对象a和对象b是密度可达。
  6. 簇:密度可达的对象所组成的集合。
    优势:自动识别噪声、支持任意形状簇、无需指定簇数。‌‌
    劣势:对参数敏感,高维或大规模数据效率较低。‌‌
  1. 层次聚类

层次聚类试图在不同层次对数据集进行划分,从而形成树形的聚类结构。数据集划分可采用“自底向上”的聚合策略,也可采用“自顶向下”的分拆策略。

5. K-means聚类算法实现原理

算法步骤如下:

  1. 随机选择k个数据点作为初始的簇中心。(注意,正因为随机选择,所以可能导致不同的初始簇会有不同的聚类结果)
  2. 对于每个数据点,计算其到每个簇中心的距离,将其划分到距离最近的簇中。
  3. 对于每个簇,重新计算其簇中心,即将簇内所有数据点的坐标取平均值。
  4. 重复步骤2和步骤3,直到簇中心不再发生变化或达到预设的迭代次数。
  5. 最终得到k个簇,每个簇内的数据点距离尽可能接近,不同簇间的数据点距离尽可能远。

Java版本代码如下:

package kmeans;import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;/*** kmeans聚类工具类* @author zygswo**/
public class KmeansUtils<T> {private int initKNodeNb; //kmeans初始几何中心数量private List<T> trainData; //kmeans训练数据private DistanceType distanceType;/*** kmeans构造方法(默认为欧式距离公式)* @param initKNodeNb kmeans初始几何中心数量* @param trainData	训练数据*/public KmeansUtils(List<T> trainData, int initKNodeNb) {this.initKNodeNb = initKNodeNb;this.trainData = trainData;this.distanceType = DistanceType.EUCLID;}/*** kmeans构造方法(默认为欧式距离公式)* @param initKNodeNb kmeans初始几何中心数量* @param trainData	训练数据* @param distanceType 距离公式*/public KmeansUtils(List<T> trainData, int initKNodeNb, DistanceType distanceType) {this.initKNodeNb = initKNodeNb;this.trainData = trainData;this.distanceType = distanceType;}/*** kmeans模型训练*/public void fit(){//计算距离List<Map<String,Double>> initKNodeDistanceVal = Collections.synchronizedList(new ArrayList<>());//初始化几何列表List<List<T>> resList = Collections.synchronizedList(new ArrayList<>());if (this.trainData == null || this.trainData.isEmpty()) {throw new IllegalArgumentException("训练集为空");}if (this.initKNodeNb <=0) {throw new IllegalArgumentException("几何中心数量小于0");}if (this.initKNodeNb > this.trainData.size()) {throw new IllegalArgumentException("几何中心数量超过数组数量");}if (this.distanceType == null) {throw new IllegalArgumentException("距离类型为空");}//1.获取前initKNodeNb个数据放入initKNodeList列表中//初始化的几何中心,需要选择差异较大的
//		this.trainData.sort((T item1,T item2)-> {
//			return (int)(calcDiff(item1,this.trainData.get(0)) - calcDiff(item2,this.trainData.get(0)));
//		});this.trainData = sort(this.trainData);int step = this.trainData.size() / initKNodeNb;//选择从小到大的initKNodeNb个元素作为初始几何for (int i = 0; i < this.trainData.size() && resList.size() < initKNodeNb; i+=step) {List<T> temp = Collections.synchronizedList(new ArrayList<>());temp.add(this.trainData.get(i));resList.add(temp); //多个几何列表设置初始结点}
//		System.out.println(this.trainData);
//		System.out.println(resList.toString());//2.计算所有变量到不同的几何中心距离,如果稳定了(几何中心固定了),就退出循环while(true) {boolean balanced = true; //是否已经平衡for (T item: this.trainData) {double distance, minDistance = Double.MAX_VALUE; //求最小距离int preIndex = 0,afterIndex = 0; //preIndex-原位置initKNodeDistanceVal.clear();//计算几何中心for (int i = 0; i < initKNodeNb; i++) {if (resList.get(i).size() > 0)initKNodeDistanceVal.add(calc(resList.get(i))); //计算初始结点距离}//计算原来的位置for (int i = 0; i < initKNodeNb; i++) {if(resList.get(i).contains(item)) {preIndex = i;break;}}
//				System.out.println("item = " + item.toString());//计算不同变量到不同的几何中心距离for (int i = 0; i < initKNodeNb; i++) {if (resList.get(i).size() > 0 && i < initKNodeDistanceVal.size()) {distance = calcDistance(item, initKNodeDistanceVal.get(i));
//						System.out.println("distance = " + distance);
//						System.out.println("minDistance = " + minDistance);if (distance < minDistance) {minDistance = distance;afterIndex = i;}}					}
//				System.out.println("preIndex = " + preIndex);
//				System.out.println("afterIndex = " + afterIndex);//位置替换,如果替换就还没结束if (preIndex != afterIndex) {resList.get(preIndex).remove(item);resList.get(afterIndex).add(item);balanced = false;} //如果preIndex == afterIndex == 0if (preIndex == afterIndex) {//如果新增就还没结束if (!resList.get(preIndex).contains(item)) {resList.get(preIndex).add(item);balanced = false;}}}if (balanced){break;}}//打印结果for (List<T> list : resList) {System.out.println(list.toString());}}/*** 排序* @param trainData*/private List<T> sort(List<T> list) {List<T> res = new ArrayList<>();Map<Double,List<T>> map = new ConcurrentHashMap<>();//计算距离for(T item:list) {double distance = calcDiff(item,list.get(0));if (!map.containsKey(distance)) {List<T> arr = new ArrayList<>();arr.add(item);map.put(distance, arr);} else {List<T> arr = map.get(distance);arr.add(item);map.put(distance, arr);}}//按照距离从小到大排列SortedMap<Double,List<T>> sortedMap = new TreeMap<>(map);
//		System.out.println(sortedMap.toString());for (Double key: sortedMap.keySet()) {res.addAll(sortedMap.get(key));}return res;}/*** 计算距离* @param item1 item1* @param item2 item2* @return*/private double calcDiff(T item1, T item2) {List<T> list = Collections.synchronizedList(new ArrayList<>());list.add(item2);Map<String, Double> map = calc(list);double dist = calcDistance(item1, map);
//		System.out.println(item1.toString() + "=>" +item2.toString()+"dist = " + dist);return dist;}
/*** 计算距离* @param item 当前对象* @param map 几何中心* @return*/private double calcDistance(T item, Map<String, Double> map) {double distance = 0.0;//距离int level = 0;//根据距离公式判断距离计算等级Class<?> cls = item.getClass();Field[] fs = cls.getDeclaredFields();for (Field f : fs) {double dist1 = 0.0, dist2 = 0.0;f.setAccessible(true);//获取需要计算的参数Elem el = f.getAnnotation(Elem.class);if (el == null) {continue;}try {switch(el.type()) {case BASIC: break;case XUSHU://获取数组String[] arr = el.list();if (arr == null) {throw new IllegalArgumentException("序数属性需配置属性集合数组");}//数组排序Arrays.sort(arr);List<String> list = Arrays.asList(arr);//计算差距步长Double diffStep = 1 / (list.size() * 1.0);//获取当前对象序数属性的值Object value = f.get(item);dist1 = list.indexOf(value) * diffStep;break;case NUMBER: //获取当前对象数值属性的值Object value1 = f.get(item); //数据转换Double intVal = Double.parseDouble(String.valueOf(value1));dist1 = intVal;break;case ERYUAN://获取数组String[] arr1 = el.list();if (arr1 == null) {arr1 = new String[]{"0","1"};} else {//数组排序Arrays.sort(arr1);}//转列表List<String> list1 = Arrays.asList(arr1);//计算差距步长Double diffStep1 = 1 / (list1.size() * 1.0);Object value2 = f.get(item);int ind = list1.indexOf(value2);dist1 = ind * diffStep1;break;}//获取当前几何中心属性的值dist2 = map.get(f.getName());//计算距离switch(distanceType) {case EUCLID: level = 2; break;case MANHATTAN: level = 1;break;case QIEBIXUEFU: level = 100;break;}distance += Math.pow(Math.abs(dist1 - dist2),level);} catch(Exception ex) {throw new RuntimeException(ex.getMessage());}distance = Math.pow(distance, 1/(level * 1.0));}	return distance;}/*** 计算几何中心坐标* @param kNodeList* @return 几何中心坐标map*/private Map<String, Double> calc(List<T> kNodeList) {if (kNodeList == null || kNodeList.size() <= 0) {throw new IllegalArgumentException("几何中心列表数组为空");}//反射获取参数,形成数值数组Map<String, Double> result = new ConcurrentHashMap<>();T item = kNodeList.get(0);Class<?> cls = item.getClass();Field[] fs = cls.getDeclaredFields();for (Field f: fs) {//获取需要计算的参数Elem el = f.getAnnotation(Elem.class);if (el == null) {continue;}//将数据转换成数值Double dist = 0.0;switch(el.type()) {case BASIC: break;case XUSHU: //获取数组String[] arr = el.list();if (arr == null) {throw new IllegalArgumentException("序数属性需配置属性集合数组");}//数组排序Arrays.sort(arr);//转列表List<String> list = Arrays.asList(arr);//计算差距步长Double diffStep = 1 / (list.size() * 1.0);for (T kNode : kNodeList) {try {//获取当前对象序数属性的值Object value = f.get(kNode);int ind = list.indexOf(value);//求和dist += ind * diffStep;} catch (IllegalArgumentException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();}}break;case NUMBER: for (T kNode : kNodeList) {try {//获取当前对象数值属性的值Object value = f.get(kNode);//数据转换Double intVal = Double.parseDouble(String.valueOf(value));dist += intVal;} catch (IllegalArgumentException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();}}break;case ERYUAN://获取数组String[] arr1 = el.list();if (arr1 == null) {arr1 = new String[]{"0","1"};} else {//数组排序Arrays.sort(arr1);}//转列表List<String> list1 = Arrays.asList(arr1);//计算差距步长Double diffStep1 = 1 / (list1.size() * 1.0);for (T kNode : kNodeList) {try {//获取当前对象二元属性的值Object value = f.get(kNode);int ind = list1.indexOf(value);//求和dist += ind * diffStep1;} catch (IllegalArgumentException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();}}break;}dist /= (kNodeList.size() * 1.0); //求平均值result.put(f.getName(), dist);}return result;}public static void main(String[] args) {List<Student> trainData = new ArrayList<>();trainData.add(new Student("zyl",28,"男"));trainData.add(new Student("sjl",28,"女"));trainData.add(new Student("xxx",27,"男"));trainData.add(new Student("stc",30,"男"));trainData.add(new Student("wxq",30,"女"));trainData.add(new Student("zzz",27,"男"));trainData.add(new Student("sss",27,"女"));trainData.add(new Student("mmm",20,"男"));trainData.add(new Student("qqq",20,"女"));trainData.add(new Student("666",30,"男"));trainData.add(new Student("nnn",20,"男"));trainData.add(new Student("lll",25,"男"));trainData.add(new Student("ppp",25,"女"));trainData.add(new Student("aaa",19,"男"));trainData.add(new Student("ccc",19,"女"));KmeansUtils<Student> utils = new KmeansUtils<>(trainData, 3);utils.fit();}
}

student类

package kmeans;import java.util.List;public class Student{@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + ", gender=" + gender + ", myHobby=" + myHobby+ ", myDream=" + myDream + "]";}public List<MyHobby> getMyHobby() {return myHobby;}public Student setMyHobby(List<MyHobby> myHobby) {this.myHobby = myHobby;return this;}public String getName() {return name;}public Student setName(String name) {this.name = name;return this;}public int getAge() {return age;}public Student setAge(int age) {this.age = age;return this;}public String getGender() {return gender;}public Student setGender(String gender) {this.gender = gender;return this;}String name;@Elem(type = ElemType.NUMBER)int age;@Elem(type = ElemType.XUSHU,list={"男","女"})String gender;@Elem()List<MyHobby> myHobby;@Elem()List<String> myDream;public Student(String name, int age, String gender) {super();this.name = name;this.age = age;this.gender = gender;}public Student(String name, int age, String gender,List<MyHobby> myHobby) {this(name,age,gender);this.myHobby = myHobby;}public Student(String name, int age, String gender,List<MyHobby> myHobby, List<String> myDreams) {this(name,age,gender);this.myHobby = myHobby;this.myDream = myDreams;}
}

distanceType类

public enum DistanceType {EUCLID("欧几里得距离"),MANHATTAN("曼哈顿距离"),QIEBIXUEFU("切比雪夫距离");private String name;private DistanceType(String name) {this.setName(name);}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

elem注解


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Elem {ElemType type() default ElemType.BASIC; //属性类型String[] list() default {}; //选择项
}

elemType枚举类

/*** 元素属性类型(标称属性、序数属性、数值属性、二元属性)* @author zygswo**/
public enum ElemType {BASIC("标称属性"),XUSHU("序数属性"),NUMBER("数值属性"),ERYUAN("二元属性");private String name;private ElemType(String name) {this.setName(name);}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

6. DBSCAN聚类算法实现原理

算法步骤如下:

  1. 遍历样本集,获取核心对象集,记为Ω(邻域范围内的对象数量超过最小点数的对象),每一个核心对象作为初始簇。
  2. 随机选择一个核心对象,记为α,插入队列。
  3. 从队列中获取第一个核心对象,记为β。
  4. 获取β的邻域对象集,遍历当前邻域对象集,依次不重复的加入队列,并从样本集中剔除领域对象;如果没有领域对象就继续。
  5. 重复步骤3和4,直到队列为空。
  6. 从核心对象集Ω中剔除当前核心对象α,设置α的最终簇为原队列中的所有对象。
  7. 如果核心对象集Ω为空就结束,否则重复步骤1至6。
  8. 得到k个最终簇,返回结果。

Java版本代码如下:

package dbscancluster;import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;/*** 密度聚类算法* @author zygswo**/
public class DbScanUtils {class Item {@Overridepublic String toString() {return "Item [density=" + density + ", rate=" + rate + ", sweetRate=" + sweetRate + "]";}public double getDensity() {return density;}public void setDensity(double density) {this.density = density;}public double getRate() {return rate;}public void setRate(double rate) {this.rate = rate;}public double getSweetRate() {return sweetRate;}public void setSweetRate(double sweetRate) {this.sweetRate = sweetRate;}double density;double rate;double sweetRate;public Item() {super();}public Item(double density, double rate) {super();this.density = density;this.rate = rate;}	public Item(double density, double rate,double sweetRate) {this(density,rate);this.sweetRate = sweetRate;}	}/*** main* @param args*/public static void main(String[] args) {System.out.println("----------------------------------------");System.out.println("------------- DbScan 密度聚类  -------------");System.out.println("----------------------------------------");DbScanUtils density = new DbScanUtils();Item item1 = density.new Item(6.77,0.55,0.33);Item item2 = density.new Item(6.57,0.45,0.12);Item item3 = density.new Item(6.76,0.55,0.30);Item item4 = density.new Item(6.58,0.45,0.14);Item item5 = density.new Item(4.28,0.99);Item item6 = density.new Item(7.28,0.48);Item item7 = density.new Item(6.70,0.52,0.35);Item item8 = density.new Item(4.32,0.96);List<Item> items = new ArrayList<>();items.add(item1);items.add(item2);items.add(item3);items.add(item4);items.add(item5);items.add(item6);items.add(item7);items.add(item8);System.out.println("---------- start ----------");long startTime = System.currentTimeMillis();List<List<Item>> result = getDensityCluster(items, 0.1, 1);for (List<Item> res:result) {System.out.println(res);}System.out.println("---------- end ----------");System.out.println("---------- 总耗时: " + (System.currentTimeMillis() - startTime) + "----------");}/*** 获取密度聚类* @param items 样本集* @param distThreashold 邻域内的对象相剧最远的阈值* @param sizeThreashold 核心对象所需的领域中的最少对象数量阈值* @return 聚类*/public static <T> List<List<T>> getDensityCluster(List<T> items,double distThreashold,int sizeThreashold) {List<List<T>> result = new ArrayList<>();Queue<T> densityQueue = new LinkedBlockingQueue<>();//设置临时聚类集,初始化为样本集List<T> tempItemList = new ArrayList<>();for (T item:items) {tempItemList.add(item);}//获取核心对象List<T> coreItemList = new ArrayList<>();for (T item:tempItemList) {List<T> adjacentItemList = getAdjacent(item,tempItemList,distThreashold);if (adjacentItemList.size() >= sizeThreashold) {coreItemList.add(item);}}if (coreItemList.isEmpty()) {return result;}//判断核心对象列表是否为空while (!coreItemList.isEmpty()) {List<T> tempClusterList = new ArrayList<>(); //临时簇//随机抽取一个核心对象int i = new Random().nextInt(coreItemList.size());//放入队列中densityQueue.add(coreItemList.get(i));//判断队列是否为空while(!densityQueue.isEmpty()) {//获取队列中第一个对象(并从队列中删除)T tempCoreItem = densityQueue.poll();//查找当前对象的所有领域并放入队列中List<T> adjacentItemList = getAdjacent(tempCoreItem,tempItemList,distThreashold);for (T adjacentItem:adjacentItemList) {//查找当前对象的所有邻域并放入队列中if (!densityQueue.contains(adjacentItem)) {densityQueue.add(adjacentItem);}//邻域对象放入临时簇里if (!tempClusterList.contains(adjacentItem)) {tempClusterList.add(adjacentItem);}//从临时聚类集中删除当前对象的邻域if (tempItemList.contains(adjacentItem)) {tempItemList.remove(adjacentItem);}}}//添加簇if (!tempClusterList.isEmpty()) {result.add(tempClusterList);}//清除当前核心对象coreItemList.remove(i);}return result;}/*** 获取邻域数组* @param item 目标对象* @param tempItemList 所有对象列表* @param distThreashold 邻域内的对象相剧最远的阈值* @return*/private static <T> List<T> getAdjacent(T item, List<T> tempItemList,double distThreashold) {List<T> result = new ArrayList<>();for (T tempItem:tempItemList) {//计算距离double dist = DiffUtils.calculDiff(item, tempItem);if (dist <= distThreashold) {result.add(tempItem);}}return result;}
}

距离计算类

package dbscancluster;import java.lang.reflect.Field;public class DiffUtils {/*** 通过反射计算欧几里得距离* @param obj1 对象1* @param obj2 对象2* @return 欧几里得距离*/public static <T> double calculDiff(T obj1,T obj2) {if (obj1 == null || obj2 == null) {throw new IllegalArgumentException("参数为空");}Class<?> cls = obj1.getClass();double total = 0;while(!cls.getSimpleName().equalsIgnoreCase("Object")) {Field[] field = cls.getDeclaredFields();for (Field f:field) {try {Object fVal = f.get(obj1);if (fVal instanceof Double) {double obj1Val = f.getDouble(obj1);double obj2Val = f.getDouble(obj2);total += EuclidDistance(obj1Val, obj2Val, 1.0);} else if (fVal instanceof Float) {Float obj1Val = f.getFloat(obj1);Float obj2Val = f.getFloat(obj2);total += EuclidDistance(obj1Val, obj2Val, 1.0);} else if (fVal instanceof Integer) {int obj1Val = f.getInt(obj1);int obj2Val = f.getInt(obj2);total += EuclidDistance(obj1Val, obj2Val, 1.0);} else if (fVal instanceof Short) {Short obj1Val = f.getShort(obj1);Short obj2Val = f.getShort(obj2);total += EuclidDistance(obj1Val, obj2Val, 1.0);} else if (fVal instanceof Long) {long obj1Val = f.getLong(obj1);long obj2Val = f.getLong(obj2);total += EuclidDistance(obj1Val, obj2Val, 1.0);}} catch (IllegalArgumentException | IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();}}cls = cls.getSuperclass();}//求平方根if (total >= 0) {total = Math.sqrt(total);total = Double.parseDouble(String.format("%.3f", total));} else {throw new IllegalArgumentException("参数计算异常");}return total;}/*** 欧几里得距离公式* @param x0* @param x1*/private static double EuclidDistance(int x0, int x1,double weight){return Math.pow(Math.abs(x0-x1), 2) * weight;}/*** 欧几里得距离公式* @param x0* @param x1*/private static double EuclidDistance(long x0, long x1,double weight){return Math.pow(Math.abs(x0-x1), 2) * weight;}/*** 欧几里得距离公式* @param x0* @param x1*/private static double EuclidDistance(double x0, double x1,double weight){return Math.pow(Math.abs(x0-x1), 2) * weight;}
}

———————————— (未完待续)————————————

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/86007.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/86007.shtml
英文地址,请注明出处:http://en.pswp.cn/web/86007.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PostgreSQL 性能优化与集群部署:PGCE 认证培训实战指南

&#xff5c;深夜被数据库报警惊醒&#xff1f; &#xff5c;海量数据查询卡死业务&#xff1f; &#xff5c;主库宕机导致服务中断&#xff1f; 如果你正被这些PostgreSQL生产难题困扰&#xff0c;是时候系统掌握数据库内核优化与高可用架构了&#xff01;深度求索联合PG分会…

Java 对象映射 数据库表映射 工具类 两个对象/表实现映射转换

场景&#xff1a;需要将一个对象的各个字段中的数据映射到另一个对象的字段数据中&#xff0c;或将一个数据库表映射到另一张表中。 本文使用泛型编程实现了一个对象映射功能的工具类。 需要源对象&#xff0c;映射关系map&#xff0c;目标类。由于是动态的类&#xff0c;所以…

Linux离线搭建Jenkins

Linux离线搭建Jenkins(centos7) Jenkins简介: Jenkins只是一个平台&#xff0c;真正运作的都是插件。这就是jenkins流行的原因&#xff0c;因为jenkins什么插件都有&#xff0c;Hudson是Jenkins的前身&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控程序重…

从零学习linux(2)——管理

一.用户管理 1.用户属性 用户名、口令、用户ID&#xff08;UID&#xff09;、用户主目录&#xff08;HOME&#xff09;、用户shell 2. 3. 4.adduser添加用户 语法&#xff1a;adduser 用户名 如# adduser superw 添加用户名为superw的新用户 5.deluser删除用户 语法&am…

《贵州棒垒球》有什么国家级比赛·棒球1号位

中国国家级棒球比赛盘点 | 小白入门指南 3月 | 中国棒球联赛 (China Baseball League) 国内最高水平职业联赛&#xff0c;各省市职业队角逐冠军&#xff01; 英文&#xff1a;Top-tier professional event with teams nationwide. 5月 | 全国青年棒球锦标赛 (National Youth …

该项目名为“EduPal“,AI推荐

## 完整代码实现 ### 后端代码 (app.py) python import os import json import uuid import requests from datetime import datetime from flask import Flask, render_template, request, jsonify from dotenv import load_dotenv # 加载环境变量 load_dotenv() app …

C++法则15:匹配失败并不是一种错误(Substitution Failure Is Not An Error)。

C法则15&#xff1a;匹配失败并不是一种错误(Substitution Failure Is Not An Error)。 应用例子&#xff1a; SFINAE &#xff1a;关于is_class&#xff0c;is_base_of&#xff0c;C编译器的魔法器&#xff0c;如何实现&#xff0c;is_class&#xff0c;is_base_of。_c is cl…

Ollama客户端 + Docker搭建本地知识库(Mac系统)

一、环境准备 1. 安装Ollama客户端 官网下载&#xff1a;https://ollama.com 验证安装&#xff1a; ollama --version2. 安装Docker Desktop 下载地址&#xff1a;https://www.docker.com/products/docker-desktop 安装后确保Docker状态为"Running" 二、基础搭建…

FastMCP 2.9 版本详解:MCP 原生中间件与类型转换增强

下面我将从三个方面来讲解这个&#xff0c;第一是讲解2.9版本的更新&#xff0c;第二是讲解什么将手动解析底层JSON-RPC 消息&#xff0c;丢失 FastMCP 高层语义&#xff0c;第三是讲一讲&#xff0c;什么叫做中间件。不了解的兄弟们系好安全带&#xff0c;我们准备发车了&…

LTspice仿真6——PWL折线波产生

1.自定义波形 2.自定义波形周期 3.以文件.txt的形式定义折线波 4.通过C语言编程&#xff0c;一系列操作&#xff0c;生成自定义正弦波&#xff08;可自定义性强&#xff09;

FunASR搭建语音识别服务和VAD检测

调整VAD参数 1. 查找VAD模型的配置文件 FunASR中的VAD模型为FSMN-VAD&#xff0c;参数配置类为VADXOptions&#xff0c;可以在以下路径中找到&#xff1a; /workspace/FunASR/runtime/python/onnxruntime/funasr_onnx/utils/e2e_vad.py 其中&#xff0c;VADXOptions类定义了…

多模态大模型(从0到1)

文章目录 一、多模态大模型二、常见模态组合 典型应用场景三、多模态&#xff08;模型 框架&#xff09;1. 多模态模型2. 多模态框架 —— 开源项目推荐&#xff08;可快速上手&#xff09; 四、入门与学习路线1. 理论基础2. 主流多模态模型实战3. 进阶与应用拓展&#x1f4d…

# Vue.js 精确轮播组件实现详解

## &#x1f4d6; 概述 本文详细介绍了一个基于 Vue.js 开发的高精度轮播组件&#xff0c;该组件实现了精确的卡片对齐和平滑滚动效果。组件支持混合布局&#xff08;大卡片网格布局&#xff09;&#xff0c;具备智能位置计算和精确滚动控制功能。 ## ✨ 组件特点 ### &#x1…

将RESP.app的备份数据转码成AnotherRedisDesktopManager的格式

将RESP.app的备份数据转码成AnotherRedisDesktopManager的格式 最近发现了AnotherRedisDesktopManager&#xff0c;这个软件可以直接展示proto数据。 将RESP.app导出的json文件&#xff0c;转码为AnotherRedisDesktopManager的ano文件&#xff08;是一个list转了base64&#xf…

前端基础知识JavaScript系列 - 09(JavaScript原型,原型链 )

一、原型 JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象 当试图访问一个对象的属性时&#xff0c;它不仅仅在该对象上搜寻&#xff0c;还会搜寻该对象的原型&#xff0c;以及该对象的原型的原型&#xff0c;依次层层向上搜索&#xff0c;直到找到一个…

vue3+ts 使用VueCropper实现剪切图片

效果图&#xff1a; 参考文档&#xff1a; Vue-Cropper 文档Vue-Cropper 文档 安装VueCropper //npm安装 npm install vue-croppernext -d --save//yarn安装 yarn add vue-croppernext 引入组件 在main.ts中全局注册&#xff1a; import VueCropper from vue-cropper; i…

el-table特殊表头样式

el-table特殊表头样式 实现表头是按钮 <el-table-column align"center"><template slot"header"><el-buttonsize"mini"type"primary"icon"el-icon-plus"circleclick"addData"></el-button&g…

el-tree的属性render-content自定义样式不生效

需求是想要自定义展示el-tree的项&#xff0c;官网有一个:render-content属性&#xff0c;用的时候发现不管是使用class还是style&#xff0c;样式都没有生效&#xff0c;还会报一个错&#xff0c;怎么个事呢&#xff0c;后来发现控制台还会报一个错“vue.js:5129 [Vue warn]: …

银杏书签里的春天

春末的细雨沾湿了旧书扉页&#xff0c;我在泛黄的《飞鸟集》里发现那枚银杏书签时&#xff0c;窗外的梧桐树正抖落最后一片枯叶。深褐色的叶脉间夹着张字条&#xff0c;娟秀的字迹被岁月晕染&#xff1a;"给永远在奔跑的人。" 十年前的我在旧书店打工&#xff0c;每天…

spring-ai 1.0.0 学习(十四)——向量数据库

向量数据库是AI系统中常用的工具&#xff0c;主要用来存储文档片段及进行语义相似度查找 与传统数据库不同&#xff0c;它执行的是相似度查找而不是精确匹配 最小化样例 首先在application.properties中&#xff0c;根据所用Embedding模型&#xff0c;添加一个嵌入式模型型号…