向量實驗:相似度算法

 


真實世界的數字不只有大小,還要方向!!

一個警察和匪徒在天台上追逐,匪徒跑到圍牆邊差點掉下去了,幸好還有一隻手扣住了牆。

這時候,您該不該救呢?(經典的警匪片裏有好多這樣的情節。)

  • 假如您重 7272 公斤,倆臂伸展爲 1.71.7 米,1.81.8 米的個子,彎腰到 9090 度;
  • 而匪徒:9090 公斤。

估算一下:

  • ① 匪徒重心離手應該比警察臂展長
  • ② 匪徒又比警察重
  • ③ 假設他們的臂展(單位長度)是相同的,但他們之間的夾角是 90º,所以只有合力的 1.41.4 左右

警察要是去救,警察都有可能被拉下去。

現在我們知道了,警察是不能直接上去拉的,但可以想別的方法救。(嘖嘖,沒想到吧)

如果把警察的拉力看成 V1V1,匪徒的重量看成 V2V2V1V1V2V2 之間是 90º90º,也會產生一個新的力 V3V3

V1V1 是警察,V2V2 是匪徒,爲了凸顯方向的作用,我們認爲他們的臂展(V1V1V2V2 單位長度 = 11)是相同的,V3V3 的長度就會隨方向(V1V1V2V2的夾角)不同而不同:

  • V1V2V1、V2 的夾角是 0ºV3=2V3 = 2
  • V1V2V1、V2 的夾角是 30º30ºV3=1.93V3 = 1.93
  • V1V2V1、V2 的夾角是 60º60ºV3=1.73V3 = 1.73
  • V1V2V1、V2 的夾角是 90º90ºV3=1.4V3 = 1.4
  • V1V2V1、V2 的夾角是 120º120ºV3=1V3 = 1
  • V1V2V1、V2 的夾角是 150º150º,V3=0.53 = 0.5
  • V1V2V1、V2 的夾角是 180º180ºV3=0V3 = 0

根據 V3V3 的值來看,如果兩個向量的夾角超過了 120º120º,那麼兩個力加起來還不如一個力的作用。

可見,方向真的很重要。要形成合力,方向就得一致,方向是怎麼樣的,就是計算倆個向量間角度。

那怎麼計算倆個向量的夾角呢?

數學家把勾股定理一般化,從 90º90º 的夾角擴展到 任意大小的角,得到了一個可計算出倆個向量夾角的定理:【餘弦定理】。

我們不妨也來推導一下,理解 TA 的來龍去脈。

餘弦定理是從勾股定理推過來的,就是三角形有一個直角,第三邊長 =c2=a2+b2=c^{2}=a^{2}+b^{2}


而後,我們把勾股定理一般化,從 90º90º 的夾角推廣到任意角,勾股定理的變化。

  • 如果 aabb 的夾角等於 90º90º(直角), a2+b2=c2a^{2}+b^{2}=c^{2}
  • 如果 aabb 的夾角大於 90º90º(銳角), a2+b2<c2a^{2}+b^{2}<c^{2}
  • 如果 aabb 的夾角小於 90º90º(鈍角), a2+b2>c2a^{2}+b^{2}>c^{2}

對比一下 a2+b2c2a^{2}+b^{2}、c^{2} 就知道夾角是什麼樣的角。


c2c^{2} 移到等式左邊,a2+b2c2=0a^{2}+b^{2}-c^{2}=0

將等式左邊作爲判定因子Δ\Delta,用 Δ\Delta00 比較大小,可判定夾角:

  • Δ>0\Delta > 0,銳角;
  • Δ=0\Delta = 0, 直角;
  • Δ<0\Delta < 0,鈍角。

如果從函數的角度看,判定因子Δ\Deltaabca、b、c 三個變量的函數。

對於同樣的角度,

  • 如果三角形邊長比較長,那Δ\Delta 的動態範圍會很大;
  • 如果三角形邊長比較短,那Δ\Delta 的動態範圍就很小。

