本文认识文本预处理以及它的作用、以及文本预处理有哪些主要环节。文本预处理是指在将文本数据用于深度学习模型训练之前,对文本数据进行一系列的清洗、转换和处理操作,以消除非结构化文本的噪声和不必要的信息,并将文本数据转化为适合模型训练的格式。总的来说,文本预处理的目的是将原始的非结构化文本数据转化为结构化的数值数据,从而提高模型的性能和准确度。
一、 文本处理的基本方法
假设我们要对以下句子进行分词:“今天的天气真好呀”。采用正向最大匹配算法,我们可以按照以下步骤进行分词:
- 确定最大分词长度。假设我们设定最大分词长度为3。
- 从左往右扫描句子,取出前3个字符“今天的”。
- 查找词库,发现“今天的”不是一个词,因此缩小分词长度,取出前2个字符“今天”。
- 查找词库,发现“今天”是一个单字词,因此将“今天”作为一个词。
- 继续扫描剩下的字符,取出前3个字符“的天气”。
- 查找词库,发现“的天气”不是一个词,因此缩小分词长度,取出前2个字符“的天”。
- 查找词库,发现“的天”不是一个词,因此缩小分词长度,取出前1个字符“的”。
- 查找词库,发现“的”是一个词,因此将“的”作为一个词。
最终的分词结果是:“今天/的/天气/真好/呀”。可以看到,这个过程会依赖一个词典,但是词典具有一定的缺点,尤其对于一些专有名词,比如“央视新闻客户端”,央视新闻是一个词,不能分成两个词网络名词,比如你耗子尾汁,会被分割成耗子,尾汁歧义分割,比如羽毛球拍卖完了,会被分割成,羽毛球,拍卖,完,了
1.1 Jieba 分词
Jieba提供了三种分词模式:
● 精确模式:试图将句子最精确地切开,适合文本分析。
● 全模式:把句子中所有可以成词的词语都扫描出来,速度非常快,但是不能解决歧义。
● 搜索引擎模式:在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
安装:pip install jieba
import jieba
sent = '南京市长江大桥'
seg_list = jieba.cut(sent, cut_all=True)
print('全模式:', '/ '.join(seg_list))
seg_list = jieba.cut(sent, cut_all=False)
print('精确模式:', '/ '.join(seg_list))
seg_list = jieba.cut(sent)
print('默认精确模式:', '/ '.join(seg_list))
seg_list = jieba.cut_for_search(sent)
print('搜索引擎模式', '/ '.join(seg_list))
输出结果:
全模式: 南京/ 南京市/ 京市/ 市长/ 长江/ 长江大桥/ 大桥
精确模式: 南京市/ 长江大桥
默认精确模式: 南京市/ 长江大桥
搜索引擎模式 南京/ 京市/ 南京市/ 长江/ 大桥/ 长江大桥
jieba支持用户自定义词典开发者可以指定自己自定义的词典,以便包含 jieba 词库里没有的词。虽然 jieba 有新词识别能力,但是自行添加新词可以保证更高的正确率。比如我们自定义如下的user_dict.txt
创新办 3 i
云计算 5 n
一个词占一行;每一行分三部分:词语、词频(可省略)、词性(可省略),用空格隔开,顺序不可颠倒。
import jieba
sent = '北上资金净流入200亿,同比增长15%,云计算板块涨的最多'
#先导入词典
jieba.load_userdict("./user_dict.txt")
seg_list = jieba.cut(sent)
print('精确模式:', '/ '.join(seg_list))
不加词典分词: 北上/ 资金/ 净流入/ 200/ 亿/ ,/ 同比/ 增长/ 15%/ ,/ 云/ 计算/ 板块/ 涨/ 的/ 最/ 多
加上自定义词典分词: 北上/ 资金/ 净流入/ 200/ 亿/ ,/ 同比/ 增长/ 15%/ ,/ 云计算/ 板块/ 涨/ 的/ 最/ 多.
1.2 pyltp 分词
pyltp 是 LTP 的 Python 封装,提供了分词,词性标注,命名实体识别,依存句法分析,语义角色标注的功能。首先下载源码,从源代码编译pyltp,python环境需要先装cmake。官网:http://ltp.ai/
$ git clone https://github.com/HIT-SCIR/pyltp
$ git submodule init
$ git submodule update
$ python setup.py install
当setup.py 执行成功之后,能进行import pyltp,说明就成功了。然后需要到http://ltp.ai/download.html 下载模型。
其中分词用到的cws.model模型
from pyltp import Segmentor
if __name__ == '__main__':
segmentor = Segmentor("./ltp_data_v3.4.0/cws.model")
world_list = segmentor.segment("奥巴马与克林顿昨晚在白宫发表了演说")
print(world_list)
输出:['奥巴马', '与', '克林顿', '昨晚', '在', '白宫', '发表', '了', '演说']
除了pyltp之外,还有hanlp分词,在此不一一列举。
1.3 pyltp 命名实体识别和语义角色标注
命名实体识别 (Named Entity Recognition, NER) 是在句子的词序列中定位并识别人名、地名、机构名等实体的任务。想要实现命名实体识别需要知道一个句子中的分词和这些词对应的词性标注。
LTP 采用 BIESO标注体系。B 表示实体开始词,I表示实体中间词,E表示实体结束词,S表示单独成实体,O表示不构成命名实体。LTP 提供的命名实体类型为:人名(Nh)、地名(Ns)、机构名(Ni)。B、I、E、S位置标签和实体类型标签之间用一个横线 - 相连;O标签后没有类型标签。
if __name__ == '__main__':
# 第一步分词
segmentor = Segmentor(MODEL_PATH + "/cws.model")
world_list = segmentor.segment("奥巴马与克林顿昨晚在白宫发表了演说")
print(world_list)
# 第二步,词性标注
postagger = Postagger(MODEL_PATH + "/pos.model")
postag_list = list(postagger.postag(world_list))
print(postag_list)
# 第三步,命名实体识别
recognizer = NamedEntityRecognizer(MODEL_PATH + "/ner.model")
ner = recognizer.recognize(world_list, postag_list)
print("ner,", ner)
# 第四步,句法依存分析
parser = Parser(MODEL_PATH + "/parser.model")
arcs = parser.parse(world_list, postag_list) # 建立依存句法分析树
# 第五步,语义角色标注
labeller = SementicRoleLabeller(MODEL_PATH + "/pisrl.model")
roles = labeller.label(world_list, postag_list, arcs)
print("roles", roles)
# 释放模型
recognizer.release()
segmentor.release()
postagger.release()
parser.release()
labeller.release()
输出:
['奥巴马', '与', '克林顿', '昨晚', '在', '白宫', '发表', '了', '演说']
['nh', 'p', 'nh', 'nt', 'p', 'n', 'v', 'u', 'v']
ner, ['S-Nh', 'O', 'S-Nh', 'O', 'O', 'O', 'O', 'O', 'O']
roles [(6, [('A0', (0, 2)), ('TMP', (3, 3)), ('LOC', (4, 5)), ('A1', (8, 8))])]
给定的句子是:"奥巴马与克林顿昨晚在白宫发表了演说。"根据语义角色标注(SRL)任务的目标,我们需要分析句子的谓词论元结构,也就是回答"谁对谁做了什么"这样的语义问题。
首先,我们需要确定谓词(predicate),即动作或状态的核心词。在这个句子中,谓词是"上涨"。
然后,我们需要确定论元(argument),即与谓词相关的词语或短语。综上所述,根据给定的句子,可以将谓词论元结构分析如下:
谓词(predicate):发表
论元(argument):
施事者 (Agent):奥巴马与克林
受事者 (Patient):演说
在何时(when):昨晚
在何地 (where) :白宫
其中(6, [('A0', (0, 2)的含义是,谓词的索引是6(发表),A0是受试者,索引是从0到2,那么施事者是('奥巴马', '与', '克林顿')。A1是受试者,索引是8(演说),那么组成一句话为:奥巴马与克林顿发表演说。TMP代表时间,索引是3(昨晚),LOC是地点,索引是4-5(在白宫)。
二、文本词向量表示方法
什么是词向量?文本词向量的表示是指在文本词向量模型中,每个词被映射到的一个固定长度的向量。比如”孙悟空“被映射成向量[2.22,1222,3.444,0.9999],常见的文本词向量模型有Word2Vec、GloVe、FastText等。在这些模型中,每个词都会被映射到一个固定长度的向量,通常是几十或几百维。
实现词向量有2大挑战。1、如何把词转换成向量?比如,孙悟空,白骨精,猪八戒是3个离散变量,如何把每个离散变量转换成一个向量表示2、如何让向量具有语义信息?比如,孙悟空,白骨精,猪八戒3个离散变量,孙悟空把猪八戒是师兄弟关系,更相近,我们改如何让词向量具备这样的语义相似关系?下面我介绍两种词向量表示方法,one-hot与word2vec
2.1 独热编码(one-hot encoding)
独热编码是将每个词表示成具有n个元素的向量,这个词向量中只有一个元素是1,其他的元素都是0,不同的词元素的为0的位置不同,其中n的大小是整个词料中不同词汇的总数。
以下是一个使用Python实现独热编码的示例:给定一句话“在nlp任务中,首先需要考虑词如何在计算机中表示”,首先分词,然后进行one-hot 编码。
import numpy as np
import jieba
# 定义一个函数来进行独热编码
def one_hot_encode(word, vocab):
# 创建一个全零的向量
one_hot = np.zeros(len(vocab))
# 获取单词在词汇表中的位置
index = vocab.index(word)
# 将对应位置设为1
one_hot[index] = 1
return one_hot
if __name__ == '__main__':
sent = '在nlp任务中,首先需要考虑词如何在计算机中表示'
vocab = jieba.lcut(sent)
print('分词结果', '/ '.join(vocab))
for word in vocab:
one_hot = one_hot_encode(word, vocab)
print(one_hot)
输出:
分词结果 在/ nlp/ 任务/ 中/ ,/ 首先/ 需要/ 考虑/ 词/ 如何/ 在/ 计算机/ 中/ 表示
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
可以看到,独热编码将每一个词转换为了一个长度为12的向量,向量中只有对应位置的元素为1,其他元素均为0,这个位置表示单词在词汇表中的位置。
在举一个例子,假设我们有四个样本(行),每个样本有三个特征(列),如图:
特征1,代表性别,取值有2个,分别是【男,女】,男用1表示,女用2表示,特征二代表爱好,取值有4个,分别是【篮球,足球,乒乓球,羽毛球】分别用数字1,2,3,4表示,特征3是 爱好程度,取值有3个,分别是【不喜欢,喜欢,特别喜欢】,分别用数字1,2,3表示
将它换成独热编码后,应该如下:
onehot编码的优势:
● 操作简单,容易理解。
劣势:
● 第一:维度灾难,有多少个词,我们的矩阵就要扩大多少维度就得是多少,造成每个向量的长度过大,占据大量内存。
● 第二:无法计算词语之间的相似度
正因为one-hot编码时明显的劣势,这种编码的方式被应用的地方越来越少,取而代之的是word2vec和word embedding.
2.2 word2vec
word2vec是一种用于将词语表示为连续向量的技术,它能够将语义相似的词语映射到向量空间中的相邻位置。word2vec模型通常分为两种:跳字模型(Skip-gram)和连续词袋模型(Continuous Bag of Words,CBOW),其中Skip-gram模型更为常用。
Skip-gram模型的基本思想是通过给定中心词来预测周围的上下文词;而CBOW模型则是通过给定周围的上下文词来预测中心词。这两种模型都会生成一个词嵌入矩阵,其中每个词都被表示为一个向量。
比如:在/ nlp/ 任务/ 中/ ,/ 首先/ 需要/ 考虑/ 词/ 如何/ 在/ 计算机/ 中/ 表示
怎么确定哪些词是上下文词呢?需要指定一个窗口大小。图中”首先“是中心词,窗口大小是2,窗口范围内是上下文词,窗口范围外的是非上下文词,其中CBOW算法是通过上下文词预测中心词,skip-gram算法是通过中心词预测上下文词。
下面是一个使用Python和gensim库进行word2vec模型训练和使用的示例代码:
import re
import numpy as np
import jieba
import gensim
from gensim.models import word2vec
from gensim.models import Word2Vec
import os
PATH = "./data/xi_you_ji.txt"
def read_crops():
file = open(PATH, encoding="utf-8")
lines = []
for line in file:
line_word_list = jieba.lcut(line)
current_word_list = []
for word in line_word_list:
# 定义正则表达式
pattern = re.compile('[^\w\s]')
# 过滤所有标点符号
text = pattern.sub('', word)
if text == "\n" or text == ' ':
continue
if len(text) > 0:
current_word_list.append(text)
if len(current_word_list) > 0:
lines.append(current_word_list)
print(lines[0:10])
return lines
def train(lines):
print("开始训练模型")
model = Word2Vec(lines, vector_size=40, window=5, min_count=3, epochs=40, negative=10)
print("孙悟空的词向量是:\n", model.wv.get_vector("悟空"))
print("孙悟空相关性最高的前10个词语是:")
word_list = model.wv.most_similar("孙悟空", topn=10)
for item in word_list:
print(item)
# 保存model
model.save('word2vec_model') # 保存模型
def load_model():
model = None
if os.path.exists("word2vec_model"):
print("模型存在,直接加载模型")
model = word2vec.Word2Vec.load('word2vec_model') # 加载模型
else:
print("模型不存在")
return model
if __name__ == '__main__':
# 读取语料
lines = read_crops()
model = load_model()
if model == None:
train(lines)
else:
word_list = model.wv.most_similar("孙悟空", topn=10)
for item in word_list:
print(item)
其中,/data/xi_you_ji.txt是从网络上下载的西游记第一章内容。
输出:
看到孙悟空相关词的预测还是可以的,说明模型效果不错。
2.13 词嵌入(word embedding)
有四个单词:“猫”、“狗”、“鱼”、“跑”。首先,用数字表示这些单词:- 猫:1- 狗:2- 鱼:3 - 跑:4。如果我们只是用数字表示,那么计算机只能知道它们是不同的单词,无法确定“猫”和“狗”更接近,还是“鱼”和“跑”更接近,因为他们之间的数值都相差1。现在我们来用 word embedding 方法来表示这些单词,词向量可能是:
- 猫:[1, 0,0,0]
- 狗:[0, 1,0,0]
- 鱼:[0, 0,1,0]
- 跑:[0, 0,0,1]
“猫”和“狗”这两个向量非常接近,因为它们都属于动物,“鱼”和“跑”这两个向量则相距很远,因为它们不论属性还是含义都相差很远。接下来看看word2vec。所以词嵌入(Word Embedding)是将词语映射到一个高维向量空间的技术。在自然语言处理(NLP)任务中,词嵌入可以将文本中的词语转化为计算机可以理解和处理的向量表示。
import torch
import jieba
from torch.utils.tensorboard import SummaryWriter
if __name__ == '__main__':
sent = '众猴拍手称扬道:“好水!好水!原来此处远通山脚之下直接大海之波。”又道:“那一个有本事的钻进去寻个源头出来不伤身体者我等即拜他为王。”连呼了三声忽见丛杂中跳出一名石猴应声高叫道:“我进去!我进去!”好猴!也是他:'
seg_list = jieba.lcut(sent)
print(seg_list)
writer = SummaryWriter()
embedded = torch.randn(len(seg_list), 50)
writer.add_embedding(embedded, metadata=seg_list)
writer.close()
运行成功之后,在当前文件夹下会有runs目录,然后启动tensorboard。
游览器访问http://0.0.0.0:7777/#projector