ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

Reactで機械学習用データラベリングアプリを作る

こんにちは、田中です。

弊社サービス、るくみーでは保育士のみなさんやプロカメラマンが撮影した写真が日々たくさんアップロードされています。 定期的にこの子ども達の様子が保護者のみなさんに公開・販売されますが、販売が終了したあとの写真は基本的にデータサーバーで眠るのみです。。><

この膨大に蓄積されている園写真を機械学習の力で価値につなげていきたい... !

ということで、その最初の一歩 兼 React勉強の一環として機械学習用データラベリングアプリを作ってみました。 ƪ(•◡•ƪ)"

できたもの

こんな感じです。

f:id:sanshonoki:20171024062117g:plain

写真の枚数が大きいと見込まれるので右側の写真リストは無限スクロールとなるように実装しました。

一番下の写真までスクロールしたときにデータが読み込まれてブラウザのスクロールバーが伸びることが分かると思います。

無限スクロール

FacebookやTwitter、Instagramといった人気のソーシャルメディアでのコンテンツ表示のUIとして使われています。 ユーザが下へスクロールするにつれてページのコンテンツが継続的にロードされるというアレです。

無限スクロール以外の選択肢としてはページネーションがあり、それぞれ長所短所があります

上の記事は主にビジネスコンテンツ目線での長所短所ですが今回はキーボードのショートカット操作だけでも楽に作業できることを重視し無限スクロールを採用しました。 (「進む」「戻る」の操作だけでよい)

react-infinite-scrollerを使った無限スクロール

react-infinite-scroller というパッケージを使いました。 他にも何個かありましたがこれが最も分かりやすく使いやすかったです。

github.com

create-react-appで作ったアプリをもとに無限スクロールを実現するコードを紹介します。

一番シンプルな例

f:id:sanshonoki:20171020104602g:plain

App.js

import React, { Component } from 'react';
import './App.css';
import InfiniteScroll from 'react-infinite-scroller'

class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      items: [],
      hasMoreItems: true
    }

    this.loadItems = this.loadItems.bind(this)
  }

  loadItems() {
    const current_item_count = this.state.items.length
    const max_items = 300
    const page_item_size = 50

    if (current_item_count < max_items) {
      const new_ids = Array.from(Array(page_item_size).keys()).map((num) => {return num+current_item_count})
      const new_items = new_ids.map((id) => {return {'id': id}})

      setTimeout(() => {
        this.setState({items: this.state.items.concat(new_items)})
      }, 500) // add some delay for demo purpose
    } else {
      this.setState({hasMoreItems: false})
    }
  }

  render() {
    const items = this.state.items.map((item) => {
      return <div>{item.id}</div>
    })

    return (
      <div className="App">
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>

        <InfiniteScroll
          pageStart={0}
          loadMore={this.loadItems}
          hasMore={this.state.hasMoreItems}
          loader={<div>Loading...</div>}
          initialLoad={true}
        >
          {items}
        </InfiniteScroll>
      </div>
    );
  }
}

export default App;
  • <InfiniteScroll></InfiniteScroll> でスクロールさせる要素を囲む
  • loadMoreで新しいデータを読み込み、配列に追加する
  • 読み込むデータがなくなったら hasMorefalse にする

基本これだけです。簡単ですね :-)

フッター固定の例

続いてフッターを固定で表示する場合です。

f:id:sanshonoki:20171020104628g:plain

App.css

html, body {
  height: 100%;
}

