1. 程式人生 > 實用技巧 >動手學深度學習基礎

動手學深度學習基礎

第一次作業:深度學習基礎

1. 影象處理基本練習

展示不同通道上的影象

# 展示三通道影象
plt.subplot(121)
plt.imshow(colony[:,:,:])
plt.title('3-channel image')
plt.axis('off')

# 展示只有一個通道的影象
plt.subplot(122)
plt.imshow(colony[:,:,0])
plt.title('1-channel image')
plt.axis('off');

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

2. Pytorch基礎

2.1 基本資料Tensor

Tensor是Pytorch中的基本操作物件,是儲存和變換資料的主要工具。與NumPy的ndarrays非常類似,不過Tensor還支援GPU運算。Tensor有多種建立方法,如基礎的Tensor(),還有多種與NumPy十分類似的方法,如ones()、eye()、zeros()和randn()等,其中PyTorch中預設的資料型別是 torch.FloatTensor。

a = torch.Tensor(2, 2)

# Tensor()與tensor的區別在於Tensor返回的資料型別是預設的,而tensor型別根據資料進行推斷
# 引數可以為Python的list、NumPy的ndarray
x = torch.tensor((2, 3, 4, 9))

# 使用ones()函式,所有元素均為1
x2 = torch.ones((2, 3))

# 通過現有的Tensor來建立,此方法會預設重用輸入Tensor的一些屬性,例如資料型別,除非自定義資料型別
# 使用dtype來定義資料型別
x3 = torch.zeros_like(x2, dtype=torch.long)

# 使用randn()函式,生成標準分佈的隨機數矩陣
x4 = torch.randn(3, 4)

# 使用arange(start, end, step)函式,表示從start到end,間距為step,一維向量
v = torch.arange(1,4)

# 使用linspace(start, end, steps)函式,表示從start到end,一共steps份,一維向量
x5 = torch.linspace(10, 20, 15)

# 檢視Tensor中總的元素個數
a.numel()

# 通過shape或者size()來獲取Tensor的形狀或維度
print(x.size())
print(x.shape)

2.2 Tensor的各種操作

索引

可以使用類似NumPy的索引操作來訪問Tensor的一部分,但是索引出來的結果與原資料共享記憶體,即修改一個,另一個會跟著修改。

Input:
m = torch.tensor([[2, 3, 4],[5, 6, 7]])
print(m[:,1])
print(m[0])
print(m[0][1])

Output:
tensor([3, 6])
tensor([2, 3, 4])
tensor(3)

# 選擇符合條件的元素並返回
Input:
a = torch.tensor([[1, 2], [3, 4]])
print(a > 2)
ind = a > 2
print(a[ind])

OUtput:
tensor([[False, False],
        [ True,  True]])
tensor([3, 4])

改變形狀

變形操作則是指改變Tensor的維度,有四類不同的基本操作。

變形操作 功能
view()、resize()、reshape() 調整形狀同時元素總數相同
transpose()、permute() 各維度之間的變換
squeeze()、unsqueeze() 處理size為1的維度
expand()、expand_as() 複製元素來擴充套件維度
# 將資料的第二維度與第三維度進行變換

Input:
a = torch.randn((2, 3, 2))
print(a)
res = a.transpose(1, 2)
print(res)

OUtput
tensor([[[-1.6705, -1.0614],
         [ 0.9338,  1.2305],
         [ 0.3439, -0.6650]],

        [[-0.9470, -0.8022],
         [ 0.8864, -0.6687],
         [-0.8743,  2.5200]]])
tensor([[[-1.6705,  0.9338,  0.3439],
         [-1.0614,  1.2305, -0.6650]],

        [[-0.9470,  0.8864, -0.8743],
         [-0.8022, -0.6687,  2.5200]]])

歸併運算

歸併運算可以沿著某一維度進行指定操作或者逐個元素進行操作。

# 按照第0維即按行選取最大值,將每一列的最大值選取出來
# 返回符合操作的數值及其下標
Input:
test = torch.tensor([[1, 8, 3],[4, 5,6]])
num, ind = torch.max(test, 0)
print(num,ind)

Output:

tensor([4, 8, 6]) tensor([1, 0, 1])

資料型別轉換

  • GPU上Tensor資料與CPU上Tensor資料互相轉換,可以通過data.cpu()data.cuda()data.to(device)實現。
# 將CPU上的Tensor資料轉換為GPU 方法一

Input:
device = torch.device('cuda:0')
t1 = torch.tensor([1, 2, 3])
print(t1.type())

t1 = t1.to(device)
print(t1.type())

Output:
torch.LongTensor
torch.cuda.LongTensor

# 方法二
t1 = t1.cuda()

# 將GPU上的Tensor資料轉換為CPU
t2 = t1.cpu()
  • Tensor與Numpy Array之間的轉換,CPU上Tensor資料可以通過data.numpy()進行轉換,但是CPU上Tensor資料要先轉換為CPU後在進行Numpy型別轉換。
# 將GPU上Tensor資料轉換為Numpy型別
t2 = t1.cpu().numpy()

#  將Numpy型別轉換為Tensor資料
Input:
t3 = torch.from_numpy(t2)
t3.type()

Output:
torch.LongTensor
  • Tensor的基本型別t轉換,可以通過torch.tensor(data, dtype=)來實現

3. 螺旋資料分類

3.1 載入相應的包

!wget https://raw.githubusercontent.com/Atcold/pytorch-Deep-Learning/master/res/plot_lib.py
import torch
import random
import math
from torch import nn, optim
from IPython import display
from plot_lib import plot_data, plot_model, set_default

