2015年2月13日金曜日

R言語 ランダムフォレスト






> require("randomForest")
Loading required package: randomForest
randomForest 4.6-7
Type rfNews() to see new features/changes/bug fixes.

> tuneRF(d[,-8],d[,8],doBest=T)
mtry = 2  OOB error = 6.43% 
Searching left ...
mtry = 1  OOB error = 9.23% 
-0.4352332 0.05 
Searching right ...
mtry = 4  OOB error = 6.6% 
-0.02590674 0.05 

Call:
 randomForest(x = x, y = y, mtry = res[which.min(res[, 2]), 1]) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 6.4%
Confusion matrix:
      No  Yes class.error
No  1399  101  0.06733333
Yes   91 1409  0.06066667
# まずはチューニングする:mtry=2が最適らしい



> d.rf<- span="">randomForest(cv~.,d,mtry=2)
# mtry=2を引数にしてrandomForest()関数で分類する

> print(d.rf)

Call:
 randomForest(formula = cv ~ ., data = d, mtry = 2) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 6.37%
Confusion matrix:
      No  Yes class.error
No  1403   97  0.06466667
Yes   94 1406  0.06266667
# OOB誤差が6.37%とまずまず

> importance(d.rf)
   MeanDecreaseGini
a1        20.320854
a2        11.490523
a3         2.380128
a4       203.135651
a5        75.415005
a6       783.553501
a7         2.679649
# 決定木同様に変数重要度が出せる。これもまた重要

> table(d$cv,predict(d.rf,d[,-8]))
     
        No  Yes
  No  1409   91
  Yes   83 1417

# 分類正答率は94.2%とまずまず




> require("randomForest")
> train_dat<-read .csv="" header="TRUE)</span" kaggle="" ocuments="" train.csv="">
> str(train_dat)

> train_dat$holiday <- as.="" font="">factor(train_dat$holiday)

> train_dat$workingday <- as.factor="" span="" train_dat="" workingday="">
> train_dat$weather <- as.ordered="" span="" train_dat="">weather)
> train_dat$season <- as.ordered="" span="" train_dat="">season)

train_dat$datetime = as.POSIXct(train_dat$datetime)


> tuneRF(train_dat[,c(-10,-11,-12)],train_dat[,12],doBest=T)

mtry = 3  OOB error = 16201.56 
Searching left ...
mtry = 2  OOB error = 17056 
-0.05273804 0.05 
Searching right ...
mtry = 6  OOB error = 15210.13 
0.06119329 0.05 
mtry = 9  OOB error = 14763.69 
0.02935166 0.05 

Call:
 randomForest(x = x, y = y, mtry = res[which.min(res[, 2]), 1]) 
               Type of random forest: regression
                     Number of trees: 500
No. of variables tried at each split: 9

          Mean of squared residuals: 14300.74
                    % Var explained: 56.41
> 

# まずはチューニングする。mtry=9が最適らしい

> train_dat.rf<-randomforest class="synSpecial" font="">(count~.,train_dat[,c(-10,-11)],mtry=9)
> print(train_dat.rf)

Call:
 randomForest(formula = count ~ ., data = train_dat[, c(-10, -11)],      mtry = 9) 
               Type of random forest: regression
                     Number of trees: 500
No. of variables tried at each split: 9

          Mean of squared residuals: 14306.06
                    % Var explained: 56.4


# テストデータを読み込む

train_dat<-read .csv="" font-family:="" gothic="" header="TRUE)</span><br style=" iragino="" kaku="" normal="" pro="" white-space:="">> str(train_dat)

> test_dat<-read .csv="" header="TRUE)</span" kaggle="" ocuments="" test.csv="">
> str(test_dat)

> test_dat$holiday <- as.factor="" holiday="" span="" test_dat="">

> test_dat$workingday <- as.factor="" span="" test_dat="" workingday="">
> test_dat$weather <- as.ordered="" span="">test_dat$weather)
test_dat $season <- as.ordered="" span="">test_dat$season)






> tuneRF(train_dat[,c(-10,-11,-12)],train_dat[,12],doBest=T)


n <- nrow(iris)
s <- sample(n,n*0.5)
iris.train <- iris[s,]
iris.test <- iris[-s,]
# random forest
forest <- randomForest(Species~., data=iris.train, ntree=500)
pred.forest <- predict(forest, newdata=iris.test, type="class")
table(pred.forest, iris.test[,5])
 
# decision tree
tree <- rpart(Species~.,data=iris.train)
pred.rpart<-predict(tree, iris.test, type="class")
table(pred.rpart, iris.test[,5])
 
# importance
getTree(forest, 1, labelVar=TRUE)
varImpPlot(forest)
 
# report
split.screen(c(2,1))
split.screen(c(1,3), screen = 2)
screen(3); partialPlot(forest, iris, Petal.Length, "setosa")
screen(4); partialPlot(forest, iris, Petal.Length, "versicolor")
screen(5); partialPlot(forest, iris, Petal.Length, "virginica")
split.screen(c(2,1), screen = 1)
screen(1); plot(forest) 
close.screen(all=T)

2015年2月8日日曜日

位置情報の取得(Geolocation API)とWebSocketによる送信

はじめに


ブラウザにて位置情報を取得し、その情報を別のクライアントへPush通知する処理の
サンプルをRuby on Railsで作成してみました。
なんらかの業務で、ある社員が持つ端末より位置情報を取得し、本社側でリアルタイムで
その情報を把握する、というような用途を想定しています。

