问答摘要与推理(nlp)之数据预处理

1.问题描述(“汽车大师问答摘要与推理”)

要求大家使用汽车大师提供的11万条(技师与用户的多轮对话与诊断建议报告数据)建立模型,模型需基于对话文本、用户问题、车型与车系,输出包含摘要与推断的报告文本,综合考验模型的归纳总结与推断能力。该解决方案可以节省大量人工时间,提高用户获取回答和解决方案的效率。

2.数据说明

对于每个用户问题"QID",有对应文本形式的文本集合 D = “Brand”, “Collection”, “Problem”, “Conversation”,要求阅读理解系统自动对D进行分析,输出相应的报告文本"Report",其中包含摘要与推理。目标是"Report"可以正确、完整、简洁、清晰、连贯地对D中的信息作归纳总结与推理。
数据集下载地址:https://aistudio.baidu.com/aistudio/competition/detail/3
训练:所提供的训练集(82943条记录)建立模型,基于汽车品牌、车系、问题内容与问答对话的文本,输出建议报告文本。
输出结果:对所提供的测试集(20000条记录)使用训练好的模型,输出建议报告的结果文件,通过最终测评得到评价分数.
数据描述
问答摘要与推理(nlp)之数据预处理_第1张图片

3.数据预处理

1.首先熟悉项目数据
2.分词以及清洗数据
3.通过训练数据以及测试数据及建立vocab词汇表

3.1 加载数据集

# 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

问答摘要与推理(nlp)之数据预处理_第2张图片

3.2 去除空值

#查看数据集的情况,看是否有空值的情况
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)

3.3 无用字符清理

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怎么样维修保养动力值得拥有吗 

3.4切词

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', '排量']

3.5自定义词典

user_dict='data/user_dict.txt'
# 载入自定义词典
jieba.load_userdict(user_dict)
print(list(jieba.cut(sentence)))
#输出:['2010', '款', '的', '宝马X1', ',', '2011', '年', '出厂', ',', '2.0', '排量']
#这个和上面的切词不同的是,加载自己的自定义字典,jieba就不会把专有名词分开了

3.6 停用词去除

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

3.7过滤停用词

# 过滤停用词
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’, ‘排量’]

3.8 合并处理流程

预处理模块

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 排量’

3.9批量预处理

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)

问答摘要与推理(nlp)之数据预处理_第3张图片

3.10 加速多线程处理

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

3.11 手动构建Vocab

对于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

3.12 保存数据

merged_df.to_csv('data/merged_train_test_seg_data.csv',index=None,header=False)
merged_df.head()

3.13 构建Vocab

方法一:

  1. 拼接combine的所有行,形成一个超大字符串
  2. 然后按空格切开,形成全量数据的words列表
  3. set去重
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)

4.训练Word2Vec

gensim实践

4.1 数据集路径

#刚预处理的数据
merger_data_path = 'data/merged_train_test_seg_data.csv'

4.2 加载数据

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()

问答摘要与推理(nlp)之数据预处理_第4张图片

4.3 Word2Vec模型创建

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)]

4.4保存模型

save_model_path='data/wv/word2vec.model'
model.save(save_model_path)

4.5载入模型

model = word2vec.Word2Vec.load(save_model_path)

4.6 使用FastText训练(其实和Word2Vec一样)

model_ft = FastText(sentences=LineSentence(merger_data_path), workers=8, min_count=5, size=200)
model_ft.wv.most_similar(['奇瑞'], topn=10)

5.构建embedding_matrix

5.1 构建Vocab

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)}

5.2 获取embedding_matrix

方法一

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

你可能感兴趣的:(nlp)