1. 程式人生 > >NLP點滴——文字相似度,計算文字間的距離

NLP點滴——文字相似度,計算文字間的距離

前言

在自然語言處理過程中,經常會涉及到如何度量兩個文字之間的相似性,我們都知道文字是一種高維的語義空間,如何對其進行抽象分解,從而能夠站在數學角度去量化其相似性。而有了文字之間相似性的度量方式,我們便可以利用劃分法的K-means、基於密度的DBSCAN或者是基於模型的概率方法進行文字之間的聚類分析;另一方面,我們也可以利用文字之間的相似性對大規模語料進行去重預處理,或者找尋某一實體名稱的相關名稱(模糊匹配)。而衡量兩個字串的相似性有很多種方法,如最直接的利用hashcode,以及經典的主題模型或者利用詞向量將文字抽象為向量表示,再通過特徵向量之間的歐式距離或者皮爾森距離進行度量。本文圍繞文字相似性度量的主題,從最直接的字面距離的度量到語義主題層面的度量進行整理總結,並將平時專案中用到的文字相似性程式碼進行了整理,如有任何紕漏還請指出,我會第一時間改正^v^。(ps.平時用的Java和scala較多,本文主要以Java為例。)

字面距離

提到如何比較兩個字串,我們從最初程式設計開始就知道:字串有字元構成,只要比較比較兩個字串中每一個字元是否相等便知道兩個字串是否相等,或者更簡單一點將每一個字串通過雜湊函式對映為一個雜湊值,然後進行比較。但是這種方法有一個很明顯的缺點,就是過於“硬”,對於相似性的度量其只有兩種,0不相似,1相似,哪怕兩個字串只有一個字元不相等也是不相似,這在NLP的很多情況是無法使用的,所以下文我們就“軟”的相似性的度量進行整理,而這些方法僅僅考慮了兩個文字的字面距離,無法考慮到文字內在的語義內容。

common lang庫

文中在部分程式碼應用中使用了Apache提供的common lang庫,該庫包含很多Java標準庫中沒有的但卻很實用的函式。其maven引用如下:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>

相同字元數

在傳統的字串比較過程中,我們考慮字串中每個字元是否相等,並且考慮了字元出現的順序,如果不考慮字元出現的順序,我們可以利用兩個文字之間相同的字元數量,很簡單不再贅述,可以利用common lang中的getFuzzyDistance:

int dis = StringUtils.getFuzzyDistance(term, query, Locale.CHINA);

萊文斯坦距離(編輯距離)

定義

我們在學習動態規劃的時候,一個很經典的演算法便是計算兩個字串的編輯距離,即:

萊文斯坦距離,又稱Levenshtein距離,是編輯距離(edit distance)的一種。指兩個字串之間,由一個轉成另一個所需的最少編輯操作次數。許可的編輯操作包括將一個字元替換成另一個字元,插入一個字元,刪除一個字元。

例如將kitten一字轉成sitting:

  1. sitten (k→s)
  2. sittin (e→i)
  3. sitting (→g)

那麼二者的編輯距離為3。
俄羅斯科學家弗拉基米爾·萊文斯坦在1965年提出這個概念。

實現方式

我們可以利用common lang中StringUtils的函式來計算:

int dis = StringUtils.getLevenshteinDistance(s1, s2);
//實現
public static int getLevenshteinDistance(CharSequence s, CharSequence t) {
    if (s == null || t == null) {
        throw new IllegalArgumentException("Strings must not be null");
    }
    int n = s.length(); // length of s
    int m = t.length(); // length of t
    if (n == 0) {
        return m;
    } else if (m == 0) {
        return n;
    }
    if (n > m) {
        // swap the input strings to consume less memory
        final CharSequence tmp = s;
        s = t;
        t = tmp;
        n = m;
        m = t.length();
    }
    int p[] = new int[n + 1]; //'previous' cost array, horizontally
    int d[] = new int[n + 1]; // cost array, horizontally
    int _d[]; //placeholder to assist in swapping p and d
    // indexes into strings s and t
    int i; // iterates through s
    int j; // iterates through t
    char t_j; // jth character of t
    int cost; // cost
    for (i = 0; i <= n; i++) {
        p[i] = i;
    }
    for (j = 1; j <= m; j++) {
        t_j = t.charAt(j - 1);
        d[0] = j;
        for (i = 1; i <= n; i++) {
            cost = s.charAt(i - 1) == t_j ? 0 : 1;
            // minimum of cell to the left+1, to the top+1, diagonally left and up +cost
            d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost);
        }
        // copy current distance counts to 'previous row' distance counts
        _d = p;
        p = d;
        d = _d;
    }
    // our last action in the above loop was to switch d and p, so p now
    // actually has the most recent cost counts
    return p[n];
}

Jaro距離

定義

Jaro Distance也是字串相似性的一種度量方式,也是一種編輯距離,Jaro 距離越高本文相似性越高;而Jaro–Winkler distance是Jaro Distance的一個變種。據說是用來判定健康記錄上兩個名字是否相同,也有說是是用於人口普查。從最初其應用我們便可看出其用法和用途,其定義如下:

其中

  • 是匹配數目(保證順序相同)
  • 字串長度
  • 是換位數目

