前言
如果你能找到这里,真是我的幸运~这里是蓝白绛的学习笔记,本集合主要针对《统计学习方法》这本书,主要是基本机器学习算法代码实现的解读。
本篇的代码整理来自github:wepe-Basic Machine Learning and Deep Learning
本篇整理朴素贝叶斯实现,github地址为:wepe-NaiveBayes
有兴趣可以阅读wepe原博文:朴素贝叶斯理论推导与三种常见模型,写的非常详细。
第四章 朴素贝叶斯法
1、模型
- 朴素贝叶斯法通过训练数据集学习联合概率分布。
具体地,学习先验概率分布和条件概率分布朴素贝叶斯法对条件概率分布作了条件独立性的假设。由于这是一个较强的假设,朴素贝叶斯法也因此得名。具体的,条件独立性的假设是
- 朴素贝叶斯法分类时,通过学习到的模型计算后验概率将后验概率最大的类作为的类输出。后验概率计算根据贝叶斯定理进行:最终其实是
注:朴素贝叶斯其实就是:要预测某个数据的种类,则对每个类别,在训练数据中,根据这个类别对应的所有数据,找到当的条件下的概率,并将他们相乘,找到数值对应最大的则为预测结果。
2、朴素贝叶斯法的参数估计
朴素贝叶斯法的参数估计实际就是估计和
- 多项式模型:当特征是离散的时候,使用多项式模型。
- 极大似然估计:实际就是统计个数占比。的估计就是统计训练数据集中每个类别的数据量有多少占比;的估计就是统计当时每个特征的各个取值的占比。 为特征个数; 是特征的可能取值个数; 为数据类别个数。
- 贝叶斯估计:本质也是数个数,但是在极大似然的估计之上,考虑到了估计概率值为0的情况。 当时,称为Laplace平滑。当时,称为Lidstone平滑。
- GaussianNB高斯模型:当特征是连续的时候,用高斯模型。每个特征的分布被假设为高斯分布,通过训练数据可以统计出每个特征分布的均值和方差,的估计为:其中为类别的特征的均值,为类别的特征的方差。
- BernoulliNB伯努利模型:与多项式模型一样,伯努利模型适用于离散特征的情况,所不同的是,伯努利模型中每个特征的取值只能是1和0(以文本分类为例,某个单词在文档中出现过,则其特征值为1,否则为0).
3、代码实现
这里朴素贝叶斯实现原始代码的github地址为:wepe-NaiveBayes,我在此添加了一些注释。
下面第一个代码块是多项式模型的实现,适用于特征均是离散值的情况,连续值的情况需要用到高斯模型。高斯模型在第二个代码块中,定义了GaussianNB类,继承MultinomialNB类并且重载了相应的方法。
class MultinomialNB(object):
def __init__(self, alpha=1.0, fit_prior=True, class_prior=None):
self.alpha = alpha# 多项式模型中的lambda,平滑参数,默认1.0
self.fit_prior = fit_prior# 是否学习先验概率,默认为True
self.class_prior = class_prior# 先验概率,是一个list
self.classes = None# classes为y的可能取值
self.conditional_prob = None# 条件概率
# 对输入的feature的所有取值进行统计,每个取值的占比各是多少,返回一个dict
# 如果是高斯模型,则会对该特征的取值进行统计,返回均值和方差组成的tuple。(方差还是标准差都可以)
def _calculate_feature_prob(self, feature):
values = np.unique(feature)# values为该特征的所有可能取值
total_num = float(len(feature))
value_prob = {}
for v in values:# 对该特征的每个可能取值,计算占比
# 加入平滑后的条件概率计算:
value_prob[v] = ((np.sum(np.equal(feature, v)) + self.alpha) / (total_num + len(values) * self.alpha))
return value_prob
def fit(self, X, y):
# TODO: check X,y
self.classes = np.unique(y)# classes为y的可能取值
# 计算先验概率P( Y=c_k )
if self.class_prior == None:
class_num = len(self.classes)# class_num是y可能取值的种类数
if not self.fit_prior:# 如果设置不学习先验概率,则将先验概率等分,各类别的先验概率相等。
self.class_prior = [1.0 / class_num for _ in range(class_num)] # uniform prior
else:
self.class_prior = []# 学习各类别的先验概率
sample_num = float(len(y))
for c in self.classes:
c_num = np.sum(np.equal(y, c))# 对于每个类别c,在训练数据y中,找到类别为c的数据个数。
# 加入平滑后的先验概率计算:
self.class_prior.append((c_num + self.alpha) / (sample_num + class_num * self.alpha))
print('class_prior:')
print(self.class_prior)
# 计算条件概率: P( X_j=x_j | Y=c_k )
self.conditional_prob = {} # like { c0:{ x0:{ value0:0.2, value1:0.8 }, x1:{} }, c1:{...} }
for c in self.classes:# 对于y的每个类别
self.conditional_prob[c] = {}
for i in range(len(X[0])):# 对于每个特征
feature = X[np.equal(y, c)][:, i]# 筛选出该特征的所有数据
# 计算该特征可能取值的占比,返回关于该特征的dict:
self.conditional_prob[c][i] = self._calculate_feature_prob(feature)
print('conditional_prob:')
print(self.conditional_prob)
return self
# 根据该维特征的条件概率,和该维特征的值,找到对应的条件概率,并返回。
# 这个方法很简单,但是将它拉出来单独作为一个方法的原因是方便重载。
# 这里实现的是多项式模型,仅适用与特征均是离散值的情况。
# 如果特征是连续值,则需要用高斯模型,这里就比较放方便重载。
def _get_xj_prob(self, values_prob, target_value):
return values_prob[target_value]
# 预测单条数据的类别
def _predict_single_sample(self, x):
label = -1# 预测的label初始设为-1
max_posterior_prob = 0# max_posterior_prob存放最大的后验概率,这样就可以不用排序再得到预测类别了
# 对y的每个种类,计算后验概率:
for c_index in range(len(self.classes)):
current_class_prior = self.class_prior[c_index]# 找到对应y种类的先验概率
current_conditional_prob = 1.0# 设置一个概率初值,后面会将各个特征的条件概率相乘
feature_prob = self.conditional_prob[self.classes[c_index]]# 找到对应y种类的条件概率dict
j = 0
# 下面本来是for feature_i in feature_prob.keys():但是dict没有顺序所以进行了修改
for feature_i in range(len(feature_prob)):# 各个特征的key刚好是从0开始的数字,对于每个特征
# 计算条件概率,将各个条件概率乘起来
current_conditional_prob *= self._get_xj_prob(feature_prob[feature_i], x[j])
j += 1
# 计算完条件概率的乘积之后,在乘上该类别的先验概率
# 如果比当前max_posterior_prob大,则修改max_posterior_prob,并修改label。避免排序
if current_class_prior * current_conditional_prob > max_posterior_prob:
max_posterior_prob = current_class_prior * current_conditional_prob
label = self.classes[c_index]
return label
# predict samples (also single sample)
def predict(self, X):
# TODO1:check and raise NoFitError
# ToDO2:check X
if X.ndim == 1:# 如果只是预测单条数据,则调用_predict_single_sample()
return self._predict_single_sample(X)
else:
# 如果预测多条数据,则循环调用_predict_single_sample(),并返回预测结果组成的list
labels = []
for i in range(X.shape[0]):
label = self._predict_single_sample(X[i])
labels.append(label)
return labels
高斯模型定义如下:
class GaussianNB(MultinomialNB):
# 计算输入的特征的均值和方差,这里返回的是均值和标准差组成的tuple
def _calculate_feature_prob(self, feature):
mu = np.mean(feature)
sigma = np.std(feature)
return (mu, sigma)
# 高斯分布的概率密度函数
def _prob_gaussian(self, mu, sigma, x):
return (1.0 / (sigma * np.sqrt(2 * np.pi)) * np.exp(- (x - mu) ** 2 / (2 * sigma ** 2)))
# 根据(mu,sigma),计算得到该维特征取值的条件概率
def _get_xj_prob(self, mu_sigma, target_value):
return self._prob_gaussian(mu_sigma[0], mu_sigma[1], target_value)
总的来说,朴素贝叶斯就是计算各类的先验概率,和各个类别下特征取值的条件概率,最后将各个类别下的特征取值条件概率相乘,并乘上该类别的先验概率,取乘积最大的对应的类别作为预测类别。计算后验概率本来要将各个乘积进行归一化,但是因为只是取最大值对应的类别,所以不需要归一化。
结尾
如果您发现我的文章有任何错误,或对我的文章有什么好的建议,请联系我!如果您喜欢我的文章,请点喜欢~*我是蓝白绛,感谢你的阅读!