$yuzu->log();

技術ネタなど。

【機械学習】ナイーブベイズ分類アルゴリズムを利用した迷惑メールフィルタ実装例

ナイーブベイズ分類器とは?

ベイズの定理を利用した分類手法です。 ベイズの定理について知りたい方は下記の参照下さい。

yuzurus.hatenablog.jp

 \displaystyle

ナイーブベイズ分類は文章をカテゴリ分けする際に、テキスト中の単語の出現率を調べます。 その際、その文章をどのカテゴリに分類するのが相応しいか調べるのです。

判定の際に \( P(B|A) \) は1つの確率ではなく、複数のカテゴリ中で、どのカテゴリになる確率が一番高いかを表す情報になります。 この時 \( P(A) \) は入力テキストが与えられる確率となります。

しかし、どのカテゴリを判定するにしても同じ入力になるため、同じ値と考えて良いこととします。するとナイーブベイズ分類は次のように表せます。

\( P(B|A) = P(B)P(A|B) \)

この時の \( P(B) \) は事前確率で、各カテゴリに分類される確率を表しています。

これは単なるベイズ分類器です。ナイーブベイズ分類器は確率分布 \(P(A|B) \) をシンプルな分布に限定します。

入力テキストAに関して、単語の集合と考えて、多次元変数と考えると

\( A={a1,a2,a3,…,aN} \)

と表せます。すると \(P(A|B) \) は

{ \displaystyle
P(A|B) = \prod_{i=1}^{N} P(a_i|B)
}

となります。

ナイーブベイズ分類を利用して迷惑メールフィルタを作ってみる

ナイーブベイズ分類アルゴリズムを内包したライブラリ「fieg/bayes」を利用して迷惑メールフィルタを作ってみましょう!
単語分類のためにMecabのインストールとphp-mecabのインストールが必要になります。

composerを利用してfieg/bayesというライブラリをインストールします

composer require fieg/bayes

単語分類器 MecabTokenizer.php

<?php
require_once 'vendor/autoload.php';
require_once 'mecab.inc.php';

use Fieg\Bayes\TokenizerInterface;

class MecabTokenizer implements TokenizerInterface {
  public function tokenize($str) {
    return mecab_parse_simple($str);
  }
}

単語分類器を利用したプログラムbayes-filter.php

<?php
require_once 'vendor/autoload.php';
require_once 'MecabTokenizer.php';

use Fieg\Bayes\Classifier;

// 単語分類器と分類器の生成
$tokenizer = new MecabTokenizer();
$classifier = new Classifier($tokenizer);

// 学習
$classifier->train('迷惑メール', '年収1000万を確実に稼ぐ方法');
$classifier->train('迷惑メール', '【FX】1年で資産120倍!このスゴイ講義が無料。ちょっと信じられません・・・');
$classifier->train('迷惑メール', '月利40%のモニター募集');
$classifier->train('迷惑メール', '【年利174%】99.9%の確率で含み損が利益になるプロ技');
$classifier->train('迷惑メール', '【2日で1億1800万円】英国EU離脱騒動で稼いだオトコの正体');
$classifier->train('迷惑メールでない', '暮らしを便利にするマストアイテムを65%OFF');
$classifier->train('迷惑メールでない', 'アンケートにご回答いただくと30ポイントプレゼント【楽天】(2016/09/17)');
$classifier->train('迷惑メールでない', '【お急ぎください!】9月連休出発高速バス予約ラストチャンス!');
$classifier->train('迷惑メールでない', 'Amazon.co.jpからポイント付与についてお知らせ');
$classifier->train('迷惑メールでない', '【住信SBIネット銀行】定額自動振込サービス振込受付のお知らせ  ');

$s = '【衝撃映像】『日給1100万円確定』の瞬間を捉えた!';
$r1 = $classifier->classify($s);
echo "--- $s\n";
print_r($r1);

