质量声明:原创文章,内容质量问题请评论吐槽。如对您产生干扰,可私信删除。
主要参考:李沐等:动手学深度学习-伯克利教材
文章目录
摘要: MXNet 基础和入门,包括基本单元、模型相关、数据处理等
MXNet 基础
导入MXNet
from mxnet import nd # Tensor模块
from mxnet import autograd # 自动求梯度
from mxnet import init # 初始化
from mxnet.gluon import nn # 神经网络基本结构
from mxnet.gluon import loss as gloss # 损失函数
张量模块 nd
注:NDArray基本与NunPy保持一致,这里只列举部分函数
# 一维向量
x = nd.array([0,1,2,3,4,5])
y = nd.arange(6)
z = nd.ones_like(x)
# 多维张量
A = nd.zeros((3,4,5)) # Channals x M x N
B = nd.ones((3,4,5), dtype="uint8") # 指定dtype,默认float32
C = nd.random.normal(0, 1, shape=(3,4,5)) # 指定mu, sigma, shape, dtype, ctx
# 属性
A.shape # 张量/向量形状
A.size # 元素总数
A.dtype # 数据类型
# 重构
D = x.reshape(2,3)
D = nd.reshape(x, (2,3))
# 拼接
L = nd.concat(A, C, dim=0) # 通道连接,层数增加
V = nd.concat(A, C, dim=1) # 行连接,各层行数增加
H = nd.concat(A, C, dim=2) # 列连接,各层列数增加
# 元素级运算
A + C; A - C; A * C; A / C; A ** 2
nd.exp(A); nd.sum(B); nd.mean(C); nd.sqrt(B)
# 矩阵乘法
nd.dot(D, D.T)
# 张量乘法
x = nd.reshape(nd.arange(12), shape=(3,2,2))
y = nd.reshape(nd.arange(12), shape=(2,2,3))
result = nd.dot(x,y)
result.shape == (3, 2, 2, 3)
# L2范数
nd.norm(B)
nd.sqrt(nd.sum(B**2))
# 上述运算结果均为<NDArray>类型,转为标量
nd.norm(B).asscalar() == 7
广播机制:(可能触发) 先将复制适当元素,使二者shape一致
A = nd.ones((3,2))
B = nd.ones((1,2))
A + B
节省内存开销:
# 计算过程中不开辟新的内存
before = id(A)
nd.elemwise_add(A, A, out=A)
id(A) == before
# 简化写法也能节省内存开销
before = id(A)
A += A
id(A) == before
转为numpy实例:
A.asnumpy()
自动求梯度 autograd
步骤:
- 变量赋初值
- 申请存储梯度的内存 param_in
.attach_grad()
- 记录与梯度相关的函数计算(正向)
with autograd_record()
- 自动求梯度(反向) param_out
.backward()
# 对 theta 求梯度
x = nd.ones((3,3))
theta = nd.random.normal(shape=3)
theta.attach_grad()
with autograd.record():
y = f(x, theta)
y.backward()
# 输出梯度
print(theta.grad)
其中f( )
为前向运算函数,一般为损失函数loss ={ model(x, w, b), y }
. 即使函数包含Python控制流(条件/循环),也有可能计算出变量的梯度.
基本单元介绍
基本网络结构/层
class | 名称 | 参数 |
---|---|---|
nn.Dense | 稠密层/全连接层 | units (int) ,activation (str) |
nn.Dropout | 神经元随机丢弃 | rate (float) |
nn.Conv2D | 二维卷积层 | channels (int) ,kernel_size (int or tuple/list of 2 int),strides ,padding ,activation |
nn.BatchNorm | 批量归一化 | / |
nn.MaxPool2D | 二维最大池化层 | pool_size ,strides ,padding |
nn.Flatten | ||
nn.Embedding |
自定义网络结构/层
- 不含模型参数的自定义层:
class CenteredLayer(nn.Block):
def __init__(self, **kwargs):
super(CenteredLayer, self).__init__(**kwargs)
def forward(self, x):
return x - x.mean()
- 含参的自定义层:
由于Block类
自带的的成员变量params
是ParameterDict字典类型,则可以通过字典方法get( )
来添加参数;调用是注意给定输入个数.
class MyDense(nn.Block):
# units为该层的输出个数,in_units为该层的输入个数
def __init__(self, units, in_units, **kwargs):
super(MyDense, self).__init__(**kwargs)
self.weight = self.params.get('weight', shape=(in_units, units))
self.bias = self.params.get('bias', shape=(units,))
def forward(self, x):
linear = nd.dot(x, self.weight.data()) + self.bias.data()
return nd.relu(linear) # 整合relu激活函数
激活函数
-
参考博文:神经网络中的各种激活函数
官方文档:mxnet.ndarray.Activation
示例:nn.Dense(360, activation = "relu" )
-
sigmoid
y = 1 1 + e x p ( − x ) y = \frac{1}{1 + exp(-x)} y=1+exp(−x)1
- Sigmoid函数的输出映射在(0,1)之间,可以用来做二分类,单调连续,输出范围有限,优化稳定,可以用作输出层。
- 求导容易
- 由于其软饱和性,反向传播时,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练,导致训练出现问题。
- 激活函数计算量大,反向传播求误差梯度时,求导涉及除法。
- 其输出并不是以0为中心的。这个特性会导致后面网络层的输入也不是零中心的,进而影响梯度下降的运作
tanh
y = e x p ( x ) − e x p ( − x ) e x p ( x ) + e x p ( − x ) y = \frac{exp(x) - exp(-x)}{exp(x) + exp(-x)} y=exp(x)+exp(−x)exp(x)−exp(−x)
- 比Sigmoid函数收敛速度更快。
- 相比Sigmoid函数,其输出以0为中心,因此实际应用中 tanh 会比 sigmoid 更好,因为 tanh 的输出均值比 sigmoid 更接近 0,SGD会更接近 natural gradient(一种二次优化技术),从而降低所需的迭代次数
- 还是没有改变Sigmoid函数的最大问题——由于饱和性产生的梯度消失
-
softsign
y = x 1 + a b s ( x ) y = \frac{x}{1 + abs(x)} y=1+abs(x)x -
softrelu
y = l o g ( 1 + e x p ( x ) ) y = log(1 + exp(x)) y=log(1+exp(x)) -
relu
y = m a x ( x , 0 ) y = max(x, 0) y=max(x,0)
-
由于 x>0时导数为 1,所以ReLU 能够在x>0时保持梯度不衰减,从而缓解梯度消失问题。但随着训练的推进,部分输入会落入硬饱和区,导致对应权重无法更新。这种现象被称为“神经元死亡”,影响网络的收敛性
-
使用 ReLU 得到的 SGD 的收敛速度会比 sigmoid/tanh 快很多
-
ReLU的输出具有偏移现象,即输出均值恒大于零,影响网络的收敛性
- PReLU
- 为了避免梯度消失,提出了LReLU,其中 a i a_i ai较小且取固定值. 但在一些实验中,LReLU对准确率并没有太大的影响。很多时候,若想应用LReLU时,必须非常小心谨慎地重复训练,选取出合适的 a i a_i ai,LReLU才能表现得比ReLU好
- PReLU是ReLU 和 LReLU的改进版本,具有非饱和性,负半轴斜率 a j i a_{ji} aji可学习而非固定。PReLU收敛速度快、错误率低,可以用于反向传播的训练,可以与其他层同时优化
- ELU
- ELU融合了sigmoid和ReLU,具有左侧软饱性
- 右侧线性部分使得ELU能够缓解梯度消失,而左侧软饱能够让ELU对输入变化或噪声更鲁棒。ELU减少了正常梯度与单位自然梯度之间的差距,它的输出均值接近于零,所以收敛速度更快
参数初始化方式
-
官方文档: mxnet.initializer 定义
示例:net.initialize( init.Normal( sigma = 0.01 ) )
-
Normal([sigma])
-
Xavier([rnd_type, factor_type, magnitude])
损失函数
-
官方文档:mxnet.gluon : : loss 类定义
示例:loss = gloss.L2loss()
-
L2Loss
均方误差
L = 1 2 ∑ i ∣ l a b e l i − p r e d i ∣ 2 . L = \frac{1}{2} \sum_i \vert {label}_i - {pred}_i \vert^2. L=21i∑∣labeli−predi∣2. -
L1Loss
L = ∑ i ∣ l a b e l i − p r e d i ∣ . L = \sum_i \vert {label}_i - {pred}_i \vert. L=i∑∣labeli−predi∣. -
SigmoidBinaryCrossEntropyLoss
二分类交叉熵
p r o b = 1 1 + exp ( − p r e d ) L = − ∑ i l a b e l i ∗ log ( p r o b i ) ∗ p o s _ w e i g h t + ( 1 − l a b e l i ) ∗ log ( 1 − p r o b i ) prob = \frac{1}{1 + \exp(-{pred})} \\ L = - \sum_i {label}_i * \log({prob}_i) * pos\_weight + (1 - {label}_i) * \log(1 - {prob}_i) prob=1+exp(−pred)1L=−i∑labeli∗log(probi)∗pos_weight+(1−labeli)∗log(1−probi) -
SoftmaxCrossEntropyLoss
softmax交叉熵
p = s o f t m a x ( p r e d ) L = − ∑ i ∑ j l a b e l j log p i j p = softmax({pred}) \\ L = -\sum_i \sum_j {label}_j \log p_{ij} p=softmax(pred)L=−i∑j∑labeljlogpij -
LogisticLoss
L = ∑ i log ( 1 + exp ( − p r e d i ⋅ l a b e l i ) ) L = \sum_i \log(1 + \exp(- {pred}_i \cdot {label}_i)) L=i∑log(1+exp(−predi⋅labeli))
优化算法(小批量)
-
参考博文:优化方法optimizer总结
官方文档:mxnet.Optimizers 定义
示例:gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': 0.1, 'wd': 5})
-
随机梯度下降 SGD:
"sgd"
-
adagrad
-
adam:
"adam"
模型构建、训练和预测
模型构建
net = nn.Sequential()
net.add(nn.Dense(360, activation='relu'),
nn.Dropout(0.2),
nn.Dense(64, activation='relu'),
nn.Dropout(0.5),
nn.Dense(1))
net.initialize(init.Normal(sigma=0.01))
模型训练
- 构建小批量数据数据生成器
train_iter = gdata.DataLoader()
- 构建模型训练器
trainer = gluon.Trainer()
,接口文档 gluon.Trainer - 循环n次epoch训练. 在每个epoch中,按小批量训练,所有batch训练结束则输出本次epoch的损失
net=get_net()
loss=gloss.L2Loss()
num_epochs=20; batch_size=64; learning_rate=0.01; weight_decay=0
train_iter = gdata.DataLoader(gdata.ArrayDataset(train_features, train_labels), batch_size, shuffle=True)
trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': learning_rate, 'wd': weight_decay})
for epoch in range(num_epochs):
for X, y in train_iter:
with autograd.record():
l = loss(net(X), y)
l.backward()
trainer.step(batch_size)
train_ls.append(log_rmse(net, train_features, train_labels))
模型预测
- 模型输出:
out = net(X)
print(out.shape, out.dtype, out[0])
模型参数存储和读取
- 参数保存:
filename = 'net.params'
net.save_parameters(filename)
- 读取参数:
net = get_net()
net.load_parameters(filename)
可视化
losses, train_acc, test_acc = [],[],[]
# 可视化训练过程
idx = range(1,epochs+1)
plt.figure(figsize=(12, 4))
plt.subplot(121)
plt.xlabel("epoch")
plt.ylabel("loss")
plt.plot(idx, losses, 'o', linestyle='-')
plt.xticks(range(min(idx), max(idx)+1, 1))
plt.grid()
plt.subplot(122)
plt.xlabel("epoch")
plt.ylabel("accuracy")
plt.plot(idx, train_acc, 'o', linestyle='-', color="r", label="train accuracy")
plt.plot(idx, test_acc, 'o', linestyle='-', color="b", label="test accuracy")
plt.legend(loc="best")
plt.xticks(range(min(idx), max(idx)+1, 1))
plt.yticks(np.arange(0., 1.1, 0.1))
plt.grid()
plt.ylim([0,1.1])
plt.show()