Skip to content

ReNom で MNIST サンプルを動かして TDA (Topological Data Analysis) を触っただけのブログ

最近仕事が忙しくて完全にブログをサボってましたが、たまに書かないと寂しいので趣味で ReNom を触ってみたブログです。

まえがき

機械学習・AI分野で日本企業にはもっと頑張ってほしいところなので、GRID の機械学習・ディープラーニングプラットフォームの ReNom (リノームと読むらしい) を動かしてみました。

www.gridpredict.jp

ReNom version 2.3 — ReNom 2.3 documentation

位相幾何的な機械学習の可視化ツールとして TDA が面白そうだったので触ってみたんですが、GitHub にコード置いてあるから後は頑張れって感じなんですね… まあ、別にいいんですが…。

(あんまり抽象化しすぎるのは好きじゃないんですが) MNIST のニューラルネットサンプルがついてるので読んでみると、バックプロパゲーションはしとくからツール周りは sklearn で自分でゴリゴリ書いてねという漢気あふれる感じですねコレは…。

とりあえず ReNom を GPU 環境に導入。
Installation — ReNom 2.3 documentation

> git clone https://github.com/ReNom-dev-team/ReNom.git
> cd ReNom
> python setup.py build_ext -f -i
> pip install -e .

モデルの保存に h5py が必要だった (書いてへんやん………)

> pip install h5py

MNIST やってみた

実装

ReNom を import して、ネットワークを設計する。
ストレートフォワードなネットワークなら、renom.Sequential というのが便利で、レイヤーや活性化関数の並びをリストで与えるだけでいいらしい。Optimizer はとりあえず適当に Adam で。

import renom as rm

# GPU をアクティベート
rm.cuda.cuda.set_cuda_active(True)
        
model = rm.Sequential([
    rm.Dense(256),
    rm.Relu(),
    rm.Dropout(dropout_ratio=0.5),
    rm.Dense(10)
    ])

opt = rm.optimizer.Adam()

MNIST のデータセットを用意する関数を自分でセット。

def prepare_mnist(self):
    from sklearn.datasets import fetch_mldata
    from sklearn.cross_validation import train_test_split
    from sklearn.preprocessing import LabelBinarizer
        
    mnist = fetch_mldata('MNIST original', data_home="dataset")
    X = mnist.data
    y = mnist.target
    X = X.astype(np.float32)/255.0
    
    self.X_train, self.X_test, y_train, self.y_test = train_test_split(X, y, test_size=0.1)
    self.labels_train = LabelBinarizer().fit_transform(y_train).astype(np.float32)
    self.labels_test = LabelBinarizer().fit_transform(self.y_test).astype(np.float32)
    
    self.N_train = len(self.X_train)
    self.epoch_loop = self.N_train // self.bs

実際の学習は以下のように非常にシンプルに書けるけど、その周辺はもうちょいスマートに隠蔽してくれてもいいのにな… と思わないこともない。

def next_batch(self, perm, j):
    x = self.X_train[perm[j*self.bs:(j+1)*self.bs]]
    t = self.labels_train[perm[j*self.bs:(j+1)*self.bs]]
    return x, t

def run(self):
    for epoch in range(self.n_epoch):
        perm = np.random.permutation(self.N_train)
        total_loss = 0
        for j in range(self.epoch_loop):
            x, t = self.next_batch(perm,j)
            with self.model.train():
                y = self.model(x)
                loss = rm.softmax_cross_entropy(y, t)
                
            loss.to_cpu()
            grad = loss.grad()
            grad.update(self.opt)
            total_loss += loss
