本文章是对tf.data.TextLineDataset的学习理解
tensorflow学习笔记(使用 tf.data 加载文本数据)
TextLineDataset 通常被用来以文本文件构建数据集(原文件中的一行为一个样本) 。这适用于大多数的基于行的文本数据(例如,诗歌或错误日志) 。下面将使用相同作品(荷马的伊利亚特)三个不同版本的英文翻译,然后训练一个模型来通过单行文本确定译者。
以下是文本数据集建立的流程图
环境搭建
import tensorflow as tf
import tensorflow_datasets as tfds
import os
三个版本的翻译分别来自于:
William Cowper — text
Edward, Earl of Derby — text
Samuel Butler — text
以下是翻译的部分内容
本教程中使用的文本文件已经进行过一些典型的预处理,主要包括删除了文档页眉和页脚,行号,章节标题。请下载这些已经被局部改动过的文件。
通过循环,逐一从浏览器上下载3个文件,返回文件被保存在计算机中的路径。
利用os.path.dirname,去掉文件名,返回目录。
DIRECTORY_URL = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
FILE_NAMES = ['cowper.txt', 'derby.txt', 'butler.txt']
for name in FILE_NAMES:
text_dir = tf.keras.utils.get_file(name, origin=DIRECTORY_URL+name)
parent_dir = os.path.dirname(text_dir)
将文本加载到数据集中
os.path.join用于路径拼接,以’cowper.txt’为例,最终返回C:\Users\HUAWEI.keras\datasets\cowper.txt。
tf.data.TextLineDataset读取文本中数据,一行代表一组数据,将数据映射到tensorflow的dataset。
tf.cast()用于执行tensorflow中张量数据类型转换,此函数中将标签数据格式转换为tf.int64。
def labeler(example, index):
return example, tf.cast(index, tf.int64)
tf.data.TextLineDataset读取文本中数据,一行代表一组数据,将数据映射到tensorflow的dataset。返回
map接收一个函数,Dataset中的每个元素都会被当作这个函数的输入,并将函数返回值作为新的Dataset。该段代码将lines_dataset中的每一项贴上标签,将标签和字符串绑定,返回得labeled_dataset
最后,将所有标记好的数据集打包为labeled_data_sets。
labeled_data_sets = []
for i, file_name in enumerate(FILE_NAMES):
lines_dataset = tf.data.TextLineDataset(os.path.join(parent_dir, file_name))
labeled_dataset = lines_dataset.map(lambda ex: labeler( ex, i))
labeled_data_sets.append(labeled_dataset)
concatenate将labeled_data_sets中的三个元素合并起来,神经网络中的concatenate操作经常用于将特征联合。
对合并后的数据集进行随机化操作。最完美的shuffle是所有数据一起shuffle,但可能会导致内存不够,故每次选buffer_size个数据进行shuffle。比如Dataset一共10000个元素,buffer_size=1000, 那么首先会对前一千个元素进行shuffle。
reshuffle_each_iteration如果为true,则表示每次迭代数据集时都应伪随机地重排。
BUFFER_SIZE = 50000
BATCH_SIZE = 64
TAKE_SIZE = 5000
all_labeled_data = labeled_data_sets[0]
for labeled_dataset in labeled_data_sets[1:]:
all_labeled_data = all_labeled_data.concatenate(labeled_dataset)
all_labeled_data = all_labeled_data.shuffle(
BUFFER_SIZE, reshuffle_each_iteration=False)
使用 tf.data.Dataset.take 与 print 来查看 (example, label) 对的外观。numpy 属性显示每个 Tensor 的值。
tf.data.Dataset.take用于返回一个新的Dataset对象,新的Dataset对象包含的数据是原Dataset对象的子集。
for ex in all_labeled_data.take(5):
print(ex)
将文本编码成数字
机器学习基于的是数字而非文本,所以字符串需要被转化成数字列表。 为了达到此目的,我们需要构建文本与整数的一一映射。
建立词汇表
首先,通过将文本标记为单独的单词集合来构建词汇表。在本教程中:
迭代每个样本的 numpy 值。
使用 tfds.features.text.Tokenizer 来将其分割成 token。
Tokenize用来对文本中的词进行统计计数,生成文档词典,以支持基于词典位序生成文本的向量表示。
若返回module ‘tensorflow_datasets.core.features‘ has no attribute ‘text‘
这是因为一些方法已被弃用,换用tfds.deprecated.text.Tokenizer来创建tokenizer对象。
tokenizer = tfds.features.text.Tokenizer()
将这些 token 放入一个 Python 集合(set())中,借此来清除重复项。
vocabulary_set = set()
update() 方法用于修改当前集合,可以添加新的元素或集合到当前集合中,如果添加的元素在集合中已存在,则该元素只会出现一次,重复的会忽略。
最终得到所有单词出现的集合vocabulary_set
for text_tensor, _ in all_labeled_data:
some_tokens = tokenizer.tokenize(text_tensor.numpy())
vocabulary_set.update(some_tokens)
获取该词汇表的大小以便于以后使用。
vocab_size = len(vocabulary_set)
样本编码
TokenTextEncoder,用于基于词汇文件的单词级编码
encoder = tfds.features.text.TokenTextEncoder(vocabulary_set)
编码器的 encode 方法传入一行文本,返回一个整数列表。
example_text = next(iter(all_labeled_data))[0].numpy()
print(example_text)
b'To Ida; in his presence once arrived,'
encoded_example = encoder.encode(example_text)
print(encoded_example)
现在,在数据集上运行编码器(通过将编码器打包到 tf.py_function 并且传参至数据集的 map 方法的方式来运行)。
def encode(text_tensor, label):
encoded_text = encoder.encode(text_tensor.numpy())
return encoded_text, label
本例中,tf.py_function使用了3个参数:func(encode),inp,Tout。func函数接受一个或者是多个Tensor对象作为函数参数,并且返回一个或者是多个Tensor,或返回None。
函数的参数定义的Tensor需要与inp所指定的参数个数相符合;返回的Tensor的元素的类型需要与Tout所指定的元素类型相符合。
def encode_map_fn(text, label):
# py_func doesn't set the shape of the returned tensors.
encoded_text, label = tf.py_function(encode,
inp=[text, label],
Tout=(tf.int64, tf.int64))
# `tf.data.Datasets` work best if all components have a shape set
# so set the shapes manually:
encoded_text.set_shape([None])
label.set_shape([])
return encoded_text, label
all_encoded_data = all_labeled_data.map(encode_map_fn)
将数据集分割为测试集和训练集
使用 tf.data.Dataset.take 和 tf.data.Dataset.skip 来建立一个小一些的测试数据集和稍大一些的训练数据集。
tf.data.Dataset.skip(count)创建一个数据集,新数据是被修饰的数据集跳过了前count个元素得到的
在数据集被传入模型之前,数据集需要被分批。最典型的是,每个分支中的样本大小与格式需要一致。但是数据集中样本并不全是相同大小的(每行文本字数并不相同)。因此,使用 tf.data.Dataset.padded_batch(而不是 batch )将样本填充到相同的大小。
train_data = all_encoded_data.skip(TAKE_SIZE).shuffle(BUFFER_SIZE)
train_data = train_data.padded_batch(BATCH_SIZE)
test_data = all_encoded_data.take(TAKE_SIZE)
test_data = test_data.padded_batch(BATCH_SIZE)
现在,test_data 和 train_data 不是( example, label )对的集合,而是批次的集合。每个批次都是一对(多样本, 多标签 ),表示为数组。
sample_text, sample_labels = next(iter(test_data))
sample_text[0], sample_labels[0]
(<tf.Tensor: shape=(16,), dtype=int64, numpy= array([15746, 11433,
8394, 9006, 379, 3463, 17072, 0, 0,
0, 0, 0, 0, 0, 0, 0])>, <tf.Tensor: shape=(), dtype=int64, numpy=0>)
由于引入了一个新的 token 来编码(填充(padded_batch)用的零),因此词汇表大小增加了一个。
vocab_size += 1
建立模型
model = tf.keras.Sequential()
Embedding 是一个将离散变量转为连续向量表示的一个方式,将正整数(索引)转换为固定大小的向量,只能用作模型中的第一层。
本例中,将其input_dim=vocab_size,output_dim=64。
one-hot编码也可用于将特征转化成向量,但前者转换后维度十分高。
model.add(tf.keras.layers.Embedding(vocab_size, 64))
下一层是 LSTM 层,它允许模型利用上下文中理解单词含义。 LSTM 上的双向包装器有助于模型理解当前数据点与其之前和之后的数据点的关系。
64是该模型的输出维度。
tf.keras.layers.Bidirectional用于实现RNN神经网络的双向构造,比如LSTM、GRU等
model.add(tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64)))
输出层输出样本属于各个标签的概率,最后具有最高概率的分类标签即为最终预测结果。
for units in [64, 64]:
model.add(tf.keras.layers.Dense(units, activation='relu'))
该模型本质是将译文分为3类,故输出层节点为3。
model.add(tf.keras.layers.Dense(3, activation='softmax'))
编译这个模型。
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
训练模型
model.fit(train_data, epochs=3, validation_data=test_data)