2014-12-18

#JJUG ナイトセミナー「機械学習・自然言語処理特集!」で Java でカジュアルに機械学習する話をしてきました

はじめに

@making さんからオファーをいただいて、12/17 (水) の JJUG ナイトセミナーで Java でカジュアルに機械学習するお話をしてきました。

Java というコミュニティ的に機械学習ガチ勢の方が圧倒的に少ないことが予想されたので、あんまり小難しい技術の話は含めず初学者向けの内容構成とし、機械学習をシステムに組み込んでいく上でこれは大事だよね… と個人的に思っているポイントを整理してスライドにしていったら、文字が多くなってしまった上に若干エモくて Java 的な話がほんのり程度になってしまったのが今回の反省点です。

なお講演時の動画が YouTube にアップロードされています ので、より詳しい話を知りたい場合はこちらをご覧ください。

Java と機械学習

Java で動く機械学習ライブラリ・フレームワークはわりと数多あって、でも Java で機械学習を利用する際のワークフローをすべてカバーするのがいいかというとそんなことはない、と私は考えています。

特に、モデルを作る際の feature enginnering やそのモデルの評価など、試行錯誤的に・繰り返し手早く実行したい操作については、Java のソースコードをコンパイルして一連の処理を最初から実行する… みたいなことをするよりも、R や IPython (Notebook) などを使ってインタラクティブ・アドホックな操作ができたほうが断然効率がいいのではないでしょうか (僕は R も IPython もほとんど使ったことないので、この辺りは僕の主観が色濃く出ています…)。

じゃあ Java で機械学習する価値がないか、というとそんなことはなくて、何だかんだで Java のコードはそこそこ速く動いてくれるので、サービス的に速度・レイテンシ的な要求がある場合や、機械学習を組み込もうとしているシステムが Java で作られているならば機械学習の部分も Java で統一したい… など主にシステム化以降のところで Java の機械学習を活用するのがいいんじゃないかと思っています。

指標値・メトリクスを設定して機械学習を活用する

これは懇親会の席でとある方から伺ったお話にもあったのですが、ビジネス的 or 精度的なメトリクスを設定せず、ただ何となく流行っているから的な理由で機械学習を導入する… という話をちらほらと耳にします。

わりと身近なレコメンデーション一つをとっても、そのレコメンデーションによって全体の売上が何 % 向上したのか、みたいなビジネス上の指標値を設定することは可能だと思います。そのような指標値をせずに機械学習を導入してしまうと、それはチューニングはできない・機械学習アルゴリズムをおいそれと変更することもできない、ただただ辛みのつまったパンドラの箱に成り下がってしまうので、僕の身の回りでこういうことにならないように、気をつけないといけないよなー、と思った次第でした。

まとめ・感想

JJUG 主催のセミナーにはいままで聴講者としてたまに顔を出していましたが、まさか機械学習のネタで登壇する日が来るとは思ってもみませんでした (以前の納涼 LT 大会にスピーカーとしてエントリしたものの、会社イベントを優先する必要があって止む無く断念したこともありましたが…)。

JJUG は渋谷java ほど カジュアル (= 自分の好き勝手・気ままに話したいことを話す) なコミュニティではないので、発表する内容は理解を優先した構成にしてみました。その分、「この内容は果たして聴講者に伝わるんであろうか」とヤキモキしたりすることもあって、久々に資料作りで消耗しました。でも楽しかったですね!

2:13No comments

2014-12-13

第九回 #渋谷java で飛び入り LT してきました (Slack 用 Logback Appender を実装する話)

はじめに

会社の合宿予定が入っていて参加できそうになかったので、やむなく参加を見送っていた 12/13 (土) の 第九回 渋谷java でしたが、思ったより早く東京に戻ってくることができたので、下記のように

と参加できそうなのかつぶやいてみたところ、カジュアル無茶ぶりコメントをたけぞー先生よりいただいたので適当にネタをでっちあげて LT 発表してきたのでした。

SmartNews ♥ Slack

@amachino さんが先日 Slack の Advent calendar に投稿したとおり、スマートニュースでは 社内のコミュニケーションに Slack を活用しています。 通常の人同士の会話だけでなく Jenkins からの通知や Datadog からのアラートなども Slack に集約しており、普段扱っているコミュニケーションツールの中でも特段利用時間が長い状況になっています。

そのため、「プロダクション環境で動いている Java アプリケーションの実行状況・進捗や、例外が発生したときの詳細を Slack 経由で見たい・知りたい」という要望が Java エンジニア的には自然と沸き起こってくるわけです。

Logback の Appender を実装する

そういうわけで、Java アプリケーションからお手軽に Slack にメッセージをポストする方法として、Logback の Appender 実装を用意する方法を紹介しました。

プロダクション環境で利用するにあたって、HTTP(S) 経由で Slack API を呼び出す際に

  • ロガーメソッドを呼び出す元の実行をブロックしないように (すぐに制御が戻るように) 非同期処理する
  • 非同期処理で Slack API を叩くにしてもログのイベントが前後するのは嫌なので (Array)BlockingQueue を使ってログイベントを直列化する
  • HTTP 通信で失敗したときのためにリトライ処理を入れる

などの仕組みを入れ込んだ実装としています (発表中にお見せしたコードはまだ整備しきれていないので現在は非公開としていますが、いずれ公開する予定です)。

会場参加者からのツッコミ

これは発表の際に java-ja から来られた方 からいただいたツッコミなのですが、Appender 内部で非同期処理をする場合は、 ContextAwareBase クラス (AppenderBase クラスの親クラス) が context フィールドで保持しているオブジェクトの Context#getExecutorService() メソッドの戻り値を使って、その ExecutorService オブジェクト経由でスレッドを立ち上げるといいそうです。

まとめ・感想

  • Logback の Appender 実装はツボさえ押さえれば、あんまり難しくないよ
  • Logback のインタフェースで Slack API を叩けるようになれば、Logback 使っている既存の Java アプリケーションなら容易に Slack に通知を出せるようになるね!
  • java-ja から来た人、ちょっとコワイけど、丁寧な説明だったりアドバイスをいただけたりと、とっても親切だね!
19:441 comment

2014-10-22

僕の Redshift の圧縮エンコード使い分けメモ

最近 Redshift を触っていて、圧縮エンコードについて調べることがあったのでメモメモしておきます。なお、2014 年 10 月時点での情報であることと、わりとざっくりとした確認だったので不正確な情報が混じっているかもしれないのでご承知おきください。

文字列データ (VARCHAR など)

  • 選択肢としては、 text255, text32k, bytedict, lzo あたり
    • 空間効率的には text255 text32k はあんまりよろしくなさげ
    • また、text255 はカラムサイズが 255 を超えるカラムに適用することができない (厳密には、255 バイトを超える文字列が入っている場合に適用できない、となる)
  • 値の種類数が少なく (目安として、256 個以下ぐらい)、かつ種類が増える可能性が低い場合
    • bytedict もしくは lzo を選ぶのがよい
    • 種類数が 256 を超える場合であっても、出現頻度に偏りがある場合は bytedict を選択するのもありっぽい
    • ただし、文字列の平均長が長い場合は bytedict はあんまり性能よろしくない?
  • 最初は値の種類数が少ないが、時間経過とともに使われる値の種類が徐々に増える場合
    • lzo を選ぶのがよい
  • 時間経過とともに使われる値の種類が増えるが、ソートキーを考慮すると使われる値の種類が少数に限定されると想定できる場合
    • lzo を選ぶのがよい
  • 値の種類数が多い場合
    • lzo を選ぶのがよい

というわけで、迷ったら lzo を選べばだいたいいい感じだと思う。

数値データ

  • 選択肢としては、 bytedict, delta/delta32k, mostly8/16/32 あたり
    • lzo はあんまり向いてなさげ
  • 値の種類数が少なく、かつ種類が増える可能性が低い場合
    • 0 付近の値が頻出する場合は mostly8 を選ぶのがよい
    • それ以外では bytedict を選ぶのがよい
  • 時間経過とともに使われる値の種類が増えるが、ソートキーを考慮すると使われる値の種類が少数に限定されると想定できる場合
    • bytedict でよさそう
  • 値の種類数は多いが、出現する値に偏りがある場合
    • 0 近辺に偏っているなら、 mostly8/16/32 などを選ぶのがよい
    • それ以外なら bytedict がよさそう
  • ソートキーを考慮したときに値が昇順に並ぶ場合
    • delta/delta32k を選ぶのがよい
  • それ以外の場合
    • 諦めて、圧縮エンコードは設定しない (raw にする)

正直言って、数値データは実際のデータで性能を確認してみないとわからないと思う…

その他

  • runlength の使いどころがよくわからない…
  • 時間計算量
    • bytedict は時間計算量ちょっと高めな傾向がみられる
    • lzo はバランスがとれている感じ
9:30No comments

2014-09-21

第八回 #渋谷java にて、認証系を Java 8 で自作する話をしてきました

はじめに

毎回、他の発表者の方々が有益情報を発表してくれる中で僕一人が誰得情報をひたすら発信しているわけですが、今回もご多分に漏れず Java 8 で認証系を自作する、というニッチな話をしてきました。

昨今のパスワード流出系のセキュリティインシデントが業界内で話題になるたびに「認証系なんてものは自作するようなもんじゃない」という認識が醸成されつつあると思います。しかし、マイナー or オレオレ Web アプリケーションフレームワークなどを利用しているとそうもいってはおられず、認証系が用意されていないときはやむなく認証系の自前実装が要求されることもあるでしょう。自分自身も周りを見渡していても、過去何度かそういうことがありました。

ただ、実装者の認証セキュリティに対する認識や知識によって脆弱な認証系を作り込むことも少なくなく、そういうのをなるべく減らしたいなー、という思いを込めて今回の発表に至りました。

どうしても認証系を自前実装しなければならないときに気をつけたいこと

前述した発表資料に書いてあることにはなりますが、今一度文章に起こしておきます。

アカウント別 salt の生成と暗号論的擬似乱数生成器の利用

セキュリティを少しかじったことがある人であれば「salt ってやつとパスワードを組み合わせたものをハッシュする」ということぐらいは知っているものと思います。ただ、どうも salt だけがひとり歩きしていることがあったりして、アプリケーションで共通の事前に用意された salt を使いまわす、みたいなアレな実装を見たことがある人はそこそこいるんじゃないでしょうか?

