python版朴素贝叶斯(一)

1. 贝叶斯的知识导图


朴素贝叶斯法.png

2. 几个通俗易懂的例子

这篇文章用了四个例子简单介绍了贝叶斯的核心思想
看完这四个例子,当时留下了几个疑问。

  1. 窝草这种计算方法他们是怎么想出来的,好牛逼。
  2. 没有如果出现了一种全新的特征怎么办?

3. 正儿八经的看一看:贝叶斯分类器

3.1 贝叶斯定理

一个数据集中有N种标签,记为Y=\{c_1, c_2, ..., c_n \},每一种标签所对应的n个特征值记为\boldsymbol{x}(x是一个向量x=[x_1, x_2,...,x_n]),那么,对于贝叶斯定理来说,有如下公式:
\begin{align} P(c|x)&=\frac{P(x, c)}{P(x)}\\ P(c|x)&=\frac{P(x|c)·P(c)}{P(x)} \end{align}\tag{3.1}
将公式3.1应用于每个标签,可以计算出每个标签对应的概率,计算概率最大的标签即为预测的值。

3.2 为什么要假设满足“属性条件独立假设”


疾病分类例子

问,一个打喷嚏的建筑工人,患上感冒的概率有多大?

看着貌似很简单,但是,这样有一个问题

  1. 当特征的数量比较多的时候,它们组合的数据量是爆炸性增长的。
    以病人分类为例:2个特征\boldsymbol{x}=[症状, 职业]^T
    “症状”特征有2种取值(x_1=\{打喷嚏, 头痛\}
    “职业”特征有4种取值:x_2=\{护士, 农夫, 建筑工人, 教师\}
    那么,通过排列组合,共有16种特征组合(打喷嚏+护士、打喷嚏+农夫、打喷嚏+建筑工人。。。)。
    这样的数量是无法容忍的。
  2. 由于特征之间组合的数量过多,在实际的数据集中可能有的特征组合根本没有出现,比如(头痛+护士)到底算什么疾病?(注意,这里不能记为0,因为“未被观测到”和“出现概率为0”是两码事)。

所以,就有一个大前提:假设所有属性之间相互独立,在此基础上,则公式3.1可以写成
\begin{align} P(c|x)&=\frac{P(x|c)·P(c)}{P(x)}\\ &=\frac{P(c)}{P(x)}·\prod ^{n}_{i=1}P(x_i|c)\tag{3.2} \end{align}

  • 其中,n为特征的数目,x_i\boldsymbol{x}在第i个属性上的取值。

  • P(c)称为类先验概率
    \begin{align} P(c)=\frac{|D_c|}{|D|}\tag{3.3} \end{align}
    P(疾病=感冒)=\frac{3}{6}P(疾病=过敏)=\frac{1}{6}P(疾病=脑震荡)=\frac{2}{6}

  • P(x)是证据因子,即所取特征的概率相乘,对于类标记均相同。
    (例如P(症状=打喷嚏)=\frac{1}{2}P(工作=建筑工人)=\frac{2}{6}

  • \prod ^{n}_{i=1}P(x_i|c)是样本x对于类标记c类条件概率(似然)

    • 对于离散数据而言,Y_{c,x_i}表示Y_{c}中在第i个属性上取值为x_i的样本组成的集合:
      \begin{align} P(x_i|c)=\frac{|Y_{c,x_i}|}{|Y_c|}\tag{3.4} \end{align}
      (例如P(症状=打喷嚏|疾病=感冒)=\frac{2}{3}P(工作=建筑工人|疾病=感冒)=\frac{1}{3}
    • 对于连续数据而言:
      \begin{align} P(x_i|c)=\frac{1}{\sqrt{2 \pi} \sigma_{c,i}}exp{(-\frac{(x_i-\mu_{c,i})^2}{2\sigma_{c,i}^2})}\tag{3.5} \end{align}
      假定p(x_i|c) \sim N(\mu_{c,i}, \sigma_{c,i}^2)。其中,\mu_{c,i}, \sigma_{c,i}^2分别为c类标签在第i个属性上的均值和方差。

3.3 要是出现了未知特征怎么办?

在估计概率值是需要进行“平滑”处理,常用的方法是“拉普拉斯修正”,公式为:
\begin{align} P(c)=\frac{|D_c|+1}{|D|+N}\tag{3.6} \end{align}
N为训练集中可能的类别数(疾病分类中为3(感冒、过敏、脑震荡))。
\begin{align} P(x_i|c)=\frac{|Y_{c,x_i}|+1}{|Y_c|+N_i}\tag{3.7} \end{align}
N_i为第i个属性可能的取值数(疾病分类中,职业属性的数为:4(护士、建筑工人、教师、农夫))。

3.3 这跟极大似然估计有什么关系?

3.3.1 极大似然估计

这篇文章用了四个例子简单介绍了贝叶斯的核心思想,这四个例子都是掰着手指头数一数,就能算出所有的概率,好像没有用到所谓的极大似然估计这个概念?(啥是似然函数?

在统计学中,数学家们认为:当数据足够多的情况下,每一种特征的都是按照一定规律出现的,而这种“规律”是可以通过数学表达式计算得出。寻找表达式的过程就是参数估计,筛选出的最好的表达式的过程极大似然估计

所以,概率模型的训练过程就是参数估计的过程。而贝叶斯学派的人认为:如果一个训练集Y中的第k类样本的集合表示为Yc_k,假设这些样本是独立同分布的,则参数\theta_c对于数据集Yc_k的似然是这样的:
\begin{align} P(Yc_k|\theta_c)&=\prod _{x \in Yc_k}P(x|\theta_c) \tag{3.8} \end{align}

3.3.2 对数似然

公式3.8在连乘操作下,很容易有溢出的问题,因此可以使用对数似然进行分析。
\begin{align} LL(\theta_c)&=log P(Yc_k|\theta_c)\\ &=\sum_{x \in Yc_k}log P(x|\theta_c) \end{align}


4.pyhton代码实现

def getData():
    '''
    获取数据
    :return: 返回数据集,特征值名称以及标签类名称
    '''
    dataset = pd.DataFrame({
        'x1': [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3],
        'x2': ['S', 'M', 'M', 'S', 'S', 'S', 'M', 'M', 'L', 'L', 'L', 'M', 'M', 'L', 'L'],
        'Y': [-1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1]}
    )
    feature_names = ['x1', 'x2']
    label_names = 'Y'
    return dataset, feature_names, label_names
class NativeBayesModel(object):
    def __init__(
            self,
            dataset: pd.DataFrame,
            features_names: list,
            label_names: str,
    ):
        '''
        获取训练数据
        :param features_names: 数据的特征值的列名
        :param label_names: 数据的标签值的列名
        '''
        self.features_names = features_names
        if len(label_names) != 1:
            raise Exception('label numbers must be 1.')
        else:
            self.label_names = label_names
        # 类先验概率
        self.prior_prob = {}
        # 证据因子
        self.evidence_prob = {}
        # 类条件概率
        self.class_conditional_prob = {}
        # 给证据因子初始化
        for ifeatures in features_names:
            self.evidence_prob[ifeatures] = {}
        self.features_stat, self.label_stat = self.getStatistic(dataset)
    def getStatistic(self, dataset: pd.DataFrame):
        '''
        对每一类进行统计,存储于label_stat 和 features_stat 中
        :param features_names: 数据的特征值的列名
        :param label_names: 数据的标签值的列名
        :return: 特征值和标签值的统计结果
        '''
        # 数据特征值的列名
        features = dataset[self.features_names]
        # 数据标签值的列名
        labels = dataset[self.label_names]
        # 把统计的结果转化成字典形式
        label_stat = dict(labels.value_counts())
        features_stat = {}
        # 按照特征把统计的结果转化成字典形式
        for i in self.features_names:
            features_stat[i] = dict(features[i].value_counts())
        return features_stat, label_stat
    def getPriorProb(self, dataset_nums: int, regular=False):
        '''
        计算先验概率(类概率)
        :param label_stat: 标签的统计结果
        :param regular: 是否需要拉普拉斯修正标志
        :return:
        '''
        # 如果不用拉普拉斯修正
        if regular is False:
            for iclass, counts in self.label_stat.items():
                self.prior_prob[iclass] = counts / dataset_nums
        else:
            for iclass, counts in self.label_stat.items():
                self.prior_prob[iclass] = (counts+1) / (dataset_nums+len(self.label_stat))
    def getEvidenceProb(self, dataset_nums: int):
        '''
        计算证据因子,虽然对最后类标签的选择没啥卵用
        :param features_stat: 特征的统计结果
        :return:
        '''
        for ifeature_name in self.features_names:
            for ifeature, counts in self.features_stat[ifeature_name].items():
                self.evidence_prob[ifeature_name][ifeature] = counts / dataset_nums
    def getConditionData(self, dataset: pd.DataFrame):
        '''
        根据目标值,筛选数据
        :param dataset:
        :return: 筛选依据(标签值)筛选后的数据
        '''
        new_dataset = {}
        for iclass in self.label_stat:
            # 类条件概率初始化
            self.class_conditional_prob[iclass] = {}
            # 按照类划分数据集
            new_dataset[iclass] = dataset[dataset[self.label_names] == iclass]
        return new_dataset
    def getClassConditionalProb(self, dataset, target, iclass, regular=False):
        '''
        计算类条件概率:P(feature_i = ifeature | class = iclass)
        :param dataset: 仅包含第iclass 类的子数据集
        :param target:  目标数据的特征值,字典形式
        :param iclass:  类中的标签
        :param regular: 是否需要拉普拉斯修正标志
        :return: 计算结果为
        {
            class : {
                feature_name: {
                    features
                }
            }
        }
        '''
        for target_feature_name, target_feature in target.items():
            condition_dataset = dataset[dataset[target_feature_name] == target_feature]
            if target_feature_name not in self.class_conditional_prob[iclass]:
                self.class_conditional_prob[iclass][target_feature_name] = {}
            if target_feature not in self.class_conditional_prob[iclass][target_feature_name]:
                self.class_conditional_prob[iclass][target_feature_name][target_feature] = {}
            # 如果使用拉普拉斯修正
            if regular is False:
                prob = condition_dataset.shape[0] / dataset.shape[0]
            else:
                prob = (condition_dataset.shape[0]+1) / (dataset.shape[0]+len(self.features_stat[target_feature_name]))
            self.class_conditional_prob[iclass][target_feature_name][target_feature] = prob
    def getPredictClass(self, target):
        # 计算类别
        max_prob = 0
        predict_class = None
        for iclass in self.label_stat:
            prob = nb.prior_prob[iclass]
            for target_feature_name, target_feature in target.items():
                prob *= nb.class_conditional_prob[iclass][target_feature_name][target_feature]
            print('label', iclass, '\'s probability is:', prob)
            predict_class = iclass if prob > max_prob else predict_class
        return predict_class
if __name__ == '__main__':
    # 是否需要拉普拉斯修正
    regular_state = True
    dataset, feature_names, label_names = getData()
    dataset_nums = dataset.shape[0]
    target = {
        'x1': 2,
        'x2': 'S'
    }
    nb = NativeBayesModel(dataset, feature_names, label_names)
    # 对dataset特征和标签进行统计
    # 计算先验概率
    nb.getPriorProb(dataset_nums, regular=regular_state)
    # 计算证据因子
    nb.getEvidenceProb(dataset_nums)
    # 将数据集按照类标签划分为多个只包含一类标签的数据集
    subDataset = nb.getConditionData(dataset)
    # 依次计算每类标签的条件概率
    for iclass, subdata in subDataset.items():
        nb.getClassConditionalProb(subdata, target, iclass, regular=regular_state)
    predict_class = nb.getPredictClass(target)
    print('predict label is :', predict_class)

实例

怎样写一个拼写检查器

参考

《统计学习方法》——李航
《机器学习》——周志华
贝叶斯通俗易懂推导

你或许想:《去原作者写文章的地方

300大作战下载
「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论