.App {
  text-align: center;
  height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.scrollContainer {
  height: 100%;
  overflow: auto;
}

.footer {
  height: 30px;
  margin: 10px;
}

App.js

import React, { Component } from 'react';
import './App.css';
import InfiniteScroll from 'react-infinite-scroller'

class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      items: [],
      hasMoreItems: true
    }

    this.loadItems = this.loadItems.bind(this)
  }

  loadItems() {
    const current_item_count = this.state.items.length
    const max_items = 300
    const page_item_size = 50

    if (current_item_count < max_items) {
      const new_ids = Array.from(Array(page_item_size).keys()).map((num) => {return num+current_item_count})
      const new_items = new_ids.map((id) => {return {'id': id}})

      setTimeout(() => {
        this.setState({items: this.state.items.concat(new_items)})
      }, 500) // add some delay for demo purpose
    } else {
      this.setState({hasMoreItems: false})
    }
  }

  render() {
    const items = this.state.items.map((item) => {
      return <div>{item.id}</div>
    })

    return (
      <div className="App">
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <div className="scrollContainer">
          <InfiniteScroll
            pageStart={0}
            loadMore={this.loadItems}
            hasMore={this.state.hasMoreItems}
            loader={<div>Loading...</div>}
            initialLoad={true}
            useWindow={false}
          >
            {items}
          </InfiniteScroll>
        </div>
        <div className="footer">Footer</div>
      </div>
    );
  }
}

export default App;

こちらはスタイルを少し工夫する必要があります。

  • ウインドウ高さをheight: 100vh;で固定し、 overflow:hidden でスクロールバーを見せない
  • <InfiniteScroll></InfiniteScroll> をラップする<div>要素を作り、height: 100%; overflow: auto; にする
  • useWindow={false}のパラメータを追加する

create-react-appで適当なアプリを作ってApp.jsとApp.cssを上書きすればすぐにできるので興味あれば試してみてください

おわりに

ラベリングツールは一応の形になりました!

が、これからチマチマとラベリング作業していくのかと思うとちょっと憂鬱。w

でも、頑張りたいと思います。。 (•̀ᴗ•́)و

Actions on Google と AWS Lambda で Google Home から Slack にポストする

f:id:akanuma-hiroaki:20171013013717j:plain:w300:left

 みなさまこんにちは、ユニファCTOの赤沼です。先日やっと日本でも Google Home が発売されましたね。このブログを読んでいる方の中には買われた方もそれなりに多いのではないでしょうか。ユニファでも Google Home でどのぐらいのことができるのか検証してみるために、オフィス近くのビックカメラで購入してきました。普通に使っているだけでも天気やニュースを教えてくれたりと便利ではありますが、やはりエンジニアとしてはアプリを開発したり他のものと繋げてみたくなるものです。そこで今回は Google Home から使えるオリジナルのアプリを作ってみたいと思います。公式のドキュメントで紹介されているチュートリアルでは、バックエンドとして Firebase を使っていますが、それをそのままやっても面白くないので、今回は AWS Lambda でバックエンドの処理を実装してみたいと思います。

Google Home で使うアプリの構成

 Google Home の中で実際に音声アシスタントとして動いているのは Google Assistant で、Apple の Siri や Amazon の Alexa に当たるものです。なので Google Home で使うアプリを作るということは、 Google Assistant 用のアプリを作るということになります。ということは Google Assistant が動いていれば、 Google Home だけでなく、 Android 端末や iOS 端末でも使うことができるということになります。

 Google Assistant のアプリは Actions on Google というプラットフォーム上で開発します。主に下記のような要素から構成されています。

  • Actions on Google developer project
     アプリのプロジェクトを管理するベースとなるもので、アプリの分析や、テストのためのシミュレータ等の機能を持っています。

  • Action package
     Google Assistant がアプリをどのように起動するかや、どのように Fulfillment を呼び出すかなどのメタデータを定義しています。

  • Fulfillment
     HTTP Web サービスとしてホストされる、アプリの実機能を提供するファンクションです。JSONベースの Actions Protocol で Google Assistant からのリクエストを受け付け、処理結果を返します。

