朴素贝叶斯
朴素贝叶斯方法是一种基于贝叶斯定理和特征条件独立性假设的分类方法。由于在现实中很难满足“特征条件的独立性”,因此被称为“朴素”贝叶斯。
接下来,我们将使用朴素贝叶斯来实现手写数字的识别。
问题描述
我使用的数据集是sklearn
中的数据集之一,先导入
from sklearn.datasets import load_digits
digits = load_digits()
简单介绍一下这个数据集,它包含张大小的图片,每张图片有特征,我们可以标记为:
每张图片都有一个对应的输出,一个从到的整数,也就是图片对应的数字。
为了更直观的感受,你可以参与其中,形象化
fig = plt.figure(figsize=(6, 6))
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)
for i in range(64):
ax = fig.add_subplot(8, 8, i + 1, xticks=[], yticks=[])
ax.imshow(digits.images[i], cmap=plt.cm.binary, interpolation='nearest')
ax.text(0, 7, str(digits.target[i]))
plt.show()
效果如下:
图片左下角是实数,也就是每个数据对应的值。
那么我们要做的就是根据输入来判断对应的数字,也就是每个像素的值。
理论部分
对于给定的输入,假设他的输出总共有种可能性,我们记为,然后我们要做的是,在输入的情况下,计算输出是中每个值的概率, 我们记他为,
最后,我们返回的是概率最大的那个可能性,也就是
接下来要处理的是找到最大值的问题。根据贝叶斯定理,我们有
由于朴素贝叶斯方法是基于每个变量是相对独立的假设,也就是说,我们假设的分布是相互独立的,上式可以进一步写为
由于每个估计 解决了分母中的 ,我们必须处理的问题变成
为了方便计算,我们对上式做,使乘法变为加法,方便计算
最后一个问题是如何计算和力。
其中,好办。我们只需要从测试集中统计每次出现的概率,即先验概率。
比较复杂。我们需要得到测试集中输出特征值的均值和方差,然后通过下面的公式计算
概括
我们先理清思路,看看接下来要做什么。
- 得到一个测试集,统计测试集中特征值对应的每个输出的先验概率、均值和方差。
- 输入任意一张图片的像素信息使用第一步得到的先验概率、均值、方差,估计并与真实值进行比较,看看效果。
Python实现
划分数据集
这里直接调用sklearn
中的api来实现数据集的划分,
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
digits = load_digits()
x_train, x_test, y_train, y_test = train_test_split(digits.data,
digits.target,
test_size=0.3)
其中,x_train
和y_train
是我们用来训练的数据,分别对应特征值和正确输出。在完成模型的学习后,我们使用x_test
和y_test
来评估模型的质量。
模型训练
根据前面的理论,我们知道我们需要使用训练集来获取一些样本的均值、方差和先验概率。请注意,此处不需要诸如梯度下降之类的优化。我们可以定义一个class
来方便我们的调用。
class NaiveBayes:
def train(self, X: np.ndarray, y: np.ndarray):
"""
训练函数,获取训练集的均值,方差,先验概率
"""
# 获取数据集大小
n_samples, n_features = X.shape
# 获取数据集类别和类别数
self._classes = np.unique(y)
n_classes = len(self._classes)
# print(self._classes)
# 初始化均值,方差,先验概率
self._mean = np.zeros((n_classes, n_features), dtype=float)
self._var = np.zeros((n_classes, n_features), dtype=float)
self._priors = np.zeros(n_classes, dtype=float)
# 计算均值,方差,先验概率
for c in self._classes:
# 找到输出为c的样本
X_c = X[c == y]
# 获取输出为c的均值,方差,先验概率
self._mean[c, :] = X_c.mean(axis=0)
self._var[c, :] = X_c.var(axis=0)
self._priors[c] = X_c.shape[0] / float(n_samples)
模型预测
得到训练集的均值、方差和先验概率后,我们就可以对测试集进行预测了
def predict(self, X) -> List[int]:
"""
获取测试集的预测
:return: 预测结果
"""
pred = []
for x in X:
# 统计每中输出的可能性
probs = []
for y in range(len(self._classes)):
probs.append(self._cal_prob(x, y))
# 找到概率最大的那个类别
pred.append(np.argmax(probs))
return pred
最重要的部分是self._cal_prob(x, y)
的写作
def _cal_prob(self, x, y) -> float:
"""
计算 log p(x_1|y) + ... + log p(x_n|y) + log p(y)
注意:这里的 y 为在 self._classes 中的下标
"""
# log p(y)
p_y = np.log(self._priors[y])
# print(p_y)
# log p(x_1|y) + ... + log p(x_n|y)
mean = self._mean[y]
# print("mean are: \n", mean)
var = self._var[y]
# print("var: \n", var)
p_xy = np.log(np.exp(-(x-mean)**2 / (2*var)) / np.sqrt(2*np.pi*var))
# p_xy 的计算结果可能会出现 nan,我们将它取值改为0,让他不对输出造成影响
p_xy[np.isnan(p_xy)] = 0
return p_y + np.sum(p_xy)
模型评估
我们通过使用以下代码计算模型预测的准确性来评估我们的模型:
def accuracy(y_pred, y_true):
return np.sum(y_pred == y_true) / len(y_true)
显示结果
整个过程的代码如下,
digits= load_digits()
x_train, x_test, y_train, y_test = train_test_split(digits.data,
digits.target,
test_size=0.3)
bayes = NaiveBayes()
bayes.train(x_train, y_train)
y_pred = bayes.predict(x_test)
print(accuracy(y_pred, y_test))
我运行的输出是
0.8962962962962963
我们也可以随机选择部分测试集查看效果,测试代码如下
def show_test_result(nb: NaiveBayes):
fig = plt.figure(figsize=(6, 6)) # figure size in inches
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)
n = digits.data.shape[0]
for i in range(64):
ax = fig.add_subplot(8, 8, i + 1, xticks=[], yticks=[])
# 随机选择一张图片
idx = random.randint(0, n-1)
ax.imshow(digits.images[idx], cmap=plt.cm.binary, interpolation='nearest')
data = np.asarray(digits.data[idx]).reshape(1, -1)
# 左下角为预测值
ax.text(0, 7, str(nb.predict(data)[0]), color="green")
# 左上角为真实值
ax.text(0, 1, str(digits.target[idx]), color="red")
plt.show()
由于我们假设每个像素是相对独立的,这与现实不符,所以最后也能发现一些错误。
完整的测试代码
#!/usr/bin/python3
import numpy as np
import matplotlib.pyplot as plt
import random
from typing import List, Tuple
from sklearn.decomposition import PCA
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
import warnings
warnings.filterwarnings('ignore')
def show_dataset():
fig = plt.figure(figsize=(6, 6)) # figure size in inches
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)
for i in range(64):
ax = fig.add_subplot(8, 8, i + 1, xticks=[], yticks=[])
ax.imshow(digits.images[i], cmap=plt.cm.binary, interpolation='nearest')
# label the image with the target value
ax.text(0, 7, str(digits.target[i]))
plt.show()
class NaiveBayes:
def train(self, X: np.ndarray, y: np.ndarray):
"""
训练函数
:return: 训练集的均值,方差,先验概率
"""
# 获取数据集大小
n_samples, n_features = X.shape
# print(n_features)
# 获取数据集类别和类别数
self._classes = np.unique(y)
n_classes = len(self._classes)
# print(self._classes)
# 初始化均值,方差,先验概率
self._mean = np.zeros((n_classes, n_features), dtype=float)
self._var = np.zeros((n_classes, n_features), dtype=float)
self._priors = np.zeros(n_classes, dtype=float)
# 计算均值,方差,先验概率
for c in self._classes:
X_c = X[c == y]
self._mean[c, :] = X_c.mean(axis=0)
self._var[c, :] = X_c.var(axis=0)
self._priors[c] = X_c.shape[0] / float(n_samples)
def _cal_prob(self, x, y) -> float:
"""
计算 log p(x_1|y) + ... + log p(x_n|y) + log p(y)
注意:这里的 y 为在 self._classes 中的下标
"""
# log p(y)
p_y = np.log(self._priors[y])
# print(p_y)
# log p(x_1|y) + ... + log p(x_n|y)
mean = self._mean[y]
# print("mean are: \n", mean)
var = self._var[y]
# print("var: \n", var)
p_xy = np.log(np.exp(-(x-mean)**2 / (2*var)) / np.sqrt(2*np.pi*var))
p_xy[np.isnan(p_xy)] = 0
return p_y + np.sum(p_xy)
def predict(self, X) -> List[int]:
"""
测试数据
:return: 预测结果
"""
pred = []
for x in X:
probs = []
for y in range(len(self._classes)):
probs.append(self._cal_prob(x, y))
# 找到概率最大的那个类别
pred.append(np.argmax(probs))
return pred
def accuracy(y_pred, y_true):
return np.sum(y_pred == y_true) / len(y_true)
def show_test_result(nb: NaiveBayes):
fig = plt.figure(figsize=(6, 6)) # figure size in inches
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)
n = digits.data.shape[0]
for i in range(64):
ax = fig.add_subplot(8, 8, i + 1, xticks=[], yticks=[])
# 随机选择一张图片
idx = random.randint(0, n-1)
ax.imshow(digits.images[idx], cmap=plt.cm.binary, interpolation='nearest')
data = np.asarray(digits.data[idx]).reshape(1, -1)
# 左下角为预测值
ax.text(0, 7, str(nb.predict(data)[0]), color="green")
# 左上角为真实值
ax.text(0, 1, str(digits.target[idx]), color="red")
plt.show()
digits = load_digits()
if __name__ == '__main__':
x_train, x_test, y_train, y_test = train_test_split(digits.data,
digits.target,
test_size=0.3)
bayes = NaiveBayes()
bayes.train(x_train, y_train)
y_pred = bayes.predict(x_test)
print(accuracy(y_pred, y_test))
show_test_result(bayes)
参考
- “统计学习方法”
- Naive Bayes in Python – Machine Learning From Scratch 05 – Python Tutorial
- Simple visualization and classification of the digits dataset
文章出处登录后可见!