资讯详情

深度学习与计算机视觉教程(8) | 常见深度学习框架介绍(CV通关指南·完结)

ShowMeAI研究中心

  • 作者:韩信子@ShowMeAI
  • 教程地址:http://www.showmeai.tech/tutorials/37
  • 本文地址:http://www.showmeai.tech/article-detail/267
  • 声明:所有版权,请联系平台和作者,注明来源

本系列为 计算机视觉深度学习(Deep Learning for Computer Vision)》全套学习笔记,相应的课程视频可以在 查看。获取更多信息的方法见文末。


引言

在前一篇文章中,我们学到了很多关于神经网络的原理知识和实践技能ShowMeAI介绍深度学习硬件知识和主流深度学习框架TensorFlow和pytorch借助工具,我们可以实际构建和训练神经网络。

本篇重点

    • CPU、GPU、TPU
    • PyTorch / TensorFlow
  • 静态和动态计算图

1.深度学习硬件

(Graphics Processing Unit)它是图形处理单元(也称为显卡),在物理尺寸上比较 (Central Processing Unit)大得多,有自己的冷却系统。最初用于渲染计算机图形,尤其是游戏。选择深度学习 NVIDIA如果使用(英伟达)显卡,AMD显卡会遇到很多问题。(Tensor Processing Units)是一种特殊的深度学习硬件。

1.1 CPU / GPU / TPU

  • 一般有多个核心,每个核心都可以快速独立工作,可以同时进行多个过程,内存和系统共享,完成序列任务非常有用。CPU运行速度约为每秒 540 GFLOPs 浮点数运算,使用 32 位浮点数(注:一个 GFLOPS(gigaFLOPS)等于每秒十亿( = 1 0 9 =10^9 =109)次浮点运算)。
  • 有成千上万的核心数量,但每个核心运行缓慢,不能独立工作,适合大量并行完成类似工作。GPU一般来说,它有自己的内存和缓存系统。GPU运行速度是CPU的20多倍。
  • 它是一种特殊的深度学习硬件,运行速度非常快。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 np np.random.seed(0)  # 确保每次随机数一致  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 上运行,并且需要自己计算梯度。所以现在大部分深度学习框架的主要目标是自己写好前向传播代码,类似 Numpy,但能在 GPU 上运行且可以自动计算梯度。

TensorFlow 版本,前向传播构建计算图,梯度可以自动计算:

import numpy as np
np.random.seed(0)
import tensorflow as tf

N, D = 3, 4

# 创建前向计算图
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非常类似,但反向传播可以自动计算梯度,不用再去实现。

import torch

device = 'cuda:0'  # 在GPU上运行,即构建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

关于TensorFlow的用法也可以阅读ShowMeAI的制作的 TensorFlow 速查表,对应文章

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

实现代码如下:

1) 神经网络

import numpy as np
import tensorflow as tf

N, 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 距离计算 。但是目前并没有实际的计算,因为只是构建了计算图并没有输入任何数据。
  • 然后通过一行神奇的代码计算损失值关于 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-5
new_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, 100
x = 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, 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))
# 调用模型获取结果
y_pred = model(x)
loss = tf.losses.mean_squared_error(y_pred, y)

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

import numpy as np
import tensorflow as tf

N, 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/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已经有一些预训练好的模型可以直接拿来用,利用迁移学习,微调参数。

  • 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速查表,对应文章

1) 基本概念

  • :与Numpy数组很相似,只是可以在GPU上运行;
  • :使用Tensors构建计算图并自动计算梯度的包;
  • :神经网络的层,可以存储状态和可学习的权重。

下面的代码使用的是v0.4版本。

2) Tensors

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

代码如下:

import torch

# cpu版本
device = torch.device('cpu')
#device = torch.device('cuda:0') # 使用gpu

# 为数据和参数创建随机的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)

    # 梯度下降,参数更新
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2
  • 首先创建 x,y,w1,w2的随机 tensor,与 Numpy 数组的形式一致
  • 然后前向传播计算损失值和预测值
  • 然后手动计算梯度
  • 最后更新参数

上述代码很简单,和 Numpy 版本的写法很接近。但是需要手动计算梯度。

3) Autograd自动梯度计算

PyTorch 可以自动计算梯度:

import torch

# 创建随机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()
    # 参数更新
    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad
        w1.grad.zero_()
        w2.grad.zero_(
        标签: 度180度连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台