引言
前段時(shí)間在做一門課程的期末大作業(yè)的時(shí)候,用到了TensorFlow,構(gòu)建了含有兩層卷積層的神經(jīng)網(wǎng)絡(luò)去做 交通標(biāo)志的識(shí)別,一開始使用 24x24 的圖像作為輸入(把數(shù)據(jù)集的圖像都resize為24x24)后來感覺應(yīng)該設(shè)計(jì)大一點(diǎn)會(huì)可靠一點(diǎn),那就想把輸入的圖像都改為 64x64 的大小,相應(yīng)修改了網(wǎng)絡(luò)一些參數(shù)后,run的時(shí)候發(fā)現(xiàn)出問題了,我還以為是代碼沒有改好,仔細(xì)看一下提示信息:run out of memory 。原來是是內(nèi)存不足
那在了解到情況后,就上網(wǎng)找方法,于是乎,找到了個(gè)普遍的解決方法:使用mini_batch方法訓(xùn)練,好,那下面就是一些整理網(wǎng)上的資料了
什么是Mini_Batch方法
點(diǎn)這里可以看比較簡(jiǎn)略的介紹。
先簡(jiǎn)單介紹一下這三個(gè)常見的名詞:batch_size ,iteration,epoch
- batchsize:批大小。在深度學(xué)習(xí)中,一般采用SGD訓(xùn)練,即每次訓(xùn)練在訓(xùn)練集中取batchsize個(gè)樣本訓(xùn)練;
- iteration:1個(gè)iteration等于使用batchsize個(gè)樣本訓(xùn)練一次;
- epoch:1個(gè)epoch等于使用訓(xùn)練集中的全部樣本訓(xùn)練一次
總體來說Mini_Batch就是介于SGD(隨機(jī)梯度下降)和BGD(批梯度下降)之間的一種比較不錯(cuò)的方法,batch_size選擇合適了,既能提高訓(xùn)練速度,又能求得一個(gè)逼近全局最優(yōu)解的結(jié)果(但是在實(shí)際運(yùn)用中應(yīng)該要多次修改才能獲得合適的size),點(diǎn)這里可以看一些關(guān)于怎么選擇好batch_size的建議
怎么實(shí)現(xiàn)?
那問題來了,怎么在tensorflow中實(shí)現(xiàn)Mini_batch訓(xùn)練呢?一開始我的數(shù)據(jù)集是使用pickle存在硬盤上的
with open( 'images.pkl') as f:
# training_images 就是存儲(chǔ)了很多的使用opencv讀取的圖像,它們的類型都是np.array
training_images = pickle.load(f)
那如果是按照順序讀取,每次從取training_images的一部分,然后再取它的下一部分,似乎很容易實(shí)現(xiàn),但是如果每次喂進(jìn)去的樣本沒有隨機(jī)性的話,那似乎便失去了Mini Batch的意義,如果想實(shí)現(xiàn)隨機(jī)取的話,似乎不太容易,那我們可以利用tensorflow,制作適合Tensorflow的數(shù)據(jù)集TFRecords
簡(jiǎn)單制作TFRecords
使用TFRecord有什么好處,能把它轉(zhuǎn)成二進(jìn)制,tensorflow對(duì)它會(huì)加速。處理起來更快;可以配合tensorflow里的函數(shù),配合使用起來更方便
直接上代碼(參考網(wǎng)上)
with open('Training_images64x64.pkl') as f1,open('Training_labels.pkl') as f2:
print "loading...,please waitting for few seconds"
images = pickle.load(f1) # 數(shù)據(jù)
labels = pickle.load(f2) # 樣本標(biāo)簽
# 構(gòu)建一個(gè)writer,待會(huì)用來把TFRecords寫入硬盤的
writer = tf.python_io.TFRecordWriter("train.tfrecords")
num = len(labels)
print "the total account of the samples:",num
for i in range(num):
# build tf record
label = eval(labels[i]) # 寫入的標(biāo)簽是要數(shù)字
img = images[i]
img_raw = img.tobytes() # TFRecord 要把它轉(zhuǎn)化成字節(jié)
# 重點(diǎn),開始映射了
example = tf.train.Example(features=tf.train.Features(feature={
"label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label])),
'img_raw': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw]))
}))
# 寫入到硬盤
writer.write(example.SerializeToString()) # Serialize To String
print "all Done"
# 結(jié)束,close。跟文件操作挺類似的
writer.close()
讀取并使用TFRecords
首先先定義一個(gè)讀取 TFRecords的函數(shù)吧
# 使用隊(duì)列讀取數(shù)據(jù),這個(gè)隊(duì)列在tensorflow里面有特別的含義
# 把數(shù)據(jù)放在隊(duì)列里有很多好處,可以完成訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)的解耦
def read_and_decode(filename):
# 根據(jù)文件名生成一個(gè)隊(duì)列
filename_queue = tf.train.string_input_producer([filename])
# 定義reader,跟之前定義writer是對(duì)應(yīng)的
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue) #返回文件名和文件
features = tf.parse_single_example(serialized_example,
features={
'label': tf.FixedLenFeature([], tf.int64),
'img_raw' : tf.FixedLenFeature([], tf.string),
})
img = tf.decode_raw(features['img_raw'], tf.uint8)
# 下面這個(gè)reshape 可以根據(jù)自己的需要來決定要不要重新定義大小
# img = tf.reshape(img, [224, 224, 3])
# 轉(zhuǎn)化為tensorflow 的 float32,
img = tf.cast(img, tf.float32) * (1. / 255) - 0.5
label = tf.cast(features['label'], tf.int32)
return img, label
接下來是怎么使用的問題了。假設(shè)已經(jīng)定義好計(jì)算圖了
假設(shè)計(jì)算圖是已經(jīng)定義好的 為 graph
# 先度硬盤上讀取出來,利用上面定義好的函數(shù)
img, label = read_and_decode("train.tfrecords")
sess = tf.Session(graph=graph) # 定義回話
init.run(session=sess) # 初始化
# 啟動(dòng)隊(duì)列線程
threads = tf.train.start_queue_runners(sess=sess)
sess.run(fetches=init,feed_dict={images_ph:training_images, labels_ph:training_labels})
# !!!劃重點(diǎn)了!!!
# 使用shuffle_batch,tensorflow可以有效地幫我們隨機(jī)從訓(xùn)練數(shù)據(jù)中隨機(jī)抽出batch_size個(gè)數(shù)據(jù)樣本
image_batch ,label_batch = tf.train.shuffle_batch([img, label] ,batch_size=30)
# 上面這個(gè)函數(shù),還有capacity等其他參數(shù),作用還未明白
# image_train ,label_train = tf.train.shuffle_batch([img, label] ,batch_size=30, capacity=2000,min_after_dequeue=1000)
iteration_times = 2000 # 假定迭代次數(shù)為200
# 開始訓(xùn)練
for i in range(0,iteration_times):
# 每次都要run一次,否則取不到說好的batch的數(shù)據(jù)喲
sess.run([image_train,label_train])
# 接下來就可以把image_train,label_train
# 喂給你的訓(xùn)練節(jié)點(diǎn)了
_, loss_value =sess.run(
[train_op,loss],
feed_dict={images_ph: image_train, labels_ph:label_train}
)
# 其他代碼
訓(xùn)練完之后別忘記保存模型參數(shù)喲,具體的介紹可以看我的另一篇文章
關(guān)于隊(duì)列的詳細(xì)介紹,可以看這里,還挺復(fù)雜的
參考代碼
小結(jié)一下
我在自己的問題上,在實(shí)際訓(xùn)練的時(shí)候,使用實(shí)驗(yàn)室的帶GPU的服務(wù)器,訓(xùn)練的結(jié)果有些慢,損失loss震蕩比較厲害,一直沒有收斂,可能還是網(wǎng)絡(luò)設(shè)計(jì)的有問題吧。
后來在提交作業(yè)的時(shí)候,選擇了32x32的輸入圖像(梯度下降法,沒有使用分批訓(xùn)練),反而在測(cè)試集上的準(zhǔn)確率比較高,對(duì)于Mini_batch的訓(xùn)練方法。batch_size的選擇還是缺少指導(dǎo)方針呀。大過小都不好,實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)呀,此次作業(yè)收獲良多。