日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

编写高效的PyTorch代码技巧(上)

發(fā)布時(shí)間:2023/12/10 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 编写高效的PyTorch代码技巧(上) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

點(diǎn)擊上方“算法猿的成長(zhǎng)“,關(guān)注公眾號(hào),選擇加“星標(biāo)“或“置頂”

總第 132?篇文章,本文大約 7000?字,閱讀大約需要 20?分鐘

原文:https://github.com/vahidk/EffectivePyTorch

作者:vahidk

前言

這是一份 PyTorch 教程和最佳實(shí)踐筆記,目錄如下所示:

  • PyTorch 基礎(chǔ)

  • 將模型封裝為模塊

  • 廣播機(jī)制的優(yōu)缺點(diǎn)

  • 使用好重載的運(yùn)算符

  • 采用 TorchScript 優(yōu)化運(yùn)行時(shí)間

  • 構(gòu)建高效的自定義數(shù)據(jù)加載類(lèi)

  • PyTorch 的數(shù)值穩(wěn)定性

  • 因?yàn)樵奶L(zhǎng)所以分為上下兩篇文章進(jìn)行介紹,本文介紹前四點(diǎn),從基礎(chǔ)開(kāi)始介紹到使用重載的運(yùn)算符。

    首先 PyTorch 的安裝可以根據(jù)官方文檔進(jìn)行操作:

    https://pytorch.org/

    pip?install?torch?torchvision

    1. PyTorch 基礎(chǔ)

    PyTorch 是數(shù)值計(jì)算方面其中一個(gè)最流行的庫(kù),同時(shí)也是機(jī)器學(xué)習(xí)研究方面最廣泛使用的框架。在很多方面,它和 NumPy 都非常相似,但是它可以在不需要代碼做多大改變的情況下,在 CPUs,GPUs,TPUs 上實(shí)現(xiàn)計(jì)算,以及非常容易實(shí)現(xiàn)分布式計(jì)算的操作。PyTorch 的其中一個(gè)最重要的特征就是自動(dòng)微分。它可以讓需要采用梯度下降算法進(jìn)行訓(xùn)練的機(jī)器學(xué)習(xí)算法的實(shí)現(xiàn)更加方便,可以更高效的自動(dòng)計(jì)算函數(shù)的梯度。我們的目標(biāo)是提供更好的 PyTorch 介紹以及討論使用 PyTorch 的一些最佳實(shí)踐。

    對(duì)于 PyTorch 第一個(gè)需要學(xué)習(xí)的就是張量(Tensors)的概念,張量就是多維數(shù)組,它和 numpy 的數(shù)組非常相似,但多了一些函數(shù)功能。

    一個(gè)張量可以存儲(chǔ)一個(gè)標(biāo)量數(shù)值、一個(gè)數(shù)組、一個(gè)矩陣:

    import?torch #?標(biāo)量數(shù)值 a?=?torch.tensor(3) print(a)??#?tensor(3) #?數(shù)組 b?=?torch.tensor([1,?2]) print(b)??#?tensor([1,?2]) #?矩陣 c?=?torch.zeros([2,?2]) print(c)??#?tensor([[0.,?0.],?[0.,?0.]]) #?任意維度的張量 d?=?torch.rand([2,?2,?2])

    張量還可以高效的執(zhí)行代數(shù)的運(yùn)算。機(jī)器學(xué)習(xí)應(yīng)用中最常見(jiàn)的運(yùn)算就是矩陣乘法。例如希望將兩個(gè)隨機(jī)矩陣進(jìn)行相乘,維度分別是 和 ,這個(gè)運(yùn)算可以通過(guò)矩陣相乘運(yùn)算實(shí)現(xiàn)(@):

    import?torchx?=?torch.randn([3,?5]) y?=?torch.randn([5,?4]) z?=?x?@?yprint(z)

    對(duì)于向量相加,如下所示:

    z?=?x?+?y

    將張量轉(zhuǎn)換為 numpy 數(shù)組,可以調(diào)用 numpy() 方法:

    print(z.numpy())

    當(dāng)然,反過(guò)來(lái) numpy 數(shù)組轉(zhuǎn)換為張量是可以的:

    x?=?torch.tensor(np.random.normal([3,?5]))

    自動(dòng)微分

    PyTorch 中相比 numpy ?最大優(yōu)點(diǎn)就是可以實(shí)現(xiàn)自動(dòng)微分,這對(duì)于優(yōu)化神經(jīng)網(wǎng)絡(luò)參數(shù)的應(yīng)用非常有幫助。下面通過(guò)一個(gè)例子來(lái)幫助理解這個(gè)優(yōu)點(diǎn)。

    假設(shè)現(xiàn)在有一個(gè)復(fù)合函數(shù):g(u(x)) ,為了計(jì)算 g 對(duì) x 的導(dǎo)數(shù),這里可以采用鏈?zhǔn)椒▌t,即

    而 PyTorch 可以自動(dòng)實(shí)現(xiàn)這個(gè)求導(dǎo)的過(guò)程。

    為了在 PyTorch 中計(jì)算導(dǎo)數(shù),首先要?jiǎng)?chuàng)建一個(gè)張量,并設(shè)置其 requires_grad = True ,然后利用張量運(yùn)算來(lái)定義函數(shù),這里假設(shè) u 是一個(gè)二次方的函數(shù),而 g 是一個(gè)簡(jiǎn)單的線性函數(shù),代碼如下所示:

    x?=?torch.tensor(1.0,?requires_grad=True)def?u(x):return?x?*?xdef?g(u):return?-u

    在這個(gè)例子中,復(fù)合函數(shù)就是 ,所以導(dǎo)數(shù)是 ,如果 x=1 ,那么可以得到 -2 。

    在 PyTorch 中調(diào)用梯度函數(shù):

    dgdx?=?torch.autograd.grad(g(u(x)),?x)[0] print(dgdx)??#?tensor(-2.)

    擬合曲線

    為了展示自動(dòng)微分有多么強(qiáng)大,這里介紹另一個(gè)例子。

    首先假設(shè)我們有一些服從一個(gè)曲線(也就是函數(shù) )的樣本,然后希望基于這些樣本來(lái)評(píng)估這個(gè)函數(shù) f(x) 。我們先定義一個(gè)帶參數(shù)的函數(shù):

    函數(shù)的輸入是 x,然后 w 是參數(shù),目標(biāo)是找到合適的參數(shù)使得下列式子成立:

    實(shí)現(xiàn)的一個(gè)方法可以是通過(guò)優(yōu)化下面的損失函數(shù)來(lái)實(shí)現(xiàn):

    盡管這個(gè)問(wèn)題里有一個(gè)正式的函數(shù)(即 f(x) 是一個(gè)具體的函數(shù)),但這里我們還是采用一個(gè)更加通用的方法,可以應(yīng)用到任何一個(gè)可微分的函數(shù),并采用隨機(jī)梯度下降法,即通過(guò)計(jì)算 L(w) 對(duì)于每個(gè)參數(shù) w 的梯度的平均值,然后不斷從相反反向移動(dòng)。

    利用 PyTorch 實(shí)現(xiàn)的代碼如下所示:

    import?numpy?as?np import?torch#?Assuming?we?know?that?the?desired?function?is?a?polynomial?of?2nd?degree,?we #?allocate?a?vector?of?size?3?to?hold?the?coefficients?and?initialize?it?with #?random?noise. w?=?torch.tensor(torch.randn([3,?1]),?requires_grad=True)#?We?use?the?Adam?optimizer?with?learning?rate?set?to?0.1?to?minimize?the?loss. opt?=?torch.optim.Adam([w],?0.1)def?model(x):#?We?define?yhat?to?be?our?estimate?of?y.f?=?torch.stack([x?*?x,?x,?torch.ones_like(x)],?1)yhat?=?torch.squeeze(f?@?w,?1)return?yhatdef?compute_loss(y,?yhat):#?The?loss?is?defined?to?be?the?mean?squared?error?distance?between?our#?estimate?of?y?and?its?true?value.?loss?=?torch.nn.functional.mse_loss(yhat,?y)return?lossdef?generate_data():#?Generate?some?training?data?based?on?the?true?functionx?=?torch.rand(100)?*?20?-?10y?=?5?*?x?*?x?+?3return?x,?ydef?train_step():x,?y?=?generate_data()yhat?=?model(x)loss?=?compute_loss(y,?yhat)opt.zero_grad()loss.backward()opt.step()for?_?in?range(1000):train_step()print(w.detach().numpy())

    運(yùn)行上述代碼,可以得到和下面相近的結(jié)果:

    [4.9924135, 0.00040895029, 3.4504161]

    這和我們的參數(shù)非常接近。

    上述只是 PyTorch 可以做的事情的冰山一角。很多問(wèn)題,比如優(yōu)化一個(gè)帶有上百萬(wàn)參數(shù)的神經(jīng)網(wǎng)絡(luò),都可以用 PyTorch 高效的用幾行代碼實(shí)現(xiàn),PyTorch 可以跨多個(gè)設(shè)備和線程進(jìn)行拓展,并且支持多個(gè)平臺(tái)。


    2. 將模型封裝為模塊

    在之前的例子中,我們構(gòu)建模型的方式是直接實(shí)現(xiàn)張量間的運(yùn)算操作。但為了讓代碼看起來(lái)更加有組織,推薦采用 PyTorch 的 modules 模塊。一個(gè)模塊實(shí)際上是一個(gè)包含參數(shù)和壓縮模型運(yùn)算的容器。

    比如,如果想實(shí)現(xiàn)一個(gè)線性模型 ,那么實(shí)現(xiàn)的代碼可以如下所示:

    import?torchclass?Net(torch.nn.Module):def?__init__(self):super().__init__()self.a?=?torch.nn.Parameter(torch.rand(1))self.b?=?torch.nn.Parameter(torch.rand(1))def?forward(self,?x):yhat?=?self.a?*?x?+?self.breturn?yhat

    使用的例子如下所示,需要實(shí)例化聲明的模型,并且像調(diào)用函數(shù)一樣使用它:

    x?=?torch.arange(100,?dtype=torch.float32)net?=?Net() y?=?net(x)

    參數(shù)都是設(shè)置 requires_grad 為 true 的張量。通過(guò)模型的 parameters() 方法可以很方便的訪問(wèn)和使用參數(shù),如下所示:

    for?p?in?net.parameters():print(p)

    現(xiàn)在,假設(shè)是一個(gè)未知的函數(shù) y=5x+3+n ,注意這里的 n 是表示噪音,然后希望優(yōu)化模型參數(shù)來(lái)擬合這個(gè)函數(shù),首先可以簡(jiǎn)單從這個(gè)函數(shù)進(jìn)行采樣,得到一些樣本數(shù)據(jù):

    x?=?torch.arange(100,?dtype=torch.float32)?/?100 y?=?5?*?x?+?3?+?torch.rand(100)?*?0.3

    和上一個(gè)例子類(lèi)似,需要定義一個(gè)損失函數(shù)并優(yōu)化模型的參數(shù),如下所示:

    criterion?=?torch.nn.MSELoss() optimizer?=?torch.optim.SGD(net.parameters(),?lr=0.01)for?i?in?range(10000):net.zero_grad()yhat?=?net(x)loss?=?criterion(yhat,?y)loss.backward()optimizer.step()print(net.a,?net.b)?#?Should?be?close?to?5?and?3

    在 PyTorch 中已經(jīng)實(shí)現(xiàn)了很多預(yù)定義好的模塊。比如 torch.nn.Linear 就是一個(gè)類(lèi)似上述例子中定義的一個(gè)更加通用的線性函數(shù),所以我們可以采用這個(gè)函數(shù)來(lái)重寫(xiě)我們的模型代碼,如下所示:

    class?Net(torch.nn.Module):def?__init__(self):super().__init__()self.linear?=?torch.nn.Linear(1,?1)def?forward(self,?x):yhat?=?self.linear(x.unsqueeze(1)).squeeze(1)return?yhat

    這里用到了兩個(gè)函數(shù),squeeze 和 unsqueeze ,主要是torch.nn.Linear 會(huì)對(duì)一批向量而不是數(shù)值進(jìn)行操作。

    同樣,默認(rèn)調(diào)用 parameters() 會(huì)返回其所有子模塊的參數(shù):

    net?=?Net() for?p?in?net.parameters():print(p)

    當(dāng)然也有一些預(yù)定義的模塊是作為包容其他模塊的容器,最常用的就是 torch.nn.Sequential ,它的名字就暗示了它主要用于堆疊多個(gè)模塊(或者網(wǎng)絡(luò)層),例如堆疊兩個(gè)線性網(wǎng)絡(luò)層,中間是一個(gè)非線性函數(shù) ReLU ,如下所示:

    model?=?torch.nn.Sequential(torch.nn.Linear(64,?32),torch.nn.ReLU(),torch.nn.Linear(32,?10), )

    3. 廣播機(jī)制的優(yōu)缺點(diǎn)

    優(yōu)點(diǎn)

    PyTorch 支持廣播的元素積運(yùn)算。正常情況下,當(dāng)想執(zhí)行類(lèi)似加法和乘法操作的時(shí)候,你需要確認(rèn)操作數(shù)的形狀是匹配的,比如無(wú)法進(jìn)行一個(gè) [3, 2] 大小的張量和 [3, 4] 大小的張量的加法操作。

    但是存在一種特殊的情況:只有單一維度的時(shí)候,PyTorch 會(huì)隱式的根據(jù)另一個(gè)操作數(shù)的維度來(lái)拓展只有單一維度的操作數(shù)張量。因此,實(shí)現(xiàn) [3,2] 大小的張量和 [3,1] 大小的張量相加的操作是合法的。

    如下代碼展示了一個(gè)加法的例子:

    import?torcha?=?torch.tensor([[1.,?2.],?[3.,?4.]]) b?=?torch.tensor([[1.],?[2.]]) #?c?=?a?+?b.repeat([1,?2]) c?=?a?+?bprint(c)

    廣播機(jī)制可以實(shí)現(xiàn)隱式的維度復(fù)制操作(repeat 操作),并且代碼更短,內(nèi)存使用上也更加高效,因?yàn)椴恍枰鎯?chǔ)復(fù)制的數(shù)據(jù)的結(jié)果。這個(gè)機(jī)制非常適合用于結(jié)合多個(gè)維度不同的特征的時(shí)候。

    為了拼接不同維度的特征,通常的做法是先對(duì)輸入張量進(jìn)行維度上的復(fù)制,然后拼接后使用非線性激活函數(shù)。整個(gè)過(guò)程的代碼實(shí)現(xiàn)如下所示:

    a?=?torch.rand([5,?3,?5]) b?=?torch.rand([5,?1,?6])linear?=?torch.nn.Linear(11,?10)#?concat?a?and?b?and?apply?nonlinearity tiled_b?=?b.repeat([1,?3,?1])?#?b?shape:??[5,?3,?6] c?=?torch.cat([a,?tiled_b],?2)?#?c?shape:?[5,?3,?11] d?=?torch.nn.functional.relu(linear(c))print(d.shape)??#?torch.Size([5,?3,?10])

    但實(shí)際上通過(guò)廣播機(jī)制可以實(shí)現(xiàn)得更加高效,即 f(m(x+y)) 是等同于 f(mx+my) 的,也就是我們可以先分別做線性操作,然后通過(guò)廣播機(jī)制來(lái)做隱式的拼接操作,如下所示:

    a?=?torch.rand([5,?3,?5]) b?=?torch.rand([5,?1,?6])linear1?=?torch.nn.Linear(5,?10) linear2?=?torch.nn.Linear(6,?10)pa?=?linear1(a)?#?pa?shape:?[5,?3,?10] pb?=?linear2(b)?#?pb?shape:?[5,?1,?10] d?=?torch.nn.functional.relu(pa?+?pb)print(d.shape)??#?torch.Size([5,?3,?10])

    實(shí)際上這段代碼非常通用,可以用于任意維度大小的張量,只要它們之間是可以實(shí)現(xiàn)廣播機(jī)制的,如下所示:

    class?Merge(torch.nn.Module):def?__init__(self,?in_features1,?in_features2,?out_features,?activation=None):super().__init__()self.linear1?=?torch.nn.Linear(in_features1,?out_features)self.linear2?=?torch.nn.Linear(in_features2,?out_features)self.activation?=?activationdef?forward(self,?a,?b):pa?=?self.linear1(a)pb?=?self.linear2(b)c?=?pa?+?pbif?self.activation?is?not?None:c?=?self.activation(c)return?c

    缺點(diǎn)

    到目前為止,我們討論的都是廣播機(jī)制的優(yōu)點(diǎn)。但它的缺點(diǎn)是什么呢?原因也是出現(xiàn)在隱式的操作,這種做法非常不利于進(jìn)行代碼的調(diào)試。

    這里給出一個(gè)代碼例子:

    a?=?torch.tensor([[1.],?[2.]]) b?=?torch.tensor([1.,?2.]) c?=?torch.sum(a?+?b)print(c)

    所以上述代碼的輸出結(jié)果 c 是什么呢?你可能覺(jué)得是 6,但這是錯(cuò)的,正確答案是 12 。這是因?yàn)楫?dāng)兩個(gè)張量的維度不匹配的時(shí)候,PyTorch 會(huì)自動(dòng)將維度低的張量的第一個(gè)維度進(jìn)行拓展,然后在進(jìn)行元素之間的運(yùn)算,所以這里會(huì)將b ?先拓展為 [[1, 2], [1, 2]],然后 a+b 的結(jié)果應(yīng)該是 [[2,3], [3, 4]] ,然后sum 操作是將所有元素求和得到結(jié)果 12。

    那么避免這種結(jié)果的方法就是顯式的操作,比如在這個(gè)例子中就需要指定好想要求和的維度,這樣進(jìn)行代碼調(diào)試會(huì)更簡(jiǎn)單,代碼修改后如下所示:

    a?=?torch.tensor([[1.],?[2.]]) b?=?torch.tensor([1.,?2.]) c?=?torch.sum(a?+?b,?0)print(c)

    這里得到的 c 的結(jié)果是 [5, 7],而我們基于結(jié)果的維度可以知道出現(xiàn)了錯(cuò)誤。

    這有個(gè)通用的做法,就是在做累加( reduction )操作或者使用 torch.squeeze 的時(shí)候總是指定好維度。


    4. 使用好重載的運(yùn)算符

    和 NumPy 一樣,PyTorch 會(huì)重載 python 的一些運(yùn)算符來(lái)讓 PyTorch 代碼更簡(jiǎn)短和更有可讀性。

    例如,切片操作就是其中一個(gè)重載的運(yùn)算符,可以更容易的對(duì)張量進(jìn)行索引操作,如下所示:

    z?=?x[begin:end]??#?z?=?torch.narrow(0,?begin,?end-begin)

    但需要謹(jǐn)慎使用這個(gè)運(yùn)算符,它和其他運(yùn)算符一樣,也有一些副作用。正因?yàn)樗且粋€(gè)非常常用的運(yùn)算操作,如果過(guò)度使用可以導(dǎo)致代碼變得低效。

    這里給出一個(gè)例子來(lái)展示它是如何導(dǎo)致代碼變得低效的。這個(gè)例子中我們希望對(duì)一個(gè)矩陣手動(dòng)實(shí)現(xiàn)行之間的累加操作:

    import?torch import?timex?=?torch.rand([500,?10])z?=?torch.zeros([10])start?=?time.time() for?i?in?range(500):z?+=?x[i] print("Took?%f?seconds."?%?(time.time()?-?start))

    上述代碼的運(yùn)行速度會(huì)非常慢,因?yàn)榭偣舱{(diào)用了 500 次的切片操作,這就是過(guò)度使用了。一個(gè)更好的做法是采用 torch.unbind 運(yùn)算符在每次循環(huán)中將矩陣切片為一個(gè)向量的列表,如下所示:

    z?=?torch.zeros([10]) for?x_i?in?torch.unbind(x):z?+=?x_i

    這個(gè)改進(jìn)會(huì)提高一些速度(在作者的機(jī)器上是提高了大約30%)。

    但正確的做法應(yīng)該是采用 torch.sum 來(lái)一步實(shí)現(xiàn)累加的操作:

    z?=?torch.sum(x,?dim=0)

    這種實(shí)現(xiàn)速度就非常的快(在作者的機(jī)器上提高了100%的速度)。

    其他重載的算數(shù)和邏輯運(yùn)算符分別是:

    z?=?-x??#?z?=?torch.neg(x) z?=?x?+?y??#?z?=?torch.add(x,?y) z?=?x?-?y z?=?x?*?y??#?z?=?torch.mul(x,?y) z?=?x?/?y??#?z?=?torch.div(x,?y) z?=?x?//?y z?=?x?%?y z?=?x?**?y??#?z?=?torch.pow(x,?y) z?=?x?@?y??#?z?=?torch.matmul(x,?y) z?=?x?>?y z?=?x?>=?y z?=?x?<?y z?=?x?<=?y z?=?abs(x)??#?z?=?torch.abs(x) z?=?x?&?y z?=?x?|?y z?=?x?^?y??#?z?=?torch.logical_xor(x,?y) z?=?~x??#?z?=?torch.logical_not(x) z?=?x?==?y??#?z?=?torch.eq(x,?y) z?=?x?!=?y??#?z?=?torch.ne(x,?y)

    還可以使用這些運(yùn)算符的遞增版本,比如 x += y 和 x **=2 都是合法的。

    另外,Python 并不允許重載 and 、or 和 not 三個(gè)關(guān)鍵詞。


    精選AI文章

    1.?10個(gè)實(shí)用的機(jī)器學(xué)習(xí)建議

    2.?深度學(xué)習(xí)算法簡(jiǎn)要綜述(上)

    3.?深度學(xué)習(xí)算法簡(jiǎn)要綜述(上)

    4.?常見(jiàn)的數(shù)據(jù)增強(qiáng)項(xiàng)目和論文介紹

    5.?實(shí)戰(zhàn)|手把手教你訓(xùn)練一個(gè)基于Keras的多標(biāo)簽圖像分類(lèi)器

    精選python文章

    1.??python數(shù)據(jù)模型

    2.?python版代碼整潔之道

    3.?快速入門(mén) Jupyter notebook

    4.?Jupyter 進(jìn)階教程

    5.?10個(gè)高效的pandas技巧

    精選教程資源文章

    1.?[資源分享] TensorFlow 官方中文版教程來(lái)了

    2.?[資源]推薦一些Python書(shū)籍和教程,入門(mén)和進(jìn)階的都有!

    3.?[Github項(xiàng)目推薦] 推薦三個(gè)助你更好利用Github的工具

    4.?Github上的各大高校資料以及國(guó)外公開(kāi)課視頻

    5.?GitHub上有哪些比較好的計(jì)算機(jī)視覺(jué)/機(jī)器視覺(jué)的項(xiàng)目?

    歡迎關(guān)注我的微信公眾號(hào)--算法猿的成長(zhǎng),或者掃描下方的二維碼,大家一起交流,學(xué)習(xí)和進(jìn)步!

    ?

    如果覺(jué)得不錯(cuò),在看、轉(zhuǎn)發(fā)就是對(duì)小編的一個(gè)支持!

    創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)

    總結(jié)

    以上是生活随笔為你收集整理的编写高效的PyTorch代码技巧(上)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。