
- Apache MXNet 教程
- Apache MXNet - 首頁
- Apache MXNet - 簡介
- Apache MXNet - 安裝 MXNet
- Apache MXNet - 工具包和生態系統
- Apache MXNet - 系統架構
- Apache MXNet - 系統元件
- Apache MXNet - 統一運算元 API
- Apache MXNet - 分散式訓練
- Apache MXNet - Python 包
- Apache MXNet - NDArray
- Apache MXNet - Gluon
- Apache MXNet - KVStore 和視覺化
- Apache MXNet - Python API ndarray
- Apache MXNet - Python API gluon
- Apache MXNet - Python API autograd 和初始化器
- Apache MXNet - Python API Symbol
- Apache MXNet - Python API Module
- Apache MXNet 有用資源
- Apache MXNet - 快速指南
- Apache MXNet - 有用資源
- Apache MXNet - 討論
Apache MXNet - NDArray
本章將討論 MXNet 的多維陣列格式,稱為ndarray。
使用 NDArray 處理資料
首先,我們將瞭解如何使用 NDArray 處理資料。以下是先決條件:
先決條件
要了解如何使用這種多維陣列格式處理資料,我們需要滿足以下先決條件:
在 Python 環境中安裝 MXNet
Python 2.7.x 或 Python 3.x
實現示例
讓我們透過以下示例瞭解基本功能:
首先,我們需要像這樣匯入 MXNet 和 MXNet 中的 ndarray:
import mxnet as mx from mxnet import nd
匯入必要的庫後,我們將使用以下基本功能:
使用 Python 列表建立簡單的 1 維陣列
示例
x = nd.array([1,2,3,4,5,6,7,8,9,10]) print(x)
輸出
輸出如下:
[ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.] <NDArray 10 @cpu(0)>
使用 Python 列表建立 2 維陣列
示例
y = nd.array([[1,2,3,4,5,6,7,8,9,10], [1,2,3,4,5,6,7,8,9,10], [1,2,3,4,5,6,7,8,9,10]]) print(y)
輸出
輸出如下:
[[ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.] [ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.] [ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.]] <NDArray 3x10 @cpu(0)>
建立未初始化的 NDArray
在這裡,我們將使用.empty函式建立一個具有 3 行 4 列的矩陣。我們還將使用.full函式,它將接受一個附加運算元,用於指定要填充陣列的值。
示例
x = nd.empty((3, 4)) print(x) x = nd.full((3,4), 8) print(x)
輸出
輸出如下:
[[0.000e+00 0.000e+00 0.000e+00 0.000e+00] [0.000e+00 0.000e+00 2.887e-42 0.000e+00] [0.000e+00 0.000e+00 0.000e+00 0.000e+00]] <NDArray 3x4 @cpu(0)> [[8. 8. 8. 8.] [8. 8. 8. 8.] [8. 8. 8. 8.]] <NDArray 3x4 @cpu(0)>
使用 .zeros 函式建立全零矩陣
示例
x = nd.zeros((3, 8)) print(x)
輸出
輸出如下:
[[0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0.]] <NDArray 3x8 @cpu(0)>
使用 .ones 函式建立全一矩陣
示例
x = nd.ones((3, 8)) print(x)
輸出
輸出如下:
[[1. 1. 1. 1. 1. 1. 1. 1.] [1. 1. 1. 1. 1. 1. 1. 1.] [1. 1. 1. 1. 1. 1. 1. 1.]] <NDArray 3x8 @cpu(0)>
建立值隨機取樣的陣列
示例
y = nd.random_normal(0, 1, shape=(3, 4)) print(y)
輸出
輸出如下:
[[ 1.2673576 -2.0345826 -0.32537818 -1.4583491 ] [-0.11176403 1.3606371 -0.7889914 -0.17639421] [-0.2532185 -0.42614475 -0.12548696 1.4022992 ]] <NDArray 3x4 @cpu(0)>
查詢每個 NDArray 的維度
示例
y.shape
輸出
輸出如下:
(3, 4)
查詢每個 NDArray 的大小
示例
y.size
輸出
12
查詢每個 NDArray 的資料型別
示例
y.dtype
輸出
numpy.float32
NDArray 操作
在本節中,我們將介紹 MXNet 的陣列操作。NDArray 支援大量標準數學運算和就地運算。
標準數學運算
以下是 NDArray 支援的標準數學運算:
逐元素加法
首先,我們需要像這樣匯入 MXNet 和 MXNet 中的 ndarray:
import mxnet as mx from mxnet import nd x = nd.ones((3, 5)) y = nd.random_normal(0, 1, shape=(3, 5)) print('x=', x) print('y=', y) x = x + y print('x = x + y, x=', x)
輸出
輸出如下:
x= [[1. 1. 1. 1. 1.] [1. 1. 1. 1. 1.] [1. 1. 1. 1. 1.]] <NDArray 3x5 @cpu(0)> y= [[-1.0554522 -1.3118273 -0.14674698 0.641493 -0.73820823] [ 2.031364 0.5932667 0.10228804 1.179526 -0.5444829 ] [-0.34249446 1.1086396 1.2756858 -1.8332436 -0.5289873 ]] <NDArray 3x5 @cpu(0)> x = x + y, x= [[-0.05545223 -0.3118273 0.853253 1.6414931 0.26179177] [ 3.031364 1.5932667 1.102288 2.1795259 0.4555171 ] [ 0.6575055 2.1086397 2.2756858 -0.8332436 0.4710127 ]] <NDArray 3x5 @cpu(0)>
逐元素乘法
示例
x = nd.array([1, 2, 3, 4]) y = nd.array([2, 2, 2, 1]) x * y
輸出
您將看到以下輸出:
[2. 4. 6. 4.] <NDArray 4 @cpu(0)>
指數運算
示例
nd.exp(x)
輸出
執行程式碼時,您將看到以下輸出:
[ 2.7182817 7.389056 20.085537 54.59815 ] <NDArray 4 @cpu(0)>
矩陣轉置計算矩陣乘積
示例
nd.dot(x, y.T)
輸出
以下是程式碼的輸出:
[16.] <NDArray 1 @cpu(0)>
就地運算
在上面的示例中,每次執行操作時,我們都會分配新的記憶體來儲存其結果。
例如,如果我們寫 A = A + B,我們將取消引用矩陣 A 指向的記憶體,並改為將其指向新分配的記憶體。讓我們使用 Python 的 id() 函式透過以下示例來了解它:
print('y=', y) print('id(y):', id(y)) y = y + x print('after y=y+x, y=', y) print('id(y):', id(y))
輸出
執行後,您將收到以下輸出:
y= [2. 2. 2. 1.] <NDArray 4 @cpu(0)> id(y): 2438905634376 after y=y+x, y= [3. 4. 5. 5.] <NDArray 4 @cpu(0)> id(y): 2438905685664
實際上,我們還可以將結果分配給先前分配的陣列,如下所示:
print('x=', x) z = nd.zeros_like(x) print('z is zeros_like x, z=', z) print('id(z):', id(z)) print('y=', y) z[:] = x + y print('z[:] = x + y, z=', z) print('id(z) is the same as before:', id(z))
輸出
輸出如下:
x= [1. 2. 3. 4.] <NDArray 4 @cpu(0)> z is zeros_like x, z= [0. 0. 0. 0.] <NDArray 4 @cpu(0)> id(z): 2438905790760 y= [3. 4. 5. 5.] <NDArray 4 @cpu(0)> z[:] = x + y, z= [4. 6. 8. 9.] <NDArray 4 @cpu(0)> id(z) is the same as before: 2438905790760
從上面的輸出中,我們可以看到 x + y 仍然會分配一個臨時緩衝區來儲存結果,然後再將其複製到 z。因此,現在我們可以執行就地操作以更好地利用記憶體並避免臨時緩衝區。為此,我們將指定每個運算子都支援的 out 關鍵字引數,如下所示:
print('x=', x, 'is in id(x):', id(x)) print('y=', y, 'is in id(y):', id(y)) print('z=', z, 'is in id(z):', id(z)) nd.elemwise_add(x, y, out=z) print('after nd.elemwise_add(x, y, out=z), x=', x, 'is in id(x):', id(x)) print('after nd.elemwise_add(x, y, out=z), y=', y, 'is in id(y):', id(y)) print('after nd.elemwise_add(x, y, out=z), z=', z, 'is in id(z):', id(z))
輸出
執行上述程式後,您將獲得以下結果:
x= [1. 2. 3. 4.] <NDArray 4 @cpu(0)> is in id(x): 2438905791152 y= [3. 4. 5. 5.] <NDArray 4 @cpu(0)> is in id(y): 2438905685664 z= [4. 6. 8. 9.] <NDArray 4 @cpu(0)> is in id(z): 2438905790760 after nd.elemwise_add(x, y, out=z), x= [1. 2. 3. 4.] <NDArray 4 @cpu(0)> is in id(x): 2438905791152 after nd.elemwise_add(x, y, out=z), y= [3. 4. 5. 5.] <NDArray 4 @cpu(0)> is in id(y): 2438905685664 after nd.elemwise_add(x, y, out=z), z= [4. 6. 8. 9.] <NDArray 4 @cpu(0)> is in id(z): 2438905790760
NDArray 上下文
在 Apache MXNet 中,每個陣列都有一個上下文,一個上下文可能是 CPU,而其他上下文可能是多個 GPU。當我們將工作部署到多臺伺服器時,情況可能會更糟。因此,我們需要智慧地將陣列分配給上下文。這將最大限度地減少在裝置之間傳輸資料所花費的時間。
例如,嘗試如下初始化陣列:
from mxnet import nd z = nd.ones(shape=(3,3), ctx=mx.cpu(0)) print(z)
輸出
執行上述程式碼時,您應該看到以下輸出:
[[1. 1. 1.] [1. 1. 1.] [1. 1. 1.]] <NDArray 3x3 @cpu(0)>
我們可以使用 copyto() 方法將給定的 NDArray 從一個上下文複製到另一個上下文,如下所示:
x_gpu = x.copyto(gpu(0)) print(x_gpu)
NumPy 陣列與 NDArray
我們都熟悉 NumPy 陣列,但 Apache MXNet 提供了自己的陣列實現,稱為 NDArray。實際上,它最初的設計與 NumPy 類似,但有一個關鍵區別:
關鍵區別在於 NumPy 和 NDArray 中計算的執行方式。MXNet 中的每個 NDArray 操作都是非同步且非阻塞的,這意味著當我們編寫類似 c = a * b 的程式碼時,該函式會被推送到執行引擎,後者將啟動計算。
這裡,a 和 b 都是 NDArray。使用它的好處是,函式會立即返回,使用者執行緒可以繼續執行,即使之前的計算可能尚未完成。
執行引擎的工作原理
如果我們談論執行引擎的工作原理,它會構建計算圖。計算圖可能會重新排序或組合某些計算,但它始終遵循依賴順序。
例如,如果稍後在程式設計程式碼中對“X”進行了其他操作,則執行引擎將在“X”的結果可用後開始執行這些操作。執行引擎將為使用者處理一些重要的工作,例如編寫回調以啟動後續程式碼的執行。
在 Apache MXNet 中,藉助 NDArray,要獲得計算結果,我們只需要訪問結果變數即可。程式碼流程將被阻塞,直到計算結果被分配給結果變數。透過這種方式,它在仍然支援指令式程式設計模式的同時提高了程式碼效能。
將 NDArray 轉換為 NumPy 陣列
讓我們學習如何在 MXNet 中將 NDArray 轉換為 NumPy 陣列。
結合使用少量低階運算子來組合高階運算子
有時,我們可以使用現有的運算子來組裝高階運算子。最好的例子之一是np.full_like()運算子,它不在 NDArray API 中。它可以很容易地用現有的運算子組合來替換,如下所示:
from mxnet import nd import numpy as np np_x = np.full_like(a=np.arange(7, dtype=int), fill_value=15) nd_x = nd.ones(shape=(7,)) * 15 np.array_equal(np_x, nd_x.asnumpy())
輸出
我們將獲得類似的輸出:
True
查詢名稱和/或簽名不同的類似運算子
在所有運算子中,有些運算子的名稱略有不同,但在功能方面是相似的。一個例子是nd.ravel_index()與np.ravel()函式。同樣,有些運算子可能具有相似的名稱,但它們的簽名不同。一個例子是np.split()和nd.split()是相似的。
讓我們透過以下程式設計示例來了解它:
def pad_array123(data, max_length): data_expanded = data.reshape(1, 1, 1, data.shape[0]) data_padded = nd.pad(data_expanded, mode='constant', pad_width=[0, 0, 0, 0, 0, 0, 0, max_length - data.shape[0]], constant_value=0) data_reshaped_back = data_padded.reshape(max_length) return data_reshaped_back pad_array123(nd.array([1, 2, 3]), max_length=10)
輸出
輸出如下:
[1. 2. 3. 0. 0. 0. 0. 0. 0. 0.] <NDArray 10 @cpu(0)>
最大限度地減少阻塞呼叫的影響
在某些情況下,我們必須使用.asnumpy()或.asscalar()方法,但這將強制 MXNet 阻塞執行,直到可以檢索結果。我們可以透過在認為此值計算已完成時呼叫.asnumpy()或.asscalar()方法來最大限度地減少阻塞呼叫的影響。
實現示例
示例
from __future__ import print_function import mxnet as mx from mxnet import gluon, nd, autograd from mxnet.ndarray import NDArray from mxnet.gluon import HybridBlock import numpy as np class LossBuffer(object): """ Simple buffer for storing loss value """ def __init__(self): self._loss = None def new_loss(self, loss): ret = self._loss self._loss = loss return ret @property def loss(self): return self._loss net = gluon.nn.Dense(10) ce = gluon.loss.SoftmaxCELoss() net.initialize() data = nd.random.uniform(shape=(1024, 100)) label = nd.array(np.random.randint(0, 10, (1024,)), dtype='int32') train_dataset = gluon.data.ArrayDataset(data, label) train_data = gluon.data.DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=2) trainer = gluon.Trainer(net.collect_params(), optimizer='sgd') loss_buffer = LossBuffer() for data, label in train_data: with autograd.record(): out = net(data) # This call saves new loss and returns previous loss prev_loss = loss_buffer.new_loss(ce(out, label)) loss_buffer.loss.backward() trainer.step(data.shape[0]) if prev_loss is not None: print("Loss: {}".format(np.mean(prev_loss.asnumpy())))
輸出
輸出如下所示:
Loss: 2.3373236656188965 Loss: 2.3656985759735107 Loss: 2.3613128662109375 Loss: 2.3197104930877686 Loss: 2.3054862022399902 Loss: 2.329197406768799 Loss: 2.318927526473999