読者です 読者をやめる 読者になる 読者になる

人間だったら考えて

考えて考えて人間だったら考えて

不均衡データに対する予測結果のAccuracyは簡単に上がってしまう

機械学習 統計学

この記事は何?

機械学習における不均衡データの扱いは,学習時にも評価時にも注意する必要があります.
例えばSVMにおける学習では,クラス重みを事前に設定することで,不均衡データによるバイアスを軽減できます.
不均衡データに対する予測精度の評価について,Accuracyを用いて評価を行うと,不当に高い値が得られてしまいます.
この記事では不均衡データに対する予測結果をどのように評価すべきかについて紹介します.

不均衡データにおけるAccuracy評価を簡単に上げる

Accuracyは次式で定義されます.
 {\rm Accuracy}=\frac{{\rm TP}+{\rm TN}}{{\rm TP}+{\rm TN}+{\rm FP}+{\rm FN}}

ここで,正例数 n_{+}が負例数 n_{-}よりも多い不均衡データを考えます.
この不均衡データに対して全て正例と予測します.
すると,{\rm Accuracy}=\frac{n_{+} + 0}{n_{+} + 0 + n_{-} +0}=\frac{n_{+}}{n_{+}+n_{-}}となります.
 n_{+} \gg n_{-}より,Accuracyは1に近づいていきます.

実際に実験してみます.正例/負例の比率を0.5,0.99として,それぞれの予測結果におけるAccuracyを求めます.

import numpy as np
from sklearn.metrics import accuracy_score, confusion_matrix
def make_labels(N, positive_ratio=0.5):
    n_positive = int(N * positive_ratio)
    n_negative = N - n_positive
    labels = np.r_[np.ones(n_positive), np.zeros(n_negative)]
    return labels
N = 1000
positive_ratio = 0.5
y_true = make_labels(N, positive_ratio=positive_ratio)
y_pred = np.ones(N)
print("Accuracy:", accuracy_score(y_true, y_pred))
    Accuracy: 0.5
positive_ratio = 0.99
y_true = make_labels(N, positive_ratio=positive_ratio)
y_pred = np.ones(N)
print("Accuracy:", accuracy_score(y_true, y_pred))
    Accuracy: 0.99

というわけで,不均衡データでは簡単に高いAccuracyを出すことができます.
逆に言うと,Accuracyだけ見てすごい!と言うのは危険だということです.
Accuracyの評価の際には,ラベルの分布も一緒に見てすごいかどうか考えた方が良さそうです.

じゃあどうしたらいいの?

不均衡データに対する予測結果の評価において,Matthews correlation coefficient (MCC)が用いられています.
MCCは次式で定義されます.
 \frac{{\rm TP}\times {\rm TN} - {\rm FP} \times {\rm FN}}{\sqrt{ ({\rm TP}+{\rm FP}) ({\rm TP}+{\rm FN}) ({\rm TN}+{\rm FP}) ({\rm TN} + {\rm FN})}}
MCCは-1から1の値を取り,正しい予測のときに1・逆の予測のときに-1・ランダム予測のときに0となります.
正例に関する予測精度と負例に関する予測精度が共に考慮されているため,不均衡データに対する予測精度の評価に使えるということ…でしょうか.
MCCを使って先程の実験を行います.

positive_ratio = 0.5
y_true = make_labels(N, positive_ratio=positive_ratio)
y_pred = np.ones(N)
print(matthews_corrcoef(y_true, y_pred))
    0.0
positive_ratio = 0.99
y_true = make_labels(N, positive_ratio=positive_ratio)
y_pred = np.ones(N)
print(matthews_corrcoef(y_true, y_pred))
    0.0

 {\rm TN}=0かつ{\rm FN}=0なので,MCCは0となりました.

その他

評価指標はそれぞれ一長一短で特性も異なるので,出来る限り複数種類の評価指標で予測精度を検証した方が良いです.
もちろん,目的に応じた評価指標を使うべきなのですが…

【Python】組み込み関数のsumとnumpyのsumはどっちが速い?

Python

この記事は何?

