R貝葉斯分類器

Part 1: 本文解決的問(wèn)題:

我在有這樣的一個(gè)數(shù)據(jù)集,里面存放了人們對(duì)近期播放電影的評(píng)價(jià),當(dāng)然評(píng)價(jià)也就分成兩部分,好評(píng)和差評(píng)。我們想利用這些數(shù)據(jù)訓(xùn)練一個(gè)模型,然后可以自動(dòng)的對(duì)影評(píng)做出判斷,到底是好評(píng)還是差評(píng),差評(píng)的話,那么我們趕緊刪掉它,哈哈。

好吧,這就是自然語(yǔ)言處理領(lǐng)域的基本問(wèn)題:文本分類。文本分類在我們的日常生活中有非常多的應(yīng)用,最有名的當(dāng)屬垃圾郵件過(guò)濾啦。我們肯定希望不要受到垃圾郵件,但是我們更不希望正常的郵件被當(dāng)做垃圾郵件過(guò)濾掉了。這對(duì)我們分類的精度提出了很高的要求。

Part 2:本文的結(jié)構(gòu)

數(shù)據(jù)來(lái)源以及含義

貝葉斯公式的簡(jiǎn)單介紹

樸素貝葉斯分類器代碼編寫(xiě)

劃分測(cè)試數(shù)據(jù)和訓(xùn)練數(shù)據(jù),計(jì)算分類精度

使用sklearn自帶的樸素貝葉斯分類器,計(jì)算分類精度

比較手寫(xiě)的分類器和sklearn自帶的分類器的優(yōu)點(diǎn)和缺點(diǎn)

參考資料和引用

Part 3 :數(shù)據(jù)來(lái)源以及含義

本文所用的測(cè)試數(shù)據(jù)和訓(xùn)練數(shù)據(jù)都是來(lái)源于康奈爾大學(xué)網(wǎng)站的2M影評(píng)數(shù)據(jù)集。下載地址。里面共計(jì)有1400條影評(píng),700條好評(píng),700條差評(píng),作者已經(jīng)為我們分好了類。

Part 4: 代碼編寫(xiě)

Part4.1:文檔和單詞

新建一個(gè)文件,命名為docclass.py,里面加入一個(gè)getwords的函數(shù),完成從文本中提取特征。

def getwords(doc):

splitter = re.compile('\\W*')

words = [s.lower() for s in splitter.split(doc) if len(s) > 2 and len(s) < 20]

# 過(guò)濾掉單詞中包含數(shù)字的單詞

words = [word for word in words if word.isalpha()]

with open(r'E:\研究生階段課程作業(yè)\python\好玩的數(shù)據(jù)分析\stopwords.txt') as f:

stopwords = f.read()

stopwords = stopwords.split('\n')

stopwords = set(stopwords)

# 過(guò)濾掉一些經(jīng)常出現(xiàn)的單詞,例如 a,an,we,the

words = [word for word in words if word not in stopwords]

return set(words)

該函數(shù)的輸入一個(gè)文檔,一般來(lái)說(shuō)是一個(gè)大的字符串,我們首先使用正則表達(dá)式劃分單個(gè)單詞,對(duì)于一些特別常見(jiàn)的單詞,例如a,an,the,these,這些毫無(wú)意義的單詞,我們都保存在stopwords 中,并進(jìn)行過(guò)濾,最后返回一組文檔中不重復(fù)的單詞(所有的單詞都是小寫(xiě)的形式)。

Part4.2: 編寫(xiě)分類器

新建一個(gè)classifier的類:

class classifier:

def __init__(self, getfeatures):

# Counts of feature/category combinations

self.fc = {}

# Counts of documents in each category

self.cc = {}

self.getfeatures = getfeatures

該類中有三個(gè)實(shí)例變量:fc,cc, getfeatures.

變量fc將記錄位于各分類中不同特征的數(shù)量。例如:

{'python': {'bad': 0, 'good': 6}, 'money': {'bad': 5, 'good': 1}}

上述示例表明,單詞'money'被劃歸'bad'類文檔中已經(jīng)出現(xiàn)了5次,而被劃為'good'類只有1次,單詞'python'被劃歸'bad'類文檔中已經(jīng)出現(xiàn)了0次,而被劃為'good'類有6次。

