以下是书中给的这部分代码,我在学习中遇到的问题是net.W传递问题,即net.W是如何参与函数运算的,大家有其它的问题可以留言交流。
import sys, os
sys.path.append(os.pardir)
from common.functions import softmax, cross_entropy_error
import numpy as np
import matplotlib.pylab as plt
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
# np.nditer是迭代器
# flags=['multi_index']表示对a进行多重索引
# op_flags=['readwrite']表示不仅可以对a进行read(读取),还可以write(写入),即相当于在创建这个迭代器的时候,我们就规定好了有哪些权限。
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
# 计算x+h处的函数值
idx = it.multi_index
tmp_val = x[idx]
x[idx] = float(tmp_val) + h
fxh1 = f(x) # f(x+h)
# 计算x-h处的函数值
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 还原值
it.iternext()
return grad
# 建立类
class simpleNet:
def __init__(self):
self.W = np.random.randn(2,3) # 用高斯分布进行初始化
def predict(self, x):
return np.dot(x, self.W)
def loss(self, x, t):
z = self.predict(x)
y = softmax(z)
loss = cross_entropy_error(y, t) #输出一个值
return loss
x = np.array([0.6, 0.9])#输入的图像数据
t = np.array([0, 0, 1])#正确标签
net = simpleNet()
# 求损失函数关于权重参数的梯度
f = lambda w: net.loss(x, t)
# print(f)
# def f(W):
# return net.loss(x, t)
dW = numerical_gradient(f, net.W)
print(dW)
下面从这一段代码给出解释:
f = lambda w: net.loss(x, t)
# 也可写为
def f(W):
return net.loss(x, t)
上面为lambda匿名函数,下面为有名字的函数,区别仅此而已。重点在于,这一函数的调用源于w的改变,这一点很重要。
接着是这一段代码:
dW = numerical_gradient(f, net.W)
这一段代码是调用上一块定义的函数f,和随机生成的W数组。看到这里的时候,我很迷惑net.W是如何参与运算的,在这里废了不少时间,希望能给遇到同类问题的同学一点启发。
接着是调用:
idx = it.multi_index
tmp_val = x[idx]
x[idx] = float(tmp_val) + h
fxh1 = f(x) # f(x+h)
这一段最后一句很容易让我们初学者摸不着头脑,因为上述定义的f在我们看来应该有两个参数x,t。而这两个参数已经定义好了,那么net.W怎么参与运算呢。
注意此时的net.W中的值已经发生改变
这就是答案:
class simpleNet:
def __init__(self):
self.W = np.random.randn(2,3) # 用高斯分布进行初始化
def predict(self, x):
return np.dot(x, self.W)
def loss(self, x, t):
z = self.predict(x)
y = softmax(z)
loss = cross_entropy_error(y, t) #输出一个值
return loss
当调用f时,首先进行z的运算,此时调用predict函数进行矩阵乘法运算,注意,由于在上一步中net.W发生改变,这里self.W发生相应改变,所以每次输出的z值是不一样的!即每次返回的fxh1,fxh2值是不一样的!
至此问题解决,总结一下:
f作为形式上的损失函数,在w改变时被调用,然后真实的损失函数loss进行运算,由于self.W值改变,所以每次返回的值都不一样,最终获得损失函数关于权重的梯度值。