深度學習是加深了層的深度神經網絡。只需通過疊加層,就可以創建深度網絡。
1、 加深網絡
將深度學習中的重要技術(構成神經網絡的各種層、學習時的有效技巧、對圖像特別有效的CNN、參數的最優化方法等)匯總起來,創建一個深度網絡,對MNIST數據集的手寫數字識別。
1.1、構建網絡
上圖網絡有如下特點:
1、基于3×3的小型濾波器的卷積層。特點是隨著層的加深,通道數變大(卷積層的通道數從前面的層開始按順序以16、16、32、32、64、64的方式增加)
2、插入了池化層,以逐漸減小中間數據的空間大小
3、激活函數是ReLU。
4、全連接層的后面使用Dropout層。
5、基于Adam的最優化。
6、使用He初始值作為權重初始值。
實現代碼:
# 構建深層網絡
import sys, os
sys.path.append(os.pardir)
import pickle
import numpy as np
from collections import OrderedDict
from common.layers import *class DeepConvNet:"""識別率為99%以上的高精度的ConvNet網絡結構如下所示conv - relu - conv- relu - pool -conv - relu - conv- relu - pool -conv - relu - conv- relu - pool -affine - relu - dropout - affine - dropout - softmax"""def __init__(self, input_dim=(1, 28, 28),conv_param_1 = {'filter_num':16, 'filter_size':3, 'pad':1, 'stride':1},conv_param_2 = {'filter_num':16, 'filter_size':3, 'pad':1, 'stride':1},conv_param_3 = {'filter_num':32, 'filter_size':3, 'pad':1, 'stride':1},conv_param_4 = {'filter_num':32, 'filter_size':3, 'pad':2, 'stride':1},conv_param_5 = {'filter_num':64, 'filter_size':3, 'pad':1, 'stride':1},conv_param_6 = {'filter_num':64, 'filter_size':3, 'pad':1, 'stride':1},hidden_size=50, output_size=10):# 初始化權重===========# 各層的神經元平均與前一層的幾個神經元有連接(TODO:自動計算)pre_node_nums = np.array([1*3*3, 16*3*3, 16*3*3, 32*3*3, 32*3*3, 64*3*3, 64*4*4, hidden_size])wight_init_scales = np.sqrt(2.0 / pre_node_nums) # 使用ReLU的情況下推薦的初始值self.params = {}pre_channel_num = input_dim[0]for idx, conv_param in enumerate([conv_param_1, conv_param_2, conv_param_3, conv_param_4, conv_param_5, conv_param_6]):self.params['W' + str(idx+1)] = wight_init_scales[idx] * np.random.randn(conv_param['filter_num'], pre_channel_num, conv_param['filter_size'], conv_param['filter_size'])self.params['b' + str(idx+1)] = np.zeros(conv_param['filter_num'])pre_channel_num = conv_param['filter_num']self.params['W7'] = wight_init_scales[6] * np.random.randn(64*4*4, hidden_size)self.params['b7'] = np.zeros(hidden_size)self.params['W8'] = wight_init_scales[7] * np.random.randn(hidden_size, output_size)self.params['b8'] = np.zeros(output_size)# 生成層===========self.layers = []self.layers.append(Convolution(self.params['W1'], self.params['b1'], conv_param_1['stride'], conv_param_1['pad']))self.layers.append(Relu())self.layers.append(Convolution(self.params['W2'], self.params['b2'], conv_param_2['stride'], conv_param_2['pad']))self.layers.append(Relu())self.layers.append(Pooling(pool_h=2, pool_w=2, stride=2))self.layers.append(Convolution(self.params['W3'], self.params['b3'], conv_param_3['stride'], conv_param_3['pad']))self.layers.append(Relu())self.layers.append(Convolution(self.params['W4'], self.params['b4'],conv_param_4['stride'], conv_param_4['pad']))self.layers.append(Relu())self.layers.append(Pooling(pool_h=2, pool_w=2, stride=2))self.layers.append(Convolution(self.params['W5'], self.params['b5'],conv_param_5['stride'], conv_param_5['pad']))self.layers.append(Relu())self.layers.append(Convolution(self.params['W6'], self.params['b6'],conv_param_6['stride'], conv_param_6['pad']))self.layers.append(Relu())self.layers.append(Pooling(pool_h=2, pool_w=2, stride=2))self.layers.append(Affine(self.params['W7'], self.params['b7']))self.layers.append(Relu())self.layers.append(Dropout(0.5))self.layers.append(Affine(self.params['W8'], self.params['b8']))self.layers.append(Dropout(0.5))self.last_layer = SoftmaxWithLoss()def predict(self, x, train_flg=False):for layer in self.layers:if isinstance(layer, Dropout):x = layer.forward(x, train_flg)else:x = layer.forward(x)return xdef loss(self, x, t):y = self.predict(x, train_flg=True)return self.last_layer.forward(y, t)def accuracy(self, x, t, batch_size=100):if t.ndim != 1 : t = np.argmax(t, axis=1)acc = 0.0for i in range(int(x.shape[0] / batch_size)):tx = x[i*batch_size:(i+1)*batch_size]tt = t[i*batch_size:(i+1)*batch_size]y = self.predict(tx, train_flg=False)y = np.argmax(y, axis=1)acc += np.sum(y == tt)return acc / x.shape[0]def gradient(self, x, t):# forwardself.loss(x, t)# backwarddout = 1dout = self.last_layer.backward(dout)tmp_layers = self.layers.copy()tmp_layers.reverse()for layer in tmp_layers:dout = layer.backward(dout)# 設定grads = {}for i, layer_idx in enumerate((0, 2, 5, 7, 10, 12, 15, 18)):grads['W' + str(i+1)] = self.layers[layer_idx].dWgrads['b' + str(i+1)] = self.layers[layer_idx].dbreturn gradsdef save_params(self, file_name="params.pkl"):params = {}for key, val in self.params.items():params[key] = valwith open(file_name, 'wb') as f:pickle.dump(params, f)def load_params(self, file_name="params.pkl"):with open(file_name, 'rb') as f:params = pickle.load(f)for key, val in params.items():self.params[key] = valfor i, layer_idx in enumerate((0, 2, 5, 7, 10, 12, 15, 18)):self.layers[layer_idx].W = self.params['W' + str(i+1)]self.layers[layer_idx].b = self.params['b' + str(i+1)]
# 訓練deep_convent
import sys, os
sys.path.append(os.pardir)
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from ch08.deep_convnet import DeepConvNet
from common.trainer import Trainer(x_train, t_train), (x_test, t_test) = load_mnist(flatten=False)network = DeepConvNet()
trainer = Trainer(network, x_train, t_train, x_test, t_test,epochs=20, mini_batch_size=100,optimizer='Adam', optimizer_param={'lr':0.001},evaluate_sample_num_per_epoch=1000)
trainer.train()# 保存參數
network.save_params("deep_convnet_params.pkl")
print("Saved Network Parameters!")
運行結果:
如上結果,這個網絡的識別精度約為99.4%,錯誤識別率為0.6%。
識別錯誤的例子:
所以,這次的深度CNN盡管識別精度很高,但是對于某些圖像,也犯了和人類同樣的“識別錯誤”。
1.2、進一步提高識別精度-Data Augmentation(數據擴充)
Data Augmentation基于算法“人為地”擴充輸入圖像(訓練圖像)。具體地說,如下圖,對于輸入圖像,通過施加旋轉、垂直或水平方向上的移動等微小變化,增加圖像的數量。這在數據集的圖像數量有限時尤其有效。
除了上圖的變形之外,Data Augmentation還可以通過其他各種方法擴充圖像,比如裁剪圖像的“crop處理”、將圖像左右翻轉的“flip處理”等。對于一般的圖像,施加亮度等外觀上的變化、放大縮小等尺度上的變化也是有效的。綜上,通過Data Augmentation巧妙地增加訓練圖像,可以提高深度學習的識別精度。雖然這個看上去只是一個簡單的技巧,不過經常會有很好的效果。
1.3、加深層的動機
1、與沒有加深層的網絡相比,加深了層的網絡可以用更少的參數達到同等水平(或者更強)的表現力。
疊加小型濾波器來加深網絡的好處是可以減少參數的數量,擴大感受(receptive field,給神經元施加變化的某個局部空間區域)。并且,通過疊加層,將 ReLU等激活函數夾在卷積層的中間,進一步提高了網絡的表現力。
這是因為向網絡添加了基于激活函數的“非線性”表現力,通過非線性函數的疊加,可以表現更加復雜的東西。
例子:
5×5的卷積運算:
重復兩次3×3的卷積層運算
如上,一次5 × 5的卷積運算的區域可以由兩次3 × 3的卷積運算抵充。并且,相對于前者的參數數量25(5 × 5),后者一共是18(2 × 3 × 3),通過疊加卷積層,參數數量減少了。而且,這個參數數量之差會隨著層的加深而變大。比如,重復三次3 × 3的卷積運算時,參數的數量總共是27。而為了用一次卷積運算“觀察”與之相同的區域,需要一個7 × 7的濾波器,此時的參數數量是49。
2、與沒有加深層的網絡相比,加深了層的網絡可以可以減少學習數據,從而高效地進行學習。
在CNN的卷積層中,神經元會對邊緣等簡單的形狀有響應,隨著層的加深,開始對紋理、物體部件等更加復雜的東西有響應。
例子:
要用淺層網絡解決這個問題的話,卷積層需要一下子理解很多“狗”的特征。“狗”有各種各樣的種類,根據拍攝環境的不同,外觀變化也很大。因此,要理解“狗”的特征,需要大量富有差異性的學習數據,而這會導致學習需要花費很多時間。
通過加深網絡,就可以分層次地分解需要學習的問題。因為和印有“狗”的照片相比,包含邊緣的圖像數量眾多,并且邊緣的模式比“狗”的模式結構更簡單。所以,各層需要學習的問題就變成了更簡單的問題。比如,最開始的層只要專注于學習邊緣就好,這樣一來,只需用較少的學習數據就可以高效地進行學習。
2、 代表性網絡
2.1、VGG
VGG是由卷積層和池化層構成的基礎的CNN。
如上圖,VGG的特點:
1、將有權重的層(卷積層或者全連接層)疊加至16層或者19層,具備了深度(稱為“VGG16”或“VGG19”)。
2、基于3×3的小型濾波器的卷積層的運算是連續進行的。如圖,重復進行“卷積層重疊2次到4次,再通過池化層將大小減半”的處理,最后經由全連接層輸出結果。
2.2、GoogLeNet
GoogLeNet的網絡結構如下圖。圖中的矩形表示卷積層、池化層等:
如上圖,GoogLeNet的特點:
1、網絡不僅在縱向上有深度,在橫向上也有深度(廣度)。
2、將Inception結構用作一個構件(構成元素)(橫向上的“廣度”,稱為“Inception結構”。如下圖,Inception結構使用了多個大小不同的濾波器和池化,最后再合并它們的結果)。
2.3、ResNet
ResNet以VGG網絡為基礎,引入快捷結構以加深層,結果如下圖:
ResNet的特點
1、導入“快捷結構”導致即便加深到150層以上,識別精度也會持續提高。
2、通過以2個卷積層為間隔跳躍式地連接來加深層,使其具有比以前的網絡更深的結構。
原理:
在深度學習中,過度加深層的話,很多情況下學習將不能順利進行,導致最終性能不佳。而在ResNet中,為了解決這類問題,導入了“快捷結構”。導入快捷結構后,就可以隨著層的加深而不斷提高性能了(層的加深是有限度的)。
如上圖,這是因為在連續2層的卷積層中,將輸入x跳著連接至2層后的輸出。這里的重點是,通過快捷結構,原來的2層卷積層的輸出F(x)變成了F(x) + x。又因為,通過快捷結構,反向傳播時信號可以無衰減地傳遞。所以通過引入這種快捷結構,即使加深層,也能高效地學習。
3、 深度學習的高速化-卷積層運算高速化
3.1、GPU
由于GPU不僅可以高速地進行圖像處理,也可以高速地進行并行數值計算,因此 ,GPU計算(基于GPU進行通用的數值計算的操作)的目標就是將這種壓倒性的計算能力用于各種用途。
CPU vs GPU:
CPU比較擅長連續的、復雜的計算。
GPU比較擅長大量的乘積累加運算,或者大型矩陣的乘積運算。
綜上,與使用單個CPU相比,使用GPU進行深度學習的運算可以達到驚人的高速化。
3.2、分布式學習
分布式學習:將深度學習的學習過程擴展開來。
為了進一步提高深度學習所需的計算的速度,可以考慮在多個GPU或者多臺機器上進行分布式計算。至于“如何進行分布式計算”是一個非常難的課題。它包含了機器間的通信、數據的同步等多個無法輕易解決的問題。可以將這些難題都交給TensorFlow等優秀的框架。
3.3、運算精度的位數縮減
為了避免流經GPU(或者CPU)總線的數據超過某個限制,要盡可能減少流經網絡的數據的位數。又因為神經網絡的健壯性,深度學習并不那么需要數值精度的位數。所以,在深度學習中,即便是16位的半精度浮點數(half float),也可以順利地進行學習且識別精度不會下降。
代碼證明:
# 證明在深度學習中,即便是16位的半精度浮點數(half float),也可以順利地進行學習
import sys, os
sys.path.append(os.pardir)
import numpy as np
import matplotlib.pyplot as plt
from ch08.deep_convnet import DeepConvNet
from dataset.mnist import load_mnist(x_train, t_train), (x_test, t_test) = load_mnist(flatten=False)network = DeepConvNet()
network.load_params("deep_convnet_params.pkl")sampled = 10000 # 為了實現高速化
x_test = x_test[:sampled]
t_test = t_test[:sampled]print("caluculate accuracy (float64) ... ")
print(network.accuracy(x_test, t_test))# 轉換為float16型
x_test = x_test.astype(np.float16)
for param in network.params.values():param[...] = param.astype(np.float16)print("caluculate accuracy (float16) ... ")
print(network.accuracy(x_test, t_test))
4、 深度學習的應用
深度學習并不局限于物體識別,在圖像、語音、自然語言等各個不同的領域,深度學習都展現了優異的性能。
4.1、物體檢測(R-CNN)
物體檢測是從圖像中確定物體的位置,并進行分類的問題。
解決方法為R-CNN的處理流(R-CNN用一個CNN來完成所有處理,使得高速處理成為可能):
上圖中的第2步和第3步“2.Extract region proposals”(候選區域的提取)和“3.Compute CNN features”(CNN特征的計算)的處理部分。這里,首先(用Selective Search、Faster R-CNN等方法)找出形似物體的區域,然后對提取出的區域應用CNN進行分類。R-CNN中會將圖像變形為正方形,或者在分類時使用SVM(支持向量機),實際的處理流會稍微復雜一些,不過從宏觀上看,也是由剛才的兩個處理(候選區域的提取和CNN特征的計算)構成的。
4.2、圖像分割(FCN)
圖像分割是指在像素水平上對圖像進行分類。如下圖,使用以像素為單位對各個對象分別著色的監督數據進行學習。然后,在推理時,對輸入圖像的所有像素進行分類。
解決方法為用FCN(Fully Convolutional Network)通過一次forward處理,對所有像素進行分類:
FCN即“全部由卷積層構成的網絡”。相對于一般的CNN包含全連接層,FCN將全連接層替換成發揮相同作用的卷積層。在物體識別中使用的網絡的全連接層中,中間數據的空間容量被作為排成一列的節點進行處理,如上圖只由卷積層構成的網絡中,FCN最后導入了擴大空間大小的處理。基于這個處理,變小了的中間數據可以一下子擴大到和輸入圖像一樣的大小,空間容量可以保持原樣直到最后的輸出。
FCN最后進行的擴大處理是基于雙線性插值法的擴大(雙線性插值擴大)。FCN中,這個雙線性插值擴大是通過去卷積(逆卷積運算)來實現的。
4.3、圖像標題的生成(NIC)
給出一個圖像后,會自動生成介紹這個圖像的文字。
解決方法為NIC(Neural Image Caption)模型:
如上圖,NIC由深層的CNN和處理自然語言的RNN(Recurrent Neural Network)構成。
RNN是呈遞歸式連接的網絡,經常被用于自然語言、時間序列數據等連續性的數據上。
NIC基于CNN從圖像中提取特征,并將這個特征傳給RNN。
RNN以CNN提取出的特征為初始值,遞歸地生成文本。
多模態處理:將組合圖像和自然語言等多種信息進行的處理。
4.4、其他應用
1、圖像風格變換:輸入兩個圖像后,會生成一個新的圖像。
2、圖像的生成:事先學習大量圖像后生成新的圖像
3、自動駕駛:計算機代替人類駕駛汽車
4、強化學習(reinforcement learning):像人類通過摸索試驗來自主學習