1. 从零开始搭建NLP知识框架我的自然语言处理入门笔记搞自然语言处理这些年被问得最多的问题就是“我想入门NLP该从哪儿下手”。说实话这个问题我当年也问过别人得到的答案五花八门——有人让你先啃《统计学习方法》有人让你直接上深度学习还有人甩给你一篇“NLP已死”的博客让你别学了。几年下来我踩过的坑比读过的论文还多慢慢才理出一条相对靠谱的路径。这篇笔记就是把我自己学习自然语言处理的过程重新梳理一遍把那些“当时要是有人告诉我就好了”的关键节点都标出来。自然语言处理英文叫Natural Language Processing简称NLP说白了就是让计算机能听懂、能理解、能生成人类语言的一门技术。你手机里的输入法联想、搜索引擎的查询理解、客服机器人的自动回复背后都是NLP在撑着。这篇文章适合两类人一类是刚接触这个领域、面对海量资料不知道从哪看起的新手另一类是有一定编程基础、想系统补全NLP知识体系但之前学的比较零散的开发者。我会从最基础的概念讲起把核心任务、技术演进、实操工具和常见坑都过一遍尽量做到你看完就能自己动手跑起来。1.1 NLP到底在解决什么问题很多人一上来就急着学Transformer、BERT结果连NLP的基本任务分类都说不清楚。我刚开始也这样后来发现基础不牢真的会走很多弯路。NLP的核心任务其实可以分成几个大类理解了这些分类你再看论文和代码的时候就知道它在解决哪一层的问题。第一类是词法层面的任务处理的是“词”这个粒度。比如中文分词把“我爱自然语言处理”切成“我/爱/自然语言/处理”词性标注给每个词标上名词、动词、形容词还有命名实体识别从文本里找出人名、地名、机构名。这些任务看起来简单但它们是上层任务的基础分词分错了后面的理解全歪。第二类是句法层面的任务关注词与词之间的关系。比如句法分析搞清楚“咬死了猎人的狗”到底是“狗咬死了猎人”还是“咬死了猎人的那条狗”依存句法分析找出句子中每个词跟哪个词有直接关系。这类任务在机器翻译和问答系统里特别重要。第三类是语义层面的任务这才是真正让机器“理解”语言的部分。文本分类判断一段话是正面还是负面情绪语义相似度计算判断两句话是不是一个意思信息抽取从大段文本里提取结构化信息还有问答系统和对话生成。语义任务是最难的也是目前深度学习发力最猛的地方。提示新手最容易犯的错误就是跳过词法和句法直接搞语义。我的建议是至少把分词和词性标注亲手实现一遍哪怕用现成工具跑一遍也行这样你对文本数据的“颗粒感”会有完全不同的认识。1.2 为什么现在入门NLP比五年前容易得多我2018年刚开始学的时候光装一个分词工具就能折腾一整天各种依赖冲突、编码报错。现在情况完全不一样了开源社区把门槛降得非常低。你不需要自己从零实现CRF不需要手写特征模板甚至不需要标注数据——预训练模型直接拿来微调就行。这个变化的核心原因是预训练语言模型的出现。从Word2Vec到ELMo从BERT到GPT系列技术路线越来越清晰先用海量无标注文本让模型学会语言的一般规律再用少量标注数据针对具体任务微调。这个范式让NLP的入门难度断崖式下降你不需要再纠结特征工程把精力放在理解任务和调参上就行。另一个变化是工具生态的成熟。HuggingFace的Transformers库把主流模型都封装好了几行代码就能加载BERTspaCy和jieba让分词和基础NLP任务变得极其简单各种中文预训练模型也开源了直接下载就能用。我实测下来现在一个新手从零到跑通一个文本分类任务快的话一个下午就够了这在五年前是不可想象的。但门槛降低也带来一个问题很多人只会调库不知道背后发生了什么。模型效果不好就换一个再不好就换数据完全靠试错。这种“黑盒式”的学习方式短期能出活长期来看天花板很低。所以我的建议是工具要用但原理也要懂至少知道BERT的输入输出是什么、注意力机制在算什么、微调的时候改的是哪部分参数。2. 核心概念拆解词向量、注意力与预训练这一章是我认为NLP入门最关键的三个概念。你把这三点吃透了再看后面的模型和论文就不会觉得云里雾里。我会尽量用生活化的类比来解释同时给出具体的计算过程和代码示例保证你不仅看懂还能自己复现。2.1 词向量把词变成计算机能算的数字计算机不认识文字只认识数字。所以NLP的第一步永远是把文本转成数字表示。最早的方法是one-hot编码比如词表有10000个词“猫”是第3个词那它的向量就是[0,0,1,0,...,0]只有第3位是1。这个方法简单但有两个致命问题一是维度太高词表多大向量就多长二是词与词之间没有关系“猫”和“狗”的向量内积是0计算机不知道它们都是动物。Word2Vec解决了这个问题。它的核心思想是一个词的含义由它周围的词决定。你经常在“猫”旁边看到“喵”“鱼”“毛”在“狗”旁边看到“汪”“骨头”“尾巴”那“猫”和“狗”的向量就会比较接近。具体实现有两种方式CBOW用周围词预测中心词Skip-gram用中心词预测周围词。训练完之后每个词变成一个固定长度的稠密向量通常是100维到300维。我当年自己训练Word2Vec的时候踩过一个坑语料太少导致效果很差。Word2Vec需要大量文本才能学到好的表示至少得几百万字起步。新手建议直接用预训练好的词向量比如腾讯AI Lab的中文词向量覆盖800多万词直接下载就能用。加载的代码大概长这样import gensim # 加载预训练词向量 model gensim.models.KeyedVectors.load_word2vec_format( tencent-ailab-embedding-zh-d100-v0.2.0-s.txt, binaryFalse ) # 查看词向量 print(model[自然语言]) # 输出100维向量 print(model.most_similar(自然语言, topn5)) # 找最相似的词运行结果会返回“自然语言”最相似的词比如“计算机语言学”“计算语言学”“人工智能”等。这个相似度计算就是通过余弦相似度算出来的公式是两向量点积除以模长乘积。你可以自己手算验证一下加深理解。注意Word2Vec有个著名的问题叫“一词多义”。比如“苹果”在“吃苹果”和“苹果手机”里含义完全不同但Word2Vec只给一个向量。这个问题后来被ELMo和BERT用上下文相关的动态词向量解决了这是NLP发展的一个重要分水岭。2.2 注意力机制让模型学会“看重点”注意力机制是Transformer的核心也是BERT和GPT的基础。它的直觉很简单你读一句话的时候不会平均用力看每个词而是会根据当前理解的需要把注意力集中在某些关键词上。比如读“小猫坐在垫子上它很舒服”你理解“它”指代什么的时候会重点看“小猫”而不是“垫子”。自注意力机制的计算过程可以拆成三步。第一步把每个词的向量分别乘以三个矩阵W_Q、W_K、W_V得到Query、Key、Value三个向量。第二步用当前词的Query和所有词的Key做点积得到注意力分数再除以根号d_kd_k是向量维度防止梯度消失然后过softmax归一化。第三步用归一化后的分数对Value加权求和得到当前词的输出表示。我刚开始看这个公式的时候也觉得抽象后来自己用NumPy手写了一遍才真正理解。核心代码就这几行import numpy as np def self_attention(Q, K, V): d_k Q.shape[-1] scores np.dot(Q, K.T) / np.sqrt(d_k) weights np.exp(scores) / np.exp(scores).sum(axis-1, keepdimsTrue) return np.dot(weights, V)这个计算过程有个关键点所有词是并行计算的。不像RNN必须一个词一个词按顺序算自注意力可以一次性处理整个句子这也是Transformer训练效率远高于RNN的原因。但并行也带来一个问题模型不知道词的顺序。所以Transformer额外加了位置编码用正弦余弦函数给每个位置生成一个独特的向量加到词向量上。多头注意力就是把上面的过程重复多次每次用不同的W_Q、W_K、W_V矩阵最后把结果拼接起来。这样做的好处是模型可以同时关注不同层面的关系比如一个头关注语法关系另一个头关注语义关系。BERT-base用了12个头每个头64维拼起来还是768维。2.3 预训练微调NLP的“通用底座”模式预训练微调这个范式我觉得是NLP领域过去十年最重要的思想转变。以前做NLP每个任务都要从头标注数据、设计特征、训练模型成本极高。现在先用海量无标注文本训练一个通用模型再针对具体任务用少量标注数据微调效果又好又省事。预训练阶段通常做两个任务掩码语言模型和下一句预测。掩码语言模型就是随机遮住输入句子中15%的词让模型猜被遮住的是什么。比如输入“自然[MASK]处理很有趣”模型要预测出“语言”。这个任务迫使模型学会双向上下文信息。下一句预测是给模型两个句子判断它们是不是连续的这让模型学会句子间的关系。微调阶段就简单了把预训练好的模型加载进来在顶层加一个针对具体任务的输出层然后用你的标注数据训练几轮就行。比如文本分类任务取BERT输出的[CLS]位置的向量接一个全连接层做分类。学习率要设得很小通常是2e-5到5e-5因为预训练模型的参数已经很好了微调只是微调不能大改。from transformers import BertTokenizer, BertForSequenceClassification import torch # 加载预训练模型和分词器 tokenizer BertTokenizer.from_pretrained(bert-base-chinese) model BertForSequenceClassification.from_pretrained( bert-base-chinese, num_labels2 # 二分类任务 ) # 编码输入 inputs tokenizer(这个电影太好看了, return_tensorspt) outputs model(**inputs) predictions torch.softmax(outputs.logits, dim-1) print(predictions) # 输出各类别概率这段代码我实测下来在中文情感分类任务上用几千条标注数据微调三到五轮准确率就能到90%以上。相比自己从头训练效率提升非常明显。提示微调的时候有个常见坑是学习率设太大导致预训练学到的知识被“冲掉”。我一般先用2e-5试如果loss震荡就降到1e-5。另外batch size不要太小16或32比较稳太小的话梯度噪声大微调效果会打折扣。3. 中文NLP的特殊挑战与实操方案中文NLP和英文NLP有个根本区别英文单词天然用空格隔开中文必须先分词。这个差异看起来小实际上影响很大。分词错了后面的词性标注、句法分析、语义理解全都会受影响。这一章我专门讲中文NLP的实操细节包括分词工具选型、常见问题和解决方案。3.1 中文分词NLP的第一道坎中文分词的方法经历了三代演进。第一代是基于词典匹配比如最大正向匹配、最大逆向匹配、双向匹配。方法简单但遇到未登录词就歇菜比如“给力”“内卷”这些新词词典里没有就分不出来。第二代是统计方法用HMM或CRF模型把分词当成序列标注问题每个字标B词首、M词中、E词尾、S单字词。第三代是深度学习方法用BiLSTM-CRF或BERT做序列标注效果最好但需要标注数据。实际工作中怎么选我的经验是通用场景用jieba专业领域用自定义词典统计模型追求极致效果用BERT微调。jieba的好处是开箱即用支持三种模式精确模式、全模式和搜索引擎模式。精确模式适合文本分析全模式会把所有可能的词都切出来适合搜索搜索引擎模式在精确模式基础上对长词再切分。import jieba text 自然语言处理是人工智能的重要方向 # 精确模式 print(jieba.lcut(text)) # [自然语言, 处理, 是, 人工智能, 的, 重要, 方向] # 全模式 print(jieba.lcut(text, cut_allTrue)) # [自然, 自然语言, 语言, 处理, 是, 人工, 人工智能, 智能, 的, 重要, 方向] # 搜索引擎模式 print(jieba.lcut_for_search(text)) # [自然, 语言, 自然语言, 处理, 是, 人工, 智能, 人工智能, 的, 重要, 方向]jieba支持加载自定义词典这对专业领域特别重要。比如医疗文本里的“房室传导阻滞”通用词典可能切成“房室/传导/阻滞”但你应该把它当成一个词。加载自定义词典的代码是jieba.load_userdict(userdict.txt)文件格式是“词语 词频 词性”词频和词性可以省略。我踩过的一个坑是自定义词典的词频设置。如果你不设词频jieba会用默认值可能导致自定义词被切碎。我的经验是给专业术语设一个较高的词频比如10000以上确保它优先被识别。另外动态添加词用jieba.add_word(新词)删除词用jieba.del_word(旧词)这两个方法在交互式场景下很方便。3.2 停用词处理什么时候该删什么时候不该删停用词就是“的”“了”“在”“是”这类高频但信息量低的词。很多教程一上来就让你删停用词但我的经验是不是所有任务都需要删停用词。文本分类任务删了通常有帮助因为减少噪声但情感分析任务里“不”“没”“很”这些词恰恰是关键删了就把情感极性搞反了。我一般会准备两个停用词表一个通用停用词表包含“的”“了”“在”等纯功能词一个任务相关停用词表根据具体任务调整。比如做新闻分类我会把“记者”“报道”“讯”这些新闻体裁词加进去做电商评论分析我会保留“不”“很”“太”这些程度和否定词。# 加载停用词 def load_stopwords(filepath): with open(filepath, r, encodingutf-8) as f: return set([line.strip() for line in f]) stopwords load_stopwords(stopwords.txt) # 过滤停用词 text 这个产品真的很不错 words jieba.lcut(text) filtered [w for w in words if w not in stopwords] print(filtered) # [产品, 真的, 不错]注意停用词表不是越大越好。我见过有人用了一个包含几万词的停用词表结果把“不”“没”都删了情感分析准确率直接掉到60%以下。建议先用小停用词表根据任务效果逐步调整。3.3 中文预训练模型选型对比现在中文预训练模型很多新手容易挑花眼。我把自己用过的几个主流模型做个对比方便你根据场景选择。模型参数量特点适用场景BERT-base-chinese110M谷歌官方通用性强通用NLP任务入门首选RoBERTa-wwm-ext102M哈工大讯飞全词掩码中文任务效果通常优于BERTERNIE 3.010B百度知识增强需要外部知识的任务ChatGLM6B清华生成式对话生成、文本续写新手我推荐从BERT-base-chinese或RoBERTa-wwm-ext开始。这两个模型大小适中单卡就能微调社区资源也丰富。ERNIE 3.0和ChatGLM参数量大需要多卡或大显存适合有资源的研究场景。RoBERTa-wwm-ext的全词掩码是个重要改进。BERT的掩码是随机遮字比如“自然语言”可能遮成“自[MASK]语言”模型猜“然”。但中文里“自然”是一个词遮一个字让任务变得太简单。全词掩码是遮住整个词“自然”让模型猜“自然”难度更大学到的表示更好。我实测在文本分类任务上RoBERTa-wwm-ext比BERT-base-chinese准确率高1到2个百分点。4. 实操项目从数据到模型的完整流程光说不练假把式。这一章我带你走一遍完整的NLP项目流程用新闻分类作为案例。为什么选新闻分类因为它涵盖了NLP项目的主要环节数据获取、预处理、特征工程、模型训练、评估调优而且任务定义清晰适合新手练手。4.1 数据获取与清洗新闻分类的数据可以从多个渠道获取。公开数据集有THUCNews清华新闻数据集74万篇新闻10个类别、TNEWS今日头条新闻标题数据集。如果做课程设计THUCNews的子集就够用了下载下来是每行一篇新闻格式是“类别\t标题\t正文”。拿到数据第一件事是清洗。新闻文本里通常有HTML标签、特殊符号、乱码、重复内容。我一般按这个顺序处理去HTML标签用正则re.sub(r[^], , text)去特殊符号保留中文、英文、数字和基本标点去重复用drop_duplicates()去空值用dropna()。import re import pandas as pd def clean_text(text): # 去HTML标签 text re.sub(r[^], , text) # 只保留中文、英文、数字和基本标点 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、], , text) # 去多余空格 text re.sub(r\s, , text).strip() return text df pd.read_csv(news.csv, sep\t, names[label, title, content]) df[content] df[content].apply(clean_text) df df.drop_duplicates(subset[content]).dropna() print(f清洗后数据量{len(df)})清洗完之后要做数据分析看看类别分布是否均衡。如果某个类别特别少要么补充数据要么用重采样。我一般会画个柱状图看看类别不均衡太严重的话训练出来的模型会偏向多数类。4.2 分词与特征构建清洗完的文本要分词。新闻文本我一般用jieba精确模式加载一个新闻领域的自定义词典把“人工智能”“区块链”“碳中和”这些新词加进去。分词之后过滤停用词但保留“不”“没”“很”这些情感词。特征构建有两种路线传统方法和深度学习方法。传统方法用TF-IDF把文本转成向量然后喂给SVM或朴素贝叶斯。TF-IDF的核心思想是一个词在当前文档出现越多、在所有文档出现越少它就越重要。计算公式是TF乘以IDFTF是词频IDF是log(总文档数/包含该词的文档数)。from sklearn.feature_extraction.text import TfidfVectorizer # 分词后的文本列表 corpus [ .join(jieba.lcut(text)) for text in df[content]] # TF-IDF向量化 vectorizer TfidfVectorizer(max_features5000, ngram_range(1, 2)) X vectorizer.fit_transform(corpus) print(f特征矩阵形状{X.shape})max_features5000表示只保留TF-IDF值最高的5000个词ngram_range(1,2)表示同时考虑单个词和相邻两个词的组合。这两个参数需要根据数据量调整数据少就减小max_features防止过拟合。深度学习方法就直接用BERT的tokenizer把文本转成input_ids和attention_mask喂给BERT模型。不需要手动做TF-IDFBERT会自己学表示。4.3 模型训练与评估传统方法我推荐先用朴素贝叶斯跑个baseline速度快效果也不差。然后用SVM或LightGBM提升。深度学习就直接微调BERT。我一般会两种方法都跑一遍对比效果。from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X, df[label], test_size0.2, random_state42 ) # 训练朴素贝叶斯 nb MultinomialNB() nb.fit(X_train, y_train) # 评估 y_pred nb.predict(X_test) print(classification_report(y_test, y_pred))评估指标不能只看准确率。新闻分类如果类别不均衡准确率会虚高。我一般看宏平均F1值它给每个类别相同的权重能反映模型在所有类别上的综合表现。另外混淆矩阵也要看找出哪些类别容易混淆比如“体育”和“娱乐”经常分错因为都涉及明星。BERT微调的代码前面给过了这里补充几个调参经验。学习率2e-5到5e-5batch size 16或32训练轮数3到5轮。轮数太多会过拟合我一般用早停策略验证集loss连续两轮不降就停。最大序列长度设128或256新闻标题短设128够用正文长设512。提示微调BERT的时候如果显存不够可以冻结底层参数只训练顶层。for param in model.bert.parameters(): param.requires_grad False这样显存占用能降一半效果损失不大。等有资源了再解冻全部参数微调。4.4 错误分析与迭代优化模型跑通只是开始真正提升效果靠的是错误分析。我一般会导出预测错误的样本一条条看为什么错。常见错误类型有标注错误人工标错了、文本太短信息不足、类别边界模糊、模型没学到关键特征。针对不同错误有不同的优化策略。标注错误就重新标注文本太短就补充上下文或标题正文拼接类别模糊就合并类别或增加类别定义模型没学到特征就增加数据或换更强的模型。我做过一个实验把预测错误的样本人工修正后加入训练集迭代三轮F1值从0.82提升到0.89效果非常明显。另一个技巧是数据增强。新闻分类可以用同义词替换、随机插入、随机交换、随机删除来扩充数据。比如“股价大涨”可以替换成“股价飙升”“公司发布财报”可以插入“今日”变成“公司今日发布财报”。但要注意增强后的文本标签不能变情感分析里“好”替换成“差”就出大事了。5. 常见问题与排查技巧实录这一章整理我在NLP实操中遇到的高频问题和解决方法都是踩过坑之后总结出来的常规教程里不一定有。5.1 环境配置与依赖冲突NLP项目的环境配置是个老大难。Python版本、CUDA版本、PyTorch版本、Transformers版本任何一个不匹配都可能报错。我的经验是用conda创建独立环境不要用系统Python。创建环境的命令是conda create -n nlp python3.8然后激活conda activate nlp。PyTorch安装要去官网查对应CUDA版本的命令不要直接pip install torch那样装的是CPU版本。Transformers库版本要和PyTorch匹配我一般用transformers4.30.0配torch1.13.0这个组合比较稳。如果遇到“ImportError: cannot import name xxx”大概率是版本不匹配降级或升级transformers试试。问题原因解决方法CUDA out of memory显存不足减小batch size或冻结底层参数ImportError版本不匹配查官方文档固定版本号分词结果乱码编码问题统一用utf-8编码读写文件模型不收敛学习率太大降到1e-5或2e-5加warmup预测结果全同一类数据不均衡重采样或加类别权重5.2 模型效果不达预期的排查思路模型效果差先别急着换模型。我一般按这个顺序排查数据质量→标注一致性→特征质量→模型选型→超参数。数据质量是第一位的我见过太多案例是数据本身有问题换什么模型都没用。检查数据质量的方法随机抽100条看标注是否正确看文本是否有乱码或截断看类别分布是否均衡。标注一致性可以多人标注同一批数据算Kappa系数低于0.8说明标注标准不清晰需要重新对齐。特征质量方面传统方法看TF-IDF的top词是否合理深度学习看tokenizer是否把关键信息切碎了。我遇到过一个案例用户把“不予退款”分词成“不予/退款”模型学不到“不予”的否定含义后来把“不予退款”加入自定义词典效果立刻提升。超参数调优我一般用网格搜索但不会全量搜先粗调再细调。学习率试[1e-5, 2e-5, 5e-5]batch size试[16, 32]轮数试[3, 5]。用验证集选最优组合不要用测试集调参否则会过拟合测试集。5.3 实操心得与避坑清单最后分享几条我这些年总结的实操心得每一条都是踩坑换来的。第一条先跑通再优化。新手最容易犯的错是追求完美数据要清洗得干干净净模型要选最新的结果一周过去了还没跑通第一个baseline。我的建议是先用一个简单模型跑通全流程哪怕准确率只有60%至少你知道流程是通的然后再逐步优化。第二条版本控制很重要。数据、代码、模型、实验结果都要版本控制。我用git管理代码用DVC管理数据和模型每次实验记录超参数和结果。这样你回头找“上次那个效果好的模型是怎么训的”就不会抓瞎。第三条不要迷信SOTA。论文里的SOTA模型在特定数据集上效果好不代表在你的数据上也好。我试过用最新的模型替换BERT结果因为数据量不够效果反而下降了。模型选型要匹配数据规模和任务难度杀鸡不用牛刀。第四条bad case比good case更有价值。模型预测对的样本你扫一眼就过了预测错的样本才值得仔细分析。我一般会建一个bad case库每次实验后把新的错误样本加进去定期回顾看看有没有共性规律。第五条多跟同行交流。NLP领域发展太快一个人闷头学很容易走弯路。我加了好几个NLP技术群遇到问题群里问一句经常比查半天文档还管用。但要注意甄别信息群里说的不一定对自己验证过再采纳。注意不要在生产环境直接跑实验代码。我见过有人把实验脚本直接部署到线上结果一个bug导致服务挂了半天。实验环境和生产环境要隔离实验代码经过充分测试再上线。5.4 学习资源与进阶路线入门之后怎么继续提升我按自己的经验给一条路线。第一阶段打基础吴恩达的深度学习课程、李宏毅的机器学习课程、CS224n自然语言处理课程。这三个课程质量都很高选一个跟完就行。第二阶段读论文从Word2Vec、Seq2Seq、Attention、Transformer、BERT、GPT这条主线读下来每篇论文配合代码实现。第三阶段做项目参加Kaggle的NLP比赛或者自己找数据集做完整项目从数据清洗到模型部署走一遍。第四阶段跟进前沿关注ACL、EMNLP、NAACL等顶会论文读arXiv上的最新预印本。中文资源方面我推荐《自然语言处理入门》这本书何晗写的对新手很友好代码也实用。另外HuggingFace的官方教程和文档质量很高英文不好的可以用浏览器翻译不影响理解。这个领域变化快但基础的东西变化慢。词向量、注意力机制、预训练微调范式这些核心概念几年内不会过时。把基础打牢再跟进新模型就不会被技术浪潮甩下。我在实际项目中发现真正拉开差距的不是谁用了最新的模型而是谁对数据和任务的理解更深入。模型是工具理解问题才是核心。