Pythonでリストやnumpy.array中の数値和を求めたい時は,組み込み関数のsumを使う方法とnumpy.sumを使う方法があります.
この記事では,どちらの方法がより高速に和を求められるのかを確認します.

結論は?

リスト中の数値和を求めるときは組み込み関数のsumが速く,numpy.array中の数値和を求めるときはnumpy.sumが速かったです.

実験

N = 10000000の要素のリストおよびnumpy.arrayを生成し,組み込み関数のsumおよびnumpy.sumの実行時間を計測します.
Pythonのバージョンはanaconda 2.5.0 (Python 3.5.1)です.

実行結果

import numpy as np
N = 10000000
python_list = [i for i in range(N)]
numpy_array = np.arange(N)
# python sum -> python list
%timeit sum(python_list)

    10 loops, best of 3: 82.6 ms per loop
# python sum -> numpy array
%timeit sum(numpy_array)

    1 loops, best of 3: 959 ms per loop
# numpy sum -> python list
%timeit np.sum(python_list)

    1 loops, best of 3: 588 ms per loop
# numpy sum -> numpy array
%timeit np.sum(numpy_array)

    100 loops, best of 3: 7.46 ms per loop

表にまとめます.

組み込みsum numpy.sum
python list 82.6 ms 588 ms
numpy array 959 ms 7.46 ms

その他

何でもかんでもnumpy.sumを使えば良いというわけでは無さそうです.numpy.arrayに変換してからnumpy関数を使って処理するのが一番良さそうですね.

CNNとRankNetを用いた画像の順序予測(ラブライブ!のキャラクター順序予測を例に)

機械学習 Python

(Chainer Advent Calendar 2016 5日目です.この記事はTokyoTechLTで発表したものと同内容のものです.)

この記事は何?

以前Chainer Advent Calendar 2015において,Chainerを用いたRankNet(ランク学習手法の1つ)の実装を紹介しました
本記事では,RankNetを応用した画像の順序予測を紹介します.

やりたいことの概要

RankNetを応用した画像順序予測の概要図を示します.
f:id:sz_dr:20161204133645p:plain


訓練データとして,既に順序付けされた画像集合を用います.
順序付けの例として,ユーザーの好みによるレイティングが考えられます.クリック率などを使う方法も可能だと思います.

得られた訓練データをCNNに入力し学習します.目的関数は,訓練データ中の画像対の順序が正しく識別できるように設定します(RankNet).

得られた予測器にレイティング未知の画像集合(テストデータ)を入力します.予測器の出力はレイティングを表しているため,これを用いて画像集合の順序付けを行います.

何に使えそう?

画像が好みの順番に並んでいると嬉しいケースは色々考えられます.

  • 漫画の表紙買い,アルバムのジャケット買い
  • pixivのようなイラストコミュニケーションサービスで,好みの画像検索
  • Amazonなどのショッピングサイトで,購買率が上がるような画像の表示

実装

まずはネットワークを書きます.CNNもRankNetも非常にシンプルに書けました(Chainer強い).
CNNのパラメータは決め打ちです,入力画像はRGBでサイズは80×80とします.

from chainer import Chain
import chainer.functions as F
import chainer.links as L


class CNN(Chain):

    def __init__(self):
        """
        picture size: 3 * 80 * 80
        Convolution2D parameter
        input_channels, output_channels, filter_size
        """
        super(CNN, self).__init__(
            conv1=F.Convolution2D(3, 20, 5),
            conv2=F.Convolution2D(20, 50, 5),
            l1=L.Linear(14450, 500),
            l2=L.Linear(500, 1)
        )

    def __call__(self, x):
        h1 = F.max_pooling_2d(F.relu(self.conv1(x)), 2)
        h2 = F.max_pooling_2d(F.relu(self.conv2(h1)), 2)
        h3 = F.relu(self.l1(h2))
        y = self.l2(h3)
        return y


