曠視MegEngine資料加載與處理
在網絡訓練與測試中,資料的加載和預處理往往會耗費大量的精力。 MegEngine 提供了一系列接口來規範化這些處理工作。
利用 Dataset 封裝一個資料集
資料集是一組資料的集合,例如 MNIST、Cifar10等圖像資料集。 Dataset 是 MegEngine 中表示資料集的抽象類。自定義的資料集類應該繼承 Dataset 并重寫下列方法:
- __init__() :一般在其中實作讀取資料源檔案的功能。也可以添加任何其它的必要功能;
- __getitem__() :通過索引操作來擷取資料集中某一個樣本,使得可以通過 for 循環來周遊整個資料集;
- __len__() :傳回資料集大小;
下面是一個簡單示例。 根據下圖所示的二分類資料,建立一個 Dataset 。每個資料是一個二維平面上的點,橫坐标和縱坐标在 [-1, 1] 之間。共有兩個類别标簽(圖1中的藍色 * 和紅色 +),标簽為0的點處于一、三象限;标簽為1的點處于二、四象限。
圖1
該資料集的建立過程如下:
- 在 __init__() 中利用 NumPy 随機生成 ndarray 作為資料;
- 在 __getitem__() 中傳回 ndarray 中的一個樣本;
- 在 __len__() 中傳回整個資料集中樣本的個數;
import numpy as np
from typing import Tuple
# 導入需要被繼承的 Dataset 類
from megengine.data.dataset import Dataset
class XORDataset(Dataset):
def __init__(self, num_points):
"""
生成如圖1所示的二分類資料集,資料集長度為 num_points
super().__init__()
# 初始化一個次元為 (50000, 2) 的 NumPy 數組。
# 數組的每一行是一個橫坐标和縱坐标都落在 [-1, 1] 區間的一個資料點 (x, y)
self.data = np.random.rand(num_points, 2).astype(np.float32) * 2 - 1
# 為上述 NumPy 數組建構标簽。每一行的 (x, y) 如果符合 x*y < 0,則對應标簽為1,反之,标簽為0
self.label = np.zeros(num_points, dtype=np.int32)
for i in range(num_points):
self.label[i] = 1 if np.prod(self.data[i]) < 0 else 0
# 定義擷取資料集中每個樣本的方法
def __getitem__(self, index: int) -> Tuple:
return self.data[index], self.label[index]
# 定義傳回資料集長度的方法
def __len__(self) -> int:
return len(self.data)
np.random.seed(2020)
# 建構一個包含 30000 個點的訓練資料集
xor_train_dataset = XORDataset(30000)
print("The length of train dataset is: {}".format(len(xor_train_dataset)))
# 通過 for 周遊資料集中的每一個樣本
for cor, tag in xor_train_dataset:
print("The first data point is: {}, {}".format(cor, tag))
break
print("The second data point is: {}".format(xor_train_dataset[1]))
輸出:
The length of train dataset is: 30000
The first data point is: [0.97255366 0.74678389], 0
The second data point is: (array([ 0.01949105, -0.45632857]), 1)
MegEngine 中也提供了一些已經繼承自 Dataset 的資料集類,友善使用,比如 ArrayDataset 。 ArrayDataset 允許通過傳入單個或多個 NumPy 數組,對它進行初始化。其内部實作如下:
- __init__() :檢查傳入的多個 NumPy 數組的長度是否一緻;不一緻則無法成功建立;
- __getitem__() :将多個 NumPy 數組相同索引位置的元素構成一個 tuple 并傳回;
- __len__() :傳回資料集的大小;
以圖1所示的資料集為例,可以通過坐标資料和标簽資料的數組直接構造 ArrayDataset ,無需使用者自己定義資料集類。
from megengine.data.dataset import ArrayDataset
# 準備 NumPy 形式的 data 和 label 資料
num_points = 30000
data = np.random.rand(num_points, 2).astype(np.float32) * 2 - 1
label = np.zeros(num_points, dtype=np.int32)
for i in range(num_points):
label[i] = 1 if np.prod(data[i]) < 0 else 0
# 利用 ArrayDataset 建立一個資料集類
xor_dataset = ArrayDataset(data, label)
通過 Sampler 從 Dataset 中采樣
Dataset 僅能通過一個固定的順序(其 __getitem__ 實作)通路所有樣本, 而 Sampler 使得可以以所期望的方式從 Dataset 中采樣,生成訓練和測試的批(minibatch)資料。 Sampler 本質上是一個資料集中資料索引的疊代器,接收 Dataset 的執行個體和批大小(batch_size)來進行初始化。
MegEngine 中提供各種常見的采樣器,如 RandomSampler (通常用于訓練)、 SequentialSampler (通常用于測試) 等。
下面示例,來熟悉 Sampler 的基本用法:
# 導入 MegEngine 中采樣器
from megengine.data import RandomSampler
# 建立一個随機采樣器
random_sampler = RandomSampler(dataset=xor_dataset, batch_size=4)
# 擷取疊代sampler時每次傳回的資料集索引
for indices in random_sampler:
print(indices)
[19827, 2614, 8788, 8641]
可以看到,在 batch_size 為4時,每次疊代 sampler 傳回的是長度為4的清單,清單中的每個元素是随機采樣出的資料索引。
如果建立的是一個序列化采樣器 SequentialSampler ,那麼每次傳回的就是順序索引。
from megengine.data import SequentialSampler
sequential_sampler = SequentialSampler(dataset=xor_dataset, batch_size=4)
# 擷取疊代sampler時傳回的資料集索引資訊
for indices in sequential_sampler:
[0, 1, 2, 3]
使用者也可以繼承 Sampler 自定義采樣器,這裡不做詳述。
用 DataLoader 生成批資料
MegEngine 中,DataLoader 本質上是一個疊代器,它通過 Dataset 和 Sampler 生成 minibatch 資料。
下列代碼通過 for 循環擷取每個 minibatch 的資料。
from megengine.data import DataLoader
# 建立一個 DataLoader,并指定資料集和順序采樣器
xor_dataloader = DataLoader(
dataset=xor_dataset,
sampler=sequential_sampler,
)
print("The length of the xor_dataloader is: {}".format(len(xor_dataloader)))
# 從 DataLoader 中疊代地擷取每批資料
for idx, (cor, tag) in enumerate(xor_dataloader):
print("iter %d : " % (idx), cor, tag)
The length of the xor_dataloader is: 7500
iter 0 : [[ 0.97255366 0.74678389]
[ 0.01949105 -0.45632857]
[-0.32616254 -0.56609147]
[-0.44704571 -0.31336881]] [0 1 0 0]
DataLoader 中的資料變換(Transform)
在深度學習模型的訓練中,經常需要對資料進行各種轉換,比如,歸一化、各種形式的資料增廣等。 Transform 是資料變換的基類,其各種派生類提供了常見的資料轉換功能。 DataLoader 構造函數可以接收一個 Transform 參數, 在建構 minibatch 時,對該批資料進行相應的轉換操作。
接下來通過 MNIST 資料集(MegEngine 提供了 MNIST Dataset)來熟悉 Transform 的使用。 首先建構一個不做 Transform 的 MNIST DataLoader,并可視化第一個 minibatch 資料。
# 從 MegEngine 中導入 MNIST 資料集
from megengine.data.dataset import MNIST
# 若是第一次下載下傳 MNIST 資料集,download 需設定成 True
# 若已經下載下傳 MNIST 資料集,通過 root 指定 MNIST資料集 raw 路徑
# 通過設定 train=True/False 擷取訓練集或測試集
mnist_train_dataset = MNIST(root="./dataset/MNIST", train=True, download=True)
# mnist_test_dataset = MNIST(root="./dataset/MNIST", train=False, download=True)
sequential_sampler = SequentialSampler(dataset=mnist_train_dataset, batch_size=4)
mnist_train_dataloader = DataLoader(
dataset=mnist_train_dataset,
for i, batch_sample in enumerate(mnist_train_dataloader):
batch_image, batch_label = batch_sample[0], batch_sample[1]
# 下面可以将 batch_image, batch_label 傳遞給網絡做訓練,這裡省略
# trainging code ...
# 中斷
print("The shape of minibatch is: {}".format(batch_image.shape))
# 導入可視化 Python 庫,若沒有,安裝
import matplotlib.pyplot as plt
def show(batch_image, batch_label):
for i in range(4):
plt.subplot(1, 4, i+1)
plt.imshow(batch_image[i][:,:,-1], cmap='gray')
plt.xticks([])
plt.yticks([])
plt.title("label: {}".format(batch_label[i]))
plt.show()
# 可視化資料
show(batch_image, batch_label)
The shape of minibatch is: (4, 28, 28, 1)
可視化第一批 MNIST 資料:
圖2
然後,建構一個做 RandomResizedCrop transform 的 MNIST DataLoader,并檢視此時第一個 minibatch 的圖檔。
# 導入 MegEngine 已支援的一些資料增強操作
from megengine.data.transform import RandomResizedCrop
dataloader = DataLoader(
mnist_train_dataset,
# 指定随機裁剪後的圖檔的輸出size
transform=RandomResizedCrop(output_size=28),
for i, batch_sample in enumerate(dataloader):
可視化第一個批資料:
圖3
可以看到,此時圖檔經過了随機裁剪并 resize 回原尺寸。
組合變換(Compose Transform)
經常需要做一系列資料變換。比如:
- 資料歸一化:可以通過 Transform 中提供的 Normalize 類來實作;
- Pad:對圖檔的每條邊補零以增大圖檔尺寸,通過 Pad 類來實作;
- 次元轉換:将 (Batch-size, Hight, Width, Channel) 次元的 minibatch 轉換為 (Batch-size, Channel, Hight, Width)(因為這是 MegEngine 支援的資料格式),通過 ToMode 類來實作;
- 其它的轉換操作
為了友善使用,MegEngine 中的 Compose 類允許組合多個 Transform 并傳遞給 DataLoader 的 transform 參數。
接下來通過 Compose 類将之前的 RandomResizedCrop 操作與 Normalize 、 Pad 和 ToMode 操作組合起來, 實作多種資料轉換操作的混合使用。運作如下代碼檢視轉換 minibatch 的次元資訊。
from megengine.data.transform import RandomResizedCrop, Normalize, ToMode, Pad, Compose
# 利用 Compose 組合多個 Transform 操作
transform=Compose([
RandomResizedCrop(output_size=28),
# mean 和 std 分别是 MNIST 資料的均值和标準差,圖檔數值範圍是 0~255
Normalize(mean=0.1307*255, std=0.3081*255),
Pad(2),
# 'CHW'表示把圖檔由 (height, width, channel) 格式轉換成 (channel, height, width) 格式
ToMode('CHW'),
])
print("The shape of the batch is now: {}".format(batch_image.shape))
The shape of the batch is now: (4, 1, 32, 32)
人工智能晶片與自動駕駛