【新詞發現】基於SNS的文本數據挖掘、短語挖掘


互聯網時代的社會語言學:基於SNS的文本數據挖掘

python實現 https://github.com/jtyoui/Jtyoui/tree/master/jtyoui/word  這是一個無監督訓練文本詞庫與分詞 (轉載)

java實現 https://gitee.com/tyoui/jsns 這個速度要快一點。邏輯比較清楚些(轉載)

 更多實現見文章末尾:

實現原理如下:(轉載)http://www.matrix67.com/blog/archives/5044

    今年上半年,我在人人網實習了一段時間,期間得到了很多寶貴的數據,並做了一些還算有意義的事情,在這里和大家一塊兒分享。感謝人人網提供的數據與工作環境,感謝趙繼承博士、詹衛東老師的支持和建議。在這項工作中,我得到了很多與眾人交流的機會,特別感謝 OpenParty 、 TEDxBeijing 提供的平台。本文已發表在了《程序員》雜志,分上下兩部分刊於 2012 年 7 月刊和 8 月刊,在此感謝盧鶇翔編輯的辛勤工作。由於眾所周知的原因,《程序員》刊出的文章被和諧過(看到后面大家就自動地知道被和諧的內容是什么了),因而我決定把完整版發在 Blog 上,同時與更多的人一同分享。對此感興趣的朋友可以給我發郵件繼續交流。好了,開始說正文吧。

    作為中文系應用語言學專業的學生以及一名數學 Geek ,我非常熱衷於用計算的方法去分析漢語資料。漢語是一種獨特而神奇的語言。對漢語資料進行自然語言處理時,我們會遇到很多其他語言不會有的困難,比如分詞——漢語的詞與詞之間沒有空格,那計算機怎么才知道,“已結婚的和尚未結婚的青年都要實行計划生育”究竟說的是“已/結婚/的/和/尚未/結婚/的/青年”,還是“已/結婚/的/和尚/未/結婚/的/青年”呢?這就是所謂的分詞歧義難題。不過,現在很多語言模型已經能比較漂亮地解決這一問題了。但在中文分詞領域里,還有一個比分詞歧義更令人頭疼的東西——未登錄詞。中文沒有首字母大寫,專名號也被取消了,這叫計算機如何辨認人名地名之類的東西?更慘的則是機構名、品牌名、專業名詞、縮略語、網絡新詞等等,它們的產生機制似乎完全無規律可尋。最近十年來,中文分詞領域都在集中攻克這一難關。自動發現新詞成為了關鍵的環節。

    挖掘新詞的傳統方法是,先對文本進行分詞,然后猜測未能成功匹配的剩余片段就是新詞。這似乎陷入了一個怪圈:分詞的准確性本身就依賴於詞庫的完整性,如果詞庫中根本沒有新詞,我們又怎么能信任分詞結果呢?此時,一種大膽的想法是,首先不依賴於任何已有的詞庫,僅僅根據詞的共同特征,將一段大規模語料中可能成詞的文本片段全部提取出來,不管它是新詞還是舊詞。然后,再把所有抽出來的詞和已有詞庫進行比較,不就能找出新詞了嗎?有了抽詞算法后,我們還能以詞為單位做更多有趣的數據挖掘工作。這里,我所選用的語料是人人網 2011 年 12 月前半個月部分用戶的狀態。非常感謝人人網提供這份極具價值的網絡語料。


 
 
    要想從一段文本中抽出詞來,我們的第一個問題就是,怎樣的文本片段才算一個詞?大家想到的第一個標准或許是,看這個文本片段出現的次數是否足夠多。我們可以把所有出現頻數超過某個閾值的片段提取出來,作為該語料中的詞匯輸出。不過,光是出現頻數高還不夠,一個經常出現的文本片段有可能不是一個詞,而是多個詞構成的詞組。在人人網用戶狀態中,“的電影”出現了 389 次,“電影院”只出現了 175 次,然而我們卻更傾向於把“電影院”當作一個詞,因為直覺上看,“電影”和“院”凝固得更緊一些。

    為了證明“電影院”一詞的內部凝固程度確實很高,我們可以計算一下,如果“電影”和“院”真的是各自獨立地在文本中隨機出現,它倆正好拼到一起的概率會有多小。在整個 2400 萬字的數據中,“電影”一共出現了 2774 次,出現的概率約為 0.000113 。“院”字則出現了 4797 次,出現的概率約為 0.0001969 。如果兩者之間真的毫無關系,它們恰好拼在了一起的概率就應該是 0.000113 × 0.0001969 ,約為 2.223 × 10-8 次方。但事實上,“電影院”在語料中一共出現了 175 次,出現概率約為 7.183 × 10-6 次方,是預測值的 300 多倍。類似地,統計可得“的”字的出現概率約為 0.0166 ,因而“的”和“電影”隨機組合到了一起的理論概率值為 0.0166 × 0.000113 ,約為 1.875 × 10-6 ,這與“的電影”出現的真實概率很接近——真實概率約為 1.6 × 10-5 次方,是預測值的 8.5 倍。計算結果表明,“電影院”更可能是一個有意義的搭配,而“的電影”則更像是“的”和“電影”這兩個成分偶然拼到一起的。

    當然,作為一個無知識庫的抽詞程序,我們並不知道“電影院”是“電影”加“院”得來的,也並不知道“的電影”是“的”加上“電影”得來的。錯誤的切分方法會過高地估計該片段的凝合程度。如果我們把“電影院”看作是“電”加“影院”所得,由此得到的凝合程度會更高一些。因此,為了算出一個文本片段的凝合程度,我們需要枚舉它的凝合方式——這個文本片段是由哪兩部分組合而來的。令 p(x) 為文本片段 x 在整個語料中出現的概率,那么我們定義“電影院”的凝合程度就是 p(電影院) 與 p(電) · p(影院) 比值和 p(電影院) 與 p(電影) · p(院) 的比值中的較小值,“的電影”的凝合程度則是 p(的電影) 分別除以 p(的) · p(電影) 和 p(的電) · p(影) 所得的熵的較小值。

    可以想到,凝合程度最高的文本片段就是諸如“蝙蝠”、“蜘蛛”、“彷徨”、“忐忑”、“玫瑰”之類的詞了,這些詞里的每一個字幾乎總是會和另一個字同時出現,從不在其他場合中使用

 
    光看文本片段內部的凝合程度還不夠,我們還需要從整體來看它在外部的表現。考慮“被子”和“輩子”這兩個片段。我們可以說“買被子”、“蓋被子”、“進被子”、“好被子”、“這被子”等等,在“被子”前面加各種字;但“輩子”的用法卻非常固定,除了“一輩子”、“這輩子”、“上輩子”、“下輩子”,基本上“輩子”前面不能加別的字了。“輩子”這個文本片段左邊可以出現的字太有限,以至於直覺上我們可能會認為,“輩子”並不單獨成詞,真正成詞的其實是“一輩子”、“這輩子”之類的整體。可見,文本片段的自由運用程度也是判斷它是否成詞的重要標准。如果一個文本片段能夠算作一個詞的話,它應該能夠靈活地出現在各種不同的環境中,具有非常豐富的左鄰字集合和右鄰字集合。

    “信息熵”是一個非常神奇的概念,它能夠反映知道一個事件的結果后平均會給你帶來多大的信息量。如果某個結果的發生概率為 p ,當你知道它確實發生了,你得到的信息量就被定義為 – log(p) 。 p 越小,你得到的信息量就越大。如果一顆骰子的六個面分別是 1 、 1 、 1 、 2 、 2 、 3 ,那么你知道了投擲的結果是 1 時可能並不會那么吃驚,它給你帶來的信息量是 – log(1/2) ,約為 0.693 。知道投擲結果是 2 ,給你帶來的信息量則是 – log(1/3) ≈ 1.0986 。知道投擲結果是 3 ,給你帶來的信息量則有 – log(1/6) ≈ 1.79 。但是,你只有 1/2 的機會得到 0.693 的信息量,只有 1/3 的機會得到 1.0986 的信息量,只有 1/6 的機會得到 1.79 的信息量,因而平均情況下你會得到 0.693/2 + 1.0986/3 + 1.79/6 ≈ 1.0114 的信息量。這個 1.0114 就是那顆骰子的信息熵。現在,假如某顆骰子有 100 個面,其中 99 個面都是 1 ,只有一個面上寫的 2 。知道骰子的拋擲結果是 2 會給你帶來一個巨大無比的信息量,它等於 – log(1/100) ,約為 4.605 ;但你只有百分之一的概率獲取到這么大的信息量,其他情況下你只能得到 – log(99/100) ≈ 0.01005 的信息量。平均情況下,你只能獲得 0.056 的信息量,這就是這顆骰子的信息熵。再考慮一個最極端的情況:如果一顆骰子的六個面都是 1 ,投擲它不會給你帶來任何信息,它的信息熵為 – log(1) = 0 。什么時候信息熵會更大呢?換句話說,發生了怎樣的事件之后,你最想問一下它的結果如何?直覺上看,當然就是那些結果最不確定的事件。沒錯,信息熵直觀地反映了一個事件的結果有多么的隨機。

    我們用信息熵來衡量一個文本片段的左鄰字集合和右鄰字集合有多隨機。考慮這么一句話“吃葡萄不吐葡萄皮不吃葡萄倒吐葡萄皮”,“葡萄”一詞出現了四次,其中左鄰字分別為 {吃, 吐, 吃, 吐} ,右鄰字分別為 {不, 皮, 倒, 皮} 。根據公式,“葡萄”一詞的左鄰字的信息熵為 – (1/2) · log(1/2) – (1/2) · log(1/2) ≈ 0.693 ,它的右鄰字的信息熵則為 – (1/2) · log(1/2) – (1/4) · log(1/4) – (1/4) · log(1/4) ≈ 1.04 。可見,在這個句子中,“葡萄”一詞的右鄰字更加豐富一些。

    在人人網用戶狀態中,“被子”一詞一共出現了 956 次,“輩子”一詞一共出現了 2330 次,兩者的右鄰字集合的信息熵分別為 3.87404 和 4.11644 ,數值上非常接近。但“被子”的左鄰字用例非常豐富:用得最多的是“曬被子”,它一共出現了 162 次;其次是“的被子”,出現了 85 次;接下來分別是“條被子”、“在被子”、“床被子”,分別出現了 69 次、 64 次和 52 次;當然,還有“疊被子”、“蓋被子”、“加被子”、“新被子”、“掀被子”、“收被子”、“薄被子”、“踢被子”、“搶被子”等 100 多種不同的用法構成的長尾⋯⋯所有左鄰字的信息熵為 3.67453 。但“輩子”的左鄰字就很可憐了, 2330 個“輩子”中有 1276 個是“一輩子”,有 596 個“這輩子”,有 235 個“下輩子”,有 149 個“上輩子”,有 32 個“半輩子”,有 10 個“八輩子”,有 7 個“幾輩子”,有 6 個“哪輩子”,以及“n 輩子”、“兩輩子”等 13 種更罕見的用法。所有左鄰字的信息熵僅為 1.25963 。因而,“輩子”能否成詞,明顯就有爭議了。“下子”則是更典型的例子, 310 個“下子”的用例中有 294 個出自“一下子”, 5 個出自“兩下子”, 5 個出自“這下子”,其余的都是只出現過一次的罕見用法。事實上,“下子”的左鄰字信息熵僅為 0.294421 ,我們不應該把它看作一個能靈活運用的詞。當然,一些文本片段的左鄰字沒啥問題,右鄰字用例卻非常貧乏,例如“交響”、“后遺”、“鵝卵”等,把它們看作單獨的詞似乎也不太合適。我們不妨就把一個文本片段的自由運用程度定義為它的左鄰字信息熵和右鄰字信息熵中的較小值

 
    在實際運用中你會發現,文本片段的凝固程度和自由程度,兩種判斷標准缺一不可。只看凝固程度的話,程序會找出“巧克”、“俄羅”、“顏六色”、“柴可夫”等實際上是“半個詞”的片段;只看自由程度的話,程序則會把“吃了一頓”、“看了一遍”、“睡了一晚”、“去了一趟”中的“了一”提取出來,因為它的左右鄰字都太豐富了。

 
 
    我們把文本中出現過的所有長度不超過 d 的子串都當作潛在的詞(即候選詞,其中 d 為自己設定的候選詞長度上限,我設定的值為 5 ),再為出現頻數、凝固程度和自由程度各設定一個閾值,然后只需要提取出所有滿足閾值要求的候選詞即可。為了提高效率,我們可以把語料全文視作一整個字符串,並對該字符串的所有后綴按字典序排序。下表就是對“四是四十是十十四是十四四十是四十”的所有后綴進行排序后的結果。實際上我們只需要在內存中存儲這些后綴的前 d + 1 個字,或者更好地,只儲存它們在語料中的起始位置。

 

更多見原文 http://www.matrix67.com/blog/archives/5044

 

更多相關實現:

 

我把樓主的算法用python實現,並進行一些必要的優化,並加入到我的分詞庫里。開源的分詞庫地址:https://github.com/jannson/yaha
實現在 yaha/wordmaker.py里
實用示例在 tests/test_cuttor.py里
歡迎大家進行測試。6M以下的文本問題不大,如若要分析更大的文本,后續會添加一個c++實現的版本,測試發現比python快 10倍
https://github.com/sing1ee/dict_build 這個簡單用java實現了,看效果還不錯。挺好玩的。
C++11/14實現了一個,寫得渣勿噴。。 https://github.com/zouyxdut/new-words-discoverer
成詞條件
互信息
左右熵
位置成詞概率
ngram 頻率


自動構建中文詞庫:http://www.matrix67.com/blog/archives/5044
python 3 實現:
https://github.com/yanghanxy/New-Word-Detection
Python 3 實現了一個,(新)



Neologism

https://github.com/jtyoui/Jtyoui/tree/master/jtyoui/word

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM