AI开发:逻辑回归 - 实战演练- 垃圾邮件的识别(二)
接上一篇AI开发:逻辑回归 - 实战演练- 垃圾邮件的识别(一)
new_email 无论为什么文本,识别结果几乎都是垃圾邮件,因此我们需要对源码的逻辑进行梳理一下:
在代码中,new_email
无论赋值为何内容都被识别为垃圾邮件(spam),很可能是以下原因之一导致的:
1. 模型训练数据偏差
- 如果训练数据中垃圾邮件的数量占绝大多数,模型可能会倾向于将所有输入都分类为垃圾邮件。这种问题叫 类别不平衡。
- 解决方法:
- 平衡数据集:增加非垃圾邮件的样本数量或减少垃圾邮件的样本数量。
- 使用加权损失函数:在模型训练中对少数类赋予更高的权重。
- 使用过采样(如 SMOTE)或欠采样技术调整数据分布。
2. 决策边界问题
- 如果模型的决策边界定义不明确(比如分类器的阈值设置过低或过高),可能会导致模型将所有样本错误分类为某一类别。
- 解决方法:
- 检查分类器的阈值(例如,对于逻辑回归,默认阈值为 0.5),并调整为更合理的值。
- 可视化模型的概率分布,分析垃圾邮件和非垃圾邮件的分类结果。
3. 特征处理问题
- 如果
new_email
的输入特征没有正确提取(例如特征全为零或异常值),分类器可能会默认分类为垃圾邮件。 - 解决方法:
- 检查
new_email
的特征提取过程,确保提取后的特征与训练集特征保持一致。 - 验证输入数据是否经过与训练数据相同的预处理步骤(如分词、TF-IDF 向量化等)。
- 检查
4. 过拟合问题
- 如果模型在训练过程中过拟合,它可能会将所有未知输入归类为训练集中出现频率较高的类别。
- 解决方法:
- 重新训练模型,使用正则化技术(如 L1 或 L2)。
- 增加训练数据的多样性,避免模型记住训练集中的特定模式。
5. 代码逻辑问题
- 检查预测逻辑是否正确,比如是否意外硬编码了返回值。
- 示例问题:
if predicted_label == 1: return "Spam" else: return "Spam" # 错误逻辑,导致所有都被识别为垃圾邮件
6. 模型性能问题
- 如果模型本身性能很差,可能是因为特征不足或算法选择不当,导致分类效果失败。
- 解决方法:
- 评估模型性能(如查看混淆矩阵、F1 分数)。
- 尝试使用更强的分类器(如随机森林、SVM 或深度学习模型)。
对代码的检查建议
-
打印输入特征: 打印出
new_email
的特征向量,检查特征是否异常。 -
验证模型输出: 检查模型的概率预测输出(如
predict_proba
方法),确认阈值是否设置合理。 -
调试代码: 确认是否存在逻辑错误,特别是在预处理和分类阶段。
因此我们调试一个单独的测试单元:
基于之前的代码,结合分析问题的原因,进行修改以确保模型能够正常工作并准确分类新邮件。以下是一些改进:
修改代码的核心点
-
处理类别不平衡
使用class_weight='balanced'
或对数据进行平衡处理。 -
检查输入特征
确保new_email
的特征提取与训练数据一致。 -
调整决策阈值
添加概率输出,并允许用户调整分类阈值。 -
增加性能调试信息
输出模型的预测概率和特征向量,方便检查。
修订后的代码测试单元
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix
# 示例数据
data = {
'email': [
"Win a free iPhone",
"Your account is locked, click here",
"Meeting tomorrow at 10 AM",
"How are you doing today?"
],
'label': [1, 1, 0, 0]
}
data_df = pd.DataFrame(data)
# 特征提取
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(data_df['email'])
y = data_df['label']
# 训练测试集划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 处理类别不平衡
model = LogisticRegression(class_weight='balanced') # 添加 class_weight
model.fit(X_train, y_train)
# 测试模型
y_pred = model.predict(X_test)
print("分类报告:\n", classification_report(y_test, y_pred))
# 测试混淆矩阵
print("混淆矩阵:\n", confusion_matrix(y_test, y_pred))
# 新邮件分类
new_email = "Meeting schedule confirmed" # 替换为任何测试字符串
new_email_features = vectorizer.transform([new_email])
predicted_prob = model.predict_proba(new_email_features)[0] # 获取概率
predicted_label = (predicted_prob[1] > 0.5).astype(int) # 自定义阈值,默认 0.5
print(f"预测概率:{predicted_prob}")
print(f"预测结果:{'Spam' if predicted_label == 1 else 'Not Spam'}")
修改内容说明
-
类别不平衡处理:
- 在
LogisticRegression
中增加class_weight='balanced'
,让模型在训练时对垃圾邮件和非垃圾邮件赋予相同权重。
- 在
-
可调阈值:
- 默认设置阈值为
0.5
,但可以修改为更低或更高以优化精度和召回率。
- 默认设置阈值为
-
模型输出可视化:
- 添加
predict_proba
输出,显示垃圾邮件和非垃圾邮件的概率,便于调试。
- 添加
-
新增输入检查:
- 确保
new_email
特征提取与训练数据一致。
- 确保
测试案例
试试将 new_email
替换为以下字符串,观察输出:
"Win a free vacation now!"
(垃圾邮件)"Lunch meeting tomorrow at noon"
(非垃圾邮件)"Click here to reset your password"
(垃圾邮件)
如果模型仍然总是输出垃圾邮件,请注意以下的关键点:
- 数据集是否过于不平衡。
classification_report
的输出。predict_proba
的详细概率分布。
这样不断调试,我们可以进一步优化代码。
以下是改进了处理流程、模型平衡和调试信息的展示:
改进内容
-
处理类别不平衡:
- 使用
class_weight='balanced'
或直接进行数据采样平衡,避免模型倾向于输出多数类。 - 打印垃圾邮件和正常邮件的分布以确保平衡性。
- 使用
-
特征优化:
- 增加
TfidfVectorizer
的参数调整,如max_features
和ngram_range
,以优化特征表达。
- 增加
-
可调阈值:
- 使用概率预测,支持调整垃圾邮件分类的阈值。
-
增加调试信息:
- 输出分类报告和混淆矩阵,用于评估模型性能。
-
文件路径和异常处理:
- 添加对文件夹路径检查的提示和文件读取异常的处理。
在第一篇文章基础上改进后的全部代码
import os
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix
import numpy as np
# 步骤 1:读取文件内容
def read_files(folder_path):
if not os.path.exists(folder_path):
raise FileNotFoundError(f"文件夹 {folder_path} 不存在,请检查路径!")
texts = []
for filename in os.listdir(folder_path):
file_path = os.path.join(folder_path, filename)
if os.path.isfile(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as file:
texts.append(file.read())
except Exception as e:
print(f"无法读取文件 {filename}:{e}")
return texts
# 假设 A 文件夹为垃圾邮件文件夹,B 文件夹为正常邮件文件夹
folder_A = "path_to_folder_A" # 垃圾邮件文件夹路径
folder_B = "path_to_folder_B" # 正常邮件文件夹路径
# 读取文件内容
spam_texts = read_files(folder_A)
ham_texts = read_files(folder_B)
# 数据和标签
texts = spam_texts + ham_texts
labels = [1] * len(spam_texts) + [0] * len(ham_texts)
print(f"垃圾邮件数量: {len(spam_texts)}, 正常邮件数量: {len(ham_texts)}")
# 特征提取
vectorizer = TfidfVectorizer(stop_words='english', max_features=1000, ngram_range=(1, 2))
X = vectorizer.fit_transform(texts)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, labels, test_size=0.3, random_state=42)
# 步骤 2:处理类别不平衡
model = LogisticRegression(class_weight='balanced', random_state=42)
model.fit(X_train, y_train)
# 步骤 3:评估模型
y_pred = model.predict(X_test)
print("分类报告:\n", classification_report(y_test, y_pred))
print("混淆矩阵:\n", confusion_matrix(y_test, y_pred))
# 步骤 4:判定新的邮件是否是垃圾邮件
def predict_spam_or_ham(new_email_text, threshold=0.5):
new_email_vec = vectorizer.transform([new_email_text])
predicted_prob = model.predict_proba(new_email_vec)[0]
print(f"预测概率:{predicted_prob}")
return "垃圾邮件" if predicted_prob[1] > threshold else "正常邮件"
# 示例:输入一个新的邮件文本进行预测
new_email = "Congratulations! You've won a free vacation. Claim now!"
result = predict_spam_or_ham(new_email)
print(f"新邮件预测结果: {result}")
# 可尝试调整分类阈值
adjusted_threshold = 0.6
result_adjusted = predict_spam_or_ham(new_email, threshold=adjusted_threshold)
print(f"新邮件预测结果 (调整阈值 {adjusted_threshold}): {result_adjusted}")
改进点详细说明
-
类别平衡:
class_weight='balanced'
用于在数据分布不均衡时平衡类别权重。- 打印垃圾邮件和正常邮件数量,及时发现数据分布问题。
-
特征优化:
max_features=1000
限制特征数量,避免高维稀疏特征对模型性能的影响。ngram_range=(1, 2)
增加特征的上下文表达能力。
-
分类阈值调整:
- 提供
threshold
参数以灵活调整垃圾邮件判定的概率阈值,适应不同应用场景。
- 提供
-
调试信息:
classification_report
和confusion_matrix
帮助评估模型的准确率、召回率和精确率。
-
文件路径检查和异常处理:
- 在读取文件时检查路径合法性,并捕获异常,防止单个文件导致整个流程中断。
测试步骤
- 将垃圾邮件放入
folder_A
,正常邮件放入folder_B
。 - 调整特征提取参数,如
max_features
和ngram_range
。 - 替换
new_email
测试不同邮件内容,并观察概率和分类结果。
需要注意的是,这时候还是需要再微调,因为运行程序后你会发现识别还是不准确,在不断调整后,
将 adjusted_threshold = 0.6 改成 adjusted_threshold = 0.555,最后用如下文本分别测试:
# 示例:输入一个新的邮件文本进行预测
#new_email = "Congratulations! You've won a free vacation. Claim now!"
#new_email = "hi Dad , Will you go to China?"
new_email = "Congratulations! You won a chance to travel for free. Claim now!"
new_email = "This is a notice of school opening"
result = predict_spam_or_ham(new_email)
print(f"新邮件预测结果: {result}")
# 可尝试调整分类阈值
adjusted_threshold = 0.555
result_adjusted = predict_spam_or_ham(new_email, threshold=adjusted_threshold)
print(f"新邮件预测结果 (调整阈值 {adjusted_threshold}): {result_adjusted}")
以下结果清晰展示了成功识别了不同的文本。
当然,因为样本数据量很少,整个模型的识别率还是很低,因此需要不断累计足够的样本,才能更精准地识别出垃圾邮件。