深度學習筆記一
1. 深度學習筆記一
1.1 緒論
(1)關於人工智慧、機器學習與深度學習之間的關係:
人工智慧是一種科技領域,分為機器學習,資料探勘(大概是大資料方向)以及其他方面如作為AL分支的NLP等。對於機器學習,根據有無監督又分為全監督學習(迴歸演算法、樸素貝葉斯以及SVM等),無監督學習(聚類演算法,如sklearn的KMeans)以及半監督學習;根據是否應用了神經網路又分為傳統機器學習以及神經網路(Neural Network)——深度學習。
(2)傳統機器學習中,特徵是人工設計的,在實際應用中,特徵往往比分類器更重要。整個過程為原始資料—資料預處理—特徵提取—特徵轉換—預測識別(淺層學習),最終得到結果。Kaggle版Hello World之泰坦尼克號乘客生存預測可以加深這方面的理解。
(3)傳統機器學習 VS 深度學習
專家系統方法:手工設計程式
傳統機器學習:人基於對問題的理解,手動設計特徵。學習的過程,是從手動設計的特徵到輸出這樣一個對映。
簡單的表示學習:沒有人工參與,自動學習特徵。人工設計的特徵不一定能窮盡真實世界的情況,且主觀因素會產生影響。
深度學習:真正有效的特徵應該是分層的,深度學習的過程就是從輸入到簡單特徵,再到複雜特徵,經過對映得到輸出的過程。
(4)BP演算法
在神經網路裡增加了一個隱層,解決了XOR難題,效率比感知器大大提高。反向傳播演算法把糾錯的運算量下降到只和神經元數目成正比。
(5)DBM & RBM
演算法借用了統計力學中玻爾茲曼分佈的概念:一個微粒在某個狀態的機率,和那個狀態的能量的指數成反比,和它的溫度的倒數的指數成反比。使用所謂的受限玻爾茲曼機來學習。RBM相當於一個兩層的神經網路,同層神經元不可連線(所以叫“受限”)。深度置信網路DBN就是幾層RBM疊加在一起。RBM可以從輸入資料進行預先訓練,自己發現重要的特徵,對神經網路連線的權重進行有效的初始化。被稱作:特徵提取器或者自動編碼器。
(6)GPU
工作的核心特點:同時處理海量資料。而GPU在底層的ALU是基於單指令多資料流的架構,善於對大批量資料進行處理。神經網路的計算工作,本質上是大量矩陣運算的操作,適合使用GPU。
(老黃趕緊釋出30系顯示卡,這樣我就能買上一代了T^T)
(7)深度學習的“不能”
演算法輸出不穩定,容易被攻擊。兩張影象素級別的差別就可能造成識別的錯誤。
模型複雜度高,難以糾錯和除錯 。
模型層級複合程度高,引數不透明。
端到端訓練方式對資料依賴性強,模型增量性差。
專注直觀感知類問題,對開放性推理問題無能為力。
人類知識無法有效引入進行監督,機器偏見難以避免(演算法必然依賴大資料,但資料不是中立的,從真實社會中抽取必然帶有社會固有的不平等、排斥性和歧視)。
1.2 神經網路基礎
1. 淺層神經網路
(1)生物神經元:多輸入單輸出,具有時間整合和空間整合的特性,分興奮性輸出和抑制性輸入兩種型別,具有閾值特性。
(2)M-P神經元:對生物神經元的抽象和簡化。多輸入訊號進行累加\(\Sigma_i x_i\)
權值\(\omega_i\)正負模擬興奮或抑制,大小模擬強度。輸入和超過閾值\(\theta\),神經元被啟用(fire)。
輸出為\(y=f(\Sigma \omega_i x_i - \theta)\)。有時看到的寫法往往是\(\omega^T x\),看不到\(\theta\),可以這麼想:把\(\omega_0\)看作\(-\theta\), \(x_0\)看作1,就是對上式更簡潔的描述了。
為什麼需要啟用函式呢?對於啟用函式\(f\),神經元繼續傳遞資訊、產生新連線的概率(超過閾值被啟用,但不一定傳遞,比如鬧鐘響了我還有可能賴床2333)。如果沒有啟用函式,就相當於矩陣連乘,\(x^T W_1 ... W_n = x^T \Pi W_k\),多層和一層一樣,只能擬合線性函式。
(3)常見的啟用函式:
線性函式(如 恆等函式):\(f(x)=kx+c\)
S性函式:\(\sigma(z)= \frac{1}{1+e^-z}\) \(\sigma(z)'= \sigma(z)(1-\sigma(z))\) 問題:容易飽和且輸出不對稱。
(4)單層感知器
M-P神經元的權重預先設定,無法學習。單層感知器是首個可以學習的人工神經網路。
邏輯非:\(h_\Theta(x)=g(10-20x_1)\) 邏輯或:\(h_\Theta(x)=g(-10+20x_1+20x_2)\)
(5)多層感知器
可以實現同或門,解決非線性問題。
(6)萬有逼近定理
如果一個隱層包含足夠多的神經元,三層前饋神經網路(輸入-隱層-輸出)能夠任意精度逼近任意預定的連續函式。
為什麼線性分類任務組合後可以解決非線性分類任務?第二層感知器看到的其實不是原始的資料分佈,看到的是被第一層感知器進行空間變換後的分佈。
當隱層足夠寬時,雙隱層感知器(輸入-隱層1-隱層2-輸出)可以逼近任意非連續函式,可以解決任何複雜的分類問題。
(7)神經網路每一層的作用
每一層數學公式:\(\vec{y}=a(W\vec{x}+b)\)
完成輸入->輸出空間變換,包括:
線性變換\(W\vec{x}\):升/降維,放大/縮小,旋轉。
\(+b\):平移
\(a\):彎曲
訓練資料就是讓神經網路去選擇這樣一個線性或者非線性的變換。
神經網路學習如何利用矩陣的線性變換加啟用函式的非線性變換,將原始輸入空間投影到線性可分的空間去分類/迴歸。
增加節點數:增加維度,即增加線性轉換能力。
增加層數:增加啟用函式的次數,即增加非線性轉換次數。
(8)更寬 or 更深?
在神經元總數相同的情況下,增加網路深度可以比增加寬度帶來更強的網路表示能力,產生更多的線性區域。
寬度和深度對函式複雜度的貢獻是不同的,深度的貢獻是指數增長,寬度的貢獻是線性的。
(9)神經網路的引數學習:誤差反向傳播
多層神經網路可看成是一個複合的非線性多元函式。給定訓練資料\({x^i,y^i}\),希望損失
\(\Sigma_i loss(F(x^i),y^i)\)儘可能小。
無約束優化:梯度下降
引數沿負梯度方向更新可以使函式值下降(通過泰勒展開可證明)。
\(\theta_j=\theta_j-\alpha\frac{\partial }{\partial \theta_j}J(\theta)\)
三層前饋神經網路的BP演算法:
前饋為\(z[2]=W[2]*a[1]\)
反饋為\(da[1]=W[2]*dz[2]\)
殘差為損失函式在某個節點的偏導數。
(10)深度學習開發框架
為什麼要基於PyTorch?漲勢迅猛,穩坐榜眼。相比Tensorflow更友好。
(11)深層神經網路的問題:梯度消失
Sigmoid啟用函式求導後在\(\sigma(z)=\frac{1}{2}\)處最大為\(\frac{1}{4}\),取其他值時更小,這樣反向傳播時有很多個節點。就會造成梯度消失這樣的問題。
增加深度會造成梯度消失(gradient vanishing),誤差無法傳播。多層網路容易陷入區域性極值,難以訓練。因此三層神經網路是主流。同時預訓練和新啟用函式使深度成為可能。
2. 深層神經網路
(1)逐層預訓練(layer-wise- pre-training)
問題一:網路層數越多,區域性極小值就越多,有可能網路收斂到很差的區域性極小值裡。
問題二:只能更新後面幾層的引數。
解決:找到一個還不錯的初始值,即權重初始化。
每次選擇一層進行逐層預訓練,最後再從上往下進行一次微調(fine-tuning)
(2)受限玻爾茲曼機和自編碼器
逐層預訓練是看不到輸出的,應該怎麼計算引數呢?
自編碼器(autoencoder)假設輸入與輸出相同(target=input),是一種儘可能復現輸入訊號的神經網路。將input輸入一個encoder編碼器,就會得到一個code;加一個decoder解碼器輸出資訊。通過調整encoder和decoder的引數,使得重構誤差最小。沒有額外監督資訊:無標籤資料,誤差的來源是直接重構後訊號與原輸入相比得到。
受限玻爾茲曼機(RBM)是兩層的神經網路,包含可見層v(輸入層)和隱藏層h。不同層之間全連線,層內無連線,是二分圖(匈牙利演算法和二分圖最大獨立集啥的還沒學QAQ)。與感知器不同,RBM沒有顯式的重構過程。從聯合概率到條件概率。
自編碼器 VS 受限玻爾茲曼機
Python中的影象處理
1. 下載並顯示影象
!wget https://raw.githubusercontent.com/summitgao/ImageGallery/master/yeast_colony_array.jpg
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
import skimage
from skimage import data
from skimage import io
colony = io.imread('yeast_colony_array.jpg')
print(type(colony))
print(colony.shape)
可以看到colony的型別是numpy的ndarray(多維容器)。
在這裡Image讀出來的是PIL的型別,而skimage.io讀出來的資料是numpy格式的。
# Plot all channels of a real image
plt.subplot(121)
plt.imshow(colony[:,:,:])
plt.title('3-channel image')
plt.axis('off')
# Plot one channel only
plt.subplot(122)
plt.imshow(colony[:,:,0])
plt.title('1-channel image')
plt.axis('off');
matplotlib在大資料的課上學過了,就不贅述了。
2. 讀取並改變影象畫素值
# Get the pixel value at row 10, column 10 on the 10th row and 20th column
camera = data.camera()
print(camera[10, 20])
# Set a region to black
camera[30:100, 10:100] = 0
plt.imshow(camera, 'gray')
設定了一個區域為灰色。
skimage.data.camera()即灰度相機影象,data自帶的。
# Set the first ten lines to black
camera = data.camera()
camera[:10] = 0
plt.imshow(camera, 'gray')
設定前十行為灰色。
# Set to "white" (255) pixels where mask is True
camera = data.camera()
mask = camera < 80
camera[mask] = 255
plt.imshow(camera, 'gray')
這波是黑白反轉。
# Change the color for real images
cat = data.chelsea()
plt.imshow(cat)
展示一下貓,這也是自帶的。
# Set brighter pixels to red
red_cat = cat.copy()
reddish = cat[:, :, 0] > 160
red_cat[reddish] = [255, 0, 0]
plt.imshow(red_cat)
把>160的高亮地方的畫素設定為紅色。
# Change RGB color to BGR for openCV
BGR_cat = cat[:, :, ::-1]
plt.imshow(BGR_cat)
改變顏色。
3. 轉換影象資料型別
-
img_as_float Convert to 64-bit floating point.
-
img_as_ubyte Convert to 8-bit uint.
-
img_as_uint Convert to 16-bit uint.
-
img_as_int Convert to 16-bit int.
from skimage import img_as_float, img_as_ubyte
float_cat = img_as_float(cat)
uint_cat = img_as_ubyte(float_cat)
skimage中,圖片用numpy的陣列來儲存。float的範圍是-1~1。
4. 顯示影象直方圖
img = data.camera()
plt.hist(img.ravel(), bins=256, histtype='step', color='black');
numpy的ravel()將多維陣列降至一維。
5. 影象分割
# Use colony image for segmentation
colony = io.imread('yeast_colony_array.jpg')
# Plot histogram
img = skimage.color.rgb2gray(colony)
plt.hist(img.ravel(), bins=256, histtype='step', color='black');
# Use thresholding
plt.imshow(img>0.5)
使用閾值(作用為降噪...?)
6. Canny 運算元用於邊緣檢測
from skimage.feature import canny
from scipy import ndimage as ndi
img_edges = canny(img)
img_filled = ndi.binary_fill_holes(img_edges)
Plot
plt.figure(figsize=(18, 12))
plt.subplot(121)
plt.imshow(img_edges, 'gray')
plt.subplot(122)
plt.imshow(img_filled, 'gray')
Canny運算元是目前找到的一個最優的邊緣檢測運算元。步驟的話balabala
貌似是第一張圖顯示邊緣,第二張圖顯示填充後的?
7. 改變影象對比度
# Load an example image
img = data.camera()
plt.imshow(img, 'gray')
# Contrast stretching
p2, p98 = np.percentile(img, (2, 98))
img_rescale = exposure.rescale_intensity(img, in_range=(p2, p98))
plt.imshow(img_rescale, 'gray')
這裡會出現一個NameError: name 'exposure' is not defined,原因是缺少from skimage import exposure。
percentile(img, (2, 98))在python中計算一個多維陣列的任意百分比分位數,此處的百分位是從小到大排列,只需用np.percentile即可。這裡的寫法是解包了..?
skimage.exposure.exposure 模組中的函式,在對影象進行拉伸或者伸縮強度水平後返回修改後的影象,
rescale_intensity(image, in_range=’image’, out_range=’dtype’)
輸入影象和輸出影象的強度範圍分別由in_range 和out_range指定,用來拉伸或縮小輸入影象的強度範圍。
# Equalization
img_eq = exposure.equalize_hist(img)
plt.imshow(img_eq, 'gray')
這裡是對直方圖均衡化。
# Adaptive Equalization
img_adapteq = exposure.equalize_adapthist(img, clip_limit=0.03)
plt.imshow(img_adapteq, 'gray')
另一個直方圖均衡化函式,有限對比度自適應直方圖均衡。clip_limit為剪下極限,歸一化在0和1之間(更高的值給出更多的對比度)。
# Display results
def plot_img_and_hist(img, axes, bins=256):
"""Plot an image along with its histogram and cumulative histogram.
"""
img = img_as_float(img)
ax_img, ax_hist = axes
ax_cdf = ax_hist.twinx()
# Display image
ax_img.imshow(img, cmap=plt.cm.gray)
ax_img.set_axis_off()
ax_img.set_adjustable('box')
# Display histogram
ax_hist.hist(img.ravel(), bins=bins, histtype='step', color='black')
ax_hist.ticklabel_format(axis='y', style='scientific', scilimits=(0, 0))
ax_hist.set_xlabel('Pixel intensity')
ax_hist.set_xlim(0, 1)
ax_hist.set_yticks([])
# Display cumulative distribution
img_cdf, bins = exposure.cumulative_distribution(img, bins)
ax_cdf.plot(bins, img_cdf, 'r')
ax_cdf.set_yticks([])
return ax_img, ax_hist, ax_cdf
fig = plt.figure(figsize=(16, 8))
axes = np.zeros((2, 4), dtype=np.object)
axes[0, 0] = fig.add_subplot(2, 4, 1)
for i in range(1, 4):
axes[0, i] = fig.add_subplot(2, 4, 1+i, sharex=axes[0,0], sharey=axes[0,0])
for i in range(0, 4):
axes[1, i] = fig.add_subplot(2, 4, 5+i)
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img, axes[:, 0])
ax_img.set_title('Low contrast image')
y_min, y_max = ax_hist.get_ylim()
ax_hist.set_ylabel('Number of pixels')
ax_hist.set_yticks(np.linspace(0, y_max, 5))
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_rescale, axes[:, 1])
ax_img.set_title('Contrast stretching')
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_eq, axes[:, 2])
ax_img.set_title('Histogram equalization')
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_adapteq, axes[:, 3])
ax_img.set_title('Adaptive equalization')
ax_cdf.set_ylabel('Fraction of total intensity')
ax_cdf.set_yticks(np.linspace(0, 1, 5))
fig.tight_layout()
plt.show()
PyTorch學習筆記
1. 定義資料
一般定義資料使用torch.Tensor , tensor的意思是張量,是數字各種形式的總稱
import torch
# 可以是一個數
x = torch.tensor(666)
print(x)
# 可以是一維陣列(向量)
x = torch.tensor([1,2,3,4,5,6])
print(x)
張量是數字各種形式的總稱。
# 可以是二維陣列(矩陣)
x = torch.ones(2,3)
print(x)
# 可以是任意維度的陣列(張量)
x = torch.ones(2,3,4)
print(x)
輸出為tensor([[[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]], [[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]]])
Tensor支援各種各樣型別的資料,包括:
torch.float32, torch.float64, torch.float16, torch.uint8, torch.int8, torch.int16, torch.int32, torch.int64 。這裡不過多描述。
建立Tensor有多種方法,包括:ones, zeros, eye, arange, linspace, rand, randn, normal, uniform, randperm, 使用的時候可以線上搜,下面主要通過程式碼展示。
# 建立一個空張量
x = torch.empty(5,3)
print(x)
# 建立一個隨機初始化的張量
x = torch.rand(5,3)
print(x)
# 建立一個全0的張量,裡面的資料型別為 long
x = torch.zeros(5,3,dtype=torch.long)
print(x)
# 基於現有的tensor,建立一個新tensor,
# 從而可以利用原有的tensor的dtype,device,size之類的屬性資訊
y = x.new_ones(5,3) #tensor new_* 方法,利用原來tensor的dtype,device
print(y)
z = torch.randn_like(x, dtype=torch.float) # 利用原來的tensor的大小,但是重新定義了dtype
print(z)
2. 定義操作
凡是用Tensor進行各種運算的,都是Function
最終,還是需要用Tensor來進行計算的,計算無非是
- 基本運算,加減乘除,求冪求餘
- 布林運算,大於小於,最大最小
- 線性運算,矩陣乘法,求模,求行列式
基本運算包括: abs/sqrt/div/exp/fmod/pow ,及一些三角函式 cos/ sin/ asin/ atan2/ cosh,及 ceil/round/floor/trunc 等具體在使用的時候可以百度一下
布林運算包括: gt/lt/ge/le/eq/ne,topk, sort, max/min
線性計算包括: trace, diag, mm/bmm,t,dot/cross,inverse,svd 等
# 建立一個 2x4 的tensor
m = torch.Tensor([[2, 5, 3, 7],
[4, 2, 1, 9]])
print(m.size(0), m.size(1), m.size(), sep=' -- ')
顯示每一個維度的size。
# 返回 m 中元素的數量
print(m.numel())
# 返回 第0行,第2列的數
print(m[0][2])
# 返回 第1列的全部元素
print(m[:, 1])
# 返回 第0行的全部元素
print(m[0, :])
# Create tensor of numbers from 1 to 5
# 注意這裡結果是1到4,沒有5
v = torch.arange(1, 5)
print(v)
# Scalar product
m @ v
# Calculated by 1*2 + 2*5 + 3*3 + 4*7
m[[0], :] @ v
# Add a random tensor of size 2x4 to m
m + torch.rand(2, 4)
# 轉置,由 2x4 變為 4x2
print(m.t())
# 使用 transpose 也可以達到相同的效果,具體使用方法可以百度
print(m.transpose(0, 1))
# returns a 1D tensor of steps equally spaced points between start=3, end=8 and steps=20
torch.linspace(3, 8, 20)
#輸出為tensor([3.0000, 3.2632, 3.5263, 3.7895, 4.0526, 4.3158, 4.5789, 4.8421, 5.1053,5.3684, 5.6316, 5.8947, 6.1579, 6.4211, 6.6842, 6.9474, 7.2105, 7.4737,7.7368, 8.0000])
from matplotlib import pyplot as plt
# matlabplotlib 只能顯示numpy型別的資料,下面展示了轉換資料型別,然後顯示
# 注意 randn 是生成均值為 0, 方差為 1 的隨機數
# 下面是生成 1000 個隨機數,並按照 100 個 bin 統計直方圖
plt.hist(torch.randn(1000).numpy(), 100);
#注意上面轉換為numpy的方法
# 當資料非常非常多的時候,正態分佈會體現的非常明顯
plt.hist(torch.randn(10**6).numpy(), 100);
# 建立兩個 1x4 的tensor
a = torch.Tensor([[1, 2, 3, 4]])
b = torch.Tensor([[5, 6, 7, 8]])
# 在 0 方向拼接 (即在 Y 方各上拼接), 會得到 2x4 的矩陣
print( torch.cat((a,b), 0))
# 在 1 方向拼接 (即在 X 方各上拼接), 會得到 1x8 的矩陣
print( torch.cat((a,b), 1))
螺旋資料分類
如同課程視訊裡那個例子,對於螺旋資料需要對空間進行變換。
!wget https://raw.githubusercontent.com/Atcold/pytorch-Deep-Learning/master/res/plot_lib.py
首先是下載資料,然後import基本的庫,對引數初始化。
import random
import torch
from torch import nn, optim
import math
from IPython import display
from plot_lib import plot_data, plot_model, set_default
# 因為colab是支援GPU的,torch 將在 GPU 上執行
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('device: ', device)
# 初始化隨機數種子。神經網路的引數都是隨機初始化的,
# 不同的初始化引數往往會導致不同的結果,當得到比較好的結果時我們通常希望這個結果是可以復現的,
# 因此,在pytorch中,通過設定隨機數種子也可以達到這個目的
seed = 12345
random.seed(seed)
torch.manual_seed(seed)
N = 1000 # 每類樣本的數量
D = 2 # 每個樣本的特徵維度
C = 3 # 樣本的類別
H = 100 # 神經網路裡隱層單元的數量
之前導論課智慧小車的實驗並沒有按照這樣寫:
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
結果小車GPU有問題,瘋狂報錯T^T。
設定種子是對隨機化的狀態記錄,很巧妙。
初始化 X 和 Y。 X 可以理解為特徵矩陣,Y可以理解為樣本標籤。 結合程式碼可以看到,X的為一個 NxC 行, D 列的矩陣。C 類樣本,每類樣本是 N個,所以是 N*C 行。每個樣本的特徵維度是2,所以是 2列。
在 python 中,呼叫 zeros 類似的函式,第一個引數是 y方向的,即矩陣的行;第二個引數是 x方向的,即矩陣的列,大家得注意下,不要搞反了。下面結合程式碼看看 3000個樣本的特徵是如何初始化的。
X = torch.zeros(N * C, D).to(device)
Y = torch.zeros(N * C, dtype=torch.long).to(device)
for c in range(C):
index = 0
t = torch.linspace(0, 1, N) # 在[0,1]間均勻的取10000個數,賦給t
# 下面的程式碼不用理解太多,總之是根據公式計算出三類樣本(可以構成螺旋形)
# torch.randn(N) 是得到 N 個均值為0,方差為 1 的一組隨機數,注意要和 rand 區分開
inner_var = torch.linspace( (2*math.pi/C)*c, (2*math.pi/C)*(2+c), N) + torch.randn(N) * 0.2
# 每個樣本的(x,y)座標都儲存在 X 裡
# Y 裡儲存的是樣本的類別,分別為 [0, 1, 2]
for ix in range(N * c, N * (c + 1)):
X[ix] = t[index] * torch.FloatTensor((math.sin(inner_var[index]), math.cos(inner_var[index])))
Y[ix] = c
index += 1
print("Shapes:")
print("X:", X.size())
print("Y:", Y.size())
# visualise the data
plot_data(X, Y)
1. 構建線性模型進行分類
learning_rate = 1e-3
lambda_l2 = 1e-5
# nn 包用來建立線性模型
# 每一個線性模型都包含 weight 和 bias
model = nn.Sequential(
nn.Linear(D, H),
nn.Linear(H, C)
)
model.to(device) # 把模型放到GPU上
# nn 包含多種不同的損失函式,這裡使用的是交叉熵(cross entropy loss)損失函式
criterion = torch.nn.CrossEntropyLoss()
# 這裡使用 optim 包進行隨機梯度下降(stochastic gradient descent)優化
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay=lambda_l2)
# 開始訓練
for t in range(1000):
# 把資料輸入模型,得到預測結果
y_pred = model(X)
# 計算損失和準確率
loss = criterion(y_pred, Y)
score, predicted = torch.max(y_pred, 1)
acc = (Y == predicted).sum().float() / len(Y)
print('[EPOCH]: %i, [LOSS]: %.6f, [ACCURACY]: %.3f' % (t, loss.item(), acc))
display.clear_output(wait=True)
# 反向傳播前把梯度置 0
optimizer.zero_grad()
# 反向傳播優化
loss.backward()
# 更新全部引數
optimizer.step()
這裡對上面的一些關鍵函式進行說明:
使用 print(y_pred.shape) 可以看到模型的預測結果,為[3000, 3]的矩陣。每個樣本的預測結果為3個,儲存在 y_pred 的一行裡。值最大的一個,即為預測該樣本屬於的類別
score, predicted = torch.max(y_pred, 1) 是沿著第二個方向(即X方向)提取最大值。最大的那個值存在 score 中,所在的位置(即第幾列的最大)儲存在 predicted 中。下面程式碼把第10行的情況輸出,供解釋說明
此外,大家可以看到,每一次反向傳播前,都要把梯度清零,參考:https://www.zhihu.com/question/303070254
執行結果:[EPOCH]: 999, [LOSS]: 0.861541, [ACCURACY]: 0.504
print(y_pred.shape)
print(y_pred[10, :])
print(score[10])
print(predicted[10])
# Plot trained model
print(model)
plot_model(X, Y, model)
上面使用 print(model) 把模型輸出,可以看到有兩層:
- 第一層輸入為 2(因為特徵維度為主2),輸出為 100;
- 第二層輸入為 100 (上一層的輸出),輸出為 3(類別數)
從上面圖示可以看出,線性模型的準確率最高只能達到 50% 左右,對於這樣複雜的一個數據分佈,線性模型難以實現準確分類。
2. 構建兩層神經網路分類
learning_rate = 1e-3
lambda_l2 = 1e-5
# 這裡可以看到,和上面模型不同的是,在兩層之間加入了一個 ReLU 啟用函式
model = nn.Sequential(
nn.Linear(D, H),
nn.ReLU(),
nn.Linear(H, C)
)
model.to(device)
# 下面的程式碼和之前是完全一樣的,這裡不過多敘述
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate, weight_decay=lambda_l2) # built-in L2
# 訓練模型,和之前的程式碼是完全一樣的
for t in range(1000):
y_pred = model(X)
loss = criterion(y_pred, Y)
score, predicted = torch.max(y_pred, 1)
acc = ((Y == predicted).sum().float() / len(Y))
print("[EPOCH]: %i, [LOSS]: %.6f, [ACCURACY]: %.3f" % (t, loss.item(), acc))
display.clear_output(wait=True)
# zero the gradients before running the backward pass.
optimizer.zero_grad()
# Backward pass to compute the gradient
loss.backward()
# Update params
optimizer.step()
輸出為:[EPOCH]: 999, [LOSS]: 0.213117, [ACCURACY]: 0.926
# Plot trained model
print(model)
plot_model(X, Y, model)
可以看到分類效果較好,關鍵在於加入了ReLU啟用函式。ReLU函式速度快精度高,逐漸取代了Sigmoid函式。
import random
import torch
from torch import nn, optim
import math
from IPython import display
from plot_lib import plot_data, plot_model, set_default
from matplotlib import pyplot as plt
set_default()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
seed = 1
random.seed(seed)
torch.manual_seed(seed)
N = 1000 # 每類樣本的數量
D = 1 # 每個樣本的特徵維度
C = 1 # 類別數
H = 100 # 隱層的神經元數量
X = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1).to(device)
y = X.pow(3) + 0.3 * torch.rand(X.size()).to(device)
print("Shapes:")
print("X:", tuple(X.size()))
print("y:", tuple(y.size()))
在座標系上顯示資料
plt.figure(figsize=(6, 6))
plt.scatter(X.cpu().numpy(), y.cpu().numpy())
plt.axis('equal');