關於訓練深度學習模型deepNN時,訓練精度維持固定值,模型不收斂的解決辦法(tensorflow實現)
一、背景
最近一直在做人臉表情的識別,用到的程式是之間的一篇文章中的程式:深度學習(一)——deepNN模型實現攝像頭實時識別人臉表情(C++和python3.6混合程式設計)。這裡我只進行了簡單的程式修改。
由於該程式是利用fer2013資料集做的,效果不是很好,人臉表情的識別精度僅有70%左右,因此我想自己製作資料集,自己訓練模型,關於如何製作資料集,可參考文章:從零開始製作人臉表情的資料集。
本文主要介紹在訓練模型的過程中出現的問題:即無論訓練多少次,其訓練精度一直維持在0.23。下面會具體介紹問題及解決辦法。
二、問題出現
這裡先給出我的程式碼。首先是關於資料讀取的程式碼,這裡給出關鍵部分程式碼
def load_data(txt_dir):
# 省略內容:根據txt的路徑讀取影象資料和標籤
data_set = np.empty((count, 128, 128, 1), dtype="float32") # 定義data_set
label = np.empty((count,10), dtype="uint8") # 定義label
# 省略內容:讀取data和標籤
return data_set, label
然後是deepNN模型的程式碼,這個完全參考之前的程式,只不過我的影象大小改成了128*128,表情種類為10類:
def deepnn(x): x_image = tf.reshape(x, [-1, 128, 128, 1]) # conv1 w_conv1 = weight_variables([5, 5, 1, 64]) b_conv1 = bias_variable([64]) h_conv1 = tf.nn.relu(conv2d(x_image, w_conv1) + b_conv1) h_pool1 = maxpool(h_conv1) norm1 = tf.nn.lrn(h_pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75) # conv2 w_conv2 = weight_variables([3, 3, 64, 64]) b_conv2 = bias_variable([64]) h_conv2 = tf.nn.relu(conv2d(norm1, w_conv2) + b_conv2) norm2 = tf.nn.lrn(h_conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75) h_pool2 = maxpool(norm2) # Fully connected layer w_fc1 = weight_variables([32 * 32 * 64, 384]) b_fc1 = bias_variable([384]) h_conv3_flat = tf.reshape(h_pool2, [-1, 32 * 32 * 64]) h_fc1 = tf.nn.relu(tf.matmul(h_conv3_flat, w_fc1) + b_fc1) # Fully connected layer w_fc2 = weight_variables([384, 192]) b_fc2 = bias_variable([192]) h_fc2 = tf.matmul(h_fc1, w_fc2) + b_fc2 # linear w_fc3 = weight_variables([192, 10]) # 一共10類 b_fc3 = bias_variable([10]) # 一共10類 y_conv = tf.add(tf.matmul(h_fc2, w_fc3), b_fc3) return y_conv def weight_variables(shape): initial = tf.truncated_normal(shape, stddev=0.1) return tf.Variable(initial) def bias_variable(shape): initial = tf.constant(0.1, shape=shape) return tf.Variable(initial) def conv2d(x, w): return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME') def maxpool(x): return tf.nn.max_pool(x, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME')
最後是訓練過程的程式碼,當然這裡我根據我的實際情況對原始碼進行了修改:
def train_model():
# 構建模型----------------------------------------------------------
x = tf.placeholder(tf.float32, [None, 16384])
y_ = tf.placeholder(tf.float32, [None, 10])
y_conv = deepnn(x)
cross_entropy = tf.reduce_mean(
tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 構建完畢----------------------------------------------------------
# 讀取資料
data_set, label = load_data('./data/list.txt')
max_train_epochs = 30001
batch_size = 100
# 判斷是否存在輸出模型的路徑,如果不存在,則建立
if not os.path.exists('./models/emotion_model'):
os.makedirs('./models/emotion_model')
with tf.Session() as sess:
saver = tf.train.Saver()
sess.run(tf.global_variables_initializer())
batch_num = int(data_set.shape[0] / batch_size)
for i in range(max_train_epochs):
for j in range(batch_num):
# 製作每一個batch的影象和標籤
train_image = data_set[j * batch_size:j * batch_size + batch_size]
train_image = train_image.reshape(-1, 128*128)
train_label = label[j * batch_size:j * batch_size + batch_size]
train_label = np.reshape(train_label, [-1, 10])
# 逐個batch訓練模型
train_step.run(feed_dict={x: train_image, y_: train_label})
# 每訓練一個epoch儲存一次精度
if i % 1 == 0:
train_accuracy = accuracy.eval(feed_dict={
x: train_image, y_: train_label})
print('epoch %d, training accuracy %f' % (i, train_accuracy))
# 每1000個epoch儲存一次模型
if i % 1000 == 0:
saver.save(sess, './models/emotion_model', global_step=i + 1)
好了,現在準備好資料之後,直接執行train_model():
if __name__ == '__main__':
train_model()
如果不出意外,其每行的輸出應該是:
epoch DD, training accuracy FFFFF
且隨著訓練次數的增加,training accuracy的值也應該是逐漸接近1的。但是實際上的結果:
training accuracy完全沒有任何增加的跡象,訓練至1000次仍是這樣。
三、問題解決
模型不收斂的話,問題出在哪呢?反覆排查後確定了模型沒有任何問題。那自然只可能是輸入資料的問題了。
原來在資料讀取過程中,在load_data(txt_dir)函式中,label語句的定義為:
label = np.empty((count,10), dtype="uint8")
np.empty()函式導致了label中的很多資料是隨機產生的,最終的標籤結果也並非是0,1二值資料,而是非常混亂的資料:
既然已經查到問題所在了,那麼解決方法也自然就明瞭了。我們的目的是為了產生二值標籤,即影象所屬的表情類別標記為1,非所屬類別標記為0,如此可這樣修改上述程式碼:
def load_data(txt_dir):
# 省略內容:根據txt的路徑讀取影象資料和標籤
# count表示影象的數量
data_set = np.empty((count, 128, 128, 1), dtype="float32") # 定義data_set
label = np.zeros((count,10), dtype="uint8") # 定義label
# 省略內容:讀取data和標籤
return data_set, label
修改之後,先看看我們的標籤label是否正確:
從上圖可以看出,label已經完全沒有問題,下來我們再看看訓練過程中的training accuracy:
好了,可以看到training accuracy在逐步提高,說明這個問題已完美解決。後續大約在訓練60個epoch時,訓練精度幾乎可以接近1: