Pytorch 基礎操作
主要是在讀深度學習入門之PyTorch這本書記的筆記。強烈推薦這本書
1. 常用類numpy操作
torch.Tensor(numpy_tensor)
torch.from_numpy(numpy_tensor)
GPU上的Tensor不能直接轉換為Numpy ndarry,要用.cpu()將其轉換到CPU
# 第一種方式是定義 cuda 數據類型
dtype = torch.cuda.FloatTensor # 定義默認 GPU 的 數據類型
gpu_tensor = torch.randn(10, 20).type(dtype)
# 第二種方式更簡單,推薦使用
gpu_tensor = torch.randn(10, 20).cuda(0) # 將 tensor 放到第一個 GPU 上
gpu_tensor = torch.randn(10, 20).cuda(1) # 將 tensor 放到第二個 GPU 上
# 將tensor放回CPU
cpu_tensor = gpu_tenor.cpu()
-
得到tensor大小
.size()
得到tensor數據類型
.type()
得到tensor的維度
.dim()
得到tnsor的所有元素個數
.numel() -
全1矩陣。數據類型是floatTensor
torch.ones(n, m)
轉化為整型數據/浮點型數據
.long().float()
返回一個張量,包含了從區間[0, 1)的均勻分布中抽取的一組隨機數
torch.rand(n, m)
返回張量,包含了從標准正態分布(均值為0,方差為1,即高斯白噪聲)中抽取的一組隨機數。張量的形狀由參數sizes定義torch.randn(n, m)返回一個1維張量,包含在區間start和end上均勻間隔的step個點
torch.linspace(start, end, steps=100, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)沿着維度dim取最大值
max_value, max_index = torch.max(x, dim)
沿着維度dim對x求和
torch.sum(x, dim)維度的變換:
# 在第n維增加 x.unsqueeze(n) # 減少一維 x.squeeze(n) # 將 tensor 中所有的一維全部都去掉 x.squeeze() # 重新排列維度 x.permute(a, b, c) #交換tensor中的兩個維度 x.transpose(a, b) # view的操作。(reshape進階版) x.view(-1, b) # -1表示任意大小 x.view(a, b) x.view_as(others) # 這個挺方便的 # 就是將x reshape成 others的形狀Tips:
pytorch中大多數的操作都支持 inplace 操作,也就是可以直接對 tensor 進行操作而不需要另外開辟內存空間,方式非常簡單,一般都是在操作的符號后面加_inplace參數的理解:
修改一個對象時:
inplace=True:不創建新的對象,直接對原始對象進行修改;
inplace=False:對數據進行修改,創建並返回新的對象承載其修改結果
2. Variable及自動求導機制
導入:
from torch.autograd import Variable
將Tensor變成Variable
x = Variable(x_tensor, requires_grad=True)
每個 Variabel都有三個屬性,Variable 中的 tensor本身.data,對應 tensor 的梯度.grad以及這個 Variable 是通過什么方式得到的.grad_fn
求梯度操作:
x_tensor = torch.randn(10, 5)
y_tensor = torch.randn(10, 5)
# 將 tensor 變成 Variable
x = Variable(x_tensor, requires_grad=True) # 默認 Variable 是不需要求梯度的,所以我們用這個方式申明需要對其進行求梯度
y = Variable(y_tensor, requires_grad=True)
z = torch.sum(x + y)
print(z.data)
print(z.grad_fn)
# 求 x 和 y 的梯度
z.backward()
print(x.grad)
print(y.grad)
通過調用 backward 我們可以進行一次自動求導,如果我們再調用一次 backward,會發現程序報錯,沒有辦法再做一次。這是因為 PyTorch 默認做完一次自動求導之后,計算圖就被丟棄了,所以兩次自動求導需要手動設置一個東西
x = Variable(torch.FloatTensor([3]), *requires_grad*=True)
y = x * 2 + x ** 2 + 3
y.backward(*retain_graph*=True)
設置 retain_graph 為 True 來保留計算圖
**Tips: **
PyTorch0.4中,.data 仍保留,但建議使用 .detach(), 區別在於 .data 返回和 x 的相同數據 tensor, 但不會加入到x的計算歷史里,且require s_grad = False, 這樣有些時候是不安全的, 因為 x.data 不能被 autograd 追蹤求微分 。 .detach() 返回相同數據的 tensor ,且 requires_grad=False ,但能通過 in-place 操作報告給 autograd 在進行反向傳播的時候.
舉例:
tensor.data
>>> a = torch.tensor([1,2,3.], requires_grad =True)
>>> out = a.sigmoid()
>>> c = out.data
>>> c.zero_()
tensor([ 0., 0., 0.])
>>> out # out的數值被c.zero_()修改
tensor([ 0., 0., 0.])
>>> out.sum().backward() # 反向傳播
>>> a.grad # 這個結果很嚴重的錯誤,因為out已經改變了
tensor([ 0., 0., 0.])
tensor.detach()
>>> a = torch.tensor([1,2,3.], requires_grad =True)
>>> out = a.sigmoid()
>>> c = out.detach()
>>> c.zero_()
tensor([ 0., 0., 0.])
>>> out # out的值被c.zero_()修改 !!
tensor([ 0., 0., 0.])
>>> out.sum().backward() # 需要原來out得值,但是已經被c.zero_()覆蓋了,結果報錯
RuntimeError: one of the variables needed for gradient
computation has been modified by an
此Tips從夢家的博文摘抄而來
3.構建網絡
pytorch中有很多內置數學函數
import torch.nn.functional as F
來導入,例如:
F.sigmoid()
torch.clamp(input, min, max, out=None) → Tensor
來限制輸入的上限和下限
手動更新參數其實挺麻煩的,可以用
torch.optim和數據類型nn.Parameter來操作
不過nn.Parameter 是默認要求梯度的
用nn.optim.SGD可以用梯度下降法來更新參數
例:
# 使用 torch.optim 更新參數
from torch import nn
w = nn.Parameter(torch.randn(2, 1))
b = nn.Parameter(torch.zeros(1))
def logistic_regression(x):
return F.sigmoid(torch.mm(x, w) + b)
optimizer = torch.optim.SGD([w, b], lr=1.)
# 進行 1000 次更新
import time
start = time.time()
for e in range(1000):
# 前向傳播
y_pred = logistic_regression(x_data)
loss = binary_loss(y_pred, y_data) # 計算 loss
# 反向傳播
optimizer.zero_grad() # 使用優化器將梯度歸 0
loss.backward()
optimizer.step() # 使用優化器來更新參數
# 計算正確率
mask = y_pred.ge(0.5).float()
acc = (mask == y_data).sum().data[0] / y_data.shape[0]
if (e + 1) % 200 == 0:
print('epoch: {}, Loss: {:.5f}, Acc: {:.5f}'.format(e+1, loss.data[0], acc))
during = time.time() - start
print()
print('During Time: {:.3f} s'.format(during))
有幾個關鍵操作:
optimizer.zero_grad()
歸零梯度。相當於w.grad.data.zero_()
optimizer.step()
用優化器更新參數。相當於https://blog.csdn.net/lens___/article/details/83960810
w.data = w.data - 0.1 * w.grad.data
再舉個栗子:
for e in range(100):
out = logistic_regression(Variable(x))
loss = criterion(out, Variable(y))
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (e + 1) % 20 == 0:
print('epoch: {}, loss: {}'.format(e+1, loss.data[0]))
上面的都是線性網絡的例子,下面舉一個神經網絡的例子。用到nn.Parameter
# 定義兩層神經網絡的參數
w1 = nn.Parameter(torch.randn(2, 4) * 0.01) # 隱藏層神經元個數 2
b1 = nn.Parameter(torch.zeros(4))
w2 = nn.Parameter(torch.randn(4, 1) * 0.01)
b2 = nn.Parameter(torch.zeros(1))
# 定義模型
def two_network(x):
x1 = torch.mm(x, w1) + b1
x1 = F.tanh(x1) # 使用 PyTorch 自帶的 tanh 激活函數
x2 = torch.mm(x1, w2) + b2
return x2
optimizer = torch.optim.SGD([w1, w2, b1, b2], 1.)
criterion = nn.BCEWithLogitsLoss()
# 我們訓練 10000 次
for e in range(10000):
out = two_network(Variable(x))
loss = criterion(out, Variable(y))
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (e + 1) % 1000 == 0:
print('epoch: {}, loss: {}'.format(e+1, loss.data[0]))
記錄一個決策邊界繪制代碼
def plot_decision_boundary(model, x, y):
# Set min and max values and give it some padding
x_min, x_max = x[:, 0].min() - 1, x[:, 0].max() + 1
y_min, y_max = x[:, 1].min() - 1, x[:, 1].max() + 1
h = 0.01
# Generate a grid of points with distance h between them
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
# Predict the function value for the whole grid
Z = model(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# Plot the contour and training examples
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
plt.ylabel('x2')
plt.xlabel('x1')
plt.scatter(x[:, 0], x[:, 1], c=y.reshape(-1), s=40, cmap=plt.cm.Spectral)
np.meshgrid的作用:
知乎專欄
contour和contourf都是畫三維等高線圖的,不同點在於contour() 是繪制輪廓線,contourf()會填充輪廓.。詳細說明:
CSDN
4. Sequential 和 Module
Sequential
# Sequential基本操作
seq_net = nn.Sequential(
nn.Linear(2, 4), # PyTorch 中的線性層,wx + b
nn.Tanh(),
nn.Linear(4, 1)
)
序列模塊可以通過索引訪問每一層
sq_net[0] 第一層
可以得到出第一層的權重
w0 = seq_net[0].weight
通過parameters可以取得模型的參數
param = seq_net.parameters()
模型的保存
1.
將參數和模型保存在一起
torch.save(seq_net, save_seq_net.pth')
參數一個是模型,一個是路徑
讀取保存的模型:
seq_net1 = torch.load('save_seq_net.pth')
2.
保存模型參數
torch.save(seq_net.state_dict(), save_seq_net_params.pth')
通過上面的方式,我們保存了模型的參數,如果要重新讀入模型的參數,首先我們需要重新定義一次模型,接着重新讀入參數
讀入參數操作:
seq_net2.load_state_dict(toech.load('save_seq_net_params.pth))
Module
Module模板:
class 網絡名字(nn.Module):
def __init__(self, 一些定義的參數):
super(網絡名字, self).__init__()
self.layer1 = nn.Linear(num_input, num_hidden)
self.layer2 = nn.Sequential(...)
...
定義需要用的網絡層
def forward(self, x): # 定義前向傳播
x1 = self.layer1(x)
x2 = self.layer2(x)
x = x1 + x2
...
return x
注意的是,Module 里面也可以使用 Sequential,同時 Module 非常靈活,具體體現在 forward 中,如何復雜的操作都能直觀的在 forward 里面執行。(想要親身體會請看一些論文源碼),里面可以用各種數據處理.
建議自己實現一個resnet網絡。可以很快熟悉基本操作,以后論文基本上都是用這個網絡
Module中,訪問模型的某一層可以直接通過名字來訪問:
l1 = mo_net.lay1(這是基本的操作吧)
訪問權重:l1.weight
定義完網絡,就可以for in 來訓練網絡了。
直接:
out = mo_net(Variable(x))
就可以得到output
保存模型一樣,還是用
.state_dict()
5. 數據的讀取操作(MNIST為例)
pytorch是內置了MNIST的
from torchvision.datasets import mnist
然后就可以通過內置函數來下載mnist數據集了
train_set = mnist.MNIST('./data', *train*=True, *download*=True)
test_set = mnist.MNIST('./data', *train*=False, *download*=True)
注:數據結構是這樣的:
a_data, a_label = train_set[i]
a_data指的是圖片矩陣,a_label則是對應的標簽
讀入的數據的PIL庫中的格式
最好轉換為numpy array格式來:
a_data = np.array(a_data, dtype='float32')
接下來就對a_data進行處理,由於要用神經元,所以得拉平,用reshape操作。當然還要正則化等數據處理。然后就可以正常進行了。用softmax函數作為評價函數即可。用BCELoss(交叉熵損失)。
注:用 _, pred = out.max(1)來記錄准確度。0維是batch維,共64, 1維則是通過網絡預測出來的評分結果維,有10個,我們取最大評分的pred即可。最后通過
.max(dim)方法中,若是2維函數,則是0代表每列的最大值,1代表每行的最大值
訓練的時候,要用DataLoader定義一個數據迭代器
注意!這里可以進行很多操作!比如說數據的處理,圖片的分割等等!
from torch.utils.data import DataLoader
# 使用 pytorch 自帶的 DataLoader 定義一個數據迭代器
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
test_data = DataLoader(test_set, batch_size=128, shuffle=False)
使用這樣的數據迭代器是非常有必要的,如果數據量太大,就無法一次將他們全部讀入內存,所以需要使用 python 迭代器,每次生成一個批次的數據
上面只是簡單舉一個例子。實際應用的時候最好單獨寫一個文件.方便修改
然后用
a, a_label = next(iter(train_data))
注意:
net.train()是進入訓練模式(train集)
net.eval()是進入預測模式(test集)
一般來說打印要打印:
epoches, Train_Loss, Train_Acc, Eval_Loss, Eval_Acc。方便比較
並且,在訓練和測試的過程中,要用losses,acces, eval_losses和 eval_acces集合來實時保存訓練或者測試出來的loss和acc。然后訓練完可以畫出圖來,方便對數據進行分析改進
6. 初始化參數操作
from torch.nn import init
Xavier初始化:
init,xavier_uniform(net[0].weight)
用Xavier初始化方法初始化網絡的第一層
還有很多初始化方法。可以查閱:
簡書
7. pytorch中實現一些優化器的方法
- SGD
# 手動實現
def sgd_update(parameters, lr):
for param in parameters:
param.data = param.data - lr * param.grad.data
調用內置函數:
optimzier = torch.optim.SGD(net.parameters(), learning_rate)
-
動量法
# 手動實現 def sgd_momentum(parameters, vs, lr, gamma): for param, v in zip(parameters, vs): v[:] = gamma * v + lr * param.grad.data param.data = param.data - v調用內置函數:
torch.optim.SGD(momentum=0.9)
僅僅在SGD函數中加一個動量變量就行了 -
Adagrad 自適應學習率優化算法
Adagrad 的核心想法就是,如果一個參數的梯度一直都非常大,那么其對應的學習率就變小一點,防止震盪,而一個參數的梯度一直都非常小,那么這個參數的學習率就變大一點,使得其能夠更快地更新
\(\frac{\eta}{s+\epsilon}\)
def sgd_adagrad(parameters, sqrs, lr):
eps = 1e-10
for param, sqr in zip(parameters, sqrs):
sqr[:] = sqr + param.grad.data ** 2
div = lr / tortorch.optim.Adagrad()ch.sqrt(sqr + eps) * param.grad.data
param.data = param.data - div
調用內置函數:
torch.optim.Adagrad(net.parameters(), lr=1e-2)
-
RMSProp
Adagrad 算法有一個問題,就是學習率分母上的變量 s 不斷被累加增大,最后會導致學習率除以一個比較大的數之后變得非常小,這不利於我們找到最后的最優解,所以 RMSProp 的提出就是為了解決這個問題。用移動平均來計算這個s
\[s_i = \alpha s_{i-1} + (1 - \alpha) \ g^2 \]
g為當前求出的參數梯度,\(\alpha\)為移動平均的系數
# 手動實現
def rmsprop(parameters, sqrs, lr, alpha):
eps = 1e-10
for param, sqr in zip(parameters, sqrs):
sqr[:] = alpha * sqr + (1 - alpha) * param.grad.data ** 2
div = lr / torch.sqrt(sqr + eps) * param.grad.data
param.data = param.data - div
用內置函數:
torch.optim.RMSprop()
-
Adadelta
Adadelta 跟 RMSProp 一樣,先使用移動平均來計算 s\[s = \rho s + (1 - \rho) g^2 \]這里 \(\rho\) 和 RMSProp 中的 \(\alpha\) 都是移動平均系數,g 是參數的梯度,然后我們會計算需要更新的參數的變化量
\[g' = \frac{\sqrt{\Delta \theta + \epsilon}}{\sqrt{s + \epsilon}} g \]\(\Delta \theta\) 初始為 0 張量,每一步做如下的指數加權移動平均更新
\[\Delta \theta = \rho \Delta \theta + (1 - \rho) g'^2 \]最后參數更新如下
\[\theta = \theta - g' \]
# 手動實現(反正我沒怎么看這部分)
def adadelta(parameters, sqrs, deltas, rho):
eps = 1e-6
for param, sqr, delta in zip(parameters, sqrs, deltas):
sqr[:] = rho * sqr + (1 - rho) * param.grad.data ** 2
cur_delta = torch.sqrt(delta + eps) / torch.sqrt(sqr + eps) * param.grad.data
delta[:] = rho * delta + (1 - rho) * cur_delta ** 2
param.data = param.data - cur_delta
調用函數:
torch.optim.Adadelta(net.parameters(), rho= 0.9)
-
Adam
現在一般都用adam# 手動實現 def adam(parameters, vs, sqrs, lr, t, beta1=0.9, beta2=0.999): eps = 1e-8 for param, v, sqr in zip(parameters, vs, sqrs): v[:] = beta1 * v + (1 - beta1) * param.grad.data sqr[:] = beta2 * sqr + (1 - beta2) * param.grad.data ** 2 v_hat = v / (1 - beta1 ** t) s_hat = sqr / (1 - beta2 ** t) param.data = param.data - lr * v_hat / torch.sqrt(s_hat + eps)調用函數:
torch.optim.Adam(net.parameters(), lr=1e-3)
8. 卷積神經網絡的構建
-
卷積在pytorch中有兩種方式
torch.nn.Conv2d()
torch.nn.functional.conv2d()
兩個本質是一樣的,輸入的要求也是一樣的
輸入的是一個torch.autograd.Variable()類型,大小為(batch, channel, H, W)使用
nn.Conv2d()相當於直接定義了一層卷積網絡結構,而使用torch.nn.functional.conv2d()相當於定義了一個卷積的操作,所以使用后者需要再額外去定義一個 weight,而且這個 weight 也必須是一個 Variable,而使用nn.Conv2d()則會幫我們默認定義一個隨機初始化的 weight,如果我們需要修改,那么取出其中的值對其修改,如果不想修改,那么可以直接使用這個默認初始化的值,非常方便實際使用中我們基本都使用 nn.Conv2d() 這種形式
-
池化操作也有兩種方法:
nn.MaxPool2d()
torch.nn.functional.max_pool2d() -
批標准化 Batch Normalization
首先肯定要對數據進行數據預處理。
現在一般是進行中心化和標准化。PCA和白化很少用了。
這里要注意,中心化和標准化的時候,使用的方差和均值統統都是用訓練集的數據。包括預處理測試集和驗證集數據的時候。批標准化,簡而言之,就是對於每一層網絡的輸出,對其做一個歸一化,使其服從標准的正態分布,這樣后一層網絡的輸入也是一個標准的正態分布,所以能夠比較好的進行訓練,加快收斂速度。
pytorch 當然也為我們內置了批標准化的函數,一維和二維分別是
torch.nn.BatchNorm1d() torch.nn.BatchNorm2d()
pytorch 不僅將 γγ 和 ββ 作為訓練的參數,也將 moving_mean 和 moving_var 也作為參數進行訓練
9. 數據增強操作
常用的數據增強方法如下:
1.對圖片進行一定比例縮放
2.對圖片進行隨機位置的截取
3.對圖片進行隨機的水平和豎直翻轉
4.對圖片進行隨機角度的旋轉
5.對圖片進行亮度、對比度和顏色的隨機變化
這些方法一般是用torchvision中的transforms來進行操作,還有PIL庫中的image,以及sys庫用來操作文件
import sys
from PIL import image
from torchvision import transforms as tfs
# 讀入一張圖片
im = Image.open('./cat.png')
比例縮放:
new_im = tfs.Resize((100, 200))(image)
隨機位置截取:
在 torchvision 中主要有下面兩種方式
一個是 torchvision.transforms.RandomCrop()
傳入的參數就是截取出的圖片的長和寬,對圖片在隨機位置進行截取;
第二個是 torchvision.transforms.CenterCrop()
同樣傳入截取初的圖片的大小作為參數,會在圖片的中心進行截取
隨機水平翻轉(鏡像)
torchvision.transforms.RandomHorizontalFlip()
隨機豎直翻轉: torchvision.transforms.RandomVerticalFlip()
隨機角度旋轉:
torchvision.transforms.RandomRotation(a)
a是角度
亮度,對比度和顏色的變化
torchvision.transforms.ColorJitter(brightness=1, contrast=1, hue=0.5, (R,G,B))
第一個參數就是亮度的比例,第二個是對比度,第三個是飽和度,第四個是顏色
brightness: 隨機從 0 ~ 2 之間亮度變化,1 表示原圖
contrast: 隨機從 0 ~ 2 之間對比度變化,1 表示原圖
hue: 隨機從 -0.5 ~ 0.5 之間對顏色變化
上面這么多圖像增強方法,其實是可以聯合起來用的。比如先做隨機翻轉,然后隨機截取,再做對比度增強等等,torchvision 里面有個非常方便的函數能夠將這些變化合起來,就是 torchvision.transforms.Compose()
# 舉例im_aug = tfs.Compose([
tfs.Resize(120),
tfs.RandomHorizontalFlip(),
tfs.RandomCrop(96),
tfs.ColorJitter(brightness=0.5, contrast=0.5, hue=0.5)
])
10 正則化操作與學習率衰減
regularzation現在很少用dropout, 而是用正則化來懲罰權重。
torch.optim.SGD(net.parameters(), lr=0.1, weight_decay=1e-4)
weight_decay參數就是權重衰減。 意思就是正則化。這是L2正則。
注意正則項的系數的大小非常重要,如果太大,會極大的抑制參數的更新,導致欠擬合,如果太小,那么正則項這個部分基本沒有貢獻,所以選擇一個合適的權重衰減系數非常重要.一般嘗試會用1e-4或者1e-3來進行。
在 pytorch 中學習率衰減非常方便,使用 torch.optim.lr_scheduler
或者用參數組的方式實現:
參數組:就是我們可以將模型的參數分成幾個組,每個組定義一個學習率。這個參數組是一個字典,里面有很多屬性,比如學習率,權重衰減等等
例:optimizer.param_groups[0]['lr']
optimizer.param_groups[0]['weight_decay']
def set_learning_rate(optimizer, lr):
for param_group in optimizer.param_groups:
param_group['lr'] = lr
...
# 訓練途中修改學習率
if epoch == 20:
set_learning_rate(optimizer, 0.01) # 20 次修改學習率為 0.01
11. 主流網絡實現(略)
數據集cifar10
torchvision.datasets.CIFAR10
此部分最好自己手動實現各個網絡
更能熟悉
-
VGGNet:

-
GoogleNet


GoogleNet的改進:
v1:最早的版本
v2:加入 batch normalization 加快訓練v3:對 inception 模塊做了調整
v4:基於 ResNet 加入了 殘差連接 -
ResNet




-
DenseNet

短路鏈接機制:

密集鏈接機制:

前向過程:

網絡結構:

