ユニファ開発者ブログ

ユニファ株式会社システム開発部メンバーによるブログです。

AWS LambdaでTensorFlow 2.0を使った画像分類

こんにちは、R&Dの宮崎です。 普段はTensorFlowを使った画像の認識モデルの開発を行なっています。 無事に精度の高いモデルができると、次は実際にサービスとして運用するための基盤を準備しなければいけません。 そこで今回は、AWS LambdaでTensorFlow 2.0を動かせるか検証してみましたので、紹介したいと思います。

TensorFlowで推論するための基盤

TensorFlowの運用基盤としてはいくつかの選択肢が考えられます。 AWSではTensorFlow ServingのコンテナをECSやEKSで動かしたり、SageMakerを使ったりするなどです。 これらに対し、AWS Lambdaはサーバのプロビジョニングが不要で運用の手間がかからず、使った時間しか課金が発生しないため、推論リクエストが散発的に発生する場合はコストを抑えられるといったメリットがあります。 一方で、AWS Lambdaはコールドスタートからの起動時間がかかったり、使えるコンピュータリソースが少ないというデメリットもあります。 特に、TensorFlowを扱う上ではその大きなパッケージやモデルを、いかに限られたディスクサイズに収めるかが鍵となります。本記事ではサイズの削減方法を中心に検証していきます。

続きを読む

fastlaneを利用してdSYMファイルを自動アップロードしてみる

こんにちは。iOSエンジニアのキムです。今回はアプリのクラッシュに関する内容になります。

アプリが良い評価をされるためには動作の安定性が必要不可欠です。コンテンツの内容やデザインなども大事ですが、アプリの動作が不安定でよくクラッシュしたりすると、そのアプリは使われなくなリます。そのため、エンジニアたちはアプリの安定性をモニタリングし、安定性の向上のために絶えずに取り組みを行っています。

アプリの品質を低下させるクラッシュですが、クラッシュログを生のデータのままで解析するのはほぼ不可能です。そのため、dSYM(デバッグシンボル)ファイルというのが必要になります。dSYMファイルを用いてシンボル化(Symbolicate)することで、解析可能なスタックトレースへと復元することができるようになります。

クラッシュレポートツール(Firebase Crashlyticsなど)を使うと、dSYMファイルを自動的にアップロードしてくれるとかクラッシュしたデバイスの情報などの追跡もできるので、クラッシュの原因を調査する時間も節約できます。

背景

以前Firebase Crashlyticsでクラッシュログを調べる際に「Missing dSYM」というアラートが表示されて、クラッシュログが正常に表示されないことがありました。

調べると、アプリのBitcode設定を有効にしていたのが原因でした。アプリのBitcode設定を有効にしてAppleに送信すると、Appleによって特定のデバイスとアーキテクチャ向けに再コンパイルされ、dSYMファイルも新しく生成されるので、それをAppleから手動でダウンロードして、Firebase Crashlyticsにアップロードする必要がありました。

しかし、新しく生成されたdSYMファイルは数十個もありましたし、それを毎回アップロードするのはなかなか手間がかかる作業でした。

今回はそのdSYMファイルをfastlaneを利用して自動でアップロードする方法について紹介したいと思います。

*ちなみに、dSYMファイルはXcodeのOrganizerのDownload Debug Symbolsからもダウンロードできます。

準備

Firebase Crashlyticsfastlaneについてある程度の知識があることを前提で話します。

処理内容

fastlaneでdSYMアップロード用のlaneを作ってみます。

lane : refresh_dsyms do |options|
    # バージョンの指定がなければ、最新バージョンのdSYMファイルをダウンロードする
    version = options[:version] || 'latest'
    download_dsyms(
      version: version
    )
    # dSYMファイルをcrashlyticsにアップロードする
    upload_symbols_to_crashlytics
    # ローカルのdSYMファイルを削除する
    clean_build_artifacts
end
利用方法
fastlane refresh_dsyms version:1.0.0
結果

上記のlaneを実行すると、dSYMファイルをAppleからダウンロードして、crashlyticsにアップロードしてくれます。

f:id:unifa_tech:20190909093639p:plain

fastlaneのdocumentを見るともっと詳しい使い方が書いてあります。

最後に

これでクラッシュログも正常に表示されるようになりました。 これからはクラッシュログを解析して、どのクラッシュから解決するかの優先順位を決めて、一つずつ再現させながら対応するだけですね。

