Categories
アナリティクス データサイエンス

スプレッドシートとGoogle Apps ScriptでBigQueryを監視

分析データも監視が必要

データ分析用のテーブルやGoogle Analytics、FirebaseなどからBigQueryにインポートされたテーブルが更新されていなくて、分析結果が狂ってしまったって事はありませんか?特にGoogle Analytics360のデータは遅れてデータが取り込まれることがよくあるうえに、後から差分データがしれっとインポートされることもあります。

そのため、日次バッチなどで集計をかけていると、集計よりも後にデータがインポートされた場合に「データの件数や内容と集計結果が合わない」ということが発生します。

そういった経緯から「分析用データも監視が必要」とは思っていたのですが、勘定系や基幹系のシステムのようにミッションクリティカルではないので、監視ツールにお金をなかなかかけられません。それにそこに費用をかけるくらいなら、BigQuery MLなどに充てたいですよね?

GoogleスプレッドシートとGoogle Apps Scriptで監視ツールを作成

「PythonでローカルからBigQueryにアクセスできるから、Pythonプログラムを叩けばいいのでは?」と思ったのですが、それだと毎回自分で叩かないといけないし、PCも起動しっぱなしにしておかないといけない、といった課題があるので、他の方法を探してみました。

そして、Google Apps Scriptを使えばBigQueryの結果を取得できて、しかもトリガーで毎日定期的に実行ができる、さらに自分宛てにメールも配信できる、というまさに私の希望をすべて満たしてくれることが判明!本当にGoogleサマサマです。

BigQuery監視ツール作成手順

それではさっそく作成に入ります。監視ツールの基本的な流れは以下になります。

  1. Google Apps Scriptのトリガーで関数を実行
  2. BigQueryでクエリを実行
  3. クエリの結果の件数が1レコードでもあれば結果をメールで送付
  4. Googleスプレッドシートにログを出力

クエリの作成

最初にクエリを作成しましょう。まずは実行確認のためにBigQueryのWebコンソール上でクエリを作成します。このとき、結果が1件でも返ってきたらエラーとするようなクエリにしてください。

たとえば、Google Analytics 360から前日のデータが格納されていることを確認するには下記のように、「最新の日付が前日ではない場合に結果を返す」というクエリを作成します。

-- BigQuery内にある最新のテーブルの日付とそのテーブルのレコード数を返す
SELECT MAX(_TABLE_SUFFIX) AS tid , COUNT(1) AS cnt_row FROM `bigquery-public-data.google_analytics_sample.ga_sessions_20*`
-- 最新の日付が前日ではないという条件
HAVING MAX(_TABLE_SUFFIX) != FORMAT_DATE('%y%m%d',DATE_SUB(DATE(CURRENT_TIMESTAMP, 'Asia/Tokyo'),INTERVAL 1 DAY))
;

上記で使用しているテーブルは2017年までのデータしかないため、tid: 170801 、cnt_row: 903653 という結果を返します。とりあえず、このクエリは後ほど使いますので、メモ帳などに保存しておいてください。

Googleスプレッドシートの作成

クエリ格納用シートの作成

次にGoogleスプレッドシートに先ほど作成したクエリや各種設定情報を書き込んでいきます。このシート名を「Queries」としてください。今回は入力するのはメール送信時に使用する件名とBigQueryで実行するクエリの2つだけにします。

  • A列:タイトル(メール送信時の件名)
  • B列:BigQueryで実行するクエリ

実行したいクエリが2つ以上ある場合は、3行目以降に追記してください。空行を入れなければ続けて実行するようなスクリプトにしています(スクリプトは後述)。

クエリによってメール送信先を変えたい場合は、C列にメールアドレスの列を追加し、C列で取得したメールアドレスを後ほど説明するGoogle Apps Scriptの配信関数(sendEmail)の引数に入れれば可能です。

ログ用シートの作成

次に、「log」というシート名のシートを作成します。こちらは空で構いません。

Google Apps ScriptでBigQueryにクエリを投げる

シートから情報を取得

今度は肝心の機能の部分をGoogle Apps Scriptで実装します。Google Apps ScriptはGoogleスプレッドシートの「ツール」→「スクリプトエディタ」をクリックすると開きます。Google Apps Scriptのサイトからアクセスするとスプレッドシートとの連携が面倒なので、この方法がおすすめです。

Google Apps Scriptのウィンドウが表示されたら「コード.gs」というタブのエディタ部分に下記のコードを書いて保存します。ファイル名は何でも構いませんが、ここでは「BigQuery監視」としておきます。

function main() {
  // スプレットシートを取得
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  // ’Queries’シートを取得
  var queries_sheet = spreadsheet.getSheetByName("Queries");
  // 各行のデータを取得(1行目はヘッダーなので飛ばす)
  for (var i = 2; i <= queries_sheet.getLastRow(); i++) {
    // A列の値(件名)を取得
    var query_title = queries_sheet.getRange(i, 1).getValue();
    // B列(クエリ)を取得
    var query =  queries_sheet.getRange(i, 2).getValue();
     // 件名やクエリがない場合はその時点で終了(空行の以降の行は実行しない)
    if(query_title && query){
      // SQLを実行する関数(後述)
      runQuery( query_title, query);
    }else{
      break;
    }
  }
}

SQLを実行

次に、実際にSQLを実行する関数(runQuery)を作成します。この関数はいくつかの処理が混ざっていますが、下記のような流れになっています。

  1. SQLを実行
  2. 結果が返ってくるまでループで待つ
  3. 結果を文字列に変換
  4. 結果がある場合はエラーなのでメールで送付
  5. ログ出力
function runQuery(title, query) {
  var projectId = 'xxxxx'; // プロジェクトID
  var request = {
    query: query
  };
  // standardSQLで実行するために必要
  request.useLegacySql = false;
  var queryResults = BigQuery.Jobs.query(request, projectId);
  var jobId = queryResults.jobReference.jobId;
  // ジョブが完了したか定期的にチェック
  var sleepTimeMs = 500;
  while (!queryResults.jobComplete) {
    Utilities.sleep(sleepTimeMs);
    sleepTimeMs *= 2;
    queryResults = BigQuery.Jobs.getQueryResults(projectId, jobId);
  }
  // SQLの結果を取得.
  var rows = queryResults.rows;
  var totalRows = queryResults.totalRows;
  while (queryResults.pageToken) {
    queryResults = BigQuery.Jobs.getQueryResults(projectId, jobId, {
      pageToken: queryResults.pageToken
    });
    rows = rows.concat(queryResults.rows);
  }
  var message = '';
  // クエリの結果が1行以上ある場合、結果内容を文字列にしてメール送信&ログ出力
  if (rows) {
    // ヘッダーを追加
    var headers = queryResults.schema.fields.map(function(field) {
      return field.name;
    });
     message +=headers;
    // 結果を連結して文字列を作成.
    var data = new Array(rows.length);
    for (var i = 0; i < rows.length; i++) {
      var cols = rows[i].f;
      data[i] = new Array(cols.length);
      for (var j = 0; j < cols.length; j++) {
        data[i][j] = cols[j].v;
      }
      message += data[i].join(',') + '\n';
    }
    // 結果を指定したアドレスに送る
    sendEmail(title, message);
    // 結果をログに出力
    writeLog(title + ' ' + rows.length + ' rows returned.');
  } else {
    // 結果が0件であることをログに出力
    writeLog(title + ' No rows returned.');
  }
}

