【數(shù)據(jù)處理】使用深度學(xué)習預(yù)測未來銷量

image

《【Get】用深度學(xué)習識別手寫數(shù)字》 中瑞凑,我們通過一個手寫數(shù)字識別的例子,體驗了如何使用 深度學(xué)習 + tensorflow 解決一個具體的問題砂碉。

實際上五辽,這是一個分類問題,即將輸入的圖片數(shù)據(jù)分成 0-9 共 10 個類別蹋岩,而且我們的數(shù)據(jù)都是直接使用 MNIST 上下載的處理好的數(shù)據(jù)赖草。

在現(xiàn)實生產(chǎn)中,我們的數(shù)據(jù)源通常來自于數(shù)據(jù)庫剪个,是沒有經(jīng)過預(yù)處理的秧骑,那么我們該做些什么來讓這些數(shù)據(jù)庫里的數(shù)據(jù)能夠用于進行機器學(xué)習呢?

機器學(xué)習的前置步驟扣囊,數(shù)據(jù)預(yù)處理就是解決這個問題的乎折。

本篇 CoorChice 將會通過一個回歸預(yù)測問題,來展示如何進行這個過程侵歇。

原始數(shù)據(jù)

我們用于機器學(xué)習的數(shù)據(jù)的量不能太少骂澄,否則訓(xùn)練效果會極差。但是我們個人如何才能獲取到大量的數(shù)據(jù)來進行訓(xùn)練呢惕虑?

CoorChice 推薦 kaggle:https://www.kaggle.com/datasets坟冲,這是全球最大的數(shù)據(jù)科學(xué)社區(qū)和數(shù)據(jù)競賽平臺。在這個美妙的地方,我們可以輕松的找到各種各樣的,海量的篡石,精彩絕倫的數(shù)據(jù)照瘾。

image

打開鏈接就可以看到海量的數(shù)據(jù),我們很隨意的選擇其中一個數(shù)據(jù)源來進行訓(xùn)練。

就選第一條吧!

第一條數(shù)據(jù)是關(guān)于一個面包店的交易的數(shù)據(jù)。點擊進入后侄榴,找到下載就可以下載這套數(shù)據(jù)了。

image

也許你可能不能立刻下載网沾,因為他會要求你先登錄癞蚕。那就成為 Kaggle 的會員吧。

我們使用表格打開剛剛下載的數(shù)據(jù)觀察觀察辉哥。

image

My God桦山! 這是一堆簡陋不堪的數(shù)據(jù)攒射,共有 21293 條數(shù)據(jù),每條只有 4 個特征恒水! 4 個特征会放! 4 個特征!

沒關(guān)系钉凌,我們只是學(xué)習如何處理數(shù)據(jù)的過程咧最,所以就湊合著用一下吧。

開始數(shù)據(jù)處理

實際上御雕,通常我們的數(shù)據(jù)呆在數(shù)據(jù)庫中矢沿,就是這種表格的形式。

在開始之前酸纲,需要先安裝一下 Pandas 這個 Python 庫捣鲸,它被用來讀取我們的表格數(shù)據(jù),和進行增闽坡、刪栽惶、改、查等操作无午。

下好了媒役,我們就開始吧...

觀察數(shù)據(jù)

進行數(shù)據(jù)處理最重要的一步祝谚,就是對數(shù)據(jù)進行觀察和分析宪迟。通過觀察和分析,我們要除去一些無關(guān)緊要的數(shù)據(jù)列交惯,比如在預(yù)測天氣預(yù)報的時候次泽,我們就應(yīng)該把 ‘李雷早上吃了什么’ 這個數(shù)據(jù)列去掉,因為對模型訓(xùn)練毫無幫助席爽。

同時意荤,我們要從數(shù)據(jù)中挖掘出一些隱藏的信息,以擴充特征只锻。日入在預(yù)測產(chǎn)品的交易信息時玖像,我們從日期這個數(shù)據(jù)列,就能知道當天是否是一個節(jié)日齐饮,是什么節(jié)日捐寥。

在我們下載的這堆面包店的交易數(shù)據(jù)中,僅有慘不忍睹的 4 個類目祖驱!

通常握恳,當我們使用機器學(xué)習來解決問題時,都會有一個目的捺僻。比如乡洼,在這堆數(shù)據(jù)中崇裁,CoorChice 希望通過機器學(xué)習訓(xùn)練一個模型,能夠用于預(yù)測未來一定條件下的某種類型的面包所能達到的交易量是多少束昵。

