ホーム >  機械学習 > Google Colaboratory >  【GoogleColab】ニューラルネットワークの画像生成 その3【オートエンコーダ】

投稿日:   |  最終更新日:

【GoogleColab】ニューラルネットワークの画像生成 その3【オートエンコーダ】

Google ColaboratoryPython機械学習

GoogleColabで画像を生成するニューラルネットワークのプログラムを実行第3回目です。オートエンコーダの学習を行います。

オートエンコーダの学習

前回、オートエンコーダのニューラルネットワークの画準備を行いました。今回は、実際に学習をしてみます。

前回までの作成プログラム

①PyTorchをインポートし、学習データMINISTを取得します。
学習データ(画像)の取得

②読み込んだ学習データMINISTを表示して確認します。
データセットの画像確認

③学習用データセットのクラスを作成します。
学習用データセットの準備

④DataSubsetのインスタンスを生成し、画像などの設定を行います。
DataSubsetのインスタンス準備

⑤DataLoaderの設定を行います。設定したら一度ミニバッチを表示します。
DataLoaderの設定

⑥ニューラルネットクラスを作成します。オートエンコーダー、オートデコーダーをここで定義します。
ニューラルネットの準備

⑦結果表示用関数を作成します。オートエンコーダー、オートデコーダーを出る前と出た後の画像両方を表示します。
結果表示用の関数

⑧AutoEncorderとLinerAEのインスタンスを生成し、学習前のモデルの振る舞いを確認します。
オートエンコーダーの準備

必要なもの

必要なものはWindowsあるいはMacPCのみです。ただし、GoogleChoromeをインストールしている必要があります。GoogleColabを使うには、以下のステップが必要です。

①Googleアカウントを用意してログインする。

②Colabにアクセスし、プログラムを書き込むノートブックを作成する。

【無料】Google Colabを使ってサクッとPythonを使う【Python】

実行環境の準備

①Colabにアクセスします。

Colab

②左上の「ドライブにアクセス」をクリックします。

③「その他」の「アプリを追加」から「Colaboratory」をインストールします。

④ノートブックをひらけばpythonのコードが記述できます。


オートエンコーダの学習用関数

オートエンコーダの学習を行うための関数を以下のように定義します。

def train_ae(net, loader, num_epochs=3):
  # 勾配降下法はAdamを使用
  optimizer = torch.optim.Adam(params=net.parameters(),
    lr=0.001, betas=(0.9, 0.999), weight_decay=1e-5)
  # 損失関数
  criterion = nn.MSELoss(reduction = 'mean')

  net.train()
  loss_log = []

  for epoch in range(num_epochs):
    print(f'Epoch {epoch + 1}/{num_epochs}¥t|¥t',end='')
    sum_loss = 0.0
    for imgs in loader:
      #入力を1次元にする
      imgs = imgs.view(-1, imgs.shape[2] * imgs.shape[3])
      imgs = imgs.to(device)
      outputs = net(imgs)
      loss = criterion(outputs, imgs)
      optimizer.zero_grad() #勾配を初期化
      loss.backward() #誤差逆伝播法
      optimizer.step() #パラメータ更新
      # 損失を1バッチ分加算
      sum_loss += loss.item() * imgs.size(0)

    # 1エポック分の評価価値を計算
    e_loss = sum_loss / len(loader.dataset)  #エポックの平均損失
    print(f"Loss : {e_loss:.4f}")
    loss_log.append(e_loss)

  return loss_log

解説

  # 勾配降下法はAdamを使用
  optimizer = torch.optim.Adam(params=net.parameters(),
    lr=0.001, betas=(0.9, 0.999), weight_decay=1e-5)

重みのパラメーター更新は、誤差逆伝播法を用いた勾配降下法で行います。Adam関数は、勾配降下法を効率よく行う手法です。

  # 損失関数
  criterion = nn.MSELoss(reduction = 'mean')