コンテキストスイッチをいかにして減らしたか?

スクラムマスターの渡部です。

以前、とあるイベントに参加した際、「スクラムをやり始めたチーム向けの、ネタ帳的な情報ってあまり見かけないよね」という話をしていたのですが、それならばと思い、私たちのチームで実際にやったことの一部を紹介していこうと思います。

今回のテーマは、コンテキストスイッチのコスト削減です。

※今になって思えば、「スクラム現場ガイド」がまさにネタ帳的な内容でしたので、興味がある方はぜひお手に取っていただければと思います。

book.mynavi.jp

本記事で解説する内容
  • コンテキストスイッチとは?
  • コンテキストスイッチを体感してみよう!
  • コンテキストスイッチを減らすためにやったこと3つ
想定読者
  • (スクラムを導入しているか否かに関わらず)個人・チームのパフォーマンスを上げたいと考えている方

コンテキストスイッチとは?

コンテキストスイッチとは一言でいうと、作業Aから、異なる文脈の作業Bに思考を切り替えることです。

ただ作業を切り替えているだけのようにも思えますが、作業Bを終えて作業Aに戻ったとき、「作業Aがどこまで進んでいたのか?」、「この後何をするべきだったのか?」を思い出して復帰する工程が必要になり、ムダが発生してしまっているのです。

思い出すことができればまだ良いのですが、酷い場合は全く思い出すことができず、「昔話したアレ…結局何をすれば良かったんだっけ…?」と確認するハメに…

皆さんの中にも、「たくさんミーティングが入っている日は、他の作業が全然進まないなぁ…」というシチュエーションに身に覚えがある方はいませんか?

恐らくその時、「ミーティング」と「作業」の間でコンテキストスイッチが発生することにより、「元々の作業を思い出して復帰する」ことに思考のリソースを割いてしまっている可能性があります。

コンテキストスイッチを体感してみよう!

そうは言ったものの、「簡単な作業なら、少しくらい平気でしょ!」という気持ちも分かるので、簡単なワークでコストを体感してみたいと思います。

私がチームで実施しているのは下記の方法です。5分あれば体験できるので、良ければ是非試してみてください。

準備するもの
  • ペン
  • ストップウォッチ(スマホでOK)
概要

アルファベット10文字「a〜j」、ひらがな10文字「あ〜こ」、数字10文字「0〜9」を2通りの順番で書いていただき、書き終えるまでの時間を計測・比較します。

「アルファベットを書く作業」、「ひらがなを書く作業」、「数字を書く作業」の、3種類の作業が存在するイメージですね。

手順
  1. まずは、3種類の文字を1文字ずつ順番に書きます。(a → あ → 1 → b → い → 2…)
  2. 次に、1つの文字種をまとめて書いて、次の文字種に移ります。(a〜j → あ〜こ → 0 → 9)
  3. 1,2で書き終えるまでの時間を比較します。

いかがでしたでしょうか。

「文字を書く」という作業は日常で慣れているはずですし、特に難しい文字を書いている訳でも無いはずですが、書きにくさを感じませんでしたか?

簡単に思える作業ですらそうなのですから、当然、コードを書いたり、テストをしたり、その他開発以外のあらゆる作業も影響を受けます。

ですので、個人やチームのパフォーマンスを向上させるためには、できる限りコンテキストスイッチを減らし、同じコンテキストの作業を継続できる状況を作ることが大切になります。

次のセクションでは、私たちのチームがコンテキストスイッチ減らすために実際にやってみたことを、エンジニアからのラフな感想付きでいくつか紹介していきます。

やったこと①:運用系作業(差し込み作業)のコントロール

困っていたこと
  • 運用系作業(差し込み作業)と、それに関連する確認相談が頻繁に発生していた
  • スプリント内の全工数の内、3〜6割ほどが運用系作業で占められていた
何をしたのか
  • 運用系作業を専門で行うスタッフをアサインして、他スタッフが確認・対応する時間を減らした
  • 複数の作業依頼が発生することもあったので、運用系作業のためだけのカンバンを作成し、着手すべき優先順位順に1列で並べるルールにした
どうだったか

エンジニア曰く「これは本当に良かった、助かった」とのこと。

差し込み(コンテキストスイッチ)を減らしたことと、単純に運用系作業に対応できる量にも制限が出来たことで、もともとは3〜6割程度を占めていた運用系作業の割合が、多くて1割程度に落ち着きました。

