Aidemy Tech Blog

機械学習・ディープラーニング関連技術の活用事例や実装方法をまとめる、株式会社アイデミーの技術ブログです。

独立成分分析による音源分離

はじめまして、protonです。
数カ月前からやっと機械学習関係の勉強を始めましたが、思った以上に色々出来て面白くなってきたところです。
機械学習にはscikit-learn等の様々な便利なオープンソースライブラリがあり、それらを用いることでかなり簡単に実装することができます。
今回はあえて、scikit-learn等を使わずに独立成分分析(ICA)というものを実装し、音声データの分離をしてみました。

独立成分分析(ICA)とは?

様々な人が話している中でも、自分が話している相手の会話は聞き取ることができるという現象は(おそらく)誰でも経験していると思います。
この現象をカクテルパーティ効果といい、人には音源の位置や周波数の差から特定の音抽出するような機能が備わっています。
この機構を模したアルゴリズムが、独立成分分析(ICA : Independent Component Analysis)です。

独立成分分析のアルゴリズム

f:id:proton_1602:20180407000041p:plainf:id:proton_1602:20180407000133p:plain


これはある2つの観測データ(2つの音源が別々の割合で混合されたもの)をそれぞれ横軸と縦軸に設定した散布図で、ICAが分離した様子がわかります。(ICA前にデータの分散が大きい方向がなんとなくX字のように2つあるものを、直行するように座標変換してるイメージ。もっとわかりやすいデータを持ってきても良かったのですが、このぐらいでも分離することができることがわかってもらえると...)
これをどのようにしてプログラムで行うかというと、任意の独立な確率変数の和は正規分布に収束するという中心極限定理を利用します。
中心極限定理により、独立な音源よりもそれを足し合わせたデータのほうがより正規分布に近くなる場合が多く、逆に正規分布から離れた分布であれば、独立な音源が足し合わさっていない(と判断できる)ということになります。
どれだけ正規分布に近いかどうかを判断するのに、尖度という統計量があります。尖度は正規分布の時に0、正規分布から乖離するほど0から離れていくので、この尖度が最大になった時が観測データ同士の独立性が最も高くなる時で、元の音源を分離することができます。(ここからわかるように元のデータが正規分布に近いものだと、分離が難しくなります...)

尖度が指標としてなぜ有効なのかや、尖度を最大化させるために繰り返し更新するアルゴリズムについては最後にリンクを貼った本を読んでみてください...

準備

音源分離には様々なパターンがあるらしいですが、基本的には独立した音源の数だけそれぞれ同期したマイクを周囲に適当に設置して、マイクから拾った音を元にそれぞれの音源を抽出します。
実際にマイクをいくつか買ってきて録音するのは音源とは関係ない雑音等も入り、勉強始めたての自分には難しすぎるので、今回はフリー音源を3つ適当な割合で混合したデータから音源を分離することにします。(マイクの位置による時間差は無視して、マイクの位置による音の減衰による差を反映しています。)
扱いやすくするために、soundengine等を使ってwav形式16bit, 44100Hz, モノラルでちょうど10秒の音声データに加工します。

loop1.wav

strings.wav

fanfare.wav

フリーのBGMとゲーム用音楽素材[Wave,MP3]

次に、pythonを使って3つの音源を適当な割合で混合したデータを3つ作ります。
(コードの通りmix_1,2,3はそれぞれloop1.wav, strings.wav, fanfare.wavを(0.6, 0.3, 0.1), (0.3, 0.2, 0.5), (0.1, 0.5, 0.4)の割合で混合したものです)

import numpy as np
import scipy.io.wavfile as wf

rate1, data1 = wf.read('loop1.wav')
rate2, data2 = wf.read('strings.wav')
rate3, data3 = wf.read('fanfare.wav')
if rate1 != rate2 or rate2 != rate3:
    raise ValueError('Sampling_rate_Error')

mix_1 = data1 * 0.6 + data2 * 0.3 + data3 * 0.1
mix_2 = data1 * 0.3 + data2 * 0.2 + data3 * 0.5
mix_3 = data1 * 0.1 + data2 * 0.5 + data3 * 0.4
y = [mix_1, mix_2, mix_3]
y = [(y_i * 32767 / max(np.absolute(y_i))).astype(np.int16) for y_i in np.asarray(y)]

wf.write('mix_1.wav', rate1, y[0])
wf.write('mix_2.wav', rate2, y[1])
wf.write('mix_3.wav', rate3, y[2])

mix_1.wav

mix_2.wav

mix_3.wav

なかなかひどいものができてしまいましたが、どれか一つの音に集中しようとしてみると結構難しいと思います。(できる人もいると思うけど)
こんなぐちゃぐちゃになったデータから元のデータを抽出します。

音源分離

分離すべきデータができたので、独立成分分析(ICA)関係の処理についてまとめた
ica.py

import numpy as np

epsilon = 1e-5

class ICA:
    def __init__(self, x):
        self.x = np.matrix(x)

    def ica(self): #独立成分分析
        self.fit()
        z = self.whiten()
        y = self.analyze(z)
        return y

    def fit(self): #平均を0にする
        self.x -= self.x.mean(axis=1)

    def whiten(self): #白色化
        sigma = np.cov(self.x, rowvar=True, bias=True)
        D, E = np.linalg.eigh(sigma)
        E = np.asmatrix(E)
        Dh = np.diag(np.array(D) ** (-1/2))
        V = E * Dh * E.T
        z = V * self.x
        return z

    def normalize(self, x): #正規化
        if x.sum() < 0:
            x *= -1
        return x / np.linalg.norm(x)

    def analyze(self, z):
        c, r = self.x.shape
        W = np.empty((0, c))
        for _ in range(c): #観測数分だけアルゴリズムを実行する
            vec_w = np.random.rand(c, 1)
            vec_w = self.normalize(vec_w)
            while True:
                vec_w_prev = vec_w
                vec_w = np.asmatrix((np.asarray(z) * np.asarray(vec_w.T * z) ** 3).mean(axis=1)).T - 3 * vec_w
                vec_w = self.normalize(np.linalg.qr(np.asmatrix(np.concatenate((W, vec_w.T), axis=0)).T)[0].T[-1].T) #直交化法と正規化
                if np.linalg.norm(vec_w - vec_w_prev) < epsilon: #収束判定
                    W = np.concatenate((W, vec_w.T), axis=0)
                    break
        y = W * z
        return y

を作ります。(analyzeのアルゴリズムについては最後の本を見てください...)

次に、データの入出力をするためのコード
separation.py

import numpy as np
import scipy.io.wavfile as wf
from ica import ICA

rate1, data1 = wf.read('mix_1.wav')
rate2, data2 = wf.read('mix_2.wav')
rate3, data3 = wf.read('mix_3.wav')
if rate1 != rate2 or rate2 != rate3:
    raise ValueError('Sampling_rate_Error')

data = [data1.astype(float), data2.astype(float), data3.astype(float)]

y = ICA(data).ica()
y = [(y_i * 32767 / max(np.absolute(y_i))).astype(np.int16) for y_i in np.asarray(y)]

wf.write('music1.wav', rate1, y[0])
wf.write('music2.wav', rate2, y[1])
wf.write('music3.wav', rate3, y[2])

を作ります。
これで、必要なものは揃ったので適当なディレクトリにこれらのコードとデータを入れてseparation.pyを実行すると分離された音源データができます。

結果

music1.wav

music2.wav

music3.wav

music1がloop1.wav、music2がfanfare.wav、music3がstrings.wavとほぼ同じに聞こえます、分離することができました。
権利関係がよくわからないのでデータを上げることはできませんが、同じようにして人の話し声なども、分離することができました。

最後に

便利なライブラリを使わないでも頑張ればなんとかなるものです。
よくわからないけどこの関数を使うとなんかできるみたいな状態の人は、一度使わないで作ろうとするとライブラリの理解が深まったりありがたさがよりわかると思います。
ライブラリを使わないからにはちゃんとした理論的な説明を書きたかったのですが、理解しきれていないところも多く適当な説明になってしまいましたが、興味を持った人は

books.google.co.jp

を読んでみてください。

最後までご覧頂き、ありがとうございました。

【速報レビュー】Googleが無償公開したAIの社内教育システム「ai.google」を使ってみた

f:id:meteoputi:20180301163725j:plain

先日、GoogleがAIの社内教育プログラムを公開しましたことで話題になりました。

Learn with Google AI

https://ai.google/education/#?modal_active=none

弊社(Aidemy)も同じくAIを手がける会社ということで早速利用してみました。

Google社内教育プログラムへは、上記リンクへアクセスしていただき、「Education」の場所をクリックしていただくと遷移することができます。

 

今回は実際にAIの会社のエンジニアがGoogleのサービスを使ってみて感じた

・優れた点、使いづらいと感じた点

・ぜひ一度体験してみていただきたいサービス

・さらに改善されたら嬉しい点

について皆様に共有させていただければと思います。

 

優れた点、使いづらいと感じた点

まず、ページに移動して最初に目につくであろうコンテンツが、この動画です。

f:id:meteoputi:20180301145139p:plain

https://ai.google/education/#?modal_active=yt-cKxRvEZd3Mw

 

実際に視聴してみて感じたのが

・とにかくクオリティーが高く、わかりやすい

・英語の発音がとても綺麗な上に自動で日本語の字幕がついてくる

・ワンクリックで動画が再生でき、とても使いやすい

ということです。さすがGoogleといったところでしょうか。

内容については機械学習の概論についてわかりやすくまとめたサマリー動画という印象でした。もし、機械学習について、まだよく分かっていない方は一度視聴してみることをお勧めします。

しかしながら、ウェブサイト内にある全ての動画に日本語字幕がついている訳ではないのが残念なところです。

 

また、コードを書きながら学習を進めることができるのも素晴らしい点です。

f:id:meteoputi:20180301160609p:plain

ただし、ライブラリのインストールやソースコードのインストールは自分自身で行わなければなりません。ドキュメントも全て英語であるため、全くの初心者にとっては少し敷居が高いでしょうか。

 

ぜひ一度体験してみていただきたいサービス

こちらのウェブサイトを触っていて、とりわけ素晴らしいと感じたサービスについてご紹介させていただきます。まず、ご紹介させていただくのが Deep Ground というサービスです。(当サービスは以前から公開されていましたね。)

http://playground.tensorflow.org/#activation=tanh&batchSize=10&dataset=circle&regDataset=reg-plane&learningRate=0.03&regularizationRate=0&noise=0&networkShape=4,2&seed=0.83641&showTestData=false&discretize=false&percTrainData=50&x=true&y=true&xTimesY=false&xSquared=false&ySquared=false&cosX=false&sinX=false&cosY=false&sinY=false&collectStats=false&problem=classification&initZero=false&hideText=false

これはニューラルネットワークがどのように学習を進めていくのかが目で見てわかる可視化ツールです。

f:id:meteoputi:20180301153709p:plain

また、パラメーターを自分で調節することもできるため、触っていてとても楽しめます。

感覚的にニューラルネットワークの学習が理解できるため、ぜひ一度体験していただきたいサービスです。

詳しい説明についてはAI Adventures: 7 Steps of Machine Learningという動画に乗っています。

https://ai.google/education/#?modal_active=yt-nKW8Ndu7Mjw

 

次にご紹介するのはMachine Learning Glossary です。

https://developers.google.com/machine-learning/glossary/?utm_source=google-ai&utm_medium=card-image&utm_campaign=training-hub&utm_content=ml-glossary

機械学習では多くの専門用語が登場しますが、分からないことが多いでしょう。このページではそんな専門用語を英語でわかりやすく解説してくれています。Google.AIで学習を進める際はもちろん、英語で書かれた記事を読む際にもとても役立ちそうです。

 

さらに改善されたら嬉しい点

最後に学習を進めていく上で不便と感じるであろう点をご紹介します。

 

・ウェブサイトをはじめとして、ほとんどドキュメントは英語で書かれている。

これはGoogleが出しているサービスなので当たり前なのですが、全て英語で書かれています。英語が苦手な方は最初から苦戦してしまうかもしれません。

 

・コンテンツが分断されていてバラバラである。

確かに一つ一つのコンテンツの完成度は高いのですが、一連の学習を順番に進めていくという形式ではなく、情報がバラバラに配置されている状態ですので、体系的にAIについて学んでいきたい初学者の方にはあまりおすすめできないかもしれません。

 

無料で公開されているコンテンツですので文句は言えないですが、上記の点は改善されると、ありがたいところですね。

 

10秒でコードを書き始められるAidemyと組み合わせれば良いのでは?

手前味噌ですが、日本語で体系的にAIについて学びたい方には Aidemy をおすすめさせていただきます。

ウェブサイトにて学習を進められるため、ライブラリのインストールや環境構築などの煩雑な準備は一切必要なく10秒で学習に取り掛かることができます。また、目的に合わせた学習ルートをご用意させていただいているため、学習の順番が分からない方はそちらを参考にしていただくと、学習が進めやすいと思います。

しかし、Aidemyでは現在ビデオ教材は一切配信されていません。動画を見ながら学習を進めていきたいユーザーにとっては、このai.googleと組み合わせて実践してみると、より理解が深まるでしょう。

Aidemyは現在期間限定で14コース全て無料公開中ですので、この機会にぜひご利用ください。

aidemy.net

HIP HOPでわかるネットワーク分析

自己紹介

みなさん、はじめまして。
Aidemy 研修生の 加藤正義 (加藤正義 (@Kato_Justice) | Twitter) です。昔はテレビ東大生として、時々バラエティ番組とかに出ていました。

〈●〉EYE-CATCH〈●〉

 フィーチャリング関係で繋がった日本語ラッパーのコミュニティをグラフ化すると、以下のような構造をしている。
f:id:kato_justice:20180220153459p:plain
ノードの色は派閥を表し、媒介中心性が大きいほどノードのサイズが大きい。ラッパーの派閥は

  • フリースタイルダンジョン組
  • KGDR + RHYMESTER + 愛国ラッパー達 (韻が固い)
  • 独立したその他ユニット

という具合で分かれた。
媒介中心性の最も高いラッパーはKEN THE 390だった。

ラッパーは好き嫌いが激しい

 突然ですが、僕は日本語ラップが好きです。しかし、当のラッパー同士がお互いのことを無条件で好きかというと、どうやらそうではないようです。ラッパーはしばしば価値観の違いから対立し、ビーフと呼ばれるdisり合いの行動を取ることがあります。
kai-you.net
このようにお互いの好き嫌いが激しいラッパー達なので、彼らの人間関係もいくつかの派閥に分かれていそうだなと思い、ラッパーの派閥構造ネットワーク分析と呼ばれる手法で解析し、可視化してみようと思いました。

feat. で繋がる絆

 ラッパー同士の親しさを客観的に定義する方法のひとつとして、フィーチャリングの関係が利用できると考えました。フィーチャリングとは、ラッパーが楽曲を作製する際に別のラッパーに協力を仰ぐことで、特にラッパーの場合は互いにリスペクトしてる場合のみフィーチャリングが成立すると考えています。よって、フィーチャリングの関係はラッパー同士の絆を表すとよい指標であると考えます。
www.youtube.com
以上を踏まえて、フィーチャリング関係をもとにラッパー派閥の構造を解明していきたいと思います。

やり方

 以下のような流れでやっていきたいと思います。

  1. データを用意する、データの種類は以下の通り。
    • ラッパーと所属グループのリスト
    • 上リスト内のラッパー同士のフィーチャリング関係のリスト
  2. networkxにより、ラッパーをノードフィーチャリング関係をリンクとした無向グラフを構築 (つまり、一度でもfeat. してればダチとみなして、ラッパー達を絆で繋ぐということ)
  3. モジュラリティ指標が最も高いグラフの分割をする (つまり、いつメンで固まること)
  4. 媒介中心性を計算する (媒介中心性:『お前がいなきゃ界隈がまとまらねぇ』という度合いのこと)
  5. 構造がわかりやすいように可視化・要約する

1. データの用意

name.csv - Google スプレッドシート
 name.csvは、今回の分析の対象とするラッパーのリストです。独断と偏見で選んだ57人のラッパー達です。"Name"のカラムにはラッパーの名前が、"Group"のカラムにはそのラッパーが属する代表的なグループが書かれています。所属グループがない場合はラッパー自身の名前で置き換えています。

feat.csv - Google スプレッドシート
 一方、feat.csvにはラッパー同士のフィーチャリング関係が記されています。120行あります。各行について、"name_1"と"name_2"がフィーチャリングで繋がっている事を意味します。フィーチャリング関係をどう調べたかについてですが、YouTubeで『"ラッパー名" feat.』と検索、検索結果を2,3 ページ分スクロールしてリスト内のラッパーがいれば追加、という方法で行いました。正直、ここが一番たいへんでした。ただ、自分でデータを作るので意図しないデータ構造のデータクレンジングで疲労をしなくていいのはよかったのかな?

2. networkxによる無向グラフの構築

 今回は、networkxというpythonのパッケージを使ってグラフを書いていきます。

