上一篇介绍了朴素贝叶斯,那么这次讲讲距离公式
什么是距离公式
用自己的话来说距离公式就是判断两个属性(参数)相似度的度量公式,比如以两点间距离为例,A地经纬度为(110.9802,120.9932),B地经纬度为(110.9980,120.8280), C地经纬度为(98.0232,110.9829),那么我们可以得出|AB| < |AC|,也就是说A地离B地的距离更近。再举个例子,同一个学校里有不同的学生,学生又有性别、年龄、兴趣爱好等属性,那么如果两个学生之间的性别、年龄、兴趣爱好等属性越接近,那么这两个学生的距离公式所得出的结果也就越小,两个学生也就越相像。
为什么要有距离公式
距离公式存在的意义在于不仅可以计算两个数值属性的相似度(如同上述的经纬度信息),而且可以计算两个度量甚至标称属性的相似度。
* 数值、度量、标称、二元属性的定义详见: 数据挖掘介绍https://blog.csdn.net/qq_31236027/article/details/137046475
而计算相似度的意义在于之后可以区分不同对象的差异性,从而方便后续的挖掘关联规则并进行相似度推荐。
常见的距离计算方式(后续会拓展展开讲讲)
数值属性的距离计算公式:
- 欧几里得距离公式
- 曼哈顿距离公式
- 上确界距离(切比雪夫距离)公式
- 闵可夫斯基距离公式
序数属性的距离计算方式:
若设置Rif 表示第i个对象的第f个属性的值,且该属性为序数属性,该属性有n个可选的值,且每个值的权重相同,又设置Zif为序数距离计算后的值,则有:
Zif = (Rif - 1) / n
标称属性的距离计算公式:
d(i,j) = (p-m) / p ⇒ 个人理解为不同的属性占全部属性的比例。
二元属性的距离计算公式:
- Jaccard公式:sim(i,j) = q / q + r + s, d(i,j) = 1 - sim(i,j)
- 余弦相似度公式
混合属性的距离计算公式:
sim(i,j) = Σ(f=1, p) δij(f) * sim(i,j) (f) / Σ(f=1,p) δij(f)
怎么理解呢?后续会详细展开讲讲
欧几里得距离公式
假设有A地和B地,经纬度分别为(x1,y1)和(x2,y2)
那么AB间的距离d(A,B) = |AB| = (√该符号表示根号) √(x1-x2)^2 + (y1-y2)^2
也就是取不同数值属性的差的平方和再进行开根运算。得出的结果就是欧几里得距离公式得出的结果
曼哈顿距离公式
假设有A地和B地,经纬度分别为(x1,y1)和(x2,y2)
那么AB间的距离d(A,B) = |AB| = |x1-x2| + |y1 -y2|
也就是取不同数值属性的差的绝对值和。得出的结果就是曼哈顿距离得出的结果
闵可夫斯基距离公式
假设有A地和B地,经纬度分别为(x1,y1)和(x2,y2)
那么AB间的距离d(A,B) = √(h) |x1- x2|^h + |y1-y2|^h = (Σ(f=1,2) |xAf- xBf| ^h)^1/h
也就是取不同数值属性的差的h次方求和再进行开h次方根运算。得出的结果就是闵可
夫斯基距离公式得出的结果
上确界距离(切比雪夫距离)公式
假设有A地和B地,经纬度分别为(x1,y1)和(x2,y2)
那么AB间的距离d(A,B) = limh->∞((Σ(f=1,2) |xAf- xBf| ^h)^1/h = max(f=1, p) |xAf- XBf|
也就是取闵可夫斯基距离公式的极限,约等于差异最大的属性的差值。
上确界距离公式推导可见:https://blog.csdn.net/qq_31236027/article/details/106763491
序数属性的距离计算
例如:现在有一个属性叫成绩,其中有3个值,分别为好,一般,差,那么该属性可以进行序数化,也就是用数字代替文本求距离。(假设权重一致)
P(好) = (3-1)/(3-1) = 1, #前一个(3-1) 表示的是值的位置,后一个(3-1)是为了将值映射到[0.0,1.0]上
P(一般) = (2-1)/(3-1) = 0.5
P(差) = (1-1) / (3-1) = 0
之后计算距离就方便了,可以利用数值属性的距离计算公式进行计算。
标称属性的距离计算
标称属性计算就很简单,求出不相同的属性的数量去除以对象A和B之间所有标称属性
数量就是标称属性的距离。
二元属性的距离计算
Jaccard距离计算公式:
假设对象i属性值为1且对象j属性值也为1的二元属性数量为q,对象i属性值为0但对象j属性值为1的二元属性数量为s,对象i属性值为1但是对象j属性值为0的二元属性数量为r,对象i和对象j属性值均为0的属性数量为t,那么有:
sim(i,j) = q(对象i和对象j属性的值都为1) + t 对象i和对象j属性的值都为0) / (q+r+s+t)
又有:对象i和对象j属性值均为0的属性没有意义,所以上述公式可以优化成以下公式:
sim(i,j) = q / q + r + s = 1- d(i,j) #该公式为jaccard公式。
余弦相似度公式:
sim(i,j) = |A ∩ B| / |A ∪ B|
混合属性的距离计算
sim(i,j) = Σ(f=1, p) δij(f) * sim(i,j) (f) / Σ(f=1,p) δij(f)
就是可以简单理解为标称、序数、数值、二元属性的距离计算完成后进行求和之后取平均值,
相关代码
各位小伙伴可以借鉴以下,以下是距离公式的java实现
package diffUtil;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
/**
* 距离公式
* @author zygswo
*
*/
public class DiffUtils {
/**
* 测试对象
* @author zygswo
*
*/
class TestObject {
@Override
public String toString() {
return "TestObject [name=" + name + ", age=" + age + ", gender=" + gender + ", myHobby=" + myHobby
+ ", myDream=" + myDream + "]";
}
public List<MyHobby> getMyHobby() {
return myHobby;
}
public TestObject setMyHobby(List<MyHobby> myHobby) {
this.myHobby = myHobby;
return this;
}
public String getName() {
return name;
}
public TestObject setName(String name) {
this.name = name;
return this;
}
public int getAge() {
return age;
}
public TestObject setAge(int age) {
this.age = age;
return this;
}
public String getGender() {
return gender;
}
public TestObject setGender(String gender) {
this.gender = gender;
return this;
}
String name;
@Elem(weight=0.1,type = ElemType.NUMBER)
int age;
@Elem(weight=0.2,type = ElemType.XUSHU,list={"男","女"})
String gender;
@Elem(weight=0.3)
List<MyHobby> myHobby;
@Elem(weight=0.4)
List<String> myDream;
public TestObject(String name, int age, String gender) {
super();
this.name = name;
this.age = age;
this.gender = gender;
}
public TestObject(String name, int age, String gender,List<MyHobby> myHobby) {
this(name,age,gender);
this.myHobby = myHobby;
}
public TestObject(String name, int age, String gender,List<MyHobby> myHobby, List<String> myDreams) {
this(name,age,gender);
this.myHobby = myHobby;
this.myDream = myDreams;
}
}
/**
* 欧几里得距离公式
* @param obj1
* @param obj2
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public static <T> double EuclidDistance(T obj1, T obj2)
throws IllegalArgumentException, IllegalAccessException {
Class<?> cls = obj1.getClass();
Field[] fs = cls.getDeclaredFields();
double result = 0.0;
for (Field f:fs) {
f.setAccessible(true);
Object xVal = f.get(obj1);
Object yVal = f.get(obj2);
long x = xVal.hashCode();
long y = yVal.hashCode();
System.out.println("x = " + x);
System.out.println("y = " + y);
result += EuclidDistance(x,y,1.0);
System.out.println("result = " + result);
}
return Math.sqrt(result);
}
/**
* 计算相似度
* @param obj1
* @param obj2
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public static <T> T calculDiff(T obj1, T ... obj2)
throws IllegalArgumentException, IllegalAccessException {
double min = 1.0;
T mostLikelyItem = null;
for (T obj : obj2) {
double res = Double.parseDouble(calculDiff(obj1, obj));
System.out.println("res = " + res);
if (res < min) {
min = res;
mostLikelyItem = obj;
}
}
return mostLikelyItem;
}
/**
* 计算相似度
* @param obj1
* @param obj2
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
@SuppressWarnings("rawtypes")
public static <T> String calculDiff(T obj1, T obj2)
throws IllegalArgumentException, IllegalAccessException {
Class<?> cls = obj1.getClass();
Class<?> cls2 = obj2.getClass();
if (!cls.getName().equalsIgnoreCase(cls2.getName())) {
throw new IllegalArgumentException("参数类型不匹配");
}
Field[] fs = cls.getDeclaredFields();
double result = 0.0, restWeight = 1.0; //result=相似度,restWeight=剩余权重
int blankWeightFieldCount = 0; //elem中权重为空的field数量
//计算变量数量
for (Field f0:fs) {
Elem elem0 = f0.getAnnotation(Elem.class);
if (elem0 != null) {
if (elem0.weight() == 1) {
blankWeightFieldCount++;
} else {
restWeight = restWeight - elem0.weight();
restWeight = Double.parseDouble(
String.format("%.2f",restWeight)
);
}
if (restWeight < 0) {
throw new IllegalArgumentException("权重总量分配超过1");
}
}
}
for (Field f:fs) {
f.setAccessible(true);
Object xVal = f.get(obj1);
Object yVal = f.get(obj2);
Elem elem = f.getAnnotation(Elem.class);
if (elem == null) {
continue;
}
if (xVal instanceof ArrayList && yVal instanceof ArrayList) {
Object[] objList1 = ((ArrayList)xVal).toArray();
Object[] objList2 = ((ArrayList)yVal).toArray();
Object[] objList3 = new Object[objList1.length + objList2.length];
/**
* 1. 数组复制
* 2. 数组排序
* 3. 利用jaccard公式计算相似度(相异性 为 1- 相似度)
*/
//数组进行复制
System.arraycopy(objList1, 0, objList3, 0, objList1.length);
System.arraycopy(objList2, 0, objList3, objList1.length, objList2.length);
//数组排序
Arrays.sort(objList3);
int same = 0,total = objList3.length;
for (int i = 0; i < objList3.length-1;i++) {
Object pre = objList3[i];
Object after = objList3[i+1];
if (pre.equals(after)) {
same++; //重复的元素++
total--; //两个重复了,减去一个
i++;
}
}
//简单规则,jaccard公式计算相似度(相异性 为 1- 相似度)
System.out.println("same = " + same);
System.out.println("total = " + total);
if (total != 0) {
result += elem.weight() * (1 - same / (total * 1.0));
System.out.println("result = " + result);
}
} else {
double temp = calcDiff(f,xVal,yVal);
System.out.println("temp = " + temp);
/**
* 如果field权重为1,那么相似度 = temp /权重为1的所有field数量
* 否则就按照权重进行分配。
* 例如:
*/
if (elem.weight() > 0 && elem.weight() < 1) {
temp = temp * elem.weight();
} else {
temp = temp * restWeight / blankWeightFieldCount;
}
// System.out.println("temp = " + temp);
result += temp;
System.out.println("result = " + result);
}
}
return String.format("%.2f",result);
}
private static double calcDiff(Field f,Object xVal, Object yVal) {
long x = xVal.hashCode();
long y = yVal.hashCode();
System.out.println("x = " + x);
System.out.println("y = " + y);
Elem elem = f.getAnnotation(Elem.class);
List<String> elemVals = Arrays.asList(elem.list());
double temp = 0.0;
switch(elem.type()) {
case NUMBER:
if (x == y) {
temp = 0;
} else {
double max = x > y ? x : y;
temp = Math.sqrt(EuclidDistance(x,y,1.0))/(max * 1.0);
temp = Double.parseDouble(String.format("%.2f", temp));
}
break;
case ERYUAN: temp = (x == y) ? 0 : 1; break;
case BASIC: temp = (x == y) ? 0 : 1;break;
case XUSHU:
int xIndex = elemVals.indexOf(xVal);
int yIndex = elemVals.indexOf(yVal);
System.out.println("xIndex = " + xIndex);
System.out.println("yIndex = " + yIndex);
if (xIndex == -1) {
throw new IllegalArgumentException("第一个对象的序数参数" + f.getName() + "值不匹配");
}
if (yIndex == -1) {
throw new IllegalArgumentException("第二个对象的序数参数" + f.getName() + " 值不匹配");
}
temp = Math.abs(xIndex - yIndex)/(elemVals.size() * 1.0); break;
default:
break;
}
return temp;
}
/**
* 欧几里得距离公式
* @param x0
* @param x1
*/
private static double EuclidDistance(long x0, long x1,double weight){
return Math.pow(Math.abs(x0-x1), 2) * weight;
}
/**
* 数据统计
*/
private static <T extends TestObject> List<MyStatistics> getMyStatistics(T... objList){
Map<String, List<MyHobby>> hobbyMap = new ConcurrentHashMap<>();
Map<String, Integer> countMap = new ConcurrentHashMap<>();
List<MyStatistics> myStatistics = new ArrayList<>();
for (T obj: objList) {
String gender = obj.getGender();
countMap.put(gender,countMap.get(gender)== null ? 1 : countMap.get(gender) + 1);
if (hobbyMap.get(gender) == null) {
List<MyHobby> myHobbyList = obj.getMyHobby();
hobbyMap.put(gender,myHobbyList);
System.out.println(gender);
System.out.println(myHobbyList.toString());
} else {
List<MyHobby> mapList = hobbyMap.get(gender);
List<MyHobby> myHobbyList = obj.getMyHobby();
for (MyHobby myHobby: myHobbyList) {
if (!mapList.contains(myHobby)) {
mapList.add(myHobby);
}
}
hobbyMap.put(gender,mapList);
}
}
for (String gender: hobbyMap.keySet()) {
myStatistics.add(new MyStatistics().setGender(gender)
.setMyHobbyList(hobbyMap.get(gender)).setNbPerson(countMap.get(gender)));
}
return myStatistics;
}
public static void main(String[] args) {
DiffUtils util = new DiffUtils();
MyHobby h1 = new MyHobby("爬山");
MyHobby h2 = new MyHobby("音乐");
MyHobby h3 = new MyHobby("看书");
MyHobby h4 = new MyHobby("追剧");
MyHobby h5 = new MyHobby("摄影");
MyHobby h6 = new MyHobby("户外运动");
List<MyHobby> list1 = new ArrayList<>();
list1.add(h1);
list1.add(h2);
list1.add(h3);
list1.add(h4);
list1.add(h5);
List<MyHobby> list2 = new ArrayList<>();
list2.add(h1);
list2.add(h2);
list2.add(h3);
list2.add(h4);
List<MyHobby> list3 = new ArrayList<>();
list3.add(h6);
list3.add(h2);
List<String> characterList1 = new ArrayList<>();
characterList1.add("坚强");
characterList1.add("自信");
characterList1.add("乐观");
List<String> characterList2 = new ArrayList<>();
characterList2.add("坚强");
characterList2.add("自信");
characterList2.add("乐观");
TestObject obj1 = util.new TestObject("zyg",18,"男",list1,characterList1);
TestObject obj2 = util.new TestObject("zcx",15,"女",list2,characterList2);
TestObject obj3 = util.new TestObject("zyg",22,"男",list3,characterList1);
System.out.println(getMyStatistics(obj1,obj2,obj3));
try {
double res = Double.parseDouble(calculDiff(obj1,obj2));
System.out.println("相似度:" + (1-res));
System.out.println(calculDiff(obj1,obj2,obj3));
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
—————————————— 有问题的话评论区见——————————————