已知y和x的一组对应数据 ( ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯   , ( x n , y n ) ) ((x_{1},y_{1}),(x_{2},y_{2}),\cdots,(x_{n},y_{n})) ((x1,y1),(x2,y2),⋯,(xn,yn)),如果已知x和y满足的函数类型,如线性函数,二次函数等,那么我们可以通过待定系数法来解出 y = f ( x ) y=f(x) y=f(x);如果y与x对应的函数类型是未知的,可以通过 y = g ( h ( x ) ) y=g(h(x)) y=g(h(x))来拟合未知关系 y = f ( x ) y=f(x) y=f(x),其中 h ( x ) h(x) h(x)是若干线性变换( y = x ⋅ W 1 ⋅ W 2 ⋯ W n y=x\cdot W_{1} \cdot W_{2}\cdots W_{n} y=x⋅W1⋅W2⋯Wn)的复合, g ( x ) g(x) g(x)是非线性变化。参照待定系数法的思想,我们先设出几个待定系数( W 1 , W 2 ⋯ W n W_{1},W_{2}\cdots W_{n} W1,W2⋯Wn)并随机初始化(如正态分布),在待定系数法中还需要一个条件来构造含参等式来求解(构造的过程就是正向传递),略有不同的是由于是近似拟合,因此我们无法构造出一元等式,而是利用导数和迭代(反向传递)来不断调整参数,达到最近似拟合。以下内容摘自:Learning PyTorch with Examples
用numpy实现的基本学习单元:
import numpy as np
# N is batch size; D_in is input dimension;
# H is hidden dimension; D_out is output dimension.
N, D_in, H, D_out = 64, 1000, 100, 10
# Create random input and output data
x = np.random.randn(N, D_in)#D_IN=1000
y = np.random.randn(N, D_out)#D_OUT=10
# Randomly initialize weights
w1 = np.random.randn(D_in, H)#(a,b)(c,d)=(a,d)[b=c]
w2 = np.random.randn(H, D_out)
#x*w1*w2=(N,D_out)维度与y相同
learning_rate = 1e-6
for t in range(500):
# 正向传递
h = x.dot(w1)
#非线性变化:y=x>0?x:0
h_relu = np.maximum(h, 0)
y_pred = h_relu.dot(w2)
# 计算误差
loss = np.square(y_pred - y).sum()
print(t, loss)
# 反向传播
'''
矩阵乘法导数
Y = A * X --> DY/DX = A'
Y = X * A --> DY/DX = A
Y = A' * X * B --> DY/DX = A * B'
Y = A' * X' * B --> DY/DX = B * A'
'''
#Dloss/Dy_pred:
grad_y_pred = 2.0 * (y_pred - y)
#Dy_pred/Dw2=h_relu.T (y_pred=h_relu*w2)
#Dloss/Dw2=Dy_pred/Dw2 * Dloss/Dy_pred:
grad_w2 = h_relu.T.dot(grad_y_pred)
#Dloss/Dh_relu:
grad_h_relu = grad_y_pred.dot(w2.T)
grad_h = grad_h_relu.copy()
#relu函数的导数
grad_h[h < 0] = 0
#h=relu(x*w1)
grad_w1 = x.T.dot(grad_h)
# Update weights
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
tensor是pytorch的基本概念,和ndarray类似也是n维数组。作为pytorch的基本数据类型,支持的操作类型更多,可用来跟踪计算图和变量梯度。此外还支持利用GPU加速运算。
import torch
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0")利用GPU计算,多个显卡可以指定编号
N, D_in, H, D_out = 64, 1000, 100, 10
# Create random input and output data
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
# Randomly initialize weights
w1 = torch.randn(D_in, H, device=device, dtype=dtype)
w2 = torch.randn(H, D_out, device=device, dtype=dtype)
learning_rate = 1e-6
for t in range(500):
# Forward pass: compute predicted y
h = x.mm(w1)#矩阵乘法
h_relu = h.clamp(min=0)#设置上下界,clamp(input,min,max,out=None)返回tensor
y_pred = h_relu.mm(w2)
# Compute and print loss
loss = (y_pred - y).pow(2).sum().item()
print(t, loss)
# Backprop to compute gradients of w1 and w2 with respect to loss
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h_relu.t().mm(grad_y_pred)
grad_h_relu = grad_y_pred.mm(w2.t())
grad_h = grad_h_relu.clone()
grad_h[h < 0] = 0
grad_w1 = x.t().mm(grad_h)
# Update weights using gradient descent
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
pytorch通过声明tensor的属性选择设备,tensor支持十分丰富的运算操作,运算结果依然是tensor,一维tensor转成标量数字用one.item()。
利用pytorch中的Autograd包可以自动实现以上基本学习单位中向后传递和反向传播两个过程。
import torch
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0")
N, D_in, H, D_out = 64, 1000, 100, 10
# requires_grad=True/False 表明tensor在反向传播中需要/不需要计算梯度,
#requires_grad的属性默认为False,若一个节点requires_grad被设置为True,那么所有依赖
#它的节点的requires_grad都为True。
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
#不需要中间值
y_pred = x.mm(w1).clamp(min=0).mm(w2)
loss = (y_pred - y).pow(2).sum()
print(t, loss.item())
# 用autograd计算梯度,a.backward()计算a对标记tensor的导数,w1.grad,w2.grad分别保存loss对w1和w2的梯度
loss.backward()
# 用梯度下降法手动更新当前权重(梯度下降 x=x-αf'(x))
#torch.no_grad():可以把变量从当前网络中分离出来,不追踪计算梯度,减少运算
#还可以optimizer = optim.SGD([x, y], lr, momentum)
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
w1.grad.zero_()
w2.grad.zero_()
class MyReLU(torch.autograd.Function):
"""
通过继承torch.autograd.Function并重写forward和backward函数,实现自定义autograd函数
"""
#@staticmethod 静态方法,只是名义上归属类管理,但是不能使用类变量和实例变量,是类的工具包,该函数不传入self或者cls,所以不能访问类属性和实例属性
@staticmethod
def forward(ctx, input):
"""
在正向传递中,我们接收一个包含输入的tensor,并返回一个包含输出的tensor。ctx是一个上下文对象,可以用来为向后计算存储信息。您可以使用ctx.save_for_backward缓存任意对象,以便在向后传递中使用ctx.saved_tensors。
"""
ctx.save_for_backward(input)
return input.clamp(min=0)#下界是0
@staticmethod
def backward(ctx, grad_output):
"""
反向传播中,输入为一个包含了loss对forward函数输出梯度的tensor,我们需要计算loss相对于forward函数输入的梯度。
"""
input, = ctx.saved_tensors
grad_input = grad_output.clone()
#tensor运算:a[b<0]=0(b<0返回小于0的索引)
grad_input[input < 0] = 0
return grad_input
dtype = torch.float
device = torch.device("cpu")
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
# 使用Function.apply方法来实例自定义autograd函数relu .
relu = MyReLU.apply
# 向前传递
y_pred = relu(x.mm(w1)).mm(w2)
# 计算损失
loss = (y_pred - y).pow(2).sum()
print(t, loss.item())
# 反向传播
loss.backward()
#梯度下降
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
w1.grad.zero_()
w2.grad.zero_()
在TensorFlow中,我们定义计算图一次,然后反复执行相同的图,可能向图提供不同的输入数据。在PyTorch中,每个向前传递都定义一个新的计算图。下面是使用Tensorflow静态图来实现简单的两层网络。
import tensorflow as tf
import numpy as np
# 首先定义静态计算图
N, D_in, H, D_out = 64, 1000, 100, 10
# 计算图执行的时候,占位符用来传递实际的输入参数
x = tf.placeholder(tf.float32, shape=(None, D_in))
y = tf.placeholder(tf.float32, shape=(None, D_out))
# 创建变量,变量在图的执行过程中保持其值
w1 = tf.Variable(tf.random_normal((D_in, H)))
w2 = tf.Variable(tf.random_normal((H, D_out)))
# 向前传递
h = tf.matmul(x, w1)
h_relu = tf.maximum(h, tf.zeros(1))
y_pred = tf.matmul(h_relu, w2)
#计算损失
loss = tf.reduce_sum((y - y_pred) ** 2.0)
# 计算梯度
grad_w1, grad_w2 = tf.gradients(loss, [w1, w2])
#在TensorFlow中,更新权重值的行为是计算图的一部分,在PyTorch中,这发生在计算图之外
learning_rate = 1e-6
new_w1 = w1.assign(w1 - learning_rate * grad_w1)
new_w2 = w2.assign(w2 - learning_rate * grad_w2)
# 以上是静态图的定义部分,下面开始图的计算
with tf.Session() as sess:
# 先运行计算图一次来初始化w1和w2
sess.run(tf.global_variables_initializer())
# 创建numpy数组来保存实际数据
x_value = np.random.randn(N, D_in)
y_value = np.random.randn(N, D_out)
for _ in range(500):
'''
多次执行图表。每次执行时,我们都希望将x_value绑定到x,将y_value绑定到y,并使用feed_dict参数指定。当我们执行这个图的时候我们要计算loss,new_w1和new_w2;这些tensor作为numpy数组返回。
'''
loss_value, _, _ = sess.run([loss, new_w1, new_w2],
feed_dict={x: x_value, y: y_value})
print(loss_value)
在PyTorch中,神经网络包定义了一组模块,这些模块相当于神经网络层。模块接收输入张量并计算输出张量,但也可以保存内部状态,如包含可学习参数的张量。神经网络包还定义了一组训练神经网络时常用的有用的损失函数。我们使用nn包来实现我们的两层网络:
import torch
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
N, D_in, H, D_out = 64, 1000, 100, 10
#使用nn包将我们的模型定义为一系列层。nn.sequence是一个包含其他模块的模块,按顺序应用这些模块来产生输出。每个线性模块使用一个线性函数计算输入的输出,并保存内部的权重和偏差tensor。
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),#指定W1维度
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),#指定w2维度
)
#神经网络包还包含了常用损失函数的定义,使用均方误差(MSE)作为损失函数。reduction聚合操作,原型torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
loss_fn = torch.nn.MSELoss(reduction='sum')
learning_rate = 1e-4
for t in range(500):
#向前传递
#模块对象重载了__call__ operator来像函数一样调用。
y_pred = model(x)
# 计算损失loss_fn的两个参数是预测值和真值
loss = loss_fn(y_pred, y)
print(t, loss.item())
# 在反向传播之前将所有梯度归零
model.zero_grad()
#计算损失对所有学习参数(requires_grad=True)的梯度。
loss.backward()
# 梯度下降法更新权重
with torch.no_grad():
for param in model.parameters():
param -= learning_rate * param.grad
下例使用nn包来定义我们的模型,使用optim包提供的Adam算法来优化模型。
import torch
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),
)
loss_fn = torch.nn.MSELoss(reduction='sum')
#使用optim包来定义一个优化器,自动更新模型的权重。optim还有许多其他优化算法。Adam构造函数的第一个参数告诉优化器应该更新哪些tensor;
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for t in range(500):
y_pred = model(x)
loss = loss_fn(y_pred, y)
print(t, loss.item())
#向后传递之前,使用优化器对象将它将要更新的变量的所有梯度归零,因为每次梯度计算都是在同一个缓冲区覆盖计算
optimizer.zero_grad()
#反向传播
loss.backward()
# step函数对参数进行更新
optimizer.step()
通过继承nn.Module类并重写forward,可以自定义神经网络模块结构。
import torch
class TwoLayerNet(torch.nn.Module):
def __init__(self, D_in, H, D_out):
#在构造函数中实例化两个线性模块作为成员变量
super(TwoLayerNet, self).__init__()#父类构造函数初始化系统模块
self.linear1 = torch.nn.Linear(D_in, H)
self.linear2 = torch.nn.Linear(H, D_out)
def forward(self, x):
#接受一个输入tensor,必须返回一个输出tensor。
#可以使用构造函数中包含的的模块以及tensor上的任意运算符,模块名可作为函数名
h_relu = self.linear1(x).clamp(min=0)
y_pred = self.linear2(h_relu)
return y_pred
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
# 实例化自定义模块类得到模型
model = TwoLayerNet(D_in, H, D_out)
#构造损失函数和优化器。在SGD(随机梯度下降)构造函数中model.parameters()包含两个nn的学习参数。
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500):
# 向前传递
y_pred = model(x)
# 计算损失
loss = criterion(y_pred, y)
print(t, loss.item())
# 梯度置零,反向传播和更新参数
optimizer.zero_grad()
loss.backward()
optimizer.step()
流程如下:
定义网络模块以及向前传递函数–> 确定输入输出维度–>实例化nn模块得到模型–>定义优化器和损失函数–>迭代调用模型函数(向前传递)–>计算损失–>梯度置零,反向传播和更新参数
import random
import torch
class DynamicNet(torch.nn.Module):
def __init__(self, D_in, H, D_out):
"""
In the constructor we construct three nn.Linear instances that we will use
in the forward pass.
"""
super(DynamicNet, self).__init__()
self.input_linear = torch.nn.Linear(D_in, H)
self.middle_linear = torch.nn.Linear(H, H)
self.output_linear = torch.nn.Linear(H, D_out)
def forward(self, x):
#每次向前传递都会构建一个动态的计算图,在定义计算图时多次重用相同的模块是安全的。
#中间层线性变化重用的次数是随机的
h_relu = self.input_linear(x).clamp(min=0)
for _ in range(random.randint(0, 3)):
h_relu = self.middle_linear(h_relu).clamp(min=0)
y_pred = self.output_linear(h_relu)
return y_pred
…………