自然语言处理——02 文本预处理(上)
1 认识文本预处理
-
概念:
- 文本语料在输送给模型前一般需要一系列的预处理工作,才能符合模型输入的要求;
- 比如:将文本转化成模型需要的张量、规范张量的尺寸;
- 比如:
- 关于数据X:数据有没有脏数据、数据长度分布情况等;
- 关于标签Y:分类问题查看标签是否均匀;
-
作用:
- 指导模型超参数的选择 、提升模型的评估指标;
- 大白话:文本预处理的工作,就是准备出模型需要的x、y,然后送给模型;
-
主要环节:
-
文本处理的基本方法(先拆分、标注文本)
- 分词:把长句子切成词语(比如“我喜欢自然语言处理”→“我/喜欢/自然语言/处理”);
- 词性标注:给词语贴标签(动词、名词等,比如“喜欢”是动词);
- 命名实体识别:找出人名、地名、机构名(比如“北京有个清华大学”→识别出“北京”是地名,“清华大学”是机构名);
-
文本张量表示方法(把文本转成模型能计算的数字)
- one-hot编码:用0和1表示词语,简单但容易“词多爆内存”;
- Word2vec/Word Embedding:让意思相近的词数字更像(比如“猫”和“狗”的向量比“猫”和“桌子”更接近),能抓语义关系;
-
文本语料的数据分析(检查数据质量、找规律)
- 统计标签数量、句子长度,画出词云(比如发现“垃圾邮件”标签太多/太少,或者句子太长模型难处理),帮你判断数据合不合理,需不需要调整;
-
文本特征处理(给模型加“额外信息”)
- 添加n-gram特征:不只看单个词,还看词语搭配(比如“机器学习”一起出现,比单独“机器”+“学习”信息更多);
- 规范文本长度:把句子统一成差不多长度(太短补一补,太长砍一砍),方便模型处理;
-
数据增强方法(数据不够?“造”点新数据)
- 回忆数据增强法(比如同义替换、回译):没太多数据时,把句子换种说法(“我很开心”→“我非常高兴”),让模型见更多样的表达,避免学偏。
-
2 文本处理的基本方法
2.1 分词
2.1.1 概述
-
分词:将连续的字序列按照一定的规范重新组合成词序列的过程;
-
作用:
- 词作为语言语义理解的最小单元,是人类理解文本语言的基础;
- 是AI解决NLP领域高阶任务,如自动问答、机器翻译、文本生成的重要基础环节;
-
英文是不需要分词的,因为英文中的空格是天然的分解符,而中文没有明显的分解符。
-
例:
腾讯是一家上市公司,旗下有微信、QQ等产品,我们平时主要通过微信联系。 腾讯/是/一家/上市公司/,/旗下/有/微信/、/QQ/等/产品/,/我们/平时/主要/通过/微信/联系/。
2.1.2 jieba分词器
-
jieba——目前流行的中文分词工具
import jieba
-
支持三种分词模式(分词粒度):
-
精确模式:默认分词模式,试图将文本精确切分,减少冗余和歧义,适用于文本分析、文本挖掘等需要精准词语划分的任务,调用方法为
jieba.cut(text, cut_all=False)
;cut_all=False
:关闭全模式,开启精确模式; -
全模式:会扫描出句子中所有可能成词的词语,分词速度快,但无法解决分词歧义问题,适用于需要尽可能多地提取词语,对准确性要求不高的场景 ,调用方法为
jieba.cut(text, cut_all=True)
; -
搜索引擎模式:在精确模式基础上,对长词再次进行切分,增加分词的粒度,提高召回率,适合用于搜索引擎构建索引、处理用户查询等场景,调用方法为
jieba.cut_for_search(text)
;
-
-
支持中文繁体分词,支持用户自定义词典;
-
-
jieba.cut
VSjieba.lcut
jieba.cut
:- 返回的是一个生成器对象。生成器是一种特殊的迭代器,它并不会一次性将所有分词结果加载到内存中,而是在迭代过程中按需生成。这样在处理大量文本时,能显著减少内存占用,提升程序运行效率;
- 适合处理文本量非常大的情况,比如处理大规模的文本语料库、流式读取文件中的文本并进行分词等,通过迭代方式逐个获取分词结果,能有效控制内存使用;
jieba.lcut
:- 返回的是一个列表,即一次性将所有分词结果以列表形式存储在内存中。在处理小量文本时,使用列表便于直接进行后续操作,如统计词频、筛选特定词语等;但处理大量文本时,若内存不足,可能导致程序崩溃;
- 适用于文本量较小,需要立即对所有分词结果进行统一操作的场景,如对一篇短文章进行分词后,马上统计词频、进行词性标注等操作,因为可以直接通过索引等方式访问列表中的元素;
-
用户自定义词典时,遵循以下格式:
词语 词频 词性
- 词语:希望 jieba 优先识别的自定义词汇(比如专业术语、新造词等)
- 词频:数字表示 “建议的分词优先级”(数值越小,越优先被识别),主要用于调整分词权重
- 词性:可选的词性标注(如
n
名词、nz
其他专名),jieba 支持自定义词性,也可忽略(但格式上需保留空格占位)
-
jieba词性对照表
a 形容词ad 副词ag 形容词性语素an 名形词 b 区别词 c 连词 d 副词dfdg 副语素 e 叹词 f 方位词 g 语素 h 前接成分 i 成语 j 简称略称 k 后接成分 l 习用语 m 数词mgmq 数量词 n 名词ng 名词性语素nr 人名nrfgnrtns 地名nt 机构团体名nz 其他专名 o 拟声词 p 介词 q 量词 r 代词rg 代词性语素rr 人称代词rz 指示代词 s 处所词 t 时间词tg 时语素u 助词ud 结构助词 得ug 时态助词uj 结构助词 的ul 时态助词 了uv 结构助词 地uz 时态助词 着 v 动词vd 副动词vg 动词性语素vi 不及物动词vn 名动词vq x 非语素词 y 语气词 z 状态词zg
2.1.3 例
-
例:三种分词模式
content = "腾讯是一家上市公司,旗下有微信、QQ等产品,我们平时主要通过微信联系。"
# 精确模式 myobj1 = jieba.cut(sentence=content, cut_all= False) print('myobj1-->', myobj1) mydata1 = jieba.lcut(sentence=content, cut_all=False) print('mydata1-->', mydata1) # 全模式 myobj2 = jieba.cut(sentence=content, cut_all=True) print('myobj2-->', myobj2) mydata2 = jieba.lcut(sentence=content, cut_all=True) print('mydata2-->', mydata2) # 搜索引擎模式 myobj3 = jieba.cut_for_search(sentence=content) print('myobj3-->', myobj3) mydata3 = jieba.lcut_for_search(sentence=content) print('mydata3-->', mydata3)
-
例:支持繁体分词
# 支持繁体分词 content = "煩惱即是菩提,我暫且不提" mydata = jieba.lcut(content) print('mydata-->', mydata)
-
例:支持用户自定义词典
# 支持用户自定义词典 # 不使用用户字典 content = "腾讯是一家上市公司,旗下有王者荣耀、和平精英等游戏,但还是要少玩游戏" mydata1 = jieba.lcut(sentence=content, cut_all=False) print('mydata1-->', mydata1)# 使用用户字典 """ 上市 王者荣耀 1 n 和平精英 2 n """ jieba.load_userdict('data/userdict.txt') mydata2 = jieba.lcut(sentence=content, cut_all=False) print('mydata2-->', mydata2)
2.2 命名实体识别
-
命名实体:将人名、地名、机构名等专有名词统称命名实体。如:周杰伦、黑山县、孔子学院、24辊方钢矫直机……
-
命名实体识别(Named Entity Recognition,简称NER),识别出一段文本中可能存在的命名实体;
-
命名实体也是人类理解文本的基础单元,是AI解决NLP领域高阶任务的重要基础环节;
-
例:
鲁迅,浙江绍兴人,五四新文化运动的重要参与者,代表作朝花夕拾。 鲁迅(人名) / 浙江绍兴(地名)人 / 五四新文化运动(专有名词) / 重要参与者 / 代表作 / 朝花夕拾(专有名词)
2.3 词性标注
-
词性:语言中对词的一种分类方法,以语法特征为主要依据、兼顾词汇意义对词进行划分的结果;
-
词性标注(Part-Of-Speech tagging,简称POS),标注出一段文本中每个词汇的词性;
-
词向标注作用:对文本语言的另一个角度的理解,AI解决NLP领域高阶任务的重要基础环节;
-
例:
我爱自然语言处理 我/rr,爱/v,自然语言/n,处理/vnrr:人称代词 v:动词 n:名词 vn:动名词
-
例:
# 词性标注 import jieba.posseg as pseg mydata1 = pseg.lcut("我爱北京天安门") print('mydata1-->', mydata1)
3 文本张量的表示方式
3.1 概念
-
文本张量表示:使用张量来表示一段文本,将计算机无法理解的文本转换成计算机可以计算的向量形式;
-
表示词的向量成为词向量,那么表示一句话就是一个词向量矩阵;
-
例:
["人生", "该", "如何", "起头"] # 上面每个词对应下面矩阵中的一个向量 [[1.32, 4.32, 0.32, 5.2], [3.1, 5.43, 0.34, 3.2], [3.21, 5.32, 2, 4.32], [2.54, 7.32, 5.12, 9.54]]
-
NLP中文本词向量表示的常用方法:
- One-Hot编码
- Word2vec
- Word Embedding
3.2 One-Hot编码
-
One-Hot编码(One-Hot Encoding),也叫稀疏词向量表示
- One-Hot编码是将分类变量转换为数字格式的常用方法,通常用于AI任务中处理分类标签y数据
- 在One-Hot编码中,对于一个具有n个不同类别的分类变量,将其表示为一个n维的向量。其中只有一个维度的值为1(代表该样本属于这个类别),其他维度的值均为0;
-
例:
["红色"、"绿色"、"蓝色"] [[1, 0, 0], [0, 1, 0], [0, 0, 1]]["人生", "该", "如何", "起头"] [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
-
例:One-Hot编码的生成
import jieba from tensorflow.keras.preprocessing.text import Tokenizer import joblib
pip install tensorflow
# One-Hot编码的生成 # 定义词汇表 vocabs = ["周杰伦", "陈奕迅", "王力宏", "李宗盛", "吴亦凡", "鹿晗"]# 创建Tokenizer对象,用于文本处理和编码转换 # Tokenizer是Keras中用于文本预处理的工具,能将文本转换为整数序列或one-hot编码 mytokenizer = Tokenizer() print('mytokenizer-->', mytokenizer)# 使用词汇表训练Tokenizer,构建词汇索引 # fit_on_texts()方法会遍历输入的文本列表,统计词频并生成词汇表 mytokenizer.fit_on_texts(vocabs) # 打印index_word属性:键是整数索引,值是对应的词语(索引从1开始) print('mytokenizer.index_word-->', mytokenizer.index_word) # 打印word_index属性:键是词语,值是对应的整数索引(与index_word互为反向映射) print('mytokenizer.word_index-->', mytokenizer.word_index)# 遍历词汇表中的每个词语,手动生成One-Hot编码 for vorcab in vocabs:# 创建一个全为0的列表,长度等于词汇表大小(即6个元素)zero_list = [0] * len(mytokenizer.index_word)# 获取当前词语在word_index中的索引,减1是为了对应列表的0开始索引idx = mytokenizer.word_index[vorcab] - 1# 将对应位置设为1,形成One-Hot编码(只有一个位置为1,其余为0)print(vorcab, '的Ont-Hot编码是:', zero_list)# 使用joblib保存训练好的Tokenizer对象到指定路径 # 保存后可在其他地方通过joblib.load()加载复用,避免重复训练 joblib.dump(value=mytokenizer, filename='data/mytokenizer' ) print('保存mytokenizer END')
-
例:One-Hot编码的使用
mytokenizer = joblib.load('data/mytokenizer') # 定义要进行One-Hot编码的目标词语 token1 = "李宗盛" # 创建一个全为0的列表,长度与词汇表(vocabs)的元素数量一致 zero_list1 = [0] * len(vocabs) # 检查词汇是否在Tokenizer的词汇表中 if token1 in mytokenizer.word_index: # 通过加载好的Tokenizer,获取目标词语对应的索引,再减1转换为列表的0起始下标# mytokenizer.word_index 是一个字典,键是词语,值是该词语在Tokenizer中的索引(从1开始)idx1 = mytokenizer.word_index[token1] - 1# 将对应下标的位置置为1,完成One-Hot编码(只有对应位置为1,其余为0)zero_list1[idx1] = 1print(token1, '的One-Hot编码是:', zero_list1) else:print(f"{token1}不在词汇表中")token2 = "狗蛋儿" zero_list2 = [0] * len(vocabs) if token2 in mytokenizer.word_index: idx2 = mytokenizer.word_index[token2] - 1zero_list2[idx2] = 1print(token2, '的One-Hot编码是:', zero_list2) else:print(f"{token2}不在词汇表中")
-
优点:操作简单,容易理解;
-
缺点:完全割裂了词与词之间的联系;如果在大语料集下,每个向量的长度过大,会占据大量内存;属于稀疏词向量表示;
-
正因为One-Hot编码隔离了词和词的联系,又易浪费内存空间,所以出现了稠密向量的表示方法:
- Word2vec
- Word Embedding
3.3 Word2vec模型
3.3.1 概述
-
Word2vec模型是一种将单词转换为词向量的自然语言处理技术;
- 是利用深度学习网络挖掘单词间语义关系,用网络里的权重参数表示词向量(比如“苹果”和“水果”的向量,在语义空间里距离近);
- 是在无监督的语料(互联网大量文本,没人手动标标签)上构建了一个有监督的任务(预测单词这种带目标的事情),底成本利用海量数据;
-
Word2Vec有两种训练词向量方式:
- **CBOW(Continuous Bag of Words)**方式训练词向量
- 试图根据上下文中的周围单词来预测当前单词,即用周围词预测中间词
- 它将周围单词的词向量求和或取平均作为上下文的表示,然后通过一个神经网络进行预测
- Skip-Gram方式训练词向量
- 试图根据当前单词来预测上下文中的周围单词,即用中间词预测周围词
- 它将当前单词的词向量作为输入,然后通过一个神经网络来预测周围单词
- **CBOW(Continuous Bag of Words)**方式训练词向量
3.3.2 CBOW方式
-
已知数据:有5个字母构成的文本序列“abcdeaaeddccbadae …”,其中a、b、c、d、e的One-Hot表示为见下表
字母 One-Hot表示 a 1 0 0 0 0 b 0 1 0 0 0 c 0 0 1 0 0 d 0 0 0 1 0 e 0 0 0 0 1 -
需求:
- 构建神经网络,输入层数据要求5个特征,隐藏层有3个神经元,输出层5个神经元;
- 使用已知数据训练这个神经网络;
- 用隐藏层的权重参数充当 a、b、c、d、 e 这5个单词的词向量;
-
需求分析:
-
两个问题:
- 已知文本序列,如何构建神经网络,来探索 a、b、c、d、 e 这5个单词之间的语义关系?
- a、b、c、d、 e 这5个单词之间的语义关系,又如何保存到神经网络中?
-
思路:
- 数据流:输入数据5个特征,经过隐藏层变成3个特征,经过输出层变成5个特征;
- 数据形状:m*5 >> m*3 >> m*5(m是样本数量),即降维又升维;
- 隐藏层参数矩阵
w
为:5*3(5是输入维度,3是隐藏层维度)、输出层参数矩阵w'
:3*5(3是隐藏层维度,5是输出维度); - 训练过程:数据经过前向传播得到预测值
ŷ
,预测值ŷ
与真实值y比较得到损失,通过反向传播可以更新权重矩阵w
和w'
参数;
-
-
CBOW 核心是用周围单词预测中间单词,过程如下:
- 构建样本
- 引入滑动窗口(此处规定滑动窗口大小为3)切分文本序列,构建样本。比如:
- 样本1:x(a, c),y (b)。中间词是 b ,周围词就是 a 和 c
- 样本2:x(b, d),y ©。中间词是 c ,周围词就是 b 和 d
- ……
- 将x中的两个字母的One-Hot编码拼在一起(或求和平均)当作输入,将y的One-Hot编码当作真实值;
- 引入滑动窗口(此处规定滑动窗口大小为3)切分文本序列,构建样本。比如:
- 神经网络前向传播(降维+升维)
- 降维(参数矩阵
w
):- 输入 x(a 和 c 的One-Hot编码拼在一起或者通过求和平均后得到的值)维度是 5 ,通过参数矩阵
w(5*3维度,5是输入维度,3是隐藏层维度)
,把 5 维 “拍扁” 成 3 维隐藏层输出; - 这一步叫 “降维”,把高维、语义孤立的One-Hot,转成低维、带语义关联的向量(隐藏层输出就是 “词向量雏形”);
- 输入 x(a 和 c 的One-Hot编码拼在一起或者通过求和平均后得到的值)维度是 5 ,通过参数矩阵
- 升维(参数矩阵
w'
):- 隐藏层 3 维输出,再通过参数矩阵
w’(3*5,3是隐藏层维度,5是输出维度)
,升回 5 维,得到预测值ŷ
; - 这一步是 “升维”,让模型能输出和真实值同维度的预测结果,方便计算误差;
- 隐藏层 3 维输出,再通过参数矩阵
- 降维(参数矩阵
- 计算损失(Loss):拿预测值
ŷ
(模型猜的b^
)和真实值y
(实际的b
)比,用损失函数算差距。差距越大,Loss 越高,模型越 “差”; - 反向传播(更新
w
和w'
):根据 Loss 反向调整参数矩阵w
和w'
的数值,让下次预测更准;
- 构建样本
-
训练结束后,参数矩阵
w
就存着单词的词向量-
此处
w
是5*3维度(5是输入维度,3是隐藏层维度),每一列对应一个单词的词向量; -
比如对于字母 a 的词向量:
-
因为 a 的One-Hot是
[1,0,0,0,0]
,所以取w
的 “第 0 列”。假设w
矩阵长这样[w11, w12, w13] [w21, w22, w23] [w31, w32, w33] [w41, w42, w43] [w51, w52, w53]
-
那么 a 的词向量是
[w11, w21, w31, w41, w51]
;
-
-
这样,原本孤立的One-Hot,就通过训练,在
w
里变成了“语义相关”的低维向量。
-
3.3.3 Skip-Gram方式
-
CBOW 核心是用中间单词预测周围单词,过程如下:
-
构建样本
- 引入滑动窗口(此处规定滑动窗口大小为3)切分文本序列,构建样本。比如:
- 样本1:x(b),y (a, c)。中间词是 b ,周围词就是 a 和 c
- 样本2:x(c),y (b, d)。中间词是 c ,周围词就是 b 和 d
- ……
- 将x中的字母的One-Hot编码当作输入,将y的One-Hot编码当作真实值;
- 引入滑动窗口(此处规定滑动窗口大小为3)切分文本序列,构建样本。比如:
-
神经网络前向传播(降维+升维)
- 降维(参数矩阵
w
):- 输入 x(b 的 One-hot)维度是 5 ,通过参数矩阵
w(5*3维度,5是输入维度,3是隐藏层维度)
,把 5 维 “拍扁” 成 3 维隐藏层输出; - 这一步提取 b 的“语义特征”;
- 输入 x(b 的 One-hot)维度是 5 ,通过参数矩阵
- 升维(参数矩阵
w'
):- 隐藏层 3 维输出,再通过参数矩阵
w’(3*5,3是隐藏层维度,5是输出维度)
,升回 5 维,得到预测值ŷ
; - 这一步让模型能输出和真实值同维度的结果,方便算误差;
- 隐藏层 3 维输出,再通过参数矩阵
- 降维(参数矩阵
-
计算损失(Loss):拿预测值
ŷ
(模型猜的a^和c^
)和真实值y
(实际的a和c
)比,用损失函数算差距。差距越大,Loss 越高,模型越 “差”; -
反向传播(更新
w
和w'
):根据 Loss 反向调整参数矩阵w
和w'
的数值,让下次预测更准;
-
-
训练结束后,参数矩阵
w
就存着单词的词向量-
此处
w
是5*3维度(5是输入维度,3是隐藏层维度),每一行对应一个单词的词向量; -
比如对于字母 b 的词向量:
-
因为 b 的One-Hot是
[0,1,0,0,0]
,所以取w
的 “第 1 行”。假设w
矩阵长这样[w11, w12, w13] [w21, w22, w23] [w31, w32, w33] [w41, w42, w43] [w51, w52, w53]
-
那么 b 的词向量是
[w21, w22, w23]
;
-
-
这样,原本孤立的One-Hot,就通过训练,在
w
里变成了“语义相关”的低维向量。
-
3.3.4 Word2vec模型的训练和使用
3.3.4.1 概述
-
FastText 工具包
-
背景:Facebook(现 Meta)开源的 NLP 工具;
-
功能:文本分类、训练词向量;
-
-
Tomas Mikolov 是 Word2Vec 作者,后来又搞了 FastText ,相当于**“Word2Vec 升级版/拓展版”**:
-
Word2Vec 主要聚焦“单词级”词向量训练;
-
FastText 加入“字符级”信息(比如把“apple”拆成“app”“ppl”“ple” ),对小语种、生僻词更适配,功能也更丰富(文本分类 + 词向量)。
-
-
不管用 Word2Vec 还是 FastText ,训练词向量都绕不开这些步骤:
- 获取训练数据
- 词向量的训练、保存、加载、查看
- 模型效果检验
- 模型超参数设定
3.3.4.2 获取训练数据
-
数据来源:
http://mattmahoney.net/dc/enwik9.zip
,这是英语维基百科的部分网页信息,大小在300M左右(解压后1个G); -
原始数据中包含 XML/HTML 格式的内容,这些内容并不是我们需要的,所以接下来对原始数据做一些处理;
-
编写
wikifil.py
脚本,用于清洗enwik9
文件:#!/usr/bin/env python3 # -*- coding: utf-8 -*-""" Python版Wikipedia XML清洗脚本功能与原Perl脚本(wikifil.pl)完全相同: 1. 只保留<text>...</text>之间的内容 2. 移除#REDIRECT页面 3. 清除所有XML/HTML标签 4. 转换URL编码字符 5. 移除参考文献、外部链接、图片标记等 6. 保留图像说明文字 7. 将链接转换为普通文本 8. 将数字拼写出来(如1→one) 9. 最终只保留小写字母和空格(无连续空格) """import re import sysdef clean_wiki_text():text_mode = Falsewith open("enwik9", "r", encoding="utf-8") as f:for line in f:# 检测是否进入<text>标签if "<text " in line:text_mode = True# 忽略#REDIRECT页面if "#redirect" in line.lower():text_mode = Falseif text_mode:# 检测是否离开<text>标签if "</text>" in line:text_mode = Falsecontinue# 移除XML标签line = re.sub(r'<.*?>', '', line)# 解码URL编码字符line = line.replace('&', '&')line = line.replace('<', '<')line = line.replace('>', '>')# 移除参考文献 <ref...>...</ref>line = re.sub(r'<ref[^<]*<\/ref>', '', line)# 移除XHTML标签line = re.sub(r'<[^>]*>', '', line)# 处理URL链接,保留可见文本line = re.sub(r'\[http:[^] ]*', '[', line)# 移除图片链接标记,保留说明文字line = re.sub(r'\|thumb', '', line, flags=re.IGNORECASE)line = re.sub(r'\|left', '', line, flags=re.IGNORECASE)line = re.sub(r'\|right', '', line, flags=re.IGNORECASE)line = re.sub(r'\|\d+px', '', line, flags=re.IGNORECASE)line = re.sub(r'\[\[image:[^\[\]]*\|', '', line, flags=re.IGNORECASE)# 简化分类标记line = re.sub(r'\[\[category:([^|\]]*)[^]]*\]\]', '[[\\1]]', line, flags=re.IGNORECASE)# 移除其他语言链接line = re.sub(r'\[\[[a-z\-]*:[^\]]*\]\]', '', line, flags=re.IGNORECASE)# 移除wiki URL,保留可见文本line = re.sub(r'\[\[[^\|\]]*\|', '[[', line)# 移除{{icons}}和{tables}line = re.sub(r'\{\{[^\}]*\}\}', '', line)line = re.sub(r'\{[^\}]*\}', '', line)# 移除方括号line = line.replace('[', '').replace(']', '')# 移除其他URL编码字符line = re.sub(r'&[^;]*;', ' ', line)# 转换为小写line = line.lower()# 将数字拼写出来line = line.replace('0', ' zero ')line = line.replace('1', ' one ')line = line.replace('2', ' two ')line = line.replace('3', ' three ')line = line.replace('4', ' four ')line = line.replace('5', ' five ')line = line.replace('6', ' six ')line = line.replace('7', ' seven ')line = line.replace('8', ' eight ')line = line.replace('9', ' nine ')# 移除非字母字符,合并连续空格line = re.sub(r'[^a-z]', ' ', line)line = re.sub(r' +', ' ', line).strip()if line:print(line)if __name__ == "__main__":clean_wiki_text()
-
将下面这两个文件放在同一个目录下:
-
打开 CMD 命令行,执行:
python wikifil.py > fil9
-
可以查看生成的
fil9
文件中的前 20 行内容:Get-Content fil9 -Head 20
3.3.4.3 词向量的训练、保存、加载、查看
-
安装 fasttext:安装的方式具体还是见GitHub - facebookresearch/fastText: Library for fast text representation and classification.;
- 可能涉及到C++17的编译、由于Python版本过高不支持安装等问题,下面只给出示例代码;
pip install fasttext
-
训练、保存、加载
def fasttext_train_save_load():# 训练词向量mymodel = fasttext.train_unsupervised('data/fil9', epoch=1)# 保存词向量mymodel.save_model("data/mymodel.bin")# 加载词向量mymodel = fasttext.load_model("data/mymodel.bin")
-
查看
# 查看词向量 def fasttext_get_word_vector():# 加载已经训练好的词向量模型mymodel = fasttext.load_model("data/mymodel.bin")# 查看词向量themyvector = mymodel.get_word_vector('the')print('myvector--->', type(myvector), myvector.shape, myvector)
3.3.4.4 模型效果检验
-
检查单词向量质量的一种简单方法就是查看其邻近单词,然后主观来判断这些邻近单词是否与目标单词相关,进而来粗略评定模型效果的好坏;
-
查看临近词:
# 查看临近词 def fasttext_get_nearest_neighbors():# 加载已经训练好的词向量模型mymodel = fasttext.load_model("data/mymodel.bin")# 查看词向量dog的临近词result = mymodel.get_nearest_neighbors('dog')print('result--->', result)
3.3.4.5 模型超参数设定
# 模型参数设定
def fasttext_parm():''' unsupervised_default = {'model': "skipgram", # 1 选择词向量的训练方式'lr': 0.05, # 2 学习率'dim': 100, # 3 词向量特征数'ws': 5,'epoch': 5, # 4 训练轮次'minCount': 5,'minCountLabel': 0,'minn': 3,'maxn': 6,'neg': 5,'wordNgrams': 1,'loss': "ns",'bucket': 2000000,'thread': multiprocessing.cpu_count() - 1, # 5 线程数'lrUpdateRate': 100,'t': 1e-4,'label': "__label__",'verbose': 2,'pretrainedVectors': "",'seed': 0,'autotuneValidationFile': "",'autotuneMetric': "f1",'autotunePredictions': 1,'autotuneDuration': 60 * 5, # 5 minutes'autotuneModelSize': ""}'''mymodel = fasttext.train_unsupervised('./data/fil9', epoch=1, model='cbow', lr=0.1, dim=300, thread=8)
3.3.5 CBOW VS Skip-Gram
- 原理:CBOW用周围单词预测当前单词;Skip - Gram凭当前单词预测周围单;
- 计算效率:CBOW因对多个上下文单词向量求和平均,计算高效;Skip - Gram要为每个单词生成上下文,训练速度慢、效率低;
- 数据需求:CBOW需更多训练数据汇总整体信息;Skip - Gram在大规模数据中处理低频词更优,能生成丰富上下文信息;
- 适用场景:CBOW适合训练数据大、高频词情况;Skip - Gram对低频词、复杂语义关系场景表现好,像大规模数据集中处理低频词。
3.4 Word Embedding
-
Word Embedding(词嵌入),通过一定的方式将词汇映射到指定维度(一般是更高维度)的空间;
- 广义:只要是将单词用向量来表示的方法,都算(比如 Word2Vec、GloVe 这些),不管用不用深度学习;
- 狭义:在深度神经网络里,专门加一个 “嵌入层”(比如 PyTorch 的
nn.Embedding
),用网络训练出向量;
-
Word2Vec方式产生词向量和Word Embedding(
nn. Embedding
)方式有何异同?- 相同点:都是将单词用向量来表示,让计算机能处理文本;
- 不同点:
- Word2Vec产生词向量后,某一个词向量比如单词“the”的词向量就固定下来了,是静态的;
nn.Embedding
层产生词向量后,词嵌入层作为整体神经网络的一部分,权重参数会参与更新,是动态的;- Word2Vec使用起来一般需要两步:输入单词"the"拿到词向量 >> 再送给神经网络进行使用;
nn.Embedding
层使用起来只有一步:直接嵌入到神经网络中。
4 使用tenserboard可视化嵌入的词向量
-
需求:
-
有下面两句话
腾讯是一家上市公司,旗下有王者荣耀、和平精英等游戏,但还是要少玩游戏 我爱自然语言处理
-
对这两句话分词,并完成文本数值化、数值张量化
-
然后对词向量进行可视化
-
import torch
from tensorflow.keras.preprocessing.text import Tokenizer
from torch.utils.tensorboard import SummaryWriter
import jieba
import torch.nn as nn
import tensorflow as tf
import tensorboard as tb
tf.io.gfile = tb.compat.tensorflow_stub.io.gfile
# 1 对句子分词,构建词列表word_list
# 定义两个示例句子
sentence1 = '腾讯是一家上市公司,旗下有王者荣耀、和平精英等游戏,但还是要少玩游戏'
sentence2 = '我爱自然语言处理'
sentences = [sentence1, sentence2]
word_list = []
# 用jieba对每个句子分词,结果存入word_list
for s in sentences:word_list.append(jieba.lcut(s))
print('word_list--->', word_list)
# 2 对句子分词列表word_list做词表构建,得到每个词的索引映射
mytokenizer = Tokenizer()
mytokenizer.fit_on_texts(word_list)
print('mytokenizer.index_word--->', mytokenizer.index_word)
print('mytokenizer.word_index--->', mytokenizer.word_index)
my_token_list = list(mytokenizer.index_word.values()) # 获取词列表,用于后续可视化
print('my_token_list--->', my_token_list)
# 3 创建nn.Embedding层,实现词嵌入
# num_embeddings是词表大小(不同词的数量),embedding_dim是词向量维度
embed = nn.Embedding(num_embeddings=len(mytokenizer.index_word), embedding_dim=8)
print('词嵌入层embed--->', embed)
# 设置打印精度,方便查看张量值
torch.set_printoptions(precision=4, sci_mode=False)
print('词嵌入层的矩阵参数(每个单词的词向量)embed--->', embed.weight.data)
# 4 创建SummaryWriter对象,可视化词向量
# 用于把词嵌入矩阵和词列表写入TensorBoard,方便可视化查看
summarywriter = SummaryWriter()
summarywriter.add_embedding(embed.weight.data, my_token_list)
summarywriter.close()
# 5 通过索引获取词向量
# 遍历词表索引,获取对应词向量并打印
for idx in range(len(mytokenizer.index_word)):tmp_vec = embed(torch.tensor([idx]))print('tmp_vec--->', tmp_vec.detach().numpy())
- 在程序的当前目录下运行:
tensorboard --logdir=runs --host 0.0.0.0
,再通过http://127.0.0.1:6006
访问。