尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

PyTorch实战:从传统NLP到神经网络,手把手复现TF-IDF、n-gram、HMM与CRF

PyTorch实战:从传统NLP到神经网络,手把手复现TF-IDF、n-gram、HMM与CRF 1. 为什么还要回头翻传统NLP这本老黄历这几年但凡聊到自然语言处理十个人里有八个张口就是Transformer、预训练、微调、大模型对齐。我自己也在这个浪潮里泡了好几年从最早用循环网络做文本分类到后来全套流程迁到预训练模型上确实感受到了技术迭代带来的效率飞跃。但有个现象挺有意思团队里新来的同学上手就能调预训练模型做情感分析可一旦遇到分词边界对不齐、标签体系混乱、评估指标选错这类“脏活”往往就卡住了。问他们TF-IDF怎么算、条件随机场为什么适合做序列标注、n-gram语言模型怎么平滑很多人只能说出个大概。这就是我想写这篇回顾的出发点。传统NLP不是被淘汰的旧货它是理解现代NLP为什么这么设计的底层坐标系。你只有亲手用PyTorch把词袋模型、TF-IDF、n-gram、隐马尔可夫、条件随机场这些经典方法跑一遍才会真正明白为什么后来的模型要引入注意力、为什么要做位置编码、为什么子词切分能解决未登录词问题。这些“为什么”的答案全都藏在传统方法的局限里。这篇文章面向的是已经会用PyTorch搭网络、但对NLP历史脉络和经典方法只停留在概念层面的开发者。我会带着你把传统NLP的几个核心模块用PyTorch重新实现一遍不是简单调库而是把矩阵运算、概率计算、损失函数这些细节摊开来看。读完之后你手里会有一套可以直接复现的代码框架更重要的是你会建立起一条从“统计方法”到“神经网络方法”的完整认知链路。全文涉及的所有代码都基于PyTorch不依赖额外的NLP高级封装库方便你理解每一步到底在算什么。2. 传统NLP的整体设计思路与方案选型2.1 从规则到统计传统NLP的三层演进逻辑传统NLP的发展大致可以分成三个阶段每个阶段解决的核心矛盾不一样。最早是规则驱动阶段靠语言学家手写语法规则和词典典型代表是早期的机器翻译系统和分词工具。这个阶段的问题是规则爆炸语言现象太灵活你写一千条规则第一万条例外就冒出来了。第二个阶段是统计学习阶段核心思想是把语言问题转化成概率问题用大规模语料统计出规律。隐马尔可夫模型、条件随机场、最大熵模型都是这个时期的产物。第三个阶段是神经网络阶段用分布式表示和深层网络自动学习特征循环网络和卷积网络在NLP里的应用就是标志。我之所以要强调这个演进逻辑是因为很多初学者直接跳到第三阶段结果对“特征工程”这个词完全没有体感。传统NLP里特征是你手动设计的比如做情感分类时你会提取否定词、程度副词、标点符号这些特征。到了神经网络阶段特征变成模型自己学的但特征工程的思想没有消失它只是被封装进了网络结构里。你理解了传统方法怎么设计特征再看注意力机制就会明白那本质上是一种自动加权特征组合。2.2 为什么选PyTorch来复现传统方法有人可能会问传统NLP方法用NumPy或者scikit-learn不就行了吗为什么非要用PyTorch我的理由有三个。第一统一计算框架。你最终是要过渡到神经网络的如果传统方法用一套工具、神经网络用另一套中间会有割裂感。用PyTorch从头实现TF-IDF和条件随机场你能看到张量运算在两种范式下是一致的。第二自动求导的便利。传统方法里有些参数也需要优化比如条件随机场的转移矩阵用PyTorch的自动求导可以省去手动推导梯度的麻烦。第三GPU加速。当你的语料规模上去之后矩阵运算在GPU上的速度优势很明显尤其是计算词共现矩阵或者做大规模矩阵分解的时候。当然用PyTorch实现传统方法也有代价就是代码量会比调库多不少。但我觉得这个代价是值得的因为你是在学习不是在交付生产系统。学习阶段把轮子拆开看一遍比直接开车更有价值。2.3 核心模块拆解与依赖关系我把传统NLP的核心内容拆成四个模块它们之间有明确的依赖关系。最底层是文本表示包括one-hot、词袋、TF-IDF、n-gram这些方法解决的是“怎么把文字变成数字”的问题。往上一层是序列标注包括隐马尔可夫模型和条件随机场解决的是“给每个词打标签”的问题比如词性标注、命名实体识别。再往上是分类模型包括朴素贝叶斯、最大熵、支持向量机解决的是“给整段文本打标签”的问题比如情感分析、垃圾邮件识别。最上层是语言模型包括n-gram语言模型和平滑技术解决的是“计算一个句子出现的概率”的问题。这四个模块不是孤立的。文本表示是后面所有任务的基础序列标注和分类模型共享很多特征提取的思路语言模型则贯穿始终因为很多任务最终都可以转化成概率计算。我在下面的章节里会按照这个依赖关系从底层往上层逐个实现。3. 核心细节解析与实操要点3.1 文本表示从one-hot到TF-IDF的矩阵运算本质文本表示是NLP的第一步也是最容易被忽视的一步。很多人觉得把词映射成整数就完事了但实际上表示方式直接决定了模型能学到什么。我先说one-hot假设词表大小是V每个词表示成一个V维向量只有对应位置是1其余是0。这种表示的问题很明显向量维度等于词表大小语料稍微大一点就爆炸而且任意两个词的向量内积都是0完全丢失了词与词之间的语义关系。词袋模型是在one-hot基础上做的改进它不再关心词序只统计每个词出现的次数。你可以把一篇文档表示成一个V维向量每一维是对应词的出现次数。这个表示已经能用于分类任务了但有个致命问题高频词会主导向量比如“的”“是”“在”这些词几乎每篇文档都有它们的计数很大但对区分文档主题几乎没有贡献。TF-IDF就是来解决这个问题的。TF是词频IDF是逆文档频率。IDF的计算方式是IDF(w) log(N / (df(w) 1))其中N是文档总数df(w)是包含词w的文档数。加1是为了防止分母为零。TF-IDF就是TF乘以IDF。这个公式的直觉是如果一个词在很多文档里都出现说明它很普通IDF就小如果一个词只在少数文档里出现说明它有区分度IDF就大。用PyTorch实现TF-IDF核心是构建词频矩阵和文档频率向量。我一般会先把所有文档分词构建词表然后遍历文档统计词频。这里有个细节词表构建时要不要保留低频词。我的经验是词频低于2的词直接丢掉因为它们在训练集里出现次数太少学不到有效信息反而增加维度。但如果是做关键词提取低频词可能恰恰是关键词这时候就不能丢。import torch from collections import Counter def build_vocab(docs, min_freq2): counter Counter() for doc in docs: counter.update(doc) vocab {word: idx for idx, (word, freq) in enumerate(counter.items()) if freq min_freq} return vocab def compute_tf_idf(docs, vocab): N len(docs) V len(vocab) tf torch.zeros(N, V) df torch.zeros(V) for i, doc in enumerate(docs): word_counts Counter(doc) for word, count in word_counts.items(): if word in vocab: j vocab[word] tf[i, j] count df[j] 1 idf torch.log(torch.tensor(N, dtypetorch.float) / (df 1)) tf_idf tf * idf.unsqueeze(0) return tf_idf这段代码里有个容易踩的坑df的计算是在循环里累加的如果同一篇文档里同一个词出现多次df只应该加一次。上面的写法用word_counts去重了所以是对的。如果你直接用词列表遍历就会重复计数。3.2 n-gram语言模型概率计算与平滑技术的工程实现n-gram语言模型的核心思想是一个词出现的概率只依赖于它前面的n-1个词。比如bigram模型里P(今天|天气)表示在“天气”后面出现“今天”的概率。整个句子的概率就是所有条件概率的乘积。这个模型最大的问题是数据稀疏。如果某个n-gram在训练语料里没出现过它的概率就是0整个句子的概率就变成0了。为了解决这个问题需要做平滑。最简单的平滑是加一平滑把所有n-gram的计数都加1。但加一平滑对高频n-gram影响太大后来出现了古德-图灵估计、Kneser-Ney平滑等更精细的方法。我在实际项目里最常用的是插值平滑就是把不同阶的n-gram概率加权组合P(w_n | w_{n-1}) λ * P_ML(w_n | w_{n-1}) (1-λ) * P(w_n)其中P_ML是最大似然估计P(w_n)是unigram概率。λ一般取0.7到0.9之间。这个方法的直觉是当高阶n-gram统计不可靠时退回到低阶统计。用PyTorch实现n-gram模型关键是把条件概率存成矩阵。对于bigram可以存成一个V×V的矩阵第i行第j列表示P(w_j | w_i)。计算的时候用对数概率避免下溢def train_bigram(docs, vocab, smoothing1.0): V len(vocab) counts torch.zeros(V, V) for doc in docs: for i in range(len(doc) - 1): if doc[i] in vocab and doc[i1] in vocab: counts[vocab[doc[i]], vocab[doc[i1]]] 1 counts smoothing row_sums counts.sum(dim1, keepdimTrue) probs counts / row_sums log_probs torch.log(probs) return log_probs这里有个实操心得平滑系数不要设太大。我试过把smoothing设成10结果所有条件概率都趋近于均匀分布模型完全学不到东西。一般设成0.1到1.0之间比较合理。另外计算句子概率时一定要用对数概率相加不然长句子的概率会小到浮点数下溢。3.3 隐马尔可夫模型前向算法与维特比解码的PyTorch实现隐马尔可夫模型是传统序列标注的经典方法它假设系统有一个隐藏状态序列和一个观测序列隐藏状态之间满足马尔可夫性质观测只依赖于当前隐藏状态。在NLP里隐藏状态就是词性标签观测就是词本身。HMM有三个核心问题评估问题、解码问题、学习问题。评估问题用前向算法计算观测序列的概率解码问题用维特比算法找最可能的隐藏状态序列学习问题用Baum-Welch算法估计参数。我重点说前向算法和维特比算法因为这两个在实际应用里最常用。前向算法的核心是递推计算α[t][i]表示在时刻t处于状态i并观测到前t个观测的概率。递推公式是α[t][j] Σ_i α[t-1][i] * A[i][j] * B[j][o_t]其中A是状态转移矩阵B是发射矩阵。用PyTorch实现的时候可以把α存成一个T×S的矩阵每一步用矩阵乘法更新def forward_algorithm(obs, A, B, pi): T len(obs) S A.shape[0] alpha torch.zeros(T, S) alpha[0] pi * B[:, obs[0]] for t in range(1, T): alpha[t] (alpha[t-1] A) * B[:, obs[t]] return alpha维特比算法和前向算法结构很像只是把求和换成求最大值同时记录回溯路径。这里有个细节数值下溢。α的值会随着T增大越来越小最后变成0。解决办法是在每一步做归一化记录归一化因子最后再加回来。我在代码里一般用对数域计算把乘法变成加法避免下溢。HMM的局限性也很明显它假设观测之间相互独立且只依赖于当前状态。这个假设在NLP里太强了因为词与词之间是有依赖的。条件随机场就是来打破这个假设的。3.4 条件随机场从特征函数到CRF层的完整推导条件随机场是传统序列标注的巅峰它直接建模P(标签序列|观测序列)而不是像HMM那样建模联合概率。CRF的核心是特征函数每个特征函数f(y_{t-1}, y_t, x, t)衡量在位置t、标签从y_{t-1}转移到y_t、观测为x时的“得分”。所有特征函数的加权和就是整个标签序列的得分score(y, x) Σ_t Σ_k w_k * f_k(y_{t-1}, y_t, x, t)然后通过softmax归一化得到概率。训练时最大化对数似然预测时用维特比算法找得分最高的标签序列。用PyTorch实现CRF层关键是定义转移矩阵和发射矩阵。转移矩阵是S×S的发射矩阵是T×S的由前面的神经网络层输出。前向算法计算配分函数Z用对数域的前向算法def crf_forward(emissions, transitions, mask): T, S emissions.shape alpha torch.zeros(T, S) alpha[0] emissions[0] for t in range(1, T): alpha[t] torch.logsumexp(alpha[t-1].unsqueeze(1) transitions emissions[t].unsqueeze(0), dim0) return torch.logsumexp(alpha[-1], dim0)这段代码里logsumexp是数值稳定的求和指数函数PyTorch自带。mask用来处理变长序列padding位置不参与计算。CRF的损失函数是负对数似然loss -score(y, x) log_Z(x)其中score(y, x)是真实标签序列的得分log_Z是配分函数的对数。这个损失函数保证了正确标签的得分相对其他标签越来越高。我在实际项目里用CRF做命名实体识别效果比HMM好很多因为CRF可以灵活设计特征函数比如“当前词是否以大写字母开头”“前一个词的词性是什么”这些特征都可以加进去。但CRF的训练速度比HMM慢因为前向算法是O(T * S^2)的复杂度标签数量多的时候会很慢。4. 实操过程与核心环节实现4.1 数据准备与预处理流水线搭建我拿一个模拟的新闻分类数据集来演示整个流程。数据集包含若干篇新闻文本每篇有一个类别标签比如“体育”“财经”“科技”。预处理流水线包括分词、去停用词、构建词表、序列截断和填充。分词我用的是基于词典的最大匹配法虽然简单但能说明问题。实际项目里可以用更成熟的分词工具但为了不引入额外依赖我这里手写一个def max_match_segment(text, vocab, max_len5): result [] i 0 while i len(text): matched False for length in range(min(max_len, len(text) - i), 0, -1): word text[i:ilength] if word in vocab: result.append(word) i length matched True break if not matched: result.append(text[i]) i 1 return result这个算法的逻辑是从当前位置开始尝试匹配最长的词匹配到就切分匹配不到就单字成词。最大匹配法的缺点是歧义切分比如“乒乓球拍卖完了”可以切成“乒乓球/拍卖/完了”或者“乒乓球拍/卖/完了”。解决办法是结合双向最大匹配或者用统计模型做序列标注。去停用词我一般会维护一个停用词表把“的”“了”“在”这些词去掉。但要注意停用词表不是固定的做情感分析时“不”不能去掉做关键词提取时“的”去掉反而更好。所以停用词表要根据任务调整。序列截断和填充是为了批处理。我一般取训练集里句子长度的95分位数作为最大长度超过的截断不足的用0填充。填充的位置在计算损失时要mask掉不然模型会学到填充符的规律。4.2 用PyTorch实现一个完整的文本分类流程我用词袋模型加一个简单的全连接网络来做文本分类。虽然这个方案在现在看来很原始但它能让你看清文本分类的每一个环节。第一步构建词表并计算TF-IDF矩阵。这个前面已经讲过了。第二步把TF-IDF矩阵转成稀疏张量因为大部分元素是0。PyTorch支持稀疏张量但全连接层对稀疏张量的支持不太好所以我一般会先转成稠密张量或者用嵌入层加池化的方式。这里我选择用嵌入层加平均池化因为这种方式更接近后来的神经网络方法。每个词映射成一个D维向量然后对整篇文档的所有词向量取平均得到文档表示。这个表示再过几层全连接最后输出类别概率。class TextClassifier(torch.nn.Module): def __init__(self, vocab_size, embed_dim, num_classes): super().__init__() self.embedding torch.nn.Embedding(vocab_size, embed_dim, padding_idx0) self.fc1 torch.nn.Linear(embed_dim, 128) self.fc2 torch.nn.Linear(128, num_classes) self.dropout torch.nn.Dropout(0.3) def forward(self, x): mask (x ! 0).float().unsqueeze(-1) embedded self.embedding(x) * mask pooled embedded.sum(dim1) / mask.sum(dim1).clamp(min1) hidden torch.relu(self.fc1(pooled)) hidden self.dropout(hidden) logits self.fc2(hidden) return logits这段代码里有个细节mask的使用。因为填充符的嵌入向量也会参与平均池化如果不mask掉填充符会影响文档表示。mask.sum(dim1).clamp(min1)是为了防止除以零。训练的时候用交叉熵损失优化器用Adam学习率设成1e-3。我一般会跑10到20个epoch每个epoch后在验证集上评估准确率。这里有个经验文本分类任务很容易过拟合因为词袋表示维度高、样本少。解决办法是加dropout、加L2正则、或者用早停。4.3 序列标注任务的CRF层集成与训练序列标注任务我拿词性标注来演示。数据格式是每个词对应一个标签比如“我/代词 爱/动词 北京/名词”。标签体系我用的是简化版的只有名词、动词、形容词、代词、其他五类。模型结构是嵌入层加双向LSTM加CRF层。双向LSTM负责提取上下文特征CRF层负责建模标签之间的转移约束。比如“动词”后面跟“名词”的概率比跟“动词”的概率大CRF能学到这种约束。class BiLSTM_CRF(torch.nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_tags): super().__init__() self.embedding torch.nn.Embedding(vocab_size, embed_dim, padding_idx0) self.lstm torch.nn.LSTM(embed_dim, hidden_dim, bidirectionalTrue, batch_firstTrue) self.fc torch.nn.Linear(hidden_dim * 2, num_tags) self.transitions torch.nn.Parameter(torch.randn(num_tags, num_tags)) self.start_trans torch.nn.Parameter(torch.randn(num_tags)) self.end_trans torch.nn.Parameter(torch.randn(num_tags)) def forward(self, x, mask): embedded self.embedding(x) lstm_out, _ self.lstm(embedded) emissions self.fc(lstm_out) return emissionsCRF的损失函数计算需要前向算法和得分函数。得分函数计算真实标签序列的得分def compute_score(emissions, tags, mask, transitions, start_trans, end_trans): batch_size, seq_len, num_tags emissions.shape score start_trans[tags[:, 0]] emissions[:, 0, tags[:, 0]] for t in range(1, seq_len): score transitions[tags[:, t-1], tags[:, t]] * mask[:, t] score emissions[:, t, tags[:, t]] * mask[:, t] score end_trans[tags[:, -1]] * mask[:, -1] return score前向算法计算配分函数然后损失就是log_Z减去score。训练的时候用Adam优化器学习率设成5e-4。我实测下来双向LSTM加CRF在词性标注任务上比单独用LSTM准确率高3到5个百分点主要提升在标签转移的合理性上。4.4 评估指标选择与结果分析传统NLP任务的评估指标和现代NLP基本一致分类任务用准确率、精确率、召回率、F1值序列标注任务用实体级别的精确率、召回率、F1值。但有个细节容易被忽视序列标注的评估要按实体级别算不能按标签级别算。比如命名实体识别里一个实体由多个词组成只有所有词的标签都预测对了这个实体才算预测对。我一般会写一个评估函数把预测的标签序列和真实标签序列都转成实体集合然后计算交集和并集。这个函数在调试模型的时候特别有用因为你能看到模型到底在哪些实体上犯了错。结果分析的时候我会把错误样本打印出来看。常见的错误类型有边界错误实体多包含或少包含一个词、类型错误把“人名”预测成“地名”、漏标完全没识别出实体。针对不同的错误类型调整策略也不一样。边界错误一般是上下文特征不够可以加更多特征或者用更大的模型类型错误一般是标签体系有歧义需要重新定义标签漏标一般是训练数据里这类实体太少需要做数据增强。5. 常见问题与排查技巧实录5.1 词表构建与未登录词处理的坑未登录词是传统NLP最头疼的问题之一。词表里没有的词one-hot表示全是0TF-IDF权重也是0模型完全学不到东西。我踩过的坑是词表构建时只用了训练集结果验证集和测试集里大量词不在词表里。解决办法是构建词表时把训练集、验证集、测试集的词都统计进去但只保留训练集里出现次数超过阈值的词。这样验证集和测试集里的高频词也能被覆盖。另一个坑是数字和标点的处理。原始文本里的数字和标点如果不做归一化词表会爆炸。我一般会把所有数字替换成特殊符号把连续标点合并成一个。这样既能保留“有数字”这个信息又不会让词表太大。对于确实无法覆盖的未登录词可以用字符级别的表示作为补充。比如一个词不在词表里就把它拆成字符用字符的嵌入向量平均作为这个词的表示。这个方法在中文里特别有效因为中文的字符本身就有语义。5.2 序列标注中标签不平衡的应对策略标签不平衡在序列标注任务里非常常见。比如命名实体识别里“O”标签非实体通常占90%以上其他实体标签各占几个百分点。如果直接训练模型会倾向于全部预测成“O”因为这样准确率也有90%。我试过几种解决办法。第一种是重采样对包含实体的句子过采样对全是“O”的句子欠采样。但这样会改变数据分布模型可能会过拟合到实体句子上。第二种是损失加权给稀有标签更高的损失权重。PyTorch的交叉熵损失支持weight参数可以传入一个权重向量。第三种是focal loss降低易分类样本的权重让模型关注难分类样本。我实测下来损失加权最稳定重采样容易过拟合focal loss调参比较麻烦。权重一般设成类别频率的倒数然后归一化到和为1。但要注意权重不要设得太极端不然模型会疯狂预测稀有标签导致精确率暴跌。5.3 模型过拟合与欠拟合的快速诊断过拟合和欠拟合是训练过程中最常见的问题。我的诊断方法是看训练损失和验证损失的曲线。如果训练损失一直下降验证损失先下降后上升那就是过拟合。如果训练损失和验证损失都居高不下那就是欠拟合。过拟合的解决办法有加dropout、加L2正则、减小模型规模、增加训练数据、早停。我一般会先加dropout如果效果不明显再加L2正则。早停是最省事的办法但需要留出验证集。欠拟合的解决办法有增大模型规模、增加训练轮数、减小正则化强度、检查特征是否有效。我遇到过一次欠拟合排查了半天发现是学习率设得太小模型根本没学动。后来把学习率从1e-5调到1e-3损失立刻就降下来了。还有一个容易被忽视的问题是数据泄露。如果你在预处理阶段用了全局统计量比如全局的TF-IDF然后又在训练集和验证集上分别计算就会导致验证集的信息泄露到训练过程里。解决办法是只在训练集上计算统计量然后应用到验证集和测试集。5.4 常见问题速查表问题现象可能原因排查方法解决办法训练损失不下降学习率太小、模型太简单、特征无效检查学习率、打印梯度范数调大学习率、增加模型层数、检查特征验证损失上升过拟合对比训练和验证损失曲线加dropout、加正则、早停预测全是同一类标签不平衡、损失函数有问题打印预测分布和真实分布损失加权、重采样、检查损失函数序列标注边界错误多上下文特征不足打印错误样本加双向LSTM、加更多特征未登录词多词表太小、预处理有问题统计未登录词比例扩大词表、加字符级表示训练速度慢批大小太小、模型太大检查GPU利用率增大批大小、减小模型、用混合精度这个表是我自己踩坑总结出来的不一定全面但覆盖了大部分常见情况。排查的时候建议按顺序来先检查数据和标签再检查模型和损失最后检查优化器和超参数。大部分问题其实出在数据上而不是模型上。6. 从传统方法到神经网络我的迁移经验6.1 哪些传统方法值得保留到现代流程里虽然现在预训练模型一统天下但有些传统方法我依然会在现代流程里保留。第一个是TF-IDF做关键词提取。预训练模型虽然能做语义相似度但做关键词提取时TF-IDF的区分度反而更好因为它直接基于统计不受语义漂移影响。我一般会用TF-IDF先筛出候选关键词再用预训练模型做语义过滤。第二个是n-gram做语言模型评估。虽然现在评估生成质量主要用BLEU、ROUGE这些指标但n-gram语言模型的困惑度依然是一个快速诊断工具。如果模型生成的句子困惑度很高说明语言流畅度有问题。第三个是CRF做后处理。预训练模型做序列标注时有时会输出不合法的标签序列比如“B-人名”后面直接跟“B-地名”。这时候可以用CRF的转移矩阵做约束把不合法的序列过滤掉。这个方法在工业界很常用能显著提升实体识别的准确率。6.2 传统NLP的局限如何催生了注意力机制传统NLP最大的局限是固定窗口。n-gram只看前n-1个词HMM只看前一个状态CRF虽然能看整个序列但特征函数是手动设计的无法自动发现长距离依赖。这个局限直接催生了注意力机制。注意力机制的核心思想是每个词在生成表示时可以“关注”序列里的任意其他词关注程度由相似度决定。这相当于把n-gram的固定窗口变成了动态窗口窗口大小和位置都由模型自己学。你理解了n-gram的局限再看注意力机制就会明白它本质上是在解决“如何自动发现长距离依赖”这个问题。另一个催生注意力机制的因素是并行计算。循环网络必须按时间步串行计算无法并行。注意力机制可以一次性计算所有位置的关联大大提升了训练效率。这个优势在大规模预训练模型里体现得淋漓尽致。6.3 给初学者的学习路径建议如果你刚开始学NLP我建议按这个顺序来先花一周时间把传统方法跑一遍重点是理解文本表示、序列标注、分类模型这三个模块。不用追求SOTA效果能跑通、能解释每一步在算什么就行。然后花两周时间学循环网络和卷积网络在NLP里的应用重点是理解嵌入层、池化、序列建模。最后再进入预训练模型这时候你会发现很多设计都是顺理成章的。我见过太多人直接跳到预训练模型结果调参全靠试出了问题也不知道怎么排查。传统NLP的知识就是你的排查工具箱当你遇到分词问题、标签不平衡问题、评估指标问题时这些知识能帮你快速定位原因。学习过程中我建议你自己写代码实现一遍不要只调库。调库能让你快速出结果但自己写代码能让你理解每一步的细节。比如你调sklearn的TF-IDF可能永远不会去想IDF的分母为什么要加1但你自己实现一遍就会去查资料、去推导理解就深了。最后再分享一个小技巧用传统方法做baseline。每次接到一个新的NLP任务先用TF-IDF加逻辑回归跑一个baseline看看准确率是多少。然后再上预训练模型对比提升幅度。如果提升不明显说明任务本身可能不需要复杂模型或者数据有问题。这个习惯能帮你节省大量时间避免过度工程化。
返回列表