自己动手写一个简单的神经网络框架(2)

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

l1 = np.zeros((784,128),dtype=np.float32)
l2 = np.zeros((128,10),dtype=np.float32)
复制代码

这里注意一下默认情况下,numpy 创建一个数组类型为 np.float64 所以需要手动指定一下类型为 np.float32,然后

l1[:] = model.l1.weight.detach().numpy().transpose()
l2[:] = model.l2.weight.detach().numpy().transpose()
复制代码

用 numpy 实现前向传播

def forward(x):
  x = x.dot(l1)
  x = np.maximum(x,0)
  x = x.dot(l2)
  return x

y_test_pred = np.argmax(forward(X_test.reshape((-1,28*28))),axis=1)
(y_test_pred==Y_test).mean()
复制代码

使用 numpy 进行训练

首先我们实现一下交叉熵

y_test_pred_out[sample,Y_test[sample]]
复制代码
x [ c l a s s ] + log ( j exp ( x [ j ] ) ) -x[class] + \log(\sum_j \exp(x[j]))
sample = 1
-Y_test_pred_out[sample,Y_test[sample]] + np.log(np.exp(Y_test_pred_out[sample]).sum())
复制代码

这里要做就是我们计算所有样本交叉熵损失函数

ret = -Y_test_pred_out[range(Y_test_pred_out.shape[0]),Y_test] + np.log(np.exp(Y_test_pred_out[sample]).sum())
复制代码
imshow(X_test[np.argmax(ret)])
复制代码

不难看出这个就是模型给出判断误差最大的一张图像,的确不好分辨,即使对于我们人类来说也是一张比较难于分辨的图像。

mnist_misclassification.png

sorted(list(zip(ret,range(ret.shape[0]))),reverse=True)
复制代码
grid = sorted(list(zip(ret,range(ret.shape[0]))),reverse=True)[0:16]
hard_classification_img = X_test[[x[1] for x in grid]]
#hard_classification_img.reshape(4,28*4,28).shape
hard_classification_img.shape
imshow(np.concatenate(hard_classification_img.reshape((4,28*4,28)),axis=1))
复制代码

首先grid是一个 list 其中每一个元素是一个 tuple 类型,例如 (30.342394, 2607), 其中一个值 loss 值,另一个对应图像需要,我们需要根据图像需要拿到对应的图像,hard_classification_img 的 shape 为 ( 16 × 28 × 28 ) (16 \times 28 \times 28)

hard_classification_grid.png

np.concatenate(hard_classification_img.reshape((4,28*4,28)),axis=1)
复制代码

关键是看代码是怎么把 16 个 28 × 28 28 \times 28 图像拼接为 4 × 4 4 \times 4 排列图像

写一个训练过程

out = model(torch.tensor(X_test[0:1].reshape((-1,28*28))).float())
loss = loss_fun(out,torch.tensor(Y_test[0:1]).long())
loss.backward()
复制代码

从测试集中拿到样本输入到模型中,模型给出预测结果,再将预测结果和标签输入 loss 函数,然后对 loss 进行反向传播回传梯度,用梯度来更新模型参数

model.zero_grad()
out = model(torch.tensor(X_test[0:1].reshape((-1,28*28))).float())
loss = loss_fun(out,torch.tensor(Y_test[0:1]).long())
loss.backward()
figsize(16,16)
imshow(model.l1.weight.grad)
复制代码

将梯度以图像形式输出便于观察,从图上来看大部分梯度都是 0。

l1_weight_gradient.png

figure()
imshow(model.l2.weight.grad)
复制代码

l2_weight_gradient.png

model.zero_grad()
out = model(torch.tensor(X_test[0:1].reshape((-1,28*28))).float())
loss = loss_fun(out,torch.tensor(Y_test[0:1]).long())
print(loss)
loss.retain_grad()
loss.backward()
figsize(16,16)
imshow(model.l1.weight.grad)
figure()
imshow(model.l2.weight.grad)
复制代码

这里retain_grad() 可以保留非叶子结点以外中间结点的梯度,默认情况下为了节省内存空间是不会保留中间结点非叶子结点的梯度的。

#理解梯度下降
model.zero_grad()
out = model(torch.tensor(X_test[0:1].reshape((-1,28*28))).float())
out.retain_grad()
loss = loss_fun(out,torch.tensor(Y_test[0:1]).long())
# print(loss)
loss.retain_grad()
loss.backward()
figsize(16,16)
imshow(model.l1.weight.grad)
figure()
imshow(model.l2.weight.grad)

out.grad,loss.grad
复制代码

输出 outloss 的梯度,retain_grad 表示中间变量

(tensor([[ 8.8083e-11, 7.9277e-13, 4.0970e-04, 4.5061e-05, 1.6520e-12, 4.4796e-08, 9.0004e-15, -4.5484e-04, 1.1185e-10, 3.0328e-08]]), tensor(1.))
复制代码
...
loss_fun = nn.CrossEntropyLoss(reduction='none')
...

for i in tbar:
...

  loss = loss_fun(out,Y)
  print(loss.shape)
  # print(loss.mean())
  loss = loss.mean()
  loss.backward()
  optim.step()
  # print(loss)
复制代码

这里将 reduction 设置为 none 在每次迭代时,不会对 loss 进行求均值或者求和,所以需要手动loss.mean() 进行求均值。

CrossEntropyLoss 拆分为 LogSoftmaxNLLLoss

class ANet(torch.nn.Module):
  def __init__(self):
    ...
    self.sm = nn.LogSoftmax(dim=1)
  def forward(self,x):
    ...
    x = self.sm(x)

    return x

model = ANet()
复制代码
loss_fun = nn.NLLLoss(reduction='none')
复制代码

猜你喜欢

转载自juejin.im/post/7111630920683094046