Linear Regression use PyTorch

Linear Regression start with zero

%matplotlib inline
import matplotlib.pyplot as plt 
import torch
from IPython import display
import numpy as np
import random
import torch
num_input = 2                        #特征数/输入个数
num_examples = 1000          #随机生成批量样本特征
#np.random.normal(loc=0.0,scale=1.0,size=None)
features = torch.from_numpy(np.random.normal(0,1,(num_examples,num_inputs)))                 #真实的权重和偏差
true_w = [2,-3.4]
true_b = 4.2
labels = true_w[0] * features[:,0] + true_w[1] * feature[:,1] +true_b
#随机噪声项:服从均值为0,标准差为0.01的正态分布,size需要与labels同形
labels += torch.from_numpy(np.random.normal(0,0.01,size=labels.size()))

Notes:
features的每一行为一个长度为2的向量,而labels的每一行为一个长度为1的向量(标量)

tensor([-0.0469,  0.7326], dtype=torch.float64) 
tensor(1.6094, dtype=torch.float64)

查看features和labels
或者,我们用散点图可视化一下结果

def use_svg_display():      #用矢量表示
    display.set_matplotlib_formats('svg')
def set_figsize(figsize=(3.5,2.5)):
    use_svg_display()        #设置图的尺寸
    plt.rcParams['figure.figsize'] = figsize
set_figsize()
plt.scatter(features[:,1].numpy(),labels.numpy(),1)

通过生成第二个特征features[:1]和标签labels的散点图观察二者的线性关系

读取数据

准确的说,在训练模型是不断读取小批量的数据样本,因此可以定义一个函数data_iter(),每次返回batch_size(批量大小)个数的随机样本的特征和标签

#定义函数:每次返回batch_size(批量大小)个随机样本的特征和标签
def data_iter(batch_size,features,labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    #随即顺序读取样本 rabdom.shuffle(first_item)
    random.shuffle(indices)   
    for i in range(0,num_examples,batch_size):
        #Tensor的索引index_select(input,dim,index)
        j = torch.LongTensor(indices[i:min(i + batch_size,num_examples)])
        yield features.index_select(0,j),labels.index_select(0,j)

然后,试着读取第一个小批量数据样本并打印,每个批量的特征形状为(10,2)分别对应批量大小和输入个数;标签形状为批量大小

batch_size = 10
for x,y in data_iter(batch_size,features,labels):
    print(x,y)
    break

Out:

tensor([[-0.5245,  1.9686, -0.3142,  ..., -1.3142,  2.2752, -0.3538],
        [ 0.4080, -0.9857,  0.9890,  ...,  1.9474,  0.3551,  2.2485]],
       dtype=torch.float64) tensor([6.3743, 1.1030], dtype=torch.float64)

初始化模型参数

之后的模型训练中,需要对这些参数求梯度来迭代参数的值,因此我们需要设置requires_grad = Ture

#权重初始化均值为0、标准差为0.01的正态随机数,偏差则初始化为0
w = torch.tensor(np.random.normal(0,0.01,(num_inputs,1)),
                 dtype = torch.float64)

b = torch.zeros(1,dtype=torch.float64)
w.requires_grad_(requires_grad= True)
b.requires_grad_(requires_grad= True)

定义模型

#使用mm函数做矩阵乘法
def linreg(x,w,b):
    return torch.mm(x,w) + b

定义损失函数

#使用平方损失函数(需要把真实值y 的形状变成y_hat的形状)
def squared_loss(y_hat,y):
    #注意这里返回的是向量,另外pytorch里的MSELoss并没有除以2
    return (y_hat - y.view(y_hat.size())) **2 / 2

定义优化算法

#小批量随机梯度下降算法sgd,通过不断迭代模型来优化损失函数
def sgd(params,lr,batch_size):
    for param in params:
        param.data -= lr * param.grad / batch_size

训练模型

Notes:

  • 对标量调用backward()方法得到该变量的有关模型的梯度,而每个batch的损失batch_loss的形状大小为(batch_size,batch_loss),就是说需要再调用一个sum()使其变为标量再进行计算
  • 每次更新完参数后要记得将参数的梯度清零
  • 对于learning_rate和num_epochs这类超参数hyperparameters需要提前人为设定。
  • 训练过程中我们多次迭代模型参数,根据当前读取的小批量数据样本(特征x和标签y),通过调用反向函数backward计算小批量随机梯度,并调用优化算法迭代模型参数
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):   
    #模型一共需要迭代num_epochs个迭代周期
    #在每个迭代周期中,会使用训练数据集中的所有样本一次(假设样本数能够被批量大小整除)
    #x,y分别是小批量样本的特征和标签
    for X,y in data_iter(batch_size,features,labels):
        l = loss(net(X,w,b),y).sum()
        l.backward()
        sgd([w,b],lr,batch_size)
        
        #梯度清零
        w.grad.data.zero_()
        b.grad.data.zero_()
    train_l = loss(net(features,w,b),labels)
    print('epoch %d,loss %f'%(epoch + 1,train_l.mean().item()))

然后,我们会得到以下结果

epoch 1,loss 0.031826
epoch 2,loss 0.000106
epoch 3,loss 0.000045

简化版本:

回顾上述过程,有些步骤可以用更少的代码实现:

  • 比如在读取数据时可以直接使用PyTorch提供的data包返回以batch_size为单位的以数据样本特征和数据标签为数据项的item
    调用实现代码如下:

    #优化模块:随机索引 & 批量返回数据集の特征,标签
    batch_size = 10
    dataset = Data.TensorDataset(features,labels)
    data_iter = Data.DataLoader(dataset,batch_size,shuffle = True)
    

    打印,Check

    for X,y in data_iter:
        print(X,"\n",y)
        break
    Out:
    tensor([[ 0.0559,  0.4076],
         [-1.4700, -1.8048],
         [-0.2040,  0.4944],
         [ 0.2843, -1.1990],
         [-0.9366, -1.3344],
         [-1.1165, -0.1748],
         [ 1.1653, -0.7916],
         [-0.4482, -0.7828],
         [-0.6377,  0.0800],
         [ 0.5079,  0.1190]]) 
    tensor([2.9387, 7.3972, 2.1157, 8.8541, 6.8640, 2.5714, 9.2221, 5.9670, 2.6400,
         4.8230])
    
  • 使用torch的nn(neural network)模块

    • torch.nn利用autograd定义模型
    • nn的核心数据结构:Module<既可以表示神经网络中的某个层layer也可以表一个包含很多层的神经网络>
    • 我们定义一个类继承nn.Module来构建模型
      *与之前HelloWorld进化史那篇文章中的实现类似,实现过程如下:
    class LinearNet(nn.Module):
        def __init__(self,n_feature):
            super(LinearNet,self).__init__()
            self.linear = nn.Linear(n_feature,1)
        def forward(self,x):
            y = self.linear(x)
            return y
    net = LinearNet(num_inputs)
    print(net)
    
     LinearNet(
           (linear): Linear(in_features=2, out_features=1, bias=True))
    
  • 初始化模型参数使用PyTorch的init模块

  • 定义损失函数使用PyTorch的nn模块中的损失函数

  • 定义优化算法使用PyTorch的优化器模块torch.optim

  • 训练模型使用PyTorch的optim模块中的step函数来迭代模型参数,在step函数中指明批量大小,从而对批量中样本梯度求平均

具体优化见如下实现及代码中的注释/说明

#或者直接使用nn.Sequtial来搭建网络
#Sequential是一个有序的容器,网络层将按照在传入Sequential的顺序依次添加到计算图中

net = nn.Sequential(
    nn.Linear(num_inputs,1)
    #还可以传入其他层,比如激活函数nn.ReLU()
)
#可以分别输出看一下构建的网络结构分布print(net) 、 print(net[0])
from torch.nn import init      #初始化模型参数:weights bias requires_grad属性设置
import torch.optim as optim         #定义优化算法:SGD、Adam、RMSProp等
#初始化模型参数
#权重(随机采样与均值mean为0标准差std为0.01的正态分布normal) & 
#偏差初始化为0
init.normal_(net[0].weight,mean = 0,std= 0.01)
init.constant_(net[0].bias,val = 0)
#定义损失函数:均方误差损失MeanSquareLoss
#(nn模块中提供了nn,Module的子类来实现)
loss = nn.MSELoss()
#定义优化算法(同时我们在这里定义超参数学习率为0.03)
#torch.optim模块提供SGD、ADam、RMSProp
optimizer = optim.SGD(net.parameters(),lr = 0.03)
#训练模型
num_epochs = 3
for epoch in range(1,num_epochs + 1):
    for X,y in data_iter:
        output = net(X)
        l = loss(output,y.view(-1,1))   #还记得吗,之前说过的真实值y要与y_hat的shape一样(调用.view()方法实现)
        optimizer.zero_grad()     #其实,只要在反向传播backward pass 前对梯度进行清零就好
        l.backward()
        optimizer.step()      #帮助我们更好实现训练数据集的迭代
    print('epoch %d ,loss:%f '% (epoch,l.item()))

然后

epoch 1 ,loss:0.000243 
epoch 2 ,loss:0.000072 
epoch 3 ,loss:0.000095 

Tips:

  • numpy随机数生成
    np.random.normal(loc=0.0,scale=1.0,size=None)
    有时随机生成的Tensor的size要与模型中其他一些Variable的Tensor同形(size)

  • numpy生成随机第一维索引
    只对第一维做打乱顺序操作:random.shuffle(dim1)
    This function only shuffles the array along the first index of a multi-dimensional array
    就是说,多维矩阵中,只对第一维(行)做打乱顺序操作

  • index_select(input,dim,index)生成Tensor索引
    指定维度上选择行或列,一般生成Long类型Tensor

  • 在每个迭代周期epoch中,我们将完整遍历一遍data_iter函数

  • 并对训练集中所有样本都使用一次(假设样本数能够被批量大小整除)

  • 这里的迭代周期个数num_epochs和学习率lr都是超参数,分别设为3和0.03

  • 但是在实践中,大多超参数需要反复试错来不断调节,

  • 虽然迭代周期设的越大模型可能越有效,但训练时间可能过长。

以上为实现过程及过程中遇到问题的记录

源码:https://github.com/ShusenTang/Dive-into-DL-PyTorch

猜你喜欢

转载自blog.csdn.net/Romaga/article/details/104304553
今日推荐