autograd库测试笔记-(一个基于Numpy的自动求导库)

导入 autograd 库,同时导入这个库里的numpy(应该是作者自己把numpy放入了这个库的命名空间里面)以及逐项求导elementwise_grad

from autograd import grad
import autograd.numpy as np
from autograd import elementwise_grad

接下来定义第一个函数,这个函数非常简单,其实就是一个线性变换:

y n × l = X n × d w d × l \mathbf{y}_{n\times l} = \mathbf{X}_{n\times d} \mathbf{ w} _{d\times l} yn×l=Xn×dwd×l

这个变换也常用于神经网络的输入层。

def lin_func(x,w):
  return np.dot(x,w)

x = np.array([[1,2,3],[4,5,6]])
w = np.array([1,1,1]).astype(float)

print(lin_func(x,w))

[ 6. 15.]

这里也要注意一个问题:autograd并不能支持numpy的所有操作。比如上面的线性变换其实也可以用 x.dot(w) 来实现,但这种方式下就不能再用autograd自动求导了。因此在使用时也要注意看看文档。

接下来写出这个函数的导数 ∂ y ∂ X \frac{\partial \mathbf{y}}{\partial \mathbf{X}} Xy

用法很简单,直接将函数名作为"参数"传入 elementwise_grad 即可,得到的仍然是一个callable 的类型,这里命名为 lin_grads:

lin_grads = elementwise_grad(lin_func)

dx = lin_grads(x,w)

print(dx)

print(dx.shape)
[[1 1 1]
 [1 1 1]]
(2, 3)

这里其实存在一个问题,如果我们用标量求导来类推,那么此时导数应该是:

∂ y ∂ X = w T \frac{\partial \mathbf{y}}{\partial \mathbf{X}} =\mathbf{w}^T Xy=wT

而刚刚我们设好的 w \mathbf{w} w 3 × 1 3 \times 1 3×1 维,那么上面的导数就应该是 1 × 3 1 \times 3 1×3维,而此时得到的结果却是 2 × 3 2 \times 3 2×3 维。这一点和我们直观的认识是不一样的。因为在矩阵对矩阵求导时,许多在标量求导时的结论是不存在的。 而如果要统一这些运算,则需要考虑 Kronecker 乘积。这个问题就相对复杂了,有空可以去看看这个:Kronecker Products and Matrix Calculus in System Theory

这里最简单的解释是:刚刚的导数,对于 X \mathbf{X} X 的每一行其实都是 w T \mathbf{w}^T wT,而此时 X \mathbf{X} X 有2行,因此结果出现了2列。

紧接着另外一个问题是:这个库求导时,只是对elementwise_grad 中的第一个参数求导,下面这个例子演示了 ∂ y ∂ w \frac{\partial \mathbf{y}}{\partial \mathbf{w}} wy 的计算结果:

def lin_wx(w,x):
  return np.dot(x,w)

print(lin_wx(w,x))
[ 6. 15.]
lin_grad_wx = elementwise_grad(lin_wx)

dw = lin_grad_wx(w,x)

print(dw)
print(dw.shape)
[5. 7. 9.]
(3,)

这里其实又出现了一个“反常”的现象:同样地,如果按照标量求导的思路来看,此时的导数应该是:

∂ y ∂ w = X T \frac{\partial \mathbf{y}}{\partial \mathbf{w}} =\mathbf{X}^T wy=XT

而事实却是:

∂ y ∂ w = ∑ i = 1 n X i , : T \frac{\partial \mathbf{y}}{\partial \mathbf{w}} = \sum_{i=1}^{n} \mathbf{X}_{i,:}^T wy=i=1nXi,:T

这里 X i , : \mathbf{X}_{i,:} Xi,: X \mathbf{X} X 的第 i i i 行。 如果要想彻底弄明白这个问题还是建议去看这个:Kronecker Products and Matrix Calculus in System Theory

当然,通常而言,我们只需要考虑 loss 对某个参数的导数就可以了,这个库也做到了这一点:即对某个标量函数,其实是完全可以得到与某参数的shape一模一样的导数,这样在调用梯度做训练时也就比较简单了。

以下是官方的一个完整的logistic regression的例子。

from builtins import range
import autograd.numpy as np
from autograd import grad
from autograd.test_util import check_grads

def sigmoid(x):
    return 0.5*(np.tanh(x) + 1)

def logistic_predictions(weights, inputs):
    # Outputs probability of a label being true according to logistic model.
    return sigmoid(np.dot(inputs, weights))

def training_loss(weights):
    # Training loss is the negative log-likelihood of the training labels.
    preds = logistic_predictions(weights, inputs)
    label_probabilities = preds * targets + (1 - preds) * (1 - targets)
    return -np.sum(np.log(label_probabilities))

# Build a toy dataset.
inputs = np.array([[0.52, 1.12,  0.77],
                   [0.88, -1.08, 0.15],
                   [0.52, 0.06, -1.30],
                   [0.74, -2.49, 1.39]])
targets = np.array([True, True, False, True])

# Build a function that returns gradients of training loss using autograd.
training_gradient_fun = grad(training_loss)

# Check the gradients numerically, just to be safe.
weights = np.array([0.0, 0.0, 0.0])
check_grads(training_loss, modes=['rev'])(weights)

# Optimize weights using gradient descent.
print("Initial loss:", training_loss(weights))
for i in range(100):
    weights -= training_gradient_fun(weights) * 0.01

print("Trained loss:", training_loss(weights))
Initial loss: 2.772588722239781
Trained loss: 0.38900754315581143

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半个冯博士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值