この施策は、チームで実施してきたカイゼンの中でも特にインパクトが大きいものでした。が、その分、チームに適したフローを整えるのに頭を悩ませましたし、影響範囲も大きいので、沢山の方にご理解ご協力をいただきました。

プロジェクトとチームの状況をご理解いただき、ご協力いただいた関係者の皆様、本当にありがとうございます。 特に、フロー検討時に相談に乗っていただいたY岸さん、K林さん、グループ全体にスムーズに展開していただいたT中さん、そして何より、一手に引き受けてくださったSさん、本当にありがとうございます。

実施に際してチームごとに課題はあるかと思いますが、同じような問題に悩まれているチームは多いかと思いますので、是非試してみていただければと思います。

因みに、スクラム現場ガイド 14章では「専任チーム」として同様の事例が紹介されています。

やったこと②:プロダクトオーナーの席移動

困っていたこと
  • (詳しい事情は皆様のご想像にお任せしますが)不明なことが非常に多い中で探りながら開発を進める必要があり、都度の確認・相談のために作業の手が止まっていた
何をしたのか
  • プロダクトオーナーにコンテキストスイッチによるコストを説明・理解いただき、チームの近くに移動してもらい、気軽に確認相談できる環境を整えた
どうだったか

本当にややこしいものは、口頭のみで済ませてしまうと後で困るので、結局テキストに残すことになるのですが、「簡単な相談や、作ってその場で方向性のジャッジができることは良かった」と、エンジニアからはまずまずの評価でした。

因みに、プロダクトオーナーが近くにいることで過干渉のリスクがあると言われていましたが、私たちのチームではそのような問題はありませんでした。

やったこと③:MTGの調整

困っていたこと
  • MTGがたくさんある
  • MTGの開催時間が点在していて、長時間集中できる時間が無い
何をしたのか
  • 参加マストなMTG以外は、欠席 or 任意参加にしてもらえるよう、関係者へ交渉した
  • 参加マストなMTGで、時間調整可能なものは、朝に移動して午後はできる限り空けた
どうだったか

エンジニア曰く「MTGまであと30分くらいだから簡単な作業をしよう…とムダに考えなくて良くなったので進めやすくなった」とのこと。

ちなみに

コンテキストスイッチによる作業効率の低下は、作業単位のみならず、プロジェクト単位でも発生することがわかっています。

ざっくりとした例えですが、次のようなプロダクトA,B,Cのための3つのプロジェクトがあったとします。(必要な作業 A1,A2,A3が達成できれば、プロダクトAが出来上がるイメージです)

f:id:unifa_tech:20190903175619p:plain

まずは、全てを優先、つまり、チームが複数のプロジェクトを掛け持つ場合のスケジュールを見てみましょう。

作業間でコンテキストスイッチが発生するため、作業間に余白を入れて、下記のようなスケジュールになります。

f:id:unifa_tech:20190903170856p:plain

次に、1つずつ順番に対応する場合のスケジュールを見てみましょう。

コンテキストスイッチが発生するのは、A→Bの切り替え時、B→Cの切り替え時のみとなるので、そこに余白を入れて、下記のスケジュールとなります。

f:id:unifa_tech:20190903174725p:plain

可能な限りコンテキストスイッチを抑え、1つずつ順番に対応した場合の方がムダ(余白)が無いため、トータルで早く完了しそうだということがわかります。

(コンテキストスイッチの話とは少し脱線しますが、各プロダクトA,B,Cが早くリリースでき、多くの価値を提供できる利点もあります)

プロジェクトの同時並行に関してはやむを得ない場合もあると思われますので、可能な場合には考慮いただくとよろしいかと思います。

補足

下記ページにある表では、同時並行のプロジェクトが増えるごとに、コンテキストスイッチによってロスが生じ、1つ1つのプロジェクトに使える時間の割合が減っていくことを説明していますので、良ければ見てみてください。

www.scruminc.com

掛け持ちが3つ以上になると、1つ1つのプロジェクトに費やせる割合よりも、コンテキストスイッチによるコスト(ムダ)の割合の方が多くなるのは感慨深いです。

さいごに

いかがでしたでしょうか?

チームのパフォーマンスをできるだけ高めたいと考えている方にとってのヒントとなれば幸いです。

既に何らかの施策を実施されている方は「うちのチームはこんなことをやってみたよ!」とコメントいただけると涙を流して喜びます。

今回は、「コンテキストスイッチのコスト削減」にフォーカスしてお話しましたが、いずれ別のテーマでもネタ帳的な内容で記事を書ければと思います。

このように、私たちのチーム・会社では、効率的に目的を達成するために全員が一丸となって日々カイゼンと繰り返しています。

そんな働き方に少しでも興味を持っていただけるようでしたら、是非下記も覗いてみていただけると嬉しいです。

herp.careers

not 0, but 1

デザインチームの三好です。

今回はデザイナーっぽいこと書きます。

「アートは自己表現、デザインは問題解決」という言葉をよく聞きます。 個人的には100%そうだとは思っていませんが(稀にデザインがアートに、アートが問題解決になることもある)、やはり基本的には明確な問題に対してアプローチしていくものであることに間違い無いかと思います。

一般的にデザイナーとはセンスに長けている必要があると思われがちですが、あくまで感性とは問題解決というゴールに向けてより達成しやすくするための補助であり、最も重要なのは「問題解決能力に長けている」ことだと私は考えます。

オリジナリティを0から生み出す創造性というよりは、培った知識や経験を駆使して1から組み立てていくというほうが正しいかと思います。

では最近作成した開発チームTシャツの制作過程を例に説明してみたいと思います。

以下デザイン案です。

f:id:unifa_tech:20190903170026j:plain

社内デザイン物の中で今回の開発チームのTシャツはかなり自由度の高いデザインが許されるものです。 かといってただビジュアル要素のみ重視して制作しても問題解決にはなりません。 まず最善の答えを設定します。 例の場合、最初に課せられた効果は以下になります。

①エンジニアを感じさせるもの ②イベント登壇時に着るイメージ

まず登壇時に着用する際のアピールとして前面プリントを選択しました。 かつイベント着席時に後方の参加者からも認識してもらえるように社名ロゴを背面に配置。

開発チームのテーマである「保育をハックする」をメインに置いて(いくつか違うものも混ざってますが…)、インパクトの強い言葉に共鳴させるためボディは黒にしました。

後は「ハックする」を軸にしてイメージを具現化していきます。 今回は主に図形を使って効果の強度を高めていきました。

例えば、図形を壁に置き換えてそれを線で打ち破っていく、”既存のルールを壊していくハッカー”を表現してみたり。

f:id:unifa_tech:20190903165826j:plain

最終的には女性が着用することも考慮し、ユニセックスな要素のあるデザインで落ち着きました。 多色なラインのデザインはエンジニアに馴染みのあるターミナルカラーをイメージしています。

デザインは制作した全ての行為を言葉で説明できなければいけません。 明確な目的がある限り全てに意味がなければならない。

デザインは絶対的に他人に向けて発信されるものである為、説得力が必要になります。 自己犠牲なくして成り立たない仕事だと思っています。

1つの制作物を作るまでには思考する時間とチーム内での協力(客観的視点やレビュー)が必須です。 特に私の場合は油断すると抽象的な方向へ飛んでいってしまうので、チームからの冷静な指摘があってようやくデザインとして成り立っていきます。

では、次回はデザインがいかに地味な作業の積み重ねかということをお話ししたいと思います。

イメージキャプショニング入門

研究開発部の浅野です。深層学習で熱い分野の一つに、自動で画像の説明文を作成するイメージキャプショニングがあります。画像を解釈するコンピュータビジョンと適切な言語表現を生成する自然言語処理、どちらも手掛けたい!という欲張りなあなたにぴったりです。保育園では日誌や連絡帳などたくさんの書類作業がありますが、写真を一枚撮っておけば簡単な情景描写までは機械で済ませてくれるようになると、そうした書類作成の負荷が軽減できるかもしれません。

基本的なアプローチ

f:id:unifa_tech:20190823120903j:plain:w450
イメージキャプショニングを行うネットワークの基本構造

対象の画像をConvolutional Neural Network(CNN)に入力して特徴空間でのベクトルに変換し、作成途中の説明文(単語列)をRecurrent Neural Network (RNN)に入力して同様に特徴ベクトルにする。それらを全結合ネットワーク(Fully Connected Network, FC)に入力して次の単語を推定する、というのが基本的な流れです。

学習の実際

f:id:unifa_tech:20190823122511j:plain:w250:left

例えばこの画像(Source: PhotoAC)に対して「サングラスをかけた赤ちゃんが水辺でくつろいでいる」という説明文を生成するように学習を行う場合、まず画像をCNNに、文の開始を意味する単語をRNNに入力し、出力される単語が「サングラス」になるように各ネットワークの重みを再計算します。

次のステップでは、CNNへの入力は変わらず、RNNへの入力は「サングラス」にします。その出力が「を」になるようにネットワークの重みを修正します(下図)。このようにしていろいろな画像に対して正しい文を生成するようなモデルを作成すべく、たくさんの画像と説明文の正解データをもとに学習を行っていきます。

f:id:unifa_tech:20190823125144j:plain:w550
ネットワークの学習における入出力の例

実装

基本の構造はかなりシンプルなのでKerasによるモデル部分の記述も下記のように簡単です。今回は学習時間を短縮するため、CNN部分にはImageNetで学習済みのInceptionV3を使って事前に各画像の特徴ベクトルを作成しました。RNN部分にはLSTM(Long Short-Term Memory)を使用しています。学習用のデータにはFlickr8kを使いました。

from keras.layers import Input, Dense, LSTM, Embedding, Dropout
from keras.layers.merge import concatenate

def define_model(vocab_size, max_length):
    #photo feature extractor
    inputs1 = Input(shape=(2048, ))
    fe1 = Dropout(0.5)(inputs1)
    fe2 = Dense(256, activation='relu')(fe1)

    #sequence model
    inputs2 = Input(shape=(max_length, ))
    se1 = Embedding(vocab_size, 256, mask_zero=True)(inputs2)
    se2 = Dropout(0.5)(se1)
    se3 = LSTM(256)(se2)
    
    #decoder model
    decoder1 = concatenate([fe2, se3])
    decoder2 = Dense(256, activation='relu')(decoder1)
    outputs = Dense(vocab_size, activation='softmax')(decoder2)
    
    model = Model(inputs=[inputs1, inputs2], outputs=outputs)
    model.compile(loss='categorical_crossentropy', optimizer='adam')
        
    return model

結果

f:id:unifa_tech:20190823134658j:plain:w250:left

学習の様子(左)を見ると、学習時の損失(青線)は順調に下がっていますが、評価時の損失(橙線)はEpochが進むとすぐに下げ止まっています。今回は非常に単純な構成かつCNN部分も学習済みのものを使って重みの更新をしていないため、それほど汎化性能がよくないのはやむを得ないところです。

f:id:unifa_tech:20190823122323j:plain:w350:right

学習に使用していない画像(Source: COCO)で実際にどのような説明文が生成されるかみてみましょう(右図)。画像の上部にある"man in red shirt is riding bike on the street"がモデル自動でつけた説明です。当たらずとも遠からず、という感じですね。

まとめ

イメージキャプショニングの大まかな構成と流れについて見てきました。画像(や動画)と言語が交わる分野には、イメージキャプショニングの他にもビデオキャプショニング、動画のアクション理解、ビジュアル質問応答、映像要約など、保育の世界でも役に立つ可能性がある技術がたくさんあります。引き続き注目していきたいと思います。

基本的なサンプリングアルゴリズムである棄却サンプリングを試してみた

はじめに


こんにちわ、研究開発部の島田です。今回は統計的学習で基本的なサンプリングアルゴリズムを一つ紹介します。

統計的学習におけるサンプリング手法はいくつかありますが、大別するとマルコフ連鎖モンテカルロ法(MCMC)を使わないサンプリング手法とMCMCを使ったサンプリング手法に分けられます。

MCMCを使わないサンプリング手法の最も基本的なアルゴリズムとして、棄却サンプリングがあげられます。このアルゴリズムは難しい数式を必要としないので、直感的にもわかりやすいです。

今回はこの棄却サンプリングについて簡単な説明と実装を行います。

棄却サンプリング


ベイズ統計でやりたいことは、ある複雑な事後分布からモンテカルロ法を使ってサンプリングをしたいということになります。

そして棄却サンプリングでは、直接事後分布からサンプリングすることは難しいのでもっと単純な分布(これを提案分布と言います)を利用しようという手法です。

ここで、予測分布をp(z)、提案分布q(z)とすると、

p(z) \leq Mq(z)

上の条件を満たすMおよびq(z)を求める必要があります。この条件は予測分布が提案分布をM倍した分布に覆われていることを意味しています。

棄却サンプリングの手順は非常にシンプルで、下記のようになります。

  1. 提案分布q(z)に従う乱数yを発生
  2. [0, Mq(y)]に従う乱数sを発生
  3. s \leq p(y)の場合は受理。そうでない場合は棄却。
  4. 1から3をN回繰り返す。

