딥 러닝으로 간단한 초해상을 해보았다

소개



MNIST의 필기 숫자 이미지를 이용하여 간단한 초해상을 해 보았습니다.
전체 코드는 GitHub에 업로드했습니다.

저화질 데이터 생성



$28\times 28$의 필기 숫자 이미지를 $10\times 10$로 축소합니다.
이것을 $28\times 28$로 확장합니다.

축소했을 때에 정보가 손실되기 때문에, 당연히 흐릿한 것 같은 화상이 되고 있습니다.

고해상화 모델



흐릿한 저화질 이미지를 입력하면 원본에 가까운 이미지가 출력되도록 학습합니다.

4층의 컨벌루션 신경망을 사용했습니다.
중간층의 활성화 함수는 ReLU로 하고, 출력층만 각 화소값을 $[0,1]$에 맞추기 위해서 시그모이드 함수를 사용하고 있습니다.
또한 각 레이어에 Batch normalization을 적용했습니다.



다음은 모델 정의 부분의 코드입니다.
구현은 Chainer에서 수행했습니다.

모델 정의
import cv2
import numpy as np
import chainer
from chainer.backends import cuda
from chainer import Function, gradient_check, report, training, utils, Variable
from chainer import datasets, iterators, optimizers, serializers
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L
from chainer.training import extensions

class CNNAE(Chain):
    def __init__(self):
        super().__init__()
        with self.init_scope():
            N = 16
            kwds = {"ksize": 3, "stride": 1, "pad": 1, "nobias": True}
            self.conv1 = L.Convolution2D(1, N, **kwds)
            self.bn1 = L.BatchNormalization(N)
            self.conv2 = L.Convolution2D(N, N*2, **kwds)
            self.bn2 = L.BatchNormalization(N*2)
            self.conv3 = L.Convolution2D(N*2, N*4, **kwds)
            self.bn3 = L.BatchNormalization(N*4)
            self.conv4 = L.Convolution2D(N*4, 1, ksize=3, stride=1, pad=1)

    def forward(self, x):
        # 低画質画像をもとに戻す
        h = F.relu(self.bn1(self.conv1(x)))
        h = F.relu(self.bn2(self.conv2(h)))
        h = F.relu(self.bn3(self.conv3(h)))
        h = F.sigmoid(self.conv4(h))

        return h

    def __call__(self, x, t):
        # 高解像度化
        h = self.forward(x)
        # オリジナルとの誤差を算出
        loss = F.mean_squared_error(h, t)
        report({"loss": loss}, self)
        return loss

학습



모델 출력과 원본 이미지의 평균 제곱 오차가 줄어들도록 학습합니다.
즉, 다음 손실 함수를 모델 매개 변수 $\theta$에 대해 최소화합니다.
{\cal L}(\theta)=\mathbb{E}\left[ \| y-f(x;\theta) \|^2 \right]

단, $f(\cdot;\theta)$가 모델, $x$가 입력 화상(저화질), $y$가 오리지날 화상을 나타냅니다.

최적화 알고리즘은 Adam, 배치 사이즈는 64로 했습니다.

아래는 학습 부분의 코드입니다.

학습
# MNISTデータセットの取得
train, test = datasets.get_mnist(withlabel=False, ndim=3)

# 入力画像を作成する
# 10x10に縮小してからもとのサイズに拡大して低画質化
train_boke = F.resize_images(train, (10,10))
train_boke = F.resize_images(train_boke, (28,28)).array
test_boke = F.resize_images(test, (10,10))
test_boke = F.resize_images(test_boke, (28,28)).array

# 低画質画像とオリジナル画像のペアにする
train = datasets.TupleDataset(train_boke, train)
train_iter = iterators.SerialIterator(train, 64, shuffle=True, repeat=True)
test = datasets.TupleDataset(test_boke, test)
test_iter = iterators.SerialIterator(test, 64, shuffle=False, repeat=False)

model = CNNAE() # 超解像モデル作成
opt = optimizers.Adam() # 最適化アルゴリズムとしてAdamを選択
opt.setup(model)

# 学習の準備
updater = training.updaters.StandardUpdater(train_iter, opt)
trainer = training.Trainer(updater, (10,"epoch"))
# テストの設定
evaluator = extensions.Evaluator(test_iter, model)
trainer.extend(evaluator)
# 学習経過の表示設定
trainer.extend(extensions.LogReport())
trainer.extend(extensions.PrintReport(["epoch", "main/loss", "validation/main/loss"]))
trainer.extend(extensions.ProgressBar())

# 学習開始
trainer.run()

결과



아래는 10 에포크 학습 모델에서 테스트 데이터를 입력한 결과입니다.


언뜻 어떤 숫자인지 모르는 입력도 원래에 가까이 복원할 수 있습니다!
(과연 완전히 망가지고 있으면 안 됩니다만···)

결론



MNIST를 소재로 하여 초해상을 시도했습니다.
이번에 한 것은 흐릿한 이미지와 오리지널 이미지의 쌍으로 교사 있어 학습한다고 하는 단순한 것입니다만, 거기서 복원할 수 있었습니다.
큰 화상이나 일반 물체와 같은 바리에이션이 많은 화상이라고 궁리하지 않으면 여기까지 잘는 가지 않는다고 생각합니다만, 입문으로서는 재미 있다고 생각합니다.

좋은 웹페이지 즐겨찾기