
1. 项目概述与核心价值我最早接触NNLMNeural Network Language Model神经网络语言模型是在做文本情感分析项目的时候。当时用传统的n-gram统计模型做新闻语料的分类遇到一个很头疼的问题语料里出现没见过的词组合模型就直接宕机——概率算出来是零整个句子被判为无效输入。后来换了NNLM这个问题迎刃而解因为神经网络的核心优势就在于泛化能力它不需要见过完全一样的词序列就能根据词的语义相似度给出合理预测。NNLM最早由Bengio等人在2003年提出它的核心思想非常朴素把语言建模问题转换成一个神经网络训练问题让模型在预测下一个词的同时学会把词映射到低维稠密的向量空间中。这个映射矩阵——也就是后来广为人知的词向量Word Embedding——反而是这项研究最值钱的副产品。今天大家熟悉的Word2Vec、GloVe本质上都是沿着NNLM这条思路走出来的分支。这篇内容适合三类人看一是刚入门NLP、想弄懂词向量从哪来的新手二是需要用语言模型做文本生成或下游任务、但不想直接调包想要理解原理的开发者三是在做技术选型时想搞清楚传统统计方法和神经网络方法本质区别的从业者。文章会从原理讲到代码实现再讲训练过程中的坑最后聊一聊NNLM如何演化成今天的预训练模型体系保证是网上不太容易找到的完整经验贴。2. 为什么是神经网络从统计语言模型到NNLM的设计逻辑2.1 n-gram模型的三个致命伤在NNLM出现之前主流的语言模型是n-gram统计模型。它的做法很简单统计语料中连续n个词出现的频次再用条件概率计算下一个词出现的概率。以bigram二元模型为例核心公式是P(w_t | w_{t-1}) Count(w_{t-1}, w_t) / Count(w_{t-1})这个公式看起来简洁直白但实际用起来问题很大我踩过太多次坑了。第一个问题是数据稀疏。假设你有一个10万词规模的词表bigram组合理论上就有100亿种可能但训练语料往往只有几千万词绝大部分组合压根没出现过。这意味着模型只要遇到稍微生僻一点的语言表达概率直接归零。为了解决这个问题传统做法是加平滑Smoothing比如Katz平滑、Kneser-Ney平滑但平滑本质上是在猜加了平滑之后概率分布会被扭曲效果并不理想。第二个问题是无法捕捉词之间的相似性。n-gram模型里每个词都是独立的离散符号比如猫和狗在模型中没有任何关联。可实际上它们在语义上是相似的这个信息对语言预测极其重要。比如训练语料里有我昨天看见一只猫跑过马路模型只是机械地统计了这串词出现过但当输入变成我昨天看见一只狗跑过马路时n-gram模型并不认为这句话与前面那句话有什么关系概率完全由频次决定。这种建模方式天然割裂了词与词之间的语义联系。第三个问题是维度灾难。n-gram模型本质上是在做一个超高维的离散空间统计随着n的增大组合数量呈指数级增长。我们通常最多能用到5-gram再往上参数规模爆炸而且大部分参数都是无意义的零值计算开销还巨大。我当时用4-gram做实验光存储统计表就占了几十GB内存这还是一个规模不大的语料。2.2 NNLM对三个问题的逐一击破NNLM的设计思路是从根源上绕开这三个问题。它的关键洞察是不要直接在词序列的离散组合上建模而是先学习一个从词到稠密向量的映射再在这个连续向量空间里做预测。具体来说NNLM通过一个三层的神经网络同时学习两件事词向量表示和语言模型的概率预测。模型在训练过程中输入是前n-1个词输出是下一个词的概率分布。但在预测概率的同时输入层到投影层之间的权重矩阵恰恰就是我们要的词向量表。神经网络在学习语言规律的同时顺便学到了每个词的语义表征。这个设计妙在哪里妙在它把数据稀疏问题转化成了连续空间泛化问题。举个例子训练语料里出现过我今天坐飞机去北京没出现过我今天坐飞机去上海但模型已经学到了北京和上海在向量空间里的位置非常接近因此预测概率的时候模型会合理地给上海分配一个不低的概率。这在n-gram模型里是完全做不到的。同时因为词被表示为低维稠密向量典型维度是50到200所有参数的规模只和词表大小、向量维度、隐藏层节点数相关完全摆脱了n-gram那种随上下文长度指数爆炸的问题。我当时的感受是从n-gram切到NNLM就像从一个堆满了离散积木的房间走进了一个连续流动的数字水域整个空间形态都变了原本那些死角全部被打通。2.3 为什么说word embedding是副产品却是最大财富NNLM的损失函数是用来优化语言模型预测准确率的训练完成后输入层到投影层的权重矩阵W维度为|V| x m其中|V|是词表大小m是词向量维度就是所有词语的向量表示。这个矩阵的每一行对应一个词的向量。之所以称它为副产品是因为设计者本来的目标是预测下一个词并非显式地构造词义表示。但是训练过程中为了把预测做得更准模型不得不把语义上相近的词映射到接近的向量位置上。这是神经网络的附带收获也成为了后来深度学习在NLP领域爆发的关键火种。我做一个直观的类比帮助理解假设你是一个新员工本来任务是根据前几位同事的做事风格预测下一位同事的反应。经过一段时间的工作你在心里不自觉地形成了一张同事关系图谱——谁和谁是一派的、谁和谁风格接近虽然这个图谱不是领导要求你做的但有了它你的预测准确率大幅提升。NNLM训出来的词向量就是这个同事关系图谱。这个副产品的意义后来被放大了无数倍。2013年Mikolov等人提出的Word2Vec本质上是把NNLM结构简化专门用来训练词向量去掉语言模型预测的部分训练效率大幅提升。再后来的GloVe、fastText也都是基于这个思路的变体。可以说不搞懂NNLM的word embedding是怎么来的你就很难真正理解为什么词向量能捕捉语义关系为什么国王-男人女人≈女王这类经典运算能成立。3. NNLM网络结构与核心原理拆解3.1 模型的整体层级设计经典NNLM的结构分为三层输入层、隐藏层和输出层。我用PyTorch从零实现的时候具体结构是这么设计的。输入层接收前n-1个词的one-hot向量然后通过一个共享权重的Embedding矩阵将其映射为稠密向量再把所有词向量拼接成一个长向量。假设n-13用前3个词预测第4个词词向量维度m128那么拼接后的输入向量维度就是3x128384。隐藏层拼接后的向量经过一个全连接层激活函数通常用tanh。隐藏层节点数h是一个关键超参数一般设置在128到1024之间。如果隐藏层太窄模型没有足够容量去学习复杂语义关系太宽则容易过拟合训练速度也会明显下降。我在实际项目中常用256或512再根据验证集表现微调。输出层将隐藏层的输出再经过一次全连接映射回词表大小的维度然后用softmax归一化得到词表上所有词的概率分布。输出层的维度就是|V|如果词表是5万那么这个矩阵就是256x50000这也是整个模型参数量的主要来源。用数学公式表达整个前向传播过程输入词序列x (w_{t-n1}, ..., w_{t-1})Embedding映射e_i C(w_i)拼接后得到 x_concat [e_{t-n1}; ...; e_{t-1}]隐藏层计算h tanh(W_h · x_concat b_h)输出层计算y W_o · h b_o概率输出P(w_t | context) softmax(y)这里C就是Embedding矩阵也就是我们心心念念的词向量表。W_h和W_o分别是隐藏层和输出层的权重矩阵。3.2 训练目标交叉熵损失与softmax的计算细节NNLM的训练目标是最大化训练语料中所有词序列出现的概率等价于最小化交叉熵损失。对于每一个训练样本前n-1个词和真实的下一个词损失函数是L -log P(w_t | w_{t-n1}, ..., w_{t-1})这个公式的含义是我们希望模型给真实下一个词分配的概率尽量大。如果模型预测得很好给真实词分配了接近1的概率那么-log(1)0损失趋近于零如果模型给了很低概率损失就非常大。输出层的softmax计算是整个模型的计算瓶颈。因为softmax需要对词表中所有词计算归一化词表5万就需要算5万次指数运算。训练一次要跑几百万个样本每次都做5万维的softmax那计算量相当恐怖。我记得第一次用完整词表训练的时候一个epoch跑了我快二十个小时。后来学乖了采用了负采样或者层次softmax来优化。负采样的思路是不需要对所有词计算softmax只需要让真实词的概率尽量高同时随机抽几个负样本词的概率尽量低计算量直接降到原来的几十分之一。层次softmax则是用霍夫曼树把输出层的softmax拆成多个二分类理论上复杂度从O(|V|)降到O(log|V|)。这两个技巧在实现NNLM的时候几乎是必用的否则实验周期会拖到让人怀疑人生。3.3 上下文窗口n和向量维度m的选择经验NNLM有两个关键超参数需要特别留意上下文窗口大小n和词向量维度m。上下文窗口n决定了模型看多长的历史信息。n太小模型能利用的信息不够预测准确率上不去n太大输入维度变大参数增多训练变慢而且过远的词对预测下一个词的贡献往往很小反而带来噪声。我踩过的坑是从bigram直接跳到5-gram结果训练时间翻了一倍效果却只提升了一丁点。综合经验来看小数据集用3-5比较合适大规模语料可以尝试5-7。不过要注意n3错过的长距离依赖NNLM本身也解决不了——这就是后来出现LSTM和Transformer的原因。词向量维度m则需要权衡表达能力和计算开销。m太小比如小于50语义信息装不下词的区分度不够m太大比如超过500不仅训练慢还会引入过多噪声。如果语料足够大高维向量能取得更好的效果但如果语料只有几百万词规模300维和100维的效果差距并不大。我在英文语料上用128维、中文语料上用256维都取得过不错的效果。有一个值得记住的直觉判断词向量维度的数量级应该和语料的复杂度匹配语料越丰富、领域跨度越大需要的维度就越高。4. 从零实现NNLM完整实操与关键代码解读4.1 数据预处理构建词表与生成训练样本NNLM训练的第一步是把原始文本转换成模型能吃的数据格式。我以一段新闻语料为例走一遍完整的预处理流程。首先是分词和建词表。英文直接用空格切分即可中文需要先分词我常用的方案是jieba分词。然后统计词频过滤掉出现次数小于5的词语用特殊的UNK标记代替。过滤低频词是个关键操作否则词表会异常庞大且大量低频词在训练中只出现一两次学不到有意义的向量纯粹是浪费计算资源。接下来把语料切分成连续的词序列。比如原始文本我今天坐高铁去上海参加会议设置n4意思是用前3个词预测第4个词那么生成的训练样本就是(我今天坐, 高) 注意这里的高要改为高铁分词后是(我,今天,坐,高铁,去,上海,参加,会议)(我,今天,坐) - 高铁(今天,坐,高铁) - 去(坐,高铁,去) - 上海通过滑动窗口遍历整个语料就能生成大量的训练样本。我在处理的时候习惯用一个滑动窗口函数步长为1这样可以最大程度地利用语料中的共现信息。4.2 模型定义的PyTorch实现模型定义是NNLM的核心环节。我用PyTorch实现了一个精简版完整代码结构如下import torch import torch.nn as nn import torch.nn.functional as F class NNLM(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_steps): super(NNLM, self).__init__() # 词嵌入层vocab_size x embed_dim 的矩阵 self.embed nn.Embedding(vocab_size, embed_dim) # 隐藏层输入是拼接后的词向量 (num_steps * embed_dim) self.hidden nn.Linear(num_steps * embed_dim, hidden_dim) # 输出层映射回词表大小 self.output nn.Linear(hidden_dim, vocab_size) def forward(self, x): # x: (batch_size, num_steps) embed_out self.embed(x) # (batch_size, num_steps, embed_dim) embed_flat embed_out.view(x.size(0), -1) # 拼接所有词向量 hidden_out torch.tanh(self.hidden(embed_flat)) logits self.output(hidden_out) return logits整个模型代码不到30行但每个部分都值得仔细说说。nn.Embedding(vocab_size, embed_dim)这一行就是词向量表的实现它本质上是一个可训练的查找表。输入是词的索引整数输出是对应的词向量。训练过程中这个表的每一行都会根据梯度不断更新最终形成语义上有意义的向量表示。nn.Linear(num_steps * embed_dim, hidden_dim)是隐藏层。之所以输入维度是num_steps乘以embed_dim是因为我们要把前n-1个词的词向量首尾拼接成一个长向量。这里有一个细节拼接的顺序是有讲究的必须保持词在句子中的位置顺序不能打乱否则模型会丢失词序信息。比如我打你和你打我如果被拼成一样的向量这模型就废了。torch.tanh激活函数的选择也有讲究。早期NNLM论文里用的是tanh而不是ReLU因为tanh的输出范围是(-1,1)在一定程度上能限制梯度爆炸但更重要的是它在零附近有一个比较大的梯度这对于输出层的稠密softmax分布是有利的。我在实验中也对比过ReLU在词向量质量上tanh确实略胜一筹不过训练速度ReLU会快一点感兴趣的读者可以自己对比。4.3 训练循环批次采样、损失计算和优化器选择训练循环是另一个容易踩坑的地方。我直接把核心训练逻辑贴出来然后逐段解释def train(model, train_loader, epochs10, lr0.01): model.train() optimizer torch.optim.Adam(model.parameters(), lrlr) criterion nn.CrossEntropyLoss() for epoch in range(epochs): total_loss 0 for inputs, targets in train_loader: optimizer.zero_grad() logits model(inputs) loss criterion(logits.view(-1, logits.size(-1)), targets) loss.backward() optimizer.step() total_loss loss.item() avg_loss total_loss / len(train_loader) print(fEpoch {epoch1}, Loss: {avg_loss:.4f})这里的batch_size我推荐设成256。太小了训练不稳定梯度更新方向噪声大太大了虽然训练速度快但对内存要求高而且容易陷入局部最优。lr初始值设在0.001到0.01之间比较稳妥。我在项目里会用Adam优化器加一个简单的学习率衰减策略每5个epoch把学习率乘0.5训练后期可以让模型更稳定地收敛。一个重要的训练细节是梯度的剪裁gradient clipping。NNLM虽然结构不深但在某些情况下依旧会出现梯度爆炸特别是在语料中存在超长句时。我的习惯是对梯度范数设一个上限比如5.0超过就缩放回去。别看这一行代码它能避免很多训练崩溃的问题。torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0)4.4 模型评估除了loss还要看什么训练过程中不能只盯着loss看单纯的loss下降并不代表模型学到了高质量的语义表示。我一般会做三个维度的验证第一个是词向量相似度检查。训练完成后随便抽几个词找它的最近邻。比如北京的最近邻应该是上海、广州这类城市名苹果的最近邻应该是香蕉、橙子这类水果名。这个检查非常直观能快速判断词向量是否真的学习到了语义信息。如果最近邻完全不着调比如北京的最近邻是吃饭那基本可以断定训练有问题多半是语料量不够或者超参数设置有问题。第二个是语言模型的困惑度Perplexity评估。困惑度是NLP领域衡量语言模型好坏的经典指标计算公式是exp(loss)直观含义是模型在预测下一个词时平均有多少个候选词让模型感到困惑。困惑度越低越好一般训练一个NNLM在小型语料上困惑度能降到50左右就是可以接受的水平。如果困惑度在100以上说明模型还远没有学透语料中的语言规律。第三个是下游任务的间接验证。如果你想用NNLM做情感分析、文本分类等任务可以把训练好的词向量作为额外的特征输入下游模型对比一下没有词向量和有词向量的效果差距。我在新闻情感分析项目里用NNLM词向量把分类准确率提升了3到5个百分点这是一个非常可观的提升。5. 训练NNLM的常见问题与排查实录5.1 训练不收敛或Loss震荡严重这是我遇到的第一个大坑。刚开始训练的时候loss曲线跟心电图一样上下跳动完全没法收敛。排查下来的原因是多方面的我从易到难逐个定位。第一先检查学习率。学习率设太大参数更新一步跨太远loss很容易震荡设太小模型学得太慢几百轮都不见效果。我建议先用0.01跑5个epoch观察趋势如果不稳定就降到0.003或0.001。第二检查batch_size。太小也会导致梯度噪声大我一般不低于128。如果显存允许256是性能和稳定性的一个比较理想的折中。第三检查数据预处理。这里有一个隐藏很深的坑——词表构建时低频词过滤的阈值设置不当。如果阈值设得太大导致大量词被映射成UNK那么不同句子在输入层就变得非常相似模型学不出差异性如果阈值设太小词表太大每个低频词只出现几次模型在这几个词上的预测基本靠猜输出层的学习就会很混乱。我的经验是阈值设在3到10之间具体看语料总量语料越大阈值相应可以设大一些。第四如果以上都没问题但loss还是不稳定那就要检查是不是有标签错误。我第一次做中文语料的时候jieba分词之后词表里混入了一些奇怪的字符比如标点符号没有过滤干净导致某些预测样本的正确答案实际上是符号模型学了半天是在学标点符号的分布规律。训练前统一过滤掉标点和特殊字符是一个非常实用的小技巧。5.2 显存爆炸或训练速度慢NNLM的显存压力主要来自两个地方Embedding矩阵和输出层的softmax计算。当词表超过10万这两个地方会同时变得极大。我的解决思路分几步走。第一生成词表时做一次频率统计只保留top-N的词N的取值根据实际任务来定一般5万到10万就够用了。超出词表的词统一映射为UNK。第二使用负采样损失函数替代全量softmax。PyTorch内置的nn.CrossEntropyLoss是傻瓜式的但它要做全词表的归一化。换成负采样或者nn.BCEWithLogitsLoss配合随机负例样本能在几乎不损失效果的情况下大幅削减显存占用和计算量。第三如果显存还是不够可以考虑使用梯度累积技术。把小batch的梯度积累几步再执行一次参数更新相当于用时间换空间。我的项目里用过4步累积效果和直接256的batch_size几乎一样但每步显存占用只有原来的四分之一。训练速度慢还有一个容易被忽略的原因PyTorch的DataLoader如果num_workers设置得太小数据加载会成为瓶颈导致GPU空转。我一般设成4到8根据机器核心数而定能明显看到训练流程的流水线变顺畅。5.3 词向量的质量不好语义不相似词向量质量差的表现是最近邻搜索出来的词毫无逻辑关系。这个问题通常有三个原因。第一个原因是训练语料太小。如果语料只有几十万词模型根本没有足够多的上下文共现信息来推断词之间的相似性。我说句实在话想要训练出有意义的词向量千万词级别的语料是最低配置亿级词效果才会让人满意。如果只有小语料更现实的选择是直接用第三方预训练好的词向量比如腾讯开源的词向量数据。我在中文项目里就是这么做的效果比自己用小语料训出来的好了不止一个等级。第二个原因是窗口大小设置不合理。上下文窗口n越小词向量的语法色彩越强窗口越大语义色彩越强。如果你希望词向量能捕捉语义相关性推荐把窗口适当调大论文里Word2Vec的Skip-gram窗口默认5我自己做语义任务时喜欢调到8-10。第三个原因是没有做多次训练轮数。词向量在训练早期还没有充分收敛通常需要跑到10轮以上向量的质量才会趋于稳定。我遇到过的情况是第5轮的词向量看起来整体还行但细看某些语义关系仍然粗糙跑到15轮之后明显更细腻。当然也要注意别过拟合验证集loss开始回升的时候就要停了。6. 应用场景与现代演进NNLM之后的技术路线6.1 NNLM的直接应用场景虽然NNLM听起来像是学术概念但它在实际工程中是有真实应用场景的我自己的项目里就用到过几个。文本纠错是NNLM一个非常自然的应用方向。原理很简单利用语言模型计算每一句话的概率如果某句话中某个词替换成另一个词之后整句概率显著提升那大概率是原文中那个位置出现了错别字或音近字错误。我在新闻素材清洗环节处理OCR识别错误时用过这个思路准确率还挺可观的。中文OCR在识别千和干这类形近字时经常出错而NNLM能根据上下文判断哪个词更符合语义综合准确率能达到85%以上。输入法候选词排序也是语言模型的经典应用。输入法中的拼音转汉字本质上就是给定拼音序列找到最合适的汉字序列这需要语言模型对每个候选句子打分。NNLM虽然比后来的RNN和Transformer弱一些但在计算资源受限的场景下它仍然是一个足够基础且性价比高的方案——结构简单、训练快速、推理延迟低。文本生成方面NNLM可以用来生成简单的句子但效果确实比较有限因为它的上下文窗口只有前n-1个词一旦句子变长模型记不住前面的内容生成的话语经常会逻辑断裂。我第一次用NNLM做生成实验时生成的句子在4-5个词之后就开始胡说八道模型只能看到局部的3-4个词远超这个范围的信息全部丢失了。这也是NNLM后来被RNN取代的根本原因RNN通过循环连接可以记住更长的历史信息。6.2 从NNLM到Word2Vec的演进逻辑2013年Mikolov提出的Word2Vec并不是对NNLM的全盘推翻而是做了一次非常聪明的减法。原版NNLM训练一个样本需要同时计算隐藏层和输出层的参数更新Word2Vec发现了一个很有意思的事实既然我们真正想要的是Embedding矩阵那能不能先把语言模型预测的任务简化专注于让词向量学习得够快够好于是Word2Vec推出了两种架构CBoW连续词袋模型和Skip-gram。CBoW的做法是拿上下文词的向量取平均值直接预测中间词这里不需要隐藏层结构更简洁Skip-gram反其道而行之用一个词去预测它周围的上下文词。这两种结构都在训练效率上做了极大的优化配合负采样技巧训练速度比NNLM快了几个数量级同时词向量的质量反而更好了。从工程视角来看这个演进的核心驱动力是效率。NNLM的训练速度瓶颈在于输出层的softmax归一化每个样本都需要把所有词的概率都算一遍在GPU还不普及的年代这几乎是不可接受的。Word2Vec通过把预测词表概率分布简化为区分真实词和几个随机负样本巧妙地绕开了这个瓶颈。6.3 从静态词向量到上下文相关表示RNN与TransformerWord2Vec训练出来的词向量有一个天然缺陷它是静态的。无论词出现在什么语境中它的向量都是同一个。这意味着苹果在我吃了一个苹果和苹果发布了新款手机这两个句子中的向量完全一样语义信息的丢失在这里非常明显。为了解决这个问题ELMo2018年和BERT2019年相继登场。它们用双向语言模型替代了单向预测把词向量从静态升级为上下文相关的动态表示。但万变不离其宗无论是LSTM还是Transformer它们的训练目标仍然是预测词序列的概率底层逻辑和NNLM一脉相承。我理解NLP这些年的技术演进时喜欢把它看作两条线并行发展一条是语言模型建模能力的提升从NNLM的n-gram窗口到LSTM的长距离记忆再到Transformer的全注意力机制模型能看到的上下文越来越长另一条是词表示质量的提升从NNLM的静态词向量到ELMo的动态表示再到GPT、BERT的预训练语义表示。这两条线相互纠缠、彼此成就而起点就是20多年前的NNLM。理解这个演进过程对做NLP项目的人来说至关重要因为很多任务不需要动不动就上大模型选对合适的方案往往能获得更高的性价比。7. 实操经验总结与个人心得做NNLM这个项目前前后后也有一年多时间中间踩过的坑、积累的经验我简单做一个总结算是对自己这段经历的复盘也希望能帮到后面入坑的朋友。第一点NNLM是一个极好的教学模型但直接上生产项目时要慎重。它的价值在于让我们理解语言模型的基本范式Embedding加序列建模加概率输出。这个范式沿用到今天本质上没有任何改变。但如果要做生产级的文本生成或语义理解我建议直接用成熟的开源预训练模型比如BERT系列、GPT系列效果和性价比远超从零训练的NNLM。只有场景比较特殊比如注重延迟、需要在低功耗设备上运行或者数据量小到用大模型纯属浪费NNLM这种轻量模型才有发挥空间。第二点数据质量决定了模型效果的上限。我反复强调过滤低频词、清洗标点符号这些看起来琐碎的操作对最终效果的影响往往比调参还大。有一次我把语料中的英文标点改成全角中文标点模型的困惑度莫名其妙涨了一大截查了半天才发现是分词器对全角标点处理异常导致很多词被错误切分。从那以后我养成了一个习惯在训练任何NLP模型之前先随机抽样100条语料人工检查一遍预处理结果这个习惯帮我省了不知道多少排查问题的时间。第三点超参数的调试要有系统方法。不要每次都从头试错我推荐的做法是先固定一个初始配置跑完一个epoch观察loss下降趋势然后每次只调整一个超参数记录实验结果和趋势建立自己的参数灵敏度认知。用表格记录下来之后你会慢慢积累一套对自己数据集的调参直觉。我到现在还会保留几个经典语料上不同参数组合的实验记录这就是我的参数调优手册。第四点懂得取舍和变通。我在做情感分析时发现直接用开源的预训练中文词向量比自己在几百万词的小语料上花几小时训练出来的向量效果好了不止一个档次。很多学习者容易陷入一个误区觉得必须所有的东西都自己亲自动手做。但从工程角度来说能站在前人肩膀上做事是一种智慧省下来的时间和计算资源可以用来尝试更多优化方案。最后分享一个小技巧在训练完NNLM后用TensorBoard把词向量降维到二维平面可视化一下你会非常直观地看到语义相近的词聚在一起。我第一次看到北京上海广州在地图上聚成一片的时候那种震撼感至今印象深刻它会让你切实感受到神经网络学习到的语义是什么模样。这是我最推荐的学习者做的一步——用可视化的方式感受模型学到的内容而不是只看一堆冰冷的损失值和准确率指标。