利用Numpy及Pytorch实现线性回归(笔记)

        前言:初学者一只,内容仅为学习的总结,可能存在不全面、有错误等情况,望海涵并加以斧正。对于笔记中的一些内容,作者也是不知所以然,也望各路大佬解惑。

目录

一、库的调用:

二、数据集的创建:

三、读取数据:

四、初始化参数模型:

五、定义模型:

六、定义损失函数与优化算法:

七、训练模型:


一、库的调用:

import numpy as np
import matplotlib.pyplot as plt
import torch

二、数据集的创建:

        我们假设目标方程为y=2\times x_1-3.4\times x_2+4.2(正常情况下数据集要第三方提供,我们求解过程中不知道这个方程)

num_inputs = 2      # 特征数(即有两个x)
num_examples = 1000 # 样本总数
true_w = [2, -3.4]  # 权重(w1、w2)
true_b = 4.2        # 偏差值(b)
# 生成一个标准正态分布,shape=(1000,2):
features = torch.randn(num_examples, num_inputs,dtype=torch.float32)
# y = w1*x1 + w2*x2 + b:
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
# 在标签的基础上加一个随机噪声项:
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()),dtype=torch.float32)

        下面来整理一下具体流程并介绍一下其中一些函数的作用:

        首先,通过目标方程,我们确定有两个特征值(num_inputs)分别为x_1x_2,有一千个样本(num_examples)即一千组数据,但此时我们还不知道这些数据的具体数值。我们将两个权w_1=2w_2=-3.4储存在一个数组(true_w)中,将偏差值b=4.2储存在 true_b 中。

        至此三个常数项已经确定下来了,但数据集中的三个数据(y,x_1,x_2)亟待创建。所以,我们创建一个数组(features)来储存特征值,通过函数 torch.randn() 来随机生成一个形状为(1000,2)、类型为torch.float32的符合标准正态分布的数组,以构成每组数据的x_1x_2

        我们再利用 features,创建一个数组(labels)来储存标签值,使其满足公式y=w_1\times x_1+w_2\times x_2+b。需要注意的是,加偏差值的过程运用了广播机制

        但完全符合公式的数据那就一定不符合现实了,所以我们引入一个随机噪声项来对标签值产生干扰。

        先介绍函数 np.random.normal(loc = 0.0, scale = 1.0, size = None):利用这个函数,可以随机生成一个符合正态分布的数组。其中,loc 为分布的均值(即\mu),scale 为分布的标准差(即\sigma)。回到代码中,我们生成了一个形状与数组 labels 相同的、均值为0、标准差为0.01(即数据较为集中、偏差较小)的一个随机噪声数组。我们再将其加到 labels 数组中去,这样,一个具有随机性的数据集就创建完成啦!

        (以下内容非本小节要点)

        有时我们想更直观地观察 特征值 和 标签 之间的线性关系,那么我们就可以利用matplotlib库来绘制散点图:

plt.scatter(features[:,1],labels)
plt.show()

        那么我们会看到这样一副画面:

x_2与y的散点图

         这个线性关系就比较显而易见了,但细心且富有实践欲的读者就会发现,为什么我选择的是观察x_2y的线性关系而非x_1?为什么选择了x_1后的图像线性关系不如x_2明显(见下图)?

x_1与y的散点图

        这就是一个数学问题了:众所周知,x 前的系数 w 被称为“权重”,权重越大,y与该权重对应特征值的线性关系就越强。当我们把w_1的值改为1000后,再来观察x_1y的散点图,我们会惊奇地发现,它变成了“一条线”(见下图)。

w_1为1000时x_1与y的散点图

         同样,我们也不难发现:{\color{Red} w}为正数时,散点图有递增趋势;当{\color{Red} w}为负数时,散点图有递减趋势。

三、读取数据:

        大部分时候,我们所有的数据数量是很庞大的,所以我们通常采用小批量随机梯度下降算法

        我们来定义一个函数以实现小批量随机样本抽取:

