一、深度学习框架

  • Caffe
  • Caffe2
  • Chainer
  • CNTK(Microsoft Cognitive Toolkit)
  • Deeplearning4j
  • Keras
  • MATLAB
  • MxNet
  • TensorFlow
  • Theano
  • Torch/PyTorch

二、搭建开发环境(GPU环境)

所有相关软件的资源

1. GPU环境:NVIDIA CUDAv10.2

底层基于C/C++开发:VS2015/2017

2. 库:cuDNN v7.6.5 (for CUDAv10.2)

系统变量:cuda/bin目录

3. 开发框架:tensorflow, pytorch等

  • conda:调试很方便
    + pip: 轻量级,纯净

    4. 查看验证

  • CUDA是否正确安装:cmd窗口,输入nvcc -V
    + PyTorch torch.cuda.is_available() 返回True
    + GPU使用的设备 variable.cuda() 返回device
  • GPU训练
    (model/variable).to('cuda')
    (model/variable).to('cpu')
  • 工具
    使用:CUDA
    查看:GPUz
  • 省内存的技巧
    尽可能使用inplace操作
  • NVIDIA开发人员计划
    使用最新的NVIDIA SDK和工具来加速您在人工智能,深度学习,加速计算和高级图形等关键技术领域中的应用
    包括自动驾驶汽车,大数据,医疗保健,高性能计算,机器人技术,虚拟现实等领域

三、背景知识:CPU与GPU

1. 核心数

CPU 4~16个核心
GPU上千个核心:针对并行计算

2. 进程与线程

  • GIL Global Interpreter Lock
    解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁
    但当大家试图去拆分和去除GIL的时候,发现大量库代码开发者已经重度依赖GIL而非常难以去除了
    毫无疑问全局锁的存在会对多线程的效率有不小影响。甚至就几乎等于Python是个单线程的程序
    解决方案:用multiprocessing替代Thread,多进程代替多线程

3. GPU使用场合

  • 图像处理:适合并行运算
    openACC
    openCL

4. 编程实现

PyTorch框架调用
C++调用封装好的函数

四、PyTorch框架入门

1. 知识点

  • 张量Tensor
    向量和矩阵的泛化:类似ndarray,本质都是张量PyTorch 基础大全-附案例 | MINST(手写数字 28*28像素图) - 图1
  • 加法 torch.sum()
    dim=0:跨行相加
    dim=1:跨列相加
  • 乘法
  1. Hadamard product
    x.mul(y) 用法与乘法相同,两者都是broadcast的
    x.mul_(y) 下划线表示inplace = True
    `
    对应元素相乘:如果a与b的size不同,则以某种方式将a或b进行复制,使得复制后的a和b的size相同<br /> 标量:数乘<br /> 一维向量`:广播机制
    Tensor与行向量做乘法的结果是每列乘以行向量对应列的值
    Tensor与列向量做乘法的结果是每行乘以列向量对应行的值
  2. 矩阵相乘 (j, 1, n, m) & (k, m, p) == (j, k, n, p)
    torch.mm(x, y)x.mm(y) 矩阵大小需满足: (i, n)x(n, j)
    torch.matmul(x, y) torch.mm的broadcast版本
    torch.matmul() 支持广播;若均为一维:表示内积
  • 除法:基于列
    a (64, 10) b (64,)
    a/b 报错:若b是列向量(64, 1),则a的每一行除以b的每一行(单个数)
    关注列的维度,10/1能除尽@广播机制
    但是b(64,)默认行向量,即1*64:无法做到10/64
  • 其余运算
    torch.mean() 注意dim的取值
    torch.topk() topk, indices = torch.topk(data, k)

2. 关键细节

  • 数据类型转换(Tensor&ndarray)
    torch.from_numpy() ndarray转化为Tensor
    torch.numpy() 转化为ndarray
    注意:转换前后共用同一块内存,所以两者同时改变
  • 下划线:表示inplace=True
  • 维度调整
    .shape()
    .flatten()
    .reshape()
    .resize_() 维度小了,则删除多余的数据;维度大了,则新数据先不初始化
    weights.view() 常用,返回与weights维度相同的新的张量(与原始数据相同):如果数量不同,则报错(避免.resize_的问题)

五、实例:识别数字(28*28图像)

