本教程涉及使用卷积神经网络训练分类器。源代码可在此链接中找到。
在本教程中,我们将尝试教您如何使用 卷积神经网络(CNN)实现简单的神经网络图像分类器。这篇文章的主要目标是展示使用Google开发的TensorFlow深度学习框架训练CNN分类器的热点。这里将不讨论诸如CNN细节之类的深度学习概念。为了更好地了解卷积层并实现工作方式,请参考这篇 文章。在下一节中,我们将开始描述学习分类器的过程。
输入管道
我们在本教程中处理的数据集是 MNIST数据集可能是计算机视觉中最着名的数据集,因为它简单!主要数据集包括60000次训练和10000次测试图像。但是,这些图像可能有不同的设置。我们使用的那个在测试集中是相同的但是我们将训练集分成55000个图像作为训练集和5000个图像作为在使用交叉验证来确定一些超参数的情况下的验证集。图像是28x28x1,每个图像代表从0到9的手写数字。由于本教程应该是可以使用的,我们提供了下载和提取MNIST数据作为数据对象的代码。。下载和提取MNIST数据集的代码如下:
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
mnist = input_data.read_data_sets("MNIST_data/", reshape=False, one_hot=False)
data = input.provide_data(mnist)
上面的代码在我们运行python脚本的当前目录中的MNIST_data /文件夹中下载并提取MNIST数据。重塑标志设置为False,因为我们希望图像格式为28x28x1。原因是我们的目标是训练一个以图像为输入的CNN分类器。如果one_hot标志设置为True,则将类标签作为one_hot标签返回。但是,我们将one_hot标志设置为False以进行自定义预处理和数据组织。所述input.provide_data提供函数来获取与由训练集和测试集分离特定格式的任何数据,并返回所述结构化数据对象以用于进一步处理。从现在开始,我们会考虑数据 作为数据对象。
在任何训练,验证和测试属性中,存在图像和标签的子属性。上面的图表演示的简单性并没有被描述。例如,如果 调用data.train.imege,则其形状为[number_of_training_sample,28,28,1]。建议对数据对象进行一些操作,以便更好地了解它的工作原理和输出结果。这些帖子的GitHub存储库中提供了这些代码。
在解释了数据输入管道之后,现在是时候通过本教程使用的神经网络架构了。实现的体系结构与LeNet非常相似, 尽管我们的体系结构以完全卷积的方式实现,即,没有完全连接的层,并且所有完全连接的层都被转换为相应的卷积层。为了更好地了解如何从完全连接层转换为卷积层和反向层,请参阅此链接。一般架构原理图如下:
图1:网络的一般架构。
Tensorboard将图像描绘 为TensorFlow的可视化工具。稍后在本教程中,将解释使用Tensorboard并充分利用它的方法。从图中可以看出,卷积层之后是汇集层,最后一个完全连接的层之后是漏失层以减少过度拟合。辍学仅适用于培训阶段。设计架构的代码如下:
import tensorflow as tf
slim = tf.contrib.slim
def net_architecture(images, num_classes=10, is_training=False,
dropout_keep_prob=0.5,
spatial_squeeze=True,
scope='Net'):
# Create empty dictionary
end_points = {}
with tf.variable_scope(scope, 'Net', [images, num_classes]) as sc:
end_points_collection = sc.name + '_end_points'
# Collect outputs for conv2d and max_pool2d.
with tf.contrib.framework.arg_scope([tf.contrib.layers.conv2d, tf.contrib.layers.max_pool2d],
outputs_collections=end_points_collection):
# Layer-1
net = tf.contrib.layers.conv2d(images, 32, [5,5], scope='conv1')
net = tf.contrib.layers.max_pool2d(net, [2, 2], 2, scope='pool1')
# Layer-2
net = tf.contrib.layers.conv2d(net, 64, [5, 5], scope='conv2')
net = tf.contrib.layers.max_pool2d(net, [2, 2], 2, scope='pool2')
# Layer-3
net = tf.contrib.layers.conv2d(net, 1024, [7, 7], padding='VALID', scope='fc3')
net = tf.contrib.layers.dropout(net, dropout_keep_prob, is_training=is_training,
scope='dropout3')
# Last layer which is the logits for classes
logits = tf.contrib.layers.conv2d(net, num_classes, [1, 1], activation_fn=None, scope='fc4')
# Return the collections as a dictionary
end_points = slim.utils.convert_collection_to_dict(end_points_collection)
# Squeeze spatially to eliminate extra dimensions.
if spatial_squeeze:
logits = tf.squeeze(logits, [1, 2], name='fc4/squeezed')
end_points[sc.name + '/fc4'] = logits
return logits, end_points
def net_arg_scope(weight_decay=0.0005):
#Defines the default network argument scope.
with tf.contrib.framework.arg_scope(
[tf.contrib.layers.conv2d],
padding='SAME',
weights_regularizer=slim.l2_regularizer(weight_decay),
weights_initializer=tf.contrib.layers.variance_scaling_initializer(factor=1.0, mode='FAN_AVG',
uniform=False, seed=None,
dtype=tf.float32),
activation_fn=tf.nn.relu) as sc:
return sc
函数net_arg_scope被定义为在层之间共享一些属性。在一些属性如“SAME”填充(本质上是零填充)是不同层之间的联合的情况下,它非常有用。它基本上使用一些预定义来执行共享变量。基本上,它使我们能够指定不同的操作和/或一组参数传递给arg_scope中的任何已定义的操作。因此对于这个特定情况,参数为 tf.contrib.layers.conv2d已定义,因此所有卷积层默认参数(由arg_scope设置)都是在arg_scope中定义的。使用这个有用的arg_scope操作需要做更多的工作,本教程稍后将在一般的TensorFlow实现细节中对其进行说明。值得注意的是,arg_scope定义的所有参数都可以在特定的图层定义中进行本地覆盖。作为一个例子,看一下定义tf.contrib.layers.conv2d层(卷积层),填充设置为’VALID’,尽管arg_scope操作默认将其设置为’SAME’。现在是时候通过描述如何创建卷积和池化层来解释架构本身。
ReLU已被用作除最后一层(嵌入层)之外的所有层的非线性激活功能。着名的Xavier初始化尚未用于网络的初始化,而是使用了Variance-Scaling-Initializer,它在使用ReLU激活的情况下提供了更有希望的结果。优点是保持输入方差的比例不变,因此声称它不会通过到达最后一层参考而爆炸或减少。存在不同类型的方差缩放初始化器。我们使用的是 由TensorFlow提供的理解深度前馈神经网络训练难度的论文提出的那个。是论文提出的那个 了解训练深度前馈神经网络的难度, 并由TensorFlow提供。
现在是时候使用上面python脚本的net_architecture面板中定义的卷积和池化层构建卷积体系结构了。值得注意的是,由于层(输出张量)的输出因尺寸而不同,当我们经过网络深度时,输出尺寸逐渐减小,因此必须考虑层的输入 - 输出之间的匹配,并且最后,最后一层的输出应转换为特征向量,以便馈送到嵌入层。
如图所示,定义池化层非常简单。定义的池化层的内核大小为2x2,每个维度的步长为2。这相当于在每个2x2窗口中提取最大值,并且步长在所选窗口中不会重叠以进行最大池化操作。为了更好地理解汇集层,请参阅此链接。
可以使用tf.contrib.layers定义卷积层 。如前所述,默认填充设置为“SAME”。松散地说,'SAME’填充等于输出特征映射和输入特征映射的相同空间维度,其包含零填充以匹配形状,并且理论上,它在输入映射的每一侧均等地完成。另一方面,'有效’意味着没有填充。卷积层的整体架构如下图所示:
图2:卷积层中的操作。
的数量输出特征图被设置为32和空间内核的大小被设定为[5,5]。默认情况下, 步幅为[1,1]。所述范围参数是针对这是有用的在不同情况下,如在返回该层的输出,微调网络和相似的附图使用Tensorboard网络的一个更好的曲线图的图形优势层定义的名称。基本上,它是图层的代表,并将所有操作添加到更高级别的节点中。
我们覆盖了填充类型。它被更改为’VALID’填充。原因在于卷积层的特征。它作为完全连接的层运行。这不是因为’VALID’填充。'VALID’填充只是数学运算的一部分。原因是该层的输入具有7x7的空间大小, 并且该层的内核大小相同。这是显而易见的,因为当卷积层的输入大小等于其内核大小并且使用’VALID’池时,如果输出特征映射的数量等于1,则输出仅是一个单个神经元。因此,如果输出特征的数量地图等于1024,这个图层运行方式和filly-connected图层有1024个输出隐藏单位!
辍学是最着名的方法之一,以防止过度拟合。该操作随机杀死一部分神经元以随机地迫使神经元学习更有用的信息。该方法具有随机性,在神经网络体系结构中得到了广泛应用,并取得了良好的效果。dropout_keep_prob参数确定保持不变的神经元部分,并且不会被丢失层禁用。此外,该标志is_training应该影响漏失层,并迫使它是活性在训练阶段和去激活在测试/评估阶段。
卷积层产生具有维数的4维张量
[batch_size,width,height,channel]。结果,嵌入层
除了指示批次的第一个通道外,它们组合了所有通道。因此[batch_size,width,height,channel]的维度变为[batch_size,width x height x channel]。这是softmax之前的最后一个完全连接的层,其输出单元的数量必须等于类的数量。此图层的输出具有[batch_size,1,1,num_classes]的维度。该tf.squeeze函数执行嵌入操作,其输出维度为[batch_size,num_classes]。值得注意的是,最后一层的范围会覆盖scope =‘fc4’。
此时,在描述了网络设计和不同层之后,现在是时候介绍如何使用TensorFlow实现这种架构。使用TensorFlow,一切都应该在名为GRAPH的东西上定义。图表有责任告诉TensorFlow后端要做什么以及如何进行所需的操作。TensorFlow使用Session来运行操作。
图形操作在包含变量状态的会话环境中执行。为了运行每个创建的会话,需要一个特定的图表,因为每个会话只能在一个图表上运行。因此,单个会话中不能使用多个图表。如果用户未明确使用其名称的会话,则TensorFlow将使用默认会话。
图表包含张量和在该图上定义的操作。因此,该图可用于多个会话。与会话一样,如果用户未明确定义图形,则TensorFlow本身会设置默认图形。虽然使用默认图表没有任何危害,但建议明确定义图表。实验设置的一般图如下:
该图在我们的实验中明确定义。以下脚本逐面板显示了我们实验的图形设计:
graph = tf.Graph()
with graph.as_default():
# global step
global_step = tf.Variable(0, name="global_step", trainable=False)
# learning rate policy
decay_steps = int(num_train_samples / FLAGS.batch_size *
FLAGS.num_epochs_per_decay)
learning_rate = tf.train.exponential_decay(FLAGS.initial_learning_rate,
global_step,
decay_steps,
FLAGS.learning_rate_decay_factor,
staircase=True,
name='exponential_decay_learning_rate')
# Place holders
image_place = tf.placeholder(tf.float32, shape=([None, height, width, num_channels]), name='image')
label_place = tf.placeholder(tf.float32, shape=([None, FLAGS.num_classes]), name='gt')
dropout_param = tf.placeholder(tf.float32)
# MODEL
arg_scope = net.net_arg_scope(weight_decay=0.0005)
with tf.contrib.framework.arg_scope(arg_scope):
logits, end_points = net.net_architecture(image_place, num_classes=FLAGS.num_classes, dropout_keep_prob=dropout_param,
is_training=FLAGS.is_training)
# Define loss
with tf.name_scope('loss'):
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=label_place))
# Accuracy
with tf.name_scope('accuracy'):
# Evaluate model
correct_pred = tf.equal(tf.argmax(logits, 1), tf.argmax(label_place, 1))
# Accuracy calculation
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
# Define optimizer by its default values
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
# Gradient update.
with tf.name_scope('train'):
grads_and_vars = optimizer.compute_gradients(loss)
train_op = optimizer.apply_gradients(grads_and_vars, global_step=global_step)
arr = np.random.randint(data.train.images.shape[0], size=(3,))
tf.summary.image('images', data.train.images[arr], max_outputs=3,
collections=['per_epoch_train'])
# Histogram and scalar summaries sammaries
for end_point in end_points:
x = end_points[end_point]
tf.summary.scalar('sparsity/' + end_point,
tf.nn.zero_fraction(x), collections=['train', 'test'])
tf.summary.histogram('activations/' + end_point, x, collections=['per_epoch_train'])
# Summaries for loss, accuracy, global step and learning rate.
tf.summary.scalar("loss", loss, collections=['train', 'test'])
tf.summary.scalar("accuracy", accuracy, collections=['train', 'test'])
tf.summary.scalar("global_step", global_step, collections=['train'])
tf.summary.scalar("learning_rate", learning_rate, collections=['train'])
# Merge all summaries together.
summary_train_op = tf.summary.merge_all('train')
summary_test_op = tf.summary.merge_all('test')
summary_epoch_train_op = tf.summary.merge_all('per_epoch_train')
为方便起见,将在以下小节中使用相同的命名约定来解释上述各部分。
如前所述,建议手动设置图形,在该部分中,我们将图形命名为图形。稍后,它会注意到这个定义很有用,因为我们可以将图形传递给其他函数和会话,并且它将被识别。
学习过程需要不同的参数。global_step就是其中之一。定义global_step背后的原因是要跟踪我们在培训过程中的位置。这是一个不可学习的张量,应该按每个梯度更新递增,这将在每个批次上完成。decay_steps确定应该通过预定义策略减少学习速率的步数或时期。可以看出,num_epochs_per_decay定义了衰减因子,该因子被限制为传递的历元数。learning_rate张量确定学习率策略。请参阅TensorFlow官方文档,以便更好地了解tf.train.exponential_decay图层。值得注意的是tf.train.exponential_decay层将global_step作为其计数器,以实现何时必须更改学习速率。
tf.placeholder操作创建一个占位符变量tensor,它将在测试/培训阶段提供给网络。图像和标签必须具有占位符,因为它们实质上是训练/测试阶段的网络输入。必须将占位符的类型和形状定义为必需参数。shape参数的第一个维度设置为 None,允许占位符获取任何维度。第一个维度是batch_size并且是灵活的。
dropout_param占位符用于保持神经元活跃的概率。为dropout参数定义占位符的原因是为了使设置能够在运行每个任意会话时使用此参数,从而丰富实验以在运行测试会话时禁用它。
默认提供的参数由arg_scope运算符确定 。该 tf.nn.softmax_cross_entropy_with_logits是对未归一logits作为损失函数操作。此功能在内部计算softmax激活,使其更稳定。最后,计算准确度。
现在是定义培训申请的时候了。Adam Optimizer被用作最佳的当前优化算法之一,并且由于其自适应特性和出色的性能而被广泛使用。必须使用定义的损失张量计算梯度,并且必须将这些计算作为列车操作添加到图形中。基本上’train_op’是为参数的梯度更新运行的操作。每次执行’train_op’都是一个训练步骤。通过将’global_step’传递给优化器,每次运行’train_op’时,TensorFlow都会更新’global_step’并将其递增1!
在本节中,我们将介绍如何创建汇总操作并将其保存到已分配的张量中。最后,摘要应显示在Tensorboard中,以便可视化网络黑盒内发生的事情。有不同类型的摘要。在该实现中使用三种类型的图像,标量和直方图摘要。为了避免这篇文章变得过于冗长,我们不会对摘要操作进行深入的解释,我们将在另一篇文章中再回过头来解释它。
创建图像摘要,其具有将输入元素可视化到摘要张量的任务。这些元素是来自列车数据的3个随机图像。在不同层的输出将被馈送到相关的汇总张量。最后,创建一些标量摘要以跟踪训练收敛和测试性能。摘要定义中的collections参数是一个主管,它将每个摘要张量指向相关操作。例如,某些摘要只需要在培训阶段生成,而某些摘要仅在测试中需要。我们也有一个名为’per_epoch_train’的集合,并且在训练阶段每个纪元只需要生成一次的摘要将存储在此列表中。最后,使用collections键将摘要收集在相应的摘要列表中。
现在是时候完成培训程序了。它由不同的步骤组成,这些步骤由会话配置启动以保存 模型检查点。
首先,为方便起见,应收集张量,并且必须配置会话。代码如下:
tensors_key = ['cost', 'accuracy', 'train_op', 'global_step', 'image_place', 'label_place', 'dropout_param',
'summary_train_op', 'summary_test_op', 'summary_epoch_train_op']
tensors = [loss, accuracy, train_op, global_step, image_place, label_place, dropout_param, summary_train_op,
summary_test_op, summary_epoch_train_op]
tensors_dictionary = dict(zip(tensors_key, tensors))
# Configuration of the session
session_conf = tf.ConfigProto(
allow_soft_placement=FLAGS.allow_soft_placement,
log_device_placement=FLAGS.log_device_placement)
sess = tf.Session(graph=graph, config=session_conf)
很明显,所有张量都存储在字典中,稍后由相应的键使用。allow_soft_placement标志允许在不同设备之间来回切换。当用户将“GPU”分配给所有操作而不考虑使用TensorFlow的GPU不支持所有操作时,这非常有用。在这种情况下,如果allow_soft_placement操作员被禁用,错误可以阻止程序继续,用户必须启动调试过程,但活动标志的使用通过自动从不受支持的设备切换到受支持的设备来防止此问题。log_device_placement标志用于显示在哪些设备上设置了哪些操作。这对于调试很有用,它会在终端中投射一个详细的对话框。最终,使用定义的图形进行会话。训练阶段使用以下脚本开始:
with sess.as_default():
# Run the saver.
# 'max_to_keep' flag determine the maximum number of models that the tensorflow save and keep. default by TensorFlow = 5.
saver = tf.train.Saver(max_to_keep=FLAGS.max_num_checkpoint)
# Initialize all variables
sess.run(tf.global_variables_initializer())
###################################################
############ Training / Evaluation ###############
###################################################
train_evaluation.train(sess, saver, tensors_dictionary, data,
train_dir=FLAGS.train_dir,
finetuning=FLAGS.fine_tuning,
num_epochs=FLAGS.num_epochs, checkpoint_dir=FLAGS.checkpoint_dir,
batch_size=FLAGS.batch_size)
train_evaluation.evaluation(sess, saver, tensors_dictionary, data,
checkpoint_dir=FLAGS.checkpoint_dir)
运行tf.train.Saver是为了提供保存和加载模型的操作。该max_to_keep标志确定保存的模型,该TensorFlow保持其默认值由TensorFlow设置为“5”的最大数量。运行会话以初始化所有必需的变量。最后,提供train_evaluation函数以运行训练/测试阶段。
培训功能如下:
from __future__ import print_function
import tensorflow as tf
import numpy as np
import progress_bar
import os
import sys
def train(sess, saver, tensors, data, train_dir, finetuning,
num_epochs, checkpoint_dir, batch_size):
"""
This function run the session whether in training or evaluation mode.
:param sess: The default session.
:param saver: The saver operator to save and load the model weights.
:param tensors: The tensors dictionary defined by the graph.
:param data: The data structure.
:param train_dir: The training dir which is a reference for saving the logs and model checkpoints.
:param finetuning: If fine tuning should be done or random initialization is needed.
:param num_epochs: Number of epochs for training.
:param checkpoint_dir: The directory of the checkpoints.
:param batch_size: The training batch size.
:return:
Run the session.
"""
# The prefix for checkpoint files
checkpoint_prefix = 'model'
###################################################################
########## Defining the summary writers for train /test ###########
###################################################################
train_summary_dir = os.path.join(train_dir, "summaries", "train")
train_summary_writer = tf.summary.FileWriter(train_summary_dir)
train_summary_writer.add_graph(sess.graph)
test_summary_dir = os.path.join(train_dir, "summaries", "test")
test_summary_writer = tf.summary.FileWriter(test_summary_dir)
test_summary_writer.add_graph(sess.graph)
# If fie-tuning flag in 'True' the model will be restored.
if finetuning:
saver.restore(sess, os.path.join(checkpoint_dir, checkpoint_prefix))
print("Model restored for fine-tuning...")
###################################################################
########## Run the training and loop over the batches #############
###################################################################
for epoch in range(num_epochs):
total_batch_training = int(data.train.images.shape[0] / batch_size)
# go through the batches
for batch_num in range(total_batch_training):
#################################################
########## Get the training batches #############
#################################################
start_idx = batch_num * batch_size
end_idx = (batch_num + 1) * batch_size
# Fit training using batch data
train_batch_data, train_batch_label = data.train.images[start_idx:end_idx], data.train.labels[
start_idx:end_idx]
########################################
########## Run the session #############
########################################
# Run optimization op (backprop) and Calculate batch loss and accuracy
# When the tensor tensors['global_step'] is evaluated, it will be incremented by one.
batch_loss, _, train_summaries, training_step = sess.run(
[tensors['cost'], tensors['train_op'], tensors['summary_train_op'],
tensors['global_step']],
feed_dict={tensors['image_place']: train_batch_data,
tensors['label_place']: train_batch_label,
tensors['dropout_param']: 0.5})
########################################
########## Write summaries #############
########################################
# Write the summaries
train_summary_writer.add_summary(train_summaries, global_step=training_step)
# # Write the specific summaries for training phase.
# train_summary_writer.add_summary(train_image_summary, global_step=training_step)
#################################################
########## Plot the progressive bar #############
#################################################
progress = float(batch_num + 1) / total_batch_training
progress_bar.print_progress(progress, epoch_num=epoch + 1, loss=batch_loss)
# ################################################################
# ############ Summaries per epoch of training ###################
# ################################################################
train_epoch_summaries = sess.run(tensors['summary_epoch_train_op'],
feed_dict={tensors['image_place']: train_batch_data,
tensors['label_place']: train_batch_label,
tensors['dropout_param']: 0.5})
# Put the summaries to the train summary writer.
train_summary_writer.add_summary(train_epoch_summaries, global_step=training_step)
#####################################################
########## Evaluation on the test data #############
#####################################################
# WARNING: In this evaluation the whole test data is fed. In case the test data is huge this implementation
# may lead to memory error. In presence of large testing samples, batch evaluation on testing is
# recommended as in the training phase.
test_accuracy_epoch, test_summaries = sess.run([tensors['accuracy'], tensors['summary_test_op']],
feed_dict={tensors['image_place']: data.test.images,
tensors[
'label_place']: data.test.labels,
tensors[
'dropout_param']: 1.})
print("Epoch " + str(epoch + 1) + ", Testing Accuracy= " + \
"{:.5f}".format(test_accuracy_epoch))
###########################################################
########## Write the summaries for test phase #############
###########################################################
# Returning the value of global_step if necessary
current_step = tf.train.global_step(sess, tensors['global_step'])
# Add the counter of global step for proper scaling between train and test summaries.
test_summary_writer.add_summary(test_summaries, global_step=current_step)
###########################################################
############ Saving the model checkpoint ##################
###########################################################
# # The model will be saved when the training is done.
# Create the path for saving the checkpoints.
if not os.path.exists(checkpoint_dir):
os.makedirs(checkpoint_dir)
# save the model
save_path = saver.save(sess, os.path.join(checkpoint_dir, checkpoint_prefix))
print("Model saved in file: %s" % save_path)
############################################################################
########## Run the session for pur evaluation on the test data #############
############################################################################
def evaluation(sess, saver, tensors, data, checkpoint_dir):
# The prefix for checkpoint files
checkpoint_prefix = 'model'
# Restoring the saved weights.
saver.restore(sess, os.path.join(checkpoint_dir, checkpoint_prefix))
print("Model restored...")
# Evaluation of the model
test_accuracy = 100 * sess.run(tensors['accuracy'], feed_dict={tensors['image_place']: data.test.images,
tensors[
'label_place']: data.test.labels,
tensors[
'dropout_param']: 1.})
print("Final Test Accuracy is %% %.2f" % test_accuracy)
注释中描述了函数的输入参数。摘要编写器是针对列车和测试阶段单独定义的。程序检查是否需要微调,然后加载模型,然后继续操作。批次从训练数据中提取。对于单个训练步骤,将对一批数据评估模型,并更新模型参数和权重。该模型最终将被保存。
训练循环将摘要保存在列车汇总部分中。通过使用Tensorboard并指向保存日志的目录,我们可以可视化培训过程。列车的损失和准确性如下所示:
最后一个完全连接层的激活将在下图中描述:
图5:最后一层的激活。
对于最后一层,可以对神经元输出的分布进行可视化。通过使用直方图摘要,可以在整个训练步骤中显示分布。结果如下:
图7:测试准确度。
训练阶段终端进度条的表示如下:
为了澄清结果,需要考虑很少的事情:
在本教程中,我们使用卷积神经网络训练神经网络分类器。MNIST数据用于简化及其广泛使用。TensorFlow已被用作深度学习框架。本教程的主要目标是使用TensorFLow提供一个简单易用的训练分类器实现。此类别中的许多教程看起来在代码中过于冗长或在解释中过于简短。我的努力是提供一个在编码意义上易于理解的教程,并且在描述意义上是全面的。为简单起见,有些TensorFlow(如摘要)和数据输入管道的细节已被忽略。我们将在以后的帖子中回复他们。我希望你喜欢它。
完整见代码