一、什么是数据变换 Transforms ?
通常情况下,直接加载的原始数据并不能直接送入神经网络进行训练,此时我们需要对其进行数据预处理。MindSpore提供不同种类的数据变换(Transforms
),配合数据处理Pipeline来实现数据预处理。所有的Transforms
均可通过map
方法传入,实现对指定数据列的处理。
mindspore.dataset
提供了面向图像、文本、音频等不同数据类型的Transforms
,同时也支持使用Lambda
函数。下面分别对其进行介绍。
环境准备:
%%capture captured_output
# 实验环境已经预装了mindspore==2.2.14,如需更换mindspore版本,可更改下面mindspore的版本号
!pip uninstall mindspore -y
!pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspore==2.2.14
import numpy as np
from PIL import Image
from download import download
from mindspore.dataset import transforms, vision, text
from mindspore.dataset import GeneratorDataset, MnistDataset
二、Common Transforms
mindspore.dataset.transforms
模块支持一系列通用Transforms。这里我们以Compose
为例,介绍其使用方式。
Compose
Compose
接收一个数据增强操作序列,然后将其组合成单个数据增强操作。我们仍基于Mnist数据集呈现Transforms的应用效果。
下载数据集:
# Download data from open datasets
url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/" \"notebook/datasets/MNIST_Data.zip"
path = download(url, "./", kind="zip", replace=True)train_dataset = MnistDataset('MNIST_Data/train')
image, label = next(train_dataset.create_tuple_iterator())
print(image.shape)
输出:
# 图像的高度和宽度都是28像素 并且他是一个灰度图
(28, 28, 1)
在这段代码中,
train_dataset.create_tuple_iterator()
被用来创建一个迭代器,该迭代器从训练数据集
train_dataset 中逐个提取图像和标签的元组。然后,使用next()
函数从迭代器中获取下一个元组,这个元组被解包为 image和 label。这里,image 代表了一个图像数据,而 label 是与该图像对应的标签或类别。
print(image.shape)
这行代码用于打印出图像数据的形状(shape)。图像的形状通常由其高度(height)、宽度(width)和颜色通道数(channels)决定。对于灰度图像,颜色通道数为1;对于RGB彩色图像,颜色通道数为3。例如,如果输出是 (224, 224, 3),那么这意味着图像的高度和宽度都是224像素,并且它是一个RGB彩色图像(因为有3个颜色通道)。
这个输出对于理解数据集中图像的尺寸和类型非常有用,特别是在进行图像预处理或模型训练时,确保输入数据的尺寸与模型期望的输入尺寸相匹配。
# 定义一个数组增强操作
composed = transforms.Compose([vision.Rescale(1.0 / 255.0, 0),vision.Normalize(mean=(0.1307,), std=(0.3081,)),vision.HWC2CHW()])
# 使用上面定义的 composed 对数据集进行变换
train_dataset = train_dataset.map(composed, 'image')
image, label = next(train_dataset.create_tuple_iterator())
print(image.shape)
输出:
# 可以看到转换前后的图像格式已经发生了变化 转化前为(height, width, channel),转化后为(channel, height, width )
(1, 28, 28)
三、Vision Transforms
mindspore.dataset.vision
模块提供一系列针对图像数据的Transforms。在Mnist数据处理过程中,使用了Rescale
、Normalize
和HWC2CHW
变换。下面对其进行详述。
3.1 Rescale
Rescale变换用于调整图像像素值的大小,包括两个参数:
- rescale:缩放因子。
- shift:平移因子。 图像的每个像素将根据这两个参数进行调整,输出的像素值为
𝑜𝑢𝑡𝑝𝑢𝑡𝑖=𝑖𝑛𝑝𝑢𝑡𝑖∗𝑟𝑒𝑠𝑐𝑎𝑙𝑒+𝑠ℎ𝑖𝑓𝑡 。
这里我们先使用numpy随机生成一个像素值在[0, 255]的图像,将其像素值进行缩放。
使用 NumPy 的 random.randint 函数生成了一个 48x48 的随机整数数组 random_np,这些整数的范围是从 0到 2555
random_np = np.random.randint(0, 255, (48, 48), np.uint8)
random_image = Image.fromarray(random_np)
print(random_np)
print(random_np.shape)
输出:
# 数组中每个元素的取值范围都在0-255
[[ 32 218 60 ... 216 84 58][ 57 59 58 ... 186 250 120][228 75 188 ... 142 103 38]...[199 193 216 ... 209 130 24][250 76 222 ... 8 209 87][206 60 16 ... 37 79 103]]# 数组的shape是48行48列
(48, 48)
为了更直观地呈现Transform前后的数据对比,我们使用Transforms的Eager
模式进行演示。首先实例化Transform对象,然后调用对象进行数据处理。
Rescale函数的两个参数
- 1.0 / 255.0:这个值用作归一化的比例因子。图像数据通常以像素值的形式存储,其范围通常是 0 到 255(对于 8 位深度的图像)。通过将每个像素值除以 255,我们可以将这个范围转换为 0 到 1
的浮点数范围。这样做有助于改善模型的训练过程,因为许多机器学习算法和深度学习模型在处理小数值时表现更好。- 0:这个参数通常指定了归一化后的最小值。在这个例子中,由于我们使用了 1.0 / 255.0 作为比例因子,并且原始像素值的最小值是0,因此归一化后的最小值仍然是 0(0 * (1.0 / 255.0) = 0)。然而,在某些情况下,这个参数可能允许你指定一个非零的最小值,但这取决于 Rescale 函数的具体实现。
rescale = vision.Rescale(1.0 / 255.0, 0)
rescaled_image = rescale(random_image)
print(rescaled_image)
输出:
[[0.1254902 0.854902 0.23529413 ... 0.8470589 0.32941177 0.227451 ][0.22352943 0.23137257 0.227451 ... 0.7294118 0.9803922 0.47058827][0.8941177 0.29411766 0.7372549 ... 0.5568628 0.4039216 0.14901961]...[0.7803922 0.7568628 0.8470589 ... 0.8196079 0.50980395 0.09411766][0.9803922 0.29803923 0.8705883 ... 0.03137255 0.8196079 0.34117648][0.8078432 0.23529413 0.0627451 ... 0.14509805 0.30980393 0.4039216 ]]
可以看到,使用Rescale后的每个像素值都进行了缩放。
3.2 Normalize
Normalize变换用于对输入图像的归一化,包括三个参数:
- mean:图像每个通道的均值。
- std:图像每个通道的标准差。
- is_hwc:bool值,输入图像的格式。True为(height,width, channel),False为(channel, height, width)。
图像的每个通道将根据mean和std进行调整,计算公式为
𝑜𝑢𝑡𝑝𝑢𝑡𝑐=𝑖𝑛𝑝𝑢𝑡𝑐−𝑚𝑒𝑎𝑛𝑐𝑠𝑡𝑑𝑐 ,其中 𝑐 代表通道索引。
# 这里我就有点看不懂了,以后懂了再补充注释
normalize = vision.Normalize(mean=(0.1307,), std=(0.3081,))
normalized_image = normalize(rescaled_image)
print(normalized_image)
输出:
[[-0.01690946 2.3505423 0.33948112 ... 2.3250859 0.644958730.31402466][ 0.3012964 0.32675287 0.31402466 ... 1.9432386 2.75784561.1031753 ][ 2.4778247 0.5304046 1.968695 ... 1.3831964 0.88679530.05945994]...[ 2.1087058 2.0323365 2.3250859 ... 2.2359881 1.2304575-0.11873532][ 2.7578456 0.54313284 2.4014552 ... -0.3223871 2.23598810.6831434 ][ 2.1978035 0.33948112 -0.2205612 ... 0.04673171 0.581317540.8867953 ]]
3.3 HWC2CHW
HWC2CHW
变换用于转换图像格式。在不同的硬件设备中可能会对(height, width, channel)或(channel, height, width)两种不同格式有针对性优化。MindSpore设置HWC为默认图像格式,在有CHW格式需求时,可使用该变换进行处理。
这里我们先将前文中normalized_image
处理为HWC格式,然后进行转换。可以看到转换前后的shape发生了变化。
hwc_image = np.expand_dims(normalized_image, -1)
hwc2chw = vision.HWC2CHW()
chw_image = hwc2chw(hwc_image)
print(hwc_image.shape, chw_image.shape)
输出:
(48, 48, 1) (1, 48, 48)
更多Vision Transforms详见mindspore.dataset.vision。
四、Text Transforms
mindspore.dataset.text
模块提供一系列针对文本数据的Transforms。与图像数据不同,文本数据需要有分词(Tokenize)、构建词表、Token转Index等操作。这里简单介绍其使用方法。
首先我们定义三段文本,作为待处理的数据,并使用GeneratorDataset
进行加载。
# 定义文本
texts = ['Welcome to Beijing']
# 使用GeneratorDataset 加载文本
test_dataset = GeneratorDataset(texts, 'text')
4.1 PythonTokenizer(分词)
分词(Tokenize)操作是文本数据的基础处理方法,MindSpore提供多种不同的Tokenizer。这里我们选择基础的PythonTokenizer
举例,此Tokenizer允许用户自由实现分词策略。随后我们利用map
操作将此分词器应用到输入的文本中,对其进行分词。
# 定义一个简单的分词函数 split函数不传递任何参数时,默认根据任何空白字符(包括空格、换行符 \n、制表符 \t、回车符 \r 等)来分割字符串
def my_tokenizer(content):return content.split()test_dataset = test_dataset.map(text.PythonTokenizer(my_tokenizer))
print(next(test_dataset.create_tuple_iterator()))
4.2 Lookup
Lookup
为词表映射变换,用来将Token转换为Index。在使用Lookup
前,需要构造词表,一般可以加载已有的词表,或使用Vocab
生成词表。这里我们选择使用Vocab.from_dataset
方法从数据集中生成词表。
# 获取词表
vocab = text.Vocab.from_dataset(test_dataset)# 使用vocab方法查看词表
print(vocab.vocab())
输出:
{'to': 2, 'Welcome': 1, 'Beijing': 0}
生成词表后,可以配合map
方法进行词表映射变换,将Token转为Index。
test_dataset = test_dataset.map(text.Lookup(vocab))
print(next(test_dataset.create_tuple_iterator()))
输出:
[Tensor(shape=[3], dtype=Int32, value= [1, 2, 0])]
更多Text Transforms详见mindspore.dataset.text。
五、Lambda Transforms
Lambda函数是一种不需要名字、由一个单独表达式组成的匿名函数,表达式会在调用时被求值。Lambda Transforms可以加载任意定义的Lambda函数,提供足够的灵活度。在这里,我们首先使用一个简单的Lambda函数,对输入数据乘2:
test_dataset = GeneratorDataset([1, 2, 3], 'data', shuffle=False)
test_dataset = test_dataset.map(lambda x: x * 2)
print(list(test_dataset.create_tuple_iterator()))
输出:
# 由结果可以得知,经过Lambda函数处理之后,数据集中每一个元素的值都*2,由 1,2,3变为 2,4,6
[[Tensor(shape=[], dtype=Int64, value= 2)], [Tensor(shape=[], dtype=Int64, value= 4)], [Tensor(shape=[], dtype=Int64, value= 6)]]
可以看到map传入Lambda函数后,迭代获得数据进行了乘2操作。
我们也可以定义较复杂的函数,配合Lambda函数实现复杂数据处理:
# 定义一个函数,传入的每个参数都返回该参数的平方再加2
def func(x):return x * x + 2# 数据集中的每个参数都经过自定义函数处理
test_dataset = test_dataset.map(lambda x: func(x))print(list(test_dataset.create_tuple_iterator()))
输出:
[[Tensor(shape=[], dtype=Int64, value= 6)], [Tensor(shape=[], dtype=Int64, value= 18)], [Tensor(shape=[], dtype=Int64, value= 38)]]
通过上面的结果可以很清楚的看出来,传入的数据集经过上一个Lambda表达式处理之后 变为 2,4,6
经过自定义函数处理之后变为
2*2+2=6
4*4+2=18
6*6+2=38