import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
from community import community_louvain

#グラフの大きさ決める
plt.figure(figsize = (10, 10))
# ラッパーのデータをデータフレーム化
df_name = pd.read_csv("name.csv")
df_ft = pd.read_csv("feat.csv")

#ラッパーの繋がりを格納しておくための辞書
#{"ラッパー名": "フィーチャリングしているラッパー達" , ...} という形で格納
vector = {}
for i in range(len(df_name)):
  vector[df_name.iloc[i,0]] = []

#同じグループで活動しているラッパーについては、無条件で繋げる
for i in range(len(df_name)):
  for j in range(len(df_name)):
    if df_name.iloc[i,1] == df_name.iloc[j,1] and i != j:
      vector[df_name.iloc[i,0]].append(df_name.iloc[j,0])

#フィーチャリングしているラッパーについて、ノードを接続
for i in range(len(df_ft)):
  vector[df_ft.iloc[i,0]].append(df_ft.iloc[i,1])

#vectorをグラフ化する
G = nx.Graph(vector)

この操作により、変数Gにグラフが格納されました。この時点でグラフGを

nx.draw_networkx(G)

によって出力すると、以下のようになります。目を凝らして観察すれば誰が誰と繋がっているのかはわかれど、派閥の存在や、誰が派閥の中心なのかをこのグラフから読み取るのは厳しいですね。
f:id:kato_justice:20180220154518p:plain

3. モジュラリティを最大化するグラフの分割

 さて、単にノード同士を繋いだだけではどういう派閥が存在するのかはわかりません。なので、所属する派閥ごとにグラフを分割したいのですが、どのような基準でグラフを分割をすれば納得感があるでしょうか?このようなシチュエーションで役に立つ概念の一つとして、モジュラリティというものがあります。以下では、モジュラリティについて説明します。
 まずは、ノードがランダムに繋がった(=派閥のない)グラフを見てみましょう。
f:id:kato_justice:20180220220746p:plain
隣接行列には、どのノードがどのノードと繋がっているかの情報が含まれています。隣接行列のij列の値は、ノードiとノードjが繋がっていれば1、そうでなければ0とします。ノード同士をランダムに接続するグラフを作る場合、隣接行列において対角成分を除けば全ての要素の期待値が等しいはずです (対角成分は常に0(自分自身とは繋がれないので(人はいつもほんとうの意味で孤独)))。

 一方で、グラフ内でコミュニティが分化していれば(=グラフ内に派閥構造を持っていれば) グラフおよび隣接行列は以下のようになります。
f:id:kato_justice:20180220225101p:plain
図中の/で示されるコミュニティの内部でのリンクは多く存在するのに対して、コミュニティ間を繋ぐリンクは稀にしか存在しないのがおわかりでしょうか?それに対応するように隣接行列においても、グループ内の繋がりを表す要素には1が多く、グループ間の繋がりを表す要素には0が多いという偏りができています。この偏りを利用して、コミュニティが分化している程度を表そうというのがモジュラリティの考え方です。下の図でモジュラリティの出し方を説明します。ここでは、簡単のために、2分割のみの場合を扱います。
f:id:kato_justice:20180220230632p:plain
モジュラリティQは、所与の分割をした際に各コミュニティ内部でのリンク数から、リンクがランダムであった場合のリンク数期待値を引き、これを総リンク数で割ることで定義されます。つまり、Qはコミュニティ内部にリンクが集中している割合を表す指標というわけです。

 さて、モジュラリティを最大化する分割をどう実装するかなのですが、communityというパッケージの中にモジュラリティが最大になるような分割をしてくれる機能があります。呼び方は簡単で、

partition = community_louvain.best_partition(G)

とすればOKです。値は辞書型で渡され、以下のように数値によってコミュニティが振り分けられます。

{'KEN THE 390': 0, 'TWIGY': 1, 'KREVA': 2, 
... , 'G.K.MARYAN': 1, 'REKKO': 0, 'TAKUMAKI': 0}

グループに関する情報を入れて、Gを再び

nx.draw_networkx(G,node_color=[partition[node] 
for node in G.nodes()], cmap=plt.cm.RdYlBu)

で描写すると、以下のような感じになります。
f:id:kato_justice:20180221180909p:plain
だんだんわかってきましたね。けれど、ネットワークにおいて誰がどれくらい影響力を持っているかはまだわかりません。次は媒介中心性という概念で、誰がラッパーのコミュニティで影響力を持っているのかを明らかにしていきましょう。

4. 媒介中心性を求める ~UZIを例にして

 媒介中心性とは、ネットワーク分析において最もよく使われている中心性の指標で、そのノードを通る最短経路が多いほど媒介中心性は高くなります。詳しい説明するために、以下のような、お互い繋がりあうラッパー達の事を考えてみましょう。このグラフでは、いくつかのノードを経由することにより、全てのノードが互いに辿り着きあえるようになっています。
f:id:kato_justice:20180221230336p:plain
しかし、この界隈に事件が起きて、あるノードが消失したらどうでしょうか?ここでは、UZIが御用となってしまった世界の事を考えてみましょう。
www.excite.co.jp
UZIが塀の中に入ってしまうと、様々な不都合が起きます。フリースタイルダンジョンが放送中止となるだけではなく、これまでUZIを媒介として繋がりあっていたノード同士の繋がりが、断絶されます*1(例: 下図中のKダブとDJ YAS)。また、UZIなしでは連絡をつけるために遠回りをしないといけない場合もあります(例: 下図中のTwiGyと童子-T)。UZIがいなくなってはじめて、UZIがラッパー達の媒介としての役割を担っていたことがわかります。
f:id:kato_justice:20180221231353p:plain
このUZIが果たしていた役割に、スコアをつけることはできないでしょうか?下の図は、YOU THE ROCK★にとっての、UZIの媒介としての価値を示しています。
f:id:kato_justice:20180221234121p:plain
YOU THE ROCK★が最短で各ラッパーに辿り着く経路を考えてみましょう。NORIKIYO、K DUB SHINEにアクセスするには、UZIを通る経路が唯一の最短経路であることがわかります。また、童子-Tにアクセスするには、UZIを経由する経路とDJ-MASTERKEYを経由する経路の2つの最短経路があることがわかります。その他のラッパーにアクセスには、UZIを経由する必要はありません(ほかは全員雷家族のメンバーなので当然そう)。するこのとき、UZIを経由する唯一の最短経路に対してスコア+1最短経路が複数ある場合はUZIを通るものにスコア+1/n(今回は、1/2) を与えます。このスコアの和は、YOU THE ROCK★にとっての、UZIの媒介中心としての価値そのものです。YOU THE ROCKに限らず、全てのノードにとってのUZIのスコアを足し合わせることで、UZIの媒介中心性が表せます*2
 さて、この中心媒介性をnetworkxで求める方法ですが、実際簡単で、

between_cent = nx.communicability_betweenness_centrality(G)

で求めることができます。between_centの中身は辞書型で、

{'KEN THE 390': 0.34846233379185004, 'TWIGY': 0.1644836555192994, 'KREVA': 0.1557333845321839, 
... , 'G.K.MARYAN': 0.15635132015072525, 'ANARCHY': 0.013911874100152521, 'TAKUMAKI': 0.004676026664705854}

のような形になっています。

5. 可視化・要約

 さて、今までの作業でラッパーのfeat. 関係グラフに関する、モジュラリティ最大化媒介中心性を求めました。折角なので、これをわかりやすく可視化していきたいです。
グラフ分割についての可視化は先ほどやったのですが、ここでは、媒介中心性の程度もわかるように、媒介中心性が大きいほどノードも大きくなるグラフを書きたいと思います。以下のようなコードで実現できます。

#モジュラリティが最大になるような分割をする
partition = community_louvain.best_partition(G)
#中心媒介性を出す
between_cent = nx.communicability_betweenness_centrality(G)
#中心媒介性をもとにノードの大きさを決める
#そのままじゃ小さすぎるで雑に5000くらいかける
node_size = [5000 * size for size in list(between_cent.values())]
#いい感じにグラフのレイアウトをやる
pos = nx.spring_layout(G)
#グラフを出力。派閥ごとに色分けし、
#中心媒介性の大きいノードほど大きく表示
nx.draw_networkx(G,pos, node_color=[partition[node]
for node in G.nodes()],node_size=node_size, cmap=plt.cm.RdYlBu)

