2014年5月4日日曜日

python(scikit-learn)で決定木

ここでRのパッケージを使った決定木による分類の紹介をしていたので、python(というかscikit-learn)でも同じことをやってみた。せっかくなのでこの場で書いておく。

※下記に示したpythonソースはIPythonNotebookにまとめたのでこちらを参照してもらうとよいかも。

■まずは分類したいデータを用意。

ここでは、わかりやすさのために、自家製のデータセットを学習データとして使う。用意したデータは説明変数が実数をとる「x」と「y」の2種類で、目的変数は「0」と「1」の2つのクラスをとるような学習データだ。この学習データの説明変数と目的変数の関係をプロットすると(※1)以下のようになる。青い点が「クラス0」、赤い点が「クラス1」のデータを表わす。

ここと同様にXORパターンデータにしていて、
  ・クラス0は座標(1,1)と座標(-1,-1)を平均として分散0.5で正規分布
  ・クラス1は座標(-1,1)と座標(1,-1)を平均として分散0.5で正規分布
するという学習データになっている。
githubに学習データを置いておいた。



■scikit-learnを使って決定木で分類してみる。

教師データからscikit-learnの決定木ライブラリで学習させて、その結果を用いて、新しいデータを与えて分類させる一連のコードを書いた。コードは以下のとおり。
実行結果は、[0 1] となり、つまりは
  ・ x=2.0, y=1.0 のデータはクラス「0」に分類
  ・ x=1.0, y= -0.5のデータはクラス「1」に分類
されており、予想どおりの分類結果になってメデタシ、メデタシ。

■分類境界を可視化してみる。

上記の2つのデータでは、それっぽい分類ができているようだけれど、一般的にどう分類されるのかを確認してみる。そのためには上の図の学習データのプロット上に、決定木アルゴリズムの学習結果の分類境界をプロットするのが良いだろう。

可視化のコードは以下のとおり(上述のコードに追記して実行する。)
少々長いけど、やっていることはプロットの領域を細かいメッシュ(xとyをそれぞれ0.05区切り)に分けて、それぞれの点で学習結果からどちらに分類されるかを全て計算し、その結果(それぞれ0,か1の値)を等高線プロットしている。

★上述のコードに追記して実行する。
実行結果は以下の図のようになる。

本当の正解(第1&3象限は青(クラス0)で、第2&4象限は赤(クラス1))を知っている人間としては
少々複雑に分類しすぎているとも思えるけれど、与えられた有限個の学習データをキチンと分類できていることが見て取れます。(データのない所は正しく学習できないのは当たり前)

先に学習結果から分類した2つのデータ(2.0, 1.0)と(1.0, -0.5)は、それぞれ前者が上図の青の領域内、後者が赤の領域内にあったから、それぞれ青と赤に分類されたことになる。

■実際の決定木を可視化

他の分類アルゴリズムと比べたときの決定木の良さは、学習データを元にどのように分類されていっているのかを簡単に知ることができるところだ。

結局のところ決定木のアルゴリズムは、学習データをきれいに分類できる説明変数の閾値を探してその閾値で分類した各グループをさらに他の閾値で分類していく、If-Elseロジックを繰り返していっているだけである。If-Elseの数だけ判断の分岐が増えていきまるで木のような構造になるから「決定木」と呼ぶわけだ。

Python(scikit-learn)でも閾値によるの分岐の可視化もできるので、実際に出力してみて、上図の境界がどのように境界が決められていっているのかを見てみる。
出力方法は、scikit-learnのライブラリで、決定木のツリーをdot言語で記述したdotファイルを出力し、それをgraphvizのような可視化ツールでツリー構造を可視化する流れになる。

dotファイルは以下のコードで出力する(前の2つのコードの後に追記して実行)。

これを実行して出力した「xor_simple.dot」ファイルをgraphvizをつかって出力すると以下の図になる。(実際のツリー構造ははもっと大きいが一部だけをここでは表示している。)



