實際上,這是一個分類問題,即將輸入的圖片數據分紅 0-9 共 10 個類別,並且咱們的數據都是直接使用 MNIST 上下載的處理好的數據。 數據庫
在現實生產中,咱們的數據源一般來自於數據庫,是沒有通過預處理的,那麼咱們該作些什麼來讓這些數據庫裏的數據可以用於進行機器學習呢? bash
機器學習的前置步驟,數據預處理就是解決這個問題的。 網絡
本篇 CoorChice 將會經過一個迴歸預測問題,來展現如何進行這個過程。 app
咱們用於機器學習的數據的量不能太少,不然訓練效果會極差。可是咱們我的如何才能獲取到大量的數據來進行訓練呢? less
CoorChice 推薦 kaggle:https://www.kaggle.com/datasets,這是全球最大的數據科學社區和數據競賽平臺。在這個美妙的地方,咱們能夠輕鬆的找到各類各樣的,海量的,精彩絕倫的數據。 機器學習
打開連接就能夠看到海量的數據,咱們很隨意的選擇其中一個數據源來進行訓練。 函數
就選第一條吧!性能
第一條數據是關於一個麪包店的交易的數據。點擊進入後,找到下載就能夠下載這套數據了。 學習
也許你可能不能馬上下載,由於他會要求你先登陸。那就成爲 Kaggle 的會員吧。 測試
咱們使用表格打開剛剛下載的數據觀察觀察。
My God! 這是一堆簡陋不堪的數據,共有 21293 條數據,每條只有 4 個特徵! 4 個特徵! 4 個特徵!
不要緊,咱們只是學習如何處理數據的過程,因此就湊合着用一下吧。
實際上,一般咱們的數據呆在數據庫中,就是這種表格的形式。
在開始以前,須要先安裝一下 Pandas 這個 Python 庫,它被用來讀取咱們的表格數據,和進行增、刪、改、查等操做。
下好了,咱們就開始吧...
進行數據處理最重要的一步,就是對數據進行觀察和分析。經過觀察和分析,咱們要除去一些可有可無的數據列,好比在預測天氣預報的時候,咱們就應該把 ‘李雷早上吃了什麼’ 這個數據列去掉,由於對模型訓練毫無幫助。
同時,咱們要從數據中挖掘出一些隱藏的信息,以擴充特徵。日入在預測產品的交易信息時,咱們從日期這個數據列,就能知道當天是不是一個節日,是什麼節日。
在咱們下載的這堆麪包店的交易數據中,僅有慘不忍睹的 4 個類目!
一般,當咱們使用機器學習來解決問題時,都會有一個目的。好比,在這堆數據中,CoorChice 但願經過機器學習訓練一個模型,可以用於預測將來必定條件下的某種類型的麪包所能達到的交易量是多少。
所以,咱們將交易量 Transaction 這一列數據做爲數據集的標籤數據。
接下來剩下的只有 日期、時間、麪包類目
這三個特徵了。
僅有這三個特徵來進行訓練,結果可想而知會有多差。比你想的差還要差,因此咱們須要從現有的數據中挖掘出更多的信息來。好比,在圖片數據中,一般會進行一些旋轉、平移之類的操做,來補充數據量。
首先看看日期能挖掘到什麼有用的信息?
固然是星期啦。今天是星期1、星期2、...、對交易確定是有影響的。咱們經過 Date 能夠計算出當前日期是周幾。
還有什麼可挖掘的嗎?嗯,冥思苦想...咦,節日確定也是影響麪包交易量的一個重要因素,有了 Date 咱們也能知道當前日期是什麼節日。
而後,經過觀察能夠發現,隨着日期的增長,交易量也是在累加的,因此日期的大小也可直接做爲一個特徵。
因爲 CoorChice 要嘗試預測的是將來某個時間、某種麪包所能達到的交易量,因此日期的大小對結果必然是有影響的。但咱們不能直接將日期轉換爲一個形如 20161030
這樣的數字,由於一年只有 12 個月,每個月天數不同的,因此這個數值跳躍度比較大,這樣就大大增長了訓練的難度和準確率。咱們須要的是一個統一標準的數值。若是肯定某一天爲基準,而後把日期轉換爲和這一天的距離,那量化標準天然就是很明瞭的了。由於數據是從 2016-10-30
開始,那麼把這天做爲基準日就再合適不過了。
接下來觀察 Time 有什麼可用的沒。
Time 自己是一些比較密集的數據,間隔不大。咱們能夠把它劃歸到小時裏,所以就會有 24 個小時。好比,「10:13:03」 就讓它屬於 10 點這個小時吧。
經過時間,咱們還能夠知道如今是處於 早晨、上午、中午、下午、傍晚、晚上、深夜
中的那個時間段。不一樣的時間段或多或少也會影響交易量。
肯定好了思路,接下來就開始找着這個思路處理數據了。
處理數據的第一步固然是先讀取數據啦,咱們前面下載的 Pandas 就使用來幹這個的。
# 讀取數據
datas = pd.read_csv('data/BreadBasket_DMS.csv')
複製代碼
讀取數據後,輸出一下看看長什麼樣子。
print(datas.head())
->>
Date Time Transaction Item
0 2016-10-30 09:58:11 1 Bread
1 2016-10-30 10:05:34 2 Scandinavian
2 2016-10-30 10:05:34 2 Scandinavian
3 2016-10-30 10:07:57 3 Hot chocolate
4 2016-10-30 10:07:57 3 Jam
[5 rows x 4 columns]
複製代碼
而後,咱們對原始數據按照上面分析的思路進行處理。
# 挖掘,周幾對交易量的影響
datas['Weekday'] = datas['Date']
datas['Weekday'] = datas['Weekday'].map(lambda x: get_weekday(x))
# 挖掘,各個節日對交易量的影響
datas['Festival'] = datas['Date']
datas['Festival'] = datas['Festival'].map(lambda x: get_festival(x))
# 挖掘,當前所處的時間段對交易量的影響
datas['Time_Quantum'] = datas['Time']
datas['Time_Quantum'] = datas['Time_Quantum'].map(lambda x: get_time_quantum(x))
# 將 Date 一列變爲與 2016-10-30 這天的距離
datas['Date'] = datas['Date'].map(lambda x: get_delta_days(x))
# 將時間轉變爲時段
datas['Time'] = datas['Time'].map(lambda x: get_time_range(x))
# 縮小數據大小
datas['Transaction'] = datas['Transaction'].map(lambda x: (float(x) / 1000.))
複製代碼
如今,再輸出一下數據看看。
print(datas.head())
->>
Date Time Transaction ... Weekday Festival Time_Quantum
0 0.0 9 0.001 ... 6 null 上午
1 0.0 10 0.002 ... 6 null 上午
2 0.0 10 0.002 ... 6 null 上午
3 0.0 10 0.003 ... 6 null 上午
4 0.0 10 0.003 ... 6 null 上午
[5 rows x 7 columns]
複製代碼
從表格數據中能夠看到,Date 和 Transaction 都變成浮點數了,由於把它們進行縮小,有利於後面的梯度降低,不然 loss 會很是很是的大。
如今,咱們已經擴充了一些特徵。
接下來,對於離散的數據,好比 Weekday、Time 這樣的,它的大小並不會對交易產生影響,但這種離散的特徵的類別取值是會對結果產生影響的。好比,春節這天會對食物的交易量產生影響。
咱們須要對這樣的特徵數據進行 獨熱編碼 。關於 獨熱編碼 ,你能夠在 CoorChice 的 《機器學習,看完就明白了》 這篇文章,瞭解相關信息。
ont_hot_data = pd.get_dummies(datas, prefix=['Time', 'Item', 'Weekday', 'Festival', 'Time_Quantum'])
複製代碼
看看如今數據表變成了什麼樣子了。
print(ont_hot_data.head())
->>
Date Transaction ... Time_Quantum_晚上 Time_Quantum_深夜
0 0.0 0.001 ... 0 0
1 0.0 0.002 ... 0 0
2 0.0 0.002 ... 0 0
3 0.0 0.003 ... 0 0
4 0.0 0.003 ... 0 0
[5 rows x 136 columns]
複製代碼
通過獨熱編碼後,columns 已經擴充到了驚人的 136 列!
之因此需對數據進行獨熱編碼,還有一個緣由是在訓練時,獨熱編碼在分類是 0 和 1 的關係,而若是用 1,2,3,.. 的分類標籤方式,是不利於梯度降低的,計算出的梯度每每比較大。
對 Date、Transaction,能夠看到表格中,CoorChice 還對他們進行縮小,目的是爲了把數值範圍變小,這樣有利於梯度降低的求解。
咱們看看如今數據的分佈狀況:
接下來,咱們須要將元素的有序的數據進行打亂,這樣有助於提升訓練出來的模型的泛化能力。
ont_hot_data = ont_hot_data.sample(frac=1, replace=False)
ont_hot_data = ont_hot_data.sample(frac=1, replace=False)
ont_hot_data = ont_hot_data.sample(frac=1, replace=False)
複製代碼
一遍不夠,要打亂 3 遍!
看 Date 一列,說明數據已經被打的足夠亂了。
最後,咱們要把處理好的數據分爲訓練集和測試集,CoorChice 按大概 30% 取測試集數據,而後分別保存。
# 測試數據集大小
test_count = 6000
train_count = ont_hot_data.shape[0] - test_count
# 切割出訓練數據集
train_data = ont_hot_data[:train_count]
# 切割出測試數據集
test_data = ont_hot_data[train_count:]
# 分別保存兩個數據集
train_data.to_csv('data/train_data.csv', index=False, header=True)
test_data.to_csv('data/test_data.csv', index=False, header=True)
複製代碼
保存處理好的數據很簡單,使用 Pandas 提供的 to_csv()
就能夠把數據存 csv 格式。
index 和 header 分別表示是否保存行號和列名稱。
如今,咱們已經把原始數據處理好,而且分割成了訓練集和測試集。咱們有 15,293 條訓練數據,和 6000 條測試數據。
在開始以前,建議先看一看這一篇 《【Get】用深度學習識別手寫數字》 ,由於套路基本上是同樣的,只是有的地方須要根據具體狀況調整。
在手寫數字識別中,咱們構建的是一個簡單 4 層網絡,它由兩個卷積層,一個全連接層和一個輸出層組成。
回顧一下這個網絡結構。
一開始,CoorChice 直接使用了這個網絡進行訓練,通過 10w 次的漫長訓練以後發現,準確率最高不過 0.1%。
起初 CoorChice 減少 lr(學習率),想着多是學習率過大,致使震盪了。然而,毫無做用。
出現這種現象,基本能夠判斷大機率上是發生欠擬合了。
發生欠擬合,能夠經過如下步驟一步步的排除問題和改進網絡。
本次訓練中,權重的初始化沿用了上次的正太分佈初始化方案,應該是比較優秀的方案了,因此就不作改動了。
在上一次的網絡構建中,卷積層、全連接層都用的是 ReLu 激活函數,輸出層使用的是 Softmax 激活函數。而此次 CoorChice 把激活函數全換成了優秀的 ReLu 激活函數,這應該也是沒毛病的。
這是一張各類優化器的比較圖,從圖中能夠看到,有一個名叫 Adadelta 的優化器表現十分亮眼啊!
Adadelta 是一種自適應的優化器,它可以自動的計算自變量更新量的平方的指數加權移動的平均項來做爲學習率。所以,咱們設置的學習率實際上影響已經不是太大了。
從圖中能夠看到,這種優化器在訓練之初和中期又可以快速降低,只不過到接近最優解的時候會出現小幅震盪。而 SGD 因全程都保持一個學習率,因此在設置合適的狀況下,在最優解附近收斂的更乾脆。
# 使用 Adadelta 進行損失函數的梯度降低求解
train_step = tf.train.AdadeltaOptimizer(0.0001).minimize(cross_entropy)
複製代碼
但因爲 Adadelta 實際訓練過程當中與咱們起初告訴它的學習率關係不大,就是不受控,訓練起來到後面仍是很難受的,因此 CoorChice 嘗試以後仍是選擇換成 SGD 。
lr = tf.Variable(1e-5)
# 使用 SGD 進行損失函數的梯度降低求解
train_step = tf.train.GradientDescentOptimizer(lr).minimize(cross_entropy)
複製代碼
並且,CoorChice 使用了一個變量 lr 來設置優化器的學習率,這樣在訓練過程當中,咱們還能夠動態的額控制當前的學習率。
當發生欠擬合時,也有多是網絡過於簡單,權重太少,致使沒法學習到足夠的信息。在本次訓練中,CoorChice 仍然使用了上次的 4 層單核網絡,彷佛確實是太簡單了。
那就增強它吧。
# coding=utf-8
from cnn_utils import *
class CnnBreadBasketNetwork:
def __init__(self):
with tf.name_scope("input"):
self.x_data = tf.placeholder(tf.float32, shape=[None, 135], name='x_data')
# 補位
input_data = tf.pad(self.x_data, [[0, 0], [0, 1]], 'CONSTANT')
with tf.name_scope("input_reshape"):
# 變形爲能夠和卷積核卷積的張量
input_data = tf.reshape(input_data, [-1, 17, 8, 1])
tf.summary.image("input", input_data, 1)
self.y_data = tf.placeholder(tf.float32, shape=[None], name='y_data')
# ------------------------構建第一層網絡---------------------
with tf.name_scope("hidden1"):
# 第一個卷積
with tf.name_scope("weights1"):
W_conv11 = weight_variable([3, 3, 1, 64])
variable_summaries(W_conv11, "W_conv11")
with tf.name_scope("biases1"):
b_conv11 = bias_variable([64])
variable_summaries(b_conv11, "b_conv11")
h_conv11 = tf.nn.relu(conv2(input_data, W_conv11) + b_conv11)
tf.summary.histogram('activations_h_conv11', h_conv11)
# 第二個卷積
with tf.name_scope("weights2"):
W_conv12 = weight_variable([3, 3, 64, 64])
variable_summaries(W_conv12, "W_conv12")
with tf.name_scope("biases2"):
b_conv12 = bias_variable([64])
variable_summaries(b_conv12, "b_conv12")
h_conv12 = tf.nn.relu(conv2(h_conv11, W_conv12) + b_conv12)
tf.summary.histogram('activations_h_conv11', h_conv12)
# 池化
h_pool1 = max_pool_2x2(h_conv12)
tf.summary.histogram('pools_h_pool1', h_pool1)
# ------------------------構建第二層網絡---------------------
with tf.name_scope("hidden2"):
# 第一個卷積核
with tf.name_scope("weights1"):
W_conv21 = weight_variable([3, 3, 64, 128])
variable_summaries(W_conv21, 'W_conv21')
with tf.name_scope("biases1"):
b_conv21 = bias_variable([128])
variable_summaries(b_conv21, 'b_conv21')
h_conv21 = tf.nn.relu(conv2(h_pool1, W_conv21) + b_conv21)
tf.summary.histogram('activations_h_conv21', h_conv21)
# 第二個卷積核
with tf.name_scope("weights2"):
W_conv22 = weight_variable([3, 3, 128, 128])
variable_summaries(W_conv22, 'W_conv22')
with tf.name_scope("biases2"):
b_conv22 = bias_variable([128])
variable_summaries(b_conv22, 'b_conv22')
h_conv22 = tf.nn.relu(conv2(h_conv21, W_conv22) + b_conv22)
tf.summary.histogram('activations_h_conv22', h_conv22)
# 池化
self.h_pool2 = max_pool_2x2(h_conv22)
tf.summary.histogram('pools_h_pool2', self.h_pool2)
shape_0 = self.h_pool2.get_shape()[1].value
shape_1 = self.h_pool2.get_shape()[2].value
h_pool2_flat = tf.reshape(self.h_pool2, [-1, shape_0 * shape_1 * 128])
# ------------------------ 構建第一層全連接層 ---------------------
with tf.name_scope("fc1"):
with tf.name_scope("weights"):
W_fc1 = weight_variable([shape_0 * shape_1 * 128, 4096])
variable_summaries(W_fc1, 'W_fc1')
with tf.name_scope("biases"):
b_fc1 = bias_variable([4096])
variable_summaries(b_fc1, 'b_fc1')
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
tf.summary.histogram('activations_h_fc1', h_fc1)
# ------------------------構建輸出層---------------------
with tf.name_scope("output"):
with tf.name_scope("weights"):
W_out = weight_variable([4096, 1])
variable_summaries(W_out, 'W_out')
with tf.name_scope("biases"):
b_out = bias_variable([1])
variable_summaries(b_out, 'b_out')
# 注意⚠️,此處的激活函數已經替換成 ReLu 了
self.y_conv = tf.nn.relu(tf.matmul(h_fc1, W_out) + b_out)
tf.summary.histogram('activations_y_conv', self.y_conv)
複製代碼
這就是通過改進後的網絡的所有代碼了。
能夠看到,仍是熟悉的套路。如下是版本更新內容:
每一個卷積核的 size 由 5x5 變成了 3x3
第一個卷積層的卷積核通道數由原來的 32 增長到了 64。將卷積核數量提升一倍。
卷積核層數仍然是兩層,但每層都多增長了一個卷積核組。
全連接層的神經元個數由原來的 1024 個增長至 4096 個。
再說一下網絡中的一些變化。
self.x_data = tf.placeholder(tf.float32, shape=[None, 135], name='x_data')
# 補位
input_data = tf.pad(self.x_data, [[0, 0], [0, 1]], 'CONSTANT')
# 變形爲能夠和卷積核卷積的張量
input_data = tf.reshape(input_data, [-1, 17, 8, 1])
self.y_data = tf.placeholder(tf.float32, shape=[None], name='y_data')
複製代碼
定義佔位的時候,因爲表格數據中只有 135 列,因此先定義一個 shape 爲 [None,135] 的佔位。可是比較尷尬的是,135 是一個奇數,沒法轉換成能和卷積核卷積的張量,因此 CoorChice 給它補了一列,經過 tensorflow 提供的 pad()
函數。
input_data = tf.pad(self.x_data, [[0, 0], [0, 1]], 'CONSTANT')
複製代碼
第二個參數中的數字分別表示須要在原張量的 上、下、左、右 補充多少行或列。這裏 CoorChice 再原張量的右邊補充了一列 0。
如今,就能夠變造成可和卷積核卷積的張量了。
input_data = tf.reshape(input_data, [-1, 17, 8, 1])
複製代碼
- 小技巧:一般,通過多層的卷積和池化以後,進入第一層全連接層或者直接進入輸出層的時候,咱們須要知道上一層的形狀是怎樣的,而後才能設計全連接層或者輸出層的權重形狀。能夠經過以下方式方便的得到輸入的形狀:
# 得到最後一個池化層的寬
shape_0 = self.h_pool2.get_shape()[1].value
# 得到最後一個池化層的高
shape_1 = self.h_pool2.get_shape()[2].value
# 變形爲全連接層可乘的形狀
h_pool2_flat = tf.reshape(self.h_pool2, [-1, shape_0 * shape_1 * 128])
複製代碼
實際上,這個網絡可能不是太好,深度不夠,可能致使準確率不夠。但受設備限制,設計的太深,會致使每次訓練都會花費大量的時間,因此 CoorChice 就儘可能精簡一點,主要看這個過程是怎樣進行的。
這是新的網絡結構:
看看局部的兩組卷積 hidden 層:
# coding=utf-8
import time
from cnn_model import *
from BBDATA import *
train_times = 20000
base_path = ".../BreadBasket/"
save_path = base_path + str(train_times) + "/"
# 讀取數據
BBDATA = read_datas('data/')
# 建立網絡
network = CnnBreadBasketNetwork()
x_data = network.x_data
y_data = network.y_data
y_conv = network.y_conv
keep_prob = network.keep_prob
# ------------------------構建損失函數---------------------
with tf.name_scope("cross_entropy"):
# 建立正則化對象,此處使用的是 L2 範數
regularization = tf.contrib.layers.l2_regularizer(scale=(5.0 / 500))
# 應用正則化到參數集上
reg_term = tf.contrib.layers.apply_regularization(regularization)
# 在損失函數中加入正則化項
cross_entropy = tf.reduce_mean(tf.square((y_conv - y_data))) + reg_term
tf.scalar_summary('loss', cross_entropy)
with tf.name_scope("train_step"):
lr = tf.Variable(1e-5)
# 使用 SGD 進行損失函數的梯度降低求解
train_step = tf.train.GradientDescentOptimizer(lr).minimize(cross_entropy)
# 記錄平均差值
with tf.name_scope("difference_value"):
dv = tf.reduce_mean(tf.abs(y_conv - y_data))
tf.scalar_summary('difference_value', cross_entropy)
# ------------------------構建模型評估函數---------------------
with tf.name_scope("accuracy"):
with tf.name_scope("correct_prediction"):
correct_prediction = tf.less_equal(tf.abs(y_conv - y_data), 0.5)
with tf.name_scope("accuracy"):
# 計算準確率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
tf.scalar_summary('accuracy', accuracy)
# 建立會話
sess = tf.InteractiveSession()
summary_merged = tf.merge_all_summaries()
train_writer = tf.train.SummaryWriter(save_path + "graph/train", sess.graph)
test_writer = tf.train.SummaryWriter(save_path + "graph/test")
start_time = int(round(time.time() * 1000))
# 初始化參數
sess.run(tf.initialize_all_variables())
global loss
loss = 0
global train_accuracy
for i in range(train_times):
# 從訓練集中取出 200 個樣本進行一波訓練
batch = BBDATA.train_data.batch(200)
if i % 100 == 0:
summary, train_accuracy, test_loss, dv_value = sess.run([summary_merged, accuracy, cross_entropy, dv],
feed_dict={x_data: BBDATA.test_data.data, y_data: BBDATA.test_data.label, keep_prob: 1})
test_writer.add_summary(summary, i)
consume_time = int(round(time.time() * 1000)) - start_time
print("當前共訓練 " + str(i) + "次, 累計耗時:" + str(consume_time) + "ms,實時準確率爲:%g" % (train_accuracy * 100.) + "%, "
+ "當前偏差均值:" + str(dv_value) + ", train_loss = " + str(loss) + ", test_loss = " + str(test_loss))
if i % 1000 == 0:
run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
run_metadata = tf.RunMetadata()
summary, _, loss = sess.run([summary_merged, train_step, cross_entropy],
feed_dict={x_data: batch.data, y_data: batch.label, keep_prob: 0.6}, options=run_options,
run_metadata=run_metadata)
train_writer.add_run_metadata(run_metadata, str(i))
train_writer.add_summary(summary, i)
else:
summary, _, loss = sess.run([summary_merged, train_step, cross_entropy],
feed_dict={x_data: batch.data, y_data: batch.label, keep_prob: 0.6})
train_writer.add_summary(summary, i)
# 每訓練 2000 次保存一次模型
if i != 0 and i % 1000 == 0:
test_accuracy, test_dv = sess.run([accuracy, dv], feed_dict={x_data: BBDATA.test_data.data, y_data: BBDATA.test_data.label, keep_prob: 1})
save_model(base_path + str(i) + "_" + str(test_dv) + "/", sess, i)
# 在測試集計算準確率
summary, final_test_accuracy, test_dv = sess.run([summary_merged, accuracy, dv],
feed_dict={x_data: BBDATA.test_data.data, y_data: BBDATA.test_data.label, keep_prob: 1})
train_writer.add_summary(summary)
print("測試集準確率:%g" % (final_test_accuracy * 100.) + "%, 偏差均值:" + str(test_dv))
print("訓練完成!")
train_writer.close()
test_writer.close()
# 保存模型
save_model(save_path, sess, train_times)
sess.close()
複製代碼
這是完整的訓練代碼,基本上和 《【Get】用深度學習識別手寫數字》 中是同樣的。只不過 CoorChice 多加了一個 MAE 做爲模型評估的指標。
dv = tf.reduce_mean(tf.abs(y_conv - y_data))
複製代碼
由於是進行迴歸預測,因此使用 MAE 均方方差來做爲一個評估模型好壞的指標。平均絕對差越小,代表模型預測結果越接近於真實狀況。
此外,因爲是作迴歸預測,因此以前分類使用的交叉熵損失函數就不是特別合適了,咱們換成均方偏差損失函數。
cross_entropy = tf.reduce_mean(tf.square((y_conv - y_data))) + reg_term
複製代碼
須要注意的是:
correct_prediction = tf.less_equal(tf.abs(y_conv - y_data), 0.5)
複製代碼
評估模型如今不該該再是比較預測值和標籤值是否相等了,在迴歸預測中,要作到模型可以預測出的值和真實值徹底相等幾乎是不可能的。咱們把評估模型改形成絕對差 <500 即接受該預測結果,畢竟是在我的設備上跑,要達到比較高的精度仍是很難的。
上面這段代碼中,SGD 優化器的學習率變量其實是 CoorChice 後來加上的。在此前,使用的固定的,但當 run 起來開始訓練的時候,發現 loss 總在一個值很大,但範圍很小的區間內波動,並且準確率也在小範圍的波動,提高不上去。
此時的準確大概在 10.5% 附近徘徊開來,loss 趨勢變化也不大。
這說明訓練基本處於停滯不前的狀態了,但顯然,咱們的模型精度是不夠的。出現這種狀況多是如下幾種狀況。
如圖,在多特徵的高維 loss 曲面中,可能存在不少 A 點旁邊的小坑。當進入小坑的底部時,loss 的一階導數也是爲 0 的,就網上經常說的陷入了局部最優解中,而沒有到真正的 Global Minima。
實際上,上面代碼中咱們採起的 mini-patch 抽樣進行訓練,必定程度上也是能減少陷入局部最優解的狀況的。由於可能這批樣本陷入了局部最優中,下一批樣本的波動又把你從坑裏拽出來了。固然有的時候咱們可能會看到 loss 反而增大了,也是由於這種方式致使的。
如圖,在 loss 的曲面中,會有比局部最優坑還多的馬鞍型曲面。
憨態可掬的🐎...
當咱們騎到馬鞍上的鞍點是,此處的導數也是爲0的,那麼是真正的收斂了嗎?不是的。看圖中,咱們在x軸方向上是極小值,但在y軸方向上是極大值。
若是落在這樣正在進入鞍點的尷尬位置,那麼你的訓練就會看起來幾乎毫無波動,走出這個區域須要花費很長的時間。
幸運的是,經過適當增大學習率或者適當減少 batch,都有助於儘快逃離這個區域。
而後在 loss 曲面中,同時存在着如上圖同樣的大面積的平緩曲面,處於這些區域中的點,雖然導數不爲 0 ,但值卻及其的小,即便一遍又一遍的反覆訓練,仍然看起來幾乎沒有變化。
可怕的是,咱們沒法分辨出是處於局部最優,仍是處於馬鞍區域,或者乾脆就是在類平面上。
出現以上幾種狀況,都會致使看起來不學習的狀況,特別是在性能有限的我的設備上。但咱們仍是有一些套路能夠儘可能減少這種狀況的發生的。
檢查數據。確保數據標籤對應正常。
修改調整網絡結構。儘可能使用一些開源的大牛們產出的網絡模型,如 VGG 系列。在它們的基礎上修改大機率上比本身搗騰靠譜。
調整 batch 大小。batch過大致使訓練時間過長,也容易進入局部最優或者馬鞍區域。太小又回致使震盪比較劇烈,不容易找到最優解。
多嘗試不一樣的參數初始化。若是足夠幸運的話,沒準初始化就落到了 Global Minima 的坑裏去了。
動態學習率。一成不變的小的學習率當陷入局部最優時就算是掉坑裏了,或者在平滑面上移動的特別慢。若是大了又容易錯過最優解。因此像 Adam 這樣動態的學習率優化器能必定程度上減小這些問題的發生。固然,咱們也能夠在 SGD 的基礎上,根據 loss 的狀況本身動態的增大學習率,在 loss 很大卻不變的時候,在 loss 較小的時候減少學習率,避免錯過最優解。
train loss 不斷降低,test loss不斷降低,說明網絡仍在學習;
train loss 不斷降低,test loss趨於不變,說明網絡過擬合;
train loss 趨於不變,test loss不斷降低,說明數據集100%有問題;
train loss 趨於不變,test loss趨於不變,說明學習遇到瓶頸,須要減少學習率或批量數目;
train loss 不斷上升,test loss不斷上升,說明網絡結構設計不當,訓練超參數設置不當,數據集通過清洗等問題。
經過 loss 能夠大體的判斷一些問題,上述規律能夠最爲參考,但不保證必定準確。
最後,一張圖作個總結:
- 抽出空餘時間寫文章分享須要動力,各位看官動動小手點個贊哈,信仰仍是須要持續充值的😄
- CoorChice 一直在不按期的分享新的乾貨,想要上車只需進到 CoorChice的【我的主頁】 點個關注就行了哦。