アプリの機能としては
  • JavaScriptにて、Geolocation APIを使用して位置情報を取得する 
  • websocket-railsを使い、WebSocketでサーバ側へ送信する 
  • サーバ側は、受け取った位置情報をwebsocket-railsを使い、別画面へPush通知する 
の3つに分けることができます。

これらの機能を実装するポイントについて書いていきたいと思います。
今回は、1.と2.についてです。



Geolocation APIを使用して位置情報を取得する


位置情報の取得は上記に書いた通り、JavaScriptを使いクライアント端末のGPSを起動して行います。
Geolocation.watchPosition()
位置情報を連続して取得する
を参考にしました。

まずは、位置情報を表示する画面のソースです。
次回以降に使用する、位置情報を別端末にて表示する画面と同じソースを
使用するため、テンプレートを使用しています。

app/views/welcome/_location.html.erb

1
2
3
4
5
6
7
8
9
10
<div>
  <div class="div-geolocation-header">緯度</div>
  <div id="latitude">-</div>
  <div class="div-geolocation-header">経度</div>
  <div id="longitude">-</div>
  <div class="div-geolocation-header">移動方向</div>
  <div id="heading">-</div>
  <div class="div-geolocation-header">移動速度</div>
  <div id="speed">-</div>
</div>
見ての通り、緯度・経度などの位置情報を表示するdivタグを定義しています。
このdivタグに位置情報を表示するJavaScriptは以下のようになります。
app/assets/javascripts/welcome_index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
document.addEventListener("DOMContentLoaded", function() {
    var options = {
        enableHighAccuracy: true,
        timeout: 60000,
        maximumAge: 0
    };
    // 位置情報取得
    window.navigator.geolocation.watchPosition(success, error, options);
}, false);
function success(pos) {
    // 緯度
    document.querySelector('#latitude').textContent = pos.coords.latitude;
    // 経度
    document.querySelector('#longitude').textContent = pos.coords.longitude;
    // 移動方向
    document.querySelector('#heading').textContent = pos.coords.heading;
    // 移動速度
    document.querySelector('#speed').textContent = pos.coords.speed;
    sendMessage(pos);
}
var dispatcher = new WebSocketRails(location.host + '/websocket', true);
function sendMessage(pos) {
    var location = {
        latitude: pos.coords.latitude,
        longitude: pos.coords.longitude,
        heading: pos.coords.heading,
        speed: pos.coords.heading
    };
    dispatcher.trigger('location_message', location);
}
function error(err) {
    console.warn('ERROR(' + err.code + '): ' + err.message);
}
9行目でGeolocationAPIのwatchPosition()メソッドを使用して、位置情報を取得しています。
位置情報を正常に取得した場合、13行目から始まるsuccess()メソッドを呼び出し
取得した位置情報を画面にセットしています。

この辺りの流れは、上記の参考記事とほぼ同じです。

websocket-railsを使い、サーバ側へ送信する

サーバへの送信、および別のクライアントへのPush送信は、websocket-railsを使用します。
今回はサーバ側へ送信する箇所について解説します。まずは、上記のJavaScriptのソースからです。
位置情報を表示した後、サーバに通知するために23行目でsendMessage()メソッドを呼び出します。
sendMessage()メソッドを実行する前に、26行目でWebSocketRailsのインスタンスを作成してます。
インスタンス作成時の引数として、サーバのURL + 「websocket」というURLを指定するのがポイントです。
sendMessage()メソッド内では、WebSocketRailsのインスタンスのtrigger()メソッドを呼び出し
メッセージをサーバへ送信しています。

第一引数には送信先のコントローラを表すイベントを、第二引数には送信するメッセージ(今回は位置情報のHash) を指定します。
第一引数に指定したイベントについては、後述します。

websocket-railsの準備

恐らく実際に作業する時には、最初に行うことになるかと思いますが、上記で呼び出した
websocket-railsを使うために以下の準備が必要です。

websocket-railsのインストール

Gemfileに以下を記述します。
1
gem 'websocket-rails'
記述後、以下のコマンドを実行します。
1
2
$ bundle install
$ rails g websocket_rails:install

websocket-railsのイベントの定義

websocket-railsのインストール時に作成された config/events.rb に、イベントを定義します。

config/events.rb

1
2
3
WebsocketRails::EventMap.describe do
  subscribe :location_message, :to => LocationController, :with_method => :show
end
「location_message」というイベントを定義し、呼び出し先として「LocationController」の「show」アクションを指定しています。
この「location_message」は、上記のJavaScript内のdispatcher.trigger()で指定したイベントです。

websocket-railsでの受信

websocket-railsのイベントで呼び出し先として定義されたコントローラにてメッセージを受信します。

app/controllers/location_controller.rb

1
2
3
4
5
6
class LocationController < WebsocketRails::BaseController
  def show
    puts message
    WebsocketRails[:location_channel].trigger "location", message
  end
end
websocket-railsでは「message」という変数に、クライアントから送られるメッセージが格納されています。
今回は受け取ったメッセージが分かるよう、putsで出力しています。
その下の「WebSocketRails・・・」については、次回以降に解説します。

まとめ

今回はGeolocation APIを使用して位置情報を取得し、websocket-railsを使い、WebSocketでサーバ側へ送信するところまでを書きましたが、 次回は「サーバ側は、受け取った位置情報をwebsocket-railsを使い、別画面へPush通知する」処理について書きたいと思います。