Skip to content

Commit eefa5d0

Browse files
committed
block
1 parent b468091 commit eefa5d0

File tree

1 file changed

+36
-106
lines changed

1 file changed

+36
-106
lines changed

chapter_gluon-basics/block.md

+36-106
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,11 @@ with net.name_scope():
2121

2222
```{.python .input n=2}
2323
net.initialize()
24-
x = nd.random.uniform(shape=(4, 20))
25-
y = net(x)
26-
print(y)
24+
x = nd.random.uniform(shape=(2, 20))
25+
print(net(x))
2726
```
2827

29-
```{.json .output n=2}
30-
[
31-
{
32-
"name": "stdout",
33-
"output_type": "stream",
34-
"text": "\n[[ 0.03126615 0.04562764 0.00039857 -0.08772386 -0.05355632 0.02904574\n 0.08102557 -0.01433946 -0.04224151 0.06047882]\n [ 0.02871901 0.03652265 0.00630051 -0.05650971 -0.07189322 0.08615957\n 0.05951559 -0.06045965 -0.0299026 0.05651001]\n [ 0.02147349 0.04818896 0.05321142 -0.12616856 -0.0685023 0.09096345\n 0.04064304 -0.05064794 -0.02200242 0.04859561]\n [ 0.03780478 0.0751239 0.03290457 -0.11641113 -0.03254967 0.0586529\n 0.02542157 -0.01697343 -0.00049652 0.05892839]]\n<NDArray 4x10 @cpu(0)>\n"
35-
}
36-
]
37-
```
38-
39-
在上面的例子中,`net`的输入数据`x`包含4个样本,每个样本的特征向量长度为20(`shape=(4, 20)`)。在按照默认方式初始化好模型参数后,`net`计算得到一个$4 \times 10$的矩阵作为模型的输出。其中4是数据样本个数,10是输出层单元个数。
28+
在上面的例子中,`net`的输入数据`x`包含2个样本,每个样本的特征向量长度为20(`shape=(2, 20)`)。在按照默认方式初始化好模型参数后,`net`计算得到一个$2 \times 10$的矩阵作为模型的输出。其中4是数据样本个数,10是输出层单元个数。
4029

4130
实际上,这个多层感知机计算的例子涉及到了深度学习计算的方方面面,例如模型的构造、模型参数的初始化、模型的层等。在本章中,我们将主要使用`Gluon`来介绍深度学习计算中的重要组成部分:模型构造、模型参数、自定义层、读写和GPU计算。通过本章的学习,读者将能够动手实现和训练更复杂的深度学习模型,例如之后章节里的一些模型。
4231

