【文本摘要项目】2-数据标签分离及其数值化

背景

    本文承接上一篇数据预处理与词向量训练内容,上文已描述了我们的任务和目标。本篇中,主要记录在训练和预处理后的一些初步的细节处理,如句子长度对齐、oov初步处理、构造后续用与训练与测试的Xy等等。

核心内容

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
posted @ 2021-06-07 21:51  温良Miner  阅读(355)  评论(0编辑  收藏  举报
分享到: