机器学习|朴素贝叶斯代码实现解读

前言

如果你能找到这里,真是我的幸运~这里是蓝白绛的学习笔记,本集合主要针对《统计学习方法》这本书,主要是基本机器学习算法代码实现的解读。
本篇的代码整理来自github:wepe-Basic Machine Learning and Deep Learning
本篇整理朴素贝叶斯实现,github地址为:wepe-NaiveBayes
有兴趣可以阅读wepe原博文:朴素贝叶斯理论推导与三种常见模型,写的非常详细。

第四章 朴素贝叶斯法

1、模型

  1. 朴素贝叶斯法通过训练数据集学习联合概率分布P(X,Y)
    具体地,学习先验概率分布P(Y=c_k),k=1,2,...,K.条件概率分布P(X=x|Y=c_k)=P(X^{(1)}=x^{(1)},...,X^{(n)}=x^{(n)}|Y=c_k),k=1,2,...,K.朴素贝叶斯法对条件概率分布作了条件独立性的假设。由于这是一个较强的假设,朴素贝叶斯法也因此得名。具体的,条件独立性的假设是
    \begin{align} P(X=x|Y=c_k) &=P(X^{(1)}=x^{(1)},...,X^{(n)}=x^{(n)}|Y=c_k) \\ &=\prod_{j=1}^nP(X^{(j)}=x^{(j)}|Y=c_k) \end{align}
  2. 朴素贝叶斯法分类时,通过学习到的模型计算后验概率P(Y=c_k|X=x),将后验概率最大的类作为x的类输出。后验概率计算根据贝叶斯定理进行:P(Y=c_k|X=x)=\frac{P(X=x|Y=c_k)P(Y=c_k)}{\sum_kP(X=x|Y=c_k)P(Y=c_k)}.最终其实是y=\arg\max_{c_k}P(Y=c_k)\prod_jP(X^{(j)}=x^{(j)}|Y=c_k)

注:朴素贝叶斯其实就是:要预测某个数据(x^{(1)},...,x^{(n)})的种类,则对每个类别,在训练数据中,根据这个类别c_k对应的所有数据,找到当Y=c_k的条件下X^{(j)}=x^{(j)},j=1,...,n的概率,并将他们相乘,找到数值对应最大的c_k则为预测结果。

2、朴素贝叶斯法的参数估计

朴素贝叶斯法的参数估计实际就是估计P(Y=c_k)P(X^{(j)}=x^{(j)}|Y=c_k),j=1,...,n.

  1. 多项式模型:当特征是离散的时候,使用多项式模型。
  • 极大似然估计:实际就是统计个数占比。P(Y=c_k)的估计就是统计训练数据集中每个类别的数据量有多少占比;P(X^{(j)}=x^{(j)}|Y=c_k),j=1,...,n.的估计就是统计当Y=c_k时每个特征x^{(j)}的各个取值的占比。P(Y=c_k)=\frac{\sum_{i=1}^NI(y_i=c_k)}{N}. P(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum_{i=1}^NI(x_i^{(j)}=a_{jl},y_i=c_k)}{\sum_{i=1}^NI(y_i=c_k)}. j=1,..,n. n为特征个数;l=1,..,S_j. S_j是特征j的可能取值个数;k=1,...,K. K为数据类别个数。
  • 贝叶斯估计:本质也是数个数,但是在极大似然的估计之上,考虑到了估计概率值为0的情况。P(Y=c_k)=\frac{\sum_{i=1}^NI(y_i=c_k)+\lambda}{N+K\lambda}. P(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum_{i=1}^NI(x_i^{(j)}=a_{jl},y_i=c_k)+\lambda}{\sum_{i=1}^NI(y_i=c_k)+S_j\lambda}.\lambda=1时,称为Laplace平滑。当\lambda\in(0,1)时,称为Lidstone平滑
  1. GaussianNB高斯模型:当特征是连续的时候,用高斯模型。每个特征的分布被假设为高斯分布,通过训练数据可以统计出每个特征分布的均值\mu和方差\sigma^2P(X^{(j)}=x^{(j)}|Y=c_k),j=1,...,n.的估计为:P(X^{(j)}=x^{(j)}|Y=c_k)=\frac{1}{\sqrt{2\pi\sigma^2_{kj}}}\exp(-\frac{(x^{(j)}-\mu_{kj})^2}{2\sigma^2_{kj}})其中\mu_{kj}为类别c_k的特征x_j的均值,\sigma_{kj}^2为类别c_k的特征x_j的方差。
  2. 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)

总的来说,朴素贝叶斯就是计算各类的先验概率,和各个类别下特征取值的条件概率,最后将各个类别下的特征取值条件概率相乘,并乘上该类别的先验概率,取乘积最大的对应的类别作为预测类别。计算后验概率本来要将各个乘积进行归一化,但是因为只是取最大值对应的类别,所以不需要归一化。

结尾

如果您发现我的文章有任何错误,或对我的文章有什么好的建议,请联系我!如果您喜欢我的文章,请点喜欢~*我是蓝白绛,感谢你的阅读!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,966评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,170评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,909评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,959评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,851评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,583评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,956评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,590评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,878评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,892评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,719评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,501评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,957评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,124评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,440评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,003评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,211评论 2 339

推荐阅读更多精彩内容