アプリ開発方法の選択肢

 Actions on Google でアプリを開発する際の開発方法の選択肢としては、下記の3つがあります。

  • Templates
     あらかじめ用意されているテンプレートを使う方法で、クイズやフラッシュカードなどのアプリのテンプレートが用意されています。コードを書く必要がなく、 Google Spreadsheet でデータの用意だけすれば使えるので、アプリの種類がマッチするならこの方法が一番お手軽です。

  • Dialogflow
     以前は API.AI という名前だった、自然言語で会話できる bot を作るためのAPIを提供しているサービスで、2016年9月に Google に買収されました。そして最近 Dialogflow という名前に変わりました。(Introducing Dialogflow, the new name for API.AI)Dialogflow は Actions SDK を Web IDE でラップしたもので、Action package を簡単に作成、デプロイできるようになっています。また、NLUエンジンを含んでいるので、自前で自然言語解析をする必要がなくなります。

  • Actions SDK
     限られたユーザとシンプルなやりとりを行うだけの場合に選択される方法で、既に自前のNLUエンジンを持っていてそれを使いたい場合などは Actions SDK を用いて開発する必要があります。また、 Actions SDK はIDEを提供しないので、テキストエディタなどで Action package を作成し、CLIから Google Developer project へデプロイする必要があります。

 今回は上記の3つの中から、Dialogflowを使う方法で実装します。

Actions on Google と Dialogflow での設定

 それではアプリを作成していきます。 Actions on Google と Dialogflow での設定は、基本的にはチュートリアルで紹介されている内容を踏襲していきます。

Overview  |  Actions on Google  |  Google Developers

 まず Actions on Google のコンソールにアクセスすると、プロジェクトの作成画面になりますので、+マークと Add/Import project と書かれているところをクリックします。

f:id:akanuma-hiroaki:20171013015622p:plain:w450

 するとプロジェクトの作成ダイアログが表示されますので、プロジェクト名と国・地域を選択して、 CREATE PROJECT をクリックします。

f:id:akanuma-hiroaki:20171013015913p:plain:w450

 プロジェクトが作成され、プロジェクトのOverviewの画面が表示されます。先ほど紹介した3つの開発方法のパネルが表示されますので、今回は Dialogflow のパネルの BUILD をクリックします。

f:id:akanuma-hiroaki:20171013020107p:plain:w450

 Action を追加するためのダイアログが表示されますので、 CREATE ACTIONS ON DIALOGFLOW をクリックして、Dialogflow のコンソールへ移動します。

f:id:akanuma-hiroaki:20171013020154p:plain:w450

 Dialogflow のコンソールが表示されたら、 Description にActionの内容を入力し、 DEFAULT LANGUAGE を選択して SAVE をクリックして保存します。

f:id:akanuma-hiroaki:20171013020251p:plain:w450

 すると Intents の画面が表示されます。ここでまず Fulfillment を使うためのとりあえずの設定をするため、左メニューから Fulfillment をクリックします。

f:id:akanuma-hiroaki:20171013020318p:plain:w450

 Fulfillment の画面が表示されたら、右上のトグルが DISABLED になっていますのでクリックして ENABLED に変更し、URL にとりあえずのダミーのURLを設定して、画面下部の SAVE をクリックします。

 余談ですが、 Dialogflow に名前が変更になったのと同時にこの画面に Inline Editor が用意されたので、例えばチュートリアルで紹介されていたような、テキストエディタで Function を書いて CLI で Firebase にデプロイするというようなことは、 Inline Editor のみで完結できるようになっているようです。

 保存したら左メニューから Intents をクリックして Intents の画面に移動します。

f:id:akanuma-hiroaki:20171013020503p:plain:w450

 作成済みの Intent の一覧が表示されます。最初から Default Fallback Intent と Default Welcome Intent が用意されています。まずは Default Welcome Intent をクリックします。

f:id:akanuma-hiroaki:20171013020605p:plain:w450

 Default Welcome Intent はユーザが Action を呼び出した際の WELCOME イベントによって最初に起動されます。各項目の設定方法の詳細はチュートリアルで紹介されているので割愛しますが、今回は下記のような内容で設定しています。保存したら再度左メニューから Intents をクリックして Intent の一覧画面に戻ります。

f:id:akanuma-hiroaki:20171013020634p:plain:w450

 次にユーザが Slack にポストしたい内容を受け取るための Intent を作成するため、画面上部の CREATE INTENT をクリックします。