この木構造が示しているのを一部説明すると、まず決定木は、

  1. X[1](これは説明変数「y」のライブラリ内部での名称)が1.862以下か否かを判定。(一番上部のノード)
  2. 上記の判定で「NO」の場合、それをクラス「0」とする。今回の学習データの場合、3つのデータが該当する。(上から2番目、右側のノード)
  3. 上記判定で「YES」の場合、2つめの閾値判定として、「X[0](これは説明変数「x」のライブラリ内部での名称)が1.9746以下か否か」の判定を行う。この判定を行うのは今回の学習データの場合77個(上から2番目、左側のノード)
  4. 以下同様の繰り返し
というようなIF-ELSEの判定を繰り返すことで分類が行われることを示している。

■汎化(剪定)

上記のような決定木のツリー構造をより深くしていき、学習データのほとんど全ての点を正しく分類するようにしても構わないが、いわゆる過学習の状態になりうる。そのため汎化性能を下げるような余分なツリー部分の判定ロジックを削る(つまり剪定(pruning))作業が必要になる。しかし、残念ながらその剪定アルゴリズムは現バージョンのscikit-learnのライブラリではサポートしていない・・・
そのため、剪定についてはこの場では割愛。もし必要であればこことかこの本のP183~P187を参照してほしい。

以上。

(※1)プロット用のpythonのソースはここ。
(※2)ここと同様に、少し複雑にするように、XORパターンのデータを用意した。

2014年3月2日日曜日

