Word2Vec 实践

Word2Vec 实践

gensim库使用

这里的Word2Vec借助 gensim 库实现,首先安装pip install gensim==3.8.3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from gensim.models.word2vec import Word2Vec


model = Word2Vec(
sentences=None,
size=100,
alpha=0.025,
window=5,
min_count=5,
max_vocab_size=None,
sample=1e-3,
seed=1,
workers=3,
min_alpha=0.0001,
sg=0, hs=0,
negative=5,
cbow_mean=1,
hashfxn=hash,
iter=5,
null_word=0,
trim_rule=None,
sorted_vocab=1,
batch_words=MAX_WORDS_IN_BATCH
)

参数说明:

  • sentences:可以是一个list,对于大语料集,建议使用BrownCorpus,Text8Corpus或lineSentence构建。
  • size:是指特征向量的维度,默认为100
  • alpha: 是初始的学习速率,在训练过程中会线性地递减到min_alpha
  • window:窗口大小,表示当前词与预测词在一个句子中的最大距离是多少
  • min_count: 可以对字典做截断,词频少于min_count次数的单词会被丢弃掉, 默认值为5
  • max_vocab_size: 设置词向量构建期间的RAM限制,设置成None则没有限制
  • sample: 高频词汇的随机降采样的配置阈值,默认为1e-3,范围是(0,1e-5)
  • seed:用于随机数发生器。与初始化词向量有关
  • workers:用于控制训练的并行数
  • min_alpha:学习率的最小值
  • sg: 用于设置训练算法,默认为0,对应CBOW算法;sg=1则采用skip-gram算法
  • hs: 如果为1则会采用hierarchica·softmax技巧。如果设置为0(默认),则使用negative sampling
  • negative: 如果>0,则会采用negativesampling,用于设置多少个noise words(一般是5-20)
  • cbow_mean: 如果为0,则采用上下文词向量的和,如果为1(default)则采用均值,只有使用CBOW的时候才起作用
  • hashfxn: hash函数来初始化权重,默认使用python的hash函数
  • iter: 迭代次数,默认为5
  • trim_rule: 用于设置词汇表的整理规则,指定那些单词要留下,哪些要被删除。可以设置为None(min_count会被使用)
  • sorted_vocab: 如果为1(默认),则在分配word index 的时候会先对单词基于频率降序
  • batch_words:每一批的传递给线程的单词的数量,默认为10000

关于sentences参数的更多说明: 中文的或者英文的文章都可以,一般一篇文章就是一行,要经过预处理才能使用,将文本语料进行分词,以空格,tab隔开都可以。可以是读入数据以后为list的语料,一般是二维list,每一个子list是一篇文章切词后的形式 。

1
2
3
from gensim.models.word2vec import LineSentence

LineSentence(file_path)

LinSentence 函数在使用之前需要对待处理的文本数据进行分词,并以空格分隔;函数在运行时,按行读取已经以空格分隔的文档。

保存和加载训练得到的词向量

1
2
3
4
5
6
7
8
# 二进制形式
model.save('word2vec.model') # 保存
model = Word2Vec.load('word2vec.model') # 加载

# 文本文件形式
model.wv.save_word2vec_format('word2vec.txt', binary=False) # 保存
# 加载txt词向量
model = gensim.models.KeyedVectors.load_word2vec_format('word2vec.txt')

获取训练得到的词表

1
vocab = wv_model.wv.vocab

本部分参考博客链接:https://blog.csdn.net/weixin_41097516/article/details/103320098

数据预处理

有这样一批来自汽车大师的文本信息,包括问题ID(QID)、品牌(Brand)、车型(Model)、用户问题(Question)、Dialogue(对话)和解决方法(Report)。

这里截图来自pycharm的展示,因为文本内容过大,展示不完整,具体拿出来两条数据看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
QID: Q5001
Brand: 福特
Model: 福克斯
Question: 长安福特福克斯,突然打不着车
Dialogue: 技师说:你好车主,根据提供图片,这是车辆防盗系统起作用了,需要从新匹配车辆的所有的钥匙。|车主说:哦|技师说:需要联系当地4s店,或者专业做汽车钥匙的|车主说:OK
Report: 根据提供图片,这是车辆防盗起作用了,需要从新匹配钥匙。


