Pretraining Sentiment Classifiers with Unlabeled Dialog Dataを読んだ

論文読み会をやるので久しぶりに論文を読みました。久しぶりじゃダメなんだけど...。今年のACL2018でYahoo! JAPAN Researchの方が発表された内容です。

以下は雑なメモです。

問題点

  • 教師あり学習である程度いい精度を出すには教師データが10万件程度必要だけど、集めるのが大変
    • 3値分類、3人のアノテーターでmajority voting
    • 大体300人月
    • 24Kドル(240万くらいを高いと見るか安いと見るか)
    • 8時間 * 300日 * 時給1000円
    • もっとスキルのあるアノテータにやってもらうともっとかかる
    • アノテーション基準を勉強してもらう時間などは含んでいない
  • 実験データではラベル付きが8万件、ラベルなしが2000万件
    • 8万件じゃ精度はまだまだサチらない

提案法

  • 提案法は2段階
  • 1: pretrainingとして教師なしの対話データから(tweet-reply pairs)対話モデルを作る
    • encoder-decoder
  • 2: sentiment classifier(encoder-labeler)を教師ありデータを使って学習
    • 1で作ったencoder-decoderモデルのencoderパートを初期値として与え、pretrainingとして生かす
  • 言語モデルをベースにした既存手法もあるが、replyが付くようなtweetは感情に基づくことが多いのではないか?という仮説

モデル

  • dialog modelのベースラインはencoder-decoder
    • 入力は単語レベルじゃなくて文字レベル(絵文字とか顔文字とか多いからこっちのほうが性能高い)

比較した手法

  • 事前学習あり
    • Dial: 提案法(pretrainあり)
    • Lang: tweetのペアなしで言語モデルとして学習したLSTMをpretrainとして使う
    • SeqAE: sequence autoencoderをpretrainとして使う
    • Emo2M, Emo6M: この絵文字/顔文字が入っていたら正例/負例と見なすという一種のdistant supervisionで疑似的な学習データを使ったもの
  • 事前学習なし
    • Default: pretrainなし
    • LogReg, LinSVM: unigramとbigramのbag-of-words

実験結果

  • 学習データを5000件から8万件に変化させたときのF値の変化を見る
  • 学習データ量のサイズに関わらず、提案手法のDialが一貫して他の方法よりよい性能
  • Emo2Mは学習データにノイズが入るためDefaultより低い精度になっているっぽい
    • 例: うらやましいです。おめでとうございます orzがorzが入ってるので負例になってしまう
  • 古典的な機械学習のLogRegLinSVMはデータ量が少ないときにはDefaultよりもよい性能を出している
    • とはいえ、事前学習したDialLangのほうが性能が高い
  • 事前学習されたencoder-decoderの生成結果も感情に基づいている感じで、結果がよくなるのも納得

ゼロから作るDeep Learning ? ―自然言語処理編

ゼロから作るDeep Learning ? ―自然言語処理編

ついに開幕したTリーグを観戦しに行きました

10/24に開幕した卓球リーグ、Tリーグに観戦してきたので日記を書きます。

京都から名古屋の武田テバオーシャンアリーナに、以下の3試合を観戦しにいきました。

  • 10/27 TT彩たま vs 岡山リベッツ
  • 10/27 木下マイスター東京 vs 琉球アスティーダ
  • 10/28 TT彩たま vs 琉球アスティーダ

練習時間の写真撮影は許可されていて、こんな感じでした。圧倒的迫力があった。

よかった点

  • ライブで見れる
    • YouTubeやテレビ中継でも試合動画が見れる世の中になってきたけど、開場の熱量や試合の緊張感はやはりライブならではのものがある
  • 圧倒的にプレーの質が高い
    • 世界ランクトップレベルの選手ばかりなので当たり前といえば当たり前だけど、これをライブで見れる幸せ
    • 回りの人も言っていたけど、練習を見ているだけでチケットの半額は余裕で元を取れるレベル
  • めちゃくちゃ選手との距離感が近い
    • これはアリーナ南の最前列(6000円くらい出すといける席)だったけど、これはもはやチームのベンチ入りをしているといっても過言ではないのではという距離感
    • 選手が目の前の通路を通っていく、うっかりサインをお願いしてしまう人も
      • セキュリティ的に大丈夫なんかという気もしつつ、これだけ近いのはよいですね
  • 開場と同時に入場しても選手が開場で練習しており、間近で練習風景が眺められる
  • 小中学生以下の子どもの観戦が多い
    • 小さいときに一流のプレーを見て衝撃を受けるのはどういう道に進むのであれよい影響を受けると思っています
  • 試合が終わった後、めっちゃ笑顔になる
    • これだけいいプレーを見せてもらったら自然と笑顔になってしまう

改善されるとよさそうな点

あれこれ書いてますが、開場でもアンケートを取っていたのでそちらでも伝えています。クレームとかではなく、Tリーグがこれからも継続的に続く事業であって欲しいなと思うので、こういうところを改善されるといいんじゃないかと思ったところについても書いておきます。

  • ダブルスも3セット先取でいいのではないか
    • 2セット先取だとあまりに一瞬で終わってしまった印象がある。試合全体が長くなりすぎないように考慮してのこともあるかもしれない
  • ハーフタイムは必要かどうか
    • 卓球経験者からするとハーフタイムが入ると緊張感が途切れてしまうので、ハーフタイムがないほうがいいなぁと感じた
    • 一方で、未経験者の人からすると2時間以上の試合を一気に見るのは集中力を使うため、観客への配慮でハーフタイムが導入されているのかもなというもした
  • グッズ売り場が狭い
    • 結構人気でサンプルを見るのに近づけない感じだった。売り場をもう少し広くして欲しい
    • 来客者数が読めなかったので、安全側に倒したんだろうなとは思う
  • 来場者数、もっと増えて欲しい!
    • 水谷、張本が出場する木下マイスター東京の試合はそれなりにお客さんがいたが、その他の試合は正直もっと席が埋まって欲しい。特に自由席
    • 回りの会話を聞いている感じだと卓球経験者の割合が多かったが、未経験者でも楽しめるリーグを目指して欲しい
    • 色々な見方はあると思うけど、収益が上がって事業が継続できるだけのお客さんがこないと厳しい
  • 試合が思ったよりしゅっと始まった
    • 卓球経験者だと「この選手はこういうこういう特徴がある選手だからこの辺に注目するとより試合が楽しめそう」というのが分かるけど、未経験者だとそうではない
    • TVの解説だと選手の特徴をまとめた15秒くらいのPVとかがあったりすると思うけど、そういうのがあると未経験者でもその選手のことが分かるのではないか
    • 開場で配っているパンフレットも戦型、世界ランクくらいしか書いてないので、そういったところがもっと書いてあると楽しみ方が分かるのではないか
  • あまりにいい席すぎてカメラが前にきて見えない時間が多少あった
    • 2日目では改善されていて、カメラを低い位置にして観戦の邪魔になるべくならないように、という配慮がされていました!
  • 黄鎮廷はペンだけど、それ以外の出場選手は大体シェイクドライブで大分偏りがあった
    • 自分はカットマンなので、村松選手の試合を見るのを楽しみにしたけど、今回は出場がなかった

ミーハー的感想

  • 吉村vs丹羽の試合、見てて面白かった
    • お互いチキータを警戒して、フォア前やサイドを切ったところにサーブを出して、あえてレシーブで打たせて3球目でカウンターを狙いにいく、それをいかに防ぐレシーブをするかの読み合いが熱かった
    • 全然甘くない吉村のループを丹羽がサイドを切るコースにカウンターかましていてわけが分からないよ
  • チョンヨンシクのバックブロック、固い
  • どのチームを応援というよりとにかくいいプレー、緊張感のある試合を見たかったので、ひたすらもつれた試合になることを願いながら観戦していた
  • 未経験者の観客をあれだけ定常的に動員できるサッカーや野球はやっぱりすごいんだなぁというのをひしひしと感じた

負ける人は無駄な練習をする―卓球王 勝者のメンタリティー

負ける人は無駄な練習をする―卓球王 勝者のメンタリティー

最近の砂場活動その8: Nginxとrendertronでprerenderingされた結果をクローラに見せる

趣味サイトをVue.jsを使ってSPAとして作っている。人間が見る分には問題ないが、GoogleBotがきたときにレンダリングされていなくて解釈された結果が悲しい感じになっている。

Vue.jsでもサーバーサイドレンダリングができたりするようだが、アプリケーションサーバーはnodeではなくGolangで書いている。そのため、導入が面倒そう。仕方ないので諦めていたが、レンダリングされた結果を返してくれるHTTPサーバーとして振るまってくれるrendertronというものがあるそうだ。Headless Chromeを中で使っている様子。なるほど、こういう使い方ができるのか。

早速導入してみた。nginxでこういう感じで場合分けさせる。

  • 人間がきた場合: nginx(port: 7777) => アプリサーバー(port: 7778)
  • Botがきた場合: nginx(port: 7777) => rendertron(port: 3000) => アプリサーバー(port: 7778)

設定ファイルはこんな感じ。nginx初心者なので、書き方があんまりよくないかもしれない...。

upstream local-backend {
    server localhost:7778;
}

upstream local-rendertron {
    server localhost:3000;
}

server {
    listen 7777 default_server;
    set $prerender 0;
        if ($http_user_agent ~* "googlebot") { # 他にも追加したい場合にはいい感じに正規表現を書く
        set $prerender 1;
    }

    location / {
        if ($prerender = 1) {
            rewrite ^(.*)$ /render/https://www.machine-learning.news$1 break;
            proxy_pass http://local-rendertron;
        }
        root /www/app;
        proxy_set_header Host $http_host;
        expires 1d;
        gzip on;
        gzip_types text/css application/javascript;
    }
    location /api {
        proxy_pass http://local-backend;
        gzip on;
        gzip_types application/json;
    }
}

nginxもアプリサーバーもrendertronもコンテナ使って動かしていて、ECSの1つのタスクの中に同居している。CloudFormationでの設定例はこんな感じ。

  ServerTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: 'go-active-learning'
      Cpu: 256
      Memory: 2048
      NetworkMode: awsvpc
      ExecutionRoleArn: !Sub "arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole"
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        - Name: "go-active-learning-app"
          PortMappings:
            - ContainerPort: 7778
          Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/go-active-learning:latest"
          EntryPoint:
            - "/app/go-active-learning.bin"
            - "serve"
          Essential: "true"
        - Name: "go-active-learning-nginx"
          PortMappings:
            - ContainerPort: 7777
          Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/go-active-learning-nginx:latest"
          Essential: "true"
        - Name: "rendertron"
          Image: "nkonev/rendertron-docker:latest"
          PortMappings:
            - ContainerPort: 3000
          Essential: "true"

これでGoogleBotもいい感じに解釈してくれるようになった。

問題点

トップページはGoogleBotもレンダリングされた結果を解釈してくれるようになってめでたいが、トップページ以外はVue.jsのルーティングがうまく動くようにまだ書けていない。いい感じにする方法があれば教えてください...。

参考

最近の砂場活動その7: Nginxで特定のパスはVPC内からしか接続できないようにする

しょうもないことですが、Nginx初心者なので自分用メモです。用語とか色々間違ってるかもしれない...。

Client => ALB => Nginx => Appのような構成を考える。ヘルスチェックやサーバーの特定の負荷状況を返すエンドポイント(こういうやつ)が存在し、これはAppから返す。これらは普通のユーザーには見せたくなくて、ALBやVPC内部からのみ参照されたい、というユースケース。ALBで細かいことは難しそうなので、Nginxで制御したい。

VPC範囲内のipからのアクセスであれば拒否すればいいじゃんと思ったけど、Nginxに渡ってくるipは何もしないとALBのipが渡ってきてしまう。このままでは素通ししてしまうので、Clientのipを渡して欲しい。remote_addrというのを変更してあげるようにすればよいらしい。それを実現するNginxの設定は例えばこんな感じ。

server {
    listen 7777 default_server;

    set_real_ip_from 10.0.0.0/16;
    real_ip_header X-Forwarded-For;

    location /api/(server_avail|stats) {
        proxy_pass http://local-backend;
        allow 10.0.0.0/16; # 内部ネットワークのみ許可
        deny all;
    }
}

リバースプロキシってやつだ。

これでALBやVPC内に立てたEC2インスタンスからは/api/statsなどが参照でき、VPC外からは403が返るようになった。ちょいちょい練習して、Nginxのconfファイルを見ても頭が真っ白にならないようにしたい...。

困ったこと

Nginxの設定、自信がないので手元で動作確認をしたりテストを書いたりしたいけど、どうするのがいいのか...。趣味のやつだと気軽に本番をぶっこわすけど、仕事だとそうもいかない。

参考

nginx実践入門 (WEB+DB PRESS plus)

nginx実践入門 (WEB+DB PRESS plus)

最近の砂場活動その6: パフォーマンスを改善する

最近は登壇とかでエネルギーを取られて、砂場活動シリーズ(?)全然できていませんでした。せっかく前期に砂場を作ったので、勉強の場にしていきます。現状のボトルネックがどこにあって、どこを改善していきましょう、というループを回します。ISUCONとかやってる人には当たり前のことばかりだと思うけど、その辺あまり慣れていないのでぼちぼちとやっていきます。派手なことななく地味にやっていく。

現状を知る

改善をするためには現状のパフォーマンスを知ることが重要です。趣味サイトはユーザーが自分しかいないので、ベンチマークスクリプトをかけてパフォーマンスを見ていきます。スプレッドシートにまとめていくのは面倒だったので、Mackerelのプラグインを書いて可視化しました。

改善していく中でRDS/ElastiCacheのCPU/メモリ/コネクション数/トラフィック量などが一目で分かるようなダッシュボードも作りました。結果ではなく原因になった場所がどこかを調べるのに便利。

数値だけ見ても分からないことも多いので、CloudWatch LogsにアプリやNginxのログを流すように設定。

あれこれ改善

自分しか使っていないとアホなことをやっていても問題にならないけど、ベンチマークを回すとそういったところが問題点になってくるので改善していく。

  • fullscanが走っているところにDBにインデックスを張る
  • コネクションプールを使い回す
  • N+1を潰す
    • 潰しましょう
  • Redisのパイプラインを使う
    • 1つずつコマンドを送るのではなくまとめてやれる方法
  • 必要なフィールドのみ取得するようにする
    • 特徴量やhtmlもredisに雑に放り込んで学習時には使っていたけど、apiのレスポンスを返すときには使っていない(SELECT *的なことになっていた)
    • 結構巨大なフィールドなので、不必要なものは削って必要なものだけ返すようにする
    • jsonとしてredisのvalueに放り込んでたけど、取ってくるフィールドを選択できるようにハッシュ型に変更した
    • https://github.com/syou6162/go-active-learning/pull/31
  • アプリケーションサーバーやDBのリソースは余ってるけど、レイテンシーが悪化するようになってきた
    • ちょっと悩んでしまったけど、負荷試験をされる側ではなくて負荷試験をする側のネットワーク帯域がボトルネックになっていた
    • 負荷試験を複数のインスタンス(Fargate)から実行するようにして負荷分散
  • goroutineでtoo many open files
  • nginxでToo many open files
    • 何やねんと思ったけど、ファイルディスクリプタの上限に引っかかってしまっていた
    • Fargateで動かしている場合も変更できるので増やした

次回

この辺までやるとあとはFargateの数増やしたり、ElastiCache/RDSのスペックを上げたり、リードレプリカの台数を増やせば捌けるリクエスト数も増やしていけそうなかという目処が立ってきた。次回作にご期待ください。

負荷試験をしていたら順調にAWSへの課金額も伸びてきた😇。

理論から学ぶデータベース実践入門 ~リレーショナルモデルによる効率的なSQL (WEB+DB PRESS plus)

理論から学ぶデータベース実践入門 ~リレーショナルモデルによる効率的なSQL (WEB+DB PRESS plus)

特定のエンドポイントの負荷試験の結果を投稿するMackerelプラグインを書いた

これ何

特定のエンドポイントに毎秒XリクエストをY秒流した結果をMackerelのメトリックとして投稿するプラグインを書きました。こんな感じでレスポンスタイムの99パーセンタルなどが可視化できます。

mkrを使っている人は以下でインストールできます。

% mkr plugin install syou6162/mackerel-plugin-request-pressure

当たり前ですが、他人のサイトにリクエストを飛ばしまくって迷惑をかけないようにしてください。

何で作った

Nginxのアクセスログなどがすでにあれば、レイテンシーの99パーセンタイルなどを可視化できるプラグインはすでに存在します。大体のユースケースはこれで十分。

しかし、当たり前ですがある程度流量のあるサイトでないと平均以外の値はあまり信用できません。自分が運用している趣味サイトは訪問者が自分とGoogle botくらいなので、流量が少ないです。ベンチマークスクリプトをかけてログを取ればよいですが、ログを取って可視化までやるのは面倒だなと思ってMackerelのプラグインにしました。プラグインとしてしこんでおけば定常的にレイテンシが分かって便利。-metric-key-prefixを付けておけばエンドポイント毎にグラフを出せます。このプラグイン自体も結構負荷がかかるものなので、負荷試験を行なう目的のサーバーで実行せずmonitoring専用のホストを作ってそこで実行するのがオススメです。

週末にインフラ方面の知識を付ける練習として負荷試験を行なっていたけど、パフォーマンスを継続的に見たりアラート飛ばせるようになったりするので便利。

MACHINE LEARNING Meetup KANSAI #3で機械学習を使った趣味サービスにおける工夫を紹介しました

最近、仕事のちゃんとした登壇が多かったので、趣味でやっているWebサービスにおける機械学習関連の工夫について発表してきました。基本的に昔ブログで紹介したエントリのまとめバージョンです。

趣味の余暇時間で開発しているサービスなので、いかに手間をかけずに済むかというのが大事です。テーマは怠惰!!

効率的なアノテーション方法

機械学習、データ数が多くないとなかなか精度が出ないですが、教師データをたくさん作るのも手間がかかります。そこで能動学習を使って効率的にアノテーションしていくツールを作ったので、それの紹介をしました。

精度の継続的なモニタリング

機械学習のコードでは、テストは通ってたけど実は本番では精度が下っていた...ということが起きがちです。きちんと計測して人間が目視で確認ということが毎回できれば理想的かもしれませんが、趣味でやっているサービスでは手間は最小限にしたいものです。

そこで、テストデータに対する分類性能をMackerelに継続的に送っています(Golangのライブラリを使っています)。グラフで可視化をするとどこで性能が劣化したか分かりますし、性能に対して監視ルールを入れることもできます。グラフにアノテーションをすることもできるので、deploy時のcommit idなどを書いておくと、どのcommitやPull Requestから挙動がおかしくなったかを突き止めやすいので便利です。

多様性を持たせた簡単な推薦方法

二値分類器でよい精度が出たとして、実際にユーザーに結果を提示することを考えると、スコア上位の結果は似たようなもので偏って困ってしまう場合があります。多様性のある推薦方法をお手軽にやるために、劣モジュラ最適化を使った方法を使っています。

組合せ最適化の問題を解くことになるわけですが、Golangで100行もない形で書けますし、貪欲法なので結果を出すのも速いです。貪欲法ではありますが、賢い人が性能の下限はこれより悪くならないよ!というのを証明してくれているので、ある程度安心して使うことができますね。

パイプラインジャングルと戦う

機械学習を使ったサービスは「データのクロールジョブ」「学習ジョブ」「予測ジョブ」など様々なバッチジョブが登場します。昔は「クロールが10分で終わるから15分後に学習ジョブをスタートさせる」「学習は20分で終わるから予測ジョブは30分後にスタートさせる」といった具合にcronやJenkinsで頑張ってジョブを整理していました。しかし、これだと予想より時間がかかった場合に思った挙動をしないとか、前段の処理が失敗した場合の考慮がうまくできず破滅しがちでした。

こういった機械学習に関連するパイプラインをいい感じに扱うツールがいくつかあります。

しかし、趣味でパイプライン管理ツールのお世話までやっているのはちょっとダルいです。そこで、AWSのマネージトサービスであるStep Functionsを使ってパイプラインの管理をすることにしました。

ステートマシーンを書けばよくて、図が出てくるのでどこで失敗したかが一目で分かりますし、失敗したときのretryなども簡単に指定できます。お手軽です。

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