天天看點

Pytorch—softmax回歸

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​=x1​w11​+x2​w21​+x3​w31​+x4​w41​+b1​o2​=x1​w12​+x2​w22​+x3​w32​+x4​w42​+b2​o1​=x1​w13​+x2​w23​+x3​w33​+x4​w43​+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=13​exp(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=w11​w21​w31​w41​​w12​w22​w32​w42​​w13​w23​w33​w43​​

輸入樣本為:

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∑q​yirj​logyij​

其中 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(θ)=n1​i=1∑n​H(yir​,yi​)

2 手寫softmax過程

2.1 資料集介紹

在pytorch中,存在一個自帶的資料集子產品torchvision.datasets。其内部包含了一些常用的資料集,模型結構和常用的圖檔轉換工具。

  1. torchvision.datasets:一些加載資料的函數以及資料集的接口。
  2. torchcvision.models:包含的是一些常用的模型結構。
  3. 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)
           

上面我們以字典的形式對每一個層進行優化,其中對于第二層,使用的與其他層不同的學習率進行優化。

當我們需要對模型參數的進行優化的時候,一般的形式為:

在對損失值進行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中包含多個優化算法,我們介紹比較其中比較常見的優化算法

  1. optim.SGD(parameters,lr,momentum)

    參數1:需要優化的模型參數。

    參數2:lr 學習率

    參數3:momentum 動量系數(可選,預設為0)

  2. 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)

  3. optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0)

    參數params: 需要優化的模型參數。

    參數lr:(可選), 學習率(預設:1e-2)

    參數weight_decay:(可選)權重衰減(L2懲罰)(預設: 0)

  4. 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)

  5. 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 參考

  1. 動手學深度學習—Pytorch版本
  2. Pytorch官方文檔