这是我参与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,且扰动项不是很大的回归类数据集。围绕建模目标,我们可以构建一个只包含一层的神经网络进行建模。
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
复制代码
在一张计算图中,缺少了对叶节点反向传播求导数的相关运算,计算图也就失去了核心价值。因此在实际操作过程中,应该尽量避免导致叶节点丢失的相关操作。叶节点数值修改方法有三个:
- 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)
复制代码
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"
复制代码
打开浏览器,查看绘制图像
也可以进一步通过调整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)
复制代码
由于数据本身就是按照𝑦=2𝑥1−𝑥2+1基本规律加上扰动项构建的,因此通过训练完成的参数可以看出模型效果较好。当然,真实场景下我们无法从上帝视角获得真实的数据分布规律,然后通过比对模型来判断模型好坏,此时我们就需要明确模型评估指标。
近期评论