做一个简单的回归预测模型的步骤和基本要素:
- 我们要有数据集,观察数据集的变量和标签,明确要解决的问题,分割为训练集和测试集,用于预测的标签的因素成为特征
- 模型,主要看我们怎么去考虑,线性回归就直接乘上w参数,例如面积和房龄
- 损失函数,衡量预测值和真实值之间的误差,通常会选择一个非负数作为误差,数值越小越好,这里用平方误差.
- 优化函数:误差最小化问题的解可以直接用公式表达,叫做解析解。上面的线性回归+平方误差属于这个范畴。大多数deep model是没有解析解,就需要优化算法有限次迭代模型参数,尽量让损失函数的值降到最低,这叫数值解
(常用:mini-batch 随机梯度下降,选定一组模型参数初始值,对参数进行多次迭代,降低损失函数的值,每次迭代中,先随机均匀采样一个固定的数量的训练样本组成小批量mini-batch,然后求这个mini-batch的损失函数的模型参数的导数,最后用这个结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。)
平方误差的损失函数公式:
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(i)(w,b)=21(y^(i)−y(i))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 L(w,b)=n1∑i=1nl(i)(w,b)=n1∑i=1n21(w⊤x(i)+b−y(i))2
mini-batch随机梯度下降优化函数:
(
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)
(w,b)←(w,b)−∣B∣η∑i∈B∂(w,b)l(i)(w,b)
1. 线性回归简单实现
# -*- coding: utf-8 -*-
"""
Created on Thu Feb 13 10:35:57 2020
线性回归模型从零开始的实现
以房价预测为例,参数有面积和楼龄,label为房价。1000个样本
"""
import torch
from matplotlib import pyplot as plt
import numpy as np
import random
#特征数
num_inputs = 2
#样本数
num_examples = 1000
# 真实的w和b值
true_w = [2, -3.4]
true_b = 4.2
#生成1000个样本的特征以及标签
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)
#查看数据分布
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);
#1. 读取数据集
def data_iter(batch_size,features,labels): #一个生成器
num_examples = len(features) #样本数
indices = list(range(num_examples)) #样本下标
random.shuffle(indices) #打乱顺序,看上去更加真实
for i in range(0,num_examples,batch_size): #根据batchSize大小读取数据
j = torch.LongTensor(indices[i:min(i+batch_size,num_examples)])
yield features.index_select(0,j) , labels.index_select(0,j) #index_select为pytoch方法,按照下标选择数据
def check_data_iter(features,labels):
#查看数据集读取情况
batch_size = 10
for X,y in data_iter(batch_size,features,labels):
print(X,'\n',y)
break
#2. 初始化模型参数
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)
#3.定义模型
def linreg(X,w,b):
return torch.mm(X,w)+b #定义一个线性模型,mm代表X和w矩阵相乘,mul是对应位相乘
#4.定义损失函数
def squared_loss(y_hat,y):
return (y_hat-y.view(y_hat.size())) ** 2 / 2
#5.定义优化函数,用mini-batch SGD
def sgd(params,lr,batch_size):
for param in params:
param.data -= lr * param.grad / batch_size #.data作用是让参数没有梯度的跟踪,这样不会被之前的梯度变化影响到
#6. 训练过程:
batch_size = 10
lr = 0.03
num_epochs=6
for epoch in range(num_epochs): #重复训练num_epochs次
for X,y in data_iter(batch_size,features,labels):
#计算这个batch的损失
loss_sum = squared_loss(linreg(X,w,b),y).sum()
#计算这个batch的损失的梯度
loss_sum.backward()
#利用小批量随机梯度下降去迭代模型参数
sgd([w,b],lr,batch_size)
#梯度更时时,每一次运算后都需要将上一次的梯度记录清空
w.grad.data.zero_()
b.grad.data.zero_()
#梯度更新完一次,计算所有样本的损失值
train_l = squared_loss(linreg(features,w,b),labels)
print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
print('\n w is',w,'\n true w is' ,true_w,'\n b is', b,'\n true_ is', true_b)
Pytorch简易实现
# -*- coding: utf-8 -*-
"""
Created on Thu Feb 13 12:01:49 2020
@author: zyuyiuwai
线性回归模型从零开始的实现pytoch版本
"""
import torch
from torch import nn
import numpy as np
torch.manual_seed(1)
torch.set_default_tensor_type('torch.FloatTensor')
#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. 读取数据 (从这一步开始,可以用pytoch中的方法,快速建立神经网络)
import torch.utils.data as Data
batch_size = 10
#把特征和标签集合在一个dataset中
dataset = Data.TensorDataset(features,labels)
data_iter = Data.DataLoader(
dataset = dataset, #torch tensorDataset format
batch_size=batch_size, #mini batch size
shuffle=True, #是否打乱数据
# num_workers=2, #用多线程来读取数据
)
#3. 定义模型
class LinearNet(nn.Module):
def __init__(self,n_feature):
super(LinearNet,self).__init__() #初始化父类的init方法
self.linear = nn.Linear(n_feature,1)
## 函数原型: `torch.nn.Linear(in_features, out_features, bias=True)`
def forward(self,x):
y = self.linear(x)
return y
#3.2 初始化一个多层网络的3个方法
#3.2.1
net = nn.Sequential(
nn.Linear(num_inputs,1),
#后续添加多个分层
# nn.Linear(num_inputs,1),
# nn.Linear(num_inputs,1),
#....
)
##3.2.2
#net = nn.Sequential()
#net.add_module('linear',nn.Linear(num_inputs,1))
#net.add_module('linear',nn.Linear(num_inputs,1))
#net.add_module('linear',nn.Linear(num_inputs,1))
#net.add_module('linear',nn.Linear(num_inputs,1))
##....
#
##3.2.3
#from collections import OrderedDict
#net = nn.Sequential(OrderedDict([
# ('linear',nn.Linear(num_inputs,1)),
# ('linear',nn.Linear(num_inputs,1)),
# ('linear',nn.Linear(num_inputs,1)),
# ('linear',nn.Linear(num_inputs,1)),
# ('linear',nn.Linear(num_inputs,1))
# #......
# ]))
#4. 初始化模型参数
from torch.nn import init
init.normal_(net[0].weight,mean=0.0,std=0.01)
init.constant_(net[0].bias,val=0.0)
#for param in net.parameters():
# print(param)
#5. 定义损失函数
loss = nn.MSELoss()
# function prototype: `torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')`
#6.定义优化函数
import torch.optim as optim
optimizer = optim.SGD(net.parameters(),lr = 0.03)
# function prototype: `torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False)`
#7. 训练过程
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() #重置梯度
l.backward()
optimizer.step()
print('epoch %d, loss: %f' % (epoch, l.item()))
# result comparision
dense = net[0]
print(true_w, dense.weight.data)
print(true_b, dense.bias.data)
pytoch的一些小知识点
torch.cuda.is_available()
查看是否用了显卡
view的用法:
a=torch.randn(3,4,5,7)
b = a.view(1,-1)
如上例中,第一个参数1将第一个维度的大小设定成1,
后一个-1就是说第二个维度的大小=元素总数目/第一个维度的大小,
此例中为3*4*5*7/1=420.
(-1,1)同理,第一个维度等于元素总数目,第二个维度为1
Softmax和分类模型
softmax回归就是对离散值的预测,可以转化为分类问题,例如比较简单的图像分类任务。
我们假设输入图像的像素为4,色彩为灰度,那么这4个像素就可以作为输入
x
1
,
x
2
,
x
3
,
x
4
x_1, x_2, x_3, x_4
x1,x2,x3,x4,输出假设为猪、狗、羊,并用离散数值表示类别
y
1
=
1
,
y
2
=
2
,
y
3
=
3
y_1=1, y_2=2, y_3=3
y1=1,y2=2,y3=3
softmax回归和线性回归相似,也是单层神经网络.
分类问题需要得到离散的预测输出,第一想法就是把输出值oi作为预测类别是i的置信度,把值最大的输出对应的类作为预测输出,即
arg
max
i
o
i
\underset{i}{\arg\max} o_i
iargmaxoi
不过上面的做法会导致两个问题:
1.输出层的输出值的范围不确定,难以确定这个值的意义
2.真实标签是离散值,这些离散值与不确定范围的输出值之间的误差难以衡量
所以提出了softmax运算符:
y
^
1
,
y
^
2
,
y
^
3
=
softmax
(
o
1
,
o
2
,
o
3
)
\hat{y}_1, \hat{y}_2, \hat{y}_3 = \text{softmax}(o_1, o_2, o_3)
y^1,y^2,y^3=softmax(o1,o2,o3)
y
^
1
=
exp
(
o
1
)
∑
i
=
1
3
exp
(
o
i
)
,
y
^
2
=
exp
(
o
2
)
∑
i
=
1
3
exp
(
o
i
)
,
y
^
3
=
exp
(
o
3
)
∑
i
=
1
3
exp
(
o
i
)
.
\hat{y}1 = \frac{ \exp(o_1)}{\sum_{i=1}^3 \exp(o_i)},\quad \hat{y}2 = \frac{ \exp(o_2)}{\sum_{i=1}^3 \exp(o_i)},\quad \hat{y}3 = \frac{ \exp(o_3)}{\sum_{i=1}^3 \exp(o_i)}.
y^1=∑i=13exp(oi)exp(o1),y^2=∑i=13exp(oi)exp(o2),y^3=∑i=13exp(oi)exp(o3).
因为 y ^ 1 + y ^ 2 + y ^ 3 = 1 \hat{y}_1 + \hat{y}_2 + \hat{y}_3 = 1 y^1+y^2+y^3=1且 0 ≤ y ^ 1 , y ^ 2 , y ^ 3 ≤ 1 0 \leq \hat{y}_1, \hat{y}_2, \hat{y}_3 \leq 1 0≤y^1,y^2,y^3≤1,所以这个一个合法的概率分布
小批量矢量计算softmax
softmax也是可以小批量矢量计算的,提高计算效率。
批量大小为n,输入个数(特征数)为d,输出个数(类别数)为q
设批量特征为
X
∈
R
n
×
d
\boldsymbol{X} \in \mathbb{R}^{n \times d}
X∈Rn×d,
W
∈
R
d
×
q
\boldsymbol{W} \in \mathbb{R}^{d \times q}
W∈Rd×q,
b
∈
R
1
×
q
\boldsymbol{b} \in \mathbb{R}^{1 \times q}
b∈R1×q
O
=
X
W
+
b
,
Y
^
=
softmax
(
O
)
,
\begin{aligned} \boldsymbol{O} &= \boldsymbol{X} \boldsymbol{W} + \boldsymbol{b},\\ \boldsymbol{\hat{Y}} &= \text{softmax}(\boldsymbol{O}), \end{aligned}
OY^=XW+b,=softmax(O),
softmax的损失函数选择
按照正常套路,都会先用平方损失估计,目标是让预测概率分布yhat尽可能接近真实标签概率分布 y y y,但是我们做分类其实不需要那么准确,只需要正确的预测值,例如 y ^ 3 \hat{y}_3 y^3比其他两个预测值 y ^ 1 \hat{y}_1 y^1和 y ^ 2 \hat{y}_2 y^2要大就行。不管其他两个预测值是多少,类别预测都正确了,而平方损失过于严格了
所以我们采用更加适合衡量两个概率分布差异的测量函数,交叉熵cross entropy:
H
(
y
(
i
)
,
y
^
(
i
)
)
=
−
∑
j
=
1
q
y
j
(
i
)
log
y
^
j
(
i
)
,
H\left(\boldsymbol y^{(i)}, \boldsymbol {\hat y}^{(i)}\right ) = -\sum_{j=1}^q y_j^{(i)} \log \hat y_j^{(i)},
H(y(i),y^(i))=−∑j=1qyj(i)logy^j(i),
y ^ ( i ) {\hat y}^{(i)} y^(i)是个向量,代表真实标签,只有正确的元素标签才会是1,其余都为0,所以得到 H ( y ( i ) , y ^ ( i ) ) = − log y ^ y ( i ) ( i ) H(\boldsymbol y^{(i)}, \boldsymbol {\hat y}^{(i)}) = -\log \hat y{y^{(i)}}^{(i)} H(y(i),y^(i))=−logy^y(i)(i),交叉熵只关心对正确类别的预测概率,因为只要其值足够大,就可以确保分类结果正确。
多层感知机
多层感知机想要阐述的就是关于多层神经网络的概念,而多层感知机就是含有多个隐含层的神经网络,如下图:
有一个隐藏层,5个隐藏单元
公式
依然是使用小批量的方式计算,输入为 X ∈ R n × d \boldsymbol{X} \in \mathbb{R}^{n \times d} X∈Rn×d,其批量大小为 n n n,输入个数为 d d d。用上面的例子只有一个隐藏层,其中隐藏单元个数为 h h h。隐藏层的输出为 H ∈ R n × h \boldsymbol{H} \in \mathbb{R}^{n \times h} H∈Rn×h。因为隐藏层和输出层均是全连接层,可以设隐藏层的权重参数和偏差参数分别为 W h ∈ R d × h \boldsymbol{W}_h \in \mathbb{R}^{d \times h} Wh∈Rd×h和 b h ∈ R 1 × h \boldsymbol{b}_h \in \mathbb{R}^{1 \times h} bh∈R1×h,输出层的权重和偏差参数分别为 W o ∈ R h × q \boldsymbol{W}_o \in \mathbb{R}^{h \times q} Wo∈Rh×q和 b o ∈ R 1 × q \boldsymbol{b}_o \in \mathbb{R}^{1 \times q} bo∈R1×q。
计算输出
O
∈
R
n
×
q
\boldsymbol{O} \in \mathbb{R}^{n \times q}
O∈Rn×q如下:
H
=
X
W
h
+
b
h
,
O
=
H
W
o
+
b
o
,
\begin{aligned} \boldsymbol{H} &= \boldsymbol{X} \boldsymbol{W}_h + \boldsymbol{b}_h,\\ \boldsymbol{O} &= \boldsymbol{H} \boldsymbol{W}_o + \boldsymbol{b}_o, \end{aligned}
HO=XWh+bh,=HWo+bo,
上面的式子是可以通过带入,分解成一个线性的式子:
O
=
(
X
W
h
+
b
h
)
W
o
+
b
o
=
X
W
h
W
o
+
b
h
W
o
+
b
o
.
\boldsymbol{O} = (\boldsymbol{X} \boldsymbol{W}_h + \boldsymbol{b}_h)\boldsymbol{W}_o + \boldsymbol{b}_o = \boldsymbol{X} \boldsymbol{W}_h\boldsymbol{W}_o + \boldsymbol{b}_h \boldsymbol{W}_o + \boldsymbol{b}_o.
O=(XWh+bh)Wo+bo=XWhWo+bhWo+bo.
由此得知,即使加多少隐藏层,都可以等价于一个仅含有输出层的单层神经网络,问题处在于全连接层只是对数据做仿射变换(affine transformation),仿射变换的叠加还是仿射变换,没有太大意义,所以我们需要引入非线性变换,引申出了激活函数这个概念
激活函数
我们对每个单元计算完结果后,再进行非线性函数的变换,这个非线性函数就是激活函数。以下是常用激活函数介绍:
ReLU函数
ReLU
(
x
)
=
max
(
x
,
0
)
.
\text{ReLU}(x) = \max(x, 0).
ReLU(x)=max(x,0).
特点:只保留正数元素,负数元素则清零。
torch的实现:
x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = x.relu()
还有leaky_ReLU,就是不把负数元素直接清零,而是手工指定一个数字,通常使用0.01或0.001这样很小的数字
Sigmoid函数
sigmoid函数可以将元素的值变换到0和1之间:
sigmoid ( x ) = 1 1 + exp ( − x ) . \text{sigmoid}(x) = \frac{1}{1 + \exp(-x)}. sigmoid(x)=1+exp(−x)1.
y = x.sigmoid()
当输入为0时,sigmoid函数的导数达到最大值0.25;当输入越偏离0时,sigmoid函数的导数越接近0。
tanh函数
这个中文叫双曲正切函数,把元素的值变换为-1和1之间
tanh
(
x
)
=
1
−
exp
(
−
2
x
)
1
+
exp
(
−
2
x
)
.
\text{tanh}(x) = \frac{1 - \exp(-2x)}{1 + \exp(-2x)}.
tanh(x)=1+exp(−2x)1−exp(−2x).
当输入为0时,tanh函数的导数达到最大值1;当输入越偏离0时,tanh函数的导数越接近0。
如何选择?
大多数情况,隐藏层我们会使用ReLU,因为计算量少,而且我们尽量避免用sigmoid和tanh是因为梯度消失的问题,sigmoid函数多用于分类器