スプラトゥーン2のブキをDCGANで生成してみる - 3月 28, 2020 こんにちは、ぐぐりら(@guglilac)です。 春休み、PyTorch本を買ってPyTorchを久しぶりにまた触ったり、スプラトゥーンをやったりして過ごしていたらこのような記事ができてしまいました。 コロナの影響もあり、引きこもり生活が捗るので、必然的に勉強もスプラトゥーンも捗るわけです。 今回は、スプラのブキの画像をDCGANで生成してみよう、という記事です。 最初にお断りしておくと、うまくできませんでした。ここから続けて取り組むことはないと思ったので、供養のため記事にします。 あまりGANとか詳しくないので、勉強がてら実装、実験してみたという軽〜い気持ちで書いてみた記事なので、軽〜い気持ちで読んでください。 ## 何をやった(やろうとした)のか スプラトゥーンは4 vs 4でそれぞれが好きなブキ(武器)を持って、フィールドをインクで塗り合うゲームです。 ブキの種類によって塗りの性能や、必殺技みたいなものが異なっています。 [【スプラトゥーン2】全武器(ブキ)一覧とサブ・スペシャル|ゲームエイト](https://game8.jp/splatoon-2/158447) ブキごとの以下のようなビジュアルが設定されているのですが、これをGANで新しく作れないかな、というのが今回やろうとしたことです。 上記のサイトではブキの画像が緑の背景で中央にブキ本体が配置されているという形式で統一されていたので、スクレイピングしてお借りさせていただきました。 130種類ぐらいあった気がします。 ## DCGAN 生成モデルには、DCGANというのを使いました。 あまり詳しくないので勉強しながら、この辺りが実装もよく公開されているのを見るし使えそうかな?と思いチャレンジしてみることに。 DCGAN自体の説明は他の記事に譲ります。 [できるだけ丁寧にGANとDCGANを理解する - 午睡二時四十分](http://mizti.hatenablog.com/entry/2016/12/10/224426) 今回は冒頭に述べた通り、PyTorchで実装しました。 コードはこんなかんじ。 GeneratorとDiscriminatorはこちら。 ```python class Generator(nn.Module): def __init__(self): super().__init__() self.main = nn.Sequential( nn.ConvTranspose2d(100, 256, 4, 1, 0, bias=False), nn.BatchNorm2d(256), nn.ReLU(inplace=True), nn.ConvTranspose2d(256, 128, 4, 2, 1, bias=False), nn.BatchNorm2d(128), nn.ReLU(inplace=True), nn.ConvTranspose2d(128, 64, 4, 2, 1, bias=False), nn.BatchNorm2d(64), nn.ReLU(inplace=True), nn.ConvTranspose2d(64, 32, 4, 2, 1, bias=False), nn.BatchNorm2d(32), nn.ReLU(inplace=True), nn.ConvTranspose2d(32, 3, 4, 2, 1, bias=False), nn.Tanh() ) def forward(self, x): return self.main(x) class Discriminator(nn.Module): def __init__(self): super().__init__() self.main = nn.Sequential( nn.Conv2d(3, 32, 4, 2, 1, bias=False), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(32, 64, 4, 2, 1, bias=False), nn.BatchNorm2d(64), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(64, 128, 4, 2, 1, bias=False), nn.BatchNorm2d(128), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(128, 256, 4, 2, 1, bias=False), nn.BatchNorm2d(256), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(256, 1, 4, 1, 0, bias=False), ) def forward(self, x): return self.main(x).squeeze() ``` 学習部分はこのように書きました。 ```python # 潜在特徴の次元 nz = 100 ones = torch.ones(batch_size).to("cuda:0") zeros = torch.zeros(batch_size).to("cuda:0") loss_f = nn.BCEWithLogitsLoss() # 途中結果の確認用 check_z = torch.randn(batch_size, nz, 1, 1).to("cuda:0") # 訓練関数 1epochだけ def train_dcgan(model_G, model_D, params_G, params_D, data_loader): log_loss_G = [] log_loss_D = [] for real_img, _ in data_loader: # 多分二つ目はclass(data loaderを使っているので) batch_len = len(real_img) # Treain Generator # 偽画像を生成 z = torch.randn(batch_len, nz, 1, 1).to("cuda:0") fake_img = model_G(z) fake_img_tensor = fake_img.detach() out = model_D(fake_img) # fake_imgの方は計算グラフをきっていないのでmodel_G側に伝搬してくれる loss_G = loss_f(out, ones[: batch_len]) log_loss_G.append(loss_G.item()) model_D.zero_grad() model_G.zero_grad() loss_G.backward() params_G.step() # train Discriminator real_img = real_img.to("cuda:0") real_out = model_D(real_img) loss_D_real = loss_f(real_out, ones[: batch_len]) fake_img = fake_img_tensor # detachした方を使うことで、偽画像とラベル0と渡した時のloss由来の誤差がmodel_G側に伝搬しないようにする fake_out = model_D(fake_img_tensor) loss_D_fake = loss_f(fake_out, zeros[: batch_len]) loss_D = loss_D_real + loss_D_fake log_loss_D.append(loss_D.item()) model_D.zero_grad() model_G.zero_grad() loss_D.backward() params_D.step() return mean(log_loss_G), mean(log_loss_D) ``` ## 結果 学習が進むごとに確認用の潜在表現を入力して生成した画像を保存しました。 シューターに加えて、フデっぽいもの、バケツっぽいものが見えます。 途中まではそこそこ学習しているようで、遠目から薄目でみればスプラのブキです() これはもう少し進めればより鮮明な画像を生成してくれるのかな?とワクワクして待っていると、次のようになりました。 うまく行っていると思ったのですが、急に生成される画像が暗くなり始め、砂嵐のような画像になり、ついに真っ暗になってしまいました... 悲しい ## 思ったこと GANの学習の安定性は近年の研究対象の一つだと思うのですが、こういう現象は学習が不安定だから起こっている、という認識でいいんですかね。 画像の枚数が150枚弱とかなり少ないのも問題なのだろうと思います。(同じくらいのオーダーの画像数でできている記事も見るのでなんとも言えないですが...) あと、結果を見てみるともともとデータセットにあるブキにとても似ている画像ばかり出ています。 全く新しい画像みたいなものは出てこないのかな? なんにしても、実際に触ってみると気になることが増えてくるので、これまで読んでこなかったですが、この辺りの論文を少し読んでみようかなという気持ちになりました。 この記事をシェアする Twitter Facebook Google+ B!はてブ Pocket Feedly コメント
コメント
コメントを投稿