深度学习-09-让函数更简单
本文是《深度学习入门2-自製框架》 的学习笔记,记录自己学习心得,以及对重点知识的理解。如果内容对你有帮助,请支持正版,去购买正版书籍,支持正版书籍不仅是尊重作者的辛勤劳动,也是鼓励更多优秀作品问世。
当前笔记内容主要为:步骤9 让函数更简单 章节的相关理解。
书籍总共分为5个阶段,每个阶段分很多步骤,最终是一步一步实现一个深度学习框架。例如前两个阶段为:
第 1 阶段共包括 10 个步骤 。 在这个阶段,将创建自动微分的机制
第 2 阶段,从步骤11-24,该阶段的主要目标是扩展当前的 DeZero ,使它能够执行更复杂的计算 ,使它能 够处理接收多个输入的函数和返回多个输出的函数
1.作为python 使用
思考下我们之前的使用模式,在使用函数对象的时候,我们先要定义个出来,然后再使用,这里可以优化吗?
x = Variable(np.array(0.5))A = Square()a = A(x)
是不是有点冗余,啰嗦,能否直接调用 a = f(x) 这种方式?
其实是可以的。我们对代码进行修改:
def square(x):f = Square()return f(x)def exp(x):f = Exp()return f(x)
上面代码,更进一步优化
def square(x):return Square()(x)def exp(x):return Exp()(x)
编写测试案例,看结果是否一致
x = Variable(np.array(0.5))a= square(x)b = exp(a)y = square(b)y.grad = np.array(1.0)y.backward()print(x.grad)
输出结果还是 3.297442541400256 说明函数是等价的
2.简化backward方法
这里优化的目的是,简化用户在反向传播方面的工作。省略前面代码中的 y . grad = np.array(1 . 0),每次反向传播时,我们都要定义这个代码。代码改进:
class Variable:def __init__(self, data):self.data = dataself.grad = Noneself.creator = Nonedef set_creator(self,func):self.creator = funcdef backward(self):if self.grad is None:self.grad = np.ones_like(self.data) # 初始化类型与data 一样funcs = [self.creator]while funcs:f = funcs.pop()x, y =f.input, f.outputx.grad = f.backward(y.grad)if x.creator is not None:funcs.append(x.creator)
代码验证:
# 优化ones_like 初始化后# 不需要定义 y.grad = np.array(1.0) 这个了x = Variable(np.array(0.5))y = square(exp(square(x)))y.backward()print(x.grad)
输出结果
3.297442541400256
3.仅支持ndarray
为了减少用户误用,增加参数校验。我们的框架从开始就是只支持Variable ndarray 的示例。为了避免有些用户很可能会不小心使用 float 或 int 等数据类型。例如:Variable(1. 0) 和 Variable(3) 等 这些错误的使用,我们增加参数校验。
class Variable:def __init__(self, data):if data is not None:if not isinstance(data, np.ndarray):raise TypeError('{} is not supported'.format(type(data))) #参数校验self.data = dataself.grad = Noneself.creator = None
此外,由于NumPy 的特点,带来新的问题
考虑以下代码
x = np.array([1.0])y = x ** 2print(type(x), x.ndim)print(type(y))
输出:
C:\Python\Python39-32\python.exe D:/pyworkspace/dezero-01/step09.py
<class 'numpy.ndarray'> 1
<class 'numpy.ndarray'>
这里执行时先注释掉其他case 案例
if __name__ == '__main__':# x = Variable(np.array(0.5))# a = square(x)# b = exp(a)# y = square(b)## y.grad = np.array(1.0)# y.backward()# print(x.grad)### # 优化ones_like 初始化后# # 不需要定义 y.grad = np.array(1.0) 这个了# x = Variable(np.array(0.5))# y = square(exp(square(x)))# y.backward()# print(x.grad)### # 错误使用## x = Variable(np.array(1.0))# x = Variable(None)# #x = Variable(1.0) # 错误使用# Numpy 特性问题x = np.array([1.0])y = x ** 2print(type(x), x.ndim)print(type(y))
如果要上面的案例也可以执行,就要改造。考虑一种case ,由numpy 特性导致的问题。
x= np.array(1.0)y = x ** 2print(type(x), x.ndim)print(type(y))
输出:
<class 'numpy.ndarray'> 0
<class 'numpy.float64'>
发现 0维的变成了 numpy.float64 、 numpy.float32
这意 味着 DeZero 函数的输山 Variable 可能是 numpy. float64 或 numpy. float32 类哑 的数据。我们在数据输出,输入过程中需要检查,强制转换下。
首先引入辅助函数
def as_array(x):if np.isscalar(x): # 使用 np.isscalar 函数来检查 numpy.float64 等属于标量return np.array(x)return x
修改 Function 类的输出,增加强制转换
class Function:def __call__(self, input):x = input.datay = self.forward(x)output = Variable(as_array(y)) # 转成 ndarray 类型output.set_creator(self) # 输出者保存创造者对象self.input = inputself.output = output # 保存输出者。我是创造者的信息,这是动态建立 "连接"这 一 机制的核心return outputdef forward(self, x):raise NotImplementedError() # 使用Function 这个方法forward 方法的人 , 这个方法应该通过继承采实现def backward(self, gy):raise NotImplementedError()
最后执行,发现所有的案例都可以跑通了:
if __name__ == '__main__':x = Variable(np.array(0.5))a = square(x)b = exp(a)y = square(b)y.grad = np.array(1.0)y.backward()print(x.grad)# 优化ones_like 初始化后# 不需要定义 y.grad = np.array(1.0) 这个了x = Variable(np.array(0.5))y = square(exp(square(x)))y.backward()print(x.grad)# 错误使用x = Variable(np.array(1.0))x = Variable(None)#x = Variable(1.0) # 错误使用# Numpy 特性问题x = np.array([1.0])y = x ** 2print(type(x), x.ndim)print(type(y))x= np.array(1.0)y = x ** 2print(type(x), x.ndim)print(type(y))
输出结果:
C:\Python\Python39-32\python.exe D:/pyworkspace/dezero-01/step09.py
3.297442541400256
3.297442541400256
<class 'numpy.ndarray'> 1
<class 'numpy.ndarray'>
<class 'numpy.ndarray'> 0
<class 'numpy.float64'>
进程已结束,退出代码0
4.代码总结
到此相关优化以及完成,本节所有代码如下:
'''
step09.py
优化-函数更易于使用'''import numpy as npclass Variable:def __init__(self, data):if data is not None: # 新增if not isinstance(data, np.ndarray):raise TypeError('{} is not supported'.format(type(data)))self.data = dataself.grad = Noneself.creator = Nonedef set_creator(self,func):self.creator = funcdef backward(self):if self.grad is None:self.grad = np.ones_like(self.data)funcs = [self.creator]while funcs:f = funcs.pop()x, y =f.input, f.outputx.grad = f.backward(y.grad)if x.creator is not None:funcs.append(x.creator)class Function:def __call__(self, input):x = input.datay = self.forward(x) # 新增output = Variable(as_array(y)) # 转成 ndarray 类型 output.set_creator(self) # 输出者保存创造者对象self.input = inputself.output = output # 保存输出者。我是创造者的信息,这是动态建立 "连接"这 一 机制的核心return outputdef forward(self, x):raise NotImplementedError() # 使用Function 这个方法forward 方法的人 , 这个方法应该通过继承采实现def backward(self, gy):raise NotImplementedError()class Square(Function):def forward(self, x):y = x ** 2return ydef backward(self, gy):x= self.input.datagx = 2 * x * gy #方法的参数 gy 是 一个 ndarray 实例 , 它是从输出传播而来的导数 。return gxclass Exp(Function):def forward(self, x):y = np.exp(x)return ydef backward(self, gy):x = self.input.datagx = np.exp(x) * gyreturn gxdef square(x):f = Square()return f(x)def exp(x):f = Exp()return f(x)def as_array(x): # 新增if np.isscalar(x): # 使用 np.isscalar 函数来检查 numpy.float64 等属于标量return np.array(x)return xif __name__ == '__main__':x = Variable(np.array(0.5))a = square(x)b = exp(a)y = square(b)y.grad = np.array(1.0)y.backward()print(x.grad)# 优化ones_like 初始化后# 不需要定义 y.grad = np.array(1.0) 这个了x = Variable(np.array(0.5))y = square(exp(square(x)))y.backward()print(x.grad)# 错误使用x = Variable(np.array(1.0))x = Variable(None)#x = Variable(1.0) # 错误使用# Numpy 特性问题x = np.array([1.0])y = x ** 2print(type(x), x.ndim)print(type(y))x= np.array(1.0)y = x ** 2print(type(x), x.ndim)print(type(y))