ベータ分布


今回は予測分布をベータ分布として棄却サンプリングを試してみます。 ベータ分布とは、確率密度関数が下記の式で表される確率分布のことです。

f(x|α,β) = \dfrac{x^ {α-1}(1-x)^ {β-1}}{B(α, β)}

ただし、α, βは正の実数のパラメータです。 また、ベータ分布は区間[0, 1]上の連続型の確率分布であることが特徴です。 では、ベータ分布の形を確認するためにScipyというPythonライブラリを使って確認してみます。

まずは必要なライブラリのインポートです。

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from scipy import optimize as opt
from scipy.stats import beta, uniform

次にベータ分布を描画してみます。必要なコードはたったこれだけです。

np.random.seed()

a, b = 1.8, 2.9
x = np.linspace(beta.ppf(0.001, a, b), beta.ppf(0.999, a, b), 100)
plt.plot(x, beta.pdf(x, a, b))

f:id:unifa_tech:20190816113156p:plain

棄却サンプリングの実行


では、いよいよ棄却サンプリングを試してみます。

提案分布はどんな分布であっても構わないのですが、今回はサンプリングが簡単で理解もしやすい一様分布とします。

まずは定数Mを求める必要があるのですが、下の図からわかるようにMはベータ分布の最大値p(y max)と求めることが出来ます。

そして、横軸方向について一様分布q(z)からランダムな値yを決めます。具体的には0から1の間で一つ値を決定することになります。

次に縦軸方向については[0, Mq(y)]の一様分布に従ってランダムな値sを決めます。

この処理によって横軸と縦軸のスケールの異なる一様分布から生成された二つの乱数によって座標が一点決まることになります。

そしてこの乱数によって求まった値sが予測分布よりも下の範囲であれば採用します。とてもシンプルですね!

当然ながらサンプリング数が多ければ多いほど、予測分布に近しい結果になります。

f:id:unifa_tech:20190816153734p:plain:w600

では、実装して結果を見ていきます。

まずはMを求めますが、今回はScipyのOptimize関数を使います。ScipyのOptimize関数は目的関数を最適化(最小値もしくは最大値を求める)する便利な関数です。

f = beta(a=a, b=b).pdf
res = opt.fmin(lambda x: -f(x), 0.1)
M = f(res)
print("M : ", M)

f:id:unifa_tech:20190816113912p:plain:w300

f(x)にマイナスをつけているのは、ベータ分布の最大値を求めるために最小値問題に帰着させるためです。

結果を見てみると上手くベータ分布の最大値を求められていることがわかります。

次に乱数を生成して提案分布が予測分布に近しい結果になっていくことを確認します。

まずは乱数を1000個生成した場合を見てみます。

N_MC = 1000

y = uniform.rvs(size=N_MC)
mq = M * uniform.rvs(size=N_MC)
accept = y[mq <= f(y)]

plt.hist(accept, normed=True, bins=35, rwidth=0.8, label="rejection sampling")
x = np.linspace(beta.ppf(0.001, a, b), beta.ppf(0.999, a, b), 100)
plt.plot(x, beta.pdf(x, a, b), label="target distribution")
plt.legend()

f:id:unifa_tech:20190816114630p:plain:w400

1000個の乱数だと、まだ少し予測分布(ベータ分布)に対して少しバラツキがありますね。

では、乱数を50000個に増やしてみましょう。

f:id:unifa_tech:20190816114751p:plain:w400

すると、乱数が1000個に比べてベータ分布に対してほぼ同じような曲線を描いていることがわかります。

最後に


今回は基本的なサンプリングアルゴリズムである棄却サンプリングを試してみました。異なる分布から求めたい分布のサンプルを非常にシンプルな考えで得ることが出来て面白いですね。サンプリングアルゴリズムは他にもたくさんあるので、是非試してみたいと思います。

Test Driving the Proposed Vue.js Function API

By Robin Dickson, software engineer at UniFa.

An RFC (request for comments) for Vue.js was published that explains the plan for a new Function API. Following that, a plugin was created that allows the proposed Function API to be used in current Vue applications: vue-function-api.

I thought I would experiment with the Function API by building a mini app.

Function API Installation

The base app was created using vue cli, and the vue-function-api plugin installed using yarn:

$ vue create janken
$ yarn add vue-function-api

Then the plugin installed explicitly:

import Vue from 'vue'
import { plugin } from 'vue-function-api'

