房价预测

在学习线性回归时,房价预测几乎是线性回归通常举例的第一个任务。这个案例麻雀虽小五脏俱全。直观的说明了在线性回归过程模型里所需要的概念。比如训练集、测试集、损失函数、优化函数、过拟合与欠拟合、批量计算等(只想起这些,之所以说等,是以防万一我忘了啥不被打脸)。
首先来看下面这张图,

本例呢使用了两个维度来对房价进行预测,分别是房屋面积、房屋年龄。
基于线性回归模型的客户价值预测实践案例 线性回归预测例题_损失函数

基于线性回归模型的客户价值预测实践案例 线性回归预测例题_线性回归_02基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_03基于线性回归模型的客户价值预测实践案例 线性回归预测例题_python_04就是最终要求的参数。提个小问题,上面的式子是几维的呢?答案是3维(基于线性回归模型的客户价值预测实践案例 线性回归预测例题_线性回归_02基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_03基于线性回归模型的客户价值预测实践案例 线性回归预测例题_基于线性回归模型的客户价值预测实践案例_07),不要忘了基于线性回归模型的客户价值预测实践案例 线性回归预测例题_基于线性回归模型的客户价值预测实践案例_07,所以最后的结果应该是一个平面。

这个式子前两项是不是很熟悉,可以转换成矩阵计算

基于线性回归模型的客户价值预测实践案例 线性回归预测例题_线性回归_09
基于线性回归模型的客户价值预测实践案例 线性回归预测例题_损失函数_10
基于线性回归模型的客户价值预测实践案例 线性回归预测例题_基于线性回归模型的客户价值预测实践案例_11基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_12变成粗体里,意味着是向量。
所以上面的式子就可以简写为
基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_13
基于线性回归模型的客户价值预测实践案例 线性回归预测例题_基于线性回归模型的客户价值预测实践案例_07基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_15来表示更像数学一些
基于线性回归模型的客户价值预测实践案例 线性回归预测例题_python_16

损失函数

损失函数是反应预测值与真实值之间的误差的函数,本例中使用的是平方差。
基于线性回归模型的客户价值预测实践案例 线性回归预测例题_基于线性回归模型的客户价值预测实践案例_17
其中基于线性回归模型的客户价值预测实践案例 线性回归预测例题_损失函数_18为预测值,基于线性回归模型的客户价值预测实践案例 线性回归预测例题_损失函数_19为训练集样本中的真实值。
基于线性回归模型的客户价值预测实践案例 线性回归预测例题_基于线性回归模型的客户价值预测实践案例_17
基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_15带入上式中
基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_22
这里这个基于线性回归模型的客户价值预测实践案例 线性回归预测例题_基于线性回归模型的客户价值预测实践案例_23表示的还是第基于线性回归模型的客户价值预测实践案例 线性回归预测例题_线性回归_24个样本点的损失,推广到全体样本点得到如下式子
基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_25
注意到,模型的总误差基于线性回归模型的客户价值预测实践案例 线性回归预测例题_线性回归_26取的是所有样本的误差的和的平均值。

优化函数

这里首先要提的概念是:解析解(analytical solution)、数值解(numerical solution)。所谓解析解是上面的误差最小化问题的解可以直接用公式表达出来。然而大多数深度学习模型的误差值无法用具体的式子来表示,只能在迭代训练中不断优化以达到最小值,这样算出来的叫数值解。
在求解最优问题时使用的算法称梯度下降,梯度下降有几种变形:批量随机下降(BGD)、随机梯度下降(SGD)、小批量梯度下降(MBGD)。

在深度学习之中,我们往往不是一个数据一个数据的计算,而是同时计算一批(batch)数据,所以实际使用中往往用的是小批量随机梯度下降(mini-batch stochastic gradient descent),单个数据与批量数据的区别只是传入的数据是一个列向量还是多个列向量。
小批量随机梯度下降

  1. 选取一组模型参数的初始值,如随机选取
  2. 计算该批量数据的平均损失的梯度,更新基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_27基于线性回归模型的客户价值预测实践案例 线性回归预测例题_基于线性回归模型的客户价值预测实践案例_28
  3. 用新的基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_27基于线性回归模型的客户价值预测实践案例 线性回归预测例题_基于线性回归模型的客户价值预测实践案例_28来继续循环第一步
    基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_31
    基于线性回归模型的客户价值预测实践案例 线性回归预测例题_损失函数_32是学习率,代表在每次优化中,能够学习的步长的大小,本质上是给要更新梯度的值进行放缩。步子不能太大,因为容易…哈哈哈
    这里其实简化了中间一步,直接求偏导对基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_27基于线性回归模型的客户价值预测实践案例 线性回归预测例题_基于线性回归模型的客户价值预测实践案例_28进行了更新.

pytorch代码

版本1:手写实现

import torch
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random

# 生成数据
# set input feature number 两个列向量
num_inputs = 2
# set example number 1000个维度,也就是1000行
num_examples = 1000
#真实的w和b,用来对随机生成的数据 生成标签
true_w = [2, -3.4]
true_b = 4.2
#随机生成样本,结果是一个1000*2的矩阵
features = torch.randn(num_examples, num_inputs,dtype=torch.float32)
#生成标签 大小为1000*1的矩阵
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)

#数据展示
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1)

基于线性回归模型的客户价值预测实践案例 线性回归预测例题_线性回归_35


我试了试,如果不加噪声,长得也差不多,只是看上去更稀疏一些。

基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_36

#读取数据集
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    #创建数据索引,indices是一个1维数组,存储数据形如[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    indices = list(range(num_examples))
    #打乱数据,最后indices形如[1, 9, 6, 4, 5, 3, 2, 0,8, 7]
    random.shuffle(indices)  # random read 10 samples
    #取第i到i+batch_size的数据
    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
        #index_select 参数0表示按行索引,1表示按列进行索引
        yield  features.index_select(0, j), labels.index_select(0, j)

以上是数据初始化和取数据的代码,到这里训练集已经准备好了,下面开始训练数据。(有点拗口,就是根据上面的数据来训练W和b,看看和我们的true_w、true_b是不是一样呢)

# w 形如tensor([[ 0.0160],[-0.0028]], requires_grad=True)
# b 形如tensor([0.], requires_grad=True)
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)
b = torch.zeros(1, dtype=torch.float32)
# 对下面两行还是不理解,跟其bp的计算方式有关吧
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)

定义模型

def linreg(X, w, b):
	# 注意 torch.mm返回的是矩阵,即使是n*1的,也是矩阵,和数组有区别,所以后面要对形状进行处理。
    return torch.mm(X, w) + b

损失函数

# 这个hat是啥缩写,hesitate?
# view函数的解释>>
def squared_loss(y_hat, y): 
    return (y_hat - y.view(y_hat.size())) ** 2 / 2

优化函数

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

训练

# 学习率
lr = 0.03
# 训练周期
num_epochs = 5
# 线性模型
net = linreg
# 使用均方误差损失函数
loss = squared_loss

# 开始训练
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):
    	# net(X, w, b)计算得出的是输入数据的标签值,是batch_size个结果。
        l = loss(net(X, w, b), y).sum()  
        # 计算梯度,这个梯度应该就是保存在了w和b中。
        l.backward()  
        # 通过梯度更新w_true和b_true
        bmgd([w, b], lr, batch_size)  
        # 梯度的临时保存容器,用完后要清零才能继续用。因为前面设置了requires_grad_(requires_grad=True)的缘故。这样设计确实省了代码量。
        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()))

以上是自己定义函数来实现的,下面是torch里的实现

版本2:torch实现

import torch
from torch import nn
import numpy as np
torch.manual_seed(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)

读取数据

import torch.utils.data as Data

batch_size = 10

# 简单多了
dataset = Data.TensorDataset(features, labels)

# put dataset into DataLoader
data_iter = Data.DataLoader(
    dataset=dataset,            # torch TensorDataset format
    batch_size=batch_size,      # batch size
    shuffle=True,               # 打乱数据
    num_workers=2,              # 2个线程读数据,额每个线程读batch_size/2吗?
)