これで、アイキャッチで出てきたような、わかりやすいグラフが出力されます。ノードの色は派閥を表し、大きさは媒介中心性を表します。
f:id:kato_justice:20180220153459p:plain
これでちゃんとわかりやすいので、よかったですね。

 しかし、このグラフだけでは詳しい事までは読み取れないので、今回得られたデータを要約してみましょう。まず、各派閥ごとの属性を見てみます。「だいたいこんな感じ」というやつなので、全員に当てはまるわけではありません。

派閥 属性
0 フリースタイルダンジョン組
1 KAMINARI-KAZOKU.
2 KGDR + RHYMESTER + その他愛国的ラッパー、韻が固い
3 30~40歳くらい? ゼロ年代中盤くらいにヒットしたラッパーたちで、古参である派閥2よりは若い
4 濃水 SOUL SCREAM
5 BUDDHA BRAND

自明なこととして、所属ユニットが派閥に強く影響しているようです*3。少しだけ自明でない事としては、派閥0が示す通りユニットを組んでいなくてもフリースタイルダンジョン組の結びつきは強いことや、派閥2が示すように活動時期や価値観が似たラッパー達の結びつきは強い事が挙げられます。

 媒介中心性のランキングは以下のようになりました。このランキングが高いほどラッパー界での影響力が高いことになります(なるのか?) みんな有名なラッパーたちばっかですね。

ランク 名前 媒介中心性
1位 KEN THE 390 0.35
2位 Zeebra 0.34
3位 UZI 0.31
4位 DJ MASTERKEY 0.28
5位 般若 0.24
6位 Mummy-D 0.24
7位 R-指定 0.17
8位 NORIKIYO 0.17
9位 TwiGy 0.16
10位 SKY-HI 0.16

おわりに

  • 怠け者の僕にしてはけっこう頑張りました、分析そのものより、データ集めやブログ執筆のためのポンチ絵作りに手間がかかりました。ポンチ絵を作るのは楽しい。
  • この記事がきっかけで、ネットワーク分析というよりかは日本語ラップに興味を持つ人が増えてくれればいいなと思っています。
  • ラッパーのfeat. 関係ですが、手作業で集めたので漏れなどがあると思います。間違いを指摘してくれるとうれしいです。
  • B-BOYになりたい(写真は、B-BOYだった頃のぼくです、成り格好は一丁前だがまだスキルがない)

f:id:kato_justice:20180222122326j:plain

コード全文

 コピペして使えるようにまとめておきます。

import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
from community import community_louvain

#グラフの大きさ決める
plt.figure(figsize=(10, 10))
# ラッパーのデータをデータフレーム化
df_name=pd.read_csv("name.csv")
df_ft=pd.read_csv("feat.csv")

vector={}
for i in range(len(df_name)):
  vector[df_name.iloc[i,0]] = []
    
#同じグループで活動しているラッパーについては、無条件で繋げる
for i  in range(len(df_name)):
  for j in range(len(df_name)):
    if df_name.iloc[i,1]==df_name.iloc[j,1] and i!=j:
      vector[df_name.iloc[i,0]].append(df_name.iloc[j,0])

#フィーチャリングしているラッパーについて、ノードを接続
for i  in range(len(df_ft)):
  vector[df_ft.iloc[i,0]].append(df_ft.iloc[i,1])

G= nx.Graph(vector) 

# モジュラリティ最大の分割
partition = community_louvain.best_partition(G)
#媒介中心性を求める
between_cent = nx.communicability_betweenness_centrality(G)
node_size = [5000 * size for size in list(between_cent.values())]
#形を整えて、出力
pos = nx.spring_layout(G)
nx.draw_networkx(G,pos, node_color=[partition[node] for node in G.nodes()],node_size=node_size, cmap=plt.cm.RdYlBu)

*1:もちろん、実際はUZIなしでも連絡を取り合えると思うのですが、話を簡単にするためにそうさせてください

*2:実際は、この値を、UZIを通らない頂点対の数(N個の頂点のグラフなら、(N-1)*(N-2)/2 )で割ります

*3:同じグループ内のラッパーは全てリンクさせたのでそれはそう

機械学習で乃木坂46を顏分類してみた

こんなことをしてみたい

f:id:shintarom4869:20171217150543p:plain
↑これがしたい

pythonによる機械学習の勉強をしたので、実践ということで、人気アイドル「乃木坂46」の個人的に好きな5人のメンバーを区別して見ました。大きな流れはこんな感じです。

  1. web上から五人の画像を100枚ずつ取ってくる
  2. 画像から顔部分を取り出して保存、テストデータの取り出し
  3. 画像の水増し
  4. モデルを定義して、学習
  5. テスト(顔を四角く囲って、その人の名前を出力)

説明はこんなもんにして、彼女らの可愛さについて語りたいところですが、そういうブログではないので、少し技術的なことを書きます。 今回はjupyterを使って作業を進めました。notebook形式なので結果が見やすく初心者にはいい環境でした。環境は以下。

  • macOS:10.13.1
  • python:3.6.1
  • openCV:3.3.0
  • keras:2.1.2

まずはじめに、カレントディテクトリ上に、origin_image,face_image,face_scratch_image,test_imageディテクトリを追加しました。


1,Google APIを用いた画像取得

これを取得することで、Googleが提供するリソースにアクセスすることができます。今回でいえば、例えば「西野七瀬」と画像検索した時の上位100枚のURLを取得できます。APIのコードについてはこちらのブログを参考にしました。

Google Custom Search APIを使って画像収集 - Qiita

以下のコードでは、keywordsリスト["生田絵梨花","齋藤飛鳥","白石麻衣","西野七瀬","橋本奈々未"]で画像検索、URLを取得後、画像に変換し、jpgファイルとしてorigin_imageディテクトリに保存という流れになっています。100枚以上取れればいいんですが、600枚くらいで403Error Forbiddenが出てしまいました。また、1検索あたりの最大取得URLは100枚のようです。101枚以上を指定するとHTTP Error 400 – Bad Requestが出てしまいました。

import urllib.request
from urllib.parse import quote
import httplib2
import json 
import os
import cv2
import sys
import shutil

#keywordsの画像のurlを取得後、jpg画像に変換しファイルにどんどん入れてく
#全5人100個ずつ取得

API_KEY = ""#省略
CUSTOM_SEARCH_ENGINE = ""#省略

keywords=["生田絵梨花","齋藤飛鳥","白石麻衣","西野七瀬","橋本奈々未"]


def get_image_url(search_item, total_num):
    img_list = []
    i = 0
    while i < total_num:
        query_img = "https://www.googleapis.com/customsearch/v1?key=" + API_KEY + "&cx=" + CUSTOM_SEARCH_ENGINE + "&num=" + str(10 if(total_num-i)>10 else (total_num-i)) + "&start=" + str(i+1) + "&q=" + quote(search_item) + "&searchType=image"
        res = urllib.request.urlopen(query_img)
        data = json.loads(res.read().decode('utf-8'))
        for j in range(len(data["items"])):
            img_list.append(data["items"][j]["link"])
        i += 10
    return img_list

def get_image(search_item, img_list,j):
    opener = urllib.request.build_opener()
    http = httplib2.Http(".cache")
    for i in range(len(img_list)):
        try:
            fn, ext = os.path.splitext(img_list[i])
            print(img_list[i])
            response, content = http.request(img_list[i]) 
            filename = os.path.join("./origin_image",str("{0:02d}".format(j))+"."+str(i)+".jpg")
            with open(filename, 'wb') as f:
                f.write(content)
        except:
            print("failed to download the image.")
            continue
            
for j in range(len(keywords)):
    print(keywords[j])
    img_list = get_image_url(keywords[j],100)
    get_image(keywords[j], img_list,j)

2,画像から顔部分を取り出し

まずopencvに搭載されているカスケード分類器を利用して、顔を検出します。今回は正面顔を検出するhaarcascade_frontalface_alt.xmlという分類器を使いました。引数のminNeighborsの値が重要でした。以下のコードでは、検出した顔部分をトリミングし、全て64×64ピクセルにリサイズして(学習器に入れやすくなる為)、jpg形式でface_imageディテクトリに保存ということをしてます。実際ここで、100内60~70枚ほどに減ってしまいます。その理由として、正面顔しか検出しないため、正面を向いていても傾いた顔は検出できないため、などが挙げられます。ここで唯一、自力でデータを操作しなければなりません。検出できなかったものは自分の目でチェックして自力でトリミングした方がいいデータができると思いますし、またツーショットなどは別の人の顔も一緒に検出してまったので、そういうのはラベルを変えるor削除しましょう。僕の場合は、この作業をしている時が一番こうf...幸せでした。またこのタイミングででテストデータを取り出しました。 カスケード分類器に関してはこのブログがおすすめです。

