逻辑回归
逻辑回归是指用类似线性回归的手段,解决分类问题。在多类别分类中,一般采用softmax 方法。
比如图片分类、恶意软件分类、文字分类问题。
Softmax:
y
^
=
s
o
f
t
m
a
x
(
o
)
意味着
y
i
=
e
x
p
(
o
i
)
Σ
k
e
x
p
(
o
k
)
\hat y=softmax(o)意味着\\ y_i=\frac{exp(o_i)}{\mathop\Sigma\limits_{k} exp(o_k)}
y^=softmax(o)意味着yi=kΣexp(ok)exp(oi)
对类别进行embedding,最简单的编码方式为one-hot编码。预测分布和真实概率的差异可以看作是“损失函数值”,通常用“交叉熵”来做。设
y
^
\hat y
y^是预测分布,
y
y
y是真是分布
类别 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
真是分布(y) | 0 | 1 | 0 | 0 |
预测分布(softmax) | 0.06 | 0.9 | 0.02 | 0.02 |
则CrossEntropyLoss 为
L
o
s
s
=
Σ
i
y
i
(
−
l
o
g
y
^
i
)
=
0
⋅
(
−
l
o
g
0.06
)
+
1
⋅
(
−
l
o
g
0.9
)
+
0
⋅
(
−
l
o
g
0.02
)
+
0
⋅
(
−
l
o
g
0.02
)
=
−
l
o
g
(
y
t
a
r
g
e
t
C
l
a
s
s
)
Loss=\mathop\Sigma\limits_{i} y_i(-log\hat y_i)=0\cdot(-log0.06)+1\cdot (-log0.9)+0\cdot (-log0.02)+0\cdot (-log0.02) \\ =-log(y_{targetClass})
Loss=iΣyi(−logy^i)=0⋅(−log0.06)+1⋅(−log0.9)+0⋅(−log0.02)+0⋅(−log0.02)=−log(ytargetClass)
逻辑回归案例:fashion-mnist分类
-
导包
import torch import torchvision from torch.utils import data import numpy as np import matplotlib.pyplot as plt from torch import nn
-
加载mnist数据集
def load_fashion_mnist_data(batch_size,resize=None): trans=[torchvision.transforms.ToTensor()] if resize: trans.append[torchvision.transforms.Resize(resize)] trans=torchvision.transforms.Compose(trans) mnist_train_dataset=torchvision.datasets.FashionMNIST(root="../data/",train=True,transform=trans,download=True) mnist_test_dataset=torchvision.datasets.FashionMNIST(root="../data/",train=False,transform=trans,download=True) mnist_train_dataloader=data.DataLoader(dataset=mnist_train_dataset,batch_size=batch_size,shuffle=True,num_workers=0) mnist_test_dataloader=data.DataLoader(dataset=mnist_test_dataset,batch_size=batch_size,shuffle=True,num_workers=0) return mnist_train_dataset,mnist_test_dataset,mnist_train_dataloader, mnist_test_dataloader batch_size=256 mnist_train_dataset,mnist_test_dataset,mnist_train_dataloader,mnist_test_dataloader=load_fashion_mnist_data(batch_size)
-
查看dataset的结构(配对)
print(mnist_train_dataset[0][0].shape,mnist_train_dataset[0][1])
-
查看dataloader的结构和大小(成组)
X,y=next(iter(mnist_test_iter)) print(X.shape,y.shape) print(y[:10])
-
写出y值和图片类别的对应函数,用于最后测试
def get_fashion_mnist_labels(labels): text_labels=['t-shirt','trouser','pullover','dress','coat','sandal','shirt','sneaker','bag','ankle boot'] return text_labels[labels]
-
构建模型,本例不用卷积,将图片视为28*28=784维度向量,放入神经网络中运算.nn.Flatten会将tensor第一维度不动,后面全部拉平:(batchsize,channel,w,h)->(batchsize,-1)
num_inputs=784 num_outputs=10 net=nn.Sequential(nn.Flatten(),nn.Linear(num_inputs,num_outputs))
-
初始化模型参数.Pytorch 中的
model.apply(fn)
会递归地将函数fn
应用到父模块的每个子模块以及model这个父模块自身。通常用于初始化模型的参数def init_weights(m): if(type(m)==nn.Linear): nn.init.normal_(m.weight.data,std=0.01) net.apply(init_weights)
-
定义损失函数和优化器。 n n . C r o s s E n t r o p y L o s s ( ) nn.CrossEntropyLoss() nn.CrossEntropyLoss()中自带softmax过程,所以定义网络时不需要加入softmax层。
其中 n n . C r o s s E n t r o p y L o s s ( ) ( y ^ , y ) nn.CrossEntropyLoss( ) (\hat y,y) nn.CrossEntropyLoss()(y^,y)接受的参数 y ^ \hat y y^是 T e n s o r ( s i z e = ( b s , 10 ) ) Tensor(size=(bs,10)) Tensor(size=(bs,10)), y y y是 T e n s o r ( s i z e = ( b s ) ) Tensor(size=(bs)) Tensor(size=(bs)),拿上述介绍交叉熵损失时用的表格分布举例子: y ^ [ i ] = [ 0.06 , 0.9 , 0.02 , 0.02 ] , y [ i ] = 2 \hat y[i]=[0.06,0.9,0.02,0.02],y[i]=2 y^[i]=[0.06,0.9,0.02,0.02],y[i]=2 ,而 l o s s ( y ^ , y ) [ i ] = − l o g y ^ [ y [ i ] ] = − l o g 0.9 , 最终 l o s s ( y ^ , y ) = m e a n ( l o s s ( s i z e = ( b s ) ) ) loss(\hat y,y)[i]=-log\hat y[y[i]]=-log0.9 ,最终loss(\hat y,y)=mean(loss(size=(bs))) loss(y^,y)[i]=−logy^[y[i]]=−log0.9,最终loss(y^,y)=mean(loss(size=(bs))) ,成为一个标量。但如果loss=nn.CrossEntropyLoss(reduction=None),则不会最终求均值,返回的是 l o s s ( s i z e = ( b s ) ) loss(size=(bs)) loss(size=(bs))向量。
简而言之,虽然神经网络output的是预测的onehot编码,但是在用API求损失时,传进去的真实分布用数据集提供的真实label值即可,不需要把label也变成one-hot形式。label值即是每个类别对应的号,在CrossEntropyLoss中,会自动将其视为一个在下标是label值地方为 1、其他位为0的onehot编码。最终神经网络也会成功拟合一个离散分布,分布的X取值范围为种类个数,分布的峰值P(x)对应的随机变量x等于真实label值。
loss=nn.CrossEntropyLoss() optim=torch.optim.SGD(net.parameters(),lr=0.1)
-
定义accuracy函数,用于反馈预测正确率
y h a t : T e n s o r ( s i z e = ( b s , 10 ) ) , y : T e n s o r ( s i z e = ( b s ) ) y_{hat}:Tensor(size=(bs,10)),y:Tensor(size=(bs)) yhat:Tensor(size=(bs,10)),y:Tensor(size=(bs))
def accuracy(y_hat,y): y_hat=y_hat.argmax(axis=1) comp=(y_hat==y) return float(comp.type(y.dtype).sum())/len(y)
-
训练网络
网络模型小,数据量大——会导致欠拟合【train的loss还未到令人满意的程度就无法下降了,test的loss也是无法下降】
网络模型大,数据量小——会导致过拟合【train的loss持续下降,但是test的loss下降到最低点后无法继续减小,或者反而上升】
显然本样例是欠拟合的model,且仅仅2个epoch左右,模型的train的loss就不再下降,不会表现得更好了。def train(train_iter,test_iter,net,loss,optim,num_epochs): if isinstance(net,nn.Module): net.train() for epoch in range(num_epochs): for X,y in train_iter: y_hat=net(X) l=loss(y_hat,y) optim.zero_grad() l.backward() optim.step() test_X,text_y=next(iter(test_iter)) test_acc=accuracy(net(test_X),text_y) print(f"epoch {epoch+1}:test_acc={test_acc:.4f}") num_epochs=10 train(mnist_train_dataloader,mnist_test_dataloader,net,loss,optim,num_epochs)
-
测试图片分类结果
def show_img_2D(images,labels,y_hat): plt.figure() for i,idx in enumerate(np.random.randint(0,labels.numel(),6)): plt.subplot(2,3,i+1) plt.imshow(images[idx][0],cmap="gray") plt.title(f"truth:{get_fashion_mnist_labels(labels[idx])} \nguess:{get_fashion_mnist_labels(y_hat[idx])}") plt.tight_layout() plt.show() def predict(net, test_iter, n=6): X, y = next(iter(test_iter)) y_hat = net(X).argmax(axis=1) show_img_2D(X,y,y_hat) predict(net,mnist_test_dataloader)