NDArray.ipynbhtml
機器學習處理的對象是數據,數據通常是由外部傳感器(sensors)採集,通過數字化後存儲在計算機中,多是文本、聲音,圖片、視頻等不一樣形式。
這些數字化的數據最終會加載到內存進行各類清洗,運算操做。
幾乎全部的機器學習算法都涉及到對數據的各類數學運算,好比:加減、點乘、矩陣乘等。因此咱們須要一個易用的、高效的、功能強大的工具來處理這些數據並組支持各類複雜的數學運算。前端
在C/C++中已經開發出來了不少高效的針對於向量、矩陣的運算庫,好比:OpenBLAS,Altlas,MKL等。python
對於Python來講Numpy無疑是一個強大針對數據科學的工具包,它提供了一個強大的高維數據的數組表示,以及支持Broadcasting的運算,並提供了線性代數、傅立葉變換、隨機數等功能強大的函數。git
MXNet的NDArray與Numpy中的ndarray極爲類似,NDAarray爲MXNet中的各類數學計算提供了核心的數據結構,NDArray表示一個多維的、固定大小的數組,而且支持異構計算。那爲何不直接使用Numpy呢?MXNet的NDArray提供額外提供了兩個好處:github
每一個NDarray都具備如下重要的屬性,咱們能夠經過相應的api來訪問:算法
ndarray.shape
:數組的維度。它返回了一個整數的元組,元組的長度等於數組的維數,元組的每一個元素對應了數組在該維度上的長度。好比對於一個n行m列的矩陣,那麼它的形狀就是(n,m)。ndarray.dtype
:數組中全部元素的類型,它返回的是一個numpy.dtype的類型,它能夠是int32/float32/float64
等,默認是'float32'的。ndarray.size
:數組中元素的個數,它等於ndarray.shape
的全部元素的乘積。ndarray.context
:數組的存儲設備,好比:cpu()
或gpu(1)
import mxnet as mx import mxnet.ndarray as nd a = nd.ones(shape=(2,3),dtype='int32',ctx=mx.gpu(1)) print(a.shape, a.dtype, a.size, a.context)
通常來常見有2種方法來建立NDarray數組:apache
ndarray.array
直接將一個list或numpy.ndarray轉換爲一個NDArrayzeros
,ones
以及一些隨機數模塊ndarray.random
建立NDArray,並預填充了一些數據。import numpy as np l = [[1,2],[3,4]] print(nd.array(l)) # 從List轉到NDArray print(nd.array(np.array(l))) # 從np.array轉到NDArray # 直接利用函數建立指定大小的NDArray print (nd.zeros((3,4), dtype='float32')) print (nd.ones((3,4), ctx=mx.gpu())) # 從一個正態分佈的隨機數引擎生成了一個指定大小的NDArray,咱們還能夠指定分佈的參數,好比均值,標準差等 print (nd.random.normal(shape=(3,4))) print (nd.arange(18).reshape(3,2,3))
通常狀況下,咱們能夠經過直接使用print來查看NDArray中的內容,咱們也可使用nd.asnumpy()
函數,將一個NDArray轉換爲一個numpy.ndarray來查看。後端
a = nd.random.normal(0, 2, shape=(3,3)) print(a) print(a.asnumpy())
NDArray之間能夠進行加減乘除等一系列的數學運算,其中大部分的運算都是逐元素進行的。api
shape=(3,4) x = nd.ones(shape) y = nd.random_normal(0, 1, shape=shape) x + y # 逐元素相加 x * y # 逐元素相乘 nd.exp(y) # 每一個元素取指數 nd.sin(y**2).T # 對y逐元素求平方,而後求sin,最後對整個NDArray轉置 nd.maximum(x,y) # x與y逐元素求最大值
這裏須要注意的是*
運算是兩個NDArray之間逐元素的乘法,要進行矩陣乘法,必須使用ndarray.dot
函數進行矩陣乘數組
nd.dot(x, y.T)
MXNet NDArray提供了各類截取的方法,其用法與Python中list的截取操做以及Numpy.ndarray中的截取操做基本一致。
x = nd.arange(0, 9).reshape((3,3)) x[1:3] # 截取x的axis=0的第1和第2行 x[1:2,1:3] # 截取x的axis=0的第1行,axis=1的第一行和第二行
在對NDArray進行算法運算時,每一個操做都會開闢新的內存來存儲運算的結果。例如:若是咱們寫y = x + y
,咱們會把y
從如今指向的實例轉到新建立的實例上去。咱們能夠把上面的運算當作兩步:z = x + y; y = z
。
咱們可使用python的內置函數id()
來驗證。id()
返回一個對象的標識符,當這個對象存在時,這個標識符必定是唯一的,在CPython中這個標識符實際上就是對象的地址。
x = nd.ones((3,4)) y = nd.ones((3,4)) before = id(y) y = x + y print(before, id(y))
在不少狀況下,咱們但願可以在原地對數組進行運算,那麼咱們可使用下面的一些語句:
y += x print(id(y)) nd.elemwise_add(x, y, out=y) print(id(y)) y[:] = x + y print(id(y))
在NDArray中通常的賦值語句像y = x
,y實際上只是x的一個別名而已,x和y是共享一份數據存儲空間的
x = nd.ones((2,2)) y = x print(id(x)) print(id(y))
若是咱們想獲得一份x的真實拷貝,咱們可使用copy函數
y = x.copy() print(id(y))
廣播是一種強有力的機制,可讓不一樣大小的NDArray在一塊兒進行數學計算。咱們經常會有一個小的矩陣和一個大的矩陣,而後咱們會須要用小的矩陣對大的矩陣作一些計算。
舉個例子,若是咱們想要把一個向量加到矩陣的每一行,咱們能夠這樣作
# 將v加到x的每一行中,並將結果存儲在y中 x = nd.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]]) v = nd.array([1, 0, 1]) y = nd.zeros_like(x) # Create an empty matrix with the same shape as x for i in range(4): y[i, :] = x[i, :] + v print (y)
這樣是行得通的,可是當x矩陣很是大,利用循環來計算就會變得很慢很慢。咱們能夠換一種思路:
x = nd.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]]) v = nd.array([1, 0, 1]) vv = nd.tile(v, (4, 1)) # Stack 4 copies of v on top of each other y = x + vv # Add x and vv elementwise print (y) # 也能夠經過broadcast_to來實現 vv = v.broadcast_to((4,3)) print(vv)
NDArray的廣播機制使得咱們不用像上面那樣先建立vv,能夠直接進行運算
x = nd.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]]) v = nd.array([1, 0, 1]) y = x + v print(y)
對兩個數組使用廣播機制要遵照下列規則:
NDArray支持數組在GPU設備上運算,這是MXNet NDArray和Numpy的ndarray最大的不一樣。默認狀況下NDArray的全部操做都是在CPU上執行的,咱們能夠經過ndarray.context來查詢數組所在設備。在有GPU支持的環境上,咱們能夠指定NDArray在gpu設備上。
gpu_device = mx.gpu(0) def f(): a = mx.nd.ones((100,100)) b = mx.nd.ones((100,100), ctx=mx.cpu()) c = a + b.as_in_context(a.context) print(c) f() # 在CPU上運算 # 在GPU上運算 with mx.Context(gpu_device): f()
上面語句中使用了with來構造了一個gpu環境的上下文,在上下文中的全部語句,若是沒有顯式的指定context,則會使用wtih語句指定的context。
當前版本的NDArray要求進行相互運算的數組的context必須一致。咱們可使用as_in_context
來進行NDArray context的切換。
有兩種方法能夠對NDArray對象進行序列化後保存在磁盤,第一種方法是使用pickle
,就像咱們序列化其餘python對象同樣。
import pickle a = nd.ones((2,3)) data = pickle.dumps(a) # 將NDArray直接序列化爲內存中的bytes b = pickle.loads(data) # 從內存中的bytes反序列化爲NDArray pickle.dump(a, open('tmp.pickle', 'wb')) # 將NDArray直接序列化爲文件 b = pickle.load(open('tmp.pickle', 'rb')) # 從文件反序列化爲NDArray
在NDArray模塊中,提供了更優秀的接口用於數組與磁盤文件(分佈式存儲系統)之間進行數據轉換
a = mx.nd.ones((2,3)) b = mx.nd.ones((5,6)) nd.save("temp.ndarray", [a, b]) # 寫入與讀取的路徑支持Amzzon S3以及Hadoop HDFS等。 c = nd.load("temp.ndarray")
MXNet使用了惰性求值來追求最佳的性能。當咱們在Python中運行a = b + 1
時,Python線程只是將運算Push到了後端的執行引擎,而後就返回了。這樣作有下面兩個好處:
後端引擎必需要解決的問題就是數據依賴和合理的調度。但這些操做對於前端的用戶來講是徹底透明的。咱們可使用wait_to_read
來等侍後端對於NDArray操做的完成。在NDArray模塊一類將數據拷貝到其餘模塊的操做,內部已經使用了wait_to_read,好比asnumpy()
。
import time def do(x, n): """push computation into the backend engine""" return [mx.nd.dot(x,x) for i in range(n)] def wait(x): """wait until all results are available""" for y in x: y.wait_to_read() tic = time.time() a = mx.nd.ones((1000,1000)) b = do(a, 50) print('time for all computations are pushed into the backend engine:\n %f sec' % (time.time() - tic)) wait(b) print('time for all computations are finished:\n %f sec' % (time.time() - tic))
除了分析數據的讀寫依賴外,後端的引擎還可以將沒有彼此依賴的操做語句進行並行化調度。好比下面的代碼第二行和第三行能夠被並行的執行。
a = mx.nd.ones((2,3)) b = a + 1 c = a + 2 d = b * c
下面的代碼演示了在不一樣設備上並行調度
n = 10 a = mx.nd.ones((1000,1000)) b = mx.nd.ones((6000,6000), gpu_device) tic = time.time() c = do(a, n) wait(c) print('Time to finish the CPU workload: %f sec' % (time.time() - tic)) d = do(b, n) wait(d) print('Time to finish both CPU/GPU workloads: %f sec' % (time.time() - tic))
tic = time.time() c = do(a, n) d = do(b, n) #上面兩條語句能夠同時執行,一條在CPU上運算,一條在GPU上運算 wait(c) wait(d) print('Both as finished in: %f sec' % (time.time() - tic))