実行
> learn
epoch 0 train_loss:[0.0008993] test_loss:[0.24510358]
epoch 1 train_loss:[0.0009692] test_loss:[0.18671361]
epoch 2 train_loss:[0.00055986] test_loss:[0.15481536]
epoch 3 train_loss:[0.00047782] test_loss:[0.13698734]
epoch 4 train_loss:[0.00046527] test_loss:[0.13123505]
epoch 5 train_loss:[0.0004298] test_loss:[0.11457814]
epoch 6 train_loss:[0.00034942] test_loss:[0.11229]
epoch 7 train_loss:[0.00040245] test_loss:[0.10209548]
epoch 8 train_loss:[0.000268] test_loss:[0.10060599]
epoch 9 train_loss:[0.00063943] test_loss:[0.09608371]
> confusion matrix
[[700   1   3   1   0   1   2   1   3   1]
[  0 796   1   3   2   0   3   0   0   0]
[  4   3 669   4   3   0   2   4   1   2]
[  1   3   7 663   0   8   1   1   6   1]
[  2   1   1   1 660   0   3   1   1  14]
[  0   3   2   4   1 558   3   0   1   4]
[  3   2   3   0   0   2 729   0   3   0]
[  0   0   8   3   5   1   0 728   2   6]
[  2   3   7   3   2   2   5   1 623   1]
[  1   2   1   6  16   2   1   9   2 655]]
precision    recall  f1-score   support
0.0       0.98      0.98      0.98       713
1.0       0.98      0.99      0.98       805
2.0       0.95      0.97      0.96       692
3.0       0.96      0.96      0.96       691
4.0       0.96      0.96      0.96       684
5.0       0.97      0.97      0.97       576
6.0       0.97      0.98      0.98       742
7.0       0.98      0.97      0.97       753
8.0       0.97      0.96      0.97       649
9.0       0.96      0.94      0.95       695
avg / total       0.97      0.97      0.97      7000

一応ちゃんと学習はできているらしい。出力の実体は sklearn ですが。

TDA を触ってみた

導入

ここからが本題 (本題というほどでもないが)。TDA (Topological Data Analysis) については以下から引用させて頂く。

www.gridpredict.jp

f:id:mocchitam:20180202142321p:plain

■ReNom TDAとは
ReNom TDAとは、高次元データを位相空間(集合に位相の情報を付加した空間距離のない空間)にマッピングし、可視化・分析するためのモジュールです。データの形状を把握することや、変数同士の関係性を直感的に把握することで、データを解析するエンジニアのモデリングを助けます。また、データの前処理や、データ構造の把握に限らず、高度なプロファイリングツールとして活用することができます。例えば、複雑なデータ間のつながりを可視化することで、顧客データの分析や、マシンデータの解析、金融や不正アクセス、サイバーセキュリティの解析など、アイディア次第で様々なデータをプロファイリングすることが可能になります。

■TDA概要
TDAとは、位相幾何学(Topology)を用いた新しいデータ分析の手法で、位相空間でデータの形状を可視化し、データが持つ意味を抽出することができます。位相幾何学とは、切り貼りせず連続的に変形しても保たれる性質(輪っかや、空洞を特徴として考える)に注目し、位相空間で繋がりを考える数学の分野です。位相空間でデータの構造や密度を考え可視化することにより、従来の方法では、データを低次元化する際に失われていた特徴を失う事なく、データの特徴を維持したまま低次元で可視化することが可能になります。

とのこと。ReNomTDA を実行するには、別の GitHub から引っ張ってくる必要があるらしいので、またインストールしておく。

git clone https://github.com/ReNom-dev-team/ReNomTDA.git
cd ReNomTDA
pip install -e .

ユーザーとしては GUI でちょこちょこやるのがよさそうですが、一応APIを叩いて自分で処理が書ければいいなと思ったものの lens の AutoEncoder あたりは完全にそういう使われ方を想定されていませんね(笑)

ReNom TDA GUIの使い方 — ReNom 2.3 ドキュメント

f:id:mocchitam:20180202142731p:plain

まあ、それだと終わってしまうので、さっきダウンロードした MNIST のデータセットで一応外から動かしてみます。

実装

位相空間に投影するためのレンズというのがいくつか用意されているので、今回は代表的な PCA, t-SNE とちょっと気になる AutoEncoder というので結果を見てみます。

MNIST Dataset Mapping — ReNom 2.3 documentation

AutoEncoder の部分は自分で用意しないといけないんですが、さっきは Sequencial を使いましたが、renom.Model を継承して forward と encode という関数を持つ AutoEncoder 用のクラスを書く必要がありそうです。
ここで GUI のソースコードをカンニング。encode で一回2次元に落としてるけど AutoEncoder として大丈夫か… と思ったんですが、情報を最大限残す2次元ベクトル方向に射影する作業だと思えばまあいいのか。

class MTae(rm.Model):
    def __init__(self):
        self.conv1 = rm.Dense(10)
        self.conv2 = rm.Dense(2)
        self.deconv1 = rm.Dense(10)
        self.deconv2 = rm.Dense(784)
        
    def forward(self, x):
        h = self.encode(x)
        h = self.deconv1(h)
        h = rm.relu(h)
        y = self.deconv2(h)
        loss = rm.mse(y, x)
        return loss
    
    def encode(self, x):
        h = self.conv1(x)
        h = rm.relu(h)
        h = self.conv2(h)
        return h

あとは、renom_tda. topology にある Topology に lens をセットすると描画してくれるみたいです。

import renom as rm
from renom_tda.topology import Topology
from renom_tda.lens import PCA, TSNE
from renom_tda.lens_renom import AutoEncoder

mode = 'ae'
metric = None
if mode == 'pca':
    lens=[PCA(components=[0,1])]
elif mode == 'tsne':
    lens=[TSNE(components=[0,1])]
elif mode == 'ae':
    lens = [AutoEncoder(epoch=100,
                        batch_size=256,
                        network=MTae(),
                        opt=rm.optimizer.Adam(),
                        verbose=1)]

# dataset
mnist = fetch_mldata('MNIST original', data_home="dataset")
data = mnist.data[::10]
target = mnist.target[::10]
data = data.astype(np.float32)/255.0
    
# topology
topology = Topology()
topology.load_data(data)
        
topology.fit_transform(metric=metric, lens=lens)
   
topology.map(resolution=50, overlap=1, eps=0.4, min_samples=2)
topology.color(target, color_method='mode', color_type='rgb')
topology.show(fig_size=(15,15), node_size=1, edge_width=0.1, mode='spring', strength=0.05)
結果

PCA

f:id:mocchitam:20180202144511p:plain

t-SNE

f:id:mocchitam:20180202144529p:plain

AutoEncoder

f:id:mocchitam:20180202144546p:plain

t-SNE すぎょい… (知ってた速報)。AutoEncoder も PCA と雰囲気は似てるので、まあ、このネットワーク構造だと結局 PCA をやるのが最適だと判断された改良版 PCA になってるのかなという妥当っぽい解釈。もっとチューニングから頑張れ自分。

Parametric t-SNE 的なハイブリッドでやったらもっとキレイに出来たりするかなと思うんだけど、いかんせん処理が重い…。もうすこしコードを読み込んで、うまくやる方法を調べた方がよさそう。

Parametric t-SNE を Keras で書いた | ZABURO app

今回遊んだソースコードは以下に置いておきますので、参考程度に。

github.com

ただ動かしただけなんで、ReNom TDA なんかすごそうとしか言えないですが、ここで色んな統計解析をするための API が増強されてくような気がするので、ゴリゴリ組み込んでビジュアライズしていけばかなり実用的に良さそうな気がする。
結局ゴリゴリやるなら TensorFlow で十分なのではってなりそうだけど、中身をよく理解できていないので結論は先送りで。

(も・ω・ち)

    コメントを残す

    メールアドレスが公開されることはありません。 が付いている欄は必須項目です

    %d人のブロガーが「いいね」をつけました。