贝叶斯算法是一种常用的概率统计方法,它利用贝叶斯定理来进行分类和预测。其在计算机还没有出现前几十年就存在了,那个时候科学家们都是用手算的,是最早的机器学习形式之一,该算法基于统计学原理,通过已知的先验概率和观测到的数据,更新对事件发生概率的估计。因为有着一个很强的假设,每个数据特征都是独立的,这也是条件独立的前提条件,也叫"朴素的"的假设,故叫朴素贝叶斯算法。
具体公式推导如下:
其中,
贝叶斯定理的核心思想是通过已知的条件概率和边际概率,计算出后验概率。先验概率是在考虑任何观测数据之前,基于先前的知识或经验得出的概率。后验概率是在考虑观测数据之后,根据先验概率和条件概率计算得出的概率。
其中,
为了进行分类,我们需要计算每个类别的后验概率P(C|X) ,并选择具有最高后验概率的类别作为预测结果。具体大白话来说就是,更具已知数据中这个类别的概率是多少再乘上数据中特征属于这个类别的概率的结果就是其后验概率了。而其中的这个 P(X|C) 似然概率就需要进行参数估计,见下一部分。
其中,
假设有样本数据
,它们独立同分布,概率密度函数或概率质量函数为
,其中
是未知参数。则似然函数为:
极大似然估计的目标是找到一组
的值,使得这个似然函数最大(即概率最大):
即满足:
解这个方程组就可以得到
的极大似然估计。 极大似然估计有以下几个主要性质:
综上,极大似然估计通过最大化似然函数,找到最有可能产生观测数据的模型参数,是一种常用而有效的参数估计方法。它具有渐进最优、无需先验等优点,理论性质良好。
拉普拉斯平滑
和Lidstone
平滑。,然后再进行归一化,以平滑先验概率的估计。贝叶斯平滑则使用一个更一般的先验分布,例如狄利克雷分布,通过调整参数来控制平滑的程度。
,我们可以将每个取值的出现次数加上一个常数
,然后再进行归一化,以平滑条件概率的估计。
贝叶斯估计相对于极大似然估计的优势在于它可以通过引入先验分布来减小估计的偏差,**并在数据较少时提供更稳定的估计结果。**然而,贝叶斯估计也会引入一定的偏差,因为它基于先验分布的假设。 在实际应用中,我们可以根据数据集的大小和特征的分布情况选择适当的参数估计方法。对于较大的数据集,极大似然估计通常可以提供较好的结果。而对于较小的数据集或特征分布较稀疏的情况,贝叶斯估计可以提供更稳定的估计结果。
这些公式和推导提供了贝叶斯算法的基本原理,但具体应用时需要根据实际情况进行相应的调整和优化。
基于朴素贝叶斯算法的生活案例可以是垃圾邮件分类。我们可以使用朴素贝叶斯算法来训练一个模型,该模型可以根据邮件的内容将其分类为垃圾邮件或非垃圾邮件。
具体的推导建模步骤如下:
例如,假设我们有两个文档: 文档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
实践大于理论 —— 伽利略 对数函数具有一些特殊的性质,使得它在概率计算中非常有用。 在概率计算中,我们通常需要计算多个概率的乘积。当这些概率非常小的时候,连续相乘可能会导致结果变为零或非常接近零。这是由于计算机浮点数的精度限制所导致的。 为了解决这个问题,我们可以使用对数概率进行计算。对数函数具有以下性质:
通过将概率转换为对数概率,我们可以将概率的乘法转换为对数概率的加法,从而避免了下溢问题。这样做的好处是,我们可以在对数空间中进行计算,而不会丢失精度(其实相当于是转换到对数空间了)。 在代码中,使用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}")
输出:
<class 'scipy.sparse._csr.csr_matrix'>
(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
的基本原理:
CountVectorizer
首先将文本分解为单独的单词(在英文中通常是通过空格来分隔),这个过程被称为分词。例如,句子 “The cat sat on the mat” 可能会被分解为 “The”, “cat”, “sat”, “on”, “the”, “mat”。
CountVectorizer
会创建一个词汇表,其中包含所有出现在所有文档中的唯一单词。例如,如果我们有两个文档,一个是 “The cat sat on the mat”,另一个是 “The dog sat on the log”,那么词汇表就会是 “The”, “cat”, “sat”, “on”, “the”, “mat”, “dog”, “log”。
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,不需要存储和显示。
总结一下,这个输出表示了每个邮件中包含的单词及其出现次数,这些特征已经转换为了向量化的表示,作为后续机器学习算法的输入。