基础操作:感知器

  1. #导入模块
  2. import torch
  3. #自定义函数 激活函数
  4. def activation(x):
  5. return 1/(1+torch.exp(-x))
  6. #单层感知器 5*1
  7. #初始化
  8. torch.manual_seed(7)
  9. features = torch.randn((1, 5)) #1行5列 正态分布随机
  10. weights = torch.randn_like(features) #维度相同randn_like
  11. bias = torch.randn((1, 1))
  12. #前馈过程
  13. h = torch.sum(features.mul(weights)) + bias #一维向量.mul()表示* Hadamard product
  14. y = activation(h)
  15. #多层感知器 3*2*1
  16. #初始化
  17. torch.manual_seed(7)
  18. features = torch.randn((1, 3))
  19. n_input = features.shape[1]
  20. n_hidden = 2
  21. n_output = 1
  22. W1 = torch.randn(n_input, n_hidden) #3*2
  23. W2 = torch.randn(n_hidden, n_output) #2*1
  24. B1 = torch.randn((1, n_hidden))
  25. B2 = torch.randn((1, n_output))
  26. #前馈过程
  27. h = activation(features.mm(W1) + B1)
  28. y = activation(h.mm(W2) + B2)

  • 预备工作:导入数据 数字0-9识别 数据源
    数据大小:[64, 1, 28, 28]
    一个Batch:64张图,每张图是28*28像素
  1. from torchvision import datasets, transforms
  2. #Define a transform to normalize the data
  3. transform = transforms.Compose([transforms.ToTensor(),
  4. transforms.Normalize((0.5, ), (0.5, ))])
  5. #Download and load the training data
  6. trainset = datasets.MNIST('~/.pytorch/NNIST_data/', download=True, train=True, transform=transform)
  7. testset= datasets.MNIST('~/.pytorch/NNIST_data/', download=True, train=False, transform=transform)
  8. trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)
  9. testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=True)

实现1)清晰过程

1.1 建立模型

  1. #导入模块
  2. import torch
  3. from torch import nn, optim
  4. import torch.nn.functional as F
  5. #封装成类
  6. class Classifier(nn.Module):
  7. def __init__(self):
  8. super().__init__()
  9. #3个隐藏层
  10. self.fc1 = nn.Linear(784, 256) #fc full connect
  11. self.fc2 = nn.Linear(256, 128)
  12. self.fc3 = nn.Linear(128, 64)
  13. self.fc4 = nn.Linear(64, 10)
  14. def forward(self, x):
  15. x = x.view(x.shape[0], -1)
  16. x= F.relu(self.fc1(x))
  17. x= F.relu(self.fc2(x))
  18. x= F.relu(self.fc3(x))
  19. x= F.log_softmax(self.fc4(x), dim=1)
  20. return x
  • 前馈测试
  1. model = Classifier()
  2. images, labels = next(iter(testloader)) #测试样本 28*28图像;标签表示具体数字
  3. #计算结果:概率值
  4. #.forward()中含有等价于.flatten的操作
  5. ps = torch.exp(model(images)) #不需要model.forward() 默认为输入(继承nn.Module 且覆写方法.forward())
  6. print(ps.shape) #结果显示([64, 10])

1.2 训练网络-反向传播 + 1.3 验证测试

  1. #1.2 训练网络-反向传播
  2. model = Classifier()
  3. criterion = nn.NLLLoss()
  4. optimizer = optim.Adam(model.parameters(), lr=0.003)
  5. epochs = 2
  6. steps = 0
  7. train_losses, test_losses = [], []
  8. for e in range(epochs):
  9. running_loss = 0
  10. for images, labels in trainloader:
  11. optimizer.zero_grad()
  12. log_ps = model.(images)
  13. loss = criterion(log_ps, labels)
  14. loss.backward()
  15. optimizer.step()
  16. running_loss += loss.item()
  17. #1.3 验证测试
  18. else:
  19. test_loss = 0
  20. accuracy = 0
  21. #验证过程中:关闭梯度跟踪
  22. with torch.no_grad():
  23. for images, labels in testloader:
  24. log_ps = model.(images)
  25. test_loss += criterion(log_ps, labels)
  26. ps = torch.exp(log_ps)
  27. top_p, top_class = ps.topk(1, dim=1)
  28. equals = top_class == labels.view(*top_class.shape)
  29. accuracy += torch.mean(equals.type(torch.FloatTensor))
  30. train_losses.append(running_loss/len(trainloader))
  31. test_losses.append(test_loss/len(testloader))
  32. print("" .format, ...)

PyTorch 基础大全-附案例 | MINST(手写数字 28*28像素图) - 图2

实现2)nn.Module

2.1 建立模型

  1. input_size = 784
  2. hidden_sizes = [128, 64]
  3. output_size = 10
  4. #建立模型
  5. #实现方法(1)逐个列举
  6. model = nn.Sequential(nn.Linear(input_size, hidden_sizes[0]),
  7. nn.ReLU(),
  8. nn.Linear(hidden_sizes[0], hidden_sizes[1]),
  9. nn.ReLU(),
  10. nn.Linear(hidden_sizes[1], output_size),
  11. nn.Softmax(dim=1))
  12. #实现方法(2)有序字典 collections.OrderedDict()
  13. from collections import OrderedDict
  14. model = nn.Sequential(OrderedDict([
  15. ('fc1', nn.Linear(input_size, hidden_sizes[0])),
  16. ('relu1', nn.ReLU()),
  17. ('fc2', nn.Linear(hidden_sizes[0], hidden_sizes[1])),
  18. ('relu2', nn.ReLU()),
  19. ('output', nn.Linear(hidden_sizes[1], output_size)),
  20. ('softmax', nn.Softmax(dim=1))]))
  21. #model[0]等价于model.fc1

