♂️ 个人主页: @AI_magician
主页地址: 作者简介:CSDN内容合伙人,全栈领域优质创作者。
景愿:旨在于能和更多的热爱计算机的伙伴一起成长!!
♂️声明:本人目前大学就读于大二,研究兴趣方向人工智能&硬件(虽然硬件还没开始玩,但一直很感兴趣!希望大佬带带)
该文章收录专栏
[✨— 《深入解析机器学习:从原理到应用的全面指南》 —✨]
贝叶斯算法是一种常用的概率统计方法,它利用贝叶斯定理来进行分类和预测。其在计算机还没有出现前几十年就存在了,那个时候科学家们都是用手算的,是最早的机器学习形式之一,该算法基于统计学原理,通过已知的先验概率和观测到的数据,更新对事件发生概率的估计。因为有着一个很强的假设,每个数据特征都是独立的,这也是条件独立的前提条件,也叫"朴素的"的假设,故叫朴素贝叶斯算法。
具体公式推导如下:
其中,
贝叶斯定理的核心思想是通过已知的条件概率和边际概率,计算出后验概率。先验概率是在考虑任何观测数据之前,基于先前的知识或经验得出的概率。后验概率是在考虑观测数据之后,根据先验概率和条件概率计算得出的概率。
其中,
为了进行分类,我们需要计算每个类别的后验概率P(C|X) ,并选择具有最高后验概率的类别作为预测结果。具体大白话来说就是,更具已知数据中这个类别的概率是多少再乘上数据中特征属于这个类别的概率的结果就是其后验概率了。而其中的这个 P(X|C) 似然概率就需要进行参数估计,见下一部分。
其中,
假设样本数据是从某个概率分布生成的,该分布属于一个参数化家族。
在这个参数化家族中,找到一组参数,使得样本数据Based on这组参数生成的概率(即似然函数)最大。
这组参数就是对总体参数的极大似然估计。
假设有样本数据 X 1 , X 2 , . . . , X n X_1, X_2, ..., X_n X1,X2,...,Xn,它们独立同分布,概率密度函数或概率质量函数为 f ( x ∣ θ ) f(x|\theta) f(x∣θ),其中 θ \theta θ 是未知参数。则似然函数为:
L ( θ ∣ X 1 , X 2 , . . . , X n ) = f ( X 1 ∣ θ ) f ( X 2 ∣ θ ) . . . f ( X n ∣ θ ) L(\theta|X_1,X_2,...,X_n) = f(X_1|\theta)f(X_2|\theta)...f(X_n|\theta) L(θ∣X1,X2,...,Xn)=f(X1∣θ)f(X2∣θ)...f(Xn∣θ)
极大似然估计的目标是找到一组 θ \theta θ 的值,使得这个似然函数最大(即概率最大):
θ MLE = arg max θ L ( θ ∣ X 1 , X 2 , . . . , X n ) \theta_{\text{MLE}} = \arg\max_\theta L(\theta|X_1,X_2,...,X_n) θMLE=argmaxθL(θ∣X1,X2,...,Xn)
即满足:
d L d θ = 0 \frac{dL}{d\theta} = 0 dθdL=0
解这个方程组就可以得到 θ \theta θ 的极大似然估计。
极大似然估计有以下几个主要性质:
当样本量趋于无穷大时,MLE是渐进最优的,即收敛于真实参数值。
MLE不需要事先指定先验分布。
对给定的数据集,MLE总是存在的。
MLE是充分利用数据信息的。
MLE比较容易计算。
综上,极大似然估计通过最大化似然函数,找到最有可能产生观测数据的模型参数,是一种常用而有效的参数估计方法。它具有渐进最优、无需先验等优点,理论性质良好。
拉普拉斯平滑
和Lidstone
平滑。
- 贝叶斯估计(Bayesian Estimation):
- 先验概率的估计:贝叶斯估计引入了一个先验分布,用于对先验概率进行平滑。常见的先验分布包括拉普拉斯平滑和贝叶斯平滑。拉普拉斯平滑将每个类别的出现次数加上一个常数 α \alpha α,然后再进行归一化,以平滑先验概率的估计。贝叶斯平滑则使用一个更一般的先验分布,例如狄利克雷分布,通过调整参数来控制平滑的程度。
- 条件概率的估计:对于条件概率的估计,我们可以使用与先验概率相似的方法进行平滑。例如,对于每个特征 X j X_j Xj,我们可以将每个取值的出现次数加上一个常数 α \alpha α,然后再进行归一化,以平滑条件概率的估计。
贝叶斯估计相对于极大似然估计的优势在于它可以通过引入先验分布来减小估计的偏差,**并在数据较少时提供更稳定的估计结果。**然而,贝叶斯估计也会引入一定的偏差,因为它基于先验分布的假设。
在实际应用中,我们可以根据数据集的大小和特征的分布情况选择适当的参数估计方法。对于较大的数据集,极大似然估计通常可以提供较好的结果。而对于较小的数据集或特征分布较稀疏的情况,贝叶斯估计可以提供更稳定的估计结果。
这些公式和推导提供了贝叶斯算法的基本原理,但具体应用时需要根据实际情况进行相应的调整和优化。
基于朴素贝叶斯算法的生活案例可以是垃圾邮件分类。我们可以使用朴素贝叶斯算法来训练一个模型,该模型可以根据邮件的内容将其分类为垃圾邮件或非垃圾邮件。
具体的推导建模步骤如下:
数据准备:收集一批已经标记好的垃圾邮件和非垃圾邮件样本数据。每个样本数据包含邮件的内容和标签(垃圾或非垃圾)。
数据预处理:对邮件内容进行预处理,包括去除停用词、标点符号和数字,将文本转换为小写,并进行词干提取或词形还原等操作。
特征提取:将每个邮件转换为特征向量表示。常用的方法是使用词袋模型(Bag of Words),将每个单词作为一个特征,统计每个单词在邮件中出现的频率。
词袋模型是一种常用的文本特征提取方法,它将文本转换为固定长度的向量表示。在词袋模型中,文本被看作是一个袋子,其中包含了所有单词,而忽略了它们的顺序和语法结构。每个词都独立地出现。
以下是词袋模型的基本步骤:
- 构建词汇表:首先,需要构建一个词汇表,其中包含了所有在训练数据中出现的单词。这个词汇表可以根据整个语料库或者特定的文档集合来构建。
- 向量化:对于每个文本样本,将其转换为一个特征向量。特征向量的长度等于词汇表中单词的数量。通常,可以使用稀疏向量表示来表示特征向量,其中大部分元素为零。
- 计算词频:对于每个文本样本,计算每个单词在文本中出现的频率。将这些频率作为特征向量的元素值。
例如,假设我们有两个文档:
文档1: “我喜欢看电影”
文档2: “我不喜欢看电影”在这个例子中,我们的词汇表是 {我,喜欢,看,电影,不}。那么,文档1和文档2的词袋表示分别是:
文档1: [1, 1, 1, 1, 0]
文档2: [1, 1, 1, 1, 1]词袋模型的主要优点是简单和高效。然而,它也有一些缺点。首先,它忽略了词的顺序,这在许多情况下是非常重要的。其次,它假设所有的词都是独立的,不考虑词与词之间的关系。最后,它只考虑了词频,没有考虑词的重要性,这在信息检索中是非常重要的。(在一些应用中只需要词频很有效)
为了解决这些问题,有一些改进的词袋模型,如TF-IDF模型,它考虑了词的重要性。还有词嵌入模型,如Word2Vec和GloVe,它们考虑了词与词之间的关系。
计算概率:根据训练数据计算每个特征在垃圾邮件和非垃圾邮件中出现的概率。具体来说,这些单词对应的特征控制了属于垃圾邮箱还是非垃圾邮箱的概率。
训练模型:根据计算得到的概率,训练一个朴素贝叶斯分类器模型。
预测分类:对于一个新的邮件,将其转换为特征向量表示,并使用训练好的模型预测其分类。
下面是一个不使用封装库的Python代码示例:
import re import math # 数据准备 spam_emails = [ "Get a free laptop now!", "Earn money fast with no effort!", "Enlarge your assets with our product!", "Meet singles in your area tonight!" ] ham_emails = [ "Hi, how are you?", "Can we meet tomorrow?", "Remember to buy groceries on your way home.", "I'll be there in 5 minutes." ] # 数据预处理 def preprocess_email(email): email = email.lower() # 全部小写 email = re.sub(r'\W', ' ', email) # 去除非字母数字字符 email = re.sub(r'\s+', ' ', email) # 合并多个空格为一个空格 return email.strip() # 去除空格 spam_emails = [preprocess_email(email) for email in spam_emails] ham_emails = [preprocess_email(email) for email in ham_emails] # 特征提取(每一封邮件) def extract_features(email): features = {} words = email.split() for word in words: features[word] = features.get(word, 0) + 1 # 字典查询不存在值返回默认值0实现自动添加。 return features spam_features = [extract_features(email) for email in spam_emails] ham_features = [extract_features(email) for email in ham_emails] print(spam_emails) print(spam_features) # 计算概率 (建立词汇表及其对应单词的概率) spam_word_count = {} ham_word_count = {} spam_total_words = 0 ham_total_words = 0 for email in spam_features: for word, count in email.items(): spam_word_count[word] = spam_word_count.get(word, 0) + count spam_total_words += count for email in ham_features: for word, count in email.items(): ham_word_count[word] = ham_word_count.get(word, 0) + count ham_total_words += count print(spam_word_count,spam_total_words) spam_word_prob = {} ham_word_prob = {} for word, count in spam_word_count.items(): spam_word_prob[word] = count / spam_total_words # 极大似然估计求概率 ( p(x|c(spam))/m^i ) for word, count in ham_word_count.items(): ham_word_prob[word] = count / ham_total_words print("spam_word_prob:\n",spam_word_prob) # 训练模型 spam_prior = len(spam_emails) / (len(spam_emails) + len(ham_emails)) # 先验概率 P(C(spam)|X) ham_prior = len(ham_emails) / (len(spam_emails) + len(ham_emails)) # 先验概率 P(C(ham)|X) # 预测分类 def predict(email): email = preprocess_email(email) features = extract_features(email) # 加法求分数 (避免概率相乘时出现下溢问题) spam_score = math.log(spam_prior) ham_score = math.log(ham_prior) print(spam_score,ham_score) for word, count in features.items(): # print(word,spam_word_prob[word]) if word in spam_word_prob: spam_score -= math.log(spam_word_prob[word]) * count # 因为词频一定是在0~1之间的,要递增需要相减 if word in ham_word_prob: ham_score -= math.log(ham_word_prob[word]) * count print(spam_score,ham_score) spam_score = math.exp(spam_score) ham_score = math.exp(ham_score) # 乘法分数 (概率相乘时出现下溢问题) # spam_score = (spam_prior) # ham_score = (ham_prior) # for word, count in features.items(): # if word in spam_word_prob: # spam_score *= spam_word_prob[word] ** count # if word in ham_word_prob: # ham_score *= ham_word_prob[word] ** count print(spam_score,ham_score) if spam_score >= ham_score: return "spam" else: return "ham" # 测试 test_email = "Get a free laptop now!" prediction = predict(test_email) print(f"\nPrediction for email '{test_email}': {prediction}")
输出:
['get a free laptop now', 'earn money fast with no effort', 'enlarge your assets with our product', 'meet singles in your area tonight'] [{'get': 1, 'a': 1, 'free': 1, 'laptop': 1, 'now': 1}, {'earn': 1, 'money': 1, 'fast': 1, 'with': 1, 'no': 1, 'effort': 1}, {'enlarge': 1, 'your': 1, 'assets': 1, 'with': 1, 'our': 1, 'product': 1}, {'meet': 1, 'singles': 1, 'in': 1, 'your': 1, 'area': 1, 'tonight': 1}] {'get': 1, 'a': 1, 'free': 1, 'laptop': 1, 'now': 1, 'earn': 1, 'money': 1, 'fast': 1, 'with': 2, 'no': 1, 'effort': 1, 'enlarge': 1, 'your': 2, 'assets': 1, 'our': 1, 'product': 1, 'meet': 1, 'singles': 1, 'in': 1, 'area': 1, 'tonight': 1} 23 spam_word_prob: {'get': 0.043478260869565216, 'a': 0.043478260869565216, 'free': 0.043478260869565216, 'laptop': 0.043478260869565216, 'now': 0.043478260869565216, 'earn': 0.043478260869565216, 'money': 0.043478260869565216, 'fast': 0.043478260869565216, 'with': 0.08695652173913043, 'no': 0.043478260869565216, 'effort': 0.043478260869565216, 'enlarge': 0.043478260869565216, 'your': 0.08695652173913043, 'assets': 0.043478260869565216, 'our': 0.043478260869565216, 'product': 0.043478260869565216, 'meet': 0.043478260869565216, 'singles': 0.043478260869565216, 'in': 0.043478260869565216, 'area': 0.043478260869565216, 'tonight': 0.043478260869565216} -0.6931471805599453 -0.6931471805599453 2.4423470353692043 -0.6931471805599453 5.577841251298354 -0.6931471805599453 8.713335467227504 -0.6931471805599453 11.848829683156653 -0.6931471805599453 14.984323899085803 -0.6931471805599453 3218171.4999999995 0.5 Prediction for email 'Get a free laptop now!': spam
实践大于理论 —— 伽利略
对数函数具有一些特殊的性质,使得它在概率计算中非常有用。
在概率计算中,我们通常需要计算多个概率的乘积。当这些概率非常小的时候,连续相乘可能会导致结果变为零或非常接近零。这是由于计算机浮点数的精度限制所导致的。
为了解决这个问题,我们可以使用对数概率进行计算。对数函数具有以下性质:
- 对数函数是单调递增的。这意味着概率的对数值的大小顺序与概率本身的大小顺序(都会变小)相同。因此,对数概率的加法可以代替概率的乘法。
- 对数函数将乘法转换为加法。对数函数的一个重要性质是,它将乘法操作转换为加法操作。具体来说,对数函数满足以下等式:log(ab) = log(a) + log(b)。这意味着将两个概率的对数相加,等于将这两个概率相乘。
通过将概率转换为对数概率,我们可以将概率的乘法转换为对数概率的加法,从而避免了下溢问题。这样做的好处是,我们可以在对数空间中进行计算,而不会丢失精度(其实相当于是转换到对数空间了)。
在代码中,使用math.log函数对概率进行取对数操作,将概率的乘法转换为对数概率的加法。然后,在最后比较概率大小时,使用math.exp函数将对数概率转换回原始概率进行比较。
总结起来,使用对数概率进行计算是为了避免概率相乘时出现下溢问题,并且利用对数函数的性质将乘法转换为加法,从而提高计算的准确性和效率。
这段代码用了简单的词频特征提取方法,将每个单词的计数作为特征(词频),且在计算概率时没有进行平滑处理。平滑处理是为了避免在训练数据中出现未见过的单词时,概率为零的情况。sklearn中的MultinomialNB分类器**默认使用了拉普拉斯平滑(Laplace smoothing)**来处理这种情况。并默认使用了更复杂的特征提取方法,称为词袋模型(Bag of Words),它将每个单词的出现与否作为特征
下面是一个使用封装库
scikit-learn
的Python代码示例:from sklearn.feature_extraction.text import CountVectorizer from sklearn.naive_bayes import MultinomialNB # 数据准备 spam_emails = [ "Get a free laptop now!", "Earn money fast with no effort!", "Enlarge your assets with our product!", "Meet singles in your area tonight!" ] ham_emails = [ "Hi, how are you?", "The cat sat on the mat", "Can we meet tomorrow?", "Remember to buy groceries on your way home.", "I'll be there in 5 minutes." ] labels = ["spam", "spam", "spam", "spam", "ham", "ham", "ham", "ham", "ham"] # 特征提取 vectorizer = CountVectorizer() X = vectorizer.fit_transform(spam_emails + ham_emails) print(type(X)) print(X) print(vectorizer.get_feature_names_out()) print(X.toarray()) # 训练模型 model = MultinomialNB() # 多项式朴素贝叶斯 model.fit(X, labels) # 接受数组和稀疏矩阵 # 预测分类 test_email = ["Get a free laptop now!", "how are you?"] test_email_vector = vectorizer.transform(test_email) print(test_email_vector) prediction = model.predict(test_email_vector)[0] print(f"Prediction for email '{test_email}': {prediction}")
输出:
(0, 12) 1 (0, 11) 1 (0, 18) 1 (0, 25) 1 (1, 7) 1 (1, 23) 1 (1, 10) 1 (1, 39) 1 (1, 24) 1 (1, 8) 1 (2, 39) 1 (2, 9) 1 (2, 41) 1 (2, 2) 1 (2, 27) 1 (2, 28) 1 (3, 41) 1 (3, 21) 1 (3, 31) 1 (3, 17) 1 (3, 1) 1 (3, 36) 1 (4, 14) 1 (4, 16) 1 (4, 0) 1 (4, 40) 1 (5, 32) 2 (5, 6) 1 (5, 30) 1 (5, 26) 1 (5, 20) 1 (6, 21) 1 (6, 5) 1 (6, 38) 1 (6, 35) 1 (7, 41) 1 (7, 26) 1 (7, 29) 1 (7, 34) 1 (7, 4) 1 (7, 13) 1 (7, 37) 1 (7, 15) 1 (8, 17) 1 (8, 19) 1 (8, 3) 1 (8, 33) 1 (8, 22) 1 ['are' 'area' 'assets' 'be' 'buy' 'can' 'cat' 'earn' 'effort' 'enlarge' 'fast' 'free' 'get' 'groceries' 'hi' 'home' 'how' 'in' 'laptop' 'll' 'mat' 'meet' 'minutes' 'money' 'no' 'now' 'on' 'our' 'product' 'remember' 'sat' 'singles' 'the' 'there' 'to' 'tomorrow' 'tonight' 'way' 'we' 'with' 'you' 'your'] [[0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0] [0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 0 1] [0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1] [1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0] [0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 2 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0] [0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 1 0 0 0 1] [0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0]] (0, 11) 1 (0, 12) 1 (0, 18) 1 (0, 25) 1 (1, 0) 1 (1, 16) 1 (1, 40) 1 Prediction for email '['Get a free laptop now!', 'how are you?']': spam
CountVectorizer
是sklearn.feature_extraction.text
模块中的一个类,它用于将文本数据转换为向量形式,这种形式对于机器学习算法的输入非常有用。下面是CountVectorizer
的基本原理:
Tokenization(分词):
CountVectorizer
首先将文本分解为单独的单词(在英文中通常是通过空格来分隔),这个过程被称为分词。例如,句子 “The cat sat on the mat” 可能会被分解为 “The”, “cat”, “sat”, “on”, “the”, “mat”。Vocabulary Building(构建词汇表):然后,
CountVectorizer
会创建一个词汇表,其中包含所有出现在所有文档中的唯一单词。例如,如果我们有两个文档,一个是 “The cat sat on the mat”,另一个是 “The dog sat on the log”,那么词汇表就会是 “The”, “cat”, “sat”, “on”, “the”, “mat”, “dog”, “log”。Encoding(编码):最后,
CountVectorizer
会将每个文档转换为一个向量。向量的长度等于词汇表中的单词数量,每个元素代表词汇表中对应单词在文档中出现的次数。例如,对于文档 “The cat sat on the mat” 和词汇表 “The”, “cat”, “sat”, “on”, “the”, “mat”, “dog”, “log”,其对应的向量可能是 [2, 1, 1, 1, 1, 1, 0, 0](这里假设我们不区分大小写,“The” 和 “the” 被视为同一个单词,则表示出现了两次)。这就是
CountVectorizer
的基本原理。需要注意的是,CountVectorizer
还有许多参数可以调整,例如你可以选择是否将所有单词转换为小写,是否删除停用词,是否包含n-gram特征等等。其中输出是稀疏矩阵的表示形式。(0, 11) 1 表示在第0个样本(也就是第一个邮件"Get a free laptop now!")中,词汇表中的第11个词出现了1次。
(1, 6) 1 表示在第1个样本(第二个邮件"Earn money fast with no effort!")中,词汇表中的第6个词出现了1次。
以此类推。这里的词汇表是根据所有邮件内容提取出来的,包含了所有唯一的词。数字11、6等就是每个词在这个词汇表中的位置。
而输出的是稀疏矩阵(sparse matrix)的形式,只显示了非零元素,比如一个词在一个邮件中出现过,那么就显示其行号、列号和计数,没有出现的位置就不显示了。
这样的稀疏矩阵表示可以节省空间,因为大多数位置都是0,不需要存储和显示。
总结一下,这个输出表示了每个邮件中包含的单词及其出现次数,这些特征已经转换为了向量化的表示,作为后续机器学习算法的输入。
到这里,如果还有什么疑问
欢迎私信博主问题哦,博主会尽自己能力为你解答疑惑的!
如果对你有帮助,你的赞是对博主最大的支持!!