class RankNet(Chain):

    def __init__(self, predictor):
        super(RankNet, self).__init__(predictor=predictor)

    def __call__(self, x_i, x_j, t_i, t_j):
        s_i = self.predictor(x_i)
        s_j = self.predictor(x_j)
        s_diff = s_i - s_j
        if t_i.data > t_j.data:
            S_ij = 1
        elif t_i.data < t_j.data:
            S_ij = -1
        else:
            S_ij = 0
        self.loss = (1 - S_ij) * s_diff / 2. + \
            F.math.exponential.Log()(1 + F.math.exponential.Exp()(-s_diff))
        return self.loss

次は訓練のスクリプトを書きます.
入力は訓練画像とレイティングファイルです.訓練画像はnumpyでシリアライズされたファイル(.npy)とし,レイティングファイルは各画像のレイティングがN行並んだテキストファイルとします.
出力は訓練画像のレイティング結果と,予測器をシリアライズしたファイル(.pkl)です.

# -*- coding: utf-8 -*-
import argparse
import pickle
import numpy as np
from chainer import Variable, optimizers
import net


def ndcg(y_true, y_score, k=None):
    y_true = y_true.ravel()
    y_score = y_score.ravel()
    if k is None:
        k = len(y_true)
    y_true_sorted = sorted(y_true, reverse=True)
    ideal_dcg = 0
    for i in range(k):
        ideal_dcg += (2 ** y_true_sorted[i] - 1.) / np.log2(i + 2)
    dcg = 0
    argsort_indices = np.argsort(y_score)[::-1]
    for i in range(k):
        dcg += (2 ** y_true[argsort_indices[i]] - 1.) / np.log2(i + 2)
    ndcg = dcg / ideal_dcg
    return ndcg

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("X_train_npy")
    parser.add_argument("rank_file")
    parser.add_argument("--output_pred", "-op", default="pred.txt")
    parser.add_argument("--output_model", "-om", default="model.pkl")
    parser.add_argument("--image_scale", type=float, default=1/255)
    parser.add_argument("--n_iter", type=int, default=1000)
    parser.add_argument("--monitor_step", type=int, default=100)
    parser.add_argument("--ndcg_k", type=int, default=100)
    args = parser.parse_args()

    np.random.seed(0)
    X_train = np.load(args.X_train_npy)
    X_train *= args.image_scale
    y_train = np.loadtxt(args.rank_file)
    N_train = np.shape(X_train)[0]

    model = net.RankNet(net.CNN())
    optimizer = optimizers.Adam()
    optimizer.setup(model)

    for step in range(1, args.n_iter + 1):
        i, j = np.random.randint(N_train, size=2)
        # X_train: (N, 3, 80, 80)
        x_i = X_train[i].reshape((1, ) + X_train[i].shape)
        x_j = X_train[j].reshape((1, ) + X_train[j].shape)
        y_i = Variable(np.array(y_train[i]))
        y_j = Variable(np.array(y_train[j]))
        model.zerograds()
        loss = model(x_i, x_j, y_i, y_j)
        loss.backward()
        optimizer.update()
        if step % args.monitor_step == 0:
            train_pred = model.predictor(X_train).data
            train_ndcg = ndcg(y_train, train_pred, args.ndcg_k)
            print("step: {} | NDCG@{} | train: {}".format(
                step, args.ndcg_k, train_ndcg))

    y_pred = model.predictor(X_train).data
    np.savetxt(args.output_pred, y_pred)
    out_model = args.output_model
    with open(out_model, "wb") as out_fp:
        pickle.dump(model, out_fp)

最後に予測のスクリプトを書きます,といってもテスト画像と予測器を読み込んで予測するだけですが…

# -*- coding: utf-8 -*-
import argparse
import pickle
import numpy as np

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("model_pkl")
    parser.add_argument("X_test_npy")
    parser.add_argument("--output", "-o", default="pred.txt")
    args = parser.parse_args()

    with open(args.model_pkl, "rb") as model_pkl_fp:
        model = pickle.load(model_pkl_fp)
    X_test = np.load(args.X_test_npy)
    y_pred = model.predictor(X_test).data
    np.savetxt(args.output, y_pred)

使用例

以上の実装を使って,「ラブライブ!サンシャイン!!」の画像順序予測をやってみたいと思います.
訓練データには「ラブライブ!」の画像を用いました.

学習の流れ

  1. Lantisちゃんねるから『TVアニメ「ラブライブ!」先行発表PV』を取得します.
  2. OpenCVを用いて顔部分をクリップします.277枚の顔画像を生成しました.顔画像認識はlbpcascade_animefaceを参考にしました.
  3. 各画像にレイティングを付けます(277枚の画像にレイティングを付けます,つらい作業).
  4. 上記で実装したCNN+RankNetで学習します.

訓練データに対する予測結果

まず,上記の実装が正しく動いていることを確認するために,訓練データに対して予測を行い正しく順序付けされているかを確認します.
f:id:sz_dr:20161204142340p:plain

レイティング値は好み度を表しています(左に行くほど好きです).
にこにーと希ちゃんが上位にたくさん来てます.花陽ちゃんは1枚だけ見当たります,レイティング下位のキャラクターと類似性が高いためでしょうか…
訓練データ内に非常に類似した画像が含まれていますが,これはクリッピング後にクラスタリング等を行っていないためです.冗長なデータは訓練データから取り除いた方が良いと思います.

テストデータに対する予測結果

学習の結果得られた予測器を使って,「ラブライブ!サンシャイン!!」の画像順序予測をします.
テスト画像はLantisちゃんねるからラブライブ!サンシャイン!! Aqoursメンバー紹介PVを取得し,先程と同様に顔画像をクリップします.各キャラ毎に2種類の画像を取り出して計18枚の画像をテストデータとしました.
f:id:sz_dr:20161204143225p:plain

上が私がランク付けした結果(左に行くほど好き)で,下がCNN+RankNetによる予測結果です.
なかなか私のランク付け結果とは一致しませんが,訓練データの傾向は反映しているように見えます.
また,同じキャラクターの画像は似たような順位に来ていることがわかります.

まとめ

この記事ではCNN+RankNetを使った画像の順序予測について,実装および使用例を紹介しました.
使用例で紹介したキャラクター順序予測は難しい問題です.ランク付けの際に,キャラクターへの思い入れが入ってしまうので…
キャラクター順序予測に本気で取り組む際には,キャラクターの性格を学習にどうにかして取り入れる必要がありそうです.
これについては,TokyoTechLTで発表後に以下のような意見をいただきました.



マルチモーダル学習になってきましたね,テキストで順位付けしている例はまさにwebページランキングですね.画像情報を組合せたランキング予測なんてやられているんでしょうか…??新しい研究ネタになりそうです.

レイティングを付ける作業は結構大変です,そのため学習データサイズが小さくなってしまうという問題点があります.これについては既存の学習済み予測器をfine tuningしたり,自動でレイティングを得る方法(画像クリック率)を考えると良さそうです.

あまりChainer自体の話はしていませんでしたが,Pylearn2 -> Caffe -> Chainerと移り変わってきた身からすると,Chainerはかなり楽にネットワークを書けるので好きです.Deep Learningフレームワークが溢れているご時世ですが,何でも良いので1つ使えるようにしておけば良いと思います…色々なフレームワークを平均的に知っているよりも,1つのフレームワークを極める方が良いんじゃないかなあ(・。・)

z-scoreに変換しても相関係数は変わらない

機械学習 統計学

この記事は何?

機械学習の前処理として特徴量のスケーリングを行うことがありますが,スケーリング手法の1つとしてz-score変換があります.
z-scoreは平均が0,標準偏差が1となるようにスケーリングを行います,z-scoreを10倍して50を加えるとお馴染みの偏差値になります.

特徴量XYの相関係数を\rhoとします.このとき,Xをz-score化したX'Yの相関係数\rho'を考えます.
直感的には\rho\rho'は等しくなるんじゃないかなと思いますが,本当に等しくなるのか確かめてみたときのメモです

証明

結論から先に言うと,\rho\rho'は等しくなります.

z-score変換は,平均を引いて標準偏差で割って求めるため,線形変換です.
そこで,X'=aX+bとし,\rho=\rho'となることを示します.


\rho'= \frac{{\rm cov}[X', Y]}{\sqrt{ V[X' ]V[Y] }}\\
 = \frac{E[X'Y] - E[X']E[Y]}{\sqrt{ V[X' ]V[Y] }}\\
 = \frac{E[aXY+bY ] - E[aX+b]E[Y]}{\sqrt{ V[X' ]V[Y] }}\\
 = \frac{aE[XY] + bE[Y] - (aE[X]+b)E[Y]}{\sqrt{ V[X' ]V[Y] }}\\
 = \frac{a(E[XY]-E[X]E[Y])}{\sqrt{ V[X' ]V[Y] }} \\
 = \frac{a(E[XY]-E[X]E[Y])}{\sqrt{ V[aX+b ]V[Y] }} \\
 = \frac{a(E[XY]-E[X]E[Y])}{a\sqrt{ V[X ]V[Y] }} \\
 = \frac{E[XY]-E[X]E[Y]}{\sqrt{ V[X ]V[Y] }} \\
 = \rho

というわけで,\rho=\rho'となることが示されました.
これで安心して相関係数を計算できますヽ(^o^)丿

追記(2016/11/20)

上式では,aの正負によっては\rho=-\rho'になることがありますね…
z-scoreではaは正なので(標準偏差は正)問題は無いのですががが

Pythonのset演算は演算子使うとスマート

Python

この記事は何?

Pythonでset演算をするには,union関数やintersection関数を用います.
最近,set演算は演算子を用いた方法もサポートされていることを知ったので(ドキュメントに書いてあるんですけどね),紹介したいと思います.

どのように書けるのか?

set演算は演算子を用いて,以下のように書けます.

>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 | s2
{1, 2, 3, 4}
>>> s1 & s2
{2, 3}
>>> s1 - s2
{1}
>>> s1 ^ s2
{1, 4}
>>> s3 = set([1, 2, 3])
>>> s4 = set([1, 2])
>>> s3 > s4
True
>>> s3 < s4
False

ビット演算と同じような気持ちで書けます,union関数やintersection関数を呼ぶよりもスマートな気がします.

どんな演算子がサポートされてるの?

これに関しては公式ドキュメント
http://docs.python.jp/3.5/library/stdtypes.html#set-types-set-frozenset
を参照です,たまに公式ドキュメントを読むと発見がありますね…

numpyのmax関数とmaximum関数

Python

numpyのmax関数を用いた,次のコードの実行結果を予想してみてください.

import numpy as np
xs = np.array([1, -2, 3])
np.max(xs, 0)

この出力は3となります.[1, -2, 3]と0の4つの数字のうち,最も大きい値を出力します.

一方で,[max(1, 0), max(-2, 0), max(3, 0)]を出力したい時があります.
その時は,numpyのmaximum関数を用います.

xs = np.array([1, -2, 3])
np.maximum(xs, 0)  # [1, 0, 3]

maxとmaximumという2つのメソッド名はどうにからならなかったものか(・_・;)

PowerPointでもTeX数式を使いたい!

デザイン PowerPoint

この記事は何?

PowerPointアドインにIguanaTeXというものがあります.
IguanaTeXは,TeX出力をPowerPointに図として簡単に挿入できるアドインです.

PowerPointのデフォルト数式に辟易した方にオススメなので紹介したいと思います.

PowerPoint数式と,IguanaTeX出力の数式を比較します.
f:id:sz_dr:20160918221653p:plain

PowerPoint数式の J fがあまり好きじゃないんですよね…なんだか上にヒョロっとしている感じで…

IguanaTeX出力の数式はいつも見慣れているフォントで安心できます.

インストール

インストールは公式TeX Wikiなどが参考になると思います.

使い方

IguanaTeXをインストールすると,ツールバーにIguanaTeXタブが現れると思います.
f:id:sz_dr:20160918222443p:plain

(New LaTeX display)をクリックするとTeXソース編集画面が開きます.適当に数式を打ってみましょう.
f:id:sz_dr:20160918222934p:plain

(Generate)をクリックすると挿入されます.挿入されたオブジェクトを選択した状態で(Edit LaTeX display)をクリックすると,先程の編集画面に戻ることが出来ます.