基於word2vec與Word Mover Distance的文件相似度計算

yaphetsssf發表於2017-06-07

一、通過詞向量進行文件相似度判定的背景與意義 

隨著人類對自身祕密的探索與研究的不斷深入,以生物技術為基石的人工智慧技術也在快速的發展。通過對自身的神經以及思維的研究,人們創造出人工智慧,來使在各個領域中創造出能替自己完成工作的“機器”成為可能,從而減輕人們因繁重的體力或腦力勞動而產生的壓力。就目前來講,人工智慧的熱門領域集中在自然語言生成,語音識別,機器學習,決策管理,和文字分析NLP等等。本文將就文字分析進行展開。文字分析主要應用於問答系統的開發,如基於知識的問答系統(Knowledge-based QA),基於文件的問答系統(Documen-based QA),以及基於FAQ的問答系統(Community-QA)等。無論哪一種問答系統的開發,都離不開自然語言的理解,而文件相似度的判斷對這個方面有著重要影響。對於問題的內容,需要進行相似度匹配,從而選擇出與問題最接近,同時最合理的答案。其中,基於Google開源軟體word2vec訓練生成詞向量後進行文件相似度匹配是較重要的方向之一。

二、文件相似度判定的研究內容

2.1利用Google開源工具word2vec訓練詞向量

word2vec是Google在2013推出的一款用於獲取詞向量的工具包,它的簡單與高效引起了大量從事相關工作的開發者的關注。它的特點如下:

   (1)word2vec的模型通過一種神經網路語言模型(Neural Network Language Model)對語料中的所有詞彙進行訓練並生成相應的詞向量(Word Embedding),通過對詞向量的距離(如餘弦值或者歐氏距離)的計算即可得出兩個詞的相似度。             

   (2)與CBOW(連續詞袋模型)利用詞語的上下文來預測詞語相反,word2vec利用的Skip-Gram模型利用詞語來預測其上下文。通過把一個個的詞語當做特徵,將特徵對映到K維向量空間中去,來獲得文字資料更精確的特徵顯示。

   (3)根據詞頻用Huffman編碼,使得出現頻率越高的詞語,他們啟用的隱藏層數目越少,這樣有效的降低了計算的複雜度

   (4)綜上,word2vec高效的實現了詞向量的訓練,甚至在優化好的單機版本中一天就可訓練上億個詞彙,這是我選擇word2vec來生成詞向量的原因。

2.2利用WMD(Word Mover Distance)模型計算文件相似度

2.2.1WMD模型原理介紹

WMD的模型基於EMD(Earth Mover Distance)模型。EMD和歐式距離一樣,它們都是一種距離度量的定義、可以用來測量某兩個分佈之間的距離。其主要應用在影象處理和語音訊號處理領域,WMD的模型正是基於EMD,將該模型的適用範圍延伸到了自然語言處理領域。對EMD的詳細原理不再此贅述。

Matt等人(見參考文獻)將詞嵌入與EMD相聯絡,用來度量文件距離。提出了WMD(word mover’s distance)演算法,以及WCD(word centroid distance)、RWMD(relaxed word mover’s distance)兩種犧牲精度降低複雜度的演算法。

   (1)WMD演算法利用NBOW(normalized bag-of-words即歸一化的詞袋模型)表示分佈P。其中P1表示詞語本身,用來計算該詞在當前文件中的權重,其中表示詞語i在所屬文件中出現了幾次,P1的特徵量用該詞語的詞向量表示。

   (2)WMD演算法利用求一個Word travel cost來計算詞i到詞j的相似度,即是求詞i與詞j的詞向量的歐式距離,距離值為C(i,j)=|(vecI-vecJ)|。在這裡將C(i,j)看做將詞i轉化為詞j所付出的代價。

  (3)WMD演算法利用求Document distance來表示文件間的距離。

   (如上圖所示,箭頭表示各個文件(D)轉化到其他文件時,各個詞在轉化代價中的貢獻值)

在這裡定義矩陣T ,。其中TijTij>=0)表示d文件中的詞i有多少轉化為了d’文件中的詞j 。為了保證文件d能轉化為文件d’,需要保證詞i轉化為d’中所有詞語的量之和為di,即,同理,也應該滿足d文件中轉化到d’文件中的詞j的總量為dj,即。我們即可定義文件dd’的距離為,將d中所有詞轉化為d’中詞的最小代價,即求

 