我們可以將 Δ\Delta 除以夾角的倆個邊長的積 aba*bΔ\Delta 的動態範圍就固定到 [2,2][-2, 2]。(這一步的推導可能有疑問,不清楚怎麼推的,那可以再思考思考)

  • Δ=2\Delta = -2 時,是 180º180º(最大的夾角);
  • Δ=0\Delta = 0 時,是 90º90º

不知道您有木有想到什麼,範圍是 [2,2][-2, 2]…如果再除以2,範圍就是 [1,1][-1, 1],這也是夾角的餘弦。

我們從勾股定理開始,讓 Δ\Delta 和角度的函數用餘弦表示出來,這種關係也被稱爲【餘弦定理】。


 


餘弦相似度

那這個會計算向量的夾角有什麼用呢,就欣賞一下純推理嗎?

向量在工程中運用的十分廣泛,就說計算向量的夾角吧,它就可以進行相似性度量。

相似度度量是計算個體之間相似程度的算法,此類算法多如牛毛,比如餘弦相似度。

餘弦相似度:用空間中倆個向量夾角的餘弦值的大小,來衡量倆個向量間的差異

多用於處理文字之間相似度的算法。應用有論文查重、文章自動分類、廣告推送、訂單識別、人羣聚類、簡歷篩選自動化。
 


新聞分類自動化

現在瀏覽器上的新聞,都是計算機自動分類的,而且就是根據相似度將文章自動分入科技、體育、軍事等類別中。

餘弦定理可以只靠倆個三角形的倆個邊的向量,計算出這倆個邊的夾角。

如果我們採用餘弦相似度計算相似度,那計算機新聞分類的原理是這樣:

一篇新聞裏會有很多詞,像 “之乎者也的” 這種虛詞,對判斷新聞的分類沒有太大的意義。而像 “股票”、“利息” 這種實詞,是判斷新聞分類的重點詞。

科學家精選了一個詞彙表,這裏面收錄着 6400064000 個詞,每個詞都對應一個編號。他們先把大量文字數據輸入計算機,算出每個詞出現的次數。

一般出現次數越少的詞越有搜索價值,比如 “愛因斯坦”、“某個人名”;而出現次數越多的詞,越沒有搜索價值,比如“一個”、“這裏” 等等。

根據這個標準,把詞彙表裏的 6400064000 個詞都算出各自的權重,越特殊的詞權重越大。

然後,再往計算機裏輸入要分類的新聞,計算出這 6400064000 個詞在這篇新聞裏的分佈,如果某些詞沒有在這篇新聞裏出現,對應的值就是零,如果出現,對應的值就是這個詞的權重。

這樣,這 6400064000 個數,就構成了一個 6400064000 維的向量,我們就用這個向量來代表這篇新聞,把它叫做這篇新聞的特徵向量。

不同類型的新聞,用詞上有不同的特點,比如金融類新聞就經常出現 “股票”、“銀行” 這些詞,所以不難判斷,同類新聞的特徵向量會有相似性。

只要算出不同新聞特徵向量之間夾角的大小,就可以判斷出是不是同一類新聞。

這時就要用到餘弦定理,來把兩則新聞的特徵向量之間的夾角算出來。

科學家可以人工設定一個值,只要兩個向量之間的夾角小於這個值,這兩則新聞就可以判定成同一類新聞。

在向量中公式轉換爲:

再把公式翻譯爲 C++ 代碼:

double CosSimilarity(double *va, double *vb, int vn) { 
	// vn 是多少維,也就是詞典中有多少個詞
    double cossu = 0.0;
    double cossda = 0.0;
    double cossdb = 0.0;
 
    for (int i = 0; i < vn; i++) {
        cossu += va[i] * vb[i];
        cossda += va[i] * va[i];
        cossdb += vb[i] * vb[i];
    }
 
    return cossu / (sqrt(cossda) * sqrt(cossdb));
}

測試用例:

  • 1文件1 的內容是:口徑爲 155155 毫米的榴彈炮,炮彈的射程超過 4040 公里,炮彈發射後擊中目標的彈道是一條拋物線
  • 2文件2 的內容是:大口徑榴彈炮射程很遠且彈道彎曲,炮彈通常都不是直接對着目標瞄準,而是計算好拋物線彈道,以一定的仰角和方向發射炮彈
  • 3文件3 的內容是:我們必須統一口徑,抵擋敵人發射的糖衣炮彈的進攻
#include <stdio.h>
#include <math.h>
 
double CosSimilarity(double *va, double *vb, int vn) {
    double cossu = 0.0;
    double cossda = 0.0;
    double cossdb = 0.0;
 
    for (int i = 0; i < vn; i++) {
        cossu += va[i] * vb[i];
        cossda += va[i] * va[i];
        cossdb += vb[i] * vb[i];
    }
 
    return cossu / (sqrt(cossda) * sqrt(cossdb));
}
 
// 建立的詞典
const int VN = 11;      // 11 個詞即 11維
const char *base_words[] = {
    "進攻", "炮彈", "射程", "榴彈炮", "發射", "口徑", "迫擊炮", "瞄準", "後坐力", "彈道", "目標"
};

int main(){
	// v1 代表 文件1 的 11 個關鍵字出現的權重(一一對應, 出現n次權重爲n)
    double v1[] = { 0, 2, 1, 1, 1, 1, 0, 0, 0, 1, 1 };  
    // 對於 文件1 ,“進攻”出現 0 次,“炮彈”出現 2 次,“射程”出現 1 次……,統計完成後,得到一個 11 維向量
    
    double v2[] = { 0, 2, 1, 1, 1, 1, 0, 1, 0, 2, 1 };
    double v3[] = { 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 };
 
    /* 檢查相似度 */
    printf("第一行 和 第二行 的相似度: %.2lf\n", CosSimilarity(v1, v2, VN)); // 0.9297
    printf("第一行 和 第三行 的相似度: %.2lf\n", CosSimilarity(v1, v3, VN)); // 0.6325
    printf("第二行 和 第三行 的相似度: %.2lf\n", CosSimilarity(v2, v3, VN)); // 0.5345
    
    // 代碼還可補充,當相似度大於設定值時,歸爲一類新聞...... 
    return 0;
}

新聞分類的步驟:

  • 建立詞典
  • 詞頻統計(如果是中文,數據得先經過中文分詞)
  • 計算餘弦相似性
  • 根據相似度做判斷,餘弦相似度,值越大(越接近 11)就表示越相似。

原理就是這樣,現在把這個項目完整的寫出來:

// 運行:可打開命令行,輸入 g++ -std=c++11 當前源文件.cpp
#include <iostream> 
#include <map>
#include <string>
#include <vector>
#include <cstdio>
#include <cmath>
using namespace std;

/* Step-1: 建立詞典*/
int N = 11;             // 建立軍事新聞詞, 採用軍事領域的統一詞彙表, 詞典一共 11 個詞
map<string, int> m = { {"進攻", 0}, {"炮彈", 0}, {"射程", 0}, {"榴彈炮", 0}, {"發射", 0}, {"口徑", 0}, {"迫擊炮", 0}, {"瞄準", 0}, {"後坐力", 0}, {"彈道", 0}, {"目標", 0} };

/* ===  FUNCTION  ======================================================================
 *         Name:  word_count
 *  Description:  Step-2: 詞頻統計
 * ===================================================================================== */
void word_count(vector<string> v, int *arr){
  for( auto i: v )
      if( m.count(i) )
          m[ i ] ++;
  
  map<string, int>::iterator itr; int i=0;
    for (itr = m.begin(); itr != m.end(); ++itr, i++) 
        arr[i] = itr->second; 
}

