# Alink漫談(六) : TF-IDF演算法的實現 [TOC] ## 0x00 摘要 Alink 是阿里巴巴基於實時計算引擎 Flink 研發的新一代機器學習演算法平臺,是業界首個同時支援批式演算法、流式演算法的機器學習平臺。TF-IDF(term frequency–inverse document frequency)是一種用於資訊檢索與資料探勘的常用加權技術。本文將為大家展現Alink如何實現TF-IDF。 ## 0x01 TF-IDF TF-IDF(term frequency–inverse document frequency)是一種統計方法,一種用於資訊檢索與資料探勘的常用加權技術。 TF是詞頻(Term Frequency),IDF是逆文字頻率指數(Inverse Document Frequency)。 為什麼要用TF-IDF?**因為計算機只能識別數字,對於一個一個的單詞,計算機是看不懂的**,更別說是一句話,或是一篇文章。**而TF-IDF就是用來將文字轉換成計算機看得懂的語言,或者說是機器學習或深度學習模型能夠進行學習訓練的資料集**。 ### 1.1 原理 TF-IDF用以評估一個詞對於一個檔案集或一個語料庫中的其中一份檔案的重要程度。字詞的重要性隨著它在檔案中出現的次數成正比增加,但同時會隨著它在語料庫中出現的頻率成反比下降。 TF-IDF的主要思想是:如果某個詞或短語在一篇文章中出現的頻率TF高,並且在其他文章中很少出現,則認為此詞或者短語具有很好的類別區分能力,適合用來分類。 TF-IDF實際上是:TF * IDF,TF詞頻(Term Frequency),IDF逆向檔案頻率(Inverse Document Frequency)。 **詞頻**(term frequency,TF)指的是某一個給定的詞語在該檔案中出現的頻率。這個數字是對**詞數**(term count)的歸一化,以防止它偏向長的檔案(同一個詞語在長檔案裡可能會比短檔案有更高的詞數,而不管該詞語重要與否)。 而**IDF逆向檔案頻率 (inverse document frequency, IDF)**反應了一個詞在**所有文字(整個文件)**中出現的頻率,**如果一個詞在很多的文字中出現,那麼它的IDF值應該低。而反過來如果一個詞在比較少的文字中出現,那麼它的IDF值應該高**。比如一些專業的名詞如“Machine Learning”。這樣的詞IDF值應該高。一個極端的情況,如果一個詞在所有的文字中都出現,那麼它的IDF值應該為0。 如果單單以TF或者IDF來計算一個詞的重要程度都是片面的,因此TF-IDF綜合了TF和IDF兩者的優點,用以評估**一字詞**對於一個檔案集或一個語料庫中的其中一份檔案的重要程度。字詞的重要性隨著它在檔案中出現的次數成正比增加,但同時會隨著它在語料庫中出現的頻率成反比下降。上述引用總結就是:**一個詞語在一篇文章中出現次數越多, 同時在所有文件中出現次數越少, 越能夠代表該文章,越能與其它文章區分開來。** ### 1.2 計算方法 TF的計算公式如下: $$ TF_w = \frac {N_w}{N} $$ 其中 N_w 是在某一文字中詞條w出現的次數,N 是該文字總詞條數。 IDF的計算公式如下: $$ IDF_w = log(\frac {Y}{Y_w + 1}) $$ 其中 Y 是語料庫的文件總數,Y_w 是包含詞條w的文件數,分母加一是為了避免w 未出現在任何文件中從而導致分母為0 的情況。 TF-IDF 就是將TF和IDF相乘 : $$ TF-IDF_w = TF_w * IDF_w $$ 從以上計算公式便可以看出,某一特定檔案內的高詞語頻率,以及該詞語在整個檔案集合中的低檔案頻率,可以產生出高權重的TF-IDF。因此,**TF-IDF傾向於過濾掉常見的詞語,保留重要的詞語。** ## 0x02 Alink示例程式碼 ### 2.1 示例程式碼 首先我們給出示例程式碼,下文是通過一些語料來訓練出一個模型,然後用這個模型來做預測: ```java public class DocCountVectorizerExample { AlgoOperator getData(boolean isBatch) { Row[] rows = new Row[]{ Row.of(0, "二手舊書:醫學電磁成像"), Row.of(1, "二手美國文學選讀( 下冊 )李宜燮南開大學出版社 9787310003969"), Row.of(2, "二手正版圖解象棋入門/謝恩思主編/華齡出版社"), Row.of(3, "二手中國糖尿病文獻索引"), Row.of(4, "二手郁達夫文集( 國內版 )全十二冊館藏書") }; String[] schema = new String[]{"id", "text"}; if (isBatch) { return new MemSourceBatchOp(rows, schema); } else { return new MemSourceStreamOp(rows, schema); } } public static void main(String[] args) throws Exception { DocCountVectorizerExample test = new DocCountVectorizerExample(); BatchOperator batchData = (BatchOperator) test.getData(true); // 分詞 SegmentBatchOp segment = new SegmentBatchOp() .setSelectedCol("text") .linkFrom(batchData); // TF-IDF訓練 DocCountVectorizerTrainBatchOp model = new DocCountVectorizerTrainBatchOp() .setSelectedCol("text") .linkFrom(segment); // TF-IDF預測 DocCountVectorizerPredictBatchOp predictBatch = new DocCountVectorizerPredictBatchOp() .setSelectedCol("text") .linkFrom(model, segment); model.print(); predictBatch.print(); } } ``` ### 2.2 TF-IDF模型 TF-IDF模型打印出來如下: ```java model_id|model_info --------|---------- 0|{"minTF":"1.0","featureType":"\"WORD_COUNT\""} 1048576|{"f0":"二手","f1":0.0,"f2":0} 2097152|{"f0":"/","f1":1.0986122886681098,"f2":1} 3145728|{"f0":"出版社","f1":0.6931471805599453,"f2":2} 4194304|{"f0":")","f1":0.6931471805599453,"f2":3} 5242880|{"f0":"(","f1":0.6931471805599453,"f2":4} 6291456|{"f0":"入門","f1":1.0986122886681098,"f2":5} ...... 36700160|{"f0":"美國","f1":1.0986122886681098,"f2":34} 37748736|{"f0":"謝恩","f1":1.0986122886681098,"f2":35} 38797312|{"f0":"象棋","f1":1.0986122886681098,"f2":36} ``` ### 2.3 TF-IDF預測 TF-IDF預測結果如下: ```java id|text --|---- 0|$37$0:1.0 6:1.0 10:1.0 25:1.0 26:1.0 28:1.0 1|$37$0:1.0 1:1.0 2:1.0 4:1.0 11:1.0 15:1.0 16:1.0 19:1.0 20:1.0 32:1.0 34:1.0 2|$37$0:1.0 3:2.0 4:1.0 5:1.0 8:1.0 22:1.0 23:1.0 24:1.0 29:1.0 35:1.0 36:1.0 3|$37$0:1.0 12:1.0 27:1.0 31:1.0 33:1.0 4|$37$0:1.0 1:1.0 2:1.0 7:1.0 9:1.0 13:1.0 14:1.0 17:1.0 18:1.0 21:1.0 30:1.0 ``` ## 0x03 分詞 Segment 中文分詞(Chinese Word Segmentation) 指的是將一個漢字序列切分成一個一個單獨的詞。分詞就是將連續的字序列按照一定的規範重新組合成詞序列的過程。 示例程式碼中,分詞部分如下: ```java SegmentBatchOp segment = new SegmentBatchOp() .setSelectedCol("text") .linkFrom(batchData); ``` 分詞主要是如下兩個類,其作用就是把中文文件分割成單詞。 ```java public final class SegmentBatchOp extends Map