Python 实战人工智能数学基础:朴素贝叶斯

18 阅读6分钟

1.背景介绍

在数据挖掘、机器学习、模式识别、信息检索、图像处理等领域,基于概率论的统计方法是最重要的方法之一。而贝叶斯公式就是一种简单而有效的解决概率分布判别问题的方法。本文将通过介绍朴素贝叶斯法、概率图模型以及条件概率假设等知识点来详细阐述贝叶斯方法。并通过Python编程语言以真实的数据集演示如何实现分类任务。

2.核心概念与联系

2.1 什么是朴素贝叶斯?

贝叶斯方法(Bayesian Method)是一个利用已知的一些事件及其发生的可能性对某件事情发生性质进行推断的方法。其基本思想是由一系列相互独立的事件组成一个全概率空间,再根据这些事件发生的先后顺序以及每一个事件发生时所占的概率,用相关联的概率规则求出某件事情发生的概率。

2.2 为什么叫做朴素贝叶斯法呢?

因为它是基于贝叶斯定理,也就是说它假设每个变量都服从正态分布。因此,如果某个变量不是服从正态分布的,那么它的朴素贝叶斯算法就无法运作了。所以,朴素贝叶斯法的名称中加入了一个“朴素”二字,表示它只假设变量之间存在着某种依赖关系。换句话说,这意味着该方法所估计的参数是一个“朴素”的或简单的概率分布,而且这种概率分布往往难以捕捉到实际情况中的复杂结构。

2.3 什么是概率图模型?

概率图模型(Probabilistic Graphical Model, PGM)是一种概率模型,它把随机变量以及它们之间的关系用图的方式呈现出来。这种图上的节点代表随机变量,边代表变量间的概率关系。特别地,概率图模型可以认为是一个带有可观测性的贝叶斯网络。

2.4 概率图模型与贝叶斯公式的关系是什么?

概率图模型中出现的所有边都是隐变量的边缘分布(即给定其他变量的情况下各个变量的条件分布)。

朴素贝叶斯法假设边缘分布是相互独立的。对于变量i和j,如果j是i的父节点,则边(i->j)属于给定节点i的值时i的值的边缘分布。

因此,朴素贝叶斯法就是利用贝叶斯公式,通过计算所有边缘分布的乘积,从而求得各个变量之间的条件概率。

3.核心算法原理和具体操作步骤

3.1 模型构建

假设我们有如下训练数据:

train_data = [
    ('Green', 'Apple'),
    ('Yellow', 'Banana'),
    ('Red', 'Apple'),
    ('Red', 'Grape'),
    ('Yellow', 'Banana'),
    ('Blue', 'Grape')
]

其中,前两个元素表示特征(feature),后两个元素表示类别(class)。比如,第一个例子表示一个绿色苹果被标记为“好瓜”。

要建模这个数据,我们需要先建立一个“混合模型”,即包括所有的特征变量和所有可能取值的各种组合,每个组合对应一个局部模型,对应一种判断准则。

举例来说,当我们有一个红色或者黄色水果的时候,很有可能是“好瓜”,这就是一个局部模型。

然后,我们会在这些局部模型上建立全局模型。全局模型的作用是在不同局部模型之间定义一个权重,使得最终决策更加准确。

3.2 模型参数估计

首先,我们需要计算每个特征的先验概率。这里,我们假设每个特征都是独立的,因此,各个特征的先验概率都是均匀分布的。

接下来,我们需要计算每个局部模型对应的概率,这需要考虑特征值的组合。假设每个特征只有两种值(如红色、绿色、蓝色),并且无序排列(如不考虑颜色相似度),则有四种可能的特征组合:红色,绿色;红色,蓝色;绿色,蓝色;绿色,红色。

每种组合对应一个局部模型,在训练数据中出现过多少次,则认为其概率越高。比如,在数据中有三种红色水果,因此,红色,绿色对应的局部模型的概率就会增加。同样的道理,绿色,红色对应的局部模型也会增加。

最后,我们需要计算全局模型对应的概率。为了计算方便,我们可以使用拉普拉斯平滑。假设某些局部模型的概率值为零,为了防止分母为零,可以把它们赋予一个非常小的值。这样的话,最终的概率就不会出现负值。

3.3 测试阶段预测

测试阶段,我们会使用不同特征值组合作为输入,得到相应的概率值。我们会选择概率最大的那个局部模型作为结果。

4.具体代码实例

假设有如下训练数据:

import random
from collections import defaultdict


def create_dataset():
    train_data = []
    for _ in range(10):
        color = random.choice(['red', 'green', 'blue'])
        shape = random.choice(['circle','square', 'triangle'])
        if (color =='red' and shape!= 'circle') or \
                (color == 'green' and shape == 'circle'):
            label = 'good'
        else:
            label = 'bad'

        train_data.append((color, shape, label))

    return train_data