2.2.2WMD的更快計算方法

WMD演算法的複雜度是O(P^3*logP),其中P表示nBOW模型的長度,即資料集中不同詞語的數目(去處停用詞)。

為了降低模型的負責度,Matt等人還提出了WCD和RWMD兩個演算法,通過降低精度來降低演算法的複雜度。這兩個演算法都是求得WMD的其中一種下限。實現時採用後者。

 

 

(1)WCD(Word centroid distance)

WCD方法是指讓文件的加權平均詞向量來代表各個文件。通過少量的矩陣運算就能得出結果,演算法複雜度僅O(dp),可以求得WMD問題的其中一個下限,是最快的一種計算方法。將作為WCD,其中X是一個

的矩陣,d表示詞向量的維度,p表示NBOW模型長度。

 

(2)RWMD(Relaxed word moving distance)

儘管WCD計算很快,但是下限並不是非常緊。我們可以通過放寬WMD的一些條件限制來降低WMD問題的複雜度,即去除掉2個限制條件中的後者。修改後的問題如下:

拿倉庫問題來類比,通過去掉條件2,其實是去掉了倉庫容量的限制,我們可以將貨物全部運到離其最近的倉庫,而不需要考慮倉庫的容量。我們在運某個貨物時,往離該貨物最近的倉庫運送,即在對d文件中的詞i進行轉換時,我們只需要將它轉換到d’文件中離它最近的詞j即可,而不需要考慮j所能容納的轉換的限制。

在用RWMD計算文件相似度時,可以先求出d文件中所有id’文件中任意j的最近的C(i,j)(此處為歐式距離)即可,演算法複雜度為O(p^2)。之後再用各個Tij乘以相應的最小C(i,j)即可得出結果,演算法複雜度為O(p)。真個RWMD演算法的總複雜度為O(p^2)

 

2.2.3RWMD演算法的C#實現

word2vec的訓練語料來自Wiki百科,訓練後的詞向量資料大小約1G。文件分詞采用中科院的NIPIR。

此處用對問題答案對的相似度計算來演示文件相似度的計算。