其中t換位數目表示:兩個分別來自S1和S2的字元如果相距不超過

我們就認為這兩個字串是匹配的;而這些相互匹配的字元則決定了換位的數目t,簡單來說就是不同順序的匹配字元的數目的一半即為換位的數目t,舉例來說,MARTHA與MARHTA的字元都是匹配的,但是這些匹配的字元中,T和H要換位才能把MARTHA變為MARHTA,那麼T和H就是不同的順序的匹配字元,t=2/2=1。
而Jaro-Winkler則給予了起始部分就相同的字串更高的分數,他定義了一個字首p,給予兩個字串,如果字首部分有長度為 的部分相同,則Jaro-Winkler Distance為:

  • 是兩個字串的Jaro Distance
  • 是字首的相同的長度,但是規定最大為4
  • 則是調整分數的常數,規定不能超過0.25,不然可能出現dw大於1的情況,Winkler將這個常數定義為0.1

舉個簡單的例子:
計算s_1=DIXON,s_2=DICKSONX的距離

我們利用\lfloor \frac{max(|s_1|,|s_2|)}{2}-1 \rfloor可以得到一個匹配視窗距離為3,圖中黃色部分便是匹配視窗,其中1表示一個匹配,我們發現兩個X並沒有匹配,因為其超出了匹配視窗的距離3。我們可以得到:

其Jaro score為:

d_j=\frac{1}{3}(\frac{4}{5}+\frac{4}{8}+\frac{4-0}{4})=0.767

而計算Jaro–Winkler score,我們使用標準權重p=0.1,\ell=2,其結果如下:

實現方式

同樣我們可以利用common lang中的getJaroWinklerDistance函式來實現,注意這裡實現的是Jaro–Winkler distance

double dis = StringUtils.getJaroWinklerDistance(reviewName.toLowerCase(), newsName.toLowerCase());

//實現
public static double getJaroWinklerDistance(final CharSequence first, final CharSequence second) {
    final double DEFAULT_SCALING_FACTOR = 0.1; //標準權重
    if (first == null || second == null) {
        throw new IllegalArgumentException("Strings must not be null");
    }
    final double jaro = score(first,second); // 計算Jaro score
    final int cl = commonPrefixLength(first, second); // 計算公共字首長度
    final double matchScore = Math.round((jaro + (DEFAULT_SCALING_FACTOR * cl * (1.0 - jaro))) *100.0)/100.0;   // 計算 Jaro-Winkler score

    return  matchScore;
}

應用

在Wetest輿情監控中,我們在找尋遊戲名簡稱和全稱的對應關係時便使用到了Jaro-Winkler score進行衡量,其中我們將Jaro分數大於0.6的認為是相似文字,之後在總的相似文字中提取最相似的作為匹配項,實現效果還不錯:

其中冒號左邊是待匹配項,右邊是匹配項<遊戲名 詞頻,Jaro-Winkler score>,Jaro-Winkler score較高的一般都是正確的匹配項。

SimHash

定義

SimHash是一種區域性敏感hash,它也是Google公司進行海量網頁去重使用的主要演算法。
傳統的Hash演算法只負責將原始內容儘量均勻隨機地對映為一個簽名值,原理上僅相當於偽隨機數產生演算法。傳統的hash演算法產生的兩個簽名,如果原始內容在一定概率下是相等的;如果不相等,除了說明原始內容不相等外,不再提供任何資訊,因為即使原始內容只相差一個位元組,所產生的簽名也很可能差別很大。所以傳統的Hash是無法在簽名的維度上來衡量原內容的相似度,而SimHash本身屬於一種區域性敏感雜湊演算法,它產生的hash簽名在一定程度上可以表徵原內容的相似度。
我們主要解決的是文字相似度計算,要比較的是兩個文章是否相似,當然我們降維生成了hash簽名也是用於這個目的。看到這裡估計大家就明白了,我們使用的simhash就算把文章中的字串變成 01 串也還是可以用於計算相似度的,而傳統的hash卻不行。

我們可以來做個測試,兩個相差只有一個字元的文字串,“你媽媽喊你回家吃飯哦,回家羅回家羅” 和 “你媽媽叫你回家吃飯啦,回家羅回家羅”。
通過simhash計算結果為:
1000010010101101111111100000101011010001001111100001001011001011
1000010010101101011111100000101011010001001111100001101010001011
通過傳統hash計算為:
0001000001100110100111011011110
1010010001111111110010110011101

通過上面的例子我們可以很清晰的發現simhash的區域性敏感性,相似文字只有部分01變化,而hash值很明顯,即使變化很小一部分,也會相差很大。

基本流程

注:具體的事例摘自Lanceyan[10]的部落格《海量資料相似度計算之simhash和海明距離》

  1. 分詞,把需要判斷文字分詞形成這個文章的特徵單詞。最後形成去掉噪音詞的單詞序列併為每個詞加上權重,我們假設權重分為5個級別(1~5)。比如:“ 美國“51區”僱員稱內部有9架飛碟,曾看見灰色外星人 ” ==> 分詞後為 “ 美國(4) 51區(5) 僱員(3) 稱(1) 內部(2) 有(1) 9架(3) 飛碟(5) 曾(1) 看見(3) 灰色(4) 外星人(5)”,括號裡是代表單詞在整個句子裡重要程度,數字越大越重要。
  2. hash,通過hash演算法把每個詞變成hash值,比如“美國”通過hash演算法計算為 100101,“51區”通過hash演算法計算為 101011。這樣我們的字串就變成了一串串數字,還記得文章開頭說過的嗎,要把文章變為數字計算才能提高相似度計算效能,現在是降維過程進行時。
  3. 加權,通過 2步驟的hash生成結果,需要按照單詞的權重形成加權數字串,比如“美國”的hash值為“100101”,通過加權計算為“4 -4 -4 4 -4 4”;“51區”的hash值為“101011”,通過加權計算為 “ 5 -5 5 -5 5 5”。
  4. 合併,把上面各個單詞算出來的序列值累加,變成只有一個序列串。比如 “美國”的 “4 -4 -4 4 -4 4”,“51區”的 “ 5 -5 5 -5 5 5”, 把每一位進行累加, “4+5 -4+-5 -4+5 4+-5 -4+5 4+5” ==》 “9 -9 1 -1 1 9”。這裡作為示例只算了兩個單詞的,真實計算需要把所有單詞的序列串累加。
  5. 降維,把4步算出來的 “9 -9 1 -1 1 9” 變成 0 1 串,形成我們最終的simhash簽名。 如果每一位大於0 記為 1,小於0 記為 0。最後算出結果為:“1 0 1 0 1 1”。
    整個過程的流程圖為:

相似性度量

有了simhash值,我們需要來度量兩個文字間的相似性,就像上面的例子一樣,我們可以比較兩個simhash間0和1不同的數量。這便是漢明距離(Hamming distance)

在資訊理論中,兩個等長字串之間的漢明距離(英語:Hamming distance)是兩個字串對應位置的不同字元的個數。換句話說,它就是將一個字串變換成另外一個字串所需要替換的字元個數。
漢明重量是字串相對於同樣長度的零字串的漢明距離,也就是說,它是字串中非零的元素個數:對於二進位制字串來說,就是1的個數,所以11101的漢明重量是4。
例如:
1011101與1001001之間的漢明距離是2

一般在利用simhash進行文字相似度比較時,我們認為漢明距離小於3的文字是相似的。

儲存索引


儲存:

  1. 將一個64位的simhash簽名拆分成4個16位的二進位制碼。(圖上紅色的16位)
  2. 分別拿著4個16位二進位制碼查詢當前對應位置上是否有元素。(放大後的16位)
  3. 對應位置沒有元素,直接追加到連結串列上;對應位置有則直接追加到連結串列尾端。(圖上的 S1 — SN)

查詢:

  1. 將需要比較的simhash簽名拆分成4個16位的二進位制碼。
  2. 分別拿著4個16位二進位制碼每一個去查詢simhash集合對應位置上是否有元素。
  3. 如果有元素,則把連結串列拿出來順序查詢比較,直到simhash小於一定大小的值,整個過程完成。
  4. 在去重時,因為漢明距離小於3則為重複文字,那麼如果存在simhash相似的文字,對於四段simhash則至少有一段simhash是相同的,所以在去重時對於待判斷文字D,如果D中每一段的simhash都沒有相同的,那麼D為無重複文字。

原理:
借鑑hashmap演算法找出可以hash的key值,因為我們使用的simhash是區域性敏感雜湊,這個演算法的特點是隻要相似的字串只有個別的位數是有差別變化。那這樣我們可以推斷兩個相似的文字,至少有16位的simhash是一樣的。具體選擇16位、8位、4位,大家根據自己的資料測試選擇,雖然比較的位數越小越精準,但是空間會變大。分為4個16位段的儲存空間是單獨simhash儲存空間的4倍。

實現

在實際NLP的使用中,我利用Murmur3作為字串的64位雜湊值,用Java和spark分別實現了一個simhash的版本
我將原始碼放在了github上,如下連結:

其中利用了結巴作為文字的分詞工具,Murmur3用來產生64位的hashcode。另外根據上述儲存方式,進行了simhash分段儲存,提高搜尋速度,從而進行高效查重。

應用

simhash從最一開始用的最多的場景便是大規模文字的去重,對於爬蟲從網上爬取的大規模語料資料,我們需要進行預處理,刪除重複的文件才能進行後續的文字處理和挖掘,那麼利用simhash是一種不錯的選擇,其計算複雜度和效果都有一個很好的折中。
但是在實際應用過程中,也發現一些badcase,完全無關的文字正好對應成了相同的simhash,精確度並不是很高,而且simhash更適用於較長的文字,但是在大規模語料進行去重時,simhash的計算速度優勢還是很不錯的。

語義相似性

在NLP中有時候我們度量兩個短文字或者說更直接的兩個詞語的相似性時,直接通過字面距離是無法實現的,如:中國-北京,義大利-羅馬,這兩個短語之間的相似距離應該是類似的,因為都是首都與國家的關係;再比如(男人、男孩),(女人、女孩)應該是相同的關係,但是我們看其字面距離都是0。
想要做到語義層面的度量,我們需要用到機器學習建模,而自然語言的問題轉化為機器學習的首要問題便是找到一種方法把自然語言的符號數學化。

