NLP:文本张量表示方法
本文目录:
- 一、one-hot编码
- 二、word2vec模型
- (一)概念
- 1.CBOW(Continuous bag of words)模式
- 2.skipgram模式:
- 3.词向量的检索获取
- (二)word2vec的训练和使用
- 1. 获取训练数据
- 2.查看原始数据
- 3.原始数据处理,并查看处理后的数据
- 4.词向量训练与保存
- 5.查看单词对应的词向量
- 6.模型效果检验
- 7.模型超参数检验
- 三、词嵌入word embedding
- (一)概念
- (二)代码实现
- (三)tensorboard可视化
前言:前文分享了NLP的数据处理的几种方式,本文讲解文本张量的表示方法。
张量表示方法主要有三种:one-hot编码、Word2vec、Word Embedding,本文也主要介绍这三种方法。
一、one-hot编码
又称独热编码,将每个词表示成具有n个元素的向量,这个词向量中只有一个元素是1,其他元素都是0,不同词汇元素为0的位置不同,其中n的大小是整个语料中不同词汇的总数。
举个例子:
["改变", "要", "如何", "起手"]`
==>[[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 1, 0],[0, 0, 0, 1]]
onehot编码实现前,需要先讲解一下Tokenizer,简单来说,它是一个词汇映射器,内部可生成index_word 、word_index等,原理展示如下:
import joblibclass Tokenizer:def __init__(self, max_len=100):self.max_len = max_lenself.word_idx = {}self.idx_word = {}def encode(self,word):if word not in self.word_idx:idx_=len(self.word_idx)+1self.word_idx[word] = idx_ #形式为:{word:idx}self.idx_word[idx_] = word #形式为:{idx:word}def fit_on_text(self,text):for word in text:self.encode(word)def decode(self,idx):word=self.idx_word[idx]return worddef indexs_to_words(self,idxs):words=[]for idx in idxs:words.append(self.decode(idx))return wordsdef __len__(self):return len(self.word_idx)def __str__(self):return f'{self.idx_word}\n{self.word_idx}'if __name__ == '__main__':tokenizer = Tokenizer()words=['花','世界','草','树','动物']tokenizer.fit_on_text(words)print(tokenizer)# print(len(tokenizer))word2=tokenizer.indexs_to_words([1,2,3])print(word2)
运行结果:
{1: '花', 2: '世界', 3: '草', 4: '树', 5: '动物'}
{'花': 1, '世界': 2, '草': 3, '树': 4, '动物': 5}
['花', '世界', '草', '树', '动物']
讲完Tokenizer,下面是运用Tokenizer实现了onehot编码:
import jieba
# 导入keras中的词汇映射器Tokenizer
from tensorflow.keras.preprocessing.text import Tokenizer
# 导入用于对象保存与加载的joblib
from sklearn.externals import joblib# 思路分析 生成onehot
# 1 准备语料 vocabs
# 2 实例化词汇映射器Tokenizer, 使用映射器拟合现有文本数据 (内部生成 index_word 、word_index)
# 2-1 注意idx序号-1:在Keras的Tokenizer类中,fit_on_texts()方法生成的词索引(word index)默认是从1开始的,而不是从0开始。
# 3 查询单词idx 赋值 zero_list,生成onehot
# 4 使用joblib工具保存映射器 joblib.dump()
def dm_onehot_gen():# 1 准备语料 vocabsvocabs = {"周杰伦", "陈奕迅", "王力宏", "李宗盛", "小刚", "鹿晗"}# 2 实例化词汇映射器Tokenizer, 使用映射器拟合现有文本数据 (内部生成 index_word、word_index)# 2-1 注意idx序号-1mytokenizer = Tokenizer()mytokenizer.fit_on_texts(vocabs)# 3 查询单词idx 赋值 zero_list,生成onehotfor vocab in vocabs:zero_list = [0] * len(vocabs)idx = mytokenizer.word_index[vocab] - 1zero_list[idx] = 1print(vocab, '的onehot编码是', zero_list)# 4 使用joblib工具保存映射器 joblib.dump()mypath = './mytokenizer'joblib.dump(mytokenizer, mypath)print('保存mytokenizer End')# 注意5-1 字典没有顺序 onehot编码没有顺序 []-有序 {}-无序 区别# 注意5-2 字典有的单词才有idx,idx从1开始# 注意5-3 查询没有注册的词会有异常 eg: 狗蛋print(mytokenizer.word_index)print(mytokenizer.index_word)
运行结果:
陈奕迅 的onehot编码是 [1, 0, 0, 0, 0, 0]
王力宏 的onehot编码是 [0, 1, 0, 0, 0, 0]
鹿晗 的onehot编码是 [0, 0, 1, 0, 0, 0]
周杰伦 的onehot编码是 [0, 0, 0, 1, 0, 0]
李宗盛 的onehot编码是 [0, 0, 0, 0, 1, 0]
吴亦凡 的onehot编码是 [0, 0, 0, 0, 0, 1]保存mytokenizer End{'陈奕迅': 1, '王力宏': 2, '鹿晗': 3, '周杰伦': 4, '李宗盛': 5, '小刚': 6}
{1: '陈奕迅', 2: '王力宏', 3: '鹿晗', 4: '周杰伦', 5: '李宗盛', 6: '小刚'}
二、word2vec模型
(一)概念
Word2Vec 是一种用于词嵌入(Word Embedding)的经典模型,由 Google 团队于 2013 年提出(Mikolov et al.)。它通过神经网络将单词映射到低维稠密向量空间,使得语义相似的词在向量空间中距离相近。
它包含CBOW和skipgram两种训练模式。
1.CBOW(Continuous bag of words)模式
给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用上下文词汇预测目标词汇。
上图分析:
窗口大小为9, 使用前后4个词汇对目标词汇进行预测。
CBOW模式下的word2vec过程说明:
假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope can set,因为是CBOW模式,所以将使用Hope和set作为输入,can作为输出,在模型训练时, Hope,can,set等词汇都使用它们的one-hot编码. 如图所示: 每个one-hot编码的单词与各自的变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘之后再相加, 得到上下文表示矩阵(3x1)。
接着, 将上下文表示矩阵与变换矩阵(参数矩阵5x3, 所有的变换矩阵共享参数)相乘, 得到5x1的结果矩阵, 它将与我们真正的目标矩阵即can的one-hot编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模型迭代。
最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示。
2.skipgram模式:
给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用目标词汇预测上下文词汇。
上图分析:
窗口大小为9, 使用目标词汇对前后四个词汇进行预测。
skipgram模式下的word2vec过程说明:
假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope can set,因为是skipgram模式,所以将使用can作为输入 ,Hope和set作为输出,在模型训练时, Hope,can,set等词汇都使用它们的one-hot编码. 如图所示: 将can的one-hot编码与变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘, 得到目标词汇表示矩阵(3x1)。
接着, 将目标词汇表示矩阵与多个变换矩阵(参数矩阵5x3)相乘, 得到多个5x1的结果矩阵, 它将与我们Hope和set对应的one-hot编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模型迭代。
最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵即参数矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示。
3.词向量的检索获取
神经网络训练完毕后,神经网络的参数矩阵w就我们的想要词向量。如何检索某1个单词的向量呢?以CBOW方式举例说明如何检索a单词的词向量。
如下图所示:a的onehot编码[10000],用参数矩阵[3,5] * a的onehot编码[10000],可以把参数矩阵的第1列参数给取出来,这个[3,1]的值就是a的词向量。
(二)word2vec的训练和使用
第一步: 获取训练数据
第二步: 训练词向量
第三步: 模型超参数设定
第四步: 模型效果检验
第五步: 模型的保存与重加载
1. 获取训练数据
数据来源:http://mattmahoney.net/dc/enwik9.zip
在这里, 我们将研究英语维基百科的部分网页信息, 它的大小在300M左右。这些语料已经被准备好, 我们可以通过Matt Mahoney的网站下载。
2.查看原始数据
$ head -10 data/enwik9# 原始数据将输出很多包含XML/HTML格式的内容, 这些内容并不是我们需要的
<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.3/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.3/ http://www.mediawiki.org/xml/export-0.3.xsd" version="0.3" xml:lang="en"><siteinfo><sitename>Wikipedia</sitename><base>http://en.wikipedia.org/wiki/Main_Page</base><generator>MediaWiki 1.6alpha</generator><case>first-letter</case><namespaces><namespace key="-2">Media</namespace><namespace key="-1">Special</namespace><namespace key="0" />
3.原始数据处理,并查看处理后的数据
# 使用wikifil.pl文件处理脚本来清除XML/HTML格式的内容
# perl wikifil.pl data/enwik9 > data/fil9 #该命令已经执行# 查看前80个字符
head -c 80 data/fil9# 输出结果为由空格分割的单词anarchism originated as a term of abuse first used against early working class
4.词向量训练与保存
# 1.训练词向量工具库的安装
# 方法1 简洁版
pip install fasttext
# 方法2:源码安装(推荐)
# 以linux安装为例: 目录切换到虚拟开发环境目录下,再执行git clone 操作
git clone https://github.com/facebookresearch/fastText.git
cd fastText
sudo pip install . (从当前目录安装,即从源码安装)
#sudo表示以管理员权限运行
#pip是Python的包管理工具
#install是pip的安装命令2.训练词向量
# 导入fasttext
import fasttextdef dm_fasttext_train_save_load():# 1 使用train_unsupervised(无监督训练方法) 训练词向量mymodel = fasttext.train_unsupervised('./data/fil9')print('训练词向量 ok')# 2 save_model()保存已经训练好词向量 # 注意,该行代码执行耗时很长 mymodel.save_model("./data/fil9.bin")print('保存词向量 ok')# 3 模型加载mymodel = fasttext.load_model('./data/fil9.bin')print('加载词向量 ok')# 上述代码运行效果如下:
有效训练词汇量为124M, 共218316个单词
Read 124M words
Number of words: 218316
Number of labels: 0
Progress: 100.0% words/sec/thread: 53996 lr: 0.000000 loss: 0.734999 ETA: 0h 0m
5.查看单词对应的词向量
# 通过get_word_vector方法来获得指定词汇的词向量, 默认词向量训练出来是1个单词100特征
def dm_fasttext_get_word_vector():mymodel = fasttext.load_model('./data/fil9.bin')myvector = mymodel.get_word_vector('the')print(myvector)# 运行效果如下:
array([-0.03087516, 0.09221972, 0.17660329, 0.17308897, 0.12863874,0.13912526, -0.09851588, 0.00739991, 0.37038437, -0.00845221,...-0.21184735, -0.05048715, -0.34571868, 0.23765688, 0.23726143],dtype=float32)
6.模型效果检验
# 检查单词向量质量的一种简单方法就是查看其邻近单词, 通过我们主观来判断这些邻近单词是否与目标单词相关来粗略评定模型效果好坏.# 查找"运动"的邻近单词, 我们可以发现"体育网", "运动汽车", "运动服"等.
>>> model.get_nearest_neighbors('sports')[(0.8414610624313354, 'sportsnet'), (0.8134572505950928, 'sport'), (0.8100415468215942, 'sportscars'), (0.8021156787872314, 'sportsground'), (0.7889881134033203, 'sportswomen'), (0.7863013744354248, 'sportsplex'), (0.7786710262298584, 'sporty'), (0.7696356177330017, 'sportscar'), (0.7619683146476746, 'sportswear'), (0.7600985765457153, 'sportin')]# 查找"音乐"的邻近单词, 我们可以发现与音乐有关的词汇.
>>> model.get_nearest_neighbors('music')[(0.8908010125160217, 'emusic'), (0.8464668393135071, 'musicmoz'), (0.8444250822067261, 'musics'), (0.8113634586334229, 'allmusic'), (0.8106718063354492, 'musices'), (0.8049437999725342, 'musicam'), (0.8004694581031799, 'musicom'), (0.7952923774719238, 'muchmusic'), (0.7852965593338013, 'musicweb'), (0.7767147421836853, 'musico')]# 查找"小狗"的邻近单词, 我们可以发现与小狗有关的词汇.
>>> model.get_nearest_neighbors('dog')[(0.8456876873970032, 'catdog'), (0.7480780482292175, 'dogcow'), (0.7289096117019653, 'sleddog'), (0.7269964218139648, 'hotdog'), (0.7114801406860352, 'sheepdog'), (0.6947550773620605, 'dogo'), (0.6897546648979187, 'bodog'), (0.6621081829071045, 'maddog'), (0.6605004072189331, 'dogs'), (0.6398137211799622, 'dogpile')]
7.模型超参数检验
# 在训练词向量过程中, 我们可以设定很多常用超参数来调节我们的模型效果, 如:
# 无监督训练模式: 'skipgram' 或者 'cbow', 默认为'skipgram', 在实践中,skipgram模式在利用子词方面比cbow更好.
# 词嵌入维度dim: 默认为100, 但随着语料库的增大, 词嵌入的维度往往也要更大.
# 数据循环次数epoch: 默认为5, 但当你的数据集足够大, 可能不需要那么多次.
# 学习率lr: 默认为0.05, 根据经验, 建议选择[0.01,1]范围内.
# 使用的线程数thread: 默认为12个线程, 一般建议和你的cpu核数相同.>>> model = fasttext.train_unsupervised('data/fil9', "cbow", dim=300, epoch=1, lr=0.1, thread=8)Read 124M words
Number of words: 218316
Number of labels: 0
Progress: 100.0% words/sec/thread: 49523 lr: 0.000000 avg.loss: 1.777205 ETA: 0h 0m 0s
三、词嵌入word embedding
(一)概念
通过一定的方式将词汇映射到指定维度(一般是更高维度)的空间。
广义的word embedding包括所有密集词汇向量的表示方法,如之前学习的word2vec, 即可认为是word embedding的一种。
狭义的word embedding是指在神经网络中加入的embedding层, 对整个网络进行训练的同时产生的embedding矩阵(embedding层的参数), 这个embedding矩阵就是训练过程中所有输入词汇的向量表示组成的矩阵。
(二)代码实现
可通过使用tensorboard可视化嵌入的词向量。
import torch
from tensorflow.keras.preprocessing.text import Tokenizer
from torch.utils.tensorboard import SummaryWriter
import jieba
import torch.nn as nn# 注意:
# fs = tf.io.gfile.get_filesystem(save_path)
# AttributeError: module 'tensorflow._api.v2.io.gfile' has no attribute 'get_filesystem'
# 错误原因分析:
# 1 from tensorboard.compat import tf 使用了tf 如果安装tensorflow,默认会调用它tf的api函数import os
import tensorboard.compat.tensorflow_stub.io.gfile as gfilegfile.join = os.path.join# 实验:nn.Embedding层词向量可视化分析
# 1 对句子分词 word_list
# 2 对句子word2id求my_token_list,对句子文本数值化sentence2id
# 3 创建nn.Embedding层,查看每个token的词向量数据
# 4 创建SummaryWriter对象, 可视化词向量
# 词向量矩阵embd.weight.data 和 词向量单词列表my_token_list添加到SummaryWriter对象中
# summarywriter.add_embedding(embd.weight.data, my_token_list)
# 5 通过tensorboard观察词向量相似性
# 6 也可通过程序,从nn.Embedding层中根据idx拿词向量def dm02_nnembeding_show():# 1 对句子分词 word_listsentence1 = '日月教育是一家上市公司,旗下有孟子撰稿人品牌。我是在孟子这里学习文本编辑'sentence2 = "我爱自然语言处理"sentences = [sentence1, sentence2]word_list = []for s in sentences:word_list.append(jieba.lcut(s))# print('word_list--->', word_list)# 2 对句子word2id求my_token_list,对句子文本数值化sentence2idmytokenizer = Tokenizer()mytokenizer.fit_on_texts(word_list)# print(mytokenizer.index_word, mytokenizer.word_index)# 打印my_token_listmy_token_list = mytokenizer.index_word.values()print('my_token_list-->', my_token_list)# 打印文本数值化以后的句子sentence2id = mytokenizer.texts_to_sequences(word_list)print('sentence2id--->', sentence2id, len(sentence2id))# 3 创建nn.Embedding层embd = nn.Embedding(num_embeddings=len(my_token_list), embedding_dim=8)# print("embd--->", embd)# print('nn.Embedding层词向量矩阵-->', embd.weight.data, embd.weight.data.shape, type(embd.weight.data))# 4 创建SummaryWriter对象 词向量矩阵embd.weight.data 和 词向量单词列表my_token_listsummarywriter = SummaryWriter()summarywriter.add_embedding(embd.weight.data, my_token_list)summarywriter.close()# 5 通过tensorboard观察词向量相似性# cd 程序的当前目录下执行下面的命令# 启动tensorboard服务 tensorboard --logdir=runs# 6 从nn.Embedding层中根据idx拿词向量for idx in range(len(mytokenizer.index_word)):tmpvec = embd(torch.tensor(idx))print(f'索引:{idx}, 单词:{mytokenizer.index_word[idx+1]}, 词向量:{tmpvec.detach().numpy()}')
运行结果:
my_token_list--> dict_values(['是', '孟子', '我', '日月', '教育', '一家', '上市公司', ',', '旗下', '有', '撰稿人', '品牌', '。', '在', '这里', '学习', '文本编辑', '爱', '自然语言', '处理'])
sentence2id---> [[4, 5, 1, 6, 7, 8, 9, 10, 2, 11, 12, 13, 3, 1, 14, 2, 15, 16, 17], [3, 18, 19, 20]] 2
索引:0, 单词:是, 词向量:[ 0.69119644 1.558375 1.6096373 0.98399407 2.19424 0.728914-1.4884706 0.74123204]
索引:1, 单词:孟子, 词向量:[ 0.54096115 0.554475 -0.36367872 1.0417004 -0.08993819 0.363280861.0169278 -2.0248666 ]
索引:2, 单词:我, 词向量:[-0.28916082 1.991956 -1.0531932 2.2981353 -0.12863453 -0.43955870.37317175 -1.9240963 ]
索引:3, 单词:日月, 词向量:[-0.3404994 0.64136696 -0.41038752 0.4643729 -1.4025296 -0.72162175-0.9584457 -0.5583694 ]
索引:4, 单词:教育, 词向量:[ 0.6051798 0.5324559 1.3707999 -0.9619674 -0.4099177 1.07640221.169339 -0.5462842]
索引:5, 单词:一家, 词向量:[ 1.0526828e+00 1.2929035e+00 -5.1964927e-01 -3.8134031e-02-1.1102496e+00 -7.6204950e-01 -4.0794726e-04 -8.9704728e-01]
索引:6, 单词:上市公司, 词向量:[-0.58729947 0.6140487 -0.7034455 -0.20555277 -1.0994186 -0.153647840.78021735 0.21966906]
索引:7, 单词:,, 词向量:[ 0.49117938 -0.35445458 0.51375175 -0.21544148 0.27322227 -0.59599570.4328904 -0.19425716]
索引:8, 单词:旗下, 词向量:[-0.47452 -1.5148909 0.08764656 -0.23282683 0.1024209 -0.2980003-0.40261132 0.08614016]
索引:9, 单词:有, 词向量:[ 0.23161238 -0.2524453 0.387037 2.454544 1.2310503 0.400453930.30937108 0.822938 ]
索引:10, 单词:撰稿人, 词向量:[-0.92806697 1.5328188 -1.8695339 0.51484096 1.1806546 -1.51092030.00246807 0.04051867]
索引:11, 单词:品牌, 词向量:[-0.5824984 -0.2912002 0.0206483 -0.09639109 0.28824955 -0.52154961.0424472 0.9492429 ]
索引:12, 单词:。, 词向量:[-0.09745022 -1.2284819 1.1367943 0.54926044 -0.95832205 2.6447535-0.47664887 0.86957765]
索引:13, 单词:在, 词向量:[ 1.097833 1.8800806 0.08421371 0.18953332 0.33601582 0.715077940.13900037 -0.08530565]
索引:14, 单词:这里, 词向量:[ 0.19942084 0.7394484 0.3601424 0.04815122 -0.53084886 -0.63848720.26165137 -0.4921349 ]
索引:15, 单词:学习, 词向量:[ 0.6652479 0.29150504 -1.5092025 -1.6463886 0.18891443 1.2385565-0.9700842 0.56979525]
索引:16, 单词:文本编辑, 词向量:[ 0.8814708 0.95923316 -0.86357886 1.7589295 1.7440987 -0.19017713-2.1311624 -0.75222325]
索引:17, 单词:爱, 词向量:[ 0.41096488 0.4740911 0.21957813 -0.56212014 -0.8156858 -0.30315447-1.0360459 0.16603687]
索引:18, 单词:自然语言, 词向量:[-1.1221373 0.3029554 0.5329474 -0.65763694 0.2559778 -0.560264650.19609775 0.25071865]
索引:19, 单词:处理, 词向量:[ 1.83175 -0.57898027 -0.07835663 1.0467906 0.22850354 -0.2075311-0.08430544 -1.1057415 ]
(三)tensorboard可视化
#首先终端输入以下命令:
$ cd ~
$ tensorboard --logdir=runs
运行结果:
今天的分享到此结束。