@@ -21,22 +21,11 @@ with net.name_scope():
21
21
22
22
``` {.python .input n=2}
23
23
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))
27
26
```
28
27
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是输出层单元个数。
40
29
41
30
实际上,这个多层感知机计算的例子涉及到了深度学习计算的方方面面,例如模型的构造、模型参数的初始化、模型的层等。在本章中,我们将主要使用` Gluon ` 来介绍深度学习计算中的重要组成部分:模型构造、模型参数、自定义层、读写和GPU计算。通过本章的学习,读者将能够动手实现和训练更复杂的深度学习模型,例如之后章节里的一些模型。
42
31
@@ -53,6 +42,7 @@ class MLP(nn.Block):
53
42
with self.name_scope():
54
43
self.hidden = nn.Dense(256)
55
44
self.output = nn.Dense(10)
45
+
56
46
def forward(self, x):
57
47
return self.output(nd.relu(self.hidden(x)))
58
48
```
@@ -73,23 +63,11 @@ class MLP(nn.Block):
73
63
``` {.python .input n=12}
74
64
net2 = MLP()
75
65
net2.initialize()
76
- x = nd.random.uniform(shape=(4, 20))
77
- y = net2(x)
78
- print(y)
66
+ print(net2(x))
79
67
print('hidden layer name with default prefix:', net2.hidden.name)
80
68
print('output layer name with default prefix:', net2.output.name)
81
69
```
82
70
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)>\n hidden layer name with default prefix: mlp1_dense0\n output layer name with default prefix: mlp1_dense1\n "
89
- }
90
- ]
91
- ```
92
-
93
71
在上面的例子中,隐藏层和输出层的名字前都加了默认前缀。接下来我们通过` prefix ` 指定它们的名字前缀。
94
72
95
73
``` {.python .input n=5}
@@ -98,16 +76,6 @@ print('hidden layer name with "my_mlp_" prefix:', net3.hidden.name)
98
76
print('output layer name with "my_mlp_" prefix:', net3.output.name)
99
77
```
100
78
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\n output layer name with \" my_mlp_\" prefix: my_mlp_dense1\n "
107
- }
108
- ]
109
- ```
110
-
111
79
接下来,我们重新定义` MLP_NO_NAMESCOPE ` 类。它和` MLP ` 的区别就是不含` with self.name_scope(): ` 。这是,隐藏层和输出层的名字前都不再含指定的前缀` prefix ` 。
112
80
113
81
``` {.python .input n=6}
@@ -116,6 +84,7 @@ class MLP_NO_NAMESCOPE(nn.Block):
116
84
super(MLP_NO_NAMESCOPE, self).__init__(**kwargs)
117
85
self.hidden = nn.Dense(256)
118
86
self.output = nn.Dense(10)
87
+
119
88
def forward(self, x):
120
89
return self.output(nd.relu(self.hidden(x)))
121
90
@@ -124,22 +93,12 @@ print('hidden layer name without prefix:', net4.hidden.name)
124
93
print('output layer name without prefix:', net4.output.name)
125
94
```
126
95
127
- ``` {.json .output n=6}
128
- [
129
- {
130
- "name" : " stdout" ,
131
- "output_type" : " stream" ,
132
- "text" : " hidden layer name without prefix: dense0\n output 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 ` 。
138
97
139
98
` nn.Block ` 类主要提供模型参数的存储、模型计算的定义和自动求导。读者也许已经发现了,以上` nn.Block ` 的子类中并没有定义如何求导,或者是` backward ` 函数。事实上,` MXNet ` 会使用` autograd ` 对` forward() ` 自动生成相应的` backward() ` 函数。
140
99
141
100
142
- ## ` nn.Sequential ` 是特殊的` nn.Block `
101
+ ### ` nn.Sequential ` 是特殊的` nn.Block `
143
102
144
103
在` Gluon ` 里,` nn.Sequential ` 是` nn.Block ` 的子类。它也可以被看作是一个` nn.Block ` 的容器:通过` add ` 函数来添加` nn.Block ` 。在` forward() ` 函数里,` nn.Sequential ` 把添加进来的` nn.Block ` 逐一运行。
145
104
@@ -149,8 +108,10 @@ print('output layer name without prefix:', net4.output.name)
149
108
class MySequential(nn.Block):
150
109
def __init__(self, **kwargs):
151
110
super(MySequential, self).__init__(**kwargs)
111
+
152
112
def add(self, block):
153
113
self._children.append(block)
114
+
154
115
def forward(self, x):
155
116
for block in self._children:
156
117
x = block(x)
@@ -165,99 +126,68 @@ with net5.name_scope():
165
126
net5.add(nn.Dense(256, activation="relu"))
166
127
net5.add(nn.Dense(10))
167
128
net5.initialize()
168
- y = net5(x)
169
- y
129
+ net5(x)
170
130
```
171
131
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
+ ### 构造更复杂的模型
184
133
185
- 可以看到, ` nn.Sequential ` 的主要好处是定义网络起来更加简单。但 ` nn.Block ` 可以提供更加灵活的网络定义。考虑下面这个例子
134
+ 与 ` nn.Sequential ` 相比,使用 ` nn.Block ` 可以构造更复杂的模型。下面是一个例子。
186
135
187
- ``` {.python .input n=21 }
136
+ ``` {.python .input n=9 }
188
137
class FancyMLP(nn.Block):
189
138
def __init__(self, **kwargs):
190
139
super(FancyMLP, self).__init__(**kwargs)
191
- self.weight = nd.random_uniform(shape=(256, 20))
140
+ self.rand_weight = nd.random_uniform(shape=(10, 20))
192
141
with self.name_scope():
193
- self.dense = nn.Dense(256 )
142
+ self.dense = nn.Dense(10 )
194
143
195
144
def forward(self, x):
196
145
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)
198
147
x = nd.relu(self.dense(x))
199
148
return x
200
149
```
201
150
202
- 看到这里我们直接手动创建和初始了权重 ` weight ` ,并重复用了 ` dense ` 的层 。测试一下:
151
+ 在这个 ` FancyMLP ` 模型中,我们使用了常数权重 ` rand_weight ` (注意它不是模型参数)、做了矩阵乘法操作( ` nd.dot ` )并重复使用了相同的 ` nn.Dense ` 层 。测试一下:
203
152
204
- ``` {.python .input n=22 }
153
+ ``` {.python .input n=10 }
205
154
fancy_mlp = FancyMLP()
206
155
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)
219
157
```
220
158
221
- ## ` nn.Block ` 和` nn.Sequential ` 的嵌套使用
222
-
223
- 现在我们知道了` nn ` 下面的类基本都是` nn.Block ` 的子类,他们可以很方便地嵌套使用。
159
+ 由于` nn.Sequential ` 是` nn.Block ` 的子类,它们还可以嵌套使用。下面是一个例子。
224
160
225
161
``` {.python .input n=11}
226
- class RecMLP (nn.Block):
162
+ class NestMLP (nn.Block):
227
163
def __init__(self, **kwargs):
228
- super(RecMLP , self).__init__(**kwargs)
164
+ super(NestMLP , self).__init__(**kwargs)
229
165
self.net = nn.Sequential()
230
166
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 )
234
170
235
171
def forward(self, x):
236
172
return nd.relu(self.dense(self.net(x)))
237
173
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))
252
179
```
253
180
254
181
## 小结
255
182
256
- * 不知道你同不同意,通过` nn.Block ` 来定义神经网络跟玩积木很类似。
183
+ * 我们可以通过` nn.Block ` 来构造复杂的模型。
184
+ * ` nn.Sequential ` 是特殊的` nn.Block ` 。
185
+
257
186
258
187
## 练习
259
188
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
+
261
191
262
192
## 扫码直达[ 讨论区] ( https://discuss.gluon.ai/t/topic/986 )
263
193
0 commit comments