朴素贝叶斯(Naive Bayes)是一种基于贝叶斯定理的简单概率分类器,广泛应用于文本分类、垃圾邮件过滤等领域。其"朴素"体现在假设特征之间相互独立,虽然现实中这个假设往往不成立,但实际应用中仍能取得不错的效果。
多项式朴素贝叶斯(Multinomial Naive Bayes)是朴素贝叶斯的一个变种,特别适用于处理离散特征(如词频)的分类问题。在文本分类任务中,每个特征代表一个词在文档中出现的次数,多项式朴素贝叶斯通过计算词频的联合概率来进行分类。
注意:朴素贝叶斯虽然简单,但在处理高维稀疏数据(如文本)时表现优异,这也是为什么它成为自然语言处理领域的经典算法之一。
首先需要准备训练集和测试集数据。训练集包含特征向量(词频)和对应的类别标签(0或1),测试集只包含特征向量。
python复制# 示例训练数据格式
train_data = [
([2, 0, 1, 3], 0), # 特征向量和标签
([1, 1, 2, 0], 1),
([0, 3, 1, 2], 0)
]
# 示例测试数据格式
test_data = [
[1, 0, 2, 1],
[0, 2, 1, 0]
]
在实际应用中,我们通常会从文件读取数据。对于文本分类任务,还需要进行分词、构建词表等预处理步骤。
训练阶段主要完成以下统计计算:
java复制// Java示例:统计类别样本数
Map<Integer, Integer> classCounts = new HashMap<>();
for (int[] features : trainFeatures) {
int label = trainLabels[i];
classCounts.put(label, classCounts.getOrDefault(label, 0) + 1);
}
// 统计特征出现次数
Map<Integer, int[]> featureCounts = new HashMap<>();
for (int label : classCounts.keySet()) {
featureCounts.put(label, new int[numFeatures]);
}
for (int i = 0; i < trainFeatures.length; i++) {
int label = trainLabels[i];
int[] counts = featureCounts.get(label);
for (int j = 0; j < numFeatures; j++) {
counts[j] += trainFeatures[i][j];
}
}
为了避免零概率问题(某个特征在某个类别中从未出现导致概率为0),我们需要使用拉普拉斯平滑(加1平滑):
P(x_i|y) = (count(x_i,y) + α) / (count(y) + α * n)
其中α=1(k=1),n是特征维度。
python复制# Python示例:计算平滑后的条件概率对数
import numpy as np
alpha = 1 # 拉普拉斯平滑系数
n_features = len(feature_names)
# 计算每个类别的总词频
total_counts_per_class = {
cls: np.sum(feature_counts[cls]) for cls in classes
}
# 计算条件概率对数
self.feature_log_prob_ = {}
for cls in classes:
# 分子:特征计数 + alpha
# 分母:类别总词频 + alpha * n_features
smoothed_probs = (feature_counts[cls] + alpha) / (total_counts_per_class[cls] + alpha * n_features)
self.feature_log_prob_[cls] = np.log(smoothed_probs)
对于测试样本,计算其属于每个类别的对数后验概率:
log P(y|x) ∝ log P(y) + Σ (x_i * log P(x_i|y))
然后比较两个类别的对数概率,取较大者作为预测结果。
cpp复制// C++示例:预测函数
vector<int> predict(const vector<vector<int>>& X) {
vector<int> predictions;
for (const auto& features : X) {
double max_log_prob = -numeric_limits<double>::max();
int best_class = -1;
for (int cls : classes_) {
double log_prob = log(class_prior_[cls]);
for (int i = 0; i < features.size(); ++i) {
log_prob += features[i] * feature_log_prob_[cls][i];
}
if (log_prob > max_log_prob) {
max_log_prob = log_prob;
best_class = cls;
}
}
predictions.push_back(best_class);
}
return predictions;
}
直接计算多个小概率的乘积会导致数值下溢(结果趋近于0,超出浮点数精度范围)。使用对数概率可以:
当特征维度很高(如文本分类中的词表很大)时,可以使用稀疏矩阵表示来节省内存和计算资源:
python复制from scipy.sparse import csr_matrix
# 将稠密矩阵转换为稀疏矩阵
sparse_train = csr_matrix(train_features)
# 稀疏矩阵的统计计算
for cls in classes:
# 只计算非零元素
cls_mask = (train_labels == cls)
cls_features = sparse_train[cls_mask]
feature_counts[cls] = cls_features.sum(axis=0).A1 # A1将矩阵转为1维数组
不同语言实现时需要注意各自的特点:
| 特性 | Java实现 | C++实现 | Python实现 |
|---|---|---|---|
| 数据结构 | HashMap, ArrayList | unordered_map, vector | dict, list, numpy array |
| 矩阵运算 | 需要第三方库或手动实现 | Eigen等库或手动实现 | numpy原生支持 |
| 稀疏矩阵 | 需要自定义或使用第三方库 | 需要自定义或使用第三方库 | scipy.sparse原生支持 |
| 开发效率 | 中等 | 较低 | 高 |
| 运行效率 | 高 | 最高 | 中等(numpy部分优化) |
问题描述:测试集中出现了训练集中从未见过的特征(词),导致条件概率为0。
解决方案:
问题描述:训练集中不同类别的样本数量差异很大,导致模型偏向多数类。
解决方案:
python复制# 处理类别不平衡的先验概率计算
def compute_class_prior(class_counts):
total = sum(class_counts.values())
# 不使用实际样本比例,而是设为均匀
return {cls: 1.0/len(class_counts) for cls in class_counts}
python复制# 向量化计算示例
def predict_proba(X):
jll = np.zeros((X.shape[0], len(self.classes_)))
for idx, cls in enumerate(self.classes_):
jll[:, idx] = self._joint_log_likelihood(X, cls)
# 计算概率
log_prob_x = logsumexp(jll, axis=1)
return np.exp(jll - log_prob_x[:, np.newaxis])
实际项目中,多项式朴素贝叶斯通常作为基线模型,它的训练速度快、实现简单,适合作为初步解决方案。当需要更高准确率时,可以考虑逻辑回归、SVM或深度学习模型。
java复制// 混合朴素贝叶斯的Java示例
public class HybridNaiveBayes {
// 处理连续特征的部分
private Map<Integer, GaussianDistribution> gaussianParams;
// 处理离散特征的部分
private Map<Integer, MultinomialDistribution> multinomialParams;
public void train(double[][] continuousFeatures, int[][] discreteFeatures, int[] labels) {
// 分别训练连续和离散部分的参数
trainGaussian(continuousFeatures, labels);
trainMultinomial(discreteFeatures, labels);
}
// ...其他实现细节
}
在实现朴素贝叶斯分类器时,我发现在处理高维稀疏数据时,使用对数概率和稀疏矩阵表示可以显著提升性能和减少内存使用。另外,拉普拉斯平滑系数的选择对模型效果有较大影响,需要通过交叉验证来确定最佳值。