3.2 生成資料集

由於PyTorch為資料在GPU上執行提供了便利的操作。因此可以在GPU進行運算,torch.cuda.is_available()可以用來判斷當前環境下GPU是否可用,使用tensor.to(device)可以將資料轉移到GPU上。

# 將所有最開始讀取資料時的tensor變數copy一份到device所指定的GPU上去,之後的運算都在GPU上進行。
# 這句話需要寫的次數等於需要儲存GPU上的tensor變數的個數;
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('device',device)

seed=1234
random.seed(seed)
torch.manual_seed(seed)

構造一個簡單的訓練資料集,訓練資料集樣本數為1000,特徵數為2,樣本標籤數為3

N = 1000  # 每類樣本的數量
D = 2  # 每個樣本的特徵維度
C = 3  # 樣本的類別
H = 100  # 神經網路裡隱層單元的數量

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())

資料如圖所示:

3.3 定義模型

torch.nn.Sequential是一個Sequential容器,模組將按照在傳入構造器的順序依次被新增到計算圖中執行,當模型中只是簡單的前饋網路時,即上一層的輸出直接作為下一層的輸入,可以採用nn.Sequential()模組來快速搭建模型。

nn.optim中包含了各種常見的優化演算法,包括隨機梯度下降演算法SGD、Adam等。SGD容易陷入區域性最優難以跳出,因此相對於SGD,Adam通常會有更好的效果。

# 網路模組
# # 每一個線性模型都包含 weight 和 bias
model = nn.Sequential(
    nn.Linear(D, H),
    nn.Linear(H, C)
)
model.to(device)


# 損失函式的定義,為交叉熵(cross entropy loss)損失函式
loss_dev = torch.nn.CrossEntropyLoss()

# 梯度下降的學習率
learnrate = 1e-3
decay = 1e-5

# 優化引數定義,為隨機梯度下降(stochastic gradient descent)
optimize = torch.optim.SGD(model.parameters(), lr=learnrate, weight_decay=decay)

3.4 模型訓練

for t in range(1000):
  # 模型的預測輸出
  y_hat = model(X)
  
  # 求得預測輸出的結果值及其下標
  score, predict = torch.max(y_hat, 1)
  
  # 模型預測的準確率和損失
  acc = (Y==predict).sum().float()/len(Y)
  loss = loss_dev(y_hat, Y)
  display.clear_output(wait=True)
  if t % 100 == 0:
    print('epoch:%d loss:%.6f accuracy:%.3f'%(t, loss.item(), acc))
  
  # 反向傳播前把梯度置 0 
  optimize.zero_grad()
  # # 反向傳播優化 
  loss.backward()
  # 通過step函式來迭代模型引數
  optimize.step()

最終執行結果為:

epoch:900 loss:0.772457 accuracy:0.502

模型的結構為

Sequential(
  (0): Linear(in_features=2, out_features=100, bias=True)
  (1): Linear(in_features=100, out_features=3, bias=True)
)

我們從model獲得需要的層,並訪問其權重(weight)和偏差(bias

dense = model[0]
print(dense.weight.size())
print(dense.bias)

3.5 改變模型結構

由於之前的模型訓練出來的結果不太理想,所以這次改變了模型的結構,增加了ReLu(),ReLu啟用函式將模型從線性變成非線性,並將優化演算法變為了Adam。

model = nn.Sequential(
    nn.Linear(D, H),
    nn.ReLU(),
    nn.Linear(H, C)
)
model.to(device)
loss_dev = torch.nn.CrossEntropyLoss()
optimize = torch.optim.Adam(model.parameters(), lr=learnrate, weight_decay=decay)

最終執行結果為:

epoch:900 loss:0.220317 accuracy:0.925

模型的結構為

Sequential(
  (0): Linear(in_features=2, out_features=100, bias=True)
  (1): ReLU()
  (2): Linear(in_features=100, out_features=3, bias=True)
)

4. 迴歸分析

4.1 載入相應的包

import torch
import math
import random
from torch import nn, optim
from matplotlib import pyplot as plt
from IPython import display
from plot_lib import plot_data, plot_model, set_default

4.2 生成資料集

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)

展示資料:

plt.figure(figsize=(6, 6))
plt.scatter(X.cpu().numpy(), y.cpu().numpy())
plt.axis('equal')
plt.show()

4.3 定義模型與訓練

模型與訓練過程與螺旋資料分析一樣

訓練結果:

epoch:900 loss:0.037766

畫圖展示訓練模型結果:

plt.figure(figsize=(6, 6))
plt.scatter(X.cpu().numpy(), y.cpu().numpy())
plt.plot(X.cpu().numpy(), y_hat.data.cpu().numpy(), 'r')
plt.axis('equal')
plt.show()

4.5 改變模型結構

第一種改變

在模型中加入了ReLu(),並將優化演算法變為了Adam。

model = nn.Sequential(
    nn.Linear(D, H),
    nn.ReLU(),
    nn.Linear(H, C)   
)
model.to(device)

訓練結果:

epoch:900 loss:0.006540

第二種改變

在模型中加入了Tanh(),並將優化演算法變為了Adam。

model = nn.Sequential(
    nn.Linear(D, H),
    nn.Tanh(),
    nn.Linear(H, C)   
)
model.to(device)

訓練結果:

epoch:900 loss:0.009457

畫圖展示兩種訓練模型結果: