2016年6月19日日曜日

Rと集団学習(ランダムフォレスト)

 1.集団学習とは

 集団学習(ensemble learning)は、決して精度が高くない複数の結果を統合・組み合わせることで精度を向上させる機械学習方法である。複数の結果の統合・組み合わせの方法としては、分類問題では多数決、数値の予測(回帰)問題では平均が多く用いられている。
 集団学習では、異なる重み、あるいは異なるサンプルから単純なモデルを複数作成し、これらを何らかの方法で組み合わせることで、精度と汎化力を両立するモデルを構築する。
 本稿では、集団学習方法による、回帰・分類のアルゴリズムバギング(bagging)、ブースティング(boosting)、ランダムフォレスト(random forest)の基本概念およびこれらのRのパッケージと関数を紹介する。
 機械学習の問題では、学習によって回帰・分類を行うシステムを学習機械と呼ぶ。文献によっては学習機械を仮説(hypothesis)、分類器・識別器(classifier)、学習器(learner)とも呼ぶ。分類器は分類問題だけではなく、回帰分析、生存分析にも対応する学習機械の別名である。

2.バギング

 バギング(bagging)のbaggingは、bootstrap aggregatingの頭の文字列を組み合わせた造語である。バギングは1996年Breimanによって提案された[1]。

2.1 バギングのアルゴリズム

 バギングは、与えられたデータセットから、ブートストラップ(bootstrap)と呼ばれているリサンプリング法による複数の学習データセットを作成し、そのデータを用いて作成した回帰・分類結果を統合・組み合わせる方法で精度を向上させる。ブートストラップサンプルはそれぞれ独立で、学習は並列に行うことができる。ブートストラップサンプルは、与えられたデータの経験分布とその推定量に基づいたリサンプリングにより得られたサンプルである。
 バギングの大まかなアルゴリズムを次に示す。いま、 個の個体により構成された回帰・判別を目的とした教師付きデータがあるとする。
 (1) 教師付きデータから復元抽出方法でm回抽出を行い、新たな訓練用サブデータセットを作成し、回帰・判別のモデルhを構築する。
 (2) ステップ(1)をB回繰り返し、回帰・判別のモデルをB個{hi;i=1,2,…,B}構築する。
 (3) 回帰の問題では、B個の平均値を結果とする。B個の平均値の式
   判別の問題では、多数決をとる。H(x)=argmax|{ihi=y}|

2.2 パッケージと関数

 パッケージipredにバギング関数が実装されている。パッケージipredはCRANミラーサイトからダウンロードできる。パッケージipredの中のバギングの関数はbaggingである。バギング関数baggingは、回帰分析、分類分析、生存分析を前提とし、Breimanの方法に従っている[1]。
bagging(formula,data,nbagg=25…)
 メインの引数の指定は、基本的には関数rpartと同じである。引数formulaには目的変数と説明変数を指定し、dataには用いるデータを指定する。引数nbaggでは、繰り返しの回数 を指定する。デフォルトは25になっている。関数rpartのパラーメタの調整は引数controlとrpart.controlを用いデフォルト値を替えることができる。
 関数printは、指定したモデルやnbaggなどを返す。関数summaryはbaggingの結果の要約を返すが、繰り返した毎回の結果を返すので、夥しい結果が返される。出力結果は引数nbaggに依存する。
 構築したモデルによるテストデータへの適応は、関数predictを用いる。

2.3 データと解析

 パッケージkernlabの中に用意されているデータspamを用いて、関数baggingの使用例を示す。パッケージkernlab はCRANミラーサイトからダウンロードできる。
> library(kernlab);data(spam)
 まず、学習用データとテスト用のデータセットを作成する。ここでは、データspamから2500個体をランダムに選び学習用とし、その残りをテスト用とする。
> set.seed(50)
> tr.num<-sample br="">> spam.train<-spam br="" tr.num="">> spam.test<-spam p="" tr.num="">
 次に、作成した学習データを用いて学習を行い、その結果を用いてテストデータを用いて予測・判別を行う。
>spam.bag<-bagging data="spam.train," nbagg="40) <br" type="">>spam.bagp<-predict p="" spam.bag="" spam.test="" type="class">
 テストデータにおける判別・誤判別のクロス表を次に示す。
>(spam.bagt<-table p="" spam.bagp="" spam.test="">
Spam.bagp
nonspamspam
nonspam123052
spam85734
 正しく判別できた比率(正解率)は次のコマンドで求めることができる。
> sum(diag(spam.bagt))/sum(spam.bagt)
[1] 0.934793

3.ブースティング

 ブースティング(boosting)は、与えた教師付きデータを用いて学習を行い、その学習結果を踏まえて逐次に重みの調整を繰り返すことで複数の学習結果を求め、その結果を統合・組み合わせ、精度を向上させる。

3.1 ブースティングのアルゴリズム

 ブースティングの中で最も広く知られているのはAdaBoostというアルゴリズムである。AdaBoostは、FreundとSchapierによって1996年に提案された[3]。その後、幾つかの変種(Discrite AdaBoost、Gentle AdaBoost、Real AdaBoost、Logit AdaBoost、Modest AdaBoostなど)が提案されている。そのアルゴリズムの詳細を説明する余裕がないので、大まかな共通点のみを次に示す。
(1) 重みの初期値w1iを生成する。
(2) 学習を行い重みの更新を繰り返す(t=1,2,・・・,T)。
   (a) 重みwtiを用いて弱学習器弱学習器の式を構築する。
   (b) 誤り率αt=errtを計算する。
   (c) 結果の信頼度βtを計算する。
   (d) 重みを更新する。 w(t+1)i=g(wti)
(3) 重み付き多数決で結果を出力する。2値判別の場合は
 提案された幾つかの方法の大きい違いは、重みの初期値の与え方、信頼度の計算と重みの更新の方法である。

3.2 パッケージと関数

 ブースティングを行うパッケージはboost、adaがある。パッケージboostはCRANダウンロードミラーサイトからダウンロードでき、パッケージadaは次のサイトからダウンロードできる。
 http://www.stat.lsa.umich.edu/~culpm/math/ada/img.html
 ローカルディスクにダウンロードしたzip形式で圧縮されたパッケージは、RGuiのメニューの「パッケージ」⇒「ローカルにあるzipファイルからパッケージをインストール」をクリックし、zipファイルを読み込むことでインストールできる。
 パッケージadaは、ブースティングの専用パッケージである。ブースティングを行う関数はadaである。関数adaの書き式を次に示す。
ada(formula,iter=50,type = c("discrete","real","logit","gentle"),…)
 引数formulaは前節の関数baggingと同じである。引数iterは繰り返しの回数で、デフォルトは50になっている。引数typeでは、4種類のブースティングの方法からひとつを指定する。デフォルトではdiscreteが指定されている。

3.3 データと解析

 本節でもspamデータを用いることにする。 次に繰り返しの回数を20にした使用例を示す。
> library(ada)
> spam.ada<-ada data="spam.trai," iter="20)</p" type="">
 学習結果を用いて、テストデータを当てはめるのには、関数predictを用いる。関数predictの書き式は基本的にはその他の回帰・判別関数と同じである。引数typeには、"vector"(予測値)、"prob"(確率)、 "both"(予測値と確率)などが指定できる。
> spam.adap<-predict br="" spam.ada="" spam.test="" type="vector">> (spam.adat<-table p="" spam.adap="" spam.test="">
Spam.adap
nonspamspam
nonspam122161
spam55764
> sum(diag(spam.adat))/sum(spam.adat)
[1] 0.9447882
 パッケージadaには、作図関数plot、varplot、pairsがある。関数plotは学習の繰り返しの回数と誤り率との対応図を作成する。関数plotにテストデータを引数として用いるとテストデータにおける繰り返しの回数と誤り率の対応図を作成することができる。またkappa引数を真(TRUE)とした場合はk統計量(kappa statistic; 多数決における一致性の指標)と学習の回数との対応関係図が作成される。
> plot(spam.ada,kappa=TRUE, spam.test[,-58],spam.test[,58])
学習回数と誤り率およびk系統計量との関係
図1 学習回数と誤り率およびk系統計量との関係
 関数varplotは、回帰・分類における変数の重要度を計算して図示する。現時点では若干不安定であり、更なるバージョンアップを期待する。
 現時点のブースティング関数boost、adaは、いずれも分類は2つクラスに限定されている。k個のクラスに対応するためには若干のテクニックが必要である。関数adaを用いたkクラスに対応する例が[4]に紹介されている。

4.ランダム森

 ランダム森(RF; random forest)は、バギングの提案者Breimanにより今世紀に提案された新しいデータ解析の方法である[4]。

4.1 ランダム森のアルゴリズム

 RFのアルゴリズムを次に示す。
 (1) 与えられたデータセットから 組のブートストラップサンプルを作成する。
 (2) 各々のブートストラップサンプルデータを用いて未剪定の最大の決定・回帰木を作成する。ただし、分岐のノードはランダムサンプリングされた変数の中の最善のものを用いる。
 (3) 全ての結果を統合・組み合わせ(回帰の問題では平均、分類の問題では多数決)、新しい予測・分類器を構築する。
 バギングとRFの大きい違いは、バギングは全ての変数用いるが、RFでは変数をランダムサンプリングしたサブセットを用いることができるので、高次元データ解析に向いている。
 ランダムサンプリングする変数の数Mはユーザが自由に設定することができる。Breimanは、Mは変数の数の正の平方根をと取ることを勧めている。
 RFは、様々な工夫が施され、多くの長所を持っている。幾つかの長所は[5]で紹介している。

4.2 パッケージと関数

 ランダム森の専用パッケージrandomForestは、CRANダウンロードミラーサイトからダウンロードできる。ランダム森のメイン関数はrandomForestである。関数randomForestの書き式を次に示す。
randomForest(formula, data=NULL,…, subset, na.action=na.fail)
 関数randomForestの引数やそれに関連する関数は、関数bagging、adaより多い。表1に主な引数、表2には主な関連関数をまとめて示す。
表1 関数randomForestの主な引数
引数
formulaモデルの形式、y~x1+x3のように記する
x、y説明変数と目的変数、formulaの替わりに用いる
data、subset用いるデータ
na.action欠損値の表記型の指定
ntree木の数、デフォルト値は500である
mtry木の生成に用いる変数の数、デフォルトでは、分類の場合はルートn、回帰の場合はn/3nは変数の総数
importance変数の重要度
・・・・・・

表2 関数randomForestと関連する主な関数
関数機能
print、summary結果の出力と要約の出力
plot木の数と誤り率との対応図
predict予測と判別
importance重要度の計算
varImpPlot分類に置ける変数の重要度のグラフ作成
classCenter分類におけるクラスの中心を求める
MDSPlotMDSの散布図を作成する
treesize用いた木のサイズ
varUsedRFに用いられた変数の頻度
・・・・・・

4.3 データと解析

 前節で用意したデータspamの学習データspam.trainを用いたrandomForestの使用例を示す。
> library(randomForest)
> spam.rf<-randomforest br="" data="spam.train,na.action=" na.omit="" type="">> print(spam.rf)
span.rfの結果
 結果のオブジェクトに、記録されている項目は関数summaryで確認できる。
> summary(spam.rf)
spam.rfの要約
 それぞれの項目は$***で返すことができる。例えば、上記で行ったRFは回帰問題であるか、それとも分類問題であるかに関する情報は次のコマンドで返す。
> spam.rf$type
[1] "classification"
 木の数と誤判別率との対応関係は、$err.rateに記録される。関数plotを用いると$err.rateの折れ線グラフが作成される。次のコマンドの結果を図3に示す。
> plot(spam.rf)
木の数と誤判別率との対応関係
図3 木の数と誤判別率との対応関係
 図3には3つの折れ線があるが、凡例がないので、どの線が何を示しているかを読み取るためには、spam.rf$err.rateと対応付ける必要がある。
> spam.rf$err.rate
OBBnonspamspam
[1,]0.104444440.095238100.11864407
[2,]0.103678930.089601770.12521151
<後略>
 返された結果には、 OOB(out-of-bag)がある。OOBは交差確認・検証法(cross-validation)における誤り率に対応するモデル評価の結果である。ランダム森では、作成したモデルの評価は、交差確認法および専用のテストデータセットを用いていない。ランダム森では、ブートストラップサンプルの3分の1をテスト用として外してモデルを作成し、外した3分の1を用いてテストを行う。OOBはこのテスト結果における誤判別率である。
 ランダム森では、データセットにおける変数の重要度を計算する。変数の重要度は次のように返すことができる。関数varImpPlotを用いてグラフで示すことができる。
>spam.rf$importance
MeanDecreaseGini
make4.7557913
address6.4192663
all10.7081099
<中略>
capitalLong72.4899123
capitalTotal51.4710153
> varImpPlot(spam.rf)
変数の重要度(Gini係数)
図4 変数の重要度(Gini係数)
 構築したRFのモデルを用いて新しいデータについて回帰・分類を行うのには関数predictを次のように用いる。
> spam.rfp<-predict br="" spam.rf="" spam.test="">> (spam.rft<-table p="" spam.rfp="" spam.test="">
Spam.rfp
nonspamspam
nonspam124240
spam73746
> sum(diag(spam.rft))/sum(spam.rft)
[1] 0.946216
 同じデータセットについてサポートベクトルマシンを用いて判別分析を行うとその正解率は0.9277で、ランダム森の結果より若干低い。

5.その他

 本稿で紹介した集団学習のバギング、ブースティング、ランダム森が実用化された歴史は長くない。特にランダム森はその方法が提案されたのも約5年の年月しか経っていないので、その内容を扱っている和書は見当たらない。ランダム森の回帰・分類の精度は高く、その適応性もよいので、幅広い応用が期待できる。バギングとブースティングに関しては[5]が詳しい。

Webサービスの作り方を実例のRailsで【ログイン認証・devise】

会員ユーザーの登路機能を実装していきたいと思います。
Railsのdeviseという機能を使って実装を行います。

1.deviseをGemfileに追加
1
gem 'devise'
$ bundle install

2.Railsプロジェクトにdevise関連ファイルを追加
$ rails generate devise:install
 
このコマンドにより、5つの初期設定に関するメッセージが表示。
・メール送信時のホスト名を指定(今回は抜粋)
・rootのURL指定(設定済み)
・flashメッセージの表示領域(後に設置します。)
・Rails3.2だけ必要なのでスキップ
・Viewのカスタマイズ
今回はViewをカスタマイズしたいと思いますので、コマンドを使います。
$ rails g devise:views
ご紹介するのはBootstrapによるflashメッセージの装飾です。
app/helpers/application_helper.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module ApplicationHelper
    def bootstrap_class_for(flash_type)
        case flash_type
            when :success
            "alert-success"
            when :error
            "alert-danger"
            when :alert
            "alert-warning"
            when :notice
            "alert-info"
            else
            flash_type.to_s
        end
    end
end
flashメッセージを全体にわたって表示
app/views/layouts/application.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>
<head>
  <title>BasicWebsite</title>
  <meta name="viewport" content="width=device-width,user-scalable=no,maximum-scale=1" />
  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>
<%= render 'shared/navbar' %>
<% flash.each do |key, value| %>
<div class="alert <%= bootstrap_class_for(key) %>"><%= value %></div>
<% end %>
<%= yield %>
</body>
</html>

3.deviseでユーザーモデルを作成
$ rails g devise user
$ rake db:migrate
nameとemailという2つの情報をもったユーザーモデルを作成しているので
nameという情報を追加するためのマイグレーションを作成
$ rails generate migration add_name_to_users name:string
$ rake db:migrate

4.入力値の値を設定するユーザー検証を行う
app/models/user.rb
1
validates :name,  presence: true, length: { maximum: 50 }
を挿入する。

5.ユーザーの情報を表示するページを作成
$ rails generate controller Users show
config/routes.rb
1
2
3
devise_for  :users
resources :users, only: [:show]
root 'pages#home'
app/controllers/users_controller.rb
1
2
3
4
5
6
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
  end
end
ユーザー情報を表示するview
app/views/users/show.html.erb
1
2
3
4
5
6
7
8
9
10
<% provide(:title, @user.name) %>
<div class="row">
  <aside class="col-md-4">
    <section>
      <h1>
        <%= @user.name %>
      </h1>
    </section>
  </aside>
</div>
ここまでで、実際にページ作成が行われているか確認
$ rake db:migrate RAILS_ENV=production
$ bundle exec rake assets:precompile RAILS_ENV=production
$ sudo service httpd restart
http://*****/users/sign_up
(*****)はお使いの独自ドメイン。
これで完了です。