python+OpenCVで顔認識をやってみる - Qiita

f:id:shintarom4869:20171217150344p:plain
オリジナル写真

import numpy as np
import cv2
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
import glob
import os
 
#元画像を取り出して顔部分を正方形で囲み、64×64pにリサイズ、別のファイルにどんどん入れてく
in_dir = "./origin_image/*"
out_dir = "./face_image"
in_jpg=glob.glob(in_dir)
in_fileName=os.listdir("./origin_image/")
# print(in_jpg)
# print(in_fileName)
print(len(in_jpg))
for num in range(len(in_jpg)):
    image=cv2.imread(str(in_jpg[num]))
    if image is None:
        print("Not open:",line)
        continue
    
    image_gs = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    cascade = cv2.CascadeClassifier("/usr/local/opt/opencv/share/OpenCV/haarcascades/haarcascade_frontalface_alt.xml")
    # 顔認識の実行
    face_list=cascade.detectMultiScale(image_gs, scaleFactor=1.1, minNeighbors=2,minSize=(64,64))
    #顔が1つ以上検出された時
    if len(face_list) > 0:
        for rect in face_list:
            x,y,width,height=rect
            image = image[rect[1]:rect[1]+rect[3],rect[0]:rect[0]+rect[2]]
            if image.shape[0]<64:
                continue
            image = cv2.resize(image,(64,64))
    #顔が検出されなかった時
    else:
        print("no face")
        continue
    print(image.shape)
    #保存
    fileName=os.path.join(out_dir,str(in_fileName[num])+".jpg")
    cv2.imwrite(str(fileName),image)

in_dir = "./face_image/*"
in_jpg=glob.glob(in_dir)
img_file_name_list=os.listdir("./face_image/")
#img_file_name_listをシャッフル、そのうち2割をtest_imageディテクトリに入れる
random.shuffle(in_jpg)
import shutil
for i in range(len(in_jpg)//5):
    shutil.move(str(in_jpg[i]), "./test_image")

3,画像の水増し

顔検出してテストデータを抜き取って残った数が、それぞれ50~70枚ほどになってしまい、これでは学習データとして不足するだろうと思ったので、左右反転、閾値処理、ぼかしを使って水増しを行います。これで学習データが8倍になります。(それでも少ない気がしますが。。)。画像の水増しは自動でやってくれるツールがありますが、自分でかけるコードは自分で書きたいので(キリッ)。このドキュメントは、画像の前処理を行う上で、もっともスタンダードな方法の一つです。これを知ってれば早いです。 画像の前処理 - Keras Documentation ImageDataGenerator関数はリアルタイムにデータ拡張しながら,テンソル画像データのバッチを生成します。

以下のコードでは水増し加工をしたものをface_scratch_image1ディテクトリに保存してます。また、学習器に入れるために画像リストXと正解ラベルリストyを用意して、それぞれ追加していきます。

#左右反転、閾値処理、ぼかしで8倍の水増し
import os
import numpy as np
import matplotlib.pyplot as plt
import cv2

def scratch_image(img, flip=True, thr=True, filt=True):
    # 水増しの手法を配列にまとめる
    methods = [flip, thr, filt]
    # ぼかしに使うフィルターの作成
    filter1 = np.ones((3, 3))
    # オリジナルの画像データを配列に格納
    images = [img]
    # 手法に用いる関数
    scratch = np.array([
        lambda x: cv2.flip(x, 1),
        lambda x: cv2.threshold(x, 100, 255, cv2.THRESH_TOZERO)[1],
        lambda x: cv2.GaussianBlur(x, (5, 5), 0),
    ])
    # 加工した画像を元と合わせて水増し
    doubling_images = lambda f, imag: np.r_[imag, [f(i) for i in imag]]

    for func in scratch[methods]:
        images = doubling_images(func, images)
    return images
    
# 画像の読み込み
in_dir = "./face_image/*"
in_jpg=glob.glob(in_dir)
img_file_name_list=os.listdir("./face_image/")
for i in range(len(in_jpg)):
    print(str(in_jpg[i]))
    img = cv2.imread(str(in_jpg[i]))
    scratch_face_images = scratch_image(img)
    for num, im in enumerate(scratch_face_images):
        fn, ext = os.path.splitext(img_file_name_list[i])
        file_name=os.path.join("./face_scratch_image"[f:id:shintarom4869:20171220032312p:plain],str(fn+"."+str(num)+".jpg"))
        cv2.imwrite(str(file_name) ,im)
# 画像と正解ラベルをリストにする
import random
from keras.utils.np_utils import to_categorical
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt


img_file_name_list=os.listdir("./face_scratch_image/")
print(len(img_file_name_list))

for i in range(len(img_file_name_list)):
    n=os.path.join("./face_scratch_image",img_file_name_list[i])
    img = cv2.imread(n)
    if isinstance(img,type(None)) == True:
        img_file_name_list.pop(i)
        continue
print(len(img_file_name_list))

X_train=[]
y_train=[]

for j in range(0,len(img_file_name_list)-1):
    n=os.path.join("./face_scratch_image/",img_file_name_list[j])
    img = cv2.imread(n)
    b,g,r = cv2.split(img)
    img = cv2.merge([r,g,b])
    X_train.append(img)
    n=img_file_name_list[j]
    y_train=np.append(y_train,int(n[0:2])).reshape(j+1,1)

X_train=np.array(X_train)

img_file_name_list=os.listdir("./test_image1/")
print(len(img_file_name_list))

for i in range(len(img_file_name_list)):
    n=os.path.join("./test_image1",img_file_name_list[i])
    img = cv2.imread(n)
    if isinstance(img,type(None)) == True:
        img_file_name_list.pop(i)
        continue
print(len(img_file_name_list))

X_test=[]
y_test=[]

for j in range(0,len(img_file_name_list)):
    n=os.path.join("./test_image1",img_file_name_list[j])
    img = cv2.imread(n)
    b,g,r = cv2.split(img)
    img = cv2.merge([r,g,b])
    X_test.append(img)
    n=img_file_name_list[j]
    y_test=np.append(y_test,int(n[0:2])).reshape(j+1,1)
    
X_test=np.array(X_test)

4,モデルを定義して、学習

さて、一人当たり400~600枚ほどのデータを取得できました。いよいよ本番です。これを学習させる学習器を構築して、学習させます。水増しした練習データは合計で1832枚、テストデータ57枚です。 これ以降のディープニューラルネットワークのモデルの作成・学習はKerasというライブラリを使用しました。KarasはTheanoやTensorFlowといった機械学習のライブラリのラッパーです。さて、学習器の構造ですが、CIFAR-10のサンプルを参考に作って見ました。それと、keras documentationは日本語のドキュメントなのでわかりやすかったです。畳み込み層、プーリング層の数を増やしたり、epoch数-精度のグラフをみて、精度の高かったものがこちらです。またこちらのブログも参考になりました。 TensorFlowによるももクロメンバー顔認識(前編) - Qiita

ディープラーニングでザッカーバーグの顔を識別するAIを作る①(学習データ準備編) - Qiita

- 入力 (64x64 3chカラー)
- 畳み込み層1
- プーリング層1
- 畳み込み層2
- プーリング層2
- 畳み込み層3
- プーリング層3
- 平坦層1
- 全結合層1(sigmoid関数)
- 全結合層2(sigmoid関数)
- 全結合層3(softmax関数 出力5)

epochs数を60にしてテストデータの精度は70%になりました。低いですね。理由として、みんな可愛いから特徴量が低い、取得データ不足などが挙げられます。全結合層の関数は一般的にはrelu関数の方が良いとされますが、試して見たところ今回はsigmoid関数の方が精度が高かったです。浅めの学習モデルだとsigmoid関数の方がいいのかもしれません。

epochs-accグラフはこちらです。

f:id:shintarom4869:20171220032312p:plain
横軸:epoch数,縦軸:精度

from keras.layers import Activation, Conv2D, Dense, Flatten, MaxPooling2D
from keras.models import Sequential, load_model

y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

#plt.imshow(X_train[0])
#plt.show()
#print(y_train[0])

# モデルの定義
model = Sequential()
model.add(Conv2D(input_shape=(64, 64, 3), filters=32,kernel_size=(2, 2), strides=(1, 1), padding="same"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(filters=32, kernel_size=(2, 2), strides=(1, 1), padding="same"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(filters=32, kernel_size=(2, 2), strides=(1, 1), padding="same"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(256))
model.add(Activation("sigmoid"))
model.add(Dense(128))
model.add(Activation('sigmoid'))
model.add(Dense(5))
model.add(Activation('softmax'))


# コンパイル
model.compile(optimizer='sgd', loss='categorical_crossentropy',metrics=['accuracy'])

# 学習
# model.fit(X_train, y_train, batch_size=32, epochs=50)

#グラフ用
history = model.fit(X_train, y_train, batch_size=32, epochs=100, verbose=1, validation_data=(X_test, y_test))

# 汎化制度の評価・表示
score = model.evaluate(X_test, y_test, batch_size=32, verbose=0)
print('validation loss:{0[0]}\nvalidation accuracy:{0[1]}'.format(score))

#acc, val_accのプロット
plt.plot(history.history["acc"], label="acc", ls="-", marker="o")
plt.plot(history.history["val_acc"], label="val_acc", ls="-", marker="x")
plt.ylabel("accuracy")
plt.xlabel("epoch")
plt.legend(loc="best")
plt.show()

#モデルを保存
model.save("my_model.h5")

5,テスト

見分けるモデルが完成したので、これを使ってアイドルの顔を見分けていこうと思います。画面に文字を追加したり、四角く囲ったりするのもopencvを用いました。いろんな画像でテストして見ると、生田絵梨花と誤認識する確率が高いように思いました。

コードは、画像中の顔を検出、四角くくくる、モデルで測定、名前を記入、といった感じです。

import numpy as np
import matplotlib.pyplot as plt

def detect_face(image):
    print(image.shape)
    #opencvを使って顔抽出
    image_gs = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    cascade = cv2.CascadeClassifier("/usr/local/opt/opencv/share/OpenCV/haarcascades/haarcascade_frontalface_alt.xml")
    # 顔認識の実行
    face_list=cascade.detectMultiScale(image_gs, scaleFactor=1.1, minNeighbors=2,minSize=(64,64))
    #顔が1つ以上検出された時
    if len(face_list) > 0:
        for rect in face_list:
            x,y,width,height=rect
            cv2.rectangle(image, tuple(rect[0:2]), tuple(rect[0:2]+rect[2:4]), (255, 0, 0), thickness=3)
            img = image[rect[1]:rect[1]+rect[3],rect[0]:rect[0]+rect[2]]
            if image.shape[0]<64:
                print("too small")
                continue
            img = cv2.resize(image,(64,64))
            img=np.expand_dims(img,axis=0)
            name = detect_who(img)
            cv2.putText(image,name,(x,y+height+20),cv2.FONT_HERSHEY_DUPLEX,1,(255,0,0),2)
    #顔が検出されなかった時
    else:
        print("no face")
    return image
    
def detect_who(img):
    #予測
    name=""
    print(model.predict(img))
    nameNumLabel=np.argmax(model.predict(img))
    if nameNumLabel== 0: 
        name="Ikuta Erika"
    elif nameNumLabel==1:
        name="Saito Asuka"
    elif nameNumLabel==2:
        name="Shiraishi Mai"
    elif nameNumLabel==3:
        name="Nishino Nanase"
    elif nameNumLabel==4:
        name="Hashimoto Nanami"
    return name

# model = load_model('./my_model.h5')

image=cv2.imread("./origin_image/01.0.jpg")
if image is None:
    print("Not open:")
b,g,r = cv2.split(image)
image = cv2.merge([r,g,b])
whoImage=detect_face(image)

plt.imshow(whoImage)
plt.show()
最後にちょっとだけ感想

今回のコードの制作期間は二日くらいでしたが、その6,7割くらいの時間を素材集めに使ってしまいまして、機械学習についてもっと触れたかったなと。。。でも、細かい文法などの知識が固まったかなあ。もっと人数の増やした、良い学習モデルを作ってサービスとしてリリースしたいなと思いました。また、さらなる上達のために、今回の応用として転移学習にも挑戦していきたいです。

ラストに結果を載せていきます。失敗込みです。(汗)

f:id:shintarom4869:20171217213221p:plainf:id:shintarom4869:20171217150444p:plainf:id:shintarom4869:20171217150441p:plainf:id:shintarom4869:20171217150403p:plainf:id:shintarom4869:20171217150443p:plainf:id:shintarom4869:20171217150445p:plain
テスト

追記

モデル学習のテストデータに水増しされた写真も入っていたことを指摘いただきまして、修正しました。テストデータに水増しされたデータを使うと、テストデータの精度は高くなりますが、オリジナルのデータに弱いモデルとなります。その他細かいコードの手直しをしました。

ニューラルネットワークの新技術 世界一わかりやすい”カプセルネットワーク”とは?

こんにちは、たくやです。

今回は69歳のグーグル研究員、ジェフ・ヒントンが40年の歳月をかけて熟考して発表した新技術、カプセルネットワークをご紹介します。

今回も例によってわかりにくい数式や専門用語をできるだけ使わずに感覚的に解説していきます。

元論文 

http://papers.nips.cc/paper/6975-dynamic-routing-between-capsules.pdf

 

この、カプセルネットワークは今、これまで機械学習で不動の地位を築いていたニューラルネットワークの技術を超える新技術なのではないかと期待されています。

彼の出した2つの論文によると、カプセルネットワークの精度は従来のニューラルネットワークの最高時の精度に、誤答率は従来のニューラルネットワークの最低時の半分にまで減少したといいます。

 

・従来のニューラルネットワークとの違い

では、何が従来のニューラルネットワークと違うのでしょうか?

一言でいうと、従来のニューラルネットワークが全体をその大きさで見ていたのに対して、カプセルネットワークが特徴ごとに”ベクトル”で見ているという点です。

もう少し詳しく説明します。

例えば顔を認識する際に、従来のニューラルネットワークであるCNN(Convolution Newral Network) はそれが目なのか、鼻なのか、口なのかにしか着目していませんでした。(画像左)

*CNNが何かを知らない方はこちらの記事の”CNNのおさらい”をご覧ください。

不気味なロボットから考えるCNNの仕組みのおさらいとAIによる画像認識の攻防戦

 

しかし、今回のカプセルネットワークはそれらの特徴がどのような関係で配置されているのかまで認識します。(画像右)

f:id:meteoputi:20171202201831p:plain

つまり、カプセルネットワークは個々の特徴を独立的に捉え、それぞれがどのような関係にあるのかということにまで着目します。カプセルネットワークの名前の由来がここにあります。ひとつひとつのカプセルに詰まったニューロンが個々の特徴に着目し、それぞれの関係に着目するのです。

これによって何が起こるのでしょうか?

f:id:meteoputi:20171202200032j:plain

 例えばこの写真、私たち人間の目には実物の自由の女神像を見たことがなくても、全て自由の女神像に見えます。しかし、私たちは、何千枚と自由の女神の写真を見てきたわけではないですよね?私たちは、十数枚の写真を見ただけで、それが自由の女神像だと認識することができます。

それと同じことが機械学習でも可能になるのです。

機械学習を行うには5つのプロセスがありました。

  1. データの収集
  2. データの前処理
  3. モデルの構築
  4. 実際に人工知能に学習させる
  5. モデルの改善

機械学習で最も大変なのは、実のところ、1と2のプロセスでした。しかし、今回のカプセルネットワークが実際に実用に耐えうるものだとされれば、1と2の手間がかなり省けるために、機械学習の可能性が一気に広がります。

 

 

・カプセルネットワークの仕組み

なぜそのようなことができるのでしょうか?

ひとつには上記で話したように、ベクトルで対象を認識しているからということが挙げられます。しかし、もうひとつ、重要な点があります。それが”プーリング”です。

開発者のジェフ・ヒントンはこのような言葉を残しています。

I believe Convolution, but I don't believe Pooling. - Geoffrey Hinton

つまり、CNNの仕組みは基本的に素晴らしいが、最後の処理であるプーリングには疑問を呈しているのです。

プーリング、特によく使われるマックス・プーリングは元のデータから導き出された特徴量の最大値をとって、残りを切り捨てる操作でした。しかし、これによって、特徴同士がどのような関係にあるのかがわからなくなってしまうというのです。

それもそうですよね。簡単に言えば特徴がどれだけ出ているかを表す数値で構成された写真の解像度をわざわざ下げて、特徴を強調しているわけですから。特徴の強さがどのように移り変わっているのかは分かりにくくなってしまいます。

 

f:id:meteoputi:20171202205205p:plain

 

そのかわり、カプセルネットワークではsquash関数という関数によってベクトル全体が潰されることになります。

(数学を見ると蕁麻疹が出てしまう方はこの部分を読み飛ばしてください)

f:id:meteoputi:20171202210859p:plain

具体的にはこのような関数になります。‖ ‖で囲まれている部分はノルムといってベクトルの長さを表すと考えてもらって大丈夫です。確かに分母で1がプラスされているため、圧縮されているのがわかります。ちなみにSj / ‖Sj‖ の部分は単位ベクトルといって、向きを表すために掛け算しています。

 

最後に、従来のニューラルネットワークとカプセルネットワークの違いを明確に表す画像を見つけたので、引用させてもらいます。

f:id:meteoputi:20171202211713p:plain

上の写真を見ればわかる通り、両者の構造はとても似ており、異なる点は3つだけ。

  1. 入力に重さをかけてベクトルにしている。
  2. バイアスを足さずに、どこの部分を強調して見るということをしないようにしている。
  3. 最後に従来の関数と違い、squash関数を入れ、データを圧縮している。

カプセルネットワークの技術的な話をもっと知りたい方は、英語版ですが、とてもわかりやすくカプセルネットワークの技術について解説してくれている動画も見つけましたので、よかったらご覧になってみてください。

https://www.youtube.com/watch?v=pPN8d0E3900

 

参考

https://qiita.com/onlyzs/items/5096f50a21758a536d9a

http://www.it-lifelog.com/entry/2017/11/28/205914

http://shunk031.me/paper-survey/paper-summary/CV/Dynamic_Routing_Between_Capsules.html

https://medium.com/ai%C2%B3-theory-practice-business/understanding-hintons-capsule-networks-part-i-intuition-b4b559d1159b

https://qiita.com/motokimura/items/cae9defed10cb5efeb62

https://hackernoon.com/what-is-a-capsnet-or-capsule-network-2bfbe48769cc

TensorFlowをあなたの手の上で TensorFlow Liteついにリリース

晩秋の候、ゆく秋も感慨深く思います。技術顧問の木村です。

今回は、先日リリースされたTesorFlow Liteをご紹介します。また、その他のディープラーニングフレームワークについて簡単に紹介します。

TensorFlow LiteはTensorFlowをモバイル端末で動くように改良したものです。

これは膨大な量のデータを用いるモデルの学習を行うためではなく、すでに訓練済みのモデルを使って、デバイス上で、素早く処理を実行するために作られました。

今までAppleの「Siri」、Googleの「Google Assistant」や、「Amazon Alexa」などはディープラーニングの処理をクラウド上で行なっています。

TensorFlow Liteにより、取得したデータを端末側にとどめておくことでプライバシーの問題を緩和できるというメリットもあります。

TensorFlow Liteの特徴

  • 軽い  (Lightweight)
    処理能力が低いモバイル端末のデバイス上でも起動や初期化が素早く行えるよう設計されています。
  • 複数のプラットフォームで動く(Cross-platform)
    TensorFlow Liteは様々なプラットフォーム上で動かすことができる。今回のプレビューの初期段階では、AndroidとiOSに対応しています。AndroidではJavaとC++, iOSではC++ の APIが用意されています。
  • 早い(Fast)
    携帯電話やIoTなどに最適化されていて、モデルの読み込みなどが劇的に改善されました。

最近はエッジコンピューティングが注目されつつあります。

エッジコンピューティングとは

「端末の近くにサーバーを分散配置する」というネットワークコンピューティングの技法のひとつです。「 端末」、つまりスマートフォンなどにネットワーク的に近い場所へ、サーバーと呼ばれるコンピュータを配置するのです。

これは、クラウドコンピューティングとは逆の流れです。背景には計算資源が安価になったことがあります。クラウドコンピューティングでは、データを集約して計算していました。プロセッサなどの計算資源は高価である一方、通信は安価だったからです。昨今ではプロセッサの高速化が進みました。現代のスマートフォンの処理速度は100G Flops程度、一方で、10年ほど前のPC用ハイエンドCPUであったPentium 4 は7.6 GFLOPSしかありません。

TensorFlow Liteはそんなエッジコンピューティングの流れを加速させるかもしれませんね。

ディープラーニングフレームワークとは

TensorFlowはディープラーニングフレームワークの一つです。ディープラーニングモデルを学習するためには、GPUを使ったプログラムが不可欠です。しかし、GPUプログラムを直接書くのはCPUのプログラムになかなか大変です。ディープラーングフレームワークを用いれば、GPU上での演算を簡単に書くことができます。

有名なディープラーニングフレームワークの一覧

 参考:マルチGPUでの各ディープラーニングフレームワークの実行速度(小さいほど速い)

  f:id:meteoputi:20171125194130p:plain

出典: Shaohuai Shi, Qiang Wang, Pengfei Xu, Xiaowen Chu, Benchmarking State-of-the-Art Deep Learning Software Tools, arXiv:1608.07249v7 [cs.DC] 17 Feb 2017.

■TensorFlow

Google製のディープラーニングフレームワークです。最もユーザが多く、活発に開発されています。

TensorFlowそのものはテンソル演算を行うもので、低水準な演算をサポートしています。低水準演算でディープラーニングを書くのはやや骨が折れます。そこで、対応する高水準のラッパーライブラリも多いです。

ラッパーとは

プログラミングやソフトウェア開発の分野では、ソフトウェアやプログラム部品などが提供するクラスや関数、データ型などを本来とは異なる環境や方法で利用できるようにしたもの

 

高水準は 記述の抽象度が高い言語を指します。

低水準は 逆にコードがより機械語に近いことを表します。

他のフレームワークと比べ、実行が遅いという弱点があります。

■CNTK

Microsoft製のディープラーニングフレームワークです。Brain scriptという独自の言語で書きます。最近、Pythonにも対応しました。

CPU版の実行速度はほかのフレームワークを圧倒しています。

やや、ユーザが少ないのが欠点です。

■Chainer

日本のディープラーニング企業である Prefferd Networksによって開発されています。国内での人気は高く、日本語の情報が多いです。計算グラフを動的に作成するデファインバイランをいち早く実装しました。

拡張したい場合は、微分方法(バックワード演算)を明示したり、cuDNNライブラリ用のC++コードを書く必要があるという弱点を持ちます。cupyというnumpy互換のライブラリを利用することもできます。

■Torch & PyTorch

Pytorch は Facebook が後援しているディープラーニングフレームワークです。Chainerとよく似た構造をしています。バックワード演算を書く必要はありません。最近は論文の実装が PyTorchで公開されることも多く、じわじわと人気が高まっています。

■MxNet

ワシントン大学とCMUによって開発されているディープラーニングライブラリで、AmazonやBaiduで利用されています。メモリ負荷が低い、実行速度が速いなどの特徴を持ちます。一方で、まだまだドキュメントが不足している印象があります。

■Caffe

バークレイによるディープラーニングライブラリです。高速に動作し、画像処理に強いという特徴をもちます。豊富な学習済みモデルが、Model Zoo · BVLC/caffe Wiki · GitHub で公開されています。あまり触ったことはありませんが、モデル定義ファイルがプログラムと分離していたりして少し特殊なライブラリという印象があります。

■Keras

バックエンドと呼ぶ低水準の下位ライブラリをラップする形の高水準ライブラリです。バックエンドには TensorFlow, CNTK、Theano(今回は紹介しない)が利用できます。非常に簡単にディープラーニングのコードを書くことができます。入門には最適です。一方で、複雑なネットワークを組みたい場合は力不足感が否めません。

ラップするとは

そのプログラム特有の型を使わず、Stringとはintとか、javaで共通して使える引数や戻り値でinterfaceをつくり、それを継承したクラスで、実装すること

■Gluon

MxNetをラップする高水準ライブラリです。将来的にCNTKもサポートする予定だそうです。Kerasとくらべて高い拡張性を持っています。一方で公式でサポートするレイヤの数はまだまだ少ないです。

■H2O

 HadoopやSpark上で動かすことを想定した機械学習ライブラリです。ドキュメントが少ないのが欠点ですが、分散環境との相性は良いでしょう。

結局、どのフレームワークを使えばよいのか?

あなたがディープラーニングプログラムの初心者なら、Kerasを一番におすすめします。入門者用に思われがちですが、ある程度の汎用性があります。私もKerasでかけるな、と思ったらKerasで書くことが多いです。

ディープラーニングプログラムをプロジェクトでしっかり書きたいと思った時にはどうすればいいのか。これはなかなか難問です。ディープラーニングはまだ発展中の技術です。フレームワークの仕様もどんどん変わります。現状ではフレームワークを使い捨てていく感じが良いのではないかというのが個人的な感想です。独自のラッパーライブラリを書く、などとしていたら、依存ライブラリの仕様が変わって全部書き直し、となってしまいます。

 

参照

https://www.codexa.net/tensorflow-lite-annoucement/

人材採用から人材管理まで AI×HRのサービス10選!

 

こんにちは、たくやです。

今週はニュースからちょっと離れて、AIがHR(Human Resourse, 人事)にてどのように利用されているのか、また利用されようとしているのかについて、いくつかのサービスをご紹介させていただきたいと思います。

これからHRにAIを導入しようと考えている方、また、AIが具体的にどのようなメリットをもたらすのかに興味がある方が読みやすい構成にいたしました。

これまで人事は上司の好き嫌い、経験や勘、飲みニケーションなどの不透明な条件下で行われていましたが、そういったものを画一的に体系化、数値化することで、従来よりも効率的に業務改善などの業務を達成できることが期待されています。

 

<チャットを利用した個人の仕事適正判定サービス >

1、株式会社フォーラムエンジニアリングの「Insight Matching 」

Watson のテクノロジーを活用した人材マッチングシステム。

実際にユーザーとのテキストチャット形式を通し、対話履歴書のみでは知ることが難しかった趣味や性格などの人間的な側面も分析し、独自のアルゴリズムを用いて適正を数値化してくれます。

例えば、設計士を募集する企業があったとします。その企業にとって適合性の高いエンジニアを提案する際に、エンジニアとの対話から得た情報をそれぞれ数値で表します。具体的には、数値上では電話営業のスキルが35点だったとしても、設計士を募集する企業の事業領域とのスキルやモチベーションにおける適合性が90点なので、クライアント企業とエンジニアの適合性は 70点。エンジニアと企業の適合性が十分に高いとし、人材コンサルタントに提案するといった仕組みです。

IT領域から自動車関連など、多岐に渡るエンジニアが約5000人登録しているそうです。

www.youtube.com

 

 

 

<テキストのパフォーマンス分析サービス>

2、Textio

機械学習と自然言語処理を駆使し、候補者の作成したレジュメや求人広告に利用されている文言やレイアウト、オファーメール、応募効果(ニーズにあった人材の獲得ができたか、獲得までに要した時間)などを解析し、候補者の属性によって効果的な、またはネガティブにとらえられる可能性の高い文言や文章を割り出だしてくれます。

2014年にシアトルで設立したスタートアップ企業です。

TwitterMicrosoft、スターバックスが利用しています。 

textio.com

 

<求職者データベース検索サービス>

3、Connectifier

2012年に元Google社員のBen McCann氏とJohn Jersin氏が創業したカリフォルニアの企業です。

求職者情報をデータベース化し、人事担当者がキーワードやロケーションなどの条件を入力すると、データベースから潜在的な候補者を含む、適合性の高いターゲットを迅速にピックアップしてくれます。

なんと、4億人を上回る求職者情報をデータベース化しています。

具体的なアルゴリズムは公開していないものの「AIを活用する」とコーポレートサイトに明記されており、AI×HRのスタートアップとしてかねてより注目が集まっていました。

2016年2月、Connectifier は LinkedInに買収されました。

 

4、AI人事部長

チームメンバーや応募者が用意しているアンケートに答えることによって、その人の強みや特徴を視覚化。チームがどういう状況なのか、または応募者がチームにフィットするかなどを分析してくれます。

できることは

チームメンバの個人単位でのサポート

  • プロファイリング分析:メンバーがどういった価値観を持っているか。
  • メンタルパワー分析:各人のストレス耐性や感情統制力などの評価。
  • 個性活用マニュアル:それぞれが好む対応やNGワードなど。モチベーションを上げるために必要な事項などの提案。

面接のサポート

「こういった人材が欲しい」というのを社内のメンバーから選択すると事前に応募者に行ったアンケートから面接より前にAIが応募者の特性を分析し、その人がチームが求める人材かを分類してくれます。

 特筆すべきは”無料”という点。これならすぐにでも使い始めることができます。

WORKUP AI人事部長

 

 

<SNSなどから自動で情報を収集し、データベース化、マッチングするサービス>

5、Gild

サンフランシスコにある企業で、エンジニア採用向けのサービスを提供しています。

一般的なSNSだけではなく、エンジニアが利用するGithubやStack Overflowなどのプログラミング技術に関するコミュニティサイトや個人ブログなど、Webサイトをクロールして大量のデータを収集し、データベース化してくれます。(”スクレイピング”という手法を用いていると推測されます。)

エンジニアの実力は履歴書や面接だけでは、適切に評価することは難しいものですが、常に最新化されるビッグデータを独自のアルゴリズムで解析することで、エンジニアのレベルを可視化することができます。

さらにGild Spotlightという求職者として顕在化していないエンジニアが転職をしたくなるタイミングも推測して企業の人事担当者に推薦することができるサービスも提供してくれます。

Facebook、trip advisorなどの企業がGildのサービスを利用しています。

glid.com

 

6、Entelo

Enteloはサンフランシスコの企業で、人事担当者がエンジニアとデザイナーをWEB上から検索できるサービスを提供しています。

サービスの仕組みはGlidと似たようなもので、それにデザイナーの採用がプラスされたものです。

応募があった候補者のデータを簡単に管理・分析できるインバウンド向けのEntelo Stackと、自ら検索して候補者を探すアウトバウンド向けのEntelo Searchがあります。

Entelo Stackは、応募があった数多くの候補者のレジュメに対して、あらかじめ設けた基準と照合し、最良の候補者が優先的にリストアップできるサービスを提供しています。
人事担当者が自社にマッチした候補者を優先順に目を通せるため、選考プロセスや選考スピードの効率化につながります。

Entelo Searchは、膨大なデータベースから、ニーズにあった候補者を検索することが可能なサービスです。

候補者の転職検討タイミングの推測、ビジネススキル・人種・軍隊経験までわかる多様な検索軸、優秀な候補者のレコメンドなど多彩な機能があります。

Microsoft、Znefits、Facebookなどの企業がサービスを利用しています。

www.entelo.com

 

7、Talent Base

日本がリリースしているサービスになります。

お気に入りの人材をプールし一括管理することができます。また、作成した人材プールや行動履歴を解析して、各社に最適な人材を自動で発掘してくれます。

企業別にソーシャルでの影響度をランキングしたり、ゲームをすると勝手に友達を評価する「評価ゲーム」など、面白い独自コンテンツが入っています。

talentbase.io

 

 

<社員のエンゲージメントを数値化して評価してくれるサービス>

8、IBM Kenexa

「どんな特徴が社員の生産性にどう影響を与えるのか?」という曖昧なテーマに対し “既に学習済みのデータベース” を用いて、導き出された仮説に対して、即時「こうしたらいいのでは?」という提案までくれます。 

主に行われることは3つ

  • 設問ライブラリから5問程度を選択。オンラインで意識調査を実施。
  • 得られた回答を解析。WatsonAnalyticsの持つビックデータを活かし仮説を構築。
  • 数値をグラフィカルに明示しつつ、具体的な改善方法や対応策を提案。

世界の優良企業をランキングするフォーチュン・グローバルのTOP10企業の内8社までもが導入する、とても有名なツールとなっています。

www-01.ibm.com

 

9、HRMOS

日本の会社であるビズリーチが提供するサービスです。

できることは3つ。

  • 社員の成果、成果に対する上長コメントなどの言語解析。
  • 出退勤データや経歴データのディープラーニング。
  • 上記をベースとした『その企業が伸びるために採用するべき人材像』の明確な見える化。

IBM Kenexaに比べて、その会社の雰囲気を明確化するというところまで踏み込んでいるのがユニークな点です。

hrmos.co

 

10、Fronteo Kibit

こちらは上記二つとは趣旨が違い、”離職を防ぐ”を目的として作られたサービスです。

Fronteoは日本の会社で、AI業界ではなかなか有名な会社です。

従業員のメールをチェックして、

「離職しそうな可能性がある。」

「正当な評価を受けていないことをストレスに感じている可能性がある。」

「特定のリーダーからパワハラを受けている可能性がある。」

といったことをアラートで通知してくれます。

Watsonよりも安価で実装でき、過去にはわずか5件の教師データのみでユーザーボイスの感情判別を98%超の正答率で処理した実績もあるというAIです。

www.fronteo.com

 

・まとめ

最近のHRの流行はAI×ソーシャルデータという、技能をさらに詳しくみるだけでなくだけでなく、個人の特性や感情まで組み込んだ人事に移ってきているということが読み取れましたね。

 

参照

http://studyhacker.net/vocabulary/hr-tech

https://www.ibm.com/think/jp-ja/watson/cognitive-staffing/

https://www.hrpro.co.jp/agora/4272

https://bita.jp/dml/ai_hr