前回セグメンテーション用のデータを準備しました。
-
-
参考【python AI】セマンティックセグメンテーションの実装方法 -データの準備-
続きを見る
次はこのデータを使って学習をして、画像に映った犬と背景を分けれるようにします。
本記事はこんな方におすすめです。

どのライブラリを使えばわからない
本記事の内容
- セグメンテーションできるライブラリ
- 学習の実装
- 学習後の確認方法
目次
ライブラリ
ライブラリは以下のものを使用します。
| AI | pytorch |
| セグメンテーション | segmentation-models-pytorch |
| データ拡張 | albumentations |
| 画像 | opencv-python |
| 画像表示 | matplotlib |
AIライブラリのpytorchは下記リンクを参照して、OSなどを選択してインストールしてください。
次のようにOSやCUDAのバージョンを選択して、出てくるpip コマンドを使います。

セグメンテーション用のモデルは下記のGitHubのものを使わせていただきます。
セグメンテーションのモデルを構成する、エンコーダー、デコーダーのモデルが色々組み合わせて選ぶことができて、使い方も比較的簡単です。
>> segmentation-models-pytorch (GitHub)
学習にはGPUがのったパソコンが必要になります。
手元に無い方は、Google Colaboratoryを利用してください。
その場合のライブラリのインストールはセルでpipを実行します。
次のように!をつけるとインストールができます。

学習の実装
前回の「【python AI】セマンティックセグメンテーションの実装方法 -データの準備-」で準備したデータを使って、学習をしていきます。
-
-
参考【python AI】セマンティックセグメンテーションの実装方法 -データの準備-
続きを見る
コード全体は少し長くなるので、下記のGitHubを参照ください。
上記コードを小分けにして紹介していきますが、ライブラリで紹介されているコードを参考にさせていただいています。
>> segmentation-models-pytorch のサンプルコード
ライブラリ読み込み
まず、必要なライブラリを読み込みます。
# ライブラリ読み込み import glob import cv2 import numpy as np import os from torch.utils.data import DataLoader from torch.utils.data import Dataset as BaseDataset import torch import torch.nn as nn import torch.nn.functional as F import segmentation_models_pytorch as smp import albumentations as albu import matplotlib.pyplot as plt
関数の宣言
いくつか関数を宣言します。
データセットの確認
データセットの中身を確認するための関数です
# データ確認用
def visualize(**images):
"""PLot images in one row."""
n = len(images)
plt.figure(figsize=(16, 5))
for i, (name, image) in enumerate(images.items()):
plt.subplot(1, n, i + 1)
plt.xticks([])
plt.yticks([])
plt.title(' '.join(name.split('_')).title())
plt.imshow(image)
plt.show()
データ拡張
データが少ない場合、データをずらしたり反転したりすることで、データの量を増やしますが、その処理関数になります。
# データ拡張
def get_training_augmentation():
IMAGE_SIZE = 256
train_transform = [
albu.HorizontalFlip(p=0.5),
albu.ShiftScaleRotate(scale_limit=0.5, rotate_limit=0, shift_limit=0.1, p=1, border_mode=0),
albu.PadIfNeeded(min_height=IMAGE_SIZE, min_width=IMAGE_SIZE, always_apply=True, border_mode=0),
albu.RandomCrop(height=IMAGE_SIZE, width=IMAGE_SIZE, always_apply=True),
albu.IAAAdditiveGaussianNoise(p=0.2),
albu.IAAPerspective(p=0.5),
albu.OneOf(
[
albu.CLAHE(p=1),
albu.RandomBrightness(p=1),
albu.RandomGamma(p=1),
],
p=0.9,
),
albu.OneOf(
[
albu.IAASharpen(p=1),
albu.Blur(blur_limit=3, p=1),
albu.MotionBlur(blur_limit=3, p=1),
],
p=0.9,
),
albu.OneOf(
[
albu.RandomContrast(p=1),
albu.HueSaturationValue(p=1),
],
p=0.9,
),
]
return albu.Compose(train_transform)
データ の前処理
imagenetの学習済みモデルを部分的に使うので、その正規化処理や、画像の「高さ,幅,CH」を「CH,高さ,幅」の入れ替えを行なっています。
# テンソル化
def to_tensor(x, **kwargs):
return x.transpose(2, 0, 1).astype('float32')
# 前処理
def get_preprocessing(preprocessing_fn):
_transform = [
albu.Lambda(image=preprocessing_fn),
albu.Lambda(image=to_tensor, mask=to_tensor),
]
return albu.Compose(_transform)
データセット
関数ではありませんが、BaseDatasetを継承して、Datasetを作ります。
# データセット
class Dataset(BaseDataset):
CLASSES = ['background', 'dog']
def __init__(
self,
images_dir,
masks_dir,
classes=None,
augmentation=None,
preprocessing=None,
):
self.ids = os.listdir(images_dir)
self.images_fps = [os.path.join(images_dir, image_id) for image_id in self.ids]
self.masks_fps = [os.path.join(masks_dir, image_id) for image_id in self.ids]
# convert str names to class values on masks
self.class_values = [classes.index(cls.lower()) for cls in classes]
self.augmentation = augmentation
self.preprocessing = preprocessing
def __getitem__(self, i):
# read data
image = cv2.imread(self.images_fps[i])
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# mask
mask = cv2.imread(self.masks_fps[i], 0)
masks = [(mask == v) for v in self.class_values]
mask = np.stack(masks, axis=-1).astype('float')
# apply augmentations
if self.augmentation:
sample = self.augmentation(image=image, mask=mask)
image, mask = sample['image'], sample['mask']
# apply preprocessing
if self.preprocessing:
sample = self.preprocessing(image=image, mask=mask)
image, mask = sample['image'], sample['mask']
return image, mask
def __len__(self):
return len(self.ids)
モデルを宣言
モデルを決めます。
今回はエンコーダーにresnet34、デコーダーにUNetを選んでいます。
色々な組み合わせが、ありますので、精度が良くなるように色々試してみると良いでしょう。
# モデルを宣言
ENCODER = 'resnet34'
ENCODER_WEIGHTS = 'imagenet'
CLASSES = ['dog']
ACTIVATION = 'sigmoid' # could be None for logits or 'softmax2d' for multicalss segmentation
DEVICE = 'cuda'
DECODER = 'unet'
model = smp.Unet(
encoder_name=ENCODER,
encoder_weights=ENCODER_WEIGHTS,
classes=len(CLASSES),
activation=ACTIVATION,
)
model = model.to("cuda")
データセットを作成
データセットを作成します。
前回作った、データはtrain、valフォルダに分けましたので、それを読み込みます。
train_dir = 'train'
val_dir = 'val'
preprocessing_fn = smp.encoders.get_preprocessing_fn(ENCODER, ENCODER_WEIGHTS)
# データセットを作成
train_dataset = Dataset(
os.path.join(train_dir, 'images'),
os.path.join(train_dir, 'masks'),
augmentation=get_training_augmentation(),
preprocessing=get_preprocessing(preprocessing_fn),
classes=['dog'],
)
valid_dataset = Dataset(
os.path.join(val_dir, 'images'),
os.path.join(val_dir, 'masks'),
preprocessing=get_preprocessing(preprocessing_fn),
classes=['dog'],
)
train_loader = DataLoader(train_dataset, batch_size=10, shuffle=True, num_workers=0)
valid_loader = DataLoader(valid_dataset, batch_size=1, shuffle=False, num_workers=0)
データを確認してみます。
# データ確認
dataset = Dataset(os.path.join(train_dir, 'images'), os.path.join(train_dir, 'masks'), classes=['dog'])
image, mask = dataset[5] # get some sample
visualize(
image=image,
dog_mask=mask.squeeze(),
)

ちゃんと読み込めていることが確認できます。
学習
データが読めたので、学習に進みます。
学習の精度指標やloss、最適化関数を決めます。
# 精度確認指標
metrics = [
smp.utils.metrics.IoU(threshold=0.5),
]
# ロス
loss = smp.utils.losses.DiceLoss()
# 最適化関数
optimizer = torch.optim.Adam([
dict(params=model.parameters(), lr=0.001),
])
最適化関数など変更できる方は、色々試してみると良いでしょう。
精度指標は、IoUという指標ですが、下記リンクがとてもわかりやすく紹介されています。
ライブラリに、1エポック学習するものが用意されているので、それを使います。
# 1Epochトレイン用
train_epoch = smp.utils.train.TrainEpoch(
model,
loss=loss,
metrics=metrics,
optimizer=optimizer,
device=DEVICE,
verbose=True,
)
valid_epoch = smp.utils.train.ValidEpoch(
model,
loss=loss,
metrics=metrics,
device=DEVICE,
verbose=True,
)
あとは、決めた回数分学習しますが、今回は40とします。
25Epoch目に学習率を変更しています。
# 学習 40EPoch 25Epochで学習率を下げる
max_score = 0
for i in range(0, 40):
print('\nEpoch: {}'.format(i))
try:
train_logs = train_epoch.run(train_loader)
val_logs = valid_epoch.run(valid_loader)
except Exception as e:
print(e)
# do something (save model, change lr, etc.)
if max_score < val_logs['iou_score']:
max_score = val_logs['iou_score']
torch.save(model, f'{DECODER}_{ENCODER}.pth')
print('Model saved!')
if i == 25:
optimizer.param_groups[0]['lr'] = 1e-4
print('Decrease decoder learning rate to 1e-4!')
こんな感じで学習が完了します。

学習後の確認
学習したモデルを使って、どのようにセグメンテーションできるか確認します。
テスト用の画像を用意せず、そのままvalフォルダのデータを使っていきます。
# 画像読み込み
val_files = glob.glob('val/images/*')
f = val_files[5]
image_src = cv2.imread(f)
image_src = cv2.cvtColor(image_src, cv2.COLOR_BGR2RGB)
# 前処理
image = preprocessing_fn(image_src)
image = image.transpose(2, 0, 1).astype('float32')
# モデルで推論
image=torch.from_numpy(image).to(DEVICE).unsqueeze(0)
predict = model(image)
predict = predict.detach().cpu().numpy()[0].reshape((256,256))
# 0.5以上を1とする
predict_img = np.zeros([256,256]).astype(np.int8)
predict_img = np.where(predict>0 .5, 1 , predict_img)
これで、AIが予測して結果が取得できたので、表示してみます。
# 横並び fig = plt.figure(figsize=(12, 6)) ax1 = fig.add_subplot(1, 2, 1) ax2 = fig.add_subplot(1, 2, 2) ax1.imshow(image_src) ax2.imshow(predict_img)

精度がいいとは言えませんね。
100枚の学習なので仕方がありませんが、AIモデルのエンコーダー、デコーダなど調整して色々試すとまた違った結果になりますので、色々試してみてください。