定义模型

class LinearNet(nn.Module):
    def __init__(self, n_feature):
        super(LinearNet, self).__init__()      # call father function to init 
        self.linear = nn.Linear(n_feature, 1)  # function prototype: `torch.nn.Linear(in_features, out_features, bias=True)`
	
	# 如何传播,就是正向的计算,得到的y就是预测的标签值
    def forward(self, x):
        y = self.linear(x)
        return y
    
net = LinearNet(num_inputs)
print(net)

print的结果如下,2个输入,1个输出,1一个bias偏置

LinearNet(
(linear): Linear(in_features=2, out_features=1, bias=True)
)

初始化模型参数

from torch.nn import init
# 这个就可以把init当成一个工具,把要初始化的参数传进去就可以了。
# init.normal_是正态分布初始化,这里w就俩参数,感觉也没啥必要
# init的方法自动加上了那句requires_grad_(requires_grad=True)
init.normal_(net[0].weight, mean=0.0, std=0.01)
# torch.nn.init.constant_(tensor, val) 初始化常量
init.constant_(net[0].bias, val=0.0)  # or you can use `net[0].bias.data.fill_(0)` to modify it directly  这句话意思是 可以直接给他填充0也行。

定义损失函数

# 就是均方误差损失函数
loss = nn.MSELoss()

定义优化函数

import torch.optim as optim
# 很明显,函数名叫SGD
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 就是之前的y
        output = net(X)
        l = loss(output, y.view(-1, 1))
        l.backward()
        # 更新梯度
        optimizer.step()
        # 参数附加梯度清零,这个清零还是放到最后吧,理解上好一点。虽然把他放到l.backward()之前也不影响迭代
        optimizer.zero_grad() # reset gradient, equal to net.zero_grad()
    print('epoch %d, loss: %f' % (epoch, l.item()))
# 输出
dense = net[0]
print(true_w, dense.weight.data)
print(true_b, dense.bias.data)

epoch 1, loss: 0.000148
epoch 2, loss: 0.000098
epoch 3, loss: 0.000145

[2, -3.4] tensor([[ 1.9995, -3.3996]])
4.2 tensor([4.2005])

如何初始化多层网络

# 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])

l.backward() 到底做了什么?

字面上理解是做了反向传播,那么是如何计算的?
其实很简单,我们做一个小的实验

import torch
from torch.autograd import Variable as var
# 初始化一个a,这个例子是求a的梯度
a = var(torch.FloatTensor([5, 2]), requires_grad=True)
b = a + 3
c = b ** 2
d = c.mean()
d.backward()

执行完这段代码后,a的梯度就放到了a.grad.data中,先来手算一遍a的梯度值是多少。

基于线性回归模型的客户价值预测实践案例 线性回归预测例题_损失函数_37

基于线性回归模型的客户价值预测实践案例 线性回归预测例题_损失函数_38基于线性回归模型的客户价值预测实践案例 线性回归预测例题_损失函数_39的偏导为

基于线性回归模型的客户价值预测实践案例 线性回归预测例题_机器学习_40

同理对基于线性回归模型的客户价值预测实践案例 线性回归预测例题_线性回归_41的偏导

基于线性回归模型的客户价值预测实践案例 线性回归预测例题_python_42

我们计算

基于线性回归模型的客户价值预测实践案例 线性回归预测例题_python_43

基于线性回归模型的客户价值预测实践案例 线性回归预测例题_损失函数_44

所以最后的基于线性回归模型的客户价值预测实践案例 线性回归预测例题_基于线性回归模型的客户价值预测实践案例_45的梯度为(8,5)

用程序验证一下

基于线性回归模型的客户价值预测实践案例 线性回归预测例题_基于线性回归模型的客户价值预测实践案例_46

画一张图

基于线性回归模型的客户价值预测实践案例 线性回归预测例题_python_47

参考文章

  1. 两个张量是否是broadcastable
  2. broadcasting-semantics
  3. torch.view详解
  4. 传送门:对每种梯度下降的理解