18.5_使用 with 语句切换
18.5 使用with语句切换
Python中有一个语法叫with,用于自动进行后处理。该语法比较有代表性的一个使用例子是文件的打开和关闭。如果在不使用with语句的情况下向文件写入一些内容,则需要按如下方式编写代码。
f = open('sample.txt', 'w')
f.write('hello world!')
f.close()上面的代码使用open()打开一个文件,往里面写了一些内容后,使用close()关闭文件。每次都写close()有点烦琐,而且可能会忘记写这行代码。为了防止这种情况发生,可以像下面这样使用with语句。
with open('sample.txt', 'w') as f:
f.write('hello world!')在上面的代码中,当处理进入with块时,文件被打开,文件在with块内保持打开状态;当处理退出with块时,文件被关闭(在幕后)。因此,通过with语句,代码可以自动进行“进入with块时的预处理”和“退出with块时的后处理”。
下面使用with语句切换到“禁用反向传播模式”。具体的使用方法如下所示(using_config方法的实现将在后面说明)。
steps/step18.pywith using_config("enable_backprop", False):
x = Variable(np.array(2.0))
y = square(x)如上所示,只有在with using_config("enable_backprop", False):语句中才是“禁用反向传播模式”。退出with语句后,又回到了正常模式(启用反向传播模式)。

我们在实际操作中,常常需要临时将模式切换为“禁用反向传播模式”。例如,在神经网络训练阶段,为了(在训练过程中)评估模型,常常使用不需要梯度的模式。
下面编写代码,使模式切换可以通过with语句实现。最简单的方法是使用contextlib模块。这里先来说明如何使用contextlib模块,它的用法如下所示。
importcontextlib
@contextlib.contextmanager
defconfig_test(): print('start') #预处理 try: yield finally: print('done') #后处理
withconfig_test(): print('process...')运行结果
start process... done如代码所示,添加装饰器@contextlib.contextmanager后,就可以创建一个判断上下文的函数了。在这个函数中,yield之前是预处理的代码,yield之后是后处理的代码。这样一来,我们就可以使用with config_test():了。在使用with config_test():的情况下,当处理进入with块的作用域时,预处理会被调用,当处理离开with块的作用域时,后处理会被调用。

with块中可能会发生异常。如果with块中发生了异常,这个异常也会被发送到正在执行yield的地方。因此,yield必须用try/finally括起来。
基于上述内容,我们按如下方式实现using_config函数。
steps/step18.py
import contextlib
@contextlib.contextmanager
def using_config(name, value): old_value $=$ getattr(Config, name) setattr(Config, name, value) try: yield finally: setattr(Config, name, old_value)using_config(name, value) 的参数 name 类型是 str,这里将其指定为 Config 的属性名(类属性名),然后使用 getattr 函数从 Config 类中获取指定 name 的值,最后使用 setattr 函数来设置新的值。
这样在进入with块时,Config类中用name指定的属性会被设置为value的值。在退出with块时,这个属性会恢复为原始值(old_value)。现在我们来实际使用一下using_config函数。
steps/step18.py
with using_config('enable_backprop', False):
x = Variable(np.array(2.0))
y = square(x)如上面的代码所示,当不需要反向传播时,程序只在with块中执行正向传播代码。这样可以免去不必要的计算,节省内存,不过每次都要编写with using_config('enable_backprop', False):有点烦琐。这里我们准备一个名为no_grad的函数,代码如下所示。
steps/step18.py
def no_grad():
return using_config('enable_backprop', False)
with no_grad():
x = Variable(np.array(2.0))
y = square(x)no_grad函数的实现仅仅是调用using_config('enable_backward', False)(然后通过return返回),这意味着在不需要计算梯度时调用no_grad函数即可。到这里,本步骤就结束了。今后在不需要计算梯度,只需要计算正向传播时,请使用本步骤实现的“模式切换”。
步骤19
让变量更易用
DeZero 的基础内容已经完成,现在我们可以创建计算图并实现自动微分了。接下来的任务是让 DeZero 变得更加易用。首先,我们来提高 Variable 类的易用程度。