因此拔稳,我們將交易量 Transaction 這一列數(shù)據(jù)作為數(shù)據(jù)集的標簽數(shù)據(jù)。

接下來剩下的只有 日期锹雏、時間壳炎、面包類目 這三個特征了。

從數(shù)據(jù)中挖掘更多的信息

僅有這三個特征來進行訓(xùn)練逼侦,結(jié)果可想而知會有多差匿辩。比你想的差還要差,所以我們需要從現(xiàn)有的數(shù)據(jù)中挖掘出更多的信息來榛丢。比如铲球,在圖片數(shù)據(jù)中,通常會進行一些旋轉(zhuǎn)晰赞、平移之類的操作稼病,來補充數(shù)據(jù)量。

日期 Date

首先看看日期能挖掘到什么有用的信息掖鱼?

  1. 當然是星期啦然走。今天是星期一、星期二戏挡、...芍瑞、對交易肯定是有影響的。我們通過 Date 可以計算出當前日期是周幾褐墅。

  2. 還有什么可挖掘的嗎拆檬?嗯,左思右想...咦妥凳,節(jié)日肯定也是影響面包交易量的一個重要因素竟贯,有了 Date 我們也能知道當前日期是什么節(jié)日。

  3. 然后逝钥,通過觀察可以發(fā)現(xiàn)屑那,隨著日期的增加,交易量也是在累加的艘款,所以日期的大小也可直接作為一個特征持际。

  4. 由于 CoorChice 要嘗試預(yù)測的是未來某個時間、某種面包所能達到的交易量磷箕,所以日期的大小對結(jié)果必然是有影響的选酗。但我們不能直接將日期轉(zhuǎn)換為一個形如 20161030 這樣的數(shù)字,因為一年只有 12 個月岳枷,每一個月天數(shù)不一樣的芒填,所以這個數(shù)值跳躍度比較大呜叫,這樣就大大增加了訓(xùn)練的難度和準確率。我們需要的是一個統(tǒng)一標準的數(shù)值殿衰。如果確定某一天為基準朱庆,然后把日期轉(zhuǎn)換為和這一天的距離,那量化標準自然就是很明了的了闷祥。因為數(shù)據(jù)是從 2016-10-30 開始娱颊,那么把這天作為基準日就再合適不過了。

時間 Time

接下來觀察 Time 有什么可用的沒凯砍。

  1. Time 本身是一些比較密集的數(shù)據(jù)箱硕,間隔不大。我們可以把它劃歸到小時里悟衩,因此就會有 24 個小時剧罩。比如,“10:13:03” 就讓它屬于 10 點這個小時吧座泳。

  2. 通過時間惠昔,我們還可以知道現(xiàn)在是處于 早晨、上午挑势、中午镇防、下午、傍晚潮饱、晚上来氧、深夜 中的那個時間段。不同的時間段或多或少也會影響交易量饼齿。

確定好了思路饲漾,接下來就開始找著這個思路處理數(shù)據(jù)了蝙搔。

處理原始數(shù)據(jù)

處理數(shù)據(jù)的第一步當然是先讀取數(shù)據(jù)啦缕溉,我們前面下載的 Pandas 就使用來干這個的。

讀取數(shù)據(jù)

# 讀取數(shù)據(jù)
datas = pd.read_csv('data/BreadBasket_DMS.csv')

讀取數(shù)據(jù)后吃型,輸出一下看看長什么樣子证鸥。

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]

增強數(shù)據(jù)

然后,我們對原始數(shù)據(jù)按照上面分析的思路進行處理勤晚。

# 挖掘枉层,周幾對交易量的影響
datas['Weekday'] = datas['Date']
datas['Weekday'] = datas['Weekday'].map(lambda x: get_weekday(x))

# 挖掘,各個節(jié)日對交易量的影響
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 一列變?yōu)榕c 2016-10-30 這天的距離
datas['Date'] = datas['Date'].map(lambda x: get_delta_days(x))
# 將時間轉(zhuǎn)變?yōu)闀r段
datas['Time'] = datas['Time'].map(lambda x: get_time_range(x))
# 縮小數(shù)據(jù)大小
datas['Transaction'] = datas['Transaction'].map(lambda x: (float(x) / 1000.))

現(xiàn)在鸟蜡,再輸出一下數(shù)據(jù)看看。

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]

從表格數(shù)據(jù)中可以看到挺邀,Date 和 Transaction 都變成浮點數(shù)了揉忘,因為把它們進行縮小跳座,有利于后面的梯度下降,否則 loss 會非常非常的大泣矛。

現(xiàn)在疲眷,我們已經(jīng)擴充了一些特征。

進行 One-Hot 編碼

接下來您朽,對于離散的數(shù)據(jù)狂丝,比如 Weekday、Time 這樣的哗总,它的大小并不會對交易產(chǎn)生影響几颜,但這種離散的特征的類別取值是會對結(jié)果產(chǎn)生影響的。比如讯屈,春節(jié)這天會對食物的交易量產(chǎn)生影響菠剩。

我們需要對這樣的特征數(shù)據(jù)進行 獨熱編碼 。關(guān)于 獨熱編碼 耻煤,你可以在 CoorChice 的 《機器學(xué)習具壮,看完就明白了》 這篇文章,了解相關(guān)信息哈蝇。

ont_hot_data = pd.get_dummies(datas, prefix=['Time', 'Item', 'Weekday', 'Festival', 'Time_Quantum'])

看看現(xiàn)在數(shù)據(jù)表變成了什么樣子了棺妓。

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]

經(jīng)過獨熱編碼后,columns 已經(jīng)擴充到了驚人的 136 列炮赦!

之所以需對數(shù)據(jù)進行獨熱編碼怜跑,還有一個原因是在訓(xùn)練時,獨熱編碼在分類是 0 和 1 的關(guān)系吠勘,而如果用 1性芬,2,3剧防,.. 的分類標簽方式植锉,是不利于梯度下降的,計算出的梯度往往比較大峭拘。

對 Date俊庇、Transaction,可以看到表格中鸡挠,CoorChice 還對他們進行縮小辉饱,目的是為了把數(shù)值范圍變小,這樣有利于梯度下降的求解拣展。

我們看看現(xiàn)在數(shù)據(jù)的分布情況:

image

打亂數(shù)據(jù)

接下來彭沼,我們需要將元素的有序的數(shù)據(jù)進行打亂,這樣有助于提高訓(xùn)練出來的模型的泛化能力备埃。

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 遍译株!

image

看 Date 一列,說明數(shù)據(jù)已經(jīng)被打的足夠亂了挺益。

保存數(shù)據(jù)

最后歉糜,我們要把處理好的數(shù)據(jù)分為訓(xùn)練集和測試集,CoorChice 按大概 30% 取測試集數(shù)據(jù)望众,然后分別保存匪补。

# 測試數(shù)據(jù)集大小
test_count = 6000
train_count = ont_hot_data.shape[0] - test_count
# 切割出訓(xùn)練數(shù)據(jù)集
train_data = ont_hot_data[:train_count]
# 切割出測試數(shù)據(jù)集
test_data = ont_hot_data[train_count:]
# 分別保存兩個數(shù)據(jù)集
train_data.to_csv('data/train_data.csv', index=False, header=True)
test_data.to_csv('data/test_data.csv', index=False, header=True)

保存處理好的數(shù)據(jù)很簡單,使用 Pandas 提供的 to_csv() 就可以把數(shù)據(jù)存 csv 格式烂翰。

index 和 header 分別表示是否保存行號和列名稱夯缺。

image

現(xiàn)在,我們已經(jīng)把原始數(shù)據(jù)處理好甘耿,并且分割成了訓(xùn)練集和測試集踊兜。我們有 15,293 條訓(xùn)練數(shù)據(jù),和 6000 條測試數(shù)據(jù)佳恬。

開始訓(xùn)練吧捏境!

在開始之前,建議先看一看這一篇 《【Get】用深度學(xué)習識別手寫數(shù)字》 毁葱,因為套路基本上是一樣的垫言,只是有的地方需要根據(jù)具體情況調(diào)整。

在手寫數(shù)字識別中倾剿,我們構(gòu)建的是一個簡單 4 層網(wǎng)絡(luò)筷频,它由兩個卷積層,一個全鏈接層和一個輸出層組成前痘。

image_graph

回顧一下這個網(wǎng)絡(luò)結(jié)構(gòu)凛捏。

發(fā)生了欠擬合!

