寻找“最好”(6)——心的距离

   “距离”这个词经常在用到,在初中几何上,它指两点间直线的长度,想要测量它很容易,然而果真如此吗?乘坐出租车从家到公司,下车后计价表显示30公里,这可不是两点间的直线。《三国》里,探马回报:“袁军距我军30里处的官渡处下寨,绵延百里”,到底是30里还是百里,怎样才算30里?2018年法国队赢得世界杯冠军,距离他们上次夺冠,已经过去了20年,这里的距离又是时间的跨度。一对单身男女相亲,在一顿无聊的晚餐后得出彼此“距离太远”的结论,人心的距离又该如何测量?

距离的多种度量

  先来看一个简单的例子,平坦的地面上有一个边长为1的正方形凹陷,AB两点位于凹陷的边缘处,如下图所示,AB两点间的距离是多少?

  暂且不考虑边长的单位,假设一个成年人正好可以一步跨越,那么这个人从A到B所经过的距离是1,如下图所示:

  一个身材矮小的少年来了,他把凹陷当作楼梯,先下后上,于是有了这样的行进路线:

  少年经过的实际距离是两个三角形的斜边,具体数值是:

  又来了一个幼儿园的小朋友,他需要手脚并用,爬上爬下,行进距离是3:

  现在有意思了,一个简单的行进,只因为有了一个小小的凹陷就导致了三种不同的行进距离。实际上也许有更多距离,比如一个文艺青年用大跳跨越了一个优美的弧线:

  也许有人会说,反正结果都是从A到B,简单的计算为1不就好了?从空间转移来说这没错,但是在自然状态下,各种运动所消耗的能量不同,小朋友爬上爬下的消耗一定比成年人一步跨越要多。类似的例子很多,比如盘山路,更没办法算成两点间的直线了。

  其实例子中的几种测量都有道理,根据实际需要和条件的变化,改变距离的计算方法才能得到相对靠谱的数值,接下来将要介绍的就是几种常见的距离计算方法。

一维空间的距离

  一维空间可以看作一把带有刻度的直尺,在一维空间上计算距离是最简单的,只需要取两点间数值差的绝对值就可以了,如下图所示:

  常见的时间轴就是典型的一维距离,此时距离代表时间的跨度:

欧几里德距离

  欧几里得距离(Euclidean Distance)又称欧式距离或欧几里得度量,是以空间为基准的两点之间最短距离,简单地说,就是两点之间直线最短的概念。

  二维空间内的算比较简单,主要是点到点和点到直线,如下图所示:

  AB两点间的距离:

  所有与原点的欧几里德距离为1的点可以构成一个半径为1的圆:

  计算A点与直线的距离前,需要先把直线的表达式做个转化,变成ax + by + c = 0的形式:

  A(xA, yA)到直线 2x – y – 3 = 0的距离:

  三维空间内,主要是点到点和点到平面的距离,如下图所示,Q是平面ax + by + cz = d上的一点,P是平面外的另一点,PQ垂直于平面:

  PQ间距离的计算公式与二维空间相同,只是多了一个分量:

  计算P到平面的距离同样需要先把平面转化一下,变成ax + by + cz – d = 0,这样就和二维空间内的计算相同:

  类似的公式也可以推广到多维空间。P和Q是n维空间内的两点,PQ间的距离:

  在第1章已经讲过,n维空间的超平面可以写成:

  点到超平面的距离公式:

  将P点代入到公式中,点P到超平面的距离:

  Python可以很方便地计算欧氏距离,代码如下:

import numpy as np

x1 = [1, 1]
x2 = [2, 2]
np_x1 = np.array(x1)
np_x2 = np.array(x2)

# 直接使用公式计算
d1 = np.sqrt(np.sum((np_x1 - np_x2) ** 2))
# 使用内置的范数函数计算
d2 = np.linalg.norm(np_x1 - np_x2)

  代码中两种方式的计算结果相同。

曼哈顿距离

  想象一下在曼哈顿街头的一个路口坐出租车到另一个路口,司机会按照两个路口的直线距离行进吗?大多数时候不会,除非无视交通规则并且能穿越大楼。下图中的AB两点间的直线是欧几里德距离,折线是出租车行的实际行进距离。早在十九世纪,赫尔曼·闵可夫斯基就在曼哈顿街区研究过,将其命名为“曼哈顿距离(Manhattan Distance)”(如果在今天,可能被命名为“导航距离”)。

  城市街区大多数是在地势平缓的二维平面,两点A(x1, y1)和B(x2, y2)间曼哈顿距离的计算公式是:

  在AB间曼哈顿距离相同的情况下,可能有多种行进路线:

  所有与原点的曼哈顿距离为1的点可以构成一个边长为根号2的正方形:

  n维空间中的两点P和Q间的曼哈顿距离:

  Python计算曼哈顿距离:

import numpy as np

x1 = [1, 3]
x2 = [4, 9]
np_x1 = np.array(x1)
np_x2 = np.array(x2)

d = np.sum(np.abs(np_x1 – np_x2))

  现在来看看《三国》中哨骑的探报,如果把军队变成团伙,将官渡战场的大战变成曼哈顿街头的火拼,就可以看出,哨骑回报的距离是袁军先头部队到曹军的曼哈顿距离。

切比雪夫距离

国际象棋中,国王走一步可以直行、横行、斜行移动到相邻八个方格中的任意一个,如下图所示:

如果把两个相邻方格的距离记为1,国王从方格A(x1, y1)走到方格B(x2, y2)最少需要的最短距离,就是切比雪夫距离(Chebyshev distance),下图展示了国王从棋盘某一方格处,到达其它方格的距离:

  在二维空间内,A(x1, y1)和B(x2, y2)两点间的切比雪夫距离是两点横坐标差的绝对值与纵坐标差的绝对值中较大的那个:

  扩展到多维空间,PQ间的切比雪夫距离:

  棋盘上格子是距离的离散表示,在真正的二维坐标中,与原点距离为1的点会构成一个边长为2的正方形:

  对比上一节同为正方形的曼哈顿距离,发现它们应该可以互相转换。实际上,在二维空间内曼哈顿距离与切比雪夫距离的坐标转换关系是:

  Python计算切比雪夫距离:

import numpy as np

x1 = [1, 3]
x2 = [4, 9]
np_x1 = np.array(x1)
np_x2 = np.array(x2)

d = np.max(np.abs(np_x1 – np_x2))

夹角余弦

  夹角余弦(Cosine Similarity)测量的是两个样本间的相似性,样本间的距离越近,相似度越高。

  夹角余弦来源于向量点积的几何意义,两个向量AB的点积等于AB的模乘以二者的夹角余玄:

  可以看出,cosθ表示的是AB方向的相似度,与它们的大小无关,虽然下图中A’ 的模长远小于A的模长,但AB的夹角余弦和等于A’B的相等:

  扩展到多维空间,PQ间的夹角余弦:

  Python能够方便地使用向量计算夹角余弦,下面给出了两种计算方式:

# 使除法变成精确除法
from __future__ import division
import numpy as np

x1 = [1, 2, 3, 5]
x2 = [2, 3, 4, 6]
np_x1 = np.array(x1)
np_x2 = np.array(x2)

result1 = np.dot(np_x1, np_x2) / np.sqrt(np.dot(np_x1, np_x1) *  np.dot(np_x2, np_x2))
result2 = np.dot(np_x1, np_x2) / (np.linalg.norm(np_x1) * np.linalg.norm(np_x2))
theta = np.arccos(result1)

print(‘result1 = %f, result2 = %f, theta = %f‘ % (result1, result2, theta))
# result1 = 0.993073, result2 = 0.993073, theta = 0.117774

  夹角余弦取值范围是[-1,1],它提供了以下几个信息:

  • 余弦越大,两个向量的夹角越小,相似度越高;余弦越小,两个向量的夹角越大,相似度越低。
  • 两个向量的方向重合时余弦等于1;两个向量的方向完全相反余弦等于-1;两个向量垂直是,余弦值等于0。
  • θ < 90°,A·B > 0;θ > 90°,A·B < 0;θ = 90°,A·B = 0。

其它度量方法

  还有很多种方法用于度量距离,比如经常在推荐系统用来度量偏好习惯,但看起来不那么直观的的皮尔逊相关度;度量符号或布尔值个体间相似度的Jaccard系数和谷本系数;在信息论中,度量两个等长字符串之间对应位置的不同字符个数的汉明距离;还有马氏距离,闵可夫斯基距离,以及判断整个系统内部样本分布集中程度的信息熵等等。这些度量方法各有优缺点,不同的度量方法有些时候对于算法的结果差异很大,在实践中,可能要在不同的方法中反复切换,根据实际效果进行对比,最终选取效果最好的一个。

人心的距离

  也许是为了摆脱单身状态,也许是为了应付长辈的特意安排,许多年轻的单身男女都参加过相亲,但是大多数相亲都是以“三观不合”或“距离太远”为由而没有下文。人与人之间的距离似乎是一个文学词汇,并非物理意义上的距离,真的能够测量吗?怎么才算三观基本一致?接下来,我们就用本章的知识去尝试度量人与人之间的距离。

相亲

  小明是个28岁的城市白领,国庆期间在老妈的安排下分别见了可可、乐乐、小枫和小柔四个女孩。经过逛街吃饭后,小明对四个个女孩有了初步的了解,觉得她们都十分可爱,四个女孩似乎也想进一步发展。“你喜欢哪一个呢?”老妈问,“这个……”小明犯了难,不能脚踏四只船啊。“对了,就选和自己距离最近的!”小明想,于是他根据自己最关心的问题绘制了这样一个表格:

  小明觉得文字对比不够直观,所以他的第一步是把文字转换为数字。

数据预处理

  为了方便比较,先将非数值属性量化。小明有三个爱好,所以设自己的爱好值是3,同时设女孩们的初始爱好值是0,如果一个女孩有一个爱好与自己相同,则该女孩的爱好值加1,例如可可与小明都喜欢读书,所以可可的爱好值是1。同时小明也注意到,有些爱好虽然不同,但及其相近,比如音乐与唱歌,所以把二者看作同一爱好。于是,小明得到了下面的数据:

  其中年龄的数值较大,直接使用会占据较大的权重,所以小明对它们做了进一步处理,他以自己的数据为标准,将女孩的数据与自己相减。此外,他对是否要小孩看的很重要,对年龄差距并不十分看中,所以分别增加和缩减了两个维度的权重。现在数据数值都在一个较小的范围内:

度量距离

  现在终于可以测量了,看看那个女孩和小明最般配。不妨先将学历和爱好拿出来,在平面上看看距离,如下图所示:

  看起来小枫和自己最近,乐乐相对较远。当所有维度都参与计算时,小明使用下面的代码帮助计算:

# 使除法变成精确除法
from __future__ import division
import numpy as np

# 欧几里德距离
def d_euclidean(x1, x2):
    return np.linalg.norm(x1 - x2)

# 曼哈顿距离
def d_manhattan(x1, x2):
    return np.sum(np.abs(x1 - x2))

# 切比雪夫距离
def d_chebyshev(x1, x2):
    return np.max(np.abs(x1 - x2))

# 计算距离
def d_compute(x, d_name, d_fun):
    ‘‘‘
       :param x: 样本的特征集
       :param d_name: 距离算法名称
       :param d_fun: 距离算法
    ‘‘‘
    print(‘%s:\t%f\t%f\t%f\t%f‘ %
          (d_name, d_fun(x[0], x[1]), d_fun(x[0], x[2]), d_fun(x[0], x[3]), d_fun(x[0], x[4])))

if __name__ == ‘__main__‘:
    # 初始数据
    data_set = [[0, 2, 3, 3],  [-2, 1, 1, 3],  [-1, 2, 0, 3], [-0.5, 3, 2, -3], [2, 4, 3, 3]]
    # 将数据转换为numpy向量
    x = [np.array(data) for data in data_set]

    d_compute(x, ‘euclidean‘, d_euclidean)
    d_compute(x, ‘manhattan‘, d_manhattan)
    d_compute(x, ‘chebyshev‘, d_chebyshev)

  由于夹角余弦仅与向量的方向有关,与向量的大小无关,在这里又希望更多地考虑向量的大小,所以只使用两位三种距离度量,最终得到这样的结果:

  三种度量得到了三种不同的结果,而且每种结果中排在第一位的女孩都不同,综合考虑后,似乎小柔排在前面的次数较多,所以小明觉得明天应该重点发展一下小柔。

人心可测吗

  问题似乎圆满解决了,然而这样真的合理吗?想想儿时的小伙伴们,今天,他们的学历、爱好、收入甚至三观都与自己极为不同,但似乎并没有影响我们的矫情,时隔多年大家仍然有相同的话题,仍然可以一起喝上一杯,距离的长度似乎并不影响我们成为朋友。相反,身边的同事无论学历、经历还是工作目标都与自己接近,然而一道浅浅的隔断就阻断了大多数交流,彼此封闭起内心。人心真的可测吗?还是留给大家去思考吧。



  作者:我是8位的

  出处:http://www.cnblogs.com/bigmonkey

  本文以学习、研究和分享为主,如需转载,请联系本人,标明作者和出处,非商业用途!

  扫描二维码关注公众号“我是8位的”

原文地址:https://www.cnblogs.com/bigmonkey/p/9610964.html

时间: 2024-11-09 08:52:02

寻找“最好”(6)——心的距离的相关文章

Google”在垃圾中寻找黄金“——Page-Rank

GOOGLE"在垃圾中寻找黄金"--Page Rank GOOGLE"在垃圾中寻找黄金"--Page Rank 1.Google的低成本之道 ○不使用超级计算机,不使用存储(淘宝的去i,去e,去o之路) ○大量使用普通的PC服务器(去掉机箱,外设,硬盘),提供有冗余的集群服务 ○全世界多个数据中心,有些附带发电厂 ○运营商向Google倒付费 2.Google面对的数据和计算难题 大量的网页如何存储? Google的爬虫每天在全世界各个服务器里面爬到的很多网页,这些

寻找最大子串

题目:从一个数组中寻找一个连续子数组,使其中元素之和最大,给出该和值. 例如:数组[-2, 1, -3, 4, -1, 2, 1, -5, 4]和最大的子串是[4, -1, 2, 1],其和为6. 分析: 将数组从中间切分,中间元素属于左边,则最大子串为下面三种情况之一: 子串仅包含左边元素: 子串仅包含右边元素: 子串同时包含左边和右边的元素: 代码: // JavaScript solution function findMaxSubArray(a, begin, end) {   if(b

在字符串中,寻找第一个只出现一次的字符

在字符串中,寻找第一个只出现一次的字符,如str="abddggdbacdd", 结果是c 三种方法: 1. 使用字符字典数组,每个元素是一个结构体,第一个字段记录字符出现的次数,第二个字段记录该字符在字符串中第一次出现的位置, 先遍历一遍字符串,对字符字典数组赋值,然后遍历一遍字符字典数组,找到第一个字段为1,且位置最小的字符即为需要寻找的字符.如下: struct node{ int num; //出现次数 int index; //首次出现的位置 }; char str[1000

41. First Missing Positive【leetcode】寻找第一个丢失的整数,java,算法

41. First Missing Positive Given an unsorted integer array, find the first missing positive integer. For example,Given [1,2,0] return 3,and [3,4,-1,1] return 2. Your algorithm should run in O(n) time and uses constant space. 题目:寻找第一个丢失的整数意思为[1,2,4,5,

P2296 寻找道路

P2296 寻找道路 题目描述 在有向图G 中,每条边的长度均为1 ,现给定起点和终点,请你在图中找一条从起点到终点的路径,该路径满足以下条件: 1 .路径上的所有点的出边所指向的点都直接或间接与终点连通. 2 .在满足条件1 的情况下使路径最短. 注意:图G 中可能存在重边和自环,题目保证终点没有出边. 请你输出符合条件的路径的长度. 输入输出格式 输入格式: 输入文件名为road .in. 第一行有两个用一个空格隔开的整数n 和m ,表示图有n 个点和m 条边. 接下来的m 行每行2 个整数

Clustering by fast search and find of desity peaks(基于快速搜索与寻找密度峰值的聚类)

基于快速搜索与寻找密度峰值的聚类(Alex Rodriguez and Alessandro Laio) 摘要:聚类分析目的是基于元素之间的相似度对其进行分类,应用范围从天文学到生物信息学.文献计量学到模式识别.我们提出一种方法,思想基于簇中心具有比其邻居更大密度的特点以及与更大密度点之间有一个相对较大的距离(1.簇中心点有相对高的密度 2.簇中心点之间距离一般较大,即不同类别之间一般距离较远),这种思想形成了簇数目直观出现的聚类机制的基础,自动发现和排除异常点,同时在识别簇时,不用关心其形状和

如何用C#寻找100到999的所有水仙花数?

首先解释一下何为水仙花数:水仙花数只是自幂数的一种,严格来说是三位数的个位.十位.百位的3次幂数相加等于原来的数字,称为水仙花数.(例如:1^3 + 5^3+ 3^3 = 153) 那么如何通过C#语句来解决这个问题呢? 分析:想要找到百位的水仙花数,要解决的问题由两个,第一个是如何让计算机正确分离出百位数的个位.十位.百位的数字.第二个是如何将其不断循环让100到999都能经过检验. 第一个问题:将这个数值类型定义为整型int(这种数据类型的数字是没有小数点的) 最高位数字--对100整除,则

二叉树进阶之寻找一棵二叉树中的最大二叉搜索子树

转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6618915.html  (规律:在二叉树中寻找某性质的,都应该以递归思维:从根结点开始递归左右,一直到底,由底向上返回的信息来判断当前结点.求当前结点.即:二叉树的题目,从下往上想,递归的返回过程就是从下往上由叶到根建立二叉树的过程,在此过程中对每一步的"根"结点作性质判断,返回到根时即是整棵树的性质判断了) 从一棵树中寻找结点数最多的二叉搜索子树,并返回这棵子树的头结点. 从题目我们知道以下要求

类的寻找方法

class A: def bar(self): print('bar') self.f1() class B(A): def f1(self): print('B') class C: def f1(self): print('C') class D(C,B): pass d1 = B() d1.bar() class C: def __init__(self): print('C的构造方法') self.ty = '动物' class A(C): def __init__(self): pri