本文簡述實(shí)現(xiàn)自然語言處理中最基本需求的幾個笨辦法忆植,也是最初引發(fā)思考的要點(diǎn)放可。文章不涉及專業(yè)的先進(jìn)的系統(tǒng)化的自然語言處理方案,只是一篇散文朝刊。沒錯我就是標(biāo)題黨耀里。
事情緣起于,我在做用戶反饋的時候要對成百上千條用戶留言進(jìn)行貼標(biāo)簽的工作拾氓,也就是根據(jù)其反饋內(nèi)容和重要程度來分類冯挎,把要解決的緊急問題反饋給工程師,建設(shè)性意見提交給產(chǎn)品部門痪枫,好評作為打廣告的素材等等织堂。懶惰的本性告訴我人肉分類不是辦法,要訴諸更為高效的自動化手段奶陈。所以開始走進(jìn)自然語言處理的坑易阳。
我知道要做好這個,除了在語言學(xué)上了解句法詞法吃粒,想要獲得好的效果需要使用先進(jìn)復(fù)雜的機(jī)器學(xué)習(xí)算法潦俺,來使得變化復(fù)雜多樣的句子可以被程序“理解”。關(guān)于這些內(nèi)容徐勃,有一本專門從實(shí)踐角度介紹自然語言處理的書非常對口事示,叫做《Natural Language Processing with Python》,質(zhì)量上乘僻肖,值得深入學(xué)習(xí)肖爵。
但是考慮到我沒有大把時間和能力去深入理解復(fù)雜的算法和工具,我必須盡快在效率上給出看得見的改進(jìn)臀脏,所以我開始自己思考如何解決這樣的問題劝堪。
關(guān)鍵詞過濾
因?yàn)橛脩艚o出的反饋不是天馬行空的冀自,關(guān)于產(chǎn)品內(nèi)容有非常高的針對性,所以這套識別方法不需要具有普適性秒啦,只要能把這類特定內(nèi)容的文本做出劃分即可熬粗。
觀察發(fā)現(xiàn)在“手機(jī)空間不夠”這類反饋中大量出現(xiàn)關(guān)鍵詞 storage、full余境、room驻呐、space、memory 等芳来,這類詞具有兩種特點(diǎn):其一含末,具有高度的指向性,即在手機(jī) App 反饋這個大語境下绣张,這類詞所指的意思是精準(zhǔn)而沒有歧義的答渔;其二,在其他反饋中很少見到這類詞的使用侥涵。具有同樣特點(diǎn)的還有分類“三俗內(nèi)容”下的關(guān)鍵詞 porn沼撕、naked、bitch芜飘、xvideos务豺、fuck、inappropriate 等嗦明;在分類“費(fèi)電”
中有關(guān)鍵詞 power笼沥、battery 等;在分類“標(biāo)題黨和假新聞”中有關(guān)鍵詞 fake娶牌、inaccurate奔浅、irrelevant 等。這些適用范圍狹小而指代明確的關(guān)鍵詞诗良,在絕大多數(shù)情況下是足以作為定性依據(jù)的特征詞汹桦,所以使用關(guān)鍵詞查找來處理這些分類是比較靠譜的選擇。
所以這個方法的思路就是鉴裹,便利反饋中的所有詞舞骆,當(dāng)與預(yù)先設(shè)定的關(guān)鍵詞列表相匹配時,增加這一分類的得分径荔,最后比較不同分類得分的大小督禽,取最高值(也就是匹配關(guān)鍵詞數(shù)量最多)的分類作為整個反饋的分類。
如下定義每個分類的關(guān)鍵詞总处,以及所有采用關(guān)鍵詞過濾方法的分類的集合狈惫。
# Python Code
# Filename: Config.py
Map = {
'三俗內(nèi)容': 'KW_SanSuNeiRong',
'標(biāo)題黨和假新聞': 'KW_BiaoTiDangHeJiaXinWen',
'手機(jī)空間不夠': 'KW_ShouJiKongJianBuGou',
'費(fèi)電': 'KW_FeiDian'
}
# 三俗內(nèi)容
KW_SanSuNeiRong = [
'naked',
'porn',
'inappropriate',
'bitch',
'whore',
'unduly',
'xvideo',
'xvideos'
]
# 標(biāo)題黨和假新聞
KW_BiaoTiDangHeJiaXinWen = [
'fake',
'inaccurate',
'irrelevant',
'accurate'
]
# 手機(jī)空間不夠
KW_ShouJiKongJianBuGou = [
'storage',
'space',
'memory',
'room',
'full'
]
# 費(fèi)電
KW_FeiDian = [
'power',
'consumptiion',
'battery'
]
分析關(guān)鍵詞時,按照分類和關(guān)鍵詞得分建立字典鹦马,最后按照得分最大的分類作為結(jié)果虱岂,當(dāng)有多個得分相同的分類時玖院,由于循環(huán)結(jié)構(gòu)的特性菠红,將按照分類在 Map
中被定義的順序進(jìn)行取舍第岖,所以 Map
也暗含了一個優(yōu)先級的意思。
# Python Code
# Filename: Program.py
import Config
# words is a list of single word of lower case, preprocessed when reading files.
# example: ['my', 'phone', 'is', 'low', 'on', 'memory']
def analyzeKeyword(words):
chart = {}
for genre in Map:
score = analyzeFrequency(words, Map[genre])
chart[genre] = score
return getHighestScoreGenre(chart)
def analyzeFrequency(words, map)
score = 0
keywords = getattr(Config, map)
for kw in keywords:
for word in words:
if kw == word:
score += 1
return score
def getHighestScoreGenre(dict)
max = 0
result = "Unknown"
genres = [g for g in dict]
genres.reverse()
for genre in genres:
if dict[genre] >= max:
max = dict[genre]
result = genre
return result
res = analyzeKeyword(['my', 'phone', 'is', 'low', 'on', 'memory'])
print(res)
# Output: 手機(jī)空間不夠
上例中试溯,因?yàn)殛P(guān)鍵詞 memory 的匹配蔑滓,分類 手機(jī)空間不夠 的得分為 1,其余分類都是 0遇绞,故最終分類被定位 手機(jī)空間不夠键袱。
幾個小技巧:
- 將分類和關(guān)鍵詞列表單獨(dú)寫在文件 Config.py 中,方便之后將新的關(guān)鍵詞添加至分析列表中摹闽。
- 使用 getattr 函數(shù)蹄咖,利用反射的方法動態(tài)獲取 Config 中的列表,增加擴(kuò)展性付鹿。
-
genres.reverse()
是為了反向遍歷Map
字典澜汤,如此當(dāng)“新值大于等于舊值”就更新最大值時,實(shí)際上實(shí)現(xiàn)了“排在上面的分類比排在下面的分類具有更高優(yōu)先級”的邏輯舵匾,使得當(dāng)兩個多個分類得分相同時俊抵,總按照最上面的分類定性。
簡單的情感分析
關(guān)鍵詞過濾針對使用寬泛的形容詞束手無策坐梯,比如認(rèn)為 good 代表好評徽诲,那么前置一個否定詞 not 的話,整句意思完全相反吵血,同理 not bad 表示還不錯谎替。所以我進(jìn)一步研究了簡單的情感判斷。
思路是:前置否定詞(可有可無) + 后置形容詞 = 一個組蹋辅,否定詞負(fù)負(fù)得正钱贯,結(jié)合最后的形容詞是褒義還是貶義來定性這個組到底是好評還是差評。一個用戶反饋中以句子為單位晕翠,每個句子統(tǒng)計好評組和差評組的個數(shù)喷舀,數(shù)量多的作為句子的特性,所有句子特性采用同樣的標(biāo)準(zhǔn)組合起來淋肾,作為整個反饋的情感特性硫麻。
首先還是建立詞庫。
# Python Code
# Filename: Config.py
ST_Negative = [
"not",
"donot",
"don't",
"dont",
"doesnot",
"doesn't",
"doesnt",
"isnot",
"isn't",
"isnt",
"ain't",
"cannot",
"can't",
"cant",
"couldnot",
"couldn't",
"couldnt",
"never",
"didnot",
"didn't",
"didnt"
]
ST_Good = [
'good',
'great',
'awesome',
'amazing',
'wonderful',
'fabulous',
'cool',
'like',
'love',
'interesting',
'interested',
'use'
]
ST_Bad = [
'bad',
'useless',
'boring',
'junk',
'rubbish',
'nonsense',
'dumb',
'hate',
'stupid',
'suck',
'sucks',
'fuck',
'shitty'
]
接下來是主程序樊卓。
# Python Code
# Filename: Program.py
import Config
# sentences is a list of sentences of lower-case character preprocessed when reading files.
# example: ['it's not bad', 'and really good and shitty']
def analyzeSentence(sentences)
score = 0
for sentence in sentences:
tmp = analyzeSentenceScore(sentence)
score += tmp
return 'Good' if score > 0 else ('Bad' if score < 0 else 'Unknown')
def analyzeSentenceScore(sentence):
words = [w for w in text.split(' ') if w != ' ']
isnegative = 0
good_score = 0
bad_score = 0
for word in words:
# Check negative.
negative = 1 if word in Config.ST_Negative else 0
isnegative = 1 if negative == 1 else isnegative
# Check sentiment.
good = 1 if word in Config.ST_Good else 0
bad = 1 if word in Config.ST_Bad else 0
sentiment = 1 if good > bad else (0 if good < bad else -1)
# Score.
if sentiment != -1:
good_score += 1 if ((not isnegative) and sentiment) or (isnegative and (not sentiment)) else 0
bad_score += 0 if ((not isnegative) and sentiment) or (isnegative and (not sentiment)) else 1
isnegative = 0
# Return logic.
result = 1 if good_score > bad_score else (-1 if bad_score > good_score else 0)
return result
res = analyzeSentence(['it's not bad', 'and really good and shitty'])
print(res)
# Output: Good
上例中拿愧,第一句定性為 Good,得分 1碌尔,第二句存在一個 Good 和一個 Bad浇辜,故無法判斷券敌,得分 0,總分 1柳洋,為正待诅,故整個反饋判定為 Good。
幾個小技巧:
- Python 的三元運(yùn)算符
<TrueValue> if <Expression> else <FalseValue>
非常好用熊镣,能極大地節(jié)約寫作空間卑雁,在合適的嵌套下可以將比較復(fù)雜的思路簡單地表示出來。 - 每一個詞只可能是 好評詞绪囱、差評詞 和 無關(guān)詞 中的一種测蹲,前兩者作為 形容詞,所以當(dāng)
sentiment == -1
時鬼吵,實(shí)際上表示這個詞是無關(guān)詞扣甲,那么需要繼續(xù)向后尋找形容詞,所以“一組”的概念沒有結(jié)束齿椅。 - 當(dāng)出現(xiàn)一個 形容詞 的時候琉挖,意味著“一組”結(jié)束了,所以重置標(biāo)記變量
isnegative = 0
媒咳,表示“默認(rèn)情況下粹排,在檢測到否定詞之前,都認(rèn)為是沒有否定詞”涩澡。 - 關(guān)鍵的邏輯判斷顽耳,可以翻譯如下:對于當(dāng)前一個組來說,整個句子的好評得分 += 1 如果(沒有否定詞妙同,并且是好評射富;或者有否定詞,并且是差評)粥帚,否則 0胰耗。差評得分同理。說白了就是芒涡,“very good” 加一分柴灯,“not bad” 加一分;“very bad” 減一分费尽,“not good” 減一分赠群。
- 最后比較整個句子多個組的好評得分和差評得分,來確定整句話的特性旱幼。
整合與總結(jié)
因?yàn)榍楦信袛嗟倪壿嫺鼜?fù)雜查描,且穩(wěn)健性更低,所以將二者結(jié)合使用。當(dāng)關(guān)鍵詞出現(xiàn)無法判斷的情況時冬三,使用情感判斷匀油,給出一個大概的方向。當(dāng)情感判斷也無法給出結(jié)論時勾笆,判定為最終無法判斷敌蚜。
實(shí)測表明,這套方法在訓(xùn)練集上有 55% 的準(zhǔn)確度匠襟,在兩個全新的測試集上分別有 37% 和 43% 左右的準(zhǔn)確度钝侠。說實(shí)話我已經(jīng)很欣慰了,如此簡陋的方法竟然可以省掉平均 40%+ 的人力酸舍,π_π……
扣題,所謂“拙劣”的方法里初,是因?yàn)檫@里除了思想還有點(diǎn)東西以外啃勉,在純統(tǒng)計分析技術(shù)上幾乎沒有任何深度可言,代碼寫的也是大白話双妨。核心功能十分有限淮阐,解決不了單詞拼寫錯誤,一詞多義和復(fù)雜句前后邏輯識別等更深奧的問題刁品。在最終的實(shí)現(xiàn)里泣特,定義了 Review
和 Sentence
兩個類,來實(shí)現(xiàn)更簡明清晰的封裝挑随,在程序結(jié)構(gòu)上看著還不錯状您。另外,上一段借用了機(jī)器學(xué)習(xí)中的概念兜挨,也是強(qiáng)行打腫臉充胖子了膏孟。
自然語言處理是一個長久又新潮的問題,專業(yè)的處理方法可以使用 NLTK 包等拌汇,用更高級的機(jī)器學(xué)習(xí)和文本挖掘的方法去處理更復(fù)雜的句子柒桑。我呢,學(xué)不來那些高深的東西噪舀,只能在能力所及之內(nèi)把有限的思考投入到無限的嘗試中魁淳,解決一點(diǎn)算一點(diǎn)。但是在這個過程中我體會得到這些高級方法誕生伊始時的一些問題与倡,以及想要解決它們所引發(fā)的一些思考界逛。比如為什么會使用向量的運(yùn)算,實(shí)際上就是對具有多個分類得分的句子之間的相似性蒸走,關(guān)聯(lián)性進(jìn)行計算仇奶,因?yàn)槊總€分類方向都是一個得分,所以可以看做一個向量;而語義理解上该溯,根據(jù)詞法語法和慣用句式來分析一句話岛抄,采用不同的提取方法獲得特征值,也是常見的手段狈茉。
之前文章里提到的大神同學(xué)夫椭,最近在深入學(xué)習(xí)機(jī)器學(xué)習(xí)和文本挖掘的理論,看他寫的東西檔次就是不一樣氯庆,起點(diǎn)比我高多了蹭秋,相比之下我真是土鱉一只……接下來有時間的話我會繼續(xù)看前文提到的那本書(畢竟現(xiàn)在才看到第二章),然后搞一點(diǎn)高端的方法吧堤撵。
哈哈仁讨,哈哈哈哈哈……自娛自樂中~~~