一開始芹缔,CoorChice 直接使用了這個網(wǎng)絡(luò)進行訓(xùn)練坯癣,經(jīng)過 10w 次的漫長訓(xùn)練之后發(fā)現(xiàn),準確率最高不過 0.1%乖菱。

起初 CoorChice 減小 lr(學(xué)習率)坡锡,想著可能是學(xué)習率過大,導(dǎo)致震蕩了窒所。然而,毫無作用帆锋。

出現(xiàn)這種現(xiàn)象吵取,基本可以判斷大概率上是發(fā)生欠擬合了。

發(fā)生欠擬合锯厢,可以通過以下步驟一步步的排除問題和改進網(wǎng)絡(luò)皮官。

使用適合的權(quán)重初始化方案

本次訓(xùn)練中脯倒,權(quán)重的初始化沿用了上次的正太分布初始化方案,應(yīng)該是比較優(yōu)秀的方案了捺氢,所以就不做改動了藻丢。

image_truncated

選擇適當?shù)募せ詈瘮?shù)

在上一次的網(wǎng)絡(luò)構(gòu)建中,卷積層摄乒、全鏈接層都用的是 ReLu 激活函數(shù)悠反,輸出層使用的是 Softmax 激活函數(shù)。而這次 CoorChice 把激活函數(shù)全換成了優(yōu)秀的 ReLu 激活函數(shù)馍佑,這應(yīng)該也是沒毛病的斋否。

image

選擇適合的優(yōu)化器和學(xué)習率

image

這是一張各種優(yōu)化器的比較圖,從圖中可以看到拭荤,有一個名叫 Adadelta 的優(yōu)化器表現(xiàn)十分亮眼耙鸪簟!

Adadelta 是一種自適應(yīng)的優(yōu)化器舅世,它能夠自動的計算自變量更新量的平方的指數(shù)加權(quán)移動的平均項來作為學(xué)習率旦委。因此,我們設(shè)置的學(xué)習率實際上影響已經(jīng)不是太大了雏亚。

從圖中可以看到社证,這種優(yōu)化器在訓(xùn)練之初和中期又能夠快速下降,只不過到接近最優(yōu)解的時候會出現(xiàn)小幅震蕩评凝。而 SGD 因全程都保持一個學(xué)習率追葡,所以在設(shè)置合適的情況下,在最優(yōu)解附近收斂的更干脆奕短。

# 使用 Adadelta 進行損失函數(shù)的梯度下降求解
train_step = tf.train.AdadeltaOptimizer(0.0001).minimize(cross_entropy)

但由于 Adadelta 實際訓(xùn)練過程中與我們起初告訴它的學(xué)習率關(guān)系不大宜肉,就是不受控,訓(xùn)練起來到后面還是很難受的翎碑,所以 CoorChice 嘗試之后還是選擇換成 SGD 谬返。

lr = tf.Variable(1e-5)
# 使用 SGD 進行損失函數(shù)的梯度下降求解
train_step = tf.train.GradientDescentOptimizer(lr).minimize(cross_entropy)

而且,CoorChice 使用了一個變量 lr 來設(shè)置優(yōu)化器的學(xué)習率日杈,這樣在訓(xùn)練過程中遣铝,我們還可以動態(tài)的額控制當前的學(xué)習率。

增加網(wǎng)絡(luò)深度莉擒、寬度

當發(fā)生欠擬合時酿炸,也有可能是網(wǎng)絡(luò)過于簡單,權(quán)重太少涨冀,導(dǎo)致無法學(xué)習到足夠的信息填硕。在本次訓(xùn)練中,CoorChice 仍然使用了上次的 4 層單核網(wǎng)絡(luò),似乎確實是太簡單了扁眯。

那就加強它吧壮莹。

 # 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')

        # ------------------------構(gòu)建第一層網(wǎng)絡(luò)---------------------
        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)

        # ------------------------構(gòu)建第二層網(wǎng)絡(luò)---------------------
        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])

        # ------------------------ 構(gòu)建第一層全鏈接層 ---------------------
        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)


        # ------------------------構(gòu)建輸出層---------------------
        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')
            # 注意??,此處的激活函數(shù)已經(jīng)替換成 ReLu 了
            self.y_conv = tf.nn.relu(tf.matmul(h_fc1, W_out) + b_out)
            tf.summary.histogram('activations_y_conv', self.y_conv)