QID: Q5002
Brand: 路虎
Model: 揽胜运动版
Question: 路虎电动脚踏板无反应,请问是什么原因,如何解决?
Dialogue:技师说:电机接触不良吧|车主说:不是,是因为车主右边踏板底盘被刮了,踏板支架被挂断了,现在修复了,但是感觉电机在响,电动踏板不回位,请问如何复位或者匹配脚踏板?|技师说:是不是与踏板脱槽了|车主说:是的,电机之前和踏板脱槽了,现在已经修复上了,但是踏板不正常,开关门几次后现在再开关门有电机声音,但是不复位|技师说:踏板变形了吗?|车主说:现在如何解决?需要匹配电机|车主说:吗|车主说:踏板没变形|技师说:他这个有专门的开关吗?|车主说:16款运动揽胜|技师说:你按开关十秒左右看看能匹配上不|车主说:开关在什么位置|技师说:它开关是怎么控制的|车主说:开门和关门控制的|车主说:[图片]|车主说:这个是开关控制吗|技师说:你常按10秒以上这个开关能学习上不能|车主说:这个是减震放气的|车主说:我按了,就放气|技师说:你按遥控器上的按键试下|车主说:车架号是 SALGA3FV9HA334882|车主说:[图片]|技师说:你找个电脑匹配一下试试|车主说:没反应|车主说:用了,电脑里面关于脚踏板只有维修模式启用和禁用|车主说:都试了,没有用|技师说:那你找当地的专业维修这款车的人员给你看看|车主说:没有复位匹配的方法吗|车主说:我自己就是学修车的,不找别人,咨询可以,要靠自己修|技师说:没有,我修这车也不多
Report: 检查踏板与电机是否托槽

训练集 77943条数据,测试集 5000条数据

项目的最终目标是做一个文本摘要的模型,但是第一步要训练生成词向量,本篇文章研究词向量如何生成。

预处理包括以下步骤:

  1. 使用pandas加载cvs格式的训练数据和测试数据;
  2. 剔除report字段为空的数据,之后将所有空值替换为空字符串;
  3. 对文本进行分词(这里使用jieba分词),并先加载自定义词表,避免这些词被分割;
  4. 过滤掉停用词。

自定义词表部分示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
语音
图片
车主说
技师说
档位
长安
冲击感
漏机油
故障码
安全码
钣金
双离合
油底壳
翼子板
擦车

拓下来
副作用
扎胎
分泵轴
双闪
不客气
外球笼
一控三
中控
宝马X1
奔驰
宝马
Jeep
雪佛兰
丰田
昌河
长安
吉利汽车

因为对话中包含图片、语音这类数据,但是被[图片]、[语音]标签替代了,所以在分词时,要考虑到这种情况,在分词后把这些词和字符删掉

以下代码预处理步骤3、4的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import jieba

# 配置文件中存储了一系列路径
from src.utils import config


# 自定义词表
jieba.load_userdict(config.user_dict)

def load_stop_words(stop_word_path):
"""加载停用词
:param stop_word_path:停用词路径
:return: 停用词表 list
"""
# 打开文件
file = open(stop_word_path, 'r', encoding='utf-8')
# 读取所有行
stop_words = file.readlines()
# 去除每一个停用词前后 空格 换行符
stop_words = [stop_word.strip() for stop_word in stop_words]
return stop_words


# 加载停用词
stop_words = load_stop_words(config.stop_word_path)

remove_words = ['|', '[', ']', '语音', '图片']


def filter_words(sentence):
"""过滤停用词
:param sentence: 分好词的句子,以空格做分割
:return: 过滤后的停用词
"""
words = sentence.split(' ')
# 去掉多余空字符
words = [word for word in words if word and word not in remove_words]
# 去掉停用词 包括一下标点符号也会去掉
words = [word for word in words if word not in stop_words]
return words