public class WordEmbeddingPredictor : IPredictor 
    {
        Dictionary<string, List<double>> word2vector; //詞向量對映

        public WordEmbeddingPredictor(string word2vectorFile) //初始化Predictor
        {

            word2vector = new Dictionary<string, List<double>>();

            Console.WriteLine("Loading word2vec model...");
            LoadWord2Vec(word2vectorFile);
            Console.WriteLine("Load word2vec model done.");
        }
        public double GetSimilarity(string question, string answer) //獲取問題與答案的相似度
        {
            var qWords = Tokenizer.Tokenize(question); //分詞
            var aWords = Tokenizer.Tokenize(answer);

            return ComputeEmbeddingDistance(qWords, aWords); //計算偏移距離
        }
        void LoadWord2Vec(string word2vectorFile)//載入訓練好的詞向量檔案
        {

            using (StreamReader sr = new StreamReader(word2vectorFile))
            {
                //read vocab size + dim size
                string[] fileInfo = sr.ReadLine().Split(' ');
                var vecLength = int.Parse(fileInfo[1]);

                while (!sr.EndOfStream)
                {
                    string[] parts = sr.ReadLine().Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);

                    if (parts.Length != 1 + vecLength) continue;

                    var word = parts[0];

                    if (!word2vector.ContainsKey(word))
                    {
                        List<double> vector = new List<double>();
                        for (int i = 1; i <= vecLength; i++)
                        {
                            vector.Add(double.Parse(parts[i]));
                        }
                        word2vector.Add(word, vector);
                    }

                }
            }

        }

        double ComputeEmbeddingDistance(IList<string> qWords, IList<string> aWords)
        {
            //載入每個在詞向量中存在的詞
            var word2Frecs = GetWord2Frec(qWords, aWords);
            //Console.WriteLine("Ok");
            var qWord2Frec = word2Frecs[0];
            var aWord2Frec = word2Frecs[1];
            if (qWord2Frec.Count == 0 || aWord2Frec.Count == 0)
            {
                return -10000000; //如果問題或者答案中的詞都沒有出現在詞向量中,返回一個較小的負值,避免對其他測試資料的干擾
            }
            List<double> minMetrics = new List<double>(); //計算每個i對應的最短歐式距離
            List<double> frec = new List<double>();

            for (int i = 0; i < qWords.Count; i++) //計算過程
            {
                if (!qWord2Frec.ContainsKey(qWords[i]))
                {
                    continue;
                }
                double minMetric = double.MaxValue;
                for (int j = 0; j < aWords.Count; j++)
                {
                    if (!aWord2Frec.ContainsKey(aWords[j]))
                    {
                        continue;
                    }
                    IList<double> iVec = word2vector[qWords[i]];
                    IList<double> jVec = word2vector[aWords[j]];
                    double curMetric = GetEuclideanMetric(iVec, jVec);
                    if (curMetric < minMetric)
                    {
                        minMetric = curMetric;
                    }
                }
                minMetrics.Add(minMetric);
                // Console.WriteLine("go");
                frec.Add(qWord2Frec[qWords[i]]);
            }
            double sum = 0;
            for (int i = 0; i < minMetrics.Count; i++)
            {
                sum += minMetrics[i] * frec[i];
            }
            return -sum; //因為值越小越不相似,所以取負後越相似值越大
        }

        IList<Dictionary<string, double>> GetWord2Frec(IList<string> qWords, IList<string> aWords)//獲取所有qa中且詞庫中存在的詞以及頻率並返回
        {
            Dictionary<string, double> qWord2Frec = new Dictionary<string, double>(); //記錄詞語的出現次數
            Dictionary<string, double> aWord2Frec = new Dictionary<string, double>();
            foreach (var str in qWords)
            {
                if (!word2vector.ContainsKey(str))
                {
                    continue;
                }
                if (!qWord2Frec.ContainsKey(str))
                {
                    qWord2Frec.Add(str, 1.0);
                }
                else
                {
                    qWord2Frec[str] += 1.0;
                }
                //Console.WriteLine("Ok");
            }
            foreach (var str in aWords)
            {
                if (!word2vector.ContainsKey(str))
                {
                    continue;
                }
                if (!aWord2Frec.ContainsKey(str))
                {
                    aWord2Frec.Add(str, 1.0);
                }
                else
                {
                    aWord2Frec[str] += 1.0;
                }
                //Console.WriteLine("Ok2");
            }
            int qDicLen = qWord2Frec.Count;
            int aDicLen = aWord2Frec.Count;
            var qKeys = qWord2Frec.Keys;
            var aKeys = aWord2Frec.Keys;
            foreach (var str in qKeys.ToArray()) //不能在遍歷時修改
            {
                qWord2Frec[str] /= (double)qDicLen;
            }
            //Console.WriteLine("Ok3");
            foreach (var str in aKeys.ToArray())
            {
                aWord2Frec[str] /= (double)aDicLen;
            }
            //Console.WriteLine("Ok4");
            List<Dictionary<string, double>> dics = new List<Dictionary<string, double>>();
            dics.Add(qWord2Frec);
            dics.Add(aWord2Frec);
            return dics;
        }

        double GetEuclideanMetric(IList<double> qVec, IList<double> aVec)
        {
            double sum = 0;
            for (int i = 0; i < qVec.Count; i++)
            {
                sum += Math.Pow((qVec[i] - aVec[i]), 2);
            }
            return Math.Sqrt(sum);
        }

    }

三、總結

WMD演算法是對基於詞向量進行文件相似度判斷的一個不錯的想法,模型本身簡單明瞭,構思巧妙,但是解決實際問題時的準確度仍有限,且演算法複雜度較高。在今後的學習中,希望瞭解更多的自然語言處理的相關知識,如利用各種神經網路(CNN,RNN等)來進行處理。

四、參考文獻

[1] MattJ.Kusner YuSun等人.From Word Embeddings To Document Distances[C].

Washington University in St. Louis, 1 Brookings Dr., St. Louis, MO 63130

[2] 劉龍飛.http://ir.dlut.edu.cn/news/detail/362[Z].大連:大連理工大學資訊檢索研究室

[3] 樑俊毅. 人工智慧的發展及其認知意義[C].廣西:廣西工業職業技術學院

 




相關文章