【文本摘要项目】2-数据标签分离及其数值化
背景
本文承接上一篇数据预处理与词向量训练内容,上文已描述了我们的任务和目标。本篇中,主要记录在训练和预处理后的一些初步的细节处理,如句子长度对齐、oov
初步处理、构造后续用与训练与测试的X
和y
等等。
核心内容
1. 构造X和y
针对本文任务及数据集的特点,对于训练集和测试集来说X
是['Question', 'Dialogue']
两列,而y
是['Report']
列。因此,分别针对这两列进行处理,样例代码如下:
train_df['X'] = train_df[['Question', 'Dialogue']].apply(lambda x: ' '.join(x), axis=1)
train_df['X'].to_csv(config.train_x_seg_path, index=None, header=False)
test_df['X'] = test_df[['Question', 'Dialogue']].apply(lambda x: ' '.join(x), axis=1)
test_df['X'].to_csv(config.test_y_seg_path, index=None, header=False)
# 标签为Report列
train_df['Report'].to_csv(config.train_y_seg_path, index=None, header=False)
test_df['Report'].to_csv(config.test_y_seg_path, index=None, header=False)
2. oov和pad处理
构造用于训练和测试的X和y后,继续对其进行处理,主要设计两个方面:oov初步处理和pad对齐。
# 填充开始、结束符号,未知词用oov,长度填充
vocab = w2v_model.wv.index_to_key
# 训练集和测试集的X处理
x_max_len = max(get_max_len(train_df['X']), get_max_len(test_df['X']))
train_df['X'] = train_df['X'].apply(lambda x: pad_proc(x, x_max_len, vocab))
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(train_df['Report'])
test_df['Y'] = test_df['Report'].apply(lambda x: pad_proc(x, test_y_max_len, vocab))
中间主要涉及两个函数:pad_proc和get_max_len。其中,get_max_len是获取句子的最大长度;而pad_proc是将每个句子中,如果长度小于最大长度,则以一些特定的字符进行填充。样例代码如下:
def get_max_len(dataframe):
"""
获取合适的最大长度 :param dataframe: 带统计的数据,
train_df['Question']
:return:
"""
max_lens = dataframe.apply(lambda x: x.count(' ') + 1)
return int(np.mean(max_lens) + 2 * np.std(max_lens))
这中间又一个问题:如何衡量句子最大长度?
如果给的值过大,则每个句子会填充大量的pad
值;如果句子最大长度选的过小,则可能遗失很多信息。因此,本文中用了一个均值加标准差的方式,来选择句子的最大长度。
填充pad的样例代码如下:
def pad_proc(sentence, max_len, vocab):
"""
填充字段 < start > < end > < pad > < unk >
:param sentence:
:param x_max_len:
:param vocab:
:return:
"""
# 0. 按照空格分词
word_list = sentence.strip().split()
# 1. 截取最大长度的词
word_list = word_list[:max_len]
# 2. 填充<unk>
sentence = [word if word in vocab else Vocab.UNKNOWN_TOKEN for word in word_list]
# 3. 填充<start>和<end>
sentence = [Vocab.START_DECODING] + sentence + [Vocab.STOP_DECODING]
# 4. 长度对齐
sentence = sentence + [Vocab.PAD_TOKEN] * (max_len - len(word_list))
return ' '.join(sentence)
这段代码中只是对句子的长度问题等等,做了简单的处理,但是后续可能还需要对其进行一系列的处理。因此本项目中,定义了一个类Vocab
,用来专门处理所有关于句子长度、对齐等问题。
3. Vocab基本处理类
class Vocab():
PAD_TOKEN = '<PAD>'
UNKNOWN_TOKEN = '<UNK>'
START_DECODING = '<START>'
STOP_DECODING = '<STOP>'
MASKS = [PAD_TOKEN, UNKNOWN_TOKEN, START_DECODING, STOP_DECODING]
MASKS_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=config.vocab_key_to_index_path, vocab_max_size=None):
"""
vocab基本操作封装
:param vocab_file:
:param vocab_max_size:
"""
self.word2index, self.index2word = self.load_vocab(vocab_file, vocab_max_size)
self.count = len(self.word2index)
@staticmethod
def load_vocab(file_path, vocab_max_size=None):
word2index = {mask: index for index, mask in enumerate(Vocab.MASKS)}
index2word = {index: mask for index, mask in enumerate(Vocab.MASKS)}
vocab_dict = json.load(fp=open(file_path, 'r', encoding='utf-8')).items()
vocab_dict = vocab_dict if vocab_max_size is None else list(vocab_dict)[: vocab_max_size]
for word, index in vocab_dict:
word2index[word] = index + Vocab.MASKS_COUNT
index2word[index + Vocab.MASKS_COUNT] = word
return word2index, index2word
def word_to_index(self, word):
return self.word2index[word] if word in self.word2index else self.word2index[self.UNKNOWN_TOKEN]
def index_to_word(self, word_index):
assert word_index in self.index2word, f'word index [{word_index}] not found in vocab'
return self.index2word[word_index]
def size(self):
return self.count
4. word2vec模型重新训练
在对训练集和测试集进行oov和填充处理
后,此时我们的数据集已经发生了变化,因此需要重新训练词向量模型。本项目采用增量式训练
,即在原有模型上面继续训练。样例代码如下:
# oov和pad处理后的数据,词向量重新训练
print('start retrain word2vec model')
w2v_model.build_vocab(LineSentence(config.train_x_pad_path), update=True)
w2v_model.train(LineSentence(config.train_x_pad_path), epochs=config.word2vec_train_epochs, total_examples=w2v_model.corpus_count)
w2v_model.build_vocab(LineSentence(config.train_y_pad_path), update=True)
w2v_model.train(LineSentence(config.train_y_pad_path), epochs=config.word2vec_train_epochs, total_examples=w2v_model.corpus_count)
w2v_model.build_vocab(LineSentence(config.test_x_pad_path), update=True)
w2v_model.train(LineSentence(config.test_x_pad_path),
epochs=config.word2vec_train_epochs,
total_examples=w2v_model.corpus_count)
其中,我们此时需要继续训练的内容其实只有三部分:训练集X, 训练集y,测试集X
;而测试集y是不用加入训练的。到此,我们完成了对文本数据集的一个初步的处理。
总结
- 文本句子的最大长度如何衡量?本文采用均值加标准化的方式对齐进行简单处理
- 对于未知词、长度不够等文本的基本处理,封装成一个类,方便后续添加其他可能用到的处理。
- 完整代码:text_summarization