ユニファ開発者ブログ

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

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

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