朴素贝叶斯算法 0基础小白也能懂(附代码)

朴素贝叶斯算法 0基础小白也能懂(附代码)

原文链接

啥是朴素贝叶斯

在机器学习中如KNN、逻辑回归、决策树等模型都是判别方法,也就是直接学习出特征输出\(Y\)和特征\(X\)之间的关系(决策函数\(Y=f(x)\)或者条件分布 \(P(Y|X)\)。但朴素贝叶斯是生成方法,它直接找出特征输出\(Y\)和特征\(X\)的联合分布\(P(X,Y)\),进而通过\(P(Y|X)=\frac{P(X,Y)}{P(X)}\)计算得出结果判定

在「假设待分类项的各个属性相互独立」的情况下,构造出来的分类算法就称为朴素的,即朴素贝叶斯算法。

所谓「朴素」,是假定所有输入事件之间是相互独立。进行这个假设是因为独立事件间的概率计算更简单。

基本思想

对于给定的待分类项\(X(a_1,a_2,a_3,...,a_n)\),求解在此项出现的条件下各个类别\(y_i\)出现的概率,哪个\(P(y_i|X)\)最大,就把此待分类项归属于哪个类别。\(X\)是一个特征向量,其中的\(a_1,a_2,...,a_n\)是样本的特征值,这些特征值可以是数值、文本、类别标签等。

比如文本分类:在垃圾邮件检测任务中,待分类项可能是一封电子邮件。特征可以包括邮件中出现的词语、词频等。模型需要根据这些特征判断这封邮件是“垃圾邮件”还是“正常邮件”。\(X=(wordcount_1,wordcount2,...,wordcount_n)\)

待分类项:

定义

我们的目的还是分类,要计算最后的\(P(y_k|X)\)就需要计算那里面的n项

这些里的\(a_1,a_2,...,a_n\)都是训练集里的向量的样本特征,得到的是在各类别下各个特征的条件概率,那求这些东西有什么用呢?

图里说的还是挺隐晦的,这里其实首先用到了贝叶斯公式,之后\(P(X|y_i)\)为什么可以变成后面那一堆呢,涉及到一个链式法则,这里的\(P(X|y_i)=P(a_1|y_i)*P(a_2|a_1,y_i)*P(a_3|a_1,a_2,y_i)*...*P(a_n|a_1,a_2,...,a_{n-1},y_i)\)
然而,朴素贝叶斯假设特征是条件独立的,即在给定类别\(y_i\)的情况下,各特征之间是独立的。因此,链式法则可以简化为上面那样

到这里\(P(X|y_i)\)根据我们上一步算出来的那些东西乘一下其实已经求出来了,然后\(P(X)\)分母相当于在数据库中\(X\)存在的概率,是固定常数,要求的就只剩\(P(y_i)\)

这里面的\(D\)是样本总数,他相当于近似了一下\(P(y_i)\),毕竟\(P(y_i)=\frac{训练集中类别y_i的样本数}{训练集总样本数}\),到现在其实就已经全部出来了,后面的\(P(a_j|y_i)\)是进一步解释而已

算法整体流程如下

代码实现

这里我们使用 fetch_20newsgroups 数据集,这个数据集包含 20 个不同新闻组的文本数据,常用于文本分类任务。

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import accuracy_score, classification_report

# 1. 加载 20newsgroups 数据集
data = fetch_20newsgroups(subset='train', shuffle=True, random_state=42)

# 2. 将文本数据转换为词频矩阵
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(data.data)
y = data.target

# 3. 拆分数据集,20% 用于测试,80% 用于训练
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 4. 初始化并训练多项式朴素贝叶斯模型
nb_model = MultinomialNB()
nb_model.fit(X_train, y_train)

# 5. 对测试集进行预测
y_pred = nb_model.predict(X_test)

# 6. 计算并打印准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.2f}")

# 7. 打印分类报告
print("Classification Report:")
print(classification_report(y_test, y_pred, target_names=data.target_names))

运行起来可能有点慢,结果如下,一共20个类别的结果

Accuracy: 0.84
Classification Report:
                          precision    recall  f1-score   support

             alt.atheism       0.91      0.94      0.92        97
           comp.graphics       0.63      0.88      0.73       104
 comp.os.ms-windows.misc       0.92      0.10      0.17       115
comp.sys.ibm.pc.hardware       0.56      0.80      0.66       123
   comp.sys.mac.hardware       0.94      0.73      0.82       126
          comp.windows.x       0.70      0.92      0.80       106
            misc.forsale       0.91      0.64      0.75       109
               rec.autos       0.90      0.91      0.91       139
         rec.motorcycles       0.96      0.89      0.92       122
      rec.sport.baseball       0.98      0.93      0.95       102
        rec.sport.hockey       0.95      0.97      0.96       108
               sci.crypt       0.78      0.98      0.87       125
         sci.electronics       0.82      0.79      0.80       114
                 sci.med       0.93      0.95      0.94       119
               sci.space       0.92      0.95      0.94       127
  soc.religion.christian       0.77      0.93      0.84       122
      talk.politics.guns       0.90      0.93      0.91       121
   talk.politics.mideast       0.83      0.98      0.90       102
      talk.politics.misc       0.90      0.90      0.90       107
      talk.religion.misc       1.00      0.45      0.62        75

                accuracy                           0.84      2263
               macro avg       0.86      0.83      0.82      2263
            weighted avg       0.86      0.84      0.82      2263

下面是一段原生朴素贝叶斯的伪代码

class MultinomialNaiveBayes:
    def __init__(self):
        """
        初始化多项式朴素贝叶斯分类器。初始化类先验概率和特征条件概率的字典。
        """
        self.class_prior = {}  # 存储类别的先验概率
        self.feature_likelihood = {}  # 存储特征的条件概率(在每个类别下)
        self.classes = []  # 存储所有的类别

    def fit(self, X, y):
        """
        训练多项式朴素贝叶斯模型,计算每个类别的先验概率和每个特征的条件概率。
        
        Args:
            X (numpy.ndarray): 词频矩阵(每一行是一个文档,每一列是一个特征)。
            y (numpy.ndarray): 文档对应的类别标签。
        """
        # 获取所有的类别
        self.classes = np.unique(y)
        num_docs = X.shape[0]  # 文档数量
        num_features = X.shape[1]  # 特征数量(词汇表大小)

        # 计算每个类别的先验概率 P(C_k)
        for c in self.classes:
            # 获取属于类别 c 的所有文档
            class_docs = X[y == c]
            # 类别 c 的先验概率是该类别的文档数占总文档数的比例
            self.class_prior[c] = class_docs.shape[0] / num_docs

            # 计算类别 c 下每个特征的条件概率 P(x_i | C_k)
            total_word_count = np.sum(class_docs)  # 类别 c 中所有单词的总数
            word_count = np.sum(class_docs, axis=0)  # 类别 c 中每个特征的总计数
            # 使用拉普拉斯平滑计算条件概率
            self.feature_likelihood[c] = (word_count + 1) / (total_word_count + num_features)

    def predict(self, X):
        """
        使用训练好的模型对新数据进行预测。
        
        Args:
            X (numpy.ndarray): 待分类的文档词频矩阵。

        Returns:
            numpy.ndarray: 预测的类别标签。
        """
        predictions = []  # 存储所有文档的预测结果
        for doc in X:
            class_probabilities = {}  # 存储文档属于每个类别的概率
            for c in self.classes:
                # 计算文档属于类别 c 的对数概率(使用对数是为了防止下溢)
                log_prob = np.log(self.class_prior[c]) + np.sum(np.log(self.feature_likelihood[c]) * doc)
                class_probabilities[c] = log_prob
            # 选择概率最大的类别作为预测结果
            predictions.append(max(class_probabilities, key=class_probabilities.get))
        return np.array(predictions)  # 返回所有文档的预测类别

词频矩阵太大会报错

posted @ 2024-08-27 17:53  Mephostopheles  阅读(12)  评论(0编辑  收藏  举报