@@ -53,6 +42,7 @@ class MLP(nn.Block):
5342
with self.name_scope():
5443
self.hidden = nn.Dense(256)
5544
self.output = nn.Dense(10)
45+
5646
def forward(self, x):
5747
return self.output(nd.relu(self.hidden(x)))
5848
```
@@ -73,23 +63,11 @@ class MLP(nn.Block):
7363
```{.python .input n=12}
7464
net2 = MLP()
7565
net2.initialize()
76-
x = nd.random.uniform(shape=(4, 20))
77-
y = net2(x)
78-
print(y)
66+
print(net2(x))
7967
print('hidden layer name with default prefix:', net2.hidden.name)
8068
print('output layer name with default prefix:', net2.output.name)
8169
```
8270

83-
```{.json .output n=12}
84-
[
85-
{
86-
"name": "stdout",
87-
"output_type": "stream",
88-
"text": "\n[[-0.03891213 -0.00315471 0.02346008 0.0115821 -0.0286346 0.04750714\n -0.04022511 0.03334583 -0.02862417 0.00103271]\n [-0.02815309 -0.02004078 0.05494304 -0.01509079 -0.02990984 0.0201303\n -0.06145176 0.00554352 0.00067628 -0.00712057]\n [-0.03600026 -0.01051114 0.03732241 0.00269452 -0.04287211 0.05638577\n -0.04959137 0.03910026 -0.04694562 0.04687501]\n [-0.0008434 -0.01940108 0.06147967 0.00818795 -0.06385357 -0.00276085\n -0.03296277 0.0426305 -0.01784454 -0.01684828]]\n<NDArray 4x10 @cpu(0)>\nhidden layer name with default prefix: mlp1_dense0\noutput layer name with default prefix: mlp1_dense1\n"
89-
}
90-
]
91-
```
92-
9371
在上面的例子中,隐藏层和输出层的名字前都加了默认前缀。接下来我们通过`prefix`指定它们的名字前缀。
9472

9573
```{.python .input n=5}
@@ -98,16 +76,6 @@ print('hidden layer name with "my_mlp_" prefix:', net3.hidden.name)
9876
print('output layer name with "my_mlp_" prefix:', net3.output.name)
9977
```
10078

101-
```{.json .output n=5}
102-
[
103-
{
104-
"name": "stdout",
105-
"output_type": "stream",
106-
"text": "hidden layer name with \"my_mlp_\" prefix: my_mlp_dense0\noutput layer name with \"my_mlp_\" prefix: my_mlp_dense1\n"
107-
}
108-
]
109-
```
110-
11179
接下来,我们重新定义`MLP_NO_NAMESCOPE`类。它和`MLP`的区别就是不含`with self.name_scope():`。这是,隐藏层和输出层的名字前都不再含指定的前缀`prefix`
11280

11381
```{.python .input n=6}
@@ -116,6 +84,7 @@ class MLP_NO_NAMESCOPE(nn.Block):
11684
super(MLP_NO_NAMESCOPE, self).__init__(**kwargs)
11785
self.hidden = nn.Dense(256)
11886
self.output = nn.Dense(10)
87+
11988
def forward(self, x):
12089
return self.output(nd.relu(self.hidden(x)))
12190
@@ -124,22 +93,12 @@ print('hidden layer name without prefix:', net4.hidden.name)
12493
print('output layer name without prefix:', net4.output.name)
12594
```
12695

127-
```{.json .output n=6}
128-
[
129-
{
130-
"name": "stdout",
131-
"output_type": "stream",
132-
"text": "hidden layer name without prefix: dense0\noutput layer name without prefix: dense1\n"
133-
}
134-
]
135-
```
136-
137-
最后需要指出的是,在`gluon`里,`nn.Block`是一个一般化的部件。整个神经网络可以是一个`nn.Block`,单个层也是一个`nn.Block`。我们还可以反复嵌套`nn.Block`来构建新的`nn.Block`
96+
需要指出的是,在`gluon`里,`nn.Block`是一个一般化的部件。整个神经网络可以是一个`nn.Block`,单个层也是一个`nn.Block`。我们还可以反复嵌套`nn.Block`来构建新的`nn.Block`
13897

13998
`nn.Block`类主要提供模型参数的存储、模型计算的定义和自动求导。读者也许已经发现了,以上`nn.Block`的子类中并没有定义如何求导,或者是`backward`函数。事实上,`MXNet`会使用`autograd``forward()`自动生成相应的`backward()`函数。
14099

141100

142-
## `nn.Sequential`是特殊的`nn.Block`
101+
### `nn.Sequential`是特殊的`nn.Block`
143102

144103
`Gluon`里,`nn.Sequential``nn.Block`的子类。它也可以被看作是一个`nn.Block`的容器:通过`add`函数来添加`nn.Block`。在`forward()`函数里,`nn.Sequential`把添加进来的`nn.Block`逐一运行。
145104

@@ -149,8 +108,10 @@ print('output layer name without prefix:', net4.output.name)
149108
class MySequential(nn.Block):
150109
def __init__(self, **kwargs):
151110
super(MySequential, self).__init__(**kwargs)
111+
152112
def add(self, block):
153113
self._children.append(block)
114+
154115
def forward(self, x):
155116
for block in self._children:
156117
x = block(x)
@@ -165,99 +126,68 @@ with net5.name_scope():
165126
net5.add(nn.Dense(256, activation="relu"))
166127
net5.add(nn.Dense(10))
167128
net5.initialize()
168-
y = net5(x)
169-
y
129+
net5(x)
170130
```
171131

172-
```{.json .output n=18}
173-
[
174-
{
175-
"data": {
176-
"text/plain": "\n[[ 9.5400095e-02 5.4852065e-02 1.7744238e-02 -1.3019045e-01\n -6.2494464e-03 -1.1951900e-02 1.0515226e-02 -6.5923519e-02\n -2.3991371e-02 1.1632015e-01]\n [ 9.6949443e-02 4.0487815e-02 -6.6828108e-03 -8.5242800e-02\n -1.1140321e-02 -1.1530784e-02 -6.3379779e-03 -4.9934912e-02\n -6.0178801e-02 4.9789295e-02]\n [ 1.1893168e-01 4.7171779e-02 1.1178985e-02 -1.2403722e-01\n -2.7897144e-03 -1.9721132e-02 -1.2032428e-02 -2.1957448e-02\n -2.9257955e-02 1.0800241e-01]\n [ 1.0659003e-01 7.6273575e-02 -4.2447657e-03 -1.0006615e-01\n 4.8290327e-05 -7.5394958e-03 -1.3146491e-02 -4.5292892e-02\n -5.3848229e-02 8.6303681e-02]]\n<NDArray 4x10 @cpu(0)>"
177-
},
178-
"execution_count": 18,
179-
"metadata": {},
180-
"output_type": "execute_result"
181-
}
182-
]
183-
```
132+
### 构造更复杂的模型
184133