變量cc是一個(gè)記錄各分類被使用次數(shù)的詞典。這一信息是我們稍后討論的概率計(jì)算所需的。最后一個(gè)實(shí)例變量是 getfeatures,對(duì)應(yīng)一個(gè)函數(shù),作用是從即將被歸類的文檔中提取出特征來(lái)-本例中,就是我們剛才定義的getwords函數(shù)。

向我們剛才定義的類中加入下面的幾個(gè)函數(shù),實(shí)現(xiàn)分類器的訓(xùn)練

#增加對(duì)特征/分類組合的計(jì)數(shù)值

def incf(self, f, cat):

self.fc.setdefault(f, {})

self.fc[f].setdefault(cat, {})

self.fc[f][cat] += 1

#增加某一個(gè)分類的計(jì)數(shù)值:

def incc(self, cat):

self.cc.setdefault(cat, {})

self.cc[cat] += 1

#計(jì)算某一個(gè)特征在某一個(gè)分類中出現(xiàn)的次數(shù)

def fcount(self, f, cat):

if f in self.fc and cat in self.fc[f]:

return self.fc[f][cat]

else:

return 0.0

#屬于某一個(gè)分類的文檔總數(shù)

def catcount(self, cat):

if cat in self.cc:

return self.cc[cat]

return 0

#所有的文檔總數(shù)

def totalcount(self):

return sum(self.cc.values())

#所有文檔的種類

def categories(self):

return self.cc.keys()

train函數(shù)接受一個(gè)文檔和其所屬分類(‘good’或者‘bad’),利用我們定義的getwords函數(shù),對(duì)文檔進(jìn)行劃分,劃分成一個(gè)個(gè)獨(dú)立的單詞,然后調(diào)用incf函數(shù),針對(duì)該分類為每個(gè)特征增加計(jì)數(shù)值,最后增加該分類的總計(jì)數(shù)值:

def train(self, item, cat):

features = self.getfeatures(item)

# 針對(duì)該分類,為每個(gè)特征增加計(jì)數(shù)值

for f in features:

self.incf(f, cat)

# 增加該分類的計(jì)數(shù)值

self.incc(cat)

下面我們開(kāi)始測(cè)試我們編寫(xiě)的類是否可用

cl = classifier(getwords)

cl.train('the quick brown fox jumps over the lazy dog', 'good')

cl.train('make quick money in the online casino', 'bad')

cl.fcout('quick','good')

out: 1.0

cl.fcout('quick','bad')

out: 1.0

上面的幾行代碼很好理解,我們首先實(shí)例化了 classifier 類,然后使用兩個(gè)文檔對(duì)我們的分類器進(jìn)行了簡(jiǎn)單的訓(xùn)練。cl.fcout('quick','good') 用來(lái)計(jì)算在分類為‘good’的所有文檔中,單詞‘qucik’出現(xiàn)的次數(shù)。

當(dāng)然嘍,我們現(xiàn)實(shí)生活中的分類器訓(xùn)練肯定需要使用大量數(shù)據(jù),我們新建一個(gè)函數(shù)(需要注意的是,這個(gè)函數(shù)不屬于任何一個(gè)類),來(lái)訓(xùn)練大規(guī)模數(shù)據(jù)

def sampletrain(cl):

cl.train('nobody owns the water','good')

cl.train('the quick rabbit jumps fences','good')

cl.train('buy phamaceuticals now','bad')

cl.train('make quick money at the online casino','bad')

cl.train('the quick borwn fox jumps','good')

在上述的函數(shù)中,我們已經(jīng)計(jì)算了對(duì)于每一個(gè)特征(單詞),我們計(jì)算了它在某一個(gè)分類中出現(xiàn)的次數(shù),是時(shí)候?qū)⑵滢D(zhuǎn)化成概率了。在本例中,我們對(duì)于一個(gè)特定單詞,計(jì)算它在某個(gè)分類中所占的比例。(也就是某個(gè)分類中出現(xiàn)該單詞的文檔數(shù)目 / 該分類的文檔總數(shù))

def fprob(self, f, cat):

if self.catcount(cat) == 0:

return 0

# 特征在該分類中出現(xiàn)的次數(shù) /

