相关知识
朴素贝叶斯分类是一种十分简单的分类算法,朴素贝叶斯的思想基础是这样的:对于给出的待分类项,求解在此项出现的条件下各个类别出现的概率,哪个最大,就认为此待分类项属于哪个类别。通俗来说,就好比这么个道理,你在街上看到一个黑人,我问你你猜这哥们哪里来的,你十有八九猜非洲。为什么呢?因为黑人中非洲人的比率最高,当然人家也可能是美洲人或亚洲人,但在没有其它可用信息下,我们会选择条件概率最大的类别,这就是朴素贝叶斯的思想基础。
朴素贝叶斯分类的正式定义如下:
1.设
x={a1,a2,...,am}
为一个待分类项,而每个a为x的一个特征属性。
2.有类别集合
C={y1,y2,...,yn}
3.计算
P(y1|x), P(y2|x),...,P(yn|x)
4.如果
P(yk|x) = max{P(y1|x),P(y2|x),...,P(yn|x)}
那么现在的关键就是如何计算第3步中的各个条件概率。我们可以这么做:
1.找到一个已知分类的待分类项集合,这个集合叫做训练样本集。
2.统计得到在各类别下各个特征属性的条件概率估计。即
3.如果各个特征属性是条件独立的,则根据贝叶斯定理有如下推导:
因为分母对于所有类别为常数,因为我们只要将分子最大化皆可。又因为各特征属性是条件独立的,所以有:
根据上述分析,朴素贝叶斯分类的流程可以由下图表示(暂时不考虑验证):
第一阶段——准备工作阶段,根据具体情况确定特征属性,并对每个特征属性进行适当划分,然后由人工对部分待分类项进行分类,形成训练样本集。这一阶段的输入是待分类数据,输出是特征属性和训练样本。分类器的质量很大程度上由特征属性、特征属性划分及训练样本质量决定。
第二阶段——分类器训练阶段,主要工作是计算每个类别在训练样本中的出现频率及每个特征属性划分对每个类别的条件概率估计,并将结果记录。其输入是特征属性和训练样本,输出是分类器。
其中需要注意的是,计算条件概率是非常重要的阶段,当特征属性为离散值时,只要统计训练样本中各个划分在每个类别中出现的频率即可进行推算, 而当特征属性为连续值时,通常假定其值服从高斯分布。
另外,为了避免条件概率为0的情况(某个类别下某个特征项划分没有出现),在这里引入了Laplace校准,即对没类别下所有划分的计数加1,这样如果训练样本集数量充分大时,并不会对结果产生影响,并且解决了上述频率为0的情况。
第三阶段——应用阶段。这个阶段的任务是使用分类器对待分类项进行分类,其输入是分类器和待分类项,输出是待分类项与类别的映射关系。最常见应用场景:
(1)文本分类/垃圾文本过滤/情感判别:这大概会朴素贝叶斯应用做多的地方了,即使在现在这种分类器层出不穷的年代,在文本分类场景中,朴素贝叶斯依旧坚挺地占据着一席之地。原因嘛,因为多分类很简单,同时在文本数据中,分布独立这个假设基本是成立的。而垃圾文本过滤(比如垃圾邮件识别)和情感分析(微博上的褒贬情绪)用朴素贝叶斯也通常能取得很好的效果。
(2)多分类实时预测:对于文本相关的多分类实时预测,被广泛应用,简单又高效。
(3)推荐系统:朴素贝叶斯和协同过滤(Collaborative Filtering)是一对好搭档,协同过滤是强相关性,但是泛化能力略弱,朴素贝叶斯和协同过滤一起,能增强推荐的覆盖度和效果。
贝叶斯方法优缺点
优点
(1)对待预测样本进行预测,过程简单速度快(想想邮件分类的问题,预测就是分词后进行概率乘积,在log域直接做加法更快)。
(2)对于多分类问题也同样很有效,复杂度也不会有大程度上升。
(3)在分布独立这个假设成立的情况下,贝叶斯分类器效果奇好,会略胜于逻辑回归,同时我们需要的样本量也更少一点。
(4)对于类别类的输入特征变量,效果非常好。对于数值型变量特征,我们是默认它符合正态分布的。
缺点
(1)对于测试集中的一个类别变量特征,如果在训练集里没见过,直接算的话概率就是0了,预测功能就失效了。当然,有一种技术叫做『平滑』操作,可以缓解这个问题,最常见的平滑技术是拉普拉斯估测。
(2)朴素贝叶斯算出的概率结果,可以比较大小,但不具有实际物理含义。
(3)朴素贝叶斯有分布独立的假设前提,而现实生活中这些predictor很难是完全独立的。
举例:
现有旧金山犯罪的特征数据(sf_crime_data)。sf_crime_data里面包括train与test两部分。在train中有(Dates,Category,Descript,DayOfWeek,PdDistrict,Resolution,Address,X , Y)这9个特征信息,test数据中有(Id,Dates,DayOfWeek,PdDistrict,Address,X , Y)7个特征信息。用朴素贝叶斯(Naive Bayes)模型对旧金山犯罪分类进行预测。
其中
1.Date: 日期
2.Category: 犯罪类型,比如 Larceny/盗窃罪 等.
3.Descript: 对于犯罪更详细的描述
4.DayOfWeek: 星期几
5.PdDistrict: 所属警区
6.Resolution: 处理结果,比如说『逮捕』『逃了』
7.Address: 发生街区位置
8.X and Y: GPS坐标
一、新建python工程,在该项目下,新建python文件,名为data_extract。
将以下代码写入到data_extract.py中:
import pandas as pd
#用pandas载入csv训练数据,并解析第一列为日期格式
train=pd.read_csv('/data/python6/sf_crime_data/train.csv', parse_dates = ['Dates'])
test=pd.read_csv('/data/python6/sf_crime_data/test.csv', parse_dates = ['Dates'])
print(train.shape)
print(train.columns)
二、特征预处理
上述数据中类别和文本型非常多,我们要进行特征预处理,对于类别特征,我们用最常见的因子化操作将其转成数值型,比如我们把犯罪类型用因子化进行encode,也就是说生成如下的向量:
星期一/Monday = 1,0,0,0,…,星期二/Tuesday = 0,1,0,0,…,星期三/Wednesday = 0,0,1,0,…,用pandas的get_dummies()可以直接拿到这样的一个二值化的01向量。Pandas里面还有一个很有用的方法LabelEncoder可以用于对类别编号。对于已有的数据特征,我们打算做下面的粗略变换:
(1)用LabelEncoder对犯罪类型做编号;
(2)处理时间,在我看来,也许犯罪发生的时间点(小时)是非常重要的,因此我们会用Pandas把这部分数据抽出来;
(3)对街区,星期几,时间点用get_dummies()因子化;
(4)做一些组合特征,比如把上述三个feature拼在一起,再因子化一下;
在python项目下,新建Python file,名为feature_ preprocessing。
将以下具体的特征处理代码写入到feature_ preprocessing.py中。
import pandas as pd
import numpy as np
from sklearn.cross_validation import train_test_split
from sklearn import preprocessing
#用pandas载入csv训练数据,并解析第一列为日期格式
train=pd.read_csv('/data/python6/sf_crime_data/train.csv', parse_dates = ['Dates'])
test=pd.read_csv('/data/python6/sf_crime_data/test.csv', parse_dates = ['Dates'])
#用LabelEncoder对不同的犯罪类型编号
leCrime = preprocessing.LabelEncoder()
crime = leCrime.fit_transform(train.Category)
#因子化星期几,街区,小时等特征
days = pd.get_dummies(train.DayOfWeek)
district = pd.get_dummies(train.PdDistrict)
hour = train.Dates.dt.hour
hour = pd.get_dummies(hour)
#组合特征
trainData = pd.concat([hour, days, district], axis=1)
trainData['crime']=crime
trainData.to_csv("/data/python6/sf_crime_data/trainData.csv",index=None)
#对于测试数据做同样的处理
days = pd.get_dummies(test.DayOfWeek)
district = pd.get_dummies(test.PdDistrict)
hour = test.Dates.dt.hour
hour = pd.get_dummies(hour)
testData = pd.concat([hour, days, district], axis=1)
testData.to_csv("/data/python6/sf_crime_data/testData.csv",index=None)
print(trainData.columns)
三、建立模型
我们可以快速地筛出一部分重要的特征,搭建一个baseline系统,再考虑步步优化。比如我们这里简单一点,就只取星期几和街区作为分类器输入特征,我们用scikit-learn中的train_test_split函数拿到训练集和交叉验证集,用朴素贝叶斯和逻辑回归都建立模型,用multi-class log_loss作为评定标准,这个值越小,表示最后的效果越好,对比一下两个模型的表现。
在python项目下,新建Python file,名为:naive_bayes。
将以下具体的代码写入到naive_bayes.py中。
import pandas as pd
import numpy as np
from sklearn.cross_validation import train_test_split
from sklearn.metrics import log_loss
from sklearn.naive_bayes import BernoulliNB
from sklearn.linear_model import LogisticRegression
import time
trainData=pd.read_csv('/data/python6/sf_crime_data/trainData.csv')
#只取星期几和街区作为分类器输入特征
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)))
我们可以看到目前的特征和参数设定下,朴素贝叶斯的log损失还低一些,另外我们可以明显看到,朴素贝叶斯建模消耗的时间0.823952秒远小于逻辑回归建模66.815284秒。
四、优化模型
考虑到犯罪类型可能和犯罪事件发生的小时时间点相关,我们加入小时时间点特征再次建模,
在python项目下,新建Python file,名为:naive_bayes1。将以下具体的代码写入到naive_bayes1.py中。
import pandas as pd
import numpy as np
from sklearn.cross_validation import train_test_split
from sklearn.metrics import log_loss
from sklearn.naive_bayes import BernoulliNB
from sklearn.linear_model import LogisticRegression
import time
8trainData=pd.read_csv('/data/python6/sf_crime_data/trainData.csv')
# 添加犯罪的小时时间点作为特征
features = ['Friday', 'Monday', 'Saturday', 'Sunday', 'Thursday', 'Tuesday',
'Wednesday', 'BAYVIEW', 'CENTRAL', 'INGLESIDE', 'MISSION',
'NORTHERN', 'PARK', 'RICHMOND', 'SOUTHERN', 'TARAVAL', 'TENDERLOIN']
hourFea = [str(x) for x in range(0,24)]
features = features + hourFea
# 分割训练集(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)))
可以看到在这三个类别特征下,朴素贝叶斯相对于逻辑回归,依旧有一定的优势(log损失更小),同时训练时间很短,这意味着模型虽然简单,但是效果依旧强大。