<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從零實現(xiàn)深度學(xué)習(xí)框架(三)計算圖運算補充

          共 6708字,需瀏覽 14分鐘

           ·

          2021-12-20 00:51

          更多精彩推薦,請關(guān)注我們

          引言

          本著“凡我不能創(chuàng)造的,我就不能理解”的思想,本系列文章會基于純Python以及NumPy從零創(chuàng)建自己的深度學(xué)習(xí)框架,該框架類似PyTorch能實現(xiàn)自動求導(dǎo)。

          要深入理解深度學(xué)習(xí),從零開始創(chuàng)建的經(jīng)驗非常重要,從自己可以理解的角度出發(fā),盡量不適用外部完備的框架前提下,實現(xiàn)我們想要的模型。本系列文章的宗旨就是通過這樣的過程,讓大家切實掌握深度學(xué)習(xí)底層實現(xiàn),而不是僅做一個調(diào)包俠。

          本文額外介紹一些操作的計算圖,像求最大值(Max)、切片(Slice)、變形(Reshape)和轉(zhuǎn)置(Transpose)。

          Max

          Max操作要復(fù)雜一些,我們先來看一下當(dāng)輸入是一個1D數(shù)組時:

          取1D數(shù)組中,假設(shè)第三個元素為最大值,那么經(jīng)過Max操作后,返回的就是這個元素。當(dāng)反向傳播時,只有第三個元素有梯度,其他元素的梯度為。此時只有在第三個元素處才會將上游的梯度原封不動的傳給下游,其他元素處將不會有梯度往下游傳遞。就像電路中的開關(guān)一樣,只有第三個元素處開關(guān)是打開的,有電流通過;其他元素的位置的開關(guān)是關(guān)閉的,沒有電流通過。下面通過代碼演示一下:

          >?import?torch
          #?隨機生成一個(8,)的一維數(shù)組
          >?x?=?torch.randint(10,?(8,),?dtype=torch.float,?requires_grad=True)
          >?print(x)?#?第三個元素為最大值
          tensor([3.,?1.,?8.,?0.,?0.,?3.,?6.,?4.],?requires_grad=True)
          >?y?=?torch.max(x)
          >?print(y)
          tensor(8.,?grad_fn=)
          >?y.backward()
          >?print(x.grad)?#?只有第三個元素才有梯度
          tensor([0.,?0.,?1.,?0.,?0.,?0.,?0.,?0.])

          我們再來看輸入是2D的情況:

          在2D數(shù)組中,如果是沿著行的方向,即axis=0時,取每列的最大值,如果保持維度的話,就變成了一個的數(shù)組。就是上圖藍色背景對應(yīng)的元素。在反向傳播時,只有這些元素才會將上游的梯度傳遞到下游,其他元素的位置不會有梯度往下游傳遞。

          通過代碼來演示一下:

          >?D,?N?=?5,?4
          >?x?=?torch.randint(10,(N,D),?dtype=torch.float,?requires_grad=True)
          >?print(x)
          tensor([[1.,?1.,?5.,?9.,?1.],
          ????????[4.,?5.,?9.,?8.,?7.],
          ????????[1.,?1.,?7.,?7.,?9.],
          ????????[8.,?1.,?1.,?0.,?7.]],?requires_grad=True)
          >?y?=?torch.max(x,?dim=0,?keepdim=True).values
          >?print(y)
          tensor([[8.,?5.,?9.,?9.,?9.]],?grad_fn=)
          >?y.sum().backward()?#?或者y.backward([[1.,?1.,?1.,?1.,?1.]])
          >?print(x.grad)
          tensor([[0.,?0.,?0.,?1.,?0.],
          ????????[0.,?1.,?1.,?0.,?0.],
          ????????[0.,?0.,?0.,?0.,?1.],
          ????????[1.,?0.,?0.,?0.,?0.]])

          有一點值得注意的是,假設(shè)我們?nèi)∽畲笾禃r,包含重復(fù)元素,會怎樣呢?

          給個極端的例子:

          #?極端的例子
          >?x?=?torch.ones((2,5),?dtype=torch.float,?requires_grad=True)
          >?x
          tensor([[1.,?1.,?1.,?1.,?1.],
          ????????[1.,?1.,?1.,?1.,?1.]],?requires_grad=True)
          >?y?=?torch.max(x)
          >?y.backward()
          >?x.grad
          tensor([[0.1000,?0.1000,?0.1000,?0.1000,?0.1000],
          ????????[0.1000,?0.1000,?0.1000,?0.1000,?0.1000]])

          在這個例子中,共有10個元素,形狀是。元素值都是相等的,如果直接調(diào)用torch.max,在反向傳播后,梯度被這些元素給均分了,其實也很好理解。畢竟又不是復(fù)制操作。

          當(dāng)我們指定維度的時候,還會這樣嗎?

          #?極端的例子
          >?x?=?torch.ones((2,5),?dtype=torch.float,?requires_grad=True)
          >?x
          tensor([[1.,?1.,?1.,?1.,?1.],
          ????????[1.,?1.,?1.,?1.,?1.]],?requires_grad=True)
          >?y?=?torch.max(x,?axis=0)?#?指定axis=0,取每列的最大值
          >?y
          torch.return_types.max(values=tensor([1.,?1.,?1.,?1.,?1.],?grad_fn=),?indices=tensor([0,?0,?0,?0,?0]))?#?每列的最大值都是1,但是僅記錄了遇到的第一個元素索引
          >?y.values.sum().backward()
          >?print(x.grad)
          tensor([[1.,?1.,?1.,?1.,?1.],
          ????????[0.,?0.,?0.,?0.,?0.]])

          此時,PyTorch中的表現(xiàn)是這樣的。取每列的最大值,每列都有一個梯度,但是遇到重復(fù)元素的時候,并沒有把上游傳過來的梯度進行平分。博主更傾向于會均分梯度,因此在我們實現(xiàn)的時候,會考慮這一點。

          Slice

          切片(Slice)也是一種常見的操作,比如我們從數(shù)組中取出某個元素、某一列、某一行等。我們已經(jīng)了解了Max操作反向傳播的原理。那么理解切片應(yīng)該也不難。只有選中的元素才有資格傳遞梯度到下游。

          在上面這個的數(shù)組中,假設(shè)通過切片選擇了第三行,那么反向傳播時,只有第三行的元素上才有梯度往下游傳遞。通過代碼描述如下:

          >?D,?N?=?5,?4
          >?x?=?torch.randint(10,?(N,D),?dtype=torch.float,?requires_grad=True)
          >?x
          tensor([[3.,?8.,?8.,?4.,?0.],
          ????????[0.,?5.,?6.,?9.,?6.],
          ????????[6.,?8.,?8.,?1.,?8.],
          ????????[2.,?1.,?8.,?7.,?5.]],?requires_grad=True)
          >?y?=?x[2,:]?#?取第2行
          >?y
          tensor([6.,?8.,?8.,?1.,?8.],?grad_fn=)
          >?y.sum().backward()
          >?x.grad
          tensor([[0.,?0.,?0.,?0.,?0.],
          ????????[0.,?0.,?0.,?0.,?0.],
          ????????[1.,?1.,?1.,?1.,?1.],
          ????????[0.,?0.,?0.,?0.,?0.]])

          Reshape

          變形(Reshape)操作的反向傳播其實是最簡單的。假設(shè)經(jīng)過y = x.reshape(..),在反向傳播時,只要保證梯度的形狀和x保持一致即可。

          我們通過代碼來驗證一下:

          >?D,?N?=?6,?4
          >?x?=?torch.randint(10,?(N,D),?dtype=torch.float,?requires_grad=True)
          >?x?#?(4,6)的數(shù)組
          tensor([[9.,?4.,?8.,?7.,?6.,?0.],
          ????????[7.,?4.,?2.,?9.,?4.,?4.],
          ????????[7.,?1.,?8.,?2.,?4.,?7.],
          ????????[8.,?8.,?9.,?2.,?6.,?6.]],?requires_grad=True)
          >?y?=?x.reshape(3,?8)?#?(4,6)?->?(3,8)
          >?y
          tensor([[9.,?4.,?8.,?7.,?6.,?0.,?7.,?4.],
          ????????[2.,?9.,?4.,?4.,?7.,?1.,?8.,?2.],
          ????????[4.,?7.,?8.,?8.,?9.,?2.,?6.,?6.]],?grad_fn=)
          >?y.sum().backward()
          >?x.grad
          tensor([[1.,?1.,?1.,?1.,?1.,?1.],
          ????????[1.,?1.,?1.,?1.,?1.,?1.],
          ????????[1.,?1.,?1.,?1.,?1.,?1.],
          ????????[1.,?1.,?1.,?1.,?1.,?1.]])

          Transpose

          轉(zhuǎn)置(Transpose,我司CV大佬稱為旋轉(zhuǎn))和Reshape類似,所有元素的梯度都會往下游傳遞。但是轉(zhuǎn)置和Reshape操作本身又是有很大不同的。我們先來看一下它們的區(qū)別。

          >?import?matplotlib.pyplot?as?plt
          >?import?matplotlib.image?as?mpimg
          >?import?numpy?as?np
          >?img_array?=?mpimg.imread('https://gitee.com/nlp-greyfoss/images/raw/master/data/20211217174850.png')
          >?plt.imshow(img_array)?#?顯示圖片
          >?plt.axis("off")
          >?img_array.shape
          (157,?210,?3)

          該圖片的形狀為:

          • 寬: 210 像素
          • 高: 157 像素
          • RGB: 3

          假設(shè)我們想對圖片進行旋轉(zhuǎn),先通過Reshape進行,保持最后一個維度不變,以能展示出圖片。

          > reshaped = img_array.reshape((210,157,3))
          > plt.imshow(reshaped)
          > plt.axis("off")
          > reshaped.shape
          (210, 157, 3)

          雖然圖片是可以顯示出來,但是圖片變成了很多條狀的東西。我們可以通過下圖理解Reshape做了什么事情:

          Reshape改變矩陣形狀后,里面的元素還是根據(jù)原來的順序依次排列的。這會導(dǎo)致這些像素的相對位置會發(fā)生變化。

          我們再進行Transpose操作。

          >?transposed?=?img_array.transpose((1,0,2))?#?交換第0和第1個維度:?(0,1,2)?->?(1,0,2)?
          >?plt.imshow(transposed)
          >?plt.axis("off")

          可以看到,Transpose并不會改變元素的相對位置。具體如下:

          Transpose對于矩陣來說,就是轉(zhuǎn)置,也可以理解為對圖像進行旋轉(zhuǎn)。

          轉(zhuǎn)置的計算圖就不畫了,經(jīng)過上面的探討應(yīng)該能很好地理解。我們來看一下轉(zhuǎn)置操作如何進行反向傳播。

          >?x?=?torch.Tensor(np.arange(24).reshape(2,3,4))
          >?x.requires_grad?=?True
          >?print(x.shape)
          >?print(x)
          torch.Size([2,?3,?4])
          tensor([[[?0.,??1.,??2.,??3.],
          ?????????[?4.,??5.,??6.,??7.],
          ?????????[?8.,??9.,?10.,?11.]],

          ????????[[12.,?13.,?14.,?15.],
          ?????????[16.,?17.,?18.,?19.],
          ?????????[20.,?21.,?22.,?23.]]],?requires_grad=True)
          >?axis?=?(0,1,2)?#?和原來的軸保持一致,演示不轉(zhuǎn)置的結(jié)果
          >?y?=?torch.permute(x,axis)?#?torch中的轉(zhuǎn)置
          >?y.sum().backward()
          >?x.grad
          tensor([[[1.,?1.,?1.,?1.],
          ?????????[1.,?1.,?1.,?1.],
          ?????????[1.,?1.,?1.,?1.]],

          ????????[[1.,?1.,?1.,?1.],
          ?????????[1.,?1.,?1.,?1.],
          ?????????[1.,?1.,?1.,?1.]]])
          >?x.grad.shape
          torch.Size([2,?3,?4])

          可以看到,果然和Reshape一樣,哪怕做了個假轉(zhuǎn)置,也會有梯度。而且梯度的維度和x一致。

          所以,在反向傳播的時候,我們要將上游傳遞過來的梯度,進行逆Reshape操作,保證和x的維度一致。

          我們創(chuàng)建一個維度的向量。

          >?a?=?np.arange(24).reshape(2,3,4)
          >?a
          array([[[?0,??1,??2,??3],
          ????????[?4,??5,??6,??7],
          ????????[?8,??9,?10,?11]],

          ???????[[12,?13,?14,?15],
          ????????[16,?17,?18,?19],
          ????????[20,?21,?22,?23]]])

          下面我們先對其進行轉(zhuǎn)置,然后探討一下如何把轉(zhuǎn)置后的結(jié)果,轉(zhuǎn)置回來。

          >?b?=?a.transpose(2,0,1)
          >?print(b.shape)?#(0,1,2)?->?(2,0,1)?:?得到(4,2,3)?即第0軸到了中間,第1軸到了最后,第2軸到了最前面。
          >?print(b)
          (4,?2,?3)
          [[[?0??4??8]
          ??[12?16?20]]

          ?[[?1??5??9]
          ??[13?17?21]]

          ?[[?2??6?10]
          ??[14?18?22]]

          ?[[?3??7?11]
          ??[15?19?23]]]

          。所以要轉(zhuǎn)置回來,我們需要把進行一個怎么樣的轉(zhuǎn)置,才會變回來?中括號里面的數(shù)字表示現(xiàn)在對應(yīng)的軸。所以我們應(yīng)該把對應(yīng)的軸交換到最后(2軸),把對應(yīng)的軸交換到中間(1)軸,把軸交換到最前(0軸)。我們要對當(dāng)前的進行一個這樣的轉(zhuǎn)置操作:b.reshape(1,2,0)。下面來驗證看:

          >?b.transpose(1,2,0)
          array([[[?0,??1,??2,??3],
          ????????[?4,??5,??6,??7],
          ????????[?8,??9,?10,?11]],

          ???????[[12,?13,?14,?15],
          ????????[16,?17,?18,?19],
          ????????[20,?21,?22,?23]]])

          看起來不錯,但是每次這么分析,很耗時間啊。這里這有3個維度,如果有5個維度怎么辦,有什么規(guī)律嗎?

          嘿,還確實有規(guī)律,就是對a轉(zhuǎn)置時的元組(或者說是軸列表)進行argsort(對元素按從小到大進行排序,但返回的是排序后的索引)

          我們來試一下:

          >?b.transpose(np.argsort((2,0,1)))
          array([[[?0,??1,??2,??3],
          ????????[?4,??5,??6,??7],
          ????????[?8,??9,?10,?11]],

          ???????[[12,?13,?14,?15],
          ????????[16,?17,?18,?19],
          ????????[20,?21,?22,?23]]])

          總結(jié)

          至此,我們經(jīng)常用到操作的計算圖都了解完畢了,下篇文章開始通過Python實現(xiàn)這些計算圖來創(chuàng)造一個我們自己的自動求導(dǎo)工具。

          最后一句:BUG,走你!

          Markdown筆記神器Typora配置Gitee圖床
          不會真有人覺得聊天機器人難吧(一)
          Spring Cloud學(xué)習(xí)筆記(一)
          沒有人比我更懂Spring Boot(一)
          入門人工智能必備的線性代數(shù)基礎(chǔ)

          1.看到這里了就點個在看支持下吧,你的在看是我創(chuàng)作的動力。
          2.關(guān)注公眾號,每天為您分享原創(chuàng)或精選文章!
          3.特殊階段,帶好口罩,做好個人防護。



          瀏覽 97
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲男女激情91免费网站 | 蜜桃成人中文字幕 | 成人网站在线视频三级 | 狠狠伊人久久 | 超色97|