CIFAR-10数据集由10个类的60000个32x32彩色图像组成,每个类有6000个图像。有50000个训练图像和10000个测试图像。
数据集分为五个训练批次和一个测试批次,每个批次有10000个图像。测试批次包含来自每个类别的恰好1000个随机选择的图像。训练批次以随机顺序包含剩余图像,但一些训练批次可能包含来自一个类别的图像比另一个更多。总体来说,五个训练集之和包含来自每个类的正好5000张图像。
以下是数据集中的类,以及来自每个类的10个随机图像:
CIFAR-100 数据集就像CIFAR-10,除了它有100个类,每个类包含600个图像。,每类各有500个训练图像和100个测试图像。CIFAR-100 中的100个类被分成20个超类。每个图像都带有一个“精细”标签(它所属的类)和一个“粗糙”标签(它所属的超类)。以下是 CIFAR-100 中的类别列表:
本次共搭建18个网络层:10个卷积层、5个最大池化层以及3个全连接层,其中2个卷积层+1个最大池化层为1个单元。具体网络参数如下图所示:
本次使用的Tensorflow版本为2.0.0,为方便搭建网络,利用 kears 构建网络,因此,导入 kears 的 layers、Sequential、optimizers、datasets。
import tensorflow as tf
from tensorflow.keras import layers,Sequential,optimizers,datasets
利用 kearas.datasets 函数直接导入数据集,并分为训练集与测试集,并查看数据集形状:
(x,y),(x_test,y_test) = datasets.cifar100.load_data() # 加载数据集
print('x:',x.shape,'y:',y.shape,'x_test:',x_test.shape,"y_test:",y_test.shape)
得到的数据格式如下:
因为 y 与 y_test 都是标签集,因此,其维度均应为1维,而加载后的却是二维张量,所以要进行数据处理。
y = tf.reshape(y,[50000])
y_test = tf.reshape(y_test,[10000])
print('y:',y.shape,'y_test:',y_test.shape)
通过数据与处理函数,将 numpy 类型数据变为 tensorflow 类型数据。
def preprocess(x,y): # 预处理函数
# [0-1]
x = tf.cast(x,dtype=tf.float32) / 255 # 归一化处理
y = tf.cast(y,dtype=tf.int64) # y必须是整数型,因为onehot里输入不能为float
return x,y
通过数据预处理函数便可以将别的类型的数据变为 tensorflow 类型。因为像素均为 0 -255 值,所以进行归一化处理,将数据值分布变为 0 -1。由后面要进行 one_hot 编码,所以标签集数据必须为 tfint64类型。
train_db = tf.data.Dataset.from_tensor_slices((x,y)) # 将输入的张量的第一个维度看做样本的个数,沿其第一个维度将tensor切片,得到的每个切片是一个样本数据。实现了输入张量的自动切片。
train_db = train_db.shuffle(10000).map(preprocess).batch(64) # 64个样本为一个 batch
test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test))
test_db = test_db.map(preprocess).batch(64)
首先对于训练集,对变量进行切片处理,然后利用 shuffle 函数打乱数据,利用与处理函数进行数据转换,并64个样本为一个batch;而对于测试集,并不需要来打乱样本顺序。
为便于区分,本次搭建分为两步:一是卷积层——包括卷积与池化两个操作,选择最大池化操作;二是全连接层,最后一层输出神经元为100(本次分类的类数)且最后一层不添加激活函数。
卷积层共 5 个单元,每个单元都是“2+1”组合:2个卷积层,一个池化层,具体形式如下:
conv_layers = [
# unit 1
layers.Conv2D(64,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.Conv2D(64,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.MaxPool2D(pool_size=[2,2],strides=2,padding='same'),
# unit 2
layers.Conv2D(128,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.Conv2D(128,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.MaxPool2D(pool_size=[2,2],strides=2,padding='same'),
# unit 3
layers.Conv2D(256,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.Conv2D(256,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.MaxPool2D(pool_size=[2,2],strides=2,padding='same'),
# unit 4
layers.Conv2D(512,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.Conv2D(512,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.MaxPool2D(pool_size=[2,2],strides=2,padding='same'),
# unit 5
layers.Conv2D(512,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.Conv2D(512,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.MaxPool2D(pool_size=[2,2],strides=2,padding='same')
]
全连接层共三层,其参数如下:
fc_net = Sequential([
layers.Dense(256, activation=tf.nn.relu), # 输出为 256
layers.Dense(128, activation=tf.nn.relu), # 输出为 128
layers.Dense(100, activation=None), # 输出为 100,且不加激活函数
])
因为照片是 32 × 32 × 3 32\times32\times3 32×32×3 的,因此第一部分的输入是 [ N o n e , 32 , 32 , 3 ] [None,32,32,3] [None,32,32,3] ,其中None 代表样本数;第二部分全连接层输入为 [ N o n e , 512 ] [None,512] [None,512] ,因此,为保证输入格式正确,需要对第一部分网络的输出进行 r e s h a p e reshape reshape 操作。
选择 A d a m Adam Adam 优化器,学习率设置为 1 e − 4 1e-4 1e−4 ,网络的总变量 = 第一部分变量 + 第二部分变量。
conv_net.build(input_shape=[None, 32, 32, 3])
fc_net.build(input_shape=[None,512])
optimizer = optimizers.Adam(lr=1e-4)
variables = conv_net.variables + fc_net.variables
训练 50 次, b a t c h s i z e = 64 batchsize = 64 batchsize=64 ,每训练 100 个批次,输出打印准确率与损失函数值。
for epoch in range(50):
for step,(x,y) in enumerate(train_db): # enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中
with tf.GradientTape() as tape:
# [b,32,32,3] => [b,1,1,512]
out = conv_net(x)
# flaten
out = tf.reshape(out,[-1,512])
# [b,512] => [b,100]
logits = fc_net(out)
# onehot编码:[b] => [b,100]
y_onehotcode = tf.one_hot(y,100) # y不能为float
# compute loss
loss = tf.losses.categorical_crossentropy(y_onehotcode,logits,from_logits=True)
loss = tf.reduce_mean(loss)
grads = tape.gradient(loss,variables) # 求解梯度
optimizer.apply_gradients(zip(grads,variables)) # 梯度更新
if step % 100 ==0:
print(epoch,step,'loss:',float(loss))
for x,y in test_deb:
out = conv_net(x)
out = tf.reshape(out,[-1,512]) # 保证全连接层输入正确
logits = fc_net(out)
prob = tf.nn.softmax(logits,axis=1)
pred = tf.argmax(prob,axis= 1) # 把全连接层的输出进行分类
# 求解准确率
correct = tf.cast(tf.equal(pred,y),dtype=tf.int32)
total_num += x.shape[0]
total_correct += int(correct)
acc = total_correct / total_num
print(epoch,'acc:',acc)
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" # 屏蔽tensorflow的输出日志信息,必须放在导入TF库前
import tensorflow as tf
from tensorflow.keras import layers,Sequential,optimizers,datasets
conv_layers = [
# unit 1
layers.Conv2D(64,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.Conv2D(64,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.MaxPool2D(pool_size=[2,2],strides=2,padding='same'),
# unit 2
layers.Conv2D(128,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.Conv2D(128,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.MaxPool2D(pool_size=[2,2],strides=2,padding='same'),
# unit 3
layers.Conv2D(256,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.Conv2D(256,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.MaxPool2D(pool_size=[2,2],strides=2,padding='same'),
# unit 4
layers.Conv2D(512,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.Conv2D(512,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.MaxPool2D(pool_size=[2,2],strides=2,padding='same'),
# unit 5
layers.Conv2D(512,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.Conv2D(512,kernel_size=[3,3],padding='same',activation=tf.nn.relu),
layers.MaxPool2D(pool_size=[2,2],strides=2,padding='same')
]
def preprocess(x,y): # 预处理函数
# [0-1]
x = tf.cast(x,dtype=tf.float32) / 255
y = tf.cast(y,dtype=tf.int64) # y必须是整数型,因为onehot里输入不能为float
return x,y
(x,y),(x_test,y_test) = datasets.cifar100.load_data() # 加载数据集
y = tf.reshape(y,[50000])
y_test = tf.reshape(y_test,[10000])
train_db = tf.data.Dataset.from_tensor_slices((x,y)) # 将输入的张量的第一个维度看做样本的个数,沿其第一个维度将tensor切片,得到的每个切片是一个样本数据。实现了输入张量的自动切片。
train_db = train_db.shuffle(10000).map(preprocess).batch(64)
test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test))
test_db = test_db.map(preprocess).batch(64)
def main():
# [b,32,32,3] => [b,1,1,512]
conv_net = Sequential(conv_layers)
# x =tf.random.normal([4,32,32,3])
# out = conv_net(x)
# print(out.shape)
fc_net = Sequential([
layers.Dense(256,activation=tf.nn.relu),
layers.Dense(128,activation=tf.nn.relu),
layers.Dense(100,activation=None) # 最后一个全连接层,因为有100个种类,所以输出为100
])
conv_net.build(input_shape=[None, 32, 32, 3])
fc_net.build(input_shape=[None,512])
optimizer = optimizers.Adam(lr=1e-4)
variables = conv_net.variables + fc_net.variables
for epoch in range(50):
for step,(x,y) in enumerate(train_db): # enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中
with tf.GradientTape() as tape:
# [b,32,32,3] => [b,1,1,512]
out = conv_net(x)
# flaten
out = tf.reshape(out,[-1,512])
# [b,512] => [b,100]
logits = fc_net(out)
# onehot编码:[b] => [b,100]
y_onehotcode = tf.one_hot(y,100) # y不能为float
# compute loss
loss = tf.losses.categorical_crossentropy(y_onehotcode,logits,from_logits=True)
loss = tf.reduce_mean(loss)
grads = tape.gradient(loss,variables) # 求解梯度
optimizer.apply_gradients(zip(grads,variables)) # 梯度更新 zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
# 如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同。
if step % 100 == 0:
print(epoch,step,'loss:',float(loss))
total_num = 0
total_correct = 0
for x,y in test_db:
out = conv_net(x)
out = tf.reshape(out,[-1,512])
logits = fc_net(out)
prob = tf.nn.softmax(logits,axis=1) # 进行预测
pred = tf.argmax(prob,axis=1)
correct = tf.cast(tf.equal(pred,y),dtype=tf.int32)
total_num += x.shape[0]
total_correct += int(correct)
acc = total_correct / total_num
print(epoch,'acc:',acc)
if __name__ == '__main__':
main()