覚えたら書く

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

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がスローされます



関連エントリ

アプリ実行中に条件に合ったクラスを抽出する

前回のエントリでFastClasspathScannerを利用して特定のアノテーションが付与されたクラスを抽出するサンプルを実行しました。


FastClasspathScannerでは、他にも色々な条件でクラス等を抽出できます。いくつかサンプルを試してみます


インターフェースクラスを全部取得する

あるパッケージ以下に存在する全てのインターフェースクラスを抽出する場合、以下のAPIを利用します

  • FastClasspathScanner#matchAllInterfaceClasses


■サンプルコード

sample.app.domainというパッケージ以下に存在する全インターフェースクラスを取得する例です

import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;

new FastClasspathScanner("sample.app.domain")
        .matchAllInterfaceClasses(
                clazz -> System.out.println("Found class: " + clazz.getName()))
        .scan();

■実行結果

Found class: sample.app.domain.Interface1
Found class: sample.app.domain.Interface1$InnerInterface
Found class: sample.app.domain.Interface2
Found class: sample.app.domain.SampleInterface


特定のインターフェースを実装したクラスを取得する

特定のインターフェースを実装したクラスを取得する場合、以下のAPIを利用します

  • FastClasspathScanner#matchClassesImplementing


■サンプルコード

sample.appというパッケージ以下に存在するクラスの中でSampleInterfaceというインターフェースを実装したものを取得する例です

import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;

new FastClasspathScanner("sample.app")
        .matchClassesImplementing(SampleInterface.class,
                clazz -> System.out.println("Found class: " + clazz.getName()))
        .scan();

■実行結果

Found class: sample.app.domain.Impl1
Found class: sample.app.domain.Impl3


特定のインターフェースを継承したサブインターフェースを取得する

特定のインターフェースを継承したインターフェースクラスを取得する場合、以下のAPIを利用します

  • FastClasspathScanner#matchSubinterfacesOf


■サンプルコード

sample.appというパッケージ以下に存在するインターフェースの中でSampleInterfaceというインターフェースを継承したものを取得する例です

import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;

new FastClasspathScanner("sample.app")
        .matchSubinterfacesOf(SampleInterface.class,
                clazz -> System.out.println("Found class: " + clazz.getName()))
        .scan();

■実行結果

Found class: sample.app.domain.Interface1


特定のクラスを継承したサブクラスを取得する

特定のクラスを継承したサブクラスを取得する場合、以下のAPIを利用します

  • FastClasspathScanner#matchSubclassesOf


■サンプルコード

sample.appというパッケージ以下に存在するクラスの中でAbstractSampleというクラスを継承したものを取得する例です

import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;

new FastClasspathScanner("sample.app")
        .matchSubclassesOf(AbstractSample.class,
                clazz -> System.out.println("Found class: " + clazz.getName()))
        .scan();

■実行結果

Found class: sample.app.domain.SubClass
Found class: sample.app.domain.SubClass$InnerSubClass


メソッドに特定のアノテーションが付与されたものを取得する

メソッドに特定のアノテーションが付与されているクラスとそのメソッドを取得する場合以下のAPIを利用します

  • FastClasspathScanner#matchClassesWithMethodAnnotation


■サンプルコード

sample.app.domainというパッケージ以下に存在するクラスの中で@MethodAnnotationというアノテーションが付与されているメソッドを持つクラスとそのメソッドを取得する例です

import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;

new FastClasspathScanner("sample.app.domain")
        .matchClassesWithMethodAnnotation(MethodAnnotation.class,
                (clazz, method) -> System.out.println("Found : " + clazz.getName() + "#" + method.getName()))
        .scan();

■実行結果

Found : sample.app.domain.SimpleClass#printHello
Found : sample.app.domain.SubClass#execute1
Found : sample.app.domain.SubClass#execute2



関連エントリ

特定のアノテーションが付与されたクラスを実行時に抽出したい

Javaアプリケーションの処理内で、特定のパッケージ配下にあるクラスの中で、指定の条件を満たすものを抽出したい場合があります。

例えば、アプリ実行中に特定のアノテーションが付与されたクラスを探したい 等がこれに該当します。

このような場合、FastClasspathScannerを利用すると簡単に実現できます。

FastClasspathScannerを用いると、classpathをスキャンして指定の条件に合うクラスを抽出することができます。


(追記: FastClasspathScannerClassGraph に名称が変更された? ようです)


準備

pom.xmlに以下を追記します。

<dependency>
    <groupId>io.github.lukehutch</groupId>
    <artifactId>fast-classpath-scanner</artifactId>
    <version>{lib-version}</version>
</dependency>

今回のサンプルではバージョンとして 2.0.13 を指定しました


サンプル

@SampleAnnotation というアノテーションを用意して、sample.appパッケージ以下にあるクラスの中でそのアノテーションが付与されているクラスを取得します。


■サンプルコード

クラスに付与するアノテーション(@SampleAnnotation)は以下です。(何の変哲もないアノテーションです)

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface SampleAnnotation {
}


FastClasspathScannerを用いて@SampleAnnotationが付与されたクラスを探す処理は以下のように記述できます。

import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;


public class SampleMain {

    public static void main(String[] args) {
        new FastClasspathScanner("sample.app")
                .matchClassesWithAnnotation(SampleAnnotation.class,
                        clazz -> System.out.println("Found class : " + clazz.getName()))
                .scan();
    }
}

FastClasspathScanner#matchClassesWithAnnotation で 対象のアノテーションを指定しています


■実行結果

Found class : sample.app.domain.Logic1
Found class : sample.app.util.Util1

(条件を明確に記載していないので分かりにくいのですが)@SampleAnnotation が 付与されたクラスだけが抽出されました


補足

今回は、特定のアノテーションが付与されたクラスを探すサンプルを実行しましたが、FastClasspathScannerでは他にも色々な条件でクラス等を探すことができます。



関連エントリ