一、用Sequential来构建网络非常方便,并且不要忘记初始化
from mxnet import nd
from mxnet.gluon import nn
net = nn.Sequential()
with net.name_scope():
net.add(nn.Dense(256,activation = "relu"))
net.add(nn.Dense(10))
net.initialize()
x = nd.random_normal(shape = (4,20))
y = net(x)
二、如果想自己实现一个层,可以用下面的方法
class MLP(nn.Block):
# 声明带有模型参数的层,这里声明了两个全连接层
def __init__(self, **kwargs):
# 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数
# 参数,如“模型参数的访问、初始化和共享”一节将介绍的模型参数params
super(MLP, self).__init__(**kwargs)
self.hidden = nn.Dense(256, activation='relu') # 隐藏层
self.output = nn.Dense(10) # 输出层
# 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
def forward(self, x):
return self.output(self.hidden(x))
net1 = MLP()
net1.initialize()
y = net1(x)
虽然Sequential类可以使模型构造更加简单,且不需要定义forward函数,但直接继承Block类可以极大地拓展模型构造的灵活性。下面我们构造一个稍微复杂点的网络FancyMLP。在这个网络中,我们通过get_constant函数创建训练中不被迭代的参数,即常数参数。在前向计算中,除了使用创建的常数参数外,我们还使用NDArray的函数和Python的控制流,并多次调用相同的层。
class FancyMLP(nn.Block):
def __init__(self, **kwargs):
super(FancyMLP, self).__init__(**kwargs)
# 使用get_constant创建的随机权重参数不会在训练中被迭代(即常数参数)
self.rand_weight = self.params.get_constant(
'rand_weight', nd.random.uniform(shape=(20, 20)))
self.dense = nn.Dense(20, activation='relu')
def forward(self, x):
x = self.dense(x)
# 使用创建的常数参数,以及NDArray的relu函数和dot函数
x = nd.relu(nd.dot(x, self.rand_weight.data()) + 1)
# 复用全连接层。等价于两个全连接层共享参数
x = self.dense(x)
# 控制流,这里我们需要调用asscalar函数来返回标量进行比较
while x.norm().asscalar() > 1:
x /= 2
if x.norm().asscalar() < 0.8:
x *= 10
return x.sum()
net3 = FancyMLP()
net3.initialize()
y = net3(x)
三、Sequential和自定义的也可以混合使用
class RecMLP(nn.Block):
def __init__(self, **kwargs):
super(RecMLP, self).__init__(**kwargs)
self.net = nn.Sequential()
with self.name_scope():
self.net.add(nn.Dense(129,activation = "relu"))
self.net.add(nn.Dense(64,activation = "relu"))
self.dense = nn.Dense(256)
def forward(self, x):
return self.net(nn.relu(self.dense))
rec_mlp = nn.Sequential()
rec_mlp.add(RecMLP())
rec_mlp.add(nn.Dense(64,activation = "relu"))
rec_mlp.add(RecMLP())
rec_mlp.add(nn.Dense(10))
print(rec_mlp)
用以上这几种方式可以任意且便捷地定义网络层。
四、关于网络里的参数
可以通过【标号】来访问每一个层的名字、shape
w = net[0].weight
b = net[0].bias
print([w,b])
可以访问模型参数的数值和梯度
print(w.data())
print(w.grad())
print(b.data())
print(b.grad())
共享模型参数,当我们想让两个层的参数一致时,可以通过param来手动指定参数,而不是让系统自动生成
net4 = nn.Sequential()
with net4.name_scope():
net4.add(nn.Dense(4,in_units=4,activation = "relu"))
net4.add(nn.Dense(4,in_units = 4,activation = "relu",params = net4[-1].params))
net4.initialize()
print(net4[0].weight.data())
print(net4[1].weight.data())
读写ndarray,用save和load比较方便
from mxnet import nd
x = nd.ones(3)
y = nd.zeros(4)
filename = "data/test1.params"
nd.save(filename,[x,y])
#再把存好的文件读进来
a,b = nd.load(filename)
print(a,b)
五、下面写一个自定义网络
from mxnet import gluon, nd
from mxnet.gluon import nn
class CenteredLayer(nn.Block):
def __init__(self, **kwargs):
super(CenteredLayer, self).__init__(**kwargs)
def forward(self, x):
return x - x.mean()
net5 = nn.Sequential()
with net5.name_scope():
net5.add(nn.Dense(128))
net5.add(nn.Dense(64))
net5.add(CenteredLayer())
net5.initialize()
y = net5(nd.random.uniform(shape=(4, 8)))
y.mean().asscalar()
六、Dropout方法
dropout会把部分参数置为0,从而随机简化网络。
def dropout(X, drop_prob):
assert 0 <= drop_prob <= 1
keep_prob = 1 - drop_prob
# 这种情况下把全部元素都丢弃
if keep_prob == 0:
return X.zeros_like()
mask = nd.random.uniform(0, 1, X.shape) < keep_prob
return mask * X / keep_prob
a = nd.arange(20).reshape((4,5))
a = dropout(a,0.5)
七、AlexNet
net = nn.Sequential()
# 使用较大的11 x 11窗口来捕获物体。同时使用步幅4来较大幅度减小输出高和宽。这里使用的输出通
# 道数比LeNet中的也要大很多
net.add(nn.Conv2D(96, kernel_size=11, strides=4, activation='relu'),
nn.MaxPool2D(pool_size=3, strides=2),
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2D(256, kernel_size=5, padding=2, activation='relu'),
nn.MaxPool2D(pool_size=3, strides=2),
# 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。
# 前两个卷积层后不使用池化层来减小输入的高和宽
nn.Conv2D(384, kernel_size=3, padding=1, activation='relu'),
nn.Conv2D(384, kernel_size=3, padding=1, activation='relu'),
nn.Conv2D(256, kernel_size=3, padding=1, activation='relu'),
nn.MaxPool2D(pool_size=3, strides=2),
# 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
nn.Dense(4096, activation="relu"), nn.Dropout(0.5),
nn.Dense(4096, activation="relu"), nn.Dropout(0.5),
# 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Dense(10))
print(net)
AlexNet的整体结构如下,还是比较清楚的
八、VGG
def vgg_block(num_convs, num_channels):
blk = nn.Sequential()
for _ in range(num_convs):
blk.add(nn.Conv2D(num_channels, kernel_size=3,
padding=1, activation='relu'))
blk.add(nn.MaxPool2D(pool_size=2, strides=2))
return blk
def vgg(conv_arch):
net = nn.Sequential()
# 卷积层部分
for (num_convs, num_channels) in conv_arch:
net.add(vgg_block(num_convs, num_channels))
# 全连接层部分
net.add(nn.Dense(4096, activation='relu'), nn.Dropout(0.5),
nn.Dense(4096, activation='relu'), nn.Dropout(0.5),
nn.Dense(10))
return net
VGG关键是有一个vgg_block,然后搭建网络的过程中会重复用这个block。VGG16相比AlexNet的一个改进是采用连续的几个3x3的卷积核代替AlexNet中的较大卷积核(11x11,7x7,5x5)。