在机器学习分类任务中,朴素贝叶斯 (Naive Bayes
)因其简单高效而广受欢迎,但它的**"朴素"**之名也暗示了其局限性。
为了突破这一局限,半朴素贝叶斯 (Semi-Naive Bayes
) 应运而生。
本文将详细介绍朴素贝叶斯和半朴素贝叶斯的原理、应用场景以及如何使用scikit-learn
库实现它们。
1. 朴素贝叶斯:简单但"天真"
朴素贝叶斯 是一种基于贝叶斯定理的简单概率分类器,它的核心思想是利用特征之间的独立性假设来简化计算。
具体来说,朴素贝叶斯假设每个特征之间是相互独立的,即给定一个类别标签,所有特征的联合概率可以分解为各个特征的条件概率的乘积。
用数学公式表示为: P(X\|Y)=P(x_1\|Y)\\times P(x_2\|Y)\\times\\cdots\\times P(x_n\|Y)
其中, X \\(是特征向量,\\) Y \\(是类别标签,\\) x_1,x_2,\\ldots,x_n 是各个特征。
朴素贝叶斯的优势在于计算高效,适合高维数据(如新闻分类)。
2. 半朴素贝叶斯:放松独立性假设
尽管朴素贝叶斯 在许多场景下表现出色,但它的一个关键假设:特征独立性,在实际应用中往往难以满足。
在现实世界中,特征之间通常存在一定的相关性。
例如,在文本分类中,某些词汇的出现可能与其他词汇的出现密切相关。
这种情况下,朴素贝叶斯的独立性假设会导致分类器的性能下降。
为了解决这一问题,半朴素贝叶斯应运而生。
半朴素贝叶斯在一定程度上放宽了特征独立性的假设,允许特征之间存在一定的相关性,从而提高分类器的性能。
半朴素贝叶斯的核心改进在于允许部分特征之间存在依赖关系,通过捕捉关键特征间的依赖,提升分类精度,同时保持计算复杂度可控。
3. 实战对比
下面构造一个简单的示例,用来对比朴素 和半朴素贝叶斯的在属性存在依赖关系时的准确率。
首先,生成测试数据:
- 类别
Y
有两种值,0
和1
- 类别
Y
决定X1
的分布(Y=0
时均值为0
,Y=1
时均值为1
) X2
依赖于X1
(X2 = X1 + 噪声),模拟属性间的依赖关系
python
import numpy as np
from sklearn.model_selection import train_test_split
# 生成模拟数据:Y影响X1和X2,且X2依赖X1
np.random.seed(42)
n_samples = 1000
Y = np.random.randint(0, 2, n_samples)
X1 = np.zeros(n_samples)
X2 = np.zeros(n_samples)
for i in range(n_samples):
if Y[i] == 0:
x1 = np.random.normal(0, 1)
x2 = x1 + np.random.normal(0, 0.5) # X2依赖X1
else:
x1 = np.random.normal(1, 1)
x2 = x1 + np.random.normal(0, 0.5) # X2依赖X1
X1[i] = x1
X2[i] = x2
X = np.vstack((X1, X2)).T
X_train, X_test, y_train, y_test = train_test_split(
X,
Y,
test_size=0.3,
random_state=42,
)
然后分别使用朴素和半朴素贝叶斯模型来训练数据,看看各自的准确率。
注意,scikit-learn
没有直接提供半朴素贝叶斯的实现,下面的示例中通过手动计算特征之间的相关性来改进朴素贝叶斯模型。
python
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import accuracy_score
from sklearn.linear_model import LinearRegression
# 朴素贝叶斯(假设属性独立)
nb = GaussianNB()
nb.fit(X_train, y_train)
y_pred_nb = nb.predict(X_test)
acc_nb = accuracy_score(y_test, y_pred_nb)
# 半朴素贝叶斯(手动实现,假设X2依赖X1)
# 训练阶段:估计每个类别的参数
def train_semi_naive_bayes(X, y):
params = {}
for cls in [0, 1]:
X_cls = X[y == cls]
X1_cls = X_cls[:, 0]
X2_cls = X_cls[:, 1]
# 估计P(X1|Y)的参数(高斯分布)
mu_X1 = np.mean(X1_cls)
sigma_X1 = np.std(X1_cls)
# 估计P(X2|Y,X1)的参数(线性回归)
lr = LinearRegression().fit(X1_cls.reshape(-1, 1), X2_cls)
a, b = lr.coef_[0], lr.intercept_
residuals = X2_cls - lr.predict(X1_cls.reshape(-1, 1))
sigma_X2_given_X1 = np.std(residuals)
params[cls] = {
"prior": np.sum(y == cls) / len(y),
"mu_X1": mu_X1,
"sigma_X1": sigma_X1,
"a": a,
"b": b,
"sigma_X2_given_X1": sigma_X2_given_X1,
}
return params
# 预测阶段:计算对数概率
def predict_semi_naive_bayes(X, params):
y_pred = []
for x1, x2 in X:
log_prob = {0: 0, 1: 0}
for cls in [0, 1]:
p = params[cls]
# 计算P(Y)
log_prob[cls] += np.log(p["prior"])
# 计算P(X1|Y)
log_prob[cls] += -0.5 * np.log(2 * np.pi * p["sigma_X1"] ** 2) - (
x1 - p["mu_X1"]
) ** 2 / (2 * p["sigma_X1"] ** 2)
# 计算P(X2|Y,X1)
mu_x2 = p["a"] * x1 + p["b"]
log_prob[cls] += -0.5 * np.log(2 * np.pi * p["sigma_X2_given_X1"] ** 2) - (
x2 - mu_x2
) ** 2 / (2 * p["sigma_X2_given_X1"] ** 2)
y_pred.append(0 if log_prob[0] > log_prob[1] else 1)
return np.array(y_pred)
params = train_semi_naive_bayes(X_train, y_train)
y_pred_semi = predict_semi_naive_bayes(X_test, params)
acc_semi = accuracy_score(y_test, y_pred_semi)
# 输出结果
print(f"朴素贝叶斯准确率: {acc_nb:.4f}")
print(f"半朴素贝叶斯准确率: {acc_semi:.4f}")
## 输出结果:
'''
朴素贝叶斯准确率: 0.6333
半朴素贝叶斯准确率: 0.7000
'''
朴素贝叶斯 因假设属性独立,在X1
和X2
存在依赖时性能略有下降。
而半朴素贝叶斯 通过显式建模X2
对X1
的依赖,更准确地估计联合概率,从而获得更高的准确率。
此示例简单展示了半朴素贝叶斯在属性存在依赖关系时的优势。
4. 总结
朴素贝叶斯 和半朴素贝叶斯都是基于贝叶斯定理的分类算法。
其中,朴素贝叶斯假设特征之间相互独立,适用于特征独立性较强的场景,比如:
- 特征独立性较强:当特征之间确实相互独立时,朴素贝叶斯能够发挥其优势。例如,在垃圾邮件分类中,邮件中的词汇通常可以被视为独立的特征。
- 数据量较少:由于朴素贝叶斯的计算复杂度较低,它在数据量较少的情况下也能快速训练模型。
- 对分类精度要求不高:在一些对分类精度要求不高的场景中,朴素贝叶斯可以作为一种快速且有效的解决方案。
而半朴素贝叶斯在一定程度上放宽了这一假设,适用于特征存在相关性的场景,比如:
- 特征存在相关性:当特征之间存在一定的相关性时,半朴素贝叶斯可以更好地捕捉这些关系,从而提高分类性能。例如,在医学诊断中,某些症状之间可能存在关联。
- 对分类精度要求较高:在需要高精度分类的场景中,半朴素贝叶斯可以通过考虑特征之间的相关性来提升性能。
- 数据量较大:当有足够的数据来估计特征之间的相关性时,半朴素贝叶斯能够更好地发挥其优势。