目錄
2. torch.utils.data.TensorDataset
3. torch.utils.data.DataLoader
4. torchvision.datasets.ImageFolder
一、Pytorch介紹
1.1 什麼是Pytorch
PyTorch 是 Torch7 團隊開發的,從它的名字就可以看出,其與 Torch 的不同之處在於 PyTorch 使用了 Python 作爲開發語言。
PyTorch 既可以看做加入了 GPU 支持的 numpy,同時也可以看 成一個擁有自動求導功能的強大的深度神經網絡,除了 Facebook之外,它還已經被 Twitter、CMU 和 Salesforce 等機構採用。
1.2 爲何要使用Pytorch
- 掌握一個框架並不能一勞永逸,現在深度學習並沒有誰擁 有絕對的壟斷地位。多學一個框架,以備不時之需。
- Tensorflow 與 Caffe 都是命令式的編程語言,而且是靜態的,首先必須構建一個神經網絡,然後一次又一次使用同樣的結構, 如果想要改變網絡的結構,就必須從頭開始。但是對於 PyTorch,通過一種反向自動求導的技術,可以讓你零延遲地任意改變神經網絡的行爲,儘管這項技術不是 Pytorch 獨有,但目前爲止它實現是最快的,能夠爲你任何瘋狂想法的實現獲得最高的速度和最佳的靈活性, 這也是 PyTorch 對比 Tensorflow 最大的優勢。
- Pytorch 的設計思路是線性、直觀且易於使用的。
- Pytorch 的代碼相對於 Tensorflow 而言,更加簡潔直觀,同 時對於 Tensorflow 高度工業化的很難看懂的底層代碼,Pytorch的源代碼就要友好得多,更容易看懂。
PyTorch的特點:
- 支持GPU
- 動態神經網絡
- Python優先
- 命令式體驗
- 輕鬆擴展
1.3 配置Pytorch深度學習環境
安裝GPU版本的Pytorch鏈接:
https://blog.csdn.net/weixin_40431584/article/details/105119633
二、Pytorch基礎
2.1 Tensor(張量)
PyTorch 裏面處理的最基本的操作對象就是 Tensor,Tensor 是張量的英文,表示的是一個多維的矩陣,比如零維就是一個點,一維就是向量,二維就是一般的矩陣,多維就相當於一個多維的數組,這和 numpy 是對應的,而且 PyTorch 的 Tensor 可以和 numpy的ndarray相互轉換,唯一不同的是PyTorch可以在GPU上運行,而numpy的ndarray只能在CPU上運行。
常用的不同數據類型的Tensor:
- 32位浮點型torch.FloatTensor
- 64位浮點型 torch.DoubleTensor
- 16位整型torch.ShortTensor
- 32位整型torch.IntTensor
- 64位整型torch.LongTensor
import torch
# 定義一個三行兩列給定元素的矩陣,並且顯示出矩陣的元素和大小
a = torch.Tensor([[2, 3], [4, 8], [7, 9]])
print('a is: {}'.format(a))
print('a size is: {}'.format(a.size()))
a is: tensor([[2., 3.],
[4., 8.],
[7., 9.]])
a size is: torch.Size([3, 2])
torch.Tensor默認的是torch.FloatTensor數據類型,也可以定義我們想要的數據類型,如下:
b = torch.LongTensor([[2, 3], [4, 8], [7, 9]])
print('b is: {}'.format(b))
b is: tensor([[2, 3],
[4, 8],
[7, 9]])
也可以創建一個全是0的空Tensor或者取一個正態分佈作爲隨機初始值:
c = torch.zeros((3, 2))
print('zero tensor: {}'.format(c))
d = torch.randn((3, 2))
print('normal random is:{}'.format(d))
zero tensor: tensor([[0., 0.],
[0., 0.],
[0., 0.]])
normal random is:tensor([[-0.7224, 0.6373],
[-0.0982, 0.1669],
[-1.4247, 0.5982]])
我們也可以像numpy一樣通過索引的方式取得其中的元素,同時也可以改變它的值,比如將a的第一行第二列改變爲100。
a[0, 1] = 100
print('changed a is: {}'.format(a))
changed a is: tensor([[ 2., 100.],
[ 4., 8.],
[ 7., 9.]])
除此之外,還可以在Tensor與numpy.ndarray之間相互轉換:
import numpy as np
numpy_b = b.numpy()
print('conver to numpy is \n {}'.format(numpy_b))
e = np.array([[2, 3], [4, 5]])
torch_e = torch.from_numpy(e)
print('from numpy to torch.Tensor is \n {}'.format(torch_e))
f_torch_e = torch_e.float()
print('cahnge data type to float tensor \n {}'.format(f_torch_e))
conver to numpy is
[[2 3]
[4 8]
[7 9]]
from numpy to torch.Tensor is
tensor([[2, 3],
[4, 5]], dtype=torch.int32)
cahnge data type to float tensor
tensor([[2., 3.],
[4., 5.]])
通過簡單的b.numpy(),就能將b轉換爲numpy數據類型,同時使用 torch.from_numpy()就能將numpy轉換爲tensor,如果需要更改tensor的數據類型,只需要在轉換後的tensor後面加上你需要的類型,比如想將a的類型轉換成float,只需a.float()就可以了。
如果你的電腦支持GPU加速,還可以將Tensor放到GPU上。首先通過torch.cuda.is_available()判斷一下是否支持GPU,如果想把tensor a放到GPU上,只需a.cuda()就能夠將tensor a放到GPU上了。
if torch.cuda.is_available():
a_cuda = a.cuda()
print(a_cuda)
tensor([[ 2., 100.],
[ 4., 8.],
[ 7., 9.]], device='cuda:0')
2.2 Variable(變量)
這個在numpy裏面就沒有了,是神經網絡計算圖裏特有的一個概念,就是Variable提供了自動求導的功能。
Variable和Tensor本質上沒有區別,不過Variable會被放入一個計算圖中,然後進行前向傳播,反向傳播,自動求導。首先Variable是在torch.autograd.Variable中,要將一個tensor變成Variable也非常簡單,比如想讓一個tensor a變成Variable,只需要 Variable(a)就可以了。
Variable有三個比較重要的組成屬性:
- data:通過data可以取出Variable裏面的tensor數值。
- grad_fn:grad_fn表示的是得到這個Variable的操作,比如通過加減還是乘除來得到的。
- grad:grad是這個Variabel的反向傳播梯度。
下面通過例子來具體說明一下:
from torch.autograd import Variable # torch中Variable模塊
# 創建變量
x = Variable(torch.Tensor([1]), requires_grad=True)
w = Variable(torch.Tensor([2]), requires_grad=True)
b = Variable(torch.Tensor([3]), requires_grad=True)
# 建立一個計算圖
y = w * x + b
# 計算梯度
y.backward()
print(x.grad)
print(w.grad)
print(b.grad)
tensor([2.])
tensor([1.])
tensor([1.])
構建Variable,要注意得傳入一個參數requires_grad=True,這個參數表示是否對這個變量求梯度,默認的是False,也就是不對這個變量求梯度,這裏我們希望得到這些變量的梯度,所以需要傳入這個參數。
從上面的代碼中,我們注意到了一行y.backward(),這一行代碼就是所謂的自動求導,這其實等價y.backward(torch.FloatTensor([1])),只不過對於標量求導裏面的參數就可以不寫了,自動求導不需要你再去明確地寫明哪個函數對哪個函數求導,直接通過這行代碼就能對所有的需要梯度的變量進行求導,得到它們的梯度,然後通過x.grad可以得到x的梯度。
上面是標量的求導,同時也可以做矩陣求導,比如:
x = torch.randn(3)
x = Variable(x, requires_grad=True)
y = x * 2
print(y)
y.backward(torch.FloatTensor([1, 0.1, 0.01]))
print(x.grad)
tensor([ 2.3863, 1.3822, -2.5512], grad_fn=<MulBackward0>)
tensor([2.0000, 0.2000, 0.0200])
相當於給出了一個三維向量去做運算,這時候得到的結果y就是一個向量,這裏對這個向量求導就不能直接寫成 y.backward(),這樣程序是會報錯的。這個時候需要傳入參數聲明,比如y.backward(torch.FloatTensor([1, 1, 1])),這樣得到的結果就是它們每個分量的梯度,或者可以傳入y.backward(torch.FloatTensor([1, 0.1, 0.01])),這樣得到的梯度就是它們原本的梯度分別乘上1,0.1和0.01。
2.3 Dataset(數據集)
在處理任何機器學習問題之前都需要數據讀取,並進行預處理。PyTorch提供了很多工具使得數據的讀取和預處理變得很容易。接下來介紹 Dataset,TensorDataset,DataLoader,ImageFolder的簡單用法。
1. torch.utils.data.Dataset
它是代表這一數據的抽象類。你可以自己定義你的數據類,繼承和重寫這個抽象類,非常簡單,只需要定義__len__和__getitem__這兩個函數:
from torch.utils.data import Dataset
import pandas as pd
class myDataset(Dataset):
def __init__(self, csv_file, txt_file, root_dir, other_file):
self.csv_data = pd.read_csv(csv_file)
with open(txt_file, 'r') as f:
data_list = f.readlines()
self.txt_data = data_list
self.root_dir = root_dir
def __len__(self):
return len(self.csv_data)
def __getitem__(self, idx):
data = (self.csv_data[idx], self.txt_data[idx])
return data
通過上面的方式,可以定義我們需要的數據類,可以通過迭代的方式來取得每一個數據,但是這樣很難實現取batch,shuffle或者是多線程去讀取數據。
2. torch.utils.data.TensorDataset
它繼承自Dataset,新版把之前的data_tensor和target_tensor去掉了,輸入變成了可變參數,也就是我們平常使用*args。
# 原版使用方法
train_dataset = Data.TensorDataset(data_tensor=x, target_tensor=y)
# 新版使用方法
train_dataset = Data.TensorDataset(x, y)
import torch
import torch.utils.data as Data
BATCH_SIZE = 5
x = torch.linspace(1, 10, 10)
y = torch.linspace(10, 1, 10)
torch_dataset = Data.TensorDataset(x, y)
loader = Data.DataLoader(
dataset=torch_dataset,
batch_size=BATCH_SIZE,
shuffle=True,
num_workers=0,
)
for epoch in range(3):
for step, (batch_x, batch_y) in enumerate(loader):
print('Epoch: ', epoch, '| Step: ', step, '| batch x: ', batch_x.numpy(), '| batch y: ', batch_y.numpy())
Epoch: 0 | Step: 0 | batch x: [6. 8. 9. 2. 5.] | batch y: [5. 3. 2. 9. 6.]
Epoch: 0 | Step: 1 | batch x: [10. 4. 7. 3. 1.] | batch y: [ 1. 7. 4. 8. 10.]
Epoch: 1 | Step: 0 | batch x: [ 5. 10. 2. 6. 7.] | batch y: [6. 1. 9. 5. 4.]
Epoch: 1 | Step: 1 | batch x: [9. 1. 8. 4. 3.] | batch y: [ 2. 10. 3. 7. 8.]
Epoch: 2 | Step: 0 | batch x: [ 5. 7. 1. 10. 9.] | batch y: [ 6. 4. 10. 1. 2.]
Epoch: 2 | Step: 1 | batch x: [6. 8. 2. 4. 3.] | batch y: [5. 3. 9. 7. 8.]
3. torch.utils.data.DataLoader
PyTorch中提供了一個簡單的辦法來做這個事情,通過torch.utils.data.DataLoader來定義一個新的迭代器,如下:
from torch.utils.data import DataLoader
dataiter = DataLoader(myDataset,batch_size=32,shuffle=True,collate_fn=defaulf_collate)
其中的參數都很清楚,只有collate_fn是標識如何取樣本的,我們可以定義自己的函數來準確地實現想要的功能,默認的函數在一般情況下都是可以使用的。
(需要注意的是,Dataset類只相當於一個打包工具,包含了數據的地址。真正把數據讀入內存的過程是由Dataloader進行批迭代輸入的時候進行的。)
4. torchvision.datasets.ImageFolder
另外在torchvison這個包中還有一個更高級的有關於計算機視覺的數據讀取類:ImageFolder,主要功能是處理圖片,且要求圖片是下面這種存放形式:
root/dog/xxx.png
root/dog/xxy.png
root/dog/xxz.png
root/cat/123.png
root/cat/asd/png
root/cat/zxc.png
之後這樣來調用這個類:
from torchvision.datasets import ImageFolder
dset = ImageFolder(root='root_path', transform=None, loader=default_loader)
其中 root 需要是根目錄,在這個目錄下有幾個文件夾,每個文件夾表示一個類別:transform 和 target_transform 是圖片增強,後面我們會詳細介紹;loader是圖片讀取的辦法,因爲我們讀取的是圖片的名字,然後通過 loader 將圖片轉換成我們需要的圖片類型進入神經網絡。
2.4 nn.Module(模組)
在PyTorch裏面編寫神經網絡,所有的層結構和損失函數都來自於torch.nn,所有的模型構建都是從這個基類nn.Module繼承的,於是有了下面這個模板。
import torch.nn as nn
class net_name(nn.Module):
def __init__(self, other_arguments):
super(net_name, self).__init__()
self.convl = nn.Conv2d(in_channels, out_channels, kernel_size)
# 其他網路層
def forward(self, x):
x = self.convl(x)
return x
這樣就建立了一個計算圖,並且這個結構可以複用多次,每次調用就相當於用該計算圖定義的相同參數做一次前向傳播,這得益於PyTorch的自動求導功能,所以我們不需要自己編寫反向傳播,而所有的網絡層都是由nn這個包得到的,比如線性層nn.Linear。
定義完模型之後,我們需要通過nn這個包來定義損失函數。常見的損失函數都已經定義在了nn中,比如均方誤差、多分類的交叉熵,以及二分類的交叉熵等等,調用這些已經定義好的損失函數也很簡單:
criterion = nn.CrossEntropyLoss()
loss = criterion(output, target)
criterion = nn.CrossEntropyLoss() loss = criterion(output, target)
2.5 torch.optim(優化)
在機器學習或者深度學習中,我們需要通過修改參數使得損失函數最小化(或最大化),優化算法就是一種調整模型參數更新的策略。優化算法分爲兩大類。
1. 一階優化算法
這種算法使用各個參數的梯度值來更新參數,最常用的一階優化算法是梯度下降。所謂的梯度就是導數的多變量表達式,函數的梯度形成了一個向量場,同時也是一個方向,這個方向上方向導數最大,且等於梯度。梯度下降的功能是通過尋找最小值,控制方差,更新模型參數,最終使模型收斂,網絡的參數更新公式是:
其中 是學習率,是函數的梯度。
2. 二階優化算法
二階優化算法使用了二階導數(也叫做Hessian方法)來最小化或最大化損失函數,主要基於牛頓法,但是由於二階導數的計算成本很高,所以這種方法並沒有廣泛使用。torch.optim是一個實現各種優化算法的包,大多數常見的算法都能夠直接通過這個包來調用,比如隨機梯度下降,以及添加動量的隨機梯度下降,自適應學習率等。在調用的時候將需要優化的參數傳入,這些參數都必須是Variable,然後傳入一些基本的設定,比如學習率和動量等。
optimizer = optim.SGD(model.parameters(), lr = 0.01, momentum=0.9)
這樣我們就將模型的參數作爲需要更新的參數傳入優化器,設定學習率是0.01,動量是0.9隨機梯度下降,在優化之前需要先將梯度歸零,即optimizer.zeros(),然後通過loss.backward()反向傳播,自動求導得到每個參數的梯度,最後只需要optimizer.step()就可以通過梯度作一步參數更新。
import torch
import torch.utils.data as Data
import torch.nn.functional as F
from torch.autograd import Variable
import matplotlib.pyplot as plt
torch.manual_seed(1)
LR = 0.01
BATCH_SIZE = 32
EPOCH = 12
# 模擬數據
x = torch.unsqueeze(torch.linspace(-1, 1, 1000), dim=1)
y = x.pow(2) + 0.1*torch.normal(torch.zeros(*x.size()))
# 繪製數據集
plt.scatter(x.numpy(), y.numpy())
plt.show()
2.6 模型的保存和加載
在PyTorch裏面使用torch.save來保存模型的結構和參數,有兩種保存方式:
- 保存整個模型的結構信息和參數信息,保存的對象是模型 model;
- 保存模型的參數,保存的對象是模型的狀態model.state_dict()。
可以這樣保存,save的第一個參數是保存對象,第二個參數是保存路徑及名稱:
torch.save(model, './model.pth')
torch.save(model.state_dict(), './model_state.pth')
加載模型有兩種方式對應於保存模型的方式:
- 加載完整的模型結構和參數信息,使用 load_model=torch.load('model.pth'),在網絡較大的時候加載的時間比較長,同時存儲空間也比較大;
- 加載模型參數信息,需要先導入模型的結構,然後通過 model.load_state_dic(torch.load('model_state.pth'))來導入。