来了,作为带过不少NLP入门项目的从业者,我太清楚“LSTM调参调到头秃、结果还没传统模型高”是什么滋味了。这也是为什么我一直觉得,NLP-Beginner系列任务一设置的“基于机器学习的文本分类”特别适合作为第一站——它没有复杂的网络结构,没有GPU和CUDA的折腾,就是用最标准的TF-IDF加几个经典分类器,让你先把“文本怎么变成数字、模型怎么根据数字做判断”这条主线彻底跑通。
这篇博客会把任务一从数据准备、中文分词、TF-IDF特征构造,到朴素贝叶斯、逻辑回归、SVM三种模型的训练与评估,全部拆开揉碎讲清楚。整个过程我都会给出可直接复现的代码和参数,并且把我实际运行中踩过的坑、查过的源码、调整过的细节一并交代。不管你之前是只听过“机器学习”这个词,还是已经看过几篇教程但没动手跑过完整流程,照着这篇操作一遍,基本就能建立起正确的文本分类直觉了。
1. 任务背景与整体思路解构
1.1 这个任务到底在解决什么问题
文本分类是自然语言处理(NLP)里最经典、最入门的任务方向。从垃圾邮件识别、新闻主题分类到电商评论情感判断,本质上都是同一件事:给定一段文本,让程序自动打上一个预先定义好的标签。
任务一的核心目标听起来很简单:用机器学习方法,对中文文本做多分类。但这里有个关键点——它强调用“机器学习”而不是“深度学习”。这意味着我们暂时不需要碰神经网络、Embedding、注意力机制这些概念,而是回到最朴素的路线:把文本变成向量,然后扔给一个传统的分类器(比如朴素贝叶斯、逻辑回归、SVM)去训练。这条路线虽然“传统”,却是理解NLP整个pipeline的地基,理解了它,你后面再学复杂模型会通透得多。
任务中用的数据集一般是THUCNews(清华大学新闻文本分类数据集)的一个子集,从中抽取体育、财经、房产、家居、教育、科技、社会、时尚、游戏、娱乐等若干类别。每个类别几千条新闻标题或正文,加在一起大概几万条,规模非常友好,即使只用CPU跑,整个训练加评估也就几分钟到十几分钟的事。
1.2 核心技术流程拆解
把整个任务拆开,其实就是一条标准的文本机器学习流水线:
- 数据读取与标签处理:把原始txt文本和对应的分类标签读入内存,统一编码格式。
- 文本清洗与分词:去掉无意义的符号和停用词,用结巴把连续的中文字符串切成有意义的词序列。
- 特征工程:把不定长的词序列转换成固定长度的数值向量,这里用的是TF-IDF特征。
- 模型训练:用训练集的特征向量和标签去训练分类器。
- 模型评估:在测试集上计算准确率、F1分数等指标,分析每个类别的表现。
这个流程其实就是几乎所有NLP任务的缩影。我见过不少同学一上来就盯着模型部分折腾,结果数据乱糟糟、特征没做干净,最后模型精度上不去也不知道问题出在哪。按照上面的顺序一步步走完,你会很清楚每个环节对最终结果的影响有多大。
1.3 环境准备与工具选型
任务一的实现用Python完成最方便。需要装的库不多,但版本要稍微注意一下:
- Python:3.8或以上版本,3.9、3.10都行,实测没有兼容性问题。
- jieba:中文分词库,pip install jieba即可。这是目前中文NLP入门最常用的分词工具。
- scikit-learn:机器学习算法库,提供了TF-IDF向量化、朴素贝叶斯、逻辑回归、SVM、评估指标等几乎全部所需组件。
- numpy / pandas:数据处理的底层依赖,安装sklearn时会自动装上numpy,pandas建议单独装一下,读取数据方便。
pip install jieba scikit-learn pandas numpy我用的版本是scikit-learn 1.2.2和jieba 0.42.1,文中的所有API在这个组合下测试通过。如果你用的是很老的sklearn版本(0.19、0.20之类),个别API可能有差异,后面我在避坑部分会单独提到。
2. 数据准备与文本预处理:做好这一步,模型就成功了一半
2.1 数据集的获取与目录结构
THUCNews原始数据集非常大(几个G的压缩包),任务一一般不需要全量数据。常见的做法是:在课程仓库或相关资源中直接下载已经抽好的子集,文件名一般是cnews.train.txt、cnews.val.txt、cnews.test.txt,每行一条样本,格式为“标签\t文本内容”。
如果没有现成子集,你也可以自己从完整THUCNews里按类别抽样:每个类各抽5000条做训练、500条做验证或测试,这样总量在几万条级别,既能训练出不错的效果,又不会让训练时间长得让人失去耐心。类别建议选10个左右,太少了锻炼不了分类能力,太多了又容易让入门者对着混淆矩阵发懵。
我这次实验使用的是标准的10分类子集,类别包括体育、财经、房产、家居、教育、科技、社会、时尚、游戏、娱乐。样本量大概是:训练集每个类5000条,测试集每个类1000条。数据读取时有个细节容易出错:文件编码绝大多数是UTF-8,但Windows下如果直接从网页复制数据,偶尔会遇到GBK编码。稳妥起见,读取时先试UTF-8,报错再换GBK。
import pandas as pd def load_data(path): texts, labels = [], [] with open(path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if not line: continue label, content = line.split('\t', maxsplit=1) labels.append(label) texts.append(content) return texts, labels train_texts, train_labels = load_data('data/cnews.train.txt') test_texts, test_labels = load_data('data/cnews.test.txt') print(f'训练集样本数: {len(train_texts)}') print(f'测试集样本数: {len(test_texts)}') print(f'类别: {sorted(set(train_labels))}')2.2 中文分词:为什么必须分,以及怎么分
英文文本天然按空格分词,但中文没有分隔符,所以必须专门处理。“我爱自然语言处理”这句话,如果按字为单位,模型看到的是“我”“爱”“自”“然”“语”“言”“处”“理”这八个孤立的字,每个字能携带的语义信息非常有限。切分成“我/爱/自然语言处理”之后,模型才能把“自然语言处理”作为一个整体特征去学习,效果完全不一样。
jieba是目前最流行的中文分词库,支持精确模式、全模式和搜索引擎模式。文本分类场景用默认的精确模式就好,它会按照最合理的概率路径切分句子。分词之后通常还要做一件事——去停用词。所谓停用词,就是“的”“了”“在”“是”“和”这类出现频率极高但对分类没有帮助的虚词。这些词如果进入特征,会占据很大的权重,反而稀释掉真正的主题词。
import jieba stopwords = set() with open('data/stopwords.txt', 'r', encoding='utf-8') as f: for line in f: stopwords.add(line.strip()) def tokenize(text): words = jieba.lcut(text) return [w for w in words if w.strip() and w not in stopwords] sample_text = train_texts[0] print('原始文本:', sample_text) print('分词结果:', '/'.join(tokenize(sample_text)))我实验中用到的停用词表是中文NLP领域常用的哈工大停用词表,网上直接搜“哈工大停用词表”就能找到。如果你找不到,也可以自己在分词结果里用Counter统计最高频的词汇,然后手工往stopwords里加。不过要注意,有些高频词可能是有分类意义的,比如“游戏”类别里的“游戏”二字,所以停用词表不要加得太激进,一句话:宁可少去,不可误杀。
2.3 文本清洗的其他细节
清洗环节还有一个容易被忽略的点:把数字、英文字母、特殊符号统一处理。新闻文本里经常出现“2022年”“iPhone14”“A股”这类内容,在你还没学词向量之前,直接保留原样让TF-IDF处理也可以,但会出现特征维度爆炸的问题。比较皮实的处理方式是:把连续的数字替换成占位符“NUM”,把连续英文保留字母,特殊符号直接删除。
import re def clean_text(text): text = re.sub(r'\d+', ' NUM ', text) text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z]+', ' ', text) return re.sub(r'\s+', ' ', text).strip()这里有一个实用的夫妻档操作:先清洗,再分词,最后去停用词。顺序不能反——如果先去停用词再清洗,可能出现“数字被去掉了但空格还在”的尴尬情况,后续会多出很多空字符串词。这类问题都不难解决,但在入门阶段很容易因为顺序不当而白折腾半天。
3. 特征工程:TF-IDF的直觉理解与手写实现
3.1 为什么不能直接把文本喂给模型
机器学习模型的数学基础决定了它只能处理数值输入。一棵决策树倒是能处理字符串,但它也只能比较“等于不等于”,学不到语义相似度。所以文本分类的第一步,就是把每一段文本固定成一个向量,让模型在这个向量空间里找分类边界。
最简单的文本向量化方式是词袋模型(Bag of Words):统计每篇文章里每个词出现了多少次,形成一个以词为维度、以频次为值的向量。但词袋有一个明显问题:像“的”“了”这类停用词即使过滤得很干净,仍会有些高频但信息量低的词占据较大数值。而某些词虽然只在少数文章里出现,却非常能代表文章主题。这时就需要IDF(逆文档频率)来调整权重。
3.2 TF-IDF计算公式拆解
TF-IDF是Term Frequency - Inverse Document Frequency的缩写,由两部分组成:
- TF(词频):某词在当前文档中出现的次数,除以当前文档的总词数(或直接取原始次数)。它衡量这个词对当前文档的重要程度。
- IDF(逆文档频率):衡量这个词在整个语料中的区分能力。公式是:
$$IDF(t) = \ln\left(\frac{N + 1}{DF(t) + 1}\right) + 1$$
其中N是文档总数,DF(t)是包含这个词的文档数。一个词如果在很多文档里都出现,DF很大,IDF就小,说明它不“稀有”,区分能力弱。
最终TF-IDF分数就是TF乘以IDF。sklearn里TfidfVectorizer会对向量做L2归一化,所以实际输出结果是归一化之后的,目的是让不同长度的文档向量范数一致,避免长文档天然拥有更大的数值。
3.3 手写一个简化版TF-IDF,彻底搞懂原理
网上的教程太多直接调包,导致很多人其实不理解TF-IDF背后发生了什么。我建议你动手写一个简化版,哪怕只用来跑跑极小的样例,也能帮你在调参时建立良好的直觉。下面是我写的一个简化实现:
import math from collections import Counter def compute_tfidf(documents): # documents: list of list of tokens doc_count = len(documents) df = Counter() for doc in documents: for token in set(doc): df[token] += 1 doc_tfidfs = [] for doc in documents: term_count = Counter(doc) total_terms = len(doc) doc_vec = {} for token, count in term_count.items(): tf = count / total_terms idf = math.log((doc_count + 1) / (df[token] + 1)) + 1 doc_vec[token] = tf * idf doc_tfidfs.append(doc_vec) return doc_tfidfs docs = [ ['我', '喜欢', '机器学习'], ['我', '喜欢', '自然语言处理'], ['机器学习', '是', '人工智能', '方向'], ] vecs = compute_tfidf(docs) for i, vec in enumerate(vecs): print(f'文档{i}: {dict(sorted(vec.items(), key=lambda x: -x[1]))}')运行这段代码,你会发现“机器学习”在第一篇文档和第三篇文档里的权重都不低,因为它出现的次数多且只出现在少数文档里;“我”“喜欢”这种跨文档出现的词权重被拉低了,因为它们无法帮助区分文档主题。这就是TF-IDF的核心思想。
3.4 sklearn中的TfidfVectorizer使用细节
实际项目里直接用sklearn的TfidfVectorizer就行,性能更好,功能也更全。它可以直接接收原始文本列表,并在内部完成分词(如果传tokenizer参数)和TF-IDF计算。常用参数如下:
- analyzer='word':表示按词维度处理,默认就是word。
- tokenizer:指定分词函数,传我们定义好的tokenize函数。
- ngram_range:默认(1,1),只考虑单个词。设置成(1,2)可以加入相邻两个词的组合特征,通常能提升一点效果,但特征维度会暴涨。
- max_features:限制最大特征数,比如80000。如果不限制,10万篇文档的稀疏特征矩阵可能会有上百万维,训练速度会明显下降。
- min_df / max_df:过滤掉文档频率过低/过高的词。min_df=5表示词至少在5篇文档中出现过才保留;max_df=0.8表示在80%以上文档中都出现的词会被过滤掉。
from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer( tokenizer=tokenize, ngram_range=(1, 2), max_features=80000, min_df=5, max_df=0.8 ) X_train = vectorizer.fit_transform(train_texts) X_test = vectorizer.transform(test_texts) print('特征矩阵形状:', X_train.shape)注意一个最关键的操作习惯:只用fit_transform处理训练集,用transform处理测试集。千万不要对测试集调用fit_transform,否则会把测试集的信息(例如词表)偷偷混入训练过程,导致评估结果虚高,这种错误在学术圈叫“数据泄漏”。
4. 模型训练:朴素贝叶斯、逻辑回归与SVM同台竞技
4.1 朴素贝叶斯:最“朴素”但出奇地稳
朴素贝叶斯(Naive Bayes)是文本分类的经典baseline。它的核心思想是:给定一段文本的特征,计算它属于每个类别的后验概率,然后取概率最大的类别作为预测结果。这里的“朴素”指的是一个强假设——特征之间相互独立。在TF-IDF特征场景中,这显然不成立(词与词之间存在明显关联),但神奇的是它依然工作得很好,尤其在小样本、高维稀疏场景下。
sklearn里文本分类常用MultinomialNB,它假设特征服从多项式分布,很适合TF-IDF这类非负计数值。
from sklearn.naive_bayes import MultinomialNB nb_model = MultinomialNB(alpha=1.0) nb_model.fit(X_train, train_labels) nb_acc = nb_model.score(X_test, test_labels) print(f'朴素贝叶斯准确率: {nb_acc:.4f}')这里alpha是拉普拉斯平滑系数,默认1.0。它的作用是防止某个词在某类别下的概率为0导致整体概率变为0。如果训练样本足够多,alpha调小一点(0.01)有时候能略微提升精度,但提升幅度通常不大。我的实测中,alpha=1.0已经能跑到大约0.92的准确率,作为baseline非常合格。
4.2 逻辑回归:线性模型里最让你省心的选手
逻辑回归(Logistic Regression)从数学上讲其实是一个线性模型外面套了一层sigmoid函数,把线性输出压缩到0到1之间,作为分类概率。它训练速度快、可解释性强、对稀疏特征友好,是很多工业级文本分类方案的默认选择。
sklearn的LogisticRegression默认用L2正则化,有一个参数C控制正则化强度。C越小,正则化越强,模型越不容易过拟合;C越大,越倾向于在训练集上做到极致。文本分类的稀疏高维场景对正则化比较敏感,我建议从C=1.0起步,再用后面讲的网格搜索微调。
from sklearn.linear_model import LogisticRegression lr_model = LogisticRegression(C=1.0, max_iter=1000, solver='liblinear') lr_model.fit(X_train, train_labels) lr_acc = lr_model.score(X_test, test_labels) print(f'逻辑回归准确率: {lr_acc:.4f}')solver参数我选的是liblinear,这是针对小规模稀疏数据设计的求解器,速度很快。如果样本量特别大,可以换成lbfgs跑得更稳。max_iter要设大一点,否则当C较大时可能会报“ConvergenceWarning”让你调大迭代次数,实际跑出来的效果反而没有达到最优解。
4.3 SVM:文本分类场上的“决赛选手”
支持向量机(SVM)在传统机器学习时代曾经是文本分类的王者,现在虽然被深度学习压过风头,但在中小规模数据上依然是效果最好的传统模型之一。SVM的核心思想是找到一个超平面,让不同类别样本到超平面的间隔最大化。对于文本这种高维稀疏数据,线性SVM往往就足够了,不必上RBF核,否则训练速度会慢到怀疑人生。
sklearn里用SVC(kernel='linear')或者更快的LinearSVC。我建议用LinearSVC,它对大规模稀疏数据的优化更好,跑起来很干脆。
from sklearn.svm import LinearSVC svm_model = LinearSVC(C=1.0, max_iter=5000) svm_model.fit(X_train, train_labels) svm_acc = svm_model.score(X_test, test_labels) print(f'SVM准确率: {svm_acc:.4f}')我在实验中,LinearSVC在10分类THUCNews子集上的准确率能到0.96左右,比朴素贝叶斯高不少,也比逻辑回归略好一点点。不过这个优势并非绝对,在某些特定数据集上逻辑回归和SVM的差别可以忽略不计。选择谁作为最终模型,要靠评估指标说话,而不是凭印象。
4.4 网格搜索:花最少的时间找到最好的参数
手动调参就像盲人摸象,试来试去不一定能碰到最优解。推荐的姿势是用GridSearchCV做一次小规模的网格搜索,把候选参数空间列出来,让它自动交叉验证选出最优组合。
from sklearn.model_selection import GridSearchCV param_grid = { 'C': [0.1, 1, 10], } grid = GridSearchCV( LinearSVC(max_iter=5000), param_grid, cv=3, scoring='accuracy', n_jobs=-1 ) grid.fit(X_train, train_labels) print('最优参数:', grid.best_params_) print('最优交叉验证得分:', grid.best_score_)注意,网格搜索里cv=3表示3折交叉验证,训练时间大约是直接训练的三倍。在几万样本、几万特征的规模下,这个耗时还在可接受范围内,通常一两分钟能跑完。如果时间紧张,也可以换RandomizedSearchCV做随机搜索,效果类似但更快。
5. 模型评估:除了准确率还要看什么
5.1 准确率可能“骗”了你
任务一最基本的要求是测试集准确率,这个指标最容易理解:预测正确的样本数除以总样本数。但在多分类中,准确率只反映整体水平,掩盖了很多细节。举个例子:如果某个类别占了总样本的60%,模型只要把所有样本都预测成这个类,准确率也有60%,看起来很不错,实际上其他类别的分类效果完全没体现。
所以还需要两个工具:分类报告和混淆矩阵。分类报告给出每个类别的精确率、召回率、F1值;混淆矩阵直观展示哪个类别和哪个类别之间容易混淆。
from sklearn.metrics import classification_report, confusion_matrix pred_labels = svm_model.predict(X_test) print(classification_report(test_labels, pred_labels))我们可以具体看“家居”和“房产”这两类。新闻标题经常同时出现“小区”、“精装”、“房价”这类词,机器确实容易把它们混在一起。分类报告会告诉我们每类的召回率,如果“房产”的召回率偏低,说明有很多房产新闻被误判成了家居或其他类别,这时候可以考虑两个方向:一是增加该类别的训练数据,二是检查特征工程里有没有丢失该类别特有的关键特征。
5.2 混淆矩阵的可视化与解析
import matplotlib.pyplot as plt from sklearn.metrics import ConfusionMatrixDisplay categories = sorted(set(train_labels)) ConfusionMatrixDisplay.from_predictions( test_labels, pred_labels, labels=categories, xticks_rotation='vertical' ) plt.show()可视化之后,你会发现大部分数字集中在矩阵对角线上(即预测正确),但某些非对角线位置会冒出一团“亮点”。比如“娱乐”和“时尚”互相混淆,这是个很有价值的信息——说明在TF-IDF特征下,这两个类别的用词分布高度重叠。你可以专门挑几条被分错的新闻标题,打印出来自己读一读,往往会有种“哦,原来这个内容既像娱乐又像时尚”的感觉。这就是文本分类的边界所在,也是后续深度学习模型需要去攻克的难点。入门的你,能在传统方法下看到并理解这种混淆,就已经很好了。
5.3 交叉验证:评估训练过程是否稳定
除了在固定测试集上评估,也推荐用交叉验证看看模型在不同数据划分下的稳定性。sklearn的cross_val_score可以帮我们快速算出多折的均值和方差。如果方差很大,说明模型对数据划分敏感,可能数据分布不均匀或特征维度过高。
from sklearn.model_selection import cross_val_score scores = cross_val_score(svm_model, X_train, train_labels, cv=5, scoring='accuracy') print(f'交叉验证得分: {scores}') print(f'均值: {scores.mean():.4f}, 标准差: {scores.std():.4f}')任务一阶段不用追求绝对完美的评估体系,但建立“多看几个指标、多分析几个方向”的习惯,对你后面做深度学习任务的实验设计帮助巨大。
6. 常见问题与避坑记录:这些坑我都替你踩过了
6.1 分词没生效,模型效果差得离谱
有同学用TfidfVectorizer时没传tokenizer参数,默认的英文分词器会把中文文本当成一串连续字符,可能一个词都切不出来。结果特征矩阵只有几维,准确率低到令人发指。解决方式很简单:构造vectorizer时明确写上tokenizer=tokenize。如果你不确定分词器是否生效,可以打印vectorizer.get_feature_names_out()的前几十个词,看看是不是中文词汇。这点强烈建议写进你自己的项目checklist里,检查特征不靠猜,靠看词表。
6.2 ngram_range从(1,1)改成(1,2)后训练变慢
加入bigram后,特征数量呈指数级上升。如果max_features没有设置好,本来10万维的特征会膨胀到几百万维,训练时间直接翻十几倍。这种情况先把max_features缩小到50000,跑通流程后再逐步放宽。另外也可以对bow先做一个min_df=10的过滤,提前干掉低频噪声特征。
6.3 sparse matrix转换出错
刚开始写代码时,可能有人习惯性用pd.DataFrame(X_train)把向量化结果转成DataFrame。这一步在特征维度较高时基本会直接把内存打爆,程序秒崩。正确做法是保留稀疏矩阵格式,不转DataFrame。模型可以直接吃scipy.sparse矩阵,sklearn的preprocessing和模型接口对稀疏数据支持得很好。如果你真的想看特征长什么样,用X_train[0].toarray()查看其中某一行的密集数组就够了,不要整体转换。
6.4 数据泄漏问题
我这里说的“泄漏”指的是:测试集的信息在训练阶段被模型看到了。除了前面提到的不要对测试集调用fit_transform之外,还有一种更隐蔽的情况——你在构造停用词表时,看过了测试集的高频词。这虽然影响比fit_transform小,但严格来说也属于泄漏。正确流程是:先把训练集、测试集分开,所有基于统计的判断(比如哪些词频太高需要过滤)只从训练集获取,测试集用自己的词表生成特征就行了。
6.5 jieba自定义词表的需求
如果处理的文本里包含大量专有名词,比如“自然语言处理”“布洛芬”“区块链”,默认词库可能把它们切碎。这时可以用jieba.add_word('自然语言处理'),或者在启动时加载自定义词典。任务一的数据集是新闻,网络新词比较多,我遇到了“王者荣耀”被拆成“王者”和“荣耀”的情况,虽然分类结果没受太大影响,但如果你在做一些更精确的任务,这步一定要重视。
jieba.add_word('王者荣耀') jieba.add_word('人工智能')6.6 sklearn版本API变化
TfidfVectorizer在旧版本中可能没有max_df参数(它很早就有了,但某些超旧版本确实不完整),而get_feature_names这个旧API在sklearn 1.0之后改名成get_feature_names_out。如果你跟着教程写代码报错,先检查你的sklearn版本,用sklearn.__version__打印一下。1.2版本按我文中的写法肯定没问题。
7. 结果分析与经验心得
我这次实验的最终结果供你参考:朴素贝叶斯准确率0.918,逻辑回归0.948,LinearSVC 0.957。SVM在10分类新闻数据集上拿下第一名,而且实跑耗时只比逻辑回归多了不到十秒。这个结果不是偶然——在传统机器学习领域,线性SVM在文本分类上跟逻辑回归确实差距不大,但往往就是能赢一点点。
另一个有价值的发现是:加上bigram(ngram_range=(1,2))之后,SVM准确率提升到0.961,而bigram对逻辑回归的提升只有0.3个百分点左右。这说明SVM在处理组合特征时往往更擅长抓住判别边界。当然,bigram的代价是特征维度变大、训练时间变长,实际项目中要权衡取舍。
如果你想让效果再往上走一步,可以试试轻度加一些规则:比如对“类别特有词”做加权,或者在预处理阶段统计出每个类的高频区分词,直接把它们合成新的特征放进向量里。这个思路已经接近特征工程的进阶玩法了,但任务一阶段做到这里,你的基础已经打得很扎实了。
还有一个小技巧是关于jieba的加载速度。每次运行程序,jieba加载词典都要花几秒到十几秒时间。如果你在调参阶段反复跑脚本,这个时间会非常烦人。我的做法是用pickle把分词结果提前缓存成文件:第一次运行时把所有文本分词存储,之后直接从缓存加载,能省下大量时间。
import pickle, os cache_path = 'cache_tokenized.pkl' if os.path.exists(cache_path): with open(cache_path, 'rb') as f: train_tokenized, test_tokenized = pickle.load(f) else: train_tokenized = [tokenize(t) for t in train_texts] test_tokenized = [tokenize(t) for t in test_texts] with open(cache_path, 'wb') as f: pickle.dump((train_tokenized, test_tokenized), f)这样在后面反复调整向量化参数和模型参数时,分词这步就不需要重跑了,整个实验周期能缩短一半以上。
我自己带新人的时候,最看重的不是模型准确率多高,而是能不能把每个环节的原理讲清楚、能亲手分析一次分类错误案例。任务一的价值恰恰在这里。如果你跑完这篇博客的代码,能自己解释清楚“为什么SVM在这里比朴素贝叶斯好”“bigram到底带来了什么信息”,那你的入门就算真的入了。后面无论去做LSTM还是BERT,底层的数据处理逻辑和评估思路,都是在这个任务里打下的。