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].com'
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
アナリティクス

Googleタグマネージャーを使う場合のコンテンツセキュリティポリシーの設定

Googleタグマネージャーは今では一般的なツールとなっていますが、 ブラウザなどによるセキュリティ関連の新しい仕様によって、あるときから急にタグマネージャーが使えないといったことが起きたりします。

コンテンツセキュリティポリシー(CSP)とは

そのうちの1つでよくあるのがコンテンツセキュリティポリシー(Content Security Policy:CSP)です。ざっくり説明すると、 Webサイト・ページはクロスサイトスクリプティング(XSS)などに対する脆弱性があるのですが、ファイルやスクリプトなどのリソースを拒否・受け入れする仕様です。

つまり、ページで「特定のサイトからのスクリプト(ファイル)しか使わないので、ほかはすべて拒否します」といった宣言をし、ブラウザはそれをもとにスクリプトの取捨選択をするといった感じです。

GoogleタグマネージャーやGoogleアナリティクスも例外ではなく、ここで宣言をしておかないと使えなくなり、Chromeなどのブラウザのコンソールには下記のようなエラーが表示されます。

コンテンツセキュリティポリシーの設定と種類

コンテンツセキュリティポリシーの設定

CSPの設定方法は主に HTTPレスポンスヘッダと<meta>タグの2種類あります。(他にもありますが割愛します)

  • HTTPヘッダー:Content-Security-Policy: default-src ‘self’
  • metaタグ:<meta http-equiv=”Content-Security-Policy” content=”default-src ‘self'”>

上記の default-src ‘self’ の部分をディレクティブと呼び、複数のディレクティブを設定する場合は;でつなげます。

ちなみに、上記の設定は「すべてのコンテンツを自身のドメイン(サブドメインは除く)から取得する」ということを表しています。

コンテンツセキュリティポリシーの制御対象

CSPでは制御対象のリソースを指定できます。主なものは以下になります。

  • script-src:Javascriptの読み込み
  • style-src:CSSの読み込み
  • frame-src:<iframe>による読み込み
  • default-src :設定がない場合に適用

Googleタグマネージャーへの影響

このようにCSPでリソースの取得の制限をしているので、このWebサイトから見たら外部に当たるGoogleタグマネージャーやGoogleアナリティクスも対象になってしまいます。

下記の例では、すべてのコンテンツを自身のドメイン (サブドメインを除く)からのみ許可しているため、 GoogleタグマネージャーやGoogleアナリティクスのスニペット(スクリプト)は無効になります。

<meta http-equiv="Content-Security-Policy" content="default-src 'self'">

Googleタグマネージャーが使えるようにするためのCSPの設定

基本設定

Googleタグマネージャーを使えるようにするには、まず最初にCSPのscript-src に ‘unsafe-inline’ を追加します。

script-src: 'unsafe-inline' https://www.googletagmanager.com
img-src: https://www.googletagmanager.com

非セキュアな場合はimg-srcディレクティブのGoogleタグマネージャーをhttpにします。

script-src: 'unsafe-inline' https://www.googletagmanager.com
img-src: http://www.googletagmanager.com

カスタム JavaScript変数

カスタムJavaScript変数を使用している場合はscript-src: ‘unsafe-eval’も追加する必要があります。

script-src: 'unsafe-eval'

プレビュー モード

Googleタグマネージャーのプレビューモードを使う場合は、さらにCSPに下記のディレクティブを追加します。

script-src: https://tagmanager.google.com
style-src: https://tagmanager.google.com https://fonts.googleapis.com
img-src: https://ssl.gstatic.com https://www.gstatic.com
font-src: https://fonts.gstatic.com data:

非セキュアな場合はscript-src: https://tagmanager.google.comのプロトコルをhttpに変更してください。

Googleアナリティクスを使えるようにするためのCSPの設定

Googleアナリティクスを使用する場合はgoogle-analytics.comもCSPの設定に加えなければなりません。

script-src: https://www.google-analytics.com https://ssl.google-analytics.com
img-src: https://www.google-analytics.com
connect-src: https://www.google-analytics.com

非セキュアな場合はscript-src ディレクティブで https://ssl.google-analytics.comを削除して構いません。

Web開発者との情報共有が大切

Gerd AltmannによるPixabayからの画像

このCSPの設定は通常はWeb開発者が行っていますが、彼らはタグマネージャーやアナリティクスについてはあまり気にしていません。そのため、CSPの設定を追加しても連絡が来ないことはよくあります。

その一方で、Web分析者やタグマネージャーの設定者もCSPなんてふだんは気にしないですし、内容もわかりません。コンソールにエラーが出ても通常は無視するでしょう。

アナリティクスにイベントやページビューが出てこないことはすぐに気づきますが、その原因を探すのは難しいです。

ですので、問題を防ぐにはふだんからWeb開発者との情報共有が大切です。コミュニケーションを取っておくことで、Web開発者から「今度CSPの設定を入れることになりました。CSPとは・・・・」といったことを教えてもらうことで、「タグマネージャーやアナリティクスに影響がでるかもしれない」と気づくことができます。

]]>
Categories
ビザ 英語

ニュージーランドでワーホリ成功例その1

これまで、ワーキングホリデービザを使って ニュージーランドに来たけどうまく行かなかった例を紹介してきました。

今回は逆に成功(とは行かないまでもよく考えて行動した)例を紹介します。

学生ビザ&友達で現地を視察

Dさんは、日本で社会人経験がある20代なかばの女性です。親友(Eさん)がニュージーランドで働くということになったので、思い切って本人も仕事を辞めてニュージーランドへ来ることにしました。

多くの人がこの時点でワーキングホリデービザを取得してしまうのですが、彼女の良いところは最初に学生ビザでニュージーランドへ入国したところです。

学生ビザでも働ける

知ってて当然と思うかもしれませんが、学生ビザでも一定の条件を満たしていればアルバイトは可能です。語学学校へ通わないといけないのでその分の学費かかってしまいますが、ニュージーランドでは学費程度の金額はアルバイトで稼げます。

Dさんは学校に通い始めて2~3週間後に学校の近くのレストランで仕事を見つけてすぐに働き始めました。学校のすぐ近くなので、移動時間や交通費も気にする必要はありませんでした。(ニュージーランドではバイト、正社員に関係なく交通費は基本出ません)

ビザの期限の心配せずに現地を視察

ワーキングホリデービザで来てしまうと、現地を理解したり、職探しの間にビザの期間がどんどん短くなってしまいます。しかも、もしもその都市が気に入らなかったり職が見つからなかった場合、また別の都市へ行って同じことを繰り返さなければなりません。

学生ビザでも現地を理解することは可能ですし、語学学校の同級生などで他の街へ旅行したことがある人も結構います。学校によっては永住権保有者の語学サポートを受け入れているので(彼らの学費はほぼ無料)、現地のリアルな情報も入ってきます。

私も語学学校に通っていた頃に、 クイーンズタウンの語学学校に通う中国人留学生が短期間だけオークランドの私のクラスに来ていた事がありました。そこで彼からクイーンズタウンの情報を教えてもらいました。

また、別の同級生は永住権を持っており、仕事もしていないのである意味暇つぶしに学校に来ていました。なので毎週末は家族で旅行したり、レストラン巡りをしていたので、情報収集ができました。

学校卒業後に放浪し、1年後にワーホリでニュージーランドへ

Dさんは語学学校に約半年間通い、その後ニュージーランドだけでなくオーストラリアなど数カ国を旅行して帰国しました。

帰国する前にワーホリの話は聞いてなかったので、「日本で仕事を辞めて次を決める前に海外へ行きたかった」程度なのかと勝手に想像していたのですが、それから約1年後に、Dさんの親友のEさんから「Dさんは今○○(オークランドのジャパレス)で働いてますよ」と連絡がありました。

そのお店は我が家から来るまで10分くらいの日本食レストランで、以前にも行ったことがあったのですが、全く気づきませんでした。

それからちょっとして、そのお店に行くとDさんが厨房で働いていました。

選択肢の多さと自分の判断の重要性

Gerd AltmannによるPixabayからの画像

これを読んで「結局ジャパレスかよ!」って思うかもしれませんが、前回紹介したジャパレス勤務のC君とは違い、Dさんの場合はオークランドだけでなくいくつかの街を実際に旅行して比較検討した結果オークランドを選んでいます。

選択肢が1つしかないところから選ぶのと、複数ある中から選ぶのでは大違いです。検討したときに「なぜここにしたのか?」という理由が明確だと、そこで自分がやりたいことや目標がはっきりするので、結果も変わってくるでしょう。

Dさんはまだワーホリ期間中なので今後どうなるかはまだわかりませんが、ワーホリをするまでの道のりとしてはとても良い方法だったと思います。

]]>
Categories
ビザ 英語

ニュージーランドでワーホリ失敗例(ジャパレス勤務)

前回前々回でワーキングホリデービザでニュージーランドに来た人の失敗例を紹介しました。第3回目の今回は、前回と比べるとマシですが、もっともよくあるパターンなので紹介したいと思います。

日本にいる間に就活。入国後すぐ仕事

C君(20代男性)は日本で調理師の経験があり、その経験を生かしてワーホリでニュージーランドへ来る前にインターネットで就活をしており、すでに勤務先が決まった状態でニュージーランドへ来ました。就活時間でビザの期間を使わずに済んだのはとても大きいと思います。

勤務先はオークランドでも結構有名な庶民的な日本食レストランで、現地の人たちも好んで訪れるので、週末になると1時間待ちになることもあるくらい人気のお店です。

ニュージーランドに慣れるまでは、職探しで無駄に時間を浪費するより 、日本人経営者のレストラン(通称ジャパレス)で働くのは、個人的にはありだと思います。

ですが、期間を決めておくべきですね。

英語が不要な環境で生活

人気のお店なので週休1日(ワーホリの後半は週休2日に増えてました)でした。しかも飲食業には定番の休みは平日ということもあり、最初の数カ月間は休日はほとんど部屋にこもっていました。

同僚たちとは仲良くやっていたようで、仕事終わりに一緒に飲んだり、休日に遊びに行ったりしていましたが、同僚はほとんど日本人なので英語を話す機会もなければ勉強する必要もありませんでした。

英語のテキストは自室においてありましたがほとんど勉強してなかったようです。

そうなるとなんのためにワーホリへ来たのか?ということになります。これは彼に限らずワーホリで来る日本人の大半に共通する問題点です。後で述べますが、これが彼のその後にも影響します。

ワークビザを取らずに帰国

約8ヶ月ほど同じお店で働き続けたC君に、ビザについて話した事がありました。彼の職種や給料、過去の仕事経験が十分で勤務先のサポートもありそうなので、ワークビザ(しかも英語力の証明が不要なWork To Residenceビザ)が取れそうということがわかりました。

ですが、それから1ヶ月後、彼は仕事を辞めて日本へ帰国することを選びました。理由は、

  1. ニュージーランドは飽きた
  2. 日本で自分がやりたい仕事がしたい

ということでした。1つ目については以前のブログ(ニュージーランドでワーホリ失敗例(すぐ帰国した現役大学生))でも書きましたが、20代にとってニュージーランドは刺激が足りないので飽きてしまうという話はよく聞きます。ですが、「ニュージーランドに飽きた 」と言えるほど体験したのかが疑問でした。

2つ目のしごとについても、彼がやりたい仕事はニュージーランドにもあるのですが、あまり調べていませんでした。

手段が目的

C君が、ニュージーランドで仕事をして「ニュージーランドが自分に合わない」という結論に達した事自体は構わないのですが、ジャパレスで日本人に囲まれて働くだけで、もっと色々経験して判断できたはずが、結局ワーホリと言う名のバイト生活でしかありませんでした。

日本人によくあるのですが、「ワーホリでニュージーランドへ来る」という手段が目的になってしまって、本当の目的がないまま来ている人が多く見受けられます。

ワーキングホリデービザだけでなく、20代の若い1年間という時間自体が本当にもったいないので、「ニュージーランドで1年間過ごしてどうなりたいか?」というゴールを決めておくことをおすすめします。

]]>
Categories
ビザ 英語

ニュージーランドでワーホリ失敗例(語学学校半年通学)

前回は、語学学校を卒業してからわずか1ヶ月で帰国してしまったワーホリを紹介しました。ワーホリ失敗例を書く理由も前回の記事に書いていますのでそちらを読んでもらえると嬉しいです。

今回のワーホリ失敗談は、ワーホリで来たのに語学学校に半年間も通ったBさんです。

大学卒業後すぐにワーホリ

Bさんは日本の4年制大学を卒業した直後(4月上旬)にオークランドへやってきました。卒業直後に来るということは、就活に失敗した可能性が高いのですが、卒業してしまうと「新卒採用」枠には応募できないので、卒業してしまったのがまず失敗でした。

目的・ゴール・ワーホリ後のキャリアパスが未定

彼女はそもそもワーホリに来た目的が、英語を学ぶこと以外はあまり決まっていませんでした。「それなら語学留学でいいんじゃない?」と思ってしまいましたが、来てしまったのであえてそれは言いませんでした。

また、ワーホリ後に帰国したあとにどうしたいのか?どうなりたいのか?といったキャリアパスも全く考えていませんでした。22~3の子に「キャリアパスを考えろ」というのは少々ハードルが高いかもしれませんが、せっかくのワーホリを使うのだから、そこまで考えてから来るべきだと思いました。

英語力が足りなくて語学学校に半年通学

彼女の3つ目の失敗は英語力です。よく「英語ができなくてもワーホリは楽しかった」という話も聞きますが、正直そのワーホリの1年間は学生がアルバイトして旅行する程度でしかありません。「人生観が変わった」という話も聞きます。それはたしかにありますが、英語を話せていろいろな経験をできたほうが人生観はもっと変わるでしょう。

Bさんの場合、ワーホリで来たのに語学学校へ約半年ほど通っていました。その間、現地人の家にホームステイはしていましたが、英語を学ぶだけの生活になってせっかくのワーキングホリデービザを有効に活用できませんでした。

ニュージーランドに来たことがなかった

もう1つの彼女の失敗は、ニュージーランドへ来たことがなかったということです。大学時代に時間はあったはずですので、ニュージーランドはもちろん、オーストラリアやカナダ、イギリスなどのワーホリ可能な国へ実際に旅行して、比較検討すべきでした。

仮にニュージーランドにするにしても、オークランドだけでなく、首都のウェリントン、南島最大の都市のクライストチャーチ、観光街のクイーンズタウンなど複数の都市があるので、それらを実際に行ってみてから決めるべきでした。

彼女も語学学校に通って半年弱の頃に「オークランドは飽きた」と嘆いていました。このあたりは前回、ワーホリ実質1ヶ月で帰国したA君と同じですね。

帰国後はホテル勤務

Bさんは、語学学校卒業後、 オークランド市内のレストランで半年間アルバイトをして、最後の数週間は北島・南島を観光して帰国しました。

帰国後すぐに就職活動をしてホテルで働いています。ですが、英語を使うことはあまりないそうです。

「今度はオーストラリアにワーホリへ行きたい」と言っていたので、彼女にとって今回のワーホリは失敗ではなかったかもしれませんが、

  • 目的を決めておく
  • ニュージーランドを調べておく
  • 英語力を来る前に身につけておく(もしくは語学留学後にワーホリビザを取得)

といった事ができていれば、もっと充実したワーホリ生活を送れていた思います。

]]>
Categories
ビザ 英語

ニュージーランドでワーホリ失敗例(すぐ帰国した現役大学生)

ワーキングホリデーの失敗例を書く理由

私の家にはワーホリや留学生をフラットメイト(日本で言うシェアハウス)として受けて入れて数年が経ちました。その中でいろいろな人がいたのですが、ワーキングホリデー(ワーホリ)期間を最後まで使って仕事や旅行を楽しんだ人もいれば、逆にわずか数ヶ月で帰国した人もいました。

ワーホリは人生で1回しか使えないし30歳までしか使えない、という限られたビザなので、無駄にはしたくはありませんよね?その機会を無駄にしてもらいたくないので、失敗例を書く事にしました。

私の家に来た人たちの中から、今回はワーホリを途中リタイヤしたA君について紹介したいと思います。

現役大学生が語学学校3ヶ月通学

A君は当時大学3年生で、3年生の秋にニュージーランドへ彼女と一緒にワーホリへ来ました。最初の3ヶ月はクライストチャーチの語学学校に3ヶ月通いました。

何人ものワーホリを見てきましたが、現役大学生がワーホリに来て語学学校に通うほど無駄なことはありません。「大学生なら時間がたくさんあるので、日本にいる間に学んでおけば良いのに」といつも思います。

それと彼ら、彼女らの多くに共通しているのは、学校を卒業しても英語力はたかが知れてて仕事には使えない、ということです。

話を戻して、A君はA君だけオークランドに移動して私の家に住むことになり、彼女はそのままクライストチャーチに残っていました。この時点でA君はは仕事は決まっていなかったので、仕事探しから始まります。

近所の日本食レストランで週5勤務

幸運(?)にも就活数日目に私の家から徒歩5分のところにある日本食レストランでバイトすることになりました。しかし、そのお店の店員ほぼ全員日本人で担当は厨房。つまり、英語を話す機会がまったくありません。

しかも、週5で昼間と夜の両方とも勤務だったので、朝11時前に家を出て、ランチタイム後に1度帰宅して、夕方また出勤して、夜10時頃に帰宅。まかないがでるので食事をすることもなく部屋にこもって、SNSやYoutubeを楽しむ生活でした。

