学习pytorch

基本学习单元

已知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=xW1W2Wn)的复合, g ( x ) g(x) g(x)是非线性变化。参照待定系数法的思想,我们先设出几个待定系数( W 1 , W 2 ⋯ W n W_{1},W_{2}\cdots W_{n} W1,W2Wn)并随机初始化(如正态分布),在待定系数法中还需要一个条件来构造含参等式来求解(构造的过程就是正向传递),略有不同的是由于是近似拟合,因此我们无法构造出一元等式,而是利用导数和迭代(反向传递)来不断调整参数,达到最近似拟合。以下内容摘自:Learning PyTorch with Examples

numpy

用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

torch tensor

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()。

Autograd

利用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_()

自定义autograd函数

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_()

动态图VS静态图

在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)

nn module

在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

通过继承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
…………
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值