脚摸脚带你学深度学习(一)——线性回归

2020/2/13 9:20:58

本文主要是介绍脚摸脚带你学深度学习(一)——线性回归,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

起名“脚摸脚”一来是为了致敬大佬,二来是为了在易读性上有所超越,本系列初衷为督促不太勤劳的作者本人好好学习,希望作者和读者都可以有所收获

一句话描述线性回归:通过优化函数,找到一条直线,最大程度的拟合数据集

模型

我们期望的拟合结果,比如y=kx_1+by = kx_1+kx_2+b,今后如遇到更复杂的再进行介绍

数据集

我们通常收集一系列的真实数据,例如多栋房屋的真实售出价格和它们对应的面积和房龄。我们希望在这个数据上面寻找模型参数来使模型的预测价格与真实价格的误差最小。在机器学习术语里,该数据集被称为训练数据集(training data set)或训练集(training set),一栋房屋被称为一个样本(sample),其真实售出价格叫作标签(label),用来预测标签的两个因素叫作特征(feature)。特征用来表征样本的特点。

损失函数

衡量对数据拟合是否准确的尺度,可以简单的视为预测值与数据真实值的差,但一般为衡量损失的方式为一般为

l^{(i)}(\mathbf{w}, b) = \frac{1}{2} \left(\hat{y}^{(i)} - y^{(i)}\right)^2,
L(\mathbf{w}, b) =\frac{1}{n}\sum_{i=1}^n l^{(i)}(\mathbf{w}, b) =\frac{1}{n} \sum_{i=1}^n \frac{1}{2}\left(\mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}\right)^2.

优化函数

下文以随机梯度下降算法为例

当模型和损失函数形式较为简单时,上面的误差最小化问题的解可以直接用公式表达出来。这类解叫作解析解(analytical solution)。然而有时深度学习问题会很复杂,很难直接用公式表达,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作数值解(numerical solution)。

优化函数有以下两个步骤:

  • (i)初始化模型参数,一般来说使用随机初始化;
  • (ii)我们在数据上迭代多次,通过在负梯度方向移动参数来更新每个参数。

理论唠叨完,就是脚摸脚环节了

我们假设价格只取决于房屋状况的两个因素,即面积(平方米)和房龄(年)。接下来我们希望探索价格与这两个因素的具体关系。线性回归假设输出与各个输入之间是线性关系:

\mathrm{price} = w_{\mathrm{area}} \cdot \mathrm{area} + w_{\mathrm{age}} \cdot \mathrm{age} + b

调包侠先调包

# import packages and modules
import torch
from IPython import display
import numpy as np
import random

print(torch.__version__)
复制代码

构建[假]数据

生成数据并使其尽可能满足

\mathrm{price} = w_{\mathrm{area}} \cdot \mathrm{area} + w_{\mathrm{age}} \cdot \mathrm{age} + b
# set input feature number 
num_inputs = 2
# set example number
num_examples = 1000

# 设置真实的权重和偏差以生成相应的标签
true_w = [2, -3.4]
true_b = 4.2
# torch.randn返回一个包含了num_examples行,num_input列的类型为float32的张量
# #API# torch.randn():torch.randn(*sizes, out=None)->Tensor
features = torch.randn(num_examples, num_inputs,
                      dtype=torch.float32)
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b

# 使labels值进行随机浮动
# #API# np.random.normal(loc=0,scale=1e-2,size=shape) loc:均值,scale:标准差
# #API# torch.Tensor(list) 可通过list构建Tensor 
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()),
                       dtype=torch.float32)
复制代码

读取数据集

'''

:param batch_size 数据批量大小
:param features 输入的数据
:param labels 输出的标签
return batch_size个数据与标签
'''
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    # 下面两行的作用是打乱数据顺序
    indices = list(range(num_examples))
    random.shuffle(indices)  
    # #API# range(start, stop[, step])
    for i in range(0, num_examples, batch_size):
        j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)]) # 防止最后一次迭代时越界
        # #API# torch.index_select(a,b,c) 第一个参数a是指索引的对象,同时可以用a.index_select(b,c)的方式使用本函数, 
        # #API#                           第二个参数b为0表示按行索引,为1表示按列索引
        # #API#                           第三个参数c为Tensor,为索引的序号
        yield  features.index_select(0, j), labels.index_select(0, j)
复制代码

yield :当函数出现此关键字时,会将函数编译为一个生成器,可供给for n in range()使用,同时此关键字承载return的功能。[这里讲的有些粗糙,有机会单独用一篇文章讲述]

初始化模型参数

# 生成随机权重的w 与  零向量b
# 其中w的shape为特征的数量*1 b的shape为1
w = torch.tensor(np.random.normal(0, 0.01, (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)
复制代码

定义模型

定义用来训练参数的训练模型:

\mathrm{price} = w_{\mathrm{area}} \cdot \mathrm{area} + w_{\mathrm{age}} \cdot \mathrm{age} + b
def linreg(X, w, b):
    # #API# torch.mm(mat1, mat2, out=None) → Tensor 对矩阵mat1和mat2进行相乘。 
    # #API# 如果mat1 是一个n×m 张量,mat2 是一个 m×p 张量,将会输出一个 n×p 张量out。
    return torch.mm(X, w) + b
复制代码

定义损失函数

我们使用的是均方误差损失函数:

l^{(i)}(\mathbf{w}, b) = \frac{1}{2} \left(\hat{y}^{(i)} - y^{(i)}\right)^2,
def squared_loss(y_hat, y): 
    return (y_hat - y.view(y_hat.size())) ** 2 / 2
复制代码

画个重点

  • y_hat是y的预测值 ,y.view()类似与Tensorflow的reshape()
  • 这里之所以将y改变形状,是因为y_hat 为[n,1],而y为[n]
  • 这是由于y_hat为X与w的乘积,即[n,m]与[m,1],其中n是数据的个数,m是数据特征的个数

定义优化函数

在这里优化函数使用的是小批量随机梯度下降:

(\mathbf{w},b) \leftarrow (\mathbf{w},b) - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{(\mathbf{w},b)} l^{(i)}(\mathbf{w},b)
"""
这里使用小批量随机梯度下降
:param params 需要调整的参数,这里指w与b
:param lr 学习率
"""
#lr表示学习率
def sgd(params, lr, batch_size): 
    for param in params:
        # 使用.data 操作来实现同时更新参数
        param.data -= lr * param.grad / batch_size 
复制代码

训练

# 超参数的设置
lr = 0.03
num_epochs = 5

net = linreg
loss = squared_loss

# training
for epoch in range(num_epochs):  
    # 在一个epoch中,每个数据都会被使用一次
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y).sum()  
        # 计算批次数据的梯度
        l.backward()  
        sgd([w, b], lr, batch_size)  
        # 清空参数的梯度
        w.grad.data.zero_()
        b.grad.data.zero_()
    train_l = loss(net(features, w, b), labels)
    print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
复制代码

写在最后

写完了总是感觉自己写的是个什么狗屁东西,但也算是在写的过程中有所收获,期待与各位一同完善。



这篇关于脚摸脚带你学深度学习(一)——线性回归的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程