覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

MBassador - サブクラスのメッセージ受信時の動き

MBassadorを用いてメッセージのハンドリングを行う場合のメッセージのサブクラスに対する動きを確認します。


本エントリでは、MessageObj とその子クラスの SubMessageObj を定義しました。これらをEventBusにpublishします。

■メッセージクラスの定義

public class MessageObj {

    protected final String rawMsg;

    public MessageObj(String rawMsg) {
        this.rawMsg = rawMsg;
    }

    String message() {
        return "MessageObj -> " + rawMsg;
    }
}
public class SubMessageObj extends MessageObj {

    public SubMessageObj(String rawMsg) {
        super(rawMsg);
    }

    @Override
    public String message() {
        return "MessageImpl -> " + rawMsg;
    }
}


デフォルトでのサブクラスのメッセージ受信時の動き

■Handlerの定義

MessageObjを引数に取るhandle1とSubMessageObjを引数に取るhandle2を定義しました

import net.engio.mbassy.listener.Handler;

class MessageObjListener {

    @Handler
    public void handle1(MessageObj msg){
        System.out.println("handle [MessageObj]: " + msg.message());
    }

    @Handler
    public void handle2(SubMessageObj msg){
        System.out.println("handle [SubMessageObj]: " + msg.message());
    }
}


■EventBusを作成してメッセージ送信

EventBusに上で定義したハンドラをセットしてMessageObjSubMessageObjのインスタンスを送信し動きを確認します

import net.engio.mbassy.bus.MBassador;


public class Main {

    public static void main(String[] args) {
        MBassador<Object> bus = new MBassador<>();
        bus.subscribe(new MessageObjListener());

        bus.publishAsync(new MessageObj("Hello World! - 1"));
        bus.publishAsync(new MessageObj("Hello World! - 2"));
        bus.publishAsync(new SubMessageObj("Hello World! - 1000"));
        bus.publishAsync(new SubMessageObj("Hello World! - 2000"));

        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("\n>>> App end");
    }
}


■実行結果

handle [MessageObj]: MessageObj -> Hello World! - 1
handle [MessageObj]: MessageObj -> Hello World! - 2
handle [SubMessageObj]: SubMessageObj -> Hello World! - 1000
handle [MessageObj]: SubMessageObj -> Hello World! - 1000
handle [SubMessageObj]: SubMessageObj -> Hello World! - 2000
handle [MessageObj]: SubMessageObj -> Hello World! - 2000

>>> App end

MessageObjListener#handle1の引数はMessageObjですが、MessageObjおよびその子クラスのSubMessageObjも受け取っていることが分かります。


サブクラスのメッセージ受信しないようにする

場合によってはサブクラスのメッセージを受信したくない場合もあります。その場合は@HandlerアノテーションのrejectSubtypesパラメータを利用します

■Handlerの定義

ハンドラとして扱うメソッドに@Handlerを付与するとともにrejectSubtypesパラメータにtrueを指定します。これで引数のサブクラスのメッセージが受信しなくなります。

import net.engio.mbassy.listener.Handler;

class MessageObjListener {

    @Handler(rejectSubtypes = true)
    public void handle1(MessageObj msg){
        System.out.println("handle [MessageObj]: " + msg.message());
    }

    @Handler
    public void handle2(SubMessageObj msg){
        System.out.println("handle [SubMessageObj]: " + msg.message());
    }
}


■EventBusを作成してメッセージ送信

EventBusに上で定義したハンドラをセットしてMessageObjSubMessageObjのインスタンスを送信し動きを確認します

import net.engio.mbassy.bus.MBassador;


public class Main {

    public static void main(String[] args) {
        MBassador<Object> bus = new MBassador<>();
        bus.subscribe(new MessageObjListener());

        bus.publishAsync(new MessageObj("Hello World! - 1"));
        bus.publishAsync(new MessageObj("Hello World! - 2"));
        bus.publishAsync(new SubMessageObj("Hello World! - 1000"));
        bus.publishAsync(new SubMessageObj("Hello World! - 2000"));

        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("\n>>> App end");
    }
}


■実行結果

handle [MessageObj]: MessageObj -> Hello World! - 1
handle [MessageObj]: MessageObj -> Hello World! - 2
handle [SubMessageObj]: SubMessageObj -> Hello World! - 1000
handle [SubMessageObj]: SubMessageObj -> Hello World! - 2000

>>> App end

MessageObjListener#handle1MessageObjのメッセージだけ受け取り、その子クラスのSubMessageObjのメッセージを受信しなくなっていることが分かります。



関連エントリ

MBassador - 異なる型のメッセージを1つのHandlerでハンドリングする

MBassadorを用いてメッセージのハンドリングを行う場合、通常メッセージの型ごとにハンドラを用意することになりますが、
@Envelopedアノテーションを使用することで複数の異なる型のメッセージを1つのハンドラでハンドリグすることが可能となります。

以下実行サンプルです


■Handlerの定義

ハンドラとして扱うメソッドに@Handlerを付与するとともに、@Envelopedアノテーションを付与します。
そしてこの@Envelopedアノテーションのmessagesパラメータに、このハンドラで取り扱うメッセージの型を指定します。
さらにハンドラとするメソッドの引数の型をMessageEnvelopeにします。

import net.engio.mbassy.listener.Enveloped;
import net.engio.mbassy.listener.Handler;
import net.engio.mbassy.subscription.MessageEnvelope;

import java.time.LocalDate;

class SimpleMsgListener {

    @Handler
    @Enveloped(messages = {Integer.class, String.class, LocalDate.class})
    public void handle(MessageEnvelope envelope) {
        System.out.println("type = " + envelope.getMessage().getClass() + ", msg = " + envelope.getMessage());
    }
}


■EventBusを作成してメッセージ送信

EventBusに上で定義したハンドラをセットしてメッセージを送信し動きを確認します

import net.engio.mbassy.bus.MBassador;

import java.time.LocalDate;

public class SampleLauncher {

    public static void main(String[] args) {
        System.out.println(">>> App start");

        MBassador eventBus = new MBassador();
        SimpleMsgListener listener = new SimpleMsgListener();
        eventBus.subscribe(listener);    // ハンドラの登録

        // StringとintとLocalDateのメッセージ送信
        for (int i = 0; i < 3; i++) {
            eventBus.publishAsync(i);
            eventBus.publishAsync("Hello World" + i);
            eventBus.publishAsync(LocalDate.of(2017, 2, i + 10));
        }

        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("\n>>> App end");
    }
}

■実行結果

>>> App start
type = class java.lang.Integer, msg = 0
type = class java.lang.String, msg = Hello World0
type = class java.time.LocalDate, msg = 2017-02-10
type = class java.lang.Integer, msg = 1
type = class java.lang.String, msg = Hello World1
type = class java.time.LocalDate, msg = 2017-02-11
type = class java.lang.String, msg = Hello World2
type = class java.time.LocalDate, msg = 2017-02-12
type = class java.lang.Integer, msg = 2

>>> App end

無事に、一つのハンドラでString型、int型、LocalDate型のメッセージをハンドリングできました。



関連エントリ

TensorFlow User Group #3 行ってきたよ

TensorFlow User Group #3に行ってきました。

今回は開催回数が奇数回だったので、TensorFlowの利用事例の発表がメインでした。

以下自分用のメモです


概要

  • 場所: Google ジャパン
  • 日時: 2017/2/22 19:00~


Retty流 「2200万ユーザさんを支える機械学習基盤」の作り方

Retty株式会社 樽石将人 氏

https://speakerdeck.com/taru0216/tfug-number-3-rettyliu-2200mo-yuzasanwozhi-eruji-jie-xue-xi-ji-pan-falsezuo-rifang-dockerbian

  • Rettyのサービス
    • 「人から探せる」グルメサービスを運営
    • 「この人のおすすめは自分に合ってそう」というのが直感的にわかるユーザ体験を作っている
  • グルメ情報に関する信頼性等向上の取り組み
    • 実名・顔写真公開による投稿
    • 投稿内容をリアルな友達・知人に見てもらう
    • 二次著作の許諾
  • Rettyのデータの種類と規模
    • 口コミ
    • 画像
    • 人の行動・ソーシャルグラフ
    • 店舗情報
  • Retty機械学習マシン
    • GPU付自作PCを全自動ネットインストールでセットアップ
    • ブラウザでも開発できます
    • Kubernets(Docker) + juju + MAAS
    • すべてのDockerイメージはコアDocker(retty2-runtime-core)から継承
    • KubernetsのDaemon Setで全マシンにデプロイ
    • Kubernetsはjuju & MAASでネット自動インストール


DeepLearningの自然言語処理への応用事例 -文字単位CNNによる口コミ分類-

Retty株式会社 氏原淳志 氏

  • 日本語の自然言語の処理の大きな壁。単語の切れ目がさっぱりわからない
    • 分かち書き(形態素解析)
    • これには辞書が必要。未知の単語には対応できない。
  • 自然言語処理でのDeep Learning
    • 画像は1pixel単位でCNNにかけてる。なら文字列も単語単位ではなく文字単位にCNNにかける
    • 文章を文字単位分割⇒UNICODEに変換⇒それぞれの文字N次元ベクトル⇒CNN
  • デートに使える店の口コミ
    • 焼き鳥店の口コミでも内容によって分類結果が大きく異なった
  • 教師作りのソルジャーは必要


SENSYにおける深層学習活用事例とTensorFlowの悩み相談

カラフル・ボード株式会社 武部雄一 氏

  • SENSY
    • SENSY=パーソナルAI
    • SENSYの位置づけ=特化型人工知能
  • SENSYを応用したtoBソリューション
    • AI技術提供
      • toCサービスで語りにした成果物を応用して企業へ提供・導入
  • SENSYにおける機械学習/深層学習の活用事例
    • 画像に対するカテゴリや雰囲気のタグ付け
    • 画像背景の透過
    • コーディネートの自動作成
  • 事例
    • 広告CVR予測 with TensorFlow
      • マーチャントとパートナーの特徴量作成にオートエンコーダーを利用。RBFNを用いてCVRの予測回帰モデルを作成
      • 今回の対象ではシグモイド関数を利用
    • ファッションアイテムの推薦最適化
      • ヒートマップは、画像特徴量をt-SNEで2次元に落とし込み、各画像の推定スコアを色で表現
      • CNNはChainer
      • 可視化されていると顧客との共通認識を持ちやすい
      • 今後、分散化による速度向上を目的にTensorFlow / Cloud ML に変えていく予定
  • Chainerで作った既存プロジェクト、TensorFlowでもやってみたいとは思うが各フレームワークの設計思想が異なりモデルの変換は絶望的
  • Chainerは実装しながらモデルを創れるのでミスを発見しやすい。比べてTensorFlowはミスや想定外箇所を特定しづらい
  • TensorFLowは小一時間で基本構成要素を理解できる


NNで広告配信のユーザー最適化をやってみた

GMOインターネット次世代システム研究室 勝田隼一郎 氏

  • AkaNe
    • 広告配信のルール⇒学習Model
    • Model候補
      • オーディエンス拡張
        • 特徴量の空間でclickするUserに近いclusterを見つけ、拡張Userとして配信ターゲットにする
        • 今回はこれは不採用
      • MLP
        • 配信履歴よりclickしたUserしてないUserに分類
        • これを教師データとしてMLP(多層パーセプトロン)で学習を行う
    • Embedding
      • スパースなデータを圧縮(Embedding)する必要があった
      • 大量データを扱うため、Apache SparkのMLibのALSを用いた
  • 実配信によるABテストで従来に比べてCTRが2倍に向上したことを確認できた



関連エントリ

MBassador - EventBus内で発生した例外をハンドリングする

MBassadorを用いてイベントのハンドリングを行っている場合、デフォルトではハンドラ内部で例外が発生しても何事も無かったようになってしまい、エラーが発生したことすら分からないような状態になってしまいます。

さすがにこれはまずいので、基本的にはエラーをハンドリングをするためのエラーハンドラをEventBusに対してセットする必要があります。


エラーハンドラをセットしない場合

まず、エラーハンドラをセットしなかった場合の動きを確認しておきます

■Handlerの定義

ハンドラ内であえてIllegalArgumentExceptionをスローするようにしています

import net.engio.mbassy.listener.Handler;

public class MsgHandler {

    @Handler
    public void handlMessage(String msg){
        System.out.println("handle start. msg=" + msg);

        System.out.println("---------------");

        throw new IllegalArgumentException("null parameter");
    }
}

■EventBusを作成してメッセージ送信

EventBusに上で定義したハンドラをセットしてメッセージを送信しています。

import net.engio.mbassy.bus.MBassador;