MES損失を誤差逆伝播法における誤差の基準として指定します。つまり、オートエンコーダの出力が、入力とできるだけ近くように学習させます。

  for epoch in range(num_epochs):
    print(f'Epoch {epoch + 1}/{num_epochs}¥t|¥t',end='')
    sum_loss = 0.0
    for imgs in loader:
      #入力を1次元にする
      imgs = imgs.view(-1, imgs.shape[2] * imgs.shape[3])
      imgs = imgs.to(device)
      outputs = net(imgs)
      loss = criterion(outputs, imgs)
      optimizer.zero_grad() #勾配を初期化
      loss.backward() #誤差逆伝播法
      optimizer.step() #パラメータ更新
      # 損失を1バッチ分加算
      sum_loss += loss.item() * imgs.size(0)

    # 1エポック分の評価価値を計算
    e_loss = sum_loss / len(loader.dataset)  #エポックの平均損失
    print(f"Loss : {e_loss:.4f}")
    loss_log.append(e_loss)

データセット全てのデータを1回ずつ入力することを「エポック」(epoch)と呼びます。このエポックを繰り返すループです。また1つのエポック内では、データセットからミニバッチを順に取り出します。これが内側のforループになります。各反復でどのデータが選ばれるかランダムになっていますが、一通り全てのデータが取り出されると、1つのエポックは終了です。

    # 1エポック分の評価価値を計算
    e_loss = sum_loss / len(loader.dataset)  #エポックの平均損失
    print(f"Loss : {e_loss:.4f}")
    loss_log.append(e_loss)

最後そのエポックにおける誤差(損失)の平均を計算し、表示します。


学習の実行

学習の実行を以下のように行います。

num_epochs = 100
print('=' * 10, 'Train AutoEncoder', '=' * 10)
ae_loss_log = train_ae(ae_net, loader, num_epochs=num_epochs)

print('=' * 10, 'Train LinerAE', '=' * 10)
linae_loss_log = train_ae(linae_net, loader, num_epochs=num_epochs)

#学習曲線をプロット
fig = plt.figure(figsize=(8,6))
plt.plot(range(1, len(ae_loss_log) + 1), ae_loss_log, '.-', label='AE')
plt.plot(range(1, len(linae_loss_log) + 1), linae_loss_log, '.-', label='Linear')
plt.legend()
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.ylim(0)
plt.show()

#再構成画像を確認
print('AutoEncorder')
input_and_reconst(ae_net, loader, seed=0)
print('Linear module')
input_and_reconst(linae_net, loader, seed=0)

解説

num_epochs = 100

number_epochsでは、学習を何エポック繰り返すかを指定しています。ここでは、100とします。

print('=' * 10, 'Train AutoEncoder', '=' * 10)
ae_loss_log = train_ae(ae_net, loader, num_epochs=num_epochs)

print('=' * 10, 'Train LinerAE', '=' * 10)
linae_loss_log = train_ae(linae_net, loader, num_epochs=num_epochs)

通常の非線形なオートエンコーダ(Autoencoder)と、線形なオートエンコーダ(LinearAE)を「traon_ae()」で学習します。

#学習曲線をプロット
fig = plt.figure(figsize=(8,6))
plt.plot(range(1, len(ae_loss_log) + 1), ae_loss_log, '.-', label='AE')
plt.plot(range(1, len(linae_loss_log) + 1), linae_loss_log, '.-', label='Linear')
plt.legend()
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.ylim(0)
plt.show()

学習時に損失がどのように変化していったかを、横軸をエポック数にしてプロットしたものです。いずれのモデルもエポック数が大きくなるに従って損失が小さくなっていきます。

#再構成画像を確認
print('AutoEncorder')
input_and_reconst(ae_net, loader, seed=0)
print('Linear module')
input_and_reconst(linae_net, loader, seed=0)

学習後の再構成画像を入力画像と共に確認します。学習後は、以下の下段のように、いずれの入力画像もある程度復元されているのが分かります。線形モデルの結果がぼやけているのに対し、非線形の活性化関数を用いたオートエンコーダの方は、比較的明瞭です。