這就是經(jīng)過改進后的網(wǎng)絡(luò)的全部代碼了姻檀。

可以看到命满,還是熟悉的套路。以下是版本更新內(nèi)容:

  • 每個卷積核的 size 由 5x5 變成了 3x3

  • 第一個卷積層的卷積核通道數(shù)由原來的 32 增加到了 64绣版。將卷積核數(shù)量提高一倍胶台。

  • 卷積核層數(shù)仍然是兩層,但每層都多增加了一個卷積核組僵娃。
  • 全鏈接層的神經(jīng)元個數(shù)由原來的 1024 個增加至 4096 個概作。

再說一下網(wǎng)絡(luò)中的一些變化。

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')

定義占位的時候默怨,由于表格數(shù)據(jù)中只有 135 列讯榕,所以先定義一個 shape 為 [None,135] 的占位匙睹。但是比較尷尬的是愚屁,135 是一個奇數(shù),無法轉(zhuǎn)換成能和卷積核卷積的張量痕檬,所以 CoorChice 給它補了一列霎槐,通過 tensorflow 提供的 pad() 函數(shù)。

input_data = tf.pad(self.x_data, [[0, 0], [0, 1]], 'CONSTANT')

第二個參數(shù)中的數(shù)字分別表示需要在原張量的 上梦谜、下丘跌、左、右 補充多少行或列唁桩。這里 CoorChice 再原張量的右邊補充了一列 0闭树。

現(xiàn)在,就可以變形成可和卷積核卷積的張量了荒澡。

input_data = tf.reshape(input_data, [-1, 17, 8, 1])
  • 小技巧:通常报辱,經(jīng)過多層的卷積和池化之后,進入第一層全鏈接層或者直接進入輸出層的時候单山,我們需要知道上一層的形狀是怎樣的碍现,然后才能設(shè)計全鏈接層或者輸出層的權(quán)重形狀∶准椋可以通過如下方式方便的獲得輸入的形狀:
# 獲得最后一個池化層的寬
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])

實際上昼接,這個網(wǎng)絡(luò)可能不是太好,深度不夠躏升,可能導(dǎo)致準確率不夠辩棒。但受設(shè)備限制,設(shè)計的太深膨疏,會導(dǎo)致每次訓(xùn)練都會花費大量的時間一睁,所以 CoorChice 就盡量精簡一點,主要看這個過程是怎樣進行的佃却。

這是新的網(wǎng)絡(luò)結(jié)構(gòu):

image

看看局部的兩組卷積 hidden 層:

image

開啟訓(xùn)練!

# 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) + "/"

# 讀取數(shù)據(jù)
BBDATA = read_datas('data/')

# 創(chuàng)建網(wǎng)絡(luò)
network = CnnBreadBasketNetwork()
x_data = network.x_data
y_data = network.y_data
y_conv = network.y_conv
keep_prob = network.keep_prob
# ------------------------構(gòu)建損失函數(shù)---------------------
with tf.name_scope("cross_entropy"):
    # 創(chuàng)建正則化對象者吁,此處使用的是 L2 范數(shù)
    regularization = tf.contrib.layers.l2_regularizer(scale=(5.0 / 500))
    # 應(yīng)用正則化到參數(shù)集上
    reg_term = tf.contrib.layers.apply_regularization(regularization)
    # 在損失函數(shù)中加入正則化項
    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 進行損失函數(shù)的梯度下降求解
    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)

# ------------------------構(gòu)建模型評估函數(shù)---------------------
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)

# 創(chuàng)建會話
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))

# 初始化參數(shù)
sess.run(tf.initialize_all_variables())

global loss
loss = 0
global train_accuracy
for i in range(train_times):
    # 從訓(xùn)練集中取出 200 個樣本進行一波訓(xùn)練
    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("當前共訓(xùn)練 " + 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)

    # 每訓(xùn)練 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("訓(xùn)練完成饲帅!")
train_writer.close()
test_writer.close()
# 保存模型
save_model(save_path, sess, train_times)

sess.close()

這是完整的訓(xùn)練代碼复凳,基本上和 《【Get】用深度學(xué)習識別手寫數(shù)字》 中是一樣的。只不過 CoorChice 多加了一個 MAE 作為模型評估的指標灶泵。

dv = tf.reduce_mean(tf.abs(y_conv - y_data))

