我有一个巨大的培训CSV文件(709M)和一个大型测试CSV文件(125M),我希望在使用高级Tensorflow API的上下文中将其发送到DNNClassifier
。
input_fn
和fit
接受的evaluate
param似乎必须在内存中保存所有功能和标签数据,但我目前希望在本地计算机上运行此功能,因此,如果我将这些文件读入内存然后处理它们,那么它会很快耗尽内存。
我浏览了streamed-reading of data上的文档,但是用于读取CSV的示例代码似乎是针对低级Tensorflow API的。
并且 - 如果你原谅了一些抱怨 - 对于将训练有素的训练和测试数据文件发送到Estimator
的琐碎用例来说似乎过于复杂......尽管如此在Tensorflow中训练和测试大量数据实际上需要复杂程度吗?
无论如何,我真的很感激使用高级API的方法,如果它甚至可能,我开始怀疑。
在探索之后,我确实找到了DNNClassifier#partial_fit
,并尝试将其用于训练。
如何使用这种方法的例子可以节省一些时间,但希望我会在接下来的几个小时内偶然发现正确的用法。
然而,似乎没有相应的DNNClassifier#partial_evaluate
...虽然我怀疑我可以将测试数据分解成更小的部分并在每个批次上连续运行DNNClassifier#evaluate
实际上可能是一个很好的方法,因为我可以将测试数据分成同类群组,从而获得每队列的准确性。
====更新====
简短版本:
DomJack的建议应该是公认的答案。
然而,我的Mac的16GB RAM足以将整个709Mb训练数据集保存在内存中而不会崩溃。因此,当我最终部署应用程序时,我将使用DataSet功能,但我还没有将它用于本地开发工作。
更长的版本:
我开始使用如上所述的partial_fit
API,但每次使用时都会发出警告。
所以,我去查看方法here的源代码,发现它的完整实现如下:
logging.warning('The current implementation of partial_fit is not optimized'
' for use in a loop. Consider using fit() instead.')
return self.fit(x=x, y=y, input_fn=input_fn, steps=steps,
batch_size=batch_size, monitors=monitors)
......这让我想起了Hitchhiker's Guide的这个场景:
Arthur Dent:如果我按下这个按钮会怎么样?Ford Prefect:我不会 -
亚瑟登特:哦。Ford Prefect:发生了什么事?
Arthur Dent:一个标志亮了起来,说'请不要再按此按钮'。这就是说:partial_fit
似乎只是为了告诉你不要使用它而存在。
此外,在训练文件块上迭代使用partial_fit
生成的模型远小于在整个训练文件中使用fit
生成的模型,这强烈表明只有最后{{1}培训块实际上是“拿走”。
答案 0 :(得分:18)
查看tf.data.Dataset
API。有许多方法可以创建数据集。我将概述四个 - 但您只需实施一个。
我假设您的csv
个文件的每一行都是n_features
个浮点值,后跟一个int
值。
tf.data.Dataset
Dataset.from_generator
最简单的入门方法是包装本机python生成器。这可能会出现性能问题,但可能适用于您的目的。
def read_csv(filename):
with open(filename, 'r') as f:
for line in f.readlines():
record = line.rstrip().split(',')
features = [float(n) for n in record[:-1]]
label = int(record[-1])
yield features, label
def get_dataset():
filename = 'my_train_dataset.csv'
generator = lambda: read_csv(filename)
return tf.data.Dataset.from_generator(
generator, (tf.float32, tf.int32), ((n_features,), ()))
这种方法非常通用,允许您独立于TensorFlow测试您的生成器函数(read_csv
)。
支持tensorflow版本1.12 +,tensorflow数据集是我最喜欢的创建数据集的方式。它会自动序列化您的数据,收集统计信息,并通过info
和builder
对象向您提供其他元数据。它还可以处理自动下载和提取,使协作变得简单。
将tensorflow_datasets导入为tfds
class MyCsvDatasetBuilder(tfds.core.GeneratorBasedBuilder):
VERSION = tfds.core.Version("0.0.1")
def _info(self):
return tfds.core.DatasetInfo(
builder=self,
description=(
"My dataset"),
features=tfds.features.FeaturesDict({
"features": tfds.features.Tensor(
shape=(FEATURE_SIZE,), dtype=tf.float32),
"label": tfds.features.ClassLabel(
names=CLASS_NAMES),
"index": tfds.features.Tensor(shape=(), dtype=tf.float32)
}),
supervised_keys=("features", "label"),
)
def _split_generators(self, dl_manager):
paths = dict(
train='/path/to/train.csv',
test='/path/to/test.csv',
)
# better yet, if the csv files were originally downloaded, use
# urls = dict(train=train_url, test=test_url)
# paths = dl_manager.download(urls)
return [
tfds.core.SplitGenerator(
name=tfds.Split.TRAIN,
num_shards=10,
gen_kwargs=dict(path=paths['train'])),
tfds.core.SplitGenerator(
name=tfds.Split.TEST,
num_shards=2,
gen_kwargs=dict(cvs_path=paths['test']))
]
def _generate_examples(self, csv_path):
with open(csv_path, 'r') as f:
for i, line in enumerate(f.readlines()):
record = line.rstrip().split(',')
features = [float(n) for n in record[:-1]]
label = int(record[-1])
yield dict(features=features, label=label, index=i)
用法:
builder = MyCsvDatasetBuilder()
builder.download_and_prepare() # will only take time to run first time
# as_supervised makes output (features, label) - good for model.fit
datasets = builder.as_dataset(as_supervised=True)
train_ds = datasets['train']
test_ds = datasets['test']
上述缺点之一是使用大小为n
的shuffle缓冲区对结果数据集进行混洗,需要加载n
个示例。这将在您的管道中创建定期暂停(大n
)或导致可能较差的混乱(小n
)。
def get_record(i):
# load the ith record using standard python, return numpy arrays
return features, labels
def get_inputs(batch_size, is_training):
def tf_map_fn(index):
features, labels = tf.py_func(
get_record, (index,), (tf.float32, tf.int32), stateful=False)
features.set_shape((n_features,))
labels.set_shape(())
# do data augmentation here
return features, labels
epoch_size = get_epoch_size()
dataset = tf.data.Dataset.from_tensor_slices((tf.range(epoch_size,))
if is_training:
dataset = dataset.repeat().shuffle(epoch_size)
dataset = dataset.map(tf_map_fn, (tf.float32, tf.int32), num_parallel_calls=8)
dataset = dataset.batch(batch_size)
# prefetch data to CPU while GPU processes previous batch
dataset = dataset.prefetch(1)
# Also possible
# dataset = dataset.apply(
# tf.contrib.data.prefetch_to_device('/gpu:0'))
features, labels = dataset.make_one_shot_iterator().get_next()
return features, labels
简而言之,我们只创建一个记录索引的数据集(或任何我们可以完全加载到内存中的小记录ID)。然后,我们对此最小数据集进行改组/重复操作,然后map
通过tf.data.Dataset.map
和tf.py_func
将索引转换为实际数据。有关用法,请参阅下面的Using with Estimators
和Testing in isolation
部分。请注意,这需要您的数据可以按行访问,因此您可能需要将csv
转换为其他格式。
您还可以使用csv
直接阅读tf.data.TextLineDataset
文件。
def get_record_defaults():
zf = tf.zeros(shape=(1,), dtype=tf.float32)
zi = tf.ones(shape=(1,), dtype=tf.int32)
return [zf]*n_features + [zi]
def parse_row(tf_string):
data = tf.decode_csv(
tf.expand_dims(tf_string, axis=0), get_record_defaults())
features = data[:-1]
features = tf.stack(features, axis=-1)
label = data[-1]
features = tf.squeeze(features, axis=0)
label = tf.squeeze(label, axis=0)
return features, label
def get_dataset():
dataset = tf.data.TextLineDataset(['data.csv'])
return dataset.map(parse_row, num_parallel_calls=8)
parse_row
函数有点复杂,因为tf.decode_csv
期望批处理。如果在解析之前批量处理数据集,则可以使其更简单。
def parse_batch(tf_string):
data = tf.decode_csv(tf_string, get_record_defaults())
features = data[:-1]
labels = data[-1]
features = tf.stack(features, axis=-1)
return features, labels
def get_batched_dataset(batch_size):
dataset = tf.data.TextLineDataset(['data.csv'])
dataset = dataset.batch(batch_size)
dataset = dataset.map(parse_batch)
return dataset
或者,您可以将csv
文件转换为TFRecord文件并使用TFRecordDataset。这是一个全面的教程here。
步骤1:将csv
数据转换为TFRecords数据。以下示例代码(请参阅上面read_csv
示例中的from_generator
)。
with tf.python_io.TFRecordWriter("my_train_dataset.tfrecords") as writer:
for features, labels in read_csv('my_train_dataset.csv'):
example = tf.train.Example()
example.features.feature[
"features"].float_list.value.extend(features)
example.features.feature[
"label"].int64_list.value.append(label)
writer.write(example.SerializeToString())
这只需要运行一次。
步骤2:编写解码这些记录文件的数据集。
def parse_function(example_proto):
features = {
'features': tf.FixedLenFeature((n_features,), tf.float32),
'label': tf.FixedLenFeature((), tf.int64)
}
parsed_features = tf.parse_single_example(example_proto, features)
return parsed_features['features'], parsed_features['label']
def get_dataset():
dataset = tf.data.TFRecordDataset(['data.tfrecords'])
dataset = dataset.map(parse_function)
return dataset
def get_inputs(batch_size, shuffle_size):
dataset = get_dataset() # one of the above implementations
dataset = dataset.shuffle(shuffle_size)
dataset = dataset.repeat() # repeat indefinitely
dataset = dataset.batch(batch_size)
# prefetch data to CPU while GPU processes previous batch
dataset = dataset.prefetch(1)
# Also possible
# dataset = dataset.apply(
# tf.contrib.data.prefetch_to_device('/gpu:0'))
features, label = dataset.make_one_shot_iterator().get_next()
estimator.train(lambda: get_inputs(32, 1000), max_steps=1e7)
我强烈建议您独立于估算工具测试数据集。使用上面的get_inputs
,它应该像
batch_size = 4
shuffle_size = 100
features, labels = get_inputs(batch_size, shuffle_size)
with tf.Session() as sess:
f_data, l_data = sess.run([features, labels])
print(f_data, l_data) # or some better visualization function
假设您使用GPU来运行网络,除非csv
文件的每一行都很庞大且您的网络很小,否则您可能无法发现性能上的差异。这是因为Estimator
实现强制在CPU上执行数据加载/预处理,而prefetch
意味着可以在CPU上准备下一批,因为当前批处理是在GPU上进行训练。唯一的例外是,如果您在每个记录有大量数据的数据集上有大量的随机大小,那么在通过GPU运行任何内容之前,最初需要花费一些时间来加载大量示例。
答案 1 :(得分:3)
我同意DomJack关于使用Dataset
API,除了需要读取整个csv文件然后转换为TfRecord
。我特此建议TextLineDataset
- Dataset
API的子类,直接将数据加载到TensorFlow程序中。可以找到一个直观的教程here。
下面的代码用于说明MNIST分类问题,希望能回答OP的问题。 csv文件有784列,类的数量是10.我在这个例子中使用的分类器是一个具有16个relu单元的1-隐藏层神经网络。
首先,加载库并定义一些常量:
# load libraries
import tensorflow as tf
import os
# some constants
n_x = 784
n_h = 16
n_y = 10
# path to the folder containing the train and test csv files
# You only need to change PATH, rest is platform independent
PATH = os.getcwd() + '/'
# create a list of feature names
feature_names = ['pixel' + str(i) for i in range(n_x)]
其次,我们使用Dataset API创建一个读取文件的输入函数,然后将结果提供给Estimator API。返回值必须是按如下方式组织的双元素元组:第一个元素必须是一个dict,其中每个输入要素是一个键,然后是训练批次的值列表,第二个元素是标签列表对于培训批次。
def my_input_fn(file_path, batch_size=32, buffer_size=256,\
perform_shuffle=False, repeat_count=1):
'''
Args:
- file_path: the path of the input file
- perform_shuffle: whether the data is shuffled or not
- repeat_count: The number of times to iterate over the records in the dataset.
For example, if we specify 1, then each record is read once.
If we specify None, iteration will continue forever.
Output is two-element tuple organized as follows:
- The first element must be a dict in which each input feature is a key,
and then a list of values for the training batch.
- The second element is a list of labels for the training batch.
'''
def decode_csv(line):
record_defaults = [[0.]]*n_x # n_x features
record_defaults.insert(0, [0]) # the first element is the label (int)
parsed_line = tf.decode_csv(records=line,\
record_defaults=record_defaults)
label = parsed_line[0] # First element is the label
del parsed_line[0] # Delete first element
features = parsed_line # Everything but first elements are the features
d = dict(zip(feature_names, features)), label
return d
dataset = (tf.data.TextLineDataset(file_path) # Read text file
.skip(1) # Skip header row
.map(decode_csv)) # Transform each elem by applying decode_csv fn
if perform_shuffle:
# Randomizes input using a window of 256 elements (read into memory)
dataset = dataset.shuffle(buffer_size=buffer_size)
dataset = dataset.repeat(repeat_count) # Repeats dataset this # times
dataset = dataset.batch(batch_size) # Batch size to use
iterator = dataset.make_one_shot_iterator()
batch_features, batch_labels = iterator.get_next()
return batch_features, batch_labels
然后,小批量可以计算为
next_batch = my_input_fn(file_path=PATH+'train1.csv',\
batch_size=batch_size,\
perform_shuffle=True) # return 512 random elements
接下来,我们定义要素列是数字
feature_columns = [tf.feature_column.numeric_column(k) for k in feature_names]
第三,我们创建了一个估算工具DNNClassifier
:
classifier = tf.estimator.DNNClassifier(
feature_columns=feature_columns, # The input features to our model
hidden_units=[n_h], # One layer
n_classes=n_y,
model_dir=None)
最后,使用测试csv文件训练DNN,同时在测试文件上执行评估。请更改repeat_count
和steps
,以确保培训符合代码中所需的时代数。
# train the DNN
classifier.train(
input_fn=lambda: my_input_fn(file_path=PATH+'train1.csv',\
perform_shuffle=True,\
repeat_count=1),\
steps=None)
# evaluate using the test csv file
evaluate_result = classifier.evaluate(
input_fn=lambda: my_input_fn(file_path=PATH+'test1.csv',\
perform_shuffle=False))
print("Evaluation results")
for key in evaluate_result:
print(" {}, was: {}".format(key, evaluate_result[key]))