• 作者:韩信子@ShowMeAI
  • 教程地址:http://www.showmeai.tech/tutorials/37
  • 本文地址:http://www.showmeai.tech/article-detail/267
  • 申明:版权所有,转载请分割平台与作者并注明出处
  • 珍藏ShowMeAI查看更多精彩内容

本系列为 斯坦福CS231n 《深度学习与计算机视觉(Deep Learning for Computer Vision)》的全套学习笔记,对应的课程视频能够在 这里 查看。更多材料获取形式见文末。


引言

大家在前序文章中学习了很多对于神经网络的原理常识和实战技巧,在本篇内容中ShowMeAI给大家开展介绍深度学习硬件常识,以及目前支流的深度学习框架TensorFlow和pytorch相干常识,借助于工具大家能够理论搭建与训练神经网络。

本篇重点

  • 深度学习硬件

    • CPU、GPU、TPU
  • 深度学习框架

    • PyTorch / TensorFlow
  • 动态与动静计算图

1.深度学习硬件

GPU(Graphics Processing Unit)是图形处理单元(又称显卡),在物理尺寸上就比 CPU(Central Processing Unit)大得多,有本人的冷却系统。最后用于渲染计算机图形,尤其是游戏。在深度学习上抉择 NVIDIA(英伟达)的显卡,如果应用AMD的显卡会遇到很多问题。TPU(Tensor Processing Units)是专用的深度学习硬件。

1.1 CPU / GPU / TPU


  • CPU个别有多个外围,每个外围速度都很快都能够独立工作,可同时进行多个过程,内存与零碎共享,实现序列工作时很有用。图上CPU的运行速度是每秒约 540 GFLOPs 浮点数运算,应用 32 位浮点数(注:一个 GFLOPS(gigaFLOPS)等于每秒十亿(\(=10^9\))次的浮点运算)。
  • GPU有上千个外围数,但每个外围运行速度很慢,也不能独立工作,适宜大量的并行实现相似的工作。GPU个别自带内存,也有本人的缓存零碎。图上GPU的运行速度是CPU的20多倍。
  • TPU是专门的深度学习硬件,运行速度十分快。TITANV 在技术上并不是一个「TPU」,因为这是一个谷歌术语,但两者都有专门用于深度学习的硬件。运行速度十分快。

若是将这些运行速度除以对应的价格,可失去下图:

1.2 GPU的劣势与利用

GPU 在大矩阵的乘法运算中有很显著的劣势。


因为后果中的每一个元素都是相乘的两个矩阵的每一行和每一列的点积,所以并行的同时进行这些点积运算速度会十分快。卷积神经网络也相似,卷积核和图片的每个区域进行点积也是并行运算。

CPU 尽管也有多个外围,然而在大矩阵运算时只能串行运算,速度很慢。

能够写出在 GPU 上间接运行的代码,办法是应用NVIDIA自带的形象代码 CUDA ,能够写出相似 C 的代码,并能够在 GPU 间接运行。

然而间接写 CUDA 代码是一件十分艰难的事,好在能够间接应用 NVIDIA 曾经高度优化并且开源的API,比方 cuBLAS 蕴含很多矩阵运算, cuDNN 蕴含 CNN 前向流传、反向流传、批量归一化等操作;还有一种语言是 OpenCL,能够在 CPU、AMD 上通用,然而没人做优化,速度很慢;HIP能够将CUDA 代码主动转换成能够在 AMD 上运行的语言。当前可能会有跨平台的规范,然而当初来看 CUDA 是最好的抉择。

在理论利用中,同样的计算工作,GPU 比 CPU 要快得多,当然 CPU 还能进一步优化。应用 cuDNN 也比不应用要快靠近三倍。



