搭建一个简单线性回归神经网络
在用Pytorch搭建线性回归网络之前,我们先看一下什么是线性回归:
通俗来讲,回归分析函数对于我们来讲就是一个黑匣子,在我们抛入一系列值之后,如果得到的是连续值(如天气,房价等),那么就是回归问题;如果得到的是离散值,则是分类问题。
而线性回归函数就是当我们抛入的值和得到的连续值呈线性关系的函数,其基本形式是y = k*x+b,在我们以往的学习中都是已知k和b的值,通过x来计算出y的值(这个x可能是一个标量也可能是一个矩阵),而现在我们要做的就是则相反,通过已有的x和y来反向计算出k和b的值。
这样做有什么用呢?当我们能够通过已有数据来得到k和b的值,当出现新的x的值的时候,我们就可以来预测接下来会出现的y的值了!!!
所以我们要做的就是:
1.数据处理:即得到y和x,不过在这里我们称为标签和特征,名字高大上了一点,之后当我们用更加复杂的数据以及模型的时候,则需要对其进行一些预处理来的到更好的结果
2.构建模型:在这里我们的模型就是线性回归函数y = k*x+b
3.训练模型:在这个过程中,我们会不断调整k和b的值来贴近真实值,具体怎么操作在之后会详细说明
4.预测:很明显,就是通过新的x值来预测以后的y值
接下来,让我们动手来实现第一个简单的神经网络:
- 数据处理
我们先导入所需的python库,具体做何用之后会说明,在此不用纠结
import torch import numpy as np import random
为了在模型训练完之后对比真实的k,b值和我们训练出来的值的差距,在这里我们设定输入的x为一个1000行2列的矩阵。k和b的真实值也由我们来设定,通过真实值来构建一些源数据
#构建数据 num_examples = 1000 num_inputs = 2 true_k = [3.4,1.2] true_b = 2 #通过k和b我们就可以直接来构造数据,这里用的是pytorch的相关函数 #x我们构造x为服从均值为0,方差为1的正态分布的矩阵 x = torch.from_numpy(np.random.normal(0,1,(num_examples,num_inputs))) #接下来就是通过矩阵运算得到y值 y = x[:,0] * true_k[0] + x[:,1] * true_k[1] + true_b
在构造完数据之后,我们不能直接将数据抛入模型之中,因为数据量过大,我们需要一个函数来帮助我们读取数据集。函数原理就是将数据集分割为若干个模块,函数每次会返回相应的特征和标签:
#读取数据
def Duqushuju(batch_size,x,y):
index = list(range(len(x)))#确定读取的顺序,注意是随机的
random.shuffle(index)#打乱顺序
for i in range(0,len(x),batch_size):
j = torch.LongTensor(index[i:min(i + batch_size,len(x))])
yield x.index_select(0, j), y.index_select(0,j)
简单叙述下上面函数做了什么事情,通过index这个列表来确定我们读取数据集是哪一块,之后的yield则是返回相应的特征以及标签.
- 构建模型
构造模型的第一步,我们先需要初始化一下相关的模型参数,在这里我们只需要设定k和b的值就OK
#初始化模型参数 k = torch.tensor(np.random.normal(0,0.01,(num_inputs,1)),dtype=torch.float64) b = torch.zeros(1,dtype=torch.float64)
直接设定k的值为0不利于之后我们找到k和b的最优解,因此在这里我们设定k为服从均值为0,方差为0.01,形状为2行1列的矩阵,之后的dtype=torch.float64表明我们的float类型数据占64个字节
接下来就是构造我们的线性回归模型了
#定义模型 def Moxing(x,k,b): return torch.mm(x,k)+b
构造完模型之后请思考:我们现在有了数据,有了模型的初始化参数,有了模型框架,现在的问题就是如何通过训练这个步骤来改变我们的参数值以此我们引入小批量梯度下降算法:简单来说,就是通过不断改变k和b的值,来找到最优解的目的
#定义优化算法
def sgd(params, lr, batch_size): #params就是我们要迭代的数据
for param in params:
param.data -= lr * param.grad / batch_size # 注意这里更改param时用的param.data
为了判断我们的真实值和训练出来的值的拟合程度,我们需要一个函数来度量拟合程度,这个函数就叫做损失函数:L(Y,f(x)) = (Y - f(x))^2
def squared_loss(y_hat, y):#y_hat为计算出来的值,y为真实值
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
- 训练模型
epoch概念:在梯度下降的过程中,我们如果直接将所有数据集来进行训练(fullbatch),效率便会很低,因此,我们会将其分割为若干子集,每个子集就是一个minibatch,在我们用for循环遍历所有子集之后,针对每一个子集做一次梯度下降。 然后更新参数w和b的值。接着到下一个子集中继续进行梯度下降。 这样在遍历完所有的mini batch之后我们相当于在梯度下降中做了若干次迭代。 我们将遍历一次所有样本的行为叫做一个 epoch,也就是一个世代,这样效率就会更高
#训练
#在训练之前我们要定义学习率等超参数,这些参数需要人为设定
lr = 0.03
batch_size = 10
net = Moxing
loss = squared_loss
num_epoch = 10
for epoch in range(num_epoch):
for x,y in Duqushuju(batch_size,x,y):
l = loss(net(x,k,b), y).sum()#因为l计算出来不是标量,为了方便求梯度来求和
l.backward()
sgd([k,b],lr,batch_size)
#因为梯度会累加,所以不要忘记梯度清0
w.grad.data.zero_()
b.grad.data.zero_()
train_l = loss(net(x,w,b), y)
print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
注意当我们在设置k和b的值的时候如果将其类型不设为float64则会报错
在此我们就建立了一个最简单的线性回归神经网络!
补充:梯度回归
因为我们需要代价函数来找到最优的w和b,而对于代价函数来说,最好就是在一个非凸函数里面找到最优解,如下:
在上面这张图中可以看到代价函数为了找到J(w,b)的最优解(即图中的最低点),我们会给w,b一个初值,再用梯度下降办法一次次迭代,直至找到最优解
具体操作为用原w减去学习率和梯度的乘积(即该点的导数),这样来一次次优化