f:id:akanuma-hiroaki:20171013020706p:plain:w450

 新しい Intent の作成画面が表示されたら、下記のような内容で設定します。この Intent ではユーザが話したことをそのまま Slack にポストするため、パラメータとしては @sys.any で全文を使用するようにしています。リクエストを受け取ったら Slack へ投稿するための処理を Fulfillment にリクエストするため、 Fulfillment の設定は Use webhook を選択し、Slack にポストして処理は完結なので、 Google Assistant の設定で End conversation にチェックを入れています。

f:id:akanuma-hiroaki:20171013020751p:plain:w450

 ここまでで一旦 Actions on Google と Dialogflow コンソールでの設定は終了し、 AWS Lambda と API Gateway の設定を行います。

AWS Lambda Function の作成と API Gateway の設定

 今回は AWS Lambda Function と API Gateway、 Slack の Webhook の詳しい作成方法は主題ではないので割愛しますが、まず Lambda の Function は下記のような内容で作成し、postToSlackFromGoogleHome という名前で保存しました。 SLACK_POST_URL には実際の Slack の Webhook のURLを設定してください。

#-*- coding:utf-8 -*-
from urlparse import urljoin
from urllib import urlencode
import urllib2 as urlrequest
import json
import random

SLACK_POST_URL = "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX"

def build_payload(message):
    payload = {
        "username": "Google Home",
        "text": message
    }
    return payload
    
def post(payload):
    payload_json = json.dumps(payload)
    data = urlencode({"payload": payload_json})
    req = urlrequest.Request(SLACK_POST_URL)
    response = urlrequest.build_opener(urlrequest.HTTPHandler()).open(req, data.encode('utf-8')).read()
    return response.decode('utf-8')

def post_to_slack(message):
    payload = build_payload(message)
    return post(payload)

def lambda_handler(event, context):
    return post_to_slack(event['result']['parameters']['any'])

 チュートリアルで紹介されているように node.js で Fulfillment を実装する場合には Actions on Google の SDK が利用できるので、SDKが提供するメソッドでリクエスト内容を取り出したりできますが、今回は Lambda で Python による実装なので、送信されてくる JSON から目的のパラメータを取り出しています。送信されてくる JSON で使用するパラメータに関する部分を抜粋すると下記のような内容になります。 Intent の設定でパラメータとして @sys.any を指定したので、 parameters の中で any として目的の内容が送信されてきています。今回はとりあえずユーザが話した内容だけ取れれば良いので、他の部分についてはあまり調べていません。

{
  "id": "7a27e8ff-4e18-4e80-89a0-141dbb3965a5",
  "timestamp": "2017-10-12T17:17:21.169Z",
  "lang": "ja",
  "result": {
    "source": "agent",
    "resolvedQuery": "投稿テスト",
    "action": "",
    "actionIncomplete": false,
    "parameters": {
      "any": "投稿テスト"
    }
  }
}

 作成した Lambda Function を API Gateway と紐づけ、API をデプロイしたら、そのURLを Dialogflow のコンソールの Fulfillment の画面で URL の項目に設定して保存します。

f:id:akanuma-hiroaki:20171013083215p:plain:w450

アップデートとテスト

 ここまでで一通りの設定は終了なので、テストをしてみます。まずは Dialogflow コンソールの左メニューから Integrations をクリックします。 Integration の一覧画面に移動したら、 Google Assistant のパネルをクリックします。

f:id:akanuma-hiroaki:20171013021018p:plain:w450

 初回は下記のようなダイアログが表示されるかと思いますので、そのまま DONE をクリックします。

f:id:akanuma-hiroaki:20171013021143p:plain:w450

 Google Assistant の Integration の設定ダイアログが表示されたら、下部の TEST をクリックすると、下記の画面のように Test now active と表示され、シミュレータでのテストができるようになりますので、 VIEW をクリックして Actions on Google のシミュレータに移動します。

f:id:akanuma-hiroaki:20171013021202p:plain:w450

 シミュレータに移動すると、 Input フォームにはあらかじめ「スラックポストにつないで」というテキストが入っているかと思いますので、Input フォームを選択して Enter します。

f:id:akanuma-hiroaki:20171013085449p:plain:w450

 すると Action が起動し、右側にはリクエストの JSON の内容が表示されます。続けてユーザが話した想定の文言を Input フォームから入力して Enter します。

f:id:akanuma-hiroaki:20171013085507p:plain:w450

 追加で作成した Intent の設定内容に従って処理が行われ、レスポンスが表示されます。

f:id:akanuma-hiroaki:20171013085524p:plain:w450

 実際に Slack の Webhook へのリクエストも行われ、下記のように Slack にメッセージが表示されます。

f:id:akanuma-hiroaki:20171013085544p:plain:w450

 これでとりあえず Google Home に話した内容を Slack にポストするアプリを作ることができました。

まとめ

 今回はお試しということで、ごくシンプルな内容でテスト段階までということでしたが、 Lambda 等と連携できればそこから先は自由度高く色々なことができると思います。また、一般的に使ってもらえるようなアプリを作って公開すれば、多くのユーザに使ってもらうこともできます。会話で処理を進めるというUIは画面を使うアプリと比べて用途が絞られると思いますが、もうすぐ Google Home mini も発売されますし、色々試して新しい使い方を模索してみたいと思います。

線形計画とシンプレックス法

こんにちは、システム開発部のちょうです。

最近秋の基本情報技術者試験(FE)を目標にして勉強しています。FEといえば、いろんな分野の問題が出題されます。その中に、OR・IEにおける線形計画問題は個人にとって一番難しかったです。

例えば、平成25年秋、問75

製品X及びYを生産するために2種類の原料A,Bが必要である。製品1個の生産に必要となる原料の量と調達可能量は表に示すとおりである。製品XとYの1個当たりの販売利益が,それぞれ100円,150円であるとき,最大利益は何円か。

原料 製品Xの一個当たりの必要量 製品Yの一個当たりの必要量 調達可能量
A 2 1 100
B 1 2 80

線形計画を分からなくても、問題を読んだら制約条件や目的関数が理解できると思います。

製品Xの生産量を x 、製品Yの生産量を y とする。

制約条件

2x + y <= 100
x + 2y <= 80

最大利益 max の関数

max = 100x + 150y

一見二変数の方程式ですが、不等式のゆえ、そのまま解けてはならないです。でも現実と繋がる生産の問題なので、生産量は0とそれ以上、つまり負数にならないことが分かります。

x, y >= 0

不等式と合わせて、生産量x, yの範囲は計算できそうですが、 xy お互い関連して、それといつ最大利益になるかも分からなくて、範囲を計算しても意味がないかもしれません。

ほかに方法がないでしょうか。

続きを読む

Rails 5.2で追加予定のActiveStorageを使ってみる

こんにちは、Webエンジニアの本間です。 先週、広島で RubyKaigi 2017 が開催されましたね。私は参加できなかったのですが、興味深い発表が多かったので、公開されているスライドは一通りチェックしたいなと思っています。

さて、今回のブログでは、少し乗り遅れた感がありますがRails 5.2で追加予定の ActiveStorage を使って見て、感想や気になる点などを書いていこうかなと思います。

※注意:現在開発中のalphaバージョンのRails 5.2を使用しています。記事公開後の修正によってはサンプルコードが動作しなかったり、記載された内容と動作が異なる可能性があります。

続きを読む

EC2のタグ情報で設定ファイルを切り替える

おはこんばんにちは、インフラのすずきです。

先日iPhone 6s Plusがお風呂にダイブして、新型iPhoneが出る直前で本体交換しないといけないのか!?
とドキドキしていましたが、なんとか無事で今も順調に動いてくれています。
少なくともiPhone X発売まで頑張って頂きたいところです。

本題ですが、AWSのEC2タグ情報を利用して設定ファイルを切り替える方法をご紹介します。

続きを読む

Swift3 で CoreBluetooth 実装するときにあったら良さそうな Data Extension

こんにちは、iOSエンジニアのしだです。
iOSアプリからBLE経由で Characteristics の値を読み込む場合、20バイト程度のデータをデコードする必要があります。 主に Bluetooth SIG で定められているGATTのデータフォーマットで、あったら便利な Data の Extension を共有します。

