# Pytorch基礎使用筆記
---
# matplotlib和pytorch衝突處理方法
在import torch之後,再做pyplot的畫圖,可能會導致kernel dead,解決方法如下
```python=
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
```
---
# 函數引入
```python=
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,並指定變數型別(此為浮點數)
```python=
tensor_data = torch.tensor(data, dtype=torch.float32)
```
## **to()**
可以做到變數型態轉型、cpu和cuda的移動
```python=
tensor_float32 = tensor_data.to(dtype=torch.float32)#轉換成浮點數
tensor_cuda = tensor_float32.to(device='cuda')#轉換到GPU
tensor_cpu = tensor_cuda.to(device='cpu')#轉換到CPU
```
## 強制轉型
[參考](https://pytorch.org/docs/stable/tensors.html)
直接將變數強制傳形成tensor的某個變數型別
```python=
float_data = torch.FloatTensor(data)
float_data_cuda = torch.cuda.FloatTensor(data)#強制移到GPU並轉成浮點數
```
## cpu()、cuda()
將tensor在CPU、GPU之間移動
```python=
tensor_cuda = tensor_float32.cuda()#轉換到GPU
tensor_cpu = tensor_cuda.cpu()#轉換到CPU
```
## 轉成numpy
```python=
numpy_array = torch_tensor.numpy()
```
## torch.from_numpy(numpy array)
將numpy\_array轉成torch\_array
(注意,轉換之後的torch array共用numpy array的記憶體)
---
# 類似numpy的操作
[參考網址](https://www.cupoy.com/marathon-mission/00000175AB50EF1A000000016375706F795F72656C656173654355/00000175C0DF5194000000016375706F795F72656C656173654349)
## 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
- 在指定維度增加一個維度
```python=
# 增加張量維度
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
- 可以把它拿去把資料隨機排序
#### 隨機排序資料
```python=
random_indice = torch.randperm(2 * len(x))
train_data = train_data[random_indice]
train_label = train_label[random_indice]
```
#### 搭配sampler
```python=
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:**
```python=
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()
[參考](https://blog.csdn.net/weixin_44613063/article/details/89576810)
- 輸入一個(tensor1, tesnor2,..)或[tensor1, tesnor2,..],合併裡面的tensor
```python=
train_data = torch.cat((x, fake_mnist))
```
# 批次資料(torch.utils.data)
[參考](https://blog.csdn.net/qq_41657873/article/details/115717016)
## TensorDataset
- 用在將對應的元素打包成元組,與[zip](https://www.runoob.com/python/python-func-zip.html)功能類似
- 請注意,輸入的第一個維度必須相同
- 這個用在打包**資料**和**標籤**
```python=
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不能同時使用**
```python=
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
```python=
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
```python=
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))
- 也能做模組化
- ~~看起來比較厲害~~
```python=
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__)
```python=
self.f1 = nn.Linear(in_features=10, out_features=5, weight=custom_weight, bias=custom_bias)
```
#### in_features、out_features
- 顧名思義,就是輸入的神經元數量和輸出的神經元數量
- 這是必要的輸入
#### weight
- 非必要輸入,預設是正態分布
- 可以自訂義初始權重(可參考[神經網路筆記](/IXesQRuzSQisMGYL4t1eRQ)的優化方法)
#### bias
- 非必要輸入,預設是0
- 如果bias=False就不會使用bias
### 前向傳播(forward)
```python=
x = self.f1(x)#形狀必須匹配
```
### 查看權重、偏差
```python=
print(self.f1.weight)
print(self.f1.bias)
```
## 活化函數
### **F**
```python=
x = F.relu(x)
```
### nn
```python=
relu_layer = nn.ReLU()
x = relu_layer(x)
```
## softmax
使用批次優化時,dim=1
```python=
output = F.softmax(x, dim=1)#搭配CrossEntropyLoss使用
```
```python=
output = F.log_softmax(x, dim=1)#搭配NLLLoss使用
```
## dropout
```python=
drop = nn.Dropout(0.5)
x = drop(x)
```
---
# 優化器 optimizer
## Adam
```python=
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
正規化$\lambda$的值,內建是使用L2正規化
### amsgrad
改成True會用新的更新規則,有時候會有助於穩定優化過程
## 其他優化器
```python=
optim.SGD(model.parameters(), lr = 0.003)#能固定學習率
optim.RMSprop(model.parameters(), lr=0.01)
optim.Adagrad(model.parameters(), lr=0.01)
```
---
# 損失函數
```python=
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
均方誤差
---
# 模型訓練
```python=
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優化前,先做確認
```python=
torch.cuda.is_available()
```
## 清空緩存區
GPU占用不會自己清理,如果能把不必要的清掉能減少許多GPU ram的浪費
```python=
torch.cuda.empty_cache()
```
## loss
### 取得loss
```python=
loss = loss_function(model(x), label)
```
### 取得loss數值
```python=
loss_num = loss.item()
```
## 反向傳播
```python=
optimizer.zero_grad()#重設gradient
loss.backward()#往回走取微分
optimizer.step()#對模型進行修改
```
backward這個函數會取得產生loss的過程中所參與到的所有tensor的微分,optimizer.step()則會利用過程中算出的微分去修改tensor
## 切換成訓練模式
啟用dropout
```python=
model.train()
```
---
# 模型測試
```python=
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梯度計算
關閉梯度計算能減少負擔(測試的時候不用算梯度),增加運算速度
```python=
with torch.no_grad():
#下面的部分會處於不計算梯度的模式
```
## 切換成評估模式
關閉dropout
```python=
model.eval() # 將模型切換為評估模式
```
---
# 模型存取
[參考](https://medium.com/chiachun0818/pytorch-%E5%84%B2%E5%AD%98-%E8%BC%89%E5%85%A5%E6%A8%A1%E5%9E%8B-3b32027e322f)
## 儲存整個模型
```python=
torch.save(model ,"model.pth" )
```
## 讀取已儲存模型
```python=
model = torch.load("model.pth")
```
## 儲存權重
```python=
torch.save(model.state_dict() ,"checkpoint.pth" )
```
### model.state_dict()
訓練過後,模型的各種參數
## 讀取已儲存權重
```python=
check_point = torch.load("checkpoint.pth")
model = NetWork()#要跟讀取的模型同個結構
model.load_state_dict(check_point)
```
---
# 使用現成模型做遷移學習
[參考](https://zhuanlan.zhihu.com/p/30315331)
## pretrain模型們
### [torchvision.models](https://pytorch.org/docs/0.3.0/torchvision/models.html#id5)
pytorch本身有提供許多圖像辨識的pre_train模型
### [Hugging Face](https://huggingface.co/models?sort=trending)
提供很多開源的大型模型
[教學](https://huggingface.co/learn/nlp-course/zh-TW/chapter1/3?fw=pt)
#### Llama
[教學連結](https://zhuanlan.zhihu.com/p/638035946)
## 模型載入
使用Densenet121這個已經訓練好的模型
```python=
model = models.densenet121(pretrained=True)
```
## 查看模型
```python=
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塞不下
```python=
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訓練
```python=
feature_extractor = torch.nn.Sequential(*list(pretrained_model.children())[:5])#提取pretrained_model.children前五層
features = feature_extractor(input_data)#提取特徵
"""
略過很多步驟,包含丟給GPU
"""
output = model(features)
````