1. 程式人生 > 實用技巧 >深度學習筆記一

深度學習筆記一

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');