這是一篇一本正經無聊的小研究項目。。
互聯網現在面臨很多新網絡文體,比如彈幕文體、小紅書的種草文體、網名等,這些超短文本中本身字符特徵就比較少,但是表情包占比卻很多,這是重要信息呀。
之前參加比賽,一般都是當作停用詞直接刪掉,在這些超短文本中可就不行了。
相關代碼可見我的github:py-yanwenzi
相關文章:
網絡表情NLP(一)︱顏文字表情實體識別、屬性檢測、新顏發現
網絡表情NLP(二)︱特殊表情包+emoji識別
文章目錄
1 混用的幾個庫
這裏混用了幾個筆者常用的文本處理的庫,
- jieba_fast,相比jieba,jieba_fast 使用cpython重寫了jieba分詞庫中計算DAG和HMM中的vitrebi函數,速度得到大幅提升
- flashtext,Flashtext:大規模數據清洗的利器,正則表達式在一個 10k 的詞庫中查找 15k 個關鍵詞的時間差不多是 0.165 秒。但是對於 Flashtext 而言只需要 0.002 秒。因此,在這個問題上 Flashtext 的速度大約比正則表達式快 82 倍。可參考:python︱flashtext高效關鍵詞查找與替換
- rouge,Rouge-1、Rouge-2、Rouge-L分別是:生成的摘要的1gram-2gram在真實摘要的1gram-2gram的準確率召回率和f1值,還有最長公共子序列在預測摘要中所佔比例是準確率,在真實摘要中所佔比例是召回率,然後可以計算出f1值。
1.1 模塊一:rouge
rouge是自動文本摘要算法的評估指標:
from rouge import Rouge
a = ["i am a student from xx school"] # 預測摘要 (可以是列表也可以是句子)
b = ["i am a student from school on china"] #真實摘要
rouge = Rouge()
rouge_score = rouge.get_scores(a, b)
print(rouge_score[0]["rouge-1"])
print(rouge_score[0]["rouge-2"])
print(rouge_score[0]["rouge-l"])
>>> {'f': 0.7999999950222222, 'p': 0.8571428571428571, 'r': 0.75}
>>> {'f': 0.6153846104142012, 'p': 0.6666666666666666, 'r': 0.5714285714285714}
>>> {'f': 0.7929824561399953, 'p': 0.8571428571428571, 'r': 0.75}
該模塊是使用在顏文字相似性匹配的時候,當然這邊從實驗效果來看,2-grams的效果比較好。
1.2 模塊二:jieba_fast
使用 c 重寫了jieba分詞庫中的核心函數,速度得到大幅提升。
特點
- 對兩種分詞模式進行的加速:精確模式,搜索引擎模式
- 利用cpython重新實現了 viterbi 算法,使默認帶 HMM 的切詞模式速度提升 60%左右
- 利用cpython重新實現了生成 DAG 以及從 DAG 計算最優路徑的算法,速度提升 50%左右
- 基本只是替換了核心函數,對源代碼的侵入型修改很少
- 使用import jieba_fast as jieba 可以無縫銜接原代碼。
其中,
github:https://github.com/deepcs233/jieba_fast
代碼示例:
# encoding=utf-8
import jieba_fast as jieba
text = u'在輸出層後再增加CRF層,加強了文本間信息的相關性,針對序列標註問題,每個句子的每個詞都有一個標註結果,對句子中第i個詞進行高維特徵的抽取,通過學習特徵到標註結果的映射,可以得到特徵到任> 意標籤的概率,通過這些概率,得到最優序列結果'
print("-".join(jieba.lcut(text, HMM=True))
print('-'.join(jieba.lcut(text, HMM=False)))
與jieba基本一致
1.3 關鍵詞查詢組件:flashtext
詳情可參考筆者博客:python︱flashtext高效關鍵詞查找與替換
from flashtext import KeywordProcessor
def build_actree(wordlist):
'''
AC自動機進行關鍵詞匹配
構造AC trie
'''
actree = KeywordProcessor()
for index, word in enumerate(wordlist):
actree.add_keyword(word) # 向trie樹中添加單詞
#self.actree = actree
return actree
def ac_detect(actree,text,span_info = True):
'''
AC自動機進行關鍵詞匹配
文本匹配
'''
region_wds = []
for w1 in actree.extract_keywords(text,span_info = span_info):
if len(w1) > 0:
region_wds.append(w1[0])
return region_wds
wordlist = ['健康','減肥']
text = '今天你減肥了嗎,今天你健康了嗎,減肥 = 健康!'
actree = build_actree(wordlist)
%time ac_detect(actree,text)
>>> CPU times: user 41 µs, sys: 0 ns, total: 41 µs
>>> Wall time: 47.2 µs
>>> ['減肥', '健康', '減肥', '健康']
2 顏文字檢測與識別
之前文本較多的情況,很多顏文字都是當作停用詞進行刪除;也有一些對錶情進行研究,但是顏文字比較麻煩的一點是,如果是特殊符號,☆,這類的只是一個字符,分詞的時候可以分開;
但是顏文字會佔用多個字符,分詞的時候,自己就會分得非常分散'↖', '(', '^', 'ω', '^', ')', '↗'
,這個問題就有點像新詞發現中出現得問題,如何分詞得到有效的實體,顏文字本身就是一種帶有情感色彩的實體。
所以比較理想的是不同的表情符號可以對應一些實體詞,比如顏文字網站中標記的一樣。
2.1 顏文字檢測
直接上代碼來說明使用方式:
# 初始化
json_data = {'w(゚Д゚)w': '啊啊', '(ノへ ̄、)': '抽泣', '( ̄_, ̄ )': '蔑視'}
ywz = yanwenzi(json_data)
# 檢測位置
text = 'w(゚Д゚)w^O^佳^O^w(゚Д゚)w'
ywz.detect(text,span_info=True)
# [('_啊啊_', 0, 7), ('_啊啊_', 14, 21)]
ywz.detect(text,span_info=False)
# ['_啊啊_', '_啊啊_']
ywz.ywz_replace(text)
# '_啊啊_^O^佳^O^_啊啊_'
該模塊初始化的時候,需要將一些{表情:屬性}
作爲輸入,筆者這邊自己整理了1800+,整理的一部分是抓取的,還有一部分是新顏文發現而補充進去的。初始化輸入之後,就會將這些表情包作爲關鍵詞進行匹配,同時這裏是不支持模糊匹配的,只能精準匹配,譬如^O^
如果這邊表情沒有計入,則不會被匹配到。
這裏可以看到,detect
將表情包w(゚Д゚)w
變成了中文屬性_啊啊_
,因爲_
方便分詞使用,其中參數span_info
代表是否返回角標,便於定位該表情包的文字。
另外,ywz_replace
是將文本中的表情包直接替換成中文字,並返回原文。
2.2 顏文字實體分詞
ywz.jieba_cut(text)
#['_啊啊_', '^', 'O', '^', '佳', '^', 'O', '^', '_啊啊_']
初始化之後,分詞其實就是jieba.cut
,這裏會分出_啊啊_
這樣的實體詞。
3 新顏文字發現
上面的匹配都是精準匹配,所以需要新顏文字發現,來不斷擴充顏文字詞典。
3.1 新顏文字發現
text = '璇哥!加油↖(^ω^)↗'
ywz.yanwenzi_find(text,min_n = 2,remove_spacing = True)
>>> ['↖(^ω^)↗']
這裏判定的邏輯還是比較簡單的,是通過正則的方式,最少3個(min_n )連續的特殊字符;
當然這裏要深挖也可以參考:如何精準地識別出文本中的顏文字?和glove embedding時候的清洗邏輯
input = input
.gsub(/https?:\/\/\S+\b|www\.(\w+\.)+\S*/,"<URL>")
.gsub("/"," / ") # Force splitting words appended with slashes (once we tokenized the URLs, of course)
.gsub(/@\w+/, "<USER>")
.gsub(/#{eyes}#{nose}[)d]+|[)d]+#{nose}#{eyes}/i, "<SMILE>")
.gsub(/#{eyes}#{nose}p+/i, "<LOLFACE>")
.gsub(/#{eyes}#{nose}\(+|\)+#{nose}#{eyes}/, "<SADFACE>")
.gsub(/#{eyes}#{nose}[\/|l*]/, "<NEUTRALFACE>")
.gsub(/<3/,"<HEART>")
.gsub(/[-+]?[.\d]*[\d]+[:,.\d]*/, "<NUMBER>")
.gsub(/#\S+/){ |hashtag| # Split hashtags on uppercase letters
# TODO: also split hashtags with lowercase letters (requires more work to detect splits...)
hashtag_body = hashtag[1..-1]
if hashtag_body.upcase == hashtag_body
result = "<HASHTAG> #{hashtag_body} <ALLCAPS>"
else
result = (["<HASHTAG>"] + hashtag_body.split(/(?=[A-Z])/)).join(" ")
end
result
當有了單個表情識別,如果在比較多的文本下,就可以根據頻次發現一些高頻出現的表情包了:
corpus = ['d(ŐдŐ๑)crush', '♪ ٩(。•ˇ‸ˇ•。)۶', '〜( ̄▽ ̄〜)', '木木╭(╯ε╰)╮', 'ToT(^(エ)^)', 'HLYS(ー`´ー)',
'O(∩_∩)O', '(^^)筆尖', '璇哥!加油↖(^ω^)↗', '蛻變(^_^) \ufeff']
ywz_new_list = ywz.yanwenzi_new_discovery(corpus,min_n = 2,remove_spacing = True,topn = 200)
ywz_new_list
>>> [('(ŐдŐ๑)', 1), ('♪٩(。•ˇ‸ˇ•。)۶', 1), ('〜( ̄▽ ̄〜)', 1), ('╭(╯ε╰)╮', 1),
('(^(エ)^)', 1), ('(ー`´ー)', 1), ('(∩_∩)', 1), ('(^^)', 1), ('↖(^ω^)↗', 1), ('(^_^)\ufeff', 1)]
其中,remove_spacing
是否移除空格;topn
一次性返回top多少的高頻表情包
如果有新顏文要新增,那麼需要新增到兩個模塊:分詞模塊 + 顏文識別模塊,
# 新顏文添加到分詞詞典
yanwenzi_dict_list = [ynl[0] for ynl in ywz_new_list]
ywz.add_words(yanwenzi_dict_list,freq = 100,tag = 'ywz')
# 新顏文添加到檢測詞典
ywz.actree_add_word(yanwenzi_dict_list,tag = '顏文字')
當然這裏遇到的問題,顏文字識別出來,是不帶屬性的({'↖(^ω^)↗':'_高興_'}
),所以要麼就是人工打標然後給入,當然也可以直接list方式,此時屬性就會都指定爲_顏文字_
3.2 顏文字屬性識別
上面3.1提及到一個問題是新顏文字識別出來之後,沒有附帶上屬性,就像實體詞沒有定義詞性一樣。
所以,這邊通過求相似的方式來找到最相似的表情,將最相似的表情屬性,繼承過來。這邊求相似的方式是使用rouge
這是文本摘要評價指標。
從rouge的評分來看,rouge-1太粗糙;rouge-2比較合適,
且幾個統計量中,f/p/r,f效果比較好,p/r可能會有比較多的選項,也就是差異性不明顯
參數:
- min_s = 0.35,閾值,一定要相似性大於纔會給出;如果是'rouge-1'比較合適的閾值在0.75
- score_type = 'rouge-2',rouge的得分類型,n-grams
- stat = 'f',採用的統計量
統計量:
text_a = '(^_^)'
new_yanwenzi_find(text_a,min_s = 0.35)
>>> [['(^&^)/', '噢耶',0.75]]
其中返回的是[最相似顏文字,中文屬性,f值]
最後, 該想法到實踐做的時間比較少,2天時間裏面可能會出現很多瑕疵,
歡迎覺得網絡表情需要深挖得網友,一起進步~