房价预测
在学习线性回归时,房价预测几乎是线性回归通常举例的第一个任务。这个案例麻雀虽小五脏俱全。直观的说明了在线性回归过程模型里所需要的概念。比如训练集、测试集、损失函数、优化函数、过拟合与欠拟合、批量计算等(只想起这些,之所以说等,是以防万一我忘了啥不被打脸)。
首先来看下面这张图,
本例呢使用了两个维度来对房价进行预测,分别是房屋面积、房屋年龄。
p
r
i
c
e
=
W
a
r
e
a
∗
a
r
e
a
+
W
a
g
e
∗
a
g
e
+
b
price = W_{area}*area+W_{age}*age+b
price=Warea∗area+Wage∗age+b
W a r e a W_{area} Warea、 W a g e W_{age} Wage和 b b b就是最终要求的参数。提个小问题,上面的式子是几维的呢?答案是3维( W a r e a W_{area} Warea、 W a g e W_{age} Wage、 p r i c e price price),不要忘了 p r i c e price price,所以最后的结果应该是一个平面。
这个式子前两项是不是很熟悉,可以转换成矩阵计算
令
W
=
(
W
a
r
e
a
W
a
g
e
)
\bf W = \begin{pmatrix} W_{area} \\ W_{age} \end{pmatrix}
W=(WareaWage)
X
=
(
a
r
e
a
a
g
e
)
\bf X = \begin{pmatrix} area \\ age \end{pmatrix}
X=(areaage)
W
\bf W
W和
X
\bf X
X变成粗体里,意味着是向量。
所以上面的式子就可以简写为
p
r
i
c
e
=
W
T
∗
X
+
b
price = \bf W^{T}*\bf X+b
price=WT∗X+b
p
r
i
c
e
price
price用
y
y
y来表示更像数学一些
y
=
W
T
∗
X
+
b
y = \bf W^{T}*\bf X+b
y=WT∗X+b
损失函数
损失函数是反应预测值与真实值之间的误差的函数,本例中使用的是平方差。
l
o
s
s
=
1
2
(
y
i
ˊ
−
y
i
)
2
loss = \frac{1}{2} (\acute{y^i}-y^i)^2
loss=21(yiˊ−yi)2
其中
y
i
ˊ
\acute{y^i}
yiˊ为预测值,
y
i
y^i
yi为训练集样本中的真实值。
l
o
s
s
=
1
2
(
y
i
ˊ
−
y
i
)
2
loss = \frac{1}{2} (\acute{y^i}-y^i)^2
loss=21(yiˊ−yi)2
将
y
y
y带入上式中
l
o
s
s
=
1
2
(
W
T
∗
X
i
+
b
−
y
i
)
2
loss = \frac{1}{2} (\bf W^{T}*\bf X^i+b-y^i)^2
loss=21(WT∗Xi+b−yi)2
这里这个
l
o
s
s
loss
loss表示的还是第
i
i
i个样本点的损失,推广到全体样本点得到如下式子
L
o
s
s
(
W
,
b
)
=
1
n
∑
i
=
1
n
l
o
s
s
i
=
1
n
∑
i
=
1
n
1
2
(
W
T
∗
X
i
+
b
−
y
i
)
2
Loss(W,b) = \frac{1}{n} \sum_{i=1}^nloss^i = \frac{1}{n} \sum_{i=1}^n \frac{1}{2} (\bf W^{T}*\bf X^i+b-y^i)^2
Loss(W,b)=n1i=1∑nlossi=n1i=1∑n21(WT∗Xi+b−yi)2
注意到,模型的总误差
L
o
s
s
(
W
,
b
)
Loss(W,b)
Loss(W,b)取的是所有样本的误差的和的平均值。
优化函数
这里首先要提的概念是:解析解(analytical solution)、数值解(numerical solution)。所谓解析解是上面的误差最小化问题的解可以直接用公式表达出来。然而大多数深度学习模型的误差值无法用具体的式子来表示,只能在迭代训练中不断优化以达到最小值,这样算出来的叫数值解。
在求解最优问题时使用的算法称梯度下降,梯度下降有几种变形:批量随机下降(BGD)、随机梯度下降(SGD)、小批量梯度下降(MBGD)。
在深度学习之中,我们往往不是一个数据一个数据的计算,而是同时计算一批(batch)数据,所以实际使用中往往用的是小批量随机梯度下降(mini-batch stochastic gradient descent),单个数据与批量数据的区别只是传入的数据是一个列向量还是多个列向量。
小批量随机梯度下降
- 选取一组模型参数的初始值,如随机选取
- 计算该批量数据的平均损失的梯度,更新 W \bf W W和 b b b
- 用新的
W
\bf W
W和
b
b
b来继续循环第一步
( W , b ) : = ( W , b ) − α 1 m ∑ i = 1 m ∂ w , b l o s s i (W,b) :=(W,b) - α\frac{1}{m} \sum_{i=1}^m {∂_{w,b}} loss^i (W,b):=(W,b)−αm1i=1∑m∂w,blossi
α α α是学习率,代表在每次优化中,能够学习的步长的大小,本质上是给要更新梯度的值进行放缩。步子不能太大,因为容易…哈哈哈
这里其实简化了中间一步,直接求偏导对 W \bf W W和 b b b进行了更新.
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)
我试了试,如果不加噪声,长得也差不多,只是看上去更稀疏一些。
#读取数据集
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函数的解释>>https://www.cnblogs.com/MartinLwx/p/10543604.html
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的梯度值是多少。
d
=
(
x
1
+
3
)
2
+
(
x
2
+
3
)
2
2
d=\frac{(x_1+3)^2+(x_2+3)^2}{2}
d=2(x1+3)2+(x2+3)2
d
d
d对
x
1
x_1
x1的偏导为
∂
d
∂
x
1
=
x
1
+
3
\frac{∂d}{∂x_1}=x_1+3
∂x1∂d=x1+3
同理对
x
2
x_2
x2的偏导
∂
d
∂
x
2
=
x
2
+
3
\frac{∂d}{∂x_2}=x_2+3
∂x2∂d=x2+3
我们计算
∂
d
∂
x
1
∣
x
1
=
5
=
x
1
+
3
=
8
\frac{∂d}{∂x_1}|_{x_1=5}=x_1+3=8
∂x1∂d∣x1=5=x1+3=8
∂
d
∂
x
2
∣
x
2
=
2
=
x
2
+
3
=
5
\frac{∂d}{∂x_2}|_{x_2=2}=x_2+3=5
∂x2∂d∣x2=2=x2+3=5
所以最后的
x
x
x的梯度为(8,5)
用程序验证一下