该笔记是看李沐视频学习的。
内容大概分为几部分
- 深度学习基础
- 线性模型
- CNN
- RNN
- 求导、训练
- Gluon最佳实践
- hybridize
- 自动并行
- 多卡训练
- 计算机视觉
- Resnet
- SSD
- 自然语言处理
- Word embedding
- seq2seq
- 增强学习
- 对抗网络
- 推荐系统
《动手学深度学习》是面向中文读者的能运行、可讨论的深度学习教科书。也是视频的参考书籍,可以通过访问 https://zh.d2l.ai 获取最新的内容 更新。希望通过一个月的时间学习加练习,来加强自己对深度学习的理解,为将来的论文和工作打基础。
配置Jupyter
以下命令都是进入⽂件夹“d2l-zh”的窗口来打开cmd
激活环境1
2
3conda activate gluon # 若conda版本低于4.4,使⽤命令activate gluon
#需退出虚拟环境
conda deactivate
打开Jupyter记事本1
jupyter notebook
更新代码的步骤
第⼀步是重新下载最新的包含本书全部代码的压缩包。下载地址为https://zh.d2l.ai/d2l-zh.zip。 解压后进⼊⽂件夹“d2l-zh”。
第⼆步是使⽤下⾯的命令更新运⾏环境:
1 | conda env update -f environment.yml |
使⽤GPU版的MXNet
第⼀步是卸载CPU版本MXNet。如果没有安装虚拟环境,可以跳过此步。如果已安装虚拟环境, 需要先激活该环境,再卸载CPU版本的MXNet。
1 | pip uninstall mxnet |
第⼆步是更新依赖为GPU版本的MXNet。使⽤⽂本编辑器打开本书的代码所在根⽬录下的⽂ 件environment.yml,将⾥⾯的字符串“mxnet”替换成对应的GPU版本。例如,如果计算机 上装的是8.0版本的CUDA,将该⽂件中的字符串“mxnet”改为“mxnet-cu80”。如果计算机上 安装了其他版本的CUDA(如7.5、9.0、9.2等),对该⽂件中的字符串“mxnet”做类似修改(如 改为“mxnet-cu75”“mxnet-cu90”“mxnet-cu92”等)。保存⽂件后退出。
第三步是更新虚拟环境,执⾏命令
1 | conda env update -f environment.yml |
之后,我们只需要再激活安装环境就可以使⽤GPU版的MXNet运⾏本书中的代码了。需要提醒的 是,如果之后下载了新代码,那么还需要重复这3步操作以使⽤GPU版的MXNet。
数学基础概念
一般像这里指的维度0和维度1是指矩阵的行和列
我们使用符号$\odot$表示两个矩阵按元素做乘法的运算:
范数
设$n$维向量$\boldsymbol{x}$中的元素为$x_1, \ldots, x_n$。向量$\boldsymbol{x}$的$L_p$范数为
例如,$\boldsymbol{x}$的$L_1$范数是该向量元素绝对值之和:
而$\boldsymbol{x}$的$L_2$范数是该向量元素平方和的平方根:
我们通常用$|\boldsymbol{x}|$指代$|\boldsymbol{x}|_2$。
设$\boldsymbol{X}$是一个$m$行$n$列矩阵。矩阵$\boldsymbol{X}$的Frobenius范数为该矩阵元素平方和的平方根:
其中$x_{ij}$为矩阵$\boldsymbol{X}$在第$i$行第$j$列的元素。
特征向量和特征值
对于一个$n$行$n$列的矩阵$\boldsymbol{A}$,假设有标量$\lambda$和非零的$n$维向量$\boldsymbol{v}$使
那么$\boldsymbol{v}$是矩阵$\boldsymbol{A}$的一个特征向量,标量$\lambda$是$\boldsymbol{v}$对应的特征值。
梯度
假设函数$f: \mathbb{R}^n \rightarrow \mathbb{R}$的输入是一个$n$维向量$\boldsymbol{x} = [x_1, x_2, \ldots, x_n]^\top$,输出是标量。函数$f(\boldsymbol{x})$有关$\boldsymbol{x}$的梯度是一个由$n$个偏导数组成的向量:
为表示简洁,我们有时用$\nabla f(\boldsymbol{x})$代替$\nabla_{\boldsymbol{x}} f(\boldsymbol{x})$。
假设$\boldsymbol{x}$是一个向量,常见的梯度演算包括
类似地,假设$\boldsymbol{X}$是一个矩阵,那么
海森矩阵
假设函数$f: \mathbb{R}^n \rightarrow \mathbb{R}$的输入是一个$n$维向量$\boldsymbol{x} = [x_1, x_2, \ldots, x_n]^\top$,输出是标量。假定函数$f$所有的二阶偏导数都存在,$f$的海森矩阵$\boldsymbol{H}$是一个$n$行$n$列的矩阵:
其中二阶偏导数
运算内存开销
在Python中操作经常会开心的内存来存储运算结果。
即使像Y=X+Y这样的运算,也会开新的内存,然后将Y指向新的内存。
1 | 输入 |
如果想把结果保存到指定内存,可以先通过zeros_like创建和Y形状相同且元素为0的NDArray,记为Z。接下来,我们把X+Y的结果通过[:]写进Z对应的的内存中。
1 | 输入 |
但是实际上,这里还是为X+Y开了临时内存来存储计算结果,再复制到Z对应的内存。如果想避免这个临时内存开销,我们可以使用运算符全名函数中的out参数。
1 | 输入 |
如果X的值在之后程序中不会复用,可以用 X[:] = X + Y
或者 X += Y
来减少运算的内存开销。
1 | 输入 |
从上手到多类分类
线性回归
何为线性回归?
就是给定一个数据点的集合以及对应的目标Y, 然后线性模型就会找一根线,其由向量w和位置b组成,来最好的近似每个样本的x[i]和y[i],用数学符号来表示就是将学w和b来预测。
并且最小化所有数据点上的平方误差。
创建数据集
使用人工创建数据集,方便我们知道真实的模型是怎么样子的,采用以下方法来生成:
1 | y[i] = 2 * X[i][0] - 3.4 * X[i][1] + 4.2+noise |
注意到X的每一行是一个长度为2的向量,而y的每一行是一个长度为1的向量(标量)
1 | from mxnet import ndarray as nd |
有个小知识点,关于转型的问题
上面出现了,数组+浮点数 得到了 数组,该数组是每一个值都加一次浮点数
如果数组加布尔型,也是可以的,数组里的每一个值都把该布尔型转换成float,再加一次。
数据读取
定义一个函数,每次返回batch_size个随机的样本和对应的目标。通过Python的yield来构造一个迭代器。
1 | import random |
下面代码读取第一个随机数据块
1 | for data, label in data_iter(): |
初始化模型
1 | w = nd.random.normal(shape=(num_inputs,1)) |
因为之后训练时需要对这些参数求导来更新它们的值,所以需要创建它们的梯度。
1 | for param in params: |
定义模型
1 | def net(x): |
损失函数
使用常见的平方误差来衡量预测目标和真实目标之间的差距
1 | def square_loss( yhat , y): |
优化
虽然线性回归有显式解,但绝大部分模型没有。所以通过随机梯度下降来求解。每一步都将模型参数沿着梯度的反方向走特定的距离lr。这个距离一般叫学习率。之后又会一直用这个函数,保存到utils.py里
1 | def SGD(params , lr): |
训练
训练通常需要迭代数据次数,一次迭代里,每次随机读取固定数个数据点,计算梯度并且更新模型参数。
1 | epochs = 5 |
这时候就可以跑出结果了
1 | 输出: |
训练后,用学习到的参数和真实参数进行比较
1 | 输入 |
1 | true_b, b |
可以看得出来数据是非常接近的。
使用gluon的线性回归
同样的模型,使用高层抽象包gluon
创建数据集
1 | from mxnet import autograd, nd |
数据读取
这里使用data模块来读取数据
但是由于data经常用作变量名,我们将导入的data模块用添加gluon的首字母的假名gdata代替
1 | from mxnet.gluon import data as gdata |
这里的data_iter的使用跟上一部分的一样,让我们读取并打印第一个小批量的数据样本
1 | for x , y in data_iter: |
定义模型
先导入nn(neural networks缩写)模块,里面定义了大量神经网络的层。
我们先定义一个sequential实例,在gluon中,sequential实例可以看作是一个串联各个层的容器。在构造模型时,在该容易依次添加层。当给定输入数据时,容器每一层将依次计算将输出作为下一层的输入。
1 | from mxnet.gluon import nn |
线性回归在神经网络图中,作为一个单层神经网络,线性回归输出层中的神经元和输入层中各个输入完全连接。
因此,线性回归的输出层又叫全连接层。在gluon中,全连接层是一个dense实例,我们定义该层输出个数为1.
1 | net.add(nn.Dense(1)) |
在gluon中,我们无需定义每一层输入的形状,比如线性回归的输入个数。当模型得到数据时,在后面执行next(X)时,模型将自动腿短出每一层的输入个数。
初始化模型参数
在使用net之前,需要初始化模型参数,比如线性回归的权重和偏差。
从MXNet中导入inti模块,该模块提供模型初始化的各种方法。
1 | from mxnet import init |
定义损失函数
在gluon中,loss模块定义了各种损失函数,用假名gloss代替导入的loss模块,直接使用它提供的平方损失作为模型的损失函数。
1 | from mxnet.gluon import loss as gloss |
定义优化算法
同样,我们也无需实现小批量的随机梯度下降。导入gluon后,创建一个Trainer实例,并指定学习率为0.03的小批量随机梯度下降(sgd)为优化算法。该优化算法用来迭代net实例所有通过add函数嵌套的层所包含的全部参数。
这些参数可以通过collect_params函数获取。
1 | from mxnet import gluon |
训练模型
在使用gluon训练模型时,通过调用Trainer实例的step函数来迭代模型参数。
由于变量 l 是长度为batch_size的以为NDArray,执行 l.backward()等价于执行 l.sum().backward().
按照小批量随机梯度下降的定义,我们在step函数中指明批量大小,从面对批量中样本梯度求平均。
1 | num_epochs =3 |
输出1
2
3epoch 1, loss: 0.040645
epoch 2, loss: 0.000157
epoch 3, loss: 0.000051
然后分别比较学到的模型参数和真实的模型参数。
我们从net获得需要的层,并访问起权重(weight)和偏差(bias)。学到的参数和真实的参数很接近。
1 | dense = net[0] |
输出1
2([2, -3.4], [[ 1.9996208 -3.399555 ]]
<NDArray 1x2 @cpu(0)>)
1 | true_b , dense.bias.data() |
输出1
2(4.2, [4.199315]
<NDArray 1 @cpu(0)>)
当遇到关于mxnet不懂的地方,可以尝试使用1
2
3
4
5
6想访问weight的梯度,但是不知道怎么做,可以这样做:
dense.weight?
里面会列出详细的文档
或者
dense.weight??
甚至有实现的方法
多类逻辑回归(softmax回归)
线性回归模型适用于输出为连续值的情况,但是碰到模型输出是一个像图像类别的离散值,我们可以使用诸如softmax回归在内的分类模型来进行离散值预测。
和线性回归不同,softmax回归的输出单元从一个变成了多个,且引入了softmax运算,使运算更适合离散值的预测和训练。