尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于机器学习与TF-IDF的图书分类系统实现

基于机器学习与TF-IDF的图书分类系统实现 简介这份压缩包提供了一套基于机器学习算法的图书分类系统源码与配套资料适合具备Python与机器学习基础的学生或开发者用于理解分类模型的实现与调优。资源共17个文件包含3个Python脚本实现贝叶斯分类、感知器、线性判别分析等算法、3个CSV测试数据、3个Markdown文档含支持向量机笔记与回归分析作业、2个Excel数据文件、2个PDF试题及答案说明另有R项目文件与图片总大小约3.35MB。系统涵盖数据预处理、贝叶斯两类分类、感知器模式识别、线性判别界面绘制及曲线拟合过拟合现象体验并配有用户界面设计。已有65人学习下载。通过源码与期中考试数据读者可完整复盘回归分析期中作业的建模流程掌握从数据清洗到模型评估的实操技能是课程设计或期末复习的实用参考。1. 机器学习算法的图书分类系统为什么传统文本分类仍是首选图书分类是图情领域的老问题一家小型图书馆或出版社每年新增几千册书人工按中图法归类一个人一天最多处理一两百条还容易受主观因素干扰。把书名、简介等文本交给机器学习算法让模型学会“出现‘编程’‘算法’的样本更可能归到计算机类”这正是基于机器学习算法的图书分类系统的核心目标。这类项目常出现在毕业设计、课程设计和图书管理系统的辅助模块里技术栈固定中文分词、TF-IDF 特征、朴素贝叶斯或 SVM 分类器、模型持久化再套一个命令行或 Web 接口。一个反直觉的结论是在几千到几万条的小样本场景下传统机器学习算法的效果并不比深度模型差反而训练更快、可解释性更强也更适合在普通机器上运行。适合正在搭课设或毕设骨架的人也适合给中小型馆藏做自动编目前置筛选的工程师。2. 图书分类系统的方案选型类别体系、文本表示与算法对比2.1 类别体系先于算法中图法还是自定义分类图书分类首先要定的是“分到哪几类”这不是建模问题而是业务边界问题。中图法有 22 个大类纸质图书馆必须遵循但一套课设或中小型系统的机器学习模型往往只需覆盖实际馆藏的少数类别例如文学、计算机、经济、历史、医学、艺术六类。类别数量直接决定样本需求每类 200500 条标注数据6 类约 15003000 条就能训练出一个可用的基线模型如果按中图法拆到 22 类数据准备成本会翻四倍且某些冷门类目样本极难凑齐。常见做法是先和业务方确认一本藏书的分类粒度再决定是沿用中图法大类还是自定义标签。系统设计上类别 id 与名称必须在训练前固定下来因为后续预测、统计报表、甚至书架排布都要依赖同一个映射表。提示类别不要一开始定得太多。比较稳妥的路径是先跑通 6 类基线再逐步合并或细分避免样本不均衡拖垮模型效果。2.2 文本表示TF-IDF 为什么比 Word2Vec 更稳分类器吃的是数值向量不是字符串。文本表示有两类主流选择TF-IDF 词袋模型和 Word2Vec 等词向量。TF-IDF 的核心是一个词在文档中出现得越多越重要但它同时出现在大量文档里时重要性又会下降。sklearn 中TfidfVectorizer的默认平滑公式是idf ln((1n)/(1df)) 1n是文档总数df是包含该词的文档数。这套表示有两个优点特征维度可控且每个维度都有明确的词义指向遇到误分类可以追溯。词向量的思路是用上下文训练分布式表示对同义词、近义词更鲁棒但训练需要大规模语料短文本上做平均池化会稀释关键信息。图书书名和简介通常只有几十到几百个字TF-IDF 这类稀疏表示反而能把“深度学习”“数据结构”这种判别性强的词顶起来。from sklearn.feature_extraction.text import TfidfVectorizer tfidf TfidfVectorizer( ngram_range(1, 2), # 把“机器学习”作为一个整体特征 min_df2, # 过滤掉只在 1 条样本里出现的词 max_features20000, # 限制特征维度防止稀疏矩阵过大 sublinear_tfTrue # tf 用 1log(tf) 压缩高频词优势 ) X tfidf.fit_transform([机器学习 算法 图书, 数据库 设计 原理])fit_transform先统计整个训练集的词表与 idf 权重再把每条文本映射成稀疏矩阵。ngram_range(1,2)在中文里的意义是同时保留“机器”“学习”和“机器学习”这样的二元词组能缓解分词不精确带来的信息损失代价是特征量大约翻倍所以需要min_df和max_features一起约束。sublinear_tf的作用是避免一个词在长简介里重复出现 20 次就把权重放大 20 倍。2.3 算法对比与最终选型文本分类的候选算法集中在朴素贝叶斯、线性 SVM、KNN 和逻辑回归这几种。算法训练速度高维稀疏表现概率输出本项目中的取舍朴素贝叶斯MultinomialNB快好有基线首选独立假设在短文本上损失不大LinearSVC线性 SVM快很好无 predict_proba精度高用 decision_function 当置信度KNN无训练阶段差距离计算受维数灾难影响无特征维度上万时分类耗时不可控弃用逻辑回归LogisticRegression快好有需要概率输出时可替代 LinearSVC实际方案默认跑两个模型MultinomialNB和LinearSVC交叉验证后用 F1 选优而不是一开始就固定某一个。KNN 在课上演示原理可以真实系统里不建议因为 TF-IDF 矩阵动辄上万维每预测一条都要计算与全部训练样本的距离在线场景扛不住。3. 图书分类数据准备从图书信息到 jieba 分词样本集3.1 图书数据字段的取舍与标注成本训练分类模型需要的是文本特征和标签不是图书的所有元数据。书名、简介是最有判别力的两个字段作者、出版社、ISBN 对主题分类几乎没有帮助一般不进特征。拿不到简介的冷门书可以只拼书名把缺字段的书单独标记出来避免空文本进入向量器报错。实际数据里如果只有书名和简介就先打基线目录文本可以作为第二阶段增强但目录清洗成本高不建议一开始就纳入。数据来源常见做法是爬取图书网站的“分类浏览”页面获取书名和简介再按网站栏目标注类别。这里有个坑网站分类和馆藏分类不一致时标签会带噪声比如有些网站把“科幻”单列而中图法里科幻归在文学类。数据清洗时先做规则合并把相近类别归并后再进入训练流程。3.2 构造训练集书名、简介与分类标签训练数据整理成 CSV三列title、summary、category。category用中文可读名称不用数字便于排查错误标注编码在特征工程里用LabelEncoder完成。数据量按 2.1 的估算每类不少于 200 条总样本建议在 2000 以上否则 SVM 学不出稳定的分类边界。import os import pandas as pd from sklearn.preprocessing import LabelEncoder df pd.read_csv(data/raw/books.csv) df[text] df[title].fillna() df[summary].fillna() df df[df[text].str.strip() ! ] # 去掉完全没有文本的脏样本 le LabelEncoder() df[label_id] le.fit_transform(df[category]) print(df[category].value_counts())fillna()处理缺简介的记录文本拼接用“书名 空格 简介”空格是为了避免书名末字与简介首字拼出假词。LabelEncoder把类别名映射为 0 到 n-1 的整数同时保留classes_属性预测阶段用inverse_transform把数字还原成中文类名。注意拟合LabelEncoder时必须用完整训练集测试集不能单独 fit否则两个编码器映射可能错位。3.3 jieba 分词与停用词过滤的工程细节中文分词是整个系统误差最大的来源之一。jieba 默认词典处理通用词不错但书名里大量专业术语如“Spring Boot”“数据结构”会被切成碎片。解决方法是准备自定义词典userdict.txt每行一个词格式是“词 词频 词性”Transformer 100 n Spring Boot 100 nz 图神经网络 100 n词频数字只影响最大概率路径的优先级不要求真实统计给一个比默认词频更大的值即可。import os import jieba os.makedirs(data/processed, exist_okTrue) jieba.load_userdict(data/userdict.txt) stopwords set() with open(data/stopwords.txt, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) def cut_text(text): words jieba.cut(text) return .join(w for w in words if w.strip() and w not in stopwords) df[text_cut] df[text].apply(cut_text) df.to_csv(data/processed/books_cut.csv, indexFalse)jieba.cut默认是精确模式适合文本分类全模式会输出所有可能的分词组合产生大量噪声这里不用。停用词表可以下载公开的中文停用词表做基础再往里追加本业务特有的无效词。但像“教程”这种词在某些类别里恰恰是判别特征所以更稳妥的做法是先不去停用词训练一版对比特征权重后再决定删哪些这个对比过程放到第 6 章展开。4. 特征工程与模型训练用 TF-IDF 和朴素贝叶斯/SVM 跑通分类4.1 TF-IDF 向量化的参数取舍ngram_range、min_df 与 max_features第 2 章给出的TfidfVectorizer配置可以直接作为默认起点。针对图书文本还要加一点书名和简介长度差异大长简介里同一词重复出现多次sublinear_tfTrue能压住这种差异ngram_range(1,2)设定后要观察特征总数如果max_features20000截断后每类平均特征太少就降到 15000 或适当放宽min_df。这里要区分两个过滤参数min_df按文档频率过滤小于 2 的说明该词只在一条样本里出现过不具备泛化能力max_df按最高文档频率过滤max_df0.9会剔除 90% 以上文档都出现的词这类词与类别无关。默认代码不显式设置max_df实际项目中建议加上否则“书”“出版”这类高频词会占据大量特征维度。4.2 完整训练脚本网格搜索与双模型对比训练脚本一次完成切分、网格搜索、评估、保存。用 Pipeline 把向量器和分类器串起来目的是让交叉验证时每一折都在训练折上重新拟合向量器避免测试折信息泄漏。import pandas as pd from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.svm import LinearSVC from sklearn.pipeline import Pipeline from sklearn.metrics import classification_report df pd.read_csv(data/processed/books_cut.csv) X_train, X_test, y_train, y_test train_test_split( df[text_cut], df[label_id], test_size0.2, random_state42, stratifydf[label_id] ) pipe_nb Pipeline([ (tfidf, TfidfVectorizer(ngram_range(1, 2), min_df2, max_features20000, sublinear_tfTrue)), (clf, MultinomialNB()) ]) param_nb {clf__alpha: [0.01, 0.1, 0.5, 1.0]} gs_nb GridSearchCV(pipe_nb, param_nb, cv5, scoringf1_macro, n_jobs-1) gs_nb.fit(X_train, y_train) print(gs_nb.best_params_, gs_nb.best_score_)stratify按标签比例分层切分防止小众类别在测试集里恰好没分到。GridSearchCV的scoringf1_macro比 accuracy 更关注小类别表现多类不平衡时默认准确率会偏袒多数类。clf__alpha的双下划线表示 Pipeline 中clf步骤的参数alpha是朴素贝叶斯的平滑系数值越小对训练集拟合越充分值越大越平滑0.1 到 1.0 之间通常能找到平衡点。LinearSVC 的搜索参数集中在C和class_weightpipe_svm Pipeline([ (tfidf, TfidfVectorizer(ngram_range(1, 2), min_df2, max_features20000, sublinear_tfTrue)), (clf, LinearSVC()) ]) param_svm { clf__C: [0.1, 1.0, 10.0], clf__class_weight: [balanced, None] } gs_svm GridSearchCV(pipe_svm, param_svm, cv5, scoringf1_macro, n_jobs-1) gs_svm.fit(X_train, y_train)class_weightbalanced按类别频率反比放大少数类样本的损失是处理图书类别样本不齐最省事的方法但加大后少数类可能过拟合所以把 None 也放进搜索空间对比。C是正则化强度的倒数C 越小惩罚越重、分类边界越简单图书文本特征维度高从 0.1 起步搜索即可不需要试 100 这样的大值。4.3 评估指标准确率之外的 F1 与混淆矩阵分类报告直接打印每类的 precision、recall、f1。图书分类的典型分布是计算机和经济类容易混因为跨领域教材多艺术和历史类 F1 偏低原因是样本少且简介用词抽象。下面是一个 6 类样本集的示例结果只用于说明观察方式不同数据集的绝对数值不具备可比性。类别precisionrecallF1文学0.910.930.92计算机0.860.890.87经济0.780.740.76历史0.820.790.80医学0.900.850.87艺术0.770.710.74经济与艺术 F1 低是常见现象。遇到这种情况先看混淆矩阵定位是“经济被分成计算机”还是“艺术被分成文学”再决定补数据还是调class_weight。from sklearn.metrics import ConfusionMatrixDisplay import matplotlib.pyplot as plt ConfusionMatrixDisplay.from_predictions( y_test, gs_svm.predict(X_test), display_labelsle.classes_ ) plt.xticks(rotation45) plt.tight_layout() plt.savefig(reports/confusion_matrix.png, dpi150)混淆矩阵的行是真实类别列是预测类别对角线越深越好。非对角线上的热点就是样本混淆最严重的位置下一步的数据补充应该优先针对这些格子而不是盲目增加所有类别的样本。5. 系统落地模型持久化、预测接口与源码包的结构5.1 模型持久化joblib 保存向量器与模型训练完成只是第一步系统要能用必须把向量器和分类器一起保存。TfidfVectorizer内部保存着词表和 idf 权重预测阶段新来的文本必须用同一套词表转成同维度稀疏向量所以不能只存分类器不存向量器。常见做法是把两者封在一个 Pipeline 里整体持久化。import joblib # 选择交叉验证 F1 更高的模型做最终产物 best_model gs_svm.best_estimator_ joblib.dump(best_model, models/book_classifier.pkl) joblib.dump(le, models/label_encoder.pkl)保存路径建议用基于脚本所在目录的动态路径避免从项目根目录之外启动脚本时找不到文件。加载时同样要两个一起加载顺序反过来也没有影响但两个 pkl 文件要放在同一目录下管理。model joblib.load(models/book_classifier.pkl) le joblib.load(models/label_encoder.pkl)joblib对大数组的压缩比 pickle 更高效稀疏矩阵也能兼容。注意 scikit-learn 版本差异大版本升级后 load 可能出现兼容性警告建议在 requirements.txt 里锁版本。5.2 预测接口与命令行调用Flask 与 argparse 两种方式系统形态可以是命令行脚本也可以是一个 Web 接口。命令行模式要读入书名和简介走与训练相同的分词流程再预测不能跳过分词直接把原始文本传给模型import argparse parser argparse.ArgumentParser(description图书分类预测) parser.add_argument(--title, requiredTrue, help书名) parser.add_argument(--summary, default, help图书简介) args parser.parse_args() # 复用 3.3 的 cut_text保证与训练时预处理一致 text cut_text(f{args.title} {args.summary}) label_id model.predict([text])[0] print(le.inverse_transform([label_id])[0])这里必须复用训练时的分词函数和停用词表任何不一致都会导致词表对不上。predict接收的是一个样本列表直接传字符串会报错所以写成[text]。Web 接口用 Flask 提供一个 POST 接口from flask import Flask, request, jsonify app Flask(__name__) app.post(/predict) def predict(): data request.get_json() title data.get(title, ) summary data.get(summary, ) text cut_text(f{title} {summary}) label_id model.predict([text])[0] return jsonify({ category: le.inverse_transform([label_id])[0], label_id: int(label_id) })LinearSVC 没有predict_proba接口如果要给置信度可以取decision_function的最大值但不同类别间的得分尺度不完全可比阈值需要依据验证集分布来定。如果业务上一定要概率输出把分类器换成LogisticRegression或者用CalibratedClassifierCV包一层代价是训练和推理时间小幅上升。启动命令是flask --app app run --host 0.0.0.0 --port 8000Flask 版本低于 2.3 时需要脚本末尾加app.run()再执行python app.py。5.3 源码包解压后的目录结构与依赖安装拿到标题里那个.zip源码包解压后通常会看到类似下面的结构。源码包经过不同人手文件名会有差异但职责划分一般遵循同一套逻辑。book_classifier/ ├── README.md ├── requirements.txt ├── data/ │ ├── raw/books.csv │ ├── userdict.txt │ └── processed/books_cut.csv ├── models/ │ ├── book_classifier.pkl │ └── label_encoder.pkl ├── src/ │ ├── train.py │ └── predict.py └── app.py各路径的职责如下路径职责data/raw 与 data/processed原始数据与分词后数据的落盘位置processed 由脚本生成models持久化的 Pipeline 与 LabelEncoder不应手动编辑src/train.py 与 src/predict.py训练与命令行预测入口app.pyWeb 接口入口依赖 src 里的预处理函数requirements.txt内容建议固定版本jieba0.42.1 scikit-learn1.3.2 pandas2.1.4 Flask3.0.0依赖安装和训练复现的顺序是先建虚拟环境python -m venv venv激活后执行pip install -r requirements.txt再运行python src/train.py。训练脚本跑完会覆盖models/下的 pkl 文件这一步能确认数据路径、依赖版本、代码在同一台机器上对齐。Windows 上解压 zip 后如果中文文件名乱码通常是压缩时用了 GBK 编码而系统按 UTF-8 解压用 7-Zip 或者 Python 的zipfile指定编码重新解压即可。6. 进阶排错样本不均衡、分词边界与置信度阈值6.1 三个高频问题与对应处理图书分类数据里最常见的问题是类别样本悬殊比如计算机类 800 条、艺术类只有 150 条。除class_weightbalanced外另一个有效手段是对少数类做简单过采样即重复采样或加入轻微扰动副本效果通常不如class_weight稳定但可以两者结合。分词边界问题的影响比想象中更大“数据结构”被切成“数据”和“结构”会同时削弱计算机类的几个强特征。更稳妥的做法是把“数据结构”“操作系统”“机器学习”整词收进userdict.txt。验证分词是否合理的办法是打印几条样本的text_cut肉眼看一遍重点看书名里的专有名词。错误现象可能原因处理方式经济类大量被分到计算机跨领域教材样本重叠补充单一领域样本或调整 ngram_range 观察特征某类 F1 明显低于其他类该类样本过少class_weight 或过采样再看混淆矩阵定位预测结果集中在少数类别训练集类别分布严重倾斜重新采样或按类别分层切分数据集6.2 把置信度阈值变成可用功能部署时不能只给一个类别还要判断这次预测可不可信。对 LinearSVC取decision_function的最大值作为得分在验证集上画出得分分布找到误分类样本最集中的那段区间把阈值设在它的下沿。import numpy as np # model 是训练好的 PipelineX_test 是验证集文本 scores model.decision_function(X_test).max(axis1) threshold np.percentile(scores, 20) # 取 20 分位作为初值低于阈值的预测返回“无法确定请人工复核”这对图书编目系统是真正有用的功能比模型硬给一个错误类名负责得多。np.percentile给出的只是可调起点上线后按月统计阈值附近的预测反馈再修正阈值或者改用CalibratedClassifierCV输出概率并用概率阈值做决定。本文还有配套的精品资源点击获取
返回列表