タクトピクセル開発者ブログ

印刷・コンテンツ業界向けSaaS製品を開発・運営するタクトピクセル(株)の開発者ブログ

Python 3.14 のGILなし版で処理の高速化を試みる

大分時間が経ってしまいましたが、Python 3.14 にて、GILなし版が正式リリースされました。マルチスレッドにしても単純にはパフォーマンスがスケールしない、という時代が長く続きましたが手軽に高速化できるとして期待したいと思います。

その GIL という仕組みについては、何となくの間隔で制約を感じるだけで、具体的にはどのような挙動をするのかを確かめたことがありませんでした。そこで、これまでのGILありバージョンの挙動を調べながら、新しい free-threaded 版で処理速度がどう変化するのかを実験してまとめてみました。

GIL とは

GIL とは、Python で長年にわたって標準として機能してきた Global Interpreter Lock、日本語にすればグローバルインタプリタロックという仕組みです。CPython のインタプリタが、同時にひとつのスレッドにしか Python のバイトコードを実行させないようにする、大きな排他ロックのようなものです。スレッド自体は何本でも立ちます。ただ、実際にコードを走らせる権利はいつもひとつのスレッドしか持てない。だから CPU をひたすら使う処理をスレッドに分けても、24 コアのマシンで 1 コア分しか働かないことになります。

なぜこれが要かというと、CPython がオブジェクトの寿命を参照カウントで管理しているからです。すべてのオブジェクトが「いま何箇所から参照されているか」という数字を持っていて、参照が増えたり減ったりするたびにこのカウンタを書き換えます。複数のスレッドがロックなしにこれを同時に書き換えると、カウントは簡単に壊れます。まだ使われているオブジェクトが解放されたり、逆にいつまでも解放されなかったりする。インタプリタ全体をひとつのロックで守ってしまえば、この問題は無くなります。

その GIL を外そうという提案が PEP 703 で、3.13 で実験的に入り、PEP 779 によって 3.14 で公式サポートになったそうです。ただし、free-threaded ビルドがデフォルトになったわけではなく「公式にサポートされるが、依然としてオプション」という段階です。free-threaded ビルドを別に用意する必要があります。

画像処理や深層学習だと大きなデータで重たい処理を扱うことが多いので、メモリ共有の仕組みと並列化は詳しく知っておきたいところです。

実験方法

GIL を握りっぱなしにする、CPU バウンドな処理が要ります。素直に、試し割り法で素数を数える純 Python の関数にしました。NumPy などを使うと内部で GIL を解放してしまうので、測りたいものが測れません。

def count_primes(limit: int) -> int:
    """CPU バウンド、純 Python。GIL を握り続ける。"""
    count = 0
    for num in range(2, limit):
        is_prime = True
        for i in range(2, int(math.sqrt(num)) + 1):
            if num % i == 0:
                is_prime = False
                break
        if is_prime:
            count += 1
    return count

limit=150,000 のこの関数を 24 個のタスクとして、逐次実行・マルチスレッド・マルチプロセスの 3 通りで回します。5 回計測して中央値を採り、各系列の前にウォームアップを 1 回入れました。

比較する構成は 3 つ用意しました。標準ビルドと GIL 無効を並べるだけでは、差が出たときにそれが GIL を外した効果なのか、free-threaded ビルドであること自体の効果なのかが分かりません。そこで、free-threaded ビルドに PYTHON_GIL=1 を与えて GIL を戻した中間の構成を挟みました。

構成 説明 free-threaded ビルド GIL
標準+GIL 従来どおりのビルド 有効
FT+GIL FT ビルドだが GIL を戻す 有効
FT(GIL無効) FT ビルド本来の姿 無効

Python はいずれも 3.14.6、外部依存はありません。Anaconda なら環境を 2 つ作るだけで用意できます。

conda create -n mtdemo-py314-gil   -c conda-forge python=3.14 python-gil
conda create -n mtdemo-py314-nogil -c conda-forge python=3.14 python-freethreading

GIL が本当に外れているかどうかは sys._is_gil_enabled() で確認できます。False が返れば外れています。

計測に使ったマシンはこうです。

CPU : 13th Gen Intel(R) Core(TM) i7-13700KF
      物理コア 16 / 論理プロセッサ 24
OS  : Windows 11 Home

この「物理 16・論理 24」という数字は、あとで結果を読むときに効いてきます。差の 8 が SMT による論理コアなので、8 個の物理コアだけが 2 スレッドを走らせ、残りの 8 個は 1 スレッドしか走らせない構成だと分かります。前者が高性能な P コア、後者が高効率な E コアです。24 という数は、24 個の対等な実行資源を意味してはいません。

結果

いきなり結果です。

GIL有り無し比較とマルチスレッド・マルチプロセスの比較

 

構成 実行方式 中央値 (s) 速度比(自構成の逐次比)
標準+GIL 逐次実行 2.418 1.00x
標準+GIL マルチスレッド 2.416 1.00x
標準+GIL マルチプロセス 0.262 9.21x
FT+GIL 逐次実行 2.535 1.00x
FT+GIL マルチスレッド 2.555 0.99x
FT(GIL無効) 逐次実行 2.570 1.00x
FT(GIL無効) マルチスレッド 0.234 11.01x
FT(GIL無効) マルチプロセス 0.260 9.89x

標準ビルドのマルチスレッドは 1.00x です。24 コアのマシンで 24 本のスレッドを立てても、逐次実行と 1 ミリ秒も変わりません。そして同じコードを free-threaded ビルドで走らせると 0.234 秒、11.01 倍になりました。ソースコードは 1 行も変えていません。

逐次実行の行を縦に見ると、2.418、2.535、2.570 と少しずつ遅くなっています。標準ビルドに対して free-threaded は 1.06 倍の時間がかかっています。これは GIL を外したからではなく、free-threaded ビルドであること自体のコストと思われます。3.14 のリリースノートは単スレッド性能のペナルティを「プラットフォームと C コンパイラによるが、おおむね 5〜10%」と書いていて、手元の 6% はおおよそ推定通りです。

スレッド数に応じたスケールの効果

ワーカー数は 24 の約数に限りました。24 個のタスクを均等に配れるようにするためです。

スレッド数に応じたスケールの効果
ワーカー数 標準+GIL FT(GIL無効) FT(GIL無効) の並列化効率
1 1.01x 1.00x 100%
2 1.00x 1.95x 97%
3 1.00x 2.98x 99%
4 0.96x 4.01x 100%
6 0.97x 5.03x 84%
8 1.00x 6.50x 81%
12 1.00x 8.03x 67%
24 1.02x 11.29x 47%

