Softmax回歸
首先簡單理解softmax:就是將一個回歸值轉換成一個概率(也就是把一個實數,定在[0,1.]中)
Softmax回歸名字叫做回歸,但其實是一個分類問題。(基本是個老師都會重復講這句話)
分類和回歸的差別就在,回歸只有一個輸出,而分類是有多個輸出。一般是有幾個類別多少個輸出。
並且分類輸出的i是預測為第i類的置信度。
exp()這要要進行指數,是因為指數的好處就是不管什么值,都可以變成非負(概率不能是負數)。
softmax實際上可以理解成也是一個全連接層,得到對應類別的置信度(概率)是需要和之前的輸出進行線性組合的。
最后分類問題的損失函數使用交叉熵損失函數(具體推到如果看不懂的話,建議先跳過)
損失函數
loss用來衡量真實值和預測值的差別,是機器學習中一個非常重要的概念。
這里給大家簡單介紹3個常用的損失函數。
均方損失,也叫做L2 loss。
這里除2是為了方便求導的時候可以和平方的2進行抵消。
上面有3條線,可以可視化這個損失函數的特性。
- 藍色:當y=0,變換預測值y',它的函數。可以看到它是一個二次函數
- 綠色:它的似然函數,我們並沒有要將似然函數,但它確實是統計中一個非常重要的概念,可以看到它的似然函數就是一個高斯分布了。
- 橙色:梯度,我們知道它的梯度就是一個一次函數。
我們更新梯度的時候是根據負梯度方向來更新我們的參數,所以它的導數就決定如何來更新我們的參數,如上圖紅色箭頭。大小要看梯度的值。但是有個不好的地方,就是其實預測值和真實值差的比較遠的時候,其實有時候並不想更新那么大。
L1 loss,梯度就是一個常數,在-1,1。好處就是不管真實值和預測值相差多大,都可以對參數進行一個穩定的更新,這會帶來很多穩定性上的好處。
絕對值函數在0點處是不可導的,所以在0點處會有一個比較劇烈的 變化,也就是當真實值預測值靠的比較近,也就是優化到末期,這里就會變得不那么穩定了。
Huber’s Robuts Loss就將L1 loss 和 L2 loss進行了結合。
一般看損失函數,都是看其梯度的函數形狀,來分析當預測值和真實值相差比較遠,還有相差比較近的時候,分別是什么情況。
圖片分類數據集
操作總結
def load_data_fashion_mnist(batch_size,resize=None):
# 通過ToTensor實例將圖像數據從PIL類型變換成32位浮點數格式
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize)) # 插入指定的位置
trans = transforms.Compose(trans) # 將多個transform操作進行組合
# 數據數據集
mnist_train = torchvision.datasets.FashionMNIST(root="../data", train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False, transform=trans, download=True)
# 返回兩個DataLoader
return (data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers()),
data.DataLoader(mnist_test, batch_size, shuffle=False,
num_workers=get_dataloader_workers()))
Softmax回歸從零開始實現
Softmax也要從零開始實現,因為這是一個非常重要的模型,也是后面所有深度學習模型的一個基礎。
操作總結
batch_size = 256
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size) # 加載batch_size的數據
num_inputs = 784
num_outputs = 10
# 這里初始化權重和參數
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True) # 對每一個輸出都要有一個偏移
# 定義了softmax函數
def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(1, keepdim=True) # 按照水平方向(行)來進行求和
return X_exp / partition # 這里應用了廣播機制
# 實現了softmax回歸模型
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
# 交叉熵損失函數
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
# 累加器
class Accumulator:
"""在`n`個變量上累加,這是一個累加器"""
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)] # 這是一個累加的過程
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx): # 按照[] 取值
return self.data[idx]
# 計算真實值
def accuracy(y_hat, y):
"""計算預測正確的數量,也就是判斷n行中可以預測對幾個"""
# 如果存在多行,就只存儲每一行的最大值下標
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1)
cmp = y_hat.type(y.dtype) == y
# 返回預測正確的數目
return float(cmp.type(y.dtype).sum()) # 布爾類型轉換int在求和
# 評估模型在數據集上的准確性
def evaluate_accuracy(net, data_iter):
"""計算在指定數據集上模型的精度,就是看模型在數據迭代器上的精度
net:模型
data_iter:數據迭代器
"""
if isinstance(net, torch.nn.Module): # 如果是使用了torch.nn.Module的模型
net.eval() # 將模型設置為評估模式
metric = Accumulator(2) # 正確預測數、預測總數
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel()) # 不斷地加入累加器中
return metric[0] / metric[1]
# softmax回歸訓練
def train_epoch_ch3(net, train_iter, loss, updater):
"""訓練模型一個迭代周期(定義見第3章)。"""
if isinstance(net, torch.nn.Module):
net.train() # 開啟訓練模式,也就是要訓練梯度
# [loss,correct_num,total]
metric = Accumulator(3)
for X, y in train_iter:
y_hat = net(X) # softmax回歸函數
l = loss(y_hat, y) # 得到loss
if isinstance(updater, torch.optim.Optimizer):
updater.zero_grad()
l.backward()
updater.step()
metric.add(float(l) * len(y), accuracy(y_hat, y),
y.size().numel())
else:
l.sum().backward()
updater(X.shape[0]) # 根據批量大小,反向update一下
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
return metric[0] / metric[2], metric[1] / metric[2]
# 訓練函數
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
"""訓練模型(定義見第3章)。"""
# 畫圖對象
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
test_acc = evaluate_accuracy(net, test_iter)
animator.add(epoch + 1, train_metrics + (test_acc,))
train_loss, train_acc = train_metrics
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc
# 使用SGD來優化模型的loss
lr = 0.1
def updater(batch_size):
return d2l.sgd([W, b], lr, batch_size)
# 訓練10個周期
num_epochs = 10
# 這里還封裝了一個動態的畫圖的功能,非常的酷炫(請面向github編程)
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
Softmax回歸簡潔實現
batch_size = 256
# 還是將數據加入數據迭代器中
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# 將模型進行組合
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
# 完成線性模型的初始化
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights);
# 交叉熵損失函數
loss = nn.CrossEntropyLoss()
# SGD
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
# 訓練10個epoch
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
QA
- softmax回歸和logistic回歸分析是一樣的嗎?如果不一樣的話,那些地方不同?
可以認為logistic回歸是softmax回歸的一個特例,因為前者的作用就是進行二分類任務,但是二分類任務只要預測一個類別就可以了,另一個類別的概率就是1-other。
softmax是用於多分類的。我們后面基本不會用到二分類,所以課程直接跳過了logistic回歸,直接將softmax回歸。
- 為什么用交叉熵,不用....等其他基於信息量的度量?
沒有特別為什么,一個是交叉熵比較好算。
還有就是其實我們真正關注的就是兩個分布的距離,能夠得到這個距離就可以了。
- 交叉熵損失函數為什么我們只關心正確類,不關心不正確的類呢?
其實不是我們不關心不正確的類,而是因為one-hot編碼就是把不正確類別的概率變成了0,導致我們計算的時候可以忽略掉不正確的類。
- 能對MSE的最大似然估計提一下嗎?
沒有講似然函數,是因為這一塊是統計中的概念。我們盡量不涉及太多的統計,是因為統計是可以用來解釋我們模型的一個工具,反過來講,后面的深度學習和統計沒有太多的關系。所以我們主要講的是線性代數,因為使用的所有結構是線性代數。
可以大概講一下,最小化損失就等價於最大化似然函數。似然函數就是有個模型,給定數據的情況下,我的模型(也就是權重)出現的概率有多大?我們要最大化似然函數,也即是找到一個w,是的x出現的概率是最大的,這個也是最合理的解釋。
下面這張最大似然函數和損失函數的關系圖,也就很好理解了。
我們會稍微講一點統計的東西,但是不會深入太多。
- batch_size的大小會訓練時間的影響是什么?
如果CPU的話,基本是看不出區別的。
batch_size大一點的話,在GPU是可以提高訓練的並行度,這樣可以節約訓練的時間。
當然,不管batch_size怎么取,訓練的總體計算量是沒有發生變化的。
- 為什么不在accuracy函數中除以len(y)做完呢?
因為最后一個batch可能是不能讀滿的,所以最好的做法就是Accumulator進行累加,最后在進行相除。
- 在多次迭代之后如果測試進度出現上升后再下降過擬合了嗎?可以提前終止嗎?
很有可能是過擬合了,其實可以再等等,但如果測試精度是一直下降的話,那很有可能是過擬合。
后面會講一些策略來盡量避免,比如來微調學習率,當然也可以加入各種各樣的正則項。
- cnn網絡學習到的到底是什么信息?是紋理還是輪廓還是所有內容的綜合,說不清的那種?
其實我們也不好說cnn學到了什么,目前,至少幾年前,大家認為cnn是學到的紋理,輪廓cnn其實不那么在意。
- 如果是自己的圖片數據集,需要怎么做才能用於訓練,怎么根據本地圖片訓練集和測試機創建迭代器?
這個需要查閱框架的文檔,基本都是在本地建立名字為類別的文件夾,然后在對應類別中放入圖片,然后告訴框架上層目錄即可。