尋找“最好”(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)測量的是兩個樣本間的相似性,樣本間的距離越近,相似度越高。
夾角余弦來源於向量點積的幾何意義,兩個向量A和B的點積等於A和B的模乘以二者的夾角余玄:
可以看出,cosθ表示的是A和B方向的相似度,與它們的大小無關,雖然下圖中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位的”
尋找“最好”(6)——心的距離