himanago

C#とかAzureとかMS系の技術メモ中心に書きたいです

Python3エンジニア認定基礎試験に合格したので振り返り(C#er目線で感想など)

はじめに

タイトルの通りです。昨年末、12/27のことですが、駆け込みで試験を受けてきました。その振り返りです。
Python 3 エンジニア認定基礎試験」です。
www.pythonic-exam.com

経緯

昨今のPythonの人気・需要の高まりから、「Pythonを勉強したい」という声は日に日に多く聞かれるようになり、社内で育成担当をしている身としても、もういい加減無視できなくなってきたわけです。
Pythonに関してはこれまでまったく触ったことがなく、「Pythonはインデントでブロックを定義する」とか「機械学習や統計の分野で多く使われる」とかそんな程度の知識しかない状態。
なんとか効率よく基本を押さえたい…と思い見つけたのがこの試験。

今年の6月あたりだったか、割引キャンペーンで教材のオライリー本が実質無料でついてくるセットがあったので買ったのですが、なかなか時間が取れず、有効期限が年内だったので駆け込みでぎりぎり受けてきた…という経緯です。

学習内容

教材である『Pythonチュートリアル(第3版)』をざざーっと読みました。基本、それだけ。
模擬試験がここで公開されているので、それも本を読む前に1回見ました。
先に見てしまったのは、どんなふうな問題が出るのか確認したうえで本を読むほうが効率がいいと思ったからです。 ぬいぐるみほしさに合格体験記を載せてもらいました。めちゃくちゃ本名ですが、ぬいぐるみのためなら仕方ありません(本名公開が条件)。 www.pythonic-exam.com

ちなみにここでは「Python歴3日」、と書きましたが、模擬試験を見て最初に教材を開いたのが12/25で、試験を受けたのが12/27、その日の夜に合格体験談を書いて送った、というようなスピード感です。そして12/28朝には合格体験談が公開されてました。速い!
模擬試験を見て、この試験では教材の記述、特にサンプルコードがけっこう重視されているということがわかり、その内容をしっかり押さえておけば合格点の7割は簡単にとれるだろう、ということがわかったので、そのつもりで教材を読みました。合計の勉強時間は5~6時間か、もっと少ないかも…。
試験自体はかなり簡単という印象だったものの、合格体験談にも書いたようにこの試験は基礎的な知識を得るためのきっかけ・指針としてかなり役立ちました。やっぱり目標や締め切りがあると、わかりやすくそこに向かっていけるものですからね。

Pythonチュートリアルを読んで印象に残ったこと

せっかくなので、Pythonチュートリアルを読んでPythonの特徴を勉強した中で、印象に残ったことを書いておきます。
教材のページ数と、教材と同内容のWeb版の「Pythonチュートリアル」の章番号、リンクを載せておきます。
※ 前提:これまで経験した主な言語は(学習した順に) C, VBA, Java, JavaScript, C#, Groovy など。
※ 特に最近よく使うのはC#なので、C#との比較も触れられるところは触れます。

リストの内包表記

教材P.44(5.1.3. リストの内包表記)。

squares = []
for x in range(10):
    squares.append(x**2)

squares = [x**2 for x in range(10)]

と書けるというもの。ショートコーディングがはかどりそうで慣れたらきっと便利。
2乗を ** 2 で書けるのを含めてシンプルでいいですね。

ちなみに、C#でも1行。

var squares = Enumerable.Range(0, 10).Select(x => Math.Pow(x, 2));

LINQやっぱりいいなぁ。

ループ変数のスコープ

上記の「リスト内包」の利点として説明されていた、Pythonにおけるループ変数の注意点。
Pythonはループで変数のスコープが閉じないので、さきほどのリスト内包を使わないほうのコードでは変数 x があとまで残ってしまう。そのため、リストの内包表記を使いましょうということだけど、これはうっかり気を抜くと危ないな、と思いました。
特に宣言がないので、スコープがどうなってるのかはちゃんと確認しないとまずそう。 C#とかだと、

for (int i = 0; i < 10; i++)
{
    // iが使える
}
// ここでiは使えない

とやった場合 i のスコープはループ内だけにできる。まぁ、こんな素朴なforはあんまり使うなってことなんでしょうかね(PythonC#も)。
ちなみにスコープ問題でいうとC#でも、forでのブロックとスコープの感覚だと「?」となるのがout-var(outでの変数宣言)のスコープ。

if (int.TryParse(str, out var intVal)
{
    // 当然intValが使える
}
// ここでもintValが使える!

これは、もともと↓と書いていたものと同じ動きをさせたいからこうなってる、と理解してしまえばOK。

int intVal;
if (int.TryParse(str, out intVal)
{
    // 当然intValが使える
}
// ifブロックの外で宣言しているものだから当然intValが使える

タプル

教材P.48(5.3. タプルとシーケンス)。
タプルは最近C#でも入りましたが便利な機能ですね。
Pythonの場合は手軽に

t = 12345, 54321, 'hello!'

でタプルが作れるのと、
要素0のタプルは

empty = ()

で作れるということ、
それから要素1のタプルは

singleton = 'hello',   

のように末尾にカンマを付ければいい、というのがおもしろいなと思いました。

パッキング / アンパッキング

まずは教材P.48(5.3. タプルとシーケンス)あたり。
出てきたのは「引数リストのアンパック」「タプル・パッキング」「シーケンス・アンパッキング」という用語。

タプル・パッキングは、

t = 12345, 54321, 'hello!'

で3つの要素が t へタプルとしてパックされることをいい、シーケンスアンパッキングは

x, y, z = t

t の要素がx, y, zそれぞれに展開されて代入される、という話。

それに関連して、「多重代入はタプルパッキングとシーケンスアンパッキングの組み合わせにすぎない」というのがなかなかおもしろい話だなと思いました。
多重代入はこういうの。

x, y, z = 12345, 54321, 'hello!'

3つの変数に3つの値が代入されるだけだけど、読み解けば右辺がタプルになり、左辺にアンパックされて代入される、ということになるわけですね。
こう、という話。

あとは似た用語で引数リストのアンパックとかもありました。
教材P.35(4.7.4. 引数リストのアンパック)あたり。

range(3, 6)

と渡すような関数の引数に、

args = [3, 6]
list(range(*args)) 

というふうにargsに入った要素を展開してそれぞれの引数に渡せる、というもの。 * を左側に書くことで引数リストのアンパックを実現できる。

ディクショナリのループ

教材P.53(5.6. ループのテクニック)。
ディクショナリ(辞書型)ではitems() メソッドを使うと、キーとそれに対応する値を同時に取り出せる。

knights = {'gallahad': 'the pure', 'robin': 'the brave'}
for k, v in knights.items():
    print(k, v)

これはC#でもありますね。KeyValuePairでまわすやりかた。C#だとforeachにそのまま入れるだけ。

var knights = new Dictionary<string, string>
{
    { "gallahad", "the pure" },
    { "robin", "the brave" }
};

foreach (var pair in knights)
{
    Console.WriteLine("{0} {1}", pair.Key, pair.Value);
}

C#で「Dictionaryオブジェクトをforeachで回して」っていうとだいたい上のやり方か、↓みたいなKeysで回す方法のどっちかを書いてきますね。

var knights = new Dictionary<string, string>
{
    { "gallahad", "the pure" },
    { "robin", "the brave" }
};

foreach (var k in knights.Keys)
{
    Console.WriteLine("{0} {1}", k, knights[k]);
}

比較の連鎖

教材P.54(5.7. 条件についてもう少し)。
a < b == c で、「a が b より小さく、かつ b と c が等しいかどうか」がテストできるというもの。
こういう書き方ができるのはおもしろいですね。

比較結果の代入

教材P.55(5.7. 条件についてもう少し)。

string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
non_null = string1 or string2 or string3
non_null

とやると'Trondheim'が出力される。 SQLのCOALESCEみたいな動きができておもしろい。 C#だとこうかな。

string string1 = null;
string string2 = "Trondheim";
string string3 = "Hammer Dance";
string nonNull = string1 ?? string2 ?? string3;
Console.WriteLine(nonNull);

相互参照の相対インポート

教材P.66(6.4.2. パッケージ内参照)。 パッケージ内の相互参照の相対インポートはカレントモジュールからの参照になるので、メインモジュールとして使われるものは絶対インポートで書かないとだめ。

from . import echo
from .. import formats
from ..filters import equalizer

ファイルの読み込み(ループ)

教材P.78(7.2ファイル. を読み書きする)。 for line in f: の書き方が楽でいいなと思いました。

for line in f:
    print(line, end='')

ファイルの読み込み(with)

教材P.79(7.2. ファイルを読み書きする)。 withで自動でクローズしてくれる。

with open('workfile') as f:
    read_data = f.read()

C#のusingとか、Javaのtry-with-resourcesみたいなイメージ。

例外の投げ方

教材P.86(8.4. 例外を送出する)。 例外は raise で投げる。

try:
    raise NameError('HiThere')
except NameError:
    print('An exception flew by!')
    raise

raiseはなにもかかないとそのまま上に投げてくれる。
C#でも同じだけど、 throw;throw ex; で大きな違いがあってかなりの注意が必要…。
Pythonではそういう気遣いはいらなそう?

あと try-else いいな。 for-else もだけど、 finally と別にこれがあると幅が広がりそう。

def divide(x, y):
    try:
        result = x / y
    except ZeroDivisionError:
        print("division by zero!")
    else:
        print("result is", result)
    finally:
        print("executing finally clause")

Pythonは多重継承できる

教材P.93(9.5.1. 多重継承)。 Pythonは多重継承可。そういえば多重継承って使ったことないな…。

クラス変数、インスタンス変数

教材P.102(9.3.5. クラスとインスタンス変数)。 self.~ ってやらないとstaticになる。これも知らないとあぶない。。

class Dog:

    kind = 'canine'         # 全インスタンスで共有

    def __init__(self, name):
        self.name = name    # インスタンスごと

隠蔽の手段がない

教材P.104(9.4. いろいろな注意点)。
Pythonではデータ隠蔽を強制できるものは存在しない、とのこと。 まじかー。 こういう、仕様の穴?を規約とか慣習でカバーするのはあまり好きじゃないなあ。 …まぁ、いま業務では、privateって書いても問答無用でpublicになる言語1を使ってますけど。。

10章の出題密度がすごい

教材P.115から(10. 標準ライブラリミニツアー)。
試験範囲をみると、教材でいうとわずか8ページしかない第10章から4問(全体の10%)も出題されます。
なので、ここは特に真剣に読みましたね。
ここが重視されてるのは、Pythonの使い道を知るうえで大事だから…?

doctestがお手軽ですごい

教材P.120(10.11. 品質管理)。

def average(values):
    """Computes the arithmetic mean of a list of numbers.

    >>> print(average([20, 30, 70]))
    40.0
    """
    return sum(values) / len(values)

import doctest
doctest.testmod()   # automatically validate the embedded tests

こんなかんじでコメントに埋め込みテストが書ける。手軽でいいなぁ。

おわりに

実は試験当日もはじまるまで教材を読んでた(1周目)くらいなので、勉強がうすいところも多々ありました。
それでもスコアは 865/1000(合格点700点)とれて無事合格できました。
試験自体は簡単な印象だったので、Python経験者にはものたりないかな?というかんじ。
自分のように、他の言語での開発経験があって、それとの比較でささっと勉強するうえではチュートリアルは教材としていいし、試験は目標としてもちょうどいいのかなと思いました。
この試験は続編?のデータ分析試験というものが今後始まるようなので、こちらもチェックしておこうと思います。


  1. Groovyのこと

ブログタイトルを変更しました

はじめに

あけましておめでとうございます。

ブログタイトルを変えた

シンプルにIDの文字列そのままのブログタイトルに変更しました。
変えたので、いちおう記録として記事更新。

もともと「ITエンジニア育成担当の研究ブログ」でしたが、

  • そもそも気に入ってなかった
  • 対外的な肩書が「育成担当」じゃなくなった

あたりが理由で変えました。

気に入ってなかったのは、まず長いし、研究って何よ、という感じだったので。

対外的な肩書としては、いま「テクニカルトレーナー」「アーキテクト」を名刺に載せさせてもらってます。
これを載せているのも、育成担当とか教育担当いう響きがなんとなくえらそうというか、かたくるしいイメージがあったからで、会社に相談してカタカナのロールをつけてもらいました。

…まぁ、トレーナーでもおなじように「教える」「鍛える」感があるので同じかもですが、とりあえずブログからは仕事のロールは外しました。

今後の記事投稿

仕事関係なく、技術的な話題を投稿していけたらなと思っています。

このまえのLINE関係の開発もそうですし、C#とかAzureとかのMS系技術など、自分の好きなことを書いていきたいなと思います。

LINEは絵本読み聞かせをリニューアルしてますしその話を。
MS系技術は.NET Everywhereというか、クラウド、モバイル、Webなどせっかくいろんなところで動く.NETをいろいろためしてまとめたいなという欲求があるのでそんなことを書けたらなと。

おわりに

今年もよろしくお願いします。

LINE BOOT AWARDS 2018 ファイナルステージの振り返り

はじめに

前回の記事でも書きましたが、LINE BOOT AWARDS 2018にエントリーし、2018/11/10(土)開催のファイナルステージに出場してきましたので、その報告と振り返りを書いておきたいと思います。
※つくったものとかは前の記事の内容や応募作品ページをご参照ください

結果

まずは結果から。

  • 展示審査にて落選(決勝の決勝、プレゼン審査には進めず…)
  • エーアイさんのAPIパートナー賞受賞

となりました。

振り返り

エントリーについて

前の記事にも書きましたが、

  • Clova Friendsが当たったこと
  • Alexaのアワードを観たこと
  • Cogbot勉強会のLTに触発されたこと

がエントリーのきっかけです。
Clovaが手元にあるから「何か開発したいな」と思え、Alexaのイベントで「自分も機会があれば参加してみたいな」、 LTで「短期間でも応募しようとする人がいる」という話を聞いたという3連コンボで、見事にエントリーまで一直線の気持ちになりました。
ひとつでも欠けていたらエントリーはなかったですし、結果として多少なりとも評価をいただけたので、本当にいい流れだったなと思います。

開発で気を付けたこと

エントリーまでの開発期間は実質4日間のみでしたが、短期といえどきちんと戦略を立てました。それは、「VUI/Botならではのサービス」を意識したことです。
事前にAlexaのアワードのファイナルを見に行っていたこともあり、スマートスピーカーのスキルを作るならば、スマートスピーカーを使う意義が絶対に不可欠であり、それが審査においても重要だと考えていたからです。

特にVUI(Voice UI)は音声のやりとりでシステムとのI/Oを実現するため、使い方によっては著しくUXが下がります。
「それアプリでよくない?」「Webのほうがいい」「チャットボットでやったほうが楽」と思ってしまうようなものをVUIで実装してしまうと、だれも使ってくれなくなります。「手が使えないときはVUIのほうが楽だからVUIのスキルが必要」とかそういう理由が必要ということですね。

また、今回はLINEのアワードですし、評価ポイントにも含まれていた「LINEらしさ」も意識しました。

なので「VUIである必要性」「LINEならではのメリット」は絶対に入れようと思い、逆にそこさえクリアできればある程度のところまでは進めるだろうという確信もありました。
ファイナル進出自体が24/1125ということでしたが、ここまで残れたので今回とった戦略は間違ってなかったんだなと、安心しました。

技術面の工夫

やはりバックエンドをAzureにサーバーレスでつくった点です。
慣れたAzureでの、手軽に開発できるFunctionsのおかげで、短期開発を実現できました。 (ずっと使いたかったCosmos DBも今回はじめてちゃんと触れました。それもよかった点です)

また、VUI開発は非常に制約が多いので、ちょっとした技術的アイディアひとつで他とは一味違うサービスを作ることができるのではとの考えから、Clovaの使い方で技術的な工夫を入れることを意識しました。

そのひとつが今回のサービスの肝だった「Clovaの音声とBotへのメッセージ送信の同期」で(予選突破時のフィードバックでもLINEの方からお褒めの言葉をいただきました)、もうひとつが、予選突破後・決勝前に追加した「Clovaスキルの一時停止&再開」です。

細かい話はここでは触れませんが、技術的に結構おもしろいと思うので、その話は12/23にQiitaの LINEBot&Clova Advent Calendar 2018 に書く予定です。

展示審査のこと

決勝は、プレゼン審査の予選として展示審査がありました。チームごとにブースが用意され、そこで審査員や一般のお客さん(一般投票もあり)に作品を見ていただきます。
展示審査はデモを中心に見せていきました。 が、一般のお客さんも含め、来ていただいた人にはかなり好意的に見ていただけたようです。
こういった経験ははじめてだったので、「すげー!」とか言われるととてもうれしいですね。
結果はここで落選ではありましたが、控えめに言って、展示審査、めちゃくちゃ楽しかったです。

反省点は、展示ブースが地味だったことです。
用意された模造紙を貼ったくらいで、ほかに資料とか飾りとか、なにも用意できなかったところが、ちょっと他とくらべて映えなかったかなと思います。もっとたくさんの人に来てほしかった…。
完全に一人での参加でしたし、しゃべりとデモのみでの勝負という感じで、けっこう厳しいなと思いながらやってました。
準備もぎりぎり。ごはんやトイレの時間もとれず。こういうところはチーム参加のところがうらやましく思えましたね。

最終プレゼンを見て

レベルが違う。それに尽きました。
自分が展示審査まで残ったのが奇跡かと思うくらい、自分のつくったものとは力の入れ具合が違いました。
数日で作ったものを持って行って、勝てるかもとか勝ちたいとか一瞬でも思って申し訳ありません、、、汗

プレゼンも含め本当に素晴らしく、圧巻でした。特にグランプリの高校生。すごすぎです。
プレゼンが高校生とは思えないほどの完成度と話し方で、しかもプロダクトはユーザーテストを重ねに重ねクオリティを上げている。
勝てるわけないですねこんなの。自分もそれを超えるくらいの仕事やプロダクトづくりがしたいなと思いました。大尊敬。

APIパートナー賞受賞について

本当にありがたかったです。
アワードの応募締め切りぎりぎりに「APIを使いたい」という連絡に快く対応いただいた点も含め、すばらしいAPIを提供いただいた株式会社エーアイさんには本当に感謝です。
展示ブースにも来ていただきまして、本当にうれしかったです。ありがとうございました。

今後の展望

LINEの方にもブラッシュアップしてね、と言われたりしたのですが、エーアイさんのAPIが法人利用のみだったり、Azureの運用コストがけっこう高かったりでなかなか正式リリース、とかを考えるのはハードルが高いかなぁと思ってます。
展示審査でも何人かのかたにもうリリースされているんですが?と聞かれ、(リリースできる見込みもないけど)「まだなんです…」と答えたりしてました。
ただ、

ということもあり、ちょっと続けてみようかなと思ってます。
APIの利用期限が間近なため正式リリースはすぐには無理ですが、今回の作品を大きく進化させるなり全く新しいものを作るなりして次のアワードに再挑戦したいと思います。少なくとも、実サービスとして運用しているレベルでないと、勝負にならないということが今回の最大の学びなので、次に挑戦する際にそこは妥協できないなと思います。

おわりに

最後に、11/11を記念して、プリッツ服を着たうちのClovaの写真で締めたいと思います。
プリッツの懸賞で当選させてくれたグリコさんにもこの場を借りて感謝です。

以上です!

LINE BOOT AWARDS 2018にエントリーしてみました&その話を.NET Conf 2018 Tokyoでしてきました

はじめに

ここに書くのはだいぶ久しぶりです…
先日、LINE BOOT AWARDS 2018にClovaスキル&LINE Botを開発してエントリーしたので、そのことと、 それについて.NET Conf 2018 TokyoのUnconference枠で話をしてきたので、それについて書いておこうと思います。
(.NET Confでは10分枠だったのでその補足もかねてその時よりも若干詳しく、スライドベースで書いていきます。スライド資料はここ

スマートスピーカー

f:id:himanago:20181021011841p:plain

ちらほら流行り始めている(気がする)スマートスピーカー
エンジニア界隈では使っている人も見ますが、一般家庭への浸透率ってどの程度なものなんでしょう。

LINE BOOT AWARDSへの挑戦

f:id:himanago:20181021011857p:plain

個人的にも気になっていたスマートスピーカー。LINEの「Clova」がプリッツの懸賞で当たりました。
プリッツ5箱分食べると1回応募できるという具合だったので、この懸賞のためにプリッツを食べ続ける覚悟もしていましたが、2回目の応募で当たったのでかなり運がよかったです…!

f:id:himanago:20181021011910p:plain

せっかくスマートスピーカーが入手できたので、それならばスキルの開発をしてみたいと思うのがエンジニアというもの。

f:id:himanago:20181021011918p:plain

ちょうど、LINE BOOT AWARDS 2018という開発コンテストが開催中でした。
グランプリの賞金が1000万円とものすごい額。そこまでは狙えないにしろ、どこまでいけるか、挑戦してみることにしました。

f:id:himanago:20181021011925p:plain

とはいえ、スケジュールはものすごくタイトでした。 Clovaが届いたのが9/23(日)。アワードのエントリーに締め切りが10/10(水)。
これだけでずいぶん短いですが、Clovaが届いたときには、まだ特にアワードに応募することなんて考えてもいませんでした。

応募するきっかけをくれたのが、2つのイベントです。
(スライドには書いていないのですが)ひとつは9/29(土)に実施された、Amazonが出しているスマートスピーカーである「Alexa」のアワードの最終プレゼンを見に行ったこと。
ここで結構刺激を受けてきまして、いつか自分もこういうアワードに挑戦してみたいなぁと思ったわけです。 alexaskillawards.connpass.com

もうひとつが、こちらはスライドに書いていますが10/2(火)に参加したCogbot勉強会。
ちょうどLINE BOOT AWARDSとのコラボ回だったのですが、ふつうにLINE系開発の情報収集目的で参加しました。
しかしそこでアワードの概要やC#での開発についての話、また最後のLTで短期間でもAzureを利用してアワードに挑戦するという話(これが自分にはいちばん響きました)を聞いて、これは自分もアワードに挑戦すべきなのではないか…?と思わされてしまいました。 cogbot.connpass.com

そして、10/4(木)、突然、シャワーを浴びているときに、作るもののアイディアが降ってきました。
(入浴中とかって結構アイディアを思いつくこと多いですよね。なんででしょう)
思いついたらもう最後、3連休を利用して一気に作ってやろうと決意を固めました。

3連休ではアワードのもくもく会への参加をし、また締め切り日には有休を取得して最後の追い込み。実質4日間での開発でした。

どんなものを作ったか

f:id:himanago:20181021011934p:plain 絵本読み聞かせのシステムです。
特徴としては

  • お話を読んでくれると同時にLINEにその絵が送られてくる
  • 登場人物ごとに異なる声/話し方でしゃべってくれる

というものです。

デモ動画を置いておきます。 www.youtube.com

Clovaのオーディオ再生機能とAITalk Web APIを組み合わせ、音声再生を連続して実行し、同時にLINEに画像を送っています。

仕組みなど

そのほか、アーキテクチャまわりはアワード応募時の技術解説として書いた以下の記事に書いています。
締め切り当日に一気に書いたのであんまりまとまってないですが…。 qiita.com

今回開発したのは、スマートスピーカーのスキル(Clovaスキル)と、LINE Botを連携させています。
Clovaの特徴のひとつとしてLINE Botとの連携があり、それぞれの可能性を拡げてくれます。
ClovaスキルとLINE Botの開発は似ています(ここでは触れませんがClovaやLINE以外のVUI/Botもかなり似通った利用フローや開発方法といえると思います)。

ClovaスキルとLINE Botの開発方法・今回の構成

f:id:himanago:20181021011955p:plain Clovaスキルの場合は「CEK」がデバイスと自分の作成したバックエンド(Extensionサーバー)との仲介をしてくれます。
対話の定義はCEK上で行い、ユーザーの発話の解釈などはすべてやってくれます。

f:id:himanago:20181021012011p:plain 対してBotのほうは「Messaging API」が仲介をしてくれるのですが、こちらはPush/Replyといったメッセージ送受信を中継してくれるだけです。
なので、ユーザーから送られたメッセージの解釈などは自分で開発する必要があります(バックエンドでやる)。

f:id:himanago:20181021012019p:plain さきほどの技術解説記事でも説明していますが、今回の構成図はこのようになっています。
Clova/LINE Botの共通のバックエンドとしてAzure Functionsを用いています。
読み上げる絵本の情報(本文、読み手がだれか、感情やスピードなど各種パラメータ)をCosmos DBにMongoDBの形式で格納し、本文にあわせて送られる絵の画像はBlob Storageに格納しています。

Azure Functionsによるバックエンド

f:id:himanago:20181021012026p:plain Functionsは非常に便利なサーバーレスのサービスですが、今回はサーバーレスらしい従量課金プランではなく、App Serviceプランでの稼働です。
通常のWebアプリと同じように常時稼働させる形式をとることで、従量課金プランの弱点である初回起動の遅さを克服しています。
Clovaスキルは特にタイムアウトが天敵なので、この選択は必須といえるかなと思います。

[※2018/11/7追記]
App Serviceプランにするだけではだめで、常時接続をオンにする必要があります。オフだとしばらくするとサービスが止まってしまい関数起動が遅くなってしまいます。
常時接続オンにできるのはS1以上なのでちょっとお高め。

[※2018/11/23追記]
Azure Functions のv2を使って再作成していますが、触った感触、コールドスタート問題、かなり改善されている…?
もしかしたら従量課金プランでもClovaのバックエンドとして問題なく使えるかも…。検証して再度追記します。
IPアドレスの問題があるので今回の要件ではどちらにしろApp Serviceプランにしないとですが

[※2018/11/25追記]
何度か試しましたが、成功率はかなり高くなっているようです(コールドスタートで3/4回成功)。
とはいえタイムアウトすることもまだあるので、そのままでは本番運用にはまだ厳しいような気がします。
Azure Functionsはv2からランタイムが.NET Core になってパフォーマンス改善された結果なんでしょうか。

azure.microsoft.com

[※2018/12/20追記]
Logic Appsを利用してコールドスタートを回避するパターンを思いついたので、Qiitaに書きました。
これでコールドスタートを気にする必要がなくなります。 qiita.com

(追記おわり)

また、今回は絵本を無料/有料2種類のコンテンツがある想定にしており、有料のものはLINE Payで追加購入できるようにしています。
そのため、LINE Payとの連携も行っているのですが、LINE Payは連携するシステムのサーバーのIPアドレスを事前に登録しておく必要があります。
App ServiceプランではIPアドレスが変わらないので、この点でも必須なのでは、と考えました。
(こちらの記事を参考にさせていただきました) poke-dev.hatenablog.com

Azure Functionsのリモートデバッグタイムアウトになったり迷子になったりするのでなかなか使えず、コンソール出力を使ってのデバッグがメインでした。はじめはつらかったものの、慣れればどうにかなるものでした。

BlobストレージとLINE Botの画像送信

f:id:himanago:20181021012037p:plain このスライドに書いてある通り、Blob StorageとLINE Botの画像送信は相性がいいです。
通常の画像メッセージでも今回使ったイメージマップメッセージでも、画像はバイナリデータではなくURLの指定で行います。
Blob Storageは上げたデータをURLでアクセスさせることができるので、そのまま利用できるという寸法です。

f:id:himanago:20181021012049p:plain さきほども少し触れましたが、今回は通常の画像メッセージではなく「イメージマップ」を使っています。
もともとは画像に対してタップした際のアクションを定義するLINE Botの機能ですが、トーク画面に対してめいっぱいの幅を使って画像を表示させることができるため、今回の絵本の絵の送信に適しています。もくもく会にてアドバイスいただいたのですが、これによって非常に絵本が読みやすくなりました。

このイメージマップですが、デバイス幅にあわせた画像が表示される機能で、利用するには複数の幅の画像を事前に用意しておく必要があります。
Messaging APIリファレンスからの引用ですが、

イメージマップに使える画像の仕様は以下のとおりです。

画像フォーマット:JPEGまたはPNG 画像の幅:240px、300px、460px、700px、および1040px データサイズ:最大1MB 5つの異なるサイズの画像を「baseUrl/{画像の幅サイズ}」の形式でアクセスできるようにします。これにより、デバイスに応じて望ましい解像度でLINEに画像がダウンロードされます。

たとえば、ベースURLが以下の場合を考えます。

https://example.com/images/cats

幅が700pxの画像のURLは以下になります。

https://example.com/images/cats/700

となっており、Blob Storageへの格納は、スライドに書いた通り、これに沿うようにする必要があります。
(ファイル名を拡張子を外した画像の幅サイズの数字にしてディレクトリ構成を工夫するだけ)

C#出のLINE開発について

f:id:himanago:20181021012059p:plain

Cogbot勉強会でもC#には厳しい、という話が…。
完全に個人的な意見ですが、Clovaも(AWSとセットの)Alexaに対抗するなら、Azureを味方につけやすいC#との親和性を高めてもよいのではないかなぁと思います。
公式のSDKがないとはいえ、C#で開発する場合もコミュニティSDKや周辺ツールがあります。
今回の開発でも、Messaging API、CEK、LINE Pay用のC#SDKを使わせていただきました。

今回使った機能(Clovaのオーディオ関連など)で一部SDKが対応していないものもありそこは自前で実装しましたが、ベースとなるものがあるだけ開発のハードルはかなり下がり、かなりありがたかったです。

おわりに

f:id:himanago:20181021012107p:plain

今回は一気に開発しましたが、やはり目標や締め切りがあったことで、かなり真剣に取り組めました。
.NET Confで話したい、というモチベーションもありました。
締め切り後の連絡にもかかわらずUnconference枠で参加させていただき、.NET Confの管理者の方には大変感謝しています。ありがとうございました。

そしてアワードの途中経過について

そして!
今回開発した「Clova&LINEで絵本読み聞かせ」ですが、なんとFinal Stage進出の連絡をいただきました!
このような評価をいただけて大変うれしく思います。
Finalは11/10(土)に実施とのことで、ここでは展示審査もあるようなので、さらなるブラッシュアップをしていきたいと思います。

これからもがんばります!
以上です。

【Xamarin.Forms+Cognitive Services】飯テロ防止Twitterクライアント作ってみた

はじめに

はやいものでもう11月になってしまいました。今年もあと2か月弱。
研修などが土曜に多い関係上、なかなか土曜に休むことができない日々が続き、土曜の面白そうな技術系イベントにあまり出られず、ちょっと最近フラストレーションがたまっています。
その憂さ晴らしついでに、今回はちょっと手を動かしてみました。

今回作ったもの

タイトルにある通り、「飯テロ防止Twitterクライアント」です。
Twitterで食べ物の写真をたくさん上げている人をミュートした」ということを言っている後輩がいて、たしかに食べ物の写真は、深夜だったり気分の悪いときに見るのはきついとは思うですが、ミュートしてしまうことで食べ物写真ツイート以外の他の有益な情報まで目に入らなくなってしまうのはもったいないことです。
だったら、食べ物の写真だけうまいこと表示されないような「飯テロ防止」機能を持ったTwitterクライアントがあればよいのでは?と思ったのがはじまりです。
特に実運用は想定していないので、今回は飯テロ防止に関係がある「タイムライン表示」の部分だけ実装しています。

ソースはGitHubに公開しています。
github.com

名前は「FoodPornPreventer」。
preventerはちょっと微妙かもですが、英語で飯テロに近い言葉は「food porn」と言うそう。

アプリのしくみ

まず、Twitterクライアントなので、スマホなどモバイル端末での利用を前提に、Xamarin.FormsでAndroid/iOS/UWPに対応させています。
そこにTwitterクライアントとしての機能を実装するため、「CoreTweet」というパッケージを導入しています。Twitter連携するアプリを作成するうえでの定番のようです。
CoreTweetはこちら。日本語の説明がありました。 github.com

肝心の「飯テロ防止」は、AzureのCognitive Servicesにある「Computer Vision API」による画像解析を用います。
画像を解析してさまざまなことを教えてくれるサービスです。
azure.microsoft.com

流れは、

  1. CoreTweetでTwitterのタイムラインを取得

  2. 画像を含むツイートの場合、その画像URLをComputer Vision APIに渡して解析

  3. 解析結果に「food」という文字列が含まれていれば“飯テロ”とみなし、警告画像に差し替え、それ以外はそのままタイムラインに表示させる

となります。 では実際に中身を見ていきます。

Twitter連携

CoreTweetを導入するには、NuGetでPCLプロジェクトに「CoreTweet」をインストールします。
CoreTweetを入れると「Newtonsoft.Json」のパッケージも一緒に入るのですが、これが最新の「10.x」だとUWPで動きませんでした。なので、最終的にバージョンはCoreTweetが「0.8.1.394」、Newtonsoft.Jsonが「9.0.1」の組み合わせにしています。
また、ターゲットを変更しないとうまく上記がインストールできなかったので、Profileは「7」に変更しています。

では、CoreTweetによるタイムラインの取得処理です。

以下のように生成したアクセストークンを使って、

// トークンの生成
// 今回は事前にTwitterアカウントで作成しておいたアクセス情報を使用
private Tokens Tokens { get; } = Tokens.Create(Secrets.ConsumerKey, Secrets.ConsumerSecret, Secrets.AccessToken, Secrets.AccessTokenSecret);

タイムライン取得を行います。

var timeline = await Tokens.Statuses.HomeTimelineAsync(count: TweetCount, max_id: maxId);

これはTwitter APIの「statuses/home_timeline」に相当します。CoreTweetはこのように、基本的にTwitter APIをそのままC#で呼べるよう実装されているので、Twitter APIさえわかればそれと同じような呼び出し方で使うことができ、とても楽です。

取得したツイートのデータは適当にクラスに詰めてListViewにバインドさせて表示させています。
引用リツイートとか動画とか、そのあたりは今回の本題から外れるのであまり作りこんでいません。

Computer Vision APIでの画像判定

食べ物かどうかの判定ですが、あらかじめAzureでComputer Vision APIを使うためのキー情報を取得しておきます。
Xamarinから呼び出すのは簡単で、NuGetからMicrosoft.ProjectOxford.Visionを導入すればOK。
これでTagsを調べ、飯テロかどうかを判定します。Tagsの要素内のHintまたはNameはが「food」であれば食べ物画像=飯テロとみなします。

private async Task<bool> IsFoodPornAsync(string url)
{
    try
    {
        // Computer Vision APIで解析してTagsを取得
        var vision = new VisionServiceClient(Secrets.VisionSubscriptionKey, Secrets.VisionApiRoot);
        VisualFeature[] features = { VisualFeature.Tags };
        var result = await vision.AnalyzeImageAsync(url, features.ToList());
        return result.Tags.Any(t => t.Hint == "food" || t.Name == "food");
    }
    catch (Exception ex)
    {
        return false;
    }
}

判定処理はわずか4行。お手軽ですね。

サムネイル画像の差し替え

飯テロ画像だったら警告画像に差し替えます。
今回はConverterを使って、Computer Vision APIによる飯テロ判定のフラグ値で切り分けています。

public class ImageMaskConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var imageInfo = value as TweetImageInfo;

        if (imageInfo == null) return null;

        // 飯テロ画像なら画像を差し替え
        return imageInfo.IsFoodPorn
            ? ImageSource.FromResource("FoodPornPreventer.Images.mark_chuui.png")
            : ImageSource.FromUri(new Uri(imageInfo.Url));
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

実際の動作

今回の開発にあたり、食べ物画像BOT@ibukurogさんのツイートをテストに使わせていただきました。
たとえば、このツイート。

このツイートを本アプリを通して見ると…

Android

f:id:himanago:20171105011730p:plain:w300

iOS

f:id:himanago:20171105011745j:plain:w300

UWP

f:id:himanago:20171105001349p:plain:w300

上記のように、うっかり飯テロにあうことを防ぐことができます!
ちなみに差し替えたアイコンはいらすとやさんからいただいたものです。
注意のマーク | かわいいフリー素材集 いらすとや

なお、iOSだけ、ツイートの追加読み込みでスクロール位置が戻ったり項目が上下に動いて一瞬重なって見えたりと、ちょっと変な動きをするのでいずれ直したいです。

苦労したところ

ListViewはネストできない

ツイートをListViewで並べて表示しますが、各ツイートのListItemに画像を並べる際、ListViewをネストできたら動的に画像の数が変わってくれると思ったのですが、ListViewはネストすることができなかったので、断念。Twitterでは1ツイートには画像が最大4枚という仕様なので、しかたなく4枠固定で画像の領域を用意してごまかしました。

iPhoneでの実機確認時のエラー①

iPhoneの実機で動かしたときがけっこう苦戦しました。
まず、ビルド時に以下のようなエラーが。
f:id:himanago:20171104234147p:plain

Invalid architecture: ARMv7. 32-bit architectures are not supported when deployment target is 11 or later.

端末はiPhone8(iOS11)。おそらく、iOS11から完全64bit化したため「32bitは使えないよ」というエラーが出たのだと思われます。ここは、iOSプロジェクトのビルド設定でサポートされているアーキテクチャを「ARMv7 + ARM64」から「ARM64」に変更すると解消しました。 f:id:himanago:20171104234206p:plain

あとその下の「すべての32ビット浮動小数点演算を64ビット浮動小数点演算として実行する」のチェックも入れておく必要がありそう。
f:id:himanago:20171105100753p:plain

iPhoneでの実機確認時のエラー②

もうひとつ、実機デバッグ時のみ、Computer Vision APIを呼び出す際に例外が発生しました。シミュレーターを使ったときやAndroid/UWPでは発生しなかったものだったので、最初は「?」でした。

  at System.Linq.Expressions.Expression.Call (System.Linq.Expressions.Expression instance, System.Reflection.MethodInfo method, System.Collections.Generic.IEnumerable`1[T] arguments) [0x00111] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Linq/Expressions/MethodCallExpression.cs:1239 
  at System.Linq.Expressions.Expression.Call (System.Linq.Expressions.Expression instance, System.Reflection.MethodInfo method, System.Linq.Expressions.Expression[] arguments) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Linq/Expressions/MethodCallExpression.cs:1046 
  at System.Linq.Expressions.Expression.Call (System.Reflection.MethodInfo method, System.Linq.Expressions.Expression[] arguments) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Linq/Expressions/MethodCallExpression.cs:1001 
  at System.Dynamic.ExpandoObject+MetaExpando.BindSetMember (System.Dynamic.SetMemberBinder binder, System.Dynamic.DynamicMetaObject value) [0x00033] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Dynamic/ExpandoObject.cs:863 
  at System.Dynamic.SetMemberBinder.Bind (System.Dynamic.DynamicMetaObject target, System.Dynamic.DynamicMetaObject[] args) [0x00035] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Dynamic/SetMemberBinder.cs:57 
  at System.Dynamic.DynamicMetaObjectBinder.Bind (System.Object[] args, System.Collections.ObjectModel.ReadOnlyCollection`1[T] parameters, System.Linq.Expressions.LabelTarget returnLabel) [0x000c6] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Dynamic/DynamicMetaObjectBinder.cs:90 
  at System.Runtime.CompilerServices.CallSiteBinder.BindCore[T] (System.Runtime.CompilerServices.CallSite`1[T] site, System.Object[] args) [0x00019] in <773264786149499a986a13db6a7d46fe>:0 
  at System.Runtime.CompilerServices.CallSiteOps.Bind[T] (System.Runtime.CompilerServices.CallSiteBinder binder, System.Runtime.CompilerServices.CallSite`1[T] site, System.Object[] args) [0x00000] in <773264786149499a986a13db6a7d46fe>:0 
  at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
  at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/corlib/System.Reflection/MonoMethod.cs:305 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:151 
  at System.Linq.Expressions.Interpreter.ExceptionHelpers.UnwrapAndRethrow (System.Reflection.TargetInvocationException exception) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/Utilities.cs:174 
  at System.Linq.Expressions.Interpreter.MethodInfoCallInstruction.Run (System.Linq.Expressions.Interpreter.InterpretedFrame frame) [0x00035] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/CallInstruction.cs:333 
  at System.Linq.Expressions.Interpreter.Interpreter.Run (System.Linq.Expressions.Interpreter.InterpretedFrame frame) [0x00015] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/Interpreter.cs:63 
  at System.Linq.Expressions.Interpreter.LightLambda.Run3[T0,T1,T2,TRet] (T0 arg0, T1 arg1, T2 arg2) [0x00038] in <773264786149499a986a13db6a7d46fe>:0 
  at Microsoft.ProjectOxford.Vision.VisionServiceClient+<AnalyzeImageAsync>d__19.MoveNext () [0x00053] in <26ced5c9a33f420b960f06b6963aca5a>:0 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:151 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x00037] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:187 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:156 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:128 
  at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <a89624c267f94034b6cf9aa0c56f8864>:0 
  at FoodPornPreventer.MainPage+<IsFoodPornAsync>d__11.MoveNext () [0x00050] in C:\Users\hoge\Documents\Visual Studio 2017\Projects\FoodPornPreventer\FoodPornPreventer\FoodPornPreventer\MainPage.xaml.cs:116 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:151 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x00037] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:187 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:156 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:128 
  at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:357 
  at FoodPornPreventer.MainPage+<AddTweets>d__10.MoveNext () [0x003a2] in C:\Users\hoge\Documents\Visual Studio 2017\Projects\FoodPornPreventer\FoodPornPreventer\FoodPornPreventer\MainPage.xaml.cs:85 

調べてみると、どうやらリンカーの動作を「Don't Link」にすると解決するらしい、ということがわかりました。 www.syncfusion.com github.com

iOSプロジェクトのプロパティで以下のように変更。 f:id:himanago:20171105095756p:plain

すると、無事実機デバッグできるようになりました。
ただこれだとアプリのサイズが大きくなってしまうと思われるため、以下のようにほかの回避方法もありそうですし、もう少し調べてみたいなと思っています。 spiratesta.hatenablog.com

その他

Xamarinの開発をするときは、普段メインPCをWindowsでやっていますが(iOSやるときはMac Book Airをリモートでつないでる)、実機デバッグが楽なのはUWP>AndroidiOSなので、どうしてもiOSの動作確認が最後になってしまい、個人的にそこで詰まるケースが結構多いです。普段使っているスマホiPhoneなので、ちゃんとiPhoneでも使えるようにしたいところではあるのですが。

あと、今回はなるべくシンプルに作りたかったのでコードはほとんどコードビハインドに書いてみました。ただ、これが失敗だったのか、結局コードビハインドでいろいろ頑張ることになってしまい、シンプルというよりは見通しが悪くなってしまったような印象。
拡張性とかも考えて、ちゃんとMVVMでやったほうがむしろ「シンプル」できれいになるのかも、と思いました。のちほど、Prismを使って再実装してみようかとも思います。

JAZUG 7周年総会で「社内でIoT&AIな農業系チャットボット開発講座をやってみて」というテーマで登壇してきました

はじめに

先日、9/9(土)に行われたJAZUG 7周年総会にて、50分のセッションで登壇させていただきました。
そしてこのたび、そのときのセッション動画が公開されましたので、少し間が空いてしまいましたが、簡単に内容の紹介と振り返りを書いておきたいと思います。

イベントの概要

JAZUG 7周年総会のイベントページ(connpass)はこちらです。 jazug.connpass.com

Microsoft AzureのユーザーコミュニティであるJAZUG(Japan Azure User Group)の7周年を記念したイベントということもあり、たくさんの方が参加されたイベントでした。

仕事以外のコミュニティで話をするのは4月にやったJXUG関連の初心者LT以来2度目でしたが、今回は参加人数も多く、Azureを長く使われている方も多くいる中での登壇でしかもロングの本編登壇だったので、しっかり準備しなければいけないと思い、内容については前日ぎりぎりまで悩みながら資料を作りました。

発表内容と資料・動画

タイトルは「社内でIoT&AIな農業系チャットボット開発講座をやってみて」
内容はタイトルどおり、社内でIoTやAIに絡めた講座をやった際に使ったAzureのサービスに関することを中心にお話しさせていただきました。

資料はSlideShareに上げています。

www.slideshare.net

動画はこちらです。 crash.academy

社内でやったこの講座では、植物に取り付けたIoTデバイスRaspberry Piなどで作成)が湿度データと植物を撮影した写真データを定期的にAzureに送り、そのデータを使ってLINE Botを介してユーザーと会話をする、というシステムを作りました。
f:id:himanago:20170924221756p:plain

使ったAzureのサービスも多種多様で、以下のような構成です。 f:id:himanago:20170924221802p:plain

その他、細かい内容は資料・動画を見ていただくとして、ここからは登壇にあたってどういうことを考えて準備したかや、実際に登壇してみての感想・反省などの振り返りをメインに書いていきます。

登壇のきっかけ

7月のJAZUGの勉強会に参加させていただいた際、登壇面白そうだなと思ったので、アンケートで「登壇希望」と書きました。

ちょうど昨年11月~今年3月にかけて社内でAzureのサービスを多く利用したIoT講座をやっていたりしたので、それがネタになるだろうと思ってのことです。
その後、JAZUGの方から連絡があり、予定するセッション内容などをお伝えしたところ、8月のお盆休み中に、JAZUGの7周年総会でセッションを担当できないか、と正式な依頼をいただきました。

7周年という大きな節目のイベント、しかもあまり準備日数がなさそうということで、少し迷いましたが、むしろこんな舞台でセッションをさせていただける機会はめったにないと思い、ありがたく登壇させていただくことにしました。

準備(ターゲット設定)

まず、今回登壇させていただくにあたり、たくさんの方が参加されるイベントなので、きっちり話す内容を検討しないといけないと思い、以下のようなターゲット設定を行いました。

ターゲット①:Azure歴の長い方

7周年総会ということで、当然、Azure歴の長い方が多く参加されると思いました。そういった方にも飽きずに聞いていただけるよう、以下のような話を盛り込むことにしました。

  1. Azure以外のネタ
  2. Azureのよく使うサービスだけど意外と知らないかもしれないこと
  3. Azure歴の短い人間から見たAzureの進化

1.は、今回UI部分にLINE Botを使ったので、Azure FunctionsやCognitive Servicesを使ってLINE Botを作れるという点、またLINE Beaconなどを紹介することで満たせました。
特にCognitive Services(LUIS、Computer Vision API)がLINE Botと相性が良く、事例として紹介したいと思っていたところでした。

2.については、Cognitive ServicesのComputer Vision APIのサムネイル生成機能あたりがそれに該当します。Computer Vision APIは画像の分析が注目されるAPIなので、自分自身もサムネイル生成もできるというのは少し意外だと感じたためです。
この機能は今回の教材システムのなかでも大きな役目を担っていますが、スライドでいうとこの部分。 f:id:himanago:20171001212606p:plain

少しここで補足しておくと、LINE Botでは、画像をユーザーに送る際には、いくつか注意点があります。
まず、画像の「URL」を教えてあげる必要があるという点です(公開されている必要がある)。今回送りたい画像(植物の写真)はBlob Storageに入れていましたが、Blob StorageではそれぞれのデータにURLが割り当てられるので、LINE Botで送信可能でした。

もうひとつは、LINE Botで画像を送る場合はオリジナルの画像URLとは別にサムネイルの画像URLも必要だという点です。
オリジナル画像のみでも画像の送信は可能ですが、サムネイルをセットで渡してあげないと、ユーザー側のLINEのトーク画面に、重いオリジナル画像がそのまま表示されてしまいます。これでは、画像のサイズが大きいときにトーク画面になかなか画像が表示されずストレスになってしまいます。そこで、トーク画面にはサイズの軽いサムネイルを出し、それをタップすると高解像度のオリジナル画像が表示される、というようにしてあげる必要があるのです。

今回、それはComputer Vision APIを組み合わせることで実装することができました。
LINEから「写真みせて」とメッセージを受け取ったら、Blob Storageに格納してある写真のオリジナルデータのURLをComputer Vision APIに渡し、サムネイル生成を依頼します。するとサムネイルの画像データを送り返してくるので、それをBlob Storageのサムネイル用コンテナー(オリジナル画像の入っているコンテナーとは別)に入れ、そのサムネイル画像のURLと、オリジナル画像のURLをセットでLINEのMessaging APIに送り返します。そうすることで、Messaging APIからユーザーのLINEに画像がサムネイルつきで送られることになります。Blob Storage+Computer Vision APIのサムネイル生成がどんぴしゃでこの実装に使えて、当時はちょっと感動しました。

3.は、Functions開発の進化についてです。
講座当時にはなかったVisual Studio 2017との連携について触れ、「わずか7ヶ月で進歩が感じられるほどAzureは常に進化している」ということを(確認的に)伝え、Visual StudioでのFunctions開発をデモすることにしました。

ターゲット②:今回触れるAzureサービスにそこまでなじみがない方

イベントに参加されるのはAzureに詳しい方だけとは当然限らないので、今回話に出てくるAzureのサービスをあまり知らない/使ったことがない人に向けても、「そのサービスの概要」「使う利点」などを伝えられればと思いました。
こういうコミュニティの参加者の前提知識をどこに設定するかというのは難しい問題だと思いますが、あまり基本的な話をしすぎると最初のターゲットと設定した人が退屈に感じてしまうので、今回は話の中で登場するAzureのサービスについてはひとこと説明をスライドに載せておくことで対応することにしました。

反省

「つかみ」は大事

今回、スライドのデザインはJAZUGでよく使われているものをカスタマイズさせていただいたのですが、これに関連して、 「農業関連の話なのでスライドの下のほうに草生えてます」と最初のつかみで言うのを忘れてしまいました…。

ウケるにしてもウケないにしても、こういう「つかみ」のネタをやって空気感の確認をやっておくと、話す側としてもペースをつかみやすくなるんですよね。 今回はそれができなかったこともあって、いつも通りな話し方のペースをつかむまで、少し時間がかかってしまったような気がします。 なので、前半ちょっと余計なことを話したりその場で思いついたことを話したりしてしまい、後半で少し走り気味になってしまったように思っていましたが、動画をあらためて見てもそこまで走っている感じはしなかったので、許容範囲に収まってよかったかな、と思います。
とはいえ、やはり最初に会場の雰囲気と自分の立ち位置を把握するため、しっかりと「つかみ」を大事にしないといけないな、と改めて感じました。

小道具が活かせなかった

「Spotlight」という、画面の一部をスポットライトで照らしたように見せる機能を持ったクリッカーバイスを持ってきていたのですが、あまり使う必要のあるスライドが少なく、一度だけ申し訳程度に使っただけで終わってしまいました。結局ずっとPCの前で操作をしていましたし、クリッカーとしてのも出番もなく。。
Spotlightはこれです。 www.logicool.co.jp

7周年総会の最後のLT大会で同じ道具を使っている方がいらっしゃったのですが、ものすごくウケていたのでちょっと悔しかったです^^;
次回はスライドをSpotlight使用前提で作って、さらに自分自身もPCから離れて動きながら話してみたいなと思いました。

資料作成がぎりぎり

社内で講師をするときもそうですが、本当にいつも登壇前日まで資料作成をやっている気がします…。余裕をもって準備できていないとリハーサルなども入念にできないので、早め早めの準備は心がけたいところです。
リハーサルをもっとしっかりできていれば上記2点のような反省点も出なかったはずなので、次回はきっちりと余裕をもっていきたいと思います。

振り返りブログが遅い

冒頭に「動画が公開されたので」というふうに書きましたが、それは嘘です…。
本当は登壇直後に公開したかったのですが、内容も振り返りも…といろいろ考えていたら文章のまとまりがなくなり、収拾がつかないまま時間だけが過ぎてしまいました。
動画が公開されたので、このタイミングでこそ出さなければ…!と、なんとか振り返り中心にまとめて公開までこぎつけた次第です。
次回もし登壇をした場合はもっと早く出せるよう、事前に準備しておきたいなと思います。

LINE Messaging APIに関して若干の補足

登壇時、しゃべり損ねてしまったのですが(動画を見直して気づきました)、今回使ったLINE Botの機能は「Reply API」という、ユーザーからのメッセージに応答(返信)するためのAPIで、LINE Botにはもうひとつ、「Push API」というBotからユーザーにメッセージを送るためのAPIが用意されています。
Reply APIはフリープランでも使えますが、Push APIではプロ版(月額料金がかかる)か開発者版(友だち数に制限がある)でしか使えません。そのため、今回はReply APIのみでシステムを構築しています。

まとめ

ということで、社内の講座のためにAzureを触り始め、もうすぐ8か月くらいになります。IoTやAIなんていうと大それた言葉ですが、Azure(とLINE Bot)でうまく実現でき、その可能性に大きな魅力を感じました。今回、JAZUGでの登壇を通して、その魅力を多くの方に伝えられていたらいいなと思います。
現在Azureを業務でも使えるところに取り入れられたらと思い、いろいろ試しているのでこれからも勉強していこうと思います。引き続きJAZUGには参加させていただこうと思いますので、どうぞよろしくお願いいたしますm(_ _)m

「BLOB」の読み方は?

はじめに

普段「BLOB」のことを「ビーロブ」と発音していたのですが、最近「ブロブ」と発音する人が多いことを知りました。
人前でしゃべることも多いので、正しい(もしくは多数派の)発音を知っておきたいと思い、ちょっと調べてみました。

BLOBとは何か

Binary Large OBjectの略。データベースなどでバイナリデータを格納する際のデータ型のことを言います。
似た概念に、大きなテキストデータを格納するためのデータ型であるCLOB(Character Large OBject)というものもあります。
参考:バイナリ・ラージ・オブジェクト - Wikipedia

発音の派閥

「ビーロブ」派

Oracleのドキュメント
BLOBの読み方が「ビーロブ」であると明記されています。
当然、CLOBは「シーロブ」となっています。
用語集

自分が最初に出会ったRDBMSOracleだったため、その影響で「ビーロブ」と発音するようになったようです。

ブロブ」派

・IT用語辞典 e-Words
読み方に「ビーエルオービー/ブロブ」と書かれています。
BLOBとは - IT用語辞典

しかし、CLOBのほうは「シーロブ/シーエルオービー」と書かれています。統一されていない…。
CLOB(キャラクタラージオブジェクト)とは - IT用語辞典

英語圏では?

Channel9の動画(Azure Blob Storageの話が出てくるもの)で英語スピーカーが何と言ってるかを聞いてみました。

channel9.msdn.com
channel9.msdn.com

めっちゃ「ブラーブ」と言ってますね…。
日本語っぽい発音にしたら「ブロブ」、ということになりますね。

結論

英語圏の人もそうですし日本でもそう言う人が多いので、「ブロブ」と発音したいと思います。
ただ、Oracleの文脈ではドキュメントの読み方に従い、今後も「ビーロブ」と言っていこうと思います。