バイオリンプロットの本当の描き方を見せてやる (Optuna+PIL編)

[「バイオリンプロットの本当の描き方を見せてやる」](https://qiita.com/maskot1977/items/60325c02fcac0800df85)という記事を過去に書きましたが、その続編として、OptunaとPILを使った描き方を見せてやることにします。

# バイオリンのシルエット画像

バイオリンって、ほら、こんな形をしてるじゃないですか。


```Python
from PIL import Image
import numpy as np

img_violin = Image.open('violin.png')
im_violin = np.array(img_violin)
img_violin
```

![violinplot_1_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/0fcdcf06-e67e-3e17-6e44-3fe8d4fdfecf.png)


出典：http://happy-koujimachi.com/?p=410







この画像のサイズを測ってみますね。


```Python
im_violin.shape
```




    (880, 330, 4)



白なら0, 黒なら1 の配列にしてやります。


```Python
violin_01 = np.where(im_violin[:, :, 0] == 255, 1, 0)
```

# バイオリンプロット

それでは、バイオリンプロットを普通に描いてみましょう。こんな感じです。


```Python
import random
import numpy as np
import matplotlib.pyplot as plt

dataset = []
for _ in range(2):
    dataset += list(np.random.normal(random.random() * 100, random.random() * 100, (int(random.random() * 100))))

plt.violinplot(dataset)
plt.show()
```

![violinplot_7_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/babe057f-0807-9162-9809-2e8c8b7bbcf9.png)



これを、バイオリンプロットという名にふさわしい形で出力してやろうというのが今回の目的です。

# 教師セットと同じサイズの画像として出力する

本物のバイオリンのシルエット画像を教師セットとします。それに類似したバイオリンプロットを描くために、バイオリンプロットの画像を教師セットと同じサイズの画像として出力します。

こちらの記事を参考に関数を作成してみました。
https://qiita.com/code0327/items/43118813b6085dc7e3d1


```Python
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import Divider, Size 
from mpl_toolkits.axes_grid1.mpl_axes import Axes 

def violin(
    dataset,
    ax_w_px = 400, # プロット領域の高さをピクセル単位で指定
    ax_h_px = 300,  # プロット領域の高さをピクセル単位で指定
    # サイズ指定のための処理 ↓↓ ここから ↓↓ 
    fig_dpi = 100,
    ax_margin_inch = (0, 0, 0, 0),  # Left,Top,Right,Bottom [inch]
    flag_show = True,
    title = ""
):
    ax_w_inch = ax_w_px / fig_dpi
    ax_h_inch = ax_h_px / fig_dpi
    fig_w_inch = ax_w_inch + ax_margin_inch[0] + ax_margin_inch[2] 
    fig_h_inch = ax_h_inch + ax_margin_inch[1] + ax_margin_inch[3]

    fig = plt.figure( dpi=fig_dpi, figsize=(fig_w_inch, fig_h_inch))
    ax_p_w = [Size.Fixed(ax_margin_inch[0]),Size.Fixed(ax_w_inch)]
    ax_p_h = [Size.Fixed(ax_margin_inch[1]),Size.Fixed(ax_h_inch)]
    divider = Divider(fig, (0.0, 0.0, 1.0, 1.0), ax_p_w, ax_p_h, aspect=False)
    ax = Axes(fig, divider.get_position())
    ax.set_axes_locator(divider.new_locator(nx=1,ny=1))
    fig.add_axes(ax)
    # サイズ指定のための処理 ↑↑ ここまで ↑↑

    ax.violinplot(dataset)
    ax.axis("off")
    fig.canvas.draw()
    im = np.array(fig.canvas.renderer.buffer_rgba())
    plt.title(title)
    if flag_show:
        plt.show()
    else:
        plt.close()

    return im
```

さて、同じサイズのバイオリンプロットがこちらになります。


```Python
im = violin(
    dataset,
    ax_w_px = violin_01.shape[1],  # プロット領域の幅をピクセル単位で指定
    ax_h_px = violin_01.shape[0],  # プロット領域の高さをピクセル単位で指定
    title = "cool violin"
)
```

![violinplot_12_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/9b4e7d5a-1d3a-2a33-2d51-3d187423140a.png)




まだまだ「バイオリン」とは言えませんね。今度はこれを、白黒画像に変換します。


```Python
from PIL import Image

def im2_r(im):
    img = Image.fromarray(im)
    r, g, b, x = img.split()
    src_color = (255, 255, 255)

    _r = r.point(lambda _: 1 if _ == src_color[0] else 0, mode="1")
    _g = g.point(lambda _: 1 if _ == src_color[1] else 0, mode="1")
    _b = b.point(lambda _: 1 if _ == src_color[2] else 0, mode="1")
    return _r
```

白黒画像に変換したバイオリンプロットです。


```Python
im2_r(im)
```



![violinplot_16_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/6387be8f-0d7d-7151-43d9-dfe58dd1d685.png)





これを、教師セットと同じサイズの配列として表現します。


```
plot_01 = np.where(im2_r(im), 1, 0)
plot_01.shape
```




    (880, 330)



# 目的関数

教師セット（バイオリンのシルエット画像）とバイオリンプロットの白黒画像を比較して、違いのあるピクセルの数を数えます。その数の最小化を目指すということになります。


```
np.abs(plot_01 - violin_01).sum()
```




    80378



# Optuna のインストール


```
!pip install optuna
```

# 目的関数の設計

ベストスコアが更新されたときにそのバイオリンプロットを出力するようにしましょう。


```Python
class Objective:
    def __init__(self):
        self.best_dataset = []
        self.best_score = 1e53
        self.n_trial = 0

    def __call__(self, trial):
        self.n_trial += 1
        dataset = []
        n_mountains = 100
        for n in range(n_mountains):
            dataset += list(n + np.random.rand(trial.suggest_int("n_" + str(n), 0, 1000)))

        im = violin(
            dataset,
            ax_w_px = violin_01.shape[1],  # プロット領域の幅をピクセル単位で指定
            ax_h_px = violin_01.shape[0],  # プロット領域の高さをピクセル単位で指定
            flag_show = False,
            title = "Trial {}".format(self.n_trial)
            )
        plot_01 = np.where(im2_r(im), 1, 0)
        score = np.abs(plot_01 - violin_01).sum()

        if self.best_score > score:
            self.best_score = score
            self.best_dataset = dataset
            Image.fromarray(im)
            plt.title("Trial {}, score = {}".format(self.n_trial, score))
            plt.imshow(im)
            plt.show()
            try:
                print(study1.best_trial)
            except:
                pass

        return score
```

# 実行

さあ、最初はバイオリンとは似ても似つかない姿だったのが、次第に...

```Python
import optuna

optuna.logging.set_verbosity(optuna.logging.WARN)
objective1 = Objective()
study1 = optuna.create_study(direction='minimize')
#study1.optimize(objective1, timeout=60)
study1.optimize(objective1, n_trials=1000, show_progress_bar=True)
study1.best_trial
```

![1611795640.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/98aa5b4f-0b9b-ea3b-1437-68c9e29009e9.gif)

... これはどう見てもバイオリンですありがとうございます(｀・ω・´)ｷﾘｯ