@
- 前言
- 1.tensor基礎操作
- 1.1 tensor的dtype型別
- 1.2 建立tensor(建議寫出引數名字)
- 1.2.1 空tensor(無用資料填充)
- API
- 示例
- 1.2.2 全一tensor
- 1.2.3 全零tensor
- 1.2.4 隨機值[0,1)的tensor
- 1.2.5 隨機值為整數且規定上下限的tensor
- API
- 示例
- 1.2.6 隨機值均值0方差1的tensor
- 1.2.7 從列表或numpy陣列建立tensor
- 1.2.1 空tensor(無用資料填充)
- 1.3 tensor常用成員函式和成員變數
- 1.3.1 轉為numpy陣列
- 1.3.2 獲得單元素tensor的值
item
- 1.3.3 獲取維度個數
- 1.3.4 獲取資料型別
- 1.3.5 獲取形狀
- 1.3.6 淺複製與深複製
- detach函式淺複製
- 深複製
- 1.3.7 形狀變換
- 轉置
cat
堆疊stack
堆疊view
改變形狀reshape
改變形狀
- 1.3.8 數學運算
- 1.3.9 使用指定裝置計算tensor
- 2.線性迴歸模型
- 2.1 自動求導機制
- 2.2 nn.Module的繼承(from torch import nn)
- 2.2.1 概述
- 2.2.2 例項
- 2.3 最佳化器類(from torch import optim)
- 2.3.1 概述
- 2.3.2 流程
- 2.3.3 動態學習率(import torch.optim.lr_scheduler)
- 2.4 代價函式(from torch import nn)
- 2.5 評估模型
- 2.6 線性迴歸模型的建立
- 2.6.1 流程
- 2.6.2 示例
- 3.資料集和資料載入器 (from torch.utils.data import Dataset,DataLoader)
- 3.1 Dataset類的繼承(from torch.utils.data import Dataset)
- 3.1.1 概述
- 3.1.2 例項
- 3.2 DataLoader類
- 3.2.1 API
- 3.2.2 示例
- 3.1 Dataset類的繼承(from torch.utils.data import Dataset)
- 4.影像處理:手寫數字識別
- 4.1 torchvision模組
- 4.1.1 transforms.ToTensor類(仿函式)
- 4.1.2 transforms.Normalize類(仿函式)
- 4.1.3 transforms.Compose類(仿函式)
- 4.1.4 示例
- 4.2 網路構建
- 4.2.1 啟用函式大全
- 4.2.2 演示程式碼(在gpu上)
- 4.1 torchvision模組
- 5.製作圖片資料集(以flower102為例)
- 5.1 建立資料集骨架
- 5.2 建立從名稱到數字標籤的對映
- 5.3 建立csv資料
- 5.4 完善成員函式和transform過程
- 5.5 DataLoader檢驗
- 6.遷移學習
- 6.1 現有模型的儲存和載入
- 6.1.1 儲存(torch.save函式)
- 把資料載入進網路
- 把資料載入進最佳化器
- 6.1.3 示例
- 6.1.1 儲存(torch.save函式)
- 6.2 使用預訓練的模型(以resnet50為例)
- 6.2.1 確定初始化引數
- 6.3 開始訓練
- 6.1 現有模型的儲存和載入
前言
本文只是對於pytorch深度學習框架的使用方法的介紹,如果涉及演算法中複雜的數學原理,本文將不予闡述,敬請讀者自行閱讀相關論文或者文獻。
1.tensor基礎操作
1.1 tensor的dtype型別
程式碼 | 含義 |
---|---|
float32 | 32位float |
float | floa |
float64 | 64位float |
double | double |
float16 | 16位float |
bfloat16 | 比float範圍大但精度低 |
int8 | 8位int |
int16 | 16位int |
short | short |
int32 | 32位int |
int | int |
int64 | 64位int |
long | long |
complex32 | 32位complex |
complex64 | 64位complex |
cfloat | complex float |
complex128 | 128位complex float |
cdouble | complex double |
1.2 建立tensor(建議寫出引數名字)
建立tensor時,有很多引數可以選擇,為節省篇幅,本文在列舉API時只列舉一次,不列舉過載的API。
1.2.1 空tensor(無用資料填充)
API
@overload
def empty(size: Sequence[Union[_int, SymInt]], *, memory_format: Optional[memory_format]=None, out: Optional[Tensor]=None, dtype: Optional[_dtype]=None, layout: Optional[_layout]=None, device: Optional[Union[_device, str, None]]=None, pin_memory: Optional[_bool]=False, requires_grad: Optional[_bool]=False) -> Tensor: ...
size:[行數,列數]
dtype(deepth type):資料型別
device:選擇運算裝置
requires_grad:是否進行自動求導,預設為False
示例
gpu=torch.device("cuda")
empty_tensor=torch.empty(size=[3,4],device=gpu,requires_grad=True)
print(empty_tensor)
輸出
tensor([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]], device='cuda:0', requires_grad=True)
1.2.2 全一tensor
@overload
def ones(size: _size, *, names: Optional[Sequence[Union[str, ellipsis, None]]], dtype: Optional[_dtype]=None, layout: Optional[_layout]=None, device: Optional[Union[_device, str, None]]=None, pin_memory: Optional[_bool]=False, requires_grad: Optional[_bool]=False) -> Tensor: ...
size:[行數,列數]
dtype(deepth type):資料型別
device:選擇運算裝置
requires_grad:是否進行自動求導,預設為False
1.2.3 全零tensor
@overload
def zeros(size: _size, *, names: Optional[Sequence[Union[str, ellipsis, None]]], dtype: Optional[_dtype]=None, layout: Optional[_layout]=None, device: Optional[Union[_device, str, None]]=None, pin_memory: Optional[_bool]=False, requires_grad: Optional[_bool]=False) -> Tensor: ...
1.2.4 隨機值[0,1)的tensor
@overload
def rand(size: _size, *, generator: Optional[Generator], names: Optional[Sequence[Union[str, ellipsis, None]]], dtype: Optional[_dtype]=None, layout: Optional[_layout]=None, device: Optional[Union[_device, str, None]]=None, pin_memory: Optional[_bool]=False, requires_grad: Optional[_bool]=False) -> Tensor: ...
1.2.5 隨機值為整數且規定上下限的tensor
API
@overload
def randint(low: _int, high: _int, size: _size, *, generator: Optional[Generator]=None, dtype: Optional[_dtype]=None, device: Device=None, requires_grad: _bool=False) -> Tensor: ...
示例
int_tensor=torch.randint(low=0,high=20,size=[5,6],device=gpu)
print(int_tensor)
輸出
tensor([[18, 0, 14, 7, 18, 14],
[17, 0, 2, 0, 0, 3],
[16, 17, 5, 15, 1, 14],
[ 7, 12, 8, 6, 4, 11],
[12, 4, 7, 5, 3, 3]], device='cuda:0')
1.2.6 隨機值均值0方差1的tensor
@overload
def randn(size: _size, *, generator: Optional[Generator], names: Optional[Sequence[Union[str, ellipsis, None]]], dtype: Optional[_dtype]=None, layout: Optional[_layout]=None, device: Optional[Union[_device, str, None]]=None, pin_memory: Optional[_bool]=False, requires_grad: Optional[_bool]=False) -> Tensor: ...
1.2.7 從列表或numpy陣列建立tensor
def tensor(data: Any, dtype: Optional[_dtype]=None, device: Device=None, requires_grad: _bool=False) -> Tensor: ...
- 如果使用
torch.from_numpy()
,返回的tensor與ndarray共享記憶體。
1.3 tensor常用成員函式和成員變數
1.3.1 轉為numpy陣列
def numpy(self,*args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__
pass
- 只有在CPU上運算的tensor才可以轉為numpy陣列
- tensor.requires_grad屬性為True的tensor不能轉為numpy陣列
1.3.2 獲得單元素tensor的值item
def item(self): # real signature unknown; restored from __doc__
...
- 如果tensor只有一個元素,就返回它的值
- 如果tensor有多個元素,丟擲ValueError
1.3.3 獲取維度個數
def dim(self): #real signature unknown; restored from __doc__
return 0
- 返回一個int表示維度個數
1.3.4 獲取資料型別
dtype = property(lambda self: object(), lambda self, v: None, lambda self: None) # default
1.3.5 獲取形狀
def size(self,dim=None): # real signature unknown; restored from __doc__
pass
- 使用
.shape
效果相同
1.3.6 淺複製與深複製
detach函式淺複製
假設有模型A和模型B,我們需要將A的輸出作為B的輸入,但訓練時我們只訓練模型B. 那麼可以這樣做:
input_B = output_A.detach()
它可以使兩個計算圖的梯度傳遞斷開,從而實現我們所需的功能。
返回一個新的tensor,新的tensor和原來的tensor共享資料記憶體,但不涉及梯度計算,即requires_grad=False。修改其中一個tensor的值,另一個也會改變,因為是共享同一塊記憶體。
sequence_tensor=torch.tensor(np.array([[[1,2,3],
[4,5,6]],
[[9,8,7],
[6,5,4]]]),
dtype=torch.float,device=gpu,)
sequence_tensor_shallowCp=sequence_tensor.detach()
sequence_tensor_shallowCp+=1
print(sequence_tensor)
print(sequence_tensor_shallowCp.requires_grad)
輸出
tensor([[[ 2., 3., 4.],
[ 5., 6., 7.]],
[[10., 9., 8.],
[ 7., 6., 5.]]], device='cuda:0')
False
深複製
- 法一:
.clone().detach()
- 法二:
.new_tensor()
1.3.7 形狀變換
轉置
向量或矩陣轉置
def t(self): # real signature unknown; restored from __doc__
"""
t() -> Tensor
See :func:`torch.t`
"""
return _te.Tensor(*(), **{})
- 返回值與原tensor共享記憶體!
指定兩個維度進行轉置:
def permute(self, dims: _size) -> Tensor:
r"""
permute(*dims) -> Tensor
See :func:`torch.permute`
"""
...
- 返回值與原tensor共享記憶體!
- 對矩陣來說,
.t()
等價於.permute(0, 1)
多維度同時轉置
def permute(self, *dims): # real signature unknown; restored from __doc__
"""
permute(*dims) -> Tensor
See :func:`torch.permute`
"""
return _te.Tensor(*(), **{})
- 把要轉置的維度放到對應位置上,比如對於三維tensor,x、y、z分別對應0、1、2,如果想要轉置x軸和z軸,則輸入2、1、0即可
- 返回值與原tensor共享記憶體!
cat
堆疊
cat
可以把兩個或多個tensor沿著指定的維度進行連線,連線後的tensor維度個數不變,指定維度上的大小改變,非指定維度上的大小不變。譬如,兩個shape=(3,)
行向量按dim=0
連線,變成1個shape=(6,)
的行向量;2個3階方陣按dim=0
連線,就變成1個(6, 3)
的矩陣。
cat
在使用時對輸入的這些tensor有要求:除了指定維度,其他維度的大小必須相同。譬如,1個shape=(1, 6)
的矩陣可以和1個shape=(2, 6)
的矩陣在dim=0
連線。
例子可以參考下面的定義和註釋。
def cat(tensors: Union[Tuple[Tensor, ...], List[Tensor]], dim: _int = 0, *, out: Optional[Tensor] = None) -> Tensor:
r"""
cat(tensors, dim=0, *, out=None) -> Tensor
Concatenates the given sequence of :attr:`seq` tensors in the given dimension.
All tensors must either have the same shape (except in the concatenating
dimension) or be a 1-D empty tensor with size ``(0,)``.
:func:`torch.cat` can be seen as an inverse operation for :func:`torch.split`
and :func:`torch.chunk`.
:func:`torch.cat` can be best understood via examples.
.. seealso::
:func:`torch.stack` concatenates the given sequence along a new dimension.
Args:
tensors (sequence of Tensors): any python sequence of tensors of the same type.
Non-empty tensors provided must have the same shape, except in the
cat dimension.
dim (int, optional): the dimension over which the tensors are concatenated
Keyword args:
out (Tensor, optional): the output tensor.
Example::
>>> x = torch.randn(2, 3)
>>> x
tensor([[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497]])
>>> torch.cat((x, x, x), 0)
tensor([[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497],
[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497],
[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497]])
>>> torch.cat((x, x, x), 1)
tensor([[ 0.6580, -1.0969, -0.4614, 0.6580, -1.0969, -0.4614, 0.6580,
-1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497, -0.1034, -0.5790, 0.1497, -0.1034,
-0.5790, 0.1497]])
"""
...
- 返回值與原tensor不共享記憶體!
stack
堆疊
stack
與cat
有很大的區別,stack
把兩個或多個tensor在dim
上建立一個全新的維度進行連線,非指定維度個數不變,建立的維度的大小取決於這次連線使用了多少個tensor。譬如,3個shape=(3,)
行向量按dim=0
連線,會變成一個shape=(3, 3)
的矩陣;兩個3階方陣按dim=-1
連線,就變成一個(3, 3, 2)
的tensor。
def stack(tensors: Union[Tuple[Tensor, ...], List[Tensor]], dim: _int = 0, *, out: Optional[Tensor] = None) -> Tensor:
r"""
stack(tensors, dim=0, *, out=None) -> Tensor
Concatenates a sequence of tensors along a new dimension.
All tensors need to be of the same size.
.. seealso::
:func:`torch.cat` concatenates the given sequence along an existing dimension.
Arguments:
tensors (sequence of Tensors): sequence of tensors to concatenate
dim (int, optional): dimension to insert. Has to be between 0 and the number
of dimensions of concatenated tensors (inclusive). Default: 0
Keyword args:
out (Tensor, optional): the output tensor.
Example::
>>> x = torch.randn(2, 3)
>>> x
tensor([[ 0.3367, 0.1288, 0.2345],
[ 0.2303, -1.1229, -0.1863]])
>>> x = torch.stack((x, x)) # same as torch.stack((x, x), dim=0)
>>> x
tensor([[[ 0.3367, 0.1288, 0.2345],
[ 0.2303, -1.1229, -0.1863]],
[[ 0.3367, 0.1288, 0.2345],
[ 0.2303, -1.1229, -0.1863]]])
>>> x.size()
torch.Size([2, 2, 3])
>>> x = torch.stack((x, x), dim=1)
tensor([[[ 0.3367, 0.1288, 0.2345],
[ 0.3367, 0.1288, 0.2345]],
[[ 0.2303, -1.1229, -0.1863],
[ 0.2303, -1.1229, -0.1863]]])
>>> x = torch.stack((x, x), dim=2)
tensor([[[ 0.3367, 0.3367],
[ 0.1288, 0.1288],
[ 0.2345, 0.2345]],
[[ 0.2303, 0.2303],
[-1.1229, -1.1229],
[-0.1863, -0.1863]]])
>>> x = torch.stack((x, x), dim=-1)
tensor([[[ 0.3367, 0.3367],
[ 0.1288, 0.1288],
[ 0.2345, 0.2345]],
[[ 0.2303, 0.2303],
[-1.1229, -1.1229],
[-0.1863, -0.1863]]])
"""
...
- 返回值與原tensor不共享記憶體!
view
改變形狀
view
先把資料變成一維陣列,然後再轉換成指定形狀。變換前後的元素個數並不會改變,所以變換前後的shape的乘積必須相等。詳細例子如下:
def view(self, *shape): # real signature unknown; restored from __doc__
"""
Example::
>>> x = torch.randn(4, 4)
>>> x.size()
torch.Size([4, 4])
>>> y = x.view(16)
>>> y.size()
torch.Size([16])
>>> z = x.view(-1, 8) # the size -1 is inferred from other dimensions
>>> z.size()
torch.Size([2, 8])
>>> a = torch.randn(1, 2, 3, 4)
>>> a.size()
torch.Size([1, 2, 3, 4])
>>> b = a.transpose(1, 2) # Swaps 2nd and 3rd dimension
>>> b.size()
torch.Size([1, 3, 2, 4])
>>> c = a.view(1, 3, 2, 4) # Does not change tensor layout in memory
>>> c.size()
torch.Size([1, 3, 2, 4])
>>> torch.equal(b, c)
False
"""
return _te.Tensor(*(), **{})
- 返回值與原tensor共享記憶體
reshape
改變形狀
reshape
與view
的區別如下:
view
只能改變連續(.contiguous())的tensor,如果已經對tensor進行了permute、transpose等操作,tensor在記憶體中會變得不連續,此時呼叫view
會報錯。且view
方法與原來的tensor共享記憶體。reshape
再呼叫時自動檢測原tensor是否連續,如果是,則等價於view
;如果不是,先呼叫.contiguous()
,再呼叫view
,此時返回值與原來tensor不共享記憶體。
def reshape(self, shape: Sequence[Union[_int, SymInt]]) -> Tensor:
...
1.3.8 數學運算
def mean(self, dim=None, keepdim=False, *args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__
...
def sum(self, dim=None, keepdim=False, dtype=None): # real signature unknown; restored from __doc__
...
def median(self, dim=None, keepdim=False): # real signature unknown; restored from __doc__
...
def mode(self, dim=None, keepdim=False): # real signature unknown; restored from __doc__
...
def dist(self, other, p=2): # real signature unknown; restored from __doc__
...
def std(self, dim, unbiased=True, keepdim=False): # real signature unknown; restored from __doc__
...
def var(self, dim, unbiased=True, keepdim=False): # real signature unknown; restored from __doc__
...
def cumsum(self, dim, dtype=None): # real signature unknown; restored from __doc__
...
def cumprod(self, dim, dtype=None): # real signature unknown; restored from __doc__
...
1.3.9 使用指定裝置計算tensor
to
可以把tensor轉移到指定裝置上。
def to(self, *args, **kwargs): # real signature unknown; restored from __doc__
"""
Example::
>>> tensor = torch.randn(2, 2) # Initially dtype=float32, device=cpu
>>> tensor.to(torch.float64)
tensor([[-0.5044, 0.0005],
[ 0.3310, -0.0584]], dtype=torch.float64)
>>> cuda0 = torch.device('cuda:0')
>>> tensor.to(cuda0)
tensor([[-0.5044, 0.0005],
[ 0.3310, -0.0584]], device='cuda:0')
>>> tensor.to(cuda0, dtype=torch.float64)
tensor([[-0.5044, 0.0005],
[ 0.3310, -0.0584]], dtype=torch.float64, device='cuda:0')
>>> other = torch.randn((), dtype=torch.float64, device=cuda0)
>>> tensor.to(other, non_blocking=True)
tensor([[-0.5044, 0.0005],
[ 0.3310, -0.0584]], dtype=torch.float64, device='cuda:0')
"""
return _te.Tensor(*(), **{})
2.線性迴歸模型
2.1 自動求導機制
- 在pytorch中,如果設定一個 tensor 的屬性 requires_grad 為 True,那麼它將會追蹤對於該張量的所有操作。當完成計算後可以透過呼叫 tensor.backward 函式,來自動計算所有的梯度。這個張量的所有梯度將會自動累加到 grad 屬性。
- 由於是累加,因此在進行線性迴歸模型的計算時,每輪都要用 tensor.zero_ 函式清空一次 grad 屬性
示例
sequence_tensor=torch.tensor(np.array([[[1,2,3],
[4,5,6]],
[[9,8,7],
[6,5,4]]]),
dtype=torch.float,device=gpu,requires_grad=True)
multi_tensor=sequence_tensor*3+1
multi_tensor_mean=multi_tensor.mean()
multi_tensor_mean.backward()
print(sequence_tensor.grad)
輸出
tensor([[[0.2500, 0.2500, 0.2500],
[0.2500, 0.2500, 0.2500]],
[[0.2500, 0.2500, 0.2500],
[0.2500, 0.2500, 0.2500]]], device='cuda:0')
2.2 nn.Module的繼承(from torch import nn)
2.2.1 概述
nn.Module是torch.nn提供的一個類,是pytorch中定義網路的必要的一個父類,在這個類中定義了很多有用的方法,使我們非常方便地計算。在我們進行網路的定義時,有兩個地方需要特別注意:
- 在定義成員變數時必須呼叫super函式,繼承父類__init__引數,即,在__init__中必須呼叫super(<the name of the variable>,self)函式
- 通常還會在__init__中定義網路的結構
- 必須定義forward函式,表示網路中前向傳播的過程
2.2.2 例項
class lr(nn.Module):
def __init__(self):
super(lr,self).__init__()
self.linear=nn.Linear(1,1)
def forward(self,x):
y_predict=self.linear(x)
return y_predict
其中,nn.Linear函式的引數為:輸入的特徵量,輸出的特徵量。
2.3 最佳化器類(from torch import optim)
2.3.1 概述
最佳化器(optimizer),用來操縱引數的梯度以更新引數,常見的方法有隨機梯度下降(stochastic gradient descent)(SGD)等。
- torch.optim.SGD(引數,float 學習率)
- torch.optim.Adam(引數,float 學習率)
2.3.2 流程
- 呼叫Module.parameters函式獲取模型引數,並定義學習率,進行例項化
- 用例項化物件調同 .zero_grad 函式,將引數重置為0
- 呼叫tensor.backward函式反向傳播,獲得梯度
- 用例項化物件呼叫 .step 函式更新引數
2.3.3 動態學習率(import torch.optim.lr_scheduler)
lr_scheduler允許模型在訓練的過程中動態更新學習率,且提供了許多種策略可供選擇,以下列舉一些常用的:
指數衰減:在訓練的過程中,學習率以設定的gamma引數進行指數的衰減。
class ExponentialLR(LRScheduler):
"""Decays the learning rate of each parameter group by gamma every epoch.
When last_epoch=-1, sets initial lr as lr.
Args:
optimizer (Optimizer): Wrapped optimizer.
gamma (float): Multiplicative factor of learning rate decay.
last_epoch (int): The index of last epoch. Default: -1.
verbose (bool): If ``True``, prints a message to stdout for
each update. Default: ``False``.
"""
def __init__(self, optimizer, gamma, last_epoch=-1, verbose=False):
self.gamma = gamma
super().__init__(optimizer, last_epoch, verbose)
固定步長衰減:在固定的訓練週期後,以指定的頻率進行衰減。
class StepLR(LRScheduler):
"""Decays the learning rate of each parameter group by gamma every
step_size epochs. Notice that such decay can happen simultaneously with
other changes to the learning rate from outside this scheduler. When
last_epoch=-1, sets initial lr as lr.
Args:
optimizer (Optimizer): Wrapped optimizer.
step_size (int): Period of learning rate decay.
gamma (float): Multiplicative factor of learning rate decay.
Default: 0.1.
last_epoch (int): The index of last epoch. Default: -1.
verbose (bool): If ``True``, prints a message to stdout for
each update. Default: ``False``.
Example:
>>> # xdoctest: +SKIP
>>> # Assuming optimizer uses lr = 0.05 for all groups
>>> # lr = 0.05 if epoch < 30
>>> # lr = 0.005 if 30 <= epoch < 60
>>> # lr = 0.0005 if 60 <= epoch < 90
>>> # ...
>>> scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
>>> for epoch in range(100):
>>> train(...)
>>> validate(...)
>>> scheduler.step()
"""
def __init__(self, optimizer, step_size, gamma=0.1, last_epoch=-1, verbose=False):
self.step_size = step_size
self.gamma = gamma
super().__init__(optimizer, last_epoch, verbose)
- 用法:建立scheduler的時候繫結optimizer物件,然後在呼叫
optimizer.step()
後面跟著scheduler.step()
即可。
2.4 代價函式(from torch import nn)
在torch.nn中已經定義好了很多代價函式,只需要呼叫它們並且傳入真實值、預測值,就可以返回結果,例如:
- 均方誤差:nn.MSELoss()
- 交叉熵誤差:nn.CrossEntropyLoss()
當然,也可以自己定義loss的計算過程。
2.5 評估模型
- Module.eval()表示設定模型為評估模式,即預測模式
- Module.train(mdoe=True)表示設定模型為訓練模式
2.6 線性迴歸模型的建立
2.6.1 流程
- 定義網路,注意:實現super函式和forward函式
- 準備資料
- 例項化網路、代價函式、最佳化器
- 進行迴圈,呼叫Module.forward函式前向傳播,呼叫代價函式進行計算,呼叫最佳化器類進行引數更新
- 使用pyplot進行模型評估
2.6.2 示例
if __name__=="__main__":
import torch
import numpy as np
from torch import nn
from torch import optim
from matplotlib import pyplot
gpu=torch.device("cuda")
cpu="cpu"
#定義網路
class lr(nn.Module):
def __init__(self):
#繼承成員變數
super(lr,self).__init__()
self.linear=nn.Linear(1,1)
#定義前向傳播函式
def forward(self,x):
y_predict=self.linear(x)
return y_predict
#準備資料
x_train=torch.rand([200,1],device=gpu)
y_train=torch.matmul(x_train,torch.tensor([[3]],dtype=torch.float32,requires_grad=True,device=gpu))+8
#例項化
model_lr=lr().to(gpu)
optimizer=optim.SGD(model_lr.parameters(),0.02)
cost_fn=nn.MSELoss()
#開始計算
for i in range(1000):
y_predict=model_lr.forward(x_train)
cost=cost_fn(y_predict,y_train)
optimizer.zero_grad()
cost.backward(retain_graph=True)
optimizer.step()
if i%20==0:
print(cost.item())
print(list(model_lr.parameters()))
#進行預測與評估
model_lr.eval()
y_predict_numpy=model_lr.forward(x_train).to(cpu).detach().numpy()
x_train_numpy=x_train.to(cpu).detach().numpy()
y_train_numpy=y_train.to(cpu).detach().numpy()
pyplot.scatter(x_train_numpy,y_predict_numpy,c="r")
pyplot.plot(x_train_numpy,y_train_numpy)
pyplot.show()
輸出
4.7310328227467835e-05
[Parameter containing:
tensor([[3.0237]], device='cuda:0', requires_grad=True), Parameter containing:
tensor([7.9876], device='cuda:0', requires_grad=True)]
繪製圖
3.資料集和資料載入器 (from torch.utils.data import Dataset,DataLoader)
3.1 Dataset類的繼承(from torch.utils.data import Dataset)
3.1.1 概述
在pytorch中提供了資料集的父類torch.utils.data.Dataset,繼承這個父類,我們可以非常快速地實現對資料的載入,與繼承nn.Module類一樣,我們同樣必須定義一些必要的成員函式
- __getitem__(self,index),用來進行索引,可以用 [ ]
- __len__(self),用來獲取元素個數
3.1.2 例項
SMSData_path="D:\Desktop\PycharmProjects\exercise\SMSSpamCollection"
#資料來源:http://archive.ics.uci.edu/ml/machine-learning-databases/00228/
class SMSData(Dataset):
def __init__(self):
self.data=open(SMSData_path,"r",encoding="utf-8").readlines()
def __getitem__(self, index):
current_line=self.data[index].strip()
label=current_line[:4].strip()
content=current_line[4:].strip()
return [label,content]
def __len__(self):
return len(self.data)
SMSex=SMSData()
print(SMSex.__getitem__(5))
print(SMSex.__len__())
輸出
['spam', "FreeMsg Hey there darling it's been 3 week's now and no word back! I'd like some fun you up for it still? Tb ok! XxX std chgs to send, £1.50 to rcv"]
5574
3.2 DataLoader類
3.2.1 API
class DataLoader(Generic[T_co]):
def __init__(self, dataset: Dataset[T_co], batch_size: Optional[int] = 1,
shuffle: Optional[bool] = None, sampler: Union[Sampler, Iterable, None] = None,
batch_sampler: Union[Sampler[Sequence], Iterable[Sequence], None] = None,
num_workers: int = 0, collate_fn: Optional[_collate_fn_t] = None,
pin_memory: bool = False, drop_last: bool = False,
timeout: float = 0, worker_init_fn: Optional[_worker_init_fn_t] = None,
multiprocessing_context=None, generator=None,
*, prefetch_factor: int = 2,
persistent_workers: bool = False,
pin_memory_device: str = ""):
#只列出參數列,以下詳細內容不再列出
dataset:以Dataset類為父類的自定義類的例項化物件
batch_size:批處理的個數
shuffle:bool型別,若為True則表示提前打亂資料
num_workers:載入資料時用到的執行緒數
drop_last :bool型別,若為True:這個是對最後的未完成的batch來說的,比如你的batch_size設定為64,而一個訓練集只有100個樣本,那麼訓練的時候後面的36個就被扔掉了。如果為False(預設),那麼會繼續正常執行,只是最後的batch_size會小一點。
timeout:如果是正數,表明等待從worker程序中收集一個batch等待的時間,若超出設定的時間還沒有收集到,那就不收集這個內容了。這個numeric應總是大於等於0,預設為0
3.2.2 示例
import torch
from torch.utils.data import Dataset,DataLoader
import chardet
gpu = torch.device("cuda")
cpu="cpu"
try:
SMSData_path="SMSSpamCollection"
#獲取檔案編碼方式
with open(SMSData_path,"rb") as file:
file_format=chardet.detect(file.read())["encoding"]
class SMSData(Dataset):
def __init__(self):
self.data=open(SMSData_path,"r",encoding=file_format).readlines()
def __getitem__(self, index):
current_line=self.data[index].strip()
origin=current_line[:4].strip()
content=current_line[4:].strip()
return [origin,content]
def __len__(self):
return len(self.data)
SMSex=SMSData()
SMSData_loader=DataLoader(dataset=SMSex,batch_size=2,shuffle=False,num_workers=2)
if __name__=='__main__':#如果設定多執行緒,一定要加這句話,否則會報錯
for i in SMSData_loader:
print("遍歷一:",i)
break
for i in enumerate(SMSData_loader):
print("遍歷二:",i)
break
for batch_index,(label,content) in enumerate(SMSData_loader):
print("遍歷三:",batch_index,label,content)
break
except BaseException as error:
print(error)
輸出
遍歷一: [('ham', 'ham'), ('Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat...', 'Ok lar... Joking wif u oni...')]
遍歷二: (0, [('ham', 'ham'), ('Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat...', 'Ok lar... Joking wif u oni...')])
遍歷三: 0 ('ham', 'ham') ('Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat...', 'Ok lar... Joking wif u oni...')
- 可見,DataLoader是一個可遍歷物件,每輪中返回的資料以列表的方式儲存,且列表中每個元素都是一個元組,列表的長度等於Dataset.__getitem__返回的列表長度,元組的長度等於batch_size引數的大小
4.影像處理:手寫數字識別
4.1 torchvision模組
4.1.1 transforms.ToTensor類(仿函式)
class ToTensor:
def __init__(self) -> None:
_log_api_usage_once(self)
- 將原始的PILImage資料型別或者numpy.array資料型別化為tensor資料型別。
- 如果 PIL Image 屬於 (L, LA, P, I, F, RGB, YCbCr, RGBA, CMYK, 1)中的一種影像型別,或者 numpy.ndarray 格式資料型別是 np.uint8 ,則將 [0, 255] 的資料轉為 [0.0, 1.0] ,也就是說將所有資料除以 255 進行歸一化。
4.1.2 transforms.Normalize類(仿函式)
class Normalize(torch.nn.Module):
def __init__(self, mean, std, inplace=False):
super().__init__()
_log_api_usage_once(self)
self.mean = mean
self.std = std
self.inplace = inplace
mean:資料型別為元組,元組的長度取決於通道數
std:資料型別為元組,元組的長度取決於通道數
- 此函式可以將tensor進行標準化,使其在每個通道上都轉化為均值為mean,標準差為std的高斯分佈。
4.1.3 transforms.Compose類(仿函式)
class Compose:
def __init__(self, transforms):
if not torch.jit.is_scripting() and not torch.jit.is_tracing():
_log_api_usage_once(self)
self.transforms = transforms
transforms:資料型別為列表,列表中每個元素都是transforms模組中的一個類,如ToTensor和Normalize(隱式構造)。
- 此函式可以將許多transforms類結合起來同時使用。
4.1.4 示例
import torchvision
if __name__ == '__main__':
MNIST=torchvision.datasets.MNIST(root="./data",train=True,download=False,transform=None)
MNIST_normalize=torchvision.transforms.Compose([torchvision.transforms.ToTensor(),torchvision.transforms.Normalize((0),(1))])(MNIST[0][0])
print(MNIST_normalize)
4.2 網路構建
4.2.1 啟用函式大全
- 在pytorch中已經實現了上述很多的啟用函式,下面我們將使用ReLU啟用函式進行網路構建。
4.2.2 演示程式碼(在gpu上)
import torchvision
import torch
from torch.utils.data import DataLoader
from torch import nn
from torch import optim
from torch.nn import functional as Activate
from matplotlib import pyplot
# 定義所用網路
class ExNet(nn.Module):
def __init__(self):
# super函式呼叫
super(ExNet, self).__init__()
# 卷積層1
self.conv1 = nn.Conv2d(1, 15, 5)
'''
輸入通道數1,輸出通道數15,核的大小5,輸入必須為1,輸出可以自定義
'''
# 卷積層2
self.conv2 = nn.Conv2d(15, 30, 3)
'''
輸入通道數15,輸出通道數30,核的大小3,輸入必須與上層的輸出一致,輸出可以自定義
'''
# 全連線層1
self.fully_connected_1 = nn.Linear(30 * 10 * 10, 40)
'''
MNIST原始影像是1*28*28,輸入為batch_size*1*28*28,經過卷積層1後,變為batch_size*15*24*24
經過池化層後,變為batch_size*15*12*12
經過卷積層2後,變為batch_size*30*10*10
這個全連線層的第一層輸入個數就是這麼來的
'''
# 全連線層2
self.fully_connected_2 = nn.Linear(40, 10)
'''
輸入與上層保持一致
由於要鑑別十個數字,因此輸出層的神經元個數必須是10
'''
# 定義前向傳播
def forward(self, x):
in_size = x.size(0) # 在本例中in_size,也就是BATCH_SIZE的值。輸入的x可以看成是batch_size*1*28*28的張量。
# 卷積層1
out = self.conv1(x) # batch*1*28*28 -> batch*15*24*24
out = Activate.relu(out) # 呼叫ReLU啟用函式
# 池化層
out = Activate.max_pool2d(out, 2, 2) # batch*15*24*24 -> batch*15*12*12(2*2的池化層會減半)
# 卷積層2
out = self.conv2(out) # batch*15*12*12 -> batch*30*10*10
out = Activate.relu(out) # 呼叫ReLU啟用函式
# flatten處理
out = out.view(in_size, -1)
# 全連線層1
out = self.fully_connected_1(out)
out = Activate.relu(out)
# 全連線層2
out = self.fully_connected_2(out)
# 歸一化處理,以便進行交叉熵代價函式的運算
out = Activate.log_softmax(out, dim=1)
return out
# 開始訓練
def train(the_model, the_device, train_loader, the_optimizer, the_epoch):
# 模型相關設定
the_model=the_model.to(device=the_device)
the_model.train(mode=True)
# 用來繪製影像的變數
list_times = []
list_cost = []
# 每輪迴圈
for batch_idx, (data, target) in enumerate(train_loader):
# 轉移到指定裝置上計算
data = data.to(the_device);target = target.to(the_device)
# 最佳化器引數重置
the_optimizer.zero_grad()
# 向前計算
output = the_model.forward(data)
# 計算誤差
cost = Activate.nll_loss(output, target)
# 反向傳播
cost.backward()
# 引數更新
the_optimizer.step()
# 列印資訊
if batch_idx % 10 == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
the_epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), cost.item()))
print(batch_idx, cost.item())
list_times.append(batch_idx)
list_cost.append(cost.item())
# 繪製影像
pyplot.scatter(list_times, list_cost)
pyplot.savefig("costImage.jpg")
pyplot.show()
return
def test(the_model, the_device, the_test_loader):
# 設定訓練模式
the_model=the_model.to(device=the_device)
the_model.eval()
# 測試的結果集
acc_vector = []
cost_vector = []
#開始測試
with torch.no_grad():
for index, (data, target) in enumerate(the_test_loader):
# 轉移到指定裝置上計算
data = data.to(the_device);target = target.to(the_device)
# 向前計算
output = the_model.forward(data)
# 計算誤差
cost = Activate.nll_loss(output, target)
cost_vector.append(cost)
pred = output.max(dim=1)[-1] # output的尺寸是[batch_size,10],對每行取最大值,返回索引編號,即代表模型預測手寫數字的結果
cur_acc = pred.eq(target).float().mean() # 均值代表每組batch_size中查準率
acc_vector.append(cur_acc)
# 列印結果
print("平均查準率:{}".format(sum(acc_vector)/len(acc_vector)))
print("average cost:{}".format(sum(cost_vector)/len(cost_vector)))
return
if __name__ == '__main__':
gpu = torch.device("cuda")
cpu = "cpu"
# 準備資料
transAndNorm = torchvision.transforms.Compose([torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize((0), (1))])
MNISTData = torchvision.datasets.MNIST(root="./data", train=True, download=False, transform=transAndNorm)
MNISTtest = torchvision.datasets.MNIST(root="./data", train=False, download=False, transform=transAndNorm)
MNISTData_loader = DataLoader(dataset=MNISTData, batch_size=10, shuffle=True)
MNISTtest_loader = DataLoader(dataset=MNISTtest, batch_size=10, shuffle=True)
# 例項化網路和最佳化器
MNISTnet_Ex = ExNet()
MNIST_optimizer = optim.Adam(MNISTnet_Ex.parameters(), lr=0.001) # lr(learning rate)是學習率
for i in range(1,2):
train(the_model=MNISTnet_Ex, the_device=gpu, train_loader=MNISTData_loader, the_optimizer=MNIST_optimizer, the_epoch=i)
test(the_model=MNISTnet_Ex, the_device=gpu, the_test_loader=MNISTtest_loader)
輸出
平均查準率:0.9804015159606934
average cost:0.061943911015987396
散點圖
5.製作圖片資料集(以flower102為例)
在剛剛的MNIST手寫數字識別分類任務中,我們使用的資料集是pytorch官方內建的圖片資料集。現在,我們要從零開始,嘗試製作我們自己的資料集。
Oxford 102 Flower 是一個影像分類資料集,由 102 個花卉類別組成。被選為英國常見花卉的花卉。每個類別由 40 到 258 張影像組成。影像具有大尺度、姿勢和光線變化。此外,還有一些類別在類別內有很大的變化,還有幾個非常相似的類別。這裡是flower102資料集的下載地址。解壓後的檔案目錄如下:
5.1 建立資料集骨架
如第三章一樣建立即可,如下:
import torch
from torch.utils.data import Dataset
import os
gpu = torch.device("cuda")
cpu = "cpu"
class flower102(Dataset):
def __init__(self,root,resize,mode):
super(flower102,self).__init__()
pass
def __len__(self):
pass
def __getitem__(self, item):
pass
5.2 建立從名稱到數字標籤的對映
在訓練集中,這102種花的類別名稱如上圖所示(我這裡是經過重新命名的),我們定義名稱flower1
為數字標籤1
,這樣我們就建立了一個對映。接下來,稍微修改一下建構函式,就可以實現全部的對映。如下:
import csv
import glob
import random
import os
from PIL import Image
import torch
from torch.utils.data import Dataset, DataLoader
import torchvision.transforms as transforms
gpu = torch.device("cuda")
cpu = "cpu"
class flower102(Dataset):
def __init__(self, root, resize, mode):
super(flower102, self).__init__()
self.root = root
self.train_root = os.path.join(self.root, "train")
self.val_root = os.path.join(self.root, "valid")
self.test_root = os.path.join(self.root, "test")
self.resize = resize
self.mode = mode
self.mean = [0.485, 0.456, 0.406]
self.std = [0.229, 0.224, 0.225]
self.cat2label = {} # 建立一個空字典,用於儲存對映關係。
for name in sorted(os.listdir(os.path.join(self.train_root))): # 遍歷訓練集目錄下的檔案和資料夾,並按照名稱排序。
if not os.path.isdir(os.path.join(self.train_root, name)): # 如果遍歷到的是檔案而不是資料夾,則跳過該項繼續遍歷下一項。
continue
elif not (name in self.cat2label):
self.cat2label[name] = len(self.cat2label.keys()) # 將資料夾名稱與類別標籤對應,類別標籤為字典長度(每次迴圈增加1)。
print(self.cat2label) # 列印對映關係字典。
def __len__(self):
pass
def __getitem__(self, idx):
pass
# 建立資料集例項
db = flower102(r"D:\Desktop\Datasets\flower102\dataset", resize=224, mode="train")
結果如下:
{'flower1': 0, 'flower10': 1, 'flower100': 2, 'flower101': 3, 'flower102': 4, 'flower11': 5, 'flower12': 6, 'flower13': 7, 'flower14': 8, 'flower15': 9, 'flower16': 10, 'flower17': 11, 'flower18': 12, 'flower19': 13, 'flower2': 14, 'flower20': 15, 'flower21': 16, 'flower22': 17, 'flower23': 18, 'flower24': 19, 'flower25': 20, 'flower26': 21, 'flower27': 22, 'flower28': 23, 'flower29': 24, 'flower3': 25, 'flower30': 26, 'flower31': 27, 'flower32': 28, 'flower33': 29, 'flower34': 30, 'flower35': 31, 'flower36': 32, 'flower37': 33, 'flower38': 34, 'flower39': 35, 'flower4': 36, 'flower40': 37, 'flower41': 38, 'flower42': 39, 'flower43': 40, 'flower44': 41, 'flower45': 42, 'flower46': 43, 'flower47': 44, 'flower48': 45, 'flower49': 46, 'flower5': 47, 'flower50': 48, 'flower51': 49, 'flower52': 50, 'flower53': 51, 'flower54': 52, 'flower55': 53, 'flower56': 54, 'flower57': 55, 'flower58': 56, 'flower59': 57, 'flower6': 58, 'flower60': 59, 'flower61': 60, 'flower62': 61, 'flower63': 62, 'flower64': 63, 'flower65': 64, 'flower66': 65, 'flower67': 66, 'flower68': 67, 'flower69': 68, 'flower7': 69, 'flower70': 70, 'flower71': 71, 'flower72': 72, 'flower73': 73, 'flower74': 74, 'flower75': 75, 'flower76': 76, 'flower77': 77, 'flower78': 78, 'flower79': 79, 'flower8': 80, 'flower80': 81, 'flower81': 82, 'flower82': 83, 'flower83': 84, 'flower84': 85, 'flower85': 86, 'flower86': 87, 'flower87': 88, 'flower88': 89, 'flower89': 90, 'flower9': 91, 'flower90': 92, 'flower91': 93, 'flower92': 94, 'flower93': 95, 'flower94': 96, 'flower95': 97, 'flower96': 98, 'flower97': 99, 'flower98': 100, 'flower99': 101}
5.3 建立csv資料
在建立了從名稱到數字標籤的對映後,我們希望有一個csv檔案,裡面儲存了所有的圖片路徑及其數字標籤,接下來,我們將定義一個load_csv函式去完成這件事,如下:
import csv
import glob
import random
import os
from PIL import Image
import torch
from torch.utils.data import Dataset, DataLoader
import torchvision.transforms as transforms
gpu = torch.device("cuda")
cpu = "cpu"
class flower102(Dataset):
def __init__(self, root, resize, mode):
super(flower102, self).__init__()
self.root = root
self.train_root = os.path.join(self.root, "train")
self.val_root = os.path.join(self.root, "valid")
self.test_root = os.path.join(self.root, "test")
self.resize = resize
self.mode = mode
self.mean = [0.485, 0.456, 0.406]
self.std = [0.229, 0.224, 0.225]
self.cat2label = {} # 建立一個空字典,用於儲存對映關係。
for name in sorted(os.listdir(os.path.join(self.train_root))): # 遍歷訓練集目錄下的檔案和資料夾,並按照名稱排序。
if not os.path.isdir(os.path.join(self.train_root, name)): # 如果遍歷到的是檔案而不是資料夾,則跳過該項繼續遍歷下一項。
continue
elif not (name in self.cat2label):
self.cat2label[name] = len(self.cat2label.keys()) # 將資料夾名稱與類別標籤對應,類別標籤為字典長度(每次迴圈增加1)。
print(self.cat2label) # 列印對映關係字典。
if mode == "train":
self.images, self.labels = self.load_csv("images_train.csv")
elif mode == "valid":
self.images, self.labels = self.load_csv("images_valid.csv")
else:
raise Exception("invalid mode!", self.mode)
# 載入CSV檔案並返回影像路徑和標籤列表
def load_csv(self, filename):
# 如果CSV檔案不存在,則根據訓練集目錄和對映關係生成CSV檔案
if not os.path.exists(os.path.join(self.root, filename)):
images = []
for name in self.cat2label.keys():
images += glob.glob(os.path.join(self.root, self.mode, name, "*.png"))
images += glob.glob(os.path.join(self.root, self.mode, name, "*.jpg"))
images += glob.glob(os.path.join(self.root, self.mode, name, "*.jpeg"))
random.shuffle(images)
with open(os.path.join(self.root, filename), mode="w", newline="") as f:
writer = csv.writer(f)
for img in images:
label = self.cat2label[img.split(os.sep)[-2]]
writer.writerow([img, label])
print("written into csv file:", filename)
# 從CSV檔案中讀取影像路徑和標籤
images = []
labels = []
with open(os.path.join(self.root, filename)) as f:
reader = csv.reader(f)
for row in reader:
img, label = row
label = int(label)
images.append(img)
labels.append(label)
assert len(images) == len(labels)
return images, labels
# 反歸一化
def denormalize(self, x_hat):
pass
def __len__(self):
pass
def __getitem__(self, idx):
pass
# 建立資料集例項
db = flower102(r"D:\Desktop\Datasets\flower102\dataset", resize=224, mode="train")
然後,我們獲得了一個如下的csv檔案:
5.4 完善成員函式和transform過程
在完成了load_csv函式後,這個資料集基本製作完成,接下來只需要完善__len__函式和__getitem__函式,並定義transform過程即可。
import csv
import glob
import random
import os
from PIL import Image
import torch
from torch.utils.data import Dataset, DataLoader
import torchvision.transforms as transforms
gpu = torch.device("cuda")
cpu = "cpu"
class flower102(Dataset):
def __init__(self, root, resize, mode):
super(flower102, self).__init__()
self.root = root
self.train_root = os.path.join(self.root, "train")
self.val_root = os.path.join(self.root, "valid")
self.test_root = os.path.join(self.root, "test")
self.resize = resize
self.mode = mode
self.mean = [0.485, 0.456, 0.406]
self.std = [0.229, 0.224, 0.225]
self.cat2label = {} # 建立一個空字典,用於儲存對映關係。
for name in sorted(os.listdir(os.path.join(self.train_root))): # 遍歷訓練集目錄下的檔案和資料夾,並按照名稱排序。
if not os.path.isdir(os.path.join(self.train_root, name)): # 如果遍歷到的是檔案而不是資料夾,則跳過該項繼續遍歷下一項。
continue
elif not (name in self.cat2label):
self.cat2label[name] = len(self.cat2label.keys()) # 將資料夾名稱與類別標籤對應,類別標籤為字典長度(每次迴圈增加1)。
print(self.cat2label) # 列印對映關係字典。
if mode == "train":
self.images, self.labels = self.load_csv("images_train.csv")
elif mode == "valid":
self.images, self.labels = self.load_csv("images_valid.csv")
else:
raise Exception("invalid mode!", self.mode)
# 載入CSV檔案並返回影像路徑和標籤列表
def load_csv(self, filename):
# 如果CSV檔案不存在,則根據訓練集目錄和對映關係生成CSV檔案
if not os.path.exists(os.path.join(self.root, filename)):
images = []
for name in self.cat2label.keys():
images += glob.glob(os.path.join(self.root, self.mode, name, "*.png"))
images += glob.glob(os.path.join(self.root, self.mode, name, "*.jpg"))
images += glob.glob(os.path.join(self.root, self.mode, name, "*.jpeg"))
random.shuffle(images)
with open(os.path.join(self.root, filename), mode="w", newline="") as f:
writer = csv.writer(f)
for img in images:
label = self.cat2label[img.split(os.sep)[-2]]
writer.writerow([img, label])
print("written into csv file:", filename)
# 從CSV檔案中讀取影像路徑和標籤
images = []
labels = []
with open(os.path.join(self.root, filename)) as f:
reader = csv.reader(f)
for row in reader:
img, label = row
label = int(label)
images.append(img)
labels.append(label)
assert len(images) == len(labels)
return images, labels
# 反歸一化
def denormalize(self, x_hat):
# x_hat = (x - mean) / std
# x = x_hat * std + mean
# x.size(): [c, h, w]
# mean.size(): [3] => [3, 1, 1]
mean = torch.tensor(self.mean).unsqueeze(1).unsqueeze(1)
std = torch.tensor(self.std).unsqueeze(1).unsqueeze(1)
x = x_hat * std + mean
return x
def __len__(self):
# 返回資料集中樣本的數量
return len(self.images)
def __getitem__(self, idx):
# 根據索引獲取影像和標籤
img, label = self.images[idx], self.labels[idx]
# 定義資料的預處理操作
tf = transforms.Compose([
lambda x: Image.open(x).convert("RGB"), # 以RGB格式開啟影像
transforms.Resize((int(self.resize * 1.25), int(self.resize * 1.25))), # 調整影像大小為resize的1.25倍
transforms.RandomRotation(15), # 隨機旋轉影像(最大旋轉角度為15度)
transforms.CenterCrop(self.resize), # 將影像中心裁剪為resize大小
transforms.ToTensor(), # 將影像轉換為Tensor型別
transforms.Normalize(mean=self.mean, std=self.std), # 歸一化影像
])
# 對影像進行預處理操作
img = tf(img)
label = torch.tensor(label)
return img, label
# 建立資料集例項
db = flower102(r"D:\Desktop\Datasets\flower102\dataset", resize=224, mode="train")
5.5 DataLoader檢驗
if __name__=='__main__' :
loader = DataLoader(dataset=db, shuffle=True,num_workers=1,batch_size=8)
import matplotlib.pyplot as plt
data,target=next(iter(db))
print(data.shape)
plt.imshow(transforms.ToPILImage()(db.denormalize(data)))
plt.show()
成功顯示:
6.遷移學習
6.1 現有模型的儲存和載入
6.1.1 儲存(torch.save函式)
我們要儲存的是:
- 例項化的網路的資料
- 例項化的最佳化器的資料
def save(
obj: object,
f: FILE_LIKE,
pickle_module: Any = pickle,
pickle_protocol: int = DEFAULT_PROTOCOL,
_use_new_zipfile_serialization: bool = True
) -> None:...
- 我們只需要把string型別的檔名作為引數輸入即可
把資料載入進網路
- Module.load_state_dict函式,我們只需要用torch.load函式的返回值作為引數即可
把資料載入進最佳化器
- optim.load_state_dict函式,我們只需要用torch.load函式的返回值作為引數即可
6.1.3 示例
torch.save(MNISTnet_Ex.state_dict(),"MNIST.pt")
torch.save(optimzer.state_dict(),"optimizer.pt")
MNISTnet_Ex.load_state_dict(torch.load("MNIST.pt"))
optimzer.load_state_dict(torch.load("optimizer.pt"))
6.2 使用預訓練的模型(以resnet50為例)
pytoch官方提供了不少與訓練的模型可供使用,如下:
model |
---|
AlexNet |
ConvNeXt |
DenseNet |
EfficientNet |
EfficientNetV2 |
GoogLeNet |
Inception V3 |
MaxVit |
MNASNet |
MobileNet V2 |
MobileNet V3 |
RegNet |
ResNet |
ResNeXt |
ShuffleNet V2 |
SqueezeNet |
SwinTransformer |
VGG |
VisionTransformer |
Wide ResNet |
關於這些模型的詳細用途,可以自行前往pytorch官網查閱相關資料,具體原理本文不再涉及。
6.2.1 確定初始化引數
在使用預訓練模型的過程中,最重要的一步是,確定這個預訓練模型中哪些引數是需要訓練的,哪些引數是不需要訓練的,哪些引數是要修改的。
首先,檢視一下resnet50的網路結構:
import torchvision.models as models
print(models.resnet50(pretrained=True))
Resnet(
...
(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
(fc): Linear(in_features=2048, out_features=1000, bias=True)
)
看到最後一層是一個1000分類的全連線層,而我們第五章製作的資料集裡,只需要102分類,因此,我們選擇只修改最後一層的引數並訓練。如下所示:
import torchvision.models as models
import torch.nn as nn
def set_parameter_requires_grad(model,need_train):
if not need_train:
for para in model.parameters():
para.requires_grad = False
return
def initalize_resnet50(num_classes,need_train=False,pretrained=True):
trained_model=models.resnet50(pretrained=pretrained)
input_size=224
set_parameter_requires_grad(trained_model, need_train)
trained_model.fc = nn.Sequential(
nn.Linear(trained_model.fc.in_features, num_classes),
nn.LogSoftmax(dim=1),
)
# trained_model.fc = nn.Sequential(
# nn.Linear(trained_model.fc.in_features, num_classes),
# nn.Flatten(),
# )
return trained_model,input_size
resnet50,input_size=initalize_resnet50(num_classes=102,need_train=False,pretrained=True)
6.3 開始訓練
訓練的流程和記錄如第四章所示即可,如下:
import copy # 匯入copy模組,用於深複製物件
import os.path # 匯入os.path模組,用於操作檔案路徑
import time # 匯入time模組,用於計時
def train(model, dataLoader, criterion, optimzer, num_epoch, device, filename):
"""
訓練函式
Args:
model: 模型物件
dataLoader: 資料載入器
criterion: 損失函式
optimzer: 最佳化器
num_epoch: 迭代次數
device: 計算裝置
filename: 儲存模型的檔名
Returns:
model: 訓練後的模型
train_acc_history: 訓練集準確率歷史
train_losses: 訓練集損失歷史
l_rs: 最佳化器學習率歷史
"""
since = time.time() # 獲取當前時間
best_epoch = {"epoch": -1,
"acc": 0
} # 儲存最佳模型的epoch和準確率
model.to(device) # 將模型移動到計算裝置上
train_acc_history = [] # 儲存訓練集準確率歷史
train_losses = [] # 儲存訓練集損失歷史
l_rs = [optimzer.param_groups[0]['lr']] # 儲存最佳化器學習率歷史
best_model_wts = copy.deepcopy(model.state_dict()) # 深複製當前模型的權重作為最佳模型權重
for epoch in range(num_epoch): # 迭代訓練
print("Epoch {}/{}".format(epoch, num_epoch - 1))
print('*' * 10)
running_loss = 0.0 # 初始化損失總和
running_correct = 0.0 # 初始化正確預測的樣本數總和
for data, target in dataLoader: # 遍歷資料載入器中的每個批次
data = data.to(device) # 將輸入資料移動到計算裝置上
target = target.to(device) # 將目標資料移動到計算裝置上
optimzer.zero_grad() # 清零梯度
output = model.forward(data) # 前向傳播
loss = criterion(output, target) # 計算損失
pred = output.argmax(dim=1) # 獲取預測結果
loss.backward() # 反向傳播
optimzer.step() # 更新引數
running_loss += loss.item() * data.size(0) # 累加損失
running_correct += torch.eq(pred, target).sum().float().item() # 累加正確預測的樣本數
epoch_loss = running_loss / len(dataLoader.dataset) # 計算平均損失
epoch_acc = running_correct / len(dataLoader.dataset) # 計算準確率
time_elapsed = time.time() - since # 計算訓練時間
print("Time elapsed {:.0f}m {:.0f}s".format(time_elapsed // 60, time_elapsed % 60))
print("Loss: {:4f} Acc:{:.4f}".format(epoch_loss, epoch_acc))
train_acc_history.append(epoch_acc) # 將準確率新增到歷史列表中
train_losses.append(epoch_loss) # 將損失新增到歷史列表中
if (epoch_acc > best_epoch["acc"]): # 更新最佳模型資訊
best_epoch = {
"epoch": epoch,
"acc": epoch_acc
}
best_model_wts = copy.deepcopy(model.state_dict()) # 深複製當前模型權重作為最佳模型權重
state = {
"state_dict": model.state_dict(),
"best_acc": best_epoch["acc"],
"optimzer": optimzer.state_dict(),
}
torch.save(state, filename) # 儲存最佳模型的狀態字典到檔案
print("Optimzer learning rate : {:.7f}".format(optimzer.param_groups[0]['lr'])) # 列印當前最佳化器學習率
l_rs.append(optimzer.param_groups[0]['lr']) # 將當前最佳化器學習率新增到歷史列表中
print()
time_elapsed = time.time() - since # 計算總訓練時間
print("Training complete in {:.0f}m {:.0f}s".format(time_elapsed // 60, time_elapsed % 60))
print("Best epoch:", best_epoch)
model.load_state_dict(best_model_wts) # 載入最佳模型權重
return model, train_acc_history, train_losses, l_rs
if __name__ == "__main__":
import torch
import Net
import torch.nn as nn
import torch.optim as optim
optimzer = optim.Adam(params=Net.resnet50.parameters(), lr=1e-2) # 建立Adam最佳化器
sche = optim.lr_scheduler.StepLR(optimizer=optimzer, step_size=10, gamma=0.5) # 建立學習率排程器
criterion = nn.NLLLoss() # 建立負對數似然損失函式
#criterion=nn.CrossEntropyLoss()
import flower102
from torch.utils.data import DataLoader
db = flower102.flower102(r"D:\Desktop\Datasets\flower102\dataset", resize=Net.input_size, mode="train") # 建立資料集物件
loader = DataLoader(dataset=db, shuffle=True, num_workers=1, batch_size=5) # 建立資料載入器
model = Net.resnet50 # 建立模型物件
filename = "checkpoint.pth" # 模型儲存檔名
if os.path.exists(filename): # 如果存在模型檔案
checkpoint = torch.load(filename) # 載入模型狀態字典
model.load_state_dict(checkpoint["state_dict"]) # 載入模型權重
model, train_acc_history, train_loss, LRS = train(model=model, dataLoader=loader, criterion=criterion,
optimzer=optimzer, num_epoch=5,
device=torch.device("cuda"), filename=filename)
下面是我訓練5輪的結果:
Epoch0/4
**********
Time elapsed 0m 37s
Loss: 11.229704 Acc:0.3515
Optimzer learning rate : 0.0100000
Epoch1/4
**********
Time elapsed 1m 12s
Loss: 8.165128 Acc:0.5697
Optimzer learning rate : 0.0100000
Epoch2/4
**********
Time elapsed 2m 4s
Loss: 7.410833 Acc:0.6363
Optimzer learning rate : 0.0100000
Epoch3/4
**********
Time elapsed 2m 60s
Loss: 6.991850 Acc:0.6822
Optimzer learning rate : 0.0100000
Epoch4/4
**********
Time elapsed 3m 44s
Loss: 6.482804 Acc:0.7128
Optimzer learning rate : 0.0100000
Training complete in 3m 44s
Best epoch: {'epoch': 4, 'acc': 0.7127594627594628}
本文由部落格一文多發平臺 OpenWrite 釋出!