深度学习之线性回归建模

497 阅读6分钟

这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战

一 深度学习建模流程

在实际深度学习建模过程中,无论是手动实现还是调库实现,都需要遵循深度学习建模一般流程。

  • 模型选择:在深度学习领域,模型选择过程就是确定神经网络的基本结构,确定神经网络的层数,每一层神经元的个数及选择激活函数
  • 确定目标函数:在确定模型基本机构之后,根据模型实际情况确定目标函数,构建一个包含模型参数的函数工程,且方程取值和建模目的一致,大多数情况下,我们是求解方程的极小值
  • 选择优化方法:围绕目标函数进行最小值求解,根据损失函数的函数特性,兼顾实际算力的消耗,选择最优的工具,
  • 模型训练:将模型训练到可用,利用优化方法,求解损失函数,并得到一组模型函数,对应在神经网络中,得到一组连接神经元的参数取值。

二 线性回归建模的手动实现

import random
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import torch
from torch import nn,optim
import torch.nn.functional as F
from torch.utils.data import Dataset,TensorDataset,DataLoader
from torch.utils.tensorboard import SummaryWriter

函数创建中曾创建的函数

#回归类数据集创建函数
def tensorGenReg(num_examples=1000,w=[2,-1,1],bias=True,delta=0.01,deg=1):
    """
    param num_examples:创建数据集的数据量
    param w:截距的特征向量
    param bias:是否有解决
    param delta:扰动项取值
    param deg:方程次数
    
    """
    if bias==True:
        num_inputs=len(w)-1
        features_true=torch.randn(num_examples,num_inputs)  #不包含全是1的列的特征张量
        w_true=torch.tensor(w[:-1]).reshape(-1,1).float()   #自变量系数
        b_true=torch.tensor(w[-1]).float()   #截距
        if num_inputs==1:
            labels_true=torch.pow(features_true,deg)*w_true+b_true
        else:
            labels_true=torch.mm(torch.pow(features_true,deg),w_true)+b_true
        features=torch.cat((features_true,torch.ones(len(features_true),1)),1)
        labels=labels_true+torch.randn(size=labels_true.shape)*delta
        
    else:
        num_inputs=len(w)
        features=torch.randn(num_examples,num_inputs)
        w_true=torch.tensor(w).reshape(-1,1).float()
        if num_inputs==1:
            labels_true=torch.pow(features,deg)*w_true
        else:
            labels_true=torch.mm(torch.pow(features,deg),w_true)
        labels=labels_true+torch.randn(size=labels_true.shape)*delta
    return features,labels

#分类数据集的创建函数
def tensorGenCla(num_examples=500,num_inputs=2,num_class=3,deg_dispersion=[4,2],bias=False):
    """
    param num_examples:每个类别的数据数量
    Parma num_inputs:数据集特征数量
    param num_class:数据集标签类别总数
    param deg_dispersion:数据分布离散程度参数,需要输入一个列表,其中第一个参数表示每个类别的均值
    第二个参数表示随机数标准差
    param bias:逻辑回归模型时是否有截距
    
    """
    cluster_l=torch.empty(num_examples,1)  #每一类标签张量的形状
    mean_=deg_dispersion[0]    #每一类特征张量均值的参考值
    std_=deg_dispersion[1]       #每一类特征张量的方差
    lf=[]
    ll=[]
    k=mean_*(num_class-1)/2
    for i in range(num_class):
        data_temp=torch.normal(i*mean_-k,std_,size=(num_examples,num_inputs))
        lf.append(data_temp)
        labels_temp=torch.full_like(cluster_l,i)  #生产类的标签
        ll.append(labels_temp)
        
    features=torch.cat(lf).float()
    labels=torch.cat(ll).long()
    
    if bias==True:
        features=torch.cat((features,torch.ones(len(features),1)),1)  #添加一列全是1的列
    return features,labels

#小批量切分函数
def data_iter(batch_size,features,labels):
    """
    param batch_size:每个子数据集包含的数据
    Param features:输入的特征张量
    param labels:输入的标签张量
    
    """
    num_examples=len(features)
    indices=list(range(num_examples))
    random.shuffle(indices)
    l=[]
    for i in range(0,num_examples,batch_size):
        j=torch.tensor(indices[i:min(i+batch_size,num_examples)])
        l.append([torch.index_select(features,0,j),torch.index_select(labels,0,j)])
    return l

2.1 建模流程

2.1.1 模型选择

创建一个真实关系为𝑦=2𝑥1−𝑥2+1y=2x1−x2+1,且扰动项不是很大的回归类数据集。围绕建模目标,我们可以构建一个只包含一层的神经网络进行建模。

image.png

torch.manual_seed(420)
features,labels=tensorGenReg()
def linreg(X,w):
    return torch.mm(X,w)

2.1.2 确定目标函数

使用MSE作为损失函数,也就是目标函数

def squared_loss(y_hat,y):
    num_=y.numel()
    sse=torch.sum((y_hat.reshape(-1,1)-y.reshape(-1,1))**2)
    return sse/num_

2.1.3 定义优化算法

采用小批量梯度下降进行求解,每一次迭代过程都是(参数-学习率*梯度)。

def sgd(params,lr):
    params.data-=lr*params.grad
    parmas.grad.zero_()

