背景
學習Tensorflow的代碼,加強工程應用能力,本篇文章將對CIFAR10數據集利用簡單的CNN模型進行分類,對代碼進行整理和分析理解。
CIFAR10數據集
CIFAR-10數據集,包含有60000張32x32彩色圖像,10個不同類別,每個類別包含6000張圖像。訓練圖像50000張,測試圖像10000張。詳見http://www.cs.toronto.edu/~kriz/cifar.html
代碼學習及分析
1.加載cifer10數據
'''1.加載數據集'''
(x,y), (x_test, y_test) = datasets.cifar10.load_data() #下載cifar10數據集,自動下載,返回ndarray
y = tf.squeeze(y, axis=1) #返回一個tensor,這個tensor的每個維度都不爲1
y_test = tf.squeeze(y_test, axis=1)
print(x.shape, y.shape, x_test.shape, y_test.shape)
#x是(50000,32,32,3)表示5w張32x32x3的圖像,y是(50000,)x_test是(10000,32,32,3)y_test是(10000,),
'''訓練集'''
train_db = tf.data.Dataset.from_tensor_slices((x,y))
train_db = train_db.shuffle(1000).map(preprocess).batch(128)#隨機打散、預處理、批處理
'''測試集'''
test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test))
test_db = test_db.map(preprocess).batch(64)
sample = next(iter(train_db)) #從訓練集中採樣一個 Batch,並觀察
print('sample:', sample[0].shape, sample[1].shape,
tf.reduce_min(sample[0]), tf.reduce_max(sample[0]))
在這個代碼中,from_tensor_slices
的作用是把給定的元組、列表和張量等數據進行特徵切片,將圖片x和標籤y轉換爲數據集對象,train_db的shape和type如下圖,另外,參數的數據集對象,一般都要經過一些數據處理,例如預處理、隨機打散、按批裝載等,接下來會提到這些函數
train_db.shuffle(1000)
的作用是隨機打散,防止按順序的產生,出現記憶
map(preprocess)
是一個映射函數,也就是將上一步的結果(隨機打散後的數據集)進行preprocess處理,具體方法由自己實現,這裏使用下列代碼,也就是歸一化操作
def preprocess(x, y):
# [0~1]
x = 2*tf.cast(x, dtype=tf.float32) / 255.-1
y = tf.cast(y, dtype=tf.int32)
return x,y
batch(128)
是設之數據集爲批訓練模式,也就是每128個數據喂入網絡進行訓練,這個大小得按照自己處理器的性能來決定。當然,batch_size的選擇是有要求的,越大,訓練時間越大,內存容量也受不了,或會導致梯度下降方向不再改變;越小,不收斂,精度低。因此得適中。
2.構建網絡模型
這裏使用VGG13作爲網絡模型,如下圖所示。
首先是卷積網絡部分,包含有5個單元,每個單元都是2個卷積層+1個池化層:
conv_layers = [ # 5 units of conv + max pooling
# 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'
]
conv_net = Sequential(conv_layers)
然後是全連接網絡部分,3個全連接層,使用的ReLU作爲激活函數:
fc_net = Sequential([
layers.Dense(256, activation=tf.nn.relu),
layers.Dense(128, activation=tf.nn.relu),
layers.Dense(10, activation=None),
])
然後是對其進行build,利用summary函數就可以打印網絡參數
conv_net.build(input_shape=[None, 32, 32, 3])
fc_net.build(input_shape=[None, 512])
conv_net.summary()
fc_net.summary()
3.訓練網絡
optimizer = optimizers.Adam(lr=1e-4)
# [1, 2] + [3, 4] => [1, 2, 3, 4]
variables = conv_net.trainable_variables + fc_net.trainable_variables
for epoch in range(50):
for step, (x,y) in enumerate(train_db):
with tf.GradientTape() as tape:
# [b, 32, 32, 3] => [b, 1, 1, 512]
out = conv_net(x)
# flatten, => [b, 512]
out = tf.reshape(out, [-1, 512])
# [b, 512] => [b, 10]
logits = fc_net(out)
# [b] => [b, 10]
y_onehot = tf.one_hot(y, depth=10)
# compute loss
# loss = tf.losses.categorical_crossentropy(y_onehot, logits, from_logits=True)#tf2.0
loss = tf.keras.backend.categorical_crossentropy(y_onehot, 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)) # tf2.0
print(epoch, step, 'loss:', 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) #得到得分值最大那個的序號
pred = tf.cast(pred, dtype=tf.int32)
correct = tf.cast(tf.equal(pred, y), dtype=tf.int32)
correct = tf.reduce_sum(correct)
total_num += x.shape[0]
total_correct += int(correct)
acc = total_correct / total_num
print(epoch, 'acc:', acc)
optimizer
選用Adam,參數1e-4,優化器是用來輔助梯度下降的。
variables = conv_net.trainable_variables + fc_net.trainable_variables
表示將兩個網絡給合併在一起,最後形成VGG13
grads = tape.gradient(loss, variables);optimizer.apply_gradients(zip(grads, variables))
中,loss選用交叉熵損失函數,對網絡進行梯度求解,應用到apply_gradients方法,進行梯度下降。
one-hot()
是對標籤進行one-hot編碼,比如y分爲0~9,10個分類,那麼類別4,0001000000。目的是爲了方便進行損失計算。
acc
的計算方式就理解起來就比較容易了,對測試集中每一個數據,進行計算,放到網絡中,出來的結果是啥,跟真實標籤匹配不,匹配就加1,最後得到匹配結果的數目的比例
參考代碼
https://github.com/dragen1860/Deep-Learning-with-TensorFlow-book/tree/master/ch10
相關參考資料
1.from_tensor_slices
的作用
2.深度學習中batch_size大小選擇的影響
3.Adam算法
4.交叉熵損失函數
[其它自查]