本次筆記使用的材料是2018版的斯坦福計算機課程Lecture8的ppt,相較于2017版改動了一些內(nèi)容类腮,總體是比較新的缸逃。
本課重點:
-
深度學習硬件:
-
CPU需频、GPU 昭殉、TPU
-
-
深度學習框架:
-
PyTorch / TensorFlow
-
靜態(tài)與動態(tài)計算圖
-
1 深度學習硬件
GPU(Graphics Processing Unit)是圖形處理單元(又稱顯卡),在物理尺寸上就比CPU(Central Processing Unit)大得多乾蓬,有自己的冷卻系統(tǒng)巢块。最初用于渲染計算機圖形,尤其是游戲越走。在深度學習上選擇NVIDIA(英偉達)的顯卡廊敌,如果使用AMD的顯卡會遇到很多問題骡澈。TPU(Tensor Processing Units)是專用的深度學習硬件。
CPU/GPU/TPU
- CPU一般有多個核心护锤,每個核心速度都很快都可以獨立工作,可同時進行多個進程氯析,內(nèi)存與系統(tǒng)共享,完成序列任務時很有用宴杀。圖上CPU的運行速度是每秒約540 GFLOPs浮點數(shù)運算旷余,使用32位浮點數(shù)蠢熄。(注:一個GFLOPS(gigaFLOPS)等于每秒十億(=10^9)次的浮點運算签孔。)
- GPU有上千個核心數(shù),但每個核心運行速度很慢但绕,也不能獨立工作捏顺,適合大量的并行完成類似的工作。GPU一般自帶內(nèi)存本今,也有自己的緩存系統(tǒng)懂拾。圖上GPU的運行速度是CPU的20多倍岖赋。
- TPU時專門的深度學習硬件选脊,運行速度非晨疑叮快。TITAN V在技術(shù)上并不是一個“TPU”硝桩,因為這是一個谷歌術(shù)語碗脊,但兩者都有專門用于深度學習的硬件衙伶。運行速度非常快卧须。
GPU的優(yōu)勢與應用
實際應用中非常能體現(xiàn)GPU優(yōu)勢的一個例子是矩陣的乘法。由于結(jié)果中的每一個元素都是相乘的兩個矩陣的每一行和每一列的點積隘击,所以并行的同時進行這些點積運算速度會非常快凶赁。卷積神經(jīng)網(wǎng)絡也類似致板,卷積核和圖片的每個區(qū)域進行點積也是并行運算斟或。然而GPU雖然也有多個核心萝挤,但是在大矩陣運算時只能串行運算,速度很慢。
可以寫出在GPU上直接運行的代碼侈沪,方法是使用NVIDIA自帶的抽象代碼CUDA 亭罪,可以寫出類似C的代碼,并可以在GPU直接運行箩祥。但是直接寫CUDA代碼是一件非常困難的事袍祖,好在可以直接使用NVIDIA已經(jīng)高度優(yōu)化并且開源的API,比如 cuBLAS包含很多矩陣運算凳鬓, cuDNN包含CNN前向傳播缩举、反向傳播奶赔、批量歸一化等操作站刑;還有一種語言是OpenCL,可以在CPU因悲、AMD上通用晃琳,但是沒人做優(yōu)化,速度很慢顾翼;HIP可以將CUDA代碼自動轉(zhuǎn)換成可以在AMD上運行的語言适贸。以后可能會有跨平臺的標準,但是現(xiàn)在來看CUDA是最好的選擇砾隅。
在實際應用中晴埂,同樣的計算任務儒洛,GPU比CPU要快得多,當然CPU還能進一步優(yōu)化恼蓬。使用cuDNN也比不使用要快接近三倍小槐。實際應用GPU還有一個問題是訓練的模型一般存放在GPU,而用于訓練的數(shù)據(jù)存放在硬盤里控嗜,由于GPU運行快疆栏,而機械硬盤讀取慢,就會拖累整個模型的訊練速度。有多種解決方法:
- 如果訓練數(shù)據(jù)數(shù)量較小痹愚,可以把所有數(shù)據(jù)放到GPU的RAM中拯腮;
- 用固態(tài)硬盤代替機械硬盤;
- 使用多個CPU線程預讀取數(shù)據(jù)琼懊,放到緩存供GPU使用哼丈。
2 深度學習軟件
2.1 概述
現(xiàn)在有很多種深度學習框架,目前最流行的是TensorFlow车胡。第一代框架大多由學術(shù)界編寫的丧慈,比如Caffe就是伯克利大學開發(fā)的伊滋。第二代往往由工業(yè)界主導,比如Caffe2是由Facebook開發(fā)筒主。這里主要講解PyTorch和TensorFlow乌妙。回顧之前計算圖的概念,一個線性分類器可以用計算圖表示泽艘,網(wǎng)絡越復雜匹涮,計算圖也越復雜。之所以使用這些深度學習框架有三個原因:
- 構(gòu)建大的計算圖很容易雳攘,可以快速的開發(fā)和測試新想法来农;
- 這些框架都可以自動計算梯度只需寫出前向傳播的代碼涩咖;
- 可以在GPU上高效的運行檩互,已經(jīng)擴展了cuDNN等包以及處理好數(shù)據(jù)如何在CPU和GPU中流動。
這樣我們就不用從頭開始完成這些工作了饵较。
比如下面的一個計算圖:我們以前的做法是使用Numpy寫出前向傳播,然后計算梯度茄猫,代碼如下:
import numpy as np
np.random.seed(0) # 保證每次的隨機數(shù)一致
N, D = 3, 4
x = np.random.randn(N, D)
y = np.random.randn(N, D)
z = np.random.randn(N, D)
a = x * y
b = a + z
c = np.sum(b)
grad_c = 1.0
grad_b = grad_c * np.ones((N, D))
grad_a = grad_b.copy()
grad_z = grad_b.copy()
grad_x = grad_a * y
grad_y = grad_a * x
這種做法API干凈划纽,易于編寫代碼勇劣,但問題是沒辦法再GPU上運行,并且需要自己計算梯度。所以現(xiàn)在大部分深度學習框架的主要目標是自己寫好前向傳播代碼粘咖,類似Numpy,但能在GPU上運行且可以自動計算梯度浓镜。
TensorFlow版本路呜,前向傳播構(gòu)建計算圖,梯度可以自動計算:
import numpy as np
np.random.seed(0)
import tensorflow as tf
N, D = 3, 4
# 創(chuàng)建前向計算圖
x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)
z = tf.placeholder(tf.float32)
a = x * y
b = a + z
c = tf.reduce_sum(b)
# 計算梯度
grad_x, grad_y, grad_z = tf.gradients(c, [x, y, z])
with tf.Session() as sess:
values = {
x: np.random.randn(N, D),
y: np.random.randn(N, D),
z: np.random.randn(N, D),
}
out = sess.run([c, grad_x, grad_y, grad_z], feed_dict=values)
c_val, grad_x_val, grad_y_val, grad_z_val = out
print(c_val)
print(grad_x_val)
PyTorch版本,前向傳播與Numpy非常類似庆锦,但反向傳播可以自動計算梯度艇搀,不用再去實現(xiàn)焰雕。
import torch
device = 'cuda:0' # 在GPU上運行淀散,即構(gòu)建GPU版本的矩陣
# 前向傳播與Numpy類似
N, D = 3, 4
x = torch.randn(N, D, requires_grad=True, device=device)
# requires_grad要求自動計算梯度,默認為True
y = torch.randn(N, D, device=device)
z = torch.randn(N, D, device=device)
a = x * y
b = a + z
c = torch.sum(b)
c.backward() # 反向傳播可以自動計算梯度
print(x.grad)
print(y.grad)
print(z.grad)
可見這些框架都能自動計算梯度并且可以自動在GPU上運行郭膛。
2.2 TensoFlow
下面以一個兩層的神經(jīng)網(wǎng)絡為例则剃,非線性函數(shù)使用ReLU函數(shù)、損失函數(shù)使用L2范式己肮。當然這個例子什么也做不了谎僻,只當學習用。實現(xiàn)代碼如下:
神經(jīng)網(wǎng)絡
import numpy as np
import tensorflow as tf
N, D , H = 64, 1000, 100
# 創(chuàng)建前向計算圖
x = tf.placeholder(tf.float32, shape=(N, D))
y = tf.placeholder(tf.float32, shape=(N, D))
w1 = tf.placeholder(tf.float32, shape=(D, H))
w2 = tf.placeholder(tf.float32, shape=(H, D))
h = tf.maximum(tf.matmul(x, w1), 0) # 隱藏層使用折葉函數(shù)
y_pred = tf.matmul(h, w2)
diff = y_pred - y # 差值矩陣
loss = tf.reduce_mean(tf.reduce_sum(diff ** 2, axis=1)) # 損失函數(shù)使用L2范數(shù)
# 計算梯度
grad_w1, grad_w2 = tf.gradients(loss, [w1, w2])
# 多次運行計算圖
with tf.Session() as sess:
values = {
x: np.random.randn(N, D),
y: np.random.randn(N, D),
w1: np.random.randn(D, H),
w2: np.random.randn(H, D),
}
out = sess.run([loss, grad_w1, grad_w2], feed_dict=values)
loss_val, grad_w1_val, grad_w2_val = out
整個過程可以分成兩部分诱鞠,with
之前部分定義計算圖肋乍,with
部分多次運行計算圖墓造。這種模式在TensorFlow中很常見。
- 首先涮俄,我們創(chuàng)建了
x,y,w1,w2
四個tf.placeholder
對象孕锄,這四個變量作為“輸入槽”畸肆,下面再輸入數(shù)據(jù)。 - 然后使用這四個變量創(chuàng)建計算圖大咱,使用矩陣乘法
tf.matmul
和折葉函數(shù)tf.maximum
計算y_pred
碴巾,使用L2距離計算s損失。但是目前并沒有實際的計算低匙,因為只是構(gòu)建了計算圖并沒有輸入任何數(shù)據(jù)。 - 然后通過一行神奇的代碼計算損失值關(guān)于
w1
和w2
的梯度绞呈。此時仍然沒有實際的運算佃声,只是構(gòu)建計算圖,找到loss
關(guān)于w1
和w2
的路徑志鹃,在原先的計算圖上增加額外的關(guān)于梯度的計算曹铃。 - 完成計算圖后,創(chuàng)建一個會話
Session
來運行計算圖和輸入數(shù)據(jù)评甜。進入到Session
后蜕着,需要提供Numpy數(shù)組給上面創(chuàng)建的“輸入槽”。 - 最后兩行代碼才是真正的運行韧骗,執(zhí)行
sess.run
需要提供Numpy數(shù)組字典feed_dict
和需要輸出的計算值loss, grad_w1, grad_w2
,最后通過解包獲取Numpy數(shù)組政模。
上面的代碼只是運行了一次,我們需要迭代多次趁猴,并設置超參數(shù)儡司、參數(shù)更新方式等:
with tf.Session() as sess:
values = {
x: np.random.randn(N, D),
y: np.random.randn(N, D),
w1: np.random.randn(D, H),
w2: np.random.randn(H, D),
}
learning_rate = 1e-5
for t in range(50):
out = sess.run([loss, grad_w1, grad_w2], feed_dict=values)
loss_val, grad_w1_val, grad_w2_val = out
values[w1] -= learning_rate * grad_w1_val
values[w2] -= learning_rate * grad_w2_val
這種迭代方式有一個問題是每一步需要將Numpy和數(shù)組提供給GPU跷坝,GPU計算完成后再解包成Numpy數(shù)組,但由于CPU與GPU之間的傳輸瓶頸顿颅,非常不方便粱腻。解決方法是將w1
和w2
作為變量而不再是“輸入槽”,變量可以一直存在于計算圖上斩跌。由于現(xiàn)在w1
和w2
變成了變量绍些,所以就不能從外部輸入Numpy數(shù)組來初始化,需要由TensorFlow來初始化耀鸦,需要指明初始化方式柬批。此時仍然沒有具體的計算。
w1 = tf.Variable(tf.random_normal((D, H)))
w2 = tf.Variable(tf.random_normal((H, D)))
現(xiàn)在需要將參數(shù)更新操作也添加到計算圖中氮帐,使用賦值操作assign
更新w1
和w2
参咙,并保存在計算圖中(位于計算梯度后面):
learning_rate = 1e-5
new_w1 = w1.assign(w1 - learning_rate * grad_w1)
new_w2 = w2.assign(w2 - learning_rate * grad_w2)
現(xiàn)在運行這個網(wǎng)絡净宵,需要先運行一步參數(shù)的初始化tf.global_variables_initializer()
,然后運行多次代碼計算損失值:
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
values = {
x: np.random.randn(N, D),
y: np.random.randn(N, D),
}
for t in range(50):
loss_val, = sess.run([loss], feed_dict=values)
優(yōu)化器
上面的代碼有一個bug是訓練過程中損失值不會有變化番舆!原因是我們執(zhí)行的sess.run([loss], feed_dict=values)
語句只會計算loss
禾怠,TensorFlow非常高效污尉,與損失值無關(guān)的計算一律不會進行蛮放,所以參數(shù)就無法更新娩嚼。一個解決辦法是在執(zhí)行run
時加入計算兩個參數(shù)呵俏,這樣就會強制執(zhí)行參數(shù)更新麻车,但是又會產(chǎn)生CPU與GPU的通信問題。一個技巧是在計算圖中加入兩個參數(shù)的依賴,在執(zhí)行時需要計算這個依賴姆怪,這樣就會讓參數(shù)更新事镣。這個技巧是group
操作,執(zhí)行完參數(shù)賦值操作后,執(zhí)行updates = tf.group(new_w1, new_w2)
,這個操作會在計算圖上創(chuàng)建一個節(jié)點;然后執(zhí)行的代碼修改為loss_val, _ = sess.run([loss, updates], feed_dict=values)
,在實際運算時荠藤,updates
返回值為空布疼。這種方式仍然不夠方便,好在TensorFlow提供了更便捷的操作,使用自帶的優(yōu)化器颤芬。優(yōu)化器需要提供學習率參數(shù)聚蝶,然后進行參數(shù)更新胜嗓。有很多優(yōu)化器可供選擇牵啦,比如梯度下降彭羹、Adam等扶叉。
optimizer = tf.train.GradientDescentOptimizer(1e-5) # 使用優(yōu)化器
updates = optimizer.minimize(loss) # 更新方式是使loss下降,內(nèi)部其實使用了group
執(zhí)行的代碼也是:loss_val, _ = sess.run([loss, updates], feed_dict=values)
損失
計算損失的代碼也可以使用TensorFlow自帶的函數(shù):
loss = tf.losses.mean_squared_error(y_pred, y) # 損失函數(shù)使用L2范數(shù)
層
目前仍有一個很大的問題是x,y,w1,w2
的形狀需要我們自己去定義螃征,還要保證它們能正確連接在一起,此外還有偏差饵筑。如果使用卷積層睛低、批量歸一化等層后套蒂,這些定義會更加麻煩。TensorFlow可以解決這些麻煩:
N, D , H = 64, 1000, 100
x = tf.placeholder(tf.float32, shape=(N, D))
y = tf.placeholder(tf.float32, shape=(N, D))
init = tf.variance_scaling_initializer(2.0) # 權(quán)重初始化使用He初始化
h = tf.layers.dense(inputs=x, units=H, activation=tf.nn.relu, kernel_initializer=init)
# 隱藏層使用折葉函數(shù)
y_pred = tf.layers.dense(inputs=h, units=D, kernel_initializer=init)
loss = tf.losses.mean_squared_error(y_pred, y) # 損失函數(shù)使用L2范數(shù)
optimizer = tf.train.GradientDescentOptimizer(1e-5)
updates = optimizer.minimize(loss)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
values = {
x: np.random.randn(N, D),
y: np.random.randn(N, D),
}
for t in range(50):
loss_val, _ = sess.run([loss, updates], feed_dict=values)
上面的代碼浅碾,x,y
的初始化沒有變化排嫌,但是參數(shù)w1,w2
隱藏起來了,初始化使用He初始化倦淀。前向傳播的計算使用了全連接層tf.layers.dense
,該函數(shù)需要提供輸入數(shù)據(jù)inputs
真椿、該層的神經(jīng)元數(shù)目units
、激活函數(shù)activation
围苫、卷積核(權(quán)重)初始化方式kernel_initializer
等參數(shù)意鲸,可以自動設置權(quán)重和偏差。
更高層次的抽象——Keras
Keras是基于TensorFlow的更高層次的封裝,會讓整個過程變得簡單玄括,曾經(jīng)是第三方庫挨厚,現(xiàn)在已經(jīng)被內(nèi)置到了TensorFlow。使用Keras的部分代碼如下,其他與上文一致:
N, D , H = 64, 1000, 100
x = tf.placeholder(tf.float32, shape=(N, D))
y = tf.placeholder(tf.float32, shape=(N, D))
model = tf.keras.Sequential() # 使用一系列層的組合方式
# 添加一系列的層
model.add(tf.keras.layers.Dense(units=H, input_shape=(D,), activation=tf.nn.relu))
model.add(tf.keras.layers.Dense(D))
# 調(diào)用模型獲取結(jié)果
y_pred = model(x)
loss = tf.losses.mean_squared_error(y_pred, y)
這種模型已經(jīng)簡化了很多工作赐纱,但是還有更魔幻的操作(最終版本):
import numpy as np
import tensorflow as tf
N, D , H = 64, 1000, 100
# 創(chuàng)建模型,添加層
model = tf.keras.Sequential()
model.add(tf.keras.layers.Dense(units=H, input_shape=(D,), activation=tf.nn.relu))
model.add(tf.keras.layers.Dense(D))
# 配置模型:損失函數(shù)、參數(shù)更新方式
model.compile(optimizer=tf.keras.optimizers.SGD(lr=1e-5), loss=tf.keras.losses.mean_squared_error)
x = np.random.randn(N, D)
y = np.random.randn(N, D)
# 訓練
history = model.fit(x, y, epochs=50, batch_size=N)
代碼非常簡潔:
- 定義模型:
tf.keras.Sequential()
表明模型是一系列的層枚尼,然后添加兩個全連接層破镰,并設置激活函數(shù)、每層的神經(jīng)元數(shù)目等孵滞; - 配置模型:用
model.compile
方法配置模型的優(yōu)化器尔苦、損失函數(shù)等; - 輸入訓練數(shù)據(jù)儿礼;
- 訓練模型:使用
model.fit
,需要設置迭代周期次數(shù)卤妒、批量數(shù)等,可以直接用原始數(shù)據(jù)訓練模型。
其他知識
常見的拓展包
Keras (https://keras.io/)
TensorFlow內(nèi)置:
tf.keras (https://www.tensorflow.org/api_docs/python/tf/keras)
tf.layers (https://www.tensorflow.org/api_docs/python/tf/layers)
tf.estimator (https://www.tensorflow.org/api_docs/python/tf/estimator)
tf.contrib.estimator (https://www.tensorflow.org/api_docs/python/tf/contrib/estimator)
tf.contrib.layers (https://www.tensorflow.org/api_docs/python/tf/contrib/layers)
tf.contrib.slim (https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slim)
tf.contrib.learn (https://www.tensorflow.org/api_docs/python/tf/contrib/learn) (棄用)Sonnet (https://github.com/deepmind/sonnet) (by DeepMind)
第三方包:
TFLearn (http://tflearn.org/)
TensorLayer (http://tensorlayer.readthedocs.io/en/latest/) TensorFlow: High-Level
預訓練模型
TensorFlow已經(jīng)有一些預訓練好的模型可以直接拿來用扰肌,利用遷移學習剂习,微調(diào)參數(shù)。
- tf.keras: (https://www.tensorflow.org/api_docs/python/tf/keras/applications)
- TF-Slim: (https://github.com/tensorflow/models/tree/master/slim/nets)
Tensorboard
- 增加日志記錄損失值和狀態(tài)档悠;
-
繪制圖像
分布式操作
可以在多臺機器上運行,谷歌比較擅長琳水。
TPU(Tensor Processing Units)
TPU是專用的深度學習硬件肆糕,運行速度非常快在孝。Google Cloud TPU 算力為180 TFLOPs 诚啃,NVIDIA Tesla V100算力為125 TFLOPs。
Theano
TensorFlow的前身私沮,二者許多地方都很相似绍申。
2.3 PyTorch
基本概念
- Tensor:與Numpy數(shù)組很相似,只是可以在GPU上運行顾彰;
- Autograd:使用Tensors構(gòu)建計算圖并自動計算梯度的包极阅;
- Module:神經(jīng)網(wǎng)絡的層,可以存儲狀態(tài)和可學習的權(quán)重涨享。
下面的代碼使用的是v0.4版本筋搏。
Tensors
下面使用Tensors訓練一個兩層的神經(jīng)網(wǎng)絡,激活函數(shù)使用ReLU厕隧、損失使用L2損失奔脐。代碼如下:
import torch
# cpu版本
device = torch.device('cpu')
#device = torch.device('cuda:0') # 使用gpu
# 為數(shù)據(jù)和參數(shù)創(chuàng)建隨機的Tensors
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in, device=device)
y = torch.randn(N, D_out, device=device)
w1 = torch.randn(D_in, H, device=device)
w2 = torch.randn(H, D_out, device=device)
learning_rate = 1e-6
for t in range(500):
# 前向傳播俄周,計算預測值和損失
h = x.mm(w1)
h_relu = h.clamp(min=0)
y_pred = h_relu.mm(w2)
loss = (y_pred - y).pow(2).sum()
# 反向傳播手動計算梯度
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h_relu.t().mm(grad_y_pred)
grad_h_relu = grad_y_pred.mm(w2.t())
grad_h = grad_h_relu.clone()
grad_h[h < 0] = 0
grad_w1 = x.t().mm(grad_h)
# 梯度下降,參數(shù)更新
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
- 首先創(chuàng)建
x,y,w1,w2
的隨機tensor髓迎,與Numpy數(shù)組的形式一致 - 然后前向傳播計算損失值和預測值
- 然后手動計算梯度
- 最后更新參數(shù)
這一版的代碼很簡單峦朗,和Numpy版本的寫法很接近。但是需要手動計算梯度排龄。
Autograd
PyTorch可以自動計算梯度:
import torch
# 創(chuàng)建隨機tensors
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
w1 = torch.randn(D_in, H, requires_grad=True)
w2 = torch.randn(H, D_out, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
# 前向傳播
y_pred = x.mm(w1).clamp(min=0).mm(w2)
loss = (y_pred - y).pow(2).sum()
# 反向傳播
loss.backward()
# 參數(shù)更新
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
w1.grad.zero_()
w2.grad.zero_()
與第一版的主要區(qū)別如下:
- 創(chuàng)建
w1,w2
時要求requires_grad=True
波势,這樣會自動計算梯度,并創(chuàng)建計算圖橄维。x1,x2
不需要計算梯度尺铣。 - 前向傳播與之前的類似,但現(xiàn)在不用保存節(jié)點争舞,PyTorch可以幫助我們跟蹤計算圖凛忿。
- 使用
loss.backward()
自動計算要求的梯度。 - 按步對權(quán)重進行更新竞川,然后將梯度歸零店溢。
Torch.no_grad
的意思是“不要為這部分構(gòu)建計算圖”。以下劃線結(jié)尾的PyTorch方法是就地修改Tensor委乌,不返回新的Tensor床牧。
TensorFlow與PyTorch的區(qū)別是TensorFlow需要先顯式的構(gòu)造一個計算圖,然后重復運行福澡;PyTorch每次做前向傳播時都要構(gòu)建一個新的圖叠赦,使程序看起來更加簡潔。
PyTorch支持定義自己的自動計算梯度函數(shù)革砸,需要編寫 forward除秀,backward
函數(shù)。與作業(yè)中很相似算利〔岵龋可以直接用到計算圖上,但是實際上自己定義的時候并不多效拭。
NN
與Keras類似的高層次封裝暂吉,會使整個代碼變得簡單。
import torch
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
# 定義模型
model = torch.nn.Sequential(torch.nn.Linear(D_in, H),
torch.nn.ReLu(),
torch.nn.Linear(H, D_out))
learning_rate = 1e-2
for t in range(500):
# 前向傳播
y_pred = model(x)
loss = torch.nn.functional.mse_loss(y_pred, y)
# 計算梯度
loss.backward()
with torch.no_grad():
for param in model.parameters():
param -= learning_rate * param.grad
model.zero_grad()
- 定義模型是一系列的層組合缎患,在模型中定義了層對象比如全連接層慕的、折葉層等,里面包含可學習的權(quán)重挤渔;
- 前向傳播將數(shù)據(jù)給模型就可以直接計算預測值肮街,進而計算損失;
torch.nn.functional
含有很多有用的函數(shù)判导,比如損失函數(shù)嫉父; - 反向傳播會計算模型中所有權(quán)重的梯度沛硅;
- 最后每一步都更新模型的參數(shù)。
Optimizer
PyTorch同樣有自己的優(yōu)化器:
import torch
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
# 定義模型
model = torch.nn.Sequential(torch.nn.Linear(D_in, H),
torch.nn.ReLu(),
torch.nn.Linear(H, D_out))
# 定義優(yōu)化器
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
# 迭代
for t in range(500):
y_pred = model(x)
loss = torch.nn.functional.mse_loss(y_pred, y)
loss.backward()
# 更新參數(shù)
optimizer.step()
optimizer.zero_grad()
- 使用不同規(guī)則的優(yōu)化器绕辖,這里使用Adam;
- 計算完梯度后摇肌,使用優(yōu)化器更新參數(shù),置零梯度仪际。
定義新的模塊
PyTorch中一個模塊就是一個神經(jīng)網(wǎng)絡層围小,輸入和輸出都是tensors。模塊中可以包含權(quán)重和其他模塊弟头,可以使用Autograd定義自己的模塊吩抓。比如可以把上面代碼中的兩層神經(jīng)網(wǎng)絡改成一個模塊:
import torch
# 定義上文的整個模塊為單個模塊
class TwoLayerNet(torch.nn.Module):
# 初始化兩個子模塊涉茧,都是線性層
def __init__(self, D_in, H, D_out):
super(TwoLayerNet, self).__init__()
self.linear1 = torch.nn.Linear(D_in, H)
self.linear2 = torch.nn.Linear(H, D_out)
# 使用子模塊定義前向傳播赴恨,不需要定義反向傳播,autograd會自動處理
def forward(self, x):
h_relu = self.linear1(x).clamp(min=0)
y_pred = self.linear2(h_relu)
return y_pred
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
# 構(gòu)建模型與訓練和之前類似
model = TwoLayerNet(D_in, H, D_out)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
for t in range(500):
y_pred = model(x)
loss = torch.nn.functional.mse_loss(y_pred, y)
loss.backward()
optimizer.step()
optimizer.zero_grad()
這種混合自定義模塊非常常見伴栓,定義一個模塊子類伦连,然后作為作為整個模型的一部分添加 到模塊序列中。比如用定義一個下面這樣的模塊钳垮,輸入數(shù)據(jù)先經(jīng)過兩個并列的全連接層得到的結(jié)果相乘后經(jīng)過ReLU:
class ParallelBlock(torch.nn.Module):
def __init__(self, D_in, D_out):
super(ParallelBlock, self).__init__()
self.linear1 = torch.nn.Linear(D_in, D_out)
self.linear2 = torch.nn.Linear(D_in, D_out)
def forward(self, x):
h1 = self.linear1(x)
h2 = self.linear2(x)
return (h1 * h2).clamp(min=0)
然后在整個模型中應用:
model = torch.nn.Sequential(ParallelBlock(D_in, H),
ParallelBlock(H, H),
torch.nn.Linear(H, D_out))
使用ParallelBlock
的新模型計算圖如下:
DataLoader
DataLoader包裝數(shù)據(jù)集并提供獲取小批量數(shù)據(jù)惑淳,重新排列,多線程讀取等饺窿,當需要加載自定義數(shù)據(jù)時歧焦,只需編寫自己的數(shù)據(jù)集類:(最終版)
import torch
from torch.utils.data import TensorDataset, DataLoader
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
loader = DataLoader(TensorDataset(x, y), batch_size=8)
model = TwoLayerNet(D_in, H, D_out)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-2)
for epoch in range(20):
for x_batch, y_batch in loader:
y_pred = model(x_batch)
loss = torch.nn.functional.mse_loss(y_pred, y_batch)
loss.backward()
optimizer.step()
optimizer.zero_grad()
上面的代碼仍然是兩層神經(jīng)完網(wǎng)絡,使用了自定義的模塊肚医。這次使用了DataLoader來處理數(shù)據(jù)绢馍。最后更新的時候在小批量上更新,一個周期會迭代所有的小批量數(shù)據(jù)肠套。一般的PyTorch模型基本都長成這個樣子舰涌。
預訓練模型
使用預訓練模型非常簡單:https://github.com/pytorch/vision
Visdom
可視化的包,類似TensorBoard你稚,但是不能像TensorBoard一樣可視化計算圖瓷耙。Torch
PyTorch的前身,不能使用Python刁赖,沒有Autograd搁痛,但比較穩(wěn)定,不推薦使用宇弛。
2.4 靜態(tài)與動態(tài)圖(Static vs Dynamic Graphs )
TensorFlow使用的是靜態(tài)圖(Static Graphs):
- 構(gòu)建計算圖描述計算鸡典,包括找到反向傳播的路徑;
- 每次迭代執(zhí)行計算涯肩,都使用同一張計算圖轿钠。
與靜態(tài)圖相對應的是PyTorch使用的動態(tài)圖(Dynamic Graphs)巢钓,構(gòu)建計算圖與計算同時進行:
- 創(chuàng)建tensor對象;
- 每一次迭代構(gòu)建計算圖數(shù)據(jù)結(jié)構(gòu)疗垛、尋找參數(shù)梯度路徑症汹、執(zhí)行計算;
- 每一次迭代拋出計算圖贷腕,然后再重建背镇。之后重復2。
靜態(tài)圖的優(yōu)勢
-
使用靜態(tài)圖形泽裳,由于一張圖需要反復運行很多次瞒斩,這樣框架就有機會在計算圖上做優(yōu)化。比如下面的自己寫的計算圖可能經(jīng)過多次運行后優(yōu)化成右側(cè)涮总,提高運行效率胸囱。
- 靜態(tài)圖只需要構(gòu)建一次計算圖,所以一旦構(gòu)建好了即使源代碼使用Python寫的瀑梗,也可以部署在C++上烹笔,不用依賴源代碼;而動態(tài)圖每次迭代都要使用源代碼抛丽,構(gòu)件圖和運行是交織在一起的谤职。
動態(tài)圖的優(yōu)勢
- 動態(tài)圖的代碼比較簡潔,很像Python操作亿鲜。
- 在條件判斷邏輯中允蜈,由于PyTorch可以動態(tài)構(gòu)建圖,所以可以使用正常的Python流操作蒿柳;而TensorFlow只能一次性構(gòu)建一個計算圖饶套,所以需要考慮到所有情況,只能使用TensorFlow流操作其馏,這里使用的是和條件有關(guān)的
tf.cond
凤跑。 - 在循環(huán)結(jié)構(gòu)中,也是如此叛复。PyTorch只需按照Python的邏輯去寫仔引,每次會更新計算圖而不用管最終的序列有多長;而TensorFlow由于使用靜態(tài)圖必須把這個循環(huán)結(jié)構(gòu)顯示的作為節(jié)點添加到計算圖中褐奥,所以需要用到TensorFlow的循環(huán)流
tf.foldl
咖耘。并且大多數(shù)情況下,為了保證只構(gòu)建一次循環(huán)圖撬码,TensorFlow只能使用自己的控制流儿倒,比如循環(huán)流、條件流等,而不能使用Python語法夫否,所以用起來需要學習TensorFlow特有的控制命令彻犁。
動態(tài)圖的應用
- 循環(huán)網(wǎng)絡(Recurrent Networks):例如圖像描述,需要使用循環(huán)網(wǎng)絡在一個不同長度序列上運行凰慈,我們要生成的用于描述圖像的語句是一個序列汞幢,依賴于輸入數(shù)據(jù)的序列,即動態(tài)的取決于輸入句子的長短微谓。
- 遞歸網(wǎng)絡(Recursive Networks):用于自然語言處理森篷,遞歸訓練整個語法解析樹,所以不僅僅是層次結(jié)構(gòu)豺型,而是一種圖或樹結(jié)構(gòu)仲智,在每個不同的數(shù)據(jù)點都有不同的結(jié)構(gòu),使用TensorFlow很難實現(xiàn)姻氨。在PyTorch中可以使用Python控制流钓辆,很容易實現(xiàn)。
- Modular Networks:一種用于詢問圖片上的內(nèi)容的網(wǎng)絡哼绑,問題不一樣生成的動態(tài)圖也就不一樣岩馍。
TensorFlow與PyTorch的相互靠攏
TensorFlow與PyTorch的界限越來越模糊碉咆,PyTorch正在添加靜態(tài)功能抖韩,而TensorFlow正在添加動態(tài)功能。
- TensorFlow Fold 可以把靜態(tài)圖的代碼自動轉(zhuǎn)化成靜態(tài)圖疫铜;
-
TensorFlow 1.7增加了Eager Execution茂浮,允許使用動態(tài)圖;
- 在程序開始時使用
tf.enable_eager_execution
模式:它是一個全局開關(guān)壳咕; -
tf.random_normal
會產(chǎn)生具體的值席揽,無需placeholders / sessions,如果想要為它們計算梯度谓厘,要用tfe.Variable
進行包裝幌羞; - 在
GradientTape
下操作將構(gòu)建一個動態(tài)圖,類似于PyTorch竟稳; - 使用
tape
計算梯度属桦,類似PyTorch中的backward
。并且可以直接打印出來他爸。
- 靜態(tài)的PyTorch有Caffe2聂宾、ONNX Support
2.5 PyTorch與TensorFlow的選擇
PyTorch有干凈的API、動態(tài)圖使開發(fā)和調(diào)試變得非常容易诊笤。 可以在PyTorch中構(gòu)建模型系谐,然后使用ONNX導出到Caffe2用于生產(chǎn)和移動設備中。
對于大多數(shù)項目來說讨跟,TensorFlow是一個安全的選擇纪他。 雖然不夠完美鄙煤,但擁有龐大的社區(qū),已經(jīng)被廣泛的使用茶袒。 它可以使用相同的框架進行研究和生產(chǎn)馆类,也可以用于移動設備〉可能需要使用高級框架(Keras, Sonnet)乾巧。 如果想在TPU上運行,則只能選擇TensorFlow预愤。
總結(jié)
- 深度學習硬件最好使用GPU沟于,然后需要解決CPU與GPU的通信問題。TPU是專門用于深度學習的硬件植康,速度非晨跆快。
- PyTorch與TensorFlow都是非常好的深度學習框架销睁,都有可以在GPU上直接運行的數(shù)組供璧,都可以自動計算梯度,都有很多已經(jīng)寫好的函數(shù)冻记、層等可以直接使用睡毒。前者使用動態(tài)圖,后者使用靜態(tài)圖冗栗,不過二者都在向?qū)Ψ桨l(fā)展演顾。取舍取決于項目。