einsum滿足你一切需要:深度學習中的愛因斯坦求和約定

2021-03-02 論智

編者按:FAIR研究科學家Tim Rocktäschel簡要介紹了einsum表示法的概念,並通過真實例子展示了einsum的表達力。

當我和同事聊天的時候,我意識到不是所有人都了解einsum,我開發深度學習模型時最喜歡的函數。本文打算改變這一現狀,讓所有人都了解它!愛因斯坦求和約定(einsum)在numpy和TensorFlow之類的深度學習庫中都有實現,感謝Thomas Viehmann,最近PyTorch也實現了這一函數。關於einsum的背景知識,我推薦閱讀Olexa Bilaniuk的numpy的愛因斯坦求和約定以及Alex Riley的einsum基本指南。這兩篇文章介紹了numpy中的einsum,我的這篇文章則將演示在編寫優雅的PyTorch/TensorFlow模型時,einsum是多麼有用(我將使用PyTorch作為例子,不過很容易就可以翻譯到TensorFlow)。

如果你像我一樣,發現記住PyTorch/TensorFlow中那些計算點積、外積、轉置、矩陣-向量乘法、矩陣-矩陣乘法的函數名字和籤名很費勁,那麼einsum記法就是我們的救星。einsum記法是一個表達以上這些運算,包括複雜張量運算在內的優雅方式,基本上,可以把einsum看成一種領域特定語言。一旦你理解並能利用einsum,除了不用記憶和頻繁查找特定庫函數這個好處以外,你還能夠更迅速地編寫更加緊湊、高效的代碼。而不使用einsum的時候,容易出現引入不必要的張量變形或轉置運算,以及可以省略的中間張量的現象。此外,einsum這樣的領域特定語言有時可以編譯到高性能代碼,事實上,PyTorch最近引入的能夠自動生成GPU代碼並為特定輸入尺寸自動調整代碼的張量理解(Tensor Comprehensions)就基於類似einsum的領域特定語言。此外,可以使用opt einsum和tf einsum opt這樣的項目優化einsum表達式的構造順序。

比方說,我們想要將兩個矩陣A ∈ ℝI × K和B ∈ ℝK × J相乘,接著計算每列的和,最終得到向量c ∈ ℝJ。使用愛因斯坦求和約定,這可以表達為:

這一表達式指明了c中的每個元素ci是如何計算的,列向量Ai:乘以行向量B:j,然後求和。注意,在愛因斯坦求和約定中,我們省略了求和符號Sigma,因為我們隱式地累加重複的下標(這裡是k)和輸出中未指明的下標(這裡是i)。當然,einsum也能表達更基本的運算。比如,計算兩個向量a, b ∈ ℝJ的點積可以表達為:

在深度學習中,我經常碰到的一個問題是,變換高階張量到向量。例如,我可能有一個張量,其中包含一個batch中的N個訓練樣本,每個樣本是一個長度為T的K維詞向量序列,我想把詞向量投影到一個不同的維度Q。如果將這個張量記作T ∈ ℝN × T × K,將投影矩陣記作W ∈ ℝK × Q,那麼所需計算可以用einsum表達為:

最後一個例子,比方說有一個四階張量T ∈ ℝN × T × K × M,我們想要使用之前的投影矩陣將第三維投影至Q維,並累加第二維,然後轉置結果中的第一維和最後一維,最終得到張量C ∈ ℝM × Q × N。einsum可以非常簡潔地表達這一切:

注意,我們通過交換下標n和m(Cmqn而不是Cnqm),轉置了張量構造結果。

2. Numpy、PyTorch、TensorFlow中的einsum

einsum在numpy中實現為np.einsum,在PyTorch中實現為torch.einsum,在TensorFlow中實現為tf.einsum,均使用一致的籤名einsum(equation, operands),其中equation是表示愛因斯坦求和約定的字符串,而operands則是張量序列(在numpy和TensorFlow中是變長參數列表,而在PyTorch中是列表)。例如,我們的第一個例子,cj = ∑i∑kAikBkj寫成equation字符串就是ik,kj -> j。注意這裡(i, j, k)的命名是任意的,但需要一致。

PyTorch和TensorFlow像numpy支持einsum的好處之一是einsum可以用於神經網絡架構的任意計算圖,並且可以反向傳播。典型的einsum調用格式如下:

上式中◻是佔位符,表示張量維度。上面的例子中,arg1和arg3是矩陣,arg2是二階張量,這一einsum運算的結果(result)是矩陣。注意einsum處理的是可變數量的輸入。在上面的例子中,einsum指定了三個參數之上的操作,但它同樣可以用在牽涉一個參數、兩個參數、三個以上參數的操作上。學習einsum的最佳途徑是通過學習一些例子,所以下面我們將展示一下,在許多深度學習模型中常用的庫函數,用einsum該如何表達(以PyTorch為例)。

2.1 矩陣轉置

import torch

a = torch.arange(6).reshape(2, 3)

torch.einsum('ij->ji', [a])

tensor([[ 0.,  3.],

       [ 1.,  4.],

       [ 2.,  5.]])

2.2 求和

a = torch.arange(6).reshape(2, 3)

torch.einsum('ij->', [a])

tensor(15.)

2.3 列求和

a = torch.arange(6).reshape(2, 3)

torch.einsum('ij->j', [a])

tensor([ 3.,  5.,  7.])

2.4 行求和

a = torch.arange(6).reshape(2, 3)

torch.einsum('ij->i', [a])

tensor([  3.,  12.])

2.5 矩陣-向量相乘

a = torch.arange(6).reshape(2, 3)

b = torch.arange(3)

torch.einsum('ik,k->i', [a, b])

tensor([  5.,  14.])

2.6 矩陣-矩陣相乘

a = torch.arange(6).reshape(2, 3)

b = torch.arange(15).reshape(3, 5)

torch.einsum('ik,kj->ij', [a, b])

tensor([[  25.,   28.,   31.,   34.,   37.],

       [  70.,   82.,   94.,  106.,  118.]])

2.7 點積

向量:

a = torch.arange(3)

b = torch.arange(3,6)  # [3, 4, 5]

torch.einsum('i,i->', [a, b])

tensor(14.)

矩陣:

a = torch.arange(6).reshape(2, 3)

b = torch.arange(6,12).reshape(2, 3)

torch.einsum('ij,ij->', [a, b])

tensor(145.)

2.8 哈達瑪積

a = torch.arange(6).reshape(2, 3)

b = torch.arange(6,12).reshape(2, 3)

torch.einsum('ij,ij->ij', [a, b])

tensor([[  0.,   7.,  16.],

       [ 27.,  40.,  55.]])

2.9 外積

a = torch.arange(3)

b = torch.arange(3,7)

torch.einsum('i,j->ij', [a, b])

tensor([[  0.,   0.,   0.,   0.],

       [  3.,   4.,   5.,   6.],

       [  6.,   8.,  10.,  12.]])

2.10 batch矩陣相乘

a = torch.randn(3,2,5)

b = torch.randn(3,5,3)

torch.einsum('ijk,ikl->ijl', [a, b])

tensor([[[ 1.0886,  0.0214,  1.0690],

        [ 2.0626,  3.2655, -0.1465]],

       [[-6.9294,  0.7499,  1.2976],

        [ 4.2226, -4.5774, -4.8947]],

       [[-2.4289, -0.7804,  5.1385],

        [ 0.8003,  2.9425,  1.7338]]])

2.11 張量縮約

batch矩陣相乘是張量縮約的一個特例。比方說,我們有兩個張量,一個n階張量A ∈ ℝI1 × ⋯ × In,一個m階張量B ∈ ℝJ1 × ⋯ × Jm。舉例來說,我們取n = 4,m = 5,並假定I2 = J3且I3 = J5。我們可以將這兩個張量在這兩個維度上相乘(A張量的第2、3維度,B張量的3、5維度),最終得到一個新張量C ∈ ℝI1 × I4 × J1 × J2 × J4,如下所示:

a = torch.randn(2,3,5,7)

b = torch.randn(11,13,3,17,5)

torch.einsum('pqrs,tuqvr->pstuv', [a, b]).shape

torch.Size([2, 7, 11, 13, 17])

2.12 雙線性變換

如前所述,einsum可用於超過兩個張量的計算。這裡舉一個這方面的例子,雙線性變換。

a = torch.randn(2,3)

b = torch.randn(5,3,7)

c = torch.randn(2,7)

torch.einsum('ik,jkl,il->ij', [a, b, c])

tensor([[ 3.8471,  4.7059, -3.0674, -3.2075, -5.2435],

       [-3.5961, -5.2622, -4.1195,  5.5899,  0.4632]])

3.1 TreeQN

我曾經在實現TreeQN( arXiv:1710.11417)的等式6時使用了einsum:給定網絡層l上的低維狀態表示zl,和激活a上的轉換函數Wa,我們想要計算殘差連結的下一層狀態表示。

在實踐中,我們想要高效地計算大小為B的batch中的K維狀態表示Z ∈ ℝB × K,並同時計算所有轉換函數(即,所有激活A)。我們可以將這些轉換函數安排為一個張量W ∈ ℝA × K × K,並使用einsum高效地計算下一層狀態表示。

import torch.nn.functional as F

def random_tensors(shape, num=1, requires_grad=False):

 tensors = [torch.randn(shape, requires_grad=requires_grad) for i in range(0, num)]

 return tensors[0] if num == 1 else tensors

# 參數

# -- [激活數 x 隱藏層維度]

b = random_tensors([5, 3], requires_grad=True)

# -- [激活數 x 隱藏層維度 x 隱藏層維度]

W = random_tensors([5, 3, 3], requires_grad=True)

def transition(zl):

   # -- [batch大小 x 激活數 x 隱藏層維度]

   return zl.unsqueeze(1) + F.tanh(torch.einsum("bk,aki->bai", [zl, W]) + b)

# 隨機取樣仿造輸入

# -- [batch大小 x 隱藏層維度]

zl = random_tensors([2, 3])

transition(zl)

3.2 注意力

讓我們再看一個使用einsum的真實例子,實現注意力機制的等式11-13(arXiv:1509.06664):

用傳統寫法實現這些可要費不少力氣,特別是考慮batch實現。einsum是我們的救星!

# 參數

# -- [隱藏層維度]

bM, br, w = random_tensors([7], num=3, requires_grad=True)

# -- [隱藏層維度 x 隱藏層維度]

WY, Wh, Wr, Wt = random_tensors([7, 7], num=4, requires_grad=True)

# 注意力機制的單次應用

def attention(Y, ht, rt1):

   # -- [batch大小 x 隱藏層維度]

   tmp = torch.einsum("ik,kl->il", [ht, Wh]) + torch.einsum("ik,kl->il", [rt1, Wr])

   Mt = F.tanh(torch.einsum("ijk,kl->ijl", [Y, WY]) + tmp.unsqueeze(1).expand_as(Y) + bM)

   # -- [batch大小 x 序列長度]

   at = F.softmax(torch.einsum("ijk,k->ij", [Mt, w]))

   # -- [batch大小 x 隱藏層維度]

   rt = torch.einsum("ijk,ij->ik", [Y, at]) + F.tanh(torch.einsum("ij,jk->ik", [rt1, Wt]) + br)

   # -- [batch大小 x 隱藏層維度], [batch大小 x 序列維度]

   return rt, at

# 取樣仿造輸入

# -- [batch大小 x 序列長度 x 隱藏層維度]

Y = random_tensors([3, 5, 7])

# -- [batch大小 x 隱藏層維度]

ht, rt1 = random_tensors([3, 7], num=2)

rt, at = attention(Y, ht, rt1)

einsum是一個函數走天下,是處理各種張量操作的瑞士軍刀。話雖如此,「einsum滿足你一切需要」顯然誇大其詞了。從上面的真實用例可以看到,我們仍然需要在einsum之外應用非線性和構造額外維度(unsqueeze)。類似地,分割、連接、索引張量仍然需要應用其他庫函數。

使用einsum的麻煩之處是你需要手動實例化參數,操心它們的初始化,並在模型中註冊這些參數。不過我仍然強烈建議你在實現模型時,考慮下有哪些情況適合使用einsum.

相關焦點

  • 一個函數打天下,einsum
    >>加入極市CV技術交流群,走在計算機視覺的最前沿einsum全稱Einstein summation convention(愛因斯坦求和約定),又稱為愛因斯坦標記法,是愛因斯坦1916年提出的一種標記約定,簡單的說就是省去求和式中的求和符號,例如下面的公式:以einsum的寫法就是:後者將  符號給省去了,顯得更加簡潔
  • 哈希算法、愛因斯坦求和約定,這是2020年的注意力機制
    特別需要注意的就是多頭注意力機制了。在設計之初,為了讓不同的注意力頭能夠捕捉語言模型中的不同特徵,不論是 BERT 還是 GPT-2 的設計者都將增加了注意力頭的數量。但是,由於 Query、Key 和 Value 在訓練中會進行多次線性變換,且每個頭的注意力單獨計算,因此產生了大量的冗餘參數,也需要極大地顯存。
  • 深度學習框架中的「張量」不好用?也許我們需要重新定義Tensor了
    儘管張量在深度學習的世界中無處不在,但它是有破綻的。它催生出了一些壞習慣,比如公開專用維度、基於絕對位置進行廣播,以及在文檔中保存類型信息。這篇文章介紹了一種具有命名維度的替代方法 named tensor,並對其進行了概念驗證。這一改變消除了對索引、維度參數、einsum 式解壓縮以及基於文檔的編碼的需求。
  • Excel秒殺一切的求和函數SUMIFS, 多條件求和就靠它了!
    應用場景:1、有時候單純的求和公式SUM不能滿足我們所有的要求,單一的條件求和SUMIF無法滿足多條件求和的需求,這樣SUMIFS如上圖所示,參數1為求和範圍參數,參數2,3為條件參數1(需要成組出現,包含條件範圍和條件),參數4,5為條件參數2(需要成組出現),如果有更多條件,需要把條件範圍和條件成組列明。
  • 從頭開始構建最先進的深度學習模型
    導讀:近日,Fast.ai 發布一門新課程——從零開始學深度學習(Deep Learning from the Foundations),該課程由 Jeremy Howard 教授,展示了如何從頭開始構建最先進的深度學習模型。
  • Excel教程:隔列求和+深度隱藏工作表!
    還是老老實實的來看excel隔列求和公式案例: 左邊是各個分店的進貨量和實銷量數據,右邊需要對當日各店的進貨量和實銷量匯總。 仔細觀察進貨量是偶數列,實銷量是奇數列。也就是我們本文討論的excel隔列求和問題。
  • 愛因斯坦:別偷懶了,深度思考比思考更重要
    同時,我再次強調,知識不是力量,深度認知才是真正的力量,我十幾年來低效率的學習方式,讓我付出了慘痛的代價,這使得我花了大量的時間去研究我們大腦神經元的學習機制,相關的理論基礎,大家需要先通讀一下《還有另一個現實讓人如此的可惜,我們的教育體系,奪走了人在學習知識時的最重要的一個素質,那就是學習的興趣,而這幾乎是一切成就的前提,正如愛因斯坦說的:「興趣是最好的老師」。而一旦對知識失去了興趣,你幾乎也失去了成為高深知識領域的卓越者的機會。
  • 解讀:愛因斯坦遺言:宇宙中一切物質都不存在,唯有愛永恆!
    愛因斯坦遺言曝光:宇宙中一切物質都不存在,唯有愛永恆!(宇宙中一切物質都不存在,這和金剛經上「一切如夢幻泡影」是一致的,愛因斯坦是有嚴格科學態度的科學家,是不會亂說一氣的,現代科學發現最小的「微中子」也只是一種波動,有受、想、行、識,並無實體)
  • 「幾何深度學習」受愛因斯坦啟示:讓AI擺脫平面看到更高的維度
    Welling表示:「這個框架是對曲面上深度學習問題的絕對確定的答案。」在模擬全球氣候數據的學習模式中,這種新架構的性能已大大超過了其前輩。該算法還可能改善可視3D目標的無人機和無人駕駛汽車的視覺效果,對於心臟、大腦或其他器官的不規則曲面上收集的數據,分析能力也能大為提升。
  • 薛丁格的滾與深度學習中的物理
    【導讀】作者從薛丁格的「滾」講到世界的量子性、神經網絡的最大似然等等,用頗具趣味的方式呈現了深度學習中無處不在的物理本質。最近朋友圈裡有大神分享薛丁格的滾,一下子火了,「當一個妹子叫你滾的時候,你永遠不知道她是在叫你滾還是叫你過來抱緊」,這確實是一種十分糾結的狀態,而薛丁格是搞不清楚的,他連自己的貓是怎麼回事還沒有弄清楚。
  • Excel中多條件求和你是先篩選再求和嗎?試試Sumifs函數吧!
    在會計核算工作中,經常對一些數據求和,普通的求和用SUM函數或者快捷鍵就可以完成了,但是有條件的求和,特別是多條件求和,你是不是先根據條件篩選出數據,再求和?求和數據少還好,如果數據量很大,不管是勞動強度還是工作效率 都是受影響的。今天小編和大家分享利用Sumifs函數來輕鬆實現多條件求和,以便幫助大家在今後的核算工作中提高工作效率。
  • 只要滿足條件,就把你們合到一起!條件求和——SUMIF函數
    大家好,在工作中當需要對滿足條件的數值進行求和時,你會想到哪個公式函數呢?SUMIF函數會不會是你的首選呢?本節內容就問大家詳細講述一下這個常用的SUMIF函數。SUMIF函數是將對符合指定條件的單元格進行求和,這裡的條件可以是相匹配的字符串或者滿足的邏輯關係、函數式等。二:SUMIF函數的語法結構是什麼?
  • 天天在用Excel求和,這10個求和公式、方法,總有一個是你需要的
    今天小編再次總結的工作中常用的求和公式和方法,希望對你的工作有所幫助。一、SUM求和快捷鍵表格中需要求和時,很簡單,我們一般會用SUM函數,選擇求和區域,再向下或向右拖拉填充公式,完成整列或整行求和,如果你覺得這種方法已經很簡單的話,那你就錯了,
  • 深度| 理解深度學習中的卷積
    以下是正文:卷積現在可能是深度學習中最重要的概念。正是靠著卷積和卷積神經網絡,深度學習才超越了幾乎其他所有的機器學習手段。但卷積為什麼如此強大?它的原理是什麼?在這篇博客中我將講解卷積及相關概念,幫助你徹底地理解它。網絡上已經有不少博客講解卷積和深度學習中的卷積,但我發現它們都一上來就加入了太多不必要的數學細節,艱深晦澀,不利於理解主旨。
  • 「高考」落榜生愛因斯坦,教你怎麼學習
    現階段沒有更好的辦法選拔人才,那麼通過9年義務教育和高中三年學習,學會基礎知識,通過參加高考,考入儘可能高水平的大學,在這個平臺裡再展現你的學習力,創造力。這也是沒有辦法的辦法。所以我的目的絕不是抨擊高考制度。而是說在目前的狀態下,我們拼的確實是應試能力,強調的是應試思維。大多數老師的目的也是提高學生的應試水平。至少在初三和高三兩個年級確實是這樣的。
  • EXCEL多條件求和、跨表多條件求和函數DSUM,讓你求和效率更高!
    今天分享一個簡單實用又高效的資料庫函數DSUM,它集「查找」和「求和」功能為一身,能多條件求和,還能跨表多條件求和,讓你一看到就會愛上它!一、函數解析DSUM函數:將資料庫中符合條件記錄的欄位列中的數字求和。使用它可以對數據進行多條件累加,這種方式可以很方便地修改求和的條件。
  • 財務工作中那些常用的Excel 求和函數,你都會用嗎?
    求和函數,是Excel中最基本的技能,可能好多同學會說,求和不就是用Sum函數嗎?其實你這麼想是不全面的,實際功能中的求和常常附加有條件,條件可能是一項,也可能是多項,當遇到條件求和時,你還在用Sum函數嗎?相信SUM函數大家肯定都會用,在這裡就不作解釋。
  • 學習Excel函數從求和開始,10種常用的求和公式、方法,速速收藏
    Excel表格中求和可算是日常工作中最常做的,小編以前也分享了很多關於求和的方法和公式,小夥伴們可以點擊我的頭像,去主頁查看更多教程。今天小編再次總結的工作中常用的求和公式和方法,希望對你的工作有所幫助。
  • 愛因斯坦:我相信直覺,一切科學成就都始於直覺
    直覺太重要了,愛因斯坦說:「一切偉大的科學成就都始於直覺的知識,也就是說,從不言自明的公理出發,才能加以推演。……而直覺是發現這些公理的必要條件」。愛因斯坦說:「我相信直覺和靈感。……有時候我感到自己是對的。