理论利用 GPU 还有一个问题是训练的模型个别寄存在 GPU,而用于训练的数据寄存在硬盘里,因为 GPU 运行快,而机械硬盘读取慢,就会连累整个模型的训练速度。有多种解决办法:

  • 如果训练数据数量较小,能够把所有数据放到 GPU 的 RAM 中;
  • 用固态硬盘代替机械硬盘;
  • 应用多个 CPU 线程预读取数据,放到缓存供 GPU 应用。

2.深度学习软件

2.1 DL软件概述

当初有很多种深度学习框架,目前最风行的是 TensorFlow。

第一代框架大多由学术界编写的,比方 Caffe 就是伯克利大学开发的。

第二代往往由工业界主导,比方 Caffe2 是由 Facebook 开发。这里次要解说 PyTorch 和 TensorFlow。


回顾之前计算图的概念,一个线性分类器能够用计算图示意,网络越简单,计算图也越简单。之所以应用这些深度学习框架有三个起因:

  • 构建大的计算图很容易,能够疾速的开发和测试新想法;
  • 这些框架都能够主动计算梯度只需写出前向流传的代码;
  • 能够在 GPU 上高效的运行,曾经扩大了 cuDNN 等包以及解决好数据如何在 CPU 和 GPU 中流动。

这样咱们就不必从头开始实现这些工作了。

比方上面的一个计算图:


咱们以前的做法是应用 Numpy 写出前向流传,而后计算梯度,代码如下:

import numpy as npnp.random.seed(0)  # 保障每次的随机数统一N, D = 3, 4x = np.random.randn(N, D)y = np.random.randn(N, D)z = np.random.randn(N, D)a = x * yb = a + zc = np.sum(b)grad_c = 1.0grad_b = grad_c * np.ones((N, D))grad_a = grad_b.copy()grad_z = grad_b.copy()grad_x = grad_a * ygrad_y = grad_a * x

这种做法 API 洁净,易于编写代码,但问题是没方法在 GPU 上运行,并且须要本人计算梯度。所以当初大部分深度学习框架的次要指标是本人写好前向流传代码,相似 Numpy,但能在 GPU 上运行且能够主动计算梯度。

TensorFlow 版本,前向流传构建计算图,梯度能够主动计算:

import numpy as npnp.random.seed(0)import tensorflow as tfN, D = 3, 4# 创立前向计算图x = tf.placeholder(tf.float32)y = tf.placeholder(tf.float32)z = tf.placeholder(tf.float32)a = x * yb = a + zc = 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十分相似,但反向流传能够主动计算梯度,不必再去实现。

import torchdevice = 'cuda:0'  # 在GPU上运行,即构建GPU版本的矩阵# 前向流传与Numpy相似N, D = 3, 4x = torch.randn(N, D, requires_grad=True, device=device)# requires_grad要求主动计算梯度,默认为Truey = torch.randn(N, D, device=device)z = torch.randn(N, D, device=device)a = x * yb = a + zc = torch.sum(b)c.backward()  # 反向流传能够主动计算梯度print(x.grad)print(y.grad)print(z.grad)

可见这些框架都能主动计算梯度并且能够主动在 GPU 上运行。

2.2 TensoFlow

对于TensorFlow的用法也能够浏览ShowMeAI的制作的 TensorFlow 速查表,对应文章AI 建模工具速查 | TensorFlow使用指南AI建模工具速查 | Keras使用指南

上面以一个两层的神经网络为例,非线性函数应用 ReLU 函数、损失函数应用 L2 范式(当然仅仅是一个学习示例)。


实现代码如下:

1) 神经网络

import numpy as npimport tensorflow as tfN, D , H = 64, 1000, 100# 创立前向计算图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)  # 暗藏层应用折叶函数y_pred = tf.matmul(h, w2)diff = y_pred - y  # 差值矩阵loss = tf.reduce_mean(tf.reduce_sum(diff ** 2, axis=1))  # 损失函数应用L2范数# 计算梯度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 中很常见。

  • 首先,咱们创立了x,y,w1,w2四个 tf.placeholder 对象,这四个变量作为「输出槽」,上面再输出数据。
  • 而后应用这四个变量创立计算图,应用矩阵乘法 tf.matmul 和折叶函数 tf.maximum 计算 y_pred ,应用 L2 间隔计算 s 损失。然而目前并没有理论的计算,因为只是构建了计算图并没有输出任何数据。
  • 而后通过一行神奇的代码计算损失值对于 w1w2 的梯度。此时依然没有理论的运算,只是构建计算图,找到 loss 对于 w1w2 的门路,在原先的计算图上减少额定的对于梯度的计算。
  • 实现计算图后,创立一个会话 Session 来运行计算图和输出数据。进入到 Session 后,须要提供 Numpy 数组给下面创立的「输出槽」。
  • 最初两行代码才是真正的运行,执行 sess.run 须要提供 Numpy 数组字典feed_dict 和须要输入的计算值 loss , grad_w1 , grad_w2` ,最初通过解包获取 Numpy 数组。


下面的代码只是运行了一次,咱们须要迭代屡次,并设置超参数、参数更新形式等:

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和数组提供给GPU,GPU计算实现后再解包成Numpy数组,但因为CPU与GPU之间的传输瓶颈,十分不不便。

解决办法是将 w1w2 作为变量而不再是「输出槽」,变量能够始终存在于计算图上。

因为当初 w1w2 变成了变量,所以就不能从内部输出 Numpy 数组来初始化,须要由 TensorFlow 来初始化,须要指明初始化形式。此时依然没有具体的计算。

w1 = tf.Variable(tf.random_normal((D, H)))w2 = tf.Variable(tf.random_normal((H, D)))


当初须要将参数更新操作也增加到计算图中,应用赋值操作 assign 更新 w1w2,并保留在计算图中(位于计算梯度前面):

learning_rate = 1e-5new_w1 = w1.assign(w1 - learning_rate * grad_w1)new_w2 = w2.assign(w2 - learning_rate * grad_w2)


当初运行这个网络,须要先运行一步参数的初始化 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)

2) 优化器

下面的代码,理论训练过程中损失值不会变。

起因是咱们执行的 sess.run([loss], feed_dict=values) 语句只会计算 loss,TensorFlow 十分高效,与损失值无关的计算一律不会进行,所以参数就无奈更新。

一个解决办法是在执行 run 时退出计算两个参数,这样就会强制执行参数更新,然而又会产生CPU 与 GPU 的通信问题。

一个技巧是在计算图中退出两个参数的依赖,在执行时须要计算这个依赖,这样就会让参数更新。这个技巧是 group 操作,执行完参数赋值操作后,执行 updates = tf.group(new_w1, new_w2),这个操作会在计算图上创立一个节点;而后执行的代码批改为 loss_val, _ = sess.run([loss, updates], feed_dict=values),在理论运算时,updates 返回值为空。

这种形式依然不够不便,好在 TensorFlow 提供了更便捷的操作,应用自带的优化器。优化器须要提供学习率参数,而后进行参数更新。有很多优化器可供选择,比方梯度降落、Adam等。

optimizer = tf.train.GradientDescentOptimizer(1e-5)  # 应用优化器updates = optimizer.minimize(loss)  # 更新形式是使loss降落,外部其实应用了group

执行的代码也是:loss_val, _ = sess.run([loss, updates], feed_dict=values)

3) 损失

计算损失的代码也能够应用 TensorFlow 自带的函数:

loss = tf.losses.mean_squared_error(y_pred, y)  # 损失函数应用L2范数

4) 层

目前仍有一个很大的问题是 x,y,w1,w2 的形态须要咱们本人去定义,还要保障它们能正确连贯在一起,此外还有偏差。如果应用卷积层、批量归一化等层后,这些定义会更加麻烦。

TensorFlow能够解决这些麻烦:

N, D , H = 64, 1000, 100x = tf.placeholder(tf.float32, shape=(N, D))y = tf.placeholder(tf.float32, shape=(N, D))init = tf.variance_scaling_initializer(2.0)  # 权重初始化应用He初始化h = tf.layers.dense(inputs=x, units=H, activation=tf.nn.relu, kernel_initializer=init)# 暗藏层应用折叶函数y_pred = tf.layers.dense(inputs=h, units=D, kernel_initializer=init)loss = tf.losses.mean_squared_error(y_pred, y)  # 损失函数应用L2范数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 的初始化没有变动,然而参数 w1,w2 暗藏起来了,初始化应用 He初始化。

前向流传的计算应用了全连贯层 tf.layers.dense,该函数须要提供输出数据 inputs、该层的神经元数目 units、激活函数 activation、卷积核(权重)初始化形式 kernel_initializer 等参数,能够主动设置权重和偏差。

5) High level API:tensorflow.keras

Keras 是基于 TensorFlow 的更高层次的封装,会让整个过程变得简略,已经是第三方库,当初曾经被内置到了 TensorFlow。

应用 Keras 的局部代码如下,其余与上文统一:

N, D , H = 64, 1000, 100x = 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))# 调用模型获取后果y_pred = model(x)loss = tf.losses.mean_squared_error(y_pred, y)


这种模型曾经简化了很多工作,最终版本代码如下:

import numpy as npimport tensorflow as tfN, D , H = 64, 1000, 100# 创立模型,增加层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))# 配置模型:损失函数、参数更新形式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() 表明模型是一系列的层,而后增加两个全连贯层,并设置激活函数、每层的神经元数目等;
  • 配置模型:用 model.compile 办法配置模型的优化器、损失函数等;
  • 基于数据训练模型:应用 model.fit,须要设置迭代周期次数、批量数等,能够间接用原始数据训练模型。

6) 其余常识

① 常见的拓展包

  • Keras (https://keras.io/)
  • TensorFlow内置:

    • 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...)
    • 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.readthedoc...) TensorFlow: High-Level

② 预训练模型

TensorFlow曾经有一些预训练好的模型能够间接拿来用,利用迁徙学习,微调参数。

  • tf.keras: (https://www.tensorflow.org/api_docs/python/tf/keras/applications)
  • TF-Slim: (https://github.com/tensorflow/models/tree/master/slim/nets)

③ Tensorboard

  • 减少日志记录损失值和状态
  • 绘制图像

④ 分布式操作

能够在多台机器上运行,谷歌比拟善于。

⑤ TPU(Tensor Processing Units)

TPU是专用的深度学习硬件,运行速度十分快。Google Cloud TPU 算力为180 TFLOPs ,NVIDIA Tesla V100算力为125 TFLOPs。

⑥Theano

TensorFlow的前身,二者许多中央都很类似。

2.3 PyTorch

对于PyTorch的用法也能够浏览ShowMeAI的制作的PyTorch速查表,对应文章AI 建模工具速查 | Pytorch使用指南

1) 基本概念

  • Tensor:与Numpy数组很类似,只是能够在GPU上运行;
  • Autograd:应用Tensors构建计算图并主动计算梯度的包;
  • Module:神经网络的层,能够存储状态和可学习的权重。

上面的代码应用的是v0.4版本。

2) Tensors

上面应用Tensors训练一个两层的神经网络,激活函数应用ReLU、损失应用L2损失。


代码如下:

import torch# cpu版本device = torch.device('cpu')#device = torch.device('cuda:0')  # 应用gpu# 为数据和参数创立随机的TensorsN, D_in, H, D_out = 64, 1000, 100, 10x = 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-6for 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)    # 梯度降落,参数更新    w1 -= learning_rate * grad_w1    w2 -= learning_rate * grad_w2
  • 首先创立 x,y,w1,w2的随机 tensor,与 Numpy 数组的模式统一
  • 而后前向流传计算损失值和预测值
  • 而后手动计算梯度
  • 最初更新参数

上述代码很简略,和 Numpy 版本的写法很靠近。然而须要手动计算梯度。

3) Autograd主动梯度计算

PyTorch 能够主动计算梯度:

import torch# 创立随机tensorsN, D_in, H, D_out = 64, 1000, 100, 10x = 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-6for t in range(500):    # 前向流传    y_pred = x.mm(w1).clamp(min=0).mm(w2)    loss = (y_pred - y).pow(2).sum()    # 反向流传    loss.backward()    # 参数更新    with torch.no_grad():        w1 -= learning_rate * w1.grad        w2 -= learning_rate * w2.grad        w1.grad.zero_()        w2.grad.zero_()

与上一版代码的次要区别是:

  • 创立 w1,w2 时要求 requires_grad=True,这样会主动计算梯度,并创立计算图。x1,x2 不须要计算梯度。
  • 前向流传与之前的相似,但当初不必保留节点,PyTorch 能够帮忙咱们跟踪计算图。
  • 应用 loss.backward() 主动计算要求的梯度。
  • 按步对权重进行更新,而后将梯度归零。 Torch.no_grad 的意思是「不要为这部分构建计算图」。以下划线结尾的 PyTorch 办法是就地批改 Tensor,不返回新的 Tensor。

TensorFlow 与 PyTorch 的区别是 TensorFlow 须要先显式的结构一个计算图,而后反复运行;PyTorch 每次做前向流传时都要构建一个新的图,使程序看起来更加简洁。

PyTorch 反对定义本人的主动计算梯度函数,须要编写 forwardbackward 函数。与作业中很类似。能够间接用到计算图上,然而实际上本人定义的时候并不多。

4) NN

与 Keras 相似的高层次封装,会使整个代码变得简略。

import torchN, D_in, H, D_out = 64, 1000, 100, 10x = 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-2for 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()
  • 定义模型是一系列的层组合,在模型中定义了层对象比方全连贯层、折叶层等,外面蕴含可学习的权重;
  • 前向流传将数据给模型就能够间接计算预测值,进而计算损失;torch.nn.functional 含有很多有用的函数,比方损失函数;
  • 反向流传会计算模型中所有权重的梯度;
  • 最初每一步都更新模型的参数。

5) Optimizer

PyTorch 同样有本人的优化器:

import torchN, D_in, H, D_out = 64, 1000, 100, 10x = 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-4optimizer = 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()    # 更新参数    optimizer.step()    optimizer.zero_grad()
  • 应用不同规定的优化器,这里应用Adam;
  • 计算完梯度后,应用优化器更新参数,再置零梯度。

6) 定义新的模块

PyTorch 中一个模块就是一个神经网络层,输出和输入都是 tensors。模块中能够蕴含权重和其余模块,能够应用 Autograd 定义本人的模块。

比方能够把下面代码中的两层神经网络改成一个模块:

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_predN, D_in, H, D_out = 64, 1000, 100, 10x = torch.randn(N, D_in)y = torch.randn(N, D_out)# 构建模型与训练和之前相似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()

这种混合自定义模块十分常见,定义一个模块子类,而后作为作为整个模型的一部分增加到模块序列中。

比方用定义一个上面这样的模块,输出数据先通过两个并列的全连贯层失去的后果相乘后通过 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 的新模型计算图如下:

7) DataLoader

DataLoader 包装数据集并提供获取小批量数据,重新排列,多线程读取等,当须要加载自定义数据时,只需编写本人的数据集类:

import torchfrom torch.utils.data import TensorDataset, DataLoaderN, D_in, H, D_out = 64, 1000, 100, 10x = 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()

下面的代码依然是两层神经完网络,应用了自定义的模块。这次应用了 DataLoader 来解决数据。最初更新的时候在小批量上更新,一个周期会迭代所有的小批量数据。个别的 PyTorch 模型根本都长成这个样子。

8) 预训练模型

应用预训练模型非常简单:https://github.com/pytorch/vision

import torchimport torchvisionalexnet = torchvision.models.alexnet(pretrained=True)vgg16 = torchvision.models.vggl6(pretrained=-True)resnet101 = torchvision.models.resnet101(pretrained=True)

9) Visdom

可视化的包,相似 TensorBoard,然而不能像 TensorBoard 一样可视化计算图。

10) Torch

PyTorch 的前身,不能应用 Python,没有 Autograd,但比较稳定,不举荐应用。

3.动态与动态图(Static vs Dynamic Graphs )

TensorFlow应用的是动态图(Static Graphs):

  • 构建计算图形容计算,包含找到反向流传的门路;
  • 每次迭代执行计算,都应用同一张计算图。

与动态图绝对应的是PyTorch应用的动态图(Dynamic Graphs),构建计算图与计算同时进行:

  • 创立tensor对象;
  • 每一次迭代构建计算图数据结构、寻找参数梯度门路、执行计算;
  • 每一次迭代抛出计算图,而后再重建。之后反复上一步。

3.1 动态图的劣势

应用动态图形,因为一张图须要重复运行很屡次,这样框架就有机会在计算图上做优化。

  • 比方上面的本人写的计算图可能通过屡次运行后优化成右侧,进步运行效率。


动态图只须要构建一次计算图,所以一旦构建好了即便源代码应用 Python 写的,也能够部署在C++上,不必依赖源代码;而动态图每次迭代都要应用源代码,构件图和运行是交错在一起的。

3.2 动态图的劣势

动态图的代码比拟简洁,很像 Python 操作。

在条件判断逻辑中,因为 PyTorch 能够动静构建图,所以能够应用失常的 Python 流操作;而TensorFlow 只能一次性构建一个计算图,所以须要思考到所有状况,只能应用 TensorFlow 流操作,这里应用的是和条件无关的。


在循环构造中,也是如此。

  • PyTorch 只需依照 Python 的逻辑去写,每次会更新计算图而不必管最终的序列有多长;
  • TensorFlow 因为应用动态图必须把这个循环构造显示的作为节点增加到计算图中,所以须要用到 TensorFlow 的循环流 tf.foldl。并且大多数状况下,为了保障只构建一次循环图, TensorFlow 只能应用本人的控制流,比方循环流、条件流等,而不能应用 Python 语法,所以用起来须要学习 TensorFlow 特有的管制命令。

3.3 动态图的利用

1) 循环网络(Recurrent Networks)

例如图像形容,须要应用循环网络在一个不同长度序列上运行,咱们要生成的用于形容图像的语句是一个序列,依赖于输出数据的序列,即动静的取决于输出句子的长短。

2) 递归网络(Recursive Networks)

用于自然语言解决,递归训练整个语法解析树,所以不仅仅是层次结构,而是一种图或树结构,在每个不同的数据点都有不同的构造,应用TensorFlow很难实现。在 PyTorch 中能够应用 Python 控制流,很容易实现。

3) Modular Networks

一种用于询问图片上的内容的网络,问题不一样生成的动态图也就不一样。

3.4 TensorFlow与PyTorch的互相聚拢

TensorFlow 与 PyTorch 的界线越来越含糊,PyTorch 正在增加动态性能,而 TensorFlow 正在增加动静性能。

  • TensorFlow Fold 能够把动态图的代码主动转化成动态图
  • TensorFlow 1.7减少了Eager Execution,容许应用动态图
import tensorflow as tfimport tensorflow.contrib.eager as tfetf.enable eager _execution()N, D = 3, 4x = tfe.Variable(tf.random_normal((N, D)))y = tfe.Variable(tf.random_normal((N, D)))z = tfe.Variable(tf.random_normal((N, D)))with tfe.GradientTape() as tape:    a=x * 2    b=a + z    c = tf.reduce_sum(b)grad_x, grad_y, grad_z = tape.gradient(c, [x, y, 2])print(grad_x)
  • 在程序开始时应用 tf.enable_eager_execution 模式:它是一个全局开关
  • tf.random_normal 会产生具体的值,无需 placeholders / sessions,如果想要为它们计算梯度,要用tfe.Variable进行包装
  • GradientTape 下操作将构建一个动态图,相似于 PyTorch
  • 应用tape 计算梯度,相似 PyTorch 中的 backward。并且能够间接打印进去
  • 动态的 PyTorch 有 [Caffe2](https://caffe2.ai/)、[ONNX Support](https://caffe2.ai/)

4.拓展学习

能够点击 B站 查看视频的【双语字幕】版本

  • 【课程学习指南】斯坦福CS231n | 深度学习与计算机视觉
  • 【字幕+材料下载】斯坦福CS231n | 深度学习与计算机视觉 (2017·全16讲)
  • 【CS231n进阶课】密歇根EECS498 | 深度学习与计算机视觉
  • 【深度学习教程】吴恩达专项课程 · 全套笔记解读
  • 【Stanford官网】CS231n: Deep Learning for Computer Vision

5.要点总结

  • 深度学习硬件最好应用 GPU,而后须要解决 CPU 与 GPU 的通信问题。TPU 是专门用于深度学习的硬件,速度十分快。
  • PyTorch 与 TensorFlow 都是十分好的深度学习框架,都有能够在 GPU 上间接运行的数组,都能够主动计算梯度,都有很多曾经写好的函数、层等能够间接应用。前者应用动态图,后者应用动态图,不过二者都在向对方倒退。取舍取决于我的项目。

斯坦福 CS231n 全套解读

  • 深度学习与CV教程(1) | CV引言与根底
  • 深度学习与CV教程(2) | 图像分类与机器学习根底
  • 深度学习与CV教程(3) | 损失函数与最优化
  • 深度学习与CV教程(4) | 神经网络与反向流传
  • 深度学习与CV教程(5) | 卷积神经网络
  • 深度学习与CV教程(6) | 神经网络训练技巧 (上)
  • 深度学习与CV教程(7) | 神经网络训练技巧 (下)
  • 深度学习与CV教程(8) | 常见深度学习框架介绍
  • 深度学习与CV教程(9) | 典型CNN架构 (Alexnet, VGG, Googlenet, Restnet等)
  • 深度学习与CV教程(10) | 轻量化CNN架构 (SqueezeNet, ShuffleNet, MobileNet等)
  • 深度学习与CV教程(11) | 循环神经网络及视觉利用
  • 深度学习与CV教程(12) | 指标检测 (两阶段, R-CNN系列)
  • 深度学习与CV教程(13) | 指标检测 (SSD, YOLO系列)
  • 深度学习与CV教程(14) | 图像宰割 (FCN, SegNet, U-Net, PSPNet, DeepLab, RefineNet)
  • 深度学习与CV教程(15) | 视觉模型可视化与可解释性
  • 深度学习与CV教程(16) | 生成模型 (PixelRNN, PixelCNN, VAE, GAN)
  • 深度学习与CV教程(17) | 深度强化学习 (马尔可夫决策过程, Q-Learning, DQN)
  • 深度学习与CV教程(18) | 深度强化学习 (梯度策略, Actor-Critic, DDPG, A3C)

ShowMeAI 系列教程举荐

  • 大厂技术实现:举荐与广告计算解决方案
  • 大厂技术实现:计算机视觉解决方案
  • 大厂技术实现:自然语言解决行业解决方案
  • 图解Python编程:从入门到精通系列教程
  • 图解数据分析:从入门到精通系列教程
  • 图解AI数学根底:从入门到精通系列教程
  • 图解大数据技术:从入门到精通系列教程
  • 图解机器学习算法:从入门到精通系列教程
  • 机器学习实战:手把手教你玩转机器学习系列
  • 深度学习教程:吴恩达专项课程 · 全套笔记解读
  • 自然语言解决教程:斯坦福CS224n课程 · 课程带学与全套笔记解读
  • 深度学习与计算机视觉教程:斯坦福CS231n · 全套笔记解读