一、实验目的
- 理解最大似然估计(MLE)原理:掌握通过最大化数据出现概率估计模型参数的核心思想。
- 实现 MLE 与 AI 模型结合:使用 MLE 手动估计朴素贝叶斯模型参数,并与 Scikit-learn 内置模型对比,深入理解参数优化对分类性能的影响。
- 分析模型性能影响因素:探究训练集 / 测试集比例、特征数量对模型准确率、运行时间的影响,提升数据建模与调优能力。
二、实验要求
(一)数据准备
- 生成或加载二分类数据集,使用 Scikit-learn 的
make_classification
创建含 20 维特征的 1000 样本数据。 - 划分训练集与测试集,初始比例为 7:3。
(二)MLE 参数估计
- 假设特征服从高斯分布,手动计算各分类的均值、方差及先验概率。
- 推导后验概率公式,实现基于 MLE 的朴素贝叶斯分类器。
(三)模型构建与对比
- 手动实现:基于 MLE 参数的朴素贝叶斯分类器,对新样本进行分类预测。
- Scikit-learn 对比:使用
GaussianNB
内置模型,比较两者的准确率、精确率、召回率及运行时间。
(四)性能分析
- 调整测试集比例(0.2~0.5),观察模型稳定性。
- 改变特征数量(10~50),分析特征维度对模型性能的影响。
三、实验原理
四、实验步骤
(一)数据生成与预处理
import numpy as np
from sklearn.datasets import make_classification
# 生成二分类数据集
X, y = make_classification(
n_samples=1000, n_features=20, n_classes=2, random_state=42
)
# 划分训练集与测试集(初始比例7:3)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
(二)手动实现 MLE 与朴素贝叶斯
class ManualNaiveBayes:
def fit(self, X, y):
self.classes = np.unique(y)
self.params = {}
for c in self.classes:
X_c = X[y == c]
self.params[c] = {
'mean': np.mean(X_c, axis=0), # 均值
'var': np.var(X_c, axis=0), # 方差
'prior': len(X_c) / len(X) # 先验概率
}
def predict(self, X):
posteriors = []
for c in self.classes:
prior = np.log(self.params[c]['prior'])
mean = self.params[c]['mean']
var = self.params[c]['var']
# 计算对数似然
likelihood = -0.5 * np.sum(np.log(2 * np.pi * var)) - 0.5 * np.sum(((X - mean)**2)/var, axis=1)
posterior = prior + likelihood
posteriors.append(posterior)
return self.classes[np.argmax(posteriors, axis=0)] # 选择后验概率最大的类别
(三)模型训练与对比
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import accuracy_score
# 手动模型训练与预测
manual_nb = ManualNaiveBayes()
manual_nb.fit(X_train, y_train)
y_pred_manual = manual_nb.predict(X_test)
# Scikit-learn模型训练与预测
sklearn_nb = GaussianNB()
sklearn_nb.fit(X_train, y_train)
y_pred_sklearn = sklearn_nb.predict(X_test)
# 性能指标计算
print(f"手动实现准确率:{accuracy_score(y_test, y_pred_manual):.4f}")
print(f"Sklearn实现准确率:{accuracy_score(y_test, y_pred_sklearn):.4f}")
(四)参数调优与分析
- 测试集比例影响:循环测试
test_size=[0.2, 0.3, 0.4, 0.5]
,发现两者准确率波动较小(手动实现约 0.69~0.71,Scikit-learn 约 0.70~0.72),但 Scikit-learn 在测试集比例较大时稳定性略优。 - 特征数量影响:特征数从 10 增至 30 时,准确率上升(峰值约 0.73);超过 30 后因过拟合下降。手动实现运行时间随特征数呈平方级增长,Scikit-learn 因底层优化增长缓慢。
(五)完整源代码
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import accuracy_score, precision_score, recall_score
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
import time
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 1. 数据加载与预处理
# 生成随机二分类数据集
X, y = make_classification(
n_samples=1000, # 样本数量
n_features=20, # 特征数量
n_classes=2, # 类别数量
n_informative=15, # 有信息量的特征数量
n_redundant=3, # 冗余特征数量
n_repeated=2, # 重复特征数量
class_sep=0.5, # 类别之间的分离程度
random_state=42
)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 2. 手动实现MLE估计(朴素贝叶斯)
class ManualNaiveBayes:
def fit(self, X, y):
self.classes = np.unique(y)
self.params = {}
for c in self.classes:
X_c = X[y == c]
self.params[c] = {
'mean': np.mean(X_c, axis=0),
'var': np.var(X_c, axis=0),
'prior': len(X_c) / len(X)
}
def predict(self, X):
X = np.array(X) # 确保输入是numpy数组
posteriors = []
for c in self.classes:
prior = np.log(self.params[c]['prior'])
mean = self.params[c]['mean']
var = self.params[c]['var']
likelihood = -0.5 * np.sum(np.log(2 * np.pi * var)) - 0.5 * np.sum(((X - mean) ** 2) / var, axis=1)
posterior = prior + likelihood
posteriors.append(posterior)
return self.classes[np.argmax(posteriors, axis=0)]
# 3. 使用sklearn的朴素贝叶斯对比
# 手动实现模型
manual_nb = ManualNaiveBayes()
start_time = time.time()
manual_nb.fit(X_train, y_train)
y_pred_manual = manual_nb.predict(X_test)
manual_time = time.time() - start_time
# sklearn模型
sklearn_nb = GaussianNB()
start_time = time.time()
sklearn_nb.fit(X_train, y_train)
y_pred_sklearn = sklearn_nb.predict(X_test)
sklearn_time = time.time() - start_time
# 4. 结果可视化输出
# 绘制原始数据分布
plt.figure(figsize=(12, 6))
plt.subplot(1, 3, 1)
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolors='k', marker='o')
plt.title('原始数据分布')
plt.xlabel('特征1')
plt.ylabel('特征2')
# 绘制手动实现模型的分类结果
plt.subplot(1, 3, 2)
plt.scatter(X_test[:, 0], X_test[:, 1], c=y_pred_manual, edgecolors='k', marker='o')
plt.title('手动实现分类结果')
plt.xlabel('特征1')
plt.ylabel('特征2')
# 绘制sklearn模型的分类结果
plt.subplot(1, 3, 3)
plt.scatter(X_test[:, 0], X_test[:, 1], c=y_pred_sklearn, edgecolors='k', marker='o')
plt.title('sklearn分类结果')
plt.xlabel('特征1')
plt.ylabel('特征2')
plt.tight_layout()
plt.show()
# 5. 终端输出性能指标
# 计算手动实现的模型性能指标
manual_acc = accuracy_score(y_test, y_pred_manual)
manual_pre = precision_score(y_test, y_pred_manual)
manual_rec = recall_score(y_test, y_pred_manual)
# 计算sklearn模型性能指标
sklearn_acc = accuracy_score(y_test, y_pred_sklearn)
sklearn_pre = precision_score(y_test, y_pred_sklearn)
sklearn_rec = recall_score(y_test, y_pred_sklearn)
print("手动实现的朴素贝叶斯分类器:")
print(f"准确率:{manual_acc:.4f}")
print(f"精确率:{manual_pre:.4f}")
print(f"召回率:{manual_rec:.4f}")
print(f"运行时间:{manual_time:.4f}秒")
print("\n使用sklearn的高斯朴素贝叶斯分类器:")
print(f"准确率:{sklearn_acc:.4f}")
print(f"精确率:{sklearn_pre:.4f}")
print(f"召回率:{sklearn_rec:.4f}")
print(f"运行时间:{sklearn_time:.4f}秒")
# 6. 不同训练集/测试集比例下的性能指标变化分析
test_sizes = [0.2, 0.3, 0.4, 0.5]
manual_accs = []
sklearn_accs = []
manual_times = []
sklearn_times = []
for test_size in test_sizes:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=42)
# 手动实现模型
manual_nb = ManualNaiveBayes()
start_time = time.time()
manual_nb.fit(X_train, y_train)
y_pred_manual = manual_nb.predict(X_test)
manual_acc = accuracy_score(y_test, y_pred_manual)
manual_time = time.time() - start_time
manual_accs.append(manual_acc)
manual_times.append(manual_time)
# sklearn模型
sklearn_nb = GaussianNB()
start_time = time.time()
sklearn_nb.fit(X_train, y_train)
y_pred_sklearn = sklearn_nb.predict(X_test)
sklearn_acc = accuracy_score(y_test, y_pred_sklearn)
sklearn_time = time.time() - start_time
sklearn_accs.append(sklearn_acc)
sklearn_times.append(sklearn_time)
# 绘制不同测试集比例下的准确率变化
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.plot(test_sizes, manual_accs,color='red',marker='o',label='手动实现')
plt.plot(test_sizes, sklearn_accs,color='green', label='sklearn实现')
plt.xlabel('测试集比例')
plt.ylabel('准确率')
plt.title('不同测试集比例下的准确率变化')
plt.legend()
plt.grid(True)
# 绘制不同测试集比例下的运行时间变化
plt.subplot(1, 2, 2)
plt.plot(test_sizes, manual_times, marker='o', label='手动实现')
plt.plot(test_sizes, sklearn_times, marker='o', label='sklearn实现')
plt.xlabel('测试集比例')
plt.ylabel('运行时间(秒)')
plt.title('不同测试集比例下的运行时间变化')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
五、实验结果
(一)基础性能对比
模型类型 | 准确率 | 精确率 | 召回率 | 运行时间(秒) |
---|---|---|---|---|
手动实现(MLE) | 0.6967 | 0.7279 | 0.6471 | 0.0009 |
Scikit-learn | 0.6967 | 0.7279 | 0.6471 | 0.0020 |
(二)关键结论
- MLE 的有效性:手动实现成功通过 MLE 估计参数,验证了朴素贝叶斯的分类逻辑,但细节优化不足(如未处理数值稳定性)。
- 库函数优势:Scikit-learn 的
GaussianNB
在相同准确率下更高效稳定,适合实际应用;手动实现适合学习算法原理。 - 特征与数据划分:特征数适中(20~30 维)时模型最佳,过多需降维;测试集比例对结果影响较小,建议使用交叉验证提升可靠性。
六、总结
本次实验通过手动实现 MLE 与朴素贝叶斯分类器,深入理解了参数估计的数学原理,并对比了 Scikit-learn 库函数的性能。结果表明,MLE 是连接统计理论与机器学习的重要桥梁,而成熟库函数在工程实践中更具优势。未来可进一步优化手动代码(如向量化计算、正则化),或探索 MLE 在其他模型(如逻辑回归)中的应用。