softmax誕生原因
線性迴歸主要用於連續值預測,即迴歸問題,比如判定一個東西是雞的概率是多少
而當模型需要預測多個離散值時,即分類問題,比如判定一個東西是雞還是鵝還是鴨
此時就需要多個輸出單元,並且修改運算方式從而方便預測和訓練,這也就是softmax層誕生的原因
這裏使用的是minist手寫數字識別數據集
1.首先通過Pytorch讀取數據集
def load_data():
train_dataset = datasets.MNIST(root='./data/',
train=True,
transform=transforms.ToTensor(),
download=True)
test_dataset = datasets.MNIST(root='./data/',
transform=transforms.ToTensor(),
train=False)
# Data Loader (Input Pipeline)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=batch_size,
shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
batch_size=batch_size,
shuffle=False)
return train_loader,test_loader
2.softmax迴歸模型
這裏每個輸入都是的數據,即
這裏我選擇每次都輸入batch_size爲64的數據進行優化
即輸入數據shape爲(64,784)
即64*784的矩陣
softmax和線性迴歸一樣都是對將輸入特徵用線性函數計算輸出值,但是這裏有一點不同,就是softmax迴歸的輸出值個數不再是一個,而是等於類別數,這裏輸出類別爲0123456789十個數字,因此有十個輸出值
因此此時需要十個線性函數來對應這十個輸入值到輸出值的轉化
其中w矩陣爲(784,10),即
b矩陣爲(64,10)
即
最終爲
y矩陣最終等於
其中結果爲,加上b最後還是一個的矩陣
對應一個批次64個數據中中,預測十個數字每個數字的可能性大小
代碼如下
def train():
w=np.random.randn(784,10) #生成隨機正太分佈的w矩陣
b=np.zeros((64,10)) #生成全是0的b矩陣
for batch_idx, (data, target) in enumerate(train_loader):
data= np.squeeze(data.numpy()).reshape(64,28*28)
# 把張量中維度爲1的維度去掉,並且改變維度爲(64,784)
target = target.numpy() #x矩陣 (64,784)
y_hat=np.dot(data,w)+b
print(y_hat.shape)
最終y_hat爲
3.softmax運算
假設預測值分別是1,10,…100,此時直接通過輸出值判斷,最大識別結果爲數字9
但是這樣會使得,不知道怎麼劃分這些值到底是很大還是很小,也就是難以去優化參數
比如如果這次裏100最大,但是下次中10000最大而且最小的都有1000,那麼100到底算不算大
這裏就用softmax運算來解決
即每個爲exp(當前輸出值)/所有exp(輸出值)求和
這樣一來,最終所有的和爲1,而且每個對應的都是對每個手寫數字概率的預測
def softmax(label):
#label時一個(64,10)的numpy數組
label = np.exp(label.T)#先把每個元素都進行exp運算
sum = label.sum(axis=0) #對於每一行進行求和操作
# print((label/sum).T.shape)
# print((label/sum).T)
return (label/sum).T #通過廣播機制,使每行分別除以各種的和
4.交叉熵損失函數
這裏的損失函數我們可以直接採用平方損失函數,即
但是想要預測結果正確,我們不需要使得預測值和實際值完全一致
只需要這個的預測值比其他的都大即可,而平方損失則過於嚴格
我們可以採用交叉熵作爲損失函數
對於單個的訓練樣本而言,交叉熵爲
此時對於一個訓練批次而言,損失函數爲
這裏就是
然而實際上這10個實際值只會有一個是1,其他全是0,也就是說,式子變成了
然後再結合模型
最終損失函數爲
5.梯度下降優化參數
此時需要用梯度下降優化參數
其中爲學習速率,batchsize是批量大小,爲對損失函數的偏導
假設此時的對應的類別是預測對的
即按照鏈式求導法則
KaTeX parse error: Got function '\hat' with no arguments as subscript at position 45: …ac{d_{Loss}}{d_\̲h̲a̲t̲{y}}
\frac{d_\h…
因此,更新函數變成了
這裏就是
同理
同理,假設此時的對應的類別是預測錯誤的
(推算過程我就省略了,Latex打上去太累了)
PS:我吐了,上面這個求導我一開始沒用鏈式求導,直接手算,沒經過softmax變換,直接當成了來算,最後結果完全不符合邏輯,半夜看了好多博客終於想起來自己的問題了,好不容易重新推了出來
因此,最終W和b的優化函數如下:
最終代碼如下:
w_,b_=CrossEntropyLoss(data,target,y_hat)
w=w+w_ #優化參數w的矩陣
b=b+b_ #優化參數b的矩陣
def CrossEntropyLoss(data,target,y_hat):
target = np.eye(10)[target] # 改爲one-hot形式
data=data.T #(784,64)
#y_hat-target爲預測值與實際值的one-hot挨個做差,得出一個(64,10)的y_hat-y的矩陣
w_=-np.dot(data,y_hat-target)/batch_size*learning_rate
b_=-(y_hat-target)/batch_size*learning_rate
# print(w_)
return w_,b_
這短短12行代碼我查公式整理原理,搞了一個晚上。。。
最後優化時採用了矩陣相乘的方式代替累加
即中的
直接用矩陣相乘的方法代替
這裏相乘時,爲該矩陣的每行元素
與另一矩陣的每列的元素,挨個相加並且求和
如:
也就一一對應着
計算準確率
def Accuracy(target,y_hat):
#y_hat.argmax(axis=1)==target 用於比較y_hat與target的每個元素,返回一個布爾數組
acc=y_hat.argmax(axis=1) == target
acc=acc+0 #將布爾數組轉爲0,1數組
return acc.mean() #通過求均值算出準確率
最終整個項目完整代碼如下
from torchvision import datasets, transforms
import torch.utils.data as Data
import numpy as np
from tqdm._tqdm import trange
batch_size = 64
learning_rate=0.0001
def load_data():
# 加載torchvision包內內置的MNIST數據集 這裏涉及到transform:將圖片轉化成torchtensor
train_dataset = datasets.MNIST(root='./data/', train=True, transform=transforms.ToTensor(), download=True)
test_dataset = datasets.MNIST(root='./data/', train=False, transform=transforms.ToTensor())
# 加載小批次數據,即將MNIST數據集中的data分成每組batch_size的小塊,shuffle指定是否隨機讀取
train_loader = Data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = Data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)
return train_loader,test_loader
def train(train_loader):
list=[]
w=np.random.normal(scale=0.01,size=(784,10)) #生成隨機正太分佈的w矩陣
b=np.zeros((batch_size,10)) #生成全是0的b矩陣
for i in trange(0,100):
for batch_idx, (data, target) in enumerate(train_loader):
if(data.shape[0]<batch_size):
break
data= np.squeeze(data.numpy()).reshape(batch_size,784) # 把張量中維度爲1的維度去掉,並且改變維度爲(64,784)
target = target.numpy() #x矩陣 (64,784)
y_hat=softmax(np.dot(data,w)+b)#預測結果y_hat
# print(sum(y_hat[0]))
w_=GradientOfCrossEntropyLoss(data,target,y_hat,type="weight")#計算交叉熵損失函數對於w的梯度
b_=GradientOfCrossEntropyLoss(data,target,y_hat,type="bias")#計算交叉熵損失函數對於b的梯度
w=GradientDescent(w,w_) #梯度下降優化參數w
b=GradientDescent(b,b_) #梯度下降優化參數b
list.append(Accuracy(target, y_hat))
if (batch_idx == 50):
print(Accuracy(target, y_hat))
return list
def softmax(label):
label = np.exp(label.T)#先把每個元素都進行exp運算
# print(label)
sum = label.sum(axis=0) #對於每一行進行求和操作
#print((label/sum).T.sum(axis=1))
return (label/sum).T #通過廣播機制,使每行分別除以各種的和
#梯度下降
def GradientDescent(Param,GradientOfLoss):
Param = Param -GradientOfLoss/ batch_size * learning_rate
# print(w_)
return Param
#計算交叉熵損失函數對於w或者b的梯度
def GradientOfCrossEntropyLoss(data,y,y_hat,type="weight"):
y = np.eye(10)[y] # 改爲one-hot形式
data=data.T #(784,64)
#y_hat-target爲預測值與實際值的one-hot挨個做差,得出一個(64,10)的y_hat-y的矩陣
if (type == "bias"):
return y_hat-y
else:
return np.dot(data,y_hat-y)
def Accuracy(target,y_hat):
#y_hat.argmax(axis=1)==target 用於比較y_hat與target的每個元素,返回一個布爾數組
acc=y_hat.argmax(axis=1) == target
acc=acc+0 #將布爾數組轉爲0,1數組
return acc.mean() #通過求均值算出準確率
if __name__ == '__main__':
train_loader,test_loader=load_data() #加載數據(這裏使用pytorch加載數據,後面用numpy手寫)
list=train(train_loader)
import matplotlib.pyplot as plt
plt.plot(list)
plt.show()