/* ===  FUNCTION  ======================================================================
 1.         Name:  Cos_similarity
 2.  Description:  Step-3: 計算餘弦相似性
 3.        @Para:  n 是向量的維度,即詞典中有多少個詞 
 4. ===================================================================================== */
double cos_similarity(int *va,  int *vb, int n){
    double cossu  = 0.0;
    double cossda = 0.0;
    double cossdb = 0.0;
 
    for (int i = 0; i < n; i++) {
        cossu  += va[i] * vb[i];
        cossda += va[i] * va[i];
        cossdb += vb[i] * vb[i];
    }
    return cossu / (sqrt(cossda) * sqrt(cossdb));
}
 
int main(){
 vector<string> v1 = {"口徑", "爲", "155", "毫米", "的", "榴彈炮", "炮彈", "的", "射程", "超過", "40", "公里" , "炮彈", "發射", "後","擊中", "目標", "的", "彈道", "是", "一", "條", "拋物線"};
 vector<string> v2 = {"大", "口徑", "榴彈炮", "射程", "很遠", "且", "彈道", "彎曲", "炮彈", "通常", "都","不是","直接","對着","目標","瞄準","而是","計算","好","拋物線","彈道","以","一定","的","仰角","和","方向","發射","炮彈"};
 vector<string> v3 = {"我們", "必須", "統一", "口徑", "抵擋", "敵人", "發射", "的", "糖衣", "炮彈", "的", "進攻"};

 int *cnt1 = (int *)calloc( N, sizeof(int) );
 int *cnt2 = (int *)calloc( N, sizeof(int) );
  word_count(v1, cnt1);
  word_count(v2, cnt2);
  for( int i=0; i<N; i++ )   // 後一個要減去前一個,因爲map存儲的是前一個的值
     cnt2[i] = cnt2[i] - cnt1[i];

  /* 檢查相似度 */
   double result = cos_similarity(cnt1, cnt2, N);
   cout << "相似度:" << result << " ";

  /* 檢查相似度 */
 if( result == 1 )
   puts(",倆篇文章出現了一樣的詞語,但語序可能不一樣,可以歸爲一類");
 else if( result > 0.9 )
   puts(",倆篇文章的用詞幾乎一致,可以歸爲一類");
 else if( result > 0.5 )
   puts(",倆篇文章可以歸爲同一類,可以歸爲一類");
 else if( result == 0.0 )
   puts(",這倆篇文章毫無關係,不可以歸爲一類");
 else
   puts(",這倆篇文章是對立的,不可以歸爲一類");
    
   free(cnt1), cnt1 = NULL;
   free(cnt2), cnt2 = NULL;
   return 0;
}

論文查重、文章自動分類、廣告推送、訂單識別、人羣聚類、簡歷篩選自動化。
我們再說一下,其TA工程應用:

  • 論文查重:是把統一的字典改爲從兩篇文章中整理關鍵詞彙或者倆篇文章中所有詞彙,合併成一個字典即可。

  • 文章自動分類:建立各個領域的詞典,上面我們只有一個軍事新聞的字典,而且只計算了一次;實現文章字典分類得所有領域都計算一次,分到相似度最高的領域即可。

  • 廣告推送、訂單識別:也會用到文本相似度的判斷。

  • 簡歷篩選自動化:類似。

    向量不僅可以對新聞分類,對人也可以分類。

    現在大公司在招聘夥伴時,由於簡歷特別多,會先用計算機篩選簡歷。

    原理:先把簡歷向量化,而後計算夾角。

    把各種技能和素質列在一張表裏,這個表就有 NN 個維度啦。

    而後不同崗位因爲評比的方式不同,某些向量的權值就很高,一些就很低甚至是零。

    比如,開發人員的權值:

    • 編程能力:44
    • 工程經驗:22
    • 溝通能力:11
    • 學歷:11
    • 領導力:11
    • 企業文化融合度:11

     

    接下來計算機會對每份簡歷進行分析,把每份簡歷變成一個 NN 維的向量,假設是 PP

    計算 PPVV 的夾角,如果夾角非常小說明某一份簡歷和某一個崗位比較匹配,這時簡歷纔會遞給 HRHR

    所以,寫給大公司的簡歷,一定要突出重點。別把自己描述爲全能的,不然直接被計算機卡死啦,最好是先打探內部消息,知道這家公司看重什麼維度,我再往上面寫。

 


歐式相似度

除此餘弦距離之外,向量也是空間中的一個點;既然是一個點,就可以結合 歐式距離。

相對於歐式距離,餘弦距離注重的是倆個向量在方向上的差異(注重的是方向),對絕對數值(距離)不敏感。

  • 向量夾角越大(方向差異越大),餘弦值 cosθcos \theta 就會越小(相似性越小);
  • 向量夾角越小(方向差異越小),餘弦值 cosθcos \theta 就會越大(相似性越大);

歐式距離度量注重每份數據的絕對數值差異(距離),對方向不敏感。

多用在從數值大小的發現特徵的算法,如根據用戶的行爲數據分析用戶的價值大小、評估用戶消費能力算法。

舉個例子,這是上月用戶對商品的評分數據:


先讓數據轉爲向量形式:

張三:11            \begin{Vmatrix} 1 \\ 1\\ \end{Vmatrix}~~~~~~~~~~~~ 李四:55            \begin{Vmatrix} 5 \\ 5\\ \end{Vmatrix}~~~~~~~~~~~~ 王五:50            \begin{Vmatrix} 5\\ 0\\ \end{Vmatrix}~~~~~~~~~~~~

我們要對評分的用戶進行分類(人羣聚類算法)。

物以類聚,人以羣分。這種分類看的就是一種相似度,所以我們可以使用相似度算法。

但該使用哪種相似度算法呢?

  • 餘弦距離注重的是方向,相似度是由倆個向量間的夾角決定的;
  • 歐式距離注重的是距離,相似度是由倆個向量間的距離決定的;

可以給您一個提示,可供對比。

這是上月用戶的消費數據:


先讓數據轉爲向量形式:

  • 張三:210            \begin{Vmatrix} 2 \\ 10\\ \end{Vmatrix}~~~~~~~~~~~~ 李四:20100            \begin{Vmatrix} 20 \\ 100\\ \end{Vmatrix}~~~~~~~~~~~~王五:315\begin{Vmatrix} 3 \\ 15\\ \end{Vmatrix}

​我們要通過它,評估用戶消費能力。

原理:讓選一個標準讓所有用戶和標準比,超過那個標準標爲高消費人羣,就多多推薦商品出去。

顯然,評估用戶消費能力也是一個相似性度量問題,但和人羣聚類算法有什麼不同呢?

問題在於,您可不可以看出這倆個算法的核心因素是什麼,是計算距離,還是計算方向!!

 


總結

向量的夾角計算通常用於相似算法,用於判斷相似度的理論有很多,比如歐氏距離(歐氏相似度)、餘弦距離(餘弦相似度)、JaccardJaccard 距離、編輯距離等。

我們主要學習了餘弦距離:

  • 向量夾角越大(方向差異越大),餘弦值 cosθcos \theta 就會越小(相似性越小);
  • 向量夾角越小(方向差異越小),餘弦值 cosθcos \theta 就會越大(相似性越大);

餘弦相似度,值越大(越接近 11)就表示越相似。

而後,對比了餘弦、歐式距離:

  • 歐式距離:注重的是方向(倆個向量夾角),多用在從向量的方向發現特徵的算法;
  • 餘弦距離:注重的是距離(絕對數值方面),多用在從數值的大小發現特徵的算法。

這些只是我知道一點向量的工程應用,👏👏歡迎您補充,我也想 GetGet 新知識。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章