しかもオークランドへ来たばかりなのでそもそも友達がいないし、 休みも平日しか無いので友達と外出もできませんでした。結局英語を話す機会が全くありませんでした。

わずか4ヶ月で帰国。理由は「退屈」

語学留学3ヶ月のあと、オークランドへ来て我が家に住み始めて1ヶ月後のある日。「彼女と一緒に帰国することに決めました」と連絡がありました。

私は止める気は全くありませんでしたが、あまりにも早いことだったので理由を聞きました。すると、

「ニュージーランドは退屈だから」

ということでした。たしかにニュージーランドは退屈で、しかも若者が楽しめるような場所は無いに等しいです。車がなければショッピングモールへ行くのも面倒です。だから彼の気持ちも理解できたので、「たしかに退屈だよね」と答えるだけにしました。

ニュージーランドにエンタメや刺激を求めてはいけない

ニュージーランドという国は日本と比べると遊べるところも少ないです。下の写真がニュージーランド最大の都市オークランドですが、オークランドの中心地以外では夜10時には大抵のお店は閉まってしまいます。そのため、夜遊びも一部の地域を除きほとんどありません。

なので、ワーホリの職場で盛り上がるか、Meet upなどのイベントに参加しないとなかなか遊べる機会がありません。

ニュージーランドと言えば「自然宝庫」を連想すると思いますが、半分は当たってて、オークランドも2ヶ月住んでいたらある程度の名所やスポットを制覇できて飽きてしまいます。

NZといって連想する風景

そういった点で20歳前後の若者にはニュージーランドはとても退屈な国です。だから、よっぽど強い思い入れがない限り20代前半にニュージーランドへワーキングホリデーに来るのはおすすめしません。

]]>
Categories
サッカー 英語

サッカー好きにおすすめの英語リスニング用Youtube動画2

前回はネイティブの英語に慣れるのにおすすめなサッカーYoutube動画を紹介しました。今回は、英語でサッカー戦術や歴史を学べる動画を紹介します。

Guardiola’s Barcelona 2010/11 Tactics | Pep Guardiola’s Greatest Team? | Football Made Simple

まず最初は「 Football Made Simple 」というチャンネルです。こちらは2019年頃から開始しているので古くはありませんが、毎月数本アップしており各動画の中身はかなり濃いです。ですがどの動画もだいたい10分程度なので挫折しにくいです。

この動画のポイント

  • 戦術が詳しく映像と図で紹介されている
  • 移籍や選手の獲得などの説明が丁寧
  • コンテンツがプレミアリーグやリーガの強豪チーム中心なので理解しやすい

です。特にバルセロナとマンチェスターU、アーセナルが多いので、これらのクラブのファンにはおすすめです。

欠点ではありませんが、アクセントや口調からしてネイティブスピーカーではなく、ヨーロッパのドイツや東欧あたりの出身かと思いますので、早口の英語のリスニング教材としては向いていません。

https://www.youtube.com/embed/QtRZHHfKzaQ

What is a Mezzala? | Tifo Football

2つ目は、「Tifo Football」チャンネルです。「Man City’s Big Weakness: Shots From Outside The Box」や「What is a Mezzala?」ようにアナリティクスの話もありますが、チームの歴史や選手やコーチ、オーナーなどに特化した動画が多いのが特徴です。

この動画のポイント

  • 1本あたりの動画が5分前後で見やすい
  • クラブの歴史や選手の紹介などが詳しい
  • クラブの財政やサッカー界のルールなどの動画もある
https://www.youtube.com/embed/xyKSLRwjtjs

How Would Pochettino Set Up Newcastle United | Starting XI, Formation & Transfers | Statman Dave

3つ目は「Statman Dave」チャンネルです。私は最近はこのチャンネルの動画をみることが多いです。ネイティブの英語だけど丁寧でしっかり話してくれるので聞き取りやすいです。10分以上の動画も結構多いのですが、挫折することなく見れます。

この動画のポイント

  • 移籍話や補強のポイントなど戦術の話が多い
  • 概要欄に説明がびっしり書かれているので精読の勉強にもなる
  • 映像や図、文章が動画にたくさん盛り込まれているので理解しやすい
https://www.youtube.com/embed/oaBUapfTEtU

最後に

今回は3つのチャンネルを紹介しました。もし、皆さんのおすすめの動画やチャンネルがありましたらコメントにて紹介してもらえると嬉しいです。

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」としてしまいました。。。

]]>