因為是進行回歸預(yù)測育八,所以使用 MAE 均方方差來作為一個評估模型好壞的指標。平均絕對差越小赦邻,表明模型預(yù)測結(jié)果越接近于真實情況髓棋。

此外,由于是做回歸預(yù)測惶洲,所以之前分類使用的交叉熵損失函數(shù)就不是特別合適了按声,我們換成均方誤差損失函數(shù)。

image
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)

評估模型現(xiàn)在不應(yīng)該再是比較預(yù)測值和標簽值是否相等了恬吕,在回歸預(yù)測中签则,要做到模型能夠預(yù)測出的值和真實值完全相等幾乎是不可能的。我們把評估模型改造成絕對差 <500 即接受該預(yù)測結(jié)果铐料,畢竟是在個人設(shè)備上跑渐裂,要達到比較高的精度還是很難的。

loss 不下降了

上面這段代碼中钠惩,SGD 優(yōu)化器的學(xué)習率變量實際上是 CoorChice 后來加上的柒凉。在此前,使用的固定的妻柒,但當 run 起來開始訓(xùn)練的時候扛拨,發(fā)現(xiàn) loss 總在一個值很大,但范圍很小的區(qū)間內(nèi)波動举塔,而且準確率也在小范圍的波動绑警,提升不上去。

  • 損失變化曲線
image
  • 準確率變化曲線
image

此時的準確大概在 10.5% 附近徘徊開來央渣,loss 趨勢變化也不大计盒。

這說明訓(xùn)練基本處于停滯不前的狀態(tài)了,但顯然芽丹,我們的模型精度是不夠的北启。出現(xiàn)這種情況可能是以下幾種情況。

陷入局部最優(yōu)解

image

如圖,在多特征的高維 loss 曲面中咕村,可能存在很多 A 點旁邊的小坑场钉。當進入小坑的底部時,loss 的一階導(dǎo)數(shù)也是為 0 的懈涛,就網(wǎng)上常常說的陷入了局部最優(yōu)解中逛万,而沒有到真正的 Global Minima。

實際上批钠,上面代碼中我們采取的 mini-patch 抽樣進行訓(xùn)練宇植,一定程度上也是能減小陷入局部最優(yōu)解的情況的。因為可能這批樣本陷入了局部最優(yōu)中埋心,下一批樣本的波動又把你從坑里拽出來了指郁。當然有的時候我們可能會看到 loss 反而增大了,也是因為這種方式導(dǎo)致的拷呆。

騎在鞍點上

image

如圖闲坎,在 loss 的曲面中,會有比局部最優(yōu)坑還多的馬鞍型曲面洋腮。

image

憨態(tài)可掬的??...

當我們騎到馬鞍上的鞍點是箫柳,此處的導(dǎo)數(shù)也是為0的,那么是真正的收斂了嗎啥供?不是的悯恍。看圖中伙狐,我們在x軸方向上是極小值涮毫,但在y軸方向上是極大值。

image

如果落在這樣正在進入鞍點的尷尬位置贷屎,那么你的訓(xùn)練就會看起來幾乎毫無波動罢防,走出這個區(qū)域需要花費很長的時間。

幸運的是唉侄,通過適當增大學(xué)習率或者適當減小 batch咒吐,都有助于盡快逃離這個區(qū)域。

無盡的平緩面

image

然后在 loss 曲面中属划,同時存在著如上圖一樣的大面積的平緩曲面恬叹,處于這些區(qū)域中的點,雖然導(dǎo)數(shù)不為 0 同眯,但值卻及其的小绽昼,即使一遍又一遍的反復(fù)訓(xùn)練,仍然看起來幾乎沒有變化须蜗。

可怕的是硅确,我們無法分辨出是處于局部最優(yōu)目溉,還是處于馬鞍區(qū)域,或者干脆就是在類平面上菱农。

如何處理缭付?

