本文已在公眾號
機器視覺與算法建模
發布,轉載請聯系我。
使用TensorFlow的基本流程
本篇文章將介紹使用tensorflow的訓練模型的基本流程,包括制作讀取TFRecord,訓練和保存模型,讀取模型。
准備
- 語言:Python3
- 庫:tensorflow、cv2、numpy、matplotlib
- 數據集:Chars74K dataset 的數字部分
- 網絡:CNN
所有代碼已經上傳至github:https://github.com/wmpscc/TensorflowBaseDemo
TFRecord
TensorFlow提供了一種統一的格式來存儲數據,這個格式就是TFRecord.
message Example {
Features features = 1;
};
message Features{
map<string,Feature> featrue = 1;
};
message Feature{
oneof kind{
BytesList bytes_list = 1;
FloatList float_list = 2;
Int64List int64_list = 3;
}
};
從代碼中我們可以看到, tf.train.Example 包含了一個字典,它的鍵是一個字符串,值為Feature,Feature可以取值為字符串(BytesList)、浮點數列表(FloatList)、整型數列表(Int64List)。
寫入一個TFRecord一般分為三步:
- 讀取需要轉化的數據
- 將數據轉化為Example Protocol Buffer,並寫入這個數據結構
- 通過將數據轉化為字符串后,通過TFRecordWriter寫出
方法一
這次我們的數據是分別保存在多個文件夾下的,因此讀取數據最直接的方法是遍歷目錄下所有文件,然后讀入寫出TFRecord文件。該方法對應文件MakeTFRecord.py
,我們來看關鍵代碼
filenameTrain = 'TFRecord/train.tfrecords'
filenameTest = 'TFRecord/test.tfrecords'
writerTrain = tf.python_io.TFRecordWriter(filenameTrain)
writerTest = tf.python_io.TFRecordWriter(filenameTest)
folders = os.listdir(HOME_PATH)
for subFoldersName in folders:
label = transform_label(subFoldersName)
path = os.path.join(HOME_PATH, subFoldersName) # 文件夾路徑
subFoldersNameList = os.listdir(path)
i = 0
for imageName in subFoldersNameList:
imagePath = os.path.join(path, imageName)
images = cv2.imread(imagePath)
res = cv2.resize(images, (128, 128), interpolation=cv2.INTER_CUBIC)
image_raw_data = res.tostring()
example = tf.train.Example(features=tf.train.Features(feature={
'label': _int64_feature(label),
'image_raw': _bytes_feature(image_raw_data)
}))
if i <= len(subFoldersNameList) * 3 / 4:
writerTrain.write(example.SerializeToString())
else:
writerTest.write(example.SerializeToString())
i += 1
在做數據的時候,我打算將3/4的數據用做訓練集,剩下的1/4數據作為測試集,方便起見,將其保存為兩個文件。
基本流程就是遍歷Fnt目錄下的所有文件夾,再進入子文件夾遍歷其目錄下的圖片文件,然后用OpenCV的imread
方法將其讀入,再將圖片數據轉化為字符串。在TFRecord提供的數據結構中`_bytes_feature'是存儲字符串的。
以上將圖片成功讀入並寫入了TFRecord的數據結構中,那圖片對應的標簽怎么辦呢?
def transform_label(folderName):
label_dict = {
'Sample001': 0,
'Sample002': 1,
'Sample003': 2,
'Sample004': 3,
'Sample005': 4,
'Sample006': 5,
'Sample007': 6,
'Sample008': 7,
'Sample009': 8,
'Sample010': 9,
'Sample011': 10,
}
return label_dict[folderName]
我建立了一個字典,由於一個文件下的圖片都是同一類的,所以將圖片對應的文件夾名字與它所對應的標簽,產生映射關系。代碼中label = transform_label(subFoldersName)
通過該方法獲得,圖片的標簽。
方法二
在使用方法一產生的數據訓練模型,會發現非常容易產生過擬合。因為我們在讀數據的時候是將它打包成batch讀入的,雖然可以使用tf.train.shuffle_batch
方法將隊列中的數據打亂再讀入,但是由於一個類中的數據過多,會導致即便打亂后也是同一個類中的數據。例如:數字0有1000個樣本,假設你讀取的隊列長達1000個,這樣即便打亂隊列后讀取的圖片任然是0。這在訓練時容易過擬合。為了避免這種情況發生,我的想法是在做數據時將圖片打亂后寫入。對應文件MakeTFRecord2.py
,關鍵代碼如下
folders = os.listdir(HOME_PATH)
for subFoldersName in folders:
path = os.path.join(HOME_PATH, subFoldersName) # 文件夾路徑
subFoldersNameList = os.listdir(path)
for imageName in subFoldersNameList:
imagePath = os.path.join(path, imageName)
totalList.append(imagePath)
# 產生一個長度為圖片總數的不重復隨機數序列
dictlist = random.sample(range(0, len(totalList)), len(totalList))
print(totalList[0].split('\\')[1].split('-')[0]) # 這是圖片對應的類別
i = 0
for path in totalList:
images = cv2.imread(totalList[dictlist[i]])
res = cv2.resize(images, (128, 128), interpolation=cv2.INTER_CUBIC)
image_raw_data = res.tostring()
label = transform_label(totalList[dictlist[i]].split('\\')[1].split('-')[0])
print(label)
example = tf.train.Example(features=tf.train.Features(feature={
'label': _int64_feature(label),
'image_raw': _bytes_feature(image_raw_data)
}))
if i <= len(totalList) * 3 / 4:
writerTrain.write(example.SerializeToString())
else:
writerTest.write(example.SerializeToString())
i += 1
基本過程:遍歷目錄下所有的圖片,將它的路徑加入一個大的列表。通過一個不重復的隨機數序列,來控制使用哪張圖片。這就達到隨機的目的。
怎么獲取標簽呢?圖片文件都是類型-序號
這個形式命名的,這里通過獲取它的類型
名,建立字典產生映射關系。
def transform_label(imgType):
label_dict = {
'img001': 0,
'img002': 1,
'img003': 2,
'img004': 3,
'img005': 4,
'img006': 5,
'img007': 6,
'img008': 7,
'img009': 8,
'img010': 9,
'img011': 10,
}
return label_dict[imgType]
原尺寸圖片CNN
對應CNN_train.py
文件
訓練的時候怎么讀取TFRecord數據呢,參考以下代碼
# 讀訓練集數據
def read_train_data():
reader = tf.TFRecordReader()
filename_train = tf.train.string_input_producer(["TFRecord128/train.tfrecords"])
_, serialized_example_test = reader.read(filename_train)
features = tf.parse_single_example(
serialized_example_test,
features={
'label': tf.FixedLenFeature([], tf.int64),
'image_raw': tf.FixedLenFeature([], tf.string),
}
)
img_train = features['image_raw']
images_train = tf.decode_raw(img_train, tf.uint8)
images_train = tf.reshape(images_train, [128, 128, 3])
labels_train = tf.cast(features['label'], tf.int64)
labels_train = tf.cast(labels_train, tf.int64)
labels_train = tf.one_hot(labels_train, 10)
return images_train, labels_train
通過features[鍵名]
的方式將存入的數據讀取出來,鍵名和數據類型要與寫入的保持一致。
關於這里的卷積神經網絡,我是參考王學長培訓時的代碼寫的。當然照搬肯定不行,會遇到loss NaN的情況,我解決的方法是仿照AlexNet
中,在卷積后加入LRN層,進行局部響應歸一化。在設置參數時,加入l2正則項。關鍵代碼如下
def weights_with_loss(shape, stddev, wl):
var = tf.truncated_normal(stddev=stddev, shape=shape)
if wl is not None:
weight_loss = tf.multiply(tf.nn.l2_loss(var), wl, name='weight_loss')
tf.add_to_collection('losses', weight_loss)
return tf.Variable(var)
def net(image, drop_pro):
W_conv1 = weights_with_loss([5, 5, 3, 32], 5e-2, wl=0.0)
b_conv1 = biasses([32])
conv1 = tf.nn.relu(conv(image, W_conv1) + b_conv1)
pool1 = max_pool_2x2(conv1)
norm1 = tf.nn.lrn(pool1, 4, bias=1, alpha=0.001 / 9.0, beta=0.75)
W_conv2 = weights_with_loss([5, 5, 32, 64], stddev=5e-2, wl=0.0)
b_conv2 = biasses([64])
conv2 = tf.nn.relu(conv(norm1, W_conv2) + b_conv2)
norm2 = tf.nn.lrn(conv2, 4, bias=1, alpha=0.001 / 9.0, beta=0.75)
pool2 = max_pool_2x2(norm2)
W_conv3 = weights_with_loss([5, 5, 64, 128], stddev=0.04, wl=0.004)
b_conv3 = biasses([128])
conv3 = tf.nn.relu(conv(pool2, W_conv3) + b_conv3)
pool3 = max_pool_2x2(conv3)
W_conv4 = weights_with_loss([5, 5, 128, 256], stddev=1 / 128, wl=0.004)
b_conv4 = biasses([256])
conv4 = tf.nn.relu(conv(pool3, W_conv4) + b_conv4)
pool4 = max_pool_2x2(conv4)
image_raw = tf.reshape(pool4, shape=[-1, 8 * 8 * 256])
# 全連接層
fc_w1 = weights_with_loss(shape=[8 * 8 * 256, 1024], stddev=1 / 256, wl=0.0)
fc_b1 = biasses(shape=[1024])
fc_1 = tf.nn.relu(tf.matmul(image_raw, fc_w1) + fc_b1)
# drop-out層
drop_out = tf.nn.dropout(fc_1, drop_pro)
fc_2 = weights_with_loss([1024, 10], stddev=0.01, wl=0.0)
fc_b2 = biasses([10])
return tf.matmul(drop_out, fc_2) + fc_b2
128x128x3
原圖訓練過程
在驗證集上的正確率
這里使用的是1281283的圖片,圖片比較大,所以我產生了一個想法。在做TFRecord數據的時候,將圖片尺寸減半。所以就有了第二種方法。
圖片尺寸減半CNN
對應文件CNN_train2.py
與上面那種方法唯一的區別是將圖片尺寸128*128*3
改成了64*64*3
所以我這里就不重復說明了。
64x64x3
圖片訓過程
在驗證集上的正確率
保存模型
在CNN_train.py
中,對應保存模型的代碼是
def save_model(sess, step):
MODEL_SAVE_PATH = "./model128/"
MODEL_NAME = "model.ckpt"
saver = tf.train.Saver()
saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=step)
save_model(sess, i)
i
是迭代的次數,可以不填其對應的參數global_step
在測試集上檢驗准確率
對應文件AccuracyTest.py
代碼基本與訓練的代碼相同,這里直接講怎么恢復模型。關鍵代碼
ckpt = tf.train.get_checkpoint_state(MODEL_PATH)
if ckpt and ckpt.model_checkpoint_path:
#加載模型
saver.restore(sess, ckpt.model_checkpoint_path)
值得一提的是tf.train.get_checkpoint_state
該方法會自動找到文件夾下迭代次數最多的模型,然后讀入。而saver.restore(sess, ckpt.model_checkpoint_path)
方法將恢復,模型在訓練時最后一次迭代的變量參數。
查看讀入的TFRecord圖片
對應文件ReadTest.py
如果你想檢查下在制作TFRecord時,圖片是否處理的正確,最簡單的方法就是將圖片顯示出來。關鍵代碼如下
def plot_images(images, labels):
for i in np.arange(0, 20):
plt.subplot(5, 5, i + 1)
plt.axis('off')
plt.title(labels[i], fontsize=14)
plt.subplots_adjust(top=1.5)
plt.imshow(images[i])
plt.show()
plot_images(image, label
總結
在摸索過程中遇到很多問題,多虧了王學長耐心幫助,也希望這篇文章能幫助更多人吧。
新手上路,如果有錯,歡迎指正,謝謝。
代碼已上傳github:https://github.com/wmpscc/TensorflowBaseDemo