def seg_proc(sentence):
tokens = sentence.split('|')
result = []
for t in tokens:
result.append(cut_sentence(t))
return ' | '.join(result)


def cut_sentence(line):
# 切词,默认精确模式,全模式cut参数cut_all=True
tokens = jieba.cut(line)
return ' '.join(tokens)


def sentence_proc(sentence):
"""预处理模块
:param sentence:待处理字符串
:return: 处理后的字符串
"""
# 清除无用词
# sentence = clean_sentence(sentence)
# 分段切词
sentence = seg_proc(sentence)
# 过滤停用词
words = filter_words(sentence)
# 拼接成一个字符串,按空格分隔
return ' '.join(words)
1
2
3
4
if __name__ == '__main__':
# 单条测试
text = "技师说:电机接触不良吧|车主说:不是,是因为车主右边踏板底盘被刮了,踏板支架被挂断了,现在修复了,但是感觉电机在响,电动踏板不回位,请问如何复位或者匹配脚踏板?|技师说:是不是与踏板脱槽了|车主说:是的,电机之前和踏板脱槽了,现在已经修复上了,但是踏板不正常,开关门几次后现在再开关门有电机声音,但是不复位|技师说:踏板变形了吗?|车主说:现在如何解决?需要匹配电机|车主说:吗|车主说:踏板没变形|技师说:他这个有专门的开关吗?|车主说:16款运动揽胜|技师说:你按开关十秒左右看看能匹配上不|车主说:开关在什么位置|技师说:它开关是怎么控制的|车主说:开门和关门控制的|车主说:[图片]|车主说:这个是开关控制吗|技师说:你常按10秒以上这个开关能学习上不能|车主说:这个是减震放气的|车主说:我按了,就放气|技师说:你按遥控器上的按键试下|车主说:车架号是 SALGA3FV9HA334882|车主说:[图片]|技师说:你找个电脑匹配一下试试|车主说:没反应|车主说:用了,电脑里面关于脚踏板只有维修模式启用和禁用|车主说:都试了,没有用|技师说:那你找当地的专业维修这款车的人员给你看看|车主说:没有复位匹配的方法吗|车主说:我自己就是学修车的,不找别人,咨询可以,要靠自己修|技师说:没有,我修这车也不多 "
print(sentence_proc(text))

输出结果:

1
技师说 电机 接触不良 车主说 不是 , 是因为 车主 右边 踏板 底盘 刮 , 踏板 支架 挂断 , 现在 修复 , 感觉 电机 响 , 电动 踏板 不 回位 , 请问 复位 匹配 脚踏板 ? 技师说 是不是 踏板 脱槽 车主说 , 电机 之前 踏板 脱槽 , 现在 已经 修复 上 , 踏板 不 正常 , 开关门 几次 后 现在 再 开关门 电机 声音 , 不 复位 技师说 踏板 变形 ? 车主说 现在 解决 ? 需要 匹配 电机 车主说 车主说 踏板 没 变形 技师说 专门 开关 ? 车主说 16 款 运动 揽胜 技师说 开关 十秒 左右 看看 匹配 上 不 车主说 开关 位置 技师说 开关 控制 车主说 开门 关门 控制 车主说 车主说 开关 控制 技师说 你常 10 秒 以上 开关 学习 上 不能 车主说 减震 放气 车主说 , 放气 技师说 遥控器 上 按键 试下 车主说 车架号 SALGA3FV9HA334882 车主说 技师说 找个 电脑 匹配 一下 试试 车主说 没 反应 车主说 , 电脑 里面 脚踏板 维修 模式 启用 禁用 车主说 都 试 , 没有 技师说 找 当地 专业 维修 这款 车 人员 看看 车主说 没有 复位 匹配 方法 车主说 学 修车 , 不 找 别人 , 咨询 , 修 技师说 没有 , 我修 这车 不

加载数据并训练

我们的数据”Question”、”Dialogue”和”Report”字段都包含文本信息,所以可以把这些字段分词后的结果全部合并到一起来训练词向量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import re
import os
import jieba
import logging

import numpy as np
import pandas as pd

from gensim.models.word2vec import LineSentence, Word2Vec

from src.utils import config
from src.utils.wv_loader import Vocab
from src.utils.file_utils import save_dict
from src.utils.multi_proc_utils import parallelize, cores

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

# 自定义词表
jieba.load_userdict(config.user_dict)


def build_dataset(train_data_path, test_data_path):
"""数据加载+预处理
:param train_data_path:训练集路径
:param test_data_path: 测试集路径
:return: 训练数据 测试数据 合并后的数据
"""
# 1.加载数据
train_df = pd.read_csv(train_data_path)
test_df = pd.read_csv(test_data_path)
print('train data size {},test data size {}'.format(len(train_df), len(test_df)))

# pd.set_option('display.max_columns', None)
# pd.set_option('display.max_rows', None)
# pd.set_option('max_colwidth', 1000)
# print(train_df.head())
# print(train_df[["QID", "Brand", "Model", "Question", "Dialogue", "Report"]])

# 2. 空值剔除
train_df.dropna(subset=['Report'], inplace=True)
test_df.dropna(subset=['Report'], inplace=True)

train_df.fillna('', inplace=True)
test_df.fillna('', inplace=True)

# 3.多线程, 批量数据处理
train_df = parallelize(train_df, sentences_proc)
test_df = parallelize(test_df, sentences_proc)

# 4. 合并训练测试集合
train_df['merged'] = train_df[['Question', 'Dialogue', 'Report']].apply(lambda x: ' '.join(x), axis=1)
test_df['merged'] = test_df[['Question', 'Dialogue', 'Report']].apply(lambda x: ' '.join(x), axis=1)
merged_df = pd.concat([train_df[['merged']], test_df[['merged']]], axis=0)
print('train data size {},test data size {},merged_df data size {}'.format(len(train_df),
len(test_df),
len(merged_df)))

# 5.保存处理好的 训练 测试集合
train_df = train_df.drop(['merged'], axis=1)
test_df = test_df.drop(['merged'], axis=1)

train_df.to_csv(config.train_seg_path, index=False, header=False)
test_df.to_csv(config.test_seg_path, index=False, header=False)

# 6. 保存合并数据
merged_df.to_csv(config.merger_seg_path, index=False, header=False)

# 7. 训练词向量
print('start build w2v model')
wv_model = Word2Vec(LineSentence(config.merger_seg_path),
size=config.embedding_dim,
sg=1,
workers=cores,
iter=config.wv_train_epochs,
window=5,
min_count=5)


if __name__ == '__main__':
# 数据集批量处理
build_dataset(config.train_data_path, config.test_data_path)

多进程处理代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import numpy as np
import pandas as pd
from multiprocessing import cpu_count, Pool

# cpu 数量
cores = cpu_count()
# 分块个数
partitions = cores


def parallelize(df, func):
"""多核并行处理模块
:param df: DataFrame数据
:param func: 预处理函数
:return: 处理后的数据
"""
# 数据切分
data_split = np.array_split(df, partitions)
# 线程池
pool = Pool(cores)
# 数据分发 合并
data = pd.concat(pool.map(func, data_split))
# 关闭线程池
pool.close()
# 执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
pool.join()
return data

保存词向量文件&标签数据分离

标签和数据分离其实已经不属于词向量训练部分,但是属于整体预处理的一部分,均在为后续正式训练相关模型做准备,所以合并在这一节。

训练完成之后,保存词向量,下面代码依然属于上面的build_dataset方法内。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# 8. 分离数据和标签
train_df['X'] = train_df[['Question', 'Dialogue']].apply(lambda x: ' '.join(x), axis=1)
test_df['X'] = test_df[['Question', 'Dialogue']].apply(lambda x: ' '.join(x), axis=1)

