Try   HackMD

Pytorch基礎使用筆記


matplotlib和pytorch衝突處理方法

在import torch之後,再做pyplot的畫圖,可能會導致kernel dead,解決方法如下

import os os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"

函數引入

import torch from torch import nn #可以呼叫模型、損失函數.... from torch import optim #引入優化器具 import torch.nn.functional as F #可以呼叫模型、損失函數.... from torch import TensorDataset #用來打包數據(資料, 標籤) from torch import DataLoader #可以將它用在生成批次、隨機的訓練資料

nn vs F

建構神經網路

  • nn底下的神經網路有反向傳播的功能
  • F底下的神經網路需要手動調整參數,也沒辦法做反向傳播
  • 通常都使用nn建構神經網路

其他模型架構函數

  • 需要儲存狀態的建議使用nn先把它指派給某個變數(如:drop = nn.Dropout(0.5))
  • 只需要直接使用的用F即可(如:F.relu(x))

損失函數

  • nn的loss_function要先指派給某個變數,之後再呼叫該變數使用
  • F的loss_function可以直接拿來使用

tensor資料轉換

在做資料轉換時,建議都要指定變數型別,不然可能會報錯

tensor()

將某個data轉為tensor,並指定變數型別(此為浮點數)

tensor_data = torch.tensor(data, dtype=torch.float32)

to()

可以做到變數型態轉型、cpu和cuda的移動

tensor_float32 = tensor_data.to(dtype=torch.float32)#轉換成浮點數 tensor_cuda = tensor_float32.to(device='cuda')#轉換到GPU tensor_cpu = tensor_cuda.to(device='cpu')#轉換到CPU

強制轉型

參考
直接將變數強制傳形成tensor的某個變數型別

float_data = torch.FloatTensor(data) float_data_cuda = torch.cuda.FloatTensor(data)#強制移到GPU並轉成浮點數

cpu()、cuda()

將tensor在CPU、GPU之間移動

tensor_cuda = tensor_float32.cuda()#轉換到GPU tensor_cpu = tensor_cuda.cpu()#轉換到CPU

轉成numpy

numpy_array = torch_tensor.numpy()

torch.from_numpy(numpy array)

將numpy_array轉成torch_array
(注意,轉換之後的torch array共用numpy array的記憶體)


類似numpy的操作

參考網址

tensor形狀操作

tesnor1.view(n, m, -1)

  • 將資料reshape成(n, m, -1),並回傳結果
  • -1代表tensor剩下的元素個數

tensor1.permute((0, 2, 1))

  • 代表輸出的維度順序是原本張量維度的 0,2,1 順序

tensor1.transpose(1, 2)

  • 將原本的第1維與第2維進行對調

squeeze/unsqueeze

  • 在指定維度增加一個維度
# 增加張量維度 a = torch.ones((2, 2, 3)) g = torch.unsqueeze(a, dim=1) #增加維度在1的位置 h = torch.squeeze(g) #壓縮為度為1的位置 print(g.shape, h.shape)#(2, 1, 2, 3), (2, 2, 3)

torch.randperm()

  • 輸入一個整數n,回傳一個隨機排序[0,n)的tensor
  • 可以把它拿去把資料隨機排序

隨機排序資料

random_indice = torch.randperm(2 * len(x)) train_data = train_data[random_indice] train_label = train_label[random_indice]

搭配sampler

indice = torch.randperm(len(mnist)) train_sampler = RandomSampler(indice[:int(0.9 * len(indice))]) val_sampler = RandomSampler(indice[int(0.9 * len(indice)):int(0.95 * len(indice))]) test_sampler = RandomSampler(indice[int(0.95 * len(indice)):]) train_dataloader = DataLoader(mnist, batch_size=batch, sampler=train_sampler) val_dataloader = DataLoader(mnist, batch_size=batch, sampler=val_sampler) test_dataloader = DataLoader(mnist, batch_size=batch, sampler=test_sampler)

張量產生

torch.rand(tuple(x,y))

  • 隨機平均分布生成x \times y的矩陣

torch.rand_like(tensor1)

  • 隨機平均分布生成與tensor1形狀相同的矩陣

torch.randn(tuple(x,y))

  • 隨機正態分布生成x \times y的矩陣

torch.randn_like(tensor1)

  • 隨機正態分布生成與tensor1形狀相同的矩陣

torch.randint(low, high, size)

  • 隨機回傳給定形狀(size)的張量
  • size要傳入tuple

torch.ones(size)

  • 回傳給定形狀(size)的張量,每個值都為1
  • size要傳入tuple

torch.ones_like(tensor1)

  • 生成與tensor1形狀相同的矩陣,每個值都為1

其他常用操作

torch.sum(input, dim, keepdim)

對input的某個維度求和,並回傳結果

keepdim

如果使用True,求和的維度就不會被squeeze

torch.topk(input, k, dim, largest, sorted)

找到前k大或前k小的數據其對應的index

k

要找到前k個數據

dim

指定在哪個維度尋找

largest

指定為True就按照大到小排序,False反之

