要求大家使用汽车大师提供的11万条(技师与用户的多轮对话与诊断建议报告数据)建立模型,模型需基于对话文本、用户问题、车型与车系,输出包含摘要与推断的报告文本,综合考验模型的归纳总结与推断能力。该解决方案可以节省大量人工时间,提高用户获取回答和解决方案的效率。
对于每个用户问题"QID",有对应文本形式的文本集合 D = “Brand”, “Collection”, “Problem”, “Conversation”,要求阅读理解系统自动对D进行分析,输出相应的报告文本"Report",其中包含摘要与推理。目标是"Report"可以正确、完整、简洁、清晰、连贯地对D中的信息作归纳总结与推理。
数据集下载地址:https://aistudio.baidu.com/aistudio/competition/detail/3
训练:所提供的训练集(82943条记录)建立模型,基于汽车品牌、车系、问题内容与问答对话的文本,输出建议报告文本。
输出结果:对所提供的测试集(20000条记录)使用训练好的模型,输出建议报告的结果文件,通过最终测评得到评价分数.
数据描述
1.首先熟悉项目数据
2.分词以及清洗数据
3.通过训练数据以及测试数据及建立vocab词汇表
# 1.数据集路径
train_data_path = 'data/AutoMaster_TrainSet.csv'
test_data_path = 'data/AutoMaster_TestSet.csv'
#加载停用词
stop_word_path='data/stopwords/哈工大停用词表.txt'
# 2.载入数据
def load_dataset(train_data_path, test_data_path):
'''
数据数据集
:param train_data_path:训练集路径
:param test_data_path: 测试集路径
:return:
'''
# 读取数据集
train_data = pd.read_csv(train_data_path)
test_data = pd.read_csv(test_data_path)
return train_data, test_data
#打印训练数据与测试数据的长度,并返回数据信息
train_df,test_df= load_dataset(train_data_path, test_data_path)
print('train data size {},test data size {}'.format(len(train_df),len(test_df)))
train_df.head()
#输出:
#train data size 82943,test data size 20000
#查看数据集的情况,看是否有空值的情况
train_df.info()
train_df.describe()
#去掉空值
train_df.dropna(subset=['Question', 'Dialogue', 'Report'], how='any', inplace=True)
test_df.dropna(subset=['Question', 'Dialogue'], how='any', inplace=True)
def clean_sentence(sentence):
'''
特殊符号去除
:param sentence: 待处理的字符串
:return: 过滤特殊字符后的字符串
'''
if isinstance(sentence, str):
return re.sub(
r'[\s+\-\|\!\/\[\]\{\}_,.$%^*(+\"\')]+|[::+——()?【】“”!,。?、~@#¥%……&*()]+|车主说|技师说|语音|图片|你好|您好',
'', sentence)
else:
return ''
sentence='2012款奔驰c180怎么样,维修保养,动力,值得拥有吗'
print('orign sentence :{} \n'.format(sentence))
print('clean sentence: {} \n'.format(clean_sentence(sentence)))
输出:orign sentence :2012款奔驰c180怎么样,维修保养,动力,值得拥有吗
clean sentence: 2012款奔驰c180怎么样维修保养动力值得拥有吗
jieba切词支持三种分词模式:
1.精确模式,试图将句子最精确地切开,适合文本分析;
jieba.cut(‘我来到北京清华大学’,cut_all=False) # 默认是精确模式
【精确模式】: 我/ 来到/ 北京/ 清华大学
2.全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
jieba.cut(‘我来到北京清华大学’,cut_all=True)
【全模式】: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
3.搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
jieba.cut_for_search(‘我来到北京清华大学’)
【全模式】: 我/ 来到/ 北京/ 清华/ 华大 / 大学/ 清华大学
sentence='2010款的宝马X1,2011年出厂,2.0排量'
print(list(jieba.cut(sentence)))
#输出:['2010', '款', '的', '宝马', 'X1', ',', '2011', '年', '出厂', ',', '2.0', '排量']
user_dict='data/user_dict.txt'
# 载入自定义词典
jieba.load_userdict(user_dict)
print(list(jieba.cut(sentence)))
#输出:['2010', '款', '的', '宝马X1', ',', '2011', '年', '出厂', ',', '2.0', '排量']
#这个和上面的切词不同的是,加载自己的自定义字典,jieba就不会把专有名词分开了
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(stop_word_path)
print('stop words size {}'.format(len(stop_words)))
#输出:stop words size 767
# 过滤停用词
def filter_stopwords(words):
'''
过滤停用词
:param seg_list: 切好词的列表 [word1 ,word2 .......]
:return: 过滤后的停用词
'''
return [word for word in words if word not in stop_words]
print('orign sentence :{} \n'.format(sentence))
words = list(jieba.cut(sentence))
print('words: {} \n'.format(words))
print('filter stop word : {} '.format(filter_stopwords(words)))
输出:
orign sentence :2010款的宝马X1,2011年出厂,2.0排量
words: [‘2010’, ‘款’, ‘的’, ‘宝马X1’, ‘,’, ‘2011’, ‘年’, ‘出厂’, ‘,’, ‘2.0’, ‘排量’]
filter stop word : [‘2010’, ‘款’, ‘宝马X1’, ‘2011’, ‘年’, ‘出厂’, ‘2.0’, ‘排量’]
预处理模块
def sentence_proc(sentence):
'''
预处理模块
:param sentence:待处理字符串
:return: 处理后的字符串
'''
# 清除无用词
sentence = clean_sentence(sentence)
# 切词,默认精确模式,全模式cut参数cut_all=True
words = jieba.cut(sentence)
# 过滤停用词
words = filter_stopwords(words)
# 拼接成一个字符串,按空格分隔
return ' '.join(words)
sentence
输出:‘2010款的宝马X1,2011年出厂,2.0排量’
sentence_proc(sentence)
输出:‘2010 款 宝马X1 2011 年 出厂 20 排量’
def data_frame_proc(df):
'''
数据集批量处理方法
:param df: 数据集
:return:处理好的数据集
'''
# 批量预处理 训练集和测试集
for col_name in ['Brand', 'Model', 'Question', 'Dialogue']:
df[col_name] = df[col_name].apply(sentence_proc)
if 'Report' in df.columns:
# 训练集 Report 预处理
df['Report'] = df['Report'].apply(sentence_proc)
return df
#对训练集和测试集进行处理
train_df = data_frame_proc(train_df)
test_df = data_frame_proc(test_df)
import numpy as np
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
对于QA问题,使用Question Dialogue Report三列数据作为Vocab的构建语料.
train_df['merged'] = train_df[['Question', 'Dialogue', 'Report']].apply(lambda x: ' '.join(x), axis=1)
test_df['merged'] = test_df[['Question', 'Dialogue']].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)))
输出:
train data size 82871,test data size 20000,merged_df data size 102871
merged_df.to_csv('data/merged_train_test_seg_data.csv',index=None,header=False)
merged_df.head()
方法一:
vocab=set(' '.join(merged_df['merged']).split(' '))
print(len(vocab))#vocab含有132570个词
vocab
方法二:
# 词列表
words=[]
for sentence in merged_df['merged']:
# 合并两个list
words+=sentence.split(' ')
# word去重
vocab=set(words)
gensim实践
#刚预处理的数据
merger_data_path = 'data/merged_train_test_seg_data.csv'
merger_df = pd.read_csv(merger_data_path,header=None)
print('merger_data_path data size {}'.format(len(merger_df)))
#输出:merger_data_path data size 102871
merger_df.head()
Gensim中 Word2Vec 模型的期望输入是进过分词的句子列表,即是某个二维数组。这里我们暂时使用 Python 内置的数组,不过其在输入数据集较大的情况下会占用大量的 RAM。Gensim 本身只是要求能够迭代的有序句子列表,因此在工程实践中我们可以使用自定义的生成器,只在内存中保存单条语句。
Word2Vec 参数
min_count
在不同大小的语料集中,我们对于基准词频的需求也是不一样的。譬如在较大的语料集中,我们希望忽略那些只出现过一两次的单词,这里我们就可以通过设置min_count参数进行控制。一般而言,合理的参数值会设置在0~100之间。
size
size参数主要是用来设置神经网络的层数,Word2Vec 中的默认值是设置为100层。更大的层次设置意味着更多的输入数据,不过也能提升整体的准确度,合理的设置范围为 10~数百。
workers
workers参数用于设置并发训练时候的线程数,不过仅当Cython安装的情况下才会起作用:
导包并构建模型
# 引入 word2vec
from gensim.models.word2vec import LineSentence
from gensim.models import word2vec
import gensim
# 引入日志配置
import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
model = word2vec.Word2Vec(LineSentence(merger_data_path), workers=8,min_count=5,size=200)
查找最近的词
model.wv.most_similar(['奇瑞'],topn=10)
输出:
[(‘名爵’, 0.8367724418640137),
(‘二代’, 0.8352739810943604),
(‘东南’, 0.8346521258354187),
(‘瑞虎’, 0.8206654787063599),
(‘江淮’, 0.8146065473556519),
(‘海马’, 0.8133385181427002),
(‘瑞虎5’, 0.812096893787384),
(‘东风风行’, 0.8119211196899414),
(‘昌河’, 0.8102805614471436),
(‘铃木’, 0.8097378015518188)]
save_model_path='data/wv/word2vec.model'
model.save(save_model_path)
model = word2vec.Word2Vec.load(save_model_path)
model_ft = FastText(sentences=LineSentence(merger_data_path), workers=8, min_count=5, size=200)
model_ft.wv.most_similar(['奇瑞'], topn=10)
vocab = {word:index for index, word in enumerate(model_wv.wv.index2word)}
reverse_vocab = {index: word for index, word in enumerate(model_wv.wv.index2word)}
方法一
save_embedding_matrix_path='data/embedding_matrix.txt'
def get_embedding_matrix(wv_model):
# 获取vocab大小
vocab_size = len(wv_model.wv.vocab)
# 获取embedding维度
embedding_dim = wv_model.wv.vector_size
print('vocab_size, embedding_dim:', vocab_size, embedding_dim)
# 初始化矩阵
embedding_matrix = np.zeros((vocab_size, embedding_dim))
# 按顺序填充
for i in range(vocab_size):
embedding_matrix[i, :] = wv_model.wv[wv_model.wv.index2word[i]]
embedding_matrix = embedding_matrix.astype('float32')
# 断言检查维度是否符合要求
assert embedding_matrix.shape == (vocab_size, embedding_dim)
# 保存矩阵
np.savetxt('save_embedding_matrix_path', embedding_matrix, fmt='%0.8f')
print('embedding matrix extracted')
return embedding_matrix
embedding_matrix=get_embedding_matrix(model_wv)
print(embedding_matrix.shape)
方法二
embedding_matrix_wv=model_wv.wv.vectors
标准处理流程参考:https://radimrehurek.com/gensim/models/word2vec.html