いろいろなデータを使いたいということで、自前datasetの作り方をいろいろ試してみたので、まとめておきます。
denoising, coloring, ドメイン変換などをやるためには、必須な技術です。
今回は、二つの要素をまとめます。
一つは、torchvision.transformsの各種クラスの使い方と自前クラスの作り方、もう一つはそれらを利用した自前datasetの作り方です。
後半は、以下の参考がありますが、試行錯誤を随分したので、その結果を載せることとします。
【参考】
①pyTorchのtransforms,Datasets,Dataloaderの説明と自作Datasetの作成と使用
②PyTorchでDatasetの読み込みを実装してみた
③TORCHVISION.TRANSFORMS
###やったこと
・transformsの整理
・autoencoderに応用する
・自前datasetの作り方
①data-labelの場合
②data1-data2-labelのような場合
###・transformsの整理
transformは以下のようにpytorch-lighitningのコンストラクタで出現(定義)していて、setupでデータ処理を簡単に定義し、Dataloaderで取得時にその処理を実行しています。
以下では、MNISTデータに対して、transforms.Normalize((0.1307,), (0.3081,))を実行しています。最初は、この数字は何というところからのまとめをしたいと思います。
※結論は、この数字は平均と標準偏差で、各画像数値をこの平均と標準偏差に入るように再規格化するという呪文でした
(画像なら暗すぎるものを明るくしたり、明るすぎるものを暗めにするなどの、輝度調整という処理になります)
実際、この数字がどこから来たかは今回は不明です。
全体の画像からそれぞれの平均の平均や標準偏差を求めて調整が筋ですが、そこまでやっている証拠は見つけられませんでした⇒以下の参考⑤を見ると、チャンネルごとの平均をそれぞれから引き、そのチャンネルの標準偏差で除しているようです。
しかし記述統計的にはそうすべきです。しかし、今回の興味の中心からはずれるので、パスすることにします。
※なお、研究テーマ的には、これを色々適応しつつ、精度への寄与をまじめに計測するのは面白いと思いますし、物体検出では重要なテーマ(必須で自分の対象にたいしてやるべき)だと思います
class LitAutoEncoder(pl.LightningModule):
def __init__(self, data_dir='./'):
super().__init__()
self.data_dir = data_dir
# Hardcode some dataset specific attributes
self.num_classes = 10
self.classes = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
self.dims = (1, 28, 28)
channels, width, height = self.dims
self.transform=transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))])
self.encoder = nn.Sequential(nn.Linear(28 * 28, 128), nn.ReLU(), nn.Linear(128, 32))
self.decoder = nn.Sequential(nn.Linear(32, 128), nn.ReLU(), nn.Linear(128, 28 * 28))
def forward(self, x):
# in lightning, forward defines the prediction/inference actions
embedding = self.encoder(x)
return embedding
...
def setup(self, stage=None): #train, val, testデータ分割
# Assign train/val datasets for use in dataloaders
mnist_full =MNIST(self.data_dir, train=True, transform=self.transform)
n_train = int(len(mnist_full)*0.8)
n_val = len(mnist_full)-n_train
self.mnist_train, self.mnist_val = torch.utils.data.random_split(mnist_full, [n_train, n_val])
self.mnist_test = MNIST(self.data_dir, train=False, transform=self.transform)
def train_dataloader(self):
self.trainloader = DataLoader(self.mnist_train, shuffle=True, drop_last = True, batch_size=32, num_workers=0)
# get some random training images
return self.trainloader
...
そして、このtransformsは、上記の参考③にまとめられていました。
ここでは、全てを試していませんが、当面使いそうな以下の表の機能を動かしてみました。
機能 | 備考 |
---|---|
rotate(x, angle) | 角度に基いて回転する |
to_grayscale(x) | grayscaleに変換する |
vflip(x) | 上下フリップを行う |
hflip(x) | 左右フリップを行う |
Resize(imageSize) | 指定されたサイズにResizeを行う |
Normalize(self.mean, self.std) | 指定された平均と標準偏差で画像を規格化する |
Compose() | ()内の一連の変換をまとめて実行する |
ToTensor() | torchTensorに変換する |
ToPILImage() | PILImageに変換する |
TORCHVISION.TRANSFORMSのクラス等
Compose(transforms)
CenterCrop(size)
ColorJitter(brightness=0, contrast=0, saturation=0, hue=0)
FiveCrop(size)
Grayscale(num_output_channels=1)
Pad(padding, fill=0, padding_mode='constant')
RandomAffine(degrees, translate=None, scale=None, shear=None, resample=0, fillcolor=0)
RandomApply(transforms, p=0.5)
RandomCrop(size, padding=None, pad_if_needed=False, fill=0, padding_mode='constant')
RandomGrayscale(p=0.1)
RandomHorizontalFlip(p=0.5)
RandomPerspective(distortion_scale=0.5, p=0.5, interpolation=2, fill=0)
RandomResizedCrop(size, scale=(0.08, 1.0), ratio=(0.75, 1.3333333333333333), interpolation=2)
RandomRotation(degrees, resample=False, expand=False, center=None, fill=None)
RandomSizedCrop(*args, **kwargs)
RandomVerticalFlip(p=0.5)
Resize(size, interpolation=2)
TenCrop(size, vertical_flip=False)
GaussianBlur(kernel_size, sigma=(0.1, 2.0))
Transforms on PIL Image only;
RandomChoice(transforms)
RandomOrder(transforms)
Transforms on torch.*Tensor only;
LinearTransformation(transformation_matrix, mean_vector)
Normalize(mean, std, inplace=False) output[channel] = (input[channel] - mean[channel]) / std[channel]
RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0, inplace=False)
ConvertImageDtype(dtype: torch.dtype)
Conversion Transforms;
ToPILImage(mode=None)
ToTensor
Generic Transforms;
Lambda(lambd)
Functional Transforms;
Example: you can apply a functional transform with the same parameters to multiple images like this:...
Example: you can use a functional transform to build transform classes with custom behavior:...
adjust_brightness(img: torch.Tensor, brightness_factor: float) → torch.Tensor
adjust_contrast(img: torch.Tensor, contrast_factor: float) → torch.Tensor
adjust_gamma(img: torch.Tensor, gamma: float, gain: float = 1) → torch.Tensor
adjust_hue(img: torch.Tensor, hue_factor: float) → torch.Tensor
adjust_saturation(img: torch.Tensor, saturation_factor: float) → torch.Tensor
...以下省略
コードは、おまけに掲載しておきます。
クラスの書き方は、以下の参考④を参考にしています。
また、各種のtransformの実行結果が参考⑤に掲載されています。
さらに、gaussian noizeの載せ方は、参考⑥を参考にしていますし、同じコードが参考⑦にも掲載されています。
自前のtransform関数をtransforms.Lambda(関数名)から使えることが、参考⑤に記載されていますが、今回は使用していません。
※PIL.ImageOps.equalize(image, mask=None)なども使えそう
from PIL import ImageFilter
img = Image.open("sample.jpg")
def blur(img):
"""ガウシアンフィルタを適用する。
"""
return img.filter(ImageFilter.BLUR)
transform = transforms.Lambda(blur)
img = transform(img)
img
【参考】
④vision/docs/source/transforms.rst
⑤Pytorch – torchvision で使える Transform まとめ
⑥How to add noise to MNIST dataset when using pytorch
ということで、以下のような参考⑦のようなことがsample augmentationとして簡単に実行できます。
⑦Pytorch Image Augmentation using Transforms.
###・autoencoderに応用する
pytorch-lightningのコードは以下の通りです。
以下のコードでは、画像のリサイズは行っていませんが、Networkを変更すれば行えます。
autoencoderへの応用
class LitAutoEncoder(pl.LightningModule):
def __init__(self, data_dir='./'):
super().__init__()
self.data_dir = data_dir
# Hardcode some dataset specific attributes
self.num_classes = 10
self.classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
#self.classes = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
self.dims = (3, 32, 32)
self.mean = [0.5,0.5,0.5] #[0.485, 0.456, 0.406] #[0.5,0.5,0.5]
self.std = [0.25,0.25,0.25] #[0.229, 0.224, 0.225] #[0.5,0.5,0.5]
self.imageSize = (32,32)
self.p=0.5
self.scale=(0.01, 0.05) #(0.02, 0.33)
self.ratio=(0.3, 0.3) #(0.3, 3.3)
self.value=0
self.inplace=False
#channels, width, height = self.dims
self.transform = transforms.Compose([
transforms.Resize(self.imageSize), # 画像のリサイズ
transforms.ToTensor(),
transforms.Normalize(self.mean, self.std),
transforms.RandomErasing(p=self.p, scale=self.scale, ratio=self.ratio, value=self.value, inplace=self.inplace),
MyAddGaussianNoise(0., 0.5)
])
self.encoder = Encoder()
self.decoder = Decoder()
def forward(self, x):
# in lightning, forward defines the prediction/inference actions
embedding = self.encoder(x)
return embedding
結果
どちらも、1epockでの出力だが、ノイズありの方が出力画像は改善している。
普通のdataset, Dataloaderの使い方コード
def prepare_data(self):
# download
CIFAR10(self.data_dir, train=True, download=True)
CIFAR10(self.data_dir, train=False, download=True)
def setup(self, stage=None): #train, val, testデータ分割
# Assign train/val datasets for use in dataloaders
cifar10_full =CIFAR10(self.data_dir, train=True, transform=self.transform)
n_train = int(len(cifar10_full)*0.8)
n_val = len(cifar10_full)-n_train
self.cifar10_train, self.cifar10_val = torch.utils.data.random_split(cifar10_full, [n_train, n_val])
self.cifar10_test = CIFAR10(self.data_dir, train=False, transform=self.transform)
def train_dataloader(self):
self.trainloader = DataLoader(self.cifar10_train, shuffle=True, drop_last = True, batch_size=32, num_workers=0)
# get some random training images
return self.trainloader
def val_dataloader(self):
return DataLoader(self.cifar10_val, shuffle=False, batch_size=32, num_workers=0)
def test_dataloader(self):
self.testloader = DataLoader(self.cifar10_test, shuffle=False, batch_size=32, num_workers=0)
return self.testloader
####①data-labelの場合
まずは、参考②の通り基本が大切です。
前回のmediapipeの学習のところで、以下のようなdatasetを作成して利用しました。
以下では、csvファイルからデータを読み取り、座標に変換して、out_dataとその分類であるout_labelを提供するものでした。
前回のmediapipe_handsデータのためのdatasetコード
class HandsDataset(torch.utils.data.Dataset):
def __init__(self, data_num, transform=None):
self.transform = transform
self.data_num = data_num
self.data = []
self.label = []
df = pd.read_csv('./hands/sample_hands7.csv', sep=',')
print(df.head(3)) #データの確認
df = df.astype(int)
x = []
for j in range(self.data_num):
x_ = []
for i in range(0,21,1):
x__ = [df['{}'.format(2*i)][j],df['{}'.format(2*i+1)][j]]
x_.append(x__)
x.append(x_)
y = df['42'][:self.data_num]
#以下のfloat() とlong()の指定は今回の肝です
self.data = torch.from_numpy(np.array(x)).float()
print(self.data)
self.label = torch.from_numpy(np.array(y)).long()
print(self.label)
def __len__(self):
return self.data_num
def __getitem__(self, idx):
out_data = self.data[idx]
out_label = self.label[idx]
if self.transform:
out_data = self.transform(out_data)
return out_data, out_label
今回は、自前イメージデータを自前datasetとして提供する場合を示します。
結果は以下の通りです。
自前イメージデータのためのdatasetコード
class ImageDataset(torch.utils.data.Dataset):
def __init__(self, data_num, transform=None):
self.transform = transform
self.data_num = data_num
self.data = []
self.label = []
x = []
y = []
from_dir = './face/mayuyu/'
sk = 0
for path in glob.glob(os.path.join(from_dir, '*.jpg')):
image = Image.open(path)
x.append(np.array(image)/255.)
y.append(sk)
sk += 1
self.data = torch.from_numpy(np.array(x)).float()
self.label = torch.from_numpy(np.array(y)).long()
def __len__(self):
return self.data_num
def __getitem__(self, idx):
out_data = self.data[idx]
out_label = self.label[idx]
if self.transform:
out_data = self.transform(out_data)
return out_data, out_label
mean, std = [0.5,0.5,0.5], [0.25,0.25,0.25]
model = ImageDataset(10, transform = transforms.Normalize(mean, std))
for i in range(10):
image = model.data[i]
print(model.label[i], image)
plt.title('label_{}'.format(model.label[i]))
plt.imshow(image)
plt.pause(1)
plt.close()
dataset = ImageDataset(32,transform1 = trans1, transform2 = trans2)
testloader = DataLoader(dataset, batch_size=4, shuffle=True, num_workers=0)
Cifar10データを処理データと処理無しデータ,そしてlabel提供のためのdatasetコード
import numpy as np
import torch
import torchvision
from torch.utils.data import DataLoader, random_split
from torchvision import transforms
import cv2
import matplotlib.pyplot as plt
from torchvision.datasets import CIFAR10
from PIL import Image
class ImageDataset(torch.utils.data.Dataset):
def __init__(self, data_num, transform1 = None, transform2 = None,train = True):
self.transform1 = transform1
self.transform2 = transform2
self.ts = torchvision.transforms.ToPILImage()
self.ts2 = transform=transforms.ToTensor()
self.data_dir = './'
self.data_num = data_num
self.data = []
self.label = []
# download
CIFAR10(self.data_dir, train=True, download=True)
CIFAR10(self.data_dir, train=False, download=True)
self.data =CIFAR10(self.data_dir, train=True, transform=self.ts2)
def __len__(self):
return self.data_num
def __getitem__(self, idx):
out_data = self.ts(self.data[idx][0])
out_label = np.array(self.data[idx][1])
if self.transform1:
out_data1 = self.transform1(out_data)
if self.transform2:
out_data2 = self.transform2(out_data)
return out_data1, out_data2, out_label
trans1 = torchvision.transforms.ToTensor()
trans2 = torchvision.transforms.Compose([torchvision.transforms.Grayscale(), torchvision.transforms.ToTensor()])
dataset = ImageDataset(32,transform1 = trans1, transform2 = trans2)
testloader = DataLoader(dataset, batch_size=4,
shuffle=True, num_workers=0)
ts = torchvision.transforms.ToPILImage()
for out_data1, out_data2, out_label in testloader:
print(len(out_label),out_label)
for i in range(len(out_label)):
image = out_data1[i]
image_gray = out_data2[i]
im = ts(image)
im_gray = ts(image_gray)
#print(out_label[i])
plt.imshow(np.array(im_gray), cmap='gray')
plt.title('{}'.format(out_label[i]))
plt.pause(1)
plt.clf()
plt.imshow(np.array(im))
plt.title('{}'.format(out_label[i]))
plt.pause(1)
plt.clf()
plt.close()
実行結果は以下のとおり
>python dataset_cifar10_original.py
Files already downloaded and verified
Files already downloaded and verified
4 tensor([0, 3, 2, 6], dtype=torch.int32)
tensor(0, dtype=torch.int32)
tensor(3, dtype=torch.int32)
tensor(2, dtype=torch.int32)
tensor(6, dtype=torch.int32)
4 tensor([2, 2, 9, 5], dtype=torch.int32)
tensor(2, dtype=torch.int32)
tensor(2, dtype=torch.int32)
tensor(9, dtype=torch.int32)
tensor(5, dtype=torch.int32)
4 tensor([3, 6, 1, 7], dtype=torch.int32)
tensor(3, dtype=torch.int32)
tensor(6, dtype=torch.int32)
tensor(1, dtype=torch.int32)
tensor(7, dtype=torch.int32)
4 tensor([3, 9, 4, 9], dtype=torch.int32)
tensor(3, dtype=torch.int32)
tensor(9, dtype=torch.int32)
tensor(4, dtype=torch.int32)
tensor(9, dtype=torch.int32)
4 tensor([7, 8, 4, 4], dtype=torch.int32)
tensor(7, dtype=torch.int32)
tensor(8, dtype=torch.int32)
tensor(4, dtype=torch.int32)
tensor(4, dtype=torch.int32)
4 tensor([6, 7, 9, 0], dtype=torch.int32)
tensor(6, dtype=torch.int32)
tensor(7, dtype=torch.int32)
tensor(9, dtype=torch.int32)
tensor(0, dtype=torch.int32)
4 tensor([4, 1, 9, 2], dtype=torch.int32)
tensor(4, dtype=torch.int32)
tensor(1, dtype=torch.int32)
tensor(9, dtype=torch.int32)
tensor(2, dtype=torch.int32)
4 tensor([6, 9, 6, 3], dtype=torch.int32)
tensor(6, dtype=torch.int32)
tensor(9, dtype=torch.int32)
tensor(6, dtype=torch.int32)
tensor(3, dtype=torch.int32)
・これを利用して、改めてdenoizing, coloring, 画像拡大、画像合成などの学習と利用アプリを作りたい
###おまけ
import torchvision.transforms.functional as TF
import random
import matplotlib.pyplot as plt
import cv2
from PIL import Image
import numpy as np
import torch
import torchvision
class MyRotationTransform:MyRotationTransform
"""Rotate by one of the given angles."""
def __init__(self, angles):
self.angles = angles
def __call__(self, x):
angle = random.choice(self.angles)
return TF.rotate(x, angle)
class MyGrayscaleTransform:
"""GrayScale by this class."""
def __init__(self):
pass
def __call__(self, x):
#return TF.rgb_to_grayscale(x)
return TF.to_grayscale(x)
class MyVflipTransform:
"""Vertical flip by this class."""
def __init__(self):
pass
def __call__(self, x):
return TF.vflip(x)
class MyHflipTransform:
"""Vertical flip by this class."""
def __init__(self):
pass
def __call__(self, x):
return TF.hflip(x)
from torchvision import transforms
class MyNormalizeTransform:
"""normalization by the image."""
def __init__(self):
self.imageSize = (512,512)
self.mean = [0.485, 0.456, 0.406]
self.std = [0.229, 0.224, 0.225]
def __call__(self, x):
img = self.transform = transforms.Compose([
transforms.Resize(self.imageSize), # 画像のリサイズ
transforms.ToTensor(), # Tensor化
transforms.Normalize(self.mean, self.std), # 標準化
])
return img(x)
class MyErasingTransform:
"""normalization by the image."""
def __init__(self):
self.imageSize = (512,512)
self.p=0.5
self.scale=(0.02, 0.33)
self.ratio=(0.3, 3.3)
self.value=0
self.inplace=False
def __call__(self, x):
self.transform = transforms.Compose([
transforms.Resize(self.imageSize), # 画像のリサイズ
transforms.ToTensor(), # Tensor化
transforms.RandomErasing(p=self.p, scale=self.scale, ratio=self.ratio, value=self.value, inplace=self.inplace)
])
return self.transform(x)
class MyAddGaussianNoise(object):
def __init__(self, mean=0., std=0.1):
self.std = std
self.mean = mean
def __call__(self, tensor):
return tensor + torch.randn(tensor.size()) * self.std + self.mean
def __repr__(self):
return self.__class__.__name__ + '(mean={0}, std={1})'.format(self.mean, self.std)
trans2 = torchvision.transforms.Compose([torchvision.transforms.Grayscale(), torchvision.transforms.ToTensor()])
ts = torchvision.transforms.ToPILImage()
trans3 = MyGrayscaleTransform()
trans4 = MyHflipTransform()
trans5 = MyNormalizeTransform()
trans6 = MyErasingTransform()
trans7 = transforms.Compose([
transforms.ToTensor(),
#transforms.Normalize((0.1307,), (0.3081,)),
MyAddGaussianNoise(0., 0.1)
])
angle_list =[i for i in range(-10,10,1)] #[-30, -15, 0, 15, 30]
rotation_transform = MyRotationTransform(angles=angle_list)
x = Image.open('./face/mayuyu/2.jpg')
while 1:
y = rotation_transform(x)
#z = trans5(x)
z = trans7(y)
plt.imshow(ts(z))
plt.pause(0.1)
#z = trans3(x)
#plt.imshow(z, cmap='gray')
#plt.pause(0.1)
#plt.imshow(np.array(ts(trans2(y))), cmap='gray')
#plt.pause(0.1)
plt.clf()