Vue.use(plugin)

The current API (Standard API) still works as usual, and it is even possible to use a hybrid approach (Function API + Standard API).

The app I decided to build was Janken, or in English: Rock, Scissors, Paper.

In this app there are 4 features:

  • The player can choose their hand
  • The computer chooses their hand and a winner is calculated
  • The total amount of points (wins) for each player are shown
  • The player can change their name

The initial design was:

<template>
  <div>
    <div class="score">
      <div>Player</div>
      <div>0 - 0</div>
      <div>Computer</div>
    </div>
    <div class="player-hands">
      <div>✊</div>
      <div>✊</div>
    </div>
    <ul class="hand-choices">
      <li>✊</li>
      <li>✌️</li>
      <li>🖐️</li>
    </ul>
  </div>
</template>

f:id:unifa_tech:20190814123651p:plain

Vue Implementation

Setup, Data and Value

The first change from the Standard API is that a setup option is used to set up the component logic. If you need to use props they are passed to setup as an argument (more info).

The score data would have previously be stored in the data option, which is not used in the Function API. Instead the data is stored by using the value API. Data and functions that are used in the template are returned from the setup option.

import { value } from "vue-function-api";

export default {
  setup() {
    const playerScore = value(0);
    const computerScore = value(0);

    return {
      playerScore,
      computerScore
    };
  }
};
<div class="score">
  <div>Player</div>
  <div>{{ playerScore }} - {{ computerScore }}</div>
  <div>Computer</div>
</div>

Methods

The next task was to enable the player to choose their hand. Using the Standard API this can be done using the methods option. In the Function API the same can be done using a function.

export default {
  setup() {
    // ...
    const playerHand = value(null);

    function submitHand(hand) {
      playerHand.value = hand;
    }
    return {
      playerScore,
      computerScore,
      playerHand,
      submitHand
    };
  }
};

To set (and also get) the value of playerHand within setup playerHand.value must be used.

Computed

