机器读不懂人话?用NLP自然语言处理让AI理解文本
关键词:NLP、自然语言处理、分词、词向量、BERT、文本分类
目录
- 一、NLP技术体系全景
- [1.1 什么是自然语言处理?](#1.1 什么是自然语言处理? "#11-%E4%BB%80%E4%B9%88%E6%98%AF%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86")
- [1.2 NLP技术分层架构](#1.2 NLP技术分层架构 "#12-nlp%E6%8A%80%E6%9C%AF%E5%88%86%E5%B1%82%E6%9E%B6%E6%9E%84")
- 二、中文分词:让机器认识"词"的边界
- [2.1 为什么中文需要分词?](#2.1 为什么中文需要分词? "#21-%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%AD%E6%96%87%E9%9C%80%E8%A6%81%E5%88%86%E8%AF%8D")
- [2.2 分词工具实战](#2.2 分词工具实战 "#22-%E5%88%86%E8%AF%8D%E5%B7%A5%E5%85%B7%E5%AE%9E%E6%88%98")
- 三、词性标注与命名实体识别
- [3.1 词性标注:给每个词贴"功能标签"](#3.1 词性标注:给每个词贴"功能标签" "#31-%E8%AF%8D%E6%80%A7%E6%A0%87%E6%B3%A8%E7%BB%99%E6%AF%8F%E4%B8%AA%E8%AF%8D%E8%B4%B4%E5%8A%9F%E8%83%BD%E6%A0%87%E7%AD%BE")
- [3.2 命名实体识别:找出文本中的关键信息](#3.2 命名实体识别:找出文本中的关键信息 "#32-%E5%91%BD%E5%90%8D%E5%AE%9E%E4%BD%93%E8%AF%86%E5%88%AB%E6%89%BE%E5%87%BA%E6%96%87%E6%9C%AC%E4%B8%AD%E7%9A%84%E5%85%B3%E9%94%AE%E4%BF%A1%E6%81%AF")
- 四、词向量:从One-Hot到BERT的演进之路
- [4.1 One-Hot编码的致命缺陷](#4.1 One-Hot编码的致命缺陷 "#41-one-hot%E7%BC%96%E7%A0%81%E7%9A%84%E8%87%B4%E5%91%BD%E7%BC%BA%E9%99%B7")
- [4.2 Word2Vec:稠密词向量的里程碑](#4.2 Word2Vec:稠密词向量的里程碑 "#42-word2vec%E7%A8%A0%E5%AF%86%E8%AF%8D%E5%90%91%E9%87%8F%E7%9A%84%E9%87%8C%E7%A8%8B%E7%A2%91")
- [4.3 BERT:双向预训练的革命](#4.3 BERT:双向预训练的革命 "#43-bert%E5%8F%8C%E5%90%91%E9%A2%84%E8%AE%AD%E7%BB%83%E7%9A%84%E9%9D%A9%E5%91%BD")
- 五、文本分类实战:情感分析项目
- [5.1 项目需求与数据准备](#5.1 项目需求与数据准备 "#51-%E9%A1%B9%E7%9B%AE%E9%9C%80%E6%B1%82%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%87%86%E5%A4%87")
- [5.2 构建分类模型](#5.2 构建分类模型 "#52-%E6%9E%84%E5%BB%BA%E5%88%86%E7%B1%BB%E6%A8%A1%E5%9E%8B")
- [5.3 模型训练与评估](#5.3 模型训练与评估 "#53-%E6%A8%A1%E5%9E%8B%E8%AE%AD%E7%BB%83%E4%B8%8E%E8%AF%84%E4%BC%B0")
- 常见问题
- 和AI大模型开发的关系
- 总结
一、NLP技术体系全景
1.1 什么是自然语言处理?
自然语言处理(Natural Language Processing,简称NLP)是人工智能的核心分支之一,致力于让计算机能够理解、生成和处理人类语言。
人类每天产生海量文本数据:社交媒体动态、客服对话记录、新闻报道、产品评论、合同文档......如果只能靠人工阅读分析,效率极低且容易遗漏关键信息。NLP技术的价值就在于:将非结构化的文本转化为机器可理解的结构化数据,从而支撑智能客服、舆情监控、内容审核、机器翻译等实际应用。
NLP的核心挑战在于人类语言的复杂性:
- 歧义性:"苹果"可以指水果,也可以指科技公司
- 上下文依赖:"他打了一场比赛"和"他打了一个电话",同一个"打"含义完全不同
- 隐含信息:"这家餐厅不错"可能是正面评价,也可能是委婉的负面评价(取决于语境)
1.2 NLP技术分层架构
图一:NLP技术体系全景

(图一:NLP技术体系从基础层到应用层逐层递进,基础层处理语言单元,核心层完成语义理解,应用层面向实际业务场景)
NLP技术体系通常分为三个层次:
基础层:处理语言的基本单元
- 分词:将连续文本切分为词语(中文特有需求)
- 词性标注:识别每个词的语法角色(名词、动词、形容词等)
- 句法分析:分析句子中词语之间的语法关系
- 语义理解:理解词语和句子的真实含义
核心层:完成具体的NLP任务
- 文本分类:将文本归类到预定义类别(如新闻分类、垃圾邮件识别)
- 情感分析:判断文本表达的情感倾向(正面/负面/中性)
- 命名实体识别:从文本中提取人名、地名、机构名等关键实体
- 关系抽取:识别实体之间的语义关系
应用层:面向实际业务场景
- 机器翻译:自动将一种语言翻译为另一种语言
- 文本生成:自动撰写报告、摘要、对话回复
- 问答系统:理解用户问题并给出准确答案
二、中文分词:让机器认识"词"的边界
2.1 为什么中文需要分词?
英文等拉丁语系语言中,单词之间天然有空格分隔,计算机可以直接识别词语边界。但中文是连续字符序列,没有显式的词边界标记。
例如:"自然语言处理技术应用于智能客服系统"
这句话对人类来说很容易理解,但计算机看到的是:
自 然 语 言 处 理 技 术 应 用 于 智 能 客 服 系 统
如果不分词,计算机无法知道"自然语言"是一个词还是"自然"+"语言"两个词,更无法理解整句话的含义。
分词的核心目标:将连续的中文字符序列切分为有意义的词语单元,为后续的NLP任务奠定基础。
2.2 分词工具实战
图二:中文分词流程示意

(图二:中文分词将连续字符序列切分为词语单元,并可进一步进行词性标注,为后续NLP任务提供结构化输入)
目前主流的中文分词工具包括:
| 工具 | 特点 | 适用场景 |
|---|---|---|
| jieba | 轻量级、易上手、支持自定义词典 | 快速原型开发、中小型项目 |
| HanLP | 功能全面、支持多种NLP任务 | 企业级应用、学术研究 |
| LTP | 哈工大出品、分词准确率高 | 中文信息处理、学术研究 |
| spaCy | 多语言支持、工业级性能 | 多语言项目、生产环境 |
下面用 jieba 演示分词和词性标注的完整流程:
python
import jieba
import jieba.posseg as pseg
# ========== 基础分词 ==========
text = "自然语言处理技术广泛应用于智能客服系统"
# 精确模式(推荐):最准确的切分方式
words_precise = jieba.cut(text, cut_all=False)
print("精确模式分词:", " / ".join(words_precise))
# 输出: 自然语言 / 处理 / 技术 / 广泛 / 应用 / 于 / 智能 / 客服 / 系统
# 全模式:扫描所有可能的词语
words_full = jieba.cut(text, cut_all=True)
print("全模式分词:", " / ".join(words_full))
# 输出: 自然 / 自然语言 / 语言 / 处理 / 技术 / 广泛 / 应用 / 于 / 智能 / 客服 / 系统
# 搜索引擎模式:在精确模式基础上对长词再次切分
words_search = jieba.cut_for_search(text)
print("搜索引擎模式:", " / ".join(words_search))
# ========== 词性标注 ==========
# 使用 posseg 模块同时进行分词和词性标注
words_with_pos = pseg.cut(text)
print("\n词性标注结果:")
for word, flag in words_with_pos:
print(f" {word:6s} -> {flag}")
# 输出示例:
# 自然语言 -> nz (其他专名)
# 处理 -> v (动词)
# 技术 -> n (名词)
# 广泛 -> a (形容词)
# 应用 -> v (动词)
# 于 -> p (介词)
# 智能 -> a (形容词)
# 客服 -> n (名词)
# 系统 -> n (名词)
# ========== 自定义词典 ==========
# 对于领域专有名词,可以添加自定义词典提高分词准确率
jieba.add_word("RAG检索增强")
jieba.add_word("LangChain框架")
custom_text = "RAG检索增强和LangChain框架是大模型应用的核心技术"
print("\n添加自定义词典后:")
print("分词结果:", " / ".join(jieba.cut(custom_text)))
# 输出: RAG检索增强 / 和 / LangChain框架 / 是 / 大模型 / 应用 / 的 / 核心 / 技术
分词常见问题与解决方案:
python
# 问题1:新词识别不准(如"大语言模型"被切分为"大/语言/模型")
# 解决:添加自定义词典或使用支持新词发现的模式
jieba.add_word("大语言模型")
jieba.add_word("提示词工程")
# 问题2:领域专有名词切分错误
# 解决:加载领域词典
jieba.load_userdict("domain_dict.txt") # 每行一个词
# 问题3:歧义切分(如"南京市长江大桥")
# 解决:使用更高级的分词工具如HanLP
import hanlp
tokenizer = hanlp.load(hanlp.pretrained.tokenizers.COARSE_ELECTRA_SMALL_ZH)
result = tokenizer("南京市长江大桥")
print(result) # ['南京市', '长江大桥']
三、词性标注与命名实体识别
3.1 词性标注:给每个词贴"功能标签"
词性标注(Part-of-Speech Tagging,POS Tagging)的目标是为文本中的每个词语分配一个词性标签,如名词、动词、形容词等。
词性标注的价值在于:
- 理解句子结构:知道哪些是主语、谓语、宾语
- 消歧:同一个词在不同语境下词性不同(如"打"可以是动词"打球",也可以是量词"一打")
- 支撑下游任务:句法分析、信息抽取等任务都依赖词性标注结果
python
import jieba.posseg as pseg
# 词性标注示例
text = "北京大学的学生在图书馆学习自然语言处理课程"
words = pseg.cut(text)
print("词性标注详细结果:")
print(f"{'词语':<8} {'词性':<5} {'说明'}")
print("-" * 30)
# 常见词性标签对照
pos_explain = {
'n': '名词', 'v': '动词', 'a': '形容词', 'd': '副词',
'p': '介词', 'c': '连词', 'r': '代词', 'm': '数词',
'q': '量词', 'u': '助词', 'f': '方位词', 's': '处所词',
't': '时间词', 'nr': '人名', 'ns': '地名', 'nt': '机构名',
'nz': '其他专名'
}
for word, flag in words:
explain = pos_explain.get(flag, '其他')
print(f"{word:<8} {flag:<5} ({explain})")
# 输出:
# 词语 词性 说明
# ------------------------------
# 北京大学 ns (地名)
# 的 u (助词)
# 学生 n (名词)
# 在 p (介词)
# 图书馆 n (名词)
# 学习 v (动词)
# 自然语言处理 nz (其他专名)
# 课程 n (名词)
3.2 命名实体识别:找出文本中的关键信息
命名实体识别(Named Entity Recognition,NER)是NLP中的核心任务之一,目标是从文本中识别出具有特定意义的实体,如人名、地名、机构名、时间、数字等。
python
import jieba.posseg as pseg
def extract_entities(text):
"""从文本中提取命名实体"""
words = pseg.cut(text)
entities = {
'人名': [],
'地名': [],
'机构名': [],
'时间': [],
'其他专名': []
}
# 词性标签到实体类型的映射
entity_map = {
'nr': '人名',
'ns': '地名',
'nt': '机构名',
't': '时间',
'nz': '其他专名'
}
for word, flag in words:
if flag in entity_map:
entities[entity_map[flag]].append(word)
return entities
# 测试
text = "2024年3月,阿里巴巴在杭州发布了最新的大语言模型通义千问," \
"该模型在自然语言处理领域取得了重大突破。"
entities = extract_entities(text)
print("命名实体识别结果:")
for entity_type, words in entities.items():
if words:
print(f" {entity_type}: {', '.join(words)}")
# 输出:
# 时间: 2024年, 3月
# 机构名: 阿里巴巴
# 地名: 杭州
# 其他专名: 大语言模型, 通义千问, 自然语言处理
进阶:使用深度学习模型进行NER
对于更复杂的实体识别场景,可以使用预训练模型:
python
from transformers import pipeline
# 使用预训练的中文NER模型
ner_pipeline = pipeline(
"token-classification",
model="uer/roberta-base-finetuned-jd-binary-chinese",
tokenizer="uer/roberta-base-finetuned-jd-binary-chinese"
)
text = "张三在北京的阿里巴巴总部参加了技术峰会"
results = ner_pipeline(text)
# 提取实体
entities = {}
current_entity = ""
current_type = ""
for result in results:
word = result['word']
entity_type = result['entity']
if entity_type.startswith('B-'): # 实体开始
if current_entity:
entities[current_type] = entities.get(current_type, []) + [current_entity]
current_type = entity_type[2:] # 去掉"B-"前缀
current_entity = word.replace('##', '')
elif entity_type.startswith('I-'): # 实体继续
current_entity += word.replace('##', '')
if current_entity:
entities[current_type] = entities.get(current_type, []) + [current_entity]
print("深度学习NER结果:", entities)
四、词向量:从One-Hot到BERT的演进之路
4.1 One-Hot编码的致命缺陷
在深度学习兴起之前,NLP领域最常用的文本表示方法是One-Hot编码。
One-Hot编码的原理很简单:假设词表大小为10000,每个词用一个10000维的向量表示,该词对应的位置为1,其余位置为0。
python
import numpy as np
# 假设词表:{"我": 0, "喜欢": 1, "自然": 2, "语言": 3, "处理": 4}
vocab = {"我": 0, "喜欢": 1, "自然": 2, "语言": 3, "处理": 4}
vocab_size = len(vocab)
def one_hot_encode(word, vocab):
"""将词语转换为One-Hot向量"""
vector = np.zeros(vocab_size)
vector[vocab[word]] = 1
return vector
# 示例
print("One-Hot编码示例:")
for word in ["我", "喜欢", "自然", "语言", "处理"]:
vector = one_hot_encode(word, vocab)
print(f" {word}: {vector}")
# 输出:
# 我: [1. 0. 0. 0. 0.]
# 喜欢: [0. 1. 0. 0. 0.]
# 自然: [0. 0. 1. 0. 0.]
# 语言: [0. 0. 0. 1. 0.]
# 处理: [0. 0. 0. 0. 1.]
One-Hot编码的三大缺陷:
- 维度灾难:词表越大,向量维度越高。工业级词表通常有几十万甚至上百万词,向量维度极高
- 稀疏性:每个向量只有一个位置为1,其余全为0,信息密度极低
- 无法表达语义相似度:"猫"和"狗"的One-Hot向量正交(点积为0),但语义上它们很相似;"猫"和"桌子"同样正交,但语义完全不同
python
# 语义相似度问题演示
cat_vector = one_hot_encode("自然", vocab)
dog_vector = one_hot_encode("语言", vocab)
desk_vector = one_hot_encode("处理", vocab)
# 计算余弦相似度
from sklearn.metrics.pairwise import cosine_similarity
sim_cat_dog = cosine_similarity([cat_vector], [dog_vector])[0][0]
sim_cat_desk = cosine_similarity([cat_vector], [desk_vector])[0][0]
print(f"\n语义相似度(One-Hot):")
print(f" '自然' vs '语言': {sim_cat_dog:.4f}") # 0.0000(正交)
print(f" '自然' vs '处理': {sim_cat_desk:.4f}") # 0.0000(正交)
# 问题:无法区分语义相似和语义不同的词对!
4.2 Word2Vec:稠密词向量的里程碑
2013年,Google研究员Mikolov提出了Word2Vec模型,彻底改变了词向量表示方式。
图三:词向量技术演进路线

(图三:词向量技术从One-Hot稀疏编码演进到Word2Vec稠密向量,再到BERT双向预训练,最终进入LLM时代)
Word2Vec的核心思想:用上下文预测目标词(或反之),在训练过程中学习词的稠密向量表示。
Word2Vec包含两种架构:
图四:Word2Vec CBOW vs Skip-gram对比

(图四:CBOW用上下文预测目标词,适合小规模数据;Skip-gram用目标词预测上下文,适合大规模数据和罕见词)
| 架构 | 原理 | 适用场景 |
|---|---|---|
| CBOW(连续词袋模型) | 用上下文词语预测目标词 | 训练速度快,适合小规模数据 |
| Skip-gram(跳字模型) | 用目标词预测上下文词语 | 对罕见词效果好,适合大规模数据 |
Word2Vec实战示例:
python
import gensim
from gensim.models import Word2Vec
# 准备训练语料(模拟AI技术文档)
corpus = [
["自然语言", "处理", "是", "人工智能", "的", "核心", "技术"],
["深度", "学习", "模型", "在", "图像", "识别", "中", "表现", "优异"],
["大", "语言", "模型", "如", "GPT", "和", "BERT", "推动", "了", "NLP", "发展"],
["RAG", "检索", "增强", "生成", "技术", "结合", "了", "向量", "数据库", "和", "大模型"],
["提示词", "工程", "是", "优化", "大模型", "输出", "质量", "的", "关键", "方法"],
["Transformer", "架构", "通过", "自", "注意力", "机制", "捕获", "长距离", "依赖"],
]
# 训练Word2Vec模型
model = Word2Vec(
sentences=corpus,
vector_size=50, # 词向量维度
window=3, # 上下文窗口大小
min_count=1, # 最小词频
workers=4, # 并行线程数
sg=0 # 0=CBOW, 1=Skip-gram
)
# 查看词向量
print("词向量示例:")
print(f" '自然语言' 的向量维度: {model.wv['自然语言'].shape}")
print(f" '自然语言' 的向量(前5维): {model.wv['自然语言'][:5]}")
# 计算词相似度
print("\n词相似度计算:")
similar_words = model.wv.most_similar("自然语言", topn=3)
for word, score in similar_words:
print(f" {word}: {score:.4f}")
# 词类比推理(经典测试)
# "国王" - "男人" + "女人" ≈ "女王"
try:
analogy = model.wv.most_similar(
positive=["学习", "模型"],
negative=["深度"],
topn=3
)
print("\n词类比推理:")
for word, score in analogy:
print(f" {word}: {score:.4f}")
except KeyError as e:
print(f"\n词类比推理需要更大的语料库,当前语料不足")
# 保存和加载模型
model.save("word2vec_ai_model.model")
loaded_model = Word2Vec.load("word2vec_ai_model.model")
Word2Vec的优势:
- 向量维度大幅降低(通常50-300维),解决了维度灾难
- 语义相似的词在向量空间中距离更近
- 支持词相似度计算和类比推理
Word2Vec的局限:
- 静态词向量:同一个词在不同语境下向量相同,无法处理一词多义
- 局部上下文:只考虑固定窗口内的上下文,无法捕获长距离依赖
4.3 BERT:双向预训练的革命
2018年,Google提出了BERT(Bidirectional Encoder Representations from Transformers)模型,开启了NLP的预训练时代。
图五:BERT双向编码结构

(图五:BERT通过多层Transformer编码器实现双向上下文感知,每个token的表示同时融合了左右两侧的信息)
BERT的核心创新:
- 双向上下文:传统语言模型只能从左到右(或从右到左)单向建模,BERT通过**Masked Language Modeling(MLM)**实现真正的双向编码
- Transformer架构:使用自注意力机制捕获任意距离的依赖关系
- 预训练+微调范式:先在大规模无标注语料上预训练,再在下游任务上微调
BERT预训练的两个任务:
python
# BERT预训练任务1:Masked Language Modeling(掩码语言建模)
# 随机掩盖输入序列中15%的token,让模型预测被掩盖的词
input_text = "自然[MASK]处理技术广泛应用于智能客服"
# 模型需要预测 [MASK] 位置应该是 "语言"
# BERT预训练任务2:Next Sentence Prediction(下一句预测)
# 给定两个句子A和B,判断B是否是A的下一句
sentence_a = "自然语言处理是人工智能的核心技术"
sentence_b = "它包括分词、词性标注、命名实体识别等任务"
# 标签:IsNext(是下一句)
sentence_c = "深度学习在图像识别领域也取得了巨大进展"
# 标签:NotNext(不是下一句)
使用Hugging Face Transformers调用BERT:
python
from transformers import AutoTokenizer, AutoModel
import torch
# 加载预训练的中文BERT模型
model_name = "bert-base-chinese"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
# 输入文本
text = "自然语言处理技术让机器理解人类语言"
# 分词并转换为模型输入
inputs = tokenizer(
text,
return_tensors="pt", # 返回PyTorch张量
padding=True, # 填充到批次最大长度
truncation=True, # 截断超长序列
max_length=512 # BERT最大序列长度
)
print("Tokenization结果:")
print(f" 输入IDs: {inputs['input_ids']}")
print(f" 注意力掩码: {inputs['attention_mask']}")
print(f" Token序列: {tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])}")
# 前向传播获取隐藏状态
with torch.no_grad():
outputs = model(**inputs)
# outputs.last_hidden_state 形状: [batch_size, sequence_length, hidden_size]
# 对于bert-base-chinese,hidden_size = 768
hidden_states = outputs.last_hidden_state
print(f"\n隐藏状态形状: {hidden_states.shape}")
# 输出: torch.Size([1, 15, 768])
# 1个样本,15个token,每个token的表示维度为768
# 获取[CLS] token的表示(通常用于句子级任务)
cls_embedding = hidden_states[:, 0, :]
print(f"[CLS]向量维度: {cls_embedding.shape}")
# 输出: torch.Size([1, 768])
BERT vs Word2Vec 对比:
| 特性 | Word2Vec | BERT |
|---|---|---|
| 向量类型 | 静态(一词一向量) | 动态(上下文感知) |
| 上下文 | 局部窗口 | 全局双向 |
| 一词多义 | 无法处理 | 可以处理 |
| 训练方式 | 从 scratch 训练 | 预训练+微调 |
| 计算资源 | 轻量级 | 需要GPU |
| 适用场景 | 词相似度、简单分类 | 复杂NLP任务、问答、推理 |
五、文本分类实战:情感分析项目
5.1 项目需求与数据准备
场景 :某电商平台需要自动分析用户评论的情感倾向,将评论分为正面 、负面 和中性三类,用于产品口碑监控和客服优先级排序。
python
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report, confusion_matrix
import jieba
# ========== 模拟电商评论数据 ==========
# 实际项目中应从数据库或CSV文件加载
reviews_data = [
# 正面评论
("这款AI音箱的语音识别非常准确,响应速度也很快,强烈推荐!", "正面"),
("客服态度很好,问题解决得很及时,购物体验很棒", "正面"),
("产品质量超出预期,包装精美,物流也很快", "正面"),
("大语言模型的对话能力令人惊叹,几乎像真人一样", "正面"),
("RAG技术让搜索结果更精准,用户体验提升明显", "正面"),
("界面设计简洁美观,操作流畅,五星好评", "正面"),
("智能客服回答专业,帮我快速解决了问题", "正面"),
("这款学习机的AI辅导功能很实用,孩子成绩提高了", "正面"),
# 负面评论
("语音识别准确率太低了,经常听不懂我说的话", "负面"),
("客服态度恶劣,问题拖了一周都没解决", "负面"),
("收到货就发现屏幕有划痕,质量太差", "负面"),
("大模型生成的内容经常出错,不可靠", "负面"),
("搜索功能形同虚设,根本找不到想要的东西", "负面"),
("APP经常崩溃,体验极差,已卸载", "负面"),
("智能客服答非所问,完全是人工智障", "负面"),
("学习机用了一周就坏了,售后也不管", "负面"),
# 中性评论
("产品还行吧,中规中矩,没什么特别的", "中性"),
("客服回复速度一般,问题最终解决了", "中性"),
("物流速度正常,包装一般,产品符合描述", "中性"),
("大模型的能力有限,有些问题回答不了", "中性"),
("搜索功能基本可用,但结果不够精准", "中性"),
("APP偶尔卡顿,但不影响正常使用", "中性"),
("智能客服能解决简单问题,复杂的还得转人工", "中性"),
("学习机功能一般,性价比不高", "中性"),
]
# 转换为DataFrame
df = pd.DataFrame(reviews_data, columns=["review", "sentiment"])
print("数据集概览:")
print(df.head(10))
print(f"\n数据集大小: {len(df)} 条")
print(f"类别分布:\n{df['sentiment'].value_counts()}")
# ========== 文本预处理 ==========
def preprocess_text(text):
"""中文文本预处理:分词 + 去除停用词"""
# 分词
words = jieba.cut(text)
# 常见停用词列表
stop_words = {
"的", "了", "在", "是", "我", "有", "和", "就", "不", "人",
"都", "一", "一个", "上", "也", "很", "到", "说", "要", "去",
"你", "会", "着", "没有", "看", "好", "自己", "这", "那", "着"
}
# 过滤停用词和单字
filtered_words = [
word for word in words
if word not in stop_words and len(word) > 1
]
return " ".join(filtered_words)
# 应用预处理
df["processed_review"] = df["review"].apply(preprocess_text)
print("\n预处理示例:")
for i in range(3):
print(f" 原文: {df['review'].iloc[i]}")
print(f" 处理后: {df['processed_review'].iloc[i]}")
print()
# ========== 划分训练集和测试集 ==========
X = df["processed_review"]
y = df["sentiment"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y
)
print(f"训练集大小: {len(X_train)}")
print(f"测试集大小: {len(X_test)}")
5.2 构建分类模型
python
# ========== 方法1:TF-IDF + 朴素贝叶斯(基线模型) ==========
# TF-IDF特征提取
tfidf = TfidfVectorizer(
max_features=5000, # 最大特征数
ngram_range=(1, 2), # 使用unigram和bigram
min_df=2, # 最小文档频率
max_df=0.95 # 最大文档频率(过滤过于常见的词)
)
X_train_tfidf = tfidf.fit_transform(X_train)
X_test_tfidf = tfidf.transform(X_test)
print(f"TF-IDF特征维度: {X_train_tfidf.shape}")
# 训练朴素贝叶斯分类器
nb_classifier = MultinomialNB(alpha=0.1) # alpha为平滑参数
nb_classifier.fit(X_train_tfidf, y_train)
# 预测
y_pred_nb = nb_classifier.predict(X_test_tfidf)
print("\n=== 朴素贝叶斯分类结果 ===")
print(classification_report(y_test, y_pred_nb))
# ========== 方法2:使用预训练模型(进阶方案) ==========
# 实际项目中推荐使用BERT等预训练模型获得更高准确率
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from transformers import TrainingArguments, Trainer
import torch
# 加载预训练的中文分类模型
model_name = "hfl/chinese-macbert-base"
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 标签映射
label2id = {"负面": 0, "中性": 1, "正面": 2}
id2label = {v: k for k, v in label2id.items()}
# 数据编码函数
def encode_data(texts, labels=None):
"""将文本编码为模型输入格式"""
encodings = tokenizer(
texts.tolist(),
truncation=True,
padding=True,
max_length=128,
return_tensors="pt"
)
if labels is not None:
encodings["labels"] = torch.tensor([label2id[l] for l in labels])
return encodings
# 编码训练集和测试集
train_encodings = encode_data(X_train, y_train)
test_encodings = encode_data(X_test, y_test)
# 定义数据集类
class SentimentDataset(torch.utils.data.Dataset):
def __init__(self, encodings):
self.encodings = encodings
def __getitem__(self, idx):
return {key: val[idx] for key, val in self.encodings.items()}
def __len__(self):
return len(self.encodings["input_ids"])
train_dataset = SentimentDataset(train_encodings)
test_dataset = SentimentDataset(test_encodings)
# 加载预训练模型(用于序列分类)
model = AutoModelForSequenceClassification.from_pretrained(
model_name,
num_labels=3,
id2label=id2label,
label2id=label2id
)
# 定义评估函数
def compute_metrics(eval_pred):
from sklearn.metrics import accuracy_score, f1_score
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return {
"accuracy": accuracy_score(labels, predictions),
"f1": f1_score(labels, predictions, average="weighted")
}
# 训练参数
training_args = TrainingArguments(
output_dir="./sentiment_model",
num_train_epochs=3,
per_device_train_batch_size=8,
per_device_eval_batch_size=16,
warmup_steps=50,
weight_decay=0.01,
logging_dir="./logs",
logging_steps=10,
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
metric_for_best_model="f1",
)
# 创建Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=test_dataset,
compute_metrics=compute_metrics,
)
# 开始训练(实际运行时取消注释)
# trainer.train()
# 保存模型
# trainer.save_model("./sentiment_model_final")
# tokenizer.save_pretrained("./sentiment_model_final")
print("\n预训练模型方案已配置完成")
print("调用 trainer.train() 即可开始训练")
5.3 模型训练与评估
python
# ========== 模型评估与部署 ==========
# 使用训练好的朴素贝叶斯模型进行预测示例
def predict_sentiment(review_text, classifier, vectorizer):
"""预测单条评论的情感倾向"""
# 预处理
processed = preprocess_text(review_text)
# 特征提取
features = vectorizer.transform([processed])
# 预测
prediction = classifier.predict(features)[0]
probabilities = classifier.predict_proba(features)[0]
return prediction, probabilities
# 测试新评论
new_reviews = [
"这款产品的AI功能太强大了,完全超出我的预期!",
"客服态度很差,问题一直没人解决,太失望了",
"产品一般般,没什么特别的,勉强能用",
"大模型的对话能力确实不错,但偶尔会胡说八道",
]
print("\n=== 新评论情感预测 ===")
for review in new_reviews:
sentiment, probs = predict_sentiment(review, nb_classifier, tfidf)
print(f"\n评论: {review}")
print(f"预测情感: {sentiment}")
print(f"各类别概率: ", end="")
for label, prob in zip(nb_classifier.classes_, probs):
print(f"{label}={prob:.2%} ", end="")
print()
# ========== 模型部署(Flask API示例) ==========
"""
from flask import Flask, request, jsonify
import joblib
app = Flask(__name__)
# 加载训练好的模型
classifier = joblib.load("sentiment_classifier.pkl")
vectorizer = joblib.load("tfidf_vectorizer.pkl")
@app.route("/predict", methods=["POST"])
def predict():
data = request.get_json()
review = data.get("review", "")
sentiment, probs = predict_sentiment(review, classifier, vectorizer)
return jsonify({
"sentiment": sentiment,
"probabilities": {
label: float(prob)
for label, prob in zip(classifier.classes_, probs)
}
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
# 调用示例:
# curl -X POST http://localhost:5000/predict \
# -H "Content-Type: application/json" \
# -d '{"review": "这款产品非常好用"}'
"""
常见问题
Q1:分词工具选哪个?jieba还是HanLP?
答:取决于项目需求:
- 快速原型/中小型项目 :选
jieba,轻量级、API简单、社区活跃 - 企业级应用/高准确率要求 :选
HanLP,功能全面、支持多种NLP任务、准确率高 - 学术研究 :选
LTP(哈工大)或spaCy,可定制性强
实际项目中可以先用 jieba 快速验证,再根据需求升级到 HanLP。
Q2:词向量维度怎么选?越大越好吗?
答:不是越大越好,需要根据任务和数据量权衡:
| 场景 | 推荐维度 | 原因 |
|---|---|---|
| 小型数据集(<10万句) | 50-100 | 避免过拟合 |
| 中型数据集(10万-100万句) | 100-200 | 平衡表达力和泛化 |
| 大型数据集(>100万句) | 200-300 | 充分捕获语义信息 |
| 预训练模型(BERT等) | 768/1024 | 模型架构固定 |
维度太小会导致语义信息丢失,维度太大会增加计算开销且容易过拟合。
Q3:BERT模型太大,部署困难怎么办?
答:有以下几种方案:
- 使用轻量级变体 :如
DistilBERT(参数量减少40%,性能保持97%)、TinyBERT - 模型量化:将FP32权重量化为INT8,模型大小减少75%
- 知识蒸馏:用大模型训练小模型,小模型继承大模型的能力
- 按需加载:只加载任务相关的层,减少内存占用
python
# 使用轻量级DistilBERT示例
from transformers import DistilBertTokenizer, DistilBertModel
tokenizer = DistilBertTokenizer.from_pretrained("distilbert-base-chinese")
model = DistilBertModel.from_pretrained("distilbert-base-chinese")
# 参数量约66M,而BERT-base为110M
Q4:如何处理领域专有名词的分词问题?
答:三种常用方法:
python
import jieba
# 方法1:添加自定义词典(推荐)
jieba.add_word("RAG检索增强")
jieba.add_word("LangChain框架")
jieba.add_word("提示词工程")
# 方法2:加载领域词典文件
# domain_dict.txt 格式:每行一个词,可选词频和词性
# RAG检索增强 1000 n
# LangChain框架 1000 n
jieba.load_userdict("domain_dict.txt")
# 方法3:强制不切分(使用jieba.suggest_freq)
jieba.suggest_freq("大语言模型", tune=True)
和AI大模型开发的关系
NLP技术是AI大模型开发的基础能力,大模型的许多核心功能都建立在NLP技术之上。以下是NLP在大模型开发中的4个典型应用场景:
场景1:大模型输入预处理(分词+清洗)
大模型接收用户输入前,需要进行文本清洗和分词处理:
python
import jieba
import re
class LLMInputProcessor:
"""大模型输入预处理器"""
def __init__(self):
# 加载领域词典
jieba.add_word("RAG")
jieba.add_word("LangChain")
jieba.add_word("Coze")
jieba.add_word("Dify")
def clean_text(self, text):
"""清洗文本:去除特殊字符、标准化空格"""
# 去除HTML标签
text = re.sub(r'<[^>]+>', '', text)
# 去除多余空白
text = re.sub(r'\s+', ' ', text).strip()
return text
def tokenize(self, text):
"""分词并过滤停用词"""
stop_words = {"的", "了", "在", "是", "我", "有", "和"}
words = jieba.cut(text)
return [w for w in words if w not in stop_words and len(w) > 1]
def process(self, user_input):
"""完整处理流程"""
cleaned = self.clean_text(user_input)
tokens = self.tokenize(cleaned)
return {
"original": user_input,
"cleaned": cleaned,
"tokens": tokens,
"token_count": len(tokens)
}
# 使用示例
processor = LLMInputProcessor()
result = processor.process("请问RAG和LangChain有什么区别?")
print(f"原始输入: {result['original']}")
print(f"清洗后: {result['cleaned']}")
print(f"分词结果: {result['tokens']}")
print(f"Token数量: {result['token_count']}")
场景2:意图识别(文本分类)
智能客服需要先识别用户意图,再路由到对应的处理模块:
python
from transformers import pipeline
class IntentClassifier:
"""用户意图分类器"""
def __init__(self):
# 使用预训练的中文文本分类模型
self.classifier = pipeline(
"text-classification",
model="uer/roberta-base-finetuned-jd-binary-chinese"
)
# 意图映射(实际项目中根据业务定义)
self.intent_map = {
"product_query": "产品咨询",
"technical_support": "技术支持",
"billing": "账单问题",
"complaint": "投诉建议",
"general": "其他"
}
def classify(self, user_input):
"""识别用户意图"""
result = self.classifier(user_input)[0]
intent_code = result["label"]
confidence = result["score"]
return {
"intent": self.intent_map.get(intent_code, "其他"),
"intent_code": intent_code,
"confidence": confidence,
"routing": self._get_routing(intent_code)
}
def _get_routing(self, intent_code):
"""根据意图返回路由目标"""
routing_map = {
"product_query": "product_agent",
"technical_support": "tech_agent",
"billing": "billing_agent",
"complaint": "human_agent",
"general": "general_agent"
}
return routing_map.get(intent_code, "general_agent")
# 使用示例
classifier = IntentClassifier()
test_inputs = [
"这个AI音箱怎么连接WiFi?",
"我的订单还没收到,已经等了三天了",
"你们的产品价格太贵了",
]
for text in test_inputs:
result = classifier.classify(text)
print(f"\n输入: {text}")
print(f"意图: {result['intent']} (置信度: {result['confidence']:.2%})")
print(f"路由到: {result['routing']}")
场景3:实体抽取(信息提取)
从用户对话中提取关键实体,用于后续处理:
python
import jieba.posseg as pseg
class EntityExtractor:
"""对话实体抽取器"""
def __init__(self):
# 添加领域词典
jieba.add_word("大语言模型")
jieba.add_word("提示词工程")
jieba.add_word("RAG技术")
def extract(self, text):
"""从文本中提取实体"""
words = pseg.cut(text)
entities = {
"product": [], # 产品名
"feature": [], # 功能特性
"time": [], # 时间
"location": [], # 地点
"person": [], # 人名
}
# 词性到实体类型的映射
entity_map = {
"nr": "person", # 人名
"ns": "location", # 地名
"nt": "product", # 机构/产品名
"t": "time", # 时间
"nz": "feature", # 专有名词/特性
}
for word, flag in words:
if flag in entity_map:
entities[entity_map[flag]].append(word)
# 基于规则的额外抽取
# 抽取价格信息
import re
prices = re.findall(r'[\d]+元', text)
if prices:
entities["price"] = prices
return entities
# 使用示例
extractor = EntityExtractor()
dialogue = "我想在北京买一个大语言模型的开发课程,大概3000元左右"
entities = extractor.extract(dialogue)
print("实体抽取结果:")
for entity_type, values in entities.items():
if values:
print(f" {entity_type}: {values}")
# 输出:
# location: ['北京']
# product: ['大语言模型']
# feature: ['开发课程']
# price: ['3000元']
场景4:RAG系统中的文本处理
RAG(检索增强生成)系统需要对文档进行分块和向量化:
python
import jieba
from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np
class RAGTextProcessor:
"""RAG系统文本处理器"""
def __init__(self, chunk_size=200, chunk_overlap=50):
self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap
self.vectorizer = TfidfVectorizer(
tokenizer=jieba.lcut,
max_features=10000
)
def chunk_document(self, document):
"""将长文档切分为小块"""
# 按句子切分
sentences = re.split(r'[。!?;]', document)
sentences = [s.strip() for s in sentences if s.strip()]
chunks = []
current_chunk = []
current_length = 0
for sentence in sentences:
sentence_length = len(sentence)
if current_length + sentence_length > self.chunk_size:
# 当前块已满,保存并开启新块
chunks.append("。".join(current_chunk) + "。")
# 保留重叠部分
overlap_sentences = current_chunk[-self.chunk_overlap:]
current_chunk = overlap_sentences + [sentence]
current_length = sum(len(s) for s in current_chunk)
else:
current_chunk.append(sentence)
current_length += sentence_length
# 添加最后一个块
if current_chunk:
chunks.append("。".join(current_chunk) + "。")
return chunks
def build_index(self, documents):
"""构建向量索引"""
# 切分文档
all_chunks = []
for doc in documents:
all_chunks.extend(self.chunk_document(doc))
# 向量化
chunk_vectors = self.vectorizer.fit_transform(all_chunks)
return {
"chunks": all_chunks,
"vectors": chunk_vectors,
"chunk_count": len(all_chunks)
}
def search(self, query, index, top_k=3):
"""检索相关文档块"""
# 查询向量化
query_vector = self.vectorizer.transform([query])
# 计算相似度
from sklearn.metrics.pairwise import cosine_similarity
similarities = cosine_similarity(query_vector, index["vectors"])[0]
# 返回top-k结果
top_indices = np.argsort(similarities)[-top_k:][::-1]
results = []
for idx in top_indices:
results.append({
"chunk": index["chunks"][idx],
"similarity": float(similarities[idx])
})
return results
# 使用示例
import re
processor = RAGTextProcessor(chunk_size=150, chunk_overlap=30)
# 模拟文档
documents = [
"自然语言处理是人工智能的核心技术之一。它包括分词、词性标注、命名实体识别等基础任务。"
"现代NLP技术已经从传统的统计方法发展到深度学习时代。Word2Vec、BERT等模型大幅提升了语义理解能力。",
"RAG(检索增强生成)技术结合了向量检索和大语言模型。"
"它通过检索相关文档片段,为大模型提供上下文信息,从而生成更准确的回答。"
"RAG系统通常包含文档切分、向量化、检索和生成四个核心模块。"
]
# 构建索引
index = processor.build_index(documents)
print(f"文档切分结果: {index['chunk_count']} 个块")
# 检索
query = "什么是RAG技术?"
results = processor.search(query, index, top_k=2)
print(f"\n查询: {query}")
print("相关文档块:")
for i, result in enumerate(results, 1):
print(f" {i}. [相似度: {result['similarity']:.4f}] {result['chunk'][:50]}...")
总结
本文系统梳理了NLP自然语言处理的核心技术体系:
基础层:中文分词是NLP的基石,jieba、HanLP等工具让机器能够识别词语边界;词性标注和命名实体识别进一步为文本添加结构化信息。
表示层:词向量技术从One-Hot编码演进到Word2Vec稠密向量,再到BERT双向预训练模型,语义表达能力呈指数级提升。BERT的"预训练+微调"范式已成为现代NLP的标准做法。
应用层:文本分类、情感分析等任务将NLP技术落地到实际业务场景,支撑智能客服、舆情监控、内容审核等应用。
与大模型的关系:NLP是大模型开发的底层能力,大模型的输入预处理、意图识别、实体抽取、RAG检索等核心功能都建立在NLP技术之上。掌握NLP基础,是理解和使用大模型的前提。
#NLP #自然语言处理 #分词 #词向量 #BERT #文本分类 #情感分析 #AI大模型