赞
踩
这里的Word2Vec借助 gensim 库实现,首先安装pip install gensim==3.8.3
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参数的更多说明: 中文的或者英文的文章都可以,一般一篇文章就是一行,要经过预处理才能使用,将文本语料进行分词,以空格,tab隔开都可以。可以是读入数据以后为list的语料,一般是二维list,每一个子list是一篇文章切词后的形式 。
from gensim.models.word2vec import LineSentence
LineSentence(file_path)
LinSentence 函数在使用之前需要对待处理的文本数据进行分词,并以空格分隔;函数在运行时,按行读取已经以空格分隔的文档。
保存和加载训练得到的词向量:
# 二进制形式
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')
获取训练得到的词表
vocab = wv_model.wv.vocab
本部分参考博客链接:https://blog.csdn.net/weixin_41097516/article/details/103320098
有这样一批来自汽车大师的文本信息,包括问题ID(QID
)、品牌(Brand)、车型(Model)、用户问题(Question)、Dialogue(对话)和解决方法(Report)。
这里截图来自pycharm
的展示,因为文本内容过大,展示不完整,具体拿出来两条数据看下:
QID: Q5001
Brand: 福特
Model: 福克斯
Question: 长安福特福克斯,突然打不着车
Dialogue: 技师说:你好车主,根据提供图片,这是车辆防盗系统起作用了,需要从新匹配车辆的所有的钥匙。|车主说:哦|技师说:需要联系当地4s店,或者专业做汽车钥匙的|车主说:OK
Report: 根据提供图片,这是车辆防盗起作用了,需要从新匹配钥匙。
QID: Q5002
Brand: 路虎
Model: 揽胜运动版
Question: 路虎电动脚踏板无反应,请问是什么原因,如何解决?
Dialogue:技师说:电机接触不良吧|车主说:不是,是因为车主右边踏板底盘被刮了,踏板支架被挂断了,现在修复了,但是感觉电机在响,电动踏板不回位,请问如何复位或者匹配脚踏板?|技师说:是不是与踏板脱槽了|车主说:是的,电机之前和踏板脱槽了,现在已经修复上了,但是踏板不正常,开关门几次后现在再开关门有电机声音,但是不复位|技师说:踏板变形了吗?|车主说:现在如何解决?需要匹配电机|车主说:吗|车主说:踏板没变形|技师说:他这个有专门的开关吗?|车主说:16款运动揽胜|技师说:你按开关十秒左右看看能匹配上不|车主说:开关在什么位置|技师说:它开关是怎么控制的|车主说:开门和关门控制的|车主说:[图片]|车主说:这个是开关控制吗|技师说:你常按10秒以上这个开关能学习上不能|车主说:这个是减震放气的|车主说:我按了,就放气|技师说:你按遥控器上的按键试下|车主说:车架号是 SALGA3FV9HA334882|车主说:[图片]|技师说:你找个电脑匹配一下试试|车主说:没反应|车主说:用了,电脑里面关于脚踏板只有维修模式启用和禁用|车主说:都试了,没有用|技师说:那你找当地的专业维修这款车的人员给你看看|车主说:没有复位匹配的方法吗|车主说:我自己就是学修车的,不找别人,咨询可以,要靠自己修|技师说:没有,我修这车也不多
Report: 检查踏板与电机是否托槽
训练集 77943条数据,测试集 5000条数据
项目的最终目标是做一个文本摘要的模型,但是第一步要训练生成词向量,本篇文章研究词向量如何生成。
预处理包括以下步骤:
自定义词表部分示例
语音 图片 车主说 技师说 档位 长安 冲击感 漏机油 故障码 安全码 钣金 双离合 油底壳 翼子板 擦车 值 拓下来 副作用 扎胎 分泵轴 双闪 不客气 外球笼 一控三 中控 宝马X1 奔驰 宝马 Jeep 雪佛兰 丰田 昌河 长安 吉利汽车
因为对话中包含图片、语音这类数据,但是被[图片]、[语音]标签替代了,所以在分词时,要考虑到这种情况,在分词后把这些词和字符删掉。
以下代码预处理步骤3、4的实现
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)
if __name__ == '__main__':
# 单条测试
text = "技师说:电机接触不良吧|车主说:不是,是因为车主右边踏板底盘被刮了,踏板支架被挂断了,现在修复了,但是感觉电机在响,电动踏板不回位,请问如何复位或者匹配脚踏板?|技师说:是不是与踏板脱槽了|车主说:是的,电机之前和踏板脱槽了,现在已经修复上了,但是踏板不正常,开关门几次后现在再开关门有电机声音,但是不复位|技师说:踏板变形了吗?|车主说:现在如何解决?需要匹配电机|车主说:吗|车主说:踏板没变形|技师说:他这个有专门的开关吗?|车主说:16款运动揽胜|技师说:你按开关十秒左右看看能匹配上不|车主说:开关在什么位置|技师说:它开关是怎么控制的|车主说:开门和关门控制的|车主说:[图片]|车主说:这个是开关控制吗|技师说:你常按10秒以上这个开关能学习上不能|车主说:这个是减震放气的|车主说:我按了,就放气|技师说:你按遥控器上的按键试下|车主说:车架号是 SALGA3FV9HA334882|车主说:[图片]|技师说:你找个电脑匹配一下试试|车主说:没反应|车主说:用了,电脑里面关于脚踏板只有维修模式启用和禁用|车主说:都试了,没有用|技师说:那你找当地的专业维修这款车的人员给你看看|车主说:没有复位匹配的方法吗|车主说:我自己就是学修车的,不找别人,咨询可以,要靠自己修|技师说:没有,我修这车也不多 "
print(sentence_proc(text))
输出结果:
技师说 电机 接触不良 车主说 不是 , 是因为 车主 右边 踏板 底盘 刮 , 踏板 支架 挂断 , 现在 修复 , 感觉 电机 响 , 电动 踏板 不 回位 , 请问 复位 匹配 脚踏板 ? 技师说 是不是 踏板 脱槽 车主说 , 电机 之前 踏板 脱槽 , 现在 已经 修复 上 , 踏板 不 正常 , 开关门 几次 后 现在 再 开关门 电机 声音 , 不 复位 技师说 踏板 变形 ? 车主说 现在 解决 ? 需要 匹配 电机 车主说 车主说 踏板 没 变形 技师说 专门 开关 ? 车主说 16 款 运动 揽胜 技师说 开关 十秒 左右 看看 匹配 上 不 车主说 开关 位置 技师说 开关 控制 车主说 开门 关门 控制 车主说 车主说 开关 控制 技师说 你常 10 秒 以上 开关 学习 上 不能 车主说 减震 放气 车主说 , 放气 技师说 遥控器 上 按键 试下 车主说 车架号 SALGA3FV9HA334882 车主说 技师说 找个 电脑 匹配 一下 试试 车主说 没 反应 车主说 , 电脑 里面 脚踏板 维修 模式 启用 禁用 车主说 都 试 , 没有 技师说 找 当地 专业 维修 这款 车 人员 看看 车主说 没有 复位 匹配 方法 车主说 学 修车 , 不 找 别人 , 咨询 , 修 技师说 没有 , 我修 这车 不
我们的数据"Question"、"Dialogue"和"Report"字段都包含文本信息,所以可以把这些字段分词后的结果全部合并到一起来训练词向量
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)
多进程处理代码
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方法内。
# 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)进行分割。
以上代码中使用的方法在这里,主要目的是得到最大长度的句子(不一定是实际最长),并且长度不是按照字符数量计算,而是根据词的数量;然后将所有句子补成一样的长度。
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文件
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都是词,需要转换为其在词表中的索引。
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词向量训练和其他的数据预处理全部完成。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。