朴素贝叶斯
贝叶斯算法是一种常用的概率统计方法,它利用贝叶斯定理来进行分类和预测。其在计算机还没有出现前几十年就存在了,那个时候科学家们都是用手算的,是最早的机器学习形式之一,该算法基于统计学原理,通过已知的先验概率和观测到的数据,更新对事件发生概率的估计。因为有着一个很强的假设,每个数据特征都是独立的,这也是条件独立的前提条件,也叫"朴素的"的假设,故叫朴素贝叶斯算法。
具体公式推导如下:
- 贝叶斯定理表达式:
根据贝叶斯定理,我们可以得到以下表达式:
P(A|B) = (P(B|A) * P(A)) / P(B) (注意的是 P(B|A) 的分母范围不是整体样本,而是P(A),所以乘上P(A) 就相当于是基于全局的 P(B|A)
其中,
- P(A|B) 是在给定观测数据 B 的条件下事件 A 发生的概率(后验概率)。
- P(B|A) 是在事件 A 发生的条件下观测到数据 B 的概率(似然)。
- P(A) 是事件 A 发生的先验概率。
- P(B) 是观测到的数据 B 的概率。
- 朴素贝叶斯分类器:
在朴素贝叶斯分类器中,我们假设特征之间相互独立,即给定类别的情况下,特征之间是条件独立的。基于这个假设,我们可以将贝叶斯定理改写为:
P(C|X) = (P(X|C) * P©) / P(X)
其中,
- P(C|X) 是在给定观测数据 X 的条件下类别 C 发生的概率(后验概率)。
- P(X|C) 是在类别 C 发生的条件下观测到数据 X 的概率(似然)。
- P© 是类别 C 的先验概率。
- P(X) 是观测到的数据 X 的概率。
- 具体公式推导:
假设观测数据有 n 个特征,我们可以将数据表示为 X = (x₁, x₂, …, xₙ),其中 xᵢ 表示第 i 个特征的取值。根据朴素贝叶斯分类器的条件独立性假设,上述表达式可以进一步简化为:
P(C|X) = (P(x₁|C) * P(x₂|C) * … * P(xₙ|C) * P©) / P(X)
为了进行分类,我们需要计算每个类别的后验概率P(C|X) ,并选择具有最高后验概率的类别作为预测结果。具体大白话来说就是,更具已知数据中这个类别的概率是多少再乘上数据中特征属于这个类别的概率的结果就是其后验概率了。而其中的这个 P(X|C) 似然概率就需要进行参数估计,见下一部分。
- 参数估计:
在实际应用中,我们需要利用训练数据来计算各个概率的估计值。常见的参数估计方法有极大似然估计和贝叶斯估计。 (极大似然估计 vs 贝叶斯估计:谁才是朴素贝叶斯的最佳伴侣?)
- 极大似然估计:假设训练集中包含 m 个样本,属于类别 C 的样本个数为 mᵢ(i ∈ {1, 2, …, k},k 是类别的个数)。则根据极大似然估计,我们可以得到:
P© = mᵢ / m
P(x|C) = m(x, C) / mᵢ
其中,
- P© 是类别 C 的先验概率。
- P(x|C) 是在类别 C 发生的条件下特征 x 出现的概率(似然)。
- m(x, C) 是在训练集中属于类别 C 且特征为 x 的样本个数。
- 贝叶斯估计:贝叶斯估计是对极大似然估计进行修正,以解决可能出现的概率为零的情况。常见的贝叶斯估计方法有
拉普拉斯平滑
和Lidstone
平滑。
这些公式和推导提供了贝叶斯算法的基本原理,但具体应用时需要根据实际情况进行相应的调整和优化。
计算概率:根据训练数据计算每个特征在垃圾邮件和非垃圾邮件中出现的概率。具体来说,这些单词对应的特征控制了属于垃圾邮箱还是非垃圾邮箱的概率。
训练模型:根据计算得到的概率,训练一个朴素贝叶斯分类器模型。
预测分类:对于一个新的邮件,将其转换为特征向量表示,并使用训练好的模型预测其分类。
下面是一个不使用封装库的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
这段代码用了简单的词频特征提取方法,将每个单词的计数作为特征(词频),且在计算概率时没有进行平滑处理。平滑处理是为了避免在训练数据中出现未见过的单词时,概率为零的情况。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
的基本原理:
-
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,不需要存储和显示。
总结一下,这个输出表示了每个邮件中包含的单词及其出现次数,这些特征已经转换为了向量化的表示,作为后续机器学习算法的输入。
🤞到这里,如果还有什么疑问🤞
🎩欢迎私信博主问题哦,博主会尽自己能力为你解答疑惑的!🎩
🥳如果对你有帮助,你的赞是对博主最大的支持!!🥳