LangChain与机器学习融合的垃圾邮件检测系统实现

发布时间:2026/9/8 1:25:02
LangChain与机器学习融合的垃圾邮件检测系统实现 垃圾邮件检测是最适合作为信息安全与网络安全方向毕业设计起步项目的文本分类场景之一。它不缺少公开数据集又能把文本清洗、中文分词、特征工程、机器学习建模、模型评估和接口封装完整串起来。但问题在于如果只训练一个朴素贝叶斯或逻辑回归分类器答辩时技术深度明显不够反过来如果让大模型对每一封邮件都做判断又会遇到响应速度慢、按 token 计费成本高、结果不稳定等问题。更合理的工程方案是分两层先用传统机器学习做毫秒级初筛再用 LangChain 接入大语言模型只对低置信度样本做二次研判。下面围绕“LangChain LLM 机器学习融合的垃圾邮件检测分析系统”从概念分工、环境准备、模型训练、LLM 接入、链路融合到运行验证和常见坑完整过一遍实现过程。1. 先想清楚机器学习和 LLM 在垃圾邮件检测里分别解决什么问题1.1 垃圾邮件检测的技术本质垃圾邮件检测本质上是一个文本二分类问题给定一封邮件或一条短信的文本内容判断它属于正常邮件还是垃圾邮件。传统实现里输入是邮件正文输出是类别标签。但真实场景比“二分类”要复杂因为垃圾邮件往往带有明显的诱导话术、外链、伪造发件人和异常附件单纯靠关键词匹配很容易被绕过。从技术角度看这个项目同时涉及两个方向文本方向中文分词、去停用词、TF-IDF 特征、语义理解。安全方向钓鱼邮件识别、URL 风险判断、社工话术识别。对毕业设计来说这个选题的好处是边界清晰既能展示完整的机器学习建模流程也能体现对大模型应用的理解不会因为目标过于宽泛而失控。1.2 传统机器学习与 LLM 在这个项目里的分工传统机器学习和 LLM 在能力上互补。机器学习模型推理速度快、部署成本低、结果稳定但对没见过的新话术和复杂语义理解能力有限LLM 具备较强的语义推理和零样本判断能力能给结果补充理由但速度慢、按调用量计费。两者的分工可以这样理解机器学习模型负责“快判断”处理绝大多数特征明显的邮件LLM 负责“难判断”处理特征模糊、置信度低的样本。这种设计既控制了成本又保证了系统的解释性。维度传统机器学习模型LLM 大模型训练成本低单机 CPU 可完成高通常不针对小项目微调推理速度毫秒级秒级受网络和模型大小影响可解释性通过特征权重间接解释可生成自然语言判断理由泛化能力依赖训练数据分布零样本和少样本能力较强运行成本本地运行基本无调用费按 token 计费适用场景全量初筛低置信度复核、钓鱼话术识别1.3 项目的技术主线与最终交付形态这个毕业设计项目的主线不是“训练一个模型”而是“设计一条由规则、机器学习和 LLM 组成的邮件判断链路”。最终交付形态建议包含四部分可复现的数据清洗和分词脚本。可离线训练的机器学习模型并输出评估指标。基于 LangChain 的 LLM 判断链可返回分类结果和判断理由。融合两层的统一分类接口封装成函数或 HTTP 服务。这样的结构在论文里可以拆成数据层、模型层、大模型层和应用层答辩时每一层都有内容可讲。2. 环境准备与项目结构先把地基打牢2.1 Python 版本与依赖清单推荐使用 Python 3.10 或 3.11避免低版本在安装新版依赖时出现兼容问题。项目核心依赖分为三组数据处理、机器学习和 LangChain。# requirements.txt # 以下版本是写作时的组合落地前请根据本机环境确认 pandas2.1.4 numpy1.26.3 jieba0.42.1 scikit-learn1.3.2 joblib1.3.2 langchain0.1.11 langchain-openai0.0.5 openai1.14.3 pydantic2.6.1 pyyaml6.0.1安装命令pip install -r requirements.txt要注意 LangChain 版本迭代很快接口变化频繁。如果安装的是更新的版本ChatOpenAI、ChatPromptTemplate等类的导入路径可能发生变化。出现导入报错时优先检查版本而不是怀疑代码写错。2.2 数据集选择与格式确认垃圾邮件数据集一般分为英文和中文两类。英文常用的是 Enron-Spam 数据集和 UCI 的 SMS Spam Collection中文场景可以使用公开的短信垃圾语料。数据格式统一为两列content表示邮件或短信内容label表示类别建议1表示垃圾邮件0表示正常邮件。content,label 恭喜您获得周年庆抽奖资格点击链接领取奖品,1 请查收本周项目周报见附件,0拿到数据后先做三件事查看样本总量和类别比例判断是否存在严重样本不均衡。随机抽取 20 条数据人工阅读确认标注质量。清除明显的重复数据和空白内容。这一步不要跳过后续所有模型效果都建立在数据质量之上。2.3 项目目录设计建议按功能拆分目录避免所有脚本堆在一个文件里。spam_detection/ ├── config.yaml ├── requirements.txt ├── data/ │ └── spam_data.csv ├── models/ │ ├── tfidf_vectorizer.joblib │ └── spam_model.joblib ├── scripts/ │ ├── data_prepare.py │ ├── train_model.py │ └── model_predict.py ├── llm_module/ │ └── llm_judge.py ├── pipeline/ │ └── classify_pipeline.py └── app/ └── run_demo.py目录的含义如下data存放原始数据models存放训练产物scripts存放数据预处理和模型训练脚本llm_module封装 LangChain 相关逻辑pipeline存放融合判断链路app放演示入口。这样划分之后论文里的“模块设计”章节直接照这个结构写即可。3. 第一层判断用 TF-IDF 与传统机器学习训练垃圾邮件分类模型3.1 文本清洗与中文分词处理原始邮件文本包含 HTML 标签、URL、标点和各种噪声。如果直接进入模型特征维度会爆炸效果反而变差。清洗的目标是去掉与分类无关的信息保留有判断价值的语义内容。# scripts/data_prepare.py import re import jieba import pandas as pd from sklearn.model_selection import train_test_split def clean_text(text: str) - str: text re.sub(r[^], , text) # 去除 HTML 标签 text re.sub(rhttp\S, , text) # 去除 URL text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\s], , text) # 去除非中英文和数字字符 return text.strip() def tokenize(text: str) - str: words jieba.lcut(text) return .join([w for w in words if len(w.strip()) 1]) df pd.read_csv(data/spam_data.csv) df[clean_text] df[content].apply(clean_text) df[tokenized] df[clean_text].apply(tokenize) train_df, test_df train_test_split( df, test_size0.2, random_state42, stratifydf[label] ) train_df.to_csv(data/train.csv, indexFalse) test_df.to_csv(data/test.csv, indexFalse)为什么要用jieba分词因为中文文本没有天然空格分隔直接使用字符级特征会丢失词组语义比如“免费领取”和“免费”“领取”单独出现的含义并不完全一样。分词后按空格拼接是为了让后续TfidfVectorizer在默认token_pattern下能正确切分。这里有一个关键细节train_test_split一定要加stratifydf[label]。垃圾邮件在真实分布中占比较低如果随机切分测试集里可能没有足够多的垃圾样本评估指标会失真。3.2 特征提取与数据集划分文本特征采用 TF-IDF它衡量一个词在单篇文本中的重要程度以及在整个语料中的区分能力。相比单纯的词频TF-IDF 能压低“的、了、是”这类高频但无信息量词的权重。# scripts/train_model.py from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, f1_score import joblib import pandas as pd train_df pd.read_csv(data/train.csv) test_df pd.read_csv(data/test.csv) vectorizer TfidfVectorizer(max_features10000, ngram_range(1, 2)) X_train vectorizer.fit_transform(train_df[tokenized]) X_test vectorizer.transform(test_df[tokenized]) model LogisticRegression(C1.0, max_iter1000, class_weightbalanced) model.fit(X_train, train_df[label]) y_pred model.predict(X_test) print(classification_report(test_df[label], y_pred, target_names[normal, spam])) joblib.dump(vectorizer, models/tfidf_vectorizer.joblib) joblib.dump(model, models/spam_model.joblib)需要解释的参数有三个max_features10000只保留词频最高的 10000 个特征防止低频噪声词拖慢训练并造成过拟合。调大能保留更多词但会显著增加特征矩阵体积。ngram_range(1, 2)同时使用单词和相邻双词作为特征可以捕捉“免费领取”“点击链接”这类组合表达。class_weightbalanced当两类样本数量差异大时自动给少数类更高的权重避免模型偏向多数类。一个非常容易踩的坑是训练后用fit_transform处理测试集。正确做法是训练时用fit_transform学习词典和词权重之后对测试集或线上输入统一用transform否则特征维度会不一致模型预测直接报错。3.3 模型训练、评估与保存在演示场景中逻辑回归是性价比最高的选择。它在高维稀疏特征上表现稳定训练速度快还能通过predict_proba输出概率这个概率正好是后续融合链路判断置信度的基础。评估时不要只盯准确率。如果垃圾邮件只占数据集的 10%模型把全部样本判为正常也能有 90% 准确率但这没有任何意义。需要重点看三个指标精确率被判为垃圾邮件的样本中真正是垃圾邮件的比例。召回率真实垃圾邮件中被正确找出来的比例。F1 值精确率和召回率的调和平均。训练完成后用joblib保存向量器和模型。这两个产物必须一起保存、一起加载因为预测时需要用同一个词典把新文本转换成特征向量。4. 第二层判断用 LangChain 接入 LLM 处理模糊样本4.1 LangChain 在这个项目里负责什么LangChain 不是模型本身而是一个编排框架。它把大模型调用、提示词模板、输出解析、记忆和外部工具串联成可复用的链Chain。在这个项目中LangChain 承担三件事统一管理 Prompt 模板避免在业务代码里拼接字符串。调用大模型接口并屏蔽不同模型提供方之间的差异。通过输出解析器把大模型返回的内容转成结构化 JSON方便后续程序读取。设计上llm_module只对外暴露一个judge_email(content)函数业务侧不关心内部用的是哪家模型后续更换或降级也只在模块内改。4.2 构造 Prompt 与邮件分类链LLM 判断的 Prompt 需要明确角色、任务、输入和输出格式否则模型经常答非所问。下面的实现使用 LangChain 的ChatPromptTemplate和ChatOpenAI通过base_url指向任意 OpenAI 兼容接口。# llm_module/llm_judge.py import os import json from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import JsonOutputParser from langchain_openai import ChatOpenAI SYSTEM_PROMPT 你是一名邮件安全分析专家。你的任务是对输入的邮件内容进行安全研判。 请判断该邮件属于以下哪一类 - normal正常邮件 - spam垃圾邮件包括营销推广、中奖诈骗、虚假广告等 - phishing钓鱼邮件包括诱导点击恶意链接、骗取账号密码等 请结合内容中的诱导话术、链接风险、紧急催促、索取敏感信息等特征分析。 只输出 JSON不要输出额外说明。 def build_chain(api_key: str, base_url: str, model_name: str): prompt ChatPromptTemplate.from_messages([ (system, SYSTEM_PROMPT), (human, 请分析下面这封邮件内容\n{content}), ]) llm ChatOpenAI( modelmodel_name, temperature0, api_keyapi_key, base_urlbase_url, ) parser JsonOutputParser() return prompt | llm | parser def judge_email(content: str) - dict: api_key os.environ.get(LLM_API_KEY, ) base_url os.environ.get(LLM_BASE_URL, ) model_name os.environ.get(LLM_MODEL_NAME, qwen-plus) chain build_chain(api_key, base_url, model_name) return chain.invoke({content: content[:2000]})temperature0是分类任务的关键设置。温度过高会让模型每次输出不一致同一封邮件可能一次判正常、一次判垃圾。做分类和判断任务时温度固定为 0 优先保证结果稳定。JsonOutputParser会把模型输出解析成 Python 字典。为了让模型稳定输出 JSONPrompt 末尾需要明确“只输出 JSON”。如果模型仍偶发输出多余文字可以改用PydanticOutputParser并传入完整的格式说明。4.3 使用结构化输出解析 LLM 结果实际调用后得到的结果类似这样{ label: phishing, reason: 邮件以账户异常为由制造紧迫感要求点击链接验证身份具有典型的钓鱼邮件特征。, confidence: 0.92 }在业务代码里可以按字段读取不需要依赖自然语言切片解析。这里的label用于最终判断reason用于展示给用户或写入检测日志confidence作为附加参考。建议在模块内部增加一层兼容处理如果输出缺少label字段抛出明确的业务异常而不是让KeyError直接暴露给上层。这样融合链路可以针对 LLM 调用失败做降级处理。5. 融合判断链路规则 机器学习 LLM 如何协同5.1 分层判断流程设计整个系统的核心流程可以拆成四个阶段文本预处理清洗、分词和训练阶段保持完全一致。机器学习快速判断把文本转成 TF-IDF 特征用predict_proba得到概率。置信度判断如果最高概率超过阈值直接采用机器学习结果否则进入 LLM。LLM 二次研判调用 LangChain 链把返回的label和reason作为最终结果。判断链路如下输入邮件文本 - 清洗 分词 - TF-IDF 特征化 - LogisticRegression predict_proba - 最高概率 0.85 - 输出 (ml, label, confidence) - 最高概率 0.85 - 调用 LLM - 输出 (llm, label, reason)设计完成后要补充一条规则如果邮件内容包含明显的高风险特征比如异常链接、索取密码等关键词即使机器学习概率很高也建议先进入 LLM 复核。规则在融合链路中不是可有可无它负责兜底一些模型会漏掉的强信号。5.2 置信度阈值与回退机制的取舍阈值选择需要在“误杀”和“漏网”之间权衡。阈值过高比如 0.95大量正常邮件会进入 LLM调用成本上升。阈值过低比如 0.6模糊样本得不到复核漏判风险变大。建议从 0.8 到 0.9 之间开始调。先用测试集跑一遍统计有多少样本落在低置信度区间再根据 LLM 调用预算调整。如果没有预算调用真实 LLM可以先用 mock 函数模拟 LLM 行为保证链路先跑通。LLM 调用失败时不能直接抛出异常应该回退到机器学习结果。工程上的原则是宁可接受一次可能的错判也不能让整条检测服务不可用。5.3 完整分类函数实现融合逻辑放在pipeline/classify_pipeline.py中# pipeline/classify_pipeline.py import joblib import re import jieba from llm_module.llm_judge import judge_email HIGH_THRESHOLD 0.85 def load_models(vectorizer_pathmodels/tfidf_vectorizer.joblib, model_pathmodels/spam_model.joblib): vectorizer joblib.load(vectorizer_path) model joblib.load(model_path) return vectorizer, model def clean_and_tokenize(text: str) - str: text re.sub(r[^], , text) text re.sub(rhttp\S, , text) text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\s], , text) words jieba.lcut(text.strip()) return .join([w for w in words if len(w.strip()) 1]) def classify(content: str): vectorizer, model load_models() tokenized clean_and_tokenize(content) vec vectorizer.transform([tokenized]) proba model.predict_proba(vec)[0] prob max(proba) label_idx int(model.predict(vec)[0]) label spam if label_idx 1 else normal if prob HIGH_THRESHOLD: return { source: ml, label: label, confidence: round(prob, 4), reason: 机器学习模型高置信度判断 } try: result judge_email(content) return { source: llm, label: result.get(label, normal), confidence: result.get(confidence, 0), reason: result.get(reason, ) } except Exception as e: return { source: ml_fallback, label: label, confidence: round(prob, 4), reason: fLLM 调用失败回退到机器学习结果: {e} }注意predict_proba返回的是样本属于每个类别的概率数组max(proba)取最高概率作为置信度。如果最高概率达到阈值说明模型对判断结果足够确定如果只有 0.6、0.7说明样本落到了决策边界附近这时候交给 LLM 更合适。6. 运行验证与结果分析6.1 启动与调用方式先确认模型文件存在然后从app/run_demo.py启动演示# app/run_demo.py from pipeline.classify_pipeline import classify samples [ 恭喜您获得腾讯周年庆抽奖资格点击链接立即领取 iPhone 15逾期作废。, 本周五下午三点在 401 会议室召开项目评审会请提前准备好演示环境。, 您的账户存在异常登录请在 24 小时内点击下方链接验证身份否则将冻结账户。, ] for text in samples: result classify(text) print(text[:30], -, result)运行python app/run_demo.py预期输出类似恭喜您获得腾讯周年庆抽奖资格 - {source: ml, label: spam, confidence: 0.97, ...} 本周五下午三点在 401 会议室召开 - {source: ml, label: normal, confidence: 0.91, ...} 您的账户存在异常登录 - {source: llm, label: phishing, reason: 邮件以账户异常为由...}前两条由机器学习高置信度处理第三条因为包含“异常登录”“验证身份”“链接”等模糊信号进入 LLM 后被识别为钓鱼邮件。6.2 验证用例设计演示时不能只挑好判的样本建议准备四类用例覆盖不同分支用例类型示例特征预期分支明显垃圾邮件中奖、免费领取、夸张广告词机器学习高置信度正常办公邮件会议通知、周报、日程安排机器学习高置信度模糊营销邮件含蓄的推广话术特征词少LLM 复核钓鱼邮件账户异常、紧急催办、索取密码LLM 复核并给出理由测试时还要验证异常分支比如故意把LLM_API_KEY设置成空值观察系统是否回退到机器学习结果而不是崩溃。6.3 结果指标怎么看对测试集要统计两个层面的指标机器学习单独分类的准确率、精确率、召回率和 F1。融合链路后有多少比例样本走了 LLM、多少走了机器学习以及 LLM 复核后标签发生变化的比例。如果走 LLM 的比例过高说明机器学习模型置信度普遍偏低应该检查数据质量、特征工程或调大max_features如果比例过低要检查是否存在大量应该复核的低置信度样本被高置信度覆盖调整阈值即可。注意不要只验证程序能启动还要验证输入、输出、异常分支和日志是否符合预期。毕业设计答辩时评审老师最常问的就是“边界情况你怎么处理”。7. 常见问题排查清单7.1 LangChain 与 LLM 调用相关问题一导入 LangChain 类失败现象from langchain_openai import ChatOpenAI报ModuleNotFoundError。原因LangChain 0.1 之后把模型封装拆分为独立包langchain-openai需要单独安装。处理执行pip install langchain-openai并确认langchain版本在 0.1 以上。版本相差太大时某些类名和导入路径也会变优先看官方文档对应版本。问题二调用 LLM 返回 401 或 403现象请求 LLM 接口时提示鉴权失败。原因api_key为空、过期或者环境变量名和代码里读的不一致。处理先打印os.environ.get(LLM_API_KEY)确认是否读到再确认base_url是否指向正确的兼容接口最后确认模型名称在当前接口下是否存在。问题三LLM 返回了多余文字JSON 解析失败现象JsonOutputParser抛出解析异常或result.get(label)拿到None。原因模型输出不稳定在 JSON 前后加了“分析”之类的说明文字。处理在 Prompt 中强化“只输出 JSON”的约束温度保持为 0如果还不行改用PydanticOutputParser并附上字段说明。7.2 机器学习训练相关问题四预测时特征维度不一致现象调用model.predict(vec)报维度不匹配错误。原因训练时用fit_transform学习了词典预测时又重新fit_transform生成了不同的特征空间。处理预测阶段只调用vectorizer.transform加载训练保存的tfidf_vectorizer.joblib。问题五模型准确率高但垃圾邮件基本检测不出来现象整体准确率 90% 以上但垃圾邮件召回率很低。原因类别不均衡模型倾向把样本都判为多数类。处理划分数据集时加stratify训练时设置class_weightbalanced评估时关注召回率和 F1而不是只看准确率。问题六中文文本效果明显偏差现象关键词被切碎特征里出现很多单字噪声。原因没有做中文分词或分词后没有过滤单字和停用词。处理使用jieba.lcut分词过滤长度小于 2 的词必要时加载自定义词典补充领域词。7.3 数据与文本处理相关问题现象常见原因检查方式处理建议训练和预测效果差距大数据切分随机性过大查看训练集和测试集标签分布使用 stratify 划分并固定 random_state文本清洗后为空原始内容全是 URL 或特殊字符打印清洗后的结果为空时回退到原始文本或丢弃预测结果全部为 normal特征没有正确加载检查向量器是否来自训练产物统一加载 models 目录下的 joblib 文件排查顺序建议从输入开始先确认原始文本是否正确再确认清洗分词结果然后确认特征向量的 shape最后再看模型输出和 LLM 调用日志。不要一上来就调模型参数。8. 毕业设计落地建议与扩展方向8.1 论文、答辩与演示素材准备这个项目在论文里可以按“数据预处理 - 机器学习初筛 - LLM 精准研判 - 融合判定”四层展开。每个章节都要给出核心代码片段和实验结果图表尤其是混淆矩阵、分类报告、阈值与 LLM 调用比例的关系曲线。答辩演示建议准备三样东西一段本地运行脚本展示机器学习单独识别和融合链路识别的对比。一组低置信度样本展示 LLM 如何给出判断理由。一个异常场景例如故意把 LLM 密钥置空展示系统回退机制。其中第 3 点最容易给评审留下印象因为它体现的是工程思维而不只是调包跑通。8.2 工程化扩展方向如果时间允许可以在完成基础链路后选择一两项扩展把融合链路封装成 FastAPI 服务提供 HTTP 接口并加入请求日志和耗时统计。对 LLM 调用增加缓存和限流对同一内容在短时间内的重复判断直接返回缓存结果。把“规则 机器学习 LLM”改为可配置策略通过 config 文件控制阈值和回调地址。增加邮件附件的 URL 分析对链接域名做风险标记。引入向量检索把历史已判断的高质量样本作为 few-shot 示例减少 LLM 误判。对应的生产环境建议是密钥不要写在代码里通过环境变量注入每封邮件的判断来源和理由写入日志便于事后审计LLM 超时时间要设置上限避免接口僵住拖垮整条链路。注意学习环境可以借用现成的在线大模型接口快速跑通生产或校内演示环境如果网络不稳定建议准备本地模型或 mock 方案保证演示过程不依赖外部服务。8.3 可复用的答辩前检查清单最后整理一份检查清单提交论文或准备答辩前逐项核对训练集和测试集是否使用stratify划分标签分布是否合理。向量器和模型是否都保存并正确加载预测阶段是否只使用transform。评估指标是否包含精确率、召回率、F1是否只看准确率。LLM 的温度是否为 0输出解析是否稳定缺失字段是否有兜底。低置信度阈值是否经过测试集统计是否清楚 LLM 调用比例。LLM 调用失败时是否回退到机器学习结果。配置文件、密钥、路径是否与论文中的描述一致。演示脚本是否包含正常邮件、垃圾邮件、钓鱼邮件和异常回退四类用例。是否记录了至少 3 次运行日志和对应的结果分析。这个项目的核心价值不在于“用到了大模型”而在于学生是否理解为什么需要融合、在哪里融合、以及融合之后成本和效果如何平衡。把这套链路完整跑通、把每个分支和参数讲明白已经足够支撑一份高质量的毕业设计。如果再去延伸做接口服务化和效果对比实验项目的丰富度和答辩说服力都会明显提升。