# 該特征下文檔的總數(shù)目

return self.fcount(f, cat)/self.catcount(cat)

通俗的來(lái)說(shuō),這個(gè)函數(shù)就是我們要求的條件概率。 P(word | classification),意思就是對(duì)于一個(gè)給定的分類,某個(gè)單詞出現(xiàn)的概率,下面我們測(cè)試一下這個(gè)函數(shù):

cl = classifier(getwords)

sampletrain(cl)

cl.fprob('quick','good')

out:0.6666666

從執(zhí)行的結(jié)果上看,在所有的三篇被歸類于‘good’文檔中,有2篇出現(xiàn)了單詞‘qucik’,所以我們要求的條件概率 p('quick' | 'good') = 2/3

Part 4.2.1 一個(gè)小小的問(wèn)題

在訓(xùn)練的樣本中,由于單詞‘money’只出現(xiàn)了一次,并且是一個(gè)賭博類的廣告,因此被分類‘bad’類,那我們計(jì)算p('money' | 'good') = 0,這是非常危險(xiǎn)和不公平的,由于我們訓(xùn)練樣本的缺失,導(dǎo)致所有含有‘money’這個(gè)單詞的文檔都被判斷為‘bad’類文檔。顯然這種結(jié)果是我們不愿意接受的,因此我們對(duì)概率進(jìn)行一些加權(quán),使一些即使在訓(xùn)練樣本中沒(méi)有出現(xiàn)的單詞,在求條件概率的時(shí)候,不至于為0。具體做法如下:

def weightedprob(self, f, cat, prf, weight=1, ap=0.5):

# 使用fprob函數(shù)計(jì)算原始的條件概率

basicprob = prf(f, cat)

totals = sum([self.fcount(f, c) for c in self.categories()])

bp = ((weight*ap)+(totals*basicprob))/(weight+totals)

return bp

這個(gè)函數(shù)就是經(jīng)過(guò)加權(quán)以后的條件概率,我們來(lái)對(duì)比一下加權(quán)前后的條件概率:

cl = classifier(getwords)

sampletrain(cl)

cl.fprob('money','good')

out:0

cl.weightedprob('money','good')

out:0.25

Part 4.3 樸素分類器

之所以稱為樸素貝葉斯分類器的前提是被組合的各個(gè)概率之間是獨(dú)立的,在我們的例子中,可以這樣理解:一個(gè)單詞在屬于某個(gè)分類文檔中概率,與其他單詞出現(xiàn)在該分類的概率是不相關(guān)的。事實(shí)上,這個(gè)假設(shè)并不成立,因?yàn)楹芏嘣~都是結(jié)伴出現(xiàn)的,但是我們可以忽略,實(shí)踐顯示,在假設(shè)各單詞互相獨(dú)立的基礎(chǔ)上,使用樸素貝葉斯對(duì)文本分類可以達(dá)到比較好的效果

Part 4.3.1 計(jì)算整篇文檔屬于某個(gè)分類的概率

假設(shè)我們已經(jīng)注意到,有20%的‘bad’文檔出現(xiàn)了‘python’單詞- P('python'| 'bad') = 0.2,同時(shí)有80%的文檔出現(xiàn)了單詞‘casino’-P('casino'| 'bad')=0.8,那么當(dāng)‘python’和‘casino’同時(shí)出現(xiàn)在一篇‘bad’文檔的概率是P('casino' & 'python' | 'bad') = 0.8 * 0.2 = 0.16。

我們新建一個(gè)子類,繼承自classifier,取名naivebayes,并添加一個(gè)docprob函數(shù)

class naivebayes(classifier):

def __init__(self, getfeatures):

classifier.__init__(self, getfeatures)

def docprob(self, item, cat):

features = self.getfeatures(item)

# Multiply the probabilities of all the features together

p = 1

for f in features:

p *= self.weightedprob(f, cat, self.fprob)

return p

現(xiàn)在我們已經(jīng)知道了如何計(jì)算P(Document|category),但是我們需要知道的是,最終我們需要的結(jié)果是P(category|Document),換而言之,對(duì)于一篇給定的文檔,我們需要找出它屬于各個(gè)分類的概率,我們感到欣慰的是,這就是貝葉斯需要解決的事情

