[Quant 1.2] 一些Pytorch基礎(chǔ)
視頻鏈接:https://www.youtube.com/watch?v=c36lUUr864M
因?yàn)槲覜](méi)學(xué)過(guò)Pytorch,所以需要從基礎(chǔ)開(kāi)始了。
這一篇的示例要用三個(gè)package
import torch
import numpy as np
import matplotlib.pyplot as plt
1. 數(shù)據(jù)類(lèi)型tensor的建立
建立不同維度的0-tensor
一維長(zhǎng)度為3的0-tensor
torch.empty(3)
#tensor([1.1210e-44, -0.0000e+00, 0.0000e+00])
二維??的0-tensor
torch.empty(2,3)
# tensor([[9.8091e-45, 0.0000e+00, 0.0000e+00],
# ? ? ? ? [0.0000e+00, 0.0000e+00, 0.0000e+00]])
三維 ?的0-tensor
torch.empty(2,3,4)
#tensor([[[ 2.9147e-43, ?0.0000e+00, -1.1201e-19, ?4.5779e-41],
# ? ? ? ? ? [ 6.0009e+36, ?4.5779e-41, ?0.0000e+00, ?7.0065e-45],
# ? ? ? ? ? [ 0.0000e+00, ?0.0000e+00, ?0.0000e+00, ?0.0000e+00]],
# ? ? ? ? ?[[ 0.0000e+00, ?0.0000e+00, ?0.0000e+00, ?0.0000e+00],
# ? ? ? ? ? [ 0.0000e+00, ?0.0000e+00, ?0.0000e+00, ?0.0000e+00],
# ? ? ? ? ? [ 1.4013e-45, ?0.0000e+00, ?0.0000e+00, ?0.0000e+00]]])
建立不同維度的1-tensor
二維? 的1-tensor
torch.ones(2,2)
# tensor([[1., 1.],
# ? ? ? ? [1., 1.]])
建立不同維度的隨機(jī)tensor
torch.rand(2,2)
# tensor([[0.2246, 0.5603],
# ? ? ? ? [0.5463, 0.8566]])
建立不同維度的隨機(jī)整數(shù)tensor
建立一個(gè)三維? 的隨機(jī)整數(shù)矩陣,元素的范圍是?
?區(qū)間內(nèi)的整數(shù)
x = torch.randint(2,8,(3,3,3))
x
# tensor([[[5, 3, 3],
# ? ? ? ? ?[5, 7, 7],
# ? ? ? ? ?[2, 3, 4]],
# ? ? ? ? [[5, 3, 7],
# ? ? ? ? ?[4, 6, 5],
# ? ? ? ? ?[3, 4, 5]],
# ? ? ? ? [[7, 5, 7],
# ? ? ? ? ?[3, 6, 2],
# ? ? ? ? ?[2, 7, 7]]])
以list為argument建立tensor
利用list建立一個(gè)? 的tensor
my_ten = torch.tensor([[2.5,0.1],[1,2]])
my_ten
# tensor([[2.5000, 0.1000],
# ? ? ? ? [1.0000, 2.0000]])
my_ten.size()
# ?torch.Size([2, 2])
設(shè)定tensor中的數(shù)據(jù)類(lèi)型
建立一個(gè)二維? 的1-tensor,要求tensor里面的元素的類(lèi)型是浮點(diǎn)數(shù)float16
x = torch.ones(2,2,dtype=torch.float16)
# tensor([[1., 1.],
# ? ? ? ? [1., 1.]], dtype=torch.float16)
設(shè)定tensor是否可以用來(lái)求梯度
建立一個(gè)二維? 的隨機(jī)tensor,要求tensor可以用來(lái)求梯度
torch.rand(2,3,requires_grad = True)
# tensor([[0.9961, 0.2444, 0.6532],
# ? ? ? ? [0.5307, 0.6206, 0.5152]], requires_grad=True)
在后面,我們會(huì)把tensor放入某種函數(shù)。這個(gè)函數(shù)的輸入是一個(gè)多維的tensor,輸出是一個(gè)scaler。如果這個(gè)多維tensor有requires_grad = True,那么我們就可以用backward()求函數(shù)在此多維tensor上的梯度;如果這個(gè)多維tensor有requires_grad = False,那么在使用backward()求梯度的時(shí)候,interpreter就會(huì)報(bào)錯(cuò)。
2. 數(shù)據(jù)類(lèi)型tensor的一些操作
Tensor的相加與相減
x = torch.rand(2,3)
y = torch.rand(2,3)
x, y
# (tensor([[0.5501, 0.8308, 0.2830],
# ? ? ? ? ?[0.4184, 0.3558, 0.0589]]),
# ?tensor([[0.3422, 0.9984, 0.7679],
# ? ? ? ? ?[0.2108, 0.6127, 0.6060]]))
x + y
# tensor([[0.8923, 1.8292, 1.0509],
# ? ? ? ? [0.6291, 0.9684, 0.6648]])
x.add(y)
# tensor([[0.8923, 1.8292, 1.0509],
# ? ? ? ? [0.6291, 0.9684, 0.6648]])
x, y
# (tensor([[0.5501, 0.8308, 0.2830],
# ? ? ? ? ?[0.4184, 0.3558, 0.0589]]),
# ?tensor([[0.3422, 0.9984, 0.7679],
# ? ? ? ? ?[0.2108, 0.6127, 0.6060]]))
用 '+' 將tensor相加會(huì)創(chuàng)建新的tensor,原來(lái)的tensor不會(huì)改變。對(duì)應(yīng)的,我們知道 '-' 也有相似的作用。
這里面 '+' 和 '-' 可以用函數(shù) .add() 和 .sub() 來(lái)代替,用這兩個(gè)方法相加或者相減同樣創(chuàng)建新的tensor,并不會(huì)改變?cè)璽ensor x和y的值。
x = torch.randint(2,(2,3))
x
# tensor([[1, 0, 1],
# ? ? ? ? [0, 0, 1]])
x.add_(1)
# tensor([[2, 1, 2],
# ? ? ? ? [1, 1, 2]])
x.sub_(1)
# tensor([[1, 0, 1],
# ? ? ? ? [0, 0, 1]])
但是如果我們?cè)?.add()?和?.sub() 后面加了下劃線,那么 .add_()?和?.sub_() 就會(huì)改變其作用對(duì)象的值。在這個(gè)例子中,x的值被改變了兩次。?這種操作在pytorch中很常見(jiàn),很多method不加下劃線就會(huì)創(chuàng)建新的變量,而加下劃線的話就會(huì)在改變?cè)瓉?lái)的變量。
Tensor的切片slicing
x = torch.rand(5,3)
x
# tensor([[0.8021, 0.0619, 0.2424],
# ? ? ? ? [0.1589, 0.9536, 0.5429],
# ? ? ? ? [0.3194, 0.4105, 0.3977],
# ? ? ? ? [0.4445, 0.5245, 0.5164],
# ? ? ? ? [0.2404, 0.9453, 0.9572]])
x[1,:]
# tensor([0.1589, 0.9536, 0.5429])
tensor的切片和ndarray的切片方式一樣。在上面的例子中,我們要slice這個(gè)??tensor的第一行
Tensor的變形reshape
x = torch.rand(5,3)
x
# tensor([[0.8021, 0.0619, 0.2424],
# ? ? ? ? [0.1589, 0.9536, 0.5429],
# ? ? ? ? [0.3194, 0.4105, 0.3977],
# ? ? ? ? [0.4445, 0.5245, 0.5164],
# ? ? ? ? [0.2404, 0.9453, 0.9572]])
y = x.view(15)
y
# tensor([0.8021, 0.0619, 0.2424, 0.1589, 0.9536, 0.5429, 0.3194, 0.4105, 0.3977,
# ? ? ? ? 0.4445, 0.5245, 0.5164, 0.2404, 0.9453, 0.9572])
y = x.view(-1,5)
y
# tensor([[0.8021, 0.0619, 0.2424, 0.1589, 0.9536],
# ? ? ? ? [0.5429, 0.3194, 0.4105, 0.3977, 0.4445],
# ? ? ? ? [0.5245, 0.5164, 0.2404, 0.9453, 0.9572]])
通過(guò) .view() 函數(shù),我們可以將tensor展成我們想要的size。
y = x.view(15) 是將剛剛的??tensor展成
y = x.view(-1,5) 則是將??tensor展成?
,這個(gè)?在函數(shù)輸入中用-1代替,其具體數(shù)值會(huì)自行決定,例如這個(gè)statement就相當(dāng)于 y = x.view(3,5)
3. 數(shù)據(jù)類(lèi)型tensor的gradient
我們前面提到過(guò)了tensor里面可以?xún)?nèi)含一個(gè)叫requires_grad的argument。這個(gè)argument是bool類(lèi)型,它決定著是否可以對(duì)這個(gè)tensor求gradient。
例如我們可以在建立一個(gè)tensor變量的時(shí)候來(lái)決定它是否可以被求梯度
torch.randn(2,3,requires_grad=True)
# tensor([[ 2.1278, ?1.1417, ?0.6102],
# ? ? ? ? [-1.3501, ?0.5458, ?2.5938]], requires_grad=True)
建立一個(gè)二維??的隨機(jī)tensor,元素服從標(biāo)準(zhǔn)正態(tài)分布,且這個(gè)tensor可以求梯度。
除了在建立tensor的時(shí)候決定這個(gè)argument之外,我們還可以修改已建立的tensor的argument requires_grad。一共有三種方法:
x = torch.randn(3,requires_grad=True)
x
# tensor([-1.1794, ?1.0465, -1.3400], requires_grad=True)
1. .detach()
y = x.detach()
y
# tensor([-1.1794, ?1.0465, -1.3400])
通過(guò) x.detach() ,我們建立了一個(gè)全新的tensor,這個(gè)tensor是不可以求梯度的
2. .with torch.no_grad():
with torch.no_grad():
? ?y = x + 2
? ?print(y)
? ?
# tensor([0.8206, 3.0465, 0.6600])
在這個(gè)statemtent下面,我們可以忽略一個(gè)tensor本身是否可以求梯度的性質(zhì)來(lái)對(duì)它進(jìn)行一些操作。我們忽略了x可以求梯度的性質(zhì),把x的每一個(gè)元素加2,再把加了2之后的tensor賦值給一個(gè)新的變量y。所以我們最終輸出的tensor y是不能夠求梯度的。
3. .requires_grad_(False)
x.requires_grad_(False)
# tensor([-1.4181, ?0.3631, -0.7994])
回憶上面的 .add_() 和 .sub_(),以下劃線結(jié)尾的method會(huì)改變其作用的tensor本身。這里也是一樣,例子里面我們直接改變了x的性質(zhì),讓x不能夠被求梯度。
3. 求梯度:backward函數(shù)和.grad
我目前的感覺(jué)是,如果我們對(duì)一個(gè)tensor進(jìn)行各種操作(加減乘除,所有元素求和,求所有元素平均值等elementwise或者tensor-wise的操作),得到一個(gè)新的? 的tensor,再把這個(gè)tensor賦值給另一個(gè)變量的話,pytorch會(huì)記住我們對(duì)初始tensor進(jìn)行變換的過(guò)程。
簡(jiǎn)而言之,我們對(duì)tensor A操作,最終得到了?tensor B,pytorch會(huì)自動(dòng)建立由A到B的函數(shù)關(guān)系。
我們考慮一個(gè)二元函數(shù):
這個(gè)函數(shù)的梯度是
因此,當(dāng)的時(shí)候,梯度就是
如果我們嘗試用pytorch去求函數(shù) 在?
?處的梯度的話,等價(jià)的pytorch代碼是
my_tensor = torch.tensor([1,2],requires_grad = True,dtype = torch.float64)
my_res = torch.exp(my_ten[0]) + torch.log(my_ten[1])
my_res.backward()
print(my_tensor.grad)
print(my_res)
# tensor([2.7183, 0.5000], dtype=torch.float64)
# tensor(3.4114, dtype=torch.float64, grad_fn=<AddBackward0>)
第一行,建立一個(gè)tensor?,就是我們想要求梯度的位置,記住一定要要求requires_grad = True
第二行,建立tensor??(my_tensor)和目標(biāo)?tensor (my_res)之間的聯(lián)系。用自然語(yǔ)言敘述就是,將tensor的第0項(xiàng)的自然指數(shù)和tensor第一項(xiàng)的自然對(duì)數(shù)相加。
第三行,通過(guò)my_res.backward() ,我們求my_res在my_tensor上面的梯度。前兩行代碼對(duì)pytorch指示的my_tensor和my_res之間的聯(lián)系就是上面提到的?
第四行,我們通過(guò)my_tensor.grad得到函數(shù)在tensor?上面的梯度
值得注意的是,每當(dāng)我們像第二行那樣建立了一次自變量和因變量的聯(lián)系之后,我們只能對(duì)應(yīng)的求一次梯度。如果建立一個(gè)聯(lián)系卻求兩次梯度的話,intuitively相等的新梯度會(huì)覆蓋之前求出來(lái)的舊梯度,但是實(shí)際上pytorch會(huì)對(duì)這種行為報(bào)錯(cuò)。簡(jiǎn)而言之,建立一次聯(lián)系只能求一次梯度。
weights = torch.ones(4,requires_grad = True)
for epoch in range(2):
? ?model_output = (weights*3).sum()
? ?
? ?model_output.backward()
? ?
? ?print(weights.grad)
? ?
# tensor([3., 3., 3., 3.])
# tensor([6., 6., 6., 6.])
在上面的代碼中,每次求梯度之前,我們都會(huì)跑一行代碼 model_output = (weights*3).sum() 來(lái)重新建立聯(lián)系。即使每一次循環(huán)中這個(gè)聯(lián)系是完全不變的,我們也要重新跑,否則backward會(huì)報(bào)錯(cuò)。
但是新的問(wèn)題出現(xiàn)了,在兩次循環(huán)中,建立的聯(lián)系還有自變量tensor (weights)都是不變的,但是兩次求出來(lái)的梯度卻不一樣。這是因?yàn)?.grad是自變量tensor的性質(zhì),在我們第二次求函數(shù)在自變量tensor上面的梯度時(shí),第二次求出來(lái)的梯度會(huì)和第一次求出來(lái)的梯度疊加。因此,在這個(gè)循環(huán)中,每當(dāng)我們得到了想要的自變量tensor的梯度之后,為了不影響下次循環(huán),應(yīng)該使用加一行 weights.grad.zero_() 把之前求出來(lái)的梯度清0。正確的代碼是:
weights = torch.ones(4,requires_grad = True)
for epoch in range(2):
? ?model_output = (weights*3).sum()
? ?
? ?model_output.backward()
? ?
? ?print(weights.grad)
? ?
? ?weights.grad.zero_()
?
# tensor([3., 3., 3., 3.])
# tensor([3., 3., 3., 3.])
4. 小練習(xí):下面這個(gè)函數(shù)在哪里取最小值
這個(gè)用first order condition,最小值在處取,我就不詳細(xì)寫(xiě)了。
x_list = []
y_list = []
i = 0
lr = 0.05
x = torch.randn(2,requires_grad = True)
while (i < 10000):
? ?y = x[0]**2 + x[0] * x[1] + x[1]**2 + x[0] + x[1]
? ?y.backward()
? ?if abs(x.grad).mean() < 1/100000:
? ? ? ?break
? ?#print(x) tensor([-0.0180, -1.1985], requires_grad=True)
? ?x = x - lr * x.grad
? ?#print(x) tensor([-0.0063, -1.1278], grad_fn=<SubBackward0>)
? ?x.detach_() ?
? ?x.requires_grad_(True)
? ?#print(x) tensor([-0.0063, -1.1278], requires_grad=True)
? ?i += 1
? ?
? ?x_list.append(x.detach().numpy()[0])
? ?y_list.append(x.detach().numpy()[1])
x
# tensor([-0.3333, -0.3333], requires_grad=True)
等價(jià)的,也可以
i = 0
lr = 0.05
x = torch.randn(2,requires_grad = True)
while (i < 10000):
? ?y = x[0]**2 + x[0] * x[1] + x[1]**2 + x[0] + x[1]
? ?y.backward()
? ?if abs(x.grad).mean() < 1/100000:
? ? ? ?break
? ?#print(x) tensor([-0.6739, -0.4994], requires_grad=True)
? ?with torch.no_grad():
? ? ? ?x -= lr * x.grad
? ?#print(x) tensor([-0.6316, -0.4658], requires_grad=True)
? ?x.grad.zero_()
? ?i += 1
x
# tensor([-0.3333, -0.3333], requires_grad=True)
最后可視化一下,看看隨著迭代逐漸逼近真實(shí)解的過(guò)程。
fig,ax = plt.subplots(1,figsize=(10,4))
ax.scatter(x_list,y_list,label='Estimated minimum')
ax.scatter(-1/3,-1/3,label='True minimum')
ax.legend()
