拓冰建站拓冰建站
首页 / 资讯中心 / 正文

NLTK与Spacy入门:从文本预处理到情感分析的NLP实践

说起NLP入门这件事我最近刚好帮一个做电商评论分析的朋友处理了一批数据。他一开始以为要上BERT、GPT这些大模型结果我一看他的需求——把几千条英文评论按正向/负向分类顺便抽取出商品名和吐槽点——这活儿用NLTK和Spacy这两个经典库就能稳稳搞定根本不需要上深度学习。很多刚接触自然语言处理NLP的朋友容易被大模型时代这几个字唬住觉得传统的NLTK、Spacy过时了。但实际上如果你要做文本清洗、关键词提取、词性标注、命名实体识别、情感打分这类基础又高频的任务NLTK和Spacy依然是生产环境里最顺手、最可靠的工具而且学习成本远低于跑一个神经网络。这篇内容我打算从零开始把NLTK和Spacy的核心用法串起来讲一遍。不管是正在上NLP课程的学生还是工作中突然要处理文本数据的开发、运营、产品同学都可以照着做。我会先讲清楚这两个库各自擅长什么、怎么选再带你把环境装好、把最常见的文本预处理流程跑通然后演示词性标注、命名实体识别、情感分析这几个典型场景最后分享一些我实际使用中踩过的坑和调试经验。1. 入门NLP前先搞清楚NLTK和Spacy这对老搭档到底差在哪很多教程会直接把NLTK和Spacy放在一起讲但如果你不理解两者的设计哲学差异后面写代码会非常混乱。简单说NLTK更像一个语言学研究工具包而Spacy更像一个工业化文本处理引擎。1.1 两者的血缘和设计思路完全不同NLTKNatural Language Toolkit起源于上世纪90年代末的教学场景它由宾夕法尼亚大学等机构的研究者推动目的是给语言学和计算机科学的学生提供一个可以拆开看的工具集。所以NLTK里你能看到非常多的算法实现细节比如它的分词、词性标注背后都暴露了内部规则字典你可以翻源码研究某条规则是怎么写的。这导致NLTK有两个鲜明特征一是功能非常全面从语料库加载到文本分类应有尽有二是性能相对偏慢某些接口在批量处理时明显吃力。Spacy则诞生于2015年左右它的定位从一开始就是让NLP技术进入真实产品。Spacy的核心工作流是pipeline流水线输入一段文本后自动完成分词、词性标注、依存句法分析、命名实体识别等一系列步骤并且用Cython做了底层优化处理速度通常比NLTK快一个数量级。对工程师来说这一点太重要了——我在处理几万条新闻数据时用Spacy跑完整个pipeline只需要几十秒同样的任务用NLTK要等好几分钟。1.2 一句话总结选择策略维度NLTKSpacy擅长的场景教学、算法研究、快速原型验证、语料处理生产环境、批量文本处理、上线部署处理速度较慢快Cython优化学习曲线平缓概念清晰稍陡强调整体pipeline模型风格规则统计混合透明可查预训练统计模型开箱即用分词方式基于正则和规则可自定义基于统计模型规则语言适配好词性标注有Perceptron Tagger等有更快的tagger组件命名实体识别支持但相对基础内置且效果很好常用数据格式Python字符串、NLTK语料库对象Doc对象token序列如果你要处理的数据量在几千条以内、核心需求是学习算法原理或者写一个作业Demo用NLTK就够了。但如果你的文本量上了万级或者要对接后续的机器学习流程我建议直接用Spacy打底。当然更完整的入门路径是先用NLTK理解NLP的基本概念再用Spacy做工程落地。这也是为什么这两个库总被放在一起讲的原因。2. 环境准备装NLTK和Spacy时最容易卡住的几个细节先说明一下以下操作默认你已经有Python环境3.8以上版本均可。如果你完全是个新手建议先装好Anaconda或者从Python官网装一个干净的Python解释器再跟着操作。2.1 用虚拟环境隔离依赖是第一步我见过太多人把各种库直接装进全局Python环境结果不同项目之间依赖冲突改这个坏那个。建议每个NLP项目都独立建一个虚拟环境python -m venv nlp_env source nlp_env/bin/activate # Windows下是 nlp_env\Scripts\activate激活后终端前面会出现(nlp_env)前缀后面所有包都装在这个环境里互不干扰。2.2 安装NLTK和Spacypip install nltk pip install spacy如果网络状况不佳导致安装很慢可以在pip命令后面追加国内镜像源这是常规操作pip install nltk -i https://pypi.tuna.tsinghua.edu.cn/simple pip install spacy -i https://pypi.tuna.tsinghua.edu.cn/simple装完之后建议顺手把pip本身升级一下避免后续安装依赖时出现版本解析问题pip install --upgrade pip2.3 NLTK数据包下载卡住的问题NLTK有个让新手头皮发麻的机制很多功能分词、停用词、语料库依赖它自己的数据包必须用nltk.download()下载。但官方服务器在国外国内用户经常遇到点了Download没有反应或者下载到一半报错的情况。这里我推荐一个更可控的方法——手动下载数据包。先用一行命令查看NLTK的数据路径import nltk print(nltk.data.path)输出类似[/home/username/nltk_data, /usr/share/nltk_data, /usr/local/share/nltk_data, /usr/share/nltk_data, /usr/lib/nltk_data, /usr/local/lib/nltk_data]NLTK会依次在这些目录里查找数据包。你只需要把数据解压到任意一个目录即可。以stopwords为例在能正常访问外网的网络环境下从NLTK官方仓库下载stopwords.zip对应的GitHub路径为 nltk_data/packages/corpora/stopwords.zip这是一个公开的数据仓库我用的是其发布包。把zip解压后将stopwords文件夹放到上面的某个路径下比如/home/username/nltk_data/corpora/。然后你执行如下代码就不会报错了from nltk.corpus import stopwords print(stopwords.words(english)[:10])如果你不想手动去下载也可以找一个网络比较稳定的时间段再试nltk.download()但手动放置数据包是我实测最稳妥的方式。另外有个实用小技巧在nltk.download()里指定download_dir参数把数据下载到当前项目的./nltk_data目录然后在代码里把这个目录追加到nltk.data.pathimport nltk nltk.data.path.append(./nltk_data)这样能让整个项目的数据依赖都在本地部署到服务器时也方便迁移。2.4 Spacy英文模型的下载Spacy本身不包含语言模型需要单独下载。以英文为例python -m spacy download en_core_web_smen_core_web_sm是Spacy最小的英文模型包含了词向量tokens vectors、词性标注器、依存句法分析器、命名实体识别器等组件适合学习和中小规模应用。如果你处理的数据量比较大可以换成en_core_web_md或者en_core_web_lg模型越大识别准确率通常越高但内存占用和加载时间也会明显上升。下载完成后在代码里通过下列方式加载import spacy nlp spacy.load(en_core_web_sm)Spacy模型下载同样有网络问题如果你发现下载很慢可以试试把代理相关的问题排除掉检查自己的网络状况。也可以从官方发布渠道手动下载模型包再用spacy.load指定本地路径但这属于进阶操作新手先用download命令就好。3. 核心实践一从原始文本到干净词元NLTK和Spacy的完整预处理流程真正的NLP项目里你拿到的原始文本往往非常脏有大小写混杂、标点符号、URL、数字、多余的空白字符。预处理的目的就是把文本转换成干净的、可供后续算法使用的token序列。我以两段示例文本为例演示NLTK和Spacy在其中的用法。3.1 分词word_tokenize和Spacy Tokenizer分词Tokenization是把连续文本切分成一个个词元的过程。NLTK的分词接口如下import nltk from nltk.tokenize import word_tokenize text I love NLP! Its amazing, isnt it? tokens word_tokenize(text) print(tokens)输出[I, love, NLP, !, It, s, amazing, ,, is, nt, it, ?]可以看到NLTK会把Its拆成It和s把isnt拆成is和nt这是英文缩写的标准处理方式。从结果里你也能看到NLTK默认把标点符号也作为独立token保留所以后面往往还需要过滤标点。Spacy的做法更接近语言学视角import spacy nlp spacy.load(en_core_web_sm) doc nlp(I love NLP! Its amazing, isnt it?) tokens [token.text for token in doc] print(tokens)输出[I, love, NLP, !, It, s, amazing, ,, is, nt, it, ?]单看分词结果两者在这个例子上几乎一致。实际操作中Spacy的tokenizer对特殊字符、URL、emoji的处理要稳健一些。而且Spacy的token对象自带很多属性比如token.is_alpha、token.is_stop、token.lemma_后面会用到。3.2 停用词过滤为什么要做以及两种风格停用词stopwords指对语义贡献很小的高频词比如英文里的 the、a、is、and。在文本分类或关键词提取任务里过滤停用词能显著降低噪音。但注意如果你在做人名识别、情感分析等任务停用词不一定都要删像 not 这种词在情感判断里就很关键。NLTK的停用词库非常经典from nltk.corpus import stopwords from nltk.tokenize import word_tokenize text This is a simple example to demonstrate stopword removal. tokens word_tokenize(text) stop_words set(stopwords.words(english)) filtered [w for w in tokens if w.lower() not in stop_words] print(filtered)输出[simple, example, demonstrate, stopword, removal, .]is、a、to这些词都被过滤掉了。Spacy则自带stop words属性不用单独下载语料库import spacy nlp spacy.load(en_core_web_sm) doc nlp(This is a simple example to demonstrate stopword removal.) filtered [token.text for token in doc if not token.is_stop] print(filtered)输出[simple, example, demonstrate, stopword, removal, .]输出结果几乎一样。这里有个小差异NLTK的stopwords.words(english)是纯小写集合而Spacy的token.is_stop会自动对大小写做归一化判断所以写起来更省事。3.3 词形还原 vs 词干提取别把它们混为一谈这是入门必踩的坑。词干提取Stemming是粗暴地把单词后缀去掉比如running变成run但studies可能被变成studi词形还原Lemmatization则根据词典和词性把单词还原成基本形式比如studies变成studybetter变成good。NLTK里面两个都有from nltk.stem import PorterStemmer, WordNetLemmatizer from nltk.tokenize import word_tokenize stemmer PorterStemmer() lemmatizer WordNetLemmatizer() words word_tokenize(The cats are studying their studies better than before.) for w in words: print(f{w:12s} - stem: {stemmer.stem(w):12s} lemma: {lemmatizer.lemmatize(w)})输出The - stem: the lemma: The cats - stem: cat lemma: cat are - stem: are lemma: are studying - stem: studi lemma: studying their - stem: their lemma: their studies - stem: studi lemma: study better - stem: better lemma: better before - stem: before lemma: before .看到了吗studying在词干提取下变成了 studi失去语义而词形还原保留了studying因为没有上下文时它默认是动词但不知道原形better没有被还原成good因为lemmatize默认按名词处理。要得到更好的词形还原效果需要传入词性参数print(lemmatizer.lemmatize(studying, posv)) # 输出 study print(lemmatizer.lemmatize(better, posa)) # 输出 goodNLTK里用词形还原需要自己指定词性这就凸显了预处理流程的每个环节都要自己操心。这也是Spacy的优势所在它在分词的同時就自动完成了词形还原不需要你单独调用。import spacy nlp spacy.load(en_core_web_sm) doc nlp(The cats are studying their studies better than before.) for token in doc: print(f{token.text:12s} - lemma: {token.lemma_:12s} pos: {token.pos_})输出关键几行studying - lemma: study pos: VERB studies - lemma: study pos: NOUN better - lemma: well pos: ADVSpacy的lemma已经根据上下文分析好了词性直接给出正确结果。这就是它pipeline一体化设计的价值所在。3.4 写一个通用预处理函数把上面的内容整合成一个可复用的函数日常处理英文文本基本够用import re import spacy from nltk.tokenize import word_tokenize from nltk.corpus import stopwords nlp spacy.load(en_core_web_sm, disable[ner, parser]) def clean_text_with_spacy(text): doc nlp(text) tokens [] for token in doc: if token.is_alpha and not token.is_stop: tokens.append(token.lemma_.lower()) return tokens def clean_text_with_nltk(text): tokens word_tokenize(text.lower()) stop_words set(stopwords.words(english)) tokens [re.sub(r[^a-z], , t) for t in tokens if t.isalpha()] tokens [t for t in tokens if t not in stop_words] return tokensclean_text_with_spacy里的disable[ner, parser]是先关掉不需要的组件以提升速度后面我会专门讲这个优化点。预处理作为NLP的地基你回头看会发现后面所有模型和算法都吃这一口的质量。4. 核心实践二词性标注与命名实体识别让机器理解谁在做什么预处理只是把文本切碎让程序真正读懂文本的环节是词性标注POS Tagging和命名实体识别NER。我拿一段新闻报道风格的文本做演示看看这两个库各能挖出什么信息。text Apple Inc. CEO Tim Cook announced the new iPhone 15 in California yesterday.4.1 词性标注的API差异NLTK里需要通过两步完成先分词再做词性标注。import nltk from nltk.tokenize import word_tokenize tokens word_tokenize(text) tagged nltk.pos_tag(tokens) for word, tag in tagged: print(f{word:10s} - {tag})输出部分Apple - NNP Inc. - NNP CEO - NNP Tim - NNP Cook - NNP announced - VBD the - DT new - JJ iPhone - NNP 15 - CD California - NNPNNP表示专有名词VBD表示动词过去式DT是限定词JJ是形容词CD是基数词。这套标签体系来自宾州树库Penn Treebank是NLP入门必须熟悉的标准标签集之一。Spacy的词性标注输出更丰富它同时提供细粒度标签token.tag_和粗粒度标签token.pos_。import spacy nlp spacy.load(en_core_web_sm) doc nlp(text) for token in doc: print(f{token.text:10s} - pos: {token.pos_:6s} tag: {token.tag_:6s} dep: {token.dep_})输出部分Apple - pos: PROPN tag: NNP dep: nsubj Inc. - pos: PROPN tag: NNP dep: appos CEO - pos: PROPN tag: NNP dep: appos Tim - pos: PROPN tag: NNP dep: appos Cook - pos: PROPN tag: NNP dep: nsubj announced - pos: VERB tag: VBD dep: ROOT the - pos: DET tag: DT dep: det new - pos: ADJ tag: JJ dep: amod iPhone - pos: PROPN tag: NNP dep: compound 15 - pos: NUM tag: CD dep: nummod California - pos: PROPN tag: NNP dep: obl注意一个细节这里CEO被标记成了PROPN专有名词因为在这个上下文里它是Tim Cook的职位头衔并被视为命名实体的一部分NLTK也识别为NNP。不同模型对这类词的判定有细微差异工程上要注意这一点。dep是依存句法标签比如nsubj表示名词性主语ROOT是句子的核心动词。这一层信息对语义理解非常有价值。4.2 用Spacy抽取命名实体命名实体识别是抽出文本里的专有名词并分类成公司、人名、地点、日期等。Spacy的NER组件开箱即用import spacy nlp spacy.load(en_core_web_sm) doc nlp(Apple Inc. CEO Tim Cook announced the new iPhone 15 in California yesterday.) for ent in doc.ents: print(f实体: {ent.text:15s} | 类型: {ent.label_:10s} | 说明: {spacy.explain(ent.label_)})输出实体: Apple Inc. | 类型: ORG | 说明: Companies, agencies, institutions, etc. 实体: Tim Cook | 类型: PERSON | 说明: People, including fictional 实体: iPhone 15 | 类型: PRODUCT | 说明: Objects, vehicles, foods, etc. (not services) 实体: California | 类型: GPE | 说明: Countries, cities, states 实体: yesterday | 类型: DATE | 说明: Absolute or relative dates or periods这个识别结果基本就是产品新闻的关键信息骨架了。如果你要做新闻标签提取或情报分析这一步能节省大量人工。NLTK也提供NER功能但它是基于分类器的黑盒而且效果远不如Spacy。下面的代码可以演示但通常不推荐在生产中使用import nltk from nltk.tokenize import word_tokenize tokens word_tokenize(text) tagged nltk.pos_tag(tokens) # 需要nltk.classify.maxent等模块实现流程繁琐且效果不稳定说句实话NLTK的NER在很多数据集上准确率明显偏低这也是NLP社区公认的。所以我的建议是只要涉及NER优先用Spacy。4.3 一个容易踩的坑大小写和缩写对NER的干扰用Spacy处理apple小写的水果和Apple公司时for text in [apple is delicious, Apple released a new phone]: doc nlp(text) print(text, -, [(ent.text, ent.label_) for ent in doc.ents])输出apple is delicious - [] Apple released a new phone - [(Apple, ORG)]大小写直接影响NER的判断。所以如果你要识别的是非规范文本比如社交媒体评论里大量小写拼写可能需要先做文本规范化的处理否则漏召回会非常严重。缩写同理比如US和us在Spacy里是不同的处理路径。5. 实战项目用NLTK和Spacy做一个简单的情感分析文本相似度工具光讲API太干了我来搭一个迷你实战项目。假设我们收到了一批电商平台上的英文商品评论需要做两件事判断每条评论的情绪倾向并找出和某个种子评论最相似的其他评论。5.1 用NLTK的VADER做情感打分VADERValence Aware Dictionary and sEntiment Reasoner是NLTK里一个专门面向社交媒体文本的情感分析工具最大的特点是快、不需要训练、对表情符号和网络用语也有不错的敏感度。from nltk.sentiment import SentimentIntensityAnalyzer sid SentimentIntensityAnalyzer() reviews [ This product is absolutely amazing! I love it., The delivery was too slow, but the quality is good., Worst purchase ever. The item broke in two days., ] for review in reviews: scores sid.polarity_scores(review) print(f评论: {review}) print(f得分: {scores})输出评论: This product is absolutely amazing! I love it. 得分: {neg: 0.0, neu: 0.226, pos: 0.774, compound: 0.8302} 评论: The delivery was too slow, but the quality is good. 得分: {neg: 0.235, neu: 0.765, pos: 0.118, compound: -0.3612} 评论: Worst purchase ever. The item broke in two days. 得分: {neg: 0.428, neu: 0.572, pos: 0.0, compound: -0.7717}compound是从 -1极负到 1极正的综合分数。对第二条评论虽然出现了 good但因为 too slow 的负面权重更高整体被判为负这个判断我认为是合理的。VADER 的妙处在于它完全基于词典和规则不用训练对中小规模的社交媒体文本非常实用。5.2 用Spacy算文本相似度Spacy的预训练词向量支持doc.similarity()接口底层原理是把文档中token的向量做平均再计算余弦相似度。import spacy # 使用带词向量的模型en_core_web_md或lg nlp spacy.load(en_core_web_md) review1 nlp(This product is absolutely amazing.) review2 nlp(I love this product so much.) review3 nlp(The package arrived broken and dirty.) sim_12 review1.similarity(review2) sim_13 review1.similarity(review3) print(f评论1和评论2的相似度: {sim_12:.4f}) print(f评论1和评论3的相似度: {sim_13:.4f})如果模型正常加载上面代码的输出会稳定在合理区间。运行后大概是这样评论1和评论2的相似度: 0.8512 评论1和评论3的相似度: 0.6743从语义上看两条正面评论的相似度确实高于正负对比。但必须提醒一点en_core_web_sm这个最小模型不包含真正的词向量它的similarity是基于上下文敏感张量context-sensitive tensors算的结果往往不太好。如果要做相似度计算至少用en_core_web_md模型下载命令就是之前提到的python -m spacy download en_core_web_md。5.3 把两个库配合起来一个完整的处理流程实际项目中通常不会只用一个库。以我的评论分析场景为例我会这样分工用Spacy做分词、词形还原、过滤停用词得到干净的token序列。用NLTK的VADER给每条评论打分。用Spacy的相似度找出同类评论。把最终结果输出成结构化表格如Pandas DataFrame。示意代码如下import spacy import pandas as pd from nltk.sentiment import SentimentIntensityAnalyzer nlp spacy.load(en_core_web_md) sid SentimentIntensityAnalyzer() def process_reviews(reviews): results [] for review in reviews: doc nlp(review) tokens [token.lemma_.lower() for token in doc if not token.is_stop and token.is_alpha] scores sid.polarity_scores(review) results.append({ text: review, clean_tokens: .join(tokens), compound: scores[compound], }) return pd.DataFrame(results) reviews [ This product is absolutely amazing! I love it., The delivery was too slow, but the quality is good., Worst purchase ever. The item broke in two days., ] df process_reviews(reviews) print(df)这样你就得到了一个可以直接下一步分析的结构化数据。这个组合策略我用了很久最大的感受是不要梦想一个库解决所有问题把每个库用在最适合它的地方才是工程正道。6. 运行中常见的报错排查与性能优化经验这里整理了我在课程答疑和生产环境中被问得最多的几个问题按出现频率排序。6.1 LookupError: Resource [93mstopwords[0m not found这是NLTK的经典报错。LookupError: ********************************************************************** Resource stopwords not found. Please use the NLTK Downloader to obtain the resource:原因就是第2.3节说的数据包没装好。解决方案有三种手动下载数据包并放到nltk.data.path里的任意目录。用nltk.download(stopwords, download_dir/你的路径)下载到指定目录。在代码里提前nltk.data.path.append(...)指向自定义数据目录。还有一个变种是加载punkt分词器时报同样错处理方法完全一样。6.2 OSError: [E050] Cant find model en_core_web_sm这是Spacy的经典报错OSError: [E050] Cant find model en_core_web_sm.意思是Spacy库装好了但语言模型没下载或者模型没被Spacy找到。解决办法python -m spacy download en_core_web_sm如果网络不稳定可以手动下载模型压缩包然后用nlp spacy.load(/本地路径/en_core_web_sm)6.3 加载Spacy模型时内存占用过大en_core_web_lg模型加载后可能占用几百MB内存如果你的服务是部署在云服务器上的可能会被OOM killer杀掉。解决办法很简单——只加载你需要的pipeline组件# 只保留标签器tagger和实体识别器ner nlp spacy.load(en_core_web_md, disable[parser]) # 或者只做词向量 nlp spacy.load(en_core_web_md, disable[tagger, parser, ner])通过disable参数可以显著降低内存占用和推理时间。我在处理纯文本分类任务时通常会关掉parser和ner速度能提升将近一倍。6.4 处理大批量文本时的性能建议如果你要在几万条文本上跑Spacy pipeline不要一条条nlp(text)循环调用改用nlp.pipe()批处理接口import spacy nlp spacy.load(en_core_web_md) texts [Your first text here., Another text here., ...] * 1000 # 推荐批处理 docs list(nlp.pipe(texts, batch_size50)) # 不推荐循环处理 # docs [nlp(t) for t in texts]nlp.pipe内部会做多线程/多进程优化取决于版本处理速度有非常明显的提升。另外如果你并不需要句法树等中间结果每次只用doc.ents或token.lemma_记得把不需要的组件disable掉。6.5 文本编码与中文内容的小提醒NLTK和Spacy都支持中文但Spacy的中文模型zh_core_web_sm使用前也需要单独下载。不过这篇里的示例都以英文为主因为NLP入门阶段用英文学习POS、NER这些概念会清晰很多。中文分词、词性标注的难点与英文完全不同比如没有空格分词边界以后有机会我可以单独写一篇中文NLP的实操内容。7. 一些个人习惯和经验之谈最后分享几个我自己的使用习惯算是给刚入坑的同学打个样。我在处理NLP任务时始终遵循一个原则从最小可行模型开始不要一上来就追求大而全。拿到一批文本数据先写几十行代码用NLTK或Spacy跑通一个简单版本把结果打印出来肉眼检查一下再决定要不要引入更重的模型。很多时候你发现VADER就能达到80%的效果那就不用花一周时间去微调BERT了。另外无论做哪一步处理我都会把中间结果保存下来。比如分完词之后先存一份CSV做完实体识别再存一份JSON。这样即使后面某一步代码改了前面的结果也还在可以对比差异调试效率会高很多。这算不上什么高深技巧但确实帮我省了不少时间。还有一点值得提醒NLTK和Spacy这类传统工具处理的是文本的显式信息它们不知道iPhone 15的实际口碑也判断不了一个产品评论里的讽刺语气。当你需要更深层的语义理解时才应该考虑BERT、GPT等大模型。所以入门NLP的正确路径并不是学完NLTK和Spacy就结束了而是先用它们打好基础和工程习惯再往深度学习方向扩展时会轻松很多。这两者之间是递进关系不是替代关系。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门