贝叶斯
2019-06-25
参考的寒老师多篇博客进行的总结:
https://blog.youkuaiyun.com/han_xiaoyang/article/details/50616559/
1、条件概率与贝叶斯定理
1.1、条件概率
条件概率条件概率是指事件A在另外一个事件B已经发生条件下的发生概率
蒙提霍尔悖论
参赛者面前有三扇关闭着的门,其中一扇的后面是一辆汽车,选中后面有车的那扇门就可以赢得该汽车,而另外两扇门后面则各藏有一只山羊。当参赛者选定了一扇门,但未去开启它的时候,主持人会开启剩下两扇门中的一扇,露出其中一只山羊。主持人其后会问参赛者要不要更换选择,选另一扇仍然关着的门。
- 再不换门的情况下获胜的概率为1/3
- 再换门情况有如下几种:换门获胜的概率为2/3
通过概率计算:
条件概率:
1.2、贝叶斯定理
通过上式中化简得,朴素贝叶斯公式:
-
先验概率:
先验概率仅仅依赖于主观上的经验估计,也就是事先根据已有的知识的推断,先验概率就是没有经过实验验证的概率,根据已知进行的主观臆测 -
后验概率:
后验概率是指在得到“结果”的信息后重新修正的概率
举个例子理解:
假设我们出门堵车的可能因素有两个(就是假设而已,别当真):车辆太多和交通事故。
堵车的概率就是先验概率 。
那么如果我们出门之前我们听到新闻说今天路上出了个交通事故,那么我们想算一下堵车的概率,这个就叫做条件概率 。也就是P(堵车|交通事故)。这是有因求果。
如果我们已经出了门,然后遇到了堵车,那么我们想算一下堵车时由交通事故引起的概率有多大,
那这个就叫做后验概率 (也是条件概率,但是通常习惯这么说) 。也就是P(交通事故|堵车) 。这是有果求因。 -
似然度:
-
标准化常量:
贝叶斯公式展开如下:
2、贝叶斯定理——机器学习视角
把A理解成“具有某特征”
B可以理解为“类别标签”
P ( “ 属 于 某 类 ” ∣ “ 具 有 某 特 征 ” ) = P ( “ 具 有 某 特 征 ” ∣ “ 属 于 某 类 ” ) P ( “ 属 于 某 类 ” ) P ( “ 具 有 某 特 征 ” ) P(“属于某类”|“具有某特征”)=\frac{P(“具有某特征”|“属于某类”)P(“属于某类”)}{P(“具有某特征”)} P(“属于某类”∣“具有某特征”)=P(“具有某特征”)P(“具有某特征”∣“属于某类”)P(“属于某类”)
f
(
B
∣
A
)
=
P
(
A
∣
B
)
P
(
B
)
P
(
A
)
f(B|A)= \frac{P(A|B)P(B)}{P(A)}
f(B∣A)=P(A)P(A∣B)P(B)
f
(
c
l
a
s
s
∣
f
e
a
t
u
r
e
)
=
P
(
f
e
a
t
u
r
e
∣
C
l
a
s
s
)
P
(
C
l
a
s
s
)
P
(
f
e
a
r
t
u
r
e
)
f(class|feature)= \frac{P(feature|Class)P(Class)}{P(fearture)}
f(class∣feature)=P(fearture)P(feature∣Class)P(Class)
2.1、垃圾邮件识别
判断 P(“垃圾邮件”∣“具有某特征”)是否大于1/2
训练数据:10000垃圾邮件、10000正常邮件
A =”我司可办理正规发票(保真)17%增值税发票点数优惠!”
计算后验概率时,如果按句子进行判断,则统计句子在语料中的频数,这样看来时稀疏且不合理的,难以满足大数定律,算出的概率会失真。数据集不能够覆盖所有的句子,而且完全可以构造一句不在训练语料中的句子出来。
为此引入了分词技术!!
句子是无限的,但是词语是有限的~~
拿分词后的序列作为特征:(可以再加入停用词和关键字技术)
“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”
2.2、条件独立假设——朴素贝叶斯(Naive Bayes)
假设各个特征之间是相互独立的(即各个单词,没有考虑前后顺序和上下文之间的语义)
这样处理后式子中的每一项都变得好求了,只需要分别统计各类邮件中该关键词出现得概率,例如:
朴素贝叶斯是简化了计算过程,但是存在一定得影响,没有考虑词语之间得现后顺序,不同得顺序可能会导致完全不同得含义。
eg:“武松打死了老虎”与“老虎打死了武松”会被认为是一个意思。
但是效果确实惊人得好!!
为什么呢?“有人对此提出了一个理论解释,并且建立了什么时候朴素贝叶斯的效果能够等价于非朴素贝叶斯的充要条件,这个解释的核心就是:有些独立假设在各个分类之间的分布都是均匀的所以对于似然的相对大小不产生影响;即便不是如此,也有很大的可能性各个独立假设所产生的消极影响或积极影响互相抵消,最终导致结果受到的影响不大。具体的数学公式请参考这篇paper 。
2.3、垃圾邮件——重复词处理方式
1、多项式模型
2、伯努利模型
3、混合模型
第三种方式是在计算句子概率时,不考虑重复词语出现的次数,但是在统计计算词语的概率P(“词语”|S)时,却考虑重复词语的出现次数,这样的模型可以叫作混合模型。
2.4、平滑技术
在朴素贝叶斯计算中,会存在概率为0得情况。(eg,训练模型中没有出现过词语“正规发票”,这样就会出现概率为0)面对这种情况引入了平滑技术。
1、对于伯努利模型
2、对于多项式模型
3、垃圾邮件识别——贝叶斯模型的技巧
3.1、为什么不直接匹配关键词来识别垃圾邮件
错误率高
关键词得变形会影响匹配(eg,“涐司岢办理㊣規髮票,17%增値稅髮票嚸數優蕙”),而且关键词也是不断变化的,需要大量的工作整理。
而,朴素贝叶斯就不会出现这种情况,他是基于样本数据的概率统计
3.2、技巧
1、取对数
在进行似然函数P(X|C)概率计算的时候会涉及到一堆概率的乘积,如下图所示,
计算的时间开销比较大,为此引入对数函数log,这样就变成了加法,左右两边同时取对数,得到如下所示结果:
在训练模型的时候就进行log计算,保存在模型中,实时分类时就会提升运算速度。
2、转换为权重
3、选取topk的关键词
这样导致log(C/C^)的结果都是大于0的,利用一个整数阈值来判别邮件。
4、分割样本
选取topk个词语的方法对于篇幅变动不大的邮件样本比较有效。但是对篇幅过大或者过小的邮件则会有判断误差。
比如这个垃圾邮件的例子:(“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”)。分词出了10个词语,其中有“正规发票”、“发票”2个关键词。关键词的密度还是蛮大的,应该算是敏感邮件。但因为采用最高15个词语的权重求和,并且相应的阈值是基于15个词的情况有效,可能算出来的结果还小于之前的阈值,这就造成漏判了。
类似的,如果一封税务主题的邮件有1000个词语,其中只有“正规发票”、“发票”、“避税方法”3个权重比较大的词语,它们只是在正文表述中顺带提到的内容。关键词的密度被较长的篇幅稀释了,应该算是正常邮件。但是却被阈值判断成敏感邮件,造成误判了。
这两种情况都说明topk关键词的方法需要考虑篇幅的影响。这里有许多种处理方式,它们的基本思想都是选取词语的个数及对应的阈值要与篇幅的大小成正比,本文只介绍其中一种方方法:
对于长篇幅邮件,按一定的大小,比如每500字,将其分割成小的文本段落,再对小文本段落采用topk关键词的方法。只要其中有一个小文本段落超过阈值就判断整封邮件是垃圾邮件。
对于超短篇幅邮件,比如50字,可以按篇幅与标准比较篇幅的比例来选取topk,以确定应该匹配关键词语的个数。比如选取
50
500
×
15
≈
2
\frac{50}{500}×15≈2
50050×15≈2进行匹配,相应的阈值可以是之前阈值的
2
5
\frac{2}{5}
52
。以此来判断则更合理。
5、位置权重
在权重的基础上再乘以一个位置权重。例如,在标题中出现的词语要比在正文中出现的词语对文本的影响要大。
6、更新模型
而搜集最新垃圾邮件有一个技巧,就是随便注册一些邮箱,然后将它们公布在各大论坛上。接下来就坐等一个月,到时候收到的邮件就绝大部分都是垃圾邮件了(好奸诈)。再找一些正常的邮件,基本就能够训练了。这些用于自动搜集垃圾邮件的邮箱叫做“蜜罐”。" 蜜罐“是网络安全领域常用的手段,因其原因类似诱捕昆虫的装有蜜的罐子而得名。
4、贝叶斯方法的思维方式
4.1、逆概率问题:
P(Y∣X) 不能通过直接观测来得到结果,而P(X∣Y) P(X|Y)P(X∣Y) 却容易通过直接观测得到结果,就可以通过贝叶斯公式从间接地观测对象去推断不可直接观测的对象的情况。
4.2、处理多分类概率
如下,对于三分类问题,由于分母P(X)一样,可以忽略不计。
其中的P(X|Yi)可以叫做”似然函数“
4.3、先验概率问题
在先验概率一样的情况下,上面的式子可以进一步简化:
比如之前用最大似然法算出Y1 垃圾邮件的概率大,但是因为P(Y1)特别小,用贝叶斯方法得出的结果是Y2 私人邮件的概率大。那相信哪个呢?
在我们对于先验概率一无所知时,只能假设每种猜测的先验概率是均等的(其实这也是人类经验的结果),这个时候就只有用最大似然了。
在现实运用过程中如果发现最大似然法有偏差,可以考虑对不同的似然函数设定一些系数或者阈值,使其接近真实情况。
5、常见应用
5.1、褒贬分析
流程:
- 首先是用爬虫将微博上提到这个电影名字的微博全都抓取下来,比如有10万条。
- 然后用训练好的朴素贝叶斯分类器分别判断这些微博对电影是好评还是差评。
- 最后统计出这些好评的影评占所有样本中的比例,就能形成微博网友对这个电影综合评价的大致估计。
训练分类器:
- 训练与测试样本:豆瓣影评的网友评论,用爬虫抓取下100万条。
- 标签:3星以上的是好评,3星以下的是差评。
- 特征:豆瓣评论分词后的词语。一个简单的方法是只选择其中的形容词,网上有大量的情绪词库可以为我们所用。
- 然后再用常规的朴素贝叶斯方法进行训练。
特征提取的一些技巧tricks:
否定句的特别处理,否定词与句尾标点之间的形容词都采用其否定形式。
使用伯努利模型代替多项式模型,一些最相关的情感词在文本中可能只出现一次,这样词频模型作用有限,甚至是负作用。
副词的影响,一般使用语言模型或者加入词性标注信息进行综合判定。
技术难点:
情绪表达的含蓄微妙:“导演你出来,我保证不打死你。”你让机器怎么判断是褒还是贬?
转折性表达:“我非常喜欢这些大牌演员,非常崇拜这个导演,非常赞赏这个剧本,非常欣赏他们的预告片,我甚至为了这部影片整整期待了一年,最后进了电影院发现这是个噩梦。” 五个褒义的形容词、副词对一个不那么贬义的词。机器自然判断成褒义,但这句话是妥妥的贬义。
5.2、拼写纠错
也是分类问题:
- 非词错误(Non-word Errors):指那些拼写错误后的词本身就不合法,如将“wifi”写成“wify”;
- 真词错误(Real-word Errors):指那些拼写错误后的词仍然是合法的情况,如将“wifi”写成“wife”。
下面讨论非词错误:
什么是编辑距离?
-
标签:通过计算错误词语的最小编辑距离(之前咱们提到过的),获取最相似的候选词,每个候选词作为一个分类。
-
特征:拼写错误的词本身。因为它就一个特征,所以没有什么条件独立性假设、朴素贝叶斯啥的。它就是纯而又纯的贝叶斯方法。
-
判别公式:
-
训练样本1:该场景下的正常用词语料库,用于计算P(候选词i) 。
-
训练样本2:该场景下错误词与正确词对应关系的语料库,用于计算P(错误词∣候选词i) P(错误词|候选词i)P(错误词∣候选词i)
由于自然语言的特点,有一些tricks需要注意:
-
据统计,80%的拼写错误编辑距离为1,几乎所有的拼写错误编辑距离小于等于2。我们只选择编辑距离为1或2的词作为候选词,这样就可以减少大量不必要的计算。
-
由于我们只选择编辑距离为1或2的词,其差别只是一两个字母级别差别。因此计算似然函数的时候,可以只统计字母层面的编辑错误,这样搜集的样本更多,更满足大数定律,也更简单。对于编辑距离为1的似然函数计算公式可以进化为:
如果计算P(wify|wifi)=p(wify)/p(wifi),or P(wify|wife)=p(wify)/p(wife) 样本是怎么来的,带标签的数据?
特征:wify,标签“wifi"
…
特征:wiii,标签“wifi"
…
特征:wify,标签“wife"
则P(wifi) = 标签为”wifi"的样本的频数 / 样本总数
P(wify) = 标签为”wifi"的样本中特征为“wify"的频数 -
键盘上临近的按键更容易拼写错误,据此可以对上面这个条件概率进行加权。
6、朴素贝叶斯实战和进阶
1、贝叶斯方法的优缺点
优点
-
快:对待预测样本进行预测,过程简单速度快(想想邮件分类的问题,预测就是分词后进行概率乘积,在log域直接做加法更快)。
-
对于多分类问题也同样很有效,复杂度也不会有大程度上升。
-
在分布独立这个假设成立的情况下,贝叶斯分类器效果奇好,会略胜于逻辑回归,同时我们需要的样本量也更少一点。
-
对于类别类的输入特征变量,效果非常好。对于数值型变量特征,我们是默认它符合正态分布的。
缺点
- 对于测试集中的一个类别变量特征,如果在训练集里没见过,直接算的话概率就是0了,预测功能就失效了。当然,我们前面的文章提过我们有一种技术叫做 平滑 操作,可以缓解这个问题,最常见的平滑技术是拉普拉斯估测。
而拉普拉斯平滑就是将上式修改为: p(x1|c1)= (n1 + 1) / (n + N),N是所有单词的数目。修正分母是为了保证概率和为1。
-
那个…咳咳,朴素贝叶斯算出的概率结果,比较大小还凑合,实际物理含义…恩,别太当真。
-
朴素贝叶斯有分布独立的假设前提,而现实生活中这些predictor很难是完全独立的。
2、最常见的应用场景
- 文本分类/垃圾文本过滤/情感判断:在文本分类场景中是很好的,多分类简单,而且文本之间相互独立是成立的。
- 多分类实时预测
- 推荐系统:朴素贝叶斯和协同过滤(Collaborative Filtering)是一对好搭档,协同过滤是强相关性,但是泛化能力略弱,朴素贝叶斯和协同过滤一起,能增强推荐的覆盖度和效果
3、注意点
- 连续数值型一定要调整成满足正态分布!
- 平滑技术的使用
- 相关特征去掉,避免相关度高的两个特征在模型中发挥两次作用。
- 朴素贝叶斯可调的参数比较少,比如sklearn中朴素贝叶斯可调节的参数用拉普拉斯平滑因子alpha、类别先验概率class_prior和预算数据类别先验fit_prior。因此需要集中精力进行数据的预处理和特征选择。
- 一般的模型可以通过bagging和boosting集成学习进行增强,目的是为了减少过拟合,减少variance,然而朴素贝叶斯没有variance可以减少。
模型的过拟合,不同的训练数据导致训练的模型差异很大,很不稳定,而弱模型在不同的训练样本上的性能差异不大,因此模型方差小。
朴素贝叶斯 是在假设条件独立的前提条件下的,模型简化了,所以是低方差的。
4、朴素贝叶斯训练、建模
sklearn中有3中不同类型的朴素贝叶斯:
- 高斯分布型:属性/特征服从正态分布
- 多项式型:用于离散值模型中
- 伯努利型:0 or 1
简单示例:
# 我们直接取iris数据集,这个数据集有名到都不想介绍了..
# 其实就是根据花的各种数据特征,判定是什么花
from sklearn import datasets
iris = datasets.load_iris()
iris.data[:5]
#array([[ 5.1, 3.5, 1.4, 0.2],
# [ 4.9, 3. , 1.4, 0.2],
# [ 4.7, 3.2, 1.3, 0.2],
# [ 4.6, 3.1, 1.5, 0.2],
# [ 5. , 3.6, 1.4, 0.2]])
#我们假定sepal length, sepal width, petal length, petal width 4个量独立且服从高斯分布,用贝叶斯分类器建模
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
y_pred = gnb.fit(iris.data, iris.target).predict(iris.data)
right_num = (iris.target == y_pred).sum()
print("Total testing num :%d , naive bayes accuracy :%f" %(iris.data.shape[0], float(right_num)/iris.data.shape[0]))
# Total testing num :150 , naive bayes accuracy :0.96000
5、文本主题分类器
1、新闻数据分类
数据 :搜狐新闻数据 一个文本一个txt 需要合并文件到一个txt中,一行一篇文章,id(类别)+ 正文
类别:it、汽车、财经、健康等9类
随机选取3/5的数据作为训练集,2/5的数据作为测试集,采用互信息对文本特征进行提取,提取出1000个左右的特征词。然后用朴素贝叶斯分类器进行训练,实际训练过程就是对于特征词,统计在训练集和各个类别出现的次数,测试阶段做预测也是扫描一遍测试集,计算相应的概率。因此整个过程非常高效,完整的运行代码如下:
#!encoding=utf-8
import sys, math, random, collections
def shuffle(inFile):
'''
简单的乱序操作,用于生成训练集和测试集
'''
textLines = [line.strip() for line in open(inFile)]
print "正在准备训练和测试数据,请稍后..."
random.shuffle(textLines)
num = len(textLines)
trainText = textLines[:3*num/5]
testText = textLines[3*num/5:]
print "准备训练和测试数据准备完毕,下一步..."
return trainText, testText
#总共有9种新闻类别,我们给每个类别一个编号
lables = ['A','B','C','D','E','F','G','H','I']
def lable2id(lable):
for i in xrange(len(lables)):
if lable == lables[i]:
return i
raise Exception('Error lable %s' % (lable))
def doc_dict():
'''
构造和类别数等长的0向量
'''
return [0]*len(lables)
def mutual_info(N,Nij,Ni_,N_j):
'''
计算互信息,这里log的底取为2
'''
return Nij * 1.0 / N * math.log(N * (Nij+1)*1.0/(Ni_*N_j))/ math.log(2)
def count_for_cates(trainText, featureFile):
'''
遍历文件,统计每个词在每个类别出现的次数,和每类的文档数
并写入结果特征文件
'''
docCount = [0] * len(lables)
wordCount = collections.defaultdict(doc_dict())
#扫描文件和计数
for line in trainText:
lable,text = line.strip().split(' ',1)
index = lable2id(lable[0])
words = text.split(' ')
for word in words:
wordCount[word][index] += 1
docCount[index] += 1
#计算互信息值
print "计算互信息,提取关键/特征词中,请稍后..."
miDict = collections.defaultdict(doc_dict())
N = sum(docCount)
for k,vs in wordCount.items():
for i in xrange(len(vs)):
N11 = vs[i]
N10 = sum(vs) - N11
N01 = docCount[i] - N11
N00 = N - N11 - N10 - N01
mi = mutual_info(N,N11,N10+N11,N01+N11) + mutual_info(N,N10,N10+N11,N00+N10)+ mutual_info(N,N01,N01+N11,N01+N00)+ mutual_info(N,N00,N00+N10,N00+N01)
miDict[k][i] = mi
fWords = set()
for i in xrange(len(docCount)):
keyf = lambda x:x[1][i]
sortedDict = sorted(miDict.items(),key=keyf,reverse=True)
for j in xrange(100):
fWords.add(sortedDict[j][0])
out = open(featureFile, 'w')
#输出各个类的文档数目
out.write(str(docCount)+"\n")
#输出互信息最高的词作为特征词
for fword in fWords:
out.write(fword+"\n")
print "特征词写入完毕..."
out.close()
def load_feature_words(featureFile):
'''
从特征文件导入特征词
'''
f = open(featureFile)
#各个类的文档数目
docCounts = eval(f.readline())
features = set()
#读取特征词
for line in f:
features.add(line.strip())
f.close()
return docCounts,features
def train_bayes(featureFile, textFile, modelFile):
'''
训练贝叶斯模型,实际上计算每个类中特征词的出现次数
'''
print "使用朴素贝叶斯训练中..."
docCounts,features = load_feature_words(featureFile)
wordCount = collections.defaultdict(doc_dict())
#每类文档特征词出现的次数
tCount = [0]*len(docCounts)
for line in open(textFile):
lable,text = line.strip().split(' ',1)
index = lable2id(lable[0])
words = text.split(' ')
for word in words:
if word in features:
tCount[index] += 1
wordCount[word][index] += 1
outModel = open(modelFile, 'w')
#拉普拉斯平滑
print "训练完毕,写入模型..."
for k,v in wordCount.items():
scores = [(v[i]+1) * 1.0 / (tCount[i]+len(wordCount)) for i in xrange(len(v))]
outModel.write(k+"\t"+scores+"\n")
outModel.close()
def load_model(modelFile):
'''
从模型文件中导入计算好的贝叶斯模型
'''
print "加载模型中..."
f = open(modelFile)
scores = {}
for line in f:
word,counts = line.strip().rsplit('\t',1)
scores[word] = eval(counts)
f.close()
return scores
def predict(featureFile, modelFile, testText):
'''
预测文档的类标,标准输入每一行为一个文档
'''
docCounts,features = load_feature_words()
docScores = [math.log(count * 1.0 /sum(docCounts)) for count in docCounts]
scores = load_model(modelFile)
rCount = 0
docCount = 0
print "正在使用测试数据验证模型效果..."
for line in testText:
lable,text = line.strip().split(' ',1)
index = lable2id(lable[0])
words = text.split(' ')
preValues = list(docScores)
for word in words:
if word in features:
for i in xrange(len(preValues)):
preValues[i]+=math.log(scores[word][i])
m = max(preValues)
pIndex = preValues.index(m)
if pIndex == index:
rCount += 1
#print lable,lables[pIndex],text
docCount += 1
print("总共测试文本量: %d , 预测正确的类别量: %d, 朴素贝叶斯分类器准确度:%f" %(rCount,docCount,rCount * 1.0 / docCount))
if __name__=="__main__":
if len(sys.argv) != 4:
print "Usage: python naive_bayes_text_classifier.py sougou_news.txt feature_file.out model_file.out"
sys.exit()
inFile = sys.argv[1]
featureFile = sys.argv[2]
modelFile = sys.argv[3]
trainText, testText = shuffle(inFile)
count_for_cates(trainText, featureFile)
train_bayes(featureFile, trainText, modelFile)
predict(featureFile, modelFile, testText)
2 分类结果
运行结果如下,在6515条数据上,9个类别的新闻上,有84.1%的准确度:
6、Kaggle比赛之旧金山犯罪分类预测
1、背景
大到暴力案件,小到东西被偷,车被划的事情。当地警方也是努力地去总结和想办法降低犯罪率,一个挑战是在给出犯罪的地点和时间的之后,要第一时间确定这可能是一个什么样的犯罪类型,以确定警力等等。
犯罪报告里面包括日期,描述,星期几,所属警区,处理结果,地址,GPS定位等信息
2、查看数据
使用pandas
import pandas as pd
import numpy as np
#用pandas载入csv训练数据,并解析第一列为日期格式
train=pd.read_csv('/Users/Hanxiaoyang/sf_crime_data/train.csv', parse_dates = ['Dates'])
test=pd.read_csv('/Users/Hanxiaoyang/sf_crime_data/test.csv', parse_dates = ['Dates'])
train
- Date: 日期
- Category: 犯罪类型,比如 Larceny/盗窃罪 等.
- Descript: 对于犯罪更详细的描述
- DayOfWeek: 星期几
- PdDistrict: 所属警区
- Resolution: 处理结果,比如说『逮捕』『逃了』
- Address: 发生街区位置
- X and Y: GPS坐标
大部分都是『类别』型
3、特征预处理
对于类别特征,我们用最常见的因子化操作将其转成数值型,比如我们把犯罪类型用因子化进行encode,也就是说生成如下的向量:
星期一/Monday = 1,0,0,0,...
星期二/Tuesday = 0,1,0,0,...
星期三/Wednesday = 0,0,1,0,...
...
我们之前也提到过,用pandas的get_dummies()可以直接拿到这样的一个二值化的01向量。Pandas里面还有一个很有用的方法LabelEncoder可以用于对类别编号。对于已有的数据特征,我们打算做下面的粗略变换:
- 用LabelEncoder对犯罪类型做编号;
- 处理时间,在我看来,也许犯罪发生的时间点(小时)是非常重要的,因此我们会用Pandas把这部分数据抽出来;
- 对街区,星期几,时间点用get_dummies()因子化;
- 做一些组合特征,比如把上述三个feature拼在一起,再因子化一下;
4、朴素贝叶斯和逻辑回归
多分类问题中,Kaggle的评定标准并不是precision,而是multi-class log_loss,这个值越小,表示最后的效果越好。
可以快速地筛出一部分重要的特征,搭建一个baseline系统,再考虑步步优化。比如我们这里简单一点,就只取星期几和街区作为分类器输入特征,我们用scikit-learn中的train_test_split函数拿到训练集和交叉验证集,用朴素贝叶斯和逻辑回归都建立模型,对比一下它们的表现:
ffrom sklearn.cross_validation import train_test_split
from sklearn import preprocessing
from sklearn.metrics import log_loss
from sklearn.naive_bayes import BernoulliNB
from sklearn.linear_model import LogisticRegression
import time
# 只取星期几和街区作为分类器输入特征
features = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', 'BAYVIEW', 'CENTRAL', 'INGLESIDE', 'MISSION',
'NORTHERN', 'PARK', 'RICHMOND', 'SOUTHERN', 'TARAVAL', 'TENDERLOIN']
# 分割训练集(3/5)和测试集(2/5)
training, validation = train_test_split(trainData, train_size=.60)
# 朴素贝叶斯建模,计算log_loss
model = BernoulliNB()
nbStart = time.time()
model.fit(training[features], training['crime'])
nbCostTime = time.time() - nbStart
predicted = np.array(model.predict_proba(validation[features]))
print "朴素贝叶斯建模耗时 %f 秒" %(nbCostTime)
print "朴素贝叶斯log损失为 %f" %(log_loss(validation['crime'], predicted))
#逻辑回归建模,计算log_loss
model = LogisticRegression(C=.01)
lrStart= time.time()
model.fit(training[features], training['crime'])
lrCostTime = time.time() - lrStart
predicted = np.array(model.predict_proba(validation[features]))
log_loss(validation['crime'], predicted)
print "逻辑回归建模耗时 %f 秒" %(lrCostTime)
print "逻辑回归log损失为 %f" %(log_loss(validation['crime'], predicted))
[外链图片转存失败(img-jMIhrOhh-1562567261838)(en-resource://database/623:0)]
我们可以看到目前的特征和参数设定下,朴素贝叶斯的log损失还低一些,另外我们可以明显看到,朴素贝叶斯建模消耗的时间0.640398秒远小于逻辑回归建模42.856376秒。考虑到犯罪类型可能和犯罪事件发生的小时时间点相关,我们加入小时时间点特征再次建模,代码和结果如下:
ffrom sklearn.cross_validation import train_test_split
from sklearn import preprocessing
from sklearn.metrics import log_loss
from sklearn.naive_bayes import BernoulliNB
from sklearn.linear_model import LogisticRegression
import time
# 只取星期几和街区作为分类器输入特征
features = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', 'BAYVIEW', 'CENTRAL', 'INGLESIDE', 'MISSION',
'NORTHERN', 'PARK', 'RICHMOND', 'SOUTHERN', 'TARAVAL', 'TENDERLOIN']
# 分割训练集(3/5)和测试集(2/5)
training, validation = train_test_split(trainData, train_size=.60)
# 朴素贝叶斯建模,计算log_loss
model = BernoulliNB()
nbStart = time.time()
model.fit(training[features], training['crime'])
nbCostTime = time.time() - nbStart
predicted = np.array(model.predict_proba(validation[features]))
print "朴素贝叶斯建模耗时 %f 秒" %(nbCostTime)
print "朴素贝叶斯log损失为 %f" %(log_loss(validation['crime'], predicted))
#逻辑回归建模,计算log_loss
model = LogisticRegression(C=.01)
lrStart= time.time()
model.fit(training[features], training['crime'])
lrCostTime = time.time() - lrStart
predicted = np.array(model.predict_proba(validation[features]))
log_loss(validation['crime'], predicted)
print "逻辑回归建模耗时 %f 秒" %(lrCostTime)
print "逻辑回归log损失为 %f" %(log_loss(validation['crime'], predicted))
[外链图片转存失败(img-ADq7aXDE-1562567261839)(en-resource://database/625:0)]
可以看到在这三个类别特征下,朴素贝叶斯相对于逻辑回归,依旧有一定的优势(log损失更小),同时训练时间很短,这意味着模型虽然简单,但是效果依旧强大。顺便提一下,朴素贝叶斯1.13s训练出来的模型,预测的效果在Kaggle排行榜上已经能进入Top 35% 了,如果进行一些优化,比如特征处理、特征组合等,结果会进一步提高。
7、Kaggle比赛之影评与观影者情感判定
1、数据与预处理
IMDB影评数据: 带标签的 id sentiment review
import re #正则表达式
from bs4 import BeautifulSoup #html标签处理
import pandas as pd
def review_to_wordlist(review):
'''
把IMDB的评论转成词序列
'''
# 去掉HTML标签,拿到内容
review_text = BeautifulSoup(review).get_text()
# 用正则表达式取出符合规范的部分
review_text = re.sub("[^a-zA-Z]"," ", review_text)
# 小写化所有的词,并转成词list
words = review_text.lower().split()
# 返回words
return words
# 使用pandas读入训练和测试csv文件
train = pd.read_csv('/Users/Hanxiaoyang/IMDB_sentiment_analysis_data/labeledTrainData.tsv', header=0, delimiter="\t", quoting=3)
test = pd.read_csv('/Users/Hanxiaoyang/IMDB_sentiment_analysis_data/testData.tsv', header=0, delimiter="\t", quoting=3 )
# 取出情感标签,positive/褒 或者 negative/贬
y_train = train['sentiment']
# 将训练和测试数据都转成词list
train_data = []
for i in xrange(0,len(train['review'])):
train_data.append(" ".join(review_to_wordlist(train['review'][i])))
test_data = []
for i in xrange(0,len(test['review'])):
test_data.append(" ".join(review_to_wordlist(test['review'][i])))
2、特征处理
word2vec:文本到数值域的特征抽取方式
互信息
TF-IDF
本文用的是sklearn中的tf-idf,先对分词后的文本去除停用词并加入2-gram模型
from sklearn.feature_extraction.text import TfidfVectorizer as TFIV
# 初始化TFIV对象,去停用词,加2元语言模型
tfv = TFIV(min_df=3, max_features=None, strip_accents='unicode', analyzer='word',token_pattern=r'\w{1,}', ngram_range=(1, 2), use_idf=1,smooth_idf=1,sublinear_tf=1, stop_words = 'english')
# 合并训练和测试集以便进行TFIDF向量化操作
X_all = train_data + test_data
len_train = len(train_data)
# 这一步有点慢,去喝杯茶刷会儿微博知乎歇会儿...
tfv.fit(X_all)
X_all = tfv.transform(X_all)
# 恢复成训练集和测试集部分
X = X_all[:len_train]
X_test = X_all[len_train:]
3、朴素贝叶斯VS逻辑回归
朴素贝叶斯
# 多项式朴素贝叶斯
from sklearn.naive_bayes import MultinomialNB as MNB
model_NB = MNB()
model_NB.fit(X, y_train) #特征数据直接灌进来
MNB(alpha=1.0, class_prior=None, fit_prior=True)
from sklearn.cross_validation import cross_val_score
import numpy as np
print "多项式贝叶斯分类器20折交叉验证得分: ", np.mean(cross_val_score(model_NB, X, y_train, cv=20, scoring='roc_auc'))
# 多项式贝叶斯分类器20折交叉验证得分: 0.950837239
逻辑回归
# 折腾一下逻辑回归,恩
from sklearn.linear_model import LogisticRegression as LR
from sklearn.grid_search import GridSearchCV
# 设定grid search的参数
grid_values = {'C':[30]}
# 设定打分为roc_auc
model_LR = GridSearchCV(LR(penalty = 'L2', dual = True, random_state = 0), grid_values, scoring = 'roc_auc', cv = 20)
# 数据灌进来
model_LR.fit(X,y_train)
# 20折交叉验证,开始漫长的等待...
GridSearchCV(cv=20, estimator=LogisticRegression(C=1.0, class_weight=None, dual=True,
fit_intercept=True, intercept_scaling=1, penalty='L2', random_state=0, tol=0.0001),
fit_params={}, iid=True, loss_func=None, n_jobs=1,
param_grid={'C': [30]}, pre_dispatch='2*n_jobs', refit=True,
score_func=None, scoring='roc_auc', verbose=0)
#输出结果
print model_LR.grid_scores_