機器學習之垃圾信息過濾
在網路安全中,對用戶發布的垃圾內容,廣告進行過濾,或者對文本類別進行分類都是非常重要的一環。cherry分類器使用了貝葉斯模型演算法,通過簡單的優化,使用了1000個訓練數據得到97.5%的準確率,並且提供了混淆矩陣和ROC曲線便於分析。雖然現在主流的框架都帶有貝葉斯模型演算法,大多數開發者都是直接調用api。但是在實際業務中,面對不同的數據集,必須了解演算法的原理,實現以及懂得對結果進行分析,才能達到高準確率。
cherry分類器
cherry分類器默認支持中英文分類,自帶的數據緩存中,中文訓練數據包含正常,政治敏感,賭博,色情4個類別,英文訓練數據包含正常郵件,垃圾郵件兩個類別 (訓練數據可以通過Google drive下載)。調用非常容易,使用pip安裝後,輸入句子:
警方召開了全省集中打擊賭博違法犯罪活動專項行動電視電話會議。會議的重點是「查處」六合彩、賭球賭馬等賭博活動。
import cherryresult = cherry.classify(『警方召開了全省集中打擊賭博違法犯罪活動專項行動電 電話會議。會議的重點是「查處」六合彩、賭球賭馬等賭博活動。』) Building prefix dict from the default dictionary … Loading model from cache /var/folders/md/0251yy51045d6nknpkbn6dc80000gn/T/jieba.cache Loading model cost 0.894 seconds. Prefix dict has been built succesfully.
分類器判斷輸入句子有99.7%的概率是正常句子,0.2%是政治敏感,剩餘0.1%是其他兩個類別
>>> result.percentage
[("normal.dat", 0.997), ("politics.dat", 0.002), ("gamble.dat", 0.0), ("sex.dat", 0.0)]
其中對分類器判斷影響最大的詞語分別是賭博,活動,會議,違法犯罪,警方,打擊
>>> result.word_list
[("賭博", 8.5881312727226), ("活動", 6.401543938544878), ("會議", 6.091963362021649), ("違法犯罪", 4.234845736802978), ("警方", 3.536827626008435), ("打擊", 3.2491455535566542), ("行動", 2.8561029654470476), ("查處", 2.3860993362013083), ("重點", 2.126816738271229), ("召開", 1.8628511924367634), ("專項", 1.1697040118768172), ("電視電話會議", 1.1697040118768172), ("全省", 0.47655683131687354), ("集中", -0.6220554573512382), ("六合彩", -2.29603189092291)]
關鍵字過濾
要理解分類器的原理,可以先從最簡單的分類關鍵詞演算法開始,輸入句子:
獎金將在您完成首存後即可存入您的賬戶。真人荷官,六合彩,賭球歡迎來到全新番攤遊戲!
使用關鍵字演算法,我們可以將真人荷官,六合彩這兩個詞語加入賭博類別的黑名單,每個類別都維持對應的黑名單表。當之後需要分類的時候,先判斷關鍵字有沒有出現在輸入句子中,如果有,則判斷為對應的類別。這個方法實現簡單,但是缺點也很明顯,誤判率非常高,例如遇到輸入句子:
警方召開了全省集中打擊賭博違法犯罪活動專項行動電視電話會議。會議的重點是「查處」六合彩、賭球賭馬等賭博活動。
這是一個正常的句子,但是由於包含六合彩,賭球這兩個黑名單詞語,關鍵字演算法會誤判其為賭博類別,同時,如果一個句子同時包含多個不同類別的黑名單詞語,例如賭博,色情的話,關鍵字演算法也無法判斷正確。
貝葉斯模型
其實關鍵字演算法已經接近貝葉斯模型的原理了,我們再仔細分析下關鍵字演算法。關鍵字演算法的問題在於只對輸入句子中的部分詞語進行分析,而沒有對輸入句子的整體進行分析。而貝葉斯模型會對輸入句子的所有有效部分進行分析,通過訓練數據計算出每個詞語在不同類別下的概率,然後綜合得出最有可能的結果。可以說,貝葉斯模型是關鍵字過濾加上統計學的升級版。
當貝葉斯模型去判斷輸入句子:
警方召開了全省集中打擊賭博違法犯罪活動專項行動電視電話會議。會議的重點是「查處」六合彩、賭球賭馬等賭博活動。
它會綜合分析句子中的每個詞語:
警方,召開,全省,集中打擊,... 六合彩,賭球,賭馬,...
輸入句子雖然包含六合彩,賭球這些賭博常出現的詞語,但是警方,召開,集中打擊這幾個詞代表這個句子極有可能是正常的句子。
數學推導
貝葉斯模型的數學推導非常簡單,強烈建議大家靜下心自己推導。
這裡為了簡單起見,我們只考慮句子是正常或者賭博兩種可能,我們先複習一下概率論的基礎表達:
P(A) -> A事件發生的概率,例如明天天晴的概率
P(A|B) -> 條件概率,B事件發生的前提下A事件發生的概率,例如明天天晴而我又沒帶傘的概率
P(輸入句子) -> 這個句子在訓練數據中出現的概率
P(賭博) -> 賭博類別的句子在訓練數據中出現的概率
P(賭博|輸入句子) -> 輸入句子是賭博類別的概率(也是我們最終要求的值)
P(賭博|輸入句子) + P(正常|輸入句子) = 100%
上圖,中間重疊的部分是賭博和句子同時發生的概率P(賭博,輸入句子),可以看出:
P(賭博|輸入句子) = P(賭博,輸入句子) / P(輸入句子) (1)
同理:
P(輸入句子|賭博) = P(賭博,輸入句子) / P(賭博) (2)
把(2)代入(1)得到
P(賭博|輸入句子) = P(輸入句子|賭博) * P(賭博) / P(輸入句子) (3)
登登登燈,(3)就是貝葉斯模型定理。沒看懂沒關係,靜下心再看一遍。要得到最終輸入句子是賭博類別的概率P(賭博|輸入句子),需要知道右邊3個量的值:
P(賭博)
指訓練數據中,賭博類別的句子占訓練數據的百分比。
P(輸入句子)
指這個輸入句子出現在訓練數據中的概率。我們最終目的是判斷輸入句子是哪個類別的概率比較高,也就是比較P(賭博|輸入句子)與P(正常|輸入句子),由貝葉斯定理:
P(賭博|輸入句子) = P(輸入句子|賭博) * P(賭博) / P(輸入句子) (4)
P(正常|輸入句子) = P(輸入句子|正常) * P(正常) / P(輸入句子) (5)
由於(4),(5)都要除於相同的P(輸入句子),所以(4),(5)右邊可以同時乘以P(句子),只比較等號右邊前兩個值的乘積的大小。
P(賭博|輸入句子) = P(輸入句子|賭博) P(賭博)P(正常|輸入句子) = P(輸入句子|正常) P(正常)
P(句子|賭博)
最關鍵的就是求P(輸入句子|賭博),直接求輸入句子在賭博類別句子中出現的概率非常困難,因為訓練數據不可能包含所有句子,很可能並沒有輸入句子。什麼意思呢?因為同一個句子,把詞語進行不同的排列組合都能成立,例如:
獎金將在您完成首存後即可存入您的賬戶。真人荷官,六合彩,賭球歡迎來到全新番攤遊戲!
可以變成
獎金將在您完成首存後即可存入您的賬戶。六合彩,賭球,真人荷官歡迎來到全新番攤遊戲!
或者
歡迎來到全新番攤遊戲,獎金將在您完成首存後即可存入您的賬戶。六合彩,真人荷官,賭球!
稍微變換詞語的位置就是一個新的句子了,訓練數據不可能把所有排列組合的句子都加進去,因為實在太多了。所以當我們遇到一個輸入句子,很可能它在訓練數據中沒有出現,那麼P(輸入句子|類別)對應的概率都為零,這顯然不是真實的結果。也會導致我們的分類器出錯,這個時候該怎麼辦呢?剛剛在貝葉斯模型中我們提到,它會將一個句子分成不同的詞語來綜合分析,那我們是不是也可以把句子當成詞語的集合呢?
警方召開了全省集中打擊賭博違法犯罪活動專項行動電視電話會議。會議的重點是「查處」六合彩、賭球賭馬等賭博活動。
警方召開了全省…賭馬等賭博活動 = 警方 + 召開 + 全省…+賭博活動
即:
P(輸入句子|賭博) = (P(詞語1) P(詞語2|詞語1) P(詞語3|詞語2))|賭博) ≈ P(詞語1)|P(賭博) P(詞語2)|P(賭博) P(詞語3)|P(賭博)
P(警方召開了全省…賭馬等賭博活動。|賭博) = P(警方|賭博) P(召開|賭博) P(全省|賭博) … P(賭馬|賭博) P(賭博活動|賭博)
我們把P(輸入句子|賭博)分解成所有P(詞語|賭博)概率的乘積,然後通過訓練數據,計算每個詞語在不同類別出現的概率。最終獲取的是輸入句子有效詞語在不同類別中的概率。
| 詞語 | 正常 | 賭博 | | ————-|:———-:| ———-:| | 警方 | 0.8 | 0.2 | | 召開 | 0.7 | 0.3 | | 全省 | 0.7 | 0.3 | | 賭馬 | 0.4 | 0.6 | | 賭球 | 0.3 | 0.7 | | 賭博活動 | 0.4 | 0.6 | | ———- | ———- | ———- | | 綜合概率 | 0.8 | 0.2 |
在上面的例子中,雖然賭馬,賭球,賭博活動這幾個詞是賭博類別的概率很高,但是綜合所有詞語,分類器判斷輸入句子有80%的概率是正常句子。簡單來說,要判斷句子是某個類別的概率,只需要計算該句子有效部分的詞語的在該類別概率的乘積。
貝葉斯模型實現
要計算每個詞語在不同類別下出現的概率,有以下幾個步驟:
選擇訓練數據,標記類別把所有訓練數據進行分詞,並且組成成一個包含所有詞語的詞袋集合把每個訓練數據轉換成詞袋集合長度的向量利用每個類別的下訓練數據,計算詞袋集合中每個詞語的概率
選擇訓練數據
訓練數據的選擇是非常關鍵的一步,我們可以從網路上搜索符合對應類別的句子,使每個類別的數據各佔一半。不過當你理解了貝葉斯模型的原理之後,你會發現一個難題問題,就是如何保持數據的獨立分布,例如你選擇的訓練數據如下:
賭博類別
根據您所選擇的上述六合彩遊戲,您必須在娛樂場完成總金額(存款+首存獎金)16倍或15倍流水之後,方可申請提款。
獎金將在您完成首存後即可存入您的賬戶。真人荷官 六合彩 歡迎來到全新番攤遊戲!
正常類別
Linux是一套免費使用和自由傳播的類Unix操作系統,是一個基於POSIX和UNIX的多用戶、多任務、支持多線程和多CPU的操作系統。
理查德·菲利普斯·費曼,美國理論物理學家,量子電動力學創始人之一,納米技術之父。
我們可以注意到六合彩,遊戲這兩個詞語,只在賭博類別的訓練數據出現。這兩個詞語對句子是否是賭博類別會有很大的影響性,六合彩對賭博類別確實是重要的判別詞,但是遊戲這個詞語本身和賭博沒有直接的關係,卻被錯誤劃分為賭博類別相關的詞語,當之後分類器遇到
我們提供最新最全大型遊戲下載,迷你遊戲下載,並提供大量遊戲攻略
會因為裡面的遊戲,將它判斷為賭博類別,
>>> result = cherry.classify("我們提供最新最全大型遊戲下載,迷你遊戲下載,並提供大量遊戲攻略")
>>> result.percentage
[("gamble.dat", 0.793), ("normal.dat", 0.207)]
>>> result.word_list
[("遊戲", 1.9388011143762069)]
所以,當我們要做一個賭博/正常的分類器,我們需要在正常類別的訓練數據添加:
中國遊戲第一門戶站,全年365天保持不間斷更新,您可以在這裡獲得專業的遊戲新聞資訊,完善的遊戲攻略專區
這樣的正常而且帶有遊戲關鍵字的句子。同時當訓練數據過少,輸入句子包含了訓練數據中並沒有c出現過的詞語,該詞語也會被分類器所忽略。cherry分類器)可以通過啟用debug模式得到被錯誤劃分的數據以及其權重最高的詞語,你可以根據輸出的詞語來調整訓練數據。我們之後可以通過Adaboost演算法動態調整每個詞語的權重,這個功能我們會在下一個版本推出。 另外一方面,現實生活中,正常的句子比賭博類別的句子出現的概率要多得多,這點我們也可以從訓練數據的比例上面體現,適當增加正常類別句子的數量,也可以賦予正常類別句子高權重,不過要小心 Accuracy_paradox的問題。我們在測試的時候,可以根據混淆矩陣以及ROC曲線來分析分類器的效果,再進行數據調整。
詞袋集合
為簡單起見,本篇文章只選取4個句子作為訓練數據:
賭博類別:
根據您所選擇的上述禮遇,您必須在娛樂場完成總金額(存款+首存獎金)16倍或15倍流水之後,方可申請提款。
獎金將在您完成首存後即可存入您的賬戶。真人荷官 體育博彩 歡迎來到全新番攤遊戲!
正常類別:
理查德·菲利普斯·費曼,美國理論物理學家,量子電動力學創始人之一,納米技術之父。
在公安機關持續不斷的打擊下,六合彩、私彩賭博活動由最初的公開、半公開狀態轉入地下。
要計算每個詞語在不同類別下的概率,首先需要一個詞袋集合,集合包含了訓練數據中所有非重複詞語(_vocab_list),參考函數_get_vocab_list:
def _get_vocab_list(self):
"""
Get a list contain all unique non stop words belongs to train_data
Set up:
self.vocab_list:
[
"What", "lovely", "day",
"like", "gamble", "love", "dog", "sunkist"
]
"""
vocab_set = set()
all_train_data = "".join([v for _, v in self._train_data])
token = Token(text=all_train_data, lan=self.lan, split=self.split)
vocab_set = vocab_set | set(token.tokenizer)
self._vocab_list = list(vocab_set)
默認使用結巴分詞進行中文分詞(你可以定製分詞函數),例如第一個數據:
根據您所選擇的上述禮遇,您必須在娛樂場完成總金額(存款+首存獎金)16倍或15倍流水之後,方可申請提款。
分詞後會得到:
["根據", "您", "所", "選擇", "的", "上述", "禮遇", ",", "您", "必須", "在", "娛樂場", "完成", "總金額", "(", "存款", "+", "首存", "獎金", ")", "16", "倍", "或", "15", "倍", "流水", "之後", ",", "方可", "申請", "提款", "。"]
我們去掉包含在stop_word.dat中的詞語,stop_word.dat包含了漢語中的常見的轉折詞:
如果,但是,並且,不只…
這些詞語對於我們分類器沒有用處,因為任何類別都會出現這些詞語。接下來再去掉長度等於1的字,第一個訓練數據剩下:
["選擇", "上述", "禮遇", "娛樂場", "總金額", "存款", "首存", "獎金", "16", "15", "流水", "申請", "提款"]
遍歷4個句子最終得到長度為49的詞袋集合(vocab_list):(這裡使用的集合是無序的,所以你得到的結果順序可能不同)
["提款", "存入", "遊戲", "最初", "六合彩", "娛樂場", "費曼", "獎金", "賬戶", "菲利普斯", "量子", "電動力學", "總金額", "上述", "活動", "狀態", "物理學家", "公安機關", "荷官", "即可", "理論", "申請", "半公開", "選擇", "15", "打擊", "全新", "來到", "公開", "方可", "博彩", "完成", "理查德", "納米技術", "不斷", "存款", "之一", "創始人", "真人", "私彩", "持續", "根據", "必須", "16", "賭博", "歡迎", "體育", "轉入地下", "首存", "流水", "美國", "禮遇"]
得到詞袋之後,再次使用訓練數據,並把每個訓練數據都轉變成一個長度為49的一維向量
def _get_vocab_matrix(self):
"""
Convert strings to vector depends on vocal_list
"""
array_list = []
for k, data in self._train_data:
return_vec = np.zeros(len(self._vocab_list))
token = Token(text=data, lan=self.lan, split=self.split)
for i in token.tokenizer:
if i in self._vocab_list:
return_vec[self._vocab_list.index(i)] += 1
array_list.append(return_vec)
self._matrix_lst = array_list
根據您所選擇的上述禮遇,您必須在娛樂場完成總金額(存款+首存獎金)16倍或15倍流水之後,方可申請提款。
對應轉變成:
# 長度為49的一維向量
[1, 0, 0, 0, 1, 0, ..., 1, 0, 1]
其中的1分別對應著數據分詞後的詞語在詞袋中出現的次數。接下來將所有訓練數據的一維向量組合成列表_matrix_list
[
[1, 0, 0, 0, 1, 0, ..., 1, 0, 1]
[0, 1, 1, 0, 0, 0, ..., 0, 0, 0]
...
]
要計算每個詞語在不同類別下的概率,只需要把詞語出現的次數除以該類別的所有詞語的總數, cherry分類器出於效率的考慮使用了numpy的矩陣運算。
def _training(self):
"""
Native bayes training
"""
self._ps_vector = []
# 防止有詞語在其他類別訓練數據中沒有出現過,最後的P(句子|類別)乘積就會為零,所以給每個詞語一個初始的非常小的出現概率,設置vector默認值為1,cal對應為2
# vector: 默認值為1的一維數組
# cal: 默認的分母,計算該類別所有有效詞語的總數
# num: 計算P(賭博), P(句子)
vector_list = [{
"vector": np.ones(len(self._matrix_lst[0])),
"cal": 2.0, "num": 0.0} for i in range(len(self.CLASSIFY))]
for k, v in enumerate(self.train_data):
vector_list[v[0]]["num"] += 1
# vector加上對應句子的詞向量,最後把整個向量除於cal,就得到每個詞語在該類別的概率。
# [1, 0, 0, 0, 1, 0, ..., 1, 0, 1] (根據您所選擇的...)
# [0, 1, 1, 0, 0, 0, ..., 0, 0, 0] (獎金將在您完成...)
# +
# [1, 1, 1, 1, 1, 1, ..., 1, 1, 1]
vector_list[v[0]]["vector"] += self._matrix_lst[k]
vector_list[v[0]]["cal"] += sum(self._matrix_lst[k])
for i in range(len(self.CLASSIFY)):
# 每個詞語的概率為[2, 2, 2, 1, 2, 1, ..., 2, 1, 2]/cal
self._ps_vector.append((
np.log(vector_list[i]["vector"]/vector_list[i]["cal"]),
np.log(vector_list[i]["num"]/len(self.train_data))))
遍歷完所有訓練數據之後,會得到兩個類別對應的每個詞語的概率向量,(為了防止python的小數相乘溢出,這裡的概率都是取np.log()對數之後得到的值):
# 賭博
([-2.80336038, -2.80336038, -2.80336038, -3.49650756, -3.49650756,
-2.80336038, -3.49650756, -2.39789527, -2.80336038, -3.49650756,
-3.49650756, -3.49650756, -2.80336038, -2.80336038, -3.49650756,
-3.49650756, -3.49650756, -3.49650756, -2.80336038, -2.80336038,
-3.49650756, -2.80336038, -3.49650756, -2.80336038, -2.80336038,
-3.49650756, -2.80336038, -2.80336038, -3.49650756, -2.80336038,
-2.80336038, -2.39789527, -3.49650756, -3.49650756, -3.49650756,
-2.80336038, -3.49650756, -3.49650756, -2.80336038, -3.49650756,
-3.49650756, -2.80336038, -2.80336038, -2.80336038, -3.49650756,
-2.80336038, -2.80336038, -3.49650756, -2.39789527, -2.80336038,
-3.49650756, -2.80336038]), 0.5)
# 正常
([-3.25809654, -3.25809654, -3.25809654, -2.56494936, -2.56494936,
-3.25809654, -2.56494936, -3.25809654, -3.25809654, -2.56494936,
-2.56494936, -2.56494936, -3.25809654, -3.25809654, -2.56494936,
-2.56494936, -2.56494936, -2.56494936, -3.25809654, -3.25809654,
-2.56494936, -3.25809654, -2.56494936, -3.25809654, -3.25809654,
-2.56494936, -3.25809654, -3.25809654, -2.56494936, -3.25809654,
-3.25809654, -3.25809654, -2.56494936, -2.56494936, -2.56494936,
-3.25809654, -2.56494936, -2.56494936, -3.25809654, -2.56494936,
-2.56494936, -3.25809654, -3.25809654, -3.25809654, -2.56494936,
-3.25809654, -3.25809654, -2.56494936, -3.25809654, -3.25809654,
-2.56494936, -3.25809654]), 0.5)
# 詞袋集合
["提款", "存入", "遊戲", "最初", "六合彩", "娛樂場", "費曼", "獎金", "賬戶", "菲利普斯", "量子", "電動力學", "總金額", "上述", "活動", "狀態", "物理學家", "公安機關", "荷官", "即可", "理論", "申請", "半公開", "選擇", "15", "打擊", "全新", "來到", "公開", "方可", "博彩", "完成", "理查德", "納米技術", "不斷", "存款", "之一", "創始人", "真人", "私彩", "持續", "根據", "必須", "16", "賭博", "歡迎", "體育", "轉入地下", "首存", "流水", "美國", "禮遇"]
結合向量和詞袋集合來看,提款,存入,遊戲這幾個詞是賭博的概率要大於正常的概率
#賭博 提款,存入,遊戲
[-2.80336038, -2.80336038, -2.80336038]
#正常 提款,存入,遊戲
[-3.25809654, -3.25809654, -3.25809654]
符合我們的常識,接下來就可以進行輸入句子的分類了。
判斷類別
訓練完數據,得到詞語對應概率之後,判斷類別就非常簡單,只需要把輸入句子進行相同的分詞,然後計算對應的詞語對應的概率的乘積即可,得到乘積最大的就是最有可能的類別。輸入句子:
歡迎參加澳門在線娛樂城,這裡有體育,百家樂,六合彩各類精彩遊戲。
先根據原先的詞袋集合,先轉變為一維向量
# 詞袋集合
["提款", "存入", "遊戲", "最初", "六合彩", "娛樂場", "費曼", "獎金", "賬戶", "菲利普斯", "量子", "電動力學", "總金額", "上述", "活動", "狀態", "物理學家", "公安機關", "荷官", "即可", "理論", "申請", "半公開", "選擇", "15", "打擊", "全新", "來到", "公開", "方可", "博彩", "完成", "理查德", "納米技術", "不斷", "存款", "之一", "創始人", "真人", "私彩", "持續", "根據", "必須", "16", "賭博", "歡迎", "體育", "轉入地下", "首存", "流水", "美國", "禮遇"]
# 長度為49的一維向量
[0, 0, 1, 0, 1, ...]
然後與分別與兩個概率向量相乘,求和,並加上對應的類別佔比,對應的代碼:
def _bayes_classify(self):
"""
Calculate the probability of different category
"""
possibility_vector = []
log_list = []
# self._ps_vector: ([-3.44, -3.56, -2.90], 0.4)
for i in self._ps_vector:
# 計算每個詞語對應概率的乘積
final_vector = i[0] * self.word_vec
# 獲取對分類器影響度最大的詞語
word_index = np.nonzero(final_vector)
non_zero_word = np.array(self._vocab_list)[word_index]
# non_zero_vector: [-7.3, -8]
non_zero_vector = final_vector[word_index]
possibility_vector.append(non_zero_vector)
log_list.append(sum(final_vector) + i[1])
possibility_array = np.array(possibility_vector)
max_val = max(log_list)
for i, j in enumerate(log_list):
# 輸出最大概率的類別
if j == max_val:
max_array = possibility_array[i, :]
left_array = np.delete(possibility_array, i, 0)
sub_array = np.zeros(max_array.shape)
# 通過曼哈頓舉例,計算影響度最大的詞語
for k in left_array:
sub_array += max_array - k
return self._update_category(log_list),
sorted(
list(zip(non_zero_word, sub_array)),
key=lambda x: x[1], reverse=True)
通過計算:
P(賭博|句子) = sum([0, 0, 1, 0, 1, …] * [-2.80336038, -2.80336038, -2.80336038, …]) + P(賭博) = 0.85
P(正常|句子) = sum([0, 0, 1, 0, 1, …] * [-3.25809654, -3.25809654, -3.25809654, …])+ P(正常) = 0.15
最終得到P(賭博|句子) > P(正常|句子),所以分類器判斷這個句子是賭博類別。
>>> result = cherry.classify("歡迎參加澳門在線娛樂城,這裡有體育,百家樂,六合彩各類精彩遊戲。")
>>> result.percentage
[("gamble.dat", 0.85), ("normal.dat", 0.15)]
>>> result.word_list
[("六合彩", 0.96940055718810347), ("遊戲", 0.96940055718810347), ("歡迎", 0.56393544907993931)]
測試
統計分析
測試方法有留出法(hold-out),k折交叉驗證法(cross validation),自助法(bootstrapping),這裡我們使用留出法,測試腳本默認每次從所有數據中選出60個句子當成測試數據,剩下的當成訓練數據。重複進行測試10次。運行測試腳本
>>> python runanalysis.py
This may takes some time, Go get a coffee :D.
Building prefix dict from the default dictionary ...
Loading model from cache /var/folders/md/0251yy51045d6nknpkbn6dc80000gn/T/jieba.cache
Loading model cost 0.914 seconds.
Prefix dict has been built succesfully.
+Cherry---------------+------------+------------+
| Confusion matrix | gamble.dat | normal.dat |
+---------------------+------------+------------+
| (Real)gamble.dat | 249 | 0 |
| (Real)normal.dat | 13 | 338 |
| Error rate is 2.17% | | |
+---------------------+------------+------------+
輸出分類測試數據的平均錯誤率為2.17%,同時我們可以通過混淆矩陣對分類器進行分析:
類別 | 含義 |
---|---|
真陽性(TP) | 輸入句子為賭博類別,分類器判斷為賭博類別 |
假陽性(FP) | 輸入句子為正常類別,分類器判斷為賭博類別 |
真陰性(TN) | 輸入句子為正常類別,分類器判斷為正常類別 |
假陰性(FN) | 輸入句子為賭博類別,分類器判斷為正常類別查全率(recall)(能找出賭博類別句子的概率) |
真陽性/(真陽性+假陰性) 249 / 249 = 100%
查准率(precision)(分類為賭博類別中的句子,確實是賭博類別的概率)
真陽性/(真陽性+假陽性) 249 / (249 + 13) = 95%
如果業務的需求是儘可能找到潛在的陽性數據(例如癌症初檢)那麼就要求高查全率,不過對應的,高查全率會導致查准率降低。(可以這樣理解,假如所有句子都判斷成賭博類別,那麼所有確實是賭博類別的句子確實都被檢測到了,但是查准率變得很低。)影響查全率以及查准率的一點是訓練數據數量的比例,日常的句子中,賭博類別的句子與正常類別的句子比例可能是1:50。也就是說隨便給出一個句子,不用看內容,那麼它有98%是正常的。不過在某些情況下,例如熱門評論區打廣告的用戶就很多,那麼這個比例就變成1:10或者1:20,這個比例是根據具體業務而調整的。訓練數據也應該遵循這個比例,但是實現中,我們必須要找到大量獨立分布的數據才能遵循這個比例,這就是機器學習數據常遇到的不均衡分類問題。要解決這個問題,可以引入Adaboost演算法動態調整每個詞語的權重。。我們可以通過-p參數輸出ROC曲線:
ROC曲線橫坐標代表的是假陽性(沒有問題卻被判斷為有問題),縱坐標代表的是真陽性(有問題而且被判斷出來),一個優秀的分類器儘可能維持高真陽性以及低假陽性。一般來說,如果一個分類器的ROC曲線包含了另外一個分類器的ROC曲線,代表此分類器在此數據集的分類效果更好。
演算法分析
上下文關聯
當我們計算P(輸入句子|類別)的時候,我們把輸入句子分成了詞語的集合,同時假定了輸入句子中詞語與詞語之間沒有上下文關係,其實這是不完全正確的,例如:
警方召開了全省集中打擊賭博違法犯罪活動...
從常識句子的上下文判斷,集中打擊出現在賭博違法犯罪之前的概率,要比召開出現在賭博違法犯罪之前的概率高,不過當我們把輸入句子分成詞語的集合的時候,把它們看成每個詞語都是獨立分布的。這也是此演算法稱為樸素貝葉斯的原因,如果我們有大量的數據集,計算出每個詞語對應詞袋模型其他詞語的出現概率值的話,可以提高檢測的準確率。
要注意的是,訓練數據選擇與最後進行分類的數據必須盡量關聯,如果要檢測的句子與訓練數據有非常大的差別,例如檢測的內容包含大量的英文單詞,但是訓練數據卻沒有,那麼分類器就無法進行正確的分類。同時,輸入句子過短的話,分類器也無法很好地進行分類。因為分類的結果會很容易被其中的一兩個詞語所影響。
分類器繞過
分類器無法分辨重複內容或部分無意義文本,輸入句子:
車厘子車厘子車厘子車厘子
{{{{{{{{{{{}}}}}}}}}}}
加入博彩121加qq看頭像,很為溫暖文科樓課文你問你看我呢額可能我呃讓你聽客啊啊愛看就是是過分過分你問人人官方代購極為。
前兩個是垃圾內容,但是即使我們添加垃圾內容的數據集,也很難判斷正確。最後一個前一小段是賭博類別的句子,後面一長串是無意義或者正常類別的句子,分類器綜合判斷它是正確的句子。解決這個問題我們可以用一個簡單的方法,計算句子的熵,也就是無序程度。每個句子都有合理的長度以及合理的無序程度,什麼意思呢?句子的長度大約遵循正態分布,極長(不包含標點符號)或者極短的句子出現的概率比較低,同時,通常一個句子中的詞語不會重複出現很多次,它的無序程度是在某個範圍的。當我們看到前兩個句子,因為它們詞語的重複度非常高,所以句子的無序度非常低,如何計算句子的無序程度呢?
我們找兩個輸入句子作為例子,先把輸入句子進行分詞
車厘子是一隻非常可愛的貓咪
車厘子車厘子車厘子車厘子
[車厘子,非常,可愛,貓咪]
[車厘子,車厘子,車厘子,車厘子]
計算每個詞語出現的次數除於句子的詞語數量:
P(車厘子) = P(非常) = P(可愛) = P(貓咪) = 1/4 (句子1)
P(車厘子) = 4/4 = 1 (句子2)
通過計算熵的公式,帶入每個概率值,最後除於句子的詞語數量
H = -sum(p(x)log2p(x))
H1 = ((1/4 * -2) - (1/4 * -2) - (1/4 * -2) - (1/4 * -2)) / 4= -2 / 4 = -1/2
H2 = 0
可以看到,在同樣的句子長度下,第一個句子的熵為-2,第二個為0,可以設置一個熵的範圍,如果低於該值,代表句子可能是垃圾數據。一般來說,先進行垃圾文本過濾,然後進行貝葉斯模型的分類,在工程中會有更好的效果。
總結
網路安全免不了過濾垃圾信息,理解了貝葉斯分類的原理,你就能根據自己的過濾需求,來判斷使用什麼分詞函數,使用哪些stop_word,並且以此定製適合業務的數據集,同時根據輸出的被錯誤分類的數據以及混淆矩陣,做出對應的調整。
*本文作者:WindsonYang,轉載請註明來自 FreeBuf.COM。


※HITB2018 | 安卓廠商隱藏的事實:安全補丁的更新部署並不真實完整
※某搜索引擎Self-XSS點擊劫持案例分享
TAG:FreeBuf |