在本例中:

P(category|Document) = P(Document|category) * P(category) / P(Document)

P(Document|category) 已經(jīng)被我們用 docprob 函數(shù)計(jì)算出來(lái)了,P(category)也很好理解和計(jì)算:代表我們隨你選擇一篇文檔,它屬于某個(gè)分類的概率。P(Document)對(duì)于所有的文檔來(lái)說(shuō),都是一樣的,我們直接選擇忽略掉他

我們?cè)趎aivebayes中新添加一個(gè)prob函數(shù),計(jì)算一篇文檔屬于某個(gè)分類的概率(P(Document|category) * P(category) )

def prob(self, item, cat):

catprob = self.catcount(cat)/self.totalcount()

docprob = self.docprob(item, cat)

return docprob * catprob

到現(xiàn)在為止,我們的樸素貝葉斯分類器編寫(xiě)基本完成。我們看看針對(duì)不同的文檔(字符串),概率值是如何變化的:

cl = naivebayes(getwords)

sampletrain(cl)

cl.prob('quick rabbit', 'good')

out: 0.156

cl.prob('quick rabbit', 'bad')

out: 0.05

根據(jù)訓(xùn)練的數(shù)據(jù),我們認(rèn)為相對(duì)于‘bad’分類而言,我們認(rèn)為‘quick rabbit’更適合于'good'分類.

最后我們完善一下我們的分類器,我們只需要給出文檔,分類器會(huì)自動(dòng)給我們找出概率最大的哪一個(gè)分類。

我們?yōu)閚aivebayes新添加一個(gè)方法 :classify

def classify(self, item):

max = 0.0

for cat in self.categories():

probs[cat] = self.prob(item, cat)

if probs[cat] > max:

max = probs[cat]

best = cat

return best

繼續(xù)測(cè)試:

cl = naivebayes(getwords)

sampletrain(cl)

cl.classify('quick rabbit')

out:good

但是到目前為止,我們所使用的訓(xùn)練數(shù)據(jù),或者測(cè)試數(shù)據(jù),都是簡(jiǎn)單的字符串,同時(shí)也是我們?nèi)藶橹圃斓?,但是在真?shí)的生產(chǎn)環(huán)境中,這幾乎是不可能的,數(shù)據(jù)要更為復(fù)雜,更為龐大。回到開(kāi)頭,我這里使用在康奈爾大學(xué)下載的2M影評(píng)作為訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù),里面共同、共有1400條,好評(píng)和差評(píng)各自700條,我選擇總數(shù)的70%作為訓(xùn)練數(shù)據(jù),30%作為測(cè)試數(shù)據(jù),來(lái)檢測(cè)我們手寫(xiě)的樸素貝葉斯分類器的效果

首先我們稍微修改一下:我們的訓(xùn)練函數(shù):sampletrain,以便能夠訓(xùn)練大規(guī)模數(shù)據(jù)

def sampletrain(cl, traindata, traintarget):

for left, right in zip(traindata, traintarget):.

cl.train(left, right)

我們可以把需要訓(xùn)練的數(shù)據(jù)放在一個(gè)list里面或者迭代器里面,其對(duì)應(yīng)的分類也是如此,在函數(shù)中,我們使用traindata, traintarget分別替代我們的訓(xùn)練數(shù)據(jù)和其對(duì)應(yīng)的分類。

我們定義一個(gè)函數(shù) get_dataset獲得打亂后的數(shù)據(jù)

def get_dataset():

data = []

for root, dirs, files in os.walk(r'E:\研究生階段課程作業(yè)\python\好玩的數(shù)據(jù)分析\樸素貝葉斯文本分類\tokens\neg'):

for file in files:

realpath = os.path.join(root, file)

with open(realpath, errors='ignore') as f:

data.append((f.read(), 'bad'))

for root, dirs, files in os.walk(r'E:\研究生階段課程作業(yè)\python\好玩的數(shù)據(jù)分析\樸素貝葉斯文本分類\tokens\pos'):

for file in files:

realpath = os.path.join(root, file)

with open(realpath, errors='ignore') as f:

data.append((f.read(), 'good'))

random.shuffle(data)

return data