sorted

返回的結果是否按照順序回傳

torch.gather(tensor1, 1, tensor2)

tensor1裡面放value
tensor2裡面放index
在dim = 1上操作
可以取得tensor2所對應tensor1上的value值
EX:

inputs = torch.Tensor([[1, 2], [3, 4], [5, 6]]) index = torch.LongTensor([[1], [0], [1]]) output = torch.gather(inputs, 1, index) print(output) """ tensor([[2.], [3.], [6.]]) """

torch.cat()

參考

  • 輸入一個(tensor1, tesnor2,..)或[tensor1, tesnor2,..],合併裡面的tensor
train_data = torch.cat((x, fake_mnist))

批次資料(torch.utils.data)

參考

TensorDataset

  • 用在將對應的元素打包成元組,與zip功能類似
  • 請注意,輸入的第一個維度必須相同
  • 這個用在打包資料標籤
a = torch.tensor([[1,2,3], [1,2,3], [1,2,3], [1,2,3]]) b = torch.tensor([1, 2, 3, 4]) c = torch.tensor([1, 2, 3, 4]) train_data = TensorDataset(a, b, c) for t in train_data: print(t) """ (tensor([1, 2, 3]), tensor(1), tensor(1)) (tensor([1, 2, 3]), tensor(2), tensor(2)) (tensor([1, 2, 3]), tensor(3), tensor(3)) (tensor([1, 2, 3]), tensor(4), tensor(4)) """

DataLoader

  • 可以將((tensor, tensor, ), (tensor, tensor, ),)這種格式的資料做分批、隨機
  • batch_size可以設定一個batch的大小
  • shuffle=True會隨機排列
  • sampler=某個sampler(搭配pytorch內建sampler使用)
    注意: shuffle和sampler不能同時使用
train_loader = DataLoader(train_data, shuffle=False, batch_size=2) counter = 0 for x, y, z in train_loader: counter += 1 print(f"=========================\nbatch {counter}:") print(x) print(y) print(z) """ ========================= batch 1: tensor([[1, 2, 3], [1, 2, 3]]) tensor([1, 2]) tensor([1, 2]) ========================= batch 2: tensor([[1, 2, 3], [1, 2, 3]]) tensor([3, 4]) tensor([3, 4]) """

sampler

SubsetRandomSampler

將已經random過順序的list轉成sampler

indices = list(range(len(dataset)))#取得全資料長度 np.random.shuffle(indices) #隨機 sample = SubsetRandomSampler(indices) train_data_loader = DataLoader(dataset, batch_size=32, sampler=sample)

模型建構

簡單創建有序模型序列

優點

  • 可以利用nn.Sequential將模型不同部分模組化
  • code簡潔
  • 可以用在遷移學習的特徵提取(提取前幾層)

code

model = nn.Sequential(nn.Linear(784, 128), nn.ReLU(), nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 10), nn.LogSoftmax(dim = 1) )

自行定義

優點

  • 可以做很多怪事(如:x = x.view(batch, -1))
  • 也能做模組化
  • 看起來比較厲害
class NetWork(nn.Module): def __init__(self, input_, h1, output_): super().__init__()#這個很重要 #初始化一些東西,如:linear、drop self.fc1 = nn.Linear(input_, h1) self.fc2 = nn.Linear(h1, output_) self.drop = nn.Dropout(0.5) def forward(self, x, batch): # 確保符合形狀,視情況更動 x = x.view(batch, -1) #把x帶入進行計算 x = F.relu(self.fc1(x)) x = self.drop(x) x = self.fc2(x) x = F.softmax(x, dim=1) return x

nn.Linear

初始化(__init__)

self.f1 = nn.Linear(in_features=10, out_features=5, weight=custom_weight, bias=custom_bias)

in_features、out_features

  • 顧名思義,就是輸入的神經元數量和輸出的神經元數量
  • 這是必要的輸入

weight

  • 非必要輸入,預設是正態分布
  • 可以自訂義初始權重(可參考神經網路筆記的優化方法)

bias

  • 非必要輸入,預設是0
  • 如果bias=False就不會使用bias

前向傳播(forward)

x = self.f1(x)#形狀必須匹配

查看權重、偏差

print(self.f1.weight) print(self.f1.bias)

活化函數

F

x = F.relu(x)

nn

relu_layer = nn.ReLU() x = relu_layer(x)

softmax

使用批次優化時,dim=1

output = F.softmax(x, dim=1)#搭配CrossEntropyLoss使用
output = F.log_softmax(x, dim=1)#搭配NLLLoss使用

dropout

drop = nn.Dropout(0.5) x = drop(x)

優化器 optimizer

Adam

optimizer = optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)

model.parameters()

要修改的模型參數,這個一定要有

lr

學習率

betas

adam的係數,我不知道它在幹嘛qq

eps

避免分母為0,會添加到方差的分母上

weight_decay

正規化λ的值,內建是使用L2正規化

amsgrad

改成True會用新的更新規則,有時候會有助於穩定優化過程

其他優化器