もうひとつは、4 ワーカーまでは効率 100% できれいに伸びるのに、6 ワーカーで 84% に落ちることです。P コアが 8 個あるなら、8 ワーカーまでは 8 倍近く出てほしいところです。ここは理由を特定できていません。負荷のかかるコアが増えるとターボブースト時のクロックが下がること、Windows のスケジューラが 6 本目あたりから E コアや SMT の論理コアにスレッドを載せ始めること、そして ThreadPoolExecutor.map は 24 個のタスクをワーカーへ均等に配るので、遅いコアに載ったワーカー 1 本が全体の完了時刻を決めてしまうこと。どれも効いているはずですが、コアアフィニティを固定して切り分けてはいないので、推測のみです。

標準ビルドの 4 ワーカーが 0.96 倍と、逐次実行より遅くなっているのはGILが余計な仕事をしているためだと思われます。

マルチプロセスとマルチスレッドの速度比

ここまでの速度比は、すべてワーカーを起動し終えたあとの、定常状態の実行時間です。プールを作るところから計測区間に含めてみます。

構成 実行方式 プール起動 (s) 定常の速度比 起動込みの速度比
標準+GIL マルチプロセス 0.278 9.21x 5.44x
FT(GIL無効) マルチスレッド 0.065 11.01x 9.23x

プロセスプールの起動に 0.278 秒、スレッドプールは 0.065 秒。4 倍以上の差があります。Windows の spawn 方式では、ワーカーごとに Python インタプリタを新しく起動して __main__ を再 import するため、オーバーヘッドが存在しているのかなと想像できます。

マルチプロセスは生成にコストがかかるようです。短い処理だとマルチプロセスをしてもあまり早くならないので、デバッグのしにくさもあって、あまり使ってきませんでした。さらに、プロセスをまたぐと、メモリを共有できないのもデメリットです。

コードの書き方の違い

速度比の表には現れませんが、実際に書くコードは、マルチプロセスとマルチスレッドでかなり違います。

まずマルチプロセスの場合は、

import numpy as np
from concurrent.futures import ProcessPoolExecutor

def process_tile(tile: np.ndarray) -> np.ndarray:   # モジュール直下に置く必要がある
    return heavy_filter(tile)

def run(image: np.ndarray) -> np.ndarray:
    with ProcessPoolExecutor() as ex:
        return merge(ex.map(process_tile, split(image)))

if __name__ == "__main__":      # spawn 方式では必須
    run(load_image())

ex.map に渡したタイルは pickle で直列化されてワーカーへ送られ、結果もまた pickle されて戻ってきます。4K 画像を 24 分割すれば、往復で 2 回のコピーが走ります。関数はワーカー側で import し直せる必要があるので process_tile はモジュール直下に置きますし、ラムダやクロージャは pickle できないので渡せません。深層学習系のプログラムではpickleできないものもあるので、不便な印象です。

スレッドの場合は、

from concurrent.futures import ThreadPoolExecutor

def run(image: np.ndarray) -> np.ndarray:
    out = np.empty_like(image)
    with ThreadPoolExecutor() as ex:
        list(ex.map(lambda box: apply_into(image, out, box), boxes(image)))
    return out

入力画像も出力バッファも、ワーカーはそのまま参照します。コピーはありません。ラムダも渡せます(便利!)。

差がいちばん出るのは初期化です。学習済みモデルを読み込むような重い準備があると、プロセスではワーカーの数だけ読み直すことになります。

ProcessPoolExecutor(max_workers=8, initializer=load_model)   # 8 回読む
model = load_model()                                         # スレッドなら 1 回

残る課題

今回の計測は純粋なPython だけですので、実務の画像処理は NumPy と OpenCV と Pillow の上に乗っていて、これらの C 拡張が free-threaded ビルドでどこまで動き、どこまで速いのかは未検証です。C 拡張は free-threaded 対応を明示しないと、インポート時に GIL を復活させられる仕様だと理解しています。free-threaded ビルドで動かしているつもりが GIL は有効に戻っていた、という事故はあり得るので、実行時に sys._is_gil_enabled() を確かめる必要があるようです。

まとめ

まだまだ一般的ではない技術を本番環境で使うのは怖いですが、徐々に小さなスコープのアプリから試してみようと思います。高速化はキリが無いのですが、それ自体がサービスやアプリの強みにもなったりするので、持ちうるカードとして日々情報収集を続けていきたいと思います。

参考

Amazon Lookout for Visionを用いた異常検知

概要

前回の記事に引き続き、AWSの機械学習系のサービスの製造業向けデモです。

機械学習というと「学習」という言葉で示されるように、学習のためのアルゴリズムやノウハウが注目されがちですが、データの準備段階や、それを評価する作業にとても時間がかかります。それらの負担を軽減してくれるのが、SageMakerやLookout for Visionということで、弊社でも積極的に活用を進めています。

Lookout for Visionとは

AWSが提供している、品質管理に特化した機械学習サービスです。機械学習モデルをノーコードで簡単に作成することができ、またそのモデルを用いることで画像から異常を検出することができるようになります。最低30枚という非常に少ない枚数の画像から学習を始めることができることが特徴としてあげられます。

今回はLookout for Visionを用いて機械学習モデルを作成し、異常検出を行います。

使用する画像

今回使用する画像は以下のリンクからクローンした基盤の画像です。

github.com

テスト用、トレーニング用、検証用にフォルダがわかれていますが、今回は学習のための枚数確保のためにテスト用、トレーニング用の画像を用います。

また、具体的な異常の種別ごとにアノテーションをする際、データを増幅するために異常を持った画像に回転処理を加えて枚数を増やしました。

データセットの作成

Projectsから「プロジェクトを作成」を押し、プロジェクト名を入力して「作成」を押します。

データセットを作成します。


データセット設定は「1つのデータセットを作成する」を選択しました。


イメージソース設定は「コンピュータからイメージをアップロードする」を選択しました。

「データセットを作成」を押すとデータセットが作成されます。

画像のインポート・ラベル付け

「アクション」から「トレーニングデータセットにイメージを追加します」を選択すると、ウィンドウが開かれ、画像をインポートすることができます。
一度に30枚が上限となっています。

正常な画像をインポートしました。「ラベルを追加」を押すとラベルを選択できるようになるため、「normal」を押していきます。

「正常」のラベル付けが完了しました。

続いて、異常のある画像をインポートします。
さきほどと同様に「アクション」から「トレーニングデータセットにイメージを追加します」を選択し、画像をインポートしていきます。

異常のある画像にも「Anomaly」のラベル付けを行います。

これで正常、異常のラベル付けが完了しました。

領域のアノテーション

この状態からトレーニングを始めても良いのですが、異常のラベルには、異常がある画像上の領域に印をつける作業(アノテーション)をすることができます。単に、画像全体のどこかに異常箇所が含まれていることを示すよりも、より具体的な情報を追加することができます。

「ラベルを追加」を選択し、Anomalyな画像をクリックするとアノテーション用の画面が表示されます。

「Add anomaly labels」から異常の種別を追加することができます。


今回は「solder」と入力し、ラベルを追加します。(solderは英語で「はんだ」を意味します。)

ラベルが追加されたら異常がみられる箇所を塗っていきます。
一番左の囲うボタンが便利です。


塗り終えたら「Submit and close」を押すとアノテーションが保持されます。

同様にしてほかの異常についてもラベルを追加し、色を塗っていきます。

 

各異常に対して20枚の画像が必要となります。

枚数が少ない場合はモデルを作成しようとした際にエラーが発生します。

モデルの作成

アノテーションが終わったら、「モデルをトレーニング」を押します。

トレーニングが始まりました。

トレーニングが完了し、モデルが作成されました。1時間7分のトレーニング時間でした。

テスト結果も表示されます。

異常が検出されたデータにはマーキングされているものもあります。

 

推論・検出

作成したモデルを使って異常検出を行います。

「トライアル検出を実行」を押し、タスク名、モデル、インポート方法を選択し、画面下部の「異常を検出」を押します。

「画像をアップロード」を押すと推論が行われます。

推論が完了しました。各画像についての検出結果を表示することができます。

おわりに

Lookout for Visionは直感的な操作で、また非常に少ない枚数の画像を用いて高い精度の検証を行うことができました。

シンプルな異常検出であれば非常に使いやすいサービスであると感じました。

プログラミングやコーディングを行う必要はないので、非プログラマーの方でも手軽に使用することができます。費用対効果が求められる場面ではいい選択肢になるのではないでしょうか。

異常の検出精度については、正常と異常を判別する精度は非常に高いのですが、今回はデータが少なかったので、具体的な異常領域についての推論精度には課題を感じました。


アノテーションの方法や、検出を用いた検証など、より工夫をすることで精度を高めていくことができるようです。今後も使っていきたいと思います。

SageMakerを使用した製造業向け画像分類デモ

概要

弊社では、製造現場で使用される画像を対象とした画像処理プログラムの構築や、深層学習モデルの構築を行っています。SageMakerを使用すると、TensorFlowやPyTorchなどのフレームワークを使ったことがない方でも、手軽に深層学習モデルの構築ができ、そのモデルを利用することができます。この記事では、SageMakerを使用したモデル構築の方法や特徴について、実際の動作を確かめながら解説します。

SageMakerとは

AWSが提供している、機械学習を効率的に進めるためのフルマネージドサービスです。深層学習モデルの構築、トレーニング、デプロイ等機械学習に関する様々な機能が備わっており、用途に応じて幅広く活用することが可能です。

  • Amazon SageMaker Canvas

ノーコードでモデルを作成し、予測を得られるサービスです。深層学習に関する知識のない状態からでもデータセットからモデルを作成し、推論を行うことができます。

  • Amazon SageMaker Studio

SageMaker Canvasよりもさらに柔軟な機能を持つ機械学習サービスです。Jupyter notebook等を用いてソースコードを記述しながら機械学習を行います。SageMaker JumpStartという機能を用いることでTensorFlowやPytorchを用いたサンプルノートブックや、トレーニング済みのモデルを利用することができます。今回はこちらのサービスを利用して画像分類の深層学習を行います。

データセットの準備

今回利用したデータセットは、六角ボルト、ナットなどの工業用部品の画像データです。各種200枚ずつ画像データを用意し、Amazon S3上に以下のように保存しました。

input_directory 
    |--manufacture
       |--HexBolt
           |--hexbolt_1.jpg 
           |--hexbolt_2.jpg 
             …
       |--Nut
           |--nut_1.jpg 
           |--nut_2.jpg 
             …
        …

モデル構築

SageMakerのHome画面から"JumpStart"を選択します。

検索窓に「MobileNet」と入力すると検索結果が表示されるので、「MobileNet V3 Large 1.00 224」を選択します。

Train ModelセクションのTraining data set欄にS3に保存したデータセットのパスを入力します。
「Browse」から探索することが可能です。

パスの入力後、「Train」を選択すると、学習が開始されます。

今回は8分程度待つと、モデルが作成されたというメッセージが表示されました。

エンドポイントのデプロイ

この状態で「Deploy Model」セクションの「Deploy」を選択すると、エンドポイントの作成が開始されます。

約6分後、エンドポイントの状態が「In Service」となりました。これで画像分類を行うことができるようになります。

エンドポイントの利用(推論)

「Use Endpoint from Studio」セクションの「Open Notebook」を選択すると、ノートブックが作成され、新規タブが開きます。

開いた新規タブの上で右クリックし、「Show in File Browser」を選択します。

ノートブックが保存されているフォルダのファイルブラウザが表示されるので、ここに画像をドラッグアンドドロップすることで分類を行うことができます。

今回は「hex.jpg」(六角ボルトの画像)及び「nut.jpg」(ナットの画像)の2枚を使用します。一つ目のセルの内容を書き換えます。デフォルトではcat.jpg,dog.jpgについての推論を行うための内容が書かれているため、「cat」を「hex」に、「dog」を「nut」に書き換えます。

download_from_s3メソッドは今回は使用しないため、コメントアウトします。この状態でセルを実行すると分類したい2つの画像が表示されます。

二つ目のセルについても同様に、「cat」を「hex」に、「dog」を「nut」に書き換えます。
二つ目、三つ目のセルを実行すると、画像の分類が行われ、画像とともに結果が表示されます。

エンドポイントの削除

デプロイしたモデルは、左のツールバーの「SageMaker JumpStart」下の「Launched JumpStart assets」から確認できます。
「Delete」を押すことでエンドポイントを削除することができます。

おわりに

弊社ではこれまでAWS上で深層学習の処理を行う場合は、p系やg系のGPUインスタンスを立ち上げ行うことが多く、特にクラウドアプリケーションからの連携の場合はAWS Batchから学習ジョブを制御したりなど、メンテナンスの上でも大きな負担となっていました。

SageMaker Studioを使うことにより、アプリケーション化した際のエンドポイント構築の負荷を下げることができるとともに、ある程度のレベルのソフトウェアエンジニアであれば公開済みのテンプレートを使用することによって、手軽に深層学習処理ができるようになりますので、検討初期のリサーチでも手軽に利用できそうです。

もちろん深層学習の原理やTensorFlowなどのフレームワークを知らないソフトウェアエンジニアであれば、SageMakerやいい選択肢になるかと思いますが、飛躍的に使いやすくコストもお安くなってきた今であれば、専門のAIエンジニアでも工数削減のメリットは大きそうです。

SageMaker Studioではより細かい制御が必要な場合には、学習処理のハイパーパラメータチューニングや、前処理後処理のカスタマイズも可能なので、使い方によっては独自の技術開発にも力を発揮できます。

一方で、データセットの構造化ではやはり手作業でPythonコーディングを行う部分は残りますし、データ増幅ではデータの特性依存のノウハウや、ソースコード共有のノウハウは課題が残っているので、まだまだ使いこなしが必要と思いました。引き続き使い込んでいきたいと思います。

Laravelの非同期処理の使いどころ

Laravel (ララベル) はPHP言語向けのオープンソースWebフレームワークです。タクトピクセルでは、SPA構成のWebアプリケーションを開発する場合にWeb API実装のためのフレームワークとしてLaravelを良く使っています。

Laravelにはイベント・リスナーキュー・ワーカーという仕組みがあり、これらを利用することで非同期処理を手軽に実装することが出来ます。「非同期処理」といっても、その実装方法は目的に応じて選択する必要があり、実装の工数も変わります。経験を踏まえて、どのように非同期処理を実装したら良いかを考えてみました。

タクトピクセルでは、AWSのECSというコンテナ環境を利用しているため、その場合を前提として記述していますが、Kubernetesやほかのマネージドコンテナ環境でも基本的には同様の考え方になると思います。

非同期処理を使うためのインフラ構築

実装上はLaravelで用意されている仕組みを使えばいいのですが、これを実際に手元で動かしたり、クラウド環境で動作させる場合はインフラ部分を工夫する必要があります。

Laravel queue worker (キューワーカー)を使用して、非同期処理を行います。

readouble.com

workerはsupervisordなどを使って、別のプロセスで常時起動しておきます。LaravelのWeb APIのController等でジョブをキューに登録すると、workerはその情報を取得して処理を行います。処理の内容はjob(ジョブ)という単位で登録されます。

Web APIはワーカーの動きを特に把握せずにレスポンスを返すので、ジョブの完了を知るためには、DBのフィールドに処理ステータスを示すフラグを用意しておき、別途、非同期処理のステータス状況を取得するためのWeb APIを用意しておく必要があります。

キューのドライバーは複数から選択できます。簡単に実装するのであれば、MySQLなどのRDBで良いのですが、アクセスが多少多くなってくるとすぐにDBのデッドロックに陥ります。それを回避するためには、理想的にはキュー用のドライバーにはredisやmemcachedのようなDBを選択するのが安心です。

非同期処理を使わない場合

まず、非同期処理を使わない場合についても考えたいと思います。

リスクがありますが、テスト的なWeb API実装やクローズドな環境でのアプリケーションだと、非同期処理を実装する工数が確保できないこともあると思います。

Webフレームワークでは、何か情報をリクエストしたら、処理を行い、その結果を返すだけ、という処理が基本になりますが、時間のかかる処理の場合は非同期処理をする必要があります。基本的に、1つのリクエストに対してレスポンスが返ってくるまでには1秒未満が理想的で、最大でも30秒に設定されることが多いと思います。

この最大の時間はPHPの設定で変更することが出来ます。

php.iniで設定する場合、

max_execution_time = 300

のようにすることで、300秒(5分)に伸ばすことが出来ます。この変更は、Laravelのアプリケーション全体に適用されるために、利用には注意が必要です。この他、コード上でset_time_limitを設定することでも実現できます。

この場合、考えられる問題としては、処理時間が予め予測できないような処理を含んでいる場合に、最大時間以内に処理が終わらずにタイムアウトしてしまうということです。タイムアウトした場合は、処理は途中で中断されます。

これに限らないことですが、処理が途中で中断してもデータの不整合が起こらないように、データベース操作は適切なトランザクションを設定しておく必要があります。

処理が完了せずにタイムアウトを頻発させてしまう場合、ユーザーには非常に不安定なシステムとしてみなされ、システムのUXは著しく低下してしまいます。セキュリティについての懸念も抱かせてしまうでしょう。

非同期処理方法の選択方法

ちょっとした非同期処理(10秒~1分程度):イベント・リスナー

Laravelを使ってWeb APIを開発していくと、少し複雑な処理を実装しようとすると、非同期処理はすぐに必要になります。ローカルアプリケーションの場合と異なり、Web APIの場合は、処理時間の上限を気にする必要があるのですが、画像処理やほかのサービスに対して処理を投げるなど、処理時間がデータによって変化するようなリクエストを行う場合には非同期に処理を行う必要があります。

およそ10秒以上かかるようなリクエストの場合は、この非同期処理を検討するべきだと思います。この非同期処理の場合は、特に処理の結果や進捗状況を監視する必要が無いものを想定しています。例えば、以下の様なケースが考えられます。

  • メール配信
  • チャットへの通知処理
  • 外部サービスのWeb APIのリクエスト
  • 特別なログ出力(自社製アクセス解析等)

この場合は、イベント・リスナーが適しています。

readouble.com

イベント・リスナーは、処理の発行と購読が簡単に実装できる、オブザーバーパターンを踏襲した仕組みです。(別途、Eloquent modelのobserverというのもありますが、別の目的で使われるものなのでここでは割愛。)

例えば、ファイルを削除した場合に、「削除イベント」を発行しておきます。別途、ShouldQueueクラスを継承した「削除メール通知」のためのリスナーを登録しておけば、非同期に通知処理が行われます。追加の要望で、チャットツールへの通知処理を追加したい場合は、同様にリスナーを登録するだけで、処理が追加されます。

このように処理の発行と購読を抽象化することで、手軽に短い非同期処理を実装することが出来ます。

時間のかかるバッチ処理(10分程度):キュー・ワーカー・ジョブ

例えば、以下の様なケースではLaravelのQueue(キュー)の仕組みが適しています。

  • PDF形式の帳票出力を行いたい
  • ダウンロード用のCSVファイルを作成したい
  • ダウンロード用の圧縮ファイルを作成したい
  • 外部サービスに複数のリクエストを行って集計作業を行いたい
  • ちょっとした画像処理を行いたい

readouble.com

タクトピクセルでは、プルーフロッグ(デザイン・書類のためのオンライン校正検版ツール)の画像比較処理などに、この仕組みを利用しています。

この場合は、時間のかかる処理なので、途中経過を確認するための仕組みや、失敗したときのリトライの方法について仕組みを考える必要があります。単に1つのリクエストに紐づいてジョブを処理するだけではなく、そのほか以下の様なAPIを用意しておく必要があります。REST APIの範疇を超えてくるので、実装したいジョブの内容に応じて適切なAPIを設計する必要があります。

  • ジョブの実行
  • ジョブのステータス取得
  • ジョブの中断
  • ジョブのリトライ
  • ジョブ結果の取得
  • ジョブの削除

さらに時間のかかるバッチ処理(1時間以上):AWS Batch, AWS ECS sheduled tasks

この場合はLaravelのworkerとは別の仕組みで処理を行います。例えば、以下の様なケースを考えます。

  • 1GB以上のデータに対して集計処理を行いたい
  • 時間のかかる機械学習の学習演算処理を行いたい

この場合は考えることが多くなります。途中経過を確認するための仕組みや、失敗したときのリトライの方法について仕組みを考える必要があります。

タクトピクセルでは、プードル(印刷工場のための深層学習モデル作成ツール)にて、AWS Batchを使ったGPUインスタンスによる深層学習の画像処理で、この仕組みを利用しています。数日間から1週間以上のバッチ処理を行うこともできます。

AWSでインフラを構築している場合は、AWS ECSを使っている場合にはAWS Batchやscheduled tasksが利用できます。ジョブを処理するトリガーやキューシステムはLaravelの仕組みを離れるため、PHP/Laravel以外のPythonやJavaScript/Node.jsで実装したアプリケーションを直接的に利用することもできます。

ただし、データの受け渡しやステータスの管理を行うための仕組みをきちんとアプリケーションレベルで設計する必要があります。MySQLなどのRDSを介してデータ授受を行う場合は、複数のアプリケーションでスキーマを共有したりメンテナンス時のデータの齟齬に気を付ける必要があります。S3などのファイルストレージを利用する場合は、シンプルな構造にしやすいですが、jsonなどのファイルに情報を埋め込む場合は同様に拡張性やメンテナンス性を考えて仕様を検討する必要があります。

時間のかかるバッチ処理の場合は、インフラ部分の比重が高い為、大掛かりなシステム以外では導入の検討が慎重に行う必要があります。手軽になってきたとはいえ、Laravel workerを使ってローカル環境でテストを行うことが出来れば、本番のクラウド環境でも再現しやすいのでまずはLaravel workerを使用して実装することを心がけています。

まとめ

何となく実装しがちな非同期処理を、目的別に振り分けてみました。非同期処理はデバッグの難易度が上がるとともに、予想外の要件やバグが出てくることもあるので、慎重に実装する必要があります。Laravelを使うことによって手軽に実装することが出来るようになっていますが、本質的な要件を理解したうえで、適切な方法を選択する必要があります。

Siamese Networkの紹介と印刷検査への応用

目次

はじめに

印刷検査における画像分類タスクの例

印刷検査における画像分類タスクの例

弊社では、上記のような印刷検査画像の分類タスクに対して独自のDeep Learningモデルを開発するなど、印刷分野に応用するための研究開発を行っています。今回はこれまでの研究開発で効果を示した”Siamese Network”について紹介します。

Siamese Networkはいわゆる「距離学習」に使用されているアーキテクチャであり、変化検知モデル (Change Detection model)にも応用されており、弊社の研究開発チームの調査で、印刷検査画像へも効果的に活用できることが分かってきました。

今回は、社内での議論をまとめるとともに、個人的にこの手法の素晴らしい点や注目すべきポイントについてまとめてみました。

Siamese Networkの紹介となる主な論文はこちらです。

Koch, Gregory, Richard Zemel, and Ruslan Salakhutdinov. "Siamese neural networks for one-shot image recognition." ICML deep learning workshop. Vol. 2. 2015.
https://www.cs.cmu.edu/~rsalakhu/papers/oneshot1.pdf

Siamese Networkの紹介

読み方

まず、初見で悩んでしまう"Siamese Network"の読み方ですが、英語圏ではSiameseはサイミーズと発音することが多いようです。ただ、Siamese catのことを日本では一般に「シャム猫(ネコ)」と呼ばれ、Siameseは「シャム」に対応していることから、日本語ではSiamese Networkをシャムネットワークと呼ぶのが自然と思います。

日本国内の研究発表では「シャム ネットワーク」が大勢で、「サイミーズ ネットワーク」も稀に聞かれます。

どんなもの?

そもそも、Siameseとはどういう意味でしょうか?goo辞書から引用するとこの意味で使われています。

  1. 2〈双生児が〉体の接合した

Siameseの意味 - goo辞書 英和和英

ネットワーク構造を見ると、その意味がよく理解できます。

Figure 3. of paper "Siamese Neural Networks for One-shot Image Recognition"

論文引用(Figure 3) 単純な2つの隠れ層のSiamese Network構造

2つの特徴の距離を求めるために、それぞれの入力を同じサブネットワークを通ることで特徴ベクトルに変換し、それを活用する、という方法をとっています。

このサブネットワークが特徴(機械学習の意味での”特徴量”と、文章の中で「注目するべき点」としての”特徴”が分かりにくいですね。今回は後者です)で、同じ形状をしているだけではなく、パラメータも共有しています。この「パラメータを共有する」という点は以降のSiamese Network系のアーキテクチャに引き継がれています。

これにより、2つのインプットからの特徴量が途中まで全く同じ処理をされることになり、それらの間の違い(あるいは類似)を比べやすくなります。このあたりの「途中まで全く同じアーキテクチャを共有し、途中から分岐して距離を求める」あたりをSiameseと表現しているわけですね。

由来と歴史

第3次AIブーム以降ではSiamese Networkに関する論文として上記の論文を取り上げましたが、Siamese Network自体はこれよりも前に提案されています。引用をたどってみると、2005年の時点で、顔を識別する距離学習の手法が提案されています。

Learning a similarity metric discriminatively, with application to face verification | IEEE Conference Publication | IEEE Xplore

Chopra, Sumit, Raia Hadsell, and Yann LeCun. "Learning a similarity metric discriminatively, with application to face verification." 2005 IEEE Computer Society Conference on Computer Vision and Pattern Recognition (CVPR'05). Vol. 1. IEEE, 2005.

この中では、「同じ関数Gの同じパラメータWをそれぞれの入力に対して処理するのに使用される。これは、Siameseアーキテクチャと呼ばれている」と紹介され、さらに以下の1993年のNIPSの論文が引用されています。

Signature Verification using a "Siamese" Time Delay Neural Network

この論文では手書きのサイン (signature) の識別に用いるという研究ですが、さらにこの中ではTDNN (Time Delay Neural Network) を用いた音声認識の研究として、1988年のジェフリー・ヒントン氏の論文が引用されています。現在のDeep Learningブームのはるか前に、このアーキテクチャが提案されていたことは驚きです。

距離学習 (metric learning)

Siamese Networkが新しいのは、ネットワークの構造がだけではなく、対になっているネットワーク同士の出力を距離学習によって最適化することです。

Contrasive Lossと呼ばれる損失関数を使用し、2つの入力に対して距離的に近いものか遠いものかを認識するように最適化します。単純な分類タスクをDeep Learningで実験する場合と比べて、データセットの作りやニューラルネットワークの構造の作りが異なるため、手軽に試しにくい点ではありますが、多くのタスクで本質的に理にかなった方法であるとも思います。

産業界への応用

印刷業界などの産業界への応用について考えてみたいと思います。

このモデルは以下のように設計されています。

  • 2つの画像を入力
  • 距離学習を行う
  • 新規の未知クラスに対して表現を獲得できる

この特性をうまく生かした応用例が様々に提案されています。

人物などの情報の特定

距離学習によるOne-shot Learningの有名な応用例の1つが顔認証です。事前に大量の顔画像データで学習し、埋め込むための空間を作っておくことにより、新規の顔画像を登録後、少ないデータ数で予測をすることができるようになります。これが顔認証の考え方と相性が良く、建物の入館審査や電子機器のある種の生体認証に使われています。

こうした考え方を応用することで、例えば

  • 印刷物に写った人物や物体が狙ったものとして認識されるかの検査
  • これまでの出版データと組み合わせることで売れた出版物と傾向が似ている人、キャラクタの表紙を選ぶようなマーケティング的活用

のような活用ができるかもしれません。

変化検知モデル

"Change Detection model" 変化検知モデルというものがいくつも提案されています。この応用先としては、様々なものが提案されています。

  • 衛星画像からの土地利用の変化検知
  • 道路上の放置残留物の検知
  • 骨とう品など物体の損傷の検出

基本的には、セマンティックセグメンテーション(領域検出)タスクとなります。通常は変化が無いものの、稀に起こる変化を検出するものが多いため、異常検知手法を応用したり、データの水増し方法を工夫するなどがポイントとなります。

印刷検査への応用

2つのインプット間の類似度が学習できるということは、どの程度変化があったかを学習することにも応用が効くという発想が出てきます。そこで例えば、印刷の正解データに対して実際の検査段階での画像を照合することで差分(=汚れなどの検知すべき物体)を把握することができるようになることが期待されます。

印刷検査への応用としては、具体的には2つが挙げられます。(実はこの2つを組み合わせたEnd-to-Endモデルも考えられなくは無いですが、後述するように既に普及している検査装置を活用するという観点で、ここではあくまで別の工程として考えています。)

  1. 欠陥領域の検出(インライン検査、オフライン検査)
  2. 検出領域の分類(目視ベリファイ、目視検査)

印刷製造プロセス上の検査工程

印刷製造プロセス上の検査工程
1.欠陥領域の検出

インライン検査やオフライン検査と呼ばれる検査工程で行われているのが、欠陥領域の検出処理です。一般に、印刷検査では以下の様なルールベースの画像処理で検出が行われます。

  1. 撮像
  2. 位置合わせ
  3. 差分処理
  4. ノイズ除去
  5. 領域検出
  6. 装置制御

一見、この工程だけで検査が完了するようにも思えますが、以下の様に印刷の検査は多くのノイズとの戦いになります。

印刷検査における画像差分結果の例

印刷検査における画像差分結果の例

印刷物のスキャン画像には様々な要因でノイズが混入しており、単純な1画素間の差分処理では本来検出したい欠陥箇所を特定することは困難です。位置合わせ、差分処理、ノイズ除去など、それぞれのプロセスで様々な工夫を行い、検出するにふさわしい結果を得るために検査装置メーカー各社が工夫を凝らしています。

そのような抽象度の高い比較を行う場合にDeep Learningを活用するというのは自然な発想です。様々なDeep Learningのモデルの中で、Siamese Networkは基準となる画像に対する抽象的な画像特徴を距離学習できるという点で、印刷などのパターンマッチングを必要とする検査に対して有効に機能すると思われます。

しかしながら、Deep Learningを検出に対して用いる場合は、従来型のルールベース画像処理に対して処理速度の問題や、精度保証の問題があるのと、ハードウェアベースでの画像処理ではDeep Learningの実装上のコストが見合わないこともあり、実利用は進んでいないのが現状です。

2.検出領域の分類

「2.検出領域の分類」については、より現実的です。現在でも一部の印刷製造工程では、ルールベース画像処理によって検出した後、目視による2次的な検査工程を踏まえることで歩留まりと精度の両立を目指しています。この2次的な検査工程は、画像分類タスクに置き替えることが出来るため、Deep Learningの活用がそのまま進みやすいと考えています。

この分類タスクが十分な精度で自動化できた場合は、単に目視ベリファイ工程を自動化できるというだけではなく、過検出の抑制により検査精度をさらに高めることが出来たり、検出した欠陥の品種を時系列的に解析することにより、製造装置や人材配置のマネジメントに活用できる情報が得られたりなどの応用が考えられます。

ベリファイ工程の画像分類タスク

ベリファイ工程の画像分類タスク

分類タスクにおいても、上の例のように、基準となる画像(マスター画像)と欠陥特徴が埋め込まれた検査対象画像(ターゲット画像)がついになって比較する必要があります。例えば、ごみの欠陥品種である場合に、ターゲット画像だけを見てもそれがもともとあったデザインなのか、欠陥なのかという判断はできません。あくまで基準となる画像との比較によって分類の答えが出るという点で、欠陥領域の検出と同じです。

実際にDeep Learningのモデルを設計する場合は、Siamese Networkアーキテクチャを踏襲した2つのインプットを持つ構造を採用しつつ、出力層には分類タスクに用いられるSoftmax関数を使用した層を利用するなどの工夫が必要となります。

まとめ

今回は、Siamese Networkアーキテクチャに絞って技術的に考察している内容についてまとめました。

弊社では、Siamese Networkアーキテクチャベースの独自のDeep Learningモデルを開発するなど、印刷分野に応用するための研究開発を行っています。これまであまり更新できておりませんでしたが、少しずつ社内の知見をまとめて行きたいと思います。

シード〜アーリー期のSaaSサービスの開発環境

タクトピクセルの玉城です。ブログを初めて最初の記事ですが、今回は社内の開発環境について書きたいと思います。

当社の開発環境は、初期のスタートアップが取る割と一般的な構成なのではないかと思います。AWSで構築しています。

f:id:tpxtamaki:20200726221714p:plain

印刷・コンテンツ業界向けSaaSアプリケーション

poodl.cloud

proofrog.cloud

近年は選択肢も多くなってきて、特殊な処理を行わない一般的なSaaSであれば、どの技術を採用するかはトップや初期の開発メンバーの好みに依存している気がしていて、そういうバックグラウンドがない場合は逆に判断に迷う部分が多いのではないかと思い、この記事を書いています。当社と同じような道を辿っているスタートアップや、新規事業を立ち上げる方の参考になればと思います。

私達の少し特殊な事情があるとすれば、画像を多く使うということと、機械学習のためのバッチ処理が入ってくるということくらいです。あとはtoBのビジネスであることからインフラ部分は特殊なことをしなくてもそこそこアクセスをさばけますし、DBも通常のRDS(AWS Aurora)、ストレージはAWS S3です。

※ 当社は2つのSaaS製品を開発しており、実は若干の違いがあります。その点を詳しく説明してしまうと長くなってしまうため、この記事では2つの製品を区別せずに書いています。ご了承下さい。

現在のチームの状況

合計4〜8名体制(常勤4名+非常勤)

最初は1名で、2年かけて徐々に増えてきた、という感じです。非常勤の方は入れ替わりがあったりしていますが、最近は大体同じメンバーで回しています。

コンテナ&マイクロサービスアーキテクチャ

Dockerを使っており、ローカルPCの開発環境ではdocker-compose、本番環境ではAWS ECS Fargateで稼働しています。

f:id:tpxtamaki:20200726221450p:plain

当社製品のクラウドアーキテクチャ

フロントエンド、バックエンド、画像処理はそれぞれ別々のサービスで立ち上げて相互にやり取りするような、マイクロサービスアーキテクチャを採用しています。その間はJWT認証でボディはjson形式のいわゆるREST APIで通信しています。

スモールスタートで行うのであればモノリシックな構成でも良かったのですが、創業当時の資金状況である程度は精神的な余裕が出来たので、初期投資が大きくなってもスケーラビリティを優先させてマイクロサービスアーキテクチャをいきなり採用しました。マイクロサービスが必ずしも正解ではない、というのその通りだと思います。ただ、これに慣れてしまうと、技術選定のリスク感や新メンバーをアサインするときにこの方法を基準として考えてしまうため、モノリシックには戻れない体になってしまった、という実感があります。

この判断は現在の開発スピードの加速に貢献しています。GraphQLも出始めてカンファレンスではかなり話題になっていたのですが、全体の複雑性もまだ決まっていない状態では敷居が高く採用を見送りました。Dockerはローカル上のデバッグでも本番環境でも大活躍しています。素晴らしいツールに恵まれた、とてもいい時代になりました。

プログラミング言語 / フレームワーク

選定の基準としたのは主に3つです。

  • 自分(CTO)が理解できる技術(知っている、またはキャッチアップが容易)であること
  • 将来的な拡張性が見込めること
  • 開発者人口が多いもの

フロントエンド

フロントエンドはJavaScript/Vue.jsを使っています。

まず、SPA(Single Page Application)で作ろう、と最初に決めました。フロントエンドは当時、Angular, Reactをよく耳にしていて、まずはAngularで作り始めたのですが、これが難しかった。ちょうどAngularJSからAngularへの移行の時期で和書もまだ多くなく古いバージョンのみ、実際に作ってみるとちょっとしたアプリケーションでも仕組みの理解が難しく苦労しました。その時、Vue.jsのチュートリアルを試してみたところ非常にあっさりと実現出来たことや、その上のエコシステムが素晴らしくVue.jsを選択しました。UIコンポーネントは主にElementを利用していますが、こちらも素晴らしいプロジェクトです。

バックエンド

バックエンドにはPHP/Laravelを使っています。

バックエンドはまだまだPHP言語が根強い人気があり国内の開発者人口が多いので、事業拡大期に人材採用しやすいのではないかと思い採用しました。Rubyは不勉強であまり知っておらず、goやNode.jsはまだ手を出すには早すぎると考えました。

Webフレームワークについては、CakePHPZend Frameworkはどちらも大規模な開発には難易度が上がる事がわかっていたので代替を検討していたところ、Laravelを見つけました。明快なルールがありコードが気持ちよく書けるのが気に入っており、活発に開発が進められているのも安心感があります。最近になってますます人気が高まっているのを感じます。東京のイベントにも何度か行きましたし、日本のコミュニティも存在感があり今後の発展が楽しみなフレームワークです。

画像処理・機械学習

当社が開発・運営しているPOODL(プードル)も proofrog(プルーフロッグ)も画像処理を行う必要があり、その開発にはPython/Djangoを使っています。

画像処理自体はOpenCVや一部C++で書かれており、機械学習部分はTensorFlowKerasscikit-learn等の機械学習ライブラリを利用しています。

一般的に、画像処理とWebアプリケーション開発者の経験は大きく異なっていると考えていて、求める開発業務もなるべく分けるようにし、別々の専門性を要求したいと考えています。画像処理・機械学習のWebAPIは、アーキテクチャの中に隠してユーザーからは直接触れられない領域にデプロイして利用するアーキテクチャを取っています。そうすることで、認証機能やWebサーバーとしての役割はこのサービスには求められていないため、Djangoが提供するWeb APIの機能は重厚になりすぎずに済みます。これにより、画像処理の動作テストはコマンドラインベースで行う事ができ、複雑なアルゴリズム検討に集中出来ます。

その他の開発技術

POODL(プードル)では、多くの業務アプリケーションで採用されているWindows .NET系の環境で動作するよう、C#言語で深層学習モデルが動作するようなサンプルプログラムを提供しています。SciSharpプロジェクトにはとてもお世話になっています。

また、画像データセットをPOODLにアップロードするクライアントアプリケーションとしてElectron.jsを使ったGUIアプリケーション poodl-client も提供しています。

TensorFlow/Kerasのモデルを.NET系やElectron.jsで動かすのは可能なのですが、バージョン依存が激しかったりモデルの変換が必要だったりと一筋縄ではいかない部分がありました。これはまた機会があれば別の記事にまとめたいと思います。

アプリケーション開発とは別で、データ分析の依頼を受けることもあります。その場合は、Python/pandasをよく使っています。Jupyter notebookも使います。

深層学習技術やWebの技術は、日進月歩ですので日頃からのキャッチアップが欠かせません。提供するべきアプリケーションのニーズに合わせて新しい技術を吸収しながら、現場の役に立つ機能開発を行っています。

プロジェクト管理

Git/Github Issueで管理しています。現在、マイクロサービスごとにリポジトリを作っているので、2つの製品の主要なリポジトリだけでも10個ほど、すべて合わせると20個ほどが動いています。

開発の流れは以下のような形です。簡易版git flowを採用しています。

  1. Project Manager (PM, 私)がIssueを追加してメンバーをアサインする。
  2. 開発メンバーがブランチを切って実装作業を行う。
  3. 完了したらPull Requestを送る。
  4. PMが内容を確認して問題なければmergeする。
  5. 適切なタイミングでPMが判断して本番環境にリリースする。

この流れの中で、Github Issueの機能は非常に便利に使っています。最近は、徐々にメンバーも増えるとともに、私の仕事が開発から広報にシフトしてきたこともあり、体制とツールの見直しを行っています。

git flowをフルで採用するにはプロダクト開発初期には重厚すぎるので、ケースバイケースで部分的に使うのが良いと考えています。例えば、私達はまずはfeatureとreleaseの運用のみを行ってきました。今後はsupport, hotfixなどのブランチなども活用してDevOpsを推進していきたいとも考えています。

また、最近は人が増えたこともあり、リポジトリを横断するタスク管理ツールの検討も始めています。この辺りはチームのフェーズに合わせて柔軟に変えてく必要があると思います。

そのほか利用している外部サービス

認証

見えないコストですが重要なのが認証サーバーです。当初、POODL(プードル)という製品では工場の特殊な権限管理を実現するために自前の認証サーバーをPHP/Laravelで用意していました。これがとても辛く、登録時のフローからメンバーの権限管理、パスワードの再発行、IPアドレス制限などの機能を一つ一つ実装するだけでも何人月もかかるという状況でした。

もう一つの製品を作るにあたって便利なものがないか探した結果、Auth0を見つけ、とてもお世話になっています。難しい認証の規約を理解しなくても、ある程度はサクサクと実装出来るのと、Social Signin (FacebookTwitterなど)の認証が手軽に出来るのが素晴らしいです。

決済

基本的には「BtoBの商売であれば、クレジットカードなどの決済に対応しなくても、プロダクトのリリース初期ではなんとかなってしまうもの」だと思います。当社も最初の製品POODLをリリースしてから1年間は特に必要性も感じていませんでした。2つ目の製品proofrogが多少のtoC要素もあったことから、クレジットカード決済機能の実装が必要になりました。

自前で実装するのはセキュリティとしてもリスクが高いのでいくつか調査して、最終的にはStripeを利用することにしました。サブスクリプションビジネス用の機能も最近になってどんどん追加されていっているので期待が持てます。

もう一つ便利なのが、APIキーを入れ替えるだけでテスト用の決済処理を手軽に行えることです。テストの度に実際の決済がおりてしまうと高額商品の場合はそれだけである程度のコストになってしまいますが、Stripeの場合はテスト用のクレジットカード番号で試しに決済したり、エラーを起こしたり出来るので安心して開発することが出来ました。

さほど優先度も高くなかった中で工数もあまり使えず、セキュリティの不安を抱え続けることもできなかったため、ひとまず決済手段を手軽に実装できたのはとても助かりました。

今の所、当社製品のオンライン決済はStripe経由のクレジットカードだけです。他にもPayPalやPay系決済が増えてきているので、提供しているサービスの性質によっては必須となるかもしれません。

 

エラートラッキング

Sentryを使っています。最初はフロントエンド側だけでしたが、バックエンド、画像処理などあらゆるマイクロサービスに対して埋め込みました。当初はAWS CloudWatchのアラートを設定してエラーの原因を探る、ということを行っていたのですが情報が足りなかったりログを探すのが手間だったので、Sentryはとても便利に使っています。

特にフロントエンドではエラーが起こったとしても特に何も用意していない場合は運営サイドには感知できないため、このようなトラッキングのアプリは必要不可欠だと思います。

監視

エージェント監視と外形監視をMackerelで行っています。他にも、AWS CloudWatchのアラートも設定しています。最初は別の製品を使っていたのですが、Docker対応が遅れていたのとあまり細かい機能を使いこなせずに移行しました。いまはこれで問題なく運用できています。

チャットサポート、オンラインマニュアル

お客様とのチャットによるサポート用にIntercomを使っています。かゆいところに手が届く、とても便利なツールです。今の所、唯一無二のサービスだと思っていたのですが、もう少しエディタの充実や日本語環境のUI対応がうまくいくと良いなと思っているところです。

オンラインマニュアルには、IntercomHelpDocsを併用しています。Intercomはチャットやカスタマーサクセスのためのコミュニケーションツールとしての色合いが濃く、Articleと呼ばれるオンラインマニュアルについてはまだ機能や使い勝手の面で十分ではないと考えていました。いろいろ試してみて、Intercomの資産をそのまま移行でき、使い勝手も良いHelpDocsを追加で使用しています。オンラインマニュアルに特化した機能がいくつかあり、多言語化の機能もあるのでおすすめです。Intercom側にHelpDocsを統合する機能も用意されています。

オフィス環境とリモートワーク

これは開発とは直接関係がないですが、PMとしては結構悩むポイントなのではないかと思います。プロジェクト管理をきちんと行っていけば、まず緊急的には昨今のリモートワークに対しても柔軟に対応出来るのではないかは思います。現在私達は半々くらいの人数でオフィス出社組とリモートワーク組に分かれています。今後はますますリモートワークが多くなっていく見込みです。

中長期的に会社のスタンスとしてリモートワークを推進するのかどうなのか、個人的には、リモートワークは一長一短で、メンバーの特性に依存するため、トップダウンで決めるのではなく個々の希望を聞きながら制度設計を行っていくのが良いと考えています。コミュニケーション能力はチーム開発では重要なのは言うまでもありません。しかしより詳しく見れば、「対人コミュニケーション能力」と「リモートコミュニケーション能力」と2種類あり、それぞれのメンバーの得意不得意にはばらつきがあるので、そのレベル感をうまくバランスとりながら進めていくことが必要と思います。

実際、メンバーの好みによって、会社に来たほうが捗る、ということもあるようです。それは尊重しつつ、働きやすい環境を作って開発が加速できるようにルールを整備しています。 

まとめ

実はこの記事はWantedlyに書いた自社広報用の記事で書ききれなかった仕組みについて、より膨らませてまとめたものです。OSSや素晴らしいSaaSプロダクトの力を借りながら私達の製品が構成されています。その上に独自の新しい価値を生み出し、お客様を幸せにできるプロダクト開発を目指して行きたいと思います。

溜まってきた個別のノウハウ(+つらみ)については機会があればまとめていきたいと思います。同じように試行錯誤しながら進めているスタートアップのチームも多いと思うので、何らかの参考になれば幸いです。 

また、Wantedlyではフロントエンド、機械学習のメンバーを募集しています!興味ある方は是非見てみて下さい。