【第一週】深度學習和PyTorch基礎
緒論
本次作業主要針對機器學習概念的掃盲以及PyTorch的基礎應用。全文共分為四個部分,分別是:第一部分對影象的基本操作,第二部分PyTorch的常用操作,第三部分和第四部分是PyTorch的實際應用。由於之前沒接觸過深度學習和影象處理方面的知識,故本次作業保留題目的原始碼,並附上自己的註解和總結,以供後續檢視。
注:以下是程式碼練習;進階練習見附:貓狗大戰
第一部分 影象基本操作
1.1 yeast_colony_array
1.1.1 下載影象
!wget https://raw.githubusercontent.com/summitgao/ImageGallery/master/yeast_colony_array.jpg
1.1.2 執行程式碼
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) # 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');
注:
- skimage中io.imread的函式用法
io.imread(img_dir):1.用於讀取圖片檔案,讀取的圖片為GRB格式;2.讀出圖片的格式是uint8(unsigned int),value是numpy array。
- matplotlib.pyplot庫
1.subplot(numRows,numCols,plotNum)
圖表的整個繪圖區域被分成numRows行和numCols列,plotNum引數指定建立的Axes物件所在的區域。subplot(121)表示分成一行兩列,佔用第一個,即第一行第一列的子圖。
2.axis('off')
不顯示座標尺寸
3.colony[:,:,:]和colony[:,:,0]
colony[:,:,0]表示對陣列colony切片,第二維取0得[406,604]大小的陣列
1.1.3執行結果
該影象的shape為(406,604,3),即406個二維陣列,每個二維陣列有604行和3列。
1.2 camera
1.2.1讀取影象(設定一個區域為黑色)
# 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')
1.2.2執行結果——1
1.2.3 讀取影象(設定前十行為黑色)
# Set the first ten lines to black
camera = data.camera()
camera[:10] = 0
plt.imshow(camera, 'gray')
1.2.4 執行結果——2
1.2.5 讀取影象(設定黑色的地方為白色)
# Set to "white" (255) pixels where mask is True
camera = data.camera()
mask = camera < 80
camera[mask] = 255
plt.imshow(camera, 'gray')
注:採用RGB顏色對比表,79為grey31,255為白色
1.2.6 執行結果——3
1.3 chelsea
1.3.1讀取原圖片
# Change the color for real images
cat = data.chelsea()
plt.imshow(cat)
注:chelsea、camera....為skimage程式自帶的示例圖片
1.3.2 執行結果——1
1.3.3 設定(>160)的地方為紅色
# Set brighter pixels to red
red_cat = cat.copy()
reddish = cat[:, :, 0] > 160
red_cat[reddish] = [255, 0, 0]
plt.imshow(red_cat)
1.3.4 執行結果——2
1.3.5 改變顏色
# Change RGB color to BGR for openCV
BGR_cat = cat[:, :, ::-1]
plt.imshow(BGR_cat)
注:cat[: , : , :: -1]表示對字串擷取,去除一位
1.3.6 執行結果——3
1.4 轉換影象資料型別
from skimage import img_as_float, img_as_ubyte
float_cat = img_as_float(cat)
uint_cat = img_as_ubyte(float_cat)
注:
1.5 顯示影象直方圖
img = data.camera()
plt.hist(img.ravel(), bins=256, histtype='step', color='black');
執行結果:
1.6 影象分割
# 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(降噪,過濾很小或很大畫素值的影象點)
# Use thresholding
plt.imshow(img>0.5)
注:目的是從灰度影象中分離出目標區域和背景區域
執行結果:
1.7 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')
執行結果:
1.8 改變影象的對比度
# 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')
注:出現exposure未定義的情況,加入定義 from skimage import exposure 解決問題
np.percentile ( img , ( 2 , 98 ) )取兩個位置,分別賦值到p2和p98,沒有axis屬性,按照一維進行計算。
a : array,用來算分位數的物件,可以是多維的陣列
q : 介於0-100的float,用來計算是幾分位的引數,如四分之一位就是25,如要算兩個位置的數就(25,75)
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')python
執行結果:
# 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()
執行結果:
總結
本文學習的內容主要是對影象的分割操作。前面的幾張圖片的示例演示瞭如何將圖片從img格式的照片轉換成畫素點陣,以及對三維陣列的值進行修改從而改變影象。
影象直方圖用來表示亮度數值和畫素數量的關係,通過影象直方圖可以清楚看出畫面中亮度的分佈和比例,以左暗右亮的分佈曲線形式呈現出來。
影象分割用來把影象分成若干個特定的區域,每個區域有自己的特色。從1.6 的影象中可以看到先把圖片轉為灰度圖,然後bins取值256,histtype取值step,color取值black,即條形數為256,線條的型別為未填充線條,顏色為黑色,從而畫出直方圖,根據影象可以看出0.5以後的畫素數少。
最後,改變影象的對比度。結果圖共有四組,分別為低對比度圖片,對比度拉伸圖片,直方圖均衡圖片和自適應均衡圖片。從結果中可以看出,直方圖均衡技術能夠有效增加影象相對高低的對比度,產生較高的對比度影象。自適應均衡通過計算幾個不同的直方圖,每個直方圖對應於影象的不同部分,從而提升圖片的對比度。
第二部分 學習PyTorch
2.1 定義資料
使用torch.Tensor來定義資料,tensor是張量,是數字各種形式的總稱。
torch.tensor(data, dtype=None, device=None, requires_grad=False)
其中data可以是:list, tuple, array, scalar等型別
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)
2.2 定義操作
# 建立一個 2x4 的tensor
m = torch.Tensor([[2, 5, 3, 7],
[4, 2, 1, 9]])
print(m.size(0), m.size(1), m.size(), sep=' -- ')
生成直方圖
from matplotlib import pyplot as plt
# matlabplotlib 只能顯示numpy型別的資料,下面展示了轉換資料型別,然後顯示
# 注意 randn 是生成均值為 0, 方差為 1 的隨機數
# 下面是生成 1000 個隨機數,並按照 100 個 bin 統計直方圖
plt.hist(torch.randn(1000).numpy(), 100);
# 當資料非常非常多的時候,正態分佈會體現的非常明顯
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))
第三部分 螺旋資料分類
示例分析:
圖中兩種螺旋資料,如果對其進行邏輯迴歸分類,它會創造一系列的線性平面來分離資料,但是會導致每個區域中都有不屬於該類別的點,因此為了解決這個問題,需要變換輸入空間,使得資料變得線性可分,點選檢視原理解析。
程式碼如下:
3.1 引入庫,初始化引數
!wget https://raw.githubusercontent.com/Atcold/pytorch-Deep-Learning/master/res/plot_lib.py
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 # 神經網路裡隱層單元的數量
#初始化X和Y,其中X可以理解為特徵矩陣,Y可以理解為樣本標籤
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)
執行截圖:
3.2 構建線性模型分類
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)
print(y_pred[10, :])
print(score[10])
print(predicted[10])
print(model)
plot_model(X, Y, model)
執行截圖:
上面使用print(model)把模型輸出,可以看到有兩層:
第一層輸入為2(特徵維度),輸出為100
第二層輸入為100(上一層的輸出),輸出為3(類別數)
從上圖看出,線性模型的準確率最高為0.5左右,對於螺旋資料來說,線性模型難以實現準確分類。
3.3 構建兩層神經網路分類
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()
# Plot trained model
print(model)
plot_model(X, Y, model)
執行截圖:
通過上圖可以看到,在兩層神經網路中加入ReLU啟用函式以後,分類的準確率得到顯著提高。
總結
首先,清楚神經網路的定義和基本原理;其次,啟用函式的作用,啟用函式是用來加入非線性因素,解決線性模型所不能解決的問題。假如進行二分類問題,如果僅僅利用單層或多層的感知機,只能對資料進行線性分割,可能會出現樣本點不能線性可分的情況。
因此,引入啟用函式。通過啟用函式對映之後,輸出的是一個非線性函式,因此啟用函式能夠有效解決非線性的分類問題,啟用函式的詳解。
第四部分 迴歸分析
4.1 引入庫,初始化引數
!wget https://raw.githubusercontent.com/Atcold/pytorch-Deep-Learning/master/res/plot_lib.py
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');
執行截圖:
4.2 建立線性模型(兩層網路間沒有啟用函式)
learning_rate = 1e-3
lambda_l2 = 1e-5
# 建立神經網路模型
model = nn.Sequential(
nn.Linear(D, H),
nn.Linear(H, C)
)
model.to(device) # 模型轉到 GPU
# 對於迴歸問題,使用MSE損失函式
criterion = torch.nn.MSELoss()
# 定義優化器,使用SGD
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay=lambda_l2) # built-in L2
# 開始訓練()
for t in range(1000):
# 資料輸入模型得到預測結果
y_pred = model(X)
# 計算 MSE 損失
loss = criterion(y_pred, y)
print("[EPOCH]: %i, [LOSS or MSE]: %.6f" % (t, loss.item()))
display.clear_output(wait=True)
# 反向傳播前,梯度清零
optimizer.zero_grad()
# 反向傳播
loss.backward()
# 更新引數
optimizer.step()
# 展示模型與結果
print(model)
plt.figure(figsize=(6,6))
plt.scatter(X.data.cpu().numpy(), y.data.cpu().numpy())
plt.plot(X.data.cpu().numpy(), y_pred.data.cpu().numpy(), 'r-', lw=5)
plt.axis('equal');
執行截圖:
4.3 兩層神經網路
# 這裡定義了2個網路,一個 relu_model,一個 tanh_model,
# 使用了不同的啟用函式
relu_model = nn.Sequential(
nn.Linear(D, H),
nn.ReLU(),
nn.Linear(H, C)
)
relu_model.to(device)
tanh_model = nn.Sequential(
nn.Linear(D, H),
nn.Tanh(),
nn.Linear(H, C)
)
tanh_model.to(device)
# MSE損失函式
criterion = torch.nn.MSELoss()
# 定義優化器,使用 Adam,這裡仍使用 SGD 優化器的化效果會比較差,具體原因請自行百度
optimizer_relumodel = torch.optim.Adam(relu_model.parameters(), lr=learning_rate, weight_decay=lambda_l2)
optimizer_tanhmodel = torch.optim.Adam(tanh_model.parameters(), lr=learning_rate, weight_decay=lambda_l2)
# 開始訓練
for t in range(1000):
y_pred_relumodel = relu_model(X)
y_pred_tanhmodel = tanh_model(X)
# 計算損失與準確率
loss_relumodel = criterion(y_pred_relumodel, y)
loss_tanhmodel = criterion(y_pred_tanhmodel, y)
print(f"[MODEL]: relu_model, [EPOCH]: {t}, [LOSS]: {loss_relumodel.item():.6f}")
print(f"[MODEL]: tanh_model, [EPOCH]: {t}, [LOSS]: {loss_tanhmodel.item():.6f}")
display.clear_output(wait=True)
optimizer_relumodel.zero_grad()
optimizer_tanhmodel.zero_grad()
loss_relumodel.backward()
loss_tanhmodel.backward()
optimizer_relumodel.step()
optimizer_tanhmodel.step()
執行截圖:
4.4 展示迴歸結果
plt.figure(figsize=(12, 6))
def dense_prediction(model, non_linearity):
plt.subplot(1, 2, 1 if non_linearity == 'ReLU' else 2)
X_new = torch.unsqueeze(torch.linspace(-1, 1, 1001), dim=1).to(device)
with torch.no_grad():
y_pred = model(X_new)
plt.plot(X_new.cpu().numpy(), y_pred.cpu().numpy(), 'r-', lw=1)
plt.scatter(X.cpu().numpy(), y.cpu().numpy(), label='data')
plt.axis('square')
plt.title(non_linearity + ' models')
dense_prediction(relu_model, 'ReLU')
dense_prediction(tanh_model, 'Tanh')
執行截圖:
總結
左側是使用ReLU啟用函式的網路得到的結果,右側是使用Tanh啟用函式的網路得到的結果。可以看到,效果有所不同。左側是分段線性函式,右側是連續光滑的迴歸函式。當輸入為負時,ReLU的學習速度會變得很慢,甚至使神經元直接無效,因為此時輸入小於0,梯度為0,其權重沒法更新,因此在剩下的訓練過程中會保持靜默,所以Tanh的迴歸函式要相對光滑一些。