跳转到主要内容

开发者分享 | 利用 Python 和 PyTorch 处理面向对象的数据集(2)) :创建数据集对象

本文转载自:<span id="profileBt"><a href="https://mp.weixin.qq.com/s/lLuVN9wnsPWjUNLKzXvcKw">XILINX开发者社区微信公众号</a>…;

本篇是利用 Python 和 PyTorch 处理面向对象的数据集系列博客的第 2 篇。

<a href="http://xilinx.eetrend.com/content/2021/100114171.html">如需阅读第 1 篇:原始数据和数据集,请参阅此处。</a>

我们在第 1 部分中已定义 MyDataset 类,现在,让我们来例化 MyDataset 对象

此可迭代对象是与原始数据交互的接口,在整个训练过程中都有巨大作用。

<strong>第 2 部分:创建数据集对象</strong>

输入 [9]:
<pre>mydataset = MyDataset(isValSet_bool = None, raw_data_path = raw_data_path, norm = False, resize = True, newsize = (64, 64))</pre>

以下是该对象的一些使用示例:

输入 [10]:
<pre># 对象操作示例。

# 此操作用于调用 method __getitem__ 并从第 6 个样本获取标签

mydataset[6][1]</pre>

输出 [10]:
<pre>0</pre>

输入 [11]:
<pre># 此操作用于在类声明后打印注释
MyDataset.__doc__</pre>

输出 [11]:
<pre>'Interface class to raw data, providing the total number of samples in the dataset and a preprocessed item'</pre>

输入 [12]:
<pre># 此操作用于调用 method __len__
len(mydataset)</pre>

输出 [12]:
<pre>49100</pre>

输入 [13]:
<pre># 此操作用于触发 method __str__
print(mydataset)</pre>
<pre>原始数据路径为 ./raw_data/data_images/<raw samples></pre>

<strong>可迭代对象的重要性</strong>

训练期间,将向模型提供多批次样本。可迭代的 mydataset 是获得高级轻量代码的关键。

以下提供了可迭代对象的 2 个使用示例。

示例 1:

我们可以直接获取第 3 个样本张量:

输入 [14]:
<pre>mydataset.__getitem__(3)[0].shape</pre>

输出 [14]:
<pre>torch.Size([3, 64, 64])</pre>

与以下操作作用相同

输入 [15]:
<pre>mydataset[3][0].shape</pre>

输出 [15]:
<pre>torch.Size([3, 64, 64])</pre>

示例 2:

我们可以对文件夹中的图像进行解析,并移除黑白图像:

输入 [ ]:
<pre># 数据集访问示例:创建 1 个包含标签的新文件,移除黑白图像

if os.path.exists(raw_data_path + '/'+ "labels_new.txt"):

os.remove(raw_data_path + '/'+ "labels_new.txt")

with open(raw_data_path + '/'+ "labels_new.txt", "a") as myfile:

for item, info in mydataset:

if item != None:

if item.shape[0]==1:

# os.remove(raw_data_path + '/' + info.SampleName)

print('C = {}; H = {}; W = {}; info = {}'.format(item.shape[0], item.shape[1], item.shape[2], info))

else:

#print(info.SampleName + ' ' + str(info.SampleLabel))

myfile.write(info.SampleName + ' ' + str(info.SampleLabel) + '\n') </pre>

输入 [ ]:
<pre># 查找具有非期望格式的样本

with open(raw_data_path + '/'+ "labels.txt", "a") as myfile:

for item, info in mydataset:

if item != None:

if item.shape[0]!=3:

# os.remove(raw_data_path + '/' + info.SampleName)

print('C = {}; H = {}; W = {}; info = {}'.format(item.shape[0], item.shape[1], item.shape[2], info))</pre>

修改标签文件后,请务必更新缓存:

输入 [ ]:
<pre>if os.path.exists(raw_data_path + '/'+ "labels_new.txt"):

os.rename(raw_data_path + '/'+ "labels.txt", raw_data_path + '/'+ "labels_orig.txt")

os.rename(raw_data_path + '/'+ "labels_new.txt", raw_data_path + '/'+ "labels.txt")

@functools.lru_cache(1)

def getSampleInfoList(raw_data_path):

sample_list = []

with open(str(raw_data_path) + '/labels.txt', "r") as f:

reader = csv.reader(f, delimiter = ' ')

for i, row in enumerate(reader):

imgname = row[0]

label = int(row[1])

sample_list.append(DataInfoTuple(imgname, label))

sample_list.sort(reverse=False, key=myFunc)

return sample_list

del mydataset

mydataset = MyDataset(isValSet_bool = None, raw_data_path = '../../raw_data/data_images', norm = False)

len(mydataset)</pre>

您可通过以下链接阅读了解有关 PyTorch 中的可迭代数据库的更多信息: https://pytorch.org/docs/stable/data.html

<strong>归一化</strong>

应对所有样本张量计算平均值和标准差。

如果数据集较小,可以尝试在内存中对其进行直接操作:使用 torch.stack 即可创建 1 个包含所有样本张量的栈。

可迭代对象 mydataset 支持简洁精美的代码。

使用“view”即可保留 R、G 和 B 这 3 个通道,并将其余所有维度合并为 1 个维度。

使用“mean”即可计算维度 1 的每个通道的平均值。

请参阅附件中有关 dim 使用的说明。

输入 [16]:
<pre>imgs = torch.stack([img_t for img_t, _ in mydataset], dim = 3)</pre>

输入 [17]:
<pre>#im_mean = imgs.view(3, -1).mean(dim=1).tolist()

im_mean = imgs.view(3, -1).mean(dim=1)

im_mean</pre>

输出 [17]:
<pre>tensor([0.4735, 0.4502, 0.4002])</pre>

输入 [18]:
<pre>im_std = imgs.view(3, -1).std(dim=1).tolist()

im_std</pre>

输出 [18]:
<pre>[0.28131285309791565, 0.27447444200515747, 0.2874436378479004]</pre>

输入 [19]:
<pre>normalize = transforms.Normalize(mean=[0.4735, 0.4502, 0.4002], std=[0.28131, 0.27447, 0.28744])

# free memory

del imgs</pre>

下面,我们将再次构建数据集对象,但这次将对此对象进行归一化:

输入 [21]:
<pre>mydataset = MyDataset(isValSet_bool = None, raw_data_path = raw_data_path, norm = True, resize = True, newsize = (64, 64))</pre>

由于采用了归一化,因此张量值被转换至范围 0..1 之内,并进行剪切操作。

输入 [22]:
<pre>original = Image.open('../../raw_data/data_images/img_00009111.JPEG')

fig, axs = plt.subplots(1, 2, figsize=(10, 3))

axs[0].set_title('clipped tensor')

axs[0].imshow(mydataset[5][0].permute(1,2,0))

axs[1].set_title('original PIL image')

axs[1].imshow(original)

plt.show()</pre>
<pre>将输入数据剪切到含 RGB 数据的 imshow 的有效范围内,以 [0..1] 表示浮点值,或者以 [0..255] 表示整数值。</pre>
<center><img src="http://xilinx.eetrend.com/files/2021-07/wen_zhang_/100114373-211922-shu…; alt=""></center>

<strong> 使用 torchvision.transforms 进行预处理</strong>

现在,我们已经创建了自己的变换函数或对象(原本用作为加速学习曲线的练习),我建议使用 Torch 模块 torchvision.transforms:

“此模块定义了一组可组合式类函数对象,这些对象可作为实参传递到数据集(如 torchvision.CIFAR10),并在加载数据后 __getitem__ 返回数据之前,对数据执行变换”。

以下列出了可能的变换:

输入 [23]:
<pre>from torchvision import transforms
dir(transforms)</pre>

输出 [23]:
<pre>['CenterCrop',

'ColorJitter',

'Compose',

'FiveCrop',

'Grayscale',

'Lambda',

'LinearTransformation',

'Normalize',

'Pad',

'RandomAffine',

'RandomApply',

'RandomChoice',

'RandomCrop',

'RandomErasing',

'RandomGrayscale',

'RandomHorizontalFlip',

'RandomOrder',

'RandomPerspective',

'RandomResizedCrop',

'RandomRotation',

'RandomSizedCrop',

'RandomVerticalFlip',

'Resize',

'Scale',

'TenCrop',

'ToPILImage',

'ToTensor',

'__builtins__',

'__cached__',

'__doc__',

'__file__',

'__loader__',

'__name__',

'__package__',

'__path__',

'__spec__',

'functional',

'transforms']</pre>

在此示例中,我们使用变换来执行了以下操作:

1) ToTensor - 从 PIL 图像转换为张量,并将输出格式定义为 CxHxW
2) Normalize - 将张量归一化

如需了解后续步骤,敬请期待本系列的第 3 部分。