心宽一寸,受益三分。心宽路就宽,心窄路就窄。不争自然能得到人们的尊崇,能忍则忍,一忍百安。
全文字数:2666字
阅读时间:8分钟
前言
由于在公众号上文本字数太长可能会影响阅读体验,因此过于长的文章,我会使用"[L1]"来进行分段。这个系列将主要借鉴《Tensorflow实战Google学习框架》这本书,主要介绍实现语言模型的一些前期准备,后期会出更详细的文章。
a
PTB 数据集
PTB(Penn Treebank Dataset)文本数据集是目前语言模型学习中使用最为广泛的数据集。在使用数据集之前,我们肯定需要对PTB数据集做充分的了解,才能够为后续的处理提供方便。
PTB数据集的下载地址: https://link.zhihu.com/?target=http%3A//www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz
▲下载解压后的文件目录
我们做RNN Language Mode的时候只需要使用data路径下的三个数据集即可:
ptb.test.txt #测试集数据文件 ptb.train.txt #训练集数据文件 ptb.valid.txt #验证集数据文件
当然这三个数据文件中的数据已经过预处理(未登录词都用<unk>进行替换,没有数字文本),相邻的单词之间用空格隔开。数据集中共包含了9998种不同的单词词汇。在构建词汇表的时候需要添加一些特殊的词汇:
所以也就是说数据集中一共有10002种不同的词汇。
b
数据的预处理
为了方便理解讲解说明时采用了代码段的方式实现,并没有使用函数进行抽象,后面会给出详细的函数代码。
▍2.1 文本文件 -> 词汇表
为了将文本转换为模型可以读入的单词序列,需要将这些不同的词汇分别映射到0~10001(因为我们这里有10002种不同的单词)之间的整数编号。也就是说首先要按照词频的顺序为每个词汇分配一个编号,然后将这些词汇表保存到一个独立的vocab文件中。
import codecs
import collections
from operator import itemgetter
import re
RAW_DATA = r'./data/ptb.train.txt'
VOCAB_OUTPUT = r'./model/model_ptb/ptb.train.vocab'
_DIGIT_RE = re.compile(r"\d")
normalize_digits = True
counter = collections.Counter()
with codecs.open(RAW_DATA,'r','utf-8') as f:
for line in f:
for word in line.strip().split():
#判断是否有数字,如果有的话就将其替换成"0"
#当时ptb中没有数字,所以不需要对数字进行处理
# word = _DIGIT_RE.sub(r"0", word) if normalize_digits else word
counter[word] += 1
print(counter.items())
#按照词频顺序对单词进行排序
sorted_word_to_cnt = sorted(counter.items(),key = itemgetter(1),reverse = True)
sorted_words = [x[0] for x in sorted_word_to_cnt]
#然后我们需要加入那些特殊特殊的词汇,这里预先将其加入词汇表中
sorted_words = ["<pad>","<go>","<eos>"] + sorted_words
# 设置max_vocabulary_size词汇表的最大长度,如果没有超过一定频率的就认为是未登录词
# if len(sorted_words) > max_vocabulary_size:
# sorted_words = sorted_words[:max_vocabulary_size]
with codecs.open(VOCAB_OUTPUT,'w','utf-8') as file_output:
for word in sorted_words:
file_output.write(word+"\n")
执行代码会发现在" r'./model/model_ptb/ptb.train.vocab'"这个路径下生成一个ptb.train.vocab的文件。当然无论是训练集、验证集还是测试集我们的字典都是一样的,这个其实很好理解,只有词与数字统一起来,在训练集上训练,验证集验证以及最后的测试才能够使其表示的单词一致。
▲目录结构
▲ptb.train.vocab文件内容
下面说一下代码中的几个关键点:
counter = collections.Counter()
Counter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型(所以需要后期进行排序的处理),以字典的键值对形式存储,其中元素作为key,其计数作为value。计数值可以是任意的Interger(包括0和负数)。为hashable对象计数,是字典的子类。
from collections import Counter
cnt = Counter()
for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
cnt[word] += 1
print(cnt)
'''
result:
Counter({'blue': 3, 'red': 2, 'green': 1})
'''
sorted(iterable, cmp=None, key=None, reverse=False) --> new sorted list iterable:是可迭代类型; cmp:用于比较的函数,比较什么由key决定; key:用列表元素的某个属性或函数进行作为关键字,有默认值,迭代集合中的一项; reverse:排序规则. reverse = True 降序 或者 reverse = False 升序,有默认值。 返回值:是一个经过排序的可迭代类型,与iterable一样。
counter.items()返回dict_items,将键值对变成一个个的元组。指定sorted的key为itemgetter(1),便以每个键值对元组下标为 1 的元素进行排序。这样就完成按照词频的大小进行排序构建词汇表的工作。
▍2.2 文本文件 -> 单词的编号
上面的2.1小节确定了词汇表以后,再将训练文本、测试文本等都根据词汇文件转换为单词编号。每个单词的编号就是他在词汇文件中的行号。这里需要注意的就是我们仅仅使用train样本构建词汇表,然后根据这个词汇表去替换ptb.test.txt,ptb.train.txt,ptb.valid.txt中的单词,也就是将单词换成对应词汇表中的词频。
下面是是对train样本中的单词进行替换:
import codecs
RAW_DATA = r"./data/ptb.train.txt"#原始的训练数据集文件
VOCAB = r"./model/model_ptb/ptb.train.vocab"#词汇表文件
OUTPUT_DATA = r"./model/model_ptb_id/ptb.train.id"#将单词替换为单词编号后的输出文件
#读取词汇表,并建立词汇到单词编号的映射
with codecs.open(VOCAB,'r',"utf-8") as f_vocab:
vocab = [w.strip() for w in f_vocab.readlines()]
#以字典的形式构建单词与行号的一个映射关系
word_to_id = {k:v for (k,v) in zip(vocab,range(len(vocab)))}
#如果出现了被删除的低频词,则替换为'<unk>'
def get_id(word):
return word_to_id[word] if word in word_to_id else word_to_id['<unk>']
fin = codecs.open(RAW_DATA,'r',"utf-8")
fout = codecs.open(OUTPUT_DATA,'w',"utf-8")
for line in fin:
#读取单词并添加<go>以及<eos>
words = ['<go>'] + line.strip().split() + ['<eos>']
#将每个单词替换为词汇表中的编号
output_line = ' '.join([str(get_id(w)) for w in words]) + '\n'
fout.write(output_line)
fin.close()
fout.close()
▲处理后的结果
对valid以及test样本同理使train中的方法即可:
▲数据处理以及处理后的结构
大致流程: