フレクトのクラウドblog re:newal

http://blog.flect.co.jp/cloud/からさらに引っ越しています

AWS Lambdaを使って機械学習トレーニングの前処理を高速化した話

みなさんこんにちは。技術開発室の岡田です。

私は主に機械学習&AI案件を担当しており、クラウドインテグレーションを主な生業としているFLECTには珍しく、クラウドとは少し距離を置いたところにいるエンジニアでした。 具体的な経験としては、GCPインスタンスを立ち上げてモデルをトレーニングするくらい。 しかし、去年の12月の re:Invent2019でSageMakerの大幅アップデートがあり*1機械学習&AIのエンジニアもクラウドを使いこなせなければならない時代になったのだなぁと思うに至りまして、一念発起して、ここ1〜2ヶ月は結構AWSの勉強してました。 結果、この度認定資格(Associate3種)を取得いたしました。わーい(にっこり)。

これでクラウドエンジニアとしての入り口くらいには立てたかな? f:id:Wok:20200306104716p:plain

ということで、クラウド初心者ではあるのですが、ひとつ勉強がてらにとAWS Lambdaを使って機械学習の前処理の高速化を行ったので、これについて投稿します。

機械学習の前処理とAWS Lambda

機械学習のモデル開発では前処理という重要なステップがあります。 一言に前処理といってもこれはかなり広い概念で、対象データの抽出、データの結合、各種変換、テストデータの分割などが含まれます。各種変換の中には、より高い精度のモデルを作成するために与えられた特徴量からより良い特徴量を生成するFeature Engineeringと呼ばれる処理をすることも含まれます。 典型的なパターンでは、前処理のなかでも最初の方で必要なデータだけを抽出してデータ量を減らし、その後に続く処理の計算コストを抑えるという流れが良いとされています。*2

画像の分類モデルを開発する場合も同様に、最初に重複する画像を排除するなどして無駄な計算コストがかからないようにします。また、これはモデルの性能に影響が出る場合があるので、性能と計算コストとのトレードオフで実施するかを決めることになりますが、必要以上に大きなサイズの画像のリサイズを初めの方に行うことで、後に続く画像処理の計算コストを減らしたりもします。

私が関わっているある画像分類プロジェクトでは、前処理でどうしても人手を介さなければならないところがあるのですが、この場合でも予め、重複している画像を排除し、無駄に大きな画像を作業可能な大きさにリサイズすることで、人による作業コストも減らすことができます。 ただ、この画像のリサイズに結構な時間がかかるため、毎月大量に提供される学習用の画像データをリサイズする間、待ち時間が発生してしまうという課題がありました。 これまでは、単純に32コアのサーバを用意して、フル回転でリサイズ処理をすることで時間を短縮するようにしていましたが、それでも場合によっては数時間レベルの待ち時間が発生することがあり、帰宅前に処理を流してから帰る、なんてこともしていました。

ところが、AWS Lamdaはデフォルトで1000並列まで並列処理できると。それもサーバレスで。(AWSに詳しい方からすると、いまさら、、、という話かもしれませんが。) 1000並列なら単純計算でざっくり30倍くらいの速度で処理ができるはずです。処理時間が1/30になる!試してみたい!!ということで、評価してみました。

評価環境

今回は、手元にあった画像80739枚をリサイズする処理で評価してみたいと思います。 また、ざっくりとした、構成図は次のような感じです。 f:id:Wok:20200309125529p:plain

一方(左側)は、32コアのサーバで処理をします。*3。 ローカルディスクから画像を読み出してリサイズして書き戻します。なお、CPUネックになることは確認済みです(ドライブネックにはなっていない)。

もう一方(右側)は、AWS LambdaをAPI Gateway経由で呼び出せるようにし、クライアントからこれをコールすることで処理をします。 1000並列!とイキっていましたが、並列度の上限の1000を使い切ると別で行っている処理に影響が出てしまう可能性があるので、今回は300並列を上限として実施することにします。それでも10倍近い高速化はできると思われます。 画像は、S3から読み出して、リサイズして、書き戻します。

なお、今回使用したコードは次のリポジトリに置いてあります。

github.com

結果と考察

実際にリサイズをさせてみた結果です。 f:id:Wok:20200309120732p:plain

32コアサーバでは852秒かかっていたのですが、AWS Lambdaでは198秒で完了しました。 かなり高速化できたのですが、見込んでいた10倍の速度には程遠かったです。残念。

今回のクライアントは1画像ごとに1API(Lamda)を呼び出しているのですが、 AWS Lambdaは、並列実行数の上限を超えるリクエストを受け付けると例外を発生させてしまうので、一気に全画像数のリクエストを送信することができません。 今回は、クライアントからの呼び出しを300並列に抑えており(300のうち1つ終わったら次のリクエストを送るという形。)、この結果、HTTPの送受信のオーバヘッドが見えてきてしまっているのだろうと思います。

改善版と評価

そこで、次は、今回の画像の枚数を300で割った数分だけ、1回のリクエストでリサイズ要求すれば、HTTPのオーバヘッドも見えなくなるだろうと考えました。つまり、300個のリクエストで終わるように呼び出し方を変える。 しかし、API Gatewayタイムアウトが最大29秒という制限があり、1回のリクエストで多くの画像を処理しているとタイムアウトしてしまいます。 そこで、1200分割して投げれば1回のリクエストあたり大体25秒程度で完了することが実験的に特定できたので、この分割でHTTPのオーバヘッドを見えづらくすることとしました。 さらに、AWS Lambdaの中でもシーケンシャルに実行するとS3とのやり取りでオーバヘッドが見えてくると考えられるので、AWS Lambda内部も並列実行可能にします。これなら、かなり目論見に近い高速化ができるのではないだろうか。 結果がこちらです。左が32コアサーバでの処理時間。真ん中が1リクエストあたり1画像を処理するAWS Lambdaの場合の処理時間。右が今回の改善版の処理時間です。 f:id:Wok:20200309123428p:plain

今回の改善版は103秒で処理を完了させることができました。10倍とまではいきませんでしたがかなり近い値まで高速化できました。 まだチューニングの余地もあると思いますが、ざっくりと目論見の成果が得られたので、今回の評価はここまでとしたいと思います。

考察

今回、大量の並列実行が可能なAWS Lambdaを用いて、機械学習で困っていた前処理にかかる時間を短縮してみました。 結果、正しく並列度を上げる構成にすれば並列度に応じた高速化が実現できることがわかりました。(アムダールさん万歳。) この事実は当たり前といえばそれまでですが、AWS Lambdaはマネージドサービスであり、並列度を簡単に低コストで上げることができるので、より強力な考え方になると思います。

なお、機械学習の前処理の高速化というところだと、AWS EMRとか使う話もあるようですが、これはリソースの確保が必要で比較的ハードルが高いと思いますので、手間をかけずに高速化するにはAWS Lambdaでも十分かなという印象です。

最後に

今回は、私の担当に関するということで機械学習の前処理を対象にして実験してみましたが、特にこれに限定される効果ではありません。私は、このLambdaで並列実行する方式で、Tensorflowの画像分類モデルを動かし、大量のデータのAIによる事前ラベリングを高速に実施させています。これについても機会があれば、ご紹介したいと思います。

皆様も、並列実行可能ではあるがハードウェアの制限により並列度を上げられないという処理があるようでしたら一度お試しになると良いかと思います。

それでは。

余談

今回、短期間でAWS 認定資格 Associate3種を取ったのですが、これは、FLECTにはAWSSalesforceなどの各資格を取得するための支援プログラムがあること、そしてFLECTには有識者も多数在籍していることが有利に働いたかなと思っています。 ポイントを抑えた効率のよい勉強をするためには、環境って重要ですよね。

*1:AWS re:Invent2019のSageMaker関連のレポートはこちら。cloud.flect.co.jp

*2:参考:前処理大全 技術評論社 前処理だけで1冊の本に仕立てている。平易な文章ながら基本を網羅している印象。個人的にはベストプラクティスを「〜〜するとよいでしょう。」と優しい口調で諭してくれるところが好き。

*3:ここでは、300プロセスを32コア上に載せている。簡易的な評価なのでコンテキストスイッチのオーバーヘッドとか細かいことは無視しています。32プロセスだとIOの間コアが遊ぶので、まぁどっこいどっこいと信じてる。