nginxのインストールからマルチドメインの設定まで(@CentOS)

  サイトを立ち上げる必要があったので、CentOSにnginxのインストールからマルチドメインの設定までの作業をした。今回はその時のメモ。(参考サイトはココと、ココと、ココ

■マルチドメインとは?

  マルチドメインは1つのサーバ(IPアドレス)で、複数のドメインを管理できること。
  例えば、「aaa.comとbbb.comの2つドメインでそれぞれ のサイトを立ち上げたいけど、今のところそんなにアクセス数もないのでサイト毎にサーバを用意するのはリソース(≒お金)の無駄だなー」という時に、重宝する。
  マルチドメインは、Webサーバアプリケーション(ここではnginx)のバーチャルサーバ機能(※1)を利用して行う。
  nginx は、(ブラウザからの)HTTP リクエストヘッダの “Host”情報を読み取り、どのコンテンツを返すべきかを判定する(参考→nginx公式解説ページ)(※2)。

■作業環境

今回の作業環境は以下のとおり。
  • サーバ: さくらインターネットのVPS
  • OS: CentOS 6
  • ドメイン:さくらインターネットであらかじめ2つのドメインを取得し、それぞれのドメインで上記VPSのIPアドレスへ紐づけの設定が完了している。

■手順1(nginxのインストールまで)

▼CentOSに、nginx用のyumリポジトリを登録するRPMをインストール

  nginx公式ページで、CentOS 6 用のRPMのURLを調べて、以下のコマンドを実行する。
# rpm -ivh http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
 (ここで、http://~.noarch.rpmは、上記の公式ページで調べたRPMファイルのURL)

上記を実行したら、「/etc/yum.repos.d/nginx.repo」というファイルが作成されているはずなので、確認。

▼yumでnginxをインストールし、動作確認

  nginxをyumでインストールする。
# yum install nginx

  インストール後、nginxを起動。
# /etc/init.d/nginx start

  ブラウザからサーバのURL(※3)にアクセスしてみて、nginxのwelcomeページが表示されればOK。


■手順2(nginxのバーチャルサーバ機能の設定完了まで)

▼設定ファイルを置くためのディレクトリを作成

  以下のコマンドを実行。
# mkdir /etc/nginx/sites-available
# mkdir /etc/nginx/sites-enable

  これから、運用する個々のドメイン用の設定ファイルを「sites-available」以下において、その設定ファイルを参照するシンボリックリンクを「sites-enable」以下に配置し、nginx自体はそのシンボリックリンクを参照するようにしていく(※4)。

▼個々のドメイン用の設定ファイルを作成&配置

sites-availableディレクトリ以下に、それぞれのバーチャルホスト用の設定ファイルを作成する。ファイル名は「aaa.com」のようにドメイン名と同一にしたほうが運用しやすい。
作成内容は下記の参照のこと。
例えば「vi /etc/nginx/sites-available/aaa.com」 コマンドで以下の編集をする。
# For aaa.com
server {
    listen 80;
    server_name aaa.com www.aaa.com;   #ドメイン名の指定。サブドメインも指定可。

    access_log  /var/log/nginx/access.aaa.com.log; #アクセスログの保存先もドメイン毎に。

    location / {  #コンテンツの置き場所も、ドメイン別に分けておく。
        root   /usr/share/nginx/html/aaa.com;
        index  index.html;
    }
}

上記と同じものをバーチャルサーバをつくる分だけ作り配置する。(たとえばbbb.com用など)

  次に、ここで作った設定ファイルをsite-enableディレクトリ側から参照するシンボリックリンクをはる。
# ln -s /etc/nginx/sites-available/aaa.com /etc/nginx/sites-enable/
# ln -s /etc/nginx/sites-available/bbb.com /etc/nginx/sites-enable/

▼nginxが上記で作成した設定ファイルを参照するように設定。

  viで「/etc/nginx/nginx.conf」に、
include /etc/nginx/sites-enable/*;
   
の行を加える。(他のinclude節の直後に配置しとけばよい)

▼それぞれのドメイン用コンテンツを作成。

「site-available」以下に配置したドメイン毎の設定ファイル内でlocationのrootとして設定したディレクトリを作成。
# mkdir /usr/share/nginx/html/aaa.com
# mkdir /usr/share/nginx/html/bbb.com   

作成後、それぞれのディレクトリ配下に、適当な「index.html」を作成し配置しておく。(あとで接続試験をするため)

▼設定の確認→nginx再起動→接続試験

設定に不備がないかをテストツールでテストする。問題がなければ「test is successful」のような表示がされる。
# /etc/init.d/nginx configtest   

設定に問題がなければ、nginxを再起動。
# /etc/init.d/nginx restart   

再起動後、それぞれのドメイン(今回はaaa.com、bbb.com)にブラウザからアクセスし、それぞれ意図通りのコンテンツが表示されれればOK。


以上。
意外に簡単だったので、よかった。(ここに書いているようにデフォルトバーチャルホストを設定したほうが良いかもだけど、それは今後の課題ということで。)

  • (※1)  apacheでも同じ機能があり、「バーチャルホスト機能」と呼んでる。
  • (※2)  ちなみに、仕組み上HTTPリクエストヘッダに"Host"情報を入れないブラウザでアクセスされた場合には、上手く動かない。ただそのようなブラウザはよっぽど古いブラウザだけで、現在はほとんど、Host情報を入れるようになっている。
  • (※3)  ドメイン名がまだない場合は、例えばhttp://xxx.xxx.xxx.xxxなど。ここでxxx.xxx.xxx.xxxはサーバのIPアドレス。
  • (※4)  なぜシンボリックリンクで参照させるかというと、サイトを一時的に停止したい場合、sites-enable以下のシンボリックリンクを消すだけでOKになるという運用上のメリットがあるから。

2014年2月11日火曜日

行列の対角化とは何か?

はじめてのパターン認識」の観測データの無相関化の節を読んでいて、行列の対角化ってなんだっけ?という、そもそものところでつまづいたので(※1)、行列の対角化について、ここで少し整理しようと思う。

今回は純粋に線形代数学の話題に終始して、本来のデータの無相関化については、また後日書く予定。

■対角化を理解するまでの道筋

すこし長くなるので、「行列の対角化とは何か?」を理解するための道筋を整理すると
  1. 固有値と固有ベクトルの意味を理解する。
  2. 固有ベクトルを並べた変換行列の性質を理解する。
  3. 対角化操作を理解する。
になると思う。というわけでこの道筋に従って話しを進めようと思う。
なお、ここでは話しの分かりやすさを優先するので、数学的な一般性(※2)はひとまずおいておいて、わかりやすいシチュエーションの話だけをしていきたいと思います。一般的な状況でのより詳しいことはここ(PDF)を参照すると良いです。(上記、1、2の話題について、非常にわかりやすく整理&説明されています。)


■固有値と固有ベクトルの意味を理解する。

▼固有値と固有ベクトルの定義

まずは、固有値、固有ベクトルを定義をしておくと以下のとおり。
任意の正方行列 \(A\) について, \(A \mathbf{v} = \lambda \mathbf{v}\)を満たす \(\lambda \) を\(A \) の固有値といい、\(\mathbf{v} \) を\(A \) の固有ベクトルという.
n次の正方行列にはn個の固有値とそれに対応した固有ベクトルを持つ(※3)。

▼固有値と固有ベクトルの意味

上記の定義だけだといまいちなので、これらが図形的に何を意味しているのかを少し説明しておく。
一般にベクトル\(\mathbf{v} \) に行列\(A \)を作用させるということは、ベクトルを線形変換しそのベクトルの「方向」と「長さ」を変更する操作にあたる。

たとえば、
行列\(A=\left(
\begin{array}{cc}
3 & 1 \\
2 & 4 \\
\end{array}
\right)\)
をベクトル \(\mathbf{v} = (5, 1)^T\) (下図:青)に作用させた場合、作用後のベクトルは\(A\mathbf{v} = (16, 14)^T\)(下図:赤)になる。行列を作用させることによって方向と長さが変更されたことがもわかる。



今回は2次元行列を考えているので、2次元の\(\mathbb{R}^2\)空間でのベクトルは(2つ特別なベクトルを除いて)どんなベクトルも上の例と同じように方向が変更される。

その2つの特殊なベクトルが固有ベクトルであるわけだ。

固有ベクトルの定義を見直すとわかるとおり、固有ベクトルに行列\(A \)を作用させても、元のベクトルの定数倍(固有値倍)にしかならない。つまり「方向が変更されない」のである。

実際の例を見てみよう。行列\(A \)の固有値は、\(\lambda_1 = 5\), \(\lambda_1 = 2\)の2つであり、それに対応する固有ベクトルは、\(\mathbf{u}_1 = (1, 2)^T\), \(\mathbf{u}_2 = (1, -1)^T\)である(※4)。例えばそのうちの1つの固有ベクトル\((1,2)^T\)に行列\(A \)を作用させると\((5,10)^T=5(1,2)^T\)であり元のベクトルの固有値(定数)倍で方向は変わらないのである。下図はそれを図示したもので、青が行列\(A \)作用前、赤が作用後のベクトルとなっている。わざわざ図示するまでもないけれど、方向に変化がないことがわかる。



\(\mathbf{x} = a_1 \mathbf{u}_1 +  a_2 \mathbf{u}_2\)
というように、固有ベクトルを線形結合することで、2次元\(\mathbb{R}^2\)空間の任意のベクトル\(\mathbf{x}\)が表現できることに注意すると、任意のベクトルに行列\(A \)を作用させるということは、
\(A\mathbf{x} = a_1 A \mathbf{u}_1 +  a_2 A \mathbf{u}_2 = a_1 \lambda_1 \mathbf{u}_1 +  a_2 \lambda_2 \mathbf{u}_2\)
であり、それぞれの固有ベクトル方向に固有値倍ずつ伸縮結果に他ならない。

■固有ベクトルを並べた変換行列の性質を理解する

行列Aに、一次独立なn個の固有ベクトルが存在し、それを並べた
\(P=(\mathbf{u}_1, \cdot\cdot\cdot , \mathbf{u}_n)\)
を考える。
このとき、基底ベクトル \(\mathbf{e}_i  (i = 1, 2, ..., n )\)に\(P\)を作用させると、
\(P\mathbf{e}_i = \mathbf{u}_i\)
と固有ベクトルに変換されることがわかる。
結果、任意のベクトル
\(\mathbf{x} = \sum_{i=1}^n a_n \mathbf{e}_i\)
に\(P\)を作用させると
\(P\mathbf{x} = P \sum_{i=1}^n a_n \mathbf{e}_i = \sum_{i=1}^n a_n P\mathbf{e}_i  = \sum_{i=1}^n a_n \mathbf{u}_i \)
と変換される。

先述の具体的な行列を例に見ていく。
固有値ベクトルを並べた変換行列\(P = (\mathbf{u}_1,\mathbf{u}_2)\)を考える。この行列を基底ベクトル、
\(\mathbf{e}_1 = (1, 0)^T\),\(\mathbf{e}_2 = (0, 1)^T\)
に作用させると、
\(P\mathbf{e}_1 = (1, 2)^T = \mathbf{u}_1\), \(P\mathbf{e}_2 = (1, -1)^T = \mathbf{u}_2\)
となり、基底を固有空間に移す行列になっていることがわかる。
例えば、
\(\mathbf{x} = (5, 1)^T = 5\mathbf{e}_1+ \mathbf{e}_2 \)
に\(P\)を作用させると、
\(P\mathbf{x} = 5\mathbf{u}_1+\mathbf{u}_2\)
となる。


■対角化操作

上記のように、変換行列を書けることで、任意のベクトルを基底\(\mathbf{e}_i  (i = 1, 2, ..., n )\)での表現から、行列Aの固有ベクトルの固有空間の表現に変換される。
このような変換後、行列Aを作用させると\(\mathbf{u}_i\)は向きが変わらず、長さが \(\lambda_i\)倍になるだけ。
\[AP\mathbf{x} = A \sum_{i=1}^n a_n \mathbf{u}_i = \sum_{i=1}^n \lambda_i a_i \mathbf{u}_i \]
これにさらに\(P^{-1}\)を作用させると\(\mathbf{u}_i\)は元の\(\mathbf{e}_i\)に戻り、
\[P^{-1}AP\mathbf{x} = \sum_{i=1}^n \lambda_i a_i P^{-1}\mathbf{u}_i = \sum_{i=1}^n \lambda_i a_i \mathbf{e}_i \ = D\mathbf{x}\]
ここで、
\[
  D = \left(
    \begin{array}{cccc}
      \lambda_1 & 0         & \cdots & 0 \\
      0         & \lambda_1 & \cdots & 0 \\
      \vdots    & \vdots    & \ddots & 0 \\
      0         & 0         & \cdots & \lambda_n
    \end{array}
  \right)
\]

となり、\(P^{-1}AP\)が対角化行列となるわけである。


(追伸)
ここで使った図はpython&matplotlibを使って描いた。そのソースはここを参照のこと。

  • (※1)昔は、嫌というほど線形代数学を勉強したのに、情けない話だ・・・
  • (※2)固有値が重解になる場合はどうなんだ、とかそんな話。
  • (※3)固有値が重解を持つ場合などは、一見 n個よりも少ない固有値しかないようにみえることもあるが・・・。また、回転行列は実数の固有値はないが、複素数まで考えるとちゃんと固有値を持つ。
  • (※4)求め方はやはりここ(PDF)が参考になる。
  • (※5)つまり、2つの固有ベクトルが2次元\(\mathbb{R}^2\)空間の基底であるということ。

2014年2月4日火曜日

rpy2をインストールする。

IPython NotebookからRを利用するためには、Rmagicという機能拡張を使えばいいとのことなのだけど、これがrpy2というライブラリを使うため、これをインストールしなくてはならない。
折角なので、今回行ったインストール手順をまとめておく。

■インストール

▼前提

Windows7で、pythonとRはインストールされているものとする。

▼RのPATHを設定しておく。

これをしないと・・・
  • インストールの時に「Error: Tried to guess R's HOME but no R command in the PATH.」と、Rが見つからないよ!と怒られます。
  • pythonでモジュールをimportする際に、「RuntimeError: R_HOME not defined.」とか、「RuntimeError: R_USER not defined.」とか怒られます。
スタート>「コンピューター」を右クリック>プロパティ>左パネルの「システムの詳細設定」>環境変数 を開きます。その画面で次の3つを設定。

  • ユーザー環境変数のPATHを編集し、R.exeのあるbinフォルダをセミコロンで区切って末尾に追記します。つまり「;C:\Program Files\R\R-2.15.3\bin」(←僕の環境の場合)を末尾に追加する。
  • システム環境変数で、以下のPATHを追加。
    • 変数名「R_HOME」、変数値「C:\Program Files\R\R-2.15.3」(←僕の環境の場合)
    • 変数名「R_USER」、変数値「xxxx」(xxxxはWindowsのログイン名)

▼pipでインストール

上記で環境変数を指定した後、コマンドプロンプト(*1)を開き
$pip install rpy2
と打つ。簡単!


▼pipでエラーが出る場合・・・

環境によっては、pipでインストールしようとしたら、
"C:\PROGRA~1\R\R-215~1.3\bin\R" CMD config --ldflags
Invalid substring


みたいなエラーが出る場合がある(*2)。
この場合は、このページの環境に合わせたバイナリ(僕の環境の場合はrpy2 2.3.9.win32 py2.7.exe)をダウンロードし、あとはダブルクリックでインストールすればよい模様(参考)。

▼確認

コマンドプロンプトなりでpythonを起動して
import rpy2.robjects as robjects
を打って、エラーとかでないと、とりあえずインストール成功!

■Rmagic

rpy2をインストールしたら、IPython NotebookでRmagicを使ってRのコマンドが使えるようになる。
使い方はココが分かりやすい。


(*1)Windows標準のコマンドプロントでもいいし、scipyスタックのanacondaを使ってる場合、「anaconda command prompt」でもいい。ただしどちらの場合も、環境変数の更新を行った場合には、プロンプトを開きなおさないと環境変数の設定変更が反映されないことに注意!(これでだいぶハマッた)

(*2) 僕の職場のPCへのインストールはこれでハマる (+_+)。

2014年2月2日日曜日

科学計算用のscipyスタック(anaconda)

科学計算用(というより統計や機械学習系)のpython環境をwindowsにつくろうとして、いろいろ調べていると、scipyスタック(*1) として「anaconda」というものが存在することを知った

このanaconda、かなり便利でこれ1つインストールするだけで、主なところで、
  • python 2.7.5
  • theano 0.5.0 L
  • numpy 1.7.1
  • scipy 0.13.0
  • pip 1.4.1
  • matplotlib 1.3.1
  • pandas 0.12.0
とかが一緒にインストールされる。(全パッケージ情報はここ参照)

■インストール方法(Windows)

インストール方法は全くもって簡単で、ここからwindows用インストーラーをダウンロード。ダウンロードしたインストーラーをダブルクリックで起動するだけ。

インストール中は、基本的に画面に従ってデフォルト設定で「次へ」を押していけばいいが、一点だけ注意が必要で、英語で「自分のみにインストールするか?(推奨)、それともシステム全体にインストールするか?」と聞かれる箇所がある。この時デフォルトの設定では「システム全体にインストール」側にチェックが入っているので、推奨の「自分のみにインストール」をしたければ、チェックを付替えてから「次へ」でインストールを進める。

■動作確認

インストールが終わったら、「スタートボタン>全てのプログラム」に「Anaconda」の項目がある。
例えば、「Annaconda Command Prompt」を選択し、コンソールから
$python -V
と入力してバージョン情報が表示されれば、ひとまずインストールは成功。

また、anaconda本体は、「自分のみにインストール」を選択してインストールした場合、
「C:\Users\[XXX]\Anaconda」
以下にインストールされている。(ここで[XXX]はあなたのユーザーネーム)

これだけ簡単にインストールできるとなると、pythonでのデータ分析とかの敷居がどんどん低くなっていくなーと感じる。

■パッケージをアップデート

Anacondaで使われているパッケージは若干古いバージョンのものも含まれるので、使うパッケージは最新版にアップデートしておいた方がよい。Anacondaは独自のパッケージ管理ツール「conda」で管理されるので、例えばpandasを最新版にアップデートしたい場合は、
「Annaconda Command Prompt」を起動し、コンソールから
$conda update pandas
を実行すればOK。依存するパッケージも自動でアップデートしてくれる。
また、condaで管理していないパッケージについては、従来どおりpipでアップデートも可能。