« ^ »

CPPNを学ぶ

所要時間: 約 2分

もう本当に、わからない事が多すぎて困る。しかし困ったままでは、本当に困ってしまうので、詰む前に学ぼうと思う。では何を学ぶべきだろうか?考えた結果、今回はCPPNを学ぶ事にする。

CPPNは生物の空間的パターンの生成に注目し、同じような幾何学的パターンを生成できるようにしたニューラルネットワークの事。今回は、それをPythonとnumpyで実装してみる事にする。

既にCPPNとして実装されたプログラムや、その他の資料を参考に実装する1。 CPPN以外の部分の要素としては、numpyのndarrayから画像を生成するところくらいか。しかし、それもそれほど難しくない。pillowという画像を取り扱うライブラリを用いて、画像の生成を行っている。空の画像を生成する例を示す。

import numpy as np
from PIL import Image

img_data = np.array([
    [],
], dtype=np.uint8)
im = Image.fromarray(img_data)
im.save("test.png")

活性化関数には双曲線正接関数を使う2。本来は複数の活性化関数を使用する必要があるのかもしれない。

import numpy as np
from PIL import Image

def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-1 * x))

def std_normal(row, col):
    return np.random.standard_normal(size=(row, col)).astype(np.float32)


batch_size = 1
net_size = 16  # 重みの行列の列数
h_size = 32  # 隠れ層のノード数(つまり次元数)
x_res = 512
y_res = 512
scaling = 1.0
c_dim = 3  # RGB

np.random.seed(None)  # 乱数初期化

# バッチ数x隠れ層のノード数=(1, 32)の行列を作成する。
# 各値は[-1,1]の範囲のランダムな値で初期化する。
hid_vec = np.random.uniform(
    low=-1.0,
    high=1.0,
    size=(batch_size, h_size),
).astype(np.float32)

# グリッドを作成する
num_points = x_res * y_res

# array([-1.,  0.,  1.])
x_range = np.linspace(-1 * scaling, scaling, num=x_res)

# array([-1., -0.33333333, 0.33333333,  1.])
y_range = np.linspace(-1 * scaling, scaling, num=y_res)

# マトリクスの作成
x_mat = np.matmul(np.ones((y_res, 1)), x_range.reshape((1, x_res)))
y_mat = np.matmul(y_range.reshape((y_res, 1)), np.ones((1, x_res)))
r_mat = np.sqrt(x_mat * x_mat + y_mat * y_mat)

x_mat = np.tile(x_mat.flatten(), batch_size).reshape(batch_size, num_points, 1)
y_mat = np.tile(y_mat.flatten(), batch_size).reshape(batch_size, num_points, 1)
r_mat = np.tile(r_mat.flatten(), batch_size).reshape(batch_size, num_points, 1)

# 必要なデータを取り出し整形する
hid_vec_scaled = (
    np.reshape(hid_vec, (batch_size, 1, h_size))
    * np.ones((num_points, 1), dtype=np.float32)
    * scaling
)
x_dat = np.reshape(x_mat, (batch_size * num_points, 1))
y_dat = np.reshape(y_mat, (batch_size * num_points, 1))
r_dat = np.reshape(r_mat, (batch_size * num_points, 1))
h_vec = np.reshape(hid_vec_scaled, (batch_size * num_points, h_size))


# ニューラルネットワークを構築する
bias = std_normal(1, net_size) * np.ones((h_vec.shape[0], 1), dtype=np.float32)
art_net = np.matmul(h_vec, std_normal(h_vec.shape[1], net_size)) \
    + bias \
    + np.matmul(x_dat, std_normal(x_dat.shape[1], net_size)) \
    + np.matmul(y_dat, std_normal(y_dat.shape[1], net_size)) \
    + np.matmul(r_dat, std_normal(r_dat.shape[1], net_size))

# 活性化関数を適応する
h = np.tanh(art_net)
for i in range(3):  # 層の数
    b1 = std_normal(1, net_size) * np.ones((h.shape[0], 1), dtype=np.float32)
    h = np.tanh(np.matmul(h, std_normal(h.shape[1], net_size)) + b1)

    b2 = std_normal(1, c_dim) * np.ones((h.shape[0], 1), dtype=np.float32)
    a2 = np.matmul(h, std_normal(h.shape[1], c_dim)) + b2
    out = sigmoid(a2)

# 画像として扱える形式に変換
# (画像数, 横座標、縦座標、色)
image_data = np.reshape(out, (batch_size, x_res, y_res, c_dim))

# 画像の形式に整形
img_data = np.array(1 - image_data)
img_data = np.array(img_data.reshape((y_res, x_res, c_dim))*255.0, dtype=np.uint8)

# ファイルへの出力
# (横座標、縦座標、色)
im = Image.fromarray(img_data)
im.save("art.png")

実行すると、色がグラデーションのように変化する画像が生成される。

https://res.cloudinary.com/symdon/image/upload/v1684061726/blog.symdon.info/1683615569/art_kmmvrs.png

良さそうだ。


2

これは参考にしたコードがtanhを使用していたから