深度学习修炼(六)——神经网络分类问题
文章目錄
- 6 分類任務
- 6.1 前置知識
- 6.1.1 分類
- 6.1.2 分類的網絡
- 6.2 動手
- 6.2.1 讀取數據
- 6.2.2 functional模塊
- 6.2.3 繼續搭建分類神經網絡
- 6.2.4 繼續簡化
- 6.2.5 訓練模型
- 6.3 暫退法
- 6.3.1 重新看待過擬合問題
- 6.3.2 在穩健性中加入擾動
- 6.3.3 暫退法實際的實現
- 6.4 后話
6 分類任務
在這一講中,我們打算探討一下神經網絡中是如何處理分類任務的。
6.1 前置知識
6.1.1 分類
如果只是二分分類,這種分類我們大可只用0或者1來表示,那么我們只需要采用sigmoid函數來修正為0或1即可(如果不懂后面會重新講sigmoid函數),那如果是多種可能呢?
假設每次輸入是一個2×2的灰度圖像。我們可以用一個標量表示每個像素值,每個圖像對應四個特征x1,x2,x3,x4x_1,x_2,x_3,x_4x1?,x2?,x3?,x4?,此外,假設每個圖像屬于類別"貓",“雞”,"狗"中的一個。
接下來,我們要選擇如何表示標簽,如同我們前面所說,我們肯定不可能說P = “貓”,我們有兩個明顯地選擇:最直接的想法是選擇y∈{1,2,3},其中正數分別代表{“狗”,“貓”,“雞”}。
但是統計學家很早之前就發明了一種表示分類數據的簡單方法:獨熱編碼。獨熱編碼是一個向量,它的分量和類別一樣多。類別對應的分量設置為1,其他所有分量設置為0。比如說應用到我們這個例子上的話,我們就可以用獨熱編碼來表示我們的標簽y,其中貓對應(1,0,0),雞對應(0,1,0),狗對應(0,0,1)。
6.1.2 分類的網絡
在神經網絡中,我們通常使用softmax回歸來進行分類任務。
在分類任務重,我們本質上是在算這個圖片是不是某一類的概率,這個概率是要多大才能判定這個圖片屬于這個類?這個概率要達到什么標準?這個標準是我們自己定的,也就是所謂的閾值。
我們希望把我們通過運算算出來的y^j\hat{y}_jy^?j?可以直接看做是類別j的概率,但是這明顯不可能,因為我們知道概率是處于0到1的,我們又沒對這個模型做什么處理,他憑什么輸出的值剛好就在0和1之間,是吧。
那么既然沒有處理,那接下來就要讓輸出結果某種處理后,變成0-1之間,這個處理我們就叫做校準。那我們要用什么東西來校準呢?softmax函數。softmax函數可以將未規范化的預測變換為非負并且總和為1,同時要求模型保持可導。其函數形式為:y^=softmax(o),其中y^i=exp(oj)∑kexp(ok)\hat{y} = softmax(o),其中\hat{y}_i = \frac{exp(o_j)}{\sum_kexp(o_k)}y^?=softmax(o),其中y^?i?=∑k?exp(ok?)exp(oj?)?。
這里,對于所有的j總有0<=yj^\hat{y_j}yj?^?<=1。因此,y^\hat{y}y^?可以視為一個正確的概率發布。softmax運算不會改變未規范化的預測o之間的順序,只會確定分配給每個類別的概率。
從上面的公式我們可以看出,這個公式明顯不是線性函數,但是由于我們是先通過輸入特征的仿射變換做出o,然后再把o出入上面的softmax函數中的,所以,softmax回歸是一個線性模型。
6.2 動手
或許前置的知識再多也是花里胡哨。讓我們試著動手搭建一個神經網絡來進行分類。不過,我們這一小節的目的是為了熟悉nn.Module模塊和nn.functional模塊。
6.2.1 讀取數據
讓我們先讀取數據,這里我們暫時不使用前面學過的torch.datasets和torch.utils.data.DataLoader。如果你不是很懂下面的讀取數據的代碼,沒關系,這看得懂看不懂都不是我們學習的重點。你照做即可。
%matplotlib inlinefrom pathlib import Path import requestsDATA_PATH = Path("data") PATH = DATA_PATH / "mnist"PATH.mkdir(parents=True, exist_ok=True)URL = "http://deeplearning.net/data/mnist/" FILENAME = "mnist.pkl.gz"if not (PATH / FILENAME).exists():content = requests.get(URL + FILENAME).content(PATH / FILENAME).open("wb").write(content)import pickle import gzipwith gzip.open((PATH / FILENAME).as_posix(), "rb") as f:((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")from matplotlib import pyplot import numpy as nppyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray") print(x_train.shape)out:
對于分類任務不像回歸,它的輸出并不是1,而是類別數。
如果我們要將某樣本歸為十個類中的一個,那么輸出結果就是對應類的獨熱編碼,也就是向量中有10個數字。
舉個例子,如果我們輸入的樣本是1×784,那么我可以在第二層將變為784×128,輸出層變為128×10,然后輸出的10個值再交由softmax回歸將其每個數的范圍降到0-1,然后看其屬于哪個類別的數字大即屬于哪個類別。
6.2.2 functional模塊
functional模塊有的實際上Module都有,但是在調用一些不涉及參數的例如不涉及w和b的方法時,推薦使用functional。
如,繼續上面的例子,我們先將數據分為訓練集和測試集,然后將數據轉為張量:
import torchx_train, y_train, x_valid, y_valid = map(torch.tensor, (x_train, y_train, x_valid, y_valid) ) n, c = x_train.shape x_train, x_train.shape, y_train.min(), y_train.max() print(x_train, y_train) print(x_train.shape) print(y_train.min(), y_train.max())我們利用functional模塊來搭建一個線性回歸模型試試:
import torch.nn.functional as F# 指定損失函數為交叉熵 loss_func = F.cross_entropy# 定義模型 def model(xb):return xb.mm(weights) + bias交叉熵實際上就是對數似然損失函數,這個知識點我們在機器學習的練功方式(十一)——邏輯回歸_弄鵲的博客-CSDN博客已經提過了,忘記的可以去復習一下。
# 定義小批量隨機梯度下降每次取多少樣本 bs = 64 xb = x_train[0:bs] # a mini-batch from x yb = y_train[0:bs] # 初始化隨機參數 weights = torch.randn([784, 10], dtype = torch.float, requires_grad = True) bias = torch.zeros(10, requires_grad=True)# 打印損失值 print(loss_func(model(xb), yb))6.2.3 繼續搭建分類神經網絡
讓我們繼續以上的工作。我們利用nn.Module來搭建一個softmax回歸分類器。
from torch import nnclass Mnist_NN(nn.Module):def __init__(self):super().__init__()self.hidden1 = nn.Linear(784, 128)self.hidden2 = nn.Linear(128, 256)self.out = nn.Linear(256, 10)def forward(self, x):x = F.relu(self.hidden1(x))x = F.relu(self.hidden2(x))x = self.out(x)return x在前面第三講的時候我們忘記提到的一點是:在利用torch框架搭建神經網絡時,無需自己去寫一個反向傳播,只需寫好正向傳播即可,對于反向傳播torch框架內部已經提供給我們了。
在以上搭建的神經網絡中,我們在__ init __()方法中先寫好了神經網絡的每個層,然后在forward()方法中,我們讓x首先輸入到第一層中,輸出的結果用F中的relu激活函數激活一下再流往下一層。第二層同理,得出的結果也是再次使用relu激活函數激活,然后最終通過輸出層輸出。
定義好之后,我們需要實例化該神經網絡類:
net = Mnist_NN() print(net)out:
Mnist_NN(
(hidden1): Linear(in_features=784, out_features=128, bias=True)
(hidden2): Linear(in_features=128, out_features=256, bias=True)
(out): Linear(in_features=256, out_features=10, bias=True)
)
對于net對象,實際上torch為其寫好了內置方法net_parameters,其依次返回name,parameter。
#可以打印我們定義好名字里的權重和偏置項for name, parameter in net.named_parameters():print(name, parameter,parameter.size())out:
6.2.4 繼續簡化
我們可以利用TensorDataset和DataLoader來繼續簡化上述的過程。
在前面我們學過,我們小批量隨機梯度下降。其簡單來說就是取小批量的數據,然后取其損失的均值來代替全局損失。在第二講我們學過DataLoader,它可以用于每次從總數據中每次抽取batch_size的數據。
而對于TensorDataset,它可以將傳入的數據張量轉換為torch可識別的dataset格式類型的數據,從而方便讓DataLoader處理。
讓我們看一下以下的代碼:
from torch.utils.data import TensorDataset from torch.utils.data import DataLoader# 轉換DataLoader可處理的類型并且批量提取數據 train_ds = TensorDataset(x_train, y_train) train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True)valid_ds = TensorDataset(x_valid, y_valid) valid_dl = DataLoader(valid_ds, batch_size=bs * 2) def get_data(train_ds, valid_ds, bs):return (DataLoader(train_ds, batch_size=bs, shuffle=True),DataLoader(valid_ds, batch_size=bs * 2),)6.2.5 訓練模型
一般在訓練模型時,我們都會加上model.train()方法,這樣模型在訓練時會自動加入Bathc Normalization(批量歸一化)和Dropout(暫退法),防止過擬合。而在測試模型時,我們一般加上model.eval()方法,這樣就不會調用歸一化和暫退法。
Dropout下一小節就講了,這里只要知道它是防止過擬合的即可。
讓我們看一下下面的代碼:
def loss_batch(model, loss_func, xb, yb, opt=None):# 計算損失值loss = loss_func(model(xb), yb)# 如果優化器不為空if opt is not None:# 執行反向傳播loss.backward()# 更新參數opt.step()# 梯度清零opt.zero_grad()# 返回損失總值,和批量x的長度return loss.item(), len(xb)然后訓練:
import numpy as npdef fit(steps, model, loss_func, opt, train_dl, valid_dl):for step in range(steps):model.train()for xb, yb in train_dl:loss_batch(model, loss_func, xb, yb, opt)model.eval()with torch.no_grad():losses, nums = zip(*[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl])val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)print('當前step:'+str(step), '驗證集損失:'+str(val_loss))指定優化器:
from torch import optim def get_model():model = Mnist_NN()return model, optim.SGD(model.parameters(), lr=0.001)調用上面所有寫的函數即可:
# 對總數據進行分批量 train_dl, valid_dl = get_data(train_ds, valid_ds, bs) # 拿到模型和優化器 model, opt = get_model() # 開始訓練 fit(25, model, loss_func, opt, train_dl, valid_dl)out:
[{“metadata”:{“trusted”:true},“cell_type”:“code”,“source”:"# 對總數據進行分批量\ntrain_dl, valid_dl = get_data(train_ds, valid_ds, bs)\n# 拿到模型和優化器\nmodel, opt = get_model()\n# 開始訓練\nfit(25, model, loss_func, opt, train_dl, valid_dl)",“execution_count”:null,“outputs”:[{“output_type”:“stream”,“text”:“當前step:0 驗證集損失:2.27946646194458\n當前step:1 驗證集損失:2.2453414649963377\n當前step:2 驗證集損失:2.1901785594940186\n當前step:3 驗證集損失:2.0985936725616456\n當前step:4 驗證集損失:1.9537098587036132\n當前step:5 驗證集損失:1.749969289779663\n當前step:6 驗證集損失:1.4996562250137329\n當前step:7 驗證集損失:1.2475741912841798\n當前step:8 驗證集損失:1.041046302986145\n當前step:9 驗證集損失:0.8889363418579102\n當前step:10 驗證集損失:0.7784037446022034\n當前step:11 驗證集損失:0.6955419854164123\n當前step:12 驗證集損失:0.6328013188362122\n當前step:13 驗證集損失:0.5834899019241333\n當前step:14 驗證集損失:0.5442861658096313\n當前step:15 驗證集損失:0.5122919623851776\n當前step:16 驗證集損失:0.4864934079647064\n當前step:17 驗證集損失:0.46465890626907347\n當前step:18 驗證集損失:0.4461087041854858\n當前step:19 驗證集損失:0.4306937822341919\n當前step:20 驗證集損失:0.4172893476009369\n當前step:21 驗證集損失:0.40593954257965087\n”,“name”:“stdout”}]}]
6.3 暫退法
在前一小節中我們提到了dropout暫退法。在這一小節中,我們來著重談論一下。
6.3.1 重新看待過擬合問題
當面對更多的特征而樣本不足時,線性模型往往會過擬合。相反,當給出更多的樣本而不是特征,通常線性模型不會過擬合。不幸的是,線性模型泛化的可靠性是由代價的。簡單地說,線性模型沒有考慮到特征之間的交互作用。對于每個特征,線性模型都必須指定正的或負的權重。
泛化小和靈活性之間的這種基本權衡被描述為偏差——方差權衡。線性模型有很高的偏差:它們只能表示一小類函數。然而,這些模型的方差很低:它們在不同的隨機數據樣本上可以得出了相似的結果。
如果把偏差——方差看成一個色譜,那么與之相反的一端的是深度神經網絡。神經網絡并不局限與單獨查看每個特征,而是學習特征之間的交互。例如:神經網絡可能推斷“尼日利亞”和“西聯匯款”一起出現在電子郵件中表示垃圾郵件,但單獨出現則不表示垃圾郵件。
即使我們有比特征多得多的樣本,深度神經網絡也有可能過擬合。
6.3.2 在穩健性中加入擾動
在探究泛化性之前,我們先來定義一個什么是一個“好”的預測模型。前面我們說過,我們希望訓練誤差和泛化誤差之間折中,做到一個模型能夠很好地擬合訓練數據,也能夠很好地預測未知數據。所以根據經典泛化理論 :為了縮小訓練和測試性能之間的差距,應該以簡單的模型為目標。簡單些以較小維度的形式展現。這也是為什么開頭我們用了最簡單的線性模型來講解原理的緣故。而正則化也是一樣,為了說明我們是要減少特征向量的大小,我們直接對特征向量大小對應的概念——范數來下手。
簡單性的另一個角度是平滑性。即函數不應該對其輸入的微小變化敏感(這個也叫魯棒性)。這也是為什么我們第一章線性模型所需的數據中我們往里添加了噪聲點。因為一個好的模型不應該因為某些微小的噪聲就失效。
所以根據上面所說的原理,科學家提出了一個想法:在訓練過程中,在進行后續層的計算(我們的計算比如第一層輸入層是不計算的,主要的計算在第二層)之前,我們先對網絡添加小部分噪聲。因為當訓練一個有多層的深層網絡時,注入噪聲只會在輸入——輸出映射上增強平滑性。
這個想法被稱為暫退法也叫丟棄法。暫退法在前向傳播的過程中,計算每一內部層的同時注入噪聲,這已經稱為訓練神經網絡的常用技術。這種方法之所以被稱為暫退法,是因為我們從表面上看是在訓練的過程中丟棄一些神經元。在整個訓練過程的每一次迭代中,標準暫退法包括在計算下一層之前將當前層中的一些結點置零。
6.3.3 暫退法實際的實現
回想之前做的多層感知機。如果我們將暫退法應用到隱藏層,以p的概率將隱藏單元置為零時,結果可以看作是只包含原始神經元子集的網絡。比如在下圖中,刪除了h2和h5,因此輸出的計算不再依賴于h2和h5,并且它們各自的梯度在執行反向傳播時也會消失。這樣,輸出層的計算不能過度依賴于h1-h5的任何一個元素。
通常,我們在測試的時候不會用到暫退法。給定一個訓練好的模型和一個新的樣本,我們不會丟棄任何節點,因此不需要標準化。但是研究人員就需要去用暫退法去估計神經網絡預測的不確定性了;如果通過應用不同暫退法(一種暫退法包含抹去某些節點)得到的預測結果都是一致的,那么我們可以說網絡發揮更穩定。
6.4 后話
我知道,這一小節你學的很不愉快。實際上,太多細致的知識無法在一個小節中直接說完,所以請不要緊張,這里即使你不懂太多的東西,你只要會一個就行了——搭建個網絡,即使不會訓練也沒有關系,先會搭網絡就足夠了。在后面的學習中你會發現,除了搭網絡這個地方不一樣,實際上訓練的過程都是一樣的。
總結
以上是生活随笔為你收集整理的深度学习修炼(六)——神经网络分类问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 天线的基本知识
- 下一篇: 人脸识别常用开源数据集大全