本来は、認証単位であるアカウント別に salt を生成すべきであり、かつ salt の生成には暗号論的擬似乱数生成器を用いるべきです。この salt の生成を Java で実現するならば、

    static byte[] newSalt(int length) {
        try {
            byte[] result = new byte[length];

            SecureRandom.getInstance(
                    // "NativePRNGNonBlocking"
                    // "NativePRNGBlocking"
                    "SHA1PRNG"
            ).nextBytes(result);

            return result;

        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

のような実装になることでしょう。 SecureRandom.getInstance() の呼び出しで指定しているのは擬似乱数生成アルゴリズムで、Java 7 以前は SHA1PRNG だけだったのが、Java 8 では新たに

  • NativePRNG
  • NativePRNGBlocking
  • NativePRNGNonBlocking

が (Windows を除く) *nix-like なプラットフォーム向け JRE にて導入されたようです。

暗号学的ハッシュ関数の利用とストレッチング

salt を生成したら、あとはパスワードと組み合わせてハッシュ化、なのですが、これも単に SHA-* を使えばいい、というほど簡単ではありません。

結論から言ってしまえば、PBKDF2 などのキー派生 (導出?) 関数を使ってストレッチングすべき、となります。

Java 7 では PBKDF2 の実装は PBKDF2WithHmacSHA1 しか用意されていませんでしたが、Java 8 にて

  • PBKDF2WithHmacSHA224
  • PBKDF2WithHmacSHA256
  • PBKDF2WithHmacSHA384
  • PBKDF2WithHmacSHA512

の 4 つが導入されました。この PBKDF2 を用いたパスワードのハッシュ化コードは以下になります。

    static byte[] hash(String password, byte[] salt, int numIterations, int numHashLength) {
        PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, numIterations, numHashLength);

        SecretKeyFactory factory;
        try {
            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");

        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }

        try {
            return factory.generateSecret(keySpec)
                    .getEncoded();

        } catch (InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }
    }

まとめ

なんだかんだ書いてはいますが、やっぱり僕含めて素人は認証系の実装をするのはやめたほうがいいですね (過去何度もダメな認証系を作ってしまったし…)。

既存ライブラリの Apache Shiro とか Spring Security とか、僕は使ったことないですが、多分このあたりの面倒なお仕事をこなしえくれるのではないでしょうか。

なお、

こういうライブラリもあるようですよ!

参考文献

2:52No comments

2014-09-17

"Can't connect to MySQL server on 'HOST_NAME' (111)" のエラーが出たときの原因の一つと対処法

VM 上の Ubuntu に apt-get で mysql-server-5.6 をインストールし、VM の外から VM 内部の mysqld に接続しようとしたときにこのエラーが発生した。

原因は my.cnf に

bind-address = 127.0.0.1
が設定されていたためであった。

この行を my.cnf から削りとって、ようやくVM 外部から mysqld に接続できるようになったけど、毎回こんな感じのトラブルで時間を喰われてしまっているわけで、MySQL 力が低いと何かと生きるのが辛い…

23:59No comments

2014-09-15

Java のプロパティファイルは UTF-8 エンコーディングで記述したい

「えーマジ native2ascii!?」「プロパティエディタプラグインが許されるのは J2SE 5.0 までだよねー」

ジャバエンジニアはそろそろ、プロパティファイルを扱う場合に native2ascii やら各種プロパティエディタプラグインやらの呪縛から解放されてもいいと思うのです…

概要

Java でアプリケーションの各種設定を記述したプロパティファイル (.properties) を取り扱う場合、 Properties クラスもしくは ResourceBundle クラスのお世話になるかと思います。

一昔前までは、プロパティファイル内で日本語などいわゆるマルチバイト文字を含む場合に、 native2ascii コマンドを利用して ASCII コードの文字のみで表現できる ユニコードエスケープ (!= UTF-xx エンコーディング) 表現に変換することが必要とされていました。

しかし、Java は 7 or 8 がメインストリームになり、また UTF-8 エンコーディングされたテキストを取り扱うことがわりと当たり前になりつつある昨今、いまだに Eclispe にわざわざプロパティエディタプラグインを導入してプロパティファイルをしこしこ編集したり、プロダクション環境でユニコードエスケープされたプロパティファイルを四苦八苦しながらパラメータ調整したりする現場があったりなかったりするようなので、ここで native2ascii とかプロパティエディタプラグインを必要と せずに UTF-8 エンコーディングされたプロパティファイルを扱う方法についてメモしておきます。

UTF-8 エンコーディングされたプロパティファイルを取り扱う方法

Properties クラスと ResourceBundle クラスそれぞれについて説明します。

その 1 : Properties クラスを使う場合

Properties#load(Reader) メソッドを呼び出す際の Reader オブジェクトを、UTF-8 エンコーディング指定した InputStreamReader オブジェクトにすれば OK です。

すなわち、以下のような実装になるでしょう。

その 2 : ResourceBundle クラスを使う場合

こちらは若干複雑になりますが、 ResourceBundle.Control クラスのサブクラスを用意して対処する方法をとります。

同クラスの newBundle() メソッドのオーバーライド実装にて、 Properties のときと同様に UTF-8 エンコーディング指定した InputStreamReader オブジェクトを用意し、 PropertyResourceBundle クラスのコンストラクタ引数にそのオブジェクトを指定します。

そして実際に ResourceBundle クラスのオブジェクトを取得する際に、その ResourceBundle.Control サブクラスのオブジェクトを ResourceBundle.getBundle(String, ResourceBundle.Control) メソッドの二つ目の引数に指定してやることで、UTF-8 エンコーディングなプロパティファイルを取り扱うことができるようになります。

実装は以下のとおり。

まとめ

  • PropertiesResourceBundle もどちらも、文字エンコーディング指定した InputStreamReader オブジェクトを扱うようにすれば、 native2ascii 的な呪縛から逃れることができるよ
  • UTF-8 エンコーディングなどにしておけば、プロダクション環境上でプロパティファイルをどうしても確認しなきゃいけなくなった場合でも、プロパティファイルに記述した日本語コメントを頼りにスムーズな確認ができるようになるよね
  • プロパティエディタプラグインはもうオワコン扱いでいいよね
23:173 comments