8 - NN Library Implementation

Date (online)
10/6
Instructor
Chen
Slides
Video
这节课首先讲了 .cached_data() 的用处。然后讲了在处理exp函数时,为了数值稳定而做的优化;最后讲了深度学习框架的模块化设计,包括模型,损失函数、优化器等。
理清楚,计算图是在 tenosr 做运算的时候就创建的,且通过tensor.backward() 也可以实现计算图的自动微分。因此本节所讲的主要是抽象,如何把神经网络中常用的东西抽象出来,使用户更加方便的使用。抽象本身没有实现特殊的功能,无非就是在不断的封装。

Needle 复习

如何改变tensor的data字段

# 通过 w.data 新创建了一个Tensor,但是和 w 共享 Tensor中的ndarray数据,只是把w中的 op inputs等删除掉了 # 可见,并没有做深拷贝。 z = w.data z.cached_data is w.cached_data # 因此,更新参数的时候,可以使用 : new_w = w.data + (-lr) * grad.data # 表面上看,计算图中的参数变量 没有发生改变,只是新创建了一个tensor。 # 但是实际了计算图中的tensor 对象确实不应该被 改变,只要该对象的数值发生改变即可。 # 如果一个节点的所有输入都不需要计算梯度,那么这个节点也不需要计算梯度,且不会形成计算图。 # 因此在计算的时候,需要不断的思考,是否需要附着在计算图上

数值稳定

在计算softmax的时候,为了防止数值溢出,通常会在分子分母同时除一个数。然后分子分母都是exp,所以可以转化为:
通常, 选取 每一行中最大的数值,因为分母上是对行进行指数求和。
为什么减去 一个最大的数值就可以避免数值溢出呢?因为对一个 较大的正数值做 exp()会溢出,但是对负数不会溢出
x = np.array([1000, 10000, 100], dtype="float32") def softmax_stable(x): x = x - np.max(x) z = np.exp(x) return z / np.sum(z) softmax_stable(x)

设计一个神经网络库

notion imagenotion image
# 只是 说明 Parameter是个特殊的Tensor class Parameter(ndl.Tensor): """parameter""" p = Parameter([[1, 2,], [3, 6]]) isinstance(p, Parameter) >>> True

nn.Module

_get_params 函数会把 Module 对象 内所有 Parameter 变量 保存起来。第一次传进来的参数必然是 dict 类型。然后递归的把该 Module 对象 内的子 Module 对象 内的 Parameter 变量 保存起来。
_get_params 函数可以得到模型内所有的参数,注意这里仅仅是获取参数,方便后续参数更新。计算图在执行 tensor 运算的时候已经建好了。
def _get_params(value): if isinstance(value, Parameter): return [value] if isinstance(value, dict): params = [] for k, v in value.items(): params += _get_params(v) return params if isinstance(value, Module): return value.parameters() return [] class Module: def parameters(self): return _get_params(self.__dict__) def __call__(self, *args, **kwargs): return self.forward(*args, **kwargs)

Loss function

loss function 依旧是nn.Module类型。
class L2Loss(Module): def forward(self, x ,y): z = x + (-1) * y return z * z

Optimizer

class Optimizer: def __init__(self, params): self.params = params def reset_grad(self): for p in self.params: p.grad = None def step(self): raise NotImplemented() class SGD(Optimizer): def __init__(self, params, lr): self.params = params self.lr = lr def step(self): for w in self.params: w.data = w.data + (-self.lr) * w.grad x = ndl.Tensor([2], dtype="float32") y = ndl.Tensor([2], dtype="float32") model = MultiPathScaleAdd() l2loss = L2Loss() opt = SGD(model.parameters(), lr=0.01) num_epoch = 10 for epoch in range(num_epoch): opt.reset_grad() h = model(x) loss = l2loss(h, y) training_loss = loss.numpy() loss.backward() opt.step() print(training_loss)

Initialization

参数初始化的方式有多种,在hw2中具体实现。