L1线性回归

线性回归

主要内容包括:

  1. 线性回归的基本要素
  2. 线性回归模型从零开始的实现
  3. 线性回归模型使用pytorch的简洁实现

线性回归的基本要素

模型

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

p r i c e = w a r e a a r e a + w a g e a g e + b \mathrm{price} = w_{\mathrm{area}} \cdot \mathrm{area} + w_{\mathrm{age}} \cdot \mathrm{age} + b

数据集

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

损失函数

在模型训练中,我们需要衡量价格预测值与真实值之间的误差。通常我们会选取一个非负数作为误差,且数值越小表示误差越小。一个常用的选择是平方函数。 它在评估索引为 i i 的样本误差的表达式为

l ( i ) ( w , b ) = 1 2 ( y ^ ( i ) y ( i ) ) 2 , l^{(i)}(\mathbf{w}, b) = \frac{1}{2} \left(\hat{y}^{(i)} - y^{(i)}\right)^2,

L ( w , b ) = 1 n i = 1 n l ( i ) ( w , b ) = 1 n i = 1 n 1 2 ( w x ( i ) + b y ( i ) ) 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)。

在求数值解的优化算法中,小批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch) B \mathcal{B} ,然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。

( w , b ) ( w , b ) η B i B ( w , b ) l ( i ) ( w , b ) (\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)

学习率: η \eta 代表在每次优化中,能够学习的步长的大小
批量大小: B \mathcal{B} 是小批量计算中的批量大小batch size

总结一下,优化函数的有以下两个步骤:

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

矢量计算

在模型训练或预测时,我们常常会同时处理多个数据样本并用到矢量计算。在介绍线性回归的矢量计算表达式之前,让我们先考虑对两个向量相加的两种方法。

  1. 向量相加的一种方法是,将这两个向量按元素逐一做标量加法。
  2. 向量相加的另一种方法是,将这两个向量直接做矢量加法。
import torch
import time

# init variable a, b as 1000 dimension vector
n = 1000
a = torch.ones(n)
b = torch.ones(n)

# define a timer class to record time
class Timer(object):
    """Record multiple running times."""
    def __init__(self):
        self.times = []
        self.start()

    def start(self):
        # start the timer
        self.start_time = time.time()

    def stop(self):
        # stop the timer and record time into a list
        self.times.append(time.time() - self.start_time)
        return self.times[-1]

    def avg(self):
        # calculate the average and return
        return sum(self.times)/len(self.times)

    def sum(self):
        # return the sum of recorded time
        return sum(self.times)

现在我们可以来测试了。首先将两个向量使用for循环按元素逐一做标量加法。

timer = Timer()
c = torch.zeros(n)
for i in range(n):
    c[i] = a[i] + b[i]
'%.5f sec' % timer.stop()
'0.01432 sec'

另外是使用torch来将两个向量直接做矢量加法:

timer.start()
d = a + b
'%.5f sec' % timer.stop()
'0.00024 sec'

结果很明显,后者比前者运算速度更快。因此,我们应该尽可能采用矢量计算,以提升计算效率。

一、线性回归模型从零开始的实现

1.1 导入必的包

# import packages and modules
%matplotlib inline
import torch
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random

print(torch.__version__)
1.3.0

1.2 模拟生成实验用数据集

使用线性模型来生成数据集,生成一个1000个样本的数据集,下面是用来生成数据的线性关系:

p r i c e = w a r e a a r e a + w a g e a g e + b \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

# set true weight and bias in order to generate corresponded label
true_w = [2, -3.4]
true_b = 4.2

features = torch.randn(num_examples, num_inputs,
                      dtype=torch.float32)
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)

1.3 模拟数据的可视化

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

1.4读取模拟生成的数据集

在训练模型的时候,我们需要遍历数据集并不断读取小批量数据样本。

# 定义一个函数data_iter:它每次返回 batch_size(批量大小)个随机样本的特征和标签。
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    # 样本的读取顺序是随机的
    random.shuffle(indices)  # random read 10 samples
    for i in range(0, num_examples, batch_size):
        j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)]) # the last time may be not enough for a whole batch
        #函数根据索引返回对应元素
        yield  features.index_select(0, j), labels.index_select(0, j)
# 验证torch.index_select() 功能的程序:
j=torch.LongTensor([1,2,3,4,5,6,7,8,10])
features.index_select(0,j)
 #torch.index_select()	
#在指定维度dim上选择,比如选取某些行、某些列。返回不Tensor不与原Tensor共享内存。
#在python中,dim=0,表示按行取,dim=1表示按列取
tensor([[-0.8797,  1.0840],
        [ 1.4686,  0.5604],
        [ 0.6072, -1.0188],
        [-0.3210,  1.1137],
        [ 0.4691,  1.2000],
        [-0.8294, -0.8613],
        [ 0.9604, -0.2414],
        [ 0.3751, -0.8777],
        [-0.2483,  0.1386]])
#读取第一个小批量数据样本并打印。每个批量的特征形状为(10, 2),
#分别对应批量大小和输入个数;标签形状为批量大小。
# 注意去掉break 之后 将输出所有批次数据
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break
tensor([[ 0.6798,  0.2157],
        [-0.3323, -0.7287],
        [ 0.7562, -0.0557],
        [-0.2248, -0.6173],
        [-3.0879, -0.7436],
        [-0.9020, -0.1528],
        [ 0.4947, -0.2986],
        [ 1.4328, -0.7418],
        [ 0.3510, -0.3221],
        [ 0.5044, -1.0165]]) 
 tensor([4.8287, 5.9974, 5.8995, 5.8659, 0.5635, 2.9160, 6.1984, 9.6012, 6.0001,
        8.6722])

1.5初始化模型参数

我们将权重初始化成均值为0、标准差为0.01的正态随机数,偏差则初始化成0:

w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)# 注意本问题num_inputs 为2
b = torch.zeros(1, dtype=torch.float32)                                                                           
#模型训练中需要对这些参数求梯度来迭代参数的值,因此我们需要创建它们的梯度:
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
tensor([0.], requires_grad=True)

1.6定义模型

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

p r i c e = w a r e a a r e a + w a g e a g e + b \mathrm{price} = w_{\mathrm{area}} \cdot \mathrm{area} + w_{\mathrm{age}} \cdot \mathrm{age} + b

#线性回归的矢量计算表达式的实现,我们使用 torch.mm做矩阵乘法:
#torch.mm(a, b)是矩阵a和b矩阵相乘,torch.mul(a, b)是矩阵a和b对应位相乘
def linreg(X, w, b):
    return torch.mm(X, w) + b

1.7 定义损失函数

我们使用的是均方误差损失函数:
l ( i ) ( w , b ) = 1 2 ( y ^ ( i ) y ( i ) ) 2 , l^{(i)}(\mathbf{w}, b) = \frac{1}{2} \left(\hat{y}^{(i)} - y^{(i)}\right)^2,

为实施运算,用y.view(y_hat.size())把 y 变形成 y_hat 的形状,防止y_hat,和y维度不一样
torch 类型可使用view()来改变Tensor形状。如:y = x.view(12),z = x.view(-1,6),其中-1表示所指的维度可以根据其他维度的值推算出来。
1.view()返回的新tensor与源tensor共享内存,就是一个tensor,改变其中的一个,另一个也会跟着改变,view仅仅是改变了对这个张量的观察角度。
2.此外reshape()函数可以改变形状,但是此函数并不能保证返回的是其拷贝,不推荐使用。
3.推荐使用clone创造一个副本,然后使用view。使用clone还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源Tensor。

def squared_loss(y_hat, y): 
    return (y_hat - y.view(y_hat.size())) ** 2 / 2

1.8定义优化函数

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

( w , b ) ( w , b ) η B i B ( w , b ) l ( i ) ( w , b ) (\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)
以下的sgd函数实现了小批量随机梯度下降算法。它通过不断迭代模型参数来优化损失函数。
这里自动求梯度模块计算得来的梯度是一个批量样本的梯度和。我们将它除以批量大小来得到平均值。

def sgd(params, lr, batch_size): 
    for param in params:
        param.data -= lr * param.grad / batch_size # ues .data to operate param without gradient track

训练

当数据集、模型、损失函数和优化函数定义完了之后就可来准备进行模型的训练了。

# super parameters init
lr = 0.03
num_epochs = 5

net = linreg
loss = squared_loss

# training
for epoch in range(num_epochs):  # training repeats num_epochs times
    # in each epoch, all the samples in dataset will be used once
    
    # X is the feature and y is the label of a batch sample
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y).sum()  
        # calculate the gradient of batch sample loss 
        l.backward()  
        # using small batch random gradient descent to iter model parameters
        sgd([w, b], lr, batch_size)  
        # reset parameter gradient
        #在grad更新时,每一次运算后都需要将上一次的梯度记录清空
        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()))
epoch 1, loss 0.035207
epoch 2, loss 0.000123
epoch 3, loss 0.000054
epoch 4, loss 0.000054
epoch 5, loss 0.000054
w, true_w, b, true_b
(tensor([[ 1.9999],
         [-3.4002]], requires_grad=True),
 [2, -3.4],
 tensor([4.2003], requires_grad=True),
 4.2)

二、线性回归模型使用pytorch的简洁实现

import torch
from torch import nn
import numpy as np
torch.manual_seed(1)

print(torch.__version__)
torch.set_default_tensor_type('torch.FloatTensor')
1.3.0

2.1生成数据集

在这里生成数据集跟从零开始的实现中是完全一样的。

num_inputs = 2
num_examples = 1000

true_w = [2, -3.4]
true_b = 4.2

features = torch.tensor(np.random.normal(0, 1, (num_examples, num_inputs)), dtype=torch.float)
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.float)

2.2读取数据集

import torch.utils.data as Data

batch_size = 10

# combine featues and labels of dataset
dataset = Data.TensorDataset(features, labels)

# put dataset into DataLoader
data_iter = Data.DataLoader(
    dataset=dataset,            # torch TensorDataset format
    batch_size=batch_size,      # mini batch size
    shuffle=True,               # whether shuffle the data or not
    num_workers=2,              # read data in multithreading
)
for X, y in data_iter:
    print(X, '\n', y)
    break
tensor([[ 0.0949, -2.0367],
        [ 0.0957, -2.4354],
        [ 0.1520, -1.5686],
        [ 1.3453,  0.1253],
        [ 0.3076, -1.0100],
        [-0.6013,  1.6175],
        [ 0.2898,  0.2359],
        [ 0.4352, -0.4930],
        [ 0.9694, -0.8326],
        [-1.0968, -0.2515]]) 
 tensor([11.3024, 12.6900,  9.8462,  6.4771,  8.2533, -2.4928,  3.9811,  6.7626,
         8.9806,  2.8489])

2.3定义模型

nn.Module是pytorch中提供的一个类,是所有神经网络模块的基类.我们自定义的模块要继承这个基类.

class LinearNet(nn.Module): # 继承 torch 的 Module ,前面已经有from torch import nn
    def __init__(self, n_feature):
        super(LinearNet, self).__init__()      # call father function to init, 继承 Module 的 __init__ 功能,
         # 定义每层用什么样的形式
        self.linear = nn.Linear(n_feature, 1) ## 注意 这里的linear是nn.Linear类型,可理解为线性层,支持“喂”数据作为输入,即linear(x)
        # function prototype: `torch.nn.Linear(in_features, out_features, bias=True)`
       
    def forward(self, x):#这同时也是 Module 中的 forward 功能
        y = self.linear(x)
        return y
net = LinearNet(num_inputs)
print(net)
LinearNet(
  (linear): Linear(in_features=2, out_features=1, bias=True)
)

super( LinearNet, self).init()
对继承自父类的属性进行初始化
首先找到LinearNet的父类(比如是类A),然后把类LinearNet的对象self转换为类A的对象,
然后“被转换”的类A对象调用自己的__init__函数

## 理解nn.linear 的例子
import torch
nn1 = torch.nn.Linear(100, 50)
input1 = torch.randn(140, 100)
output1 = nn1(input1)
output1.size()
torch.Size([140, 50])
# ways to init a multilayer network
# method one
net = nn.Sequential(
    nn.Linear(num_inputs, 1)
    # other layers can be added here
    )

# method two
net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1))
# net.add_module ......

# method three
from collections import OrderedDict
net = nn.Sequential(OrderedDict([
          ('linear', nn.Linear(num_inputs, 1))
          # ......
        ]))

print(net)
print(net[0])
Sequential(
  (linear): Linear(in_features=2, out_features=1, bias=True)
)
Linear(in_features=2, out_features=1, bias=True)

初始化模型参数

from torch.nn import init

init.normal_(net[0].weight, mean=0.0, std=0.01)
init.constant_(net[0].bias, val=0.0)  # or you can use `net[0].bias.data.fill_(0)` to modify it directly
Parameter containing:
tensor([0.], requires_grad=True)
for param in net.parameters():
    print(param)
Parameter containing:
tensor([[-0.0152,  0.0038]], requires_grad=True)
Parameter containing:
tensor([0.], requires_grad=True)

定义损失函数

loss = nn.MSELoss()    # nn built-in squared loss function
                       # function prototype: `torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')`

定义优化函数

import torch.optim as optim

optimizer = optim.SGD(net.parameters(), lr=0.03)   # built-in random gradient descent function
print(optimizer)  # function prototype: `torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False)`
SGD (
Parameter Group 0
    dampening: 0
    lr: 0.03
    momentum: 0
    nesterov: False
    weight_decay: 0
)

训练

num_epochs = 3
for epoch in range(1, num_epochs + 1):
    for X, y in data_iter:
        output = net(X)
        l = loss(output, y.view(-1, 1))
        optimizer.zero_grad() # reset gradient, equal to net.zero_grad()
        l.backward()
        optimizer.step()
    print('epoch %d, loss: %f' % (epoch, l.item()))
epoch 1, loss: 0.000290
epoch 2, loss: 0.000128
epoch 3, loss: 0.000107
# result comparision
dense = net[0]
print(true_w, dense.weight.data)
print(true_b, dense.bias.data)
[2, -3.4] tensor([[ 1.9997, -3.3999]])
4.2 tensor([4.2002])

两种实现方式的比较

  1. 从零开始的实现(推荐用来学习)

    能够更好的理解模型和神经网络底层的原理

  2. 使用pytorch的简洁实现

    能够更加快速地完成模型的设计与实现

发布了2 篇原创文章 · 获赞 0 · 访问量 41

猜你喜欢

转载自blog.csdn.net/xiuyu1860/article/details/104309108
今日推荐