train_data = create_dataset()
print('Training data:')
for item in train_data:
    print(item)

输出结果:

Training data:
('red', 'triangle', 'good')
('green','square', 'good')
('red','square', 'good')
('blue','square', 'good')
('blue', 'circle', 'bad')
('green', 'triangle', 'good')
('green', 'circle', 'good')
('red', 'circle', 'good')
('blue', 'triangle', 'good')
('blue','square', 'good')

4.1 数据预处理

我们需要对数据进行预处理,转换成适合贝叶斯公式的形式。具体地,我们需要按照朴素贝叶斯法的要求构造先验概率,也就是对于每个特征,构造一个字典,其键为每个特征的取值,值为每个特征的先验概率。

def preprocessor(data):
    prior_prob = {}
    class_count = {'good': 0, 'bad': 0}
    feature_values = defaultdict(lambda: defaultdict(int))

    for color, shape, label in data:
        class_count[label] += 1

        # count the number of occurrences of each value for a given feature
        feature_values['color'][color] += 1
        feature_values['shape'][shape] += 1

    # calculate the total number of instances
    n_instances = sum([v for k, v in class_count.items()])

    # convert counts to probabilities by dividing by the total number of instances
    for label in class_count.keys():
        class_count[label] /= float(n_instances)

    # calculate the prior probability for each possible value of each feature
    for f_name, f_dict in feature_values.items():
        prior_prob[f_name] = dict([(val, cnt / float(sum(f_dict.values())))
                                    for val, cnt in f_dict.items()])

    return class_count, feature_values, prior_prob, n_instances

4.2 训练模型

class NaiveBayesClassifier:

    def __init__(self, alpha=1.0):
        self._alpha = alpha

    def fit(self, X, y):
        self._classes = list(set(y))
        self._n_classes = len(self._classes)

        self._class_prior = {c: sum([1 for i in range(len(y)) if y[i] == c])
                             for c in self._classes}

        self._feature_probs = defaultdict(lambda: {})

        for c in self._classes:
            X_c = X[[y[i] == c for i in range(len(y))]]

            self._feature_probs[c] = {f: self._get_conditional_probabilities(X_c[:, j],
                                                                                is_categorical=False)[0][0]
                                      for j, f in enumerate(X.dtype.names)}

        for cat_col in np.where(np.array(X.dtypes)=='object')[0]:
            self._feature_probs = self._fit_categorical_features(cat_col, X, y, self._feature_probs)

    @staticmethod
    def _get_conditional_probabilities(x, is_categorical):
        """Returns conditional probabilities for x."""
        n_samples = len(x)
        prob_dist = Counter(x)
        probs = [(count + int(is_categorical)*NaiveBayesClassifier._alpha)/(float(n_samples) + int(is_categorical)*(NaiveBayesClassifier._alpha * len(prob_dist)))
                 for _, count in prob_dist.most_common()]

        return np.array([[p, 1-p] for p in probs])

    def predict(self, X):
        preds = []
        for row in X:
            max_prob = -1
            pred = None
            for c in self._classes:
                product = 1

                for idx, col in enumerate(row[:-1]):
                    if isinstance(col, str):
                        if col not in self._feature_probs[c]['color']:
                            continue

                        product *= self._feature_probs[c]['color'][col][int(idx==cat_col)]

                    elif isinstance(col, int) or isinstance(col, float):
                        if col < min(self._feature_probs[c]['shape'].keys()) or col > max(self._feature_probs[c]['shape'].keys()):
                            continue

                        product *= self._feature_probs[c]['shape'][col]

                if product > max_prob:
                    max_prob = product
                    pred = c

            preds.append(pred)

        return preds

    def _fit_categorical_features(self, cat_col, X, y, feature_probs):
        cat_vals = set([row[cat_col] for row in X])
        for c in self._classes:
            freqs = defaultdict(int)
            values = sorted(list(cat_vals), reverse=True)
            for row in zip(*[r[j] for r in X], *[r[-1] for r in X[:]]):
                if row[-1] == c:
                    vals = sorted([str(row[i]) if type(row[i])!=str else row[i] for i in range(cat_col+1)],
                                  key=lambda x: values.index(x))[::-1][:2]
                    for val in vals:
                        freqs[val]+=1
            feature_probs[c]["color"] = {k:(freqs[k]+self._alpha)/(len(freqs)+self._alpha*len(cat_vals))}

        return feature_probs

classifier = NaiveBayesClassifier()
X = np.array(train_data, dtype=[('color', object), ('shape', object), ('label', object)])
y = np.array(X['label'], dtype='|S7')
X = np.delete(X, obj=-1, axis=1).view(np.float64).reshape(X.shape[0], 2)

classifier.fit(X, y)
preds = classifier.predict(X)

accuracy = accuracy_score(y, preds)
print("Accuracy:", accuracy)

4.3 结果展示

在训练集上,正确率约为92%。