optim.SGD(model.parameters(), lr = 0.003)#能固定學習率 optim.RMSprop(model.parameters(), lr=0.01) optim.Adagrad(model.parameters(), lr=0.01)

損失函數

loss_function = nn.loss_function() loss = loss_function(model(input_data),labels)

nn.NLLLoss

crossEntropy加負號和把對應到正確答案的loss加總的步驟
要搭配LogSoftmax()

nn.CrossEntropyLoss

顧名思義,crossEntropy
但pytorch很奇怪
你用這個除了做crossEntropy還幫你做softmax

nn.BCELoss

二元CrossEntropy

nn.MSELoss

均方誤差


模型訓練

model = NetWork() loss_function = nn.CrossEntropyLoss() optimizer = optimizer = optim.Adam(model.parameters(), lr=0.001) use_gpu = torch.cuda.is_available() if (use_gpu): model.to(dtype=torch.float32, device='cuda') loss_function.to(device='cuda') for i in range(epoch): model.train() train_loss = 0 valid_loss = 0 for j in range(len(batch_train_x)): optimizer.zero_grad()#重設gradient x = batch_train_x[j] y = batch_train_y[j] if (use_gpu): x = x.to(dtype=torch.float32, device='cuda') y = y.to(dtype=torch.float32, device='cuda') prediction = model(x) loss = loss_function(prediction, y) loss.backward()#往回走取微分 optimizer.step()#對模型進行修改 train_loss += loss.item()

取得GPU狀態

要使用GPU優化前,先做確認

torch.cuda.is_available()

清空緩存區

GPU占用不會自己清理,如果能把不必要的清掉能減少許多GPU ram的浪費

torch.cuda.empty_cache()

loss

取得loss

loss = loss_function(model(x), label)

取得loss數值

loss_num = loss.item()

反向傳播

optimizer.zero_grad()#重設gradient loss.backward()#往回走取微分 optimizer.step()#對模型進行修改

backward這個函數會取得產生loss的過程中所參與到的所有tensor的微分,optimizer.step()則會利用過程中算出的微分去修改tensor

切換成訓練模式

啟用dropout

model.train()

模型測試

model.eval() with no_grad(): for j in range(len(batch_train_x)): x = batch_train_x[j] y = batch_train_y[j] if (use_gpu): x = x.to(dtype=torch.float32, device='cuda') y = y.to(dtype=torch.float32, device='cuda') prediction = model(x) loss = loss_function(prediction, y) valid_loss += loss.item()

關閉torch梯度計算

關閉梯度計算能減少負擔(測試的時候不用算梯度),增加運算速度

with torch.no_grad(): #下面的部分會處於不計算梯度的模式

切換成評估模式

關閉dropout

model.eval() # 將模型切換為評估模式

模型存取

參考

儲存整個模型

torch.save(model ,"model.pth" )

讀取已儲存模型

model = torch.load("model.pth")

儲存權重

torch.save(model.state_dict() ,"checkpoint.pth" )

model.state_dict()

訓練過後,模型的各種參數

讀取已儲存權重

check_point = torch.load("checkpoint.pth") model = NetWork()#要跟讀取的模型同個結構 model.load_state_dict(check_point)

使用現成模型做遷移學習

參考

pretrain模型們

torchvision.models

pytorch本身有提供許多圖像辨識的pre_train模型

Hugging Face

提供很多開源的大型模型
教學

Llama

教學連結

模型載入

使用Densenet121這個已經訓練好的模型

model = models.densenet121(pretrained=True)

查看模型

model = models.vgg16(pretrained=True) print(model) """ VGG ( (features): Sequential ( (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU (inplace) (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (3): ReLU (inplace) (4): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1)) 接下來還是一堆的捲積、池化、Relu ) (classifier): Sequential ( (0): Linear (25088 -> 4096) (1): ReLU (inplace) (2): Dropout (p = 0.5) (3): Linear (4096 -> 4096) (4): ReLU (inplace) (5): Dropout (p = 0.5) (6): Linear (4096 -> 1000) ) ) """

讓pretrain的模型為自己工作

修改載入的模型classifier的部分

這個方法的問題,是有可能因為pretrain的模型太大導致GPU ram塞不下

model.classifier = torch.nn.Sequential(torch.nn.Linear(25088, 4096), torch.nn.ReLU(), torch.nn.Dropout(p=0.5), torch.nn.Linear(4096,4096), torch.nn.ReLU(), torch.nn.Dropout(p=0.5), torch.nn.Linear(4096, 2))

特徵提前提取

  • 把ram的空間拉高肯定比GPU ram容易,所以能在cpu模式先做好的先做完
  • 先把pre_train模型我們感興趣的部分先拿出來
  • 再把資料丟給拿出來的那個東西跑forward
  • 跑完的東西跟著model丟給GPU訓練
feature_extractor = torch.nn.Sequential(*list(pretrained_model.children())[:5])#提取pretrained_model.children前五層 features = feature_extractor(input_data)#提取特徵 """ 略過很多步驟,包含丟給GPU """ output = model(features)