========== Train AutoEncoder ==========
Epoch 1/100¥t|¥tLoss : 0.0548
Epoch 2/100¥t|¥tLoss : 0.0511
Epoch 3/100¥t|¥tLoss : 0.0489
Epoch 4/100¥t|¥tLoss : 0.0473
Epoch 5/100¥t|¥tLoss : 0.0470
Epoch 6/100¥t|¥tLoss : 0.0475
Epoch 7/100¥t|¥tLoss : 0.0466
Epoch 8/100¥t|¥tLoss : 0.0467
Epoch 9/100¥t|¥tLoss : 0.0470
Epoch 10/100¥t|¥tLoss : 0.0470
Epoch 11/100¥t|¥tLoss : 0.0481
Epoch 12/100¥t|¥tLoss : 0.0483
Epoch 13/100¥t|¥tLoss : 0.0478
Epoch 14/100¥t|¥tLoss : 0.0474
Epoch 15/100¥t|¥tLoss : 0.0474
Epoch 16/100¥t|¥tLoss : 0.0464
Epoch 17/100¥t|¥tLoss : 0.0471
Epoch 18/100¥t|¥tLoss : 0.0473
Epoch 19/100¥t|¥tLoss : 0.0465
Epoch 20/100¥t|¥tLoss : 0.0462
Epoch 21/100¥t|¥tLoss : 0.0457
Epoch 22/100¥t|¥tLoss : 0.0480
Epoch 23/100¥t|¥tLoss : 0.0476
Epoch 24/100¥t|¥tLoss : 0.0473
Epoch 25/100¥t|¥tLoss : 0.0478
Epoch 26/100¥t|¥tLoss : 0.0470
Epoch 27/100¥t|¥tLoss : 0.0467
Epoch 28/100¥t|¥tLoss : 0.0457
Epoch 29/100¥t|¥tLoss : 0.0457
Epoch 30/100¥t|¥tLoss : 0.0458
Epoch 31/100¥t|¥tLoss : 0.0458
Epoch 32/100¥t|¥tLoss : 0.0461
Epoch 33/100¥t|¥tLoss : 0.0463
Epoch 34/100¥t|¥tLoss : 0.0461
Epoch 35/100¥t|¥tLoss : 0.0463
Epoch 36/100¥t|¥tLoss : 0.0466
Epoch 37/100¥t|¥tLoss : 0.0477
Epoch 38/100¥t|¥tLoss : 0.0469
Epoch 39/100¥t|¥tLoss : 0.0464
Epoch 40/100¥t|¥tLoss : 0.0459
Epoch 41/100¥t|¥tLoss : 0.0461
Epoch 42/100¥t|¥tLoss : 0.0465
Epoch 43/100¥t|¥tLoss : 0.0474
Epoch 44/100¥t|¥tLoss : 0.0489
Epoch 45/100¥t|¥tLoss : 0.0493
Epoch 46/100¥t|¥tLoss : 0.0492
Epoch 47/100¥t|¥tLoss : 0.0490
Epoch 48/100¥t|¥tLoss : 0.0473
Epoch 49/100¥t|¥tLoss : 0.0462
Epoch 50/100¥t|¥tLoss : 0.0463
Epoch 51/100¥t|¥tLoss : 0.0461
Epoch 52/100¥t|¥tLoss : 0.0460
Epoch 53/100¥t|¥tLoss : 0.0453
Epoch 54/100¥t|¥tLoss : 0.0451
Epoch 55/100¥t|¥tLoss : 0.0453
Epoch 56/100¥t|¥tLoss : 0.0448
Epoch 57/100¥t|¥tLoss : 0.0452
Epoch 58/100¥t|¥tLoss : 0.0450
Epoch 59/100¥t|¥tLoss : 0.0446
Epoch 60/100¥t|¥tLoss : 0.0448
Epoch 61/100¥t|¥tLoss : 0.0450
Epoch 62/100¥t|¥tLoss : 0.0456
Epoch 63/100¥t|¥tLoss : 0.0453
Epoch 64/100¥t|¥tLoss : 0.0450
Epoch 65/100¥t|¥tLoss : 0.0453
Epoch 66/100¥t|¥tLoss : 0.0473
Epoch 67/100¥t|¥tLoss : 0.0476
Epoch 68/100¥t|¥tLoss : 0.0475
Epoch 69/100¥t|¥tLoss : 0.0468
Epoch 70/100¥t|¥tLoss : 0.0457
Epoch 71/100¥t|¥tLoss : 0.0457
Epoch 72/100¥t|¥tLoss : 0.0452
Epoch 73/100¥t|¥tLoss : 0.0449
Epoch 74/100¥t|¥tLoss : 0.0441
Epoch 75/100¥t|¥tLoss : 0.0447
Epoch 76/100¥t|¥tLoss : 0.0448
Epoch 77/100¥t|¥tLoss : 0.0441
Epoch 78/100¥t|¥tLoss : 0.0446
Epoch 79/100¥t|¥tLoss : 0.0449
Epoch 80/100¥t|¥tLoss : 0.0450
Epoch 81/100¥t|¥tLoss : 0.0451
Epoch 82/100¥t|¥tLoss : 0.0443
Epoch 83/100¥t|¥tLoss : 0.0441
Epoch 84/100¥t|¥tLoss : 0.0437
Epoch 85/100¥t|¥tLoss : 0.0438
Epoch 86/100¥t|¥tLoss : 0.0438
Epoch 87/100¥t|¥tLoss : 0.0441
Epoch 88/100¥t|¥tLoss : 0.0437
Epoch 89/100¥t|¥tLoss : 0.0443
Epoch 90/100¥t|¥tLoss : 0.0439
Epoch 91/100¥t|¥tLoss : 0.0442
Epoch 92/100¥t|¥tLoss : 0.0449
Epoch 93/100¥t|¥tLoss : 0.0460
Epoch 94/100¥t|¥tLoss : 0.0451
Epoch 95/100¥t|¥tLoss : 0.0450
Epoch 96/100¥t|¥tLoss : 0.0461
Epoch 97/100¥t|¥tLoss : 0.0451
Epoch 98/100¥t|¥tLoss : 0.0445
Epoch 99/100¥t|¥tLoss : 0.0452
Epoch 100/100¥t|¥tLoss : 0.0451
========== Train LinerAE ==========
Epoch 1/100¥t|¥tLoss : 0.2794
Epoch 2/100¥t|¥tLoss : 0.1677
Epoch 3/100¥t|¥tLoss : 0.1490
Epoch 4/100¥t|¥tLoss : 0.1441
Epoch 5/100¥t|¥tLoss : 0.1427
Epoch 6/100¥t|¥tLoss : 0.1419
Epoch 7/100¥t|¥tLoss : 0.1415
Epoch 8/100¥t|¥tLoss : 0.1410
Epoch 9/100¥t|¥tLoss : 0.1406
Epoch 10/100¥t|¥tLoss : 0.1401
Epoch 11/100¥t|¥tLoss : 0.1397
Epoch 12/100¥t|¥tLoss : 0.1392
Epoch 13/100¥t|¥tLoss : 0.1388
Epoch 14/100¥t|¥tLoss : 0.1383
Epoch 15/100¥t|¥tLoss : 0.1379
Epoch 16/100¥t|¥tLoss : 0.1375
Epoch 17/100¥t|¥tLoss : 0.1370
Epoch 18/100¥t|¥tLoss : 0.1365
Epoch 19/100¥t|¥tLoss : 0.1361
Epoch 20/100¥t|¥tLoss : 0.1357
Epoch 21/100¥t|¥tLoss : 0.1353
Epoch 22/100¥t|¥tLoss : 0.1349
Epoch 23/100¥t|¥tLoss : 0.1345
Epoch 24/100¥t|¥tLoss : 0.1341
Epoch 25/100¥t|¥tLoss : 0.1338
Epoch 26/100¥t|¥tLoss : 0.1334
Epoch 27/100¥t|¥tLoss : 0.1331
Epoch 28/100¥t|¥tLoss : 0.1327
Epoch 29/100¥t|¥tLoss : 0.1324
Epoch 30/100¥t|¥tLoss : 0.1320
Epoch 31/100¥t|¥tLoss : 0.1317
Epoch 32/100¥t|¥tLoss : 0.1315
Epoch 33/100¥t|¥tLoss : 0.1311
Epoch 34/100¥t|¥tLoss : 0.1309
Epoch 35/100¥t|¥tLoss : 0.1306
Epoch 36/100¥t|¥tLoss : 0.1304
Epoch 37/100¥t|¥tLoss : 0.1301
Epoch 38/100¥t|¥tLoss : 0.1298
Epoch 39/100¥t|¥tLoss : 0.1297
Epoch 40/100¥t|¥tLoss : 0.1294
Epoch 41/100¥t|¥tLoss : 0.1292
Epoch 42/100¥t|¥tLoss : 0.1290
Epoch 43/100¥t|¥tLoss : 0.1288
Epoch 44/100¥t|¥tLoss : 0.1286
Epoch 45/100¥t|¥tLoss : 0.1285
Epoch 46/100¥t|¥tLoss : 0.1283
Epoch 47/100¥t|¥tLoss : 0.1281
Epoch 48/100¥t|¥tLoss : 0.1280
Epoch 49/100¥t|¥tLoss : 0.1279
Epoch 50/100¥t|¥tLoss : 0.1277
Epoch 51/100¥t|¥tLoss : 0.1276
Epoch 52/100¥t|¥tLoss : 0.1275
Epoch 53/100¥t|¥tLoss : 0.1273
Epoch 54/100¥t|¥tLoss : 0.1272
Epoch 55/100¥t|¥tLoss : 0.1271
Epoch 56/100¥t|¥tLoss : 0.1270
Epoch 57/100¥t|¥tLoss : 0.1269
Epoch 58/100¥t|¥tLoss : 0.1268
Epoch 59/100¥t|¥tLoss : 0.1267
Epoch 60/100¥t|¥tLoss : 0.1267
Epoch 61/100¥t|¥tLoss : 0.1266
Epoch 62/100¥t|¥tLoss : 0.1265
Epoch 63/100¥t|¥tLoss : 0.1264
Epoch 64/100¥t|¥tLoss : 0.1264
Epoch 65/100¥t|¥tLoss : 0.1263
Epoch 66/100¥t|¥tLoss : 0.1263
Epoch 67/100¥t|¥tLoss : 0.1262
Epoch 68/100¥t|¥tLoss : 0.1262
Epoch 69/100¥t|¥tLoss : 0.1261
Epoch 70/100¥t|¥tLoss : 0.1261
Epoch 71/100¥t|¥tLoss : 0.1261
Epoch 72/100¥t|¥tLoss : 0.1260
Epoch 73/100¥t|¥tLoss : 0.1259
Epoch 74/100¥t|¥tLoss : 0.1259
Epoch 75/100¥t|¥tLoss : 0.1259
Epoch 76/100¥t|¥tLoss : 0.1259
Epoch 77/100¥t|¥tLoss : 0.1258
Epoch 78/100¥t|¥tLoss : 0.1258
Epoch 79/100¥t|¥tLoss : 0.1258
Epoch 80/100¥t|¥tLoss : 0.1257
Epoch 81/100¥t|¥tLoss : 0.1257
Epoch 82/100¥t|¥tLoss : 0.1257
Epoch 83/100¥t|¥tLoss : 0.1257
Epoch 84/100¥t|¥tLoss : 0.1257
Epoch 85/100¥t|¥tLoss : 0.1257
Epoch 86/100¥t|¥tLoss : 0.1257
Epoch 87/100¥t|¥tLoss : 0.1256
Epoch 88/100¥t|¥tLoss : 0.1256
Epoch 89/100¥t|¥tLoss : 0.1256
Epoch 90/100¥t|¥tLoss : 0.1257
Epoch 91/100¥t|¥tLoss : 0.1256
Epoch 92/100¥t|¥tLoss : 0.1255
Epoch 93/100¥t|¥tLoss : 0.1256
Epoch 94/100¥t|¥tLoss : 0.1255
Epoch 95/100¥t|¥tLoss : 0.1255
Epoch 96/100¥t|¥tLoss : 0.1256
Epoch 97/100¥t|¥tLoss : 0.1255
Epoch 98/100¥t|¥tLoss : 0.1255
Epoch 99/100¥t|¥tLoss : 0.1255
Epoch 100/100¥t|¥tLoss : 0.1255


潜在変数からの画像の生成

潜在変数からの画像生成を行います。(本題)オートエンコーダでは、潜在変数を決めればそこからデコーダを使って画像を生成することができます。ここで用いるオートエンコーダでは潜在変数が2次元です。つまり、2つの値を指定すれば良いのですが、どのあたりの範囲で指定すれば良いのかが分かりません。そこで、まず学習用データの画像がどのような潜在変数に変換されるかを見ておきましょう。

以下のプログラムは、全てのデータセットの画像を潜在変数へ変換するための「get_latent_variables()」という関数を定義します。

●データセットの画像を潜在変数へ変換

def get_latent_variables(net, loader):
  """" エンコーダで変換後の潜在変数を取得"""
  z_list = []
  net.eval() #モデルを評価モードへ
  with torch.no_grad(): #勾配計算を止める
    for imgs in loader:     #ミニバッチを入力
      imgs = imgs.view(    # 画像を1次元化
          -1, imgs.shape[2] * imgs.shape[3]
      ).to(device)
      z_batch = net.encoder(imgs)
      z_list.append(z_batch.detach().cpu().numpy())
  return np.concatenate(z_list, axis=0)

z_encoded = get_latent_variables(ae_net, loader)
print(z_encoded.shape)
print(z_encoded[:10, :])

