前言
Pytorch使用——回归问题样例
在上一篇文章中,我们解决了一个简单的线性回归问题,即建立了一个线性模型,通过机器学习的方法把bias和weight求出来了。
那么现在我们就要真正地搭建一个神经网络,将线性层和激活函数进行排列组合,完成一些复杂的工作,比如任意函数曲线拟合,我曾经看到有人说理论上线性层加激活函数的排列组合可以模拟任意模型。
头文件
这一次我们使用了这些库
from turtle import forward
from typing import Any, Callable
import torch
import numpy as np
import time
import torch.nn.functional as F
import matplotlib.pyplot as plt
import math
建立模型
可以看到我们这一次的类依然主要包含了两个函数,分别是初始化函数__init__
以及实例化前向预测函数forward
,而LReLU
函数是我自己写的Leaky ReLU激活函数,为了兼容所有shape的tensor,所以效率很低,最后经过测试也发现实际上效率确实很低(无奈), 追踪F.relu
函数,发现它是一个导入的C函数,所以也没办法参考源码了。
我设置了3个输入参数,分别是模型的输入参数数量,输出参数数量,隐藏层参数数量,根据隐藏层参数数量的list定义了len(list)数量的线性层
class multiLayer(torch.nn.Module):
'''
构造函数
输入参数:
_input: 输入层数量,如1
_output: 输出层数量,如1
list: 中间层结构,如[3,4,5,6]
'''
def __init__(self, _input=1, _output=1, list = []):
self.layout = len(list)
self.parameters = []
# 如果中间层list的size为0,即没有中间层,则该模型回归线性模型
if self.layout == 0:
self.model = [torch.nn.Linear(_input, _output)]
# 如果存在中间层,则模型model将被组装为[_input, list[0], list[1], ..., _output]的形式
else:
for i in range(0, self.layout):
if i == 0:
self.model = [torch.nn.Linear(_input, list[0])]
else:
self.model.append(torch.nn.Linear(list[i - 1], list[i]))
self.model.append(torch.nn.Linear(list[self.layout - 1], _output))
# self.modelSize用于储存模型的总层数 ,(输入层-隐藏层1-输出层)算2层
self.modelSize = len(self.model)
# 使用self.parameters来记录每一层的偏置和权重,用于反向传播
for i in range(0, self.modelSize):
self.parameters.append(self.model[i].weight)
self.parameters.append(self.model[i].bias)
def forward(self, x):
for i in range(0, self.modelSize):
x = self.model[i](x)
if i < self.modelSize - 1:
x = F.relu(x)
# x = self.LReLU(x)
return x
def LReLU(self, x):
size = x.shape
k = 1
for i in size:
k = k * i
x = x.view(k)
for i in x:
i = i if i > 0 else 0.01 * i
return x.view(size)
__call__: Callable[..., Any] = forward
建立训练器
我们在构造函数中传入上面建立好的模型,将模型中需要训练的参数传入SGD优化器,并设置学习率,设置损失函数为MSE均方误差。
将SGD的功能都封装一下,方便调用(zero_grad/step)。
最后,我们需要添加一个归一化方法(很重要),如果不加归一化,当输入数据十分大并且存在负数时,经过几层网络的非线性层之后,一个非常大的输入很有可能直接就变成0或者一个很小的数字,导致我们所需要的特征直接丢失了,最后使网络发散。(个人理解,如果不对,还请指正)
因此我们将输入的数据线性变换到了[0-1]
范围内了,为了方便使用,我们还定义了一个predict函数,直接调用predict函数就可以得到反归一化之后的结果了。
class multiTrain():
def __init__(self, multiLayer: multiLayer) -> None:
self.model = multiLayer
self.opt = torch.optim.SGD(self.model.parameters, lr=1e-2)
self.loss = torch.nn.MSELoss()
def predict(self, x):
return self.d_normalize(self.model(x))
def zero_grad(self):
# for i in range(0, len(self.opt)):
self.opt.zero_grad()
def step(self):
# for i in range(0, len(self.opt)):
self.opt.step()
def normalize(self, x):
self.dataMax = x.max()
self.dataMin = x.min()
return (x - self.dataMin) / (self.dataMax - self.dataMin)
def d_normalize(self, x):
return x * (self.dataMax - self.dataMin) + self.dataMin
训练
模型和训练器都搭好之后我们,接下来就是训练过程。
我们模拟了一个y = 3.24 * x ^ 2 - 4.21112
的函数。
#初始化数据
x1 = torch.tensor(np.linspace(-10,20,1000), dtype=torch.float32).view(1000,1)
x2 = torch.tensor(np.linspace(-20,40,500), dtype=torch.float32).view(500,1)
yreal = (x1 * 3.24) * x1 - 4.21112 #+ torch.randn(x1.size())
T = multiTrain(multiLayer(1, 1, list = [10,10,10,10,10,10,10,10]))
yreal = T.normalize(yreal)
for i in range(50000):
y = T.model(x1)
loss = T.loss(yreal, y)
T.zero_grad()
loss.backward()
T.step()
# 打印
if i % 100 == 0:
pass
整体代码
from turtle import forward
from typing import Any, Callable
import torch
import numpy as np
import time
import torch.nn.functional as F
import matplotlib.pyplot as plt
import math
#这是一个能够拟合任意函数的模型框架
# class linearModel(torch.nn.Module):
# def __init__(self, _input = 1, _output = 1):
# super().__init__()
# self.model = torch.nn.Linear(_input, _output)
# def forward(self, x):
# return self.model(x)
# class Train():
# def __init__(self, model) -> None:
# self.model = model
# self.opt = torch.optim.SGD(model.parameters(), lr=1e-3)
# self.loss = torch.nn.MSELoss()
class multiLayer(torch.nn.Module):
def __init__(self, _input=1, _output=1, list = []):
self.layout = len(list)
self.parameters = []
if self.layout == 0:
self.model = [torch.nn.Linear(_input, _output)]
else:
for i in range(0, self.layout):
if i == 0:
self.model = [torch.nn.Linear(_input, list[0])]
else:
self.model.append(torch.nn.Linear(list[i - 1], list[i]))
self.model.append(torch.nn.Linear(list[self.layout - 1], _output))
self.modelSize = len(self.model)
for i in range(0, self.modelSize):
self.parameters.append(self.model[i].weight)
self.parameters.append(self.model[i].bias)
def LReLU(self, x):
size = x.shape
k = 1
for i in size:
k = k * i
x = x.view(k)
for i in x:
i = i if i > 0 else 0.01 * i
return x.view(size)
def forward(self, x):
for i in range(0, self.modelSize):
x = self.model[i](x)
if i < self.modelSize - 1:
x = F.relu(x)
# x = self.LReLU(x)
return x
__call__: Callable[..., Any] = forward
class multiTrain():
def __init__(self, multiLayer: multiLayer) -> None:
self.model = multiLayer
self.opt = torch.optim.SGD(self.model.parameters, lr=1e-2)
# for i in range(0, self.model.modelSize):
# self.opt.append(torch.optim.SGD(self.model.model[i].parameters(), lr=1e-4))
self.loss = torch.nn.MSELoss()
def predict(self, x):
return self.d_normalize(self.model(x))
def zero_grad(self):
# for i in range(0, len(self.opt)):
self.opt.zero_grad()
def step(self):
# for i in range(0, len(self.opt)):
self.opt.step()
def normalize(self, x):
self.dataMax = x.max()
self.dataMin = x.min()
return (x - self.dataMin) / (self.dataMax - self.dataMin)
def d_normalize(self, x):
return x * (self.dataMax - self.dataMin) + self.dataMin
#初始化数据
x1 = torch.tensor(np.linspace(-10,20,1000), dtype=torch.float32).view(1000,1)
x2 = torch.tensor(np.linspace(-20,40,500), dtype=torch.float32).view(500,1)
x3 = torch.tensor(np.linspace(-10, 20, 30), dtype=torch.float32).view(30,1)
yreal = (x1 * 3.24) * x1 - 4.21112 #+ torch.randn(x1.size())
y3 = (x3 * 3.24) * x3 - 4.21112 #+ torch.randn(x1.size())
T = multiTrain(multiLayer(1, 1, list = [10,10,10,10,10,10,10,10]))
yreal = T.normalize(yreal)
for i in range(50000):
y = T.model(x1)
loss = T.loss(yreal, y)
T.zero_grad()
loss.backward()
T.step()
# 打印
if i % 100 == 0:
pass
# print('grad w %f and r is %f' % (T.model.model.weight, T.model.model.bias))
# 跳出条件
# if not (abs(wt.grad) > 1e-3 or abs(bt.grad) > 1e-3) :
# break
y = T.predict(x1)
y2 = T.predict(x2)
yreal = T.d_normalize(yreal)
plt.plot(x1.data.numpy(),y.data.numpy(), x2.data.numpy(), y2.data.numpy())
plt.scatter(x3.data.numpy(), y3.data.numpy())
plt.show()
结果
可以看到-10到20的训练数据区,曲线拟合的还不错,而-20到-10,以及20到40的未训练区则与实际情况相差较大,并且每次训练的结果都是不一样的。