[tf]Dataset的使用 + 制作词汇表和训练数据
2018-12-17 本文已影响30人
VanJordan
从1.4版本起数据集框架从tf.contrib.data
迁移到了tf.data
,成为了TensorFlow的核心组成部件。
在数据集框架中,每一个数据集代表一个数据的来源:数据可以是一个张量,一个TFRecord文件,一个文本文件,等等。由于训练数据通常无法全部写入内存中,从数据集中读取数据时通常需要使用一个迭代器按顺序进行读取,数据集也是计算图中的一个节点。
从一个张量中创建数据集
-
from_tensor_slices
:表示从张量中获取数据。 -
make_one_shot_iterator()
:表示只将数据读取一次,然后就抛弃这个数据了。
input_data = [1,2,3,5,8]
dataset = tf.data.Dataset.from_tensor_slices(input_data)
iterator = dataset.make_one_shot_iterator()
x = iterator.get_next()
dataset中的常用函数
-
dataset = dataset.map(parser)
:map是在数据集中的最常用的操作,表示对数据集中的每一条数据都调用参数中指定的parser方法,对每一条数据处理后,map将处理后的数据包装成一个新的数据集后返回。
搭配lambda函数是最为常用的形式
dataset = dataset.map( lambda x :preprocess_for_train(x, image_size, image_size, None)
-
dataset = dataset.shuffle(buffer_size)
:buffle的机制是在内存缓冲区中保存一个buffer_size条数据,每读入一条数据后,从这个缓冲区中随机选择一条数据进行输出,缓冲区的大小越大,随机的性能就越好,但是也更耗费内存。 dataset = dataset.batch(batch_size)
-
dataset = dataset.repeat(N)
表示将数据复制N份 -
concatenate()
:表示将两个数据集顺序连接起来。 -
take(N)
:从数据集中读取前N项数据。 -
skip(N)
:表示在数据集中跳过前N项数据。 -
flap_map()
表示从多个数据集中轮流读取数据。
制作词汇表
-
counter = collections.Counter()
:创作计数器counter[word] += 1
-
sorted_word_to_cnt = sorted(counter.items(), key=itemgetter(1), reverse=True)
:表示根据词频对单词进行排序。
operator.itemgetter函数
operator模块提供的itemgetter函数用于获取对象的哪些维的数据,参数为一些序号。看下面的例子
a = [1,2,3]
>>> b=operator.itemgetter(1) //定义函数b,获取对象的第1个域的值
>>> b(a)
2
>>> b=operator.itemgetter(1,0) //定义函数b,获取对象的第1个域和第0个的值
>>> b(a)
(2, 1)
要注意,operator.itemgetter函数获取的不是值,而是定义了一个函数,通过该函数作用到对象上才能获取值。
-
vocab = [w.strip() for w in f_vocab.readlines()]
读取词汇表的信息。 -
word_to_id = {k: v for v, k in enumerate(vocab)}
制作映射字典 -
return word_to_id[word] if word in word_to_id else word_to_id["<unk>"]
对不在词汇表中的单词返回"<unk>"的id -
words = line.strip().split() + ["<eos>"] =
读取单词并添加<eos>结束符,
out_line = ' '.join([str(get_id(w)) for w in words]) + '\n'
,将单词数据用id数据代换。
import codecs
import collections
from operator import itemgetter
MODE = "PTB" # 将MODE设置为"PTB", "TRANSLATE_EN", "TRANSLATE_ZH"之一。
if MODE == "PTB": # PTB数据处理
RAW_DATA = "../../datasets/PTB_data/ptb.train.txt" # 训练集数据文件
VOCAB_OUTPUT = "ptb.vocab" # 输出的词汇表文件
elif MODE == "TRANSLATE_ZH": # 翻译语料的中文部分
RAW_DATA = "../../datasets/TED_data/train.txt.zh"
VOCAB_OUTPUT = "zh.vocab"
VOCAB_SIZE = 4000
elif MODE == "TRANSLATE_EN": # 翻译语料的英文部分
RAW_DATA = "../../datasets/TED_data/train.txt.en"
VOCAB_OUTPUT = "en.vocab"
VOCAB_SIZE = 10000
# 对单词按照词频进行排序
counter = collections.Counter()
with codecs.open(RAW_DATA, "r", "utf-8") as f:
for line in f:
for word in line.strip().split():
counter[word] += 1
# 按词频顺序对单词进行排序。
sorted_word_to_cnt = sorted(counter.items(), key=itemgetter(1), reverse=True)
sorted_words = [x[0] for x in sorted_word_to_cnt]
# 插入特殊符号
if MODE == "PTB":
# 稍后我们需要在文本换行处加入句子结束符"<eos>",这里预先将其加入词汇表。
sorted_words = ["<eos>"] + sorted_words
elif MODE in ["TRANSLATE_EN", "TRANSLATE_ZH"]:
# 在9.3.2小节处理机器翻译数据时,除了"<eos>"以外,还需要将"<unk>"和句子起始符
# "<sos>"加入词汇表,并从词汇表中删除低频词汇。
sorted_words = ["<unk>", "<sos>", "<eos>"] + sorted_words
if len(sorted_words) > VOCAB_SIZE:
sorted_words = sorted_words[:VOCAB_SIZE]
# 保存
with codecs.open(VOCAB_OUTPUT, 'w', 'utf-8') as file_output:
for word in sorted_words:
file_output.write(word + "\n")
# 读取词汇表,并建立词汇到单词编号的映射。
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>"]
制作训练数据
-
tf.string_split( source, delimiter=' ', skip_empty=True )
:返回的是一个sparseTensor ,TensorFlow使用三个dense tensor来表达一个sparse tensor:indices、values、dense_shape。indices表示索引,values表示索引处的值,shape表示形状。例如tf.SparseTensorValue(indices=[[0, 0], [1, 2]], values=[1.1, 1.2], dense_shape=[2,6])
,.values
是取出对应的tensor值。
return : A SparseTensor of rank 2, the strings split according to the delimiter. The first column of the indices corresponds to the row in source and the second column corresponds to the index of the split component in this row.
-
tf.string_to_number(string, tf.int32)
类型转换。 -
dataset.map(lambda x: (x, tf.size(x)))
获取数据和对应的lenth。 -
dataset = tf.data.Dataset.zip((src_data, trg_data))
将源数据和目标数据进行合并
通过zip操作将两个Dataset合并为一个Dataset。
现在每个Dataset中每一项数据ds由4个张量组成:
ds[0][0]是源句子
ds[0][1]是源句子长度
ds[1][0]是目标句子
ds[1][1]是目标句子长度
-
((src_input, src_len), (trg_label, trg_len)) = (src_tuple, trg_tuple)
进行数据的解包 -
src_len_ok = tf.logical_and( tf.greater(src_len, 1), tf.less_equal(src_len, MAX_LEN))
删除过小或者过大的数据。 -
dataset = dataset.filter(FilterLength)
根据条件过滤掉过长或者过短的句子。 - 因为tf中的dynamic_rnn不要求每一个batch的长度是一样的,所以padded的时候,只要保证一个batch内的形状是一样的就行了
dataset.padded_batch(batch_size, padded_shapes)
。
# 使用Dataset从一个文件中读取一个语言的数据。
# 数据的格式为每行一句话,单词已经转化为单词编号。
def MakeDataset(file_path):
dataset = tf.data.TextLineDataset(file_path)
# 根据空格将单词编号切分开并放入一个一维向量。
dataset = dataset.map(lambda string: tf.string_split([string]).values)
# 将字符串形式的单词编号转化为整数。
dataset = dataset.map(
lambda string: tf.string_to_number(string, tf.int32))
# 统计每个句子的单词数量,并与句子内容一起放入Dataset中。
dataset = dataset.map(lambda x: (x, tf.size(x)))
return dataset
# 从源语言文件src_path和目标语言文件trg_path中分别读取数据,并进行填充和
# batching操作。
def MakeSrcTrgDataset(src_path, trg_path, batch_size):
# 首先分别读取源语言数据和目标语言数据。
src_data = MakeDataset(src_path)
trg_data = MakeDataset(trg_path)
# 通过zip操作将两个Dataset合并为一个Dataset。现在每个Dataset中每一项数据ds
# 由4个张量组成:
# ds[0][0]是源句子
# ds[0][1]是源句子长度
# ds[1][0]是目标句子
# ds[1][1]是目标句子长度
dataset = tf.data.Dataset.zip((src_data, trg_data))
# 删除内容为空(只包含<EOS>)的句子和长度过长的句子。
def FilterLength(src_tuple, trg_tuple):
((src_input, src_len), (trg_label, trg_len)) = (src_tuple, trg_tuple)
src_len_ok = tf.logical_and(
tf.greater(src_len, 1), tf.less_equal(src_len, MAX_LEN))
trg_len_ok = tf.logical_and(
tf.greater(trg_len, 1), tf.less_equal(trg_len, MAX_LEN))
return tf.logical_and(src_len_ok, trg_len_ok)
dataset = dataset.filter(FilterLength)
# 从图9-5可知,解码器需要两种格式的目标句子:
# 1.解码器的输入(trg_input),形式如同"<sos> X Y Z"
# 2.解码器的目标输出(trg_label),形式如同"X Y Z <eos>"
# 上面从文件中读到的目标句子是"X Y Z <eos>"的形式,我们需要从中生成"<sos> X Y Z"
# 形式并加入到Dataset中。
def MakeTrgInput(src_tuple, trg_tuple):
((src_input, src_len), (trg_label, trg_len)) = (src_tuple, trg_tuple)
trg_input = tf.concat([[SOS_ID], trg_label[:-1]], axis=0)
return ((src_input, src_len), (trg_input, trg_label, trg_len))
dataset = dataset.map(MakeTrgInput)
# 随机打乱训练数据。
dataset = dataset.shuffle(10000)
# 规定填充后输出的数据维度。
padded_shapes = (
(tf.TensorShape([None]), # 源句子是长度未知的向量
tf.TensorShape([])), # 源句子长度是单个数字
(tf.TensorShape([None]), # 目标句子(解码器输入)是长度未知的向量
tf.TensorShape([None]), # 目标句子(解码器目标输出)是长度未知的向量
tf.TensorShape([]))) # 目标句子长度是单个数字
# 调用padded_batch方法进行batching操作。
batched_dataset = dataset.padded_batch(batch_size, padded_shapes)
return batched_dataset