Technology Topics by Brains

ブレインズテクノロジーの研究開発機関「未来工場」で働くエンジニアが、先端オープン技術、機械学習×データ分析(異常検知、予兆検知)に関する取組みをご紹介します。

Dockerコンテナ上で起動したGreengrassとWindowsとの連携で広がる新たなデータ活用の選択肢

こんにちは、ブレインズテクノロジーの岩城です。

2018年11月末に開催されたAWS re:Invent2018で、AWS Greengrass IoTをDockerコンテナ上で起動できるようになったことが発表されました。これにより、GreengrassをWindows上で活用する際のハードルが下がり、データ活用を検討する際の選択肢がこれまで以上に広がっています。

今回の記事ではエッジコンピューティングに関する最近のトピックとして、GreengrassをWindowsと連携することで可能になった新しいデータ活用のイメージを紹介します。

より技術的なことに焦点を当てた記事も後日公開予定なので、実際にGreengrassをWIndows上で動かすことに興味がある方はこちらもご参照ください。

[2019年2月4日追記]
GreengrassをDocker for Windowsで使用するまでの準備と、今回テストとして実施した内容をまとめた記事を公開しました。

blog.brains-tech.co.jp

背景

従来は、GreengrassのソフトウェアはLinux系のパッケージのみで提供されており、かつDockerコンテナ上で動かすことはできませんでした。

しかし、AWS re:Invent2018で発表があったように、現在はGreengrassをDockerコンテナ上で起動することができるようになっています。

aws.amazon.com

Linuxだけでなく、WindowsやmacOS上からでもコンテナ経由でGreengrassをより容易に使えるようになった結果、 データを収集するところからの他のデバイスとの連携という点を含め、これまではなかった効率的なデータ活用の選択肢が生まれています。

実際にどのように可能性が広がったかより明確にするため、この技術の活用イメージとして生産現場からGreengrassまでのデータの流れの一例を紹介します。

AWS IoT Greengrassとは

補足として、AWS IoT Greengrassについても簡単に紹介しておきます。

AWS IoT Greengrass (以下Greengrass)はクラウドのAWSの機能の一部をローカルデバイスでも実行できるようにすることができるソフトウェアです。 例えば、弊社の場合はクラウドで提供している異常検知機能をローカルのコンピュータ (エッジコンピュータ)で実行するために使用しています。

ざっくりとした説明ですが、Greengrassを使用することで、クラウド環境で動作している一部のアプリケーションをローカル(エッジ側)のPCでも動作させることができ、 連携対象の設定などもまとめて行うことができると認識いただければ良いかと思います。

クラウドで使用している機能やデバイス間の設定をインターネット経由でエッジ側に配信するので、 ソフトウェアのアップデートやシステムの設定を少ない労力で実施できるほか、スケールさせやすいなどのメリットがあります。

AWS IoT Greengrassについては、こちらの資料も分かりやすかったのであわせてご参照ください。

dev.classmethod.jp

そもそものエッジコンピューティングのメリットとしては、データを取得したその場で実施したい処理を行うことができるため、少ない遅延で結果を得られることや クラウドに送るデータをエッジ側で加工することで、データの送信量を削減できることなどがありました。

他にもいくつかメリットがあるので、エッジコンピューティングそのものに興味がある方は弊社CTO中澤も登壇したAWS re:Invent:2018: Machine Learning at the OkO Edge (IOT214)(英語)、弊社林の過去の登壇資料AWS summit 2018の資料などをご参照ください。

re:Invent2018の映像はAWS Senior ManagerのDavid Nunnerleyによるご講演から始まり、18:12頃からアイシン・エィ・ダブリュ株式会社の佐藤雅則様のご講演、27:55頃から弊社中澤の講演という構成になっています。

www.youtube.com

www.slideshare.net

blog.brains-tech.co.jp

https://d1.awsstatic.com/events/jp/2018/summit/tokyo/aws/12.pdf

活用イメージ例

生産現場からのデータの集約・加工

生産現場などのデータを活用するためには、まずはデータを集約し、使える形にする必要があります。

このような役割を果たす仕組みや設備は数多くあるかと思いますが、今回は三菱電機の産業用PC MELIPC1002-Wを使用した場合の例を紹介します。

MELIPC MI1002-Wは、下記の画像に示したような、およそ15cm x 20cmのほどの産業用PCです。

f:id:brains_iwaki:20190124182856p:plain
MELIPC1002-Wの外観

MELIPCはWindowsを搭載しており、主にデータを集約・加工する役割と、次のデータ活用のインターフェイスとしての役割を持ちます。

また、データの集約・加工という観点では、MELIPCは「Edgecross」という、データの収集・加工のためのソフトウェアを標準でインストールしています。

このソフトウェアを使用することで、各種機器からデータを集約し、 設定に応じて加工を施した後にcsvファイルやデータベース(PostgresSQL)に保存することなどを行うことができます。

技術的な検討を実施するにあたり、今回MELIPCの実機をお借りすることができたので、そちらで行った設定や検証については別記事でご紹介します。

MELIPCおよびEdgecrossの詳細については下記の製品ページや紹介ページをご参照ください。
http://www.mitsubishielectric.co.jp/fa/products/edge/melipc/items/mi1000/index.html
https://www.edgecross.org/ja/edgecross/

集約・加工されたデータの活用

MELIPCによって収集・加工されたデータはWindows上などに保存されます。このデータをDocker for Windows経由でWindows上で動作しているGreengrassが読み出し、より高度なデータ活用を行います。 ここでのデータ活用としては、例えば弊社の場合のGreengrassを用いたエッジ環境上での異常検知が分かりやすいと思います。

ここまで紹介した例でのデータの流れを一旦整理すると下記のようになります。

  1. まずは生産現場などでデータを取得。
  2. 取得したデータをMELIPC上のEdgecrossにより集約・加工し、MELIPC内のWindows上などに保存。
  3. 保存したデータをDocker on Windows上のコンテナ内で動作しているGreengrassが読み出し、異常検知などの用途に活用。

あくまでも一例ですが、このような流れで得られたデータを使うことができます。

Greengrassからクラウドへのデータ連携

Greengrassで目的の用途で使用された後の結果やデータなどは、次の目的に応じ、クラウドにアップロードすることができます。 クラウドへのアップロードには通常のネットワークを使用する他、SORACOMさんが提供しているIoT向けの安価なワイヤレス通信を利用する選択肢があります。

soracom.jp

データをアップロードする場合は、例えば新規に得られたデータを使って異常検知モデルを更新したり、他の分析に使用することができます。

もちろん、データのアップロードは行わず、異常検知結果や実施した記録だけをクラウドに記録すしたり、あるいは全く何もしないという選択肢もあります。この辺りはどのようにデータを使いたいか、という方針に従って決めていくことになります。

活用イメージ例のまとめ

以上を踏まえ、MELIPCによるデータ収集、SORACOMのサービスを利用したクラウドとの連携、弊社の異常検知サービスImpulseをGreengrass上で使用した場合のデータ活用例の全体像をまとめたものが下記の図です。

f:id:brains_iwaki:20190129123450p:plain
GreengrassとWindowsの連携を利用したデータ活用イメージ例

データ収集技術や通信技術などの従来から実現していた仕組みに加え、今回可能になったGreengrassとWindowsの連携により、これまで以上にデータ活用の選択肢が広がっていることを感じていただけますと幸いです。

まとめ

GreengrassがDocker上で起動できるようになったことをきっかけに広がった選択肢や活用イメージ例について、MELIPCやSORACOMなどの既存サービスとの連携も含めて紹介しました。

今回の技術的進歩がもたらした大きなメリットは、使いたいデバイスやソフトウェアがある場合や、既に導入している機器をベースに新しい取り組みを行いたい場合などに可能な選択肢が増えたことにあるのではないでしょうか。

用途やアプローチは数多くあるかと思いますが、この技術で広がった選択肢により、世の中のデータ活用が更に進むことを期待しています。


ブレインズテクノロジーでは「共に成長できる仲間」を募集中です。
採用ページはこちら

参考資料

速報 re:invent 2018

こんにちは、ブレインズテクノロジーの佐々木です。現在AWSのイベントであるre:invent 2018に参加しています。

こちらの時間で11月28日(水)の午前中にCEOのAndy Jassyによる基調講演が行われました。 基調講演は例年新しいサービス・機能が発表される場であるため、毎年非常に注目されています。

今年もたくさんの新サービスが発表されましたので、簡単に紹介したいと思います。 なお、この記事は速報のため、内容に誤りがある場合がございます。ご了承ください。

f:id:ryotasasaki:20181129042326j:plain
会場の様子

続きを読む

JavaScriptでデータフレーム操作 - dataframe-js -

はじめに

こんにちは、ブレインズテクノロジーの佐々木です。4月に新卒で入社しました。学生時代は熱帯林を這いつくばって植物と戯れていました。

CSVからデータを読み取って、ちょっとした分析をしたい!というシーンはよくありますよね。そうした手軽なデータ操作のツールとしては、PythonのpandasやRのdplyrなどが機能も充実していて使いやすいため、人気があるようです。また、大量のデータを製品環境で扱うような場面では、hadoopやsparkなどの分散処理基盤を活用することも多いようです。ただ、これらの選択肢が便利すぎるがゆえに、フロントエンドでデータ操作に迫られたときにストレスを感じる人も多いのではないでしょうか。

ということで、今回は、ブラウザ上でも便利にデータ操作ができるJavaScriptライブラリ、dataframe-jsに入門します。

インストールと準備

といいつつ実際にブラウザで動かすのは面倒なので、今回はnode上で動かします。基本的にはブラウザでも同様に動く(と信じて)います。またもっと手軽に試したい方はJupyter notebookにIJavaScriptカーネルを入れると良いかもしれません(ここまでやると、pandas使えよ!ってツッコミが入りそうなのでやめました)。

npm install dataframe-jsでインストールできます。 前提として、以下のようなディレクトリ構成で作業しています。

.
├── iris.js
├── main.js
├── package.json

csvからデータを読み込む場合、データそのものではなくてPromiseが返ってきます。今回は以下のようなIrisクラスにメソッドを書き加えて、main.jsから呼び出す形で作業を進めて行きます。

  • iris.js
const DataFrame = require('dataframe-js').DataFrame;

module.exports = class Iris {
  constructor(params) {
    this.url = params.url;
    this.dfPromise = DataFrame.fromCSV(this.url);
  }
  
  showDF() {
    this.dfPromise.then(df => {
      df.show();
    });
  }
}
  • main.js
const Iris = require('./dataframe');

const iris = new Iris({
    url: 'https://raw.githubusercontent.com/uiuc-cse/data-fa14/gh-pages/data/iris.csv'
});

iris.showDF();

node main.jsで実行します。

ちょっとアヤメの話

それでは本題に入ります。この記事のゴールは、iris(アヤメ)データセットを用いて種ごとに花弁(petal)と萼片(sepal)のサイズの関係を調べ(て、I. versicolor, I. virginica, I. setosa各種の形態的特性とその適応的意義について考察す)ることとします。

USDA Forest Serviceによると、この三種はいずれも湖畔沿などの湿地に生息しsetosa(アラスカ), versicolor(五大湖付近), virginica(ニューヨークからフロリダまで)の順に寒冷地に分布しています。一般に寒い方が大気飽差が小さく(相対湿度が大きく)蒸散で水を失いにくいことが知られています。また、組織が幅広だと乾燥重量あたりの蒸散量が増えるので、virginica, versicolor, setosa の順に花弁および萼片が細長であるという仮説をたてました。

この仮説を検証するために、各3種ごとに花弁と萼片の縦横の比の平均と標準偏差を計算し、比較します。

f:id:ryotasasaki:20181009144400p:plain http://suruchifialoke.com/2016-10-13-machine-learning-tutorial-iris-classification/

データ分析

まずはデータの基本的な整形をしてみます。行のフィルターにはfilter, 列の選択にはselectが使えます。使い方はpandasというよりも、Rのdplyrやsparkのデータフレームに似ています。

  • iris.js
filterAndSelectDF() {
    this.dfPromise.then(df => {
      df
        .filter(row => row.get("species") === "versicolor")
        .select("sepal_length", "sepal_width", "species")
        .show(3);
    });
}
  • 結果
sepal_length sepal_width species
7 3.2 versicolor
6.4 3.2 versicolor
6.9 3.1 versicolor

次に、花弁縦横比(縦/幅)を計算してみます。

  • iris.js
mutateWLratio() {
    this.dfPromise.then(df => {
      df
        .map(row => row.set('sepal_wlratio', row.get('sepal_length') / row.get('sepal_width')))
        .map(row => row.set('petal_wlratio', row.get('petal_length') / row.get('petal_width')))
        .select("sepal_wlratio", "petal_wlratio", "species")
        .show(3);
    });
}
  • 結果
sepal_wlratio petal_wlratio species
1.4571 6.9999 setosa
1.6333 6.9999 setosa
1.46875 6.5 setosa

最後に、種ごとにグループ演算をして各種の形質の平均・標準偏差を求めます。groupByで指定した列に対しGroupedDataFrameオブジェクトが返されます。GroupedDataFrameオブジェクトはaggregateメソッドをもち、グループ(種)ごとに関数を適用できます。

今回は花弁・萼片それぞれに平均と分散を種ごとに計算し、一つのデータフレームにまとめています。

  • iris.js
calcSppStats() {
    this.dfPromise.then(df => {
      const groupedDF = df
      .chain(
        row => row.set('sepal_wlratio', row.get('sepal_length') / row.get('sepal_width')),
        row => row.set('petal_wlratio', row.get('petal_length') / row.get('petal_width'))
      )
      .select('sepal_wlratio', 'petal_wlratio', 'species')
      .groupBy('species');
      groupedDF
        .aggregate(group => group.stat.mean('sepal_wlratio'))
        .rename('aggregation', 'sepal_wlratio_sp_mean')
        .join(
          groupedDF
            .aggregate(group => group.stat.sd('sepal_wlratio'))
            .rename('aggregation', 'sepal_wlratio_sp_sd')
        , 'species', 'inner')
        .join(
          groupedDF
            .aggregate(group => group.stat.mean('petal_wlratio'))
            .rename('aggregation', 'petal_wlratio_sp_mean')
        , 'species', 'inner')
        .join(
          groupedDF
            .aggregate(group => group.stat.sd('petal_wlratio'))
            .rename('aggregation', 'petal_wlratio_sp_sd')
        , 'species', 'inner')
        .show(3);
    });
}
  • 結果
species sepal_wlratio_sp_mean sepal_wlratio_sp_sd petal_wlratio_sp_mean petal_wlratio_sp_sd
setosa 1.4745 0.1186 7.0779 3.1237
versic 2.1604 0.2286 3.2428 0.3124
virginica 2.2304 0.2469 2.7806 0.4073

また、group.stat.statsというメソッドを使うと各種統計量がまとめて計算できます。

分析まとめ(アヤメ)

以上の結果をテーブルにまとめました。

species 花弁縦横比(縦/幅)・平均 ± 標準偏差 萼片縦横比(縦/幅)・平均 ± 標準偏差
setosa 1.48 ± 0.12 7.10 ± 3.12
versicolor 2.16 ± 0.23 3.24 ± 0.31
virginica 2.23 ± 0.25 2.78 ± 0.41

花弁は仮説の通り、virginica, versicolor, setosa の順に細長でした。しかし、萼片については反対にsetosa, versicolor, virginicaの順に細長でした。考察として、花弁や萼片の形状(縦横比)だけでなく面積そのものや生理学的特性も考慮する必要があると考えられます。

まとめ(dataframe-js)

流石にpandasには劣りますが、基本的なデータ操作はストレスなくできた気がします。クライアント側でデータ操作がスムーズにできることで、plotly.jsなどと組み合わせて(df.toCollection()とすると簡単にオブジェクトに変換できます)1. リッチな可視化が比較的簡単に実装できるようになる、2. pandasなどを使ってデータ処理をするためだけにサーバーサイドに処理を投げる必要がなくなりデータフローがすっきりさせられる、などのメリットがあるのではないでしょうか。

追記

はてなブログでJavaScriptが実行できることを知ったので、一応試しておきました。表示ボタンを押すとhttps://raw.githubusercontent.com/uiuc-cse/data-fa14/gh-pages/data/iris.csv のデータがロードされ、一連のこれでブラウザでも同様に動く(と信じて)ことも確認できました。

結果はここに出ます。
<script src="https://cdn.rawgit.com/Gmousse/dataframe-js/master/dist/dataframe-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<input id='show_button' type=button value='表示'>
<script type="text/javascript">
$(function() {
    $('#show_button').click(function() {
        const DataFrame = dfjs.DataFrame;
        DataFrame.fromCSV('https://raw.githubusercontent.com/uiuc-cse/data-fa14/gh-pages/data/iris.csv')
            .then(df => {
                const groupedDF = df
                    .chain(
                    row => row.set('sepal_wlratio', row.get('sepal_length') / row.get('sepal_width')),
                    row => row.set('petal_wlratio', row.get('petal_length') / row.get('petal_width'))
                    )
                    .select('sepal_wlratio', 'petal_wlratio', 'species')
                    .groupBy('species');
                const resDF = groupedDF
                    .aggregate(group => group.stat.mean('sepal_wlratio'))
                    .rename('aggregation', 'sepal_wlratio_sp_mean')
                    .join(
                        groupedDF
                        .aggregate(group => group.stat.sd('sepal_wlratio'))
                        .rename('aggregation', 'sepal_wlratio_sp_sd')
                    , 'species', 'inner')
                    .join(
                        groupedDF
                        .aggregate(group => group.stat.mean('petal_wlratio'))
                        .rename('aggregation', 'petal_wlratio_sp_mean')
                    , 'species', 'inner')
                    .join(
                        groupedDF
                        .aggregate(group => group.stat.sd('petal_wlratio'))
                        .rename('aggregation', 'petal_wlratio_sp_sd')
                    , 'species', 'inner');
                const resJSON = JSON.stringify(resDF.toCollection(), null, 2);
                $('#result').text(resJSON);
            })
    });
});
</script>


ブレインズテクノロジーでは「共に成長できる仲間」を募集中です。
採用ページはこちら

参考

まだ日本語対応していないAmazon LexのBotをAmazon Translateで翻訳させてみた

こんにちは、ブレインズテクノロジーの桑原です。

ブレインズテクノロジーは、ImpulseやNeuronといった、企業の生産性を劇的に向上させるサービス開発に従事していますが、私達自身の生産性も向上させるため、利用するシステムの探求と刷新を繰り返しています。

最近では、アマゾンウェブサービスが提供する機械学習サービスのAmazon Lexを利用して、営業記録やスケジュールの登録を簡素化できないかと模索をしていましたが、Amazon Lexとの会話は全て英語でした。

この度、Amazon Translateが日本語に対応したことを記念して、Amazon Lexとの英会話をリアルタイムに日本語に翻訳するBotを作ってみたいと思います。

f:id:brains-tech:20180830010820p:plain:w200
アマゾンウェブサービスが提供する機械学習サービス

Slack Botがやること

Slackでスケジュールを登録したいと呟くと、Amazon Lexが何の予定なのかや日時を聞いてくれるBotです。本記事ではAmazon Lexと会話するところまでを解説しますが、これをGoogle Calendar等のカレンダーと連携させて実際に予定を入れることもできたりします。

なお、ZapierがなくてもSlackとAWS Lambdaをつなぐことは出来ますが、今回は極力プログラムを書かないことを目指すためZapierを採用しています。

f:id:brains-tech:20180826220337p:plain

Amazon Translateとは?

深層学習モデルを使用して、従来の統計ベースやルールベースの翻訳アルゴリズムよりも正確で自然な翻訳を提供するニューラル機械翻訳サービスです。

2018年4月にサービスが提供され、当初は下記6言語が英語間翻訳に対応していましたが、

  • アラビア語
  • 中国語 (簡体字)
  • フランス語
  • ドイツ語
  • ポルトガル語
  • スペイン語

AWS Summit 2018 New Yorkにて、2018年7月から日本語を含めた下記6言語にも対応することが発表されました。

  • 日本語
  • ロシア語
  • イタリア語
  • 中国語 (繁体字)
  • トルコ語
  • チェコ語

Amazon Translateの詳細はこちら

Amazon Lexとは?

音声やテキストを使用して、任意のアプリケーションに対話型インターフェイスを構築するサービスです。

Amazon Alexaと同様、音声のテキスト変換には自動音声認識 (ASR)、テキストの意図認識には自然言語理解 (NLU) という高度な深層学習機能が使われているため、チャットBotのようなアプリケーションでリアルな会話を実現することができます。しかし、残念ながら現時点の対応言語は英語のみとなっており、日本語には対応していません (2018年8月時点) 。

Amazon Lexの詳細はこちら

Slack Botを作る

それでは、Slack Botを作っていきます。AWS Lambdaのところで簡単なプログラムを書きますが、後はポチポチするだけで構築完了です。大きくハマることがなければ1〜2時間程度で終わるはずです。

構築ステップ

Amazon LexとSlackの融合

公式サイトのマニュアルに詳しい手順が記載されているので、これを参考にしながら設定をしていきます。本記事では、少しややこしい連携設定を補足します。

  • ステップ 1: Amazon Lexボットを作成する
  • ステップ 2: Slackにサインアップして Slackチームを作成する
  • ステップ 3: Slackアプリケーションを作成する
  • ステップ 4: SlackアプリケーションとAmazon Lexボットを統合する
  • ステップ 5: Slack統合を完了する
  • ステップ 6: 中間テストをする

Amazon TranslateとSlackの融合

Amazon LexがSlackに返答した英語をZapierがひろってAWS Lambdaに投げる仕組みです。こちらもGUIを使ってお手軽に設定をしていきます。

  • ステップ 7: Slackに着信Webフックを追加する
  • ステップ 8: AWS Lambda関数に付与するロールを作成する
  • ステップ 9: AWS Lambda関数を作成する
  • ステップ 10: ZapierでSlackとAWS Lambdaを統合する
  • ステップ 11: 統合テストをする

ステップ 1: Amazon Lexボットを作成する

AWSマネージドコンソールからAmazon Lexを選択します。いくつかサンプルが準備されていますので、今回はその中から[ScheduleAppointment]を選択します。後は、好きなようにサンプルをいじり、[Build]と[Publish]まで実行します。

f:id:brains-tech:20180830124454p:plain

なお、[Build]が完了すると画面右側の[Test Chatbot]でテストチャットを行うことができます。こちらが入力する言葉を変えても、それに応じた返答をし、アポイントを予約するところまで導いてくれることが確認できます。

f:id:brains-tech:20180826234133p:plain:w300

ステップ 2: SlackにサインアップしてSlackチームを作成する

既存のSlackチームを使いたい場合は、このステップをとばしてOKです。

ステップ 3: Slackアプリケーションを作成する

Slack APIコンソールを開き、[Start Buildings]を押下してアプリを作成します。 [Bot Users]と[Interactive Components]の設定をした後、 [Basic Information]のClient IDClient SecretVerification Tokenをメモしておきます。

f:id:brains-tech:20180827112047p:plain

ステップ 4: SlackアプリケーションとAmazon Lexボットを統合する

AWSマネージドコンソールからAmazon Lexを選択します。先ほど作成したAmazon Lexボットを選択し、[Channels]タブからSlackとの連携設定をします。Client IDClient SecretVerification Tokenにはステップ 3のSlack APIコンソールでメモしておいた値を入力します。

f:id:brains-tech:20180827210833p:plain

[Activate]が完了するとPostback URLOAuth URLが表示されるので、こちらをメモしておきます。

f:id:brains-tech:20180827212342p:plain

ステップ 5: Slack統合を完了する

Slack APIコンソールを開き、[OAuth & Permissions]で権限設定をしたあと、Redirect URLsにステップ 4のAWSマネージドコンソールでメモしておいたOAuth URLを入力します。

f:id:brains-tech:20180827213800p:plain

[Interactive Components]と[Event Subscriptions]のRequest URLに、ステップ 4のAWSマネージドコンソールでメモしておいたPostback URLを入力します。

f:id:brains-tech:20180827215339p:plain
f:id:brains-tech:20180827215414p:plain

これで、連携設定は完了です!

ステップ 6: 中間テストをする

Slackにログインし、テストをしたいチャンネルにSlack APIで作ったBotユーザを招待します。 ここでは、realbot子さんが#test-channelにlexbot子さんを招待しています。

f:id:brains-tech:20180827223216p:plain

少し分かりづらいのですが、realbot子さんがこちらが入力したメッセージ。lexbot子さんがAmazon Lexが返してくれているメッセージです。

f:id:brains-tech:20180830114539p:plain

このように、lexbot子さんは英語しか話せないので、次のステップ以降でAmazon TranslateとSlackを融合させて、日本語を話してもらうようにします。

ステップ 7: Slackに着信Webフックを追加する

Slack App ディレクトリを開き、着信 Webフックを追加します。

# lexbot子さんの着信 Webフックを利用してもOKですが、今回は表示名を変えたかったため、別の着信 Webフックを追加しています。

f:id:brains-tech:20180829225619p:plain

名前やアイコンを指定したあと、Webhook URLをメモしておきます。

f:id:brains-tech:20180830125305p:plain

ステップ 8: AWS Lambda関数に付与するロールを作成する

AWSマネージドコンソールからIAMを選択します。[ロール]->[ロールの作成]を押下し、AWSサービスのLambdaを選択します。

f:id:brains-tech:20180829214638p:plain

TranslateReadOnlyポリシーを選択します。

f:id:brains-tech:20180829215132p:plain

任意のロール名を入力しロールを作成します。

f:id:brains-tech:20180829215301p:plain

ステップ 9: AWS Lambda関数を作成する

AWSマネージドコンソールからAWS Lambdaを選択します。[関数の作成]を選択して、関数を一から作成します。 Zapierを利用する場合、関数の名前は必ずzapier_evalとしてください。ステップ 8で作成したロールを選択して関数を作成します。

f:id:brains-tech:20180830130047p:plain

関数コードを入れて保存します。

import json
import boto3
import unicodedata
from urllib.parse import parse_qs
import urllib.request

def chk_lang(string):
    
    for char in string:
        name = unicodedata.name(char)
        if 'CJK UNIFIED' in name or 'HIRAGANA' in name or 'KATAKANA' in name:
            return True
    return False
    
def lambda_handler(event, context):
    
    plain_text = event['plain']
    lang = chk_lang(plain_text)

    if lang: return {'statusCode': 101}

    translate = boto3.client(service_name = 'translate', region_name = 'us-east-1', use_ssl = True)
        
    response = translate.translate_text(Text = plain_text, 
                SourceLanguageCode = 'en', TargetLanguageCode = 'ja')

    translated_text = response['TranslatedText']
    
    hook = 'https://hooks.slack.com/services/xxxxx/xxxxx' #ステップ 7で確認したWebhook URLを入力
    headers = {'Content-Type' : 'application/json'}
    method = 'POST'
    
    json_data = {'text' : '`(ja) ' + translated_text + '`'}
    json_data = json.dumps(json_data).encode('utf-8')

    request = urllib.request.Request(hook,
                data = json_data,
                method = method, headers=headers)

    urllib.request.urlopen(request)
    
    return translated_text

ステップ 10: ZapierでSlackとAWS Lambdaを統合する

Zapier Exploreを開き、[Make a Zap!]を選択します。

# Zapierは様々なサービスを無料で連携させることができますが、AWS Lambdaを連携させる場合は有償となります。

Triggerは、SlackにNew Message Posted to Channelがあった場合とします。

f:id:brains-tech:20180829234023p:plain

Channelを指定し、Trigger for Bot Messages?をnoからyesに変更します。

f:id:brains-tech:20180829235032p:plain

今回は、Invoke Functionを選択します。返り値を受け取ってあれこれしたい場合はRun Codeがいいでしょう。

f:id:brains-tech:20180829235959p:plain

Functionにはステップ 9で作成したzapier_evalを選択、Argumentsでeventオブジェクトに渡すデータとしてSlackのTextを指定します。

f:id:brains-tech:20180830001744p:plain

ZapのステータスをONにしたら、全ての設定が終了です。ふぅ

ステップ 11: 統合テストをする

Slackにログインし、realbot子さんがlexbot子さんに話しかけます。すると、tranbot子さんが英語を日本語に翻訳してくれました!

f:id:brains-tech:20180830131538p:plain

おわりに

トリガーとなる英語を適切に入力しないとAmazon Lexは反応してくれませんが、Hi (やあ) やHello (こんにちは) といった簡単な言葉をトリガーにしておけば、

  1. 英語で入力
  2. 英語で返答 (by Amazon Lex)
  3. 日本語に翻訳 (by Amazon Translate)

から

  1. 日本語で入力
  2. 英語に翻訳 (by Amazon Translate)
  3. 英語で返答 (by Amazon Lex)
  4. 日本語に翻訳 (by Amazon Translate)

とすることもできそうです。

それが実現できると、InputとOutputの言語はAmazon Translateが対応する12言語を自由に組み合わせることができるようになります。 Amazon Lexが英語にしか対応していなくても、多言語コミュニケーションが可能になる訳ですね。

余談

無茶なキーワードを入れたら返ってきた返答です。普段は敬語のtranbot子さんですが、たまに不機嫌になるのでご注意を (笑)

f:id:brains-tech:20180830004242p:plain


ブレインズテクノロジーでは「共に成長できる仲間」を募集中です。
採用ページはこちら

WindowsのDockerで慣れないこと(実践編)

Neuron開発チームの木村です。

Neuronは、ほとんどの場合Windowsにインストールされるため、テスト環境をWindows上に構築しています。 このテスト環境構築を効率化するため、docker上でのWindows利用を模索しています。

前回に引き続き、 Windows上のdockerでイメージをビルドする際・コンテナを動かす際に慣れないことを紹介します。

前回検討した導入方法から、dockerは「Hyper-Vコンテナ + LCOW」の形態で利用しています。 (そのため、Docker for Windowsを利用する場合とは異なる状況であることに注意ください。)

Windowsのバージョンは、
Windows 10 Pro バージョン 1803 (Version 10.0.17134.165)、

dockerのバージョンは、

Client:
 Version:           master-dockerproject-2018-07-18
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        48fbb12b
 Built:             Wed Jul 18 23:51:23 2018
 OS/Arch:           windows/amd64
 Experimental:      false

Server:
 Engine:
  Version:          master-dockerproject-2018-07-18
  API version:      1.38 (minimum version 1.24)
  Go version:       go1.10.3
  Git commit:       7f91801
  Built:            Thu Jul 19 00:00:43 2018
  OS/Arch:          windows/amd64
  Experimental:     true

LCOWのバージョンは、4.14.29-0aea33bcです。

以下、OSがWindowsであるコンテナを「WindowsOSコンテナ」、OSがLinuxであるコンテナを「LinuxOSコンテナ」と呼びます。

目次

イメージビルド編

multi-stage buildsで、名前に半角スペースを含むファイルをCOPYできない

multi-stage buildsでイメージビルドする際、名前に半角スペースを含むファイル(または、それを含むディレクトリ)を、WindowsのイメージにCOPYしようとすると、ビルドが以下のエラーで止まります。

COPY failed: file does not exist

よくある半角スペースの問題と考え、"で括るために
COPY --from=builder ["src/sample file", "."]のJSON形式で指定しても、今度は別のエラーで止まります。

COPY failed: Forbidden path outside the build context

昔からあるファイル・ディレクトリ名の半角スペース問題に、久しぶりに遭遇しました。(厳密には別の問題のようですが…)


RUN/CMDで使われるシェルを意識する

RUN/CMDにシェル形式でコマンドを指定する際、コマンド実行に使われるシェルがcmd(コマンドプロンプト)なのかpowershellなのかを意識することが重要です。powershell特有のコマンドを使う際は自然と意識すると思いますが、このふたつは環境変数の取得方法がだいぶ異なるため、javaのような自分でパスを通すコマンドを使う時に注意が必要です。

cmd powershell
環境変数VARの取得方法 %VAR% $env:VAR
存在しない環境変数NONEから取得される値 %NONE%という文字列 空文字列

シェル形式で書かれたRUN/CMDのコマンド実行に使われるシェルは、デフォルトがcmd(cmd /S /C)ですが、SHELLで変更することができます。
使用するイメージのDockerfileを読むか、docker inspectでイメージのCmdプロパティからシェルを特定するとよいでしょう。たとえば、openjdk:8u171-jdk-nanoserverpowershellをシェルに指定しています。


コンテナ実行編

[LinuxOSコンテナ] メモリ割り当てが変更できない

LinuxOSコンテナに対して、docker run時に--memoryオプションから割当メモリを指定しても無視され、 以下のように、約1GBのメモリが割り当てられます。

PS C:\> docker run --rm --memory 2g alpine:3.8 cat /proc/meminfo
MemTotal:         985568 kB
MemFree:          870932 kB
MemAvailable:     805808 kB
...

WindowsOSコンテナに対しては、以下のように、指定通り約2GBのメモリが割当たります。

PS C:\> docker run --rm --memory 2g microsoft/windowsservercore:1803 systeminfo
...
Total Physical Memory:     2,559 MB
Available Physical Memory: 2,194 MB
...

mobyのissueを読む感じでは、 LCOWを使用しているために起きる問題のようです…


[LinuxOSコンテナ] user指定が効かない

LinuxOSコンテナに対して、docker run時に--userオプションを指定しても無視され、 以下のように、rootユーザで実行されます。

PS C:\> docker run --rm --user guest alpine:3.8 whoami
root

LCOW使用時の既知のバグのようです。 イメージビルド時のUSER指定も効かないため、LinuxOSコンテナは常にrootで動くことになります。

そして、この影響なのか、コンテナ内のディレクトリ・ファイルの所有者とグループが大体root:rootになってしまいます。


[LinuxOSコンテナ] mountされたvolumeに対してchmod/chownが効かない

「イメージビルド時のVOLUME」や「docker run時の--volumeオプション」でvolumeに指定されたディレクトリ以下に対して、chmod/chownが効きません。
以下の例のように、chmodを行っても、終了コードが0であるにも関わらずパーミッションが変更されません(例ではjenkins/jenkins:2.134イメージを使用)。

root@fcdd7ae24179:/var/jenkins_home# ls -la
total 0
-r-xr-xr-x 1 root root  102 Jul 30 05:02 copy_reference_file.log
drwxrwxrwx 1 root root 4096 Jul 30 05:02 init.groovy.d
root@fcdd7ae24179:/var/jenkins_home# chmod 755 copy_reference_file.log
root@fcdd7ae24179:/var/jenkins_home# echo $?
0
root@fcdd7ae24179:/var/jenkins_home# ls -la
total 0
-r-xr-xr-x 1 root root  102 Jul 30 05:02 copy_reference_file.log
drwxrwxrwx 1 root root 4096 Jul 30 05:02 init.groovy.d

既知の問題ではあるようですが、回避策は特にない状況です。
このことが起因する問題には色々当たりましたが、大きかったのはjenkinsでリポジトリをダウンロードする際にエラーが起きることでした。 volume指定を使わないことでエラーを回避しましたが、docker cpでjenkins設定の永続化を行うことになってしまいました…


稼働中のコンテナに対してdocker cpできない

稼働中のコンテナに対してdocker cpを実行すると、以下のエラーが発生してコピーができません。

PS C:\Users\user\Documents> docker cp 7961ee45d606:/var .
Error response from daemon: filesystem operations against a running Hyper-V container are not supported

なお、停止中のコンテナに対しては正常にコピーできます。


おわりに

Windows上のdockerにて、イメージビルドやコンテナ実行の際に慣れないことを紹介しました。 LinuxOSコンテナについて、「volume周りの問題」と「メモリが最大1GB」がかなり厄介な問題です。

上記の他にも以下のようなことがありました。

  • コンテナ自体を作成できないイメージがある
    • 例えば、openjdk:8u171-jdk-nanoserver
    • しかし、openjdk:8u171-jdk-windowsservercore はコンテナ実行可能
  • LinuxOSコンテナ用のDockerfileを、WindowsOS用に書き換えるには細かいところも気にしないとならない
    • javaのclasspath区切りは、Windowsでは:ではなく;、など
  • dockerdを再起動すると、元々稼働していたコンテナがstartできなくなる

