消滅 for 循環!Python 的矩陣傳播機制和矩陣運算

一、Python 的矩陣傳播機制(Broadcasting)

我們知道在深度學習中經常要操作各種矩陣(matrix)

回想一下,我們在操作數組(list)的時候,經常習慣於用 for 循環(for-loop) 來對數組的每一個元素進行操作。例如:

my_list = [1,2,3,4]
new_list = []
for each in my_list:
    new_list.append(each*2)
print(new_list)  # 輸出 [2,3,4,5]

如果是矩陣呢:

my_matrix = [[1,2,3,4],
             [5,6,7,8]]
new_matrix = [[],[]]
for i in range(2):    
    for j in range(4):
        new_matrix[i].append(my_matrix[i][j]*2)
print(new_matrix)# 輸出 [[2, 4, 6, 8], [10, 12, 14, 16]]

實際上,上面的做法是十分的低效的!數據量小的話還不明顯,如果數據量大了,尤其是深度學習中我們處理的矩陣往往巨大,那用 for 循環去跑一個矩陣,可能要你幾個小時甚至幾天。

Python 考慮到了這一點,這也是本文主要想介紹的 **“Python 的 broadcasting” 傳播機制 **。

先說一句,python 中定義矩陣、處理矩陣,我們一般都用 numpy 這個庫。

二、下面展示什麼是 python 的傳播機制

import numpy as np# 先定義一個3×3矩陣 A:
A = np.array(
    [[1,2,3],
     [4,5,6],
     [7,8,9]]) 

print("A:\n",A)
print("\nA*2:\n",A*2) # 直接用A乘以2
print("\nA+10:\n",A+10) # 直接用A加上10

運行結果:

A:
 [[1 2 3]
 [4 5 6]
 [7 8 9]]

A*2:
 [[ 2  4  6]
 [ 8 10 12]
 [14 16 18]]

A+10:
 [[11 12 13]
 [14 15 16]
 [17 18 19]]

接着,再看看矩陣 ×(+)矩陣:

#定義一個3×1矩陣(此時也可叫向量了)
B = np.array([[10],
              [100],
              [1000]]) 
print("\nB:\n",B)
print("\nA+B:\n",A+B) 
print("\nA*B:\n",A*B)

運行結果:

B:
 [[  10]
 [ 100]
 [1000]]

A+B:
 [[  11   12   13]
 [ 104  105  106]
 [1007 1008 1009]]

A*B:
 [[  10   20   30]
 [ 400  500  600]
 [7000 8000 9000]]

可見,雖然 A 和 B 的形狀不一樣,一個是 3×3,一個是 3×1,但是我們在 python 中可以直接相加、相乘,相減相除也可以。

也許看到這,大家都對 broadcasting 有感覺了。

用一個圖來示意一下:

所謂 “傳播”,就是把一個數或者一個向量進行 “複製”,從而作用到矩陣的每一個元素上

有了這種機制,那進行向量和矩陣的運算,就太方便了!理解了傳播機制,就可以隨心所欲地對矩陣進行各種便捷的操作了。

利用 numpy 的內置函數對矩陣進行操作:

numpy 內置了很多的數學函數,例如 np.log(),np.abs(),np.maximum() 等等上百種。**直接把矩陣丟進去,就可以算出新矩陣!**示例:

print(np.log(A))

輸出把 A 矩陣每一個元素求 log 後得到的新矩陣:

array([[0.        , 0.69314718, 1.09861229],
       [1.38629436, 1.60943791, 1.79175947],
       [1.94591015, 2.07944154, 2.19722458]])

圖片

X = np.array([[1,-2,3,-4],              [-9,4,5,6]])Y = np.maximum(0,X)print(Y)
[[1 0 3 0] [0 4 5 6]]

三、定義自己的函數來處理矩陣

def relu_derivative(x):
    x[x<0] = 0
    x[x>0] = 1
    return x

X = np.array([[1,-2,3,-4],
              [-9,4,5,6]])

print(relu_derivative(X))

輸出:

[[1 0 1 0]
 [0 1 1 1]]

** 居然這麼簡潔就出來了!!!** ミ゚Д゚彡 (゚Д゚#)

這個函數 relu_derivative 中最難以理解的地方,就是 x[x>0] 了。於是我試了一下:

X = np.array([[1,-2,3,-4],
              [-9,4,5,6]])
print(X[X>0])
print(X[X<0])

輸出:

[1 3 4 5 6]
[-2 -4 -9]

它直接把矩陣 X 中滿足條件的元素取了出來!原來 python 對矩陣還有這種操作!

震驚了我好久~

所以可以這麼理解,X[X>0] 相當於一個 “選擇器”,把滿足條件的元素選出來,然後直接全部賦值。

用這種方法,我們便可以定義各種各樣我們需要的函數,然後對矩陣整體進行更新操作了!

四、綜上

可以看出,python 以及 numpy 對矩陣的操作簡直神乎其神,方便快捷又實惠。其實上面忘了寫一點,那就是計算機進行矩陣運算的效率要遠遠高於用 for-loop 來運算

不信可以用跑一跑:

# vetorization vs for loop
# define two arrays a, b:
a = np.random.rand(1000000)
b = np.random.rand(1000000)

# for loop version:
t1 = time.time()
c = 0
for i in range(1000000):
    c += a[i]*b[i]
t2 = time.time()
print(c)
print("for loop version:"+str(1000*(t2-t1))+"ms")
time1 = 1000*(t2-t1)

# vectorization version:
t1 = time.time()
c = np.dot(a,b)
t2 = time.time()
print(c)
print("vectorization version:"+str(1000*(t2-t1))+"ms")
time2 = 1000*(t2-t1)

print("vectorization is faster than for loop by "+str(time1/time2)+" times!")

運行結果:

249765.8415288075
for loop version:627.4442672729492ms
249765.84152880745
vectorization version:1.5032291412353516ms
vectorization is faster than for loop by 417.39762093576525 times!

可見,用 for 方法和向量化方法,計算結果是一樣,但是後者比前者快了 400 多倍!

推薦關注「Linux 愛好者」,提升 Linux 技能

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/NfRi2h02ZzBYzr4viPZW2g