軟工第一次作業


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。不同層之間全連接,層內無連接,是二分圖。與感知器不同,RBM沒有顯式的重構過程。從聯合概率到條件概率。

自編碼器 VS 受限玻爾茲曼機

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函數。

個人總結

1.

在筆記本原代碼中,用標量做點積操作時會報錯:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-16-d0b9c527feb4> in <module>()
      1 # Scalar product
----> 2 m @ v

RuntimeError: expected scalar type Float but found Long

發現是由於Tensor的類型不一樣導致報錯。m為Float而v為Long。因此可以把v轉為Float類型:

v = torch.arange(1, 5, 1.0)

也可更改dtype。

2.

在進行訓練時,觀看代碼發現計算loss的時候用的y_pred為:

tensor([[3.2457e-01, 3.5678e-01, 3.1865e-01],
        [3.2591e-01, 3.5311e-01, 3.2098e-01],
        [3.2227e-01, 3.5123e-01, 3.2650e-01],
        ...,
        [2.7881e-02, 8.1901e-06, 9.7211e-01],
        [5.1700e-02, 1.6200e-05, 9.4828e-01],
        [1.2679e-01, 7.8985e-05, 8.7313e-01]], device='cuda:0',
       grad_fn=<SoftmaxBackward>)

這種形式的張量,三個數的和不為1,而網絡中沒有加softmax但是卻能夠直接用來和Y一起計算交叉熵:

loss = criterion(y_pred, Y)

查閱資料發現代碼中用的criterion = torch.nn.CrossEntropyLoss()已經實現了logsoftmax。於是考慮對比一下自己實現的和nn模塊里的效果:

def softmax(x):
    s = torch.exp(x)
    return s / torch.sum(s, dim=1, keepdim=True)# 此處觸發了廣播機制
def cross_entropy(y_hat, y):
  return (-torch.log(y_hat[range(len(y_hat)), y])).sum() / len(y)
model = nn.Sequential(
    nn.Linear(D, H),
    nn.ReLU(),
    nn.Linear(H, C),
    nn.Softmax()
)

最后的效果為:

[EPOCH]: 999, [LOSS]: 0.175558, [ACCURACY]: 0.952

相比使用torch.nn.CrossEntropyLoss()而言區別不大。

3.

該數據集是一個比較簡單的數據集,而最后的准確率為0.95,顯然還可以提升。考慮將網絡改為:

model = nn.Sequential(
    nn.Linear(D, 64),
    nn.ReLU(),
    nn.Linear(64, 128),
    nn.Linear(128, C),
)

加入一層同時修改隱藏層大小讓網絡變寬變深,可以得到更好的效果。

tensor([[ -0.1507,  -0.1765,  -0.0731],
        [ -0.0824,  -0.2347,  -0.0940],
        [ -0.0760,  -0.3144,  -0.0239],
        ...,
        [ -0.0227, -15.5053,  11.8754],
        [  0.6705, -15.0096,  10.8240],
        [  2.6578, -14.0713,   8.1954]], device='cuda:0',
       grad_fn=<AddmmBackward>)
[EPOCH]: 999, [LOSS]: 0.018131, [ACCURACY]: 0.999


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM