作為一個小白中的小白,多折騰總是有好處的,看了入門書和往上一些教程,很多TF的教程都是從MNIST數據集入手教小白入TF的大門,都是直接import MNIST,然后直接構建網絡,定義loss和optimizer,設置超參數,之后就直接sess.run()了,雖然操作流程看上去很簡單,但如果直接給自己一堆圖片,如何讓tensorflow讀取,如何喂入網絡進行訓練,這些都不清楚,所以作為小白,先從最簡單的CNN——VGGnet入手吧,在網上隨便下載了個數據集——GTSRB(因為這個數據集最小,下載快。。= =),下載下來的數據的前處理已經在另一篇博文數據圖片處理介紹,這篇主要是TFrecords文件的制作和讀取,我不是CS專業,研究方向也跟這個毫不相關,(剛入學時和導師約定好的計算機視覺方向現在被否了,一度讓我想換導師,說來話長,此處省略一萬字),一邊要忙導師那邊的東西,一邊搞這個,可以說是很酸爽了 = =。。。這個程序折騰了近2個星期,最后可算是制服所有八阿哥,成功運行了,進入了所謂的“調參”環節,目前還很不理想,也許下面的程序還存在錯誤,但對於我這個小白來講這次折騰已經學到很多東西了。
下面進入正題。。。
TFrecords文件是tensorflow讀取數據的方式之一,主要用於數據較大的情況,TFRecords文件包含了tf.train.Example 協議內存塊(protocol buffer)(協議內存塊包含了字段 Features),
可以將自己的數據填入到Example協議內存塊(protocol buffer),將協議內存塊序列化為一個字符串, 並且通過tf.python_io.TFRecordWriter 寫入到TFRecords文件。
從TFRecords文件中讀取數據, 可以使用tf.TFRecordReader的tf.parse_single_example解析器。這個操作可以將Example協議內存塊(protocol buffer)解析為張量。
上面的內容來自:https://www.cnblogs.com/upright/p/6136265.html
下面直接貼代碼吧,有些部分並非原創,很多說明都寫在代碼中了(好吧,我承認我懶。。= =,這篇以后會更新的)
VGGnet.py:
1 # -*- coding: utf-8 -*- 2 import tensorflow as tf 3 import time 4 import convert_TFrecords 5 6 # 網絡超參數 7 learning_rate = 0.005 8 batch_size = 300 9 epoch = 20000 10 display_step = 10 11 12 # 網絡參數 13 Dropout = 0.75 # 失活的概率=1-Dropout 14 n_inputs = 128 * 128 * 3 # 輸入維度(img_size) 15 n_classes = 43 16 17 18 weights = {'w1': tf.Variable(tf.random_normal([3, 3, 3, 16])), 19 'w2': tf.Variable(tf.random_normal([3, 3, 16, 16])), 20 'w3': tf.Variable(tf.random_normal([3, 3, 16, 32])), 21 'w4': tf.Variable(tf.random_normal([3, 3, 32, 32])), 22 'w5': tf.Variable(tf.random_normal([3, 3, 32, 64])), 23 'w6': tf.Variable(tf.random_normal([3, 3, 64, 64])), 24 'w7': tf.Variable(tf.random_normal([3, 3, 64, 128])), 25 'w8': tf.Variable(tf.random_normal([3, 3, 128, 128])), 26 'w9': tf.Variable(tf.random_normal([3, 3, 128, 128])), 27 'w10': tf.Variable(tf.random_normal([3, 3, 128, 128])), 28 'wd1': tf.Variable(tf.random_normal([8*8*128, 4096])), 29 'wd2': tf.Variable(tf.random_normal([1*1*4096, 4096])), 30 'out': tf.Variable(tf.random_normal([4096, 43]))} # 共43個類別 31 32 biases = {'b1': tf.Variable(tf.random_normal([16])), 33 'b2': tf.Variable(tf.random_normal([16])), 34 'b3': tf.Variable(tf.random_normal([32])), 35 'b4': tf.Variable(tf.random_normal([32])), 36 'b5': tf.Variable(tf.random_normal([64])), 37 'b6': tf.Variable(tf.random_normal([64])), 38 'b7': tf.Variable(tf.random_normal([128])), 39 'b8': tf.Variable(tf.random_normal([128])), 40 'b9': tf.Variable(tf.random_normal([128])), 41 'b10': tf.Variable(tf.random_normal([128])), 42 'bd1': tf.Variable(tf.random_normal([4096])), 43 'bd2': tf.Variable(tf.random_normal([4096])), 44 'out': tf.Variable(tf.random_normal([43]))} 45 46 47 def conv(name, input, W, b, strides=1, padding='SAME'): 48 x = tf.nn.conv2d(input, W, strides=[1, strides, strides, 1], padding=padding) 49 x = tf.nn.bias_add(x, b) 50 return tf.nn.relu(x, name=name) 51 52 53 # 輸入應該是一個4維的張量,最后一維為batch_size,但這里構造的網絡只按batch_size=1的情況來構造,即只考慮 54 # 一個樣本的情況,這是沒有影響的,運行圖的時候再指定batch_size 55 def VGGnet(input, weights, biases, keep_prob): 56 x = tf.reshape(input, shape=[-1, 128, 128, 3]) # -1處的值由batch_size決定 57 conv1 = conv('conv1', x, weights['w1'], biases['b1']) 58 59 conv2 = conv('conv2', conv1, weights['w2'], biases['b1']) 60 61 pool1 = tf.nn.max_pool(conv2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool1') 62 63 conv3 = conv('conv3', pool1, weights['w3'], biases['b3']) 64 65 conv4 = conv('conv4', conv3, weights['w4'], biases['b4']) 66 67 pool2 = tf.nn.max_pool(conv4, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool2') 68 69 conv5 = conv('conv5', pool2, weights['w5'], biases['b5']) 70 71 conv6 = conv('conv6', conv5, weights['w6'], biases['b6']) 72 73 conv7 = conv('conv7', conv6, weights['w7'], biases['b7']) 74 75 pool3 = tf.nn.max_pool(conv7, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool3') 76 77 conv8 = conv('conv8', pool3, weights['w8'], biases['b8']) 78 79 conv9 = conv('conv9', conv8, weights['w9'], biases['b9']) 80 81 conv10 = conv('conv10', conv9, weights['w10'], biases['b10']) 82 83 pool4 = tf.nn.max_pool(conv10, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool4') 84 85 fc1 = tf.reshape(pool4, [-1, weights['wd1'].get_shape().as_list()[0]]) 86 fc1 = tf.add(tf.matmul(fc1, weights['wd1']), biases['bd1']) 87 88 re1 = tf.nn.relu(fc1, 're1') 89 90 drop1 = tf.nn.dropout(re1, keep_prob) 91 92 fc2 = tf.reshape(drop1, [-1, weights['wd2'].get_shape().as_list()[0]]) 93 fc2 = tf.add(tf.matmul(fc2, weights['wd2']), biases['bd2']) 94 95 re2 = tf.nn.relu(fc2, 're2') 96 97 drop2 = tf.nn.dropout(re2, keep_prob) 98 99 fc3 = tf.reshape(drop2, [-1, weights['out'].get_shape().as_list()[0]]) 100 fc3 = tf.add(tf.matmul(fc3, weights['out']), biases['out']) 101 102 # print(fc3) 檢查點 103 104 # tf.nn.softmax_cross_entropy_with_logits函數已經進行了softmax處理!不必再加一層softmax(發現這個錯誤后,訓練精度終於變得正常) 105 # sm = tf.nn.softmax(fc3) 106 107 return fc3 108 109 110 # 注意下面的shape要和傳入的tensor一致!使用mnist數據集時x的shape為[none, 28*28*1],是因為傳入的數據是展開成行的 111 x = tf.placeholder(tf.float32, [None, 128, 128, 3]) 112 y = tf.placeholder(tf.float32, [None, n_classes]) 113 dropout = tf.placeholder(tf.float32) 114 115 pred = VGGnet(x, weights, biases, dropout) 116 117 # 定義損失函數和優化器 118 # 錯誤:Only call `softmax_cross_entropy_with_logits` with named arguments (labels=..., logits=...,),解決方法:參數要以關鍵字參數的形式傳入 119 # tf.nn.softmax_cross_entropy_with_logits先是對最后一層輸出做一個softmax,然后求softmax向量里每個元素的這個值:y_i * log(yi)(y_i為實際值,yi為預測值), 120 # tf.reduce_mean對每個元素上面的乘積求和再平均 121 # 參考:https://blog.csdn.net/mao_xiao_feng/article/details/53382790 122 loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=y)) 123 optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss) 124 125 # 評估函數 126 # tf.argmax()返回每個向量最大元素的索引(axis=1),tf.equal()返回兩個數是否相等(ture or false) 127 # https://blog.csdn.net/qq575379110/article/details/70538051/ 128 # https://blog.csdn.net/uestc_c2_403/article/details/72232924 129 correct_pred = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1)) 130 accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32)) 131 132 # init = tf.initialize_all_variables() 133 batch_x, batch_y = convert_TFrecords.inputs(True, batch_size, epoch) 134 135 with tf.Session() as sess: 136 # sess.run(init) 137 # 先執行初始化工作 138 # 參考:https://blog.csdn.net/lujiandong1/article/details/53376802 139 sess.run(tf.global_variables_initializer()) 140 sess.run(tf.local_variables_initializer()) 141 # sess.run(tf.initialize_all_variables()) 142 143 # 開啟一個協調器 144 coord = tf.train.Coordinator() 145 # 使用start_queue_runners 啟動隊列填充 146 threads = tf.train.start_queue_runners(sess, coord) 147 148 try: 149 step = 1 150 while not coord.should_stop(): 151 # 獲取每一個batch中batch_size個樣本和標簽 152 # 原來下面這一句放在這個位置(改變這一句的位置后卡了幾天的問及終於解決了): 153 # batch_x, batch_y = convert_TFrecords.inputs(True, batch_size, epoch) 154 # 結果程序卡住,無法運行,也不報錯 155 # 檢查點:print('kaka') 156 157 # print(batch_x) 158 # print(batch_y) 159 # print('okok') 檢查點 160 # 沒有下面這句會報錯: 161 # The value of a feed cannot be a tf.Tensor object. Acceptable feed 162 # values include Python scalars, strings, lists, numpy ndarrays, or TensorHandles. 163 # 原以為是要用tensor.eval()將tensor轉為np.array,但batch_x, batch_y = convert_TFrecords.inputs(True, batch_size, epoch) 164 # 那時是放在sess里面,所以執行到tensor.eval()時一樣會卡住不動 165 b_x, b_y = sess.run([batch_x, batch_y]) 166 # print('haha') 檢查點 167 # 打印出tesor:默認值打印出3個參數 參考:https://blog.csdn.net/qq_34484472/article/details/75049179 168 # print(b_x, b_y) 檢查點 169 # 這里原先喂入dict的tensor變量名不是b_x,b_y,而是和key名一樣(也就是x,y),變量名與占位符名沖突,結果 170 # 會報錯:unhashable type: 'numpy.ndarray' error 171 # 這個錯誤也有可能是其他原因引起,見:https://blog.csdn.net/wongleetion/article/details/80885648 172 start = time.time() 173 sess.run(optimizer, feed_dict={x: b_x, y: b_y, dropout: Dropout}) 174 if step % display_step == 0: 175 # 原來在feed_dict里關鍵字dropout打錯成keep_prob了,結果彈出Cannot interpret feed_dict key 176 # as Tensor:Can not convert a float into a Tensor錯誤 177 # 參考https://blog.csdn.net/ice_pill/article/details/78567841 178 Loss, acc = sess.run([loss, accuracy], feed_dict={x: b_x, y: b_y, dropout: 1.0}) 179 print('iter ' + str(step) + ', minibatch loss = ' + 180 '{: .6f}'.format(Loss) + ', training accuracy = ' + '{: .5f}'.format(acc)) 181 # sess.run(tf.Print(b_y, [b_y], summarize=43)) 182 print(b_y) 183 print('iter %d, duration: %.2fs' % (step, time.time() - start)) 184 step += 1 185 except tf.errors.OutOfRangeError: # 如果讀取到文件隊列末尾會拋出此異常 186 print("done! now lets kill all the threads……") 187 finally: 188 # 協調器coord發出所有線程終止信號 189 coord.request_stop() 190 print('all threads are asked to stop!') 191 coord.join(threads) # 把開啟的線程加入主線程,等待threads結束 192 print('all threads are stopped!')
convert_TFrecords.py(TFrecords文件的制作和讀取):
1 # -*- coding: utf-8 -*- 2 3 import os 4 import tensorflow as tf 5 from PIL import Image 6 7 cur_dir = os.getcwd() 8 9 # classes = ['test_file_dir', 'train_file_dir'] 10 train_set = os.path.join(cur_dir, 'train_file_dir') 11 classes = os.listdir(train_set) 12 13 14 # 制作二進制數據 15 def create_record(): 16 print('processing...') 17 writer = tf.python_io.TFRecordWriter('train.tfrecords') 18 num_labels = len([name for name in classes]) 19 print('num of classes: %d' % num_labels) 20 label = [0] * num_labels 21 for index, name in enumerate(classes): 22 class_path = os.path.join(train_set, name) 23 label[index] = 1 24 for img_name in os.listdir(class_path): 25 img_path = os.path.join(class_path, img_name) 26 img = Image.open(img_path) 27 # img = img.resize((64, 64)) 28 img_raw = img.tobytes() # 將圖片轉化為原生bytes 29 # print(img_raw) 30 # print(index,img_raw) 31 # tfrecord數據文件是一種將圖像數據和標簽統一存儲的二進制文件,能更好的利用內存,在tensorflow中快速的復制,移動,讀取,存儲等。 32 # tfrecord文件包含了tf.train.Example 協議緩沖區(protocol buffer,協議緩沖區包含了特征 Features)。你可以寫一段代碼獲取你的數據, 33 # 將數據填入到Example協議緩沖區(protocol buffer),將協議緩沖區序列化為一個字符串, 並且通過tf.python_io.TFRecordWriter class寫 34 # 入到TFRecords文件 35 example = tf.train.Example( 36 # feature字典中每個key的值都是一個list,這些list是3種數據類型中的一種:FloatList, 或者ByteList,或者Int64List 37 # 參考https://blog.csdn.net/u012759136/article/details/52232266 38 # 參考https://blog.csdn.net/shenxiaolu1984/article/details/52857437 39 features=tf.train.Features(feature={ 40 # 設置圖片在TFrecord文件中的標簽(同一文件夾下標簽一致),注意存儲的是一個大小為num_label的list,而不是一個值!! 41 'label': tf.train.Feature(int64_list=tf.train.Int64List(value=label)), # label本來就是一個list,不用加中括號 42 # 設置圖片在TFrecord文件中的值 43 'img_raw': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw])) 44 })) 45 writer.write(example.SerializeToString()) 46 label = [0] * num_labels 47 writer.close() 48 print('TFrecords file created successfully!!') 49 50 51 # 讀取二進制數據 52 def read_and_decode(filename, num_epochs): 53 # 根據文件名,順序生成一個隊列(如果shuffle=ture) 54 filename_queue = tf.train.string_input_producer([filename], shuffle=True, num_epochs=num_epochs) 55 print('qunide') 56 reader = tf.TFRecordReader() 57 _, serialized_example = reader.read(filename_queue) # 返回文件名和文件 58 features = tf.parse_single_example(serialized_example, 59 features={ 60 # 這個函數不是很了解,原來在'label'里的shape為空([]),結果彈出錯誤:Key: label, Index: 0. Number 61 # of int64 values != expected. Values size: 43 but output shape: [] 62 # 注意數據類型要和TFrecords文件中一致!! 63 'label': tf.FixedLenFeature([43], tf.int64), 64 'img_raw': tf.FixedLenFeature([], tf.string), ########## 65 }) 66 67 img = features['img_raw'] 68 # decode_raw()函數只能用於解碼byteslist格式的數據 69 img = tf.decode_raw(img, tf.uint8) 70 img = tf.reshape(img, [128, 128, 3]) 71 img = tf.cast(img, tf.float32) * (1. / 255) - 0.5 # 規范化到±0.5之間 72 label = features['label'] 73 # label = tf.reshape(label, [43]) ????不用這樣做,原本存儲的時候shape就是[43] 74 label = tf.cast(label, tf.float32) # 因為網絡輸出的pred值是float32類型的!!(?) 75 print('label', label) 76 print('image', img) 77 78 return img, label 79 80 81 def inputs(train, batch_size, num_epochs): 82 print('qunide2') 83 if not num_epochs: 84 num_epochs = None 85 filename = os.path.join(cur_dir, 'train.tfrecords' if train else 'test.tfrecords') # 暫時先這樣 86 87 with tf.name_scope('input'): 88 image, label = read_and_decode(filename, num_epochs) 89 # print(image) 檢查點 90 # tf.train.shuffle_batch應該是從tf.train.string_input_producer生成的文件隊列中先打亂再從中抽取組成batch,所以 91 # 這個打亂后的隊列容量和min_after_dequeue(應該是決定原有隊列被抽取后的最小樣本含量,決定被抽取后再填入的量) 92 # 根據batch_size的不同會影響訓練精度(因為再填充並打亂后很多之前網絡沒見過的樣本會被送入,當所有訓練數據都過一遍后,精度會提高),這是我的個人猜測 93 images, sparse_labels = tf.train.shuffle_batch([image, label], batch_size=batch_size, 94 num_threads=2, capacity=3000, # 線程數一般與處理器核數一樣 95 # 但並不是線程越多越快,甚至更多的線程反而會使效率下降 96 # 參考:https://blog.csdn.net/lujiandong1/article/details/53376802 97 # https://blog.csdn.net/heiheiya/article/details/80967301 98 min_after_dequeue=2000) 99 # print(images) 檢查點 100 return images, sparse_labels 101 # 注意返回值的類型要與tf.placeholder()中的dtypes, shape都要相同! 102 103 104 if __name__ == '__main__': 105 create_record()
雖然程序成功運行了,但訓練精度很低,還有很多方面需要調整
除了代碼中提到的博文,還參考了下面的:
https://blog.csdn.net/dcrmg/article/details/79780331
https://blog.csdn.net/qq_30666517/article/details/79715045
https://www.cnblogs.com/upright/p/6136265.html
https://blog.csdn.net/tengxing007/article/details/56847828
