7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

NMFで画像を分解

Last updated at Posted at 2019-03-29

みなさん、NMF(Non-negative Matrix Factorization:非負値行列因子分解)って知ってますか?
知ってましたか...では、何に使ってますか?

今回、とあるシーンで使ってみたところ、意外と使えました。
でもやっぱり目で見て感動したかったので、画像の分解で使ってみました。

NMFとは

アチラコチラで説明はいっぱい書かれていますので、詳しい解説は省略します。

ざっくりとした説明ですが、ある行列を2つの行列に分解します。
このとき、分解される行列をR(MxNの行列)とし、分解後の2つの行列をP(FxNの行列)とQ(FxMの行列)とします。
このとき、「F」が分割数になり、いわゆる「特徴」を表すことになります。
「次元圧縮」とも言われたりします。

絵で書くとこんな感じです。
image.png

でも、数学をやっている人的には違和感がある図ですので、ちゃんと式にしてみます。

R \approx P \times Q^{T}

※実際には正確に分解できるわけではないので、「=」にはなりません

画像に適用すると?

これを画像に使ってみます。
といっても、Rは一枚の画像を表すわけではなく、複数枚の画像を表します。
つまり画像を2次元から1次元に変換し、画像枚数で2次元化します。

例えばMNISTの場合だと、28x28の画像ですので784になり、これを60,000枚使ったとすると、行列Rは
image.png
となります。

これを使ってNMFを実行すると、Pはどうでもいいとして、Qには60,000枚の画像データを学習して抽出したF枚の「特徴画像」が出来上がります。
image.png

今回はそれを眺めて感動(笑)したいと思います。

MNISTで試してみる

MNISTとは...はいいですね ^^;
0~9の手書き文字のデータセットです。

ちなみに、開発・実行はJupyter Notebook上で行っています。

%matplotlib inline

import numpy as np
import matplotlib.pyplot as plt

from sklearn.decomposition import NMF

import keras
from keras.datasets import mnist

まずはこんな感じでモジュールを読み込みます。
MNISTデータはkerasで読み込み、NMFはscikit-learnを使いたいと思います。
特徴画像はmatplotlibで表示します。

# MNISTデータの取得
(x_train, _), (_, _) = mnist.load_data()

x_train = x_train.reshape(-1, 784) # 2次元配列を1次元に変換
x_train = x_train.astype('float32')   # int型をfloat32型に変換
x_train /= 255                        # [0-255]の値を[0.0-1.0]に変換
print(x_train.shape[0], 'train samples')

MNISTデータを読み込みます。
テスト用とかカテゴリとかはいらないので読み込みません。
また、numpy配列にしてから、0.0~1.0に変換しています。

# 分解数
n_components = 4

# 分解
model = NMF(n_components=n_components, init='random', random_state=0) # n_componentsで特徴の次元を指定
P = model.fit_transform(x_train) # 学習
Q = model.components_

NMFで分解します。
とりあえずここでは4つに分解してみました。
最後の「Q」を使用します。

# 28x28の画像に変換
Q_image = Q.reshape(n_components, 28, 28)

# 画像の表示
fig = plt.figure(figsize=(18, 12))
for i in range(0, n_components):
    ax = fig.add_subplot(1, n_components, i+1)
    ax.imshow(Q_image[i])

plt.show()

Qを28x28になおして、画像を表示してみると、こうなりました。
image.png

0~9の数字は、「0」「3」「9」「1」で表せるようです(嘘)
正直、もうちょっとぐちゃぐちゃな画像になるかと思ったので、意外でした。

もっと試してみる

では「10個に分割したら、ちゃんと0~9になるんじゃね?」ということで、試してみました。
image.png

世の中、そんなに甘くはないです。
もっと別の特徴を見つけ出してくれたようです。
ということは、10進法はベストな数字の分割数ではないのかもしれませんね...
※形状だけでのお話ですが ( ̄ー ̄)ニヤリ

そんな中でも、「3」「6」「7」「9」あたりは特徴的な形状のようです。

使ってみての感想

非常に簡単に特徴抽出できるのは嬉しいのですが、固定値で分割数を指定しなくてはいけないのが、ちょっと気持ち悪いですね~

7
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?