文章写的不清晰请大家原谅QAQ
这篇文章我们将用 CIFAR-10数据集做一个很简易的图片分类器。 在 CIFAR-10数据集包含了60,000张图片。在此数据集中,有10个不同的类别,每个类别中有6,000个图像。每幅图像的大小为32 x 32像素。虽然这么小的尺寸通常给人类识别正确的类别带来了困难,但它实际上是对计算机模型的简化并且减少了分析图像所需的计算。
CIFAR-10数据集
我们可以通过输入模型的大量数字序列将这些图像输入到我们的模型中。每个像素由三个浮点数标识,这三个浮点数表示该像素的红色,绿色和蓝色值(RGB值)。所以每个图像有32 x 32 x 3 = 3,072 个值0.
使用非常大的卷积神经网络可以实现高质量的结果,你可以在这个连接中学习Rodrigo Benenson’s page
下载CIFAR-10数据集,网址:Python version of the dataset, 并把他安装在我们分类器代码所在的文件夹下
先上源代码
模型的源代码:
import numpy as np
import tensorflow as tf
import time
import data_helpers
beginTime = time.time()
batch_size = 100
learning_rate = 0.005
max_steps = 1000
data_sets = data_helpers.load_data()
# Define input placeholders
images_placeholder = tf.placeholder(tf.float32, shape=[None, 3072])
labels_placeholder = tf.placeholder(tf.int64, shape=[None])
# Define variables (these are the values we want to optimize)
weights = tf.Variable(tf.zeros([3072, 10]))
biases = tf.Variable(tf.zeros([10]))
# Define the classifier's result
logits = tf.matmul(images_placeholder, weights) + biases
# Define the loss function
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,
labels=labels_placeholder))
# Define the training operation
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)
# Operation comparing prediction with true label
correct_prediction = tf.equal(tf.argmax(logits, 1), labels_placeholder)
# Operation calculating the accuracy of our predictions
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
with tf.Session() as sess:
# Initialize variables
sess.run(tf.global_variables_initializer())
# Repeat max_steps times
for i in range(max_steps):
# Generate input data batch
indices = np.random.choice(data_sets['images_train'].shape[0], batch_size)
images_batch = data_sets['images_train'][indices]
labels_batch = data_sets['labels_train'][indices]
# Periodically print out the model's current accuracy
if i % 100 == 0:
train_accuracy = sess.run(accuracy, feed_dict={
images_placeholder: images_batch, labels_placeholder: labels_batch})
print('Step {:5d}: training accuracy {:g}'.format(i, train_accuracy))
# Perform a single training step
sess.run(train_step, feed_dict={images_placeholder: images_batch,
labels_placeholder: labels_batch})
# After finishing the training, evaluate on the test set
test_accuracy = sess.run(accuracy, feed_dict={
images_placeholder: data_sets['images_test'],
labels_placeholder: data_sets['labels_test']})
print('Test accuracy {:g}'.format(test_accuracy))
endTime = time.time()
print('Total time: {:5.2f}s'.format(endTime - beginTime))
处理数据集的代码
import numpy as np
import pickle
import sys
def load_CIFAR10_batch(filename):
'''load data from single CIFAR-10 file'''
with open(filename, 'rb') as f:
if sys.version_info[0] < 3:
dict = pickle.load(f)
else:
dict = pickle.load(f, encoding='latin1')
x = dict['data']
y = dict['labels']
x = x.astype(float)
y = np.array(y)
return x, y
def load_data():
'''load all CIFAR-10 data and merge training batches'''
xs = []
ys = []
for i in range(1, 6):
filename = 'cifar-10-batches-py/data_batch_' + str(i)
X, Y = load_CIFAR10_batch(filename)
xs.append(X)
ys.append(Y)
x_train = np.concatenate(xs)
y_train = np.concatenate(ys)
del xs, ys
x_test, y_test = load_CIFAR10_batch('cifar-10-batches-py/test_batch')
classes = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse',
'ship', 'truck']
# Normalize Data
mean_image = np.mean(x_train, axis=0)
x_train -= mean_image
x_test -= mean_image
data_dict = {
'images_train': x_train,
'labels_train': y_train,
'images_test': x_test,
'labels_test': y_test,
'classes': classes
}
return data_dict
def reshape_data(data_dict):
im_tr = np.array(data_dict['images_train'])
im_tr = np.reshape(im_tr, (-1, 3, 32, 32))
im_tr = np.transpose(im_tr, (0, 2, 3, 1))
data_dict['images_train'] = im_tr
im_te = np.array(data_dict['images_test'])
im_te = np.reshape(im_te, (-1, 3, 32, 32))
im_te = np.transpose(im_te, (0, 2, 3, 1))
data_dict['images_test'] = im_te
return data_dict
def gen_batch(data, batch_size, num_iter):
data = np.array(data)
index = len(data)
for i in range(num_iter):
index += batch_size
if (index + batch_size > len(data)):
index = 0
shuffled_indices = np.random.permutation(np.arange(len(data)))
data = data[shuffled_indices]
yield data[index:index + batch_size]
def main():
data_sets = load_data()
print(data_sets['images_train'].shape)
print(data_sets['labels_train'].shape)
print(data_sets['images_test'].shape)
print(data_sets['labels_test'].shape)
if __name__ == '__main__':
main()
首先我们导入了tensorflow numpy time 以及自己写的data_help包
time是为了计算整个代码的运行时间。 data_help是将数据集做成我们训练用的数据结构
data_help中的load_data()会把60000张的CIFAR数据集分成两块:500000张的训练集和100000张的测试集,具体来说他会返回这样的一个包含如下内容的字典
-
images_train
: 训练集。一个500000张 包含3072(32x32像素点x3颜色通道)值 -
labels_train
: 训练集的50,000个标签(每个标签在0到9之间,代表训练图像所属的10个类别中的哪一个) -
images_test
: 测试集(10,000 by 3,072) -
labels_test
: 测试集的10,000个标签 -
classes
: 10个文本标签,用于将数字类值转换为单词(例如0代表'plane',1代表'car')
然后我们就可以开始建立我们的模型了
先顶两个tensroflow的占位符 这些占位符不包含任何数据,但仅指定输入数据的类型和形状:
images_placeholder = tf.placeholder(tf.float32, shape=[None, 3072]) #值得注意的是,这边的Dtype是int 还有shape是没有维度的(一维的)
然后我们定义偏置和权重
weights = tf.Variable(tf.zeros([3072, 10])) biases = tf.Variable(tf.zeros([10]))
我们的输入由3,072个浮点数组成,但我们寻找的输出是10个不同的整数值之一,代表一个类别。我们如何从3,072个值到单个值?
我们采用的简单方法是分别查看每个像素。对于每个像素和每个可能的类别,我们想知道该像素的颜色是增加还是减少属于特定类别的概率。例如,如果第一个像素是红色 - 并且如果汽车的图像通常具有红色的第一个像素,那么我们希望汽车类别的分数增加。我们通过将红色通道值乘以正数并将其添加到汽车类别得分来实现此目的。
同样,如果马图像在位置1很少有红色像素,我们希望该分数降低。这意味着乘以小数或负数并将结果添加到马匹得分中。对于10个类别中的每个类别,我们在每个像素上重复此步骤,然后总结所有3,072个值以获得单个总分。这是我们的3,072像素值的总和,由该类别的3,072参数权重加权。这里的最终结果是我们将得到10个分数 - 每个类别一个。最高分给我们分类。
使用矩阵,我们可以大大简化用于将像素值与权重值相乘并总结结果的方案。我们用3,072维向量表示单个图像。如果我们将此向量乘以3,072 x 10权重矩阵,则结果是一个10维矩阵,其中包含我们想要的加权和。
3,072 x 10矩阵中的实际值是模型参数。但是,如果它们是随机的并且毫无意义,那么输出也将是。在这里,我们可以看到训练数据的值,它准备模型以最终自己确定参数值。
在上面的两行代码中,我们通知TensorFlow 3,072 x 10加权参数矩阵 - 所有这些参数在开始时都具有初始值0。我们还定义了第二个参数:包含偏差的10维数组。偏差不直接与图像数据相互作用,而是加到加权和 - 每个分数的起点。想象一个全黑图像:所有像素值都是0,因此它的所有类别得分都是0(与权重矩阵中的值无关)。偏见允许我们从非零类别分数开始。
训练方案的工作原理如下:首先,我们输入训练数据并让模型使用当前参数值进行预测。使用正确的类别对该预测进行比较,并且该比较的数值结果称为损失。损失值越小,类别预测越接近正确的类别 - 反之亦然。目的是尽量减少损失。但在我们看一下损失最小化之前,让我们来看看如何计算损失。
# Define loss function
loss=tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits,
labels_placeholder))
TensorFlow通过提供处理所有这些的功能来处理我们的所有细节。然后,我们可以将logits中包含的模型预测与labels_placeholder(正确的类别标签)进行比较。 sparse_softmax_cross_entropy_with_logits()的输出是每个图像的损失值。最后,我们计算所有输入图像的平均损失值。
tf.nn.sparse_softmax_cross_entropy_with_logits()这个函数的功能就是计算labels和logits之间的交叉熵(cross entropy)。
import tensorflow as tf
input_data = tf.Variable([[0.2, 0.1, 0.9], [0.3, 0.4, 0.6]], dtype=tf.float32)
output = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=input_data, labels=[0, 2])
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
print(sess.run(output))
# [ 1.36573195 0.93983102]
这边顺便介绍一下tf.nn.softmax_cross_entopy_with_logits()
tf.nn.softmax_cross_entropy_with_logits(
_sentinel=None,
labels=None,
logits=None,
dim=-1,
name=None
)
第一个参数基本不用。此处不说明。
第二个参数label的含义就是一个分类标签,所不同的是,这个label是分类的概率,比如说[0.2,0.3,0.5],labels的每一行必须是一个概率分布。
现在来说明第三个参数logits,logit本身就是是一种函数,它把某个概率p从[0,1]映射到[-inf,+inf](即正负无穷区间)。这个函数的形式化描述为:logit=ln(p/(1-p))。
我们可以把logist理解为原生态的、未经缩放的,可视为一种未归一化的log 概率,如是[4, 1, -2]
于是,Softmax的工作则是,它把一个系列数从[-inf, +inf] 映射到[0,1],除此之外,它还把所有参与映射的值累计之和等于1,变成诸如[0.95, 0.05, 0]的概率向量。这样一来,经过Softmax加工的数据可以当做概率来用。
也就是说,logits是作为softmax的输入。经过softmax的加工,就变成“归一化”的概率(设为q),然后和labels代表的概率分布(设为q),于是,整个函数的功能就是前面的计算labels(概率分布p)和logits(概率分布q)之间的交叉熵
(1)如果labels的每一行是one-hot表示,也就是只有一个地方为1(或者说100%),其他地方为0(或者说0%),还可以使用tf.sparse_softmax_cross_entropy_with_logits()。之所以用100%和0%描述,就是让它看起来像一个概率分布。
(2)tf.nn.softmax_cross_entropy_with_logits()函数已经过时 (deprecated),它在TensorFlow未来的版本中将被去除。取而代之的是
tf.nn.softmax_cross_entropy_with_logits_v2()。
(3)参数labels,logits必须有相同的形状 [batch_size, num_classes] 和相同的类型(float16, float32, float64)中的一种,否则交叉熵无法计算。
(4)tf.nn.softmax_cross_entropy_with_logits 函数内部的 logits 不能进行缩放,因为在这个工作会在改函数内部进行(注意函数名称中的 softmax ,它负责完成原始数据的归一化),如果 logits 进行了缩放,那么反而会影响计算正确性。
-------------------------------------------------------------------------------------------------------------------------------------------
最后,我们计算所有输入图像的平均损失值。
# Define training operationtrain_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)
如何改变参数值以减少损失? TensorFlow在这里发光,使用一种称为自动微分的技术,它根据参数值计算损耗的梯度。它计算每个参数对总体损失的影响,以及减少或增加少量用于减少损失的程度。它试图通过递归调整所有参数值来提高准确性。完成此步骤后,将使用下一个图像组重新启动该过程。
TensorFlow包含各种优化技术,用于将梯度信息转换为参数的更新。对于本教程中的目的,我们选择简单的梯度下降选项,该选项仅检查模型的当前状态以确定如何更新参数,而不考虑先前的参数值。
对输入图像进行分类,将预测与正确的类别进行比较,计算损失以及调整参数值的过程重复了很多次。计算持续时间和成本会随着更大,更复杂的模型而迅速升级,但我们这里的简单模型不需要太多耐心或高性能设备就能看到有意义的结果。
我们代码中的下两行(下面)采取精度测量。沿维度1的logg的argmax返回具有最高分数的类别的索引,这是类别标签预测。这些标签通过tf.equal()与正确的类别类别标签进行比较,后者返回一个布尔值向量 - 它被转换为浮点值(0或1),其平均值是正确预测图像的分数。
# Operation comparing prediction with true label
correct_prediction = tf.equal(tf.argmax(logits, 1), labels_placeholder)
# Operation calculating the accuracy of our predictionsaccuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
现在我们已经定义了TensorFlow图,我们可以运行它。该图可在sess变量中访问(见下文)。我们立即初始化之前创建的变量。现在,变量定义初始值已分配给变量。
迭代训练过程开始并重复max_steps次。
# Run the TensorFlow graphwith tf.Session() as sess:
# Initialize variablessess.run(tf.initialize_all_variables())
# Repeat max_steps timesfor i in range(max_steps):
接下来的几行代码随机从训练数据中选择一些图像:
# Generate batch of input dataindices = np.random.choice(data_sets['images_train'].shape[0], batch_size)images_batch = data_sets['images_train'][indices] labels_batch = data_sets['labels_train'][indices]
上面的第一行代码选择0和训练集大小之间的batch_size随机索引。然后通过选择这些索引处的图像和类别标签来构建批次。
来自训练数据的结果图像和类别组称为批次。批量大小表示执行参数更新步骤的频率。首先,我们平均特定批次中所有图像的损失,然后通过梯度下降更新参数。
如果不是在批处理后停止并对训练集中的所有图像进行分类,我们将能够计算真正的平均损失和真正的梯度而不是使用批处理时的估计。但是每个参数更新步骤需要更多的计算。在另一个极端,我们可以将批量大小设置为1,并在每个图像后执行参数更新。这将导致更频繁的更新,但更新将更加不稳定,并且往往不会朝着正确的方向前进。通常,在这两个极端之间的某种方法可以最快地改善结果。通常最好选择尽可能大的批量大小,同时仍然能够将所有变量和中间结果放入内存中。
每100次迭代,检查训练数据批次的当前准确度。
# Periodically print out the model's current accuracyif i % 100 == 0: train_accuracy = sess.run(accuracy, feed_dict={ images_placeholder: images_batch, labels_placeholder: labels_batch}) print('Step {:5d}: training accuracy {:g}'.format(i, train_accuracy))
这是训练循环中最重要的一行,我们建议模型执行单个训练步骤:
# Perform the training stepsess.run(train_step, feed_dict={images_placeholder: images_batch, labels_placeholder: labels_batch})
已经在TensorFlow图形定义中提供了所有数据。 TensorFlow知道梯度下降更新取决于损失的值,而损失的值又取决于logits,后者取决于权重,偏差和实际输入批次。
现在只需将批量训练数据输入模型,这是通过提供一个饲料字典来完成的,其中当前的训练数据批次被分配给上面定义的占位符。
培训结束后,我们转而在测试集上运行模型。由于这是模型第一次遇到测试集,因此图像对模型来说是全新的。
记住,目标是评估训练有素的模型处理未知数据的能力
# After finishing the training, evaluate on the test settest_accuracy = sess.run(accuracy, feed_dict={ images_placeholder: data_sets['images_test'], labels_placeholder: data_sets['labels_test']})print('Test accuracy {:g}'.format(test_accuracy))
最后一行打印了培训和运行模型的持续时间。
endTime = time.time()print('Total time: {:5.2f}s'.format(endTime - beginTime))