$s = '【早割】特大和洋中おせち♪国産南高梅の木箱ギフトなど【楽天】(2016/09/18)';
$r2 = $classifier->classify($s);
echo "--- $s\n";
print_r($r2);

これを実行してみると

php bayes-filter.php
--- 【衝撃映像】『日給1100万円確定』の瞬間を捉えた!
Array
(
    [迷惑メール] => 0.9
    [迷惑メールでない] => 0.1
)
--- 【早割】特大和洋中おせち♪国産南高梅の木箱ギフトなど【楽天】(2016/09/18)
Array
(
    [迷惑メールでない] => 0.99980407523511
    [迷惑メール] => 0.00019592476489028
)

簡単なテストですが、ちゃんと分類されてますね!

ソースはここにおいておきます。

github.com

【機械学習】ベイジアンフィルタ

ベイジアンフィルタとは

ベイジアンフィルタ (Bayesian Filter)とはナイーブベイズ分類を応用したもので、対象となるデータを解析・学習し分類する為のフィルタです。 現状では迷惑メールフィルタやスパム投稿などの判定で利用されています。

ベイズの定理

ベイズの定理(Bayes' theorem)とは条件付き確率に関して成り立つ定理で、トーマス・ベイズによって示されたものです。

 \displaystyle
  P(B|A) = \frac{P(A|B)P(B)}{P(A)}
  • \( (A) \)とは\( A \)が起きる確率
  • \( P(B) \)とは、\( B \)が起きる確率(事前確率)
  • \( P(A|B) \)とは、\( B \)のあとで\( A \)が起きる確率(条件付き確率、尤度)
  • \( P(B|A) \)とは、\( A \)のあとで\( B \)が起きる確率(条件付き確率、事後確率)

条件付き確率とは

条件付き確率とは、ある事象Aが起こる条件下で、別の事象Bが起こる確率のことです。これを

\( P(B|A) \)

と表します。 たとえば

  • \( P(雨) \) = 雨の降る確率
  • \( P(事故) \) = 交通事故の発生確率

とすると雨が降っていて、交通事故が発生する確率は

\( P(事故|雨) \)

と表せます。

次はベイズの定理を使って実践的な問題を解いてみましょう。

ベイズの定理から見る膵臓ガン発見の検査方法

www.huffingtonpost.jp

15歳の少年が膵臓がん発見の画期的な方法を開発したことがについて、この検査方法が統計的にどのような意味をもつのかをベイズの定理を使って計算してみます。

  • \( p1 \) = 被験者が陽性になる確率
  • \( p2 \) = 被験者が陰性になる確率
  • \( p3 \) = 被験者ががん患者である確率
  • \( X \) = 被検査者はガンであるという事象
  • \( Y\) = 検査の結果、被検査者はガンであると示す事象

とすると

  • \( p1 = P(X|Y) \)
  • \( p2 = P(X^{c}|Y^{c}) \)
  • \( p3 = P(Y) \)

と表せる。 \( X^{c} \) や \( Y^{c} \) はそれぞれの補事象。

ここで \( P(X) \) は検査結果が真陽性となる確率と偽の陰性となる確率を足したものなので、

\( P(X) = P(Y)P(X|Y) + P(Y^{c})P(X|Y^{c}) \)

さらに、

\( P(X|Y^{c}) = 1-P(X^{c}|Y^{c}) \)

のため、

ベイズの定理より

 {
\begin{align} 
  P(Y|X) &= \frac{P(X|Y)P(Y)}{P(X)} \\
&=  \frac{P(X|Y)P(Y)}{P(Y)P(X|Y) + P(Y^{c})P(X|Y^{c})} \\
&=  \frac{P(X|Y)P(Y)}{P(Y)P(X|Y) + P(Y^{c})(1-P(X^{c}|Y^{c}))} \\
&= \frac{p1p3}{p1p3+(1-p3)(1-p2)} 
      \end{align}
}

となります。

実際の数値を当てはめてみましょう。 2008年の膵臓がんの患者数は29,584人、総人口は1億2769万2000人なので

\( p3 = 0.23168 * 10^{-3} \)

また簡単のために p1, p2をひとまとめに誤検出の確率と仮定してp1=p2=qおくと、\( P(Y|X) \) が70%となるようなqは

\( q=0.99990 \)

となります。 このことから、99.99%の精度をもつ検出方法でも実際には30%も見逃してしまうということがわかります。

さらに、「400倍の精度で検査できる」という部分を誤検出の確率が400分の1になったという意味だと解釈して、

\( q' = 1- \frac{(1-q)}{400} \)

とおいてがん患者が検査の結果実際に陽性だと判定される確率を再度計算すると

\( P(Y|X) = 0.99892 \)

となります。これは30%見逃していたのが1%まで減ったということです。 実に素晴らしい結果ですね。

次回はベイズの定理を使ったナイーブベイズ分類器のお話です。

yuzurus.hatenablog.jp

ベイズ統計学入門

ベイズ統計学入門

SEOの初心者にはおすすめな本「10年使えるSEOの基本」を読んだ感想

SEOの専門家におすすめされて、読んだ本。とても良かったので紹介します。

「10年使えるSEOの基本」の感想

SEOの専門家である土居くんと、SEOに詳しくないすずちゃんが対話形式で例を交えながらSEOの話をする本です。
特にSEOのテクニック的なところは出てこず、「検索エンジンとは何か」、「Googleが目指す世界」から、SEOを紐解く至極真っ当な本です。
陳腐化する小手先なテクニックよりは、本質的なところにフォーカスをあてているため、「10年つかえる」の看板に偽りはないと思いました。
フルカラーで優しい言葉でかけあいしてるので、初心者にはすごく読みやすい本なんじゃないかと思います。 以下メモ。

検索にヒットするようになるには?

  • どういう言葉で検索されているのかを知る
  • 検索エンジンが正しく理解できるようにサイトを作る
  • みんなが検索を通じて知りたいことをコンテンツ化してサイトに掲載しておく
  • 継続的にサイトにリンクを集めていく

検索キーワードの見つけ方

  • どんなキーワードで検索しているのか?
  • キーワードボリュームは感覚ではなく必ず調べるようにする

検索キーワードをサイトに反映させる

  • titleタグにキーワードを含める
  • meta descriptionにキーワードを含める
  • 最初の見出しにはキーワードを含める
  • ページ内のテキスト要素にキーワードを含め、なるべくページ上部に出現させる
  • 「見てほしい人が使いそうな言葉」をなるべく選んで使う
  • 自然な文章で表記し、不自然な繰り返しを用いない

良いタイトル付け

  • 大事なキーワードを含めて、なんのページなのかが具体的にわかるように
  • 異なる内容のページに、同じタイトルを複数つけない ← プログラムで書いた場合はやりがち
  • 文字数は30文字以内
  • 重要なキーワードはなるべく前半に

meta descriptionの重要さ

  • 検索エンジンのランキングには関係ない
  • クリック率には関わってくる
  • 大事なキーワードを含めて、なんのページなのか具体的にわかるように
  • 異なる内容のページに、同じdescriptionを書かない
  • 50〜100文字以内

SEOとは

  • 検索エンジンで上位表示」ではなく「検索を通じて知りたい情報を見つけられるようにする」技術
  • 「自分が言いたいこと」ではなく「みんなが知りたいこと」を書く
  • コンテンツは量よりも品質を重視。

コンテンツ・マーケティングの正のスパイラル

  1. 良いコンテンツを作る
  2. 多くの人に見てもらえ、一定のリンクを獲得
  3. リンクを通じて更に見てもらえる機会が増える
  4. SEOも強くなって検索での露出が増える
  5. さらにリンクが増える
  6. 2に戻る

10年つかえるSEOの基本

10年つかえるSEOの基本

ポケモンGo!のアンテナサイトを作った。技術仕様やアプリケーション仕様などなど

酔った勢いで、ポケモンGo!のアンテナサイトを作ってみました。

pokemongo-mtm.xyz

こういうのって、スピードが大事ですよね。検索ボリュームが多い時にリリースできたのでよかったです。 1時間ぐらいでさくっと。

使用した技術

サーバーはGMOクラウドのVPSのメモリ2GB、月額1,280円のプラン。

composer.json

 "require": {
        "cakephp/cakephp": "2.*",
        "cakephp/debug_kit": "*",
        "intervention/image": "dev-master",
        "intervention/imagecache" : "*",
        "cakedc/migrations": "*",
        "nanapi/cakephp-redis": "*"
    },

アプリケーションの仕様

  • hourlyで指定したRSSをクロール。
  • サムネイルの取得は対象サイトのog:image、なかったら、RSSのdescriptionに含まれている、imgタグから取得。
  • PVのカウントはRedisで貯めてって、デイリーバッチで、MariaDBに貯めこむ。

悲しかった事。

CakeDCのマイグレーションプラグインを使ったのですが、マイグレーションプラグインがStringクラスを使っていて、PHP7から「型と同じクラス名」を作れないという制約があるため、exception error が発生するという悲しみ。
なので手動でDB作りました。

今後やりたいこと

広告周りは適当にimobileのクリック広告しか貼ってないですが、ユーザの志向にあったものを採用していきたいです。

ポケモンGo!をやってみて。

電池の減り早い! モバイルバッテリー必須ですね。
軽くて大容量なおすすめモバイルバッテリー↓

エンジニア × 海外就職 × 英語で参考になりそうな記事まとめ

最近エンジニアの海外就職報告記を見かけるようになってきました。 エンジニアとしての技術力や英語力はどのぐらい必要だったのか、ビザはどうしたのか。 参考になりそうなものをまとめてみました。


kenzan100.hatenadiary.jp

ベルリンでウェブエンジニアとして働く事になった方の記事。 ワーホリビザから就労ビザに移行するやり方があるのですね。


nzmoyasystem.hatenablog.com

ニュージーランドプログラマとして働いている方のブログ。 英語力について、職場の文化について記載があります。


blog.kyanny.me

グローバル企業であるQuipperで働く上でどのぐらい英語力が求められるか詳細に書かれている記事。


hotchemi.hateblo.jp

ソフトウェアエンジニアとして英語を学ぶ上で良かったことまとめ。 「海外の会社の転職面接を受けるのは英語の勉強に良い」は、成る程と思いました。


shiumachi.hatenablog.com

英語が話せるようになれば、グローバルの人材市場で自分の価値を判断されるようになるので英語頑張ろうというお話。


d.hatena.ne.jp

Github経由でイスラエルから仕事をいただいたという話。 こういう働き方はどんどん増えていくだろうなぁ。


tango-ruby.hatenablog.com

シンガポールのスタートアップで、絶望的に英語力が無いけど技術力がある人が入社した話。 なんとか働けているようです。


chezou.hatenablog.com

「USに行きたいのなら日本法人に入るのが定石」


anond.hatelabo.jp

10年間海外で技術者として仕事をしている人からの、海外の会社で適応するためのアドバイス。


lifeiscolourful.hatenablog.com

海外移住先としてのニュージーランドのすすめ。
ニュージーランドってITのイメージなかったんですが、高給取りなのですね。


rebuild.fm

@miyagawaさんがMCをしているテック系ポッドキャスト。 IT技術やガジェットなどの話が中心ですが、@miyagawaさんがアメリカで働いていることもあり、英語やビザの細かい話などを聞ける回もある。 私は通勤時に1.5倍速で聞いてます。


タイトルが世界の最前線とかいてあるけど、中身はシリコンバレーの話です。 日本とアメリカとの仕事環境の違いや転職やレイオフの受け止め方。どのぐらい英語ができれば良いのか。ビザの話。面接突破方法など、シリコンバレーでエンジニアとして働くことを目指すなら絶対読んでおいた方がいい1冊でした。

他にも参考になりそうな記事やメディアがありましたらおしえてください。

FXシステムトレードフレームワーク Jiji をCentOS7にセットアップ

FXシステムトレードフレームワークJiji」のセットアップメモです。

「Jiji」のドキュメントにはHeroku、AWS、Dockerの3つの方法が載っており、前者2つは詳細に説明が書かれていますが、3つ目の「Dockerにインストール」はパッケージインストールが省略されています。

この記事はCentOS7での1からのセットアップ記事です。誰かの参考になれば。

Docker、Git、docker-composeのインストール

# システムをアップデート
$ sudo yum update -y

# Gitのインストール
$ sudo yum install -y git

# Dockerの最新版をインストール
$ curl -sSL https://get.docker.com/ | sh
$ docker -v
Docker version 1.10.2, build c3959b1
$ sudo systemctl start docker
$ sudo systemctl enable docker
$ sudo usermod -a -G docker YOURUSERNAME

# docker-composeをインストール
$ curl -L https://github.com/docker/compose/releases/download/1.6.0/docker-compose-`uname -s`-`uname -m` > /tmp/docker-compose
$ sudo mv /tmp/docker-compose /usr/local/bin/
$ sudo chmod +x /usr/local/bin/docker-compose

docker-compose設定ファイルのひな形をチェックアウト

$ git clone https://github.com/unageanu/docker-jiji2

オレオレ証明書の作成

# スーパーユーザにスイッチ
$ sudo su
# jiji用のディレクトリ作成、移動
$ mkdir -p /etc/ssl/certs/jiji
$ cd /etc/ssl/certs/jiji
# 秘密鍵を生成
$ openssl genrsa 2048 > server.key
# CSRを作成
$ openssl req -new -key server.key > server.csr
# サーバー証明書を作成
$ openssl x509 -sha256 -days 365 -req -signkey server.key < server.csr > server.crt
# アクセス権を制限
$ sudo chown root.root server.key
$ sudo chmod 600 server.key
# スーパーユーザから抜ける
$ exit

docker-compose.yml の編集

vim ~/docker-jiji2
jiji:
  container_name: jiji_jiji
  image: unageanu/jiji:latest
  environment:
    # サーバー内部で秘匿データの暗号化に使うキー
    # 必ず変更して使用してください。
    USER_SECRET: 20e43fa41e75c9fe958f5f11bc9e79d2174b50b
  links:
    - mongodb

mongodb:
  container_name: jiji_mongodb
  image: mongo:3.0.7
  ports:
    # MongoDBのポート番号
    # 必要に応じて変更してください。
    - "27018:27017"
  volumes:
    # MongoDBのデータを保存するディレクトリ
    # デフォルトでは、コンテナ内に作成します。(この場合、コンテナを再作成すると、データが初期化されます)
    # コメントアウトしてパスを設定することで、ホストマシンの任意のディレクトリに変更することができます。
    # './' で始めることで、docker-compose.ymlからの相対パスで指定可能です。
    # - ./path/to/data/dir:/data/db

nginx:
  container_name: jiji_nginx
  image: unageanu/jiji-nginx:latest
  links:
    - jiji
  ports:
    # Jijiのポート番号
    # 443に変更します
    - "443:443"
  volumes:
    # SSL証明書のパス
    # './path/to/server.crt' にサーバー証明書、
    # './path/to/server.key' に秘密鍵を指定します。
    # './' で始めることで、docker-compose.ymlからの相対パスで指定可能です。
    - /etc/ssl/certs/jiji/server.crt:/etc/nginx/cert/ssl.crt:ro
    - /etc/ssl/certs/jiji/server.key:/etc/nginx/cert/ssl.key:ro

Dockerコンテナ起動

$ sudo /usr/local/bin/docker-compose up -d
Creating jiji_mongodb
Creating jiji_jiji
Creating jiji_nginx

アクセス

https://<インストール先ホスト>:<docker-compose.ymlで設定したJijiのポート/デフォルトは8443>

iPhone7等の新製品を誰よりも早く予約する為に、予約サイトがオープンしたらSlackに通知する方法

Shell Script Advent Calendar 2015 8日目を担当させていただきます、@yudsuzukです。

今回は実用的なシェルスクリプトについて書きます。

GoogleのNexusやAppleiPhone等の新製品の発表は、日本時間で深夜なため、起きているのが辛いし、いつ購入サイトがオープンしたか分かり難いですよね。
気がついたら、新製品の予約サイトがオープンしてて、お目当ての製品が既に予約でいっぱい。お届け予定日数は1ヶ月後なんて事もしばしば。

そんなあなたのために、新製品の予約サイトがオープンしたらSlackに通知させるシェルスクリプトをつくりました。

今回は、実際にそのシェルスクリプトのお陰で予約ができたNexus5X、Nexus6Pの事例と、近い将来発売されるであろうiPhone7の予約サイトオープン通知スクリプトを紹介します。

Nexus5XとNexus6Pの予約サイトオープン事例

今回はGoogleの新製品、Nexus5XとNexus6Pの予約サイトがオープンした時の事例を紹介します。
予めSlackのIncoming WebHooksでWebhook URLを取得してください。

doneFile=done.txt
if [ ! -e $doneFile ]; then
    exit 0
fi

# 通知するSlackのチェンネル
channel="#test"
# 通知する時のユーザ名
username="incoming-webhook"
# 通知するユーザのアイコン
iconemoji=":shell:"
# WebHookURL
webhookUrl="https://hooks.slack.com/services/XXXXXXXX"
# 通知文言
text="nexus_5xとnexus_6pのサイトがオープンしたよ!"
# サイトURL prefix
prefixUrl="https://store.google.com/product/"
# 製品名
products=("nexus_5x" "nexus_6p")


for product in ${products[@]}
do
    status=`/usr/bin/curl -LI $prefixUrl$product -o /dev/null -w '%{http_code}' -s`
    if [ $status -eq '200' ]; then
        /usr/bin/curl -X POST --data-urlencode "payload={\"channel\": \"${channel}\", \"username\": \"${username}\", \"text\": \"${text}\", \"icon_emoji\": \"${iconemoji}\"}" ${webhookUrl}
        touch $doneFile
        exit 0
    fi
done

cronに仕込めば完成。
5分おきぐらいに実行すれば怒られないでしょう。
URLが下記のものであるだろうという予測を元に作ってあります。

https://store.google.com/product/nexus_5x
https://store.google.com/product/nexus_6p

ドキドキでしたが、このスクリプトを仕込んだ結果、ちゃんと通知されていました。

f:id:yuzurus:20150930151034p:plain

予約サイトがオープンした時間は日本時間AM2:20でした! この通知を見て無事予約できました。

iPhone7の予約サイトがオープンしたことをSlackに通知させたい

近い将来発売されるであろうiPhone7。
iPhone7の予約サイトがオープンしたことをSlackに通知させる場合は、先ほどのスクリプトを下記のように変更してください。

# サイトURL prefix
prefixUrl="http://www.apple.com/jp/shop/buy-iphone/"
# 製品名
products=("iphone7")

このスクリプトを仕込んだあなたは、きっと誰よりも早くiPhone7を手に入れる事ができるでしょう!

※URLを予測しているので、失敗したらごめんなさい。
※発表されるであろう日に仕込んでください。

ということでShell Script Advent Calendar 2015 8日目でした。
次は@ryoana14さんです。よろしくお願いします!