天天看點

曠視MegEngine資料加載與處理

曠視MegEngine資料加載與處理

在網絡訓練與測試中,資料的加載和預處理往往會耗費大量的精力。 MegEngine 提供了一系列接口來規範化這些處理工作。

利用 Dataset 封裝一個資料集

資料集是一組資料的集合,例如 MNIST、Cifar10等圖像資料集。 Dataset 是 MegEngine 中表示資料集的抽象類。自定義的資料集類應該繼承 Dataset 并重寫下列方法:

  • __init__() :一般在其中實作讀取資料源檔案的功能。也可以添加任何其它的必要功能;
  • __getitem__() :通過索引操作來擷取資料集中某一個樣本,使得可以通過 for 循環來周遊整個資料集;
  • __len__() :傳回資料集大小;

下面是一個簡單示例。 根據下圖所示的二分類資料,建立一個 Dataset 。每個資料是一個二維平面上的點,橫坐标和縱坐标在 [-1, 1] 之間。共有兩個類别标簽(圖1中的藍色 * 和紅色 +),标簽為0的點處于一、三象限;标簽為1的點處于二、四象限。

曠視MegEngine資料加載與處理

 圖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 資料:

曠視MegEngine資料加載與處理

 圖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):

可視化第一個批資料:

曠視MegEngine資料加載與處理

 圖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)

人工智能晶片與自動駕駛