背景知識

在自然語言處理領域中,有兩大理論方向,一種是基於統計的經驗主義方法,另一種是基於規則的理性主義方法[15]。而隨著計算機效能的提升,以及網際網路發展而得到的海量語料庫,目前NLP的研究更多是基於統計的經驗主義方法。所以在本文討論的語義相似性中,也是從統計學的角度出發進行總結。

統計語言模型

對於統計語言模型而言,最基礎的理論便是貝葉斯理論(Bayes' theorem PS.關於貝葉斯理論強烈推薦:數學之美番外篇:平凡而又神奇的貝葉斯方法,一篇深入淺出的好文。另外推薦下自己師兄參與翻譯的作品《貝葉斯方法——概率程式設計與貝葉斯推斷》很全面的貝葉斯理論+實踐書籍)。對於大規模語料庫,我們可以通過詞頻的方式來獲取概率,例如100個句子中,出現了1次"Okay",那麼

而同樣的對於句子"An apple ate the chicken"我們可以認為其概率為0,因為這不符合我們說話的邏輯。
統計語言模型是用來計算一個句子的概率,其通常基於一個語料庫D來構建。如何表示一個句子的概率呢?我們用來表示一個基元(通常就是指詞語,也可以是字或短語),那麼對於一個由N個片語成的句子W可以表示為

那麼其聯合概率

就可以認為是該句子的概率,根據貝葉斯公式的鏈式法則可以得到:

其中條件概率p(\omega_1)p(\omega_2|\omega_1)p(\omega_3|\omega_1,\omega_2)...p(\omega_n|\omega_1...\omega_{n-1})便是語言模型的引數,如果我們把這些全部算出來,那麼一個句子的概率我們就能很輕易的得出。但是很明顯,這個引數的量是巨大的是無法計算的。這時我們可以將\omega_i|\omega_1...\omega_{i-1}對映到某個等價類E(\omega_i|\omega_1...\omega_{i-1}),從而降低引數數目。
ps.語料庫我們用C表示,而詞典D一般為語料中出現的所有不重複詞

n-gram模型

既然每個單詞依賴的單詞過多,從而造成了引數過多的問題,那麼我們就簡單點,假設每個單詞只與其前n-1個單詞有關,這便是n-1階Markov假設,也就是n-gram模型的基本思想。
那麼對於句子W的概率我們可以簡化如下:

那麼對於最簡單的一階情況也稱unigram或uni-gram或monogram(二階bigram 三階trigram)就簡單表示為

為了在句首和句尾能夠統一,我們一般會在句首加一個BOS標記,句尾加一個EOS標記,那麼對於句子"Mark wrote a book",其概率可以表示如下:

為了預估p(\omega_i|\omega_{i-1})條件概率,根據大數定理,簡單統計語料庫中\omega_{i-1},\omega_i出現的頻率,並進行歸一化。我們用c來表示頻率,那麼可表示如下:

p(\omega_i|\omega_{i-1})=\frac{c(\omega_{i-1}\omega_i)}{\sum_{\omega_i}c(\omega_{i-1}\omega_i)}

其中分母在unigram中就可以簡單認為是詞語\omega_{i-1}出現的次數。
在n-gram模型中還有一個很重要的問題就是平滑化,因為再大的語料庫都不可能涵蓋所有情況,考慮兩個問題:

  1. c(\omega_{i-1}\omega_i)=0那麼p(\omega_i|\omega_{i-1})=0就是0嗎?
  2. c(\omega_{i-1}\omega_i)=\sum_{\omega_i}c(\omega_{i-1}\omega_i)那麼p(\omega_i|\omega_{i-1})=0就是1嗎?

這顯然是不合理的,這就需要進行平滑,這裡不展開討論。
根據最大似然,我們可以得到:

其中C表示語料庫,Context(omega)表示詞語的上下文,而這裡對於n-gram模型Context(\omega)=\oemga^{i-1}_{i-n+1},取對數後的對數似然函式為:

從上式我們可以看出p(\omega|Context(\omega))可以看做是\omega關於Context(omega)的函式,即:

其中theta為待定引數集,通過語料庫訓練得到引數集後,F便確定了,我們不需要再儲存概率p(\omega|Context(\omega)),可以直接計算得到,而語言模型中很關鍵的就在於F的構造

詞向量

為了從使得計算機從語義層面理解人類語言,首先要做的就是將語言數學化,如何進行表示呢?人們便提出了詞向量的概念,即用一個向量來表示一個詞。

One-hot Representation

一種最簡單詞向量就是利用詞頻向量將高維的語義空間抽象成數學符號表示,向量長度為詞典的大小,這種表示方式非常直觀,但是容易造成維度災難,並且還是不能刻畫語義的資訊。

詞語表示

對於詞語而言,用一個向量來表示一個詞,最直觀簡單的方式就是將每個詞變為一個很長的向量,向量長度便是詞典的長度,其中絕大部分為0,只有一個維度為1代表了當前詞。
假設語料庫:“衝突容易引發戰爭”,那麼詞典為

  • D=[衝突,容易,引發,戰爭]
  • 衝突=[1,0,0,0]
  • 戰爭=[0,0,0,1]

每個詞都是含有一個1的n維向量(),這種方式我們壓縮儲存下,就是給每個詞語分配一個ID,通常實際變成我們最簡單的就是用hash值表示一個詞語。這種方式可以用在SVM、最大熵和CRF等等演算法中,完成NLP的大多數場景。例如,我們可以直接將
但是缺點很明顯,就是我們用這種方式依舊無法度量兩個詞的語義相似性,任意兩個詞之間都是孤立的,比如上面的衝突和戰爭是近義詞,但是卻沒有任何關聯性。

文件表示

同樣文件也可以用詞頻向量的形式來表示,一般我們會利用tf-idf作為每一個詞的特徵值,之後會挑選每篇文件比較重要的部分詞來表示一篇文件,拿遊戲來說,如下:
[王者榮耀, 陰陽師, 夢幻西遊]

  • doc1:[tf-idf(王者榮耀), tf-idf(陰陽師), tf-idf(夢幻西遊)]
  • doc2:[tf-idf(王者榮耀), tf-idf(陰陽師), tf-idf(夢幻西遊)]

然後我們就可以利用K-means等聚類演算法進行聚類分析,當然對於每篇文件,一般我們只會選取部分詞彙,因為如果詞彙過多可能造成NLP中常見的維度“災難”。這種方式在大多數NLP場景中都是適用的,但是由於這種表示往往是建立在高維空間,為了避免維度災難就要損失一定的語義資訊,這也是這種方法的弊端。

Distributed representation

另外一種詞向量的表示Distributed representation最早由 Hinton在 1986年提出。它是一種低維實數向量,這種向量一般長成這個樣子:
[0.792, −0.177, −0.107, 0.109, −0.542, …]
維度以 50 維和 100 維比較常見,當然了,這種向量的表示不是唯一的。
Distributed representation的關鍵點在於,將高維空間中的詞彙對映到一個低維的向量空間中,並且讓相關或者相似的詞,在距離上更接近(看到這裡大家有沒有想到普通hash以及simhash的區別呢?),這裡引用一張圖片(來自[13]):

圖中是英語和西班牙語通過訓練分別得到他們的詞向量空間,之後利用PCA主成分分析進行降維表示在二維座標圖中的。我們可以清晰的看出,對於兩種語系的一二三四五,在空間距離上竟是如此的相似,這就是Distributed representation詞向量表示的意義所在。
這種採用低維空間表示法,不但解決了維數災難問題,並且挖掘了word之間的關聯屬性,從而提高了向量語義上的準確度,下面我們討論的語言模型都是基於這種詞向量表示方式。
PS. 有時候也會出現Word Represention或 Word Embedding(所謂詞嵌入)的說法。另外我們這裡說的詞向量是在詞粒度進行分析,當然我們也可以在字粒度的字向量、句子粒度的句向量以及文件粒度的文件向量進行表示分析。

主題模型

在長文字的篇章處理中,主題模型是一種經典的模型,經常會用在自然語言處理、推薦演算法等應用場景中。本節從LDA的演變過程對LDA進行闡述,然後就LDA在長文字相似性的判斷聚類上做簡要說明。

LSA

首先對於一篇文件Document,詞語空間的一個詞頻向量如下:

其中每個維度表示某一詞語term在該文件中出現的次數,最終對於大量的訓練樣本,我們可以得到訓練樣本的矩陣X,如下圖:

LSA的基本思想,便是利用最基本的SVD奇異值分解,將高維語義空間對映到低維空間,其流程如下:

這樣對於訓練樣本中詞表的每一個term我們便得到了一個低維空間的向量表示。但LSA的顯著問題便是值考慮詞頻,並不區分同一詞語的不同含義

PLSA

LSA基於最基本的SVD分解,但缺乏嚴謹的數理統計邏輯,於是Hofmann提出了PLSA,其中P便是Probabilistic,其基本的假設是每個文件所表示的詞頻空間向量w服從多項式分佈(Multinomial distribution

簡單扯兩句多項式分佈:

  • 伯努利分佈Bernoulli distribution)我們從接觸概率論開始便知道,即所謂的投硬幣,其離散分佈如下:

    但是吊吊的數學家們總喜歡做一些優雅的讓人看不懂的事情,所以也可以寫作如下公式:

    其中k為0或者1

  • 二項分佈Binomial distribution):


    如果進行次投硬幣實驗,計算出現m次正面朝上的概率
    伯努利分佈是二項分佈中n=1時的特殊情況

  • Categorical分佈Categorical distribution),如果我們將投硬幣改成擲骰子,那麼原來一維向量x就會變成一個六維向量,其中每一維度為1表示出現該面,0表示沒出現,用數學表示即對於隨機變數X有k中情況,其中第種情況出現的概率為

    那麼我們可以得到其離散概率分佈如下:

    其中如果那麼為1,否則為0

  • 多項式分佈Multinomial distribution):與二項分佈類似,Categorical分佈進行N次試驗,便得到多項式分佈:

    同樣我們可以寫成吊吊的形式:

其中gamma函式:當n>0,則(ps.該形式與狄利克雷分佈(Dirichlet distribution)的形式非常相似,因為多項式分佈是狄利克雷分佈的共軛先驗)

OK簡單梳理了下過去的知識,PLSA假設每篇文件的詞頻向量服從Categorical分佈,那麼對於整個訓練樣本的詞頻矩陣W則服從多項式分佈。PLSA利用了aspect model,引入了潛在變數z(即所謂主題),使其變成一個混合模型(mixture model)。其圖模型如下:

其中表示文件集,Z便是PLSA中引入的隱含變數(主題/類別),表示詞表。表示單詞出現在文件的概率,表示文件中出現主題下的單詞的概率,給定主題出現單詞的概率。其中每個主題在所有詞項上服從Multinomial分佈,每個文件在所有主題上服從Multinmial分佈。按照生成模型,整個文件的生成過程如下:
(1)以的概率生成文件
(2)以的概率選中主題
(3)以的概率產生一個單詞
那麼對於單詞出現在文件的聯合概率分佈,而是隱含變數。

其中分別對應了兩組Multinomial分佈,PLSA需要訓練兩組分佈的引數

LDA

有了PLSA,那麼LDA就相對簡單了,其相當於貝葉斯(Bayes' theorem PS.關於貝葉斯理論強烈推薦:數學之美番外篇:平凡而又神奇的貝葉斯方法,一篇深入淺出的好文)PLSA即:
LDA=Bayesian pLSA
為什麼這麼說呢?我們站在貝葉斯理論的角度看上文提到的PLSA,基於上文的闡述,我們知道PLSA的假設是文件-詞語的詞頻矩陣服從多項式分佈(multinomial distribution),那麼在貝葉斯理論中,相當於我們找到了似然函式,那麼想要計算後驗概率時,我們需要找到先驗概率。

簡單扯兩句共軛先驗:
根據貝葉斯理論我們有如下形式:

OK其中我們可以成為似然函式即一件事情發生的似然性(最大似然估計),那麼相當於先驗概率分佈,一般為一個常數,所以忽略。那麼對於計算後驗概率,我們需要找到似然函式和先驗分佈。
一般當我們已知似然函式的形式的時候,我們需要找到先驗分佈,那麼對於所有滿足[0,1]區間內的分佈都符合這個條件,為了計算簡單,我們採用與似然函式形式儘量一致的分佈作為先驗分佈,這就是所謂的共軛先驗。
在上文中介紹多項式分佈時提到了Dirichlet分佈,我們看多項式分佈的形式如下:

那麼我們需要找尋形式相似如下的分佈:

而Dirichlet分佈的形式如下:

看出來了吧,去掉左邊的Beta分佈不說,在右邊的形式上Dirichlet分佈和Multinomial分佈是及其相似的,所以Dirichlet分佈是Multinomial分佈的共軛先驗。

再回到LDA,根據之前分析的PLSA可知,每個文件中詞的Topic分佈服從Multinomial分佈,其先驗選取共軛先驗即Dirichlet分佈;每個Topic下詞的分佈服從Multinomial分佈,其先驗也同樣選取共軛先驗即Dirichlet分佈。其圖模型如下:

我們可以看出LDA中每篇文章的生成過程如下:

  1. 選擇單詞數N服從泊松分佈,,
  2. 選擇服從狄利克雷分佈,,
  3. 對於N個單詞中的每個單詞 a. 選擇一個主題,服從多項分佈, b. 以概率生成單詞,其中表示在主題上的條件多項式概率。

在LDA中我們可以利用來表示一篇文件。

應用

從之前LDA的闡述中,我們可以利用theta來表示一篇文件,那麼我們自然可以利用這個向量對文件進行語義層面的詞語和文件的相似性分析從而達到聚類、推薦的效果。當然了LDA本身對於文件分析出的主題,以及每個主題下的詞彙,就是對於文件詞彙的一層低維聚類。
之前用過Git上Java版的LDA實現,但是語料不是很大,對其效能並不能做出很好的評估。其地址如下:
github: A Java implemention of LDA(Latent Dirichlet Allocation)

public static void main(String[] args)
{
    // 1. Load corpus from disk
    Corpus corpus = Corpus.load("data/mini");
    // 2. Create a LDA sampler
    LdaGibbsSampler ldaGibbsSampler = new LdaGibbsSampler(corpus.getDocument(), corpus.getVocabularySize());
    // 3. Train it
    ldaGibbsSampler.gibbs(10);
    // 4. The phi matrix is a LDA model, you can use LdaUtil to explain it.
    double[][] phi = ldaGibbsSampler.getPhi();
    Map<String, Double>[] topicMap = LdaUtil.translate(phi, corpus.getVocabulary(), 10);
    LdaUtil.explain(topicMap);
}

其採用吉布斯取樣的方法對LDA進行求解。之後自己也準備嘗試用spark進行實現,看是否能夠對效能進行優化。

Word2Vec

谷歌的Tomas Mikolov團隊開發了一種詞典和術語表的自動生成技術,能夠把一種語言轉變成另一種語言。該技術利用資料探勘來構建兩種語言的結構模型,然後加以對比。每種語言詞語之間的關係集合即“語言空間”,可以被表徵為數學意義上的向量集合。在向量空間內,不同的語言享有許多共性,只要實現一個向量空間向另一個的對映和轉換,語言翻譯即可實現。該技術效果非常不錯,對英語和西語間的翻譯準確率高達90%。

什麼是word2vec?你可以理解為word2vec就是將詞表徵為實數值向量的一種高效的演算法模型,其利用神經網路(關於神經網路之前有簡單進行整理:馬里奧AI實現方式探索 ——神經網路+增強學習),可以通過訓練,把對文字內容的處理簡化為K維向量空間中的向量運算,而向量空間上的相似度可以用來表示文字語義上的相似。(PS. 這裡往往人們會將word2vec和深度學習掛鉤,但其實word2vec僅僅只是用了一個非常淺層的神經網路,跟深度學習的關係並不大。)
Word2vec輸出的詞向量可以被用來做很多NLP相關的工作,比如聚類、找同義詞、詞性分析等等。如果換個思路, 把詞當做特徵,那麼Word2vec就可以把特徵對映到K維向量空間,可以為文字資料尋求更加深層次的特徵表示 。

神經網路語言模型

word2vec的思想最早起源於2003年Yoshua Bengio等人的論文A Neural Probabilistic Language Model

Traditional but very successful approaches based on n-grams obtain generalization by concatenating very short overlapping sequences seen in the training set. We propose to fight the curse of dimensionality by learning a distributed representation for words which allows each training sentence to inform the model about an exponential number of semantically neighboring
sentences. [16]

從文中摘要中的這段話我們可以看出,神經網路語言模型提出的初衷便是為了解決傳統的n-gram模型中維度災難的問題,用distributed representation詞向量的形式來表示每一個詞語。
文中提出的模型利用了一個三層神經網路如下圖(一般投影層算在輸入層中,這裡分開闡述):

其中,對於語料庫C,詞典D的長度為(|D|=N)為語料庫C的詞彙量大小。對於任意一個詞omegaContext(omega)表示其前n-1個詞語,類似於n-gram模型,二元對(Context(omega),omega)為一個訓練樣本。我們v(omega)為詞向量,詞向量的維度為m。圖中W,U分別為投影層和隱藏層以及隱藏層和輸出層之間的權值矩陣,p,q分別為隱藏層和輸出層上的偏置向量。
論文中給出的神經網路模型如下圖:

其中C(i)表示第i個詞的特徵向量(詞向量),我們看到圖中第一層為詞omega的上下文Context(omega)的每個詞向量,在第二層我們將輸入層的n-1個詞向量按順序首尾拼接在一起,形成一個長向量,其長度為(n-1)m,輸入到啟用函式tanh雙曲正切函式中,計算方式如下:

經過上述兩步計算得到的只是一個長度為N的向量,我們看到圖中第三層還做了一次softmax(Softmax function)歸一化,歸一化後
就可以表示為:

i_omega為詞omega在詞典D中的索引。
在之前的背景知識n-gram模型

我們知道語言模型中很關鍵的便是F的確定,其中引數theta如下:

  • 詞向量:

    相關推薦

    NLP點滴——文字相似計算文字距離

    前言 在自然語言處理過程中,經常會涉及到如何度量兩個文字之間的相似性,我們都知道文字是一種高維的語義空間,如何對其進行抽象分解,從而能夠站在數學角度去量化其相似性。而有了文字之間相似性的度量方式,我們便可以利用劃分法的K-means、基於密度的DBSCAN或者是基於模型的概率方法進行文字之間的聚類分析;另

    用gensim doc2vec計算文字相似Python可以跑通的程式碼

    Python3.7版本,轉載自:https://blog.csdn.net/juanjuan1314/article/details/75124046 wangyi_title.txt檔案下載地址:連結:https://pan.baidu.com/s/1uL75P13t98YHMqgv3Kx7T

    nlp文字相似計算問題

    文章的目的:文字相似度計算一直是nlp中常見的問題,本文的目標是總結並對比文字相似度計算方法。當然文字的相似度計算會有進一步的應用,比如文字的分類、聚類等。 文章結構:本文先介紹最直接的字面距離相似度度量,而後介紹語義主題層面的度量,最後介紹目前一些新的相似度計算方法。 一、字面距

    解析TF-IDF演算法原理:關鍵詞提取自動摘要文字相似計算

    Abstract:TF-IDF演算法是一種常用的詞頻統計方法,常被用於關鍵詞提取、文字摘要、文章相似度計算等。 TF-IDF的演算法思路 TF詞頻(Text Frequency):統計出現次數最多的詞 IDF逆文件頻率(Inverse Document Frequ

    Google開源word2vec文字相似計算工具

    谷歌已經使用Deep Learning技術開發了許多新方法來解析語言,目前,谷歌開源了一款基於Deep Learning的學習工具——word2vec,這是首款面向大眾的Deep Learning學習工具。 word2vec(word to vector)顧名思義,這是

    螞蟻金服金融大腦的挑戰賽的實現程式碼 NLP文字相似計算

    近期做了一些NLP的研究,並基於6月份螞蟻金服金融大腦的挑戰賽,完成了文字相似度計算的驗證。主要思路是基於word2vec來進行訓練,並實現文字相似度的計算。所使用的語料,包括了公開的wiki語料,網友收集的微信語料,以及此大賽中的語料。其中微信語料的位置在https://s

    從0到1瞭解NLP中的文字相似

    本文由雲+社群發表 作者:netkiddy 導語 AI在2018年應該是網

    計算句子文字相似-編輯距離計算

    本文轉載於:https://juejin.im/post/5b237b45f265da59a90c11d6 編輯距離,英文叫做 Edit Distance,又稱 Levenshtein 距離,是指兩個字串之間,由一個轉成另一個所需的最少編輯操作次數,如果它們的距離越大,說明它們越是不同。

    NLP文字相似(TF-IDF)

     我們在比較事物時,往往會用到“不同”,“一樣”,“相似”等詞語,這些詞語背後都涉及到一個動作——雙方的比較。只有通過比較才能得出結論,究竟是相同還是不同。但是萬物真的有這麼極端的區分嗎?在我看來不是的,生活中通過“相似度”這詞來描述可能會更加準確。比如男人和女人,雖然生理器官和可能思想有

    學習筆記--NLP文字相似之LCS(最長公共子序列)

    最長公共子序列 一個序列S任意刪除若干個字元得到的新序列T,則T叫做S的子序列 兩個序列X和Y的公共子序列中,長度最長的那個,定義為X和Y的最長公共子序列  例如:      --字串12455與245576的最長公共子序列為2455      --字串acd

    基於神經網路的文字相似計算【醫療大資料】

    任務描述 問句匹配是自然語言處理的最基本任務之一,是自動問答,聊天機器人,資訊檢索,機器翻譯等各種自然語言處理任務基礎。問句匹配的主要目的是判斷兩個問句之間的語義是否等價。判別標準主要根據主句(即提問者)所蘊含的意圖來判斷兩個語句是否等價,而不直接判斷兩個語句是否表達相

    Doc2Vec計算句子文件向量、求文字相似

    注:本文主要是記錄自己常用的關於Doc2Vec的簡單程式程式碼。因此不做過多的解釋,直接寫出程式碼,如有問題可以討論交流。 一、doc2vec求文件向量 import sys import numpy as np import gensim from gensim.mod

    DSSM演算法-計算文字相似

    轉載請註明出處: http://blog.csdn.net/u013074302/article/details/76422551 導語 在NLP領域,語義相似度的計算一直是個難題:搜尋場景下query和Doc的語義相似度、feeds場景下Doc和Doc的語義相似度、機器翻譯場景下A句

    Python 文字挖掘:使用gensim進行文字相似計算

    index = similarities.MatrixSimilarity(corpus_tfidf)#把所有評論做成索引 sims = index[vec_tfidf]#利用索引計算每一條評論和商品描述之間的相似度 similarity = list(sims)#把相似度儲存成陣列,以便寫入txt 文件

    NLP】Python例項:基於文字相似對申報專案進行查重設計

    作者:白寧超 2017年5月18日17:51:37 摘要:關於查重系統很多人並不陌生,無論本科還是碩博畢業都不可避免涉及論文查重問題,這也對學術不正之風起到一定糾正作用。單位主要針對科技專案申報稽核,傳統的方式人力物力比較大,且伴隨季度性的繁重工作,效率不高。基於此,單位覺得開發一款可以達到實用的

    計算文字相似方法大全-簡單說

    本編文章是方法論-主要給大家介紹原理思路 簡單講解 基於關鍵詞的空間向量模型的演算法,將使用者的喜好以文件描述並轉換成向量模型,對商品也是這麼處理,然後再通過計算商品文件和使用者偏好文件的餘弦相似度。 文字相似度計算在資訊檢索、資料探勘、機器翻譯、文件複製檢測等領域

    word2vec詞向量訓練及中文文字相似計算

    本文是講述如何使用word2vec的基礎教程,文章比較基礎,希望對你有所幫助!官網C語言下載地址:http://word2vec.googlecode.com/svn/trunk/官網Python下載地址:http://radimrehurek.com/gensim/mod

    文字相似計算的幾個距離公式(歐氏距離、餘弦相似、Jaccard距離、編輯距離

    本文主要講一下文字相似度計算的幾個距離公式,主要包括:歐氏距離、餘弦相似度、Jaccard距離、編輯距離。 距離計算在文字很多場景下都可以用到,比如:聚類、K近鄰、機器學習中的特徵、文字相似度等等。接下來就一一介紹一下: 假設兩個文字X=(x1, x2, x3,...xn)

    向量(文字相似(或點的距離)的計算方案彙總

           鑑於日常學習中常遇到將文字資料等轉為向量,然後計算向量相似度的問題,現將常用方案進行初步彙總,如統計不足敬請留言提示補充:1、餘弦相似度(cosine)        公式:                                即:以向量的夾角為考量角

    用gensim doc2vec計算文字相似

    最近開始接觸gensim庫,之前訓練word2vec用Mikolov的c版本程式,看了很久才把程式看明白,在gensim庫中,word2vec和doc2vec只需要幾個介面就可以實現,實在是方便。python,我越來越愛你了。 這個程式很簡單,直接上程式了。 # codin