在说用贝叶斯之前, 应该先检讨一下自己大学概率论真的是木有好好上课… 不过至少是学过, 看到贝叶斯这仨字儿还是感到十分熟悉. 所谓的贝叶斯公式, 举个栗子: 假设有3个白球7个黑球, 分别在甲乙两个盒子里, 甲盒子有1个白的3个黑的, 乙盒子有2个白的4个黑的.
那么问题来了. 这时候随机抽一个球(随机从两个盒子选, 从盒子里也随机选), 是甲盒子里的白球的概率 = 从甲盒子抽的概率 * 甲盒子里是白球的概率 = 抽到的球是白球的概率 * 白球在甲盒子的概率. 用我们概率论公式来说就是P(甲白) = P(甲) * P(白|甲) = P(白) * P(甲|白), 其中P(甲白)就是抽到的盒子是甲盒子并且球是白球的概率; P(白|甲)就是在甲盒子中抽到白球的概率, 这貌似就是条件概率, 就是在确定是甲盒子的条件下, 是白球的概率.
说到朴素贝叶斯就不得不来一发垃圾邮件分类压压惊. 朴素贝叶斯使用需要有足够多的样本, 要不概率问题就是在瞎扯(就像我不能抛一次硬币发现是正面我就说是正面的概率是1), 然后就是需要特征之间相互独立互不影响(这难道就是概率论传说中的独立事件). 朴素贝叶斯之所以叫naive bayes, 就是因为太naive暂且认为一段话里出现的单词同等重要并且出现的概率相互独立. 要不咱这程序也没法往下写了… 不过这本书的告诉我朴素贝叶斯的效果还是很不错的, 那就try一try啦.
首先, 来数据!!! email.tar.gz 这就是书上给的email的数据集, 里面ham目录下是正常的邮件, spam目录下是垃圾邮件. 我们第一步就是来段代码把数据读出来, 转换成我们用的数据, 新建file2data.py
#!/usr/bin/python
# coding: utf-8
from numpy import *
import re
import os
# email数据集的路径
EMAIL_PATH = '/home/gavin/PycharmProjects/ml-03/email'
# 读取文件, 格式化后作为list返回
def file2list(filename):
text = open(filename).read()
# 使用非字母数字 分割字符串
data = re.split(r'\W*', text)
# 遍历list 返回长度大于2的小写单词
return [item.lower() for item in data if len(item) > 2]
# 根据已有数据集, 生成词库的list
def create_vocab_list(dataSet):
vocabSet = set([])
for data in dataSet:
# 求数据集set的并集
vocabSet = vocabSet | set(data)
return list(vocabSet)
# 将input_data里的单词映射到vocab_list中形成新的list
def data2vocab_list(input_data, vocab_list):
# 初始化一个与vocab_list大小相同的list
vocabDataSet = [0] * len(vocab_list)
for word in input_data:
if word in vocab_list:
# 单词出现一次就加1
vocabDataSet[vocab_list.index(word)] += 1
else:
print "word '" + word + "' is not in my vocabulary..."
return vocabDataSet
# 从文件夹中获取数据集和分类
def get_data_set():
tmp_data_set = []
labels = []
dataSet = []
# 读取根目录内的分类列表(文件夹名字就是分类名称)
clazzes = os.listdir(EMAIL_PATH)
for clazz in clazzes:
# 读取分类内的文件列表
tmp_dir = EMAIL_PATH + '/' + clazz
files = os.listdir(tmp_dir)
for file in files:
# 读出每个文件的内容
data = file2list(EMAIL_PATH + '/' + clazz + '/' + file)
# 分别将数据和分类标签加进对应的list
tmp_data_set.append(data)
labels.append(clazz)
# 根据现有的所有的邮件创建一个词库, 里面的单词唯一
vocabList = create_vocab_list(tmp_data_set)
# 根据单词表, 计算真正用的全单词表频度的数据集
for data in tmp_data_set:
# 把每个email的单词对应到词库表中形成新的list
dataSet.append(data2vocab_list(data, vocabList))
return dataSet, labels, vocabList
if __name__ == '__main__':
dataSet, labels, vocabList = get_data_set()
# 获取数据之后打印一下各个数据集的分类
print labels
上述代码里注释把每一步干了啥都写清楚了, 最后打印了一下数据集的分类集, 验证一下读取的对不对. 数据读取好了, 那咋计算是啥分类呢?
我们回到贝叶斯上, 如果给定两个随机分配打乱的email数据集, 那么两个数据集各个单词在各个分类下出现的概率和各个分类在数据集中的概率应该是相同的. 毕竟说好的充分随机打乱的. 根据可爱的贝叶斯公式可以知道:
P(ci w) = P(ci | w) * P(w) = P(w | ci) * P(ci)
假如说公式左边是一个数据集, 公式右边是一个数据集, 根据上述条件, 这个等号是成立的:
P(ci w) 表示一个词是w并且是ci这种类型的概率
P(ci | w) 表示在数据集中w这个词是ci这种类型的概率
P(w) 表示数据集里w这个词出现的概率
P(w | ci) 表示在ci这种类型里出现w这个词的概率
P(ci) 表示ci这种类型出现的概率
这个时候上述式子是成立的, 如果说把里面的w从一个单词换成一个句子(也就是一个list), 那么因为朴素贝叶斯比较naive, 各个单词出现的概率相互独立, 所以P(w | ci) = P(w1, w2, w3…wn | ci) = P(w1| ci) * P(w2 | ci) * P(w3 | ci) * … * P(wn | ci)
我们来分析分析, 这个时候如果已知了右边数据集的信息(也就是我们的样本数据), 就可以得到一个段话是哪种类型的概率更高, 这个时候就把它当成是哪种类型. so easy, 是时候走一波贝叶斯了, 新建naive_bayes.py
#!/usr/bin/python
# coding: utf-8
from numpy import *
import file2data
import operator
def trainNB0(data_set, labels):
p_total = {} # 各种类型的总概率
p_words = {} # 各种类型下,vocab_list中对应的词的概率
count_words = {} # 各种类型下, 各个词的数量
total_words = {} # 各种类型下, 总词数
data_set = array(data_set)
for i in range(len(data_set)):
# 计算各种类型下的总词数
if total_words.has_key(labels[i]):
total_words[labels[i]] += sum(data_set[i])
count_words[labels[i]] += data_set[i]
else:
# 避免可能出现0, 给每个单词的初始至少是1, 则对应的总单词数至少初始为2
total_words[labels[i]] = sum(data_set[i]) + 2
words = array(data_set[i] + ones(len(data_set[i])))
count_words[labels[i]] = words
# set获取类型集
clazzes = set(labels)
for clazz in clazzes:
# 计算每种类型的概率
p_total[clazz] = labels.count(clazz) / float(len(labels))
# 计算每种类型中各个单词的概率
p_words[clazz] = log(count_words[clazz] / float(total_words[clazz]))
return p_words, p_total
def classifyNB(data2classify, p_words, p_total):
p = {}
for clazz in p_total:
# 计算是每种类型的概率
# 概率取log是为了避免小概率乘来乘去太小了都木有了, 所以log之后的数据相加就好了(因为原来是相乘,log之后就应该是相加)
p[clazz] = sum(data2classify * p_words[clazz]) + log(p_total[clazz])
# 字典排序取最大概率的类型
sorted_p = sorted(p.iteritems(), key=operator.itemgetter(1), reverse=True)
return sorted_p[0][0]
if __name__ == '__main__':
# 获取数据集
data_set, labels, vocab_list = file2data.get_data_set()
# 计算数据集中各个类型下单词的概率和整个数据集中各个类型的概率
p_words, p_total = trainNB0(data_set, labels)
# 测试一下 "codeine cheap visa" 句话是啥吧(这是我偷偷从spam里面找的仨词)
data2classify = file2data.data2vocab_list(['codeine', 'cheap', 'visa'], vocab_list)
print classifyNB(data2classify, p_words, p_total)
程序结果是 spam, 还不错毕竟是我从spam里面找出来的词儿…..
想要试试到底行不行, 那就把数据集分成两部分, 一部分用来做训练, 一部分用来做测试, 新建 application.py
#!/usr/bin/python
# coding: utf-8
import file2data
import naive_bayes
import random
def split_data_set(data_set_org, labels_org):
# 此处使用[:]方法复制数据集, 避免修改原始数据集
data_set = data_set_org[:]
labels = labels_org[:]
test_set = []
test_labels = []
for i in range(10):
# 随机取10个数据
rand = int(random.uniform(0, len(data_set)))
# 取到的数据从数据集中删除并加入测试集中
test_set.append(data_set[rand])
del(data_set[rand])
test_labels.append(labels[rand])
del(labels[rand])
return data_set, labels, test_set, test_labels
if __name__ == '__main__':
# 从文件中读取原始数据集
data_set_org, labels_org, vocab_list_org = file2data.get_data_set()
total = 0.0
num = 0.0
for k in range(100):
# 循环测试100次
data_set, labels, test_set, test_labels = split_data_set(data_set_org, labels_org)
p_words, p_total = naive_bayes.trainNB0(data_set, labels)
for i in range(len(test_set)):
# 每次测试10个随机出来的数据集
total += 1
clazz = naive_bayes.classifyNB(test_set[i], p_words, p_total)
if clazz != test_labels[i]:
num += 1
print "error rate: ", num / total
多试试几次, 大概都是在6%点多, 感觉还不错….