public static void main(String[] args) {

    MBassador<Object> bus = new MBassador<>();
    bus.subscribe(new MsgHandler());

    bus.publishAsync("msg-1");

    try {
        Thread.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("\n>>> App end");
}

■実行結果

handle start. msg=msg-1
---------------

>>> App end

エラー(IllegalArgumentException)が発生したことが全く分からない状態になっています


エラーハンドラをセットする場合

次にEventBusに対してエラーハンドラをセットするサンプルです。
(メッセージのHandlerには先の例と同じMsgHandlerクラスを使います)

■エラーハンドラの定義

エラーハンドラはIPublicationErrorHandlerインターフェースを実装したクラスとして定義する必要があります
(EventBus内での例外発生時に、handleErrorメソッドにエラーの詳細情報が渡されて呼び出されます。)

import net.engio.mbassy.bus.error.IPublicationErrorHandler;
import net.engio.mbassy.bus.error.PublicationError;

final class EventBusErrorHandler implements IPublicationErrorHandler {


    @Override
    public void handleError(PublicationError publicationError) {
        System.out.println("publicationError = " + publicationError);

        publicationError.getCause().printStackTrace();
    }
}

■EventBusを作成してメッセージ送信

EventBusに上で定義したハンドラをセットしてメッセージを送信しています。
先の例とは違いIBusConfigurationを生成してIBusConfiguration#addPublicationErrorHandlerでエラーハンドラをセットします。
そして生成したIBusConfigurationをEventBus(MBassadorのコンストラクタ)に渡します。

import net.engio.mbassy.bus.MBassador;
import net.engio.mbassy.bus.config.BusConfiguration;
import net.engio.mbassy.bus.config.Feature;
import net.engio.mbassy.bus.config.IBusConfiguration;

public static void main(String[] args) {

    IBusConfiguration config = new BusConfiguration()
            .addFeature(Feature.SyncPubSub.Default())
            .addFeature(Feature.AsynchronousHandlerInvocation.Default())
            .addFeature(Feature.AsynchronousMessageDispatch.Default())
            .addPublicationErrorHandler(new EventBusErrorHandler());

    MBassador<Object> bus = new MBassador<>(config);
    bus.subscribe(new MsgHandler());

    bus.publishAsync("msg-1");

    try {
        Thread.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("\n>>> App end");
}

■実行結果

handle start. msg=msg-1
---------------
publicationError = PublicationError{
    cause=java.lang.reflect.InvocationTargetException
    message='Error during invocation of message handler. There might be an access rights problem. Do you use non public inner classes?'
    handler=public void trial.app.mbassador.ErrorMsgHandler.handlMessage(java.lang.String)
    listener=trial.app.mbassador.ErrorMsgHandler@68de145
    publishedMessage=msg-1}
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at net.engio.mbassy.dispatch.ReflectiveHandlerInvocation.invoke(ReflectiveHandlerInvocation.java:29)
    at net.engio.mbassy.dispatch.MessageDispatcher.dispatch(MessageDispatcher.java:30)
    at net.engio.mbassy.dispatch.FilteredMessageDispatcher.dispatch(FilteredMessageDispatcher.java:42)
    at net.engio.mbassy.subscription.Subscription.publish(Subscription.java:72)
    at net.engio.mbassy.bus.MessagePublication.execute(MessagePublication.java:49)
    at net.engio.mbassy.bus.AbstractSyncAsyncMessageBus$1.run(AbstractSyncAsyncMessageBus.java:67)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalArgumentException: null parameter
    at trial.app.mbassador.ErrorMsgHandler.handlMessage(ErrorMsgHandler.java:13)
    ... 11 more

>>> App end

メッセージのハンドラ内で例外がスローされた際に、エラーハンドラが動作していることが分かります。
エラー発生時の原因例外はPublicationError#getCauseから辿ることができます。



関連エントリ

String#joinで文字列連結

Java8で増えたString#joinを使うと配列やListの要素を文字列連結するのが楽です。地味に便利です。

Listをカンマで連結する例で動きの確認をしておきます


複数要素の場合

List<String> twoList = Arrays.asList("A", "B");
List<String> threeList = Arrays.asList("A", "B", "C");

System.out.println(twoList + " -> " + String.join(",", twoList));
System.out.println(threeList + " -> " + String.join(",", threeList));

■実行結果

[A, B] -> A,B
[A, B, C] -> A,B,C

想定通りに指定したデリミタで各要素が連結されます


1要素の場合

List<String> singleList = Collections.singletonList("Hello");

System.out.println(singleList + " -> " + String.join(",", singleList));

■実行結果

[Hello] -> Hello

1要素でも正常に動作します。当然ですが連結結果にデリミタの文字は現れません。


要素数=0の場合

List<String> emptyList = Collections.emptyList();

System.out.println(emptyList + " -> " + String.join(",", emptyList));

■実行結果

[] -> 

空Listを連結すると空文字列が返されます


要素にnullを含む場合

List<String> includeNullList = Arrays.asList("A", null, "C");

System.out.println(includeNullList + " -> " + String.join(",", includeNullList));

■実行結果

[A, null, C] -> A,null,C

nullの要素は、"null"という文字列として扱われて連結されます


List自体がnullの場合

List<String> nullList = null;
System.out.println(nullList + " -> " + String.join(",", nullList));

■実行結果

Exception in thread "main" java.lang.NullPointerException
    at java.util.Objects.requireNonNull(Objects.java:203)
    at java.lang.String.join(String.java:2501)
    at trial.app.string.StringJoinLauncher.main(StringJoinLauncher.java:26)

List自体がnullの場合はNullPointerExceptionがスローされます



関連エントリ