出現(xiàn)以上幾種情況,都會導(dǎo)致看起來不學(xué)習的情況大莫,特別是在性能有限的個人設(shè)備上蛉腌。但我們還是有一些套路可以盡量減小這種情況的發(fā)生的官份。

  1. 檢查數(shù)據(jù)只厘。確保數(shù)據(jù)標簽對應(yīng)正常。
  1. 修改調(diào)整網(wǎng)絡(luò)結(jié)構(gòu)舅巷。盡量使用一些開源的大牛們產(chǎn)出的網(wǎng)絡(luò)模型羔味,如 VGG 系列。在它們的基礎(chǔ)上修改大概率上比自己搗騰靠譜钠右。
  1. 調(diào)整 batch 大小赋元。batch過大導(dǎo)致訓(xùn)練時間過長,也容易進入局部最優(yōu)或者馬鞍區(qū)域飒房。過小又回導(dǎo)致震蕩比較劇烈搁凸,不容易找到最優(yōu)解。
  1. 多嘗試不同的參數(shù)初始化狠毯。如果足夠幸運的話护糖,沒準初始化就落到了 Global Minima 的坑里去了。
  1. 動態(tài)學(xué)習率嚼松。一成不變的小的學(xué)習率當陷入局部最優(yōu)時就算是掉坑里了嫡良,或者在平滑面上移動的特別慢。如果大了又容易錯過最優(yōu)解献酗。所以像 Adam 這樣動態(tài)的學(xué)習率優(yōu)化器能一定程度上減少這些問題的發(fā)生寝受。當然,我們也可以在 SGD 的基礎(chǔ)上罕偎,根據(jù) loss 的情況自己動態(tài)的增大學(xué)習率很澄,在 loss 很大卻不變的時候,在 loss 較小的時候減小學(xué)習率颜及,避免錯過最優(yōu)解甩苛。

通過 loss 大致的判斷問題

  • train loss 不斷下降,test loss不斷下降器予,說明網(wǎng)絡(luò)仍在學(xué)習;

  • train loss 不斷下降浪藻,test loss趨于不變,說明網(wǎng)絡(luò)過擬合;

  • train loss 趨于不變乾翔,test loss不斷下降爱葵,說明數(shù)據(jù)集100%有問題;

  • train loss 趨于不變施戴,test loss趨于不變,說明學(xué)習遇到瓶頸萌丈,需要減小學(xué)習率或批量數(shù)目;

  • train loss 不斷上升赞哗,test loss不斷上升,說明網(wǎng)絡(luò)結(jié)構(gòu)設(shè)計不當辆雾,訓(xùn)練超參數(shù)設(shè)置不當肪笋,數(shù)據(jù)集經(jīng)過清洗等問題。

通過 loss 可以大致的判斷一些問題度迂,上述規(guī)律可以最為參考藤乙,但不保證一定準確。

總結(jié)

最后惭墓,一張圖做個總結(jié):

image
  • 抽出空余時間寫文章分享需要動力扔亥,各位看官動動小手點個贊哈徒探,信仰還是需要持續(xù)充值的??
  • CoorChice 一直在不定期的分享新的干貨孩擂,想要上車只需進到 CoorChice的【個人主頁】 點個關(guān)注就好了哦祭玉。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市钧萍,隨后出現(xiàn)的幾起案子褐缠,更是在濱河造成了極大的恐慌,老刑警劉巖风瘦,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件队魏,死亡現(xiàn)場離奇詭異,居然都是意外死亡弛秋,警方通過查閱死者的電腦和手機器躏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蟹略,“玉大人登失,你說我怎么就攤上這事⊥诰妫” “怎么了揽浙?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長意敛。 經(jīng)常有香客問我馅巷,道長,這世上最難降的妖魔是什么草姻? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任钓猬,我火速辦了婚禮,結(jié)果婚禮上撩独,老公的妹妹穿的比我還像新娘敞曹。我一直安慰自己账月,他們只是感情好,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布澳迫。 她就那樣靜靜地躺著局齿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪橄登。 梳的紋絲不亂的頭發(fā)上抓歼,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機與錄音拢锹,去河邊找鬼谣妻。 笑死,一個胖子當著我的面吹牛面褐,可吹牛的內(nèi)容都是我干的拌禾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼展哭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了闻蛀?” 一聲冷哼從身側(cè)響起匪傍,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎觉痛,沒想到半個月后役衡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡薪棒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年手蝎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俐芯。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡棵介,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吧史,到底是詐尸還是另有隱情邮辽,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布贸营,位于F島的核電站吨述,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏钞脂。R本人自食惡果不足惜揣云,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冰啃。 院中可真熱鬧邓夕,春花似錦肋层、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至汪榔,卻和暖如春蒲拉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痴腌。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工雌团, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人士聪。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓锦援,卻偏偏與公主長得像,于是被迫代替她去往敵國和親剥悟。 傳聞我的和親對象是個殘疾皇子灵寺,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354