The hands are displayed in the UI using emoji. The hand data stored as a string ('rock') is converted to an emoji ('✊') with the computed API (similar to the Standard API's computed option). Again this is stored to a variable and returned from setup to be used in the template.

import { value, computed } from "vue-function-api";

export default {
  setup() {
    const handsToEmoji = {
      rock: "✊",
      scissors: "✌️",
      paper: "🖐️" 
    };
    
    const isShowGameHands = value(false);
    // ...
    const playerHand = value(null);

    // ...
    const playerDisplayEmoji = computed(() =>
      isShowGameHands.value
        ? handsToEmoji[playerHand.value]
        : handsToEmoji["rock"]
    );
    // ...
    return {
      // ...
      playerDisplayEmoji,
      computerDisplayEmoji
    };
  }
};

If there are many methods or computed values in the returned object, these can be grouped into a single object and destructured in the object returned from setup.

// example code (not from to the Janken app)
const methods = {
  methodA() {
    // ...
  },
  methodB(arg) {
    // ...
  }
}

const computeds = {
  computedA: computed(() => 'a'),
  computedB: computed(() => 'b')
};

return {
  ...computeds,
  ...methods
};

(see in this example)

Composition Functions

After adding the logic for the game and editing the player name I tried refactoring using a technique made possible in the Function API. Using a composition function the logic (variables and methods) could be extracted to a separate function, and then included in the object returned from the main setup option.

function useName() {
  const playerName = value("Player");
  const isEditingName = value(false);

  function editName() {
    isEditingName.value = true;
  }
  function submitName() {
    isEditingName.value = false;
  }
  return { playerName, isEditingName, editName, submitName };
}

export default {
  setup() {
    // ...

    return {
      isShowGameHands,
      playerScore,
      computerScore,
      playerHand,
      computerHand,
      submitHand,
      ...computeds,
      ...useName()
    };
  }
}

By doing this the code can be organised more clearly, collecting related code together rather than it being separated between different options(data, computed, methods, etc) which can happen in the Standard API. It is also possible to reuse the logic in other components.

Although not used in this app lifecycle hooks and watchers are used in a similar way to value and computed and can also be extracted.

The Janken app and code can be seen and used below. It only took a few steps to get started with the Function API, and there are various features I did not use that I'm looking forward to trying. For more information check out the RFC and try it yourself!

<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-function-api/dist/vue-function-api.umd.js"></script>
    <title></title>
  </head>
  <body>
    <div id="app">
      <main>
          <div>
    <div v-if="isEditingName" class="emoji-button" @click="submitName">🆗</div>
    <div v-else class="emoji-button" @click="editName">✏️</div>

    <div class="score">
      <div class="input-wrapper" v-if="isEditingName">
        <input v-model="playerName" @keyup.enter="submitName" type="text" maxlength="7" />
      </div>
      <div v-else>{{ playerName }}</div>
      <div>{{ playerScore }} - {{ computerScore }}</div>
      <div>Computer</div>
    </div>
    <div class="player-hands">
      <div>{{ playerDisplayEmoji }}</div>
      <div>{{ computerDisplayEmoji }}</div>
    </div>
    <ul class="hand-choices">
      <li :class="{ selected: playerHand === 'rock' }" @click="sumbmitHand('rock')">✊</li>
      <li :class="{ selected: playerHand === 'scissors' }" @click="sumbmitHand('scissors')">✌️</li>
      <li :class="{ selected: playerHand === 'paper' }" @click="sumbmitHand('paper')">🖐️</li>
    </ul>
  </div>
      </main>
    </div>
  </body>
</html>
main {
  font-family: "Courier New", Courier, monospace;
  width: 280px;
  margin: auto;
}
.score {
  display: flex;
  justify-content: space-between;
  margin-top: 40px;
  font-size: 18px;
  font-weight: bold;
}
.score div:nth-child(1) {
  width: 35%;
  text-align: right;
}
.score div:nth-child(2) {
  width: 30%;
  text-align: center;
}
.score div:nth-child(3) {
  width: 35%;
  text-align: left;
}
.player-hands {
  display: flex;
  justify-content: space-between;
  margin-top: 80px;
  font-size: 20vh;
}
.hand-choices {
  display: flex;
  justify-content: space-between;
  margin-top: 60px;
  padding: 0;
}
.hand-choices li {
  font-size: 15vh;
  list-style: none;
  cursor: pointer;
}
.hand-choices li.selected {
  text-decoration: lightblue underline;
  transition: font-size 0.2s;
}
.score div {
  padding: 5px 0;
}
.score .input-wrapper {
  padding: 0;
}
input[type="text"] {
  width: 80px;
  padding: 5px;
  margin: 0 0 0 15px;
  border: 2px solid #ccc;
  border-radius: 5px;
  font-family: "Courier New", Courier, monospace;
}
.emoji-button {
  position: fixed;
  top: 10px;
  left: 10px;
  cursor: pointer;
}
const { plugin, value, computed } = vueFunctionApi;

Vue.config.productionTip = false;
Vue.use(plugin);

function useName() {
  const playerName = value("Player");
  const isEditingName = value(false);
  function editName() {
    isEditingName.value = true;
  }
  function submitName() {
    isEditingName.value = false;
  }
  return { playerName, isEditingName, editName, submitName };
}

var app = new Vue({
  el: '#app',
  setup() {
    const handsToEmoji = { rock: "✊", scissors: "✌️", paper: "🖐️" };
    const isShowGameHands = value(false);
    const playerScore = value(0);
    const computerScore = value(0);
    const playerHand = value(null);
    const computerHand = value(null);
    const computeds = {
      playerDisplayEmoji: computed(() =>
        isShowGameHands.value
          ? handsToEmoji[playerHand.value]
          : handsToEmoji["rock"]
      ),
      computerDisplayEmoji: computed(() =>
        isShowGameHands.value
          ? handsToEmoji[computerHand.value]
          : handsToEmoji["rock"]
      )
    };
    function sumbmitHand(hand) {
      playerHand.value = hand;
      runGame();
    }
    function randomHand() {
      const hands = Object.keys(handsToEmoji);
      return hands[Math.floor(Math.random() * hands.length)];
    }
    function addPointToWinner() {
      const handToWeakness = {
        rock: "paper",
        scissors: "rock",
        paper: "scissors"
      };
      if (handToWeakness[computerHand.value] === playerHand.value) {
        playerScore.value++;
      } else if (handToWeakness[playerHand.value] === computerHand.value) {
        computerScore.value++;
      }
    }
    function runGame() {
      computerHand.value = randomHand();
      isShowGameHands.value = true;
      addPointToWinner();
    }
    return {
      isShowGameHands,
      playerScore,
      computerScore,
      playerHand,
      computerHand,
      sumbmitHand,
      ...computeds,
      ...useName()
    };
  }
}).$mount('#app')