提交 f731a01e 编写于 作者: M muli
......@@ -58,6 +58,8 @@
方差,variance
访问参数,access parameters
非凸优化,non-convex optimization
分桶,bucketing
......@@ -146,6 +148,8 @@ softmax回归,softmax regression
收敛,converge
属性,property
数值方法,numerical method
数据集,data set
......
......@@ -12,5 +12,5 @@ dependencies:
- nbsphinx==0.2.14
- recommonmark==0.4.0
- https://github.com/mli/notedown/tarball/master
- mxnet-cu80==1.3.0
- mxnet-cu80==1.2.1
- jieba==0.39
......@@ -2,11 +2,11 @@
之前介绍了如何在只有6万张图像的FashionMNIST上训练模型。我们也介绍了ImageNet这个当下学术界使用最广的大数据集,它有超过一百万的图像和一千类的物体。但我们平常接触到数据集的规模通常在两者之间。
假如你想从图像中识别出各种凳子,然后推荐购买链接给用户。一个可能的做法是先找一百种常见的凳子,为每种凳子拍摄一千张不同角度的图像,然后在收集到的数据上训练一个分类器。这个数据集虽然可能比FashionMNIST要庞大,但仍然比ImageNet小10倍。这可能导致适用于ImageNet的复杂模型在这个数据上会过拟合。同时因为数据量有限,最终我们得到的模型的精度也可能达不到实用的要求。
假如你想从图像中识别出各种凳子,然后推荐购买链接给用户。一个可能的做法是先找出一百种常见的凳子,为每种凳子拍摄一千张不同角度的图像,然后在收集到的数据上训练一个分类器。这个数据集虽然可能比FashionMNIST要庞大,但仍然要比ImageNet小10倍。这可能会导致适用于ImageNet的复杂模型在这个数据集上会过拟合。同时因为数据量有限,最终我们得到的模型的精度也可能达不到实用的要求。
一个解决办法是收集更多的数据。但是收集和标注数据会花费大量的时间和资金。例如为了收集ImageNet这个数据集,花费了数百万美元的研究经费。虽然目前的数据采集成本降低了十倍以上,但其成本仍然不可忽略。
一个解决办法是收集更多的数据,但是收集和标注数据会花费大量的时间和资金。例如为了收集ImageNet这个数据集,研究人员花费了数百万美元的研究经费。虽然目前的数据采集成本降低了十倍以上,但其成本仍然不可忽略。
另外一种解决办法是应用迁移学习(transfer learning),将从源数据集学到的知识迁移到目标数据集上。例如,虽然ImageNet的图像基本跟椅子无关,但在其上训练的模型可以抽取通用图像特征,然后用来帮助识别边缘、纹理、形状和物体组成。类似的,它对于识别椅子也可能同样有效。
另外一种解决办法是应用迁移学习(transfer learning),将从源数据集学到的知识迁移到目标数据集上。例如,虽然ImageNet的图像基本跟椅子无关,但在其上训练的模型可以抽取通用图像特征,然后用来帮助识别边缘、纹理、形状和物体组成等。这些类似的特征对于识别椅子也可能同样有效。
本小节我们介绍迁移学习中的一个常用技术:微调(fine tuning)。如图9.1所示,微调由下面四步构成:
......@@ -21,7 +21,7 @@
## 热狗识别
接下来我们来看一个具体的例子,它使用ImageNet上训练好的ResNet用来微调一个我们构造的小数据集:其含有数千张包含热狗和不包含热狗的图像。
接下来我们来看一个具体的例子,它使用ImageNet上训练好的ResNet微调一个我们构造的小数据集:其含有数千张包含热狗和不包含热狗的图像。
### 获取数据
......@@ -68,7 +68,7 @@ not_hotdogs = [train_imgs[-i - 1][0] for i in range(8)]
gb.show_images(hotdogs + not_hotdogs, 2, 8, scale=1.4);
```
在训练时,我们先从图像中剪裁出随机大小随机长宽比的一块,然后将它们统一缩放为长宽都是224的输入。测试时,则使用简单的中心剪裁。此外,我们对输入的RGB通道数值进行了归一化。
在训练时,我们先从图像中剪裁出随机大小随机长宽比的一块,然后将它们统一缩放为长宽都是224的输入。测试时,则使用简单的中心剪裁。此外,我们对输入的RGB通道数值进行了归一化。
```{.python .input n=3}
# 指定 RGB 三个通道的均值和方差来将图像通道归一化。
......@@ -106,7 +106,7 @@ pretrained_net.output
它将ResNet最后的全局平均池化层输出转化成1000类的输出。
在微调中,我们新建一个网络,它的定义跟之前训练好的网络一样,除了最后的输出数等于当前数据的类别数。就是说新网络的`features`被初始化成前面训练好网络的权重,而`output`则是从头开始训练
在微调中,我们新建一个网络,它的定义跟之前训练好的网络一样,但是最后的输出数等于当前数据的类别数。也就是说新网络的`features`被初始化成前面训练好网络的权重,而`output`则是从头开始训练的
```{.python .input n=9}
finetune_net = model_zoo.vision.resnet18_v2(classes=2)
......@@ -134,7 +134,7 @@ def train_fine_tuning(net, learning_rate, batch_size=128, num_epochs=5):
gb.train(train_iter, test_iter, net, loss, trainer, ctx, num_epochs)
```
因为微调的网络中的主要层的已经训练的足够好,所以一般采用比较小的学习率,防止过大的步长对训练好的层产生过多影响。
因为微调的网络中的主要层已经训练的足够好,所以一般采用比较小的学习率,以防止过大的步长对其产生过多影响。
```{.python .input n=13}
train_fine_tuning(finetune_net, 0.01)
......@@ -148,7 +148,7 @@ scratch_net.initialize(init=init.Xavier())
train_fine_tuning(scratch_net, 0.1)
```
可以看到,微调的模型因为初始值更好,在相同迭代周期下能够取得更好的结果。在很多情况下,微调的模型最终会比非微调的模型取得更好的结果。
可以看到,微调的模型因为初始值更好,在相同迭代周期下能够取得更好的结果。在很多情况下,微调的模型最终会比非微调的模型取得更好的结果。
## 小结
......
# 图像增广
[“深度卷积神经网络:AlexNet”](../chapter_convolutional-neural-networks/alexnet.md)小节里我们提到过,大规模数据集是成功使用深度网络的前提。图像增广(image augmentation)技术通过对训练图像做一系列随机变化,来产生相似但又有不同的训练样本,从而扩大训练数据集规模。图像增广的另一种解释是,通过对训练样本做一些随机变形,可以降低模型对某些属性的依赖,从而提高泛化能力。例如我们可以对图像进行不同的裁剪,使得感兴趣的物体出现在不同的位置中,从而使得模型减小对物体出现位置的依赖性。也可以调整亮度色彩等因素来降低模型对色彩的敏感度。在AlexNet的成功中,图像增广技术功不可没。本小节我们将讨论这个在计算机视觉里被广泛使用的技术。
[“深度卷积神经网络:AlexNet”](../chapter_convolutional-neural-networks/alexnet.md)小节里我们提到过,大规模数据集是成功使用深度网络的前提。图像增广(image augmentation)技术通过对训练图像做一系列随机改变,来产生相似但又有不同的训练样本,从而扩大训练数据集规模。图像增广的另一种解释是,通过对训练样本做一些随机改变,可以降低模型对某些属性的依赖,从而提高模型的泛化能力。例如我们可以对图像进行不同的裁剪,使得感兴趣的物体出现在不同的位置,从而使得模型减小对物体出现位置的依赖性。我们也可以调整亮度色彩等因素来降低模型对色彩的敏感度。在AlexNet的成功中,图像增广技术功不可没。本小节我们将讨论这个在计算机视觉里被广泛使用的技术。
首先,导入本节实验所需的包或模块。
......@@ -65,7 +65,7 @@ apply(img, gdata.vision.transforms.RandomFlipLeftRight())
apply(img, gdata.vision.transforms.RandomFlipTopBottom())
```
我们使用的样例图像里,猫在图像正中间,但一般情况下可能不是这样。[“池化层”](../chapter_convolutional-neural-networks/pooling.md)一节里我们解释了池化层能弱化卷积层对目标位置的敏感度,另一方面我们可以通过对图像随机剪裁来让物体以不同的比例出现在不同位置
在我们使用的样例图像里,猫在图像正中间,但一般情况下可能不是这样。在[“池化层”](../chapter_convolutional-neural-networks/pooling.md)一节里我们解释了池化层能弱化卷积层对目标位置的敏感度,除此之外我们还可以通过对图像随机剪裁来让物体以不同的比例出现在图像的不同位置,这同样能够降低模型对位置的敏感性
下面代码里我们每次随机裁剪一片面积为原面积10%到100%的区域,其宽和高的比例在0.5和2之间,然后再将高宽缩放到200像素大小。
......@@ -77,19 +77,19 @@ apply(img, shape_aug)
### 颜色变化
另一类增广方法是变化颜色。我们可以从四个维度改变图像的颜色:亮度、对比、饱和度和色相。在下面的例子里,我们将随机亮度改为原图的50%到150%。
另一类增广方法是变化颜色。我们可以从四个维度改变图像的颜色:亮度、对比度、饱和度和色调。在下面的例子里,我们将随机亮度改为原图的50%到150%。
```{.python .input n=28}
apply(img, gdata.vision.transforms.RandomBrightness(0.5))
```
类似的,我们可以修改色相
类似的,我们也可以修改图像的色调
```{.python .input n=29}
apply(img, gdata.vision.transforms.RandomHue(0.5))
```
或者用使用`RandomColorJitter`一起使用。
或者配合`RandomColorJitter`一起使用。
```{.python .input n=30}
color_aug = gdata.vision.transforms.RandomColorJitter(
......@@ -109,13 +109,13 @@ apply(img, augs)
## 使用图像增广来训练
接下来我们来看一个将图像增广应用在实际训练中的例子,并比较其与不使用时的区别。这里我们使用CIFAR-10数据集,而不是之前我们一直使用的Fashion-MNIST。原因在于Fashion-MNIST中物体位置和尺寸都已经归一化了,而CIFAR-10中物体颜色和大小区别更加显著。下面我们展示CIFAR-10中的前32张训练图像。
接下来我们看一个将图像增广应用在实际训练中的例子,并比较其与不使用时的区别。这里我们使用CIFAR-10数据集,而不是之前我们一直使用的Fashion-MNIST。原因在于Fashion-MNIST中物体位置和尺寸都已经经过归一化处理,而在CIFAR-10中物体的颜色和大小区别更加显著。下面我们展示CIFAR-10中的前32张训练图像。
```{.python .input n=32}
show_images(gdata.vision.CIFAR10(train=True)[0:32][0], 4, 8, scale=0.8);
```
我们通常将图像增广用在训练样本上,但是在预测的时候并不使用随机增广。这里我们仅仅使用最简单的随机水平翻转。此外,我们使用`ToTensor`变换来将图像转成MXNet需要的格式,即格式为(批量,通道,高,宽)以及类型为32位浮点数。
我们通常只将图像增广用在训练样本上,在在预测的时候并不使用随机增广。在这里我们仅仅使用最简单的随机水平翻转。此外,我们使用`ToTensor`变换来将图像转成MXNet需要的格式,即格式为(批量,通道,高,宽)、类型为32位浮点数。
```{.python .input n=33}
train_augs = gdata.vision.transforms.Compose([
......@@ -128,7 +128,7 @@ test_augs = gdata.vision.transforms.Compose([
])
```
接下来我们定义一个辅助函数来方便读取图像并应用增广。Gluon的数据集提供`transform_first`函数来对数据里面的第一项(数据一般有图像和标签两项)来应用增广。另外图像增广将增加计算复杂度,这里使用4个进程来加速读取(暂不支持 Windows 操作系统)。
接下来我们定义一个辅助函数来方便读取图像并应用增广。Gluon的数据集提供`transform_first`函数来对数据里面的第一项(数据一般有图像和标签两项)来应用增广。另外图像增广将会增加计算复杂度,因此这里使用4个进程来加速读取(暂不支持 Windows 操作系统)。
```{.python .input n=34}
num_workers = 0 if sys.platform.startswith('win32') else 4
......@@ -160,7 +160,7 @@ def try_all_gpus():
return ctxes
```
然后,我们定义`evaluate_accuracy`函数评价模型的分类准确率。与[“Softmax回归的从零开始实现”](../chapter_deep-learning-basics/softmax-regression-scratch.md)[“卷积神经网络(LeNet)”](../chapter_convolutional-neural-networks/lenet.md)两节中描述的`evaluate_accuracy`函数不同,当`ctx`包含多个GPU时,这里定义的函数通过辅助函数`_get_batch`将小批量数据样本划分并复制到各个GPU上。
然后,我们定义`evaluate_accuracy`函数评价模型的分类准确率。与[“Softmax回归的从零开始实现”](../chapter_deep-learning-basics/softmax-regression-scratch.md)[“卷积神经网络(LeNet)”](../chapter_convolutional-neural-networks/lenet.md)两节中描述的`evaluate_accuracy`函数不同,当`ctx`包含多个GPU时,我们需要通过这里定义的辅助函数`_get_batch`将小批量数据样本划分并复制到各个GPU上。
```{.python .input n=36}
def _get_batch(batch, ctx):
......
......@@ -30,7 +30,7 @@ mnist_test = gdata.vision.FashionMNIST(train=False)
len(mnist_train), len(mnist_test)
```
我们可以通过`[]`来访问任意一个样本,下面获取第一个样本的图像和标签。
我们可以通过方括号`[]`来访问任意一个样本,下面获取第一个样本的图像和标签。
```{.python .input n=24}
feature, label = mnist_train[0]
......
# 自定义层
深度学习的一个魅力之处在于神经网络中各式各样的层,例如全连接层和后面章节中将要介绍的卷积层、池化层与循环层。虽然Gluon提供了大量常用的层,但有时候我们依然希望自定义层。本节将介绍如何使用NDArray来自定义一个Gluon的层,从而以后可以被重复调用。
深度学习的一个魅力在于神经网络中各式各样的层,例如全连接层和后面章节中将要介绍的卷积层、池化层与循环层。虽然Gluon提供了大量常用的层,但有时候我们依然希望自定义层。本节将介绍如何使用NDArray来自定义一个Gluon的层,从而可以被重复调用。
## 不含模型参数的自定义层
我们先介绍如何定义一个不含模型参数的自定义层。事实上,这和[“模型构造”](model-construction.md)一节中介绍的使用Block构造模型类似
我们先介绍如何定义一个不含模型参数的自定义层。事实上,这和[“模型构造”](model-construction.md)一节中介绍的使用Block类构造模型类似。以下的`CenteredLayer`类通过继承Block类自定义了一个将输入减掉均值后输出的层,并将层的计算定义在了`forward`函数里。这个层里不含模型参数
首先,导入本节中实验需要的包或模块。
```{.python .input}
```{.python .input n=1}
from mxnet import gluon, nd
from mxnet.gluon import nn
```
下面通过继承Block自定义了一个将输入减掉均值的层:CenteredLayer类,并将层的计算放在`forward`函数里。这个层里不含模型参数。
```{.python .input n=1}
class CenteredLayer(nn.Block):
def __init__(self, **kwargs):
super(CenteredLayer, self).__init__(**kwargs)
......@@ -25,7 +19,7 @@ class CenteredLayer(nn.Block):
return x - x.mean()
```
我们可以实例化这个层用起来
我们可以实例化这个层,然后做前向计算
```{.python .input n=2}
layer = CenteredLayer()
......@@ -40,7 +34,7 @@ net.add(nn.Dense(128),
CenteredLayer())
```
打印自定义层输出的均值。由于均值是浮点数,它的值是个很接近0的数。
下面打印自定义层各个输出的均值。由于均值是浮点数,它的值是个很接近0的数。
```{.python .input n=4}
net.initialize()
......@@ -50,7 +44,9 @@ y.mean().asscalar()
## 含模型参数的自定义层
我们还可以自定义含模型参数的自定义层。这样,自定义层里的模型参数就可以通过训练学出来了。我们在[“模型参数的访问、初始化和共享”](parameters.md)一节里介绍了Parameter类。其实,在自定义层的时候我们还可以使用Block自带的ParameterDict类型的成员变量`params`。顾名思义,这是一个由字符串类型的参数名字映射到Parameter类型的模型参数的字典。我们可以通过`get`函数从`ParameterDict`创建`Parameter`
我们还可以自定义含模型参数的自定义层。其中的模型参数可以通过训练学出。
[“模型参数的访问、初始化和共享”](parameters.md)一节里分别介绍了`Parameter`类和`ParameterDict`类。在自定义含模型参数的层时,我们可以利用Block类自带的`ParameterDict`类型的成员变量`params`。它是一个由字符串类型的参数名字映射到Parameter类型的模型参数的字典。我们可以通过`get`函数从`ParameterDict`创建`Parameter`实例。
```{.python .input n=7}
params = gluon.ParameterDict()
......@@ -58,7 +54,7 @@ params.get('param2', shape=(2, 3))
params
```
现在我们看下如何实现一个含权重参数和偏差参数的全连接层。它使用ReLU作为激活函数。其中`in_units``units`分别是输入单元个数和输出单元个数。
现在我们尝试实现一个含权重参数和偏差参数的全连接层。它使用ReLU作为激活函数。其中`in_units``units`分别是输入个数和输出个数。
```{.python .input n=19}
class MyDense(nn.Block):
......@@ -73,38 +69,38 @@ class MyDense(nn.Block):
return nd.relu(linear)
```
下面,我们实例化`MyDense`来看下它的模型参数。
下面,我们实例化`MyDense`并访问它的模型参数。
```{.python .input}
dense = MyDense(units=5, in_units=10)
dense = MyDense(units=3, in_units=5)
dense.params
```
我们可以直接使用自定义层做计算。
我们可以直接使用自定义层做前向计算。
```{.python .input n=20}
dense.initialize()
dense(nd.random.uniform(shape=(2, 10)))
dense(nd.random.uniform(shape=(2, 5)))
```
我们也可以使用自定义层构造模型。它用起来和Gluon的其他层很类似。
我们也可以使用自定义层构造模型。它和Gluon的其他层在使用上很类似。
```{.python .input n=19}
net = nn.Sequential()
net.add(MyDense(32, in_units=64),
MyDense(2, in_units=32))
net.add(MyDense(8, in_units=64),
MyDense(1, in_units=8))
net.initialize()
net(nd.random.uniform(shape=(2, 64)))
```
## 小结
* 使用Block,我们可以方便地自定义层
* 我们可以通过Block类自定义神经网络中的层,从而可以被重复调用
## 练习
* 如何修改自定义层里模型参数的默认初始化函数?
* 自定义一个层,使用它做一次前向计算。
## 扫码直达[讨论区](https://discuss.gluon.ai/t/topic/1256)
......
# 模型参数的延后初始化
如果你注意到了上节练习,你会发现在`net.initialize()`后和`net(x)`前模型参数的形状都是空。直觉上`initialize`会完成了所有参数初始化过程,然而Gluon中这是不一定的。我们这里详细讨论这个话题。
如果你做了上节练习,你会发现模型`net`在调用初始化函数`initialize`之后、且在做前向计算`net(x)`之前时,权重参数的形状中出现了0。虽然直觉上`initialize`完成了所有参数初始化过程,然而这在Gluon中却是不一定的。我们在本节中详细讨论这个话题。
## 延后的初始化
注意到前面使用Gluon的章节里,我们在创建全连接层时都没有指定输入大小。例如在一直使用的多层感知机例子里,我们创建了输出大小为256的隐藏层。但是当在调用`initialize`函数的时候,我们并不知道这个层的参数到底有多大,因为它的输入大小仍然是未知。只有在当我们将形状是`(2,20)``x`输入进网络时,我们这时候才知道这一层的参数大小应该是`(256,20)`。所以这个时候我们才能真正开始初始化参数。
## 延后初始化
让我们使用上节定义的MyInit类来清楚地演示这一个过程。下面我们创建多层感知机,然后使用MyInit实例来进行初始化。
也许你早就注意到了,在前面使用Gluon创建的全连接层都没有指定输入个数。例如在上一节使用的多层感知机`net`里,我们创建的隐藏层仅仅指定了输出大小为256。当调用`initialize`函数时,由于隐藏层输入个数依然未知,系统也无法得知该层权重参数的形状。只有在当我们将形状是`(2,20)`的输入`x`传进网络做前向计算`net(x)`时,系统才推断出该层的权重参数形状为`(256,20)`。因此,这时候我们才能真正开始初始化参数。
让我们使用上节定义的`MyInit`类来演示这一过程。我们创建多层感知机,并使用`MyInit`实例来初始化模型参数。
```{.python .input n=22}
from mxnet import init, nd
......@@ -24,36 +25,36 @@ net.add(nn.Dense(256, activation='relu'),
net.initialize(init=MyInit())
```
注意到MyInit在调用时会打印信息,但当前我们并没有看到相应的日志。下面我们执行前向计算。
注意,虽然`MyInit`被调用时会打印模型参数的相关信息,但上面的`initialize`函数执行完并未打印任何信息。由此可见,调用`initialize`函数时并没有真正初始化参数。下面我们定义输入并执行一次前向计算。
```{.python .input n=25}
x = nd.random.uniform(shape=(2, 20))
y = net(x)
```
这时候系统根据输入`x`的形状自动推测出所有层参数形状,例如隐藏层大小是`(256,20)`,并创建参数。之后调用MyInit实例来进行初始化,然后再进行前向计算。
这时候,有关模型参数的信息被打印出来。在根据输入`x`做前向计算时,系统能够根据输入的形状自动推断出所有层的权重参数的形状。系统在创建这些参数之后,调用`MyInit`实例对它们进行初始化,然后才进行前向计算。
当然,这个初始化只会在第一次执行被调用。之后我们再运行`net(x)`时则不会重新初始化,即我们不会再次看到MyInit实例的输出。
当然,这个初始化只会在第一次前向计算时被调用。之后我们再运行前向计算`net(x)`时则不会重新初始化,因此不会再次产生`MyInit`实例的输出。
```{.python .input}
y = net(x)
```
这种系统将真正的参数初始化延后到获得足够信息时再执行的行为被我们称之为延后初始化。它可以让模型创建更加简单,因为我们只需要定义每个层的输出大小,而不用去推测它们的输入大小。这个对于之后将介绍的多达数十甚至数百层的网络尤其有用
系统将真正的参数初始化延后到获得足够信息时才执行的行为称为延后初始化(deferred initialization)。它可以让模型的创建更加简单:我们只需要定义每个层的输出大小,而不用人工推测它们的输入个数。这对于之后将介绍的多达数十甚至数百层的网络来说尤其方便
当然正如本节开头提到的那样,延后初始化也可能会造成一定的困解。在调用第一次前向计算之前我们无法直接操作模型参数。例如无法使用`data``set_data`函数来获取和改写参数。所以经常我们会额外调用一次`net(x)`来使得参数被真正地初始化。
然而,任何事物都有两面性。正如本节开头提到的那样,延后初始化也可能会带来一定的困惑。在第一次前向计算之前,我们无法直接操作模型参数,例如无法使用`data``set_data`函数来获取和修改参数。所以,我们经常会额外做一次前向计算来迫使参数被真正地初始化。
## 避免延后初始化
当系统在调用`initialize`函数时能够知道所有参数形状,那么延后初始化就不会发生。我们这里给两个这样的情况。
如果系统在调用`initialize`函数时能够知道所有参数的形状,那么延后初始化就不会发生。我们在这里分别介绍两种这样的情况。
第一个是模型已经被初始化过,而且我们要对模型进行重新初始化时。因为我们知道参数大小不会变,所以能够立即进行重新初始化。
第一种情况是我们要对已初始化的模型重新初始化时。因为参数形状不会发生变化,所以系统能够立即进行重新初始化。
```{.python .input}
net.initialize(init=MyInit(), force_reinit=True)
```
第二种情况是我们在创建层的时候指定了每个层的输入大小,使得系统不需要额外的信息来推测参数形状。下例中我们通过`in_units`来指定每个全连接层的输入大小,使得初始化能够立即进行
第二种情况是我们在创建层的时候指定了它的输入个数,使得系统不需要额外的信息来推测参数形状。下例中我们通过`in_units`来指定每个全连接层的输入个数,使得初始化能够在`initialize`函数被调用时立即发生
```{.python .input}
net = nn.Sequential()
......@@ -65,11 +66,14 @@ net.initialize(init=MyInit())
## 小结
* 在调用`initialize`函数时,系统可能将真正的初始化延后到后面,例如前向计算时,来执行。这样做的主要好处是让模型定义可以更加简单。
* 系统将真正的参数初始化延后到获得足够信息时才执行的行为叫做延后初始化。
* 延后初始化的主要好处是让模型构造更加简单。例如,我们无需人工推测每个层的输入个数。
* 我们也可以避免延后初始化。
## 练习
* 如果在下一次`net(x)`前改变`x`形状,包括批量大小和特征大小,会发生什么?
* 如果在下一次前向计算`net(x)`前改变输入`x`的形状,包括批量大小和输入个数,会发生什么?
## 扫码直达[讨论区](https://discuss.gluon.ai/t/topic/6320)
......
......@@ -11,88 +11,85 @@ from mxnet.gluon import nn
net = nn.Sequential()
net.add(nn.Dense(256, activation='relu'))
net.add(nn.Dense(10))
net.initialize()
net.initialize() # 使用默认初始化方式。
x = nd.random.uniform(shape=(2, 20))
y = net(x)
y = net(x) # 前向计算。
```
## 访问模型参数
我们知道可以通过`[]`来访问Sequential类构造出来的网络的特定层。对于带有模型参数的层,我们可以通过Block类的`params`属性来得到它包含的所有参数。例如我们查看隐藏层的参数:
对于使用Sequential类构造的神经网络,我们可以通过方括号`[]`来访问网络的任一层。回忆一下上一节中提到的Sequential类与Block类的继承关系。对于Sequential实例中含模型参数的层,我们可以通过Block类的`params`属性来访问该层包含的所有参数。下面,访问多层感知机`net`中隐藏层的所有参数。索引0表示隐藏层为Sequential实例最先添加的层。
```{.python .input n=2}
net[0].params
net[0].params, type(net[0].params)
```
可以看到我们得到了一个由参数名称映射到参数实例的字典。第一个参数的名称为`dense0_weight`,它由`net[0]`的名称(`dense0_`)和自己的变量名(`weight`)组成。而且可以看到它参数的形状为`(256,20)`,且数据类型为32位浮点数。
为了访问特定参数,我们既可以通过名字来访问字典里的元素,也可以直接使用它的变量名。下面两种方法是等价的,但通常后者的代码可读性更好。
可以看到,我们得到了一个由参数名称映射到参数实例的字典(类型为`ParameterDict`类)。其中权重参数的名称为`dense0_weight`,它由`net[0]`的名称(`dense0_`)和自己的变量名(`weight`)组成。而且可以看到,该参数的形状为`(256,20)`,且数据类型为32位浮点数(`float32`)。为了访问特定参数,我们既可以通过名字来访问字典里的元素,也可以直接使用它的变量名。下面两种方法是等价的,但通常后者的代码可读性更好。
```{.python .input n=3}
net[0].params['dense0_weight'], net[0].weight
```
Gluon里参数类型为Parameter类,其包含参数权重和它对应的梯度,它们可以分别通过`data``grad`函数来访问。因为我们随机初始化了权重,所以它是一个由随机数组成的形状为`(256, 20)`的NDArray.
Gluon里参数类型为Parameter类,它包含参数和梯度的数值,可以分别通过`data``grad`函数来访问。因为我们随机初始化了权重,所以权重参数是一个由随机数组成的形状为`(256, 20)`的NDArray.
```{.python .input n=4}
net[0].weight.data()
```
梯度的形状跟权重一样。但由于我们还没有进行反向传播计算,所以它的值全为0.
梯度的形状跟权重的一样。由于我们还没有进行反向传播计算,所以梯度的值全为0.
```{.python .input n=5}
net[0].weight.grad()
```
类似我们可以访问其他的层的参数。例如输出层的偏差权重:
类似地,我们可以访问其他层的参数,例如输出层的偏差值。
```{.python .input n=6}
net[1].bias.data()
```
最后,我们可以使用`collect_params`函数来获取`net`实例所有嵌套(例如通过`add`函数嵌套)的层所包含的所有参数。它返回的同样是一个参数名称到参数实例的字典。
最后,我们可以使用`collect_params`函数来获取`net`变量所有嵌套(例如通过`add`函数嵌套)的层所包含的所有参数。它返回的同样是一个由参数名称到参数实例的字典。
```{.python .input n=11}
```{.python .input n=7}
net.collect_params()
```
这个函数可以通过正则表达式来匹配参数名,从而筛选需要的参数
这个函数可以通过正则表达式来匹配参数名,从而筛选需要的参数
```{.python .input}
```{.python .input n=8}
net.collect_params('.*weight')
```
## 初始化模型参数
我们在[“数值稳定性和模型初始化”](../chapter_deep-learning-basics/numerical-stability-and-init.md)一节中描述了模型的默认初始化方法:权重参数元素为[-0.07, 0.07]之间均匀分布的随机数,偏差参数则全为0。但我们经常需要使用其他的方法来初始化权重。MXNet的`init`模块里提供了多种预设的初始化方法。在下面的例子中,我们将权重参数初始化成均值为0、标准差为0.01的正态分布随机数,并依然将偏差参数清零。
当使用默认的模型初始化,Gluon会将权重参数元素初始化为[-0.07, 0.07](一个之前流行的初始化区间)之间均匀分布的随机数,偏差参数则全为0。但我们经常需要使用其他的方法来初始化权重,MXNet的`init`模块里提供了多种预设的初始化方法。例如下面例子我们将权重参数初始化成均值为0,标准差为0.01的正态分布随机数。
```{.python .input n=7}
# 非首次对模型初始化需要指定 force_reinit。
```{.python .input n=9}
# 非首次对模型初始化需要指定 force_reinit 为真。
net.initialize(init=init.Normal(sigma=0.01), force_reinit=True)
net[0].weight.data()[0]
```
或者初始成常数。
以下使用了常数来初始化权重参数。
```{.python .input}
```{.python .input n=10}
net.initialize(init=init.Constant(1), force_reinit=True)
net[0].weight.data()[0]
```
如果想只对某个特定参数进行初始化,我们可以调用`Paramter`类的`initialize`函数,它的使用跟Block类提供的一致。下例中我们对第一个隐藏层的权重使用Xavier初始化方法。
如果想只对某个特定参数进行初始化,我们可以调用`Paramter`类的`initialize`函数,它与Block类提供的`initialize`函数的使用方法一致。下例中我们对隐藏层的权重使用Xavier初始化方法。
```{.python .input n=8}
```{.python .input n=11}
net[0].weight.initialize(init=init.Xavier(), force_reinit=True)
net[0].weight.data()[0]
```
## 自定义初始化方法
有时候我们需要的初始化方法并没有在`init`模块中提供。这时,我们可以实现一个Initializer类的子类使得我们可以跟前面使用`init.Normal`那样使用它。通常,我们只需要实现`_init_weight`这个函数,将其传入的NDArray修改成需要的内容。下面例子里我们把权重初始化成`[-10,-5]``[5,10]`两个区间里均匀分布的随机数。
有时候我们需要的初始化方法并没有在`init`模块中提供。这时,我们可以实现一个`Initializer`类的子类,从而能够像使用其他初始化方法那样使用它。通常,我们只需要实现`_init_weight`这个函数,并将其传入的NDArray修改成初始化的结果。在下面的例子里,我们令权重有一半概率初始化为0,有另一半概率初始化为$[-10,-5]$和$[5,10]$两个区间里均匀分布的随机数。
```{.python .input n=9}
```{.python .input n=12}
class MyInit(init.Initializer):
def _init_weight(self, name, data):
print('Init', name, data.shape)
......@@ -105,18 +102,16 @@ net[0].weight.data()[0]
此外,我们还可以通过`Parameter`类的`set_data`函数来直接改写模型参数。例如下例中我们将隐藏层参数在现有的基础上加1。
```{.python .input n=10}
```{.python .input n=13}
net[0].weight.set_data(net[0].weight.data() + 1)
net[0].weight.data()[0]
```
## 共享模型参数
在有些情况下,我们希望在多个层之间共享模型参数。我们在[“模型构造”](model-construction.md)这一节看到了如何在Block类里`forward`函数里多次调用同一个类来完成。这里将介绍另外一个方法,它在构造层的时候指定使用特定的参数。如果不同层使用同一份参数,那么它们不管是在前向计算还是反向传播时都会共享共同的参数。
在下面例子里,我们让模型的第二隐藏层和第三隐藏层共享模型参数。
在有些情况下,我们希望在多个层之间共享模型参数。[“模型构造”](model-construction.md)一节介绍了如何在Block类的`forward`函数里多次调用同一个层来计算。这里再介绍另外一个方法,它在构造层的时候指定使用特定的参数。如果不同层使用同一份参数,那么它们在前向计算和反向传播时都会共享相同的参数。在下面例子里,我们让模型的第二隐藏层(`shared`变量)和第三隐藏层共享模型参数。
```{.python .input}
```{.python .input n=14}
net = nn.Sequential()
shared = nn.Dense(8, activation='relu')
net.add(nn.Dense(8, activation='relu'),
......@@ -137,12 +132,14 @@ net[1].weight.data()[0] == net[2].weight.data()[0]
## 小结
* 我们有多种方法来访问、初始化和共享模型参数。
* 我们可以自定义初始化方法。
## 练习
* 查阅[MXNet文档](https://mxnet.incubator.apache.org/api/python/model.html#initializer-api-reference),了解不同的参数初始化方式
* 尝试在`net.initialize()``net(x)`前访问模型参数,看看会发生什么
* 构造一个含共享参数层的多层感知机并训练。观察每一层的模型参数和梯度计算
* 查阅有关`init`模块的MXNet文档,了解不同的参数初始化方法
* 尝试在`net.initialize()``net(x)`前访问模型参数,观察模型参数的形状
* 构造一个含共享参数层的多层感知机并训练。在训练过程中,观察每一层的模型参数和梯度
## 扫码直达[讨论区](https://discuss.gluon.ai/t/topic/987)
......
......@@ -5,7 +5,7 @@
## 读写NDArrays
我们首先看如何读写NDArray。我们可以直接使用`save``load`函数分别存储和读取NDArray。下面的例子我们创建`x`,并将其存在文件名同为`x`的文件里。
我们可以直接使用`save``load`函数分别存储和读取NDArray。下面的例子创建了NDArray变量`x`,并将其存在文件名同为`x`的文件里。
```{.python .input}
from mxnet import nd
......@@ -15,14 +15,14 @@ x = nd.ones(3)
nd.save('x', x)
```
然后我们再将数据从文件读回内存。
然后我们将数据从存储的文件读回内存。
```{.python .input}
x2 = nd.load('x')
x2
```
同样我们可以存储一列NDArray并读回内存。
我们还可以存储一列NDArray并读回内存。
```{.python .input n=2}
y = nd.zeros(4)
......@@ -31,7 +31,7 @@ x2, y2 = nd.load('xy')
(x2, y2)
```
或者是一个从字符串到NDArray的字典。
我们甚至可以存储并读取一个从字符串映射到NDArray的字典。
```{.python .input n=4}
mydict = {'x': x, 'y': y}
......@@ -42,9 +42,7 @@ mydict2
## 读写Gluon模型的参数
Block类提供了`save_parameters``load_parameters`函数来读写模型参数。它实际做的事情就是将所有参数组成一个从名称到NDArray的字典并保存到文件。读取的时候会根据参数名称找到对应的NDArray并赋值。下面的例子我们首先创建一个多层感知机,初始化后将模型参数保存到文件里。
下面,我们创建一个多层感知机。
除了NDArray以外,我们还可以读写Gluon模型的参数。Gluon的Block类提供了`save_parameters``load_parameters`函数来读写模型参数。为了演示方便,我们先创建一个多层感知机,并将其初始化。回忆[“模型参数的延后初始化”](deferred-init.md)一节,由于延后初始化,我们需要先运行一次前向计算才能实际初始化模型参数。
```{.python .input n=6}
class MLP(nn.Block):
......@@ -58,27 +56,25 @@ class MLP(nn.Block):
net = MLP()
net.initialize()
# 由于延后初始化,我们需要先运行一次前向计算才能实际初始化模型参数。
x = nd.random.uniform(shape=(2, 20))
y = net(x)
```
下面我们把该模型的参数存起来
下面把该模型的参数存成文件,文件名为“mlp.params”
```{.python .input}
filename = 'mlp.params'
net.save_parameters(filename)
```
然后,我们再实例化一次我们定义的多层感知机。但跟前面不一样的是我们不是随机初始化模型参数,而是直接读取保存在文件里的参数。
接下来,我们再实例化一次定义好的多层感知机。跟随机初始化模型参数不同,我们在这里直接读取保存在文件里的参数。
```{.python .input n=8}
net2 = MLP()
net2.load_parameters(filename)
```
因为这两个实例都有同样的参数,那么对同一个`x`的计算结果将会是一样
因为这两个实例都有同样的模型参数,那么对同一个输入`x`的计算结果将会是一样。我们来验证一下
```{.python .input}
y2 = net2(x)
......@@ -87,8 +83,8 @@ y2 == y
## 小结
* 通过`save``load`可以很方便地读写NDArray。
* 通过`load_parameters``save_parameters`可以很方便地读写Gluon的模型参数。
* 通过`save``load`函数可以很方便地读写NDArray。
* 通过`load_parameters``save_parameters`函数可以很方便地读写Gluon模型的参数。
## 练习
......
......@@ -26,7 +26,7 @@
本书的一大特点是每一节都是可以运行的。你可以改动代码后重新运行来查看它对结果造成的影响。我们认为这种交互式的学习体验对于学习深度学习非常重要。因为深度学习目前并没有很好的理论解释框架,很多论断只可意会。文字解释在这时候可能较苍白,而且不足以覆盖所有细节。你需要通过不断改动代码、观察运行结果和总结经验来加深理解并获得领悟。
本书的代码基于Apache MXNet实现。MXNet是一个开源的深度学习框架。它是AWS(亚马逊云计算服务)首选的深度学习框架,也被众多学校和公司使用。本书所有代码已在MXNet 1.3.0下测试通过。但由于深度学习发展极为迅速,未来版本的MXNet可能会造成书中部分代码无法正常运行。遇到类似问题可参考[“安装和运行”](../chapter_prerequisite/install.md)一节来更新代码和运行环境。此外,为避免重复描述,我们将本书多次使用的函数、类等封装在`gluonbook`包中。这些函数、类等的定义所在的章节已在附录中[“gluonbook包索引”](../chapter_appendix/gluonbook.md)里列出。
本书的代码基于Apache MXNet实现。MXNet是一个开源的深度学习框架。它是AWS(亚马逊云计算服务)首选的深度学习框架,也被众多学校和公司使用。本书所有代码已在MXNet 1.2.1下测试通过。但由于深度学习发展极为迅速,未来版本的MXNet可能会造成书中部分代码无法正常运行。遇到类似问题可参考[“安装和运行”](../chapter_prerequisite/install.md)一节来更新代码和运行环境。此外,为避免重复描述,我们将本书多次使用的函数、类等封装在`gluonbook`包中。这些函数、类等的定义所在的章节已在附录中[“gluonbook包索引”](../chapter_appendix/gluonbook.md)里列出。
本书可以作为MXNet入门书使用。但我们提供代码的主要目的在于增加一个在文字、图像和公式外的方式来学习深度学习算法,和一个交互式的环境来理解各个模型和算法在真实数据上的实际效果。我们只使用了MXNet的`ndarray``autograd``gluon`等模块的基础功能,使得你可以尽可能了解深度学习算法的实现细节。即便你在研究和工作中使用了其他深度学习框架,我们也希望这些代码能帮助你更好地理解深度学习算法。
......
......@@ -5,6 +5,9 @@
首先导入实验所需的包或模块。
```{.python .input n=263}
import sys
sys.path.insert(0, '..')
import collections
import gluonbook as gb
import math
......@@ -209,7 +212,7 @@ embed(x)
### 小批量乘法
我们可以将背景词向量和噪音词向量合并起来,然后使用一次矩阵乘法来计算中心词向量来计算它们的内积$\boldsymbol{v}_{c}^\top\left[\boldsymbol{u}_{o_1}, \ldots, \boldsymbol{u}_{o_{n+m}}\right]$。我们需要对小批量里的每个中心词逐一做此运算,虽然可以用for循环来实现,但使用`batch_dot`通常可以得到更好的性能。假设$\boldsymbol{X}=\left[\boldsymbol{X}_1,\ldots,\boldsymbol{X}_n\right]$且$\boldsymbol{Y}=\left[\boldsymbol{Y}_1,\ldots,\boldsymbol{Y}_n\right]$,如果$\boldsymbol{Z}=\text{batch_dot}(\boldsymbol{X}, \boldsymbol{Y})$,那么$\boldsymbol{Z}=\left[\boldsymbol{X}_1\boldsymbol{Y}_1,\ldots,\boldsymbol{X}_n\boldsymbol{Y}_n\right]$。
我们可以将背景词向量和噪音词向量合并起来,然后使用一次矩阵乘法来计算中心词向量来计算它们的内积$\boldsymbol{v}_{c}^\top\left[\boldsymbol{u}_{o_1},\ldots,\boldsymbol{u}_{o_{n+m}}\right]$。我们需要对小批量里的每个中心词逐一做此运算,虽然可以用for循环来实现,但使用`batch_dot`(用$\text{bd}$表示)通常可以得到更好的性能。假设$\boldsymbol{X}=\left[\boldsymbol{X}_1,\ldots,\boldsymbol{X}_n\right]$且$\boldsymbol{Y}=\left[\boldsymbol{Y}_1,\ldots,\boldsymbol{Y}_n\right]$,如果$\boldsymbol{Z}=\text{bd}(\boldsymbol{X},\boldsymbol{Y})$,那么$\boldsymbol{Z}=\left[\boldsymbol{X}_1\boldsymbol{Y}_1,\ldots,\boldsymbol{X}_n\boldsymbol{Y}_n\right]$。
```{.python .input n=259}
X = nd.ones((2, 3, 4))
......
......@@ -141,7 +141,7 @@ show_trace_2d(f_2d, train_2d(gd_2d))
## 随机梯度下降
在深度学习里,通常目标函数是训练样本上损失函数的平均。设$f_i(\boldsymbol{x})$是有关索引为$i$的训练数据样本的损失函数,$n$是训练数据样本数,那么目标函数定义为
在深度学习里,通常目标函数是训练样本上损失函数的平均。设$f_i(\boldsymbol{x})$是有关索引为$i$的训练数据样本的损失函数,$n$是训练数据样本数,$\boldsymbol{x}$是模型的参数向量,那么目标函数定义为
$$f(\boldsymbol{x}) = \frac{1}{n} \sum_{i = 1}^n f_i(\boldsymbol{x}).$$
......@@ -171,7 +171,7 @@ def sgd_2d(x1, x2, s1, s2):
show_trace_2d(f_2d, train_2d(sgd_2d))
```
可以看到随机梯度下降的更新轨迹相对于梯度下降更加曲折。因为加入的噪音(实际中,它来自样本的噪音)使得梯度的准确度下降,所以在使用同样的超参数的情况下,随机梯度下降收敛到的值相对梯度下降来说更最优值远。但因为随机梯度下降每一次迭代的计算比梯度下降更加简单,在同样运行时间下,随机梯度下降可以进行更多次的自变量迭代,它最终得到的解的质量可能会比梯度下降更优。
可以看到随机梯度下降的更新轨迹相对于梯度下降更加曲折。因为加入的噪音(实际中,它来自样本的噪音)使得梯度的准确度下降,所以在使用同样的超参数的情况下,随机梯度下降收敛到的值相对梯度下降来说离最优值更远。但因为随机梯度下降每一次迭代的计算比梯度下降更加简单,在同样运行时间下,随机梯度下降可以进行更多次的自变量迭代,它最终得到的解的质量可能会比梯度下降更优。
## 小结
......
......@@ -6,4 +6,4 @@ dependencies:
- pandas=0.23.2
- pip:
- requests==2.18.4
- mxnet==1.3.0
- mxnet==1.2.1
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册