<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>

          從零搭建Pytorch模型教程 | 搭建Transformer網絡

          共 9161字,需瀏覽 19分鐘

           ·

          2022-04-19 02:40

          點擊下方AI算法與圖像處理”,一起進步!

          重磅干貨,第一時間送達


          前言?本文介紹了Transformer的基本流程,分塊的兩種實現方式,Position Emebdding的幾種實現方式,Encoder的實現方式,最后分類的兩種方式,以及最重要的數據格式的介紹。


          在講如何搭建之前,先回顧一下Transformer在計算機視覺中的結構是怎樣的。這里以最典型的ViT為例。
          如圖所示,對于一張圖像,先將其分割成NxN個patches,把patches進行Flatten,再通過一個全連接層映射成tokens,對每一個tokens加入位置編碼(position embedding),會隨機初始化一個tokens,concate到通過圖像生成的tokens后,再經過transformer的Encoder模塊,經過多層Encoder后,取出最后的tokens(即隨機初始化的tokens),再通過全連接層作為分類網絡進行分類。
          下面我們就根據這個流程來一步一步介紹如何搭建一個Transformer模型。

          分塊

          目前有兩種方式實現分塊,一種是直接分割,一種是通過卷積核和步長都為patch大小的卷積來分割。

          直接分割
          直接分割即把圖像直接分成多塊。在代碼實現上需要使用einops這個庫,完成的操作是將(B,C,H,W)的shape調整為(B,(H/P *W/P),P*P*C)。
          from?einops?import?rearrange,?repeat
          from?einops.layers.torch?import?Rearrange

          self.to_patch_embedding?=?nn.Sequential(
          ???????????Rearrange('b?c?(h?p1)?(w?p2)?->?b?(h?w)?(p1?p2?c)',?p1?=?patch_height,?p2?=?patch_width),
          ???????????nn.Linear(patch_dim,?dim),
          ??????)
          這里簡單介紹一下Rearrange。
          Rearrange用于對張量的維度進行重新變換排序,可用于替換pytorch中的reshape,view,transpose和permute等操作。舉幾個例子
          #假設images的shape為[32,200,400,3]
          #實現view和reshape的功能
          Rearrange(images,'b?h?w?c?->?(b?h)?w?c')#shape變?yōu)椋?2*200,?400,?3)
          #實現permute的功能
          Rearrange(images,?'b?h?w?c?->?b?c?h?w')#shape變?yōu)椋?2,?3,?200,?400)
          #實現這幾個都很難實現的功能
          Rearrange(images,?'b?h?w?c?->?(b?c?w)?h')#shape變?yōu)椋?2*3*400,?200)
          從這幾個例子看可以看出,Rearrange非常簡單好用,這里的b, c, h, w都可以理解為表示符號,用來表示操作變化。通過這幾個例子似乎也能理解下面這行代碼是如何將圖像分割的。
          Rearrange('b?c?(h?p1)?(w?p2)?->?b?(h?w)?(p1?p2?c)',?p1?=?patch_height,?p2?=?patch_width)
          這里需要解釋的是,一個括號內的兩個變量相乘表示的是該維度的長度,因此不要把"h"和"w"理解成圖像的寬和高。這里實際上h = H/p1, w = W/p2,代表的是高度上有幾塊,寬度上有幾塊。h和w都不需要賦值,代碼會自動根據這個表達式計算,b和c也會自動對應到輸入數據的B和C。
          后面的"b (h w) (p1 p2 c)"表示了圖像分塊后的shape: (B,(H/P *W/P),P*P*C)
          這種方式在分塊后還需要通過一層全連接層將分塊的向量映射為tokens。
          在ViT中使用的就是這種直接分塊方式。

          卷積分割
          卷積分割比較容易理解,使用卷積核和步長都為patch大小的卷積對圖像卷積一次就可以了。
          self.proj?=?nn.Conv2d(in_chans,?embed_dim,?kernel_size=patch_size,?stride=patch_size)

          x?=?self.proj(x).flatten(2).transpose(1,?2)??#?B?Ph*Pw?C
          在swin transformer中即使用的是這種卷積分塊方式。在swin transformer中卷積后沒有再加全連接層。

          Position Embedding

          Position Embedding可以分為absolute position embedding和relative position embedding。
          在學習最初的transformer時,可能會注意到用的是正余弦編碼的方式,但這只適用于語音、文字等1維數據,圖像是高度結構化的數據,用正余弦不合適
          在ViT和swin transformer中都是直接隨機初始化一組與tokens同shape的可學習參數,與tokens相加,即完成了absolute position embedding。
          在ViT中實現方式:
          self.pos_embedding?=?nn.Parameter(torch.randn(1,?num_patches?+?1,?dim))
          x?+=?self.pos_embedding[:,?:(n?+?1)]
          #之所以是n+1,是因為ViT中選擇隨機初始化一個class token,與分塊得到的tokens拼接。所以patches的數量為num_patches+1。
          在swin transformer中的實現方式:
          from?timm.models.layers?import?trunc_normal_
          self.absolute_pos_embed?=?nn.Parameter(torch.zeros(1,?num_patches,?embed_dim))
          trunc_normal_(self.absolute_pos_embed,?std=.02)
          在TimeSformer中的實現方式:
          self.pos_emb?=?torch.nn.Embedding(num_positions?+?1,?dim)
          以上就是簡單的使用方法,這種方法屬于absolute position embedding。
          還有更復雜一點的方法,以后有機會單獨搞一篇文章來介紹。
          感興趣的讀者可以先去看看這篇論文《ICCV2021 | Vision Transformer中相對位置編碼的反思與改進》。

          Encoder


          Encoder由Multi-head Self-attention和FeedForward組成。
          Multi-head Self-attention
          Multi-head Self-attention主要是先把tokens分成q、k、v,再計算q和k的點積,經過softmax后獲得加權值,給v加權,再經過全連接層。
          用公式表示如下:

          所謂Multi-head是指把q、k、v再dim維度上分成head份,公式里的dk為每個head的維度。
          具體代碼如下:
          class?Attention(nn.Module):
          ???def?__init__(self,?dim,?heads?=?8,?dim_head?=?64,?dropout?=?0.):
          ???????super().__init__()
          ???????inner_dim?=?dim_head?*??heads
          ???????project_out?=?not?(heads?==?1?and?dim_head?==?dim)

          ???????self.heads?=?heads
          ???????self.scale?=?dim_head?**?-0.5
          ???????self.attend?=?nn.Softmax(dim?=?-1)
          ???????self.dropout?=?nn.Dropout(dropout)

          ???????self.to_qkv?=?nn.Linear(dim,?inner_dim?*?3,?bias?=?False)
          ???????self.to_out?=?nn.Sequential(
          ???????????nn.Linear(inner_dim,?dim),
          ???????????nn.Dropout(dropout)
          ??????)?if?project_out?else?nn.Identity()

          ???def?forward(self,?x):
          ???????qkv?=?self.to_qkv(x).chunk(3,?dim?=?-1)
          ???????q,?k,?v?=?map(lambda?t:?rearrange(t,?'b?n?(h?d)?->?b?h?n?d',?h?=?self.heads),?qkv)
          ???????dots?=?torch.matmul(q,?k.transpose(-1,?-2))?*?self.scale
          ???????attn?=?self.attend(dots)
          ???????attn?=?self.dropout(attn)

          ???????out?=?torch.matmul(attn,?v)
          ???????out?=?rearrange(out,?'b?h?n?d?->?b?n?(h?d)')
          ???????return?self.to_out(out)
          這里沒有太多可以解釋的地方,介紹一下q、k、v的來源,由于這是self-attention,因此q=k=v(即tokens),若是普通attention,則k= v,而q是其它的東西,例如可以是另一個尺度的tokens,或視頻領域中的其它幀的tokens。

          FeedForward
          這里不用多介紹。
          class?FeedForward(nn.Module):
          ???def?__init__(self,?dim,?hidden_dim,?dropout?=?0.):
          ???????super().__init__()
          ???????self.net?=?nn.Sequential(
          ???????????nn.Linear(dim,?hidden_dim),
          ???????????nn.GELU(),
          ???????????nn.Dropout(dropout),
          ???????????nn.Linear(hidden_dim,?dim),
          ???????????nn.Dropout(dropout)
          ??????)
          ???def?forward(self,?x):
          ???????return?self.net(x)
          把上面兩者組合起來就是Encoder了。
          class?Transformer(nn.Module):
          ???def?__init__(self,?dim,?depth,?heads,?dim_head,?mlp_dim,?dropout?=?0.):
          ???????super().__init__()
          ???????self.layers?=?nn.ModuleList([])
          ???????for?_?in?range(depth):
          ???????????self.layers.append(nn.ModuleList([
          ???????????????PreNorm(dim,?Attention(dim,?heads?=?heads,?dim_head?=?dim_head,?dropout?=?dropout)),
          ???????????????PreNorm(dim,?FeedForward(dim,?mlp_dim,?dropout?=?dropout))
          ??????????]))
          ???def?forward(self,?x):
          ???????for?attn,?ff?in?self.layers:
          ???????????x?=?attn(x)?+?x
          ???????????x?=?ff(x)?+?x
          ???????return?x
          depth指的是Encoder的數量。PreNorm指的是層歸一化。
          class?PreNorm(nn.Module):
          ????def?__init__(self,?dim,?fn):
          ????????super().__init__()
          ????????self.norm?=?nn.LayerNorm(dim)
          ????????self.fn?=?fn
          ????def?forward(self,?x,?**kwargs):
          ????????return?self.fn(self.norm(x),?**kwargs)

          分類方法

          數據通過Encoder后獲得最后的預測向量的方法有兩種典型。在ViT中是隨機初始化一個cls_token,concate到分塊后的token后,經過Encoder后取出cls_token,最后將cls_token通過全連接層映射到最后的預測維度。
          #生成cls_token部分
          from?einops?import?repeat
          self.cls_token?=?nn.Parameter(torch.randn(1,?1,?dim))

          cls_tokens?=?repeat(self.cls_token,?'1?n?d?->?b?n?d',?b?=?b)
          x?=?torch.cat((cls_tokens,?x),?dim=1)
          ################################
          #分類部分
          self.mlp_head?=?nn.Sequential(
          ???????????nn.LayerNorm(dim),
          ???????????nn.Linear(dim,?num_classes)
          ??????)
          x?=?x.mean(dim?=?1)?if?self.pool?==?'mean'?else?x[:,?0]

          x?=?self.to_latent(x)
          return?self.mlp_head(x)

          在swin transformer中,沒有選擇cls_token。而是直接在經過Encoder后將所有數據取了個平均池化,再通過全連接層。

          self.avgpool?=?nn.AdaptiveAvgPool1d(1)
          self.head?=?nn.Linear(self.num_features,?num_classes)?if?num_classes?>?0?else?nn.Identity()

          x?=?self.avgpool(x.transpose(1,?2))??#?B?C?1
          x?=?torch.flatten(x,?1)
          x?=?self.head(x)

          組合以上這些就成了一個完整的模型

          class?ViT(nn.Module):
          ???def?__init__(self,?*,?image_size,?patch_size,?num_classes,?dim,?depth,?heads,?mlp_dim,?pool?=?'cls',?channels?=?3,?dim_head?=?64,?dropout?=?0.,?emb_dropout?=?0.):
          ???????super().__init__()
          ???????image_height,?image_width?=?pair(image_size)
          ???????patch_height,?patch_width?=?pair(patch_size)

          ???????num_patches?=?(image_height?//?patch_height)?*?(image_width?//?patch_width)
          ???????patch_dim?=?channels?*?patch_height?*?patch_width
          ???????assert?pool?in?{'cls',?'mean'},?'pool?type?must?be?either?cls?(cls?token)?or?mean?(mean?pooling)'

          ???????self.to_patch_embedding?=?nn.Sequential(
          ???????????Rearrange('b?c?(h?p1)?(w?p2)?->?b?(h?w)?(p1?p2?c)',?p1?=?patch_height,?p2?=?patch_width),
          ???????????nn.Linear(patch_dim,?dim),
          ??????)

          ???????self.pos_embedding?=?nn.Parameter(torch.randn(1,?num_patches?+?1,?dim))
          ???????self.cls_token?=?nn.Parameter(torch.randn(1,?1,?dim))
          ???????self.dropout?=?nn.Dropout(emb_dropout)
          ???????self.transformer?=?Transformer(dim,?depth,?heads,?dim_head,?mlp_dim,?dropout)

          ???????self.pool?=?pool
          ???????self.to_latent?=?nn.Identity()
          ???????self.mlp_head?=?nn.Sequential(
          ???????????nn.LayerNorm(dim),
          ???????????nn.Linear(dim,?num_classes)
          ??????)

          ???def?forward(self,?img):
          ???????x?=?self.to_patch_embedding(img)
          ???????b,?n,?_?=?x.shape

          ???????cls_tokens?=?repeat(self.cls_token,?'1?n?d?->?b?n?d',?b?=?b)
          ???????x?=?torch.cat((cls_tokens,?x),?dim=1)
          ???????x?+=?self.pos_embedding[:,?:(n?+?1)]
          ???????x?=?self.dropout(x)
          ???????x?=?self.transformer(x)
          ???????x?=?x.mean(dim?=?1)?if?self.pool?==?'mean'?else?x[:,?0]

          ???????x?=?self.to_latent(x)
          ???????return?self.mlp_head(x)


          數據的變換


          以上的代碼都是比較簡單的,整體上最麻煩的地方在于理解數據的變換。
          首先輸入的數據為(B, C, H, W),在經過分塊后,變成了(B, n, d)。
          在CNN模型中,很好理解(H,W)就是feature map,C是指feature map的數量,那這里的n,d哪個是通道,哪個是圖像特征?
          回顧一下分塊的部分
          Rearrange('b?c?(h?p1)?(w?p2)?->?b?(h?w)?(p1?p2?c)',?p1?=?patch_height,?p2?=?patch_width)
          根據這個可以知道n為分塊的數量,d為每一塊的內容。因此,這里的n相當于CNN模型中的C,而d相當于features。
          一般情況下,在Encoder中,我們都是以(B, n, d)的形式。
          在swin transformer中這種以卷積的形式分塊,獲得的形式為(B, C, L),然后做了一個transpose得到(B, L, C),這與ViT通過直接分塊方式獲得的形式實際上完全一樣,在Swin transformer中的L即為ViT中的n,而C為ViT中的d。
          因此,要注意的是在Multi-head self-attention中,數據的形式是(Batchsize, Channel, Features),分成多個head的是Features。
          前面提到,在ViT中會concate一個隨機生成的cls_token,該cls_token的維度即為(B, 1, d)。可以理解為通道數多了個1。

          以上就是Transformer的模型搭建細節(jié)了,整體上比較簡單,大家看完這篇文章后可以找?guī)灼猅ransformer的代碼來理解理解。如ViT, swin transformer, TimeSformer等。
          ViT:https://github.com/lucidrains/vit-pytorch/blob/main/vit_pytorch/vit.py
          swin:?https://github.com/microsoft/Swin-Transformer/blob/main/models/swin_transformer.py
          TimeSformer:https://github.com/lucidrains/TimeSformer-pytorch/blob/main/timesformer_pytorch/timesformer_pytorch.py

          下一篇我們將介紹如何寫train函數,以及包括設置優(yōu)化方式,設置學習率,不同層設置不同學習率,解析參數等。
          努力分享優(yōu)質的計算機視覺相關內容,歡迎關注:

          交流群


          歡迎加入公眾號讀者群一起和同行交流,目前有美顏、三維視覺計算攝影、檢測、分割、識別、醫(yī)學影像、GAN算法競賽等微信群


          個人微信(如果沒有備注不拉群!
          請注明:地區(qū)+學校/企業(yè)+研究方向+昵稱



          下載1:何愷明頂會分享


          AI算法與圖像處理」公眾號后臺回復:何愷明,即可下載。總共有6份PDF,涉及 ResNet、Mask RCNN等經典工作的總結分析


          下載2:終身受益的編程指南:Google編程風格指南


          AI算法與圖像處理」公眾號后臺回復:c++,即可下載。歷經十年考驗,最權威的編程規(guī)范!



          下載3 CVPR2021

          AI算法與圖像處公眾號后臺回復:CVPR即可下載1467篇CVPR?2020論文 和 CVPR 2021 最新論文


          瀏覽 107
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  肏屄综合网 | 国产蜜臀在线观看 | 137无码XXXX肉体裸交摄影XXX | 天堂在线中文 | 浪货跪趴开荤肉欲H文视频 |