projectIdはご自身のプロジェクトIDを入れます。プロジェクトIDについてはBigQueryのUIコンソールのプロジェクト名をクリックすると「プロジェクトの選択」ウィンドウが表示されますので、そこの「ID」列がプロジェクトIDになります。

メールを配信

メール配信部分は非常にかんたんで、MailApp.sendEmail 関数を使うだけです。ポイントは以下になります。

  • 送り主は今このスプレッドシートとスクリプトを作成しているGoogleアカウント(G Suiteでログインしている場合はそのメールアドレス)
  • 件名はフィルタリングしやすいように接頭語+各クエリのタイトル
  • 送り先は任意ですが今回は固定
  • 本文にこのスプレッドシートのURLを含める
function sendEmail(subject,message){
  var to = '[email protected]'
if(MailApp.getRemainingDailyQuota() == 0) {return;}
// 件名
  var subj = '[BigQuery]Alert : ' + subject;
  // 今回のスプレッドシートのURL
  var url = '\n Script: https://docs.google.com/spreadsheets/d/xxxxxxxxxxx'
  MailApp.sendEmail({
    // 送り先
    'to':to,
    // 件名
    'subject':subj,
    // 本文
    'htmlBody':message + url
  })
}

とくに、スプレッドシートのURLを入れておくと、Gmailで受け取ったときにそのスプレッドシートをすぐに開くことができるのでおすすめです。

なぜこれをしておくかというと、今回の場合、エラーが発生したとき(SQLの実行結果が1件以上)にのみメールが送られるので、突然メールが届いても「このメールなんだっけ?」と忘れがちです。それを防ぐためにURLを入れています。

ログを出力

エラーの有無に関係なく実行結果をログに残しておいたほうが良いので、その関数も作成します。ログは足所にスプレッドシートで作成した「log」シートに追記されます。ログはA列に実行日時、B列に結果が出力されます。

function writeLog(str){
  var spreadsheetFile =  SpreadsheetApp.getActiveSpreadsheet();
  var logSheet = spreadsheetFile.getSheetByName("log");
  var lastRow = logSheet.getLastRow();
  var lastCol = logSheet.getLastColumn();
  var startRow = lastRow+1;
  var input = [[formatDateTimeAsString(new Date()),str]];
  logSheet.getRange(startRow,1,1,2).setValues(input);
}
// 日次を返す
function formatDateTimeAsString(d) {
  var dateString = Utilities.formatDate(d, 'GMT+9:00', 'yyyy/MM/dd HH:mm:ss');
  return dateString;
}

トリガーの設定

最後はトリガーの設定になります。トリガーはGoogle Apps Scriptの編集画面から「編集」→「現在のプロジェクトのトリガー」を選択します。

トリガーの画面が表示されますので、右下の「トリガーを追加」ボタンをクリックします。毎日チェックしたい場合は下記のように「日付ベースのタイマー」を選んでください。その他、時間や分ベースでの指定も可能です。

  • 実行する関数を選択: main
  • 実行するデプロイを選択: Head
  • イベントのソースを選択: 時間主導型
  • 時間ベースのトリガータイプを選択: 日付ベースのタイマー
  • 時刻を選択:任意

トリガーは時間だけでなく、スプレッドシートの起動や変更時なども選択できます。

以上で設定は完了です。

具体的な用途

データの有無の監視以外で私は以下のような用途でもこのツールを使っています。

  • 売上高や件数の前日比が80%を下回った場合
  • 予期しない異常な値が入った場合(例:平均売上単価が5000円なのに5万円以上の購入があった場合)
  • 日時バッチのミスやデータの入れ間違いなどにより重複データが発生した場合

このツールはSQLの結果を判断材料にしているため、好きなように条件を変えられるのがウリです。

応用編

上記以外に関数等を変えてどのようなことができるか?考えてみました。

BigQueryにアクセスできないけど、データを共有したい場合

代理店などとデータを共有したいときなどに有効かと思います。

データをスプレッドシートに書き込むことでレポートの作成なども可能

メールで数値を共有するだけでなく、スプレッドシートにデータを出力してそれを共有するのもありかと思います。ただ、Googleデータポータルを使う場合はBigQueryから直接参照できるのであまり意味はないかもしれません。

「こんな使い方してます」といったおすすめがありましたらコメント欄に書いてもらえると嬉しいです。

[amazon asin=”4798061271″ kw=”Google Apps Script” rakuten id=” ” yahoo=” “] [amazon asin=”B07BNB1Z9L” kw=” Google Apps Script完全入門” rakuten id=” ” yahoo=” “]

Google Apps Scriptは情報も少ないし、Googleのドキュメントも例が少ないので、事例や使い方が書かれている本を購入しておくと便利です。

]]>
Categories
データサイエンス

jupyter notebookでUntitledファイルを自動生成しない

以前の記事で、jupyter notebookで自作したクラスを自動でimportする方法を紹介しました。この機能はjupyter notebook上でスクリプトを保存するたびにpythonファイルやhtmlファイルを自動生成&更新してくれるのでとても便利なのですが、新規作成時にも’Untitled.txt’や’Untitled.html’というファイルが生成されてしまいます。

最初は気づかなかったのですが、「テキストファイルなんて作った記憶はないのに。。。」と不思議に思ったのですがスルーしており、そのうちいろんなフォルダに生成されているので気味悪くなってきたので、対処しようと思いました。

初回時にファイルを生成しないようにスクリプトを修正

そこで、今回は前回作成したスクリプトを若干修正して、ファイル名がまだつけられていない状態(つまり’Untitled’)のときはファイルを生成しないように処理を追加します。

configファイル(jupyter_notebook_config.py)を下記のように修正します。ファイルは通常ホームディレクトの下に.jupyterフォルダがあり、その中に入っています。詳しくは過去の記事を参照してください。

#--------------------------
# Create html and py file automatically
#--------------------------
import os
from subprocess import check_call
def post_save(model, os_path, contents_manager):
    if model['type'] != 'notebook':
        return
    d, fname = os.path.split(os_path)
    if 'Untitled' in fname:     # ここに条件を追加
    	return    # ファイル名ににUntitledを含むときは終了
    else:  # ファイル名にUntitledを含まないときはファイルを生成
        check_call(['jupyter', 'nbconvert', '--to', 'script', fname], cwd=d)
	check_call(['jupyter', 'nbconvert', '--to', 'html', fname], cwd=d)
c.FileContentsManager.post_save_hook = post_save

これでファイルを保存後、jupyter notebookを再起動して、Pythonファイルを新規作成します。

すると、Untitled.ipynbファイルは作成されますが、それ以外のUntitled.txtやUntitled.htmlは生成されなくなりました。

そして、このファイルをTest1に名前を変更して保存します。

すると、きちんとTest1.pyやTest1.htmlが生成されています。

nbconvertでPDFやLaTexなども生成可能

今回は簡単な変更で、これだけだと少々面白くないので、もう少し追加を。

このスクリプトでファイルを生成しているのは下記で行われています。

check_call(['jupyter', 'nbconvert', '--to', 'script', fname], cwd=d)

このnbconvertという関数は下記の種類のファイルを生成することが可能です。毎回生成することはpy以外にあまり必要ないと思いますが、レポート作成の際などはPDFやLaTexあたりは役に立つと思いますので、この設定をしておくのも良いかもしれません。

  • HTML
  • LaTeX
  • PDF
  • Reveal.js HTML slideshow
  • Markdown
  • Ascii
  • reStructuredText
  • executable script
  • notebook

[amazon asin=”B075F3CHZ4″ kw=”PythonユーザのためのJupyter” rakuten id =” ” yahoo=” “]

]]>
Categories
データサイエンス

学習曲線(Learning Curve)で過学習、学習不足を検証

前回はvalidation_curveでパラメータの範囲を絞り込む方法を使ってGridSearchCVの実行時間削減に挑戦しました。各パラメータの最適値についてはGridSearchCVで求めることはできるようになりました。

その一方で、学習データ数によって学習不足だったり過学習を起こしていないか?という心配が出てきます。サンプル数が少なかったり、学習データのパラメータが多すぎると過学習を起こしてしまって、検証データにうまく適応できてない、ということはよくあるので、過学習のチェックは必須でしょう。

そこで使うのが学習曲線(learning_curve)です。learning_curveはサンプル数を変えながら学習データと検証データの正解率について、

  • 両者がどのくらいの正解率に着地するか?(=漸近線)
  • 両者の乖離はどれくらいか?

といった観点で比較・検証していきます。

Learning Curveのサンプル

それではさっそく、Learning Curveのコードを書いていきます。今までと同様にKaggleのTitanic課題のデータを使っていますので、学習データと検証データの作り方については過去の記事を参考にしてください。

今回のモデルはランダムフォレストのデフォルトを使ってみます。learning_curve関数の引数は以下になります。

  • estimator:検証したいモデル
  • X :入力データ
  • y : 出力データ
  • train_sizes : 試したいサンプル数([100, 200, 300, …, 1000])
  • cv : バリデーションデータセットを作成する際の分割方法(デフォルトは5-fold法)
from sklearn.model_selection import learning_curve
clf = RandomForestClassifier()
train_sizes, train_scores, test_scores = learning_curve(estimator = clf, X = X_train,train_sizes=train_sizes, y = y_train, cv=10, n_jobs=1)
train_mean = np.mean(train_scores, axis=1)
train_std  = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std  = np.std(test_scores, axis=1)

learning_curveは学習データの結果と検証データの結果の2つの結果をそれぞれ配列で返します。そこでこれらの結果を比較するためにグラフ化します。

import matplotlib.pyplot as plt
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
plt.figure()
plt.title("Learning Curve")
plt.xlabel("Training examples")
plt.ylabel("Score")
# Traing score と Test score をプロット
plt.plot(train_sizes, train_scores_mean, 'o-', color="r", label="Training score")
plt.plot(train_sizes, test_scores_mean, 'o-', color="g", label="Validation score")
# 標準偏差の範囲を色付け
plt.fill_between(train_sizes, train_scores_mean - train_scores_std, train_scores_mean + train_scores_std, color="r", alpha=0.2)
plt.fill_between(train_sizes, test_scores_mean - test_scores_std, test_scores_mean + test_scores_std, color="g", alpha=0.2)
# Y軸の範囲
plt.ylim(0.7, 1.0)
# 凡例の表示位置
plt.legend(loc="best")
plt.show()

これだけでは検証データの正解率が上がっていないのはわかりますが、だから何?と言った感じで、どうしたら良いのかがわかりませんね。

Learning Curveの使い方

Learning Curveの結果は大きく3つに分けられます。

成功パターン(両方とも高い正解率で収束)

学習データ、検証データともに高い正解率に収束し、サンプル数が増えても検証データが下がることがない場合は成功パターンと言えます。

学習不足パターン(両方とも低い正解率)

学習データと検証データが近づいているものの、正解率が低い場合は学習不足が考えられます。このような学習不足の場合はパラメータを増やすのが一般的です。

過学習(学習データだけ高い正解率)

学習データの正解率だけ高くて、検証データの正解率が低い場合、過学習を起こしている可能性が高いです。これが発生するのは主にサンプル数が少なくて、パラメータ数が多すぎる場合です。

こちらは検証データの正解率が0.83くらいで頭打ちになっていることがわかります。このことからサンプル数を増やしても正解率が上がらないことが予想されます。

この場合は、パラメータが多すぎる可能性が高いので、パラメータを減らしてみる事をオススメします。

一方、下記の場合は、サンプル数が1400の時点でも、それ以降正解率が上がり続ける可能性がありそうです。このような場合はサンプル数を増やして再度検証するのが良いでしょう。

Learning Curveでモデル作成後の過学習の検証に使う

Learning Curveは過学習の検証に使うのが多いようです。そのため、使う流れとしては、GridSearchCVなどでモデルを作成後で、このLearning Curveで過学習の可能性がある場合は、パラメータの変更が必要になってきます。

次回はパラメータ(特徴量)の選定方法について紹介したいと思います。

]]>
Categories
データサイエンス

validation_curveでGridSearchCVとRandomForestClassifierのパラメータチューニング

前回はGridSearchCVを使って、ランダムフォレスト(RandomForestClassifier)のパラメータの最適解を求めました。

「GridSearchCVを使えば、いつでも最適解を出せるから楽だよね

と思ってました。

ですが、甘かったです。前回のはわずか30分程度で終わりましたが、実は最初に適当に各パラメータの候補を最大10個くらい設定して(=配列の要素数を10個)にして行ったら、2時間以上かかってしまいました。

たった数百行の学習データでパラメータも数種類しかないのに2時間だったら、私が実務で使っているのは会員データだけで3000万レコード、購入履歴は数億レコードもあるので、GridSearchCVをそのまま使うのは非現実的です。(それを全部使うことは実際はありませんが。。。)

validation_curveでパラメータの範囲を絞る

そこで使うのがvalidation_curve です。validation_curveについての詳しい説明は省略しますが、訓練データとテストデータでの正解率を比較して、ハイパーパラメータの値が小さすぎて学習不足だったり、逆に値が大きすぎて過学習を起こしたりしていないか?を確認するものです。

それでは実際に前回と同様にKaggleのTitanic課題を使って、max_depthを1,3,5,7・・・29までとして検証してみましょう

from sklearn.model_selection import validation_curve
param_range = list(range(1,30,2))
train_scores, test_scores = validation_curve(estimator = RandomForestClassifier(), X = X_train, y = y_train, param_name="max_depth", param_range=param_range, cv=5, n_jobs=1)
train_mean = np.mean(train_scores, axis=1)
train_std  = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std  = np.std(test_scores, axis=1)
plt.plot(param_range,train_mean,label="Training score",color="black")
plt.plot(param_range,test_mean,label="Validation score",color="dimgrey")
plt.fill_between(param_range, train_mean + train_std, train_mean - train_std, alpha=0.1, color="orange")
plt.fill_between(param_range, test_mean + test_std, test_mean - test_std, alpha=0.1, color="darkblue")
plt.legend(loc="upper left")
plt.title("Validation Curve")
plt.xlabel("max_depth")
plt.ylabel("Accuracy Score")
plt.tight_layout()
plt.show();
  • Training Score:訓練データのグラフ
  • Validation Score:テストデータのグラフ( 標準偏差の範囲を色付けしています)
  • 横軸:max_depthの値(1、3、5・・・・29)
  • 縦軸(Accuracy Score):正解率

訓練データでは17あたりでほぼピークに達しているのがわかります。一方、テストデータをみると7あたりでピークいなっているのがわかりますので、GridSearchCVでは3~15くらいで検証すれば良いと推測できます。

つまり、前回のGridSearchCVではmax_depthを 3, 5, 10, 15, 20, 25, 30, 50, 100で行いましたが15や20・・・100が無駄で、その分、10以下の数値に割り当てたほうが良いということがわかります。

validation_curveを関数化

validation_curveは各パラメータについて使いますので、関数化します。今回はn_jobs=1、画像の凡例の位置を左上に固定していますが、どちらも任意です。

from sklearn.model_selection import validation_curve
import matplotlib.pyplot as plt
def func_validation_curve(model,X_train,y_train, p_name, p_range, cv ):
    train_scores, test_scores = validation_curve(estimator = model, X = X_train, y = y_train, param_name=p_name, param_range=p_range, cv=cv, n_jobs=1)
    train_mean = np.mean(train_scores, axis=1)
    train_std  = np.std(train_scores, axis=1)
    test_mean = np.mean(test_scores, axis=1)
    test_std  = np.std(test_scores, axis=1)
    plt.plot(param_range,train_mean,label="Training score",color="blue")
    plt.plot(param_range,test_mean,label="Validation score",color="red")
    plt.fill_between(param_range, train_mean + train_std, train_mean - train_std, alpha=0.1, color="cyan")
    plt.fill_between(param_range, test_mean + test_std, test_mean - test_std, alpha=0.1, color="magenta")
    plt.legend(loc="upper left")
    plt.title("Validation Curve - " + p_name)
    plt.xlabel(p_name)
    plt.ylabel("Accuracy")
    plt.tight_layout()
    plt.show();

他のパラメータでもvalidation_curveを実施

それでは他のパラメータについてもvalidation_curveを実施します。前回のGridSearchCVで算出した最適解を使って行います。

clf = RandomForestClassifier(bootstrap=True, ccp_alpha=0.0, class_weight=None, max_depth=7
                       criterion='gini', max_features=1.0,
                       max_leaf_nodes=None, max_samples=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=10,
                       min_weight_fraction_leaf=0.0, n_estimators=1000,
                       n_jobs=1, oob_score=False, random_state=1,
                       warm_start=False)

criterion

param_range = ["gini","entropy"]
func_validation_curve(clf, X_train, y_train, 'criterion', param_range, 5)

criterionはginiとentropyの2種類しかないので、GridSearchCVでは両方試して良いと思いますが、参考までにやってみました。

max_features (ノード分割する際に考慮する特徴量数)

param_range = [0.1,0.3,0.5,0.7,0.9,1.0]
func_validation_curve(clf, X_train, y_train, 'max_features', param_range, 5)

max_featuresは0.5以上が良さそうなことが伺えます。1.0になるとTraining Scoreが上がっているのに対してValidation Scoreが下がっているので、過学習を起こしているかもしれません。

min_samples_split (末端ノード内の最小サンプル数)

次にmin_samples_splitです。こちらも前回のGridSearchCVで使った範囲で行います。

param_range = [3, 5, 10, 15, 20, 25, 30, 50, 100]
func_validation_curve(clf, X_train, y_train, 'min_samples_split', param_range, 5)

っが、15以降はTraining ScoreもValidation Scoreも悪化しているので、2~20までで再度検証してみます。

これをみると2~20までほぼ横ばいなのがわかります。GridSearchCVではこの全範囲を対象にするとします。

n_estimators

前回、 n_estimatorsはやっても意味がない、ということを書きましたが、せっかくなのでn_estimatorsも検証してみましょう。

param_range = [10,20,30,50,100,250,500,750,1000]
func_validation_curve(clf, X_train, y_train, 'n_estimators', param_range, 5)

実際にやってみると100以降はほぼ横ばいです。ということで100まででもう1度検証します。

param_range = [1,5,10,20,25,50,75,100]
func_validation_curve(clf, X_train, y_train, 'n_estimators', param_range, 5)

これをみる限りでは25~150までわずかに上昇して、それ以降は下降しているように見えます。いずれにしても前回は1000で固定していたのですが、そこまで大きくする必要はなさそうです。

GridSearchCVを再実行

今回の結果をもとにGridSearchCVを再度実行してみます。結果もそうですが、時間がどれだけ短縮されるかにも着目してみます。

from sklearn.model_selection import GridSearchCV
# RandomForestClassifierで使用するパラメータ
search_params = {
     'n_estimators'      : [150],
      'criterion':['gini','entropy'],
      'max_features'      :[0.5,0.6,0.7,0,8,0.9],
      'random_state'      : [1],
      'min_samples_split' : [2, 3, 5, 7, 9, 11, 13, 15],
      'max_depth'         : [3, 5, 7, 9, 11, 13, 15],
}
# GridSearchCVのオブジェクトを作成
gs = GridSearchCV(RandomForestClassifier(), search_params, cv=5, verbose=2, n_jobs=-1)
# 学習用データを適用
gs.fit(X_train,y_train)
# 最適モデルを取得
best_clf = gs.best_estimator_
# スコアを表示
gs.best_score_

スコアは0.8330709677419355となりました。前回が 0.8406691356474798でしたので、良くなっているのがわかります。

早速これをKaggleにアップロードしてスコアを見てみましょう。

ですが、結果は前回よりも若干悪化してしまいました。

補足:各画像のタイトルで「Validation Curve」とすべきところを「Validationn Curve」としてしまいました。。。

]]>
Categories
データサイエンス

matplotlib.pyplotの'str' object is not callableエラー

謎のエラー ‘str’ object is not callable

Pythonでグラフを作成するときによく使うmatplotlib.pyplotですが、ある時、何気なくいつもどおりラベルを表示させようとしたら、‘str’ object is not callableという謎のエラーが出てました。

import matplotlib.pyplot as plt
# 中略
plt.xlabel('new label')

pyplot.xlabel(文字列)関数自体は存在するはずなのに、なぜこんなエラーが出るのか?

過去に別の呼び出し方をしていることが原因らしい

https://stackoverflow.com/questions/19442060/matplotlib-pyplot-titlestring-returns-errorhttps://stackoverflow.com/questions/50845106/matplotlib-ylabel-typeerror-str-object-is-not-callable を読むと、どうやら

plt.xlabel = 'old label'

というように、以前にすでにxlabelに文字列を挿入していたために、pyplot.xlabelが関数ではなく文字列とされてしまっていることが原因のようです。

改善策は再起動か「 Close and Halt 」

改善するには、1度終了させるしかないようなので、

  1. 保存
  2. 「File」→「Close and Halt」をクリック
  3. 再度ファイルを開く

の手順を行えばOKです。

ファイルを再度開いて実行すると、

import matplotlib.pyplot as plt
# 中略
plt.xlabel('new label')

結果がきちんと表示されました。

言われてみればなんてことない事ですが、いきなりこんなエラーが出たので焦りました。

]]>
Categories
データサイエンス

KaggleのTitanic課題をGridSearchCV+RandomForestClassifierで挑戦

前回はRandomForestClassifierでTitanic課題に挑戦しましたが、その前に行ったDecisionTreeClassifierよりも悪い結果となってしまいました。通常はRandomForestClassifierのほうが良い結果が出る傾向にあるので、パラメータのチューニングに挑戦します。

RandomForestClassifierのパラメータ

RandomForestClassiferには主に以下のようなパラメータがあります。他にもありますが今回は省略します。

n_estimators決定木の個数(≠深さ)
criterion分割基準(giniまたはentropy)
max_featuresノード分割する際に考慮する特徴量数。整数で指定した場合はその個数、小数の場合は全特徴量 数 に対する割合、autoは全特徴量 数 のルート数、log2はlog2(全特徴量数)。
max_depth決定木の深さ。過学習を避けるのが目的。
random_state乱数生成を制御するパラメータ。これを指定することで毎回発生する乱数を固定化する。
n_jobs使用するCPUのコア数。-1にすると全CPUコアを仕様。デフォルトは1となり並列実行はしない。
min_samples_split末端ノード内の最小サンプル数。整数で指定した場合は個数、小数の場合は全体に対する割合。
verbose 実行時のログ出力。verbose=1:経過時間と実行数を表示。verbose=2:各ツリーの構築に関する残りの情報を表示。

パラメータの最適解を強引に探すのがGridSearchCV

しかしながら、パラメータをチューニングといってもどこからどう手を付けて行ったら良いのか?がわかりませんし、毎回あれこれ試行錯誤するのも時間がかかります。

そこで、「わからないなら片っ端から調べて最適解を見つけよう!」というやり方がGridSearchCVです。

GridSearchCVの使い方

GridSearchCVの使い方の手順は以下のとおりです。

  1. RandomForestClassifierのパラメータを用意
  2. 予測モデルやパラメータを用いてGridSearchCVオブジェクトを作成
  3. GridSearchCVに学習用データを当てはめる
  4. 最適パラメータ、最適モデルを取得
  5. 予測

パラメータを用意

まず最初にRandomForestClassifierで試したいパラメータを用意します。今回は、とりあえず以下の6つを設定してみます。

n_estimators1000
criterion‘gini’,’entropy’
max_features0.1,0.25,0.5,0.75,1.0
random_state1
min_samples_split3, 5, 10, 15, 20, 25, 30, 50, 100
max_depth3, 5, 10, 15, 20, 25, 30, 50, 100

n_estimators、random_stateについては固定としていますが、決まった値だけを使用したい場合は、このように1つだけ値を用意すればOKです。

補足

n_estimatorsを1つだけにしている理由については、「なぜn_estimatorsやepochsをパラメータサーチしてはいけないのか」が参考になりました。

random_stateも乱数の発生を決めるものなので、1つだけで構いません。

予測モデルやパラメータを用いてGridSearchCVオブジェクトを作成

次に、GridSearchCVオブジェクトを作成します。

from sklearn.model_selection import GridSearchCV
GridSearchCV(estimator, param_grid, scoring=None, n_jobs, cv=None, verbose=0, refit=True)

GridSearchCVの主な引数は、以下の7つです。

estimatorチューニングを行うモデル(今回はRandomForestClassifier())
param_grid上記のモデルでチューニングを試したいパラメータ({パラメータ名:[値のリスト]}の辞書として入れる
scoring評価関数( ‘accuracy’, ‘precision’, ‘recall’ などを配列として入れる)
n_jobs同時使用CPU数(-1なら全CPUを使用)
cvCross validationの分割数(defaultは3分割)
verboseログの表示方法
refitTrueの場合、最適解で全学習データを使って再学習を実施

GridSearchCVに学習用データを当てはめる

GridSearchCVオブジェクトを作成したら次に学習用データを当てはめます。これはこれまで行った予測モデルと同じようにfit(説明変数,目的変数)とするだけです。

from sklearn.model_selection import GridSearchCV
gs = GridSearchCV(RandomForestClassifier(), search_params, cv=5, verbose=2, n_jobs=-1)

この時、オブジェクト作成時に設定したverboseの値によって、出力されるログが異なります。下記はVerbose=1と設定したときのログです。最初の「Fitting 5 folds for each of 810 candidates」はクロスバリデーションで5分割して、検証するパラメータの組み合わせが全部で1×2×5×1×9×9=810種類ある事を意味しています。

最適パラメータ、最適モデルを取得

終了したら最適パラメータと最適モデルを取得します。

best_param = model_tuning.best_params_
best_estimator = model_tuning.best_estimator_

最適モデルを表示すると下記のようになります。

予測

最適モデルはそのまま使えるので、テストデータを当てはめて予測します。結果は前回と同様にCSVファイルに出力します。

y_pred = best_clf.predict(test_dataset)
PassengerId = np.array(test_csv['PassengerId'].values)
my_result = pd.DataFrame(y_pred, PassengerId, columns=['Survived'])
my_result.to_csv('titanic_gridseachcv_randomforestclassifier2.csv', index_label='PassengerId', encoding='utf-8')

まとめ

今回のGridSearchCVを使ったTitanic課題用のPythonプログラムは下記のようになります。

from sklearn.model_selection import GridSearchCV
# RandomForestClassifierで使用するパラメータ
search_params = {
     'n_estimators'      : [1000],
      'criterion':['gini','entropy'],
      'max_features'      :[0.1,0.25,0.5,0.75,1.0],
      'random_state'      : [1],
      'min_samples_split' : [3, 5, 10, 15, 20, 25, 30, 50, 100],
      'max_depth'         : [3, 5, 10, 15, 20, 25, 30, 50, 100],
}
# GridSearchCVのオブジェクトを作成
gs = GridSearchCV(RandomForestClassifier(), search_params, cv=5, verbose=2, n_jobs=-1)
# 学習用データを適用
gs.fit(X_train,y_train)
# 最適モデルを取得
best_clf = gs.best_estimator_
# テストデータで予測
y_pred = best_clf.predict(test_dataset)
# Kaggleにアップロード用のCSVファイルの作成
PassengerId = np.array(test_csv['PassengerId'].values)
my_result = pd.DataFrame(y_pred, PassengerId, columns=['Survived'])
my_result.to_csv('titanic_gridseachcv_randomforestclassifier2.csv', index_label='PassengerId', encoding='utf-8')

このCSVファイルをKaggleにアップロードすると、

わずかですが新記録達成です。

GridSearchCVは時間がかかるので工夫が必要

GridSearchCVはモデルの学習を繰り返すのでその分時間がかかります。今回私の場合は、約35分で終わりましたが、その間CPUやメモリはほぼMaxでしたので、他の作業は全くできませんでした。

実を言うと、これでも各パラメータの配列の数を減らしました。今回はパラメータの組み合わせは810パターンでしたが、最初試したときは1200パターン以上あり、2時間以上かかりました。

GridSearchCVは最適解を見つけるのですが、その分時間がかかり運が悪いとフリーズすることもあります。そのため、GridSearchCVを行う前にパラメータの範囲に目星をつけておくことがとても重要です。

次回は、パラメータの範囲の見つけ方を紹介したいと思います。

]]>
Categories
データサイエンス

RandomForestClassifierでKaggle Titanicに挑戦

前回はKaggleのTitanicの課題に決定木(DecisionTreeClassifier)で挑戦して、76.5%の精度を出しました。

今度はランダムフォレスト(RandomForestClassifier)を使ってみます。

手順

今回の手順は以下のとおりです。基本的には前回と変わりません。

  1. ファイルの読み込み
  2. 前処理
  3. 予測モデルの作成
  4. テストデータで予測
  5. 検証

ファイルの読み込み

import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
train_csv = pd.read_csv('train.csv')
test_csv = pd.read_csv('test.csv')

前処理

次に前処理ですが、下記の3つを今回は関数を作って1つにまとめます。

  1. 不要なカラムの除去
  2. 欠損値を埋める
  3. カテゴリ変数の変換
def replace(df, args):
    ret = df.copy()
    ret = ret[args] # 指定したカラムのみ抽出
    ret['Age'] = ret['Age'].fillna(df['Age'].median()) # Ageの欠損値を平均値で埋める
    ret['Embarked'] = ret['Embarked'].fillna(ret['Embarked'].mode().iloc[0]) # Embarkedの欠損値を最頻値で埋める
    ret['Fare'] = ret['Fare'].fillna(df['Fare'].median())  # Fareの欠損値を平均値で埋める
    ret = pd.get_dummies(ret)
    return ret

このreplace関数では、データと抽出するカラム名の配列を引数に指定することで前処理を行います。

まずは学習用データ(train.csv)で前処理を行います。学習用データではSurvived,Pclass,Age,Sex,Fare, SibSp, Parch, Embarkedの8カラムを使います。

train_arg =  ['Survived','Pclass','Age','Sex','Fare', 'SibSp', 'Parch', 'Embarked']
train_dataset = replace(train_csv, train_arg)
print(train_dataset.shape)
print(train_dataset.isnull().sum())
train_dataset.head(10)

必要なカラムだけ抽出して、欠損値もなく、カテゴリ変数(Sex、Embarked)が変換されていることが確認できました。

同様にテスト用データ(test)も行います。テスト用データはSurvivedがないのでそれ以外のカラムを抽出します。

test_arg = ['Pclass','Age','Sex','Fare', 'SibSp', 'Parch', 'Embarked']
test_dataset = replace(test_csv, test_arg)
print(test_dataset.shape)
print(test_dataset.isnull().sum())
test_dataset.head(10)

こちらもきちんと変換されていることが確認できました。いよいよ予測モデルの作成に入ります。

予測モデルの作成

学習用データから目的変数と説明変数を取得し、ランダムフォレストクラスを作成してから目的変数と説明変数を入れて実行すると下記のようなメッセージがでます。

x_train = train_dataset.iloc[:,1:].values
y_train = train_dataset['Survived'].values
rfc = RandomForestClassifier(verbose=1, n_jobs=-1, random_state=0)
rfc.fit(x_train, y_train)

今回は特にパラメータの設定をしておりませんが、今回入れたパラメータは

verboseモデル構築の過程のメッセージを表示。デフォルトは0(=非表示)
n_jobs複数のCPUコアを使って並列に学習を実施。デフォルトは0。-1はすべて使う。
random_state乱数値の組を指定
の3つです。実際はこのパラメータのチューニングが重要なのですが、それについては次回紹介します。

そしてテストデータで予測します。

y_pred = rfc.predict(test_dataset)

結果をCSVファイルに出力して、Kaggleへアップロードします。手順については前回の記事と同じになるので省略します。

PassengerId = np.array(test_csv['PassengerId'].values)
my_result = pd.DataFrame(y_pred, PassengerId, columns=['Survived'])
my_result.head()
my_result.to_csv('titanic_randomforestclassifier.csv', index_label='PassengerId', encoding='utf-8')

Kaggleで検証した結果、

なんと、まさかの前回より悪い結果に。。。

これでは諦めきれませんので、次回はパラメータチューニングを行います。

]]>
Categories
データサイエンス

jupyter notebookで自作したクラスを自動でimport

jupyter notebookで自作したクラス(モジュール)をインポートできない

Pythonは自分で作成したクラスを簡単にインポートできます。

from ファイル名 import クラス名

ですが、jupyter notebookで作ったクラスをインポートしようとしても、エラーとなってしまいます。

これは、jupyter notebookでは.pyファイルではなく.ipynbファイルとして保存されるため、クラス(モジュール)のインポートができません。

jupyter notebookで自作したクラス(モジュール)をインポートする方法

自作したクラスをインポートできないようだと、jupyter notebookは正直使い物にならないので、インポートする方法を紹介します。

ファイルをPythonファイルでダウンロード

作成した自作クラス(モジュール)のファイルをjupyter notebook上で「File」→「Download」→「Python(.py)」を選びます。

ファイルを同じフォルダへ移動

ダウンロードしたファイルは通常「ダウンロード」フォルダに保存されるので、そのファイルを自作クラスを参照したい.ipynbファイルと同じフォルダへ移動します。(通常は自作したクラスの.ipynbファイルと同じフォルダで構いません)

これでクラス(モジュール)のインポートが可能になります。

jupyter notebookで.pyファイルを自動作成

これでクラスのインポートはできるようになりましたが、毎回これを行うのは面倒なので、自作クラスを保存するたびに自動的に.pyファイルを生成する方法を紹介します。

jupyter_notebook_config.pyファイルを探す

まず、configファイル(jupyter_notebook_config.py)を探します。通常はホームディレクトの下に.jupyterフォルダがあり、その中に入っています。

jupyter_notebook_config.pyファイルが見つからない場合は作成が必要

もし、 configファイルが見つからない場合はそもそも作成されていない可能性がありますので、その場合は、Anacondaプロンプトなどから下記のコマンドを実行すると jupyter_notebook_config.pyファイルが生成されます。

jupyter notebook --generate-config

.pyファイルを自動保存する関数を作成

この jupyter_notebook_config.pyファイルの最後に、以下を追記して保存します。この関数では.pyファイルと.htmlファイルを自動生成するようになっていますが、 htmlの生成が不要であれば、check_call([‘jupyter’, ‘nbconvert’, ‘–to’, ‘html’, fname], cwd=d) の一行を削除してください。

#--------------------------
# Create html and py file automatically
#--------------------------
import os
from subprocess import check_call
def post_save(model, os_path, contents_manager):
    if model['type'] != 'notebook':
        return
    d, fname = os.path.split(os_path)
    check_call(['jupyter', 'nbconvert', '--to', 'script', fname], cwd=d)
    check_call(['jupyter', 'nbconvert', '--to', 'html', fname], cwd=d)
c.FileContentsManager.post_save_hook = post_save

jupyter notebookを再起動したら完了です。

]]>
Categories
データサイエンス

機械学習初心者がKaggleのTitanic課題でモデルを作る

機械学習の挑戦者が集まるKaggle

機械学習初心者だったら、「Pythonでモデリングの勉強はして機械学習を実際にやってみたいけど、データがない」と悩んだことはありませんか?

そんなあなたにおすすめしたいのがKaggleです。 Kaggleは大きく分けて2つの特徴があります。1つは企業や政府とデータサイエンティスト/機械学習エンジニアを繋げる「マッチング」。

そして、もう1つが「Competetion(コンペ)」という、 企業や政府がコンペ形式(競争形式)で課題を提示し、参加者が作成した分析モデルのうち、最も制度の高い分析モデルを買い取る仕組みです。

Kaggleは無料で初心者に優しい

Kaggleは無料でコンペに参加が可能です。しかも、企業から提供されているトレーニング用のデータを利用して、モデルの訓練やテストデータで評価も可能なので、コンペ自体に参加する自信がない初心者でも、これらのデータを使ってモデル作成のトレーニングをすることができます。

Kaggleの会員登録については特に難しくないので、ここでは省略します。

PythonでTitanicの生存者を予測する

今回は最も有名な課題の1つである「Titanic: Machine Learning from Disaster」(タイタニック:災害からの機械学習)を実際に行ってみます。これはあのタイタニック号の乗船リストから生還者を予測します。

手順

予測モデルの作成と結果の検証を行うまでの手順になります。

  1. データセットのダウンロード
  2. データセットの内容把握
  3. 前処理
  4. 予測モデルの作成
  5. テストデータを使って予測
  6. 予測結果をKaggle上で検証

データセットのダウンロード

まずはKaggleのサイトから下記の2つのCSVファイルをダウンロードします。各課題では「Data」ページに、データセットの説明が記載されていますので熟読することをおすすめします。

  • train.csv (学習用データ)
  • test.csv (テスト用データ)

データセットの内容把握

それではいよいよPythonで予測モデルの作成に取り掛かります。最初にCSVファイルをインポートしてデータセットの中身を確認しましょう。

※私はJupyter notebookを使用していますので、通常のPythonと若干違う箇所があるかもしれません。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
# CSVファイルを同じフォルダに格納していますが、別のフォルダに格納する場合はフォルダの指定をしてください。
train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")

無事取り込みが完了したら、train.csv内の中身や項目を確認します。

train.head()

各カラムの説明は以下の通りです。

PassengerId乗客者ID
Survived生存フラグ(0=死亡、1=生存)
Pclassチケットクラス
Name乗客の名前
Sex性別(male=男性、female=女性)
Age年齢
SibSpタイタニックに同乗している兄弟/配偶者の数
parchタイタニックに同乗している親/子供の数
ticketチケット番号
fare料金
cabin客室番号
Embarked出港地(タイタニックへ乗った港)

このうち、Pclass(チケットクラス)は以下を表します。

  • 1 = 上層クラス(お金持ち)
  • 2 = 中級クラス(一般階級)
  • 3 = 下層クラス(労働階級)

また、Embarked(出港地)は以下の3種類になります。

  • C = Cherbourg
  • Q = Queenstown
  • S = Southanmpton

同じようにtest.csvの中身も確認しましょう。

test.head()

train.csvとほぼ同じカラム構成ですが、test.csvには”Survived”カラムが無いのがわかります。つまり、「予測モデルを作成してSurvivedを予測して、実際の正解と比較しましょう」というのが今回の課題になります。

次に両方のサイズを確認しましょう。

test_shape = test.shape
train_shape = train.shape
print(train_shape)
print(test_shape)

trainは891名の乗船者情報があり、12個のカラムで構成されています。一方、testは418名の乗船者でカラムがtrainより1個少なく11個となっているのは、上で説明した”Survivied”が存在しないためです。

続いて、 pandasのdescribe()を使って、各データセットの統計データを確認してみましょう。

train.describe()

countの部分を見ると、Ageで欠損があるのがわかります。

次にヒストグラムで見てみましょう。

train.hist(figsize = (12,12));

続いてヒートマップで相関関係を見ます。

plt.figure(figsize=(10,10))
sns.heatmap(train.corr(), annot =True, square=True, vmax=1, vmin=-1, center=0,cmap='Blues_r');

PclassとFareが負の相関がみられますが、それ以外はあまりなさそうです。

性別と生存の関係性も見ましょう。

sns.countplot(x='Sex' , hue = 'Survived',data = train);

‘Survived’=1が生存で0が死亡ですので、女性の方が生存率が高いのがわかります。では、 Pclass(チケットクラス) でも比較してみましょう。

sns.countplot(x='Pclass', hue = 'Survived',data = train);

Pclass=1(上層クラス)だけ生存者数が死者数を上回っていますが、 生存者数はどのクラスも同じくらいです。ただ、Pclass=3(下層クラス)の死者数が圧倒的に多い事がわかります。

最後に、これら両方を組み合わせたヒストグラムを見てみます。

sns.catplot(x="Pclass", col="Sex", hue="Survived",data=train, kind="count");

同様にtestの方も見てみましょう。

test.describe()

testの方は、AgeだけでなくFareにも1件欠損があるのがわかります。ヒストグラムや相関は省略します。

欠損値の確認

欠損値の確認はisnull().sum()で行います。

train.isnull().sum()
test.isnull().sum()

上がtrain、下がtestの欠損値の個数になります。 trainではAge、Cabin、Embarkedに、testではAge、Fare、Cabinに欠損値が存在するのがわかります。

※予測モデルの作成ではこの欠損値の扱い方で結果が大きく変わってきます。

前処理

予測モデルの作成の前に色々やっていくことがあります。その1つが欠損値の処理です。上で調べたようにtrainで欠損値を含むのは

  • Age
  • Cabin
  • Embarked

の3つですが、今回はCabinは使わないのでAgeとEmbarkedの2つの欠損値を埋めていきます。

欠損値を埋めるのにはfillna()を使用します。それではまずはAgeから始めます。今回はとりあえず、ということでAgeには平均を入れます。平均はmedian()を使います。

train['Age'] = train['Age'].fillna(train['Age'].median())

Embarkedですが、こちらはカテゴリ変数なので最頻値を入れたいと思います。ですが、その前に、Embarkedの種類と、各変数の個数を調べてみましょう。

train['Embarked'].unique()

array([‘S’, ‘C’, ‘Q’, nan], dtype=object)

説明どおり、3種類あることがわかりました。 (※nanは値がないことを意味しています)

では次に、各変数の個数を調べます。

train["Embarked"].value_counts()

Embarkedは”S”が最も多いことがわかりましたので、今回は欠損値には”S”を入れることにします。fillna(“S”)で直接入れても構いませんし、最頻値をmode()で算出して入れるのも同じです。その場合、mode()はpandas.DataFrameを返すので、iloc[0]を使ってpandas.Seriesとして取得する必要があります。

train['Embarked'] = train['Embarked'].fillna('S')
# または下でもOK
train['Embarked'] = train['Embarked'].fillna(train['Embarked'].mode().iloc[0])

同じようにしてtestのAgeの欠損値を埋めます。

test['Age'] = test['Age'].fillna(test['Age'].median())

最後に、testに1件だけあったFareの欠損値も平均値で埋めます。

test['Fare'] = test['Fare'].fillna(test['Fare'].median())
test['Fare'].isnull().sum()

終わったら、欠損値がなくなっているか確認します。

train['Age'].isnull().sum()
train['Embarked'].isnull().sum()
test['Age'].isnull().sum()
test['Fare'].isnull().sum()

カテゴリ変数の変換

予測モデル作成の前にもう1つやらなければならないのが、カテゴリ変数の変換です。Embarkedのように文字列があるとエラーになってしまうため、すべて数値に変換しなければなりません。

最初に、各カラムの型を確認します。

train.dtypes

今回使用するなかで変換が必要なのは、Sexと Embarkedになります。

pandas.get_dummies()を使って文字列を数値型に変換

カテゴリ変数を変換する際はいくつか方法はありますが、今回はpandas.get_dummies()を使います。

補足:pandas.get_dummies()とは

get_dummies()は指定したカラムの中身の値の数だけカラムを作成して、該当する箇所に1を入れます。

train_sex = train[['PassengerId','Sex','Sex']]
train_sex.columns = columns=['PassengerId','Sex','Sex_replace']
train_sex.head()
train_sex = pd.get_dummies(train_sex, columns=['Sex_replace'])
train_sex.head()

このように、Sex_replaceカラムからSex_replace_femaleとSex_replace_maleの2のカラムが作成され、Sex = ‘male’の行は Sex_replace_male = 1になり、 Sex = ‘female’の行は Sex_replace_female = 1になります。

この時元のカラム(Sex_replace)は無くなりますので注意しましょう。

では、実際にSexについて行います。

train = pd.get_dummies(train, columns=['Sex'])
train.head()

Sex_femaleとSex_maleカラムが作成されているのがわかります。続いてEmbarkedも行います。

train = pd.get_dummies(train, columns=['Embarked'])
train.head()

testについても同様に行います。

test = pd.get_dummies(test, columns=['Sex'])
test = pd.get_dummies(test, columns=['Embarked'])
test.head()

カテゴリ変数の変換について

「カテゴリ変数を0,1,2・・・のような数値に変換すれば?」と思った人もいるかも知れません。今回の性別のように値が2種類しかない場合は0,1でも良いのですが、3種類以上ある場合、1,2、3としてしまうと、3=1+2という関係性ができあがってしまう可能性がでてきます。それを避けるために、今回のようにpandas.get_dummies()を使うのが安全です。

不要なカラムの削除

これは最初に行っても良いのですが、今回は”PassengerId”, “Name”, “Ticket”, “Cabin”カラムは予測に使うのは難しいので、今回は削除して、それ以外の”Pclass”(チケットクラス),”Age”, “Sex”,” SibSp”(同乗兄弟・姉妹・配偶者者数),”Parch”(同乗親子者数), “Fare”で予測します。

ただし、最後にtestの”PassengerId”を使うので、これだけtest_PassengerIdに格納しておきます。

train = train.drop(['PassengerId','Name','Ticket','Cabin'],axis=1)
test_PassengerId = test['PassengerId']
test = test.drop(['PassengerId','Name','Ticket','Cabin'],axis=1)
train.head()
test.head()

予測モデルの作成

データとラベルに分割

最初に、trainを訓練データと訓練ラベル(評価・結果)に分割します。

trainのうち、最初の1カラム目”Survived”が評価ラベル(=結果)になるのでこれをy_trainに、それ以外のカラムが訓練データになるのでX_trainに入れます。

X_train, y_train, X_test, y_testという変数名は予測モデルでは頻繁に出てきますので、変な名前をつけるよりはこれを毎回使うようにしたほうが間違いを避けられます。

X_train = train.iloc[:, 1:]
y_train= train.iloc[:,0]
X_train.head()
y_train.head()

決定木で予測モデルを作成

いよいよ、予測モデルの作成になります。 今回は「決定木」を使って予測します。

  1. DecisionTreeClassifierをインポート
  2. DecisionTreeClassifierを作成
  3. fit()に上記で作成したX_train,y_trainを引数に加えて実行
  4. predict()にtestを引数に加えて実行
from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier(random_state = 0, max_depth=4)
clf = clf.fit(X_train , y_train)
pred = clf.predict(test)
pred

結果(pred)は下記のようになります。

結果のサイズをみると、418行生成されているのがわかります。

pred.shape

最後に、結果をCSVに格納しますが、上記だけだとどの乗客が生存・死亡したのかがわかりませんので、先ほど保管しておいたPassengerId(test_PassengerId)と組み合わせてからCSVに出力します。ファイルはUTF-8形式で書き込みます。

df_pred = pd.DataFrame(test_PassengerId, columns=['PassengerId'])
df_pred["Survived"] = pred
df_pred = df_pred.set_index('PassengerId')
df_pred.to_csv("titanic_pred.csv", index_label = ["PassengerId"], encoding='utf-8')

結果ファイルをテキストエディタで開くと下記のようになります。

Kaggleで結果を検証

このCSVファイルをKaggleにアップロードして検証します。 Kaggleへログイン後、タイタニックページへ進み、以下の手順で検証を行います。

  1. 上部メニューに「Submit Predictions」という項目がありますので、こちらをクリック
  2. ファイルをアップロード
  3. 下の「Make Submission」をクリック

数分で結果が返ってきます。右側の「Score」が正解率にいなります。今回は76.5%でした。

下にある「Jump to your position on the leaderboard 」をクリックすると、自分が世界中で何番目かがわかります。中には正解率100%の人もいるので驚きです。

次回は、他の予測モデルで挑戦したいと思います。

]]>
Categories
データサイエンス

Jupyter Notebookがショートカットアイコンから起動しない

Jupyter Notebookが起動しない Pythonでデータ分析をしている方はJupyter Notebookを1度は目にしたことがあると思います。詳しい使い方などは省略しますのでGoogleにて調べてみてください。 今回はWindowsでこのJupyterNotebookが起動しない場合の解決策について紹介します。 まず、Jupyter Notebookの起動方法としていくつかあります。

Anaconda Navigaorから起動

まず代表的な方法から紹介します。 Windowsのスタートメニュー→AnacondaX(※Xはバージョンの数字)→Anaconda Navigatorを選択します。 するとAnaconda Navigatorが起動するので、その中にある「Notebook」とかかれた枠にある「Launch」ボタンをクリックするとJupyter Notebookが起動します。

Windowのショートカット

もう1つの方法がWindowsのメニューにもあるショートカットからの起動です。 こちらも前述と同様にWindowsのスタートメニューから→AnacondaX(※Xはバージョンの数字)へ進み、「Jupyter Notebook」をクリックします。 通常ならこれでJupyter Notebookが起動されるはずなのですが、バージョンによっては起動しないことがあります。

起動しない原因はWindowsの文字数オーバー

Jupyter Notebookがこのショートカットから起動しない原因の1つにWindowsのパスの文字数制限があります。 Windowsのスタートメニュー→AnacondaX→Jupyter Notebookで右クリックし、「その他」→「ファイルの場所を開く」をクリックします。 すると、エクスプローラーが起動して、Jupyter Notebookのショートカットアイコンが出てきます。それを右クリックして、「プロパティ」をクリックします。 プロパティのウィンドウが表示されますので「ショートカット」タブをクリックします。その中に「リンク先」という入力フォームがあります。 Jupyter Notebookが起動しない場合、ここをよく見ると下記のように途中で切れているのがわかります。これはWindowsの仕様でここに書けるのが255字が最大なようです。 ちなみに切れてしまったのは、”l\Continuum\miniconda3\Scripts\jupyter-notebook-script.py”になります。
C:\Users\user01\AppData\Local\Continuum\Anaconda3\python.exe C:\Users\user01\AppData\Local\Continuum\Anaconda3\cwp.py C:\Users\user01\AppData\Local\Continuum\Anaconda3 "C:/Users/user01/AppData/Local/Continuum/Anaconda3/python.exe" "C:/Users/user01/AppData/Loc

環境変数などで文字数を削減

解決策はいくつかありますが、一番手っ取り早いのが環境変数を使うことです。 今回の場合は%localappdata%を使います。 %localappdata%は通常「C:\Users\user01\AppData\Local」を格納しているので、今回の場合は下記のように書き換えます。
%localappdata%\Continuum\miniconda3\python.exe %LocalAppData%\Continuum\miniconda3\cwp.py %LocalAppData%\Continuum\miniconda3 "%LocalAppData%\Continuum\miniconda3\python.exe" "%LocalAppData%\Continuum\miniconda3\Scripts\jupyter-notebook-script.py"
これをリンク先のところに入れて保存をしてこのWindowを閉じます。 そして、Jupyter Notebookのショートカットアイコンをダブルクリックして起動すると、見事にブラウザが立ち上がりJupyter Notebookが起動します。

プログラムが起動しない時はリンク先の文字数を確認

今回はJupyter Notebookが起動しない原因について説明しましたが、Windowsのショートカットからプログラムが起動しない時はリンク先の文字数を疑うと良いでしょう。 ただし、逆に今までは問題なく起動していたのに、プロパティで他のことをしたあとに保存してしまうと、運が悪いとこのリンク先内の文字が切れてしまって次回以降起動しなくなる、ということもありますので気をつけてください。]]>