1,本Notebook背景介绍
本notebook想尝试一下使用实际场景的数据,探索一下能计算出来什么结果。演练完这个notebook,我的感受是word2vec主要还是为下一步运算准备数据,自身生成的结果不要过多解读,不像我们在前面几个notebook中介绍的textRank、LDA、k-means等算法,其结果是用来解读的,相反,word2vec主要还是为了弥补one-hot编码的缺点,利用上下文隐含的语义信息产生稠密的矩阵,而且生成的词向量是不可解读的。至少从我这种悲观主义者眼中看是这样的。所以从我们推荐的研究论文范文中也可以看到,word2vec是作为一个中间步骤。
在昨天发布的《Jupyter Notebook使用gensim做Word2Vec模型实验》一文中,我们按照Gensim官网教程,基于安装gensim库时自带的英文语料库“Lee评估语料库”,进行了针对英文文本的Word2Vec模型实验,主要步骤有:
1. 加载语料库
2. 训练模型
3. 查看和输出模型
4. 保存模型
5. 加载已经保存的模型并进行更新训练
今天我们进行针对中文文本的Word2Vec模型计算:先使用GooSeeker文本分词和情感分析软件进行分词,分词得到的“分词效果表”作为中文语料库,进行word2vec计算。
那么,使用gensim的Word2Vec算法的关键一步是:把实际场景的数据变成sentences数据结构,剩下的步骤就是gensim的word2vec各种函数调用了。本Jupyter Notebook将做详细讲解。
什么是word2vec以及相关的学习材料等不再赘述,有兴趣的同学可以看《Jupyter Notebook使用gensim做Word2Vec模型实验》
2,第三方库
本notebook使用了gensim库,gensim库用于做Word2vec实验。
如果未安装,请先使用下面的命令安装gensim库,再运行实验本notebook:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple gensim #国内安装使用清华的源,速度快
3,本notebook的主要步骤
参考Gensim官网的教程,使用实际场景中的中文内容进行word2vec计算,要做以下步骤:
1. 使用GooSeeker文本分词和情感分析软件进行分词,分词得到的“分词效果表”作为中文语料库
2. 从分词工具导出的分词效果表文件“分词效果_20xxxxxxxxxxxxxxxxx.xlsx”放入本notebook的data/raw/目录下
3. 运行本Jupyter Notebook,使用Pandas把分词结果数据读出来,转成csv文件
4. 利用gensim库中的读取csv文件的函数生成sentences数据结构
5. 将sentences数据结构交给word2vec模型
第5步相对来说比较简单,只要熟练掌握gensim的相关函数调用就行了。而1-4步是在实际分析场景下必须完成的步骤。
4,准备运算环境
4.1 开启日志输出
把实验过程中的日志信息直接在Jupyter Notebook中输出,对于初学者来说,有助于掌握word2vec的算法和gensim的计算原理。熟练以后,可以把这一段删除。
import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
4.2 引入gensim库
引入gensim库下的models,datapath,utils。其中datapath是便于操作磁盘文件的程序库,而uitils中一个使用csv文件的函数可以方便我们生成word2vec需要的sentences数据结构。
from gensim.test.utils import datapath
from gensim import utils
import gensim.models
5,生成word2vec需要的数据结构
按照gensim的官方文件,sentences是一个iteratable的数据结构即可,而且要求可以rewind的,不能是iterator生成器生成的只能迭代一次的结构。我们使用utils里面的一个函数,可以把csv文件直接读进来生成sentences数据结构表示的语料库。所以,首先要将GooSeeker分词软件生成的excel文件中的分词结果数据转换成csv文件。
【注意】生成了中间文件不是唯一选择,只是一种编程便利方式,可能运行时会稍微慢一点,因为要从pandas转成csv。但是,这个csv并没有实际存盘,是内存中的,所以,也不会慢很多。
5.1 准备好pandas
导入必要的Python程序包,并且设定文件目录变量。原始数据放在raw文件夹,而处理好的数据放在processed文件夹。这里的原始数据就是从GooSeeker分词软件导出的分词效果表;而处理好的数据就是生成的csv文件。
import pandas as pd
import os
import time
%xmode Verbose
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
# 存原始数据的目录
raw_data_dir = os.path.join(os.getcwd(), '..\..\data\raw')
# 存处理后的数据的目录
processed_data_dir = os.path.join(os.getcwd(), '..\..\data\processed')
filename_temp = pd.Series(['分词效果'])
file_seg_effect = ''
输出结果:
Exception reporting mode: Verbose
5.2 检测data aw目录下是否有分词效果表
在我们发布的一系列Jupyter Notebook中,凡是处理GooSeeker分词软件导出的结果文件的,都给各种导出文件起了固定的名字。为了方便大家使用,只要把导出文件放在data/raw文件夹,notebook就会找到导出文件,赋值给对应的文件名变量。下面罗列了可能用到的文件名变量:
file_word_freq:词频表
file_seg_effect: 分词效果表
file_word_choice_matrix: 选词矩阵表
file_word_choice_match: 选词匹配表
file_word_choice_result: 选词结果表
file_co_word_matrix: 共词矩阵表
【注意】本notebook只使用分词效果表,下面的代码将检查data/raw中有没有分词效果表,如果没有会报错,后面的程序就没法执行了。
# 0:'词频', 1:'分词效果', 2:'选词矩阵', 3:'选词匹配', 4:'选词结果', 5:'共词矩阵'
print(raw_data_dir + ' ')
for item_filename in os.listdir(raw_data_dir):
if filename_temp[0] in item_filename:
file_seg_effect = item_filename
continue
if file_seg_effect:
print("分词效果excel表:", "data\raw\", file_seg_effect)
else:
print("分词效果excel表:不存在")
5.3 读取分词效果表
df = pd.read_excel(os.path.join(raw_data_dir, file_seg_effect))
5.4 查看excel表前10行数据
我们使用“分词数据”这一列,作为模型实验的语料库。可以看到“分词数据”这列的数据是分词后的效果,每个分开的词之间用空格间隔。
df.head(10)
5.5 把“分词数据”列保存到csv文件
corpus_path = os.path.join('../../data/processed', 'corpus.csv')
df["分词数据"].to_csv(corpus_path,index=False, header=False ,encoding="utf-8")
6,构建语料库
6.1 定义SegEffectCorpus语料库类
定义一个python类,为了今后方便使用。这个类就是把前面生成的csv文件的每一行读进来,一行是一个文档,所有行构建成word2vec需要的语料库数据结构。
class SegEffectCorpus:
"""An iterator that yields sentences (lists of str)."""
def __iter__(self):
corpus_path = "corpus.csv"
for line in open(corpus_path, encoding='UTF-8'):
# 每行一篇文档,每个文档由空格分隔的多个词组成
# there's one document per line, tokens separated by whitespace
yield utils.simple_preprocess(line)
6.2 生成语料库
一行一个文档,这里面有所有行,命名为sentences。
sentences = SegEffectCorpus()
7,基于句子训练word2vec模型
训练时,句子(sentences)参数必须指定,除此之外,还有其它几个可选参数:
min_count:缺省值是5, 表示在预料库里不少于5次的词才会被保留
vector_size: Gensim Word2vec 映射单词的 N 维空间的维度 (N) 数。
下面我们将训练多个模型做对比,每个模型的vector_size不一样
model_v20 = gensim.models.Word2Vec(sentences=sentences, vector_size = 20)
model_v50 = gensim.models.Word2Vec(sentences=sentences, vector_size = 50)
model_v100 = gensim.models.Word2Vec(sentences=sentences, vector_size = 100)
model_v1000 = gensim.models.Word2Vec(sentences=sentences, vector_size = 1000)
model_v5000 = gensim.models.Word2Vec(sentences=sentences, vector_size = 5000)
7.1 看看模型有哪些维度
先看一下模型所在的向量空间是多少维,原始维度是一样的,都是5513个词
print(len(model_v20.wv.index_to_key));
print(len(model_v50.wv.index_to_key));
print(len(model_v100.wv.index_to_key));
print(len(model_v1000.wv.index_to_key));
print(len(model_v5000.wv.index_to_key));
输出结果:
5513
5513
5513
5513
5513
7.2 查看模型的前10个词
for index, word in enumerate(model_v20.wv.index_to_key):
if index == 20:
break
print(f"word #{index}/{len(model_v20.wv.index_to_key)} is {word}")
输出结果:
word #0/5513 is 下岗
word #1/5513 is 一个
word #2/5513 is 没有
word #3/5513 is 国企
word #4/5513 is 现在
word #5/5513 is 企业
word #6/5513 is quot
word #7/5513 is 他们
word #8/5513 is 时候
word #9/5513 is 自己
word #10/5513 is 工人
word #11/5513 is 国家
word #12/5513 is 这个
word #13/5513 is 我们
word #14/5513 is 当时
word #15/5513 is 工作
word #16/5513 is 因为
word #17/5513 is 什么
word #18/5513 is 但是
word #19/5513 is 很多
7.3 不同模型的维度构成
上面看到所有模型中查到的原始维度都是由5513个词构成的,而用下面的语句可以看到计算完成后每个词的向量维度数等于vector_size,但是这些维度是什么?看不出来,不是原先的词,不要以为从原先5513个词选出了20个、50个...构成了新的向量空间。
vec_king = model_v20.wv['企业']
print(vec_king)
输出结果:
[-0.3846502 -0.35217553 0.78828686 0.8946948 -0.205592 -0.8147616
-0.2380229 0.6689105 0.12241378 2.2435844 1.9278524 -0.21242936
0.06304763 -0.84816504 0.917462 1.5474753 0.31649536 0.30643114
0.03492499 -1.0469717 ]
7.4 对比不同模型相近的词
查看词语“企业”最相近的10个词
print("20维:企业最相近的10个词")
for item in model_v20.wv.most_similar(positive=['企业'], topn=10):
print(item)
print("50维:企业最相近的10个词")
for item in model_v50.wv.most_similar(positive=['企业'], topn=10):
print(item)
print("100维:企业最相近的10个词")
for item in model_v100.wv.most_similar(positive=['企业'], topn=10):
print(item)
print("1000维:企业最相近的10个词")
for item in model_v1000.wv.most_similar(positive=['企业'], topn=10):
print(item)
print("5000维:企业最相近的10个词")
for item in model_v5000.wv.most_similar(positive=['企业'], topn=10):
print(item)
输出结果:
20维:企业最相近的10个词
('政府', 0.9701895117759705)
('国有企业', 0.9596976637840271)
('效率', 0.955802857875824)
('大量', 0.9421055316925049)
('中小型', 0.9379032254219055)
('低下', 0.9362995624542236)
('亏损', 0.9339385032653809)
('垄断', 0.9311338663101196)
('生产', 0.9291993379592896)
('盈利', 0.9277654886245728)
50维:企业最相近的10个词
('国有企业', 0.9838334321975708)
('社会', 0.9760490655899048)
('开放', 0.9752578139305115)
('政府', 0.9712843298912048)
('改革', 0.969390869140625)
('大量', 0.9637796878814697)
('效率', 0.9617377519607544)
('市场', 0.9595521688461304)
('国家', 0.9594038128852844)
('垄断', 0.9583749175071716)
100维:企业最相近的10个词
('国有企业', 0.9907254576683044)
('社会', 0.9851162433624268)
('开放', 0.9797177910804749)
('改革', 0.9780121445655823)
('政府', 0.9771029949188232)
('大量', 0.9718190431594849)
('效率', 0.9714088439941406)
('垄断', 0.9705336689949036)
('国家', 0.9695110321044922)
('市场', 0.9664446711540222)
1000维:企业最相近的10个词
('国有企业', 0.9982540607452393)
('社会', 0.9979686737060547)
('开放', 0.9975664615631104)
('效率', 0.9960200190544128)
('大量', 0.9959481358528137)
('垄断', 0.9956788420677185)
('低下', 0.99522864818573)
('政府', 0.9952222108840942)
('三产', 0.9947063326835632)
('盈利', 0.9945940971374512)
5000维:企业最相近的10个词
('劳动力', 0.9991272687911987)
('市场', 0.9990919828414917)
('发展', 0.9990687370300293)
('工业', 0.9990560412406921)
('僵化', 0.9990420937538147)
('抓大放小', 0.9989959001541138)
('现代化', 0.9989417195320129)
('放权', 0.9989097118377686)
('产业', 0.998893141746521)
('需求', 0.9988720417022705)
vector_size不同的时候,结果差别还是很大的。怎么解释?怎么选择?还需要进一步探索。
8,其他探索
8.1 对于模型中没有的词,是查不到的
try:
vec_cameroon = model_v20.wv['给力']
except KeyError:
print("The word '给力' does not appear in this model")
输出结果:
The word '给力' does not appear in this model
8.2 看看哪个词距离最远
print("20维的时候:" + model_v20.wv.doesnt_match([ '私企', '外资企业','企业', '集体企业', '国企', '集体企业', '外企']))
print("50维的时候:" + model_v50.wv.doesnt_match([ '私企', '外资企业','企业', '集体企业', '国企', '集体企业', '外企']))
print("100维的时候:" + model_v100.wv.doesnt_match([ '私企', '外资企业','企业', '集体企业', '国企', '集体企业', '外企']))
print("1000维的时候:" + model_v1000.wv.doesnt_match([ '私企', '外资企业','企业', '集体企业', '国企', '集体企业', '外企']))
print("5000维的时候:" + model_v5000.wv.doesnt_match([ '私企', '外资企业','企业', '集体企业', '国企', '集体企业', '外企']))
输出结果:
20维的时候:企业
50维的时候:企业
100维的时候:企业
1000维的时候:国企
5000维的时候:企业
这里也不太好解读,而且我发现把这个notebook运行多次,看到的结果会有变化,“国企”从1000维变到5000维,再变到1000维
9,保存模型
把训练得到的模型保存到磁盘,按照我们这套notebook的目录规划,把训练好的模型存在model目录中。下面将存两个文件,一个是原生结构的模型文件,另一个是字符格式的模型文件,便于观察。
#import tempfile
#model_path = ''
#with tempfile.NamedTemporaryFile(prefix='gensim-model-', delete=False) as tmp:
# temporary_filepath = tmp.name
#model_v20.save(temporary_filepath)
# model_v20.wv.save_word2vec_format(temporary_filepath)
# model_path = temporary_filepath
model_file = 'gensim-model-' + str(time.strftime("%Y%m%d%H%M%S", time.localtime()))
format_file = model_file + '-fmt'
model_path = os.path.join('../../model/', model_file)
format_path = os.path.join('../../model/', format_file)
model_v20.save(model_path)
model_v20.wv.save_word2vec_format(format_path)
输出结果:
2021-09-16 10:33:18,106 : INFO : Word2Vec lifecycle event {'fname_or_handle': '../../model/gensim-model-20210916103318', 'separately': 'None', 'sep_limit': 10485760, 'ignore': frozenset(), 'datetime': '2021-09-16T10:33:18.106494', 'gensim': '4.0.1', 'python': '3.8.5 (default, Sep 3 2020, 21:29:08) [MSC v.1916 64 bit (AMD64)]', 'platform': 'Windows-10-10.0.19041-SP0', 'event': 'saving'}
2021-09-16 10:33:18,107 : INFO : not storing attribute cum_table
2021-09-16 10:33:18,112 : INFO : saved ../../model/gensim-model-20210916103318
2021-09-16 10:33:18,118 : INFO : storing 5513x20 projection weights into ../../model/gensim-model-20210916103318-fmt
10,加载模型
从已保存的文件里加载模型
new_model = gensim.models.Word2Vec.load(model_path)
输出结果:
2021-09-16 10:33:23,673 : INFO : loading Word2Vec object from ../../model/gensim-model-20210916103318
2021-09-16 10:33:23,781 : INFO : loading wv recursively from ../../model/gensim-model-20210916103318.wv.* with mmap=None
2021-09-16 10:33:23,784 : INFO : setting ignored attribute cum_table to None
2021-09-16 10:33:23,889 : INFO : Word2Vec lifecycle event {'fname': '../../model/gensim-model-20210916103318', 'datetime': '2021-09-16T10:33:23.889030', 'gensim': '4.0.1', 'python': '3.8.5 (default, Sep 3 2020, 21:29:08) [MSC v.1916 64 bit (AMD64)]', 'platform': 'Windows-10-10.0.19041-SP0', 'event': 'loaded'}
11,继续训练
基于加载的模型,我们可以使用新的预料进行进一步的训练
more_sentences = [
['我国','GDP','最高','的','四座','省份', '分别', '是', '广东', '江苏', '山东', '浙江']
]
new_model.build_vocab(more_sentences, update=True)
new_model.train(more_sentences, total_examples=model_v20.corpus_count, epochs=model_v20.epochs)
12,下载本Notebook