1.目的
在公司實(shí)習(xí)耸峭,分別從國內(nèi)國外兩個(gè)網(wǎng)站爬取了一些展會數(shù)據(jù)茉帅,在數(shù)據(jù)處理上目前需要將其按照各個(gè)類別分類好处窥,并提供對應(yīng)展會地址的經(jīng)緯度重付。
國內(nèi)數(shù)據(jù)如下:
國內(nèi)數(shù)據(jù)比較少,占四百多條障贸,在類別上來看有所屬行業(yè)這一列错森,所以比較好處理。
國外數(shù)據(jù)就有些尷尬:
國外網(wǎng)站展會數(shù)據(jù)將近五萬多條篮洁,跟分類有關(guān)的只有
categories
這一列數(shù)據(jù)涩维,都是一些標(biāo)簽詞,還偏少袁波。現(xiàn)在需要將五萬多條展會數(shù)據(jù)進(jìn)行分類瓦阐,如何解決這個(gè)問題,我覺得可以寫個(gè)樸素貝葉斯分類器篷牌。
2.數(shù)據(jù)
樸素貝葉斯分類器是利用樣本數(shù)據(jù)來進(jìn)行訓(xùn)練的睡蟋,每個(gè)樣本必須含有一組特征詞以及對應(yīng)的分類。
2.1.數(shù)據(jù)的準(zhǔn)備
獲取的國內(nèi)數(shù)據(jù)就很適合作為訓(xùn)練數(shù)據(jù)枷颊,可以將其處理成如下格式作為樣本輸入:
訓(xùn)練數(shù)據(jù)主要是這兩列戳杀,所屬行業(yè)以及展會范圍叫倍,每一行的類別都有其對應(yīng)的展會范圍,將這些作為訓(xùn)練數(shù)據(jù)實(shí)在再好不過豺瘤,我們將其稱之為訓(xùn)練集吆倦。
那么相對應(yīng),國外待分類的展會數(shù)據(jù)便是測試集坐求。
我們將利用訓(xùn)練好的分類器蚕泽,將測試集一一輸入,看看能否得到期望的輸出結(jié)果桥嗤。
可是現(xiàn)在有個(gè)問題须妻,作為訓(xùn)練集的國內(nèi)數(shù)據(jù)只有四百多條,實(shí)在太少泛领,于是我只能再去那個(gè)國內(nèi)展會網(wǎng)站將以前的展會數(shù)據(jù)盡量爬取下來荒吏,最終訓(xùn)練集達(dá)到了
39555
條,雖說數(shù)量還是不夠渊鞋,但是不試試最終分類器的分類結(jié)果绰更,說不定準(zhǔn)確率還可以呢?
2.2.分詞&&提取
不積小流锡宋,無以成江海儡湾,以小見大,咱們看這樣一條數(shù)據(jù):
所屬行業(yè):建材五金
展會范圍:各類衛(wèi)生潔具执俩、浴室家具和配件徐钠、面盆、馬桶役首、淋浴房尝丐、浴缸、花灑衡奥、水龍頭及配件爹袁、浴室照明、鏡子杰赛、五金掛件等
思考一下呢簸,這一條數(shù)據(jù)的所屬行業(yè)和展會范圍有什么關(guān)系,我們將從這里得到編寫分類器的出發(fā)點(diǎn)乏屯。
在這條數(shù)據(jù)中,展會范圍的內(nèi)容是具有一定的代表性的瘦赫,其代表這條數(shù)據(jù)的描述很偏向建材五金這個(gè)行業(yè)辰晕。
那我么是不是可以提煉出這個(gè)描述的關(guān)鍵詞,從而讓這個(gè)關(guān)鍵詞代表建材五金這個(gè)行業(yè)席揽。
# 可以利用結(jié)巴分詞
import jieba.analyse
con = "各類衛(wèi)生潔具羊精、浴室家具和配件、面盆填抬、馬桶窘问、淋浴房辆童、浴缸、花灑惠赫、水龍頭及配件把鉴、浴室照明、鏡子儿咱、五金掛件等"
feature = jieba.analyse.extract_tags(con, 8)
print(feature)
# output:['配件', '各類', '家具', '五金', '照明', '浴室', '淋浴房', '浴缸']
那么剛剛那條數(shù)據(jù)可以這樣看:
# data_01
所屬行業(yè):建材五金
描述關(guān)鍵詞:'配件', '各類', '家具', '五金', '照明', '浴室', '淋浴房', '浴缸'
以此類推庭砍,如果一條未分類數(shù)據(jù)的關(guān)鍵詞也是這樣,那是不是可以將該數(shù)據(jù)歸為建材五金這個(gè)類別混埠,是的怠缸,你可以這么干。
但有個(gè)問題钳宪,若該未分類數(shù)據(jù)的關(guān)鍵詞只含有以上關(guān)鍵詞的某個(gè)揭北,比如:
# data_02
所屬行業(yè):未知
描述關(guān)鍵詞:'配件','家具'
這樣子若分為建材五金不大對吧,我倒覺得應(yīng)該分為房產(chǎn)家居吏颖,這個(gè)問題可以解決罐呼,就讓我們的概率出場吧。
大三的時(shí)候?qū)W過概率統(tǒng)計(jì)侦高,也記得一個(gè)公式名為貝葉斯定理嫉柴。
這表示計(jì)算條件概率的公式:
P(A\B) = P(B\A)*P(A)/P(B) == 后驗(yàn)概率 = 先驗(yàn)概率 x 調(diào)整因子
# 這樣寫會不會更加清晰
P(category\keywords) = P(category) * P(keywords\category)/ P(keywords)
## 樸素貝葉斯便是假設(shè)即將被組合的各個(gè)概率是獨(dú)立的,可以理解成keyword1出現(xiàn)在category1的概率和keyword2出現(xiàn)在category1的概率是沒有關(guān)系的奉呛,是獨(dú)立的计螺。
P(category\keywords) = P(category) * P(keyword1\category)P(keyword2\category)...P(keywordn\category)/ P(keywords)
總結(jié)就是,我們先求出樣本空間中每個(gè)分類的概率P(category)——先驗(yàn)概率
再求出一組待分類數(shù)據(jù)的關(guān)鍵詞在各個(gè)類別中的概率P(keywords\category)——調(diào)整因子
最后先驗(yàn)概率 * 調(diào)整因子得出后驗(yàn)概率瞧壮,再經(jīng)過比較登馒,后驗(yàn)概率最大的,便是待分類數(shù)據(jù)最可能的類別咆槽。
我們的準(zhǔn)備很充足了陈轿,但在寫分類器之前,還是先將下面要用到的數(shù)據(jù)提前提取出來秦忿。
2.2.1.確定訓(xùn)練數(shù)據(jù)有多少類別
2.2.2.訓(xùn)練數(shù)據(jù)的關(guān)鍵詞集合麦射,為了方便后續(xù)計(jì)算,將其轉(zhuǎn)為id:word的格式存為id_word.txt
2.2.3.計(jì)算出每個(gè)關(guān)鍵詞在每個(gè)不同類別出現(xiàn)的概率灯谣,將其轉(zhuǎn)為category=id:pro(id)的格式存為tf_id_word.txt
2.2.4.求出樣本空間中每個(gè)分類的概率P(category)——先驗(yàn)概率潜秋,將其轉(zhuǎn)為category:pro(category)的格式存為type_pro.txt
3.編碼
經(jīng)過前面的步驟,現(xiàn)在編寫代碼實(shí)在簡單胎许,不過有兩點(diǎn)要注意峻呛。
其一是關(guān)鍵詞并不是在每個(gè)類別都會出現(xiàn)罗售,這樣會導(dǎo)致P(keyword\category) = 0,進(jìn)而導(dǎo)致整個(gè)后驗(yàn)概率為0钩述,為了解決這個(gè)問題寨躁,可以引入拉普拉斯平滑,這樣便確保不會出現(xiàn)為0的情況牙勘,具體代碼中有介紹职恳。
其二是若每個(gè)調(diào)整因子的數(shù)值都很小,大家都知道很小的值相乘谜悟,會導(dǎo)致結(jié)果變得更小话肖,這樣子表現(xiàn)出各個(gè)分類結(jié)果的后驗(yàn)概率便會出現(xiàn)問題,這個(gè)問題便是下溢出問題葡幸,解決辦法便是將其轉(zhuǎn)化為對數(shù)最筒,對數(shù)相乘便是對數(shù)相加,這樣便很巧妙的解決了這個(gè)問題蔚叨。
好了床蜘,直接上代碼,讓我們看看分類的結(jié)果吧蔑水。
# -*-coding:utf-8-*-
__author__ = 'howie'
import jieba.analyse
import pandas as pd
import math
class Pridict(object):
"""
利用txt/中數(shù)據(jù)進(jìn)行樸素貝葉斯算法訓(xùn)練
"""
def __init__(self):
# 初始化結(jié)果字典
self.resultData = {}
def getKeyword(self, path):
"""
通過結(jié)巴分詞進(jìn)行關(guān)鍵詞提取
:param path: 待分詞文件路徑 相關(guān)參數(shù)可更改 也可不調(diào)用直接寫
:return: keywordDic = {index:['word1','word2'...]}
"""
keywordDic = {}
df = pd.read_csv(path)
for des in df.values:
jieba.analyse.set_idf_path('txt/type_dict.txt')
feature = jieba.analyse.extract_tags(des[15], 8)
keywordDic[des[16]] = feature
return keywordDic
def idDic(self, keywordDic):
"""
將每條待分類關(guān)鍵詞替換成對應(yīng)的單詞向量
:param keywordDic:格式 keywordDic = {index:['word1','word2'...]}
:return: keywordDic = {'1': ['12198', '16311', '6253', '8302']}
"""
with open('txt/id_word.txt', 'r', encoding='utf8') as of:
lines = of.read().split('\n')
for key, values in keywordDic.items():
id_word = []
for eachValue in values:
id_word += [eachLine.split(':')[0] for eachLine in lines if eachValue == eachLine.split(':')[-1]]
keywordDic[key] = id_word
return keywordDic
def calPro(self, keywordDic):
"""
計(jì)算每一組待分類數(shù)據(jù)關(guān)鍵詞對應(yīng)概率總和
:param keywordDic:格式 keywordDic = {'1': ['12198', '16311', '6253', '8302']}
:return: result = {'1': {'type1': -33.23204707236557, 'type3': -31.376043125934267, 'type3': -27.385192803356617...}
"""
result = {}
# print(keywordDic)
# 獲取每個(gè)類別關(guān)鍵詞出現(xiàn)概率
with open('txt/tf_id_word.txt', 'r', encoding='utf8') as of:
lines = of.read().split('\n')[0:-1]
# 循環(huán)待分類數(shù)據(jù)
for key, values in keywordDic.items():
result[key] = {}
# 讀取分類概率文件中讀取每行數(shù)據(jù)
for eachLine in lines:
valLen = len(values)
valPro = list(map(lambda x: '', [x for x in range(0, valLen)]))
laplace = ''
# 分類名稱 該分類下關(guān)鍵詞概率
lineData = eachLine.split("=")
# 該分類下每個(gè)關(guān)鍵詞概率
eachLineData = lineData[-1].split("#")
# 循環(huán)每個(gè)待分類字典的關(guān)鍵字向量
for index,eachValue in enumerate(values):
# 每個(gè)關(guān)鍵詞對應(yīng)的詞向量以及詞概率 得到所有關(guān)鍵詞該分類下的概率列表
resPro = [eachPro.split(':')[1] for eachPro in eachLineData if eachValue == eachPro.split(':')[0]]
# 防止待分類關(guān)鍵詞概率為0邢锯,添加拉普拉斯平滑
laplace = str(1 / (valLen + len(eachLineData)))
valPro[index] = (resPro[0] if resPro else laplace)
valPrlLen = int(valLen / 2)
# 處理關(guān)鍵詞為空 增加限制條件,排除小概率分類影響后驗(yàn)概率大小
if valPro:
if valPro.count(laplace) <= valPrlLen:
# 防止下溢出轉(zhuǎn)化為對數(shù)相加
typePro = sum([math.log(float(x)) for x in valPro])
result[key][lineData[0]] = typePro
else:
# 將該待分類數(shù)據(jù)標(biāo)記為None
result[key] = None
return result
def resPro(self, result):
"""
為各個(gè)分類乘上先驗(yàn)概率 提高分類成功率
:param result: 格式 result = {'1': {'type1': -33.23204707236557, 'type3': -31.376043125934267, 'type3': -27.385192803356617...}
:return: resultData = {'1': {'type': -28.135469210164924}}
"""
for eachKey, eachVal in result.items():
# 初始化每個(gè)待分類數(shù)據(jù)字典 儲存最可能分類的概率
self.resultData[eachKey] = {}
# 初始化最終分類結(jié)果概率
allPro = {}
if eachVal:
# print(eachVal)
with open('txt/type_pro.txt', 'r', encoding='utf8') as of:
lines = of.read().split('\n')
for line in lines:
lineData = line.split(':')
# 乘上該分類概率 即先驗(yàn)概率
allPro = dict(allPro,**{key:(value + math.log(float(lineData[1]))) for key, value in eachVal.items() if key == lineData[0]})
# 返回各個(gè)分類對應(yīng)后驗(yàn)概率最大值
largest = max(zip(allPro.values(),allPro.keys()))
self.resultData[eachKey][largest[1]] = largest[0]
else:
# 無法分類
self.resultData[eachKey] = {'failed': None}
return self.resultData
def mainPri(keywordDic):
pri = Pridict()
dic = pri.idDic(keywordDic)
result = pri.calPro(dic)
resultData = pri.resPro(result)
return resultData
初步寫好分類器搀别,其實(shí)這才是任務(wù)的開始丹擎,分類器的最重要的是第二部分的數(shù)據(jù)提取,接下來需要通過不斷地訓(xùn)練歇父,讓數(shù)據(jù)變得更加優(yōu)雅美麗蒂培,從而讓分類器的結(jié)果趨于完美,本人苦逼地調(diào)了一個(gè)星期榜苫,現(xiàn)在也就勉強(qiáng)能用护戳。
讓我們看看分類器的分類效果:
可以看到分類器給出的參考分類很有代表性,這一段就此結(jié)束垂睬,若有錯(cuò)誤媳荒,敬請指出,謝謝驹饺。