# 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) ````