2.2 训练网络-反向传播 + 2.3 验证测试

  1. criterion = nn.NLLLoss()
  2. optimizer = optim.SGD(model.parameters(), lr=0.003)
  3. epochs = 5
  4. for e in range (epochs) :
  5. running_loss = 0
  6. for images, labels in trainloader:
  7. #Flatten WIST images into a 784 long vector
  8. images = images. view (images.shape[0], -1)
  9. #Training pass
  10. optimizer.zero_grad()
  11. output = model(images)
  12. loss = criterion(output, labels)
  13. loss.backward()
  14. optimizer.step()
  15. running_loss += loss.item ()
  16. else:
  17. print (f"Training loss: {running_loss/len (trainloader)}")

PyTorch 基础大全-附案例 | MINST(手写数字 28*28像素图) - 图3


六、实例分析(流程梳理)

1. 模块汇总

import torch
from torch import nn

from torchvision import datasets, transforms
import torch.nn.functional as F

#torch.nn模块
(model).weight
    .weight.data.normal_(std=0.01)
(model).bias
    .bias.data.fill_(0)


torch.randn(, requires_grad=True)  #梯度跟踪
torch.empty()
.random_()
    #建模相关
    criterion()
    loss()
    loss().backword()  #backword() 某变量的偏导(基于梯度跟踪)
    nn.ReLU()
    nn.Logsoftmax() == log(nn.Softmax())
    nn.NLLLoss() == - nn.Logsoftmax()
    nn.CrossEntropyLoss() #交叉熵
    nn.Sequential(nn.Linear(), nn.ReLU(), ...) # OrderedDict()方法


#torch.nn.functional模块
F.relu()
F.sigmoid()
F.softmax()
F.log_softmax()

#autograd模块
requires_grad = True
torch.set_grad_enabled(True|False)
(model).weight.grad

x.requires_grad_(True)  #开启梯度跟踪
torch.zeros(1, requires_grad=True)
with torch.no_grad():
    ......
    ......

#optim模块
optim.SGD()
(optim.SGD()).zero_grad()

2. 操作细节

  • 验证循环时
    model.eval()将网络设为评估模式,验证环节不必dropout
    model.train() 将其设为训练模式
  • CUDA错误
    错误原因:一部分模型/张量在CPU,另一部分在GPU
    解决方法:涉及所有相关的模型和张量转移到同一device

3. 评价指标

  • 准确率
  • 精确率、召回率
  • top-5错误

4. 常见问题

  1. 欠拟合&过拟合 @类比-试穿裤子:选择大一点+腰带控制
  • 模型复杂度图表
    提前停止训练
  • 正则化:模型权重的影响
    @ODS-惩罚因子λ:对权重系数增加惩罚因子λ
    本质:避免影响,就针对该影响添加惩罚因子
    L2倾向于选择均匀、非稀疏的向量
  1. 训练不均衡
  • dropout
    每个节点有20%的概率(可设置)在前馈-反向传播的一个循环中关闭
    @肌肉训练:左右都训练
  1. 陷入局部最小点 1)随机启动random restart 设置不同的初始权值 2)动态步长momentum 动量β:结合步长的历史==动量的惯性 峡谷的例子:左右振荡——>振荡抵消 不用二阶方法:计算量太大 3)随机梯度下降SGD Stochastic gradient descent 梯度的随机近似(随机选择部分数据集),代替真正的梯度下降 更快迭代,但收敛率下降
  2. 计算量大
  • 随机梯度下降(选择数据批次)
    程序实现:
    外循环(4个批次)
    内循环(每个批次:6个点)
  1. 参数:学习速率选取
  • 实际运用:“动态”学习速率
    陡峭:步长大
    平坦:步长小
  1. 选择:不同的误差函数 1)如果参数模型定义了一个分布p(y|x;θ),我们采用最大似然原理得到代价函数:训练数据和模型预测间的交叉熵PyTorch 基础大全-附案例 | MINST(手写数字 28*28像素图) - 图4 对数函数能帮我们避免梯度过小(例如有的输出单元有一个指数函数,取对数后梯度就不那么小了) 2)如果不预测y的完整概率分布,仅仅预测在给定x条件下y的某种统计量,那就用某些专门的损失函数来计算 2.1)均方误差 2.2)平均绝对误差
  2. 均方差损失函数+Sigmoid激活函数(不推荐)
    均方误差和平均绝对误差在梯度下降法表现不好,因为饱和的输出单元梯度非常小。
    (Sigmoid的这个曲线意味着在大多数时候,我们的梯度变化值很小,导致我们的W,b更新到极值的速度较慢,也就是我们的算法收敛速度较慢)
  3. 使用交叉熵损失函数+Sigmoid激活函数(提高收敛速度)
    对比来说交叉熵代价函数更受欢迎

7. 梯度消失 选择:激活函数

  • sigmoid函数两端导数接近0
    Softmax:多个分类
  • 双曲正切tanh(x)
  • 修正线性单元ReLU

5. 网络优化

  • 网络结构
  • 数据
  • 运行时间

References

选择:不同的误差函数