(結果)

(600, 2)
[[ 5.9267254   2.1248462 ]
 [-0.88702893  2.7848637 ]
 [ 1.785806    0.42160788]
 [-0.28692353 -1.370374  ]
 [-1.2406417   4.505816  ]
 [ 2.3206477  -0.7260132 ]
 [-0.88784707 -1.036326  ]
 [ 1.8184772   0.4223911 ]
 [ 2.8289511   0.6041493 ]
 [-0.55544984  7.012882  ]]

解説

def get_latent_variables(net, loader):
  """" エンコーダで変換後の潜在変数を取得"""
  z_list = []
  net.eval() #モデルを評価モードへ
  with torch.no_grad(): #勾配計算を止める
    for imgs in loader:     #ミニバッチを入力
      imgs = imgs.view(    # 画像を1次元化
          -1, imgs.shape[2] * imgs.shape[3]
      ).to(device)
      z_batch = net.encoder(imgs)
      z_list.append(z_batch.detach().cpu().numpy())

「net.encoder()」は特に重要です。オートエンコーダのうちのエンコーダの部分のみを使って各画像の存在変数へ変換しています。

z_encoded = get_latent_variables(ae_net, loader)
print(z_encoded.shape)
print(z_encoded[:10, :])

冒頭で定義した関数を使って潜在変数をを求めます。これによって2次元の潜在変数が600個得られます。つまり、z_encodedは2次元配列になります。

次回

ランダムに選んだ点から画像を生成します。


トラックバック用のURL
プロフィール

名前:イワサキ ユウタ 職業:システムエンジニア、ウェブマスター、フロントエンドエンジニア 誕生:1986年生まれ 出身:静岡県 特技:ウッドベース 略歴 20

最近の投稿
人気記事
カテゴリー
広告