def data_iter(batch_size, features, labels): # 参数 batch_size 是指批量大小
    # 通过参数 features 回推 num_examples 的值:
    num_examples = len(features)
    # 列表[0,...,num_examples-1]
    indices = list(range(num_examples))
    # 将列表 indices 随机重新排序:
    random.shuffle(indices)
    # i从0至num_examples-1,步长为batch_size
    for i in range(0, num_examples, batch_size):
        if(i+batch_size<=num_examples):
            # 创建一个tensor以保留index
            j = torch.LongTensor(indices[i:i+batch_size])
            # 利用index对featurea进行小批量切片用于样本数据
            yield features.index_select(0, j), labels.index_select(0, j)
        else:
            break

         接下来又到了解释代码的环节了:

        因为读取小批量数据的过程是要重复很多次的,所以我们选择定义一个函数(data_iter)来避免我们的程序过于冗长。函数有三个参数(batch_size、features以及labels),显然,后面两个分别是特征集与标签集,而 batch_size 则是批量大小

        在函数中,我们首先通过求 features 的长度来得出 num_examples 的值。有的读者可能会疑惑,num_examples 的值在前面的代码中不是已经有了吗?函数里还要再求一遍是不是显得有些多此一举了?请注意:此第二小节的过程,是构建数据集的过程,而在处理实际问题时,我们是直接从此小节的过程开始的,此刻,num_examples 的值尚未被定义,我们甚至都不知道它是多少。

        在求得 num_examples 的值以后,它就是一个具体的数字了。list(range(x)) 的过程,类似于Numpy中的np.arange(x),即创建一个从0到x-1的数组([0,...,x-1]),将其储存在 indices 中,但它是普通数组,而非ndarray数组。

        到目前为止,“随机”“小批量”二词还没体现在代码中,所以现在它来了。

        函数random.shuffle(lst),其作用是将一个列表内元素的顺序随机重新排列,也就是说,原本规整的 indices 数组,在经历一轮“摧残”后变得“面目全非”了,内部顺序被完完全全地打乱。

        接下来的循环结构,便是本节中的重中之重了。可见 i 从0开始,一直取到 num_examples-1为止,步长为 batch_size。j 为一个 tensor,是 indices——就是那个被随机打乱了的数列——的切片。怎么切的呢?索引从 i 到 i + batch_size 这么切,这么算来就正好是 batch_size 个元素了(牢记最后一个数不取)。到这里,大家也应该明白条件语句的作用了:当 i + batch_size 大于 num_examples 时,我们是取不满 batch_size 个元素的,所以条件语句让我们可以避免这种情况的发生。

        (注:原代码中是没有条件语句的,而是将 indices[i : i+batch_size] 写为了 indices[i:min(i+batch_size,num_examples)],我个人认为这样的话最后一次所取的数与前几次不一样就不太好,加上强迫症作祟就把代码修改了一下,也不知道是否弄巧成拙了,望指正。)

        接下来的 yield 语句,类似于 return 语句,返回两个值,分为为 features 和 labels 的切片。介绍一下 input.index_select(dim, index) 方法:input 为张量,dim 为维度,index 为索引。需要注意的是,index 的类型为tensor。通过这种方法,我们就可以从含有大量样本的训练集中随机切出了小批量(batch_size)的数据样本。(这一块虽然大致的作用讲出来了,但不知道该怎么清楚地描述原理,无论是 yield 还是 index_select,以后可能会进行补充,大家也可以自行百度。)

四、初始化参数模型:

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

        我们将权重和偏差的初始值都设为0,来构建 tensor。为了对这些参数求梯度来迭代参数的值,我们要让它们的 requires_grad = True。

        (注:源代码中权重初始化成均值为0、标准差为0.01的正态随机数,最终结果的精度确实更高,但我尚不知原理。requires_grad=True 也不是很懂)

五、定义模型:

def linreg(X, w, b):
    return torch.mm(X, w) + b

        定义函数  linreg(X,w,b)=w_1\times x_1+w_2\times x_2+b,为预测函数模型。其中torch.mm()为矩阵乘法函数。

六、定义损失函数与优化算法:

def squared_loss(y_hat, y):
    # 注意这里返回的是向量
    return (y_hat - y.view(y_hat.size())) ** 2 / 2
def sgd(params, lr, batch_size):
    for param in params:
        param.data -= lr * param.grad / batch_size # 注意这里更改param时用的param.data

        函数定义简洁明了,无需赘述。

七、训练模型:

lr = 0.03
num_epochs = 3
batch_size = 10

for epoch in range(num_epochs):  # 训练模型一共需要num_epochs个迭代周期
    # 在每一个迭代周期中,会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除)。X
    # 和y分别是小批量样本的特征和标签
    for X, y in data_iter(batch_size, features, labels):
        l = loss(linreg(X, w, b), y).sum()  # l是有关小批量X和y的损失
        l.backward()  # 小批量的损失对模型参数求梯度
        sgd([w, b], lr, batch_size)  # 使用小批量随机梯度下降迭代模型参数

        # 不要忘了梯度清零
        w.grad.data.zero_()
        b.grad.data.zero_()
    train_l = loss(linreg(features, w, b), labels)
    print('epoch %d, loss %f' %(epoch + 1, train_l.mean().item()))

         在训练中,我们将多次迭代模型参数。在每次迭代中,我们根据当前读取的小批量数据样本(特征X和标签y ), 通过调用反向函数 backward 计算小批量随机梯度,并调用优化算法 sgd 迭代模型参数。由于 batch_ size 为10,每个小批量的损失1的形状为(10, 1)。 由于变量1并不是一个标量, 所以我们可以调用. sum()将其求和得到一个标量, 再运行1.backward()得到该变量有关模型参数的梯度。注意在每次更新完参数后不要忘了将参数的梯度清零。

        在一个迭代周期(epoch) 中,我们将完整遍历一遍data_iter函数,并对训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。这里的迭代周期个数num_ epochs和学习率1r 都是超参数,分别设3和0.03。

        如此一来,便算是实现了一次线性回归算法。


        欢迎大家 参考、补充、答疑、斧正。

        六月渔烬 文

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

六月渔烬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值