ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

新闻标题分类系统实战:从数据预处理到TF-IDF特征工程全解析

新闻标题分类系统实战:从数据预处理到TF-IDF特征工程全解析 简介这是一份完整的本科毕业设计项目源码包课题为基于机器学习的新闻标题分类系统适合计算机、人工智能等专业学生参考。压缩包共63个文件以7个Python源文件为核心配合16个HTML、13个CSS、8个JavaScript构成Web展示端另含9个文本停用词表/词表数据、SQL数据库脚本、Jupyter预处理笔记与项目说明文档整体约10.93MB目录结构较为清晰。已有88人学习下载。资源覆盖新闻文本清洗、分词、停用词过滤、特征提取到模型训练预测的完整链路并利用Flask搭建分类结果展示界面配套README、依赖清单与标签映射文件可帮助读者快速复现实验环境。对于需要参考毕业设计完整结构或学习机器学习文本分类工程化落地的同学具有很强的借鉴价值尤其适合作为本科阶段AI方向项目的起步模板。1. 机器学习新闻标题分类系统拿到毕设zip包后先别急着跑模型你可能和我一样第一次打开“TUST本科毕业设计基于机器学习的新闻标题分类系统.zip”时脑子里先冒出一串问号这到底是给了我一个能直接交差的成品还是一堆等着我填坑的半成品拆开压缩包里面通常是一份论文PDF、若干Python脚本、可能还有一份csv或json格式的数据集。这套系统的核心任务很直接——给定一条新闻标题让模型预测它属于哪个类别比如体育、财经、娱乐、科技。它本质上是文本多分类问题难点不在模型选型而在中文标题这种短文本怎么清洗、怎么表示成向量、样本不均衡怎么处理。适合谁正在做毕设的学生或者想快速搭一个文本分类雏形验证想法的工程师。反直觉的结论是这个系统真正的工作量八成花在数据预处理和特征工程上模型本身反而是最不用纠结的部分。2. 数据集与预处理把zip包变成能训练的数据先定目录再动手2.1 目录结构先行拿到文件先盘点别急着pip install我一般拿到这种毕设包第一步不是打开代码而是先列目录。常见做法是用tree或find把文件结构打出来看看有没有data、model、utils这类分目录有没有README数据集是单个文件还是按类别拆开的文件夹。unzip TUST本科毕业设计基于机器学习的新闻标题分类系统.zip -d news_classifier cd news_classifier find . -type f | head -50解压后先跑find目的是快速摸清包里有多少代码文件、数据文件在哪、有没有预训练模型权重。很多毕设包会把数据集和代码放在同一层或者数据文件特别大导致解压很慢。逻辑说明unzip的-d参数指定解压目标目录避免文件散落一地find | head -50只列前50个文件避免输出刷屏。如果你发现数据集是压缩包套压缩包比如data.zip里面还有train.zip那就先解压外层再逐个解压内层别用一条unzip *.zip碰运气。参数说明如果你的机器内存有限head -50可以改成head -20少看几个文件不影响判断。另外我习惯先看一眼文件大小——du -sh data如果你发现数据集有几百MB而代码里却写着pd.read_csv(data.csv)那多半是代码路径写死了需要改成相对路径。2.2 数据清洗三件事去重、纠偏、切分验证集新闻标题分类的数据集最常见的格式是两列label和title。label可能是中文“体育”“财经”也可能是数字ID0、1、2。这一步的目标是产出一个干净的 DataFrame为后面特征工程打好底子。import pandas as pd import re df pd.read_csv(data/train.csv, encodingutf-8) print(f原始样本数: {len(df)}) print(df[label].value_counts()) # 1. 去重 df df.drop_duplicates(subset[title], keepfirst) # 2. 去掉空标题和空标签 df df.dropna(subset[title, label]) # 3. 标题清洗去掉URL、多余空格、特殊符号 def clean_title(t): t re.sub(rhttp\S, , t) # 去URL t re.sub(r#\S#, , t) # 去话题标签 t re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , t) # 保留中文、英文、数字 t re.sub(r\s, , t).strip() return t df[title] df[title].apply(clean_title) df df[df[title].apply(len) 0] # 清洗后为空的行直接删掉 # 4. 标签统一成数字编码 df[label_id] df[label].astype(category).cat.codes label_map dict(enumerate(df[label].astype(category).cat.categories)) print(f清洗后样本数: {len(df)}) print(f类别数: {len(label_map)}) # 5. 按类别分层切分训练/验证集 from sklearn.model_selection import train_test_split train_df, val_df train_test_split( df, test_size0.2, stratifydf[label_id], random_state42 ) print(f训练集: {len(train_df)}, 验证集: {len(val_df)})这段代码做了五件事顺序不能乱。去重放在最前面因为同一标题可能被爬虫重复抓取dropna去掉空值行clean_title把URL和特殊符号清理掉这一步直接影响后面分词效果——如果你留着URLjieba会把http当成一个词切出来纯属噪音标签转成数字是sklearn的硬性要求astype(category).cat.codes比手动写字典更省事但要注意生成的编码顺序是按拼音排的不是按你数据里的出现顺序所以必须保存label_map供预测时反向映射。参数说明test_size0.2是常规比例如果你的数据集很小少于1万条建议改成test_size0.1多留点数据给训练stratifydf[label_id]是按类别比例分层抽样避免随机切分导致某个小众类别在验证集里一条都没有random_state42固定随机种子保证每次跑出来的结果一致这在调参对比模型效果时特别重要。2.3 标签不均衡先看一眼分布再决定要不要处理新闻标题分类的数据集天然不均衡——娱乐、体育这类标题数量多而“农业”“法律”这类可能只有零星几条。如果你不做处理模型会把所有样本都预测成样本量最大的那个类因为这样准确率也能到70%以上。# 看分布 dist df[label_id].value_counts(normalizeTrue) print(dist) # 简单阈值最低类别样本数 min_count df[label_id].value_counts().min() print(f最少类别样本数: {min_count})我一般不看完整分布只看两个数最大类别占比和最少类别样本数。如果最多类占比超过80%或者最少类样本数只有个位数就得做处理。常用的手段有三个对少数类做过采样比如用imbalanced-learn的RandomOverSampler、对多数类做欠采样、或者在损失函数里给少数类加权重。但对毕设系统来说最省事的做法是直接按2.2里的分层切分然后在模型训练时设置class_weightbalanced——这个参数会自动按类别频率反比加权你不用手动改数据分布。3. 特征工程中文新闻标题怎么变成模型能读的向量3.1 分词与停用词jieba是标配但你要自定义词典英文文本可以用空格直接分词中文不行。新闻标题这种短文本分词质量几乎决定了分类效果的上限。常见做法是用jieba分词再加一个自定义词典——把“新冠疫苗”“双减政策”“元宇宙”这类固定词组塞进去防止被切成“新冠/疫苗”这种奇奇怪怪的结果。import jieba # 加载自定义词典每行一个词 # 文件 custom_dict.txt: # 元宇宙 # 双减政策 # 芯片短缺 jieba.load_userdict(custom_dict.txt) # 停用词表stopwords.txt常见做法是从网上下载哈工大停用词表 # 自己再补几个新闻标题里高频但没分类信息的词 stopwords set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) def cut_title(title): words jieba.lcut(title) return .join([w for w in words if w not in stopwords and len(w.strip()) 0])这一步的逻辑jieba.load_userdict是全局生效的加载一次后续所有lcut都会优先按你的词典切词。停用词表不是越多越好——把“不”“没”这类否定词加进去会让“不看好”和“看好”变成同一个特征对分类是灾难。所以我建议停用词表只保留“的”“了”“是”“在”“和”这类纯结构词以及“记者”“报道”“来源”这种新闻模板词。参数说明jieba.lcut返回的是列表用空格 join 成字符串是为了后面直接喂给TfidfVectorizer——sklearn的文本向量化器默认按空格分词。如果你用的是jieba.cut生成器记得转成列表否则后面切词结果会丢。3.2 TF-IDF参数调法max_features、ngram_range、min_df一个都不能少新闻标题分类里TF-IDF 是最稳的baseline特征。但TfidfVectorizer的默认参数不适合短文本——默认会提取单个词ngram_range(1,1)而新闻标题里“苹果”既可能是水果也可能是公司单看一个字面特征根本没有区分度。我一般这样调from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( ngram_range(1, 2), # 提取单个词相邻词对 max_features15000, # 限制特征总量防止维度爆炸 min_df2, # 词至少在2条样本中出现过 max_df0.8, # 超过80%样本都出现的词忽略 sublinear_tfTrue # 用 1log(tf) 代替原始词频 ) X_train vectorizer.fit_transform(train_df[title_cut]) X_val vectorizer.transform(val_df[title_cut]) print(f特征维度: {X_train.shape[1]})参数说明ngram_range(1, 2)是新闻标题分类的甜点参数——单独一个词丢失上下文“不/看好”和“看/好”会被拆成不同组合max_features15000是个经验值你可以在10000到30000之间调维度太高容易过拟合太低则信息不足min_df2去掉只出现一次的冷门词这些词往往是错别字或特殊符号max_df0.8去掉 “新闻”“今日” 这类在绝大多数标题里都有的词它们完全没有区分度sublinear_tfTrue是让长标题不要因为词频高就获得过大权重因为新闻标题普遍10到30个字词频差异本身没有太大意义。注意一个常见的翻车点fit_transform只能用在训练集上验证集和测试集必须用transform否则会把验证集的信息泄漏到模型里——具体表现是你的验证准确率虚高但上线之后跌得怀疑人生。3.3 短文本的另一种表示其实可以不只盯TF-IDF如果你的毕设想显得更有工作量可以在TF-IDF之外再加一个特征用预训练的词向量对标题做平均池化把每个词的向量取平均得到标题向量然后和TF-IDF特征拼接起来喂给模型。import numpy as np # 假设你已经用 gensim 加载了中文词向量 # from gensim.models import KeyedVectors # wv KeyedVectors.load_word2vec_format(word2vec.vec) def title_vector(tokens): vecs [wv[word] for word in tokens.split() if word in wv] if len(vecs) 0: return np.zeros(wv.vector_size) return np.mean(vecs, axis0) # 处理所有样本 train_vecs np.vstack([title_vector(t) for t in train_df[title_cut]]) # 与TF-IDF特征拼接 from scipy.sparse import hstack X_train_combined hstack([X_train, train_vecs])这种做法在新闻标题这种短文本上通常能带来1到2个百分点的提升代价是训练时间变长。但对毕设来说它让系统有了“使用预训练词向量”的叙述空间。注意hstack要求两边维度匹配——TF-IDF是稀疏矩阵词向量是稠密矩阵hstack会把稠密矩阵转成稀疏格式在特征量大的时候会变慢这是正常的。4. 模型选型与训练从朴素贝叶斯到LSTM毕设该怎么选4.1 三个模型的对比朴素贝叶斯、SVM、LSTM各自的定位新闻标题分类这个任务模型选型的逻辑很清晰数据量小几千到几万条用朴素贝叶斯或SVM数据量大十万条以上才值得上LSTM或BERT。毕设zip包里最常见的是三种模型都写好了让你对比实验。我帮你把它们的参数和适用场景理清楚。模型适合的数据量训练速度效果上限关键参数朴素贝叶斯几百到几万极快一般alpha平滑系数线性SVM几千到几十万快较好C正则化强度LSTM十万以上慢好需大量数据embedding_dim、hidden_size、num_layers朴素贝叶斯假设特征独立这在文本里显然不成立“苹果”和“手机”经常一起出现但它在小数据集上仍然能打因为方差小、不容易过拟合。SVM特别是线性SVM是TF-IDF特征下的默认选择因为文本特征虽然维度高但稀疏线性分类器在这个空间里通常比非线性分类器表现更好。LSTM理论上能捕捉序列信息但如果你的训练集不到五万条它大概率打不过前面两个——这是玄学吗不是是LSTM需要大量数据来学习词序规律数据不足时就只能死记硬背。4.2 训练与评估代码一套流程同时跑三个模型我一般把三个模型放进同一个脚本里输出统一的评估报告。这样对比起来直观答辩时也拿得出手。from sklearn.naive_bayes import MultinomialNB from sklearn.svm import LinearSVC from sklearn.metrics import accuracy_score, f1_score, classification_report import time models { NaiveBayes: MultinomialNB(alpha0.1), LinearSVM: LinearSVC(C1.0, class_weightbalanced, max_iter5000), } results {} for name, model in models.items(): start time.time() model.fit(X_train, train_df[label_id]) pred model.predict(X_val) acc accuracy_score(val_df[label_id], pred) f1 f1_score(val_df[label_id], pred, averageweighted) results[name] {acc: acc, f1: f1, time: time.time() - start} print(f{name}: acc{acc:.4f}, f1{f1:.4f}, train_time{time.time()-start:.2f}s) # 如果数据量够再跑LSTM # 注意LSTM需要把文本转成序列ID不能用TF-IDF特征 if len(train_df) 80000: print(数据量足够建议尝试LSTM或BERT)参数说明MultinomialNB(alpha0.1)中的 alpha 是拉普拉斯平滑系数默认1.0但对新闻标题这种短文本调小到0.1往往效果更好因为词频分布太稀疏平滑太强会抹平差异LinearSVC(C1.0)的 C 控制正则化强度C 越小泛化越好但太小会欠拟合——你可以从 0.1、0.5、1.0、2.0 这四个值里选一般毕设数据量下1.0是安全默认值class_weightbalanced对应2.3节的样本不均衡问题这里就不用手动过采样了max_iter5000必须设否则 LinearSVC 默认的1000次迭代可能在数据量大时跑不到收敛就报警告。LSTM的说明如果你确实想跑LSTM需要额外一步——把每一条标题分词后映射成整数ID序列然后pad到固定长度新闻标题一般30个字以内pad到50就够。embedding层用随机初始化还是预训练词向量毕设一般用随机初始化就行预训练词向量在数据量不够大的时候反而可能拖慢收敛。4.3 评估只看准确率是不够的混淆矩阵和分类报告必须看准确率在类别不均衡时会骗人——你全预测“体育”也许都能到70%准确率。所以评估阶段我必看两个东西classification_report里每个类别的 precision/recall/f1以及混淆矩阵。from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt best_model models[LinearSVM] # 假设SVM效果最好 pred best_model.predict(X_val) print(classification_report(val_df[label_id], pred, target_nameslist(label_map.values()))) cm confusion_matrix(val_df[label_id], pred) plt.figure(figsize(10, 8)) sns.heatmap(cm, annotTrue, fmtd, xticklabelslist(label_map.values()), yticklabelslist(label_map.values())) plt.title(Confusion Matrix - LinearSVM) plt.savefig(confusion_matrix.png, dpi150)看混淆矩阵有两个目的一是找出“哪些类别经常被混掉”——最常见的是“科技”和“数码”互混、“财经”和“房产”互混这说明这些类别的标题用词高度重叠光靠表面特征分不开二是看对角线上的数字密集程度如果对角线明显不够“亮”说明特征工程做得不到位回去调TF-IDF参数比换模型更有效。classification_report里的 weighted f1 是最终打分依据它比 accuracy 更能反映少数类的表现。5. 避坑指南新闻标题分类系统最常见的五个翻车点5.1 类别标签泄漏验证集准确率95%上线后直接崩现象训练时验证集准确率高达95%模型保存后拿新数据一测准确率跌到60%以下。原因最典型的是数据清洗阶段用了全局信息。比如你按 2.2 节的drop_duplicates是对整个 DataFrame 操作的没问题但如果你在切分训练集和验证集之后用fit_transform对验证集也做了同样的向量化转换特征信息就泄漏了。另一个常见泄漏点是在文本清洗时用了re.sub去标签如果标签本身和类别强相关比如标题里带着“#体育#”这种话题标签去掉后模型失去了最强的特征验证时又看到了上线时又没有。解决确认fit_transform只在训练集上调用验证集一律用transform检查清洗逻辑里有没有使用label字段影响title的清洗结果——如果有拆成两步先清洗文本再切分数据集。5.2 中文分词把类别关键词切碎了现象携程、飞猪、去哪儿这类品牌词被切成“携/程”“飞/猪”导致特征维度暴涨且失去语义。原因jieba默认词典没有覆盖新闻标题里的品牌名、专有名词、新词。比如“雷军”“余承东”默认能切对但“张一鸣”可能被切成“张/一鸣”。解决建自定义词典把品牌词、人名、产品名逐行写进custom_dict.txt每行一个词。维护成本不高跑一遍验证集看哪些词的TF-IDF权重最高里面如果出现单字碎片就加进词典。5.3 样本不均衡导致小类别全部阵亡现象混淆矩阵里“体育”类别的recall有95%但“法律”类别的recall只有5%几乎全被预测成别的类。原因类别样本数差距超过10倍时模型在训练时对多数类过拟合。虽然class_weightbalanced能缓解但如果少数类只有不到100条样本权重再大也学不到什么。解决先用class_weightbalanced试一次看少数类是否从5%提升到30%以上如果不够对少数类做简单过采样——复制少数类样本到至少500条或者用RandomOverSampler只过采样训练集绝不过采样验证集。5.4 TF-IDF特征维度爆炸训练直接OOM现象训练到一半报MemoryError或者X_train.shape显示特征维度超过10万。原因TfidfVectorizer没设max_features默认全部特征保留。新闻标题虽然短但几千条样本的词汇聚起来也能轻松到几万维SVM训练时的核矩阵会直接内存溢出。解决设置max_features15000并配合min_df2。如果你用SVM还OOM把max_features降到8000不用太担心效果——长尾词本来就没提供什么信息量。还有一个隐藏技巧用hstack拼接其他特征时先X_train X_train.tocsr()转成CSR稀疏格式能省不少内存。5.5 停用词表误删了否定词现象“不看好”“未见好转”被分错类别“不”字在预处理中被移除。原因网上随便下的停用词表里“不”“没”“无”“别”这些否定词赫然在列。对新闻标题分类来说“不看好”是强烈的情感倾向去掉否定词等于把最重要的语义特征删掉。解决停用词表只保留纯虚词和标点把“不”“没”“无”“别”“莫”全部移出。判断标准很简单删掉后拿验证集跑一遍如果F1下降说明删错了。这个坑我踩过两次血泪经验。6. 让系统从“能跑”变成“能答辩”三个进阶技巧与验证方法如果你希望这个分类系统不只是跑通流程而是能在答辩时讲出深度有三个方面值得补。第一是加一个在线学习逻辑——用sklearn的partial_fit让模型能增量更新这样新数据进来不用重训全集。具体做法是把LinearSVC换成SGDClassifier(losshinge, partial_fitTrue)在已有模型基础上用新样本做一轮迭代。注意partial_fit第一次调用必须传入所有类别标签classesnp.unique(train_df[label_id])后续调用不用。这个能力在新闻场景下很有说服力——新闻在持续产生你不可能每天全量重训。第二个技巧是给预测结果加置信度阈值。分类器输出的不是标准概率LinearSVC的decision_function是距离你需要用CalibratedClassifierCV包装一层把距离校准成概率。然后设定阈值——比如置信度低于0.6的样本标记为“待人工审核”而不是强行给出一个可能错误的分类。这个设计在答辩时很加分因为它体现了对模型不确定性的认知。第三个也是最重要的验证方法留出法之外的 K 折交叉验证。如果你只用一次train_test_split结果可能受随机种子影响很大。在2.2节里我固定了random_state42但答辩时老师很可能问“换个随机种子结果还一样吗”。用cross_val_score跑5折交叉验证输出每个折的F1均值±标准差能证明你的系统是稳定的。5折均值在0.85左右标准差在0.02以内就是一个可以进答辩的成绩。我的习惯是每次改完参数先跑一次全流程再跑一次cross_val_score对比结果。如果改参数让K折均值提升但单次验证集掉点说明是随机波动如果两个方向一致说明改动是有效的。这套流程本身比任何单一模型的调参技巧都更能保护你——至少心里有数。希望帮到你。本文还有配套的精品资源点击获取
返回列表