準備

  • Xcode 8.3.3 (Swift 3.1)
続きを読む

ffmpegで写真や動画を加工してみよう!

こんにちは。エンジニアの田渕です。

東京では、夜になると秋の虫の声が聞こえるようになりました。ついこの間まで、寝苦しい夜に悩んでいたのに、月日の経つのは早いものです。

日本は秋から冬にかけ比較的行事が多く、その分、写真や動画を撮影する機会も増えます。 最近ではスマートフォンの無料アプリで驚くくらいに加工ができるので、利用されている方も多いのではないでしょうか。 今回は、そんな画像や動画の加工を、コマンドでやってみよう、という記事です。

以前、こちらのブログでffmpegを利用してクロマキー合成する、という記事を書かせてもらいましたが、今回も同じくffmpegを利用して、画像の加工をしていきます。 ffmpegって何?という部分については、前回記事を参照ください。 tech.unifa-e.com

加工してみよう!

では、さっそく加工して行きましょう。

元の画像

こちらの写真は、春先に道端で撮影したものです。夜、そしてスマートフォンのカメラ、という悪条件。 特に何か加工をしたりした訳ではなく、なんだか偶然、全般的に桜の色になっております。 ただ、……ちょっと、くらいかな。。。 f:id:unifa_tech:20170904233442j:plain

コマンド紹介

ffmpegには画像加工用のフィルターという概念があるのですが、そのフィルターのオプションを利用することで、画像や動画の加工が出来ます。 今回利用するのは、eqというオプション。

公式ドキュメントは下記になります。

FFmpeg Filters Documentation

なんだか、色々とたくさんありますが、今日はこの中の、brightness、saturation、contrastを使ってみたいと思います。

コマンドは至って簡単、下記のような具合に指定して使います。

ffmpeg -i source.jpg -vf eq=brightness=0.06 -c:a copy output.jpg

赤文字の部分が、今回の主役であるeqオプションの指定部分です。サンプルは、brightnessを指定した例。 他のオプションも、同じような感じで指定します。 組み合わせて使うことも出来ますが、今回はそれぞれの効果を見るために、一つだけ指定したものを順に見ていきます。

brightness

日本語で言う「輝度」です。サンプル画像は、上で紹介したコマンドをそのまま実行した結果です。 元の画像と比較して、全体的に明るくなっているのがわかるでしょうか? f:id:unifa_tech:20170904233603j:plain

saturation

こちらは「彩度」を変更した画像です。saturation=2という値で実行しています。かなり鮮やかになりましたね。 f:id:unifa_tech:20170904235821j:plain

contrast

最後が「コントラスト」。contrast=2で実行した場合です。元の画像と比べると、くっきり物が見えるようになったような? f:id:unifa_tech:20170904233650j:plain

どうしてffmpegか?

ここまで見てきてもらったように、コマンドでオプションを指定することで、画像の加工ができることがわかりました。実はコマンドラインで画像加工出来るソフトは他にももっと色々とあるのですが、(もっと良く使われているImage Magickとか。)なぜ今回ffmpegを使ったのか?といえば、ffmpegは↑に書かれているコマンドと、全く同じコマンドで、動画の加工が出来るからです。 入力と出力のファイル名を、それぞれ動画のファイルにするだけで、暗かった動画を明るく出来たりします。

まとめ

撮影チャンスは突然にやってくるもので、必ずしも撮影に最適な状況にはなっていません。それでも、一か八か!でシャッターを切るわけですが、結果、「真っ暗で何も見えない」とか「ぼんやりしていて……。」なんてことも、ままあります。

そんな時、園の先生方がパソコンのソフトなどを使って苦労して画像を修正するのではなく、サーバー側でなんとか出来ないかなぁ……なんてことを夢見ています。 もちろん、実現には、色々とクリアすべき課題があるのですが……。

ffmpegのフィルターオプションは膨大で、なかなか面白いものも眠っているので、また折を見てご紹介できればと思います。

それでは!