185-
可以看到,`nn.Sequential`的主要好处是定义网络起来更加简单。但`nn.Block`可以提供更加灵活的网络定义。考虑下面这个例子
134+
`nn.Sequential`相比,使用`nn.Block`可以构造更复杂的模型。下面是一个例子。
186135

187-
```{.python .input n=21}
136+
```{.python .input n=9}
188137
class FancyMLP(nn.Block):
189138
def __init__(self, **kwargs):
190139
super(FancyMLP, self).__init__(**kwargs)
191-
self.weight = nd.random_uniform(shape=(256,20))
140+
self.rand_weight = nd.random_uniform(shape=(10, 20))
192141
with self.name_scope():
193-
self.dense = nn.Dense(256)
142+
self.dense = nn.Dense(10)
194143
195144
def forward(self, x):
196145
x = nd.relu(self.dense(x))
197-
x = nd.relu(nd.dot(x, self.weight)+1)
146+
x = nd.relu(nd.dot(x, self.rand_weight) + 1)
198147
x = nd.relu(self.dense(x))
199148
return x
200149
```
201150

202-
看到这里我们直接手动创建和初始了权重`weight`,并重复用了`dense`的层。测试一下:
151+
在这个`FancyMLP`模型中,我们使用了常数权重`rand_weight`(注意它不是模型参数)、做了矩阵乘法操作(`nd.dot`)并重复使用了相同的`nn.Dense`。测试一下:
203152

204-
```{.python .input n=22}
153+
```{.python .input n=10}
205154
fancy_mlp = FancyMLP()
206155
fancy_mlp.initialize()
207-
y = fancy_mlp(x)
208-
print(y.shape)
209-
```
210-
211-
```{.json .output n=22}
212-
[
213-
{
214-
"name": "stdout",
215-
"output_type": "stream",
216-
"text": "(4, 256)\n"
217-
}
218-
]
156+
fancy_mlp(x)
219157
```
220158

221-
## `nn.Block``nn.Sequential`的嵌套使用
222-
223-
现在我们知道了`nn`下面的类基本都是`nn.Block`的子类,他们可以很方便地嵌套使用。
159+
由于`nn.Sequential``nn.Block`的子类,它们还可以嵌套使用。下面是一个例子。
224160

225161
```{.python .input n=11}
226-
class RecMLP(nn.Block):
162+
class NestMLP(nn.Block):
227163
def __init__(self, **kwargs):
228-
super(RecMLP, self).__init__(**kwargs)
164+
super(NestMLP, self).__init__(**kwargs)
229165
self.net = nn.Sequential()
230166
with self.name_scope():
231-
self.net.add(nn.Dense(256, activation="relu"))
232-
self.net.add(nn.Dense(128, activation="relu"))
233-
self.dense = nn.Dense(64)
167+
self.net.add(nn.Dense(64, activation='relu'))
168+
self.net.add(nn.Dense(32, activation='relu'))
169+
self.dense = nn.Dense(16)
234170
235171
def forward(self, x):
236172
return nd.relu(self.dense(self.net(x)))
237173
238-
rec_mlp = nn.Sequential()
239-
rec_mlp.add(RecMLP())
240-
rec_mlp.add(nn.Dense(10))
241-
print(rec_mlp)
242-
```
243-
244-
```{.json .output n=11}
245-
[
246-
{
247-
"name": "stdout",
248-
"output_type": "stream",
249-
"text": "Sequential(\n (0): RecMLP(\n (net): Sequential(\n (0): Dense(None -> 256, Activation(relu))\n (1): Dense(None -> 128, Activation(relu))\n )\n (dense): Dense(None -> 64, linear)\n )\n (1): Dense(None -> 10, linear)\n)\n"
250-
}
251-
]
174+
nest_mlp = nn.Sequential()
175+
nest_mlp.add(NestMLP())
176+
nest_mlp.add(nn.Dense(10))
177+
nest_mlp.initialize()
178+
print(nest_mlp(x))
252179
```
253180

254181
## 小结
255182

256-
* 不知道你同不同意,通过`nn.Block`来定义神经网络跟玩积木很类似。
183+
* 我们可以通过`nn.Block`来构造复杂的模型。
184+
* `nn.Sequential`是特殊的`nn.Block`
185+
257186

258187
## 练习
259188

260-
* 如果把`RecMLP`改成`self.denses = [nn.Dense(256), nn.Dense(128), nn.Dense(64)]``forward`就用for loop来实现,会有什么问题吗?
189+
* 如果把`NestMLP`中的`self.net``self.dense`改成`self.denses = [nn.Dense(64, activation='relu'), nn.Dense(32, activation='relu'), nn.Dense(16)]`,并在`forward`中用for循环实现相同计算,会有什么问题吗?
190+
261191

262192
## 扫码直达[讨论区](https://discuss.gluon.ai/t/topic/986)
263193

0 commit comments

Comments
 (0)