**可微张量的in-place operation会导致系统无法区分叶节点和其他节点的问题,例如创建一个可微的w,w也是叶节点,开启可微后,w的所有计算都会纳入计算图中,但是在计算过程中,使用in-place operation,让新生成的值替换w原始值,则会报错,因为会导致系统无法判别w是叶子节点还是其他节点。

w=torch.tensor(2.,requires_grad=True)
print(w)
print(w.is_leaf)
w1=w*2
print(w1)
w=torch.tensro(2.,requiers_grad=True)
w-=w*2

image.png 在一张计算图中,缺少了对叶节点反向传播求导数的相关运算,计算图也就失去了核心价值。因此在实际操作过程中,应该尽量避免导致叶节点丢失的相关操作。叶节点数值修改方法有三个:

  • with torch.no_grad() 暂停追踪
  • w.detach_() 生成新变量
  • .data返回可微张量的取值,避免修改过程中被追踪

2.1.4 训练模型

# 系数迭代函数
def sgd(params, lr):
    """
    系数迭代函数
    """
    params.data -= lr * params.grad 
    params.grad.zero_()
#设置随机种子
torch.manual_seed(300)
#初始化核心参数
batch_size=10  #每一个小批量的数量
lr=0.03  #学习率
num_epochs=3  #训练过程遍历的数据
w=torch.zeros(3,1,requires_grad=True)   #可微的张量

#参与训练的模型的方差
net=linreg
loss=squared_loss  #MSE 作为损失函数

for epoch in range(num_epochs):
    for X,y in data_iter(batch_size,features,labels):
        l=loss(net(X,w),y)
        l.backward()
        sgd(w,lr)
    train_l=loss(net(features,w),labels)
    print('epoch %d,  loss%f'%(epoch+1,train_l))
print("*"*100)
print(w)

image.png

2.2 使用tensorboard记录迭代过程loss的变化过程

writer=SummaryWriter(log_dir='reg_loss')
#初始化核心参数
batch_size=10
lr=0.03
num_epochs=3
w=torch.zeros(3,1,requires_grad=True)

net=linreg
loss=squared_loss
for epoch in range(num_epochs):
    for X,y in data_iter(batch_size,features,labels):
        l=loss(net(X,w),y)
        l.backward()
        sgd(w,lr)
    train_l=loss(net(features,w),labels)
    writer.add_scalar('mul',train_l,epoch)

在terminals 中输入

tensorboard --logdir="reg_loss"

打开浏览器,查看绘制图像

image.png 也可以进一步通过调整num_epochs=3遍历数据的次数,查看mse的变化。

三 线性回归的快速实现

建模的实现可以调用PyTorch中的函数和类,直接完成建模。当然,该过程也是严格按照的深度学习建模流程完成的模型构建,但是,由于深度学习的特殊性,多时候我们既无法对实际的数据进行表格式的查看,也无法精确的控制模型内部的每一步运行,外加需要创建大量的类(无论是读取数据还是建模),以及大规模的参数输入,都对初学者的学习造成了不小的麻烦。因此,通过接下来的调库建模练习。

3.1 调库建模流程

3.1.1 定义核心参数

#定义核心参数
batch_size=10
lr=0.03
num_epochs=3  #遍历数据的次数

3.1.2 数据准备

#数据准备
#设置随机种子
torch.manual_seed(300)

features,labels=tensorGenReg()
features=features[:,:-1]  #删除最后一行全为1的列
data=TensorDataset(features,labels)
batchData=DataLoader(data,batch_size=batch_size,shuffle=True)#加载数据

3.1.3 定义模型

class LR(nn.Module):
    def __init__(self,in_features=2,out_features=1):  #定义模型的点结构
        super(LR,self).__init__()
        self.linear=nn.Linear(in_features,out_features)
        
    def forward(self,x):  #模型正向传播
        out=self.linear(x)
        return out
#实例化模型
LR_model=LR()

3.1.4 定义损失函数

#定义损失函数
criterion=nn.MSELoss()

3.1.5 定义优化方法

#定义优化方法
optimizer=optim.SGD(LR_model.parameters(),lr=0.03)

3.1.6 模型训练

#模型训练
def fit(net,criterion,optimizer,batchdata,epochs):
    for epoch in range(epochs):
        for X,y in batchdata:
            yhat=net.forward(X)
            loss=criterion(yhat,y)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
        writer.add_scalar('loss',loss,global_step=epoch)

3.1.7 执行模型训练

#执行模型训练
torch.manual_seed(300)

fit(net=LR_model
   ,criterion=criterion
    ,optimizer=optimizer
    ,batchdata=batchData
    ,epochs=num_epochs
   )

3.1.8 查看训练效果

#查看训练效果
print(LR_model)
print('*'*50)
#查看模型参数
print(list(LR_model.parameters()))
print('*'*50)
#计算MSE
criterion(LR_model(features),labels)

image.png 由于数据本身就是按照𝑦=2𝑥1−𝑥2+1基本规律加上扰动项构建的,因此通过训练完成的参数可以看出模型效果较好。当然,真实场景下我们无法从上帝视角获得真实的数据分布规律,然后通过比对模型来判断模型好坏,此时我们就需要明确模型评估指标。