やはり、experimentalなLCOWを使ってLinuxOSコンテナを扱うのは、まだ早いのでしょうか。 WindowsOSコンテナのみに絞ればよいかもしれませんが、そうするとLCOWを使う理由もないですし…

docker上でのWindows利用について、LCOWは引き続き試してゆきますが、 現状では、Docker for Windowsが可能な範囲で模索してゆくほうがよさそうです。


ブレインズテクノロジーではエンジニアを募集中です
採用ページはこちら

AngularでPlotlyのグラフを描画してみた

ブレインズテクノロジーの加藤です。

例年以上に暑い日々が続いていますが、如何お過ごしでしょうか。
ブレインズテクノロジーでは、そんな暑い夏にも負けないくらいの空前のボルダリング熱波が巻き起こっています。
週に1回、有志でジムに通っていますが、いい運動になりますし、課題をクリアしたときの達成感はひとしおです。

さて今回は、AngularでPlotlyを動かしてグラフ描画する方法についてまとめます。

はじめに

Angularを触ったことのない人には、下記のチュートリアルがおすすめです。
ヒーローがどうのこうのという謎アプリを作らされますが、何となくのイメージが掴めると思います。

Angular 日本語ドキュメンテーション

新しいアプリケーションを作成

下記のコマンドで、新しいアプリケーションを作ります。
名前は何でもいいですが、今回はとりあえず「angular-plotly」とします。

$ ng new angular-plotly

Plotlyファイルのimport

先ほど作った「angular-plotly」下に移動し、npm経由でplotlyの型定義ファイルを取得します。

$ cd angular-plotly
$ npm install plotly.js --save
$ npm install @types/plotly.js --save

Plotlyによるグラフ描画の実装

シンプルに下記サンプルの棒グラフ「Basic Bar Chart」を描画します。
グラフ出力時のデータなどは、サイトの情報をそのまま用いることにします。
Javascript Graphing Library D3.js-based Bar Charts | Examples | Plotly

ここからは、実際のtypescriptのコードとhtmlファイルを記載します。

  • src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import * as Plotly from 'plotly.js';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
  ngOnInit() {
    const data = [
      {
        x: ['giraffes', 'orangutans', 'monkeys'],
        y: [20, 14, 23],
        type: 'bar'
      } as Plotly.ScatterData
    ];
    Plotly.newPlot('myDiv', data);
  }
}
  • src/app/app.component.html
<div id='myDiv'></div>

解説

簡単にコードを解説します。

import * as Plotly from 'plotly.js';

で、先程npm経由で取得したplotlyを呼べるようにしています。

また、

import { Component, OnInit } from '@angular/core';

で、起動時の動作を定義するインターフェース「OnInit」をimportしています。
「OnInit」をimplementsした「ngOnInit」関数にてPlotly描画の定義をすることにより、Angular起動と同時にグラフ描画処理が実行されます。

export class AppComponent implements OnInit {
  ngOnInit() {
    const data = [
      {
        x: ['giraffes', 'orangutans', 'monkeys'],
        y: [20, 14, 23],
        type: 'bar'
      } as Plotly.ScatterData
    ];
    Plotly.newPlot('myDiv', data);
  }
}

「ngOnInit」関数で定義している内容は、前述の「Basic Bar Chart」のJSコードを一部改変したものです。
Plotlyで描画する際に、dataの型を「Plotly.ScatterData」に変換しています。

Angular起動

下記のコマンドを実行することにより、Angularが起動します。

$ ng serve --open

・・・が、画面に何も描写されないと思います。

デベロッパーツールで確認すると、下記のようなエラーが出ています。

Uncaught ReferenceError: global is not defined

回避策として、src/polyfills.tsの文末に以下を記載します。

 // "global is not defined"の対応
(window as any).global = window;

この状態で再起動すると、無事にグラフが描画されました。

f:id:kato_takayuki:20180723171618p:plain

おわりに

今回は最もシンプルなグラフを生成しましたが、同じ要領でいろいろなグラフが生成出来ると思います。

おわり。


ブレインズテクノロジーでは「共に成長できる仲間」を募集中です。
採用ページはこちら

Pythonの機械学習ライブラリtslearnを使った時系列データのクラスタリング

こんにちは、ブレインズテクノロジーの柏木です。

今回はPythonで扱える機械学習ライブラリのtslearnを使って、時系列データをクラスタリングしていきたいと思います。

github.com

tslearnとは

時系列分析のための機械学習ツールを提供するPythonパッケージで、scikit-learnをベースとして作られているみたいです。 主な機能として、クラスタリング、教師ありの分類、複数の時系列を重ねた際の重心の計算ができたりします。 今回使用するに至った一番のモチベーションは、波形や振動などの時系列データに対してクラスタリングできるというところです。

tslearnインストール

pipコマンドでインストールできます。

pip install tslearn

Kshapeというクラスタリング手法

今回tslearnで使用するモジュールとして、Kshapeというクラスタリング手法を時系列データに適用していきたいと思います。 Kshapeは2015年に下記の論文で提唱された方法で、以下の流れで実行されるアルゴリズムになります。

  1. 相互相関測定に基づいた距離尺度を使う(Shape-based distance: SBD)
  2. 1.に基づいた時系列クラスタの重心を計算(時系列の形状を保持する新しい重心ベースのクラスタリングアルゴリズム)
  3. 各クラスタに分割する方法は一般的なkmeansと同じだが、距離尺度や重心を計算する時に上記1と2を使う

細かい数式や理論については論文を確認下さい。 個人的に、内容がしっかり記載されていてわかりやすい論文だと思います。

Paparrizos, John and Luis Gravano. “k-Shape: Efficient and Accurate Clustering of Time Series.” SIGMOD Record 45 (2015): 68.

今回使用するデータセットについて

今回使うデータセットはthe UEA & UCR Time Series Classification RepositoryにあるDataset: SonyAIBORobotSurface1を加工したものになります。 このサイトには様々な時系列データがありますので、色々見てみるのも楽しいかもしれないです。

使用したサンプルデータはこちらに置いておきます。 あるワーク単位でファイルが10個あり、1秒単位のセンサーデータを想定したものになります。

時系列データのクラスタリング

それでは、時系列データのクラスタリングをしていきたいと思います。 実際のPythonコードを記載します。 まず、ライブラリのインポートです。

#Python 3.6.4
import pandas as pd
import numpy as np
import glob
from tslearn.clustering import KShape
from tslearn.preprocessing import TimeSeriesScalerMeanVariance

import matplotlib.pyplot as plt
%matplotlib inline

次に、いくつか関数を定義しておきます。

def read_file_to_dataframe(filenames):
    #ファイルデータを読み込み、データフレームを返す
    dfs = []
    for filename in filenames:
        original_df = pd.read_csv(filename, index_col=None, header=0)
        dfs.append(original_df)
    return dfs

def time_series_data_to_array(dataframes, target_col=''):
    #データフレームを読み込み、それらを時系列の配列にする
    tsdata = []
    for i, df in enumerate(dataframes):
        tsdata.append(df[target_col].values.tolist()[:])
        #それぞれの時系列データの最大の長さを確認
        len_max = 0
        for ts in tsdata:
            if len(ts) > len_max:
                len_max = len(ts)
        #時系列データの長さを揃えるために、最後のデータを付け加える
        for i, ts in enumerate(tsdata):
            len_add = len_max - len(ts)
            tsdata[i] = ts + [ts[-1]] * len_add
    
    tsdata = np.array(tsdata)
    return tsdata

def transform_vector(time_series_array):
    #ベクトルに変換
    stack_list = []
    for j in range(len(time_series_array)):
        data = np.array(time_series_array[j])
        data = data.reshape((1, len(data))).T
        stack_list.append(data)
    #一次元配列にする
    stack_data = np.stack(stack_list, axis=0)
    return stack_data


filenames = sorted(glob.glob('sample_data/sample_data*.csv'))
df = read_file_to_dataframe(filenames=filenames)
tsdata = time_series_data_to_array(dataframes=df, target_col='data')
stack_data = transform_vector(time_series_array=tsdata)

ここでは、使用するファイルを読み込んで配列に変換します。 この時に、仮に時系列のデータの長さがファイル毎に異なる場合、長さを揃える必要があります。 そのため、今回は時系列上一番最後の値で埋め合わせを行なっています。 長さを揃えた後は、一次元のベクトルに変換しています。

それでは、加工したデータを学習して実際にクラスタリングしていきます。

seed = 0
np.random.seed(seed)
#相互相関を計算するために、正規化する必要があります。
#TimeSeriesScalerMeanVarianceがデータを正規化してくれるクラスになります。
stack_data = TimeSeriesScalerMeanVariance(mu=0.0, std=1.0).fit_transform(stack_data)

#KShapeクラスのインスタンス化
ks = KShape(n_clusters=2, n_init=10, verbose=True, random_state=seed)
y_pred = ks.fit_predict(stack_data)

#クラスタリングして可視化
plt.figure(figsize=(16,9))
for yi in range(2):
    plt.subplot(2, 1, 1 + yi)
    for xx in stack_data[y_pred == yi]:
        plt.plot(xx.ravel(), "k-", alpha=.2)
    #plt.plot(ks.cluster_centers_[yi].ravel(), "r-")
    plt.title("Cluster %d" % (yi + 1))

plt.tight_layout()
plt.show()

以下、結果をプロットしたものになります。

f:id:brains_mkashiwagi:20180607143544p:plain

KShapeクラスの引数n_clustersでクラスタ数を指定することができ、今回はn_clusters=2としました。 実際のデータセットのページを見てみると、AIBOがカーペットを歩いている場合とコンクリートを歩いている場合の二種類のデータとなっています。 それを踏まえて、結果を見てみると良い感じに分離できているように見えます。 今回はクラスタ数を2としましたが、クラスタリングする際によくあるクラスタ数をどのように決めたら良いかという問題について、エルボー法で確認した結果を次に示します。

エルボー法によるクラスターの数の計算

エルボー法とは... 各点から割り当てられたクラスタ中心との距離の二乗の合計をクラスタ内誤差平方和(SSE)として計算します。 クラスタ数を変えて、それぞれのSSE値をプロットし、「肘」のように曲がった点を最適なクラスタ数とする方法です。 また、SSEが小さいほど歪みのない良いモデルであり、うまくクラスタリングできていることになります。

エルボー法について、wikipediaに記載されています。
Elbow method (clustering) - Wikipedia

distortions = []

#1~10クラスタまで計算 
for i in range(1,11):
    ks = KShape(n_clusters=i, n_init=10, verbose=True, random_state=seed)
    #クラスタリングの計算を実行
    ks.fit(stack_data)
    #ks.fitするとks.inertia_が得られる
    #inertia_でSSEを取得できる
    distortions.append(ks.inertia_)

plt.plot(range(1,11), distortions, marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Distortion')
plt.show()

f:id:brains_mkashiwagi:20180607152931p:plain

n_clusters=2で肘のように曲がっているので、今回はクラスタ数2で正解のようです。 ただし、一般的にエルボー法ではこのようにはっきりと曲がりがある場合は少なく、あまり精度はよくないようです。 他にもクラスタ数を決める方法は色々ありシルエット分析、X-means、GAP法などがあります。 興味がある方はX-means、GAP法は論文がありますので、参考文献をご覧下さい。

クラスタリングの決め方について、wikipediaに記載されています。
Determining the number of clusters in a data set - Wikipedia

まとめ

今回はtslearnを使って、時系列データのクラスタリングをしてみました。 振動や波形データをうまくクラスタリングできるので、非常に重宝すると思います。 tslearnにはKshape以外にもTimeSeriesKMeansという手法もあり、距離尺度としてDTW(動的時間伸縮法)やSoft-DTWを使ってクラスタリングをすることができます。さらに時系列に対して重心線を引いてくれたりもします。
今回はデータ点数も少なく振動も緩やかであるため、クラスタ数を決めることが容易でしたが、一般的に時系列データのクラスタ数を決めることは難しい問題だったりするので、その辺りを決める良い方法を模索していきたいと思います。


ブレインズテクノロジーでは「共に成長できる仲間」を募集中です。
採用ページはこちら

参考文献および参考ウェブサイト