Pytorch—softmax回归
1 知识回顾
softmax回归和一般的线性回归类似,将输入特征和权重做线性叠加。与线性回归的一个主要的不同的是,softmax回归的输出值个数等于标签里的类别数数量。这里我们以4个特征和3个分类为例。所有的权重参数的数量为12,偏差的数量为3,对于每一个输入计算
o
1
,
o
2
,
o
3
o_1,o_2,o_3
o1,o2,o3这三个输出:
o
1
=
x
1
w
11
+
x
2
w
21
+
x
3
w
31
+
x
4
w
41
+
b
1
o
2
=
x
1
w
12
+
x
2
w
22
+
x
3
w
32
+
x
4
w
42
+
b
2
o
1
=
x
1
w
13
+
x
2
w
23
+
x
3
w
33
+
x
4
w
43
+
b
3
o_1=x_1w_{11}+x_2w_{21}+x_3w_{31}+x_4w_{41}+b_1\\ o_2=x_1w_{12}+x_2w_{22}+x_3w_{32}+x_4w_{42}+b_2\\ o_1=x_1w_{13}+x_2w_{23}+x_3w_{33}+x_4w_{43}+b_3
o1=x1w11+x2w21+x3w31+x4w41+b1o2=x1w12+x2w22+x3w32+x4w42+b2o1=x1w13+x2w23+x3w33+x4w43+b3
在获取到输出值之后,我们需要将其转换成概率的形式,其计算公式为:
y
i
=
e
x
p
(
o
i
)
∑
j
=
1
3
e
x
p
(
o
i
)
y_i=\frac{exp(o_i)}{∑_{j=1}^3exp(o_i)}
yi=∑j=13exp(oi)exp(oi)
通过上述的计算公式,就可以将输出值转换成概率值。最后,根据各个分类的概率值的大小来确定最后的分类。
a
r
g
m
a
x
(
o
i
)
=
a
r
g
m
a
x
(
y
i
)
argmax(o_i)=argmax(y_i)
argmax(oi)=argmax(yi)
最后,将上述的过程整理成矩阵的形式:
W
=
w
11
w
12
w
13
w
21
w
22
w
23
w
31
w
32
w
33
w
41
w
42
w
43
W=\begin{matrix}w_{11}&w_{12}&w_{13}\\ w_{21}&w_{22}&w_{23}\\ w_{31}&w_{32}&w_{33}\\ w_{41}&w_{42}&w_{43}\\ \end{matrix}
W=w11w21w31w41w12w22w32w42w13w23w33w43
输入样本为:
x
i
=
[
x
i
1
,
x
i
2
,
x
i
3
,
x
i
4
]
x_i=[x_i^1,x_i^2,x_i^3,x_i^4]
xi=[xi1,xi2,xi3,xi4]
输入层为:
o
i
=
[
o
i
1
,
o
i
2
,
o
i
3
]
o_i=[o_i^1,o_i^2,o_i^3]
oi=[oi1,oi2,oi3]
softmax的输出结果为:
y
i
=
[
y
i
1
,
y
i
2
,
y
i
3
]
y_i=[y_i^1,y_i^2,y_i^3]
yi=[yi1,yi2,yi3]
计算过程为:
O
=
X
W
+
b
O=XW+b
O=XW+b
Y
=
s
o
f
t
m
a
x
(
O
)
Y=softmax(O)
Y=softmax(O)
对于误差计算,这里我们使用交叉熵的结算函数
H
(
y
i
r
,
y
i
)
=
−
∑
j
=
1
q
y
i
r
j
l
o
g
y
i
j
H(y_i^r,y_i)=-∑_{j=1}^qy_i^{rj}logy_i^j
H(yir,yi)=−j=1∑qyirjlogyij
其中
y
i
r
y_i^r
yir表示的是真实的label。对于所有的样本,计算最后的loss函数为:
L
(
θ
)
=
1
n
∑
i
=
1
n
H
(
y
i
r
,
y
i
)
L(θ)=\frac{1}{n}∑_{i=1}^nH(y_i^r,y_i)
L(θ)=n1i=1∑nH(yir,yi)
2 手写softmax过程
2.1 数据集介绍
在pytorch中,存在一个自带的数据集模块torchvision.datasets。其内部包含了一些常用的数据集,模型结构和常用的图片转换工具。
- torchvision.datasets:一些加载数据的函数以及数据集的接口。
- torchcvision.models:包含的是一些常用的模型结构。
- torchvision.transforms:常用的图片处理,例如旋转,剪裁等等。
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import IPython.display as display
'''
在数据集中的部分:我们使用的Fashion-MNIST数据集
一共包含10个类别:
T恤,裤子,套衫,连衣裙,凉鞋,衬衫,运动鞋,包,短靴
'''
mnist_train = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=False, download=True, transform=transforms.ToTensor())
# 图形展示
def use_svg_display():
display.set_matplotlib_formats('svg')
# 设置尺寸
def set_figsize(figsize=(3.5,2.5)):
use_svg_display()
plt.rcParams['figure.figsize'] = figsize
def get_fashion_mnist_labels(labels):
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[int(i)] for i in labels]
#简单的画一下矢量图
def show_fashion_mnist(images,labels):
use_svg_display()
_,figs = plt.subplots(1,len(images),figsize=(12,12))
for f, img, lbl in zip(figs, images, labels):
f.imshow(img.view((28, 28)).numpy())
f.set_title(lbl)
f.axes.get_xaxis().set_visible(False)
f.axes.get_yaxis().set_visible(False)
plt.show()
2.2 代码实现softmax
#encoding=utf-8
import torch
import torchvision
import numpy as np
import torchvision.transforms as transforms
batch_size = 256
input_dim = 784
output_dim = 10
#定义初始化参数
w = torch.tensor(np.random.normal(0,0.01,(input_dim,output_dim)),dtype=torch.float32)
b = torch.zeros(output_dim,dtype=torch.float32)
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
'''
# 先介绍一下如何对于多维度的Tensor按照维度进行操作 ,例如 按照行或者按照列进行操作
X = torch.tensor([[1,2,3],[4,5,6]])
# keepdim主要用于控制结果的维度(True:保存原来的维度,False:生成tensor)
print(X.sum(dim=0,keepdim=False))
print(X.sum(dim=1,keepdim=False))
'''
#定义优化算法
def sgd(params,lr,batch_size):
for param in params:
param.data -= lr * param.grad / batch_size
#定义softmax函数
def softmax(X):
X_exp = X.exp()
partition = X_exp.sum(dim=1,keepdim=True)
return X_exp / partition
# 定义网络
def net(X):
return softmax(torch.mm(X.view(-1,input_dim),w)+b)
#定义损失函数
def cross_entropy(y_hat,y):
return -torch.log(y_hat.gather(1,y.view(-1,1)))
#计算准确率
def accuracy(y_hat,y):
return (y_hat.argmax(dim=1) == y).float().mean().item()
# 对于整个数据集上的准确率进行评估
def evaluate_accuracy(data_iter,net):
acc_sum,n = 0.0,0
for X,y in data_iter:
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
# y_hat = torch.tensor(([[0.1,0.3,0.6],[0.3,0.2,0.5]]))
# y = torch.LongTensor([0,2])
# gather 函数
# 按照给定的位置y,从对应的位置y_hat获取对应的数据
# 第一个参数表示维度,dim=1 表示按照列进行搜索,dim=0 表示按照行进行搜索。
# res = y_hat.gather(1,y.view(-1,1))
# print(res)
#定义训练过程
def train(net,train_iter,test_iter,loss,num_epoches,batch_size,params=None,lr=None,optimizer=None):
for epoch in range(num_epoches):
train_loss_sum,train_acc_sum,n = 0.0,0.0,0
for X,y in train_iter:
y_hat = net(X)
loss_value = loss(y_hat,y).sum()
#梯度清0
if optimizer is not None:
optimizer.zero_grad()
elif params is not None and params[0].grad is not None:
for param in params:
param.grad.data.zero_()
loss_value.backward()
if optimizer is None:
sgd(params,lr,batch_size)
else:
optimizer.step()
train_loss_sum += loss_value.item()
train_acc_sum += (y_hat.argmax(dim=1)==y).sum().item()
n += y.shape[0]
test_acc = evaluate_accuracy(test_iter,net)
print("epoch %d . loss %.4f, train acc %.3f test acc %.3f"
%(epoch+1,train_loss_sum/n,train_acc_sum/n,test_acc))
#定义超参数
num_epoches = 5
lr = 0.1
#获取数据集
mnist_train = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=False, download=True, transform=transforms.ToTensor())
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=4)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=4)
if __name__ == '__main__':
train(net,train_iter,test_iter,cross_entropy,num_epoches,batch_size,[w,b],lr)
3 使用torch自带模块实现softmax
#encoding=utf-8
import torch
import torch.nn as nn
import numpy as np
from torch.nn import init
import torchvision.transforms as transforms
import torchvision
from collections import OrderedDict
#获取数据集
batch_size = 256
mnist_train = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=False, download=True, transform=transforms.ToTensor())
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=4)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=4)
#定义超参数
input_dim = 784
output_dim = 10
class LinearNet(nn.Module):
def __init__(self,input_dim,output_dim):
super(SoftmaxLinearNet,self).__init__()
self.linear = nn.Linear(input_dim,output_dim)
def forward(self,x):
y = self.linear(x)
return y
class FlattenLayer(nn.Module):
def __init__(self):
super(FlattenLayer,self).__init__()
def forward(self,x):
return x.view(x.shape[0],-1)
net = nn.Sequential(
OrderedDict([
('flatten',FlattenLayer()),
('linear',nn.Linear(input_dim,output_dim))
])
)
# 初始化参数
init.normal_(net.linear.weight,mean=0,std=0.01)
init.constant_(net.linear.bias,val=0)
# 这里将softmax和CrossEntroy 定义在依次
loss = nn.CrossEntropyLoss()
#定义优化
#定义优化算法第一种是自定义的方式第二种是使用torch自带
def sgd(params,lr,batch_size):
for param in params:
param.data -= lr * param.grad / batch_size
optimizer = torch.optim.SGD(net.parameters(),lr=0.1)
num_epoches = 5
#计算准确率
def accuracy(y_hat,y):
return (y_hat.argmax(dim=1) == y).float().mean().item()
# 对于整个数据集上的准确率进行评估
def evaluate_accuracy(data_iter,net):
acc_sum,n = 0.0,0
for X,y in data_iter:
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
#定义训练过程
def train(net,train_iter,test_iter,loss,num_epoches,batch_size,params=None,lr=None,optimizer=None):
for epoch in range(num_epoches):
train_loss_sum,train_acc_sum,n = 0.0,0.0,0
for X,y in train_iter:
y_hat = net(X)
loss_value = loss(y_hat,y).sum()
#梯度清0
if optimizer is not None:
optimizer.zero_grad()
elif params is not None and params[0].grad is not None:
for param in params:
param.grad.data.zero_()
loss_value.backward()
if optimizer is None:
sgd(params,lr,batch_size)
else:
optimizer.step()
train_loss_sum += loss_value.item()
train_acc_sum += (y_hat.argmax(dim=1)==y).sum().item()
n += y.shape[0]
test_acc = evaluate_accuracy(test_iter,net)
print("epoch %d . loss %.4f, train acc %.3f test acc %.3f"
%(epoch+1,train_loss_sum/n,train_acc_sum/n,test_acc))
if __name__ == '__main__':
train(net,train_iter,test_iter,loss,num_epoches,batch_size,None,None,optimizer)
接下了的内容,我们重点关注torch.optim 中的相关内容:
torch.optim 是实现了各种优化算法的算法库,这里我们不关注优化算法的原理,只是关注有哪些优化算法。 在使用torch.optim的时候,需要构建一个optimizer对象,这个对象能够保持当前参数状态并基于计算得到的梯度进行参数更新。
对于优化器参数的传递包括两种方式,第一种方式是对于整个模型的所有参数或者部分参数进行优化。例如:
optimizer = optim.SGD(model.parameters(), lr = 0.01, momentum=0.9)
optimizer = optim.Adam([var1, var2], lr = 0.0001)
第一种是对于所示的参数进行优化,第二种是对部分模型参数进行优化。
当我们使用容器定义网络结构的时候,可以以字典的形式分层的对容器内的网络结构进行优化。例如:
optim.SGD([
{'params': model.base.parameters()},
{'params': model.classifier.parameters(), 'lr': 1e-3}
], lr=1e-2, momentum=0.9)
上面我们以字典的形式对每一个层进行优化,其中对于第二层,使用的与其他层不同的学习率进行优化。
当我们需要对模型参数的进行优化的时候,一般的形式为:
optimizer.step()
在对损失值进行backward之后,就可以利用这个函数对于整个模型的参数进行优化。例如:
for input, target in dataset:
optimizer.zero_grad() #注意梯度清零
output = model(input)
loss = loss_fn(output, target)
loss.backward()
optimizer.step()
一些优化算法在计算的时候需要重复进行计算多次,此时需要传入一个闭包的结构去允许它们重新计算模型,在计算闭包的时候需要清空梯度,计算损失,然后返回结果。例如:
for input, target in dataset:
def closure():
optimizer.zero_grad()
output = model(input)
loss = loss_fn(output, target)
loss.backward()
return loss
optimizer.step(closure)
在torch.optimizer中包含多个优化算法,我们介绍比较其中比较常见的优化算法
- optim.SGD(parameters,lr,momentum)
参数1:需要优化的模型参数。
参数2:lr 学习率
参数3:momentum 动量系数(可选,默认为0) - optim.Adadelta(params,lr=1.0,rho=0.9,eps=1e-06,weight_deacy=0)
参数params: 需要优化的模型参数。
参数rho:(可选), 用于计算平方梯度的运行平均值的系数(默认:0.9)
参数eps:(可选)为了增加数值计算的稳定性而加到分母里的项(默认:1e-6)
参数lr:(可选)在delta被应用到参数更新之前对它缩放的系数(默认:1.0)
参数weight_decay:(可选)权重衰减(L2惩罚)(默认: 0) - optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0)
参数params: 需要优化的模型参数。
参数lr:(可选), 学习率(默认:1e-2)
参数weight_decay:(可选)权重衰减(L2惩罚)(默认: 0) - optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)
参数params (iterable) – 待优化参数的iterable或者是定义了参数组的dict
参数lr (float, 可选) – 学习率(默认:1e-3)
参数betas (Tuple[float, float], 可选) – 用于计算梯度以及梯度平方的运行平均值的系数(默认:0.9,0.999)
参数eps (float, 可选) – 为了增加数值计算的稳定性而加到分母里的项(默认:1e-8)
参数weight_decay (float, 可选) – 权重衰减(L2惩罚)(默认: 0) - optim.RMSprop(params, lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False)
参数params (iterable) – 待优化参数的iterable或者是定义了参数组的dict
参数lr (float, 可选) – 学习率(默认:1e-2)
参数momentum (float, 可选) – 动量因子(默认:0)
参数alpha (float, 可选) – 平滑常数(默认:0.99)
参数eps (float, 可选) – 为了增加数值计算的稳定性而加到分母里的项(默认:1e-8)
参数centered (bool, 可选) – 如果为True,计算中心化的RMSProp,并且用它的方差预测值对梯度进行归一化
参数weight_decay (float, 可选) – 权重衰减(L2惩罚)(默认: 0)
4 总结
本节主要展示了手动编写softmax的传播过程,以及利用pytorch实现线性层,在利用SoftmaxCrossEntropy作为损失函数的方式来实现softmax回归。并且重点关注了torch.optim模块中的基本结构和内部的常见的优化算法。
5 参考
- 动手学深度学习—Pytorch版本
- Pytorch官方文档