资讯动态

NLTK入门实战:从分词到词形还原的完整笔记

发布时间:2026/10/14 7:27:36 来源:尧图企业网站定制
自然语言处理这门手艺我一开始是抗拒的。总觉得那是搞算法的人才会碰的东西跟我这种半路出家做数据分析的没太大关系。直到有一次我需要从几千条用户反馈里提取高频问题手动翻了两天眼睛都快瞎了才意识到——文本数据不处理就是一堆没法用的乱码。NLTK 就是在这个时候进入我的视野的。它是 Python 生态里做自然语言处理最老牌、最全面的工具库之一全称 Natural Language Toolkit中文叫自然语言工具包。这篇文章是我学习 NLTK 的完整笔记从安装到语料库加载从分词到词形还原每一步都有实操代码和我踩过的坑。如果你也是做数据分析的正在被文本数据折磨或者刚接触 NLP 不知道从哪下手这篇内容应该能帮你省下不少查文档的时间。1. 为什么数据分析师也该学点 NLTK1.1 文本数据正在变成绕不开的活儿我做数据分析这几年最明显的一个感受是结构化数据越来越不够用了。以前拉个 Excel 表做个透视画个柱状图就能交差。现在不行了业务方动不动就甩过来几千条用户评论、客服对话记录、问卷开放题问你“能不能分析一下用户在说什么”。这些数据全是文本没有字段没有标签你没法直接 groupby也没法直接算均值。这时候你有两个选择要么手动抽样看几百条凭感觉总结要么用工具把文本拆解成可以统计、可以量化的结构。前者费时费力还不客观后者就是自然语言处理要解决的问题。NLTK 作为入门级 NLP 工具库最大的好处是它把很多底层逻辑暴露给你让你能看清楚每一步到底在做什么而不是调一个黑箱 API 就完事。1.2 NLTK 在 NLP 工具链里的位置Python 做 NLP 的库不少spaCy 更快更现代jieba 做中文分词更专业Transformers 做深度学习模型更强。但 NLTK 有一个不可替代的优势它是教学性质最强的库。它的设计哲学是“让你理解每一步”而不是“帮你一步到位”。比如分词NLTK 提供了好几种分词器你可以对比它们的效果差异比如词形还原NLTK 把词性标注和还原分开逼着你去理解词性对还原结果的影响。我个人的建议是如果你刚接触 NLP先从 NLTK 入手把分词、词形还原、词性标注、停用词过滤这几个基础环节跑一遍理解文本预处理到底在做什么。等你有了这个基础再去用 spaCy 或者别的库你会知道那些“一键搞定”的背后到底省略了什么。反过来如果你直接上高级库遇到效果不好的时候你连从哪里排查都不知道。1.3 这篇笔记覆盖的内容范围这篇笔记是我学习 NLTK 第 27 天的整理主要覆盖以下几个模块NLTK 的安装和常见安装问题、语料库的加载和使用、分词的各种方法和对比、词形问题词干提取和词形还原的处理。每一个模块我都会给出可运行的代码、参数说明和我实际跑的时候遇到的问题。内容偏向基础但基础的东西讲透了后面做项目才不会虚。2. NLTK 安装看起来简单坑却不少2.1 标准安装流程安装 NLTK 本身很简单一行命令的事pip install nltk装完之后在 Python 里验证一下import nltk print(nltk.__version__)如果没报错说明库本身装好了。但 NLTK 跟别的库不一样的地方在于它很多功能依赖额外的数据包比如语料库、分词模型、词性标注器。这些数据包不在 pip 安装范围内需要单独下载。2.2 数据包下载的两种方式第一种方式是交互式下载。在 Python 里执行import nltk nltk.download()这会弹出一个图形界面你可以勾选需要的包。但这种方式在服务器或者没有图形界面的环境里用不了。第二种方式是指定包名下载这也是我更推荐的方式import nltk nltk.download(punkt) nltk.download(averaged_perceptron_tagger) nltk.download(wordnet) nltk.download(stopwords)这几个包分别对应分词模型、词性标注器、词形还原用的 WordNet 语料库、停用词表。刚开始学的时候把这四个下下来基本够用了。注意nltk.download() 默认下载到用户目录下的 nltk_data 文件夹。如果你在多用户环境或者容器里跑可能会遇到权限问题或者路径找不到的问题。可以手动指定下载路径nltk.download(punkt, download_dir/your/custom/path)然后把这个路径加到 nltk.data.path 里。2.3 安装过程中最常见的三个问题问题一下载速度慢或者超时。这个我遇到过好几次尤其是在网络环境不太稳定的时候。解决办法是设置代理或者换时间段重试。如果实在下不下来可以手动去 NLTK 的 GitHub 仓库下载对应的 zip 包解压到 nltk_data 目录下对应的文件夹里。问题二punkt 包下载了但找不到。这个通常是因为 nltk_data 的搜索路径不对。你可以用nltk.data.path查看当前的搜索路径列表确认你的下载目录在列表里。如果不在手动 append 进去import nltk nltk.data.path.append(/your/nltk_data/path)问题三在 Jupyter Notebook 里下载后重启内核又找不到了。这是因为 Jupyter 的工作目录和下载目录不一致。解决办法是在 notebook 开头就设置好路径或者把 nltk_data 放到 Jupyter 默认的工作目录下。2.4 验证安装是否完整装完之后跑一段简单的测试代码确认各个模块都能正常工作import nltk from nltk.tokenize import word_tokenize from nltk.corpus import stopwords from nltk.stem import WordNetLemmatizer text The quick brown foxes are jumping over the lazy dogs tokens word_tokenize(text) print(分词结果:, tokens) stop_words set(stopwords.words(english)) filtered [w for w in tokens if w.lower() not in stop_words] print(去停用词后:, filtered) lemmatizer WordNetLemmatizer() lemmas [lemmatizer.lemmatize(w) for w in filtered] print(词形还原后:, lemmas)如果这段代码能跑通输出结果合理说明你的 NLTK 环境基本就绪了。3. 语料库NLTK 自带的文本宝库3.1 语料库是什么为什么需要它语料库就是大量文本的集合通常带有一定的标注信息。NLTK 自带了几十个语料库涵盖新闻、小说、电影评论、聊天记录等多种类型。这些语料库的作用主要有两个一是用来做实验你学分词、词性标注的时候得有文本给你练手二是用来做基准测试你写了一个算法得有个标准数据集来评估效果。我刚开始学的时候觉得语料库这东西离实际工作很远后来发现不是。比如你要做一个情感分析模型你得有标注好的正负面评论数据来训练你要做关键词提取你得有一个停用词表和词频统计的参考。NLTK 的语料库虽然不一定直接适用于你的业务场景但用来理解原理、验证方法是非常合适的。3.2 常用语料库加载方法NLTK 的语料库加载方式很统一基本就是from nltk.corpus import xxx然后调用对应的方法。下面是我常用的几个from nltk.corpus import gutenberg # 古腾堡语料库包含多本英文经典书籍 print(gutenberg.fileids()) # 查看包含哪些书 print(gutenberg.words(austen-emma.txt)[:20]) # 查看某本书的前20个词 from nltk.corpus import brown # 布朗语料库按类别标注的新闻、小说等文本 print(brown.categories()) # 查看类别 print(brown.words(categoriesnews)[:20]) from nltk.corpus import reuters # 路透社新闻语料库 print(reuters.fileids()[:5]) from nltk.corpus import stopwords # 停用词表 print(stopwords.words(english)[:20])3.3 语料库的统计分析方法加载语料库之后你可以做很多基础统计。比如统计词频from nltk.corpus import gutenberg from nltk.probability import FreqDist words gutenberg.words(austen-emma.txt) fdist FreqDist(words) print(fdist.most_common(10))这段代码会输出这本书里出现频率最高的 10 个词。你会发现排在前面的基本都是 the、to、and 这类功能词这也就是为什么需要停用词过滤——这些词对区分文本内容没有帮助反而会干扰统计结果。还可以统计特定词的出现位置text gutenberg.words(austen-emma.txt) concordance_list text.concordance(love)concordance方法会显示某个词在文本中每次出现的上下文这对理解词的实际用法很有帮助。3.4 加载自定义语料库实际工作中你肯定要用自己的数据。NLTK 提供了PlaintextCorpusReader来加载纯文本文件from nltk.corpus import PlaintextCorpusReader corpus_root /your/text/files/path file_pattern r.*\.txt my_corpus PlaintextCorpusReader(corpus_root, file_pattern) print(my_corpus.fileids()) print(my_corpus.words()[:50])这个方式适合你的文本文件都是纯文本、没有特殊格式的情况。如果有格式要求比如每行一句话可以用corpus_root配合file_pattern和encoding参数来调整。实操心得加载自定义语料库的时候编码问题是最常见的坑。如果你的文本文件是 GBK 编码而 NLTK 默认用 UTF-8 读取就会报错。解决办法是在PlaintextCorpusReader里指定encodinggbk或者提前把文件转成 UTF-8。4. 分词把文本拆成可操作的最小单元4.1 分词到底在做什么分词就是把一整段文本拆成一个个词或者 token 的过程。英文分词看起来简单按空格拆就行了但实际上没那么容易。比如 dont 应该拆成 do 和 nt 还是保持原样U.S.A. 是一个词还是三个词New York 是一个词还是两个词这些问题没有标准答案取决于你的下游任务需要什么粒度的 token。NLTK 提供了多种分词器每种的分词策略不同适用的场景也不同。我下面会逐一介绍并对比它们的效果。4.2 常用分词方法对比word_tokenize最通用的分词器from nltk.tokenize import word_tokenize text Dont hesitate to ask. Im here to help! tokens word_tokenize(text) print(tokens) # 输出: [Do, nt, hesitate, to, ask, ., I, m, here, to, help, !]word_tokenize会把标点符号单独拆出来把缩写拆成词根和缩写部分。这个分词器基于 Penn Treebank 的分词规范适合大多数英文文本处理任务。sent_tokenize按句子拆分from nltk.tokenize import sent_tokenize text Hello there! How are you? I hope youre doing well. sentences sent_tokenize(text) print(sentences) # 输出: [Hello there!, How are you?, I hope youre doing well.]这个不是按词拆而是按句子拆。做文本摘要、段落分析的时候会用到。RegexpTokenizer用正则表达式自定义分词规则from nltk.tokenize import RegexpTokenizer tokenizer RegexpTokenizer(r\w) text Hello, world! This is a test. tokens tokenizer.tokenize(text) print(tokens) # 输出: [Hello, world, This, is, a, test]\w表示匹配一个或多个字母数字字符这样标点符号就被自动过滤掉了。如果你只关心词不关心标点这个方式很省事。WhitespaceTokenizer最简单的按空格拆分from nltk.tokenize import WhitespaceTokenizer tokenizer WhitespaceTokenizer() text Hello, world! This is a test. tokens tokenizer.tokenize(text) print(tokens) # 输出: [Hello,, world!, This, is, a, test.]这个就是纯按空格拆标点会留在词上。适合对标点不敏感的场景或者你后续会单独处理标点。4.3 分词器的选择逻辑面对这么多分词器怎么选我的经验是看你的下游任务分词器适用场景注意事项word_tokenize通用英文文本处理需要下载 punkt 包sent_tokenize句子级别分析对缩写句号处理可能不完美RegexpTokenizer需要自定义过滤规则正则写错会丢词WhitespaceTokenizer快速粗略拆分标点会附着在词上如果你不确定用哪个先用word_tokenize它是默认选择大多数情况下够用。如果发现分词结果不符合预期再根据具体问题换其他分词器。4.4 中文分词的现实NLTK 对中文分词的支持比较有限它自带的word_tokenize对中文基本是按字拆的效果不理想。做中文 NLP 的话建议用 jieba 或者 HanLP。但 NLTK 的框架和思路是通用的你理解了英文分词的逻辑迁移到中文工具上会很快。# NLTK 对中文的处理效果不推荐用于生产 from nltk.tokenize import word_tokenize text 自然语言处理很有趣 print(word_tokenize(text)) # 输出可能是按字拆的效果不好实操心得分词看起来简单但它是整个 NLP 流程的地基。分词分错了后面的词性标注、词形还原、特征提取全都会受影响。我建议你在正式处理数据之前先拿几十条样本跑一下分词人工检查一下结果确认没问题再批量处理。5. 词形问题词干提取与词形还原5.1 为什么需要处理词形英文里同一个词有不同的形态。比如 run、runs、running、ran它们表达的核心意思都跟“跑”有关但在计算机看来这是四个不同的字符串。如果你直接统计词频run 出现 10 次running 出现 8 次runs 出现 5 次你会得到三个独立的统计结果而不是一个总计 23 次的“跑”。词形处理就是要把这些不同形态归并到同一个标准形式。NLTK 提供了两种方式词干提取Stemming和词形还原Lemmatization。它们的目标相似但方法和效果不同。5.2 词干提取简单粗暴地砍后缀词干提取的思路很直接把词的后缀砍掉留下词干。NLTK 提供了几种词干提取器最常用的是 PorterStemmer 和 LancasterStemmer。from nltk.stem import PorterStemmer stemmer PorterStemmer() words [running, runs, ran, runner, easily, fairly] for w in words: print(w, -, stemmer.stem(w))输出结果running - run runs - run ran - ran runner - runner easily - easili fairly - fairli你会发现 ran 没有被还原成 run因为词干提取只是按规则砍后缀它不认识不规则动词。easily 变成了 easili虽然词干对了但拼写变得很奇怪。这就是词干提取的特点快但结果可能不是真实的词。5.3 词形还原基于词典的智能还原词形还原比词干提取更“聪明”它会查词典根据词性和词形变化规则把词还原成词典里的原形。from nltk.stem import WordNetLemmatizer lemmatizer WordNetLemmatizer() words [running, runs, ran, better, geese, easily] for w in words: print(w, -, lemmatizer.lemmatize(w))输出结果running - running runs - runs ran - ran better - better geese - geese easily - easily等等怎么什么都没变这是因为lemmatize默认词性是名词n而 running 作为名词时它的原形就是 running。你需要指定词性参数print(lemmatizer.lemmatize(running, posv)) # - run print(lemmatizer.lemmatize(ran, posv)) # - run print(lemmatizer.lemmatize(better, posa)) # - good print(lemmatizer.lemmatize(geese, posn)) # - goose指定词性之后还原效果就正常了。这也说明了一个问题词形还原依赖词性标注你得先知道这个词在句子里是什么词性才能正确还原。5.4 词性标注与词形还原的配合NLTK 提供了词性标注器可以自动标注每个词的词性from nltk import pos_tag from nltk.tokenize import word_tokenize text The dogs are running quickly tokens word_tokenize(text) tagged pos_tag(tokens) print(tagged) # 输出: [(The, DT), (dogs, NNS), (are, VBP), (running, VBG), (quickly, RB)]NLTK 的词性标签用的是 Penn Treebank 标签集常见的有NN名词单数、NNS名词复数、VB动词原形、VBG动词现在分词、VBD动词过去式、JJ形容词、RB副词等。把词性标注和词形还原结合起来就能实现更准确的还原from nltk.corpus import wordnet from nltk.stem import WordNetLemmatizer def get_wordnet_pos(treebank_tag): 把 Penn Treebank 词性标签转成 WordNet 词性标签 if treebank_tag.startswith(J): return wordnet.ADJ elif treebank_tag.startswith(V): return wordnet.VERB elif treebank_tag.startswith(N): return wordnet.NOUN elif treebank_tag.startswith(R): return wordnet.ADV else: return wordnet.NOUN lemmatizer WordNetLemmatizer() tagged pos_tag(word_tokenize(The dogs are running quickly)) lemmas [lemmatizer.lemmatize(word, get_wordnet_pos(tag)) for word, tag in tagged] print(lemmas) # 输出: [The, dog, be, run, quickly]这段代码里get_wordnet_pos函数把 Treebank 的词性标签映射到 WordNet 的词性标签然后传给lemmatize方法。这样 dogs 还原成 dogare 还原成 berunning 还原成 run效果就对了。5.5 词干提取 vs 词形还原怎么选对比维度词干提取词形还原速度快慢结果可读性可能不是真实词一定是真实词是否需要词性不需要需要适用场景搜索、召回文本分析、展示典型工具PorterStemmerWordNetLemmatizer我的建议是如果你做的是搜索或者粗粒度的文本匹配词干提取够用速度快如果你要做文本分析、情感分析、主题建模词形还原更合适因为结果更规范可解释性更强。实操心得词形还原的时候词性标注的准确率会直接影响还原效果。NLTK 自带的词性标注器在新闻类文本上表现不错但在社交媒体文本、口语化文本上准确率会下降。如果你的数据是用户评论、弹幕这类非正式文本建议先做一轮清洗或者考虑用更专业的词性标注工具。6. 完整预处理流程与常见问题排查6.1 一个完整的文本预处理管道把前面讲的分词、停用词过滤、词形还原串起来就是一个完整的预处理管道import nltk from nltk.tokenize import word_tokenize from nltk.corpus import stopwords, wordnet from nltk.stem import WordNetLemmatizer from nltk import pos_tag def preprocess_text(text): # 1. 分词 tokens word_tokenize(text) # 2. 转小写 tokens [t.lower() for t in tokens] # 3. 去停用词和标点 stop_words set(stopwords.words(english)) tokens [t for t in tokens if t.isalpha() and t not in stop_words] # 4. 词性标注 tagged pos_tag(tokens) # 5. 词形还原 lemmatizer WordNetLemmatizer() lemmas [lemmatizer.lemmatize(word, get_wordnet_pos(tag)) for word, tag in tagged] return lemmas text The dogs are running quickly through the park, and theyre having a great time! result preprocess_text(text) print(result) # 输出: [dog, run, quickly, park, great, time]这个管道可以根据你的具体需求调整。比如你不需要词形还原可以去掉第 4、5 步你想保留标点可以调整第 3 步的过滤条件。6.2 常见问题速查表问题现象可能原因解决方法LookupError: Resource punkt not foundpunkt 包未下载nltk.download(punkt)分词结果标点混乱分词器选择不当换用 RegexpTokenizer 或调整过滤规则词形还原没效果未指定词性参数配合 pos_tag 使用传入正确的 pos停用词过滤后文本太短停用词表过大自定义停用词表只保留必要的处理速度慢逐条处理未批量批量处理或用 spaCy 替代中文分词效果差NLTK 对中文支持有限换用 jieba 等中文分词工具6.3 性能优化的几个实用技巧批量处理代替逐条处理。如果你有几千条文本要处理不要写 for 循环一条条跑尽量把文本合并成列表批量处理。NLTK 的很多方法支持传入列表批量处理比逐条快很多。缓存停用词表和词形还原器。不要在每次处理文本的时候都重新加载停用词表和创建 WordNetLemmatizer 对象把它们放在函数外面或者用类封装起来只初始化一次。预处理结果持久化。如果你要对同一批数据做多次分析把预处理结果存成文件下次直接加载不要每次都重新跑一遍预处理。我一般会存成 JSON 或者 pickle 格式。考虑用 spaCy 做生产环境的预处理。NLTK 适合学习和实验但如果你要处理百万级的数据spaCy 的速度优势很明显。你可以用 NLTK 理解原理用 spaCy 做实际项目。6.4 我踩过的几个坑第一个坑是忘记下载数据包。刚装完 NLTK 兴冲冲地写代码一跑就报 LookupError查了半天才发现是 punkt 没下载。后来我养成了一个习惯新建环境之后先把常用的几个包一次性下载好。第二个坑是词形还原不指定词性。我一开始不知道lemmatize需要词性参数直接调用发现什么都没变还以为库坏了。后来查文档才知道默认是名词词性动词和形容词需要显式指定。第三个坑是停用词表用错了语言。有一次处理英文文本不小心用了stopwords.words(english)之外的参数结果停用词没过滤掉统计结果里全是 the、a、an。这个错误很低级但确实容易犯尤其是复制粘贴代码的时候。第四个坑是编码问题。处理中文文本或者从网页抓下来的文本时编码经常不是 UTF-8直接读会报 UnicodeDecodeError。解决办法是先用chardet检测编码或者手动指定encoding参数。7. 从 NLTK 出发的下一步学习方向NLTK 的基础打牢之后你可以往几个方向深入。一个是特征工程把预处理后的文本转成向量比如词袋模型、TF-IDF、词嵌入。另一个是模型训练用朴素贝叶斯、SVM 或者深度学习模型做文本分类、情感分析。还有一个方向是主题建模用 LDA 或者 NMF 从大量文本中提取主题。我自己的学习路径是先用 NLTK 把预处理跑通然后用 scikit-learn 做 TF-IDF 和文本分类再往后学 Word2Vec 和 BERT。每一步都是在前面基础上叠加不会觉得跨度太大。如果你也在学 NLP我的建议是不要急着上深度学习。把分词、词形还原、词性标注这些基础环节亲手跑一遍理解每一步在做什么后面遇到问题的时候你才知道从哪里排查。NLTK 虽然老但它的教学价值到现在依然没有替代品。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