在定義一個(gè)函數(shù),對(duì)我們的數(shù)據(jù)集進(jìn)行劃分,訓(xùn)練集和測(cè)試集分別占07和0.3

def train_and_test_data(data_):

filesize = int(0.7 * len(data_))

# 訓(xùn)練集和測(cè)試集的比例為7:3

train_data_ = [each[0] for each in data_[:filesize]]

train_target_ = [each[1] for each in data_[:filesize]]

test_data_ = [each[0] for each in data_[filesize:]]

test_target_ = [each[1] for each in data_[filesize:]]

return train_data_, train_target_, test_data_, test_target_

計(jì)算我們的分類器在真實(shí)數(shù)據(jù)上的表現(xiàn):

if __name__ == '__main__':

cl = naivebayes(getwords)

data = dataset()

train_data, train_target, test_data, test_target = train_and_test_data(data)

sampletrain(cl, train_data, train_target)? #對(duì)訓(xùn)練我們的分類器進(jìn)行訓(xùn)練

predict = []

for each in test_data:

predict.append(cl.classify(each))

count = 0

for left,right in zip(predict,test_target ):

if left == right:

count += 1

print(count/len(test_target))

out :0.694

對(duì)于我們的測(cè)試集,大約有420個(gè)影評(píng),我們使用簡(jiǎn)單的、完全手寫(xiě)的貝葉斯分類器達(dá)到了將近70%的預(yù)測(cè)準(zhǔn)確率,效果還算可以,從頭到尾,你是不是被貝葉斯的神奇應(yīng)用折服了呢。如果你是初學(xué)者,可以按照本片博客,一步一步完成樸素貝葉斯分類器的編寫(xiě),如果你嫌麻煩,可以直接向我要源碼。(其實(shí)把本文所有的代碼加起來(lái)就是完整的源碼啦)

Part 5 總結(jié)

作為學(xué)計(jì)算機(jī)的人,重復(fù)造輪子,恐怕是最消耗精力也是最得不償失的一件事情了,在下一篇文檔,我將會(huì)使用sklearn庫(kù)里自帶的貝葉斯分類器,對(duì)相同的數(shù)據(jù)進(jìn)行分類,比較我們手寫(xiě)的和自帶的有哪些優(yōu)點(diǎn)和缺點(diǎn)。

Part 6 參考資料

需要說(shuō)明的是,本篇文章關(guān)于分類器編寫(xiě)的部分,我參考了《集體智慧編程》一書(shū)的第六章: 文檔過(guò)濾,我真心推薦《集體智慧編程》這本書(shū),如果你是機(jī)器學(xué)習(xí)的初學(xué)者,那么這本書(shū)將使你受益頗多。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Part 1: 本文解決的問(wèn)題: 我在有這樣的一個(gè)數(shù)據(jù)集,里面存放了人們對(duì)近期播放電影的評(píng)價(jià),當(dāng)然評(píng)價(jià)也就分成兩部...
    YoghurtIce閱讀 16,810評(píng)論 15 24
  • 一、前言 CS231n是斯坦福大學(xué)開(kāi)設(shè)的一門(mén)深度學(xué)習(xí)與計(jì)算機(jī)視覺(jué)課程,是目前公認(rèn)的該領(lǐng)域內(nèi)最好的公開(kāi)課。目前,該課...
    金戈大王閱讀 4,930評(píng)論 3 7
  • 今天終于如愿的游玩了從旅行雜志上看到的揚(yáng)美古鎮(zhèn)。雖說(shuō)沒(méi)有想象中的那么驚艷,但也沒(méi)有令人很失望。 古落的房屋,...
    寧默默閱讀 922評(píng)論 4 3
  • 在看別人源碼的時(shí)候無(wú)意中發(fā)現(xiàn)了一個(gè)神奇的寫(xiě)法 [[UILabel appearance] setBackgroun...
    Darren_xu閱讀 400評(píng)論 0 0
  • 這周去公司培訓(xùn),學(xué)習(xí)java,最開(kāi)始的上午,講企業(yè)文化,盡力在聽(tīng),但是沒(méi)聽(tīng)懂啥,感覺(jué)很尷尬。然后在接下來(lái)的一天半...
    allwaysPositive閱讀 366評(píng)論 0 0

友情鏈接更多精彩內(nèi)容