深度学习新闻分类推荐系统:从TextCNN到个性化推荐

发布时间:2026/9/25 0:00:01
深度学习新闻分类推荐系统:从TextCNN到个性化推荐 简介这份基于深度学习的新闻分类推荐系统Python实现源码是专为课程设计与期末大作业准备的高分项目下载后无需修改即可运行适用于需要快速交付完整课题的高校学生。系统涵盖新闻数据预处理、文本分类模型训练、推荐逻辑展示等主要模块配合配置文件与数据库脚本可快速还原运行环境。压缩包共151个文件以Python源码为核心24个py同时包含73个Java文件、properties与yml配置、xml配置、jar依赖、mvnw构建脚本以及sql数据库脚本——其中properties与yml用于环境参数配置xml与jar支撑依赖管理sql脚本可初始化新闻数据表整体形成从后端服务到数据存储的完整工程闭环。包体仅573KB轻量易用已有555人学习下载。项目结构清晰既能满足课程设计答辩演示也方便二次开发帮助深入理解深度学习在新闻分类与推荐场景中的落地方案。1. 深度学习新闻分类推荐系统课程设计拿到手先别跑先把框架看懂做课程设计最怕的不是写不完而是代码能跑但讲不清。我见过太多人把深度学习新闻分类推荐系统源码跑出准确率数字答辩时被老师一句「你的推荐结果是怎么从分类模型过来的」问住。这套Python实现的项目我拆过一遍它不是单点模型而是「新闻分类 个性化推荐」两条线串在一个Web系统里文本进来先过分类模型打标签再根据标签和历史行为做TopN推荐。适合期末大作业、课程设计交差也适合想从分类模型往推荐系统跨一步的人。我的建议是拿到源码先别急着跑花半小时把数据流理清后面调参、改代码、答辩都顺。2. 新闻分类与推荐的工程框架先想清楚数据流再动手写模型2.1 数据从哪来公开语料子集与自采数据的取舍课程设计里数据是最容易翻车的一环。这个项目的训练数据一般来自THUCNews清华新闻分类数据集的10分类子集或者搜狗新闻语料的清洗版本。拿到的压缩包里如果带了data/目录建议先看里面是原始txt还是已经切好的train/dev/test三个文件。原始txt通常是「每行一条新闻标签和正文用制表符分隔」这种格式要自己写拆分脚本训练前先做一次统计确认每个类别的样本数是否均衡。import pandas as pd df pd.read_csv(data/raw_news.txt, sep\t, headerNone, names[label, text]) print(df[label].value_counts()) # 类别数少于500条的训练时分类层容易过拟合 min_count df[label].value_counts().min() if min_count 200: print(f警告最少的类别只有{min_count}条建议做类别合并或数据增强)这段代码做的事情是「数据体检」。读进来之后先看类别的分布课程设计数据通常只有几千到几万条如果某个类别样本太少后面训练出来的分类器对这类新闻几乎不生效。常见的处理办法是合并语义相近的类别比如把「体育」和「足球」合并成「体育」把「科技」和「互联网」合并成「科技」。如果压缩包里数据是空的或者只有几条示例那就得自己找数据源。我一般用THUCNews的子集它和搜狗新闻语料相比好处是标签齐、清洗干净10个类别的划分在论文里也常见答辩时容易被认可。自采数据爬虫抓头条新闻的问题在于标签噪声大、类目不齐作为课程设计不建议花时间在这上面。2.2 文本预处理与词典构建jieba分词、vocab与padding拿到原始新闻文本后预处理链条是固定的清洗HTML标签 → 去除噪声字符 → jieba分词 → 构建词典 → 转成id序列 → padding到固定长度。这个项目里分词用jieba是最常见的做法深度学习模型不直接吃中文字符串要先映射成数字索引。import jieba from collections import Counter def build_vocab(texts, max_vocab_size50000): word_counter Counter() for text in texts: words jieba.lcut(text) word_counter.update(words) # 保留高频词低频词统一映射为UNK vocab {PAD: 0, UNK: 1} for word, count in word_counter.most_common(max_vocab_size - 2): vocab[word] len(vocab) return vocab def text_to_ids(text, vocab, max_len200): words jieba.lcut(text) ids [vocab.get(w, vocab[UNK]) for w in words[:max_len]] if len(ids) max_len: ids [vocab[PAD]] * (max_len - len(ids)) return ids这段代码里有两个关键参数max_vocab_size和max_len。词典大小设50000在课程设计规模下完全够用设太大模型参数量暴涨且低频词学不到有效表示max_len设200是因为新闻正文的平均长度通常在300~500字但分类模型只需要看前200字就能捕捉主题——新闻的导语段高度概括全文这是新闻文本和普通长文本不一样的地方。预处理完成后要把结果按8:1:1切成训练集、验证集、测试集。很多课程设计源码喜欢直接用sklearn的train_test_split但推荐系统部分还需要用户行为数据所以新闻数据集和用户行为数据集要分开建索引不要混在一个DataFrame里。2.3 分类与推荐的数据流设计离线训练、在线推理两层拆这个项目时我发现很多人卡在「分类和推荐到底怎么连起来」上。整个系统可以拆成离线层和在线层离线层负责训练新闻分类模型、训练推荐模型的召回和排序部分在线层接收用户请求先把候选新闻通过分类模型打上类别标签再经过推荐排序输出TopN列表。用户侧的数据一般长这样用户ID、浏览新闻ID列表、点击时间戳、停留时长。这些数据在训练推荐模型时用来构造「用户对新闻的偏好分布」做法是把用户历史点击的新闻ID映射到分类标签上统计出用户的类别偏好向量比如「科技0.4、体育0.3、财经0.2、其他0.1」。分类模型在这里的作用是把冷启动新闻快速归入某个类别让推荐系统在「不知道该推什么」时至少能基于类别兜底。这个两层的设计在答辩时一定要反复强调因为老师最关心的就是「推荐结果到底是不是深度学习算出来的」。明确回答深度学习负责语义层面的新闻表示和类别判定推荐排序层用这些表示做候选集生成和打分两个环节是串联的不是各跑各的。3. 深度学习分类模型选型与实现TextCNN、BiLSTM-Attention与训练配置3.1 为什么优先用TextCNN小数据量下的收敛与可解释性新闻分类在这个项目里承担的是「特征表示」角色模型选型不是越复杂越好。课程设计的数据量撑不起BERT微调反而TextCNN在小数据集上收敛快、效果好且结构直观好解释。TextCNN的核心思想是用多个尺寸的卷积核捕捉文本的n-gram局部特征——比如「芯片」「制裁」这类词组合3-gram卷积核能抓住「芯片制裁」这种短语级别的语义。如果数据量超过5万条或者你想在答辩时多聊一点模型演进可以在项目中同时实现BiLSTM-Attention做对比——BiLSTM能捕捉长距离依赖Attention能可视化出「模型在分类时重点关注哪些词」这在答辩时非常加分。但基线模型一定是TextCNN因为它在CPU上训练也只要十几分钟而BiLSTM在CPU上慢得多如果答辩现场不让用GPU就尴尬了。3.2 TextCNN模型实现embedding、多尺寸卷积与池化import torch import torch.nn as nn import torch.nn.functional as F class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim128, num_filters256, class_num10): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) # 三种卷积核尺寸2-gram、3-gram、4-gram每种256个filter self.convs nn.ModuleList([ nn.Conv1d(embed_dim, num_filters, kernel_sizeks) for ks in [2, 3, 4] ]) self.dropout nn.Dropout(0.5) self.fc nn.Linear(num_filters * 3, class_num) def forward(self, x): # x: [batch, seq_len] x self.embedding(x) # [batch, seq_len, embed_dim] x x.transpose(1, 2) # Conv1d需要 [batch, embed_dim, seq_len] pooled [] for conv in self.convs: c F.relu(conv(x)) # [batch, num_filters, seq_len-k1] p F.max_pool1d(c, c.size(2)).squeeze(2) # [batch, num_filters] pooled.append(p) x torch.cat(pooled, dim1) # [batch, num_filters * 3] x self.dropout(x) return self.fc(x)关键点在Conv1d的维度处理上。Embedding之后张量形状是[batch, seq_len, embed_dim]但Conv1d默认在最后一个维度上卷积所以必须用transpose把embedding维换到中间。三种卷积核尺寸对应新闻文本里不同粒度的特征2-gram捕捉「人工智能」这类双词搭配4-gram捕捉「中国移动发布财报」这种短语结构。max_pool1d取每个feature map的最大值相当于把每个卷积核在整个句子上最强烈的响应提取出来——对分类来说一段新闻里只要有「中兴通讯」出现无论出现在开头还是结尾都强烈指向科技类。3.3 训练配置学习率、batch size与Early Stopping训练配置直接决定你能不能复现出95分级别的效果。课程设计项目最容易出现的幻觉是「训练loss降低但测试准确率不涨」这通常是学习率太大或者batch size太小导致的过拟合。我给出一套能稳定收敛的配置参数推荐值说明学习率1e-3Adam优化器超过2e-3很容易震荡Batch size64数据量小32到128都在合理范围Embedding维度128用预训练词向量时可降至100对齐Dropout0.5分类层前加dropout防止过拟合Max epochs20配合early stopping一般第5~8轮收敛Early stopping patience3验证集F1连续3轮不升就停optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.CrossEntropyLoss() best_f1 0.0 bad_epochs 0 for epoch in range(20): model.train() for batch_x, batch_y in train_loader: optimizer.zero_grad() logits model(batch_x) loss criterion(logits, batch_y) loss.backward() optimizer.step() val_f1 evaluate(model, val_loader) if val_f1 best_f1: best_f1 val_f1 bad_epochs 0 torch.save(model.state_dict(), best_model.pt) else: bad_epochs 1 if bad_epochs 3: print(f{epoch} 轮验证F1未提升提前停止) break这里有个课程设计里常见的「黑匣子」问题只保存最后一次epoch的模型可能保存的是过拟合版本。正确做法是每次验证集指标变好就覆盖保存best_model.pt最后加载的是验证集上表现最好的参数。另外加载模型时别忘了调用model.eval()否则dropout层在推理阶段还是激活状态同一个batch的新闻输出结果会抖动推荐列表不稳定现场演示时容易出洋相。4. 推荐模块从分类结果到TopN召回的完整链路4.1 推荐路线选型基于分类偏好的召回是课程设计最优解推荐系统有很多路线——协同过滤、矩阵分解、DeepFM、双塔模型。但课程设计资源包里的推荐模块我拆出来的核心逻辑基本是「基于分类偏好的召回 规则/简单模型排序」这个选型是有现实考虑的用户行为数据在课程设计场景下非常稀疏可能只有几百个用户、每人几十条浏览记录深度学习排序模型在这么小的数据上根本训不动。协同过滤的问题在于冷启动一篇刚进入系统的新新闻没有任何用户行为协同过滤永远不会推它。而分类模型恰好能解决这个问题——新新闻先过TextCNN拿到类别标签再以「用户偏好类别」为桥梁做召回这就是「深度学习驱动的推荐」在课程设计里的真正落点。4.2 用户偏好向量计算与召回排序代码import numpy as np def compute_user_preference(user_history, news_label_dict, label_list, alpha0.8): user_history: 用户历史点击的新闻ID列表 news_label_dict: 新闻ID - 类别ID的映射来自分类模型预测 label_count np.zeros(len(label_list)) for news_id in user_history: label news_label_dict.get(news_id) if label is not None: label_count[label] 1 # 归一化成用户偏好分布 pref label_count / (label_count.sum() 1e-8) # 平滑偏好向量和均匀分布做插值防止冷启动用户全为0 uniform np.ones(len(label_list)) / len(label_list) return alpha * pref (1 - alpha) * uniform def recall_by_category(user_pref, candidate_news, news_label_dict, topk20): 从候选新闻池里按用户偏好分布的加权打分召回topk scores [] for news_id in candidate_news: label news_label_dict[news_id] score user_pref[label] scores.append((news_id, score)) scores.sort(keylambda x: x[1], reverseTrue) return [news_id for news_id, _ in scores[:topk]]alpha是平滑系数推荐系统里管这个叫「用户偏好置信度」——历史行为多的人偏好向量可信少的人要向均匀分布回归。课程设计里alpha设0.8是比较稳妥的如果用户历史行为少于5条我会手动把alpha降到0.5否则推荐结果会被少数几次点击带偏。这段代码计算出的TopN列表实际展示给用户前还要做一步排序——把召回结果里类别分布过于集中的情况打散比如用户点了3条体育新闻不能只推20条体育给他至少要混入其他类别的热门新闻这步叫「多样性控制」。4.3 分类与推荐串成一条链演示时把它画出来整个系统的推理流程是这样走的用户请求带上用户ID和当前浏览的新闻ID新闻ID关联到已保存的分类模型预测结果得到类别标签读取用户历史行为计算类别偏好向量在候选新闻池里按偏好打分召回Top20用规则新鲜度、热度、多样性重排输出Top10这个链条里有三个数据存储新闻ID到类别的映射、用户历史行为表、候选新闻池的元信息。课程设计里通常用CSV或SQLite存不建议上MySQL装环境太折腾。演示时只跑在线层分类模型用训练时保存的best_model.pt加载进内存不要现场重新训练——这是很多课程设计现场翻车的最大原因后面避坑章细说。5. 运行课题源码的避坑指南环境、路径、显存与中文编码5.1 环境配置翻车Python版本和PyTorch对不上现象按README装好环境import torch直接报错或者提示torch模块不存在。原因90%的情况是Python版本和PyTorch版本不匹配。这个项目基于TextCNN实现对PyTorch版本要求不苛刻但Python 3.10以上装老版本PyTorch会碰壁反过来Python 3.6又跑不动新版NumPy。解决固定一套经过验证的组合。我常用的组合是Python 3.8 PyTorch 1.12.1 CUDA 11.3。装的时候用下面这行命令不要用pip install torch自动装最新版pip install torch1.12.1cu113 torchvision0.13.1cu113 -f https://download.pytorch.org/whl/torch_stable.html没有NVIDIA显卡也不用慌把cu113去掉装CPU版即可。课程设计的模型训练量在CPU上也就十几分钟不影响出结果。装完之后用python -c import torch; print(torch.__version__)确认版本再跑模型就省心了。5.2 中文路径和编码问题Windows下最容易踩的坑现象训练时报UnicodeDecodeError或者数据集路径带中文要么找不到文件要么读出乱码。原因Windows控制台默认编码是GBK而训练数据是UTF-8编码如果压缩包解压路径包含中文比如D:\课程设计\新闻分类\data\部分PyTorch底层接口在Windows下解析这种路径会失败。解决读文件时强制指定编码encodingutf-8所有数据文件和模型保存路径统一用英文压缩包解压到纯英文路径下。项目根目录有个config.py里面通常写着数据路径打开看一眼把data_path改成绝对路径# config.py data_path D:/CourseDesign/NewsRec/data/ # 不要用反斜杠Windows下用正斜杠 model_save_path D:/CourseDesign/NewsRec/checkpoints/best_model.pt用绝对路径还有个额外好处你在jupyter notebook里跑和直接跑python main.py都不会因为当前工作目录不同而找不到文件。这是课程设计演示现场「上午能用下午报错」的常见诱因。5.3 显存不足与训练OOM现象训练到一半报CUDA out of memory或者直接进程被杀掉。原因batch size设得太大或者序列长度max_len设太大导致Embedding表过大。课程设计机器往往是共享显卡显存可能只有4G到6G这不是代码问题是资源配置问题。解决把模型训练部分改成自动检测设备并动态调batch size。训练前检查torch.cuda.get_device_properties(0).total_memory显存小于8G就把batch size减半、max_len从200降到128。还有一招是把Embedding的padding_idx设成0——这样padding位置不参与梯度更新能省一部分显存和计算量。改的是代码里的device torch.device(cuda if torch.cuda.is_available() else cpu)这一步在模型初始化和数据加载前执行全局生效。5.4 压缩包里的杂项文件别慌mvnw.cmd、.DS_Store是什么现象解压后看到一整排mvnw.cmd和.DS_Store文件不敢动担心删了影响项目运行。原因.DS_Store是macOS系统自动生成的文件夹元数据文件mvnw.cmd其实是Maven Wrapper的批处理脚本——打包时被误收进来的大概率是原作者在某个Java项目目录下打包把无关文件一起塞了进来。这些文件和Python项目没有任何关系。解决全部删除不影响任何功能。如果删除后训练脚本还能跑说明它们确实多余如果脚本报错提示找不到某个文件再对照项目结构检查是不是删错了。我的习惯是先把它们挪到临时目录跑通一次再彻底删这相当于买一份「后悔药」。5.5 随机种子与结果复现为什么两次训练准确率不一样现象同一个数据集跑两次训练准确率差1~2个百分点老师问起来不好解释。原因模型初始化、数据打乱顺序都涉及随机性PyTorch默认不固定随机种子。课程设计演示时如果两次效果差别明显容易被质疑「结果不可靠」。解决在训练脚本最开头设置固定随机种子import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False这里有个细节cudnn.deterministic True会牺牲一点训练速度来保证卷积计算的可复现性课程设计数据量小无所谓。benchmark False防止cuDNN自动选择算法导致的微小差异。固定种子后最好再记录一组训练超参数到result.json里答辩时可以理直气壮地说「我的实验全部在固定随机种子下复现」。6. 答辩与验收技巧把黑匣子讲成你亲手搭的系统6.1 先画三张图再放运行结果答辩时不要说「我用了深度学习模型所以效果很好」老师最反感黑匣子式陈述。我建议现场依次画三张图第一张是数据流图从原始新闻到预处理、词典、batch数据每一步标注张量形状第二张是模型结构图画出Embedding层、三种尺寸卷积核的并行结构、池化拼接、全连接分类头第三张是推荐链路图把分类模型的输出如何变成用户的类别偏好、如何召回TopN标清楚。三张图画完系统的「深度」就已经立住了运行结果只是佐证。6.2 现场演示前强制走一遍的三件事第一件事冷启动测试。找一个「没有历史行为的新用户」输入到系统里看推荐结果是否是热门新闻按类别均匀分布——这一步验证平滑处理的逻辑有没有生效。第二件事复现验证。当场重新训练10个epoch显示准确率曲线的趋势不需要达到之前的最好指标只要趋势向上就说明代码逻辑没问题。第三件事单条新闻预测。随便贴一条新闻标题进去看分类模型输出的类别概率分布——如果概率集中在两个类上且都不高说明这条新闻本身类别模糊这是数据问题不是代码问题要提前准备好说辞。6.3 老师最爱追问的三个问题提前背好答案第一个TextCNN为什么适合短文本分类回答要点卷积核局部感知n-gram特征max-pooling提取最强信号参数少不易过拟合。第二个如果你的推荐系统没有真实用户数据怎么办回答要点基于分类偏好的召回本质上是一种冷启动推荐策略它绕过用户行为数据稀疏问题直接用新闻语义相似度做兜底。第三个为什么不用BERT回答要点课程设计数据量不足以微调大模型TextCNN在新闻主题分类这种粗粒度任务上准确率已经能到90%以上BERT带来的增益有限但显存和推理耗时翻倍——能从工程成本和数据规模两个角度答老师就知道你真的想过选型问题。这套源码我拆过不止一次每次给同一个建议先跑通再改一层最后重新训练。从那以后我每次拿到课程设计资源都强制自己先把数据流和模型结构在纸上各画一遍再动键盘敲代码哪个阶段出问题就能定位到具体环节不靠猜。希望帮到你——代码能跑只是起点能把原理讲明白才是这门课真正拿高分的地方。本文还有配套的精品资源点击获取