研修2日目(続き) ~ そしてcelebA、20万を超える顔画像
celebAは?
40項目の属性の組み合わせ
MNISTの場合には、単に平均をとっているだけということとなました。このこと自体は「なーんだ、それだけのことか」と思われるのではないでしょうか?
しかしcelebAの人の顔ではどうなるでしょう?
百歩譲って、その面白くない平均をとるだけだとしても、属性は40項目もあって、その組み合わせなので膨大な数になります。
従って様々な顔かたちが表出することが期待できるのです。
モデルをつくる
層数の多い複雑なニューラルネットワークも考えられますが、ここでは簡単な例を示します。
kwargs = {'optimize' : 'AdamT', 'w_decay' : 0.001}
model = nn.SequentialWithLoss(
nn.NeuronLayer(512, activate='Mish', **kwargs),
nn.NeuronLayer(C*Ih*Iw, activate='Sigmoid', **kwargs),
F.Reshape(-1,C,Ih,Iw),
lf.CrossEntropyError2(reduction='sample')
)
このようにNeuronLayerを2層積み重ねただけの簡単なものです。
学習コード
学習はMNIST同様です。
for i in range(epoch):
for x, t in loader: # data_loaderをイテレータとして使う
# 順伝播と逆伝播→更新
y, l = model(t, x)
l.backtrace()
model.update(eta=0.0003)
# -- 計測 data_loaderからデータを取得し定点観測 --
error, accuracy = measurement(t0, x0)
# -- 経過表示 --
if i%interval == 0:
print(f'{i+1:3d}/{epoch:3d} | {time.time()-start:7.1f} |',
f' {error:8.3f} | {accuracy*100:6.2f}%')
start = time.time()
顔画像の生成1(学習データによる)
学習が終わったら、以下のコードで、まずは学習で入力として使ったデータによる顔画像生成を試みます。
x, t = loader[:50] y = model(t) cf.show_multi_samples(y)
見てのとおり、データローダからはじめの50個のデータを取り出して、そのt、すなわち、属性をmodelに入れて、出力yに画像を得るのです。さっそく結果(の一例)を以下に示します。

あらためて、このtに対する元画像を次に示します。これは以下で見ることが出来ます。
先ほど述べたように、このxはデータローダからx, t = loader[:50]によってtとともに取り出したものです。だから上に示した画像を生成する際にmodelに入力したtと対応します。
したがって、下に示す元画像が上に示す生成画像に対応するのです。

2つの画像を見比べて、どうですか?生成画像の方は確かに没個性ですね?
そして元画像では左を向いたり右を向いたりしているのが、生成画像ではどれも正面を向いています。
さらに背景は元画像では様々なものが映り込んでいますが、多少の色調の違いはあるものの生成画像ではグレーの一色で塗りつぶされています。
それでも、男女の違いや、髪色の濃淡など大きな特徴については元画像の特徴が生成画像でも保存されています。
元画像の特徴が生成画像でも保存?、没個性との関係は?
これはいったいどういう事なのでしょうか?
元画像があると言っても、modelに入力されるのは、その属性です。
元画像が使われるのは、損失関数において、modelの出力の画像に対する正解、つまり教師データとしてです。ここのところの事情はMNISTで文字生成したのと同様です。
そして、modelに入力される属性ラベルtの40項目の、それぞれ値0に対してたくさんの、また値1に対してもたくさんの正解画像が与えられます。
そしてその正解画像は数が多いばかりでなく多様です。
そうすると、MNISTの場合の生成文字が平均画像になったのと同様に、多様な画像の多様性の部分は平均へと引き寄せられ、一様になるのです。
そして属性の値が0であるものの平均と値が1であるものの平均にニ分されることになるのです。
されどそういう属性が40項目あるから、その40項目の0/1それぞれの平均の組み合わせが、没個性でありながら、多様な顔を生み出しているのです。
顔画像の生成2(乱数による)
さあ何が起きているかは分かったところで、次は乱数を与えて顔画像を生成します。なぜ乱数かというと、40項目の属性の組み合わせは、celebAのデータの中にすべてが含まれるわけではないから、そのcelebAに存在しない組み合わせでもちゃんと顔画像が生成されるかを確認したいのと、その存在しない組み合わせで生成される顔画像が一体どんな顔画像を産むのかをみたいからです。
これは以下のコードで簡単に試すことが出来ます。
T = np.random.rand(50,40) Y = model(T) cf.show_multi_samples(Y)

これはすべて似たような顔になってしまいました。それというのも乱数が0と1の間のどっちつかずの値が多くを占めるからではないかと思います。
顔画像の生成3(0/1の乱数による)
顔画像の生成2でmodelにに入力した乱数は、0~1の範囲の浮動小数点数でした。これではすべてが似たり寄ったりでした。
学習に使ったデータの属性ラベルは0または1の2値なので、作った乱数を以下のコードで0か1の2値に加工し、modelに入力して画像を得ます。
Tb = T >= 0.5 Yb = model(Tb) cf.show_multi_samples(Yb)
これで得られる画像は次に示すように個性がかなりはっきりしてきます。

このように顔そのものはずいぶん個性的になっています。
しかしよく見るとやはり、みんな揃って正面を向いているし、背景も何も写っていません。
そして髪の毛やサングラスや髭などは様々ですが、これら様々なものは、属性にあった40項目に限定されていることに気付きます。
やはり、ここで起きていることは、属性として指定された40項目の0/1のほかは、すべて平均化されているということではないでしょうか?
おそらく、属性の中に顔の向きや、背景の特徴などがあれば、それらの0/1に応じた、また違った画像が生成されただろうと思われます。
結局のところ、人が禿ている/禿ていないのように0/1で付けらラベルの範囲に忠実に画像が作られたということ、なのだと思います。
多様で豊かな世界
40項目もの属性をあたえるというと、非常に多様なものを表わすかのようで、実はこの生成画像が示すのは、ラベルによって二分される解釈が表す偏狭な人物感に過ぎない、という事なのです。
こうしてみると、実は世界は非常に多様であって、それを普段は意識すらしていないけれども、非常に豊かだということなのだと思います。みなさん、電車に乗ったら、スマホばかり見ていないで、いろんな顔をした人が、これまたいろんな表情をしているのを、こっそり見てみたらいかがでしょうか?世界が豊かだと気付くと少しだけ幸せになれるかもしれません。
ブログのご感想やAIフレームワーク「ufiesia」「pyaino」に関するお問い合わせは、
問い合わせフォームからお送りください。
- お問い合わせ内容によりましては、ご期待に添えない場合やご回答が出来ない事が有ります。