train_df['X'].to_csv(config.train_x_seg_path, index=None, header=False)
train_df['Report'].to_csv(config.train_y_seg_path, index=None, header=False)
test_df['X'].to_csv(config.test_x_seg_path, index=None, header=False)
test_df['Report'].to_csv(config.test_y_seg_path, index=None, header=False)

# 9. 填充开始结束符号,未知词填充 oov, 长度填充
# 使用GenSim训练得出的vocab
vocab = wv_model.wv.vocab

# 训练集X处理
# 获取适当的最大长度
train_x_max_len = get_max_len(train_df['X'])
test_X_max_len = get_max_len(test_df['X'])
X_max_len = max(train_x_max_len, test_X_max_len)
train_df['X'] = train_df['X'].apply(lambda x: pad_proc(x, X_max_len, vocab))

# 测试集X处理
# 获取适当的最大长度
test_df['X'] = test_df['X'].apply(lambda x: pad_proc(x, X_max_len, vocab))

# 训练集Y处理
# 获取适当的最大长度
train_y_max_len = get_max_len(train_df['Report'])
train_df['Y'] = train_df['Report'].apply(lambda x: pad_proc(x, train_y_max_len, vocab))

test_y_max_len = get_max_len(test_df['Report'])
test_df['Y'] = test_df['Report'].apply(lambda x: pad_proc(x, test_y_max_len, vocab))

# 10. 保存pad oov处理后的,数据和标签
train_df['X'].to_csv(config.train_x_pad_path, index=False, header=False)
train_df['Y'].to_csv(config.train_y_pad_path, index=False, header=False)
test_df['X'].to_csv(config.test_x_pad_path, index=False, header=False)
test_df['Y'].to_csv(config.test_y_pad_path, index=False, header=False)

# 11. 保存词向量模型
if not os.path.exists(os.path.dirname(config.save_wv_model_path)):
os.makedirs(os.path.dirname(config.save_wv_model_path))
wv_model.save(config.save_wv_model_path)
print('finish retrain w2v model')
print('final w2v_model has vocabulary of ', len(wv_model.wv.vocab))

# 12. 更新vocab
vocab = {word: index for index, word in enumerate(wv_model.wv.index2word)}
reverse_vocab = {index: word for index, word in enumerate(wv_model.wv.index2word)}

# 保存字典
save_dict(config.vocab_path, vocab)
save_dict(config.reverse_vocab_path, reverse_vocab)

# 13. 保存词向量矩阵
embedding_matrix = wv_model.wv.vectors
np.save(config.embedding_matrix_path, embedding_matrix)

# 14. 数据集转换 将词转换成索引 [<START> 方向机 重 ...] -> [2, 403, 986, 246, 231
vocab = Vocab()

train_ids_x = train_df['X'].apply(lambda x: transform_data(x, vocab))
train_ids_y = train_df['Y'].apply(lambda x: transform_data(x, vocab))
test_ids_x = test_df['X'].apply(lambda x: transform_data(x, vocab))
test_ids_y = test_df['Y'].apply(lambda x: transform_data(x, vocab))

# 15. 数据转换成numpy数组
# 将索引列表转换成矩阵 [2, 403, 986, 246, 231] --> array([[2, 403, 986 , 246, 231]]
train_X = np.array(train_ids_x.tolist())
train_Y = np.array(train_ids_y.tolist())
test_X = np.array(test_ids_x.tolist())
test_Y = np.array(test_ids_y.tolist())

# 保存数据
np.save(config.train_x_path, train_X)
np.save(config.train_y_path, train_Y)
np.save(config.test_x_path, test_X)
np.save(config.test_y_path, test_Y)
return train_X, train_Y, test_X, test_Y

这里是吧”Question”和”Dailogue”作为数据(X),”Report”作为标签(Y)进行分割。

以上代码中使用的方法在这里,主要目的是得到最大长度的句子(不一定是实际最长),并且长度不是按照字符数量计算,而是根据词的数量;然后将所有句子补成一样的长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import numpy as np
from gensim.models.word2vec import Word2Vec

from src.utils.config import embedding_matrix_path, vocab_path, save_wv_model_path


class Vocab:
PAD_TOKEN = '<PAD>'
UNKNOWN_TOKEN = '<UNK>'
START_DECODING = '<START>'
STOP_DECODING = '<STOP>'
MASKS = [PAD_TOKEN, UNKNOWN_TOKEN, START_DECODING, STOP_DECODING]
MASK_COUNT = len(MASKS)

PAD_TOKEN_INDEX = MASKS.index(PAD_TOKEN)
UNKNOWN_TOKEN_INDEX = MASKS.index(UNKNOWN_TOKEN)
START_DECODING_INDEX = MASKS.index(START_DECODING)
STOP_DECODING_INDEX = MASKS.index(STOP_DECODING)

def __init__(self, vocab_file=vocab_path, vocab_max_size=None):
"""Vocab 对象,vocab基本操作封装
:param vocab_file: Vocab 存储路径
:param vocab_max_size: 最大字典数量
"""
self.word2id, self.id2word = self.load_vocab(vocab_file, vocab_max_size)
self.count = len(self.word2id)

@staticmethod
def load_vocab(file_path, vocab_max_size=None):
"""读取字典
:param file_path: 文件路径
:param vocab_max_size:
:return: 返回读取后的字典
"""
vocab = {mask: index
for index, mask in enumerate(Vocab.MASKS)}

reverse_vocab = {index: mask
for index, mask in enumerate(Vocab.MASKS)}

for line in open(file_path, "r", encoding='utf-8').readlines():
word, index = line.strip().split("\t")
index = int(index)
# 如果vocab 超过了指定大小
# 跳出循环 截断
if vocab_max_size and index > vocab_max_size - Vocab.MASK_COUNT:
print("max_size of vocab was specified as %i; we now have %i words. Stopping reading." % (
vocab_max_size, index))
break
vocab[word] = index + Vocab.MASK_COUNT
reverse_vocab[index + Vocab.MASK_COUNT] = word
return vocab, reverse_vocab

def word_to_id(self, word):
if word not in self.word2id:
return self.word2id[self.UNKNOWN_TOKEN]
return self.word2id[word]

def id_to_word(self, word_id):
if word_id not in self.id2word:
raise ValueError('Id not found in vocab: %d' % word_id)
return self.id2word[word_id]

def size(self):
return self.count


def get_max_len(data):
"""获得合适的最大长度值
:param data: 待统计的数据 train_df['Question']
:return: 最大长度值
"""
max_lens = data.apply(lambda x: x.count(' ') + 1)
return int(np.mean(max_lens) + 2 * np.std(max_lens))


def pad_proc(sentence, max_len, vocab):
"""填充字段
< start > < end > < pad > < unk > max_lens
"""
# 0.按空格统计切分出词
words = sentence.strip().split(' ')
# 1. 截取规定长度的词数
words = words[:max_len]
# 2. 填充< unk > ,判断是否在vocab中, 不在填充 < unk >
sentence = [word if word in vocab else Vocab.UNKNOWN_TOKEN for word in words]
# 3. 填充< start > < end >
sentence = [Vocab.START_DECODING] + sentence + [Vocab.STOP_DECODING]
# 4. 判断长度,填充 < pad >
sentence = sentence + [Vocab.PAD_TOKEN] * (max_len - len(words))
return ' '.join(sentence)

def save_dict(save_path, dict_data):
"""保存字典
:param save_path: 保存路径
:param dict_data: 字典路径
"""
with open(save_path, 'w', encoding='utf-8') as f:
for k, v in dict_data.items():
f.write("{}\t{}\n".format(k, v))

src.utils.config文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import os
import pathlib

# 预处理数据 构建数据集
is_build_dataset = True

# 获取项目根目录
root = pathlib.Path(os.path.abspath(__file__)).parent.parent.parent

# 训练数据路径
train_data_path = os.path.join(root, 'data', 'train.csv')
# 测试数据路径
test_data_path = os.path.join(root, 'data', 'test.csv')
# 停用词路径
# stop_word_path = os.path.join(root, 'data', 'stopwords/哈工大停用词表.txt')
stop_word_path = os.path.join(root, 'data', 'stopwords/stopwords.txt')

# 自定义切词表
user_dict = os.path.join(root, 'data', 'user_dict.txt')

# 0. 预处理
# 预处理后的训练数据
train_seg_path = os.path.join(root, 'data', 'train_seg_data.csv')
# 预处理后的测试数据
test_seg_path = os.path.join(root, 'data', 'test_seg_data.csv')
# 合并训练集测试集数据
merger_seg_path = os.path.join(root, 'data', 'merged_train_test_seg_data.csv')

# 1. 数据标签分离
train_x_seg_path = os.path.join(root, 'data', 'train_X_seg_data.csv')
train_y_seg_path = os.path.join(root, 'data', 'train_Y_seg_data.csv')

test_x_seg_path = os.path.join(root, 'data', 'test_X_seg_data.csv')
test_y_seg_path = os.path.join(root, 'data', 'test_Y_seg_data.csv')

# 2. pad oov处理后的数据
train_x_pad_path = os.path.join(root, 'data', 'train_X_pad_data.csv')
train_y_pad_path = os.path.join(root, 'data', 'train_Y_pad_data.csv')
test_x_pad_path = os.path.join(root, 'data', 'test_X_pad_data.csv')
test_y_pad_path = os.path.join(root, 'data', 'test_Y_pad_data.csv')

# 3. numpy 转换后的数据
train_x_path = os.path.join(root, 'data', 'train_X')
train_y_path = os.path.join(root, 'data', 'train_Y')
test_x_path = os.path.join(root, 'data', 'test_X')
test_y_path = os.path.join(root, 'data', 'test_Y')

# 词向量路径
save_wv_model_path = os.path.join(root, 'data', 'wv_new', 'word2vec.model')
# 词向量矩阵保存路径
embedding_matrix_path = os.path.join(root, 'data', 'wv_new', 'embedding_matrix')
# 字典路径
vocab_path = os.path.join(root, 'data', 'wv_new', 'vocab.txt')
reverse_vocab_path = os.path.join(root, 'data', 'wv_new', 'reverstest_save_dire_vocab.txt')

# 词向量训练轮数
wv_train_epochs = 5

# 模型保存文件夹
# checkpoint_dir = os.path.join(root, 'data', 'checkpoints', 'training_checkpoints_pgn_cov_not_clean')

checkpoint_dir = os.path.join(root, 'data', 'checkpoints', 'training_checkpoints_seq2seq_backed')

# checkpoint_dir = os.path.join(root, 'data', 'checkpoints', 'training_checkpoints_seq2seq')
seq2seq_checkpoint_dir = os.path.join(root, 'data', 'checkpoints', 'training_checkpoints_seq2seq')
transformer_checkpoint_dir = os.path.join(root, 'data', 'checkpoints', 'training_checkpoints_pgn_tfs')

checkpoint_prefix = os.path.join(checkpoint_dir, 'ckpt')

# 结果保存文件夹
save_result_dir = os.path.join(root, 'result')

# 词向量维度
embedding_dim = 300

sample_total = 82871

batch_size = 32
# batch_size = 4

epochs = 20

vocab_size = 30000

上面保存了训练后得到的词向量模型和词表之后,又进一步保存了词向量矩阵,并且将X和Y数据也保存为npy文件,不过因为X和Y都是词,需要转换为其在词表中的索引。

1
2
3
4
5
6
7
8
9
10
11
def transform_data(sentence, vocab):
"""word 2 index
:param sentence: [word1,word2,word3, ...] ---> [index1,index2,index3 ......]
:param vocab: 词表
:return: 转换后的序列
"""
# 字符串切分成词
words = sentence.split(' ')
# 按照vocab的index进行转换 # 遇到未知词就填充unk的索引
ids = [vocab.word2id[word] if word in vocab.word2id else vocab.UNKNOWN_TOKEN_INDEX for word in words]
return ids

自此word2vec词向量训练和其他的数据预处理全部完成。