TECH PLAY

セーフィー株式会社

セーフィー株式会社 の技術ブログ

246

はじめに CVPR2024は、コンピュータビジョンとパターン認識の分野における最前線の研究成果を集める国際会議です。今年の論文提出数は11532件で、昨年のCVPR2023から26%の増加を記録しました。その中で採択されたのは2719件、採択率は23.6%です。この中から特に優れた24件の論文が Best Paper Award 候補として選出されました。 本記事では、これらのアワード候補となった論文の概要と、その技術的な特徴を紹介します。最先端の技術動向の理解や、今後の研究開発に役立てていただければ幸いです。 はじめに Best Paper Award 候補 Objects as volumes: A stochastic geometry view of opaque solids Repurposing Diffusion-Based Image Generators for Monocular Depth Estimation Comparing the Decision-Making Mechanisms by Transformers and CNNs via Explanation Methods MMMU: A Massive Multi-discipline Multimodal Understanding and Reasoning Benchmark for Expert AGI EventPS: Real-Time Photometric Stereo Using an Event Camera MemSAM: Taming Segment Anything Model for Echocardiography Video Segmentation Correlation-aware Coarse-to-fine MLPs for Deformable Medical Image Registration Producing and Leveraging Online Map Uncertainty in Trajectory Prediction SpiderMatch: 3D Shape Matching with Global Optimality and Geometric Consistency PaSCo: Urban 3D Panoptic Scene Completion with Uncertainty Awareness PlatoNeRF: 3D Reconstruction in Plato’s Cave via Single-View Two-Bounce Lidar Temporally Consistent Unbalanced Optimal Transport for Unsupervised Action Segmentation Rich Human Feedback for Text-to-Image Generation BIOCLIP: A Vision Foundation Model for the Tree of Life Grounding and Enhancing Grid-based Models for Neural Fields NeRF-HuGS: Improved Neural Radiance Fields in Non-static Scenes Using Heuristics-Guided Segmentation Mip-Splatting: Alias-free 3D Gaussian Splatting pixelSplat: 3D Gaussian Splats from Image Pairs for Scalable Generalizable 3D Reconstruction MLP Can Be A Good Transformer Learner Task-Driven Wavelets using Constrained Empirical Risk Minimization Image Processing GNN: Breaking Rigidity in Super-Resolution Generative Image Dynamics Analyzing and Improving the Training Dynamics of Diffusion Models EGTR: Extracting Graph from Transformer for Scene Graph Generation おわりに Best Paper Award 候補 Award Candidate 論文の概要をセッション順に記載します。 Objects as volumes: A stochastic geometry view of opaque solids 著者:Bailey Miller · Hanyu Chen · Alice Lai · Ioannis Gkioulekas セッション:Orals 1B: Vision and Graphics 著者らは、物理法則に基づいて不透明物体をボリュームモデルで表現する理論を提案しています。まず、ランダムな指示関数を用いて不透明物体を確率的に表現し、光が物体を通過する際に減衰が指数関数的に変化する条件から、光の減衰係数を定式化します。この理論の一般化することで、等方性および異方性の散乱や、不透明物体の陰関数表現に対応できることを示しています。この手法によって、優れた3次元再構成を行うことができます。 本論文で提案している理論の概要を示しています。減衰係数 を密度(density)と投影面積(projected area)の積として表します。密度は、平均された陰関数(mean implicit)から空隙率(vacancy)を通して計算することができます。また、投影面積は、異方性パラメータを用いて計算します。 Repurposing Diffusion-Based Image Generators for Monocular Depth Estimation 著者:Bingxin Ke · Anton Obukhov · Shengyu Huang · Nando Metzger · Rodrigo Caye Daudt · Konrad Schindler セッション:Orals 3A: 3D from Single View 単眼深度推定は一般に、学習データに未知のデータに対して難易度が高い傾向にあります。そこで著者らは、豊富な事前知識をもつ既存の生成拡散モデルを利用した単眼深度推定手法を提案しています。提案法は Stable Diffusion から派生した手法で、合成データ(synthetic data)でファインチューニングされています。またゼロショット転移を用いることで未知のデータにも強く、単眼深度推定タスクでSOTAな結果を出しています。 ファインチューニングの過程は以下のステップで行われます。事前訓練されたStable Diffusionを利用し、画像 と深度 を潜在空間 と に変換します。 にノイズを付加し後、 と連結し、これらを入力に元の深度の潜在空間を復元するようにU-Netをファインチューニングします。損失関数は、元のノイズ とモデルが予測したノイズ の間の二乗誤差となっています。 Comparing the Decision-Making Mechanisms by Transformers and CNNs via Explanation Methods 著者:Mingqi Jiang · Saeed Khorram · Li Fuxin セッション: Orals 3B: Vision, Language, and Reasoning 著者らは、視覚認識バックボーンモデルの意思決定を分析するために、サブ説明カウント法(sub-explanation counting) と クロステスティング(cross-testing)の2つの方法を提案しています。これにより、ネットワークが持つ合成性(compositionality)と分離性(disjunctivism)という2つの特性の違いが明らかになります。 収穫機の画像をサブ説明カウント法で分析しています。合成的なモデルであるConvNeXtやトランスフォーマーは、画像の複数の部分を一緒に見て意思決定を行います。したがって、右上のツリーのように入力画像の一部がマスクされた場合、推論されるクラスラベルの信頼度はわずかに減少します。一方で、分離的なモデルであるCNNや蒸留トランスフォーマーは、少ない部分から判断するため、右下の画像のように大きな信頼度を出力します。この分析は、各モデルがどのように画像の部分を利用しているかを明らかにします。 MMMU: A Massive Multi-discipline Multimodal Understanding and Reasoning Benchmark for Expert AGI 著者:Xiang Yue · Yuansheng Ni · Kai Zhang · Tianyu Zheng · Ruoqi Liu · Ge Zhang · Samuel Stevens · Dongfu Jiang · Weiming Ren · Yuxuan Sun · Cong Wei · Botao Yu · Ruibin Yuan · Renliang Sun · Ming Yin · Boyuan Zheng · Zhenzhu Yang · Yibo Liu · Wenhao Huang · Huan Sun · Yu Su · Wenhu Chen セッション:Orals 3B: Vision, Language, and Reasoning 著者らは、マルチモーダルなモデルを評価するための、大学レベルの知識と熟考を要する大規模な複数分野のタスクからなる新しいベンチマークを提案しています。MMMUは、既存のベンチマークとは異なり特定分野の知識を必要とする高度な認識と推論に焦点を当てており、14のオープンソースLLMや、GPT-4V (ision)、Geminiの評価結果からも、その難しさが示されています。MMMUは、次世代の専門的な人工知能モデルの構築を促進することを期待しています。 MMMUには大学の試験、クイズ、教科書から厳選された11,500の多様な質問が含まれており、アート&デザイン、ビジネス、科学、健康&医療、人文社会科学、技術&工学の6つの主要分野をカバーしています。チャート、図、地図、表、楽譜、化学構造など、異種性の高い30種類の画像も含まれています。MMMUのテストでは、GPT-4VとGemini Ultraがそれぞれ56%と59%の精度しか達成しておらず、これらのモデルにはまだ改良の余地が大きいことを示しています。 EventPS: Real-Time Photometric Stereo Using an Event Camera 著者:Bohan Yu · Jieji Ren · Jin Han · Feishi Wang · Jinxiu Liang · Boxin Shi セッション:Orals 3C: Medical and Physics-Based Vision 本記事を執筆している 2024/6/4 時点で論文が未公開のため、関連情報を記載します。 タイトルから、本提案はイベントカメラを用いてリアルタイムにPhotometric Stereo(照度差ステレオ)を行う手法と考えられます。イベントカメラとはマイクロ秒単位の時間分解能を持つカメラデバイスです。Photometric Stereo は照明方向を変えながら撮影を行い、画素ごとの明るさの変化から表面の法線方向を推定する手法です。 著者らが公開しているデモ動画では、イベントカメラの撮影結果、法線の推定結果、測定するオブジェクトなどが示されています。 MemSAM: Taming Segment Anything Model for Echocardiography Video Segmentation 著者:Xiaolong Deng · Huisi Wu · Runhao Zeng · Jing Qin セッション:Orals 3C: Medical and Physics-Based Vision 著者らは、SAM(Segment Anything Model)を医療ビデオに適用することで、超音波ビデオセグメンテーションの課題に取り組んでいます。提案モデルは、空間と時間情報を組み合わせた時空メモリの使用と、予測されたマスクを活用してメモリの品質を向上させるメモリ強化メカニズムを提案しています。これにより、セグメンテーションの精度と一貫性が向上しています。 提案法は主にSAMとメモリの2つのコンポーネントで構成されています。画像エンコーダは入力画像を、プロンプトエンコーダは外部プロンプトをそれぞれ埋め込みにエンコードし、マスクデコーダが統合してセグメンテーションマスクを予測します。画像埋め込みはメモリ特徴空間に保存され、デコード時にメモリからメモリプロンプトが読み出され利用されます。メモリは強化とエンコーダを通じて更新されます。 Correlation-aware Coarse-to-fine MLPs for Deformable Medical Image Registration 著者:Mingyuan Meng · Dagan Feng · Lei Bi · Jinman Kim セッション:Orals 3C: Medical and Physics-Based Vision 近年の医用画像位置合わせでは、変形の大きい脳や心臓のMRI画像に対応するため、Transformer ベースの手法が注目されていますが、計算コストが高く高解像度の特徴を扱えない課題があります。著者らは、MLP(多層パーセプトロン)ベースのネットワークを提案しています。提案法は、複数の解像度の特徴マップを利用して、粗から密(coarse-to-file)へ段階的に位置合わせ(registration)を行います。この手法は、高い計算効率を保ちながら、変形の大きい医用画像に対してもリアルタイムに優れた性能を発揮します。 提案法は、CNNベースの階層的な特徴抽出エンコーダと、CMW-MLPブロックを用いたデコーダから構成されます。初期ステップでは、エンコーダから得られた最も粗い解像度の特徴マップ( と )を使用し、初期の変形フィールド を生成します。以降のステップでは、前のステップで得られた変形フィールドを使用して、次の解像度の特徴マップを変形し、再度CMW-MLPブロックに入力します。このプロセスを繰り返すことで、段階的に詳細な位置合わせを実現します。 Producing and Leveraging Online Map Uncertainty in Trajectory Prediction 著者:Xunjiang Gu · Guanyu Song · Igor Gilitschenski · Marco Pavone · Boris Ivanovic セッション:Orals 4A: Autonomous Navigation and Egocentric Vision 著者らは、Autonomous Vehicle(AV)の軌道予測において、オンラインマップの不確実性を推定する手法を提案しています。提案法は、不確実性を考慮することで軌道予測の学習の収束が最大で50%速くなり、軌道予測性能が最大で15%向上します。 AVで取得したカメラやLiDARなどのセンサーデータは、BEV(Bird’s Eye View)特徴量にエンコードされます。特徴量を用いて、分類モデルはマップ要素(車線、道路境界など)を予想します。回帰モデルは、ポリラインやポリゴンなどのマップ要素の頂点と、ラプラス分布で表される不確実性を予測します。生成された確率的なオンラインマップは、GNN(Graph Neural Network)やTransformer などの確率的エンコーダに入力され、軌道予測が行われます。 SpiderMatch: 3D Shape Matching with Global Optimality and Geometric Consistency 著者:Paul Roetzer · Florian Bernard セッション:Orals 4B: 3D Vision 著者らは3次元形状マッチングのための新しい経路ベースの手法を提案しています。様々な種類のマッチング問題を解くための一般的なアプローチとして、直積空間上での最短経路を求める方法があります。これは多項式時間で大域的に最適な解を計算することができますが、3次元形状マッチングへの自然な一般化は難しいことが広く知られています。著者らはこれらの課題を解決し、効率的に大域最適な解を得る手法を提案しています。 提案法は、3次元形状を3次元形状表面をなぞる長い自己交差曲線(SpiderCurve)として表現します。その結果、密なジオメトリに対しても、幾何的に矛盾のない形状マッチングができることを示しています。実行時間の検証では、メッシュ数が増えても現実的な時間でスケールしています。 PaSCo: Urban 3D Panoptic Scene Completion with Uncertainty Awareness 著者:Anh-Quan Cao · Angela Dai · Raoul de Charette セッション:Orals 4B: 3D Vision 著者らは、不完全な3次元点群からジオメトリ、セマンティック、インスタンスを推定するPanoptic Scene Completion (PSC)という新しいタスクを提案しています。提案手法はマルチスケールスパース生成デコーダーを利用し、さらにMulti-Input Multi-Outputを導入することで、不確実度を推定できるようにしています。 提案するネットワークは、同一のアーキテクチャを持つ複数のサブネットが含まれます。各サブネットは一部のパラメータを除いて共通のパラメータを使用します。1つの点群データからデータ拡張を行い、複数の入力データを生成し、それぞれの入力データをサブネットで演算することで、入力データと同数のPSCを出力します。複数の予測をアンサンブルすることで、不確実性の推定と予測精度の向上を行います。 PlatoNeRF: 3D Reconstruction in Plato’s Cave via Single-View Two-Bounce Lidar 著者:Tzofi Klinghoffer · Xiaoyu Xiang · Siddharth Somasundaram · Yuchen Fan · Christian Richardt · Ramesh Raskar · Rakesh Ranjan セッション:Orals 4B: 3D Vision 著者らは、単一視点で3D再構築を行う手法を提案しています。従来の単一視点の3D再構築に使われていたNeRF(Neural Radiance Fields)は、物理的に正確ではない事前情報に依存しています。本手法は、LiDAR(Light Detection and Ranging)データとNeRFを用いて、複雑なシーンの3Dモデルを生成します。本手法は、2回までの反射光を測定する技術を活用し、従来のNeRF手法と比較して、環境光や反射光の事前データに依存せずに再構築精度を向上させています。 シーンの各点をパルスレーザーで照射し、反射した光の飛行時間を測定します。このデータを用いてNeRFを訓練し、一次反射および二次反射光の経路をモデリングします。生成される3Dジオメトリは、密度推定に基づいて可視部分と隠された部分の両方を高精度に再構築します。 Temporally Consistent Unbalanced Optimal Transport for Unsupervised Action Segmentation 著者:Ming Xu · Stephen Gould セッション:Orals 4C: Action and Motion 著者らは、長時間動画のアクションセグメンテーションタスクに対して、ASOT(Action Segmentation Optimal Transport)という手法を提案しています。この手法は、従来の方法が抱えるアクション順序の仮定が必要であることや、アクションの時間的一貫性の欠如といった課題を解決しています。また、GPUを用いた数値解法アルゴリズムも提案されています。 ビデオフレームとアクションの間のコスト行列 には大きなノイズが含まれています。提案法(ASOT)は、アクションカテゴリが変更されるコスト と、隣接するビデオフレームでアクションが変更されるコスト を加味することで、最適な割り当て を予測します。提案法は最適輸送問題の一種である、GW(Gromov-Wasserstein)問題に基づいて構築されています。 Rich Human Feedback for Text-to-Image Generation 著者:Youwei Liang · Junfeng He · Gang Li · Peizhao Li · Arseniy Klimovskiy · Nicholas Carolan · Jiao Sun · Jordi Pont-Tuset · Sarah Young · Feng Yang · Junjie Ke · Krishnamurthy Dvijotham · Katherine Collins · Yiwen Luo · Yang Li · Kai Kohlhoff · Deepak Ramachandran · Vidhya Navalpakkam セッション:Orals 5A: Datasets and Evaluation 最近のText-to-Imageモデルは進歩していますが、生成画像には不自然さ、テキストとの不一致などの問題があります。これに対し、本研究では、生成画像の問題のある領域を詳細にアノテーションし、18Kの人間のフィードバックデータ(RichHF-18K)を収集しました。このデータセットから、フィードバックを予測するマルチモーダル変換器を訓練することで、ファインチューニングに使用するための高品質な画像を選別したり、生成画像中の問題のある領域のマスクを作成することに活用できます。 フィードバックモデルのアーキテクチャは、Text-to-Imageモデルからの生成画像とそのテキストプロンプトを入力とします。ViT(ビジョントランスフォーマー)から出力される画像トークンと、Text-embedモジュールから出力されるテキストトークンに自己注意機構を適用し、画像とテキスト情報を融合します。画像トークンは特徴マップに再構成され、不自然な箇所のヒートマップと品質スコアを出力します。テキストトークンは、画像トークンと共にトランスフォーマーデコーダに送られ、入力されたプロンプトと生成結果のどこがずれていたかを説明する文章を出力します。 BIOCLIP: A Vision Foundation Model for the Tree of Life 著者:Samuel Stevens · Jiaman Wu · Matthew Thompson · Elizabeth Campolongo · Chan Hee Song · David Carlyn · Li Dong · Wasila Dahdul · Charles Stewart · Tanya Berger-Wolf · Wei-Lun Chao · Yu Su セッション:Orals 5A: Datasets and Evaluation 著者らは、生物の画像から特徴量を抽出するための大規模なマルチモーダルモデルBioCLIPを提案しています。「TreeOfLife-10M」と呼ばれる45万種以上の生物を含む1000万枚以上の画像データセットを構築し、そのデータを用いてCLIPスタイルのモデルを学習しています。提案法により、10種類のデータセットに対する5ショット分類の平均精度が、51.5%(従来法:CLIP)から 68.8%に向上しました。 提案法は、テキストと画像の対照学習を行います。テキストをエンコードする際に、着目する階層よりも上位の分類名を含めることで、生物分類の階層の特徴と、画像の特徴の関連を学習します。対照学習では、同種のテキストの埋め込みと画像の埋め込みは近くなるように、異種のペアは遠くなるような目的関数を用います。これにより、提案モデルに系統樹の階層構造が反映されます。 Grounding and Enhancing Grid-based Models for Neural Fields 著者:Zelin Zhao · FENGLEI FAN · Wenlong Liao · Junchi Yan セッション:Orals 5B: 3D from Multiview and Sensors 著者らは、ニューラルフィールドの表現に一般的に利用されるグリッドベースのモデルを分析するための理論的枠組みを提案します。これらのモデルの近似および汎化挙動は、Grid Tangent Kernelsと呼ばれるグリッドモデル固有の量よって決定されることを示します。この枠組みを用いて、MulFAGridという新しいモデルを提案し、2D画像フィッティング、3D符号付き距離場(SDF)の再構築などのタスクで優れた性能が得られることを示しました。 上側の処理経路は、座標 をインデックス関数に入力し、位置に対してノードの集合を返し、ノード に関連付けられた特徴(重み)ベクトルを計算します。下側の経路は、多重化フィルターを用いてフーリエ特徴量を処理した後、正規化レイヤーを用いてカーネル関数を計算します。最後に、特徴ベクトルとカーネル関数を要素ごとに乗算して求める関数の近似値を出力します。 NeRF-HuGS: Improved Neural Radiance Fields in Non-static Scenes Using Heuristics-Guided Segmentation 著者:Jiahao Chen · Yipeng Qin · Lingjie Liu · Jiangbo Lu · Guanbin Li セッション:Orals 5B: 3D from Multiview and Sensors NeRFを用いて3Dモデルを構築する際、動的なシーンでは物体の移動や影の変化により画像間の一貫性が失われ、適用が難しいという問題がありました。この問題を解決するために、ヒューリスティックを用いて動的シーンから静的な3Dモデルを構築する手法を提案しています。合成データと実データを用いた実験により、提案手法は従来手法を上回る性能を示しています。 最下部に示されている提案法は2種類のヒューリスティックを用います。SfM(Structure-from-Motion)ヒューリスティックは、静的な物体において特徴点のマッチング頻度が高いことを利用します。カラー残差ヒューリスティックは、NeRFモデルのカラー残差が静的な物体で低いことを利用します。これらの情報をプロンプトとしてSAM(Segment Anything Model)に入力することで正確な静的マップが得られます。 Mip-Splatting: Alias-free 3D Gaussian Splatting 著者:Zehao Yu, Anpei Chen, Binbin Huang, Torsten Sattler, Andreas Geiger セッション:Orals 5B: 3D from Multiview and Sensors 著者らは、3D Gaussian Splatting(3DGS)技術において、様々なスケールで高品質に物体の3D画像をレンダリングするMip-Splattingという手法を提案しています。従来法は、ズームインズームアウトを行う際にノイズや歪みなどのエイリアスや、不自然なエッジのようなアーティファクトが生じる問題がありました。提案法では、高周波成分の処理やレンダリング時のフィルタの変更によってこれらの問題を解決しています。 3DGSは3Dシーンを3D ガウシアンで表現し、フィルタ処理を行い画像をレンダリングします。標本化定理に基づく適切なレンダリングのサンプリングレートを選択しない場合、高周波成分が正しく処理されず、エイリアスが発生します。また、焦点距離やカメラ距離の変化によって3D ガウシアンのサイズが過度に大きくなる時には、ぼやけや歪みが発生します。提案法では、3D smoothing filterを用いて3Dガウシアンのサイズを制約することで、高周波成分に含まれるアーティファクトを抑制します。また、レンダリング時に適切なフィルタサイズを選択することでエイリアスを抑制します。 pixelSplat: 3D Gaussian Splats from Image Pairs for Scalable Generalizable 3D Reconstruction 著者:David Charatan · Sizhe Lester Li · Andrea Tagliasacchi · Vincent Sitzmann セッション:Orals 5B: 3D from Multiview and Sensors 著者らは、画像のペア群から3次元輝度場を再構成するフィードフォワードモデルを提案しています。提案法は、3次元輝度場の高速な推論と、リアルタイムでメモリ効率の良いレンダリングを特徴としています。実世界のRealEstate10kデータセットとACIDデータセットにおいて、解釈可能で編集可能な3次元輝度場を再構成しながら、最先端のライトフィールド変換器を凌駕し、レンダリングを2.5倍高速化します。 提案モデルは3次元輝度場を3次元ガウスプリミティブによってパラメータ化します。入力画像の画素の特徴量 ( はピクセル座標) から、ガウスプリミティブのパラメータ を生成し、ガウススプラッティングによりレンダリングすることで2次元画像を求めます。 は入力画像をResNet-50やDINO ViT-B/8でエンコードしたものです。ガウスプリミティブのパラメータは が平均、 が共分散、 が透明度、 が球面調和関数の係数です。 と はNNモデル により予測され、 と は が予測したピクセルごとの深さの離散確率分布から間接的に予測されます。 MLP Can Be A Good Transformer Learner 著者:Sihao Lin · Pumeng Lyu · Dongrui Liu · Tao Tang · Xiaodan Liang · Andy Song · Xiaojun Chang セッション:Orals 5C: Low-Shot, Self-Supervised, SemiSupervised Learning セルフアテンションはトランスフォーマーの重要な要素ですが、計算リソースを多く必要とすることが課題です。著者らは、この問題を改善する手法を提案しています。具体的には、エントロピーを利用して重要性の低いアテンション層を特定し、レイヤーを軽量化することでメモリーの負荷を削減します。また、重要性の低いアテンション層の知識をその後段のMLP(Multi-Layer Perceptron)層に移すための学習手法も提案しています。 上の図は、DeiT-Bの各ブロックのエントロピーを測定し、入力に近い層ではアテンション層とそれに続くMLPレイヤでは同程度にエントロピーが低いことを示しています。次に、それを恒等写像(Identical Mapping)に置き換えることでレイヤーの修正を行います。結果としてDeiT-Bのパラメタを13.7%削減し、同一メモリ量で処理できる画像の量が20.5%増加しています。 Task-Driven Wavelets using Constrained Empirical Risk Minimization 著者:Eric Marcus · Ray Sheombarsing · Jan-Jakob Sonke · Jonas Teuwen セッション:Orals 6A: Low-level Vision and Remote Sensing 従来のディープニューラルネットワークでは、損失関数を追加することでソフト制約を課すことが一般的です。著者らは一部のパラメータに厳密な制約を設ける新しいフレームワークCERMを提案しています。具体的には、ニューラルネットワークの重みに特定の制約を課すことで、モデルの特性を制御します。これにより、異なるスケールの情報やノイズの影響を効率的に低減し、医療画像のタスクで優れた性能を示しています。 提案法は、「制約付き経験リスク最小化」(CERM)を用いて、畳み込みフィルターをウェーブレットに制約します。まず、ウェーブレットを特徴付ける方程式を導出します。導出された方程式から、リファインメントマスクと呼ばれるウェーブレットを一意に定める多項式を得ます。畳み込みフィルターに対して、リファインメントマスクの係数を制約として課します。これらの制約の下で、ネットワークを学習します。提案法を用いることで、特定のパターンを効果的に捉えることができます。 Image Processing GNN: Breaking Rigidity in Super-Resolution 著者:Yuchuan Tian · Hanting Chen · Chao Xu · Yunhe Wang セッション:Orals 6A: Low-level Vision and Remote Sensing 著者らは、超解像(Super-Resolution: SR)タスクにおいて、Graph Neural Network(GNN)を利用した手法(Image Processing GNN: IPG)を提案しています。提案法では、画素をノードとする2種類のグラフを構築します。ローカルグラフは、近隣の画素でノードを接続することで細部のディテールやテクスチャの復元を行います。一方、グローバルグラフは、画像全体で画素を接続することで、大局的なパターンを捉えます。また、提案法は、画像の単純で均一な部分と、細かいディテールが含まれている部分で、ノード間の接続数を調整し、効率的な情報集約を行います。 Urban100データセットを用いた検証結果では、提案手法(IPG)は、Bicubic補間や他の超解像手法よりも、元の高解像度画像(HQ)をより正確に復元しています。 Generative Image Dynamics 著者:Zhengqi Li · Richard Tucker · Noah Snavely · Aleksander Holynski セッション:Orals 6B: Image & Video Synthesis 著者らは、静止画像から自然でリアルな動きを持つ映像を生成する Generative Image Dynamics という手法を提案しています。提案法は、木々や花などの自然な動きを含む動画を教師データとして拡散モデルを学習します。この手法により、静止画像がインタラクティブで魅力的なコンテンツに変換されることが期待されます。 静止画像とノイズのある潜在変数をデノイズネットワーク に入力すると、各画素の動きを周波数領域で表現したスペクトルボリュームを予測します。 は2D 空間層とアテンション層が交互に配置されており、これにより特徴量マップ内のノイズを減少させながら、空間的な関係と周波数間の関係を統合します。スペクトルボリュームを逆フーリエ変換することで、各画素の変位を求めることができます。 Analyzing and Improving the Training Dynamics of Diffusion Models 著者:Tero Karras · Miika Aittala · Jaakko Lehtinen · Janne Hellsten · Timo Aila · Samuli Laine セッション:Orals 6B: Image & Video Synthesis 著者らは、一般的なADM (Ablated Diffusion Model) アーキテクチャにおいて、高レベルの構造を変更せずに拡散モデルの学習時に問題となる確率的な挙動をする損失関数等に対処するため、ネットワーク層を再設計しています。具体的には、学習過程でアクティベーション出力および重みの大きさを一定の範囲内に維持するようにしています。これにより、ImageNet-512の合成において、生成画像と実画像の統計的類似度を評価する指標であるFIDが2.41から1.81に改善されています。 U-NetベースのADMでは、エンコーダはスキップ接続を用いてデコーダに接続され、埋め込みがノイズレベルとクラスラベルを条件付けます。ADMはResNetをベースとしていますが、メインパスでは正規化が無いため、アクティベーション出力の増加を抑えられません。提案手法では、メインパスでのアクティベーション出力を一定の範囲に維持するために、MPブロック等を導入します。 EGTR: Extracting Graph from Transformer for Scene Graph Generation 著者:Jinbae Im · JeongYeon Nam · Nokyung Park · Hyungmin Lee · Seunghyun Park セッション:Orals 6C: Multi-Modal Learning シーングラフ生成(Scene Graph Generation: SGG)は、画像内のオブジェクトとそれらの間の関係を構造化するタスクです。SGGの従来法の多くはオブジェクト検出と関係予測を別々のステージで行うためモデル構造が複雑になり、計算コストが高くなることや、一貫性のある学習が難しいといった課題があります。提案法ではセルフアテンションを用いた軽量なワンステージモデルでこれらの問題を克服しています。 提案法は、入力画像をDETRエンコーダでエンコードし、続いてDETRデコーダーでオブジェクトを検出します。同時に、DETRデコーダーのアテンション層ごとの特徴量を用いることで、オブジェクト同士の関係性表現(Relation Representation) を計算します。関係性表現は、アテンション層ごとの重みを用いて、加重和がなされます。Relation Headは、関係性のラベルやその強度を推論し関係性グラフ(Relation Graph)を計算します。最後に検出したオブジェクトと関係性グラフを組み合わせてシーングラフを作成します。 おわりに 本記事では、CVPR2024のBest Paper Award候補となった論文を紹介し、コンピュータビジョンとパターン認識の分野における重要な進展を示しました。いずれの論文も興味深く、面白い内容でした。 現在、私の所属するAI Visionグループは9人で構成されています。今回の論文紹介は、各メンバーが分担して取り組みました。私たちのグループでは、最先端の研究を取り入れたAIの開発を行っています。興味のある方は募集要項をご覧ください。
はじめに 第 2 開発部モバイルグループで iOS テックリードをしている鞆です。 2024 年 5 月 23 日に開催された Findy さん主催のイベント TechBrew in 東京 ~モバイルアプリの技術的負債に向き合う~ にて、発表させていただきました。 findy.connpass.com 当日の様子を含めてイベントレポート的な形でご紹介できればと思います! はじめに 会場 オープニング 発表内容 Bitkeyのモバイルアプリを進化させるための歩き方 @arasan01_me モバイルアプリの技術的負債に全社を挙げて取り組む考え方 @mikity01985 パッケージ管理でモバイル開発を安全に進める @entaku_0818 GitHub Copilotで技術的負債に挑んでみる 懇親会 会場 Findy さんが 2024 年 4 月に大崎駅近くにオフィス移転されたとのことで、この新オフィスに併設されたイベントスペースが会場でした。 findy.co.jp 大崎駅からのアクセス についても画像付きで紹介されており、イベントスペース自体もオフィス受付のすぐ横にあるため、迷うことはなさそうです。 イベントスペース自体も 100 名収容可能ということで余裕があり、クロークやコントロールルームまである非常に本格的な作りでした。 開催時間が遅めということもあってか照明についても暗めとなっており、非常に雰囲気のある状態に仕上がっていました。 受付向かって右側すぐがイベントスペース入り口 スクリーン横には音響管理用のコントロールルーム 今回のイベントでは利用しませんでしたがカウンタースペースもありました オープニング Findy さんのイベントではオープニングで必ず乾杯!から始まるとのことなので、まずは全員で乾杯してイベントの開始となりました。 またここで、 Findy さんが作ったという IPA (クラフトビールのほうです) の紹介もありました。 挑戦 IPA 開発生産性 IPA Findy さん主催のイベントで付いてくる「TechBrew」という名称が正直なところ謎だったのですが、文字通り本当に醸していました。 挑戦 IPA と開発生産性 IPA の 2 種類があり、オフラインイベントで振舞われているとのことでしたが、限定本数となるため気になる方はお早めにとのことでした! 発表内容 私のほうからは、セーフィーで開発している Safie Viewer の iOS 版アプリ に関する技術的負債への取り組みに関して、ここ 1 年ほどで実施、検討している内容について事例紹介といった形で発表させていただきました。 speakerdeck.com 10 分間の発表に詰め込みすぎてしまった感があるのが反省です。 また、今回のテーマである「モバイルアプリの技術的負債に向き合う」についても、セーフィーのモバイルグループが置かれている現状にまさにマッチしたものとなっており、発表された内容それぞれが非常にためになりました。 Bitkeyのモバイルアプリを進化させるための歩き方 @arasan01_me speakerdeck.com Bitkey さんの workhub アプリに関して、専業エンジニアがいなかった React Native 時代から現在までの歴史に関して、技術選定の方針やネイティブ化への移行対応等の具体例を交えた発表でした。 TCA の採用についても一度見送ってから再度検討して採用する等、外部環境の変化に対して素早く対応していけるというところに開発チームの強さを感じました。 「技術選定の正解は?」という問に対して、「自分たち (開発チーム) がいいと思う方法全てが正解」という部分が非常に印象的で、セーフィーのモバイルグループでも技術選定で迷うことが非常に多いのですが、これに対して勇気づけられるような発表でした。 モバイルアプリの技術的負債に全社を挙げて取り組む考え方 @mikity01985 speakerdeck.com モバイルアプリの技術的負債を組織内でどう扱っていくかという点に関して、 EM ならではの高い視座で俯瞰的に見ていくような発表で、セーフィーの現状にも当てはまる部分が多かったです。 特に「技術的負債の中でもチームに閉じてしまうものは外部への説明に困る」といった指摘は過去を振り返ってみてもまさにその通りで、外部とのコミュニケーションに関する方法論等は非常に参考になりました。 モバイルアプリ開発における Four Key Metrics の難しさ等についても腹落ちする部分が多く、個人的には共感できる部分が多い発表でした。 パッケージ管理でモバイル開発を安全に進める @entaku_0818 speakerdeck.com 比較的歴史のある Voicy さんのアプリにおける過度な共通化という問題とそれに対する回答としての責務分割のお話しでした。 Swift Package Manager を利用してモジュール / パッケージとしてカプセル化することによって、責務としてのインターフェイスを明示化、また依存性を制限することでシステムとして安全な状態を保っているとのことで、昨今流行りの Swift Package Manager ベースのマルチモジュール化の利点として説得力のある内容だったかと思います。。 Safie Viewer の iOS 版でもこの辺りはやろうと思っていて進んでいない部分だったので、先行事例として非常に参考になる発表でした。 GitHub Copilotで技術的負債に挑んでみる www.docswell.com 意外と貴重なモバイル開発領域における GitHub Copilot 利用に関する発表でした。 特に iOS 開発ですと、 Xcode が公式に対応しておらず 3rd party ツールに依存してしまうということもあり、セーフィー社内や個人的にも GitHub Copilot を有効活用しきれていないという感触はあったのですが、やはり同様の辛みがあるのだなという納得感がありました。 また実際にデモで見せていただいた GitHub Copilot Chat との連携についてはほぼ利用していなかったため、 code suggestion 以外の使い方として一つ知見が増えました。 懇親会 一通り発表が終了した後は立食形式での懇親会となりました。 IPA も十分に用意いただいてました! 完全オフラインイベントということもあり、比較的小規模な集まりとはなったのですが、逆に参加者間でコミュニケーションが取りやすいといった面がありました。 モバイルアプリに関するイベントだったのですが、他職種のエンジニアの方も参加されており、意外なところでネットワークの広がりもできました! まとめ 今回、 Findy さん側からお話しをいただいての発表、イベント参加だったのですがテーマ的にも得ることが多いイベントでした。 完全オフラインイベントへの参加というのもコロナ禍以降で久しぶりだったのですが、やはりオフラインならではの良さ、密度の高さというのを感じました。 最後になりますがイベント企画、ならびに会場を提供いただいた Findy さん、誠にありがとうございました!
はじめに Data-centric AI Cleanlab 物体検出データセットのクレンジング データセット作成 物体検出モデルの学習 予測と正解データの生成 Cleanlabによる品質スコア算出 アノテーション修正 Cleanlabをフル活用するためのTips 同じ正解データや予測を複数回マッチングさせないようにする 閾値をモデルやデータセットに応じて適切に設定する スコアが1.0になっている画像でも一通り確認する まとめ はじめに セーフィー株式会社  で画像認識AIの開発エンジニアをしている水野です。 現在、 AI-App 人数カウント で利用される物体検出モデルの精度改善に取り組んでいます。物体検出モデルの精度改善方法としては様々な手法が考えられますが、近年はData-centric AIというアプローチが注目されています。そこで本稿では、Data-centric AIで物体検出モデルの精度を改善する一手法として Cleanlab を用いたデータセットのクレンジング方法について紹介します。 Data-centric AI Data-centric AIとは、2021年3月に Andrew Ng氏の講演「From Model-centric to Data-centric AI」 で提唱された概念で、AIモデルを固定してデータセットを改善することでモデルの精度改善を実現するアプローチのことです。これは従来主流であったデータセットを固定してAIモデルを改善するModel-centric AIとは対極にある考え方と言えそうです。ただしこれらはどちらのアプローチが優れているという話ではなく、実際の開発では両方の観点で改善していく必要があります。 データセットを改善したい場合、どのようなアプローチが考えられるでしょうか。単純に考えると画像およびアノテーションを人の目で確認し、問題のあるデータを抽出して修正するという方法が挙げられそうですが、この方法にはいくつかの課題があります。 現実のデータセットでは画像枚数が数千枚~数万枚という規模であり、人の目で一通り確認しミス無く修正しきるのはかなり時間が掛かる データセットがクリーンであることを客観的に示すことが難しい このようなデータセット改善の作業で発生する課題を解決するために Cleanlab のようなData-centric AI向けのライブラリが活用できます。 Cleanlab Cleanlab は confident learning の考え方をベースとしてデータセットのノイズを抽出するためのライブラリです。Cleanlabを使用することでデータセット内に存在するアノテーションの問題を自動的に抽出することができます。また画像ごとの品質スコアが出力されるので、アノテーションの品質がどの程度なのか定量的に示すことが可能です。従来は画像識別タスクのみサポートされていたのですが、 2023年9月のリリース から物体検出タスクに対応しています。 Cleanlabを用いた基本的なデータクレンジングの流れは下記の通りです。 データセット作成: データセットをtrainとvalidationに分けます AIモデルの学習: trainデータでモデルを作成します 予測生成: 学習したモデルを用いてvalidationデータに対する予測を生成します Cleanlabによる品質スコア算出: validationデータに対する予測と正解データを比較して画像ごとの品質スコアを算出します アノテーション修正: 品質スコアの低い画像のアノテーションを修正します お気づきの方もいるかもしれませんが、このままではvalidationデータに含まれる画像の品質しか判定できません。そこで実際にはK-fold分割を用いたOut-of-fold予測を生成し、全データに対する品質スコアを算出します。5-fold分割時の処理を図で書くと以下のようになります。5つに分割したデータで生成した各モデルで予測を生成し、結合することで実質的に全データに対する予測を生成することが出来ます。本稿では簡単化のために技術的な背景や詳細の説明は省略しますが、ご興味のある方は 公式の解説ページ をご参照ください。 5-fold分割によるOut-of-fold予測生成の流れ ( 公式の解説ページ の図をベースに作成) 物体検出データセットのクレンジング ここからは物体検出データセットに対する実際のクレンジングの方法を説明します。 物体検出モデルとして YOLOv8 、データセットとして COCO を使用します。 データセット作成 クレンジングしたいデータセットをtrainとvalidationに分割します。既に説明した通り、データセット全体をクレンジングする場合はOut-of-fold予測を生成する必要がありますので、COCOデータセット全体をクレンジングしたい場合はCOCOから提供されているtrainデータとvalidationデータを混ぜた上でK-fold分割を実施します。 K-fold分割では scikit-learn がよく利用されます。データセットの特性に応じて適切な分割方法を選択する必要がありますので、 公式ページ を参照し各自の状況に合わせた手法を選択してください。また分割数をいくつにすべきかという問題ですが、理想的には多い方が良いのですが分割数が増えると学習に必要な時間が膨大になってしまうため、実用上は5-fold分割がお勧めです。 Kaggle のコンペ等でも5-fold分割はよく利用されますし、trainとvalidationの割合としても4:1というのはバランスが良いと思います。 5-fold分割したデータセットの構成例を以下に示します。YOLO形式のラベルフォーマット等は 公式の解説ページ をご参照ください。またCOCOのフルデータセットの場合、5-fold合計で100GB(1foldあたり20GB)のディスク容量を必要としますのでディスクの空き容量にご注意ください。 $ tree datasets/ datasets/ ├── coco_fold1 │   ├── images │   │   ├── train │   │   │   └── xxx.jpg │   │   └── val │   │   └── yyy.jpg │   └── labels │   ├── train │   │   └── xxx.txt │   └── val │   └── yyy.txt ├── coco_fold2 ├── coco_fold3 ├── coco_fold4 └── coco_fold5 各foldに対応するyamlファイルの作成も必要です。fold1の例を以下に示します。 # Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..] path : ../datasets/coco_fold1 # dataset root dir train : images/train # train images (relative to 'path') val : images/val # val images (relative to 'path') test : # test images (optional) # Classes (80 COCO classes) names : 0 : person 1 : bicycle 2 : car # ... 77 : teddy bear 78 : hair drier 79 : toothbrush 物体検出モデルの学習 K-fold分割で生成した各foldで学習を実行しfold数分のモデルを生成します。まずは学習を実行するために公式のDockerコンテナを立ち上げます。 $ docker run --ipc=host -it --gpus all -v <DATASET_ROOT>:/usr/src/datasets ultralytics/ultralytics:latest 次にさきほど作成した各foldのデータセットごとに学習コマンドを実行します。yoloコマンドで学習を実行する場合のBashでのスクリプト例を示します。YOLOv8ではモデルのバリエーションとしてYOLOv8nからYOLOv8xまで5種類ラインナップがあり、ここではYOLOv8mを使用していますが、Cleanlabはモデルに依存しない手法なので基本的にどのモデルを選択しても問題ありません。実行する計算機環境やどれぐらいの時間でスコアを算出したいかに応じて選択してください。 for i in {1..5}; do yolo train data=/usr/src/datasets/coco_fold${i}.yaml model=yolov8m.pt done 予測と正解データの生成 各foldで学習したモデルで各foldのvalidationデータに対する推論を実行し、各推論結果を結合することでデータセット全体に対する予測を出力します。生成した予測はCleanlabが入力として期待するフォーマットに変換する必要があります。またvalidationデータに対する正解データの生成も必要です。 YOLOv8を用いて予測と正解データの生成を実行するサンプルスクリプトは以下の通りです。出力される predictions.pkl が予測、 labels.pkl が正解データに対応しています。 import os import pickle import numpy as np from tqdm import tqdm from ultralytics import YOLO from ultralytics.data.dataset import load_dataset_cache_file from ultralytics.engine.results import Results def make_prediction (results: Results, num_classes: int = 80 ) -> np.ndarray: """YOLOv8の推論結果をCleanlabの入力形式に変換""" pred_cls = results.boxes.cls.cpu().numpy() pred_conf = results.boxes.conf.cpu().numpy() pred_boxes = results.boxes.xyxy.cpu().numpy() prediction = [] for target_cls in range (num_classes): target_cls_mask = pred_cls == target_cls if sum (target_cls_mask) == 0 : # 該当クラスの検知結果が無い場合の処理 prediction.append(np.empty(( 0 , 5 ), dtype=np.float32)) else : target_cls_boxes = pred_boxes[target_cls_mask] target_cls_conf = pred_conf[target_cls_mask] target_cls_pred = np.concatenate([target_cls_boxes, target_cls_conf[:, None ]], axis= 1 ) prediction.append(target_cls_pred) return np.array(prediction) def make_label (data: dict ) -> dict : """YOLOv8の正解データをCleanlabの入力形式に変換""" label = { "labels" : data[ "cls" ].flatten().astype( int ), "seg_map" : os.path.basename(data[ "im_file" ])} # 正規化されたcx, cy, w, hの2次元アレイをshapeの値で元の座標に戻して、xyxy座標に変換 bboxes = data[ "bboxes" ] * np.array([data[ "shape" ][ 1 ], data[ "shape" ][ 0 ], data[ "shape" ][ 1 ], data[ "shape" ][ 0 ]]) bboxes[:, 0 ] = bboxes[:, 0 ] - bboxes[:, 2 ] / 2 bboxes[:, 1 ] = bboxes[:, 1 ] - bboxes[:, 3 ] / 2 bboxes[:, 2 ] = bboxes[:, 0 ] + bboxes[:, 2 ] bboxes[:, 3 ] = bboxes[:, 1 ] + bboxes[:, 3 ] label[ "bboxes" ] = bboxes return label def main () -> None : """5-foldのOut-of-fold予測および正解データを生成""" model_paths = [ "/usr/src/ultralytics/runs/detect/train/weights/best.pt" , "/usr/src/ultralytics/runs/detect/train2/weights/best.pt" , "/usr/src/ultralytics/runs/detect/train3/weights/best.pt" , "/usr/src/ultralytics/runs/detect/train4/weights/best.pt" , "/usr/src/ultralytics/runs/detect/train5/weights/best.pt" , ] dataset_paths = [ "/usr/src/datasets/coco_fold1/labels/val.cache" , "/usr/src/datasets/coco_fold2/labels/val.cache" , "/usr/src/datasets/coco_fold3/labels/val.cache" , "/usr/src/datasets/coco_fold4/labels/val.cache" , "/usr/src/datasets/coco_fold5/labels/val.cache" , ] predictions: list [np.ndarray] = [] labels: list [np.ndarray] = [] for model_path, dataset_path in zip (model_paths, dataset_paths): model = YOLO(model_path) cache = load_dataset_cache_file(dataset_path) for data in tqdm(cache[ "labels" ]): results = model(data[ "im_file" ]) predictions.append(make_prediction(results[ 0 ])) labels.append(make_label(data)) # pickleファイルとしてpredictionsとlabelsを保存 with open ( "predictions.pkl" , "wb" ) as f: pickle.dump(predictions, f) with open ( "labels.pkl" , "wb" ) as f: pickle.dump(labels, f) if __name__ == "__main__" : main() Cleanlabによる品質スコア算出 validationデータに対する予測と正解データを比較して画像ごとの品質スコアを算出します。品質スコアは0.0から1.0の範囲の数値で出力され、数値が高いほどアノテーション品質が高いことを表します。先ほどのスクリプトで predictions.pkl と labels.pkl が生成されているはずなので、これをCleanlabの 物体検出向けのAPI に入力します。Cleanlabは pip install Cleanlab 等でインストールできます。 スコアを算出し、スコアの低い画像を確認するサンプルコードは以下の通りです。COCOは80クラスのデータセットですが、説明のしやすさのためpersonクラスのみに絞った場合の例になっています。このコードを Jupyter Notebook 等で実行することで品質スコアの低い画像およびアノテーションを確認することが出来ます。 import pickle from Cleanlab.object_detection.rank import ( get_label_quality_scores, issues_from_scores, ) from Cleanlab.object_detection.summary import visualize IMAGE_PATH = 'all_images' predictions = pickle.load( open ( "predictions.pkl" , "rb" )) labels = pickle.load( open ( "labels.pkl" , "rb" )) # 各画像の品質スコアを計算 scores = get_label_quality_scores(labels, predictions) # 品質スコアが0.5を下回る画像インデックスをスコアの低い順にして取得 issue_idx = issues_from_scores(scores, threshold= 0.5 ) # 一番スコアの低い画像を表示 class_names = { "0" : "person" } issue_to_visualize = issue_idx[ 0 ] # ここの数値を変更することで別の画像を表示可能 label = labels[issue_to_visualize] prediction = predictions[issue_to_visualize] image_path = IMAGE_PATH + label[ 'seg_map' ] visualize(image_path, label=label, prediction=prediction, class_names=class_names, overlay= False ) 私の手元で試したCOCOデータセットにおけるいくつかスコアの低い画像および高い画像の例を載せておきます。図中の左画像の赤枠が正解データ、右画像の青枠がモデルによる予測です。 スコアの低い画像の例 右画像のモデルは左奥にいる人を予測できているにも関わらず、左画像ではアノテーションがされていないことでスコアが低くなっています (スコア: 2.092e-34) この画像は Timothy Krause氏によるMan with umbrella をCleanlabにより可視化したものです。 (C) 2012 Timothy Krause, Man with umbrella, License: http://creativecommons.org/licenses/by/2.0/ 右画像のモデルは4人の人物を正確に予測していますが、左画像では4人を囲うようにアノテーションされているためスコアが低くなっています (スコア: 4.821e-07) ちなみにCOCOでは複数人をまとめてアノテーションする場合にiscrowdという属性をTrueにするというルールがありますが、この画像はiscrowdがFalseになっていたので、そういった観点でもアノテーションに不備があると言えます この画像は Luca Vanzella氏によるDSCN0073b をCleanlabにより可視化したものです。 (C) 2005 Luca Vanzella, DSCN0073b, License: https://creativecommons.org/licenses/by-sa/2.0/ スコアの高い画像の例 正解データと予測がほぼほぼ一致しているのでスコアが高くなっています (スコア: 0.987) この画像は Michael Elleray氏によるSurfin' をCleanlabにより可視化したものです。 (C) 2011 Michael Elleray, Surfin', License: http://creativecommons.org/licenses/by/2.0/ この画像は Wonderlane氏によるMan making a sandwich, Vietnamese / Chinese, International District, Seattle, Washington, USA をCleanlabにより可視化したものです。 (C) 2010 Wonderlane, Man making a sandwich, Vietnamese / Chinese, International District, Seattle, Washington, USA, License: http://creativecommons.org/licenses/by/2.0/ アノテーション修正 スコアの計算が出来たらスコアの低い画像群の確認と修正を実施します。アノテーションツールとしてセーフィーでは FastLabel社 のツールをよく利用しています。その他 CVAT やYOLOv8公式でサポートされている Roboflow 等、様々なツールがありますので利用しやすいものを使用してください。 Cleanlabをフル活用するためのTips 今回Cleanlabを利用する中で気づいた課題やそれに対する対策についていくつか説明します。 同じ正解データや予測を複数回マッチングさせないようにする Cleanlabの現状の実装(2024/05/17時点)ではスコア計算時に同じ正解データや予測が複数回マッチングされる場合があり、スコアが正しく計算できない場合があります。スコアを出力してみて複数回マッチングの影響で意図しないスコアが出ているようであれば、スコア計算処理のマッチング部分を修正する必要があります。 閾値をモデルやデータセットに応じて適切に設定する Cleanlabでは信頼度の低い予測をスコア計算に使用しないように2つの閾値を設けています。この閾値の設定によって出力されるスコアの傾向が大きく変わるので、自分が学習したモデルやデータセットの特性に応じて調整することで精度の高いスコア計算が可能になります。 スコアが1.0になっている画像でも一通り確認する Cleanlabのスコアは0.0から1.0までの数値で表されるため、定義としては1.0は最もスコアが高い(アノテーション品質が高い)ことになります。しかし正解データと予測でマッチングするのものが無かった場合等にCleanlabはスコアのデフォルト値である1.0を出力するため、1.0というスコアは必ずしもアノテーションに誤りが無いことを表してはいません。実際上正解データと予測がぴったり合う(スコアが1.0になる)ことはほとんどあり得ないので、1.0のスコアが出力されたということは何らかの問題がある可能性があります。 以下の画像は一例ですが、スコアが1.0にも関わらず正解データではアノテーションが1つ漏れていることが分かります。これは予測のbboxの信頼度が低く、正解データとのマッチングプロセスがスキップされるために発生していると考えられます。 この画像は Brad Greenlee氏によるDonut Tower II をCleanlabにより可視化したものです。 (C) 2007 Brad Greenlee, Donut Tower II, License: http://creativecommons.org/licenses/by/2.0/ まとめ 今回はCleanlabを用いたData-centric AIによる物体検出データセットのクレンジング方法について紹介しました。Cleanlabを利用することで効率的にデータセットを改善することが出来、モデルを変えなくてもモデルの精度改善が可能になります。実際の改善では今回紹介したData-centricおよびModel-centricの両方の観点で取り組むことが重要ではありますが、Model-centricアプローチによる精度改善に手詰まりを感じられている方がいれば試してみるのも良いと思います。 最後になりますが、セーフィーではエンジニアを積極的に募集しています。気になる方はこちらをご覧ください! https://safie.co.jp/teams/engineering/ カジュアル面談から受け付けておりますので、気軽に応募いただければと思います! 最後までお読みいただき、ありがとうございました。
こんにちは!23新卒のフロントエンドエンジニアの一氏です。 新卒エンジニアの研修の一環として、社内課題の解決をテーマに企画から実装まで行う自由度の高いチーム開発研修を実施しました。 チーム開発研修を行う過程で私たち5人の新卒エンジニアはどんなことを考えていたか、何を学んだかをまとめました。 研修概要 目的 課題 チーム開発研修 研修の流れ アイディア出し 開発初期にしたこと 開発 成果物詳細 こだわりポイント 学びが深かったこと どの機能をどこまで作り込むか フロントエンド開発 アジャイル開発 もっとこうすればよかったこと 報連相の重要性 APIの仕様のすり合わせ 最後に 研修概要 目的 今回のチーム開発研修を行うにあたって、初めに以下の3つの目的が共有されました。 セーフィーで取り扱っているカメラの内部構造についてエンジニアとして理解しておく システム開発で補助をもらいながら自走可能な状態になる 業務(チーム開発)で必要なスキルを磨く(工数・チケット管理等) 特にカメラの内部構造の理解はハードウェアを扱っている弊社ならではの目的であり、面白いポイントだと思います。 課題 提示された課題は「社内課題の解決 *1 」でした。 具体的な社内課題のテーマや開発手法、開発環境などは新卒が自由に決めることができました。 制約として、セキュリティ担保のためのインフラチームによる設計レビューやRaspberry Pi(カメラ)の利用などは課せられましたが、それ以外は自由です。 目的・課題が与えられた当初はこんなに自由度高く企画から開発まで研修で行うのかという驚きが半分、新卒が企画から開発まで全てを通して実施することができるのかという不安が半分でした。 また研修で企画から開発まで一通りの流れを経験することは、配属後の業務に役に立つだろうという期待もありました。 チーム開発研修 研修の流れ 期間は2023年6月12日〜7月31日までの1ヶ月半ほどで、以下の流れで行いました。 アイディア出し 初めに何の社内課題をどのように解決するかアイディア出しを行いました。 どんな課題があるのか調査し、マインドマップを作成し、アイディアを煮詰めていきました。 アイディア出しで難しかった点は3点あります。 1点目は、フロント・サーバー・デバイスの3つの領域をカバーすることで、新卒エンジニアメンバーの配属先と同じ領域をカバーしたいと考えたためです。 2点目は、目的の1つであるカメラの内部構造の理解のためにデバイス(Raspberry Pi)の利用が必要だったことです。 3点目は、実際に私たち自身が使いたくなるようなアイディアであることで、社内課題の解決のために開発するならこれから先本当に社内で使ってくれるものを開発した方がやる気が出ると考えたためです。 上記の難しかった点と社内課題の解決というある意味選択肢が無限にある課題を満たすため様々なアイディアが出てきました。 最終的にチーム開発のテーマは弊社の福利厚生としてある社内図書を、映像から顔認証と書籍のバーコード読み取りを通して貸し借りを行う、書籍管理システムの作成になりました。 開発初期にしたこと アイディアが決定した後はプロジェクトとして進めていくのに必要なことを実施・決定していきました。具体的には以下の内容です。 プロジェクト計画書作成 要件定義書など各種仕様書作成 顔認証などの技術検証 環境構築 開発手法決定 各種仕様書の作成は新卒エンジニア全員がほぼ経験なし or 未経験で、どのように作成すれば良いのか手探りで進めていきました。 チーム開発研修の前にUdemyでプロジェクト管理の講座でどのような仕様書が必要か学んでいましたが、講座を見るのと実際に作成するのとでは勝手が大きく違いました。 特に1ヶ月半という短い期間でどの仕様書をどこまで作り込むべきか悩みながら作成しました。 仕様書を作成した後で、この部分は今回の研修では考えなくてよかったということや、初めから完璧な仕様書を作ろうとして想定以上に時間がかかってしまったなどの反省点がありました。 途中からは不完全でもある程度仕様書を作成した時点で上司にレビューしていただき、方向性が間違っていないか確認するようにしました。 早めにレビューしていただくことで、余計な手戻りを少なくしつつ仕様書作成が進められることを学びました。 今回作成するプロダクトは研修の制約としてRaspberry Piの使用が必須であるため、フロント・サーバー・デバイスの3つの環境構築が必要でした。 フロントは今回はフレームワークに頼らずできるだけ地力をつけたいという思いからフレームワークなしのTypeScriptとWeb Componentsで実装しました。(ご想像の通り茨の道でした。) サーバーは弊社で採用されることが多いFastAPIとしました。また、書籍の貸出履歴の閲覧機能などの開発工数を抑えられるようにNotion APIを利用しました。 Notion APIを採用することでNotionのデータベースをAPI経由で操作できるようになります。 デバイスは初めC++で環境構築していたのですが、購入したRaspberry Pi カメラモジュール V3がC++で動かず… Pythonでは動作したため、デバイスで使用する言語をPythonに変更しました。 Pythonに変更する事に合わせて、サーバー自体もFastAPIを使用したローカルAPIサーバーに変更することにしました。 開発 開発はアジャイル開発で進めました。今回は1スプリントを2週間として、2スプリント回すことになりました。 具体的に学んだことは後述しますが、困ったことを気軽に相談できる環境や1スプリントで何をするのかを明確にすることなどを学べたことは、今後様々な開発を行う中で役に立つと思います。 開発中に難しい箇所があった際は、フロント・サーバー・デバイスの各領域のエンジニアに相談しながら進めることができました。 実装で困ったことがあった際は基本的にSlackで随時相談しつつ、時には実装のレビューをしていただきながら進めました。各領域のエンジニアに相談できる環境は非常に心強かったです。 最後の1週間はリリースの準備として、デプロイ環境の整備や利用者向けのドキュメント作成、発表資料作成、バグ修正を行いました。リリースに向けて行うことも多く、ギリギリまでドタバタしながら作業していました。もちろん実際のサービスのリリース作業と比較すると簡単なリリース作業とは思いますが、リリースのためにする作業を一通り経験できたのはよかったです。 成果物詳細 最終的な成果物である書籍管理システム librarian は主に以下の機能を提供するサービスです。 顔認証および書籍のバーコードスキャンを用いた書籍の貸出・返却 書籍一覧の閲覧はNotionで可能 初めにユーザーは貸出か返却かを選択します。 開始画面 顔認証は事前のユーザー登録時に顔画像を登録し、登録した顔画像とスキャンした顔画像の特徴が同じか FaceNet で識別しています。 顔画像はAWSのS3上に特徴量として保存しています。 当初はRaspberry Pi上で顔認証処理を行っていましたが、処理に5秒ほどかかっていました。 サーバー上で顔認証処理を行うように変更した結果、顔認証の処理時間は1.5秒ほどになりました。(今回は社内でのみ使うシステムだったためサーバー上で顔認証処理を行うという力技で解決しました。) 顔認証成功画面 書籍のバーコードスキャンは pyzbar を用いて映像からバーコードのISBN番号を取得しています。取得したISBN番号からNotionの書籍一覧データベースの書籍情報を取得して表示します。 バーコードスキャン画面 顔認証と書籍のバーコードスキャンをした後は貸出・返却ボタンをクリックして貸出・返却処理を行います。貸出・返却処理が完了するとNotionのデータベースが更新されます。 確認画面 貸出・返却処理完了画面 なお今回は時間の問題からユーザー登録・削除はAPIのみ作成しSwagger UIから直接APIを叩き、書籍登録・削除はNotionデータベースに直接入力としました。 こだわりポイント 作成するにあたって特にこだわって作成した点は以下の2点です。 顔認証・バーコードスキャン機能による直感的なUI/UXの提供 Notionデータベースによる書籍一覧と貸出履歴の閲覧 カメラで撮影した映像から顔認証とバーコードスキャンを行うことで、貸出・返却の際に利用者カードのような持ち物は必要なく、ログインや書籍の検索などの面倒な操作なしで利用できます。 開発する際は実装前に顔認証やバーコードスキャンをした時の動作イメージを考えるため、事前にFigmaでプロトタイプを作成し、UI/UXはどうかトレーナーにも確認していただいてから実装に入りました。結果としてほぼイメージ通りの動作を実現することができました。 また、Notionデータベースを利用したことは開発工数の削減に大きく貢献しました。 Notionのデータベース機能を利用することで、データベースに関する開発と書籍一覧等の表示に関する開発の工数を削減することができました。Notionのデータベースはフィルターや並べ替え、検索機能もついており、これらの機能も含めて実装する場合は研修期間内に開発を完了させることはできなかったと思います。 作成した書籍管理システム librarian は今後ユーザー登録や書籍登録、社内への周知などを行ない、現在は実際に社内で運用されております。 学びが深かったこと どの機能をどこまで作り込むか どの機能をどこまで作り込むかに関しては1ヶ月半という開発期間ではごく一部の機能しか作り込むことはできませんでした。 開発当初は追加機能についても多くのアイディアが出ていたのですが、実際にはほぼ必須の機能しか実装することはできませんでした。 一般的には開発の難易度や開発期間、メンバーの習熟度などをふまえて、どのくらいの機能を作ることができるかを決められると思います。 今回の開発ではこれらをふまえてどの機能まで作るか目安を考える力が不足しており、1ヶ月半の期間で開発できる機能を超えて開発計画を立ててしまいました。 開発計画が予定通り進行していないことは1スプリントでどのような成果物ができたかを確認するスプリントレビューで気づきました。今回は研修期間を伸ばすことはできなかったためMVPを作成することを最優先として、実装しなくても良い機能はどれか改めて議論することになりました。 最終的にユーザー登録・削除機能はAPIのみ開発してフロントエンドの開発をしないことにして、なんとかMVPを作成することができました。 実際の開発ではQCDの観点もふまえて開発する機能を決定していくと思います。今回の研修で改めてプロジェクト計画を予定通りに進行させる難しさを学びました。 フロントエンド開発 今回フロントエンドはフレームワークなしのTypeScriptとWeb Componentsで実装することにしました。 弊社ではサービスによってAngular, Vue, Reactのフレームワークを使い分けており、フロントエンド開発の地力の部分を学びたいという思いから、フレームワークなしという選択をしました。 しかしフレームワークなしでSPAを実装したことがあるメンバーはおらず、どのように実装すれば良いのか初めは検討もつけることができませんでした。 そんな中メンバーの一人がフレームワークなしで開発する方法を調べてベースとなる開発環境を構築してくれたのですが、この開発環境のおかげでスムーズに開発に入ることができました。 作成した開発環境には事前にTSとテンプレートの組み合わせ方、ライフサイクルメソッドの使い方、コンポーネント化の方法の例が書かれていました。この例やその他のWeb Componentsでの実装方法を参考にしつつ実装を進めたことで、フロントエンドの開発のよりコアな部分の知識を身につけることができました。 アジャイル開発 今回メンバー全員が初めてのアジャイル開発に取り組みました。 弊社ではアジャイル開発を採用しているチームが多いことや内定者インターンの際はウォーターフォール開発を採用したことからアジャイル開発を採用しました。 1スプリントを2週間として、デイリースタンドアップ、スプリントプランニング、スプリントレビュー、レトロスペクティブといったスクラムイベントを実施しました。 アジャイル開発は初めてということもあり、1スプリントでどのくらいのポイントを消化できるのか、開発の方法をどのように改善していくのか手探りで進めていきました。 みんながアジャイル初心者だったことでお互いに進め方でわからない部分を気軽に相談することもできました。 今回はスプリントを2周しか回すことができなかったのですが、1周目のスプリントに比べて2周目のスプリントはベロシティを1.2倍に増やすことができました。 ベロシティが増えた要因として、だんだんと開発に慣れてタスクの抜け漏れが減ったこと、わからない部分はお互いにすぐに相談することで素早く問題解決できるようになったことが挙げられると思います。 もっとこうすればよかったこと 報連相の重要性 開発を進める中で報連相の重要性を実感しました。 例えばプロジェクト計画書の作成で上司にレビューをしてもらう必要があったのですが、初めからある程度完成したものを提出しようとしていました。 しかし、レビューをしていただくと今回のチーム開発では必要な項目が足りなかったり不必要な項目があったりしました。 どの項目が必要かを事前に相談してから作成すれば大きな修正は少なくなったと思います。 また、開発中に実装方法で悩んだ際に一人で悩み続けてしまうことがありましたが、結局他のメンバーやトレーナーに相談するとすぐに解決するということが何回かありました。 これらの経験から自分だけで悩み続けるのではなく、早めに他のメンバーやトレーナー、上司と方向性の確認や相談をすることが大切なことを再認識しました。 今回のチーム開発研修では特に研修の初期はなかなか相談することができませんでしたが、開発が進むにつれてなんでも相談できるようになってきました。 しかし、どのくらい悩んだら相談するかのラインを引くのは個人的に苦手意識があります。 なんでも相談しすぎると周りの時間を奪うことになり、相談しなさすぎるといつまでも同じ箇所で悩んで自分の時間を浪費することになります。 15分悩んだら相談するというようなルールをチームで決めることが開発を行う中で重要と感じました。 APIの仕様のすり合わせ 今回の開発の1番の反省点としてAPIの仕様のすり合わせに時間がかかったことが挙げられます。 APIの仕様でメンバー間で齟齬があったことや考慮漏れがあったことで後でAPI仕様書を修正することがありました。原因として以下の点が考えられます。 API仕様書の作成とAPIの実装を同時に行った API仕様書のレビュー方法が曖昧だった 今回APIはFastAPIで作成しましたが、FastAPIはSwagger UIを用いたAPIドキュメントを作成することができます。 今回はSwagger UIをそのままAPI仕様書とすることにしたのですが、どこまで実装してからAPI仕様書としてレビューするのかを明確にしないまま開発を進めてしまいました。 その結果、API仕様書のレビューをせずにいきなり実装まで行い、後からAPIの修正が必要な場面が何度かありました。API仕様書としてレビューできる最低限の部分だけ実装してAPI仕様書のレビューを行うというルールを作ることで防げたと思います。 また、API仕様書はトレーナーからのレビューが必須だったのですが、メンバー間でのレビューを必須としていませんでした。 トレーナーからのレビューは主にAPI仕様書の形式が問題ないかをレビューしており、APIの細かいパラメータやURIが実装をする上で問題ないかのレビューを行なっていなかったため、後からパラメータの不備などに気づくことがありました。 パラメータやURIについてはトレーナーなだけでなく、実装を行うメンバー間でレビューを行うなど、誰がどのような観点でAPI仕様書のレビューを行うのかを明確にすることが必要だったと感じました。 最後に 今回のチーム開発研修は1ヶ月半という短い期間で企画からリリースまで開発の流れを一通り行うことができる貴重な経験となりました。今後は研修で学んだことを活かして、映像から未来をつくるシステム開発に取り組んでいきたいと思います。 *1 : 本研修における社内課題の解決とは業務を支援するツールの作成ではなく、社内の環境を快適にするための何かを作成することです。
はじめまして! エンジニアリングオフィスの井上です。 本記事では、開発本部(エンジニア向け)の社内アイデアソンの企画運営に携わりましたので取り組みの様子や内容についてお伝えさせていただこうと思います! 突然ですが「どこでもドア」って売れると思いますか? そもそも「アイデアソン」ってなに 開発本部でアイデアソンをする目的 アイデアソンのテーマ アイデアソンの日程 Day1で何をしたか 使用したビジネスフレームワーク Day1終了後からDay2までの一週間 Day2で何をしたか Day2結果発表 アイデアソンを実施した感想 参加者からの感想 社内アイデアソンに参加した感想を教えてください 最後に 突然ですが「どこでもドア」って売れると思いますか? いきなりですが某猫型ロボットでおなじみの「どこでもドア」、もし製品化したら売れると思いますか?みんな欲しいから売れるに決まってるよね、と自分は考えていました。 では、「どこでもドア」の価値はどのようなものでしょうか? 下準備無しで任意の場所へ瞬時に往来できる、というのが「どこでもドア」の価値ですよね。この価値に釣り合う価格は、一体どうやって決めるのでしょうか?難しいですよね。 価格設定やルールを作るためには、主要なターゲットを明確に定義する必要があります。 仮に「どこでもドアが」販売されたとしたとしたら国際問題へと発展する可能性があります。価値の項でも記載しましたが、任意の場所へ瞬時に往来できる道具があったら使い方次第で国を跨ぐ大犯罪をリスク無しで実行できてしまいます。 上記を踏まえて考えてみると、使用に際して法整備も必要になるので顧客は一般の方や法人かもしれませんが、ステークホルダーとして政府や各国との調整が必要です。 また、利用規約に違反した人を取り締まる仕組みや機関も必要でしょう。 全世界で利用された場合のメンテナンスやケアはどうなるでしょうか。 etcetc….. 軽く考えただけでも課題が山積みです。 どこでもドアのように、凄まじく便利な物があったとしても実際に「売る」となると簡単ではない、売るのは難しいな・・・と自分は思いました。 日頃のエンジニア業務だとアイデアを出した後に深堀し続けて、どう売るか?顧客は?販売チャネルは?という事を考える機会はあまりありません。 そのための機会として、開発本部のエンジニアを対象にアイデアソンを開催しました。 そもそも「アイデアソン」ってなに アイデアソンは「アイデア(idea)」と「マラソン(marathon)」をかけ合わせた造語で、みんなで集まり議論を行いながら様々なビジネスアイデアを出し合う、という取り組みです。 似たような言葉に「ハッカソン(hack + marathon)」があります。 ハッカソンは「開発」に重きを置くのに対して、アイデアソンは「アイデア」に重きを置いています。 そのため、ハッカソンに比べて専門知識が必要なく、誰でも参加しやすいというのがアイデアソンの大きな特徴になります。 開発本部でアイデアソンをする目的 セーフィーの開発本部は2024年4月現在、以下のように職能にて組織が分かれています。 ※セーフィーの組織についてより詳しく知りたい方は、是非引用元のVPoEの仕事について - Safie Engineers' Blog! をご参照ください! 一般的には、組織というのは職能別に分かれているため、職能外の技能・知見は吸収し難いという課題があります。その課題を解決するためにも、一緒にプロダクトの将来を考えるアイデアソンをチーム横断で実施することにより、より人となりを知ったり、社内ネットワーキングの向上を一つの目的としました。 なので、参加者は応募形式とし、できるだけ同じ部署にならないようばらけたチーム構成にしています。 また、アイデアがあってもそれをサービス化するのは難しいため、アイデアをサービス化するためにファクトとして何が必要か、という事を体系立てて学ぶ事ができる機会を提供することを目指しました。 アイデアソンのテーマ Safieの映像プラットフォームを活かし、新しいビジネスアイディアを創出しよう! というものです。 参加者の関心がある課題の解決のために、セーフィー株式会社がどのようにアプローチできるか、という事を主軸に置いて、今回のテーマを設定しました。 また、セーフィーが展開している「映像プラットフォーム」を利用することで、何が出来るのか・実現できるのかということをアイデアソンを通して再発見してもらう事も本テーマを設定した理由の1つです。 アイデアソンの日程 今回の社内アイデアソンは一週間の期間で開催しました。 初日(Day1)はキックオフ、最終日(Day2)に発表日を設定し、それ以外の期間中はチームごとに集まって相談・ブレストして資料作成を進める方針にしました。 Day1で何をしたか まず、Day1にて各チームでアイデア出しをしてもらい、これぞ!というアイデアをチームの発表内容に選定してもらいます。 じゃあ各々始めてください!といって放り出す事はせず、アイデアを出す際の考え方や、アイデアをブラッシュアップするための手法・ツールについての座学を準備しました。 本アイデアソンでは、LeanCanvasというビジネスフレームワークも用いてアイデアの深堀や構造化を行いました。 各チームでアイデアの相談をしている風景 使用したビジネスフレームワーク Lean Canvasとは、新しいビジネスモデルの開発を生産効率性の向上と問題の顕在化によるムダの徹底的排除というアプローチで目指すマネジメント論を指します。 LeanCanvasツール このツール上の各項目を埋めていく事でアイデアを短時間でシンプルに可視化・検証できるため、効率的なブラッシュアップが可能で認識共有が容易といった利点があります。 上記のフレームワークを利用しつつ、実際にチーム内で現実問題として困っていること、改善されたら便利になることを挙げてもらい、セーフィー株式会社の得意分野である「映像」を用いて解決に持っていくことを模索しました。 実際にアイデアを出し合い、各チームで相談している際の風景 各チームメンバーから秀逸なアイデアが生まれ、選定に苦労していました Day1終了後からDay2までの一週間 Day1で各チームのアイデアが確定したので、Day2までの期間でプレゼン資料の作成に取り組みました。 この期間中Day1で実施した講義内容も踏まえ、各チームメンバーが役割分担を行い、競合調査・市場調査・実現性を高めるための課題の洗い出し等を実施しました。 Day2で何をしたか Day2では、各チームで作り上げたアイデアの発表を行いました。 発表の場では、実際にセーフィーの役員・部長陣がビジネスからの観点やプロダクトとしての実現性等を加味して審査頂きました。 部室長の前で練りに練ったアイデアを発表している様子です Day2結果発表 栄えある第一回社内アイデアソン優勝チームは、チームAでした! アイデアの内容については細かな内容を公表することはできませんが、優勝の決め手として、「未来の意思決定に大きく作用することができる」という所に審査員が未来と発展性を感じたからでした。 アイデアソン終了後の集合写真。無事成功して安堵しました アイデアソンを実施した感想 今回の社内アイデアソンを振り返ってみると、開発本部でアイデアソンを開催する目的である アイデアをサービス化するために体系立てた学びの機会を提供する チーム横断的な社内ネットワーキングの向上 この2つについて、間違いなく達成できたと感じています。 ・アイデアをサービス化するために体系立てた学びの機会を提供する アイデアソンを行うのは初めてだったので、業務時間内でアイデアやアウトプットができるか、といった不安がありました。 実施した結果、参加者全員が楽しみながらアイデア出しや資料作成をされており、全体を通して良いアイデアソンになったと思います。 チームで選定したアイデアは勿論、個々人のアイデアについても、これがあったら世の中もっと便利に・安全になる!実現したい!という熱量が感じられる物でした。 個々人の中で貯めこんでいたアイデア・思い・仕組みを具現化できるのはアイデアソンならではだと思います。 運営側ではありますが、自分も「こういう考え方があるのか」「人の目を釘付けにするプレゼン資料はこう作るのか」といった学びを得る事ができました。 ・ チーム横断的な社内ネットワーキングの向上 他部署の方々と気軽に相談・会話ができる方が増え、社内交流に寄与できたと実感できました。 既に関係値があるメンバーの新しい一面を見ることができるので、社内イベントとして実施するのはかなり有効だと感じました。 懇親会では「あのアイデア良かったね!」という言葉もあれば、「あのアイデアに既存のプロダクトと合体させたらもっと魅力的になるんじゃないか」といったフィードバックもあり、盛り上がりました。 エンジニア同士が交流を深め切磋琢磨できる環境を提供でき、実りのある経験を得られたと執筆しながら感じています。 参加者からの感想 参加者の皆さんに記入いただいたアンケートの感想を公開します。 社内アイデアソンに参加した感想を教えてください アイデア出しから、ターゲット選定、実現性判断、プレゼン資料作成まで一連の流れを経験できて学びになった。 他のグループの発表を聞いて、この部分もう少し深堀できたななど、振り返れるところが大きかった。 普段とは違った頭の使い方ができて楽しかったです。マネーの虎みたいでした。 自分がこういう製品が欲しいなと思ったものを他の人の意見をもらいながら、あーでもないこーでもないとブラッシュアップして、最終的にプレゼン出来るところまで持っていけたのがとても良かった。 普段使わない頭の使い方をすることができた。リーンキャンパスなどのフレームワークは実際に使ってみないと理解できないので良いきっかけだったと思う。 全体的に面白かったです。参加した人は、「こういうサービス作りたい、必要だ!」と本気で思って参加されているように思ったので、出た案が企画に回されて、具体的なサービスとして実現される可能性がもっと感じられれば良かったと思います。 アイデアソンを通して、他の人のアイデア・資料の作り方の考え方を実際に見て体感できたのはプラスだった。 普段関係値が無い人と一緒に作業を行う事で、コミュニケーションを取れる機会になったのがよかった。 最後に 本記事では、社内アイデアソンについてご紹介させていただきました。 セーフィーではエンジニアを積極的に募集しています。どのような職種があるのか気になる方はこちらをご覧ください! https://safie.co.jp/teams/engineering/ カジュアル面談から受け付けておりますので、気軽に応募いただければと思います! 皆様のご応募、心よりお待ちしております! 最後までお読みいただき、ありがとうございました。
はじめに セーフィー株式会社の第3開発部 AIVisionグループで画像認識AIの開発エンジニアをしている土井 慎也です。 今回は、Segment Anything Model(SAM)というセグメンテーションモデルで、テキストプロンプトを使用してみたいと思います。 はじめに Segment Anything Modelとは Grounded-SAMとは 実践 環境構築 モデルダウンロード 画像とテキストプロンプト、閾値を指定 実行 出力結果 まとめ 最後に Segment Anything Modelとは Meta社(旧Facebook)が2023年4月に発表 公式ページ: https://segment-anything.com/ 公式コード: https://github.com/facebookresearch/segment-anything ゼロショットセグメンテーションモデル 未知の物体に対して、追加学習を必要としない 1100万枚の画像と10億以上のマスクを使用して学習 プロンプトにセグメンテーションを行いたい物体への点や矩形を指定 論文ではテキストをプロンプトとして使用している例があるが、コードは公開されていない 論文ではテキストをプロンプトとして使用している例があるが、コードは公開されていない! 今回、テキストプロンプトを可能にするために、 Grounded-Segment-Anything(Grounded-SAM) を代わりに使用する Grounded-SAMとは Grounded-DINOというテキストプロンプトが利用可能なゼロショット物体検出モデルとSAMを組み合わせたもの Grounded-DINOとSAMの二つのモデルを使うため大きく二つの処理に分かれます。 Grounded-DINO 画像と検出したい物体のテキストプロンプトを入力 検出結果として矩形を出力 SAM 画像と検出結果の矩形をプロンプトとして入力 セグメンテーション結果を出力 実践 環境構築 リポジトリをクローンする git clone git@github.com:IDEA-Research/Grounded-Segment-Anything.git リポジトリをクローン後、 Grounded-SAM: Installation を参考に環境にあったインストールを実行 モデルダウンロード cd Grounded-Segment-Anything wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth wget https://github.com/IDEA-Research/GroundingDINO/releases/download/v0.1.0-alpha/groundingdino_swint_ogc.pth 画像とテキストプロンプト、閾値を指定 grounded_sam_simple_demo.py#L31-L35 ... SOURCE_IMAGE_PATH = "./assets/demo2.jpg" CLASSES = [ "The running dog" ] BOX_THRESHOLD = 0.25 TEXT_THRESHOLD = 0.25 NMS_THRESHOLD = 0.8 ... テキストプロンプトはドット(.)区切りで入力することで複数種類の検出が可能 実行 cd Grounded-Segment-Anything python grounded_sam_simple_demo.py 出力結果 The running dog Grounded-DINOの出力 © 2020 IDEA, Inc .  Apache-2.0 license .  Grounded-DINO+SAMの出力 © 2020 IDEA, Inc .  Apache-2.0 license .  Horse. Clouds. Grasses. Sky. Hill Grounded-DINOの出力 © 2020 IDEA, Inc .  Apache-2.0 license .  Grounded-DINO+SAMの出力 © 2020 IDEA, Inc .  Apache-2.0 license .  まとめ 今回、Grounded-SAMを利用することで自由なテキストプロンプトでSAMを利用できることを紹介しました。 この技術を使用することで、新たなデータセットを作成する際には、ある程度アノテーションを自動的に作成できるようになり、モデル改善などのスピードを上げることができると思います。実際に、アノテーションツールなどに組み込まれていたりします。 また、SAMの精度も十分な精度が出ているように思えますが、下記のようにさらに高精度なセグメンテーションができる SAM-HQ という物も発表されています。これもGrounded-SAMで使用することができるので、気になる方はぜひ使ってみてください。 SAMとSAM-HQの比較 © 2020 IDEA, Inc .  Apache-2.0 license .  © 2020 IDEA, Inc .  Apache-2.0 license .  最後に セーフィーではエンジニアを積極的に募集しています。気になる方はこちらをご覧ください! https://safie.co.jp/teams/engineering/ カジュアル面談から受け付けておりますので、気軽に応募いただければと思います! 最後までお読みいただき、ありがとうございました。
こんにちは。サーバサイドエンジニアの村田 ( @naofumimurata ) です。 本記事では、セーフィーのシステムでカメラ映像の録画機能を担うアプリケーションのデプロイを改善した話を共有したいと思います。 セーフィーの録画・配信システム カメラサーバのデプロイの課題 デプロイの流れ 実行環境 問題 時間がかかる 作業負荷が高い メンテナンス性が悪い 結果どういう状態になったか 改善に向けた取り組み GitHub Actions + AWS CodeDeployの構成に 監視の強化 成果 デプロイ時間の短縮 作業負荷の軽減 デプロイ頻度の向上 まとめ セーフィーの録画・配信システム セーフィーはクラウド防犯カメラ・録画サービスを提供しています。 バックエンドのシステムとしては、まずカメラから映像を常時受け取りストレージに保存するアプリケーション(本記事では以降「カメラサーバ」と呼びます)が稼働しています。カメラサーバでは映像の保存の他にもカメラの情報管理や制御などを行なっています。ユーザはSafie Viewerというフロントアプリケーション(Web/モバイル)を利用し、カメラサーバに入ってきた映像を別の動画配信アプリケーションを介して閲覧することができます。 カメラサーバはJava、動画配信アプリケーションはGolangで実装されています。 インフラ環境は全てAWSを利用しており、カメラサーバはEC2インスタンス上で稼働しています。現在は 約20万台以上 のカメラが接続しており、カメラサーバだけで 約2,000台以上 のEC2インスタンスが稼働しています。 インフラ構成の特徴的な点として、負荷分散のために水平分割の構成を取っています。一定カメラ台数(約13000台程度)ごとにそれを処理するためのインフラ構成(ネットワーク、DB、redis、サーバ類)一式を構築しています。社内ではこの水平分割の単位を「ゾーン」と呼んでおり、本記事でもこの用語を使います。 カメラサーバのデプロイの課題 セーフィーのシステムにおいてカメラサーバは特に重要な役割を担っていますが、そのデプロイには大きな課題がありました。ここでは具体的なデプロイの流れとどういった課題があったかについて紹介します。 デプロイの流れ カメラサーバのデプロイではざっくり以下のことを行う必要があります。 アプリケーションのビルド AMIのビルド ゴールデンイメージを用意しスケーリングで利用 jarファイルの配布 redis, DB上の接続、管理情報を更新 カメラサーバに対して停止リクエストを送る カメラサーバが担当しているデバイスを他のカメラサーバに移動させる プロセス再起動 改善前の構成 単純にビルド、成果物の配布、プロセス再起動だけではないステップ(2、3、5番)があるのが特徴的かと思います。 まず、アプリケーションのビルドを行います。カメラサーバはJavaで実装されているのでjarファイルを生成します。次にAMIのビルドを行います。カメラサーバはゴールデンイメージ運用を行なっており、jarファイルを含むAMIを用意しスケーリングで利用しています。スケーリングはEC2 Auto Scalingではなく自前でスケーリングを行うアプリケーションが別に存在するという特殊な構成になっているのですが、ここでは詳しい紹介は割愛します。生成したjarファイルをインスタンスに配布した後、プロセス再起動の前に幾つかやることがあります。 まず、redisとDBのデータ更新を行います。カメラサーバへのカメラ割り当て状況や、カメラの接続状態・割り当て履歴といった情報をredis、DBで管理しており他のバックエンドアプリケーションからも利用されています。デプロイ時にはそのカメラサーバをサービス全体から外す必要があるため、正しい情報に更新を行います。 次にカメラサーバに対してRPCでシャットダウンの要求を送ります。プロセス再起動前にこれを行う目的はカメラの接続品質向上のためです。カメラサーバはシャットダウンの要求を受け取ると担当しているカメラに対して他のカメラサーバへの再接続を指示します。全てのカメラが別のカメラサーバに移動するのを待った後にプロセスを終了させています。 無事にシャットダウン要求が処理された後にプロセスを再起動し新しいバージョンのアプリケーションが起動します。 実行環境 上記の作業は全てスクリプト化されており、シェルスクリプトとAnsibleとPythonスクリプトを組み合わせて実現されていました。スクリプトの実行はデプロイ作業用のEC2インスタンス上で手動で実行する必要がありました。 問題 さて、ここまで紹介してきたカメラサーバのデプロイ構成ですが、サービスの成長に伴い様々な問題が現れてきました。 時間がかかる まず一番大きな問題は、デプロイにもの凄く時間がかかるということでした。実際にどのくらい時間がかかっていたかというと、全てのカメラサーバのデプロイを完了するのに最短で 30時間強 かかっていました。 これには幾つかの要因があるのですが、まず根本的なところとして2000台を超えるインスタンスがあり対象が多いということがありますが、それに加えてスクリプトの作りの問題で並列度が著しく低いということがありました。 前述の通り、セーフィーの録画配信周りのシステムは「ゾーン」と呼ばれる単位で構築されており、デプロイもゾーン単位で行う必要があるのですが、1ゾーンずつ逐次でしか実行できないようになっていました。1ゾーン内では複数インスタンス並列で実行されるのですが、サービスの成長と共にゾーンの数が多くなったため全体として見るとかなり並列度が低いという状態となっていました。 1ゾーンで約2、3時間程度かかっており、ゾーン数が15を超えたあたりからトータルで30時間を超えるようになっていました。 作業負荷が高い 次にデプロイ作業を行うエンジニアの負荷がとても高いという問題がありました。まず、デプロイが自動化されておらず手動でデプロイスクリプトを実行する必要がありました。くわえて、スクリプトも実行したら放置で済むというわけではなく頻繁に途中で失敗するようになっており、それへの対応のため作業者がつきっきりで画面を見ている必要がありました。 デプロイスクリプトが途中で失敗する原因としては、前述の通りデプロイ時に接続カメラを別のカメラサーバに移動させる処理があり、これが結構時間がかかる(なかなか移動してくれないカメラがいたりする)ためでした。実際のところここの待ち時間はカメラ依存であるため仕方がないのですが、デプロイスクリプトの待ち時間の判定がシビアで頻繁にタイムアウトし失敗判定になるという状態になっていました。 途中で失敗してしまった場合は、エンジニアが実際にカメラサーバのインスタンスに入ってログを確認し問題ないかをチェックし、問題なければ再開するということを行う必要があり、頻繁に失敗することが作業負荷を著しくあげていました。 メンテナンス性が悪い 最後はデプロイの実行環境およびツールに関することで、メンテナンス性が悪いという問題がありました。 前述の通り、デプロイスクリプトはシェルスクリプトとAnsibleとPythonスクリプトが組み合わせてできているのですが、かなり複雑怪奇になっており何が実行されているのかを把握するのが難しい状態になっていました。特にPythonスクリプトの部分に関しては別のアプリケーション実装の機能を部分的に呼び出すような特殊な構成になっており、挙動を変えるには別のアプリケーションの修正を行う必要があるという状態になっていました。 結果どういう状態になったか このような問題によって、以下のような現象が発生するようになりました リリーススケジュールが後ろ倒しになる カメラサーバの変更を回避するようになる まずデプロイにもの凄く時間がかかるので、カメラサーバに変更を加えた場合それによってプロダクトのリリーススケジュールが大きく後ろ倒しになることがありました。最短で約30時間強かかると書きましたが、これは休まず寝ずに作業した場合の話で、実際にはエンジニアが日中にスケジュールが空いている時間で作業することになるので、デプロイしたいと思ってから実際に完了するまでもっと時間がかかります。金曜リリースを避けつつ、セーフィーでは本番環境へのデプロイは複数人体制で行うようにしているため、スケジュールを調整したりすると実際には2、3週間から長い場合1ヶ月程度かかっていました。 そしてもの凄く作業負荷が高いので、みんなカメラサーバのデプロイをやりたくなくなりました。その結果カメラサーバへの変更を避けるようになり、本来ならカメラサーバにあるべき機能などを別のところで実装しだし、カメラサーバへの変更を回避するようになりました。 改善に向けた取り組み こういった問題を解決するために、カメラサーバのデプロイ改善に取り組むことにしました。有志のメンバーでワーキンググループを組成し現状の調査から解決まで取り組みました。 GitHub Actions + AWS CodeDeployの構成に デプロイの構成を見直し、GitHub ActionsとAWS CodeDeployを利用した構成へとまるっと作り替えを行いました。ツールの選定理由としては、GitHub ActionsとAWS CodeDeployどちらも既に利用実績があったことが大きなポイントでした。 これまで作業用インスタンスで行っていたアプリケーションのビルドをGitHub Actions上で自動で行い、成果物の配布とアプリの再起動処理はCodeDeployのスクリプトとしてカメラサーバインスタンス上で実行するような構成にしました。 また、併せてカメラサーバのEC2インスタンスをEC2 Auto Scaling Group (ASG)の管理下に入れるように変更しました。これによりASGとCode Deployの連携機能でASGで起動したインスタンスにはCodeDeployの最新のリビジョンが自動デプロイされるようになったため、ゴールデンイメージが不要となりAMIをビルドするステップを消すことができました。 アプリケーションの終了・再起動に関連する処理はこれまでPythonスクリプトで行っていましたが、別アプリケーションの機能に密結合な実装となっており、これをカメラサーバインスタンス上に配置して実行できるようにするのは後々のメンテナンス性を考えて避けたかったため、機能を切り出したものをGolangで新たに実装しました。Golangの場合、バイナリを配置するだけで動かせるので、アプリケーションの成果物と一緒に配布し実行させる構成にしました。 また、問題だったデプロイスクリプトの失敗判定などもGolang実装に移行する段階で修正し、無駄に失敗判定にならないようにしました。 改善後の構成 監視の強化 これまではデプロイ時の正常性の確認は、作業者がEC2インスタンスに入ってログなどを目視で確認するというかなり温かみのあるやり方で行っていたため、作業負荷が非常に高いという問題がありました。 デプロイ構成を一新するにあたりここについても改善するべく、CodeDeployのデプロイスクリプトでの正常性確認ステップに加えて、メトリクス監視の強化を行いました。 セーフィーではPrometheusによるメトリクス監視の仕組みが既にありインスタンスレベルでの監視は行われていましたが、今回はアプリケーションにPrometheus Exporterを新たに実装、正常性を確認するためのメトリクスを追加し、問題があればアラートが発報し作業者が都度ログを確認する作業などが不要となるようにしました。 成果 試行錯誤の末、約1年ほど前に新たなデプロイ構成に移行が完了しました。 その結果、以下のような成果が得られました。 デプロイ時間の短縮 一番の大きな問題だったデプロイにかかる時間ですが、これは大幅に短縮できました。 これまでは最短約30時間強かかっていましたが、改善後は最短で 3時間強 程度でできるようになりました。新しい構成ではゾーン毎に並列で実行できるようになったため、ゾーン数によらず同時に全ゾーンに対してデプロイを行えるようになったところが大きなポイントでした。 作業負荷の軽減 作業負荷の問題についても大きく改善できました。これまでは、作業者が手動でデプロイを行い画面につきっきりで作業を行う必要がありましたが、新しい構成ではGitHubのイベントをトリガーに自動でデプロイが実行されるようになり、デプロイ中も基本的に放置しておけば良くなりました。 デプロイ頻度の向上 デプロイの頻度も大きく改善しました。セーフィーでは検証環境と本番環境の2つの環境があるのですが、検証環境では 9倍 程度、本番環境では 3倍 程度向上しました。 まとめ 本記事では、セーフィーのシステムで特に重要な役割を担うカメラサーバのデプロイの問題を改善した事例を共有させていただきました。大きな問題で改善に向けて様々な苦労がありましたが、無事にやり切ることができ、実際に改善効果も得られてよかったかなと思います。デプロイ構成の移行にあたっては互換性の維持を重視し、なるべく実行する処理は変えずに改善することを意識していたため、そもそもの処理の内容自体にまだまだ改善の余地が残っていると思っており、その辺りの根本的な改善も継続してやっていきたいと考えています。 さいごに、セーフィーではエンジニアを募集しております。ご興味が出た際はぜひご応募いただけたらと思います! https://safie.co.jp/teams/engineering/ 最後までお読みいただき、ありがとうございました。
はじめに セーフィー株式会社のVPoEを務めている谷口元信です。 2022年4月からVPoEとしてエンジニアの組織形成と運営に取り組んでいます。 今回はVPoEに何が求められるかについて私なりの解釈を伝えた上で、取り組んだ事と今後についてをお話ししたいと思います。 はじめに なぜVPoEが必要なのか? なぜVPoEになったのか? 取り組んだ事 取り組めた事 2022年度 育成 オンボーディングの体制強化 組織 プロダクト単位での開発体制の改善 採用 採用のプロセス改善 2023年度 育成 オンボーディングの体制強化 評価 スキルマップの可視化 組織 プロダクト単位での開発体制の改善 採用 採用のプロセス改善 エンジニア組織の認知度向上 今後について まとめ なぜVPoEが必要なのか? VPoEとはVice President of Engineeringの略ですが、直訳すると技術の副責任者となります。 CTOが最高技術責任者なので、そのサポートをする立ち位置としてVPoEが存在すると私は考えています。 海外のIT企業では2000年代初頭からVPoE職が生まれたと聞いています。 インターネットの普及が1990年代から広がっていくに伴って、インターネットを通じたサービスを提供する企業も増えていきました。 そういった企業がビジネスを拡大していくにあたって、企業内のエンジニア社員数も増えていき組織の管理が既存のやり方だけでは難しくなっていきました。 そのような状況の中でエンジニア組織に直接責任を持つ立場としてCTOが全ての責任を背負っていましたが、CTOに求められる責務が大きくなってきたので責任範囲を分けていく事が企業で求められていった結果、VPoE職が必要となった訳です。 CTO :技術を使った意思決定においての経営責任 VPoE :技術を使った意思決定を遂行する組織への責任 と分ける事で、CTOは技術に対する経営責任に集中して業務を進める事が出来るようになり、技術選定や選定した技術を使ったプロダクトのビジネス展開における企業としての意思決定速度を上げる所に貢献しやすくなります。 VPoEはエンジニア組織への責任を担う事で組織内のパフォーマンス向上や組織間の連携向上に集中する事が出来、経営で意思決定された方針を元に組織の機動力向上に貢献しやすくなります。 私はどちらも大事な役割だと思いますし、CTOとVPoEが相互に理解しながら仕事を進めていく事が組織を運営する中で意識しなければならないポイントだと思っています。 なぜVPoEになったのか? 広義でのVPoE職の役割については書かせてもらいましたが、続いてセーフィーで私がVPoE職についた経緯をお話しします。 私が入社したのは2019年4月ですが、2021年11月までは概ね下記のような職能別組織となっていました。 エンジニア数は50名程で、各技術領域をグループでまとめながら開発をしていく体制となっていました。 そして、2021年12月から事業部制組織に移行しました。 ここで大きな課題が生まれました。 3つの事業部とカスタマーサービス本部に職能単位のエンジニアを配置しましたが、各事業部でビジネス展開したいプロダクトがある中で各事業に絞り込んで開発を進めるだけのマンパワーが足りなかったのです。 一般的なSaaS企業だとフロントエンド、バックエンドのフルスタックエンジニアが入れば事業部で求められるプロダクトに対して一気通貫でコミットする事が可能です。 ただ、セーフィーの場合はフロントエンド、バックエンドに加えてネットワークカメラを主としたデバイス領域も一気通貫で見れるフルスタックエンジニアが圧倒的に不足していました。 その結果、毎月のように事業部間でエンジニアのアサイン調整をして開発を進める事となったわけです。 本来であれば事業部制に移行する事でそれぞれの事業領域を深堀しながらニーズに合ったプロダクトを提供してビジネス拡大のスピードを上げられる想定でしたが、逆にスピードが下がる結果となりました。 組織拡大をしていく中での痛みだと捉えていますが、その痛みを解決するために自ら手上げをして2022年4月からエンジニア組織の改善に対して取り組みを始めました。 改善するプロセスとしては、今までの組織で課題になっていた事を現状整理しつつステップに分けて組織変更をする案を作って実施しました。 組織変更した結果のあるべき姿として2022年4月に考えた組織は以下となります。 プロダクトに対してはバーチャルな組織で横断して向き合いながら、各技術領域における生産性を向上するための組織として考えました。 これは短期的な計画で1年以内にはこの形にするために2022年内に3回の組織変更をしてハレーションが起きないように進めました。 合わせて事業部と連携して開発を進めるにあたっての会議体を再設計し、情報共有が滞る事なく進められるようにもしました。 関連する部署とも合意を取りながら経営陣に組織変更に対して納得してもらって進める事は結構な労力と時間が必要でしたが、無事に進める事が出来ました。 2022年11月には以下の組織に変更しました。 領域が近い技術領域を部としてまとめながら、グループで開発のアサインを調整しやすい形に変えました。 過去にやりやすかった組織でのやり方に近い形に一先ずは変えつつ、横串で組織改善をするためにエンジニアリングオフィスも新設しました。 なので、2022年11月がエンジニア組織の再出発と私の中では定義しています。 取り組んだ事 再出発した中でVPoEとして取り組むべき事を育成、評価、組織、採用のカテゴリに分けて言語化しました。 やるべき事はたくさんありますが、エンジニアリングオフィスのメンバーを少しずつ増やしながら優先順位をつけて取り組む事としました。 取り組めた事 年度ごとに取り組めた事を洗い出しながら書いていきます。 2022年度 主には組織変更に伴う調整に多くの時間を割きました。また、採用プロセスの改善で応募から最終面接に至るまでのプロセス全体を短くするためにエンジニアの最終面接を私に委譲してもらいつつ採用グループとの連携に力を入れました。 育成 オンボーディングの体制強化 各グループで代表者を選出してもらい、それぞれのグループで現在使っているオンボーディング資料を洗い出しながら、必要に応じて足りてない要素を強化するための資料を追加していけるようにしました。 2023年4月から新卒1期生を受け入れるにあたって、内定承諾者のインターン受け入れを行ってエンジニアとしての開発体験をしてもらう設計・運用をしました。 組織 プロダクト単位での開発体制の改善 事業部制から開発本部にエンジニア組織を移行するために関係部署、経営陣と合意を取りながら進めて組織変更を実施しました。移行するにあたって生産性を向上していくための会議体設計をしながら業務への影響を減らしながら進めました。 採用 採用のプロセス改善 最終面接をCEOから委譲してもらい、選考におけるプロセスを効率化しました。 採用担当とデイリーミーティングを設けながら、候補者毎でのスケジュール調整を最適化するようにしました。 2023年度 育成 オンボーディングの体制強化 2022年度に引き続き、内定承諾者のインターン受け入れを行いました。2023年度はエンジニアリングオフィスのメンバーに教育を委譲し、再現性のある体制作りに努めました。 udemyを活用して技術力向上に励む事が出来る仕組みを作り始め、希望者が自学出来るようにしました。 評価 スキルマップの可視化 テックリードやエンジニアリングマネージャの職位を定義しながら、CTOや部長と共に職位に対するランクの定義を作り開発本部内で誰でも確認出来るようにしました。 組織 プロダクト単位での開発体制の改善 エンジニアリングオフィスのメンバーの採用を行い、エンジニアリングオフィスで取り組める事を増やせるようにしました。 現在の開発体制の現状整理と改善に向けての中長期の開発本部の体制を再設計しました。 採用 採用のプロセス改善 継続して採用担当とデイリーミーティングを設けながら、エンジニアリングオフィスのグループリーダーにも入ってもらい更なるプロセスの効率化を行いました。 エンジニア組織の認知度向上 セーフィーで初となるアイディアソンの企画・運営を行い、学生の方々にセーフィーを知ってもらいつつアイディアを形にするプロセスを学ぶきっかけ作りを提供しました。 技育祭に登壇し、ネットワークカメラの将来について技術周りも含めてプレゼンテーションを行いました。結果として初めて知った企業として登壇企業の中で1位となりました。 振り返ってみると、VPoEになってから総じて採用に時間をかけている事を実感しています。 本当であればもっと育成にも時間を使いたいところではありますが、開発業務における課題にも一定の時間を使っているので年単位で取り組む業務にフォーカスして取り組んでいかなければならないという自省もあります。 今後について 2023年度までに採用に多くの時間を割いてきましたが、2024年度は特に育成にフォーカスした改善を行っていきたいと思っています。 今いるエンジニアの成長が積み重ねる事で組織の成長にもつながると私は思っています。 私自身は20数年ほど開発現場で色々な開発をしてきましたが、私が成長していくにあたって必要だった事は出来る限り言語化しながら成長に対して必要な事を各自が考えて取捨選択して成長していける組織になる事を望んでいます。 まとめ 各企業には事情の異なる課題があるのでVPoEには臨機応変な課題解決能力が必要だと思っています。 エンジニアとしてプログラムに向き合って課題解決していく経験をされていた方は組織に向き合って課題解決していく所に置き換えて考えるとVPoEという職種が理解しやすいかもしれません。 プログラム以上に不確定要素は多くありますが、VPoEという職種は今後ますます必要となりますので、興味がある方がいましたら私の体験も踏まえてお話をしますので、ぜひご連絡下さい。 最後まで読んでいただき、ありがとうございました。
こんにちは。Safieの画像認識チームでサブグループリーダーをしている柏木です。 今回は私が主担当としてAIアプリ開発に携わった、 エッジAI搭載屋外向けクラウド録画カメラ について紹介します。 プレスリリースは こちら にあります。興味のある方はご覧ください。 エッジAI搭載屋外向けクラウドカメラについて 本製品は人物検出を行うAIアプリが搭載されたAIネットワークカメラで、i-PRO(アイプロ)株式会社(以下i-PRO)と共同開発したものです。i-PRO株式会社はパナソニックの技術を引き継いで2019年に設立した会社で、ネットワークカメラやセキュリティシステムの開発と販売を行っています。 今回i-PROのカメラにセーフィー独自のファームウェアおよびアプリを搭載することで、特別仕様のカメラの開発を行いました。 エッジAI搭載屋外向けクラウドカメラについて アプリ開発上の違い 実装上の工夫 独自データセットによる学習 終わりに 開発中のカメラ こちらのカメラにはAI-App 人数カウント(旧名: Store People Detection Pack)が搭載されています。AI-App 人数カウントはSafie Oneで搭載されていたアプリであり、カメラに映る人物の検知や、UI上で設定したラインを通過する人数のカウントを行うことができます。技術的な詳細については 過去のブログ にまとまっています。本アプリに興味がある方はご覧ください。 アプリ開発上の違い i-PROのカメラにおけるAI-App 人数カウントの開発は、Safie Oneで元々搭載していたアプリを移植した形になります。カメラが変わったことによるアプリの一番大きな違いは、Object Detection(物体検出)です。以下がアプリのシステム全体の構成です。 SafieOneとi-PRO、双方ともコードの大部分はC++で書かれており、これらに違いはあまりありません。特にTrackingとEvent Publishmentはほとんどそのままのコードで動作しました。 しかし、2つの機種ではカメラに搭載しているAIチップが異なります。SafieOneではQualcomm製のチップを搭載しており、 SNPE (Snapdragon Neural Processing Engine) SDK というSDKを利用することでチップでの推論が可能となっていました。一方、i-PROではambarella社のチップを搭載しており、i-PROの提供している独自SDKを利用して開発をする必要がありました。 SNPEとi-PROのSDKで、機能面での違いはそこまで大きくないです。しかし細かい仕様の違いは結構ありました。具体的は以下の面で違いがありました。 モデル変換ツール SNPEは独自形式への変換と量子化をステップごとに実行可能 i-PROは独自形式への変換と量子化がセット モデル読み込み SNPEはモデルをポインタで読み込む i-PROはパスを指定し、ファイルを読み込む 前処理の組み込み SNPEは前処理があまり用意されていない i-PROは一部前処理を組み込み可能 実装上の工夫 今回のアプリ開発はAI-App 人数カウントの移植だったため、実装上は大部分がSafie Oneのアプリと同じものでした。そこで、アプリのコードはi-PRO用に新規作成することはせず、SafieOneと同じコードで必要な箇所だけ分岐することにしました。 こうすることで保守コストを抑えることができます。例えばTrackingアルゴリズムの改修をする際はSafie Oneとi-PROの双方で適用することができます。 AI-App 人数カウントは、Make時にカメラ機種に応じてビルドタイプを変更してビルドしております。そこで機種で共通のIDetectorという検出用のInterfaceを作成し、機種専用のYoloV5がそれを継承する実装としました(今回の検出器ではYoloV5を用いています)。機種ごとに読み込むYoloV5のコードが格納されているディレクトリを分けることで実現していまます。 こうすることで、機種ごとの違いを意識せずにDetectorを扱うことができます。 classDiagram IDetector <|.. YoloV5_SafieOne IDetector <|.. YoloV5_iPro class IDetector{ + void Detect() } class YoloV5_SafieOne{ - void preprocess + void Detect() } class YoloV5_iPro{ - void preprocess + void Detect() } 独自データセットによる学習 学習モデルは屋外向けに再学習しました。屋内をターゲットとしているSafieOneと、屋外を対象としているi-PROでは、同じアプリでも検出するシーンが異なります。SafieOneでは屋内に特化したオリジナルデータセットを作成して学習していました。以下の画像のようなものをイメージしていただければと思います。 屋内データセットのイメージ 一方で屋外では以下の画像のようなイメージです。そこで、屋外においても独自に新しく画像を収集し、データセットを作成しました。屋外では背景が屋内と比較して乱雑でシーンが多様であるため、検出がやや難しくなります。今回は、本カメラが設置されるであろうシーンをいくつか選定し、それらのシーンに注力したデータセットを作成・学習しました。 屋外データセットのイメージ( APhoto より) 屋内データセット、屋外データセットそれぞれに対して、屋内用モデル・屋外用モデルで評価を行いました。PR曲線などの評価指標では大きな改善となりました。以下に結果を載せておきます。 屋内データセットに対するPR曲線 屋外データセットに対するPR曲線 屋内データセットでは大きな劣化はなく、屋外データセットにおける精度向上が見込めました。 もちろん評価ではPR曲線のような物体検出の評価だけではなく、設定したラインやエリアを通過する人数のカウント精度や人検知精度など、アプリのEnd-to-Endな評価も行っております。 今後は実際に製品を使っていただいたお客様の声や、データをもとにさらなる精度改善を行う予定です。 終わりに 屋外向けAIカメラの開発について紹介しました。複数のカメラを扱っている、セーフィーならではのEdgeAIの開発の話をしたつもりです。これをきっかけに興味を持っていただければ嬉しいです。 セーフィーではエンジニアを積極的に募集しています。どのような職種があるのか気になる方は こちら をご覧ください。カジュアル面談から受け付けておりますので、気軽に応募いただければと思います。
こんにちは。セーフィーでセキュリティマネジメント業務をしている川部です。 今回はサービス障害ハンドリングについて記載したいと思います。 サービス障害とセキュリティ?と疑問に思った方もいるかもしれません。 セキュリティの基本的な考えにあるCIAの可用性の観点から、セキュリティマネジメント業務の一環としてサービス障害発生時の取りまとめを担当しています。 主な業務はエンジニアと協力し、事象の詳細確認や影響するお客様の洗い出し、対外的な対応など多義にわたります。 今回はサービス障害ハンドリングにおける工夫という観点でお話したいと思いますのでSaaS企業で障害対応される皆さんや障害対応を任された皆さんの一助になれば幸いです。 セーフィーを取り巻く環境 スピード感をもって対応するための施策 サービス品質の維持管理するための施策 セーフィーを取り巻く環境 セーフィーでは現時点(2023年6月)で20.7万台のクラウドカメラが日々お客様の元で稼働している状況です。業界のシェア約56%であるため、広範囲な障害が発生した際は非常に多くのお客様に影響があるサービスです。 また当社の製品は様々な業界でご利用いただいており、防犯用途等で使用いただいてるお客様も少なくありません。 そのため、障害によってはお客様の目的が達成できないばかりか、お客様のビジネスにクリティカルな影響を与えてしまう可能性もあり、障害発生時の現場の緊張感は非常に高くなります。 一方で同じ商品でもBtoB向け、BtoC向けの商流があり、それぞれ障害対応時の観点も異なります。 BtoC向けであれば弊社のサポートを通して、お客様をサポートできるように社内への展開する情報を平準化しつつ、スムーズに情報展開する必要があります。 BtoB向けであれば、弊社がOEMとなり、パートナー企業様を通してサービスを提供しているため、お客様への情報展開が直販に比べ遅くなります。 そのため情報伝達のスピードが非常に重要になりますし、お客様の混乱を回避するため、パートナー企業様と発信内容を統一する必要があったりと気にすべきことはたくさんあります。 そのような背景の中、障害をハンドリングするわけですが、今回はハンドリングを行う上での大きな工夫2点をお伝えします。 スピード感をもって対応するための施策 一つ目は「スピード感をもって対応するための施策」です。 セーフィーでは障害報告が社内で上がった後、その障害の重要度を判断し、対応のスピードや対外報告の範囲を決定します。 内容によってはホームページや自社アプリ上で情報を展開する必要がありますし、影響範囲が小さければ個別に対応するなど重要度に応じてその後の工程を変えています。 この重要度判断に基づき、各部門が対応に向けて動き出します。なのでいち早く重要度を決定することがその後工程をスムーズにつなげるコツになります。 ただし、重要度を判断するには発生した障害事象を理解しながら影響範囲等を見定める必要があります。 一般的に判断と聞くと定量的な指標に基づき、判断するのだろうと思われるかと思います。もちろんそれも間違ってないです。 ただし障害はもともと想定していないもの(想定できるものは上流工程で潰されている前提ですが・・・)なので、発生事象によっては定量的な数字がスピーディーに取得できないものや定量的な指標で影響を判断できない事象等も考えられます。 そのため弊社では重要度判断基準として定量的、定性的な指標それぞれを設けて運用することでスムーズに判断できるようにしています。 サービス品質の維持管理するための施策 2つ目の工夫は障害対応が終わった後の話です。 障害発生後、暫定的に障害の封じ込めを行い、恒久対策を施し、より高い品質維持できるように工夫しています。 そのため非常にベタではありますが、障害を履歴として管理しており、遡って確認することはもちろん、発生した障害箇所により恒久的な対策やその日程感などを管理しています。 また障害の重要度に応じて対策に対しても各部門やCTOや経営層の確認を必須とする仕組みを整え、運用することで効率的かつサービス品質向上にもつながるように管理しています。 サービス障害はネガティブな事象と捉えることもできますが、サービスをより良いものにできるきっかけでもあるので、そういった意識をもってハンドリングにあたっています。 以上セーフィーにおけるサービス障害ハンドリングの工夫でした。 同じように障害対応で旗振りをしている方や障害対応について気になっていた方など何かの参考になればと思います。
​データ分析基盤グループでデータエンジニアをしている平川です。 DataVaultに関する記事の第3回目となります。 第3回の記事は、DataVaultモデリングをしている際に困った状態の対処方法についてまとめていきます 第1回: DataVaultってなに?どんな特徴があるの? 第2回: automate_dvを使ってDataVaultモデリングの中心となるテーブルを作ってみてわかったこと  第3回: BusinessVault、発展的なSatelliteテーブルやキーがNullだった場合の対処方法についてなど ← 今回はここ   前回のおさらい はじめに BusinessVaultとは StatusTrackingSatellite, EffectivitySatelliteの紹介 Status Tracking Satelliteとは Status Tracking Satelliteのテーブル構造 Effectivity Satelliteとは Effectivity Satelliteの構造 StatusTrackingSatelliteとEffectivity Satelliteの使い分け ゴーストレコード、ヌルキーとゼロキー ゴーストレコードとは ゼロキー(≒ ヌルキー)とは これらの概念を取り入れるメリット まとめ 参考資料 前回のおさらい 前回は、DataVaultモデリングの実装を簡易化するパッケージであるautomate_dvの紹介をさせていただきました。 特にDataVaultモデリングの中心となるHub/Link/Satelliteの生成について紹介させていただきました。 はじめに 今回の記事では、以下3つのトピックについて紹介させていただきます。 Hub/Link/Satelliteを利用する際に出くわす課題への対処法の1つとしてBusinessVaultについて 通常のSatelliteに追加機能を持たせたStatusTrackingSatelliteとEffectivitySatelliteについて ソーステーブルのキーの欠損などに対応するゼロキーやゴーストレコードについて BusinessVaultとは BusinessVaultとはRawVaultのデータを使いやすく整理し、特定のビジネスルールや変換処理を適用したモデルです。 この層は、RawVault層とユーザーが直接アクセスする層の間に位置しています。 このモデルを作るメリット・デメリットはいくつかあります。 メリット ビジネスロジックやデータ変換ルールを集約することで、Rawデータをルールに従った形で整理できます。これにより、分析が容易になることが考えられます。 多数のJOINや複雑な条件を含むクエリを事前に処理することで、パフォーマンス面での向上が見込めます デメリット BusinessVaultを生成する際にしばしば非正規化をするのでデータが冗長になります 適用するビジネスルールが頻繁に変わる場合、管理が困難になる可能性があります StatusTrackingSatellite, EffectivitySatelliteの紹介 Status Tracking Satelliteとは StatusTrackingSatelliteは、CDCが作用しない場合のデータの状態を追跡・管理する際にとても役に立ちます。 データソースでCDCが作用しない場合、ステータスの追跡が重要となり、ステータスの追跡をしない場合、いつレコードが消えたのかをSatelliteで把握するのは非常に困難です。 CDCが作用しない場合にはこのテーブルは非常に有用ですが、StatusTrackingSatelliteから必要なデータを取得するのは通常のSatelliteと比べるとSQLが複雑になります。 Status Tracking Satelliteのテーブル構造 Satelliteのテーブル構造とほぼ一緒ですが、record_statusというカラムを追加します。このカラムは名前の通りですが、レコードの状態を表し以下の3つのステータスを持ちます。 挿入("I"): 新しく追加されるレコードはrecord_status = 'I'としてレコードが追加されます。 更新("U"): 既存のレコードの属性情報が変わった際は、record_status = 'U'のレコードが追加されます 削除("D"): Satelliteテーブルには存在して、データソースに存在しないレコードには、record_status = 'D'としてレコードが追加されます Effectivity Satelliteとは Effectivity Satelliteはビジネスキー間の開始日と終了日を保持することで、有効期間を追跡することが可能になります。 Effectivity Satelliteの構造 StatusTrackingSatellite同様に、Satelliteテーブルが持つ基本的なカラムを持ちます。 Effectivity Satelliteには、開始日と終了日が追加されます。(automate-dvのeff_satではEFFECTIVE_FROMも追加されるようです) それぞれのカラムには以下のような値を格納します。 - 開始日: ビジネスキー間の関係性が開始した日付。 - 終了日: ビジネスキー間の関係性が終了した日付。関係性が続いている場合は、9999-12-31のように未来日が格納される。 StatusTrackingSatelliteとEffectivity Satelliteの使い分け データソースからレコードが削除された場合も、終了日を取っていれば対応できますが、StatusTrackingSatelliteとEffectivitySatelliteの違いは、追跡する対象が異なる点にあります。 前者はデータ自体の時間変化を追跡しますが、後者はビジネスキー同士の関係の有効期間を追跡します。 期間に関するデータを保持したい際には、EffectivitySatelliteが有用であると言えます。 例えば、EffectivitySatelliteを活用してキャンペーン情報と製品情報の関係性を保持することにより、売上情報との組み合わせで特定のキャンペーンがどのような影響を与えていたかを期間を絞って分析することが可能になります。 Effectivity Satelliteは終了日を更新する必要があるため、頻繁に終了日に値が入る場合はその都度updateされることになります。 両者で共通しているのは、データを抽出する際のクエリが複雑になる点です。(期間の指定やrecord_statusの絞り込みが必要になります) ゴーストレコード、ヌルキーとゼロキー ゴーストレコードとは データ(レコード)が存在しない場合に利用される仮のレコードです。このレコードを追加することで、各種モデルを結合するときのクエリを単純化することが可能になります。 ゼロキー(≒ ヌルキー)とは ビジネスキーが不明瞭であったり、欠落している場合に用いられる概念です。このキーを導入することで、データの品質を理解する手助けをし、品質改善の対策をする際に用いることが可能になります。 また、ゼロキーの概念を取り入れていない場合もしくはautomate-dvを使用してハッシュ化している場合、ハッシュ化したキーの値がnullになります。 これらの概念を取り入れるメリット データボルトモデリングをしているデータレイヤーの整合性や一貫性を保つことができます。 特にキーに欠落が発生する場合はゼロキーの概念を取り入れるべきだと思います。 ゴーストレコードについては、一時的にレコードが生成されていないという状況(例えば不具合等でデータが遅れて連携されるなど)であれば導入しなくても良いかなと思います。 まとめ 今回の記事では、DataVaultモデリングをする際に特定の場面で有効である概念の紹介をさせていただきました。 今回紹介したSatelliteを派生させたものや、キーなどがゴーストレコード、ゼロキー等である場合の対処方法は登場頻度は少ないかもしれないですが、データソースの状態や構造を直接的に変更することは難しいので、そのような時にこれらの概念を適用することでDataVaultモデリングの整合性を保ったままモデリングをすることが可能になります。 参考資料 The Data Vault Guru: a pragmatic guide on building a data vault automate_dvドキュメントページ
サーバーサイドエンジニアの三村です。 弊社では2024年の初めから国外へサービス展開をする準備として、一年ほど前からシステムの国際化対応を行ってきました。 この準備には、サービスの多言語対応や日本標準時以外のタイムゾーンでサービスが利用できるようにする改修などが含まれますが、サーバーチームでは特に後者に苦労しました。 そこでこの改修で得たPythonでタイムゾーンを扱う際の知見の一部を、このブログ記事にまとめます。 タイムゾーンについて タイムゾーンの基準 UTCオフセットだけでタイムゾーンを扱う問題点 サマータイム 特定タイムゾーンでのUTCオフセット変更 IANAのTime Zone Database (余談) Time Zone Databaseから読み取る世界の歴史 Pythonでの実装の注意点 pytzをdatetimeオブジェクトのコンストラクタに渡す pytzオブジェクトをdatetimeのコンストラクタに渡してもうまく動かない理由 zoneinfoファイルの「1901年問題」 strftime(%s)でのエポック秒変換 まとめ タイムゾーンについて Pythonでの実装の話に入る前に、まずはタイムゾーンの概念自体について説明します。 タイムゾーンの基準 世界中の各地域は異なる標準時間を用いていて、地域間に時差があることは言うまでもないと思います。 そしてこれらの異なる時間帯の基準となる時刻が存在することも有名かと思います。 こちらの基準時刻は、イギリスのグリニッジ天文台における時間を基に定められていると、昔学校で習った方も多いと思います。 経度0度の地点にある同天文台を基準時間とする考えは、GMT (Greenwich Mean Time)と呼ばれるものであり、現在こちらはUTC (Coordinated Universal Time)という概念に取って代わられています。 慣習的に同一のものと扱われることもある両者ですが、厳密には概念的、算出方法的な違いがあります。 GMTはグリニッジ天文台での地方平均時を表します。つまりGMTは特定地域での天体観測を基に算出される、ロンドンあたりを指す一つのタイムゾーンです。 比べてUTCは、天体観測だけではなく原子時計に基づく計算などから算出される基準時で、これ自体はどこかの地域のタイムゾーンではないです。地球上の地域の標準時を、この基準時との差分で表現するために存在しています。 UTCオフセットだけでタイムゾーンを扱う問題点 UTCのような基準となる時間の概念が存在すれば、それとのオフセット(基準との差異)のみを用いてシステムで世界中の時間を取り扱えそうだと一見思えます。しかし、実際はそんな甘くはないです。以下に、特定地域の人間の用いる時間の概念が単一のUTCオフセットでは表せないことの例を挙げます。 サマータイム 一部のタイムゾーンでは、一年を通して同じUTCオフセットの時間を用いているわけではなく、日照時間が長い時期にはサマータイムとして時間を早めることが行われています。 加えて、サマータイムの開始・終了時期も毎年一定とは限らず、頻繁に変更される地域もあります。 (例:1998年のブラジルでは、 サマータイムの開始日をローマ教皇の来訪時期とずらすため一日遅らせました。 ) 特定タイムゾーンでのUTCオフセット変更 歴史を遡れば、地球上の多くの地点で採用している標準時間の変更が行われてきました。 (例:サモアでは、 2011年にタイムゾーンをUTC-11からUTC+13に変更しました。 ) 後述する通り、このような標準時間の変更を経験した地域には、日本も含まれます。 IANAのTime Zone Database 上述の通り、人々が生活で用いている時間の概念は、季節的や歴史的な理由から単純なUTCオフセット一つでは表現しきれない場合があります。 そこで、このような一般的に使われるタイムゾーンを、季節的な変化や歴史的な経緯も包括してまとめているのが、Time Zone Databaseです。 www.iana.org Asia/Tokyo などのよく見かけるタイムゾーンの表記は、IANAによって編纂されているこちらのデータベースによって規定されています。 こちらのデータベースの情報は、プログラムからはいろいろな環境で利用できます。Unix likeなシステムであれば /usr/share/zoneinfo/ にバイナリ形式で入っているほか、Pythonの実行環境から参照したい場合は tzdataというモジュール を用いることでOSによらず利用できます。 こちらの中身を確認する一番簡単な方法は、上記のIANAのリンクを開き、データベースのファイルをダウンロードし解凍し開くことです。試しにニューヨークのタイムゾーン情報を開いてみると、以下のようになっています。 # Monthly Notices of the Royal Astronomical Society 44, 4 (1884-02-08), 208 # says that New York City Hall time was 3 minutes 58.4 seconds fast of # Eastern time (i.e., -4:56:01.6) just before the 1883 switch. # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule NYC 1920 only - Mar lastSun 2 : 00 1 : 00 D Rule NYC 1920 only - Oct lastSun 2 : 00 0 S Rule NYC 1921 1966 - Apr lastSun 2 : 00 1 : 00 D Rule NYC 1921 1954 - Sep lastSun 2 : 00 0 S Rule NYC 1955 1966 - Oct lastSun 2 : 00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF -4:56:01.6 Zone America/New_York - 4 : 56 : 02 - LMT 1883 Nov 18 17 :00u - 5 : 00 US E%sT 1920 - 5 : 00 NYC E%sT 1942 - 5 : 00 US E%sT 1946 - 5 : 00 NYC E%sT 1967 - 5 : 00 US E%sT 最初にコメントで、1883年の標準時間の変更までは東海岸時間より3分58.4秒早かったとする資料の紹介がされています。 その後過去のサマータイムの実施の歴史や、コメントで書かれていた1883年以前の時間の現在との時差についてが記述されています。 (余談) Time Zone Databaseから読み取る世界の歴史 上記のニューヨークの例の通り、Time Zone Databaseは単なる標準時の変化等を考慮した時刻変換のルールが記載されているのみでなく、豊富なコメントもあり読み物としても面白いです。日本の例を見てみます。 # From Paul Eggert (1995-03-06): # Today's _Asahi Evening News_ (page 4) reports that Japan had # daylight saving between 1948 and 1951, but "the system was discontinued # because the public believed it would lead to longer working hours." # From Mayumi Negishi in the 2005-08-10 Japan Times: # http://www.japantimes.co.jp/cgi-bin/getarticle.pl5?nn20050810f2.htm # Occupation authorities imposed daylight-saving time on Japan on # [1948-05-01].... But lack of prior debate and the execution of # daylight-saving time just three days after the bill was passed generated # deep hatred of the concept.... The Diet unceremoniously passed a bill to # dump the unpopular system in October 1951, less than a month after the San # Francisco Peace Treaty was signed. (A government poll in 1951 showed 53% # of the Japanese wanted to scrap daylight-saving time, as opposed to 30% who # wanted to keep it.) # -- 中略 -- # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Japan 1948 only - May Sat>= 1 24 : 00 1 : 00 D Rule Japan 1948 1951 - Sep Sat>= 8 25 : 00 0 S Rule Japan 1949 only - Apr Sat>= 1 24 : 00 1 : 00 D Rule Japan 1950 1951 - May Sat>= 1 24 : 00 1 : 00 D # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Tokyo 9 : 18 : 59 - LMT 1887 Dec 31 15 :00u 9 : 00 Japan J%sT コメント及び Rule の項には、日本が過去に実施していたサマータイムの情報が載っています。終戦直後日本ではGHQの指導によりタイムゾーンを導入していたようですが、人々はサマータイムによって労働時間を伸ばされるのではないかと不安になり大変不人気であったため、サンフランシスコ平和条約締結後日本が主権を回復すると、すぐにサマータイムは廃止されたそうです。 ちなみに Zone の項目にある通り、日本は1888年の正月より前には今より 18分59秒 早い標準時間を用いていました(東京の地方平均時)。 今までプログラムで日本時間を扱うコードを書いた際に、タイムゾーン取り扱いの不備で意図した時刻よりも 18分59秒 ずれた時刻となるバグを起こすなどしてこの時間を見たこともあるかと思いますが、この時間はここからきています(詳しい例は後述します)。 Pythonでの実装の注意点 やっとPythonの話に入ります。 Pythonでtimezone awareな形でdatetimeオブジェクトを取り扱う際に陥りそうな誤りを、いくつか挙げてみます。 pytzをdatetimeオブジェクトのコンストラクタに渡す Pythonにおけるタイムゾーン関連のよくある間違いでは一番有名な話かもしれませんが、 pytz 型のタイムゾーンをdatetimeオブジェクトのコンストラクタに渡すと、大抵の場合意図しないdatetimeオブジェクトが作成されます。 >>> import datetime >>> import pytz >>> datetime.datetime( 2023 , 8 , 25 , 12 , 0 , 0 , tzinfo=pytz.timezone( 'Asia/Tokyo' )).utcoffset() datetime.timedelta(seconds= 33540 ) # ↑9時間19分 上記のようなコードを書いた場合、実装者は大抵の場合UTCオフセットが9時間ちょうどのタイムゾーンのオブジェクトの生成を期待していると思いますが、実際には9時間19分となっています。 pytzはdatetimeオブジェクトのコンストラクタに渡されるような利用方法は想定されておらず(詳しくは後述)、このような利用方法ができないことが ドキュメントに記載されています。 This library differs from the documented Python API for tzinfo implementations; if you want to create local wallclock times you need to use the localize() method documented in this document. 上記引用にある通り、pytzでdatetimeオブジェクトにタイムゾーン情報を付与したい場合は、 localize() を用いるのが正解です。 >>> pytz.timezone( 'Asia/Tokyo' ).localize(datetime.datetime( 2023 , 8 , 25 , 12 , 0 , 0 )).utcoffset() datetime.timedelta(seconds= 32400 ) # ↑9時間ちょうど ちなみに、pytzをdatetimeオブジェクトのコンストラクタに渡す際のバグは上に例示しましたが、これはコンストラクタだけでなく replace() の引数として渡しても同様の事象は起こります。 >>> import datetime >>> import pytz # まずはtimezone unawareなdatetimeオブジェクトを作成 >>> d = datetime.datetime( 2023 , 8 , 25 , 12 , 0 , 0 ) >>> d datetime.datetime( 2023 , 8 , 25 , 12 , 0 ) # これにreplace()でtimezone情報をつけると、やはり19分ずれる >>> d.replace(tzinfo=pytz.timezone( 'Asia/Tokyo' )).utcoffset() datetime.timedelta(seconds= 33540 ) # ↑9時間19分 こちらの理由は単純で、datetimeクラスの replace() は内部で新規にオブジェクトの生成を行っていて、その際にコンストラクタの引数に replace() の引数をそのまま渡しているためです。 github.com def replace (self, year= None , month= None , day= None , hour= None , minute= None , second= None , microsecond= None , tzinfo= True , *, fold= None ): """Return a new datetime with new values for the specified fields.""" if year is None : year = self.year if month is None : month = self.month if day is None : day = self.day if hour is None : hour = self.hour if minute is None : minute = self.minute if second is None : second = self.second if microsecond is None : microsecond = self.microsecond if tzinfo is True : tzinfo = self.tzinfo if fold is None : fold = self.fold return type (self)(year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold) 最終行で、新たにオブジェクトを作成しそのコンストラクタの中でtzinfoも渡しています。 pytzオブジェクトをdatetimeのコンストラクタに渡してもうまく動かない理由 pytzのこの挙動はPython界隈では結構有名ですが、なぜこのような挙動になっているのか説明しているブログ記事等は、(自分のなけなしのリサーチ能力だと)見当たりませんでした。そこで、少し自分で実装を調べてみました。 まず、Pythonのdatetimeのドキュメントには以下のような記述がありました。 docs.python.org For applications requiring aware objects, datetime and time objects have an optional time zone information attribute, tzinfo, that can be set to an instance of a subclass of the abstract tzinfo class. These tzinfo objects capture information about the offset from UTC time, the time zone name, and whether daylight saving time is in effect. Only one concrete tzinfo class, the timezone class, is supplied by the datetime module. The timezone class can represent simple timezones with fixed offsets from UTC, such as UTC itself or North American EST and EDT timezones. Supporting timezones at deeper levels of detail is up to the application. The rules for time adjustment across the world are more political than rational, change frequently, and there is no standard suitable for every application aside from UTC. Pythonの標準ライブラリであるdatetimeでは、タイムゾーン情報としては tzinfo という抽象クラス *1 と、その実装として timezone というクラスを用意していますが、こちらの実装はUTCオフセットが固定である前提となっています。UTCオフセットが歴史的経緯などで変わってくるような、実際の時間の概念に近いものが使いたければ、同ライブラリを利用するアプリケーション側で tzinfo をよしなに実装してどうにかしてね、という意図のようです。 こちらのtzinfoのクラスですが、実装の際にUTCオフセットをいい感じに対応するには utcoffset() というメソッドを上書きしてほしいそうです。 class tzinfo : """Abstract base class for time zone info classes. Subclasses must override the tzname(), utcoffset() and dst() methods. """ __slots__ = () def tzname (self, dt): "datetime -> string name of time zone." raise NotImplementedError ( "tzinfo subclass must override tzname()" ) def utcoffset (self, dt): "datetime -> timedelta, positive for east of UTC, negative for west of UTC" raise NotImplementedError ( "tzinfo subclass must override utcoffset()" ) def dst (self, dt): """datetime -> DST offset as timedelta, positive for east of UTC. Return 0 if DST not in effect. utcoffset() must include the DST offset. """ raise NotImplementedError ( "tzinfo subclass must override dst()" ) # 以下略 次に、datetimeオブジェクトのコンストラクタに渡しても正しく動く、 dateutil.tz の実装ではこの utcoffset() をどう実装しているのかみてみます。 github.com def utcoffset (self, dt): if dt is None : return None if not self._ttinfo_std: return ZERO return self._find_ttinfo(dt).delta # 中略 ↓こちらは上の関数から呼ばれている def _find_ttinfo (self, dt): idx = self._resolve_ambiguous_time(dt) return self._get_ttinfo(idx) # 中略 ↓こちらは上の関数から呼ばれている def _resolve_ambiguous_time (self, dt): idx = self._find_last_transition(dt) # If we have no transitions, return the index _fold = self._fold(dt) if idx is None or idx == 0 : return idx # If it's ambiguous and we're in a fold, shift to a different index. idx_offset = int ( not _fold and self.is_ambiguous(dt, idx)) return idx - idx_offset 上のコードでは、 utcoffset() が呼ばれた際には毎回、内部で標準時間の変遷の歴史を参照する関数を呼び出し、その結果を参考に採用すべきUTCとのオフセットを導き出しています。このことから、dateutil.tzでは utcoffset() が呼び出されると、その都度標準時間の遍歴を考慮してUTCオフセットの判定をしていることがわかります。 比べて、コンストラクタに渡すとうまく動かないpytzの方の実装を見てみます。 github.com def utcoffset (self, dt, is_dst= None ): # 関数のコメントは長いので略 if dt is None : return None elif dt.tzinfo is not self: dt = self.localize(dt, is_dst) return dt.tzinfo._utcoffset else : return self._utcoffset こちらは条件分岐によっては、dateutil.tzでの場合のようなUTCオフセットの決定のための複雑な計算はせずに、事前にクラスにセットされた _utcoffset の値を単純に返すのみになります。基本的にdatetimeオブジェクトが生成されてからは utcoffset() が呼ばれる際には、上記コードの条件分岐一番最後の else 節に入るようであったので、pytzはインスタンス作成時に設定されたUTCオフセットを返し続ける挙動になっていそうです。 上記の実装2例からは、dateutil.tzの方はUTCオフセットを参照された場合は毎回引数のdatetimeオブジェクトの日付を確認してUTCオフセットを決定していて、pytzの方は一度UTCオフセットの値が設定されたらそれを返し続ける、という違いが見て取れます。 datetimeオブジェクトのコンストラクタにタイムゾーンを渡すユースケースの場合、先に引数として渡すために作成されるタイムゾーンオブジェクトは作成予定のdatetimeオブジェクトを読み取れないので、どのような tzinfo の実装であれ一旦は正確にはUTCオフセットを決められない状態になります。dateutil.tzの方はその後改めてdatetimeオブジェクトの日付を確認してUTCオフセットを再計算するタイミングがあるのに対し、pytzの場合はそれがなさそうです。 このことから、pytzオブジェクトをdatetimeオブジェクトのコンストラクタに渡すと、UTCオフセットが不明な状態で記録された該当タイムゾーンでの最も古いUTCオフセットの情報を保持し続け *2 、上述のような挙動が起こると思われます。 zoneinfoファイルの「1901年問題」 上で、Time Zone Databseの情報はUnix likeな環境では /usr/share/zoneinfo 以下に同情報が入っていると書きました。 このファイルを用いてタイムゾーンを扱う際の意図しない挙動を紹介します。 一部の環境 *3 で以下のようなコードを実行すると、意図した時間よりも18分59秒ずれることになります。 >>> import datetime >>> import dateutil.tz # 年月日はなしに時間のみを指定してdatetimeオブジェクトを作成し、それにdateutil.tzでAsia/Tokyoのタイムゾーン情報を付与し、UTCオフセットを取得 >> datetime.datetime.strptime( '12:00' , '%H:%M' ).replace(tzinfo=dateutil.tz.gettz( 'Asia/Tokyo' )).utcoffset() datetime.timedelta(seconds= 33539 ) # UTCオフセットは9時間(32400秒)を期待しているのに、9時間18分59秒(33539秒)となった (上のコードはそもそも、日付のない時間のデータを扱うのにdatetime.timeではなく余計に情報量の多いdatetime.datetimeを用いているところが本質的な間違いですが、そこには目を瞑ってください。) こちらは、datetimeオブジェクトで時間のみを指定すると年月日は1900年1月1日となること、一部の環境でTime Zone Databaseの情報が載っているバイナリのファイルで日付データを32ビットまでしか扱えていないこと、の二つの要因が重なってこのような挙動になっています。 前者については、1900年1月1日時点では既に日本では今と同じ標準時間が採用されている状態であったため、こちら単体ではUTCオフセットは9時間18分59秒とはならないはずです。 後者については、32ビットで扱える時間の範囲を超えるとコンピュータが誤動作すると言われる「2038年問題」と同源です。32bitの秒数でエポック時間から負の方向に向かって表現できる限界は1901年の12月13日であり、datetimeオブジェクトに自動でつけられた1900年1月1日はこれよりも古いため、一部環境でUTCオフセットの計算がうまくいかなっているようです。 上記の事象が再現する環境で参照されているタイムゾーンの情報のファイルを、 RFC8536での仕様 をもとにバイナリを読んでみます。 $ hexdump -C /usr/share/zoneinfo/Asia/Tokyo 00000000 54 5a 69 66 32 00 00 00 00 00 00 00 00 00 00 00 |TZif2...........| 00000010 00 00 00 00 00 00 00 04 00 00 00 04 00 00 00 00 |................| 00000020 00 00 00 09 00 00 00 04 00 00 00 0c 80 00 00 00 |................| 00000030 d7 3e 02 70 d7 ed 59 f0 d8 f8 fa 70 d9 cd 3b f0 |.>.p..Y....p..;.| 00000040 db 07 00 f0 db ad 1d f0 dc e6 e2 f0 dd 8c ff f0 |................| 00000050 03 01 02 01 02 01 02 01 02 00 00 83 03 00 00 00 |................| 00000060 00 8c a0 01 04 00 00 7e 90 00 08 00 00 7e 90 00 |.......~.....~..| 00000070 08 4c 4d 54 00 4a 44 54 00 4a 53 54 00 00 00 00 |.LMT.JDT.JST....| 00000080 01 00 00 00 01 54 5a 69 66 32 00 00 00 00 00 00 |.....TZif2......| 00000090 00 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 |................| 000000a0 04 00 00 00 00 00 00 00 0a 00 00 00 04 00 00 00 |................| 000000b0 0c f8 00 00 00 00 00 00 00 ff ff ff ff 65 c2 a4 |.............e..| 000000c0 70 ff ff ff ff d7 3e 02 70 ff ff ff ff d7 ed 59 |p.....>.p......Y| 000000d0 f0 ff ff ff ff d8 f8 fa 70 ff ff ff ff d9 cd 3b |........p......;| 000000e0 f0 ff ff ff ff db 07 00 f0 ff ff ff ff db ad 1d |................| 000000f0 f0 ff ff ff ff dc e6 e2 f0 ff ff ff ff dd 8c ff |................| 00000100 f0 00 03 01 02 01 02 01 02 01 02 00 00 83 03 00 |................| 00000110 00 00 00 8c a0 01 04 00 00 7e 90 00 08 00 00 7e |.........~.....~| 00000120 90 00 08 4c 4d 54 00 4a 44 54 00 4a 53 54 00 00 |...LMT.JDT.JST..| 00000130 00 00 01 00 00 00 01 0a 4a 53 54 2d 39 0a |........JST-9.| 0000013e 下記画像に説明を書いた通り、本ファイルにはUTCオフセットが今よりも18分59秒ずれていた時期のルールが記載されていますが、そちらが採用されていた最後の時間にあたる部分に、符号付き32bitのintergerの最小値である 80 00 00 00 (-2147483648)が入っています。こちらをUNIX timeとして読み取ると1901年12月13日中の時間になるため、それ以前の時間はこのファイルを参照する限りUTCオフセットが9時間18分59秒と扱われてしまいます。(実際にこのUTCオフセットの標準時間が利用されていたのは1887年末までなので、およそ14年分の日本時間は正しく変換できないことになります。) 逆に、この現象が起きない筆者のMac上の同じファイルを見てみると、この符号付き32bit整数ではUNIX timeとして扱えない時期のデータは、省略されていることがわかりました。 % hexdump -C /usr/share/zoneinfo/Asia/Tokyo 00000000 54 5a 69 66 32 00 00 00 00 00 00 00 00 00 00 00 |TZif2...........| 00000010 00 00 00 00 00 00 00 03 00 00 00 03 00 00 00 00 |................| 00000020 00 00 00 08 00 00 00 03 00 00 00 08 d7 3e 02 70 |.............>.p| 00000030 d7 ed 59 f0 d8 f8 fa 70 d9 cd 3b f0 db 07 00 f0 |..Y....p..;.....| 00000040 db ad 1d f0 dc e6 e2 f0 dd 8c ff f0 00 01 00 01 |................| 00000050 00 01 00 01 00 00 8c a0 01 00 00 00 7e 90 00 04 |............~...| 00000060 00 00 7e 90 00 04 4a 44 54 00 4a 53 54 00 00 00 |..~...JDT.JST...| 00000070 01 00 00 01 54 5a 69 66 32 00 00 00 00 00 00 00 |....TZif2.......| 00000080 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 04 |................| 00000090 00 00 00 00 00 00 00 09 00 00 00 04 00 00 00 0c |................| 000000a0 ff ff ff ff 65 c2 a4 70 ff ff ff ff d7 3e 02 70 |....e..p.....>.p| 000000b0 ff ff ff ff d7 ed 59 f0 ff ff ff ff d8 f8 fa 70 |......Y........p| 000000c0 ff ff ff ff d9 cd 3b f0 ff ff ff ff db 07 00 f0 |......;.........| 000000d0 ff ff ff ff db ad 1d f0 ff ff ff ff dc e6 e2 f0 |................| 000000e0 ff ff ff ff dd 8c ff f0 03 01 02 01 02 01 02 01 |................| 000000f0 02 00 00 83 03 00 00 00 00 8c a0 01 04 00 00 7e |...............~| 00000100 90 00 08 00 00 7e 90 00 08 4c 4d 54 00 4a 44 54 |.....~...LMT.JDT| 00000110 00 4a 53 54 00 00 00 00 01 00 00 00 01 0a 4a 53 |.JST..........JS| 00000120 54 2d 39 0a |T-9.| 00000124 データの個数が問題の起こる環境と比べ一つ減っていることと、9時間18分59秒のUTCオフセットを表す 83 03 (09:18:59を秒数にした33540の16進数表記)という値が(時間の変換ルールの項目からは)見当たらないことがわかります。 これによって1888年1月1日から1901年12月13日までの日本の日付を正しく扱えるようになりますが、逆に1887年末以前のデータは実際の当時の標準時とはずれた時間で取り扱われます。 # 1887年末以前の標準時がzoneinfoファイルに記録されていない、筆者のMacで実行 >>> datetime.datetime( 1887 , 1 , 1 , 0 , 0 , 0 , tzinfo=dateutil.tz.gettz( "Asia/Tokyo" )).utcoffset() datetime.timedelta(seconds= 32400 ) # ↑実際は33540(9時間18分59秒)であるべきが、32400(9時間)となってしまっている 何らかの理由で1887年以前の日本の日付データを扱いたい場合は、こちらの挙動に気をつける必要がありそうです。 strftime(%s)でのエポック秒変換 datetimeオブジェクトをエポック秒に変換したい場合に、 strftime(%s) を用いることでこれが実現できると書いてある記事がちらほら存在します。 しかしこちらは Pythonのdatetimeのドキュメント ではサポートされているとは一切書いておらず、非推奨です。 # 日本時間で動く環境です >>> import time >>> time.tzname ( 'JST' , 'JST' ) >>> import datetime >>> import dateutil.tz # 現在時刻を取得した後、それにニューヨークのタイムゾーンを付与します >>> d = datetime.datetime.now().astimezone(dateutil.tz.gettz( 'America/New_York' )) >>> d datetime.datetime( 2024 , 2 , 8 , 1 , 41 , 57 , 844204 , tzinfo=tzfile( '/usr/share/zoneinfo/America/New_York' )) # strftime(%s)を用いてエポック秒に変換 >>> epoch_s = d.strftime( "%s" ) >>> epoch_s '1707324117' # これをdatetimeに変換し再びニューヨークのタイムゾーンをつけると、もとより14時間ずれていることがわかります >>> datetime.datetime.fromtimestamp( int (epoch_s)).astimezone(dateutil.tz.gettz( 'America/New_York' )) datetime.datetime( 2024 , 2 , 7 , 11 , 41 , 57 , tzinfo=tzfile( '/usr/share/zoneinfo/America/New_York' )) # こちらはエポック秒変換の際に、ニューヨークでの時間としてではなく日本時間として変換されたためです 上記のコード例の通り、 strftime(%s) だと変換元のdatetimeオブジェクトがtimezone awareであったとしても、変換時にはそのタイムゾーン情報ではなく、実行環境のタイムゾーン情報を参照してしまいます。 (日本時間で動く環境で、日本時間の時刻データのみを扱っていると気づかずにこのような実装が紛れてしまうかもしれませんが、どちらか一方が日本時間以外となるとこちらは不具合を起こします。) こちらdatetimeオブジェクトをエポック秒に変換したい場合は、 timestamp() を使うのが正解です。 まとめ コンピュータで扱う場合のタイムゾーンの概念は、単なる地域間の時差の寄せ集めではなく、歴史的変遷など時間軸の情報も含んだ複雑なものです (Pythonやタイムゾーンとかに限った話ではないですが)ちゃんとドキュメント読みましょう *1 : 厳密にいうとこれは抽象クラスとは呼ばないかもしれないですが、実際の実装はなく利用側での関数のoverrideを期待しているクラスという意味で、雑にこの語を使ってます *2 : pytzのオブジェクトがコンストラクタ内でとりあえず最も古い時期のUTCオフセットを参照する部分の実装はこちら: https://github.com/stub42/pytz/blob/fb43f957c5149e750c3be3cfc72b22ad94db4886/src/pytz/tzinfo.py#L189 *3 : ubuntu:focal-20230412のDockerイメージをもとにしたコンテナでapt-get install tzdataをした際に発生するのを確認しました
はじめに セーフィー株式会社 の AI Vision グループでテックリードを務めます橋本貴博です。 セーフィーではネットワークカメラ上で動作するエッジアプリケーションの開発にC++を利用しています。公開されている推論モデルはPythonで実装されていることが多いため、C++への移植が必要です。 この記事では、GitHub Copilot を使って 推論モデルの前後処理をPythonからC++に変換した結果を紹介したいと思います。 はじめに アルゴリズム 実験条件 前処理 後処理 むすび アルゴリズム mmsegmentation リポジトリで公開されている PP-MobileSeg のPython実装された推論コードを利用します。PP-MobileSeg は、2023年4月に発表された、モバイル向けセマンティックセグメンテーションのSOTAモデルです。論文は こちら から読めます。 引用元: PaddlePaddle/PaddleSeg (GitHub) / Apache License 2.0 実験条件 mmsegmentation をcloneし、リポジトリのルートを VSCode の Open Folder から開きます git clone git@github.com:open-mmlab/mmsegmentation.git 対象とするファイル(projects/pp_mobileseg/inference_onnx.py)のみをエディタで開きます GitHub Copilot Chat の New Session を作成して、過去のチャット履歴がコンテキストに入らないようにしています(実際に利用する際は何往復か対話をしたほうが良い結果が得られると思います) 前処理 推論コード inference_onnx.py の L39 - 45 をC++に変換してみます。入力された画像にリサイズと正規化を適用する一般的な前処理です。 # Resize resized_img = cv2.resize(img, input_size) # Normalize mean = np.array([ 123.575 , 116.28 , 103.53 ], dtype=np.float32) std = np.array([ 58.395 , 57.12 , 57.375 ], dtype=np.float32) resized_img = (resized_img - mean) / std コードを選択しCopilotのコンテキストに含めた状態でChatを使用します。「C++に書き直して」だけだとEigenライブラリも使ってしまったので、OpenCVライブラリだけを使うように指示します。プロンプトと回答は次の通りです。 t-hashimoto-safie: OpenCVだけを使って、C++に書き直して GitHub Copilot: 以下に、選択されたPythonコードをC++で書き直したものを示します。このコードはOpenCVライブラリを使用しています。 // Include necessary libraries #include <opencv2/opencv.hpp> #include <opencv2/core/core.hpp> // ... // Assuming 'img' is your input cv::Mat image cv::Mat resized_img; cv::Size input_size(your_width, your_height); // replace with your dimensions // Resize cv::resize(img, resized_img, input_size); // Normalize cv::Scalar mean(123.675, 116.28, 103.53); cv::Scalar std(58.395, 57.12, 57.375); resized_img = (resized_img - mean) / std; このコードは、Pythonコードと同じように画像をリサイズし、正規化します。ただし、C++のOpenCVでは、 cv::Scalar を使用して平均と標準偏差を定義します。また、リサイズのための新しいサイズは cv::Size オブジェクトとして定義されます。 正解です!以下のポイントが押さえられています。 OpenCV API は Python と C++ で微妙にインターフェースが異なることを理解している C++ の cv::resize メソッドはリサイズ後の画像の参照渡しする C++で変数の定義に必要な型を推測できている リサイズ後の画像を cv::Mat 型で事前に定義している np.array の代わりに cv::Scalar を使えばよい 後処理 次に、 inference_onnx.py の L108-L122 を書き直してもらいます。以下のコードは、ピクセルごとのラベル(int型)を示す seg_map を、ラベルに対応する色で入力画像 img に重畳し、ファイルに保存する関数です。セマンティックセグメンテーションで一般的に行われる可視化処理です。 def visualize (img: np.ndarray, seg_map: np.ndarray, filename: str = 'output.jpg' , opacity: float = 0.8 ) -> np.ndarray: assert 0.0 <= opacity <= 1.0 , 'opacity should be in range [0, 1]' palette = np.array(PALETTE) color_seg = np.zeros((seg_map.shape[ 0 ], seg_map.shape[ 1 ], 3 ), dtype=np.uint8) for label, color in enumerate (palette): color_seg[seg_map == label, :] = color # convert to BGR color_seg = color_seg[..., ::- 1 ] img = img * ( 1 - opacity) + color_seg * opacity cv2.imwrite(filename, img) GitHub Copilot へのプロンプトと回答は以下の通りです。 t-hashimoto-safie: OpenCVだけを使って、C++に書き直して GitHub Copilot: 以下に、PythonからC++への変換を行ったコードを示します。このコードは、OpenCVライブラリを使用しています。 #include <opencv2/opencv.hpp> #include <vector> #include <cassert> // Assuming PALETTE is a vector of cv::Vec3b (BGR colors) extern std::vector<cv::Vec3b> PALETTE; void visualize(cv::Mat& img, cv::Mat& seg_map, std::string filename = "output.jpg", float opacity = 0.8) { assert(0.0 <= opacity && opacity <= 1.0); // opacity should be in range [0, 1] cv::Mat color_seg = cv::Mat::zeros(seg_map.size(), CV_8UC3); for (int label = 0; label < PALETTE.size(); ++label) { color_seg.setTo(PALETTE[label], seg_map == label); } img = img * (1 - opacity) + color_seg * opacity; cv::imwrite(filename, img); } このC++コードは、Pythonコードと同じように動作します。各ラベルに対応する色をセグメンテーションマップに適用し、その結果を元の画像に重ねて表示します。その後、結果を指定されたファイル名で保存します。 こちらも正解です!気づいた点としては以下の通りです。 利用する C++ API の理解 ラベルと一致するピクセルに色を付ける処理には [Mat::setTo](https://docs.opencv.org/4.x/d3/d63/classcv_1_1Mat.html#a0440e2a164c0b0d8462fb1e487be9876) メソッドを使えばよい。 効率化のための提案 もともとの PythonコードRGB画像からBGRに変換をする部分が非効率だった。はじめからBGRのパレット std::vector<cv::Vec3b> PALETTE を用意したほうが良いと提案している。 むすび GItHub Copilot に「プログラミング言語」から「他のプログラミング言語」に変換するタスクを解かせてみました。APIリファレンスを調べながらコーディングしなくて済むので、作業時間が短縮できたと感じました。なんとなくは覚えているけど、正確な書き方を忘れたといった場合に効果があるのではないでしょうか。 セーフィーの開発部門では GitHub Copilot を導入しています。
はじめに リリースから3年以上経過しようやくモダンな開発環境に近づけていく活動ができるようになるくらい体制が整って来ました。 今回はAndroid版Safie Viewer for Mobileが2023年に行った改善活動の振り返りの話をしたいと思います。 はじめに ユニットテストの導入 ビジュアルリグレッションテストの導入 Jetpack Composeの導入 Detektの導入 Dependabotの導入 ビルドスクリプト周りの見直し 便利だったGithubActionsのAction 最後に ユニットテストの導入 改善活動の一歩としてユニットテストを導入しました。 ここまでの間テストがないという状況が続いており機能追加やライブラリのアップデートをした時手動でのテストが欠かせない状態でした。 今年からユニットテストを導入したので手動での検証作業が少し緩和されたという状態になりました。 カバレッジ率はまだ約12%というのが現実ですが、来年以降もカバレッジ率を高めていくような活動を続けていきます。 ビジュアルリグレッションテストの導入 ユニットテストに加え Roborazzi を使用したビジュアルリグレッションテスト(以下VRT)も合わせて導入しました。 Roborazziは nowinandroid や DoroidKaigi2023 でも採用されているライブラリで、JVM上でスクリーンショットを撮影し差分比較することのできるライブラリです。 Safie Viewer for Mobileでは サンプル を参考にGithubActionsのワークフローを作成し、プルリクエスト上でVRTを実行し差分比較を行えるような環境を用意しました。 プルリクエスト上で差分の確認ができる Safie Viewer for Mobileは多言語化対応をしており動作確認のため端末の言語設定を切り替えてから対象の画面を表示しレイアウトに問題がないかを確認しておりましたがRoborazzi導入後は自動で言語毎のスクリーンショットを撮って確認する事ができるので、かなりの作業時間の削減に繋がりました。 多言語化対応しているようなアプリであれば非常に強力なライブラリになるかと思います。 github.com Jetpack Composeの導入 今年からXMLからJetpack Composeにレイアウトの作りを置き換えるという事を始めました。 移行率は10%程度とまだ殆どがXMLでの構成となっておりますが、小さい画面から順にJetpack Composeに置き換えるといった活動をしています。 Jetpack Composeに置き換えて一番大きく感じた利点はテストの書きやすさにあると感じました。 これまで、Activity/FragmentのUI周りのテストを書くのは敷居が高いと感じ実機で手動確認で済ませる事が多かったのですがJetpack Composeであれば 表示のテスト 状態変化のテスト がかなり簡単に書けるようになったと思います。 developer.android.com Detektの導入 静的コード解析として、Detektを導入しました。 GitHubActionsで実行したDetektの結果を alaegin/Detekt-Action を使用して通知する仕組みで運用しています。 目的としては、これまで目視で確認していた「コーディングルールに則って書かれているかどうか」を自動で確認するためです。 その他にDetektの便利なところは、メソッド毎に複雑度の数値「CyclomaticComplexMethod」と「CognitiveComplexMethod」が測れることにあると思います。 今まで感覚的に「このメソッドなんか複雑に感じる」というのを数値で見る事ができるのでレビューの際に指摘しやすくなるといった効果があります。 detekt.dev Dependabotの導入 今まで手作業で確認していたライブラリのアップデートをDependabotを導入して自動化しました。 手作業で確認していた分の時間がかなり削減されたので開発者体験の向上に繋がりました。 docs.github.com ビルドスクリプト周りの見直し ビルドスクリプト周りの改善として以下の事を行いました Groovy から KTSに移行 Android Gradle Pluginを7系から8系にアップデート Version Catalogの導入 Androidは今後KTSでビルドスクリプトを書くのが標準なので、それに合わせてSafie Viewer for Mobileも全てKTSに置き換えました。 ついでにライブラリのバージョンもVersion Catalogで管理するようにしましたが今のところ大きな恩恵は受けておらず、これは将来的にマルチモジュール対応などを行った時に効果を発揮することになると思います。 developer.android.com developer.android.com 便利だったGithubActionsのAction r0adkll/upload-google-play GithubActonsからアプリのリリースファイルを作成しGooglePlayConsoleへ自動アップロードする為のActionです。 このActionを導入してアプリのリリース作業を半自動化する事ができました。 AlexSim93/pull-request-analytics-action PullRequestを解析し オープンしたプルリクエストの数 マージしたプルリクエストの数 プルリクエストを承認した回数 マージまでに掛かった時間 などのレポートを出力できるActionで、取り急ぎこれまでのプルリクエストの状況を確認したかった事があったので便利でした。 執筆時のバージョンは1.8.4ですが、頻繁に更新されているライブラリで日に日に新機能が追加されて行っているのが特徴的です。 github.com github.com 最後に 2023年は改善活動に力を入れた1年でした。 1年かけて様々なものを導入して来ましたが、まだまだ改善していきたい箇所が多く継続的に改善活動を続けていく必要があります。 日々の改善活動を通して、チームとして力を伸ばしていきたいと思います。 また、セーフィーではエンジニアの採用を積極的に行っております。もし興味が出てきた際はぜひご応募いただけたらと思います。 safie.co.jp
はじめに セーフィー株式会社 の AI Vision グループでテックリードを務めます橋本貴博です。セーフィーの一部のAIネットワークカメラは、Snapdragon Neural Processing Engine(SNPE)をランタイムに使ってエッジ推論を行っています。この記事では、SOTA セグメンテーションモデル PP-MobileSeg を SNPEで動かす方法を解説したいと思います。 はじめに PP-MobileSegとは? モデル変換 大まかな流れ PyTorch から ONNX への変換 ONNX モデルの修正 ONNX から DLC への変換 推論結果 むすび PP-MobileSegとは? PP-MobileSeg は、2023年4月に発表された、モバイル向けセマンティックセグメンテーションのSOTAモデルです。 ADE20Kデータセット で学習がなされており、既存モデルと比較して低レイテンシ、かつ、認識性能(mIoU)が高いことが分かります。論文は こちら (arXiv) から読めます。 引用元: PaddlePaddle/PaddleSeg (GitHub) / Apache License 2.0 モデル変換 大まかな流れ PP-MobileSegのPyTorchモデルをONNXモデルに変換します。ONNXモデルで移植性向上のためのノード編集を行なった後、ONNXモデルからSNPEのネイティブフォーマット(DLC形式)に変換します。 PyTorch から ONNX への変換 mmsegmentation リポジトリ v1.1.2 で 公開(2023年9月)されている PP-MobileSeg-Tiny の PyTorchモデルを利用します。はじめに、 Get started: Install and Run MMSeg を参考に環境構築を済ませておきます。 公式マニュアル に記載の手順にしたがって作業すれば、PyTorchモデルからONNXモデルに変換できます。 ONNX モデルの修正 ONNXモデルで使われている HardSigmoid オペレータ は、SNPEでサポートされないため、単純なオペレータの組み合わせに変換します。SNPEでサポートされるオペレータは 公式リファレンス から確認できます。 活性化関数に HardSigmoid が使われている。 https://netron.app/ で可視化。 まず、今回のノード編集で利用する ONNX の opset バージョンを確認しておきます。opset versionによってオペレータの定義が異なるため、入力モデルの opset バージョンに揃えることにします。以下のスクリプトはモデルを読み込み opset バージョンを表示します。 import onnx model = onnx.load(input_path) for opset in model.opset_import: print(f"Model opset version: {opset.version}") 今回使用するモデルの opset バージョンは 11 ということが分かります。 model opset version: 11 次に、HardSigmoid オペレータを変換します。HardSigmoid の定義は、入力を x 、出力を y として、 y = max ( 0 , min ( 1 , alpha * x + beta)) # alpha, beta はパラメータ なので、ONNXの Mul、Add、Clip オペレータ を用いて分解できます。 x1 = Mul(alpha, x) x2 = Add(x1, beta) y = Clip(x2, 0, 1) 以下のスクリプトは、Mul、Add、Clip を用いて、ONNXモデルのすべての HardSigmoid を消去します。13か所で HardSigmoid が使われていることが分かります。 from onnx import TensorProto, defs, helper count = 0 for pos, node in enumerate (model.graph.node): if node.op_type == "HardSigmoid" : count += 1 # Count HardSigmoid operator # Create Mul node and insert n0_name = node.name x0_name = node.input[ 0 ] alpha = node.attribute[ 0 ].f alpha_name = n0_name + "_alpha" alpha_tensor = helper.make_tensor(alpha_name, TensorProto.FLOAT, [ 1 ], [alpha]) y0_name = n0_name + "_mul" multiply_node = helper.make_node( "Mul" , [x0_name, alpha_name], [y0_name]) model.graph.node.insert(pos, multiply_node) # Create Add node and insert y1_name = n0_name + "_add" beta_name = n0_name + "_beta" beta_tensor = helper.make_tensor(beta_name, TensorProto.FLOAT, [ 1 ], [ 0.5 ]) add_node = helper.make_node( "Add" , [y0_name, beta_name], [y1_name]) model.graph.node.insert(pos + 1 , add_node) # Create Clip node and insert y2_name = node.output[ 0 ] min_name = n0_name + "_min" max_name = n0_name + "_max" min_tensor = helper.make_tensor(min_name, TensorProto.FLOAT, [ 1 ], [ 0.0 ]) max_tensor = helper.make_tensor(max_name, TensorProto.FLOAT, [ 1 ], [ 1.0 ]) clip_node = helper.make_node( "Clip" , [y1_name, min_name, max_name], [y2_name]) model.graph.node.insert(pos + 2 , clip_node) model.graph.node.remove(node) # Remove HardSigmoid node model.graph.initializer.extend([alpha_tensor, beta_tensor, min_tensor, max_tensor]) print (f "# hardsigmoid nodes: {count}" ) # Outputs 13 作成されたモデルを検証して保存します。 onnx.checker.check_model(model) # Validation onnx.save(model, output_path) # Save 変換後のONNXモデルを見ると、HardSigmoid オペレータが、Mul、Add、Clip オペレータに置き換わっていることが確認できます。 HardSigmoid が Mul/Add/Clip に変換されている。 https://netron.app/ で可視化。 ONNX から DLC への変換 SNPE SDKに含まれるツール を使って、ONNXモデルからDLCモデルに変換が可能です。モデル実行時の入力テンソルのサイズを固定します。 snpe-onnx-to-dlc --input_network model.onnx --input_dim "input" 1,3,512,512 推論結果 当社のAIネットワークカメラ(Safie One)にモデルをインストールし、推論を行います。クラウド経由でモデルをアップロードできます。 SafieOne 推論が動作する様子をWebアプリ(Safie Viewer)から確認します。ここでは人物クラスを水色で表示しています。人物の輪郭が抽出されていることが分かります。 人物が水色でセグメンテーションされている むすび セマンティックセグメンテーションのSOTAモデル PP-MobileSeg をAI ネットワークカメラで動かしました。ONNXモデルからHardSigmoid オペレータを除去し、DLCモデルに変換する方法を詳しく解説しました。最後に、AIネットワークカメラ上の推論結果をWebアプリから確認しました。 セーフィーではエッジAIを用いたプロダクトの開発を行っています!
Androidチームの品質改善の取り組みとして、今回はアプリサイズを計測した話をしたいと思います。 はじめに 動作環境 rulerとは rulerの導入方法 settings.gradleの編集 app/build.gradleの編集 analyzeタスクの実行 継続的に計測する ワークフロー 最後に はじめに 頻繁にアプリを更新しているとある日突然「アプリのサイズが肥大化している」と気が付く時があります。 新しいリソースファイルを追加した 新しいライブラリを導入した など原因は様々考えられますがこれがいつの時期から発生したのかを調べるのは一苦労です。 そうならない為にも今回は「ruler」を使用し継続的にアプリのサイズを監視できる仕組みを導入しました。 動作環境 Android Studio:Flamingo 2022.2.1 Patch 2 AGP:8.0.2 Gradle Version:8.0 ruler:1.4.0 rulerとは ruler とは、Spotifyが公開しているGradle pluginです。 アプリのサイズはもちろんのこと、各モジュールと依存関係がどれくらいアプリのサイズに影響を与えているかを レポートで出力することができるプラグインです。 rulerの導入方法 settings.gradleの編集 プロジェクト直下の「settings.gradle」を編集しrulerのプラグインを追加します。 pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() } //追加 resolutionStrategy { eachPlugin { if (requested.id.id == "com.spotify.ruler") { useModule("com.spotify.ruler:ruler-gradle-plugin:1.4.0") } } } } dependencyResolutionManagement { //省略 } 参考: https://github.com/spotify/ruler/issues/85 app/build.gradleの編集 app/build.gradleにrulerの設定を追記します plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'com.spotify.ruler' //追加 } android { //省略 } dependencies { //省略 } //追加 環境に応じて設定値は変更してください。 ruler { abi.set("arm64-v8a") locale.set("ja_JP") screenDensity.set(480) sdkVersion.set(33) } analyzeタスクの実行 ここまで上手くいくとGradleタスクに「analyze Bundle」のタスクが追加されています。 このタスクを実行するとアプリサイズを計測したHTMLとJSONのレポートが出力されます。 継続的に計測する いつアプリのサイズが肥大化したのか継続的に監視する為にrulerのタスクをGithub Actionsのワークフローに取り入れ 出力したJSONファイルを解析し内製のダッシュボードに記録ことで監視する仕組みを導入しました。 ワークフロー 以下実際に作成したものから抜粋したワークフローです。 Kotlin Script「sendAppSizeToDashboard.main.kts」でJSONを解析し結果を内製のダッシュボードに送信するという仕組みを作りました。 name: Output App Size Task on: push: paths: - 'app/**' branches: - main jobs: rulerJob: name: Exec ruler runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' cache: gradle - name: Make gradlew executable run: chmod +x ./gradlew - name: Run ruler run: ./gradlew analyzeStagingDebugBundle - name: Make scripts executable run: chmod -R +x ./.github/workflows/scripts #rulerの結果を内製ダッシュボードに送信するスクリプトを実行 - name: Send dashboard env: TZ: 'Asia/Tokyo' run: ./.github/workflows/scripts/sendAppSizeToDashboard.main.kts 計測したデータ 計測データを元に数値をグラフで可視化 最後に 今回の取り組みでは rulerを使用した継続的なアプリサイズの計測 という環境を作りました。 以下の記事によると「アプリのサイズはインストール率に影響している」との事なので、もし「アプリのサイズを計測したい」と思った際に本記事が少しでも参考になれば幸いです。 developer.android.com また、セーフィーではエンジニアの採用を積極的に行っております。もし興味が出てきた際はぜひご応募いただけたらと思います。 safie.co.jp
今回は直近のAndroidチームの品質改善の取り組みとして、ユニットテストを導入した件についてお話したいと思います。 はじめに 実際にやった事 ユニットテストを書く どこからユニットテストを書くか 技術スタック JUnit4 + Mockito-Kotlin + Truthを使用したテストコード カバレッジ率の可視化 Jacocoの設定 Jacocoの出力 CIで自動でテストが実行される環境 導入したアクション ワークフロー 最後に はじめに Android版Safie Viewer for Mobileは開発が始まって3年が経過しようとしています。 3年間、継ぎ足しを重ねて作成してきたコードはもうじき5万行を超える大きさになりました。 Androidチームは直近までプロジェクトのメンバーが少なく、機能を実装してリリースする事で精一杯になっていました。 なんとかリリースまで辿り着けているものの、目の前には日に日に複雑になっていくコードがあるというのが現実でコードのリファクタリングまで手が回らずメンテナンスする事ができない状況に陥っていました。 この状況はAndroidチームとしても今後の課題となって行く事が目に見えていたため リファクタリングの前段階として、既存コードが設計通りに動くかの保証を目的としたユニットテストを導入する事にしました。 実際にやった事 主な取り組みとして以下の3点を行いました ユニットテストを書く カバレッジ率の可視化 CIで自動でテストが実行される環境構築 ユニットテストを書く どこからユニットテストを書くか Android版Safie Viewer for Mobileのアーキテクチャは 公式 が推奨しているような UI Layer Domain Layer Data Layer の3層に分け、責務を分離する事を心がけ作られています。 幸いにもこのルールが守られている事もあってか、レイヤ間が疎結合にはなっておりユニットテストが書ける土壌にはありました。 アーキテクチャの図(公式より引用) しかしながら、土壌はあったもののAndroidチームとしてこれまでユニットテストを書くという文化が無かったので いきなりActivity/FragmentやViewModelなどの画面に近い箇所のテストコードを書くのはハードルが高いという事もあり 画面から遠く機能としても孤立しているData Layerからテストを書き始め、徐々に上のレイヤーに向かっていくという方針にしました。 技術スタック 今回、ユニットテストを書くために導入した技術スタックは主に以下になります。 JUnit4 Androidのプロジェクト作成時に既に導入されているテストフレームワーク Robolectric Android依存部分のテストを行うためのライブラリ Mockito-Kotlin モックライブラリ Mockito をKotlinでも使いやすくしたライブラリ Truth アサーションライブラリ Jacoco テストカバー率の測定 JUnit4 + Mockito-Kotlin + Truthを使用したテストコード 記述例として、下記のようなAndroidに依存しないData Layerのクラスを用意してテストコードを書きます。 package com.safie.test.data import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext interface EmployeeService { /**従業員IDの最大値を取得*/ fun getMaxId(): Int } class EmployeeRepository( private val service: EmployeeService, private val dispatcher: CoroutineDispatcher ) { /** * IDの最大値にプラス1した、新しい従業員IDを取得 * @return 従業員ID */ suspend fun getNewId() = withContext(dispatcher) { val maxId = service.getMaxId() return@withContext maxId + 1 } } 上記のようなクラスに対しては次のようなテストコードを書くことができます。 import com.google.common.truth.Truth import com.safie.test.data.EmployeeRepository import com.safie.test.data.EmployeeService import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Test import org.mockito.kotlin.mock import org.mockito.kotlin.whenever class EmployeeRepositoryTest { @Test fun testGetNewId() = runTest { val mockEmployeeService: EmployeeService = mock() val dispatcher = StandardTestDispatcher(testScheduler) val employeeRepository = EmployeeRepository(mockEmployeeService, dispatcher) //getMaxId()を実行した時、10を返すように設定 val mockMaxId = 10 whenever(mockEmployeeService.getMaxId()).thenReturn(mockMaxId) //maxIdにプラス1した値が返却される事を検証 val id = employeeRepository.getNewId() Truth.assertThat(id).isEqualTo(mockMaxId + 1) } } カバレッジ率の可視化 ユニットテストを書き続けるというモチベーションを維持するのはとても大変です。 そのモチベーションを維持する取り組みとして テストコードによってどの行が実行されたか 各クラスのカバレッジ率は何%か の計測結果をレポートとして出力するためにJacocoの導入をしました。 Jacocoの設定 以下のQiitaの記事を参考にJacocoの設定を行いました。 Android開発のテストカバー率取得にはこのツールを使い分けると良いという話 #Android - Qiita apply plugin: 'jacoco' jacoco { toolVersion = "0.8.9" } android.applicationVariants.all { variant -> def variantName = variant.name.capitalize() //ex. ProdDebug def realVariantName = variant.name //ex. prodDebug if (variant.buildType.name != "debug") { return } task("jacoco${variantName}TestReport", type: JacocoReport) { dependsOn "test${variantName}UnitTest" group = "testing" description = "Generate Jacoco coverage reports for ${realVariantName}" reports { xml.required = false html.required = true } //無視するファイル(excludes)の設定を行います def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', 'android/**/*.*', 'androidx/**/*.*', '**/Lambda$*.class', '**/Lambda.class', '**/*Lambda.class', '**/*Lambda*.class', '**/*Lambda*.*', '**/*Builder.*', '**/*Activity*.class', '**/*Fragment*.class' ] def javaDebugTree = fileTree(dir: "${buildDir}/intermediates/javac/${realVariantName}/compile${variantName}JavaWithJavac/classes", excludes: fileFilter) def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/${realVariantName}", excludes: fileFilter) def mainSrc = "${project.projectDir}/src/main/java" getSourceDirectories().setFrom(files([mainSrc])) //Java, Kotlin混在ファイル対応 getClassDirectories().setFrom(files([javaDebugTree, kotlinDebugTree])) getExecutionData().setFrom(fileTree(dir: project.projectDir, includes: [ '**/*.exec', //JUnit Test Result '**/*.ec']) //Espresso Test Result ) } } Jacocoの出力 設定したJacocoのタスクを実行することで以下のようなHTMLのレポートが出力され テストコードによってどの行が実行されたか 各クラスのカバレッジ率は何%か を確認をすることができます。 CIで自動でテストが実行される環境 Android版Safie Viewer for MobileではCIにGithub Actionsを採用しております。 PullRequestのイベントをトリガーにテストを自動で実行し、テストの実行結果と現在のカバレッジ率を表示するGithub Actionsのワークフローを用意しました。 導入したアクション ユニットテストの結果とカバレッジ率を表示する為に導入したアクションは以下になります。 action-junit-report Junitで出力したテスト結果のレポートを表示するワークフロー jacoco-reporter Jacocoで出力されたXMLをインプットに結果を表示するワークフロー ワークフロー 今回の取り組みで実際に用意したワークフローは以下の通りです。 name: PullRequest Tasks on: pull_request: paths: - 'app/**' branches: - main jobs: UnitTestJob: name: Android Staging Unit Test runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v3 - name: set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' cache: gradle - name: Make gradlew executable run: chmod +x ./gradlew - name: run UnitTest StagingDebug run: ./gradlew jacocoStagingDebugTestReport - name: JaCoCo Code Coverage Report id: jacoco_reporter uses: PavanMudigonda/jacoco-reporter@v4.8 with: coverage_results_path: ${{ github.workspace }}/app/build/reports/jacoco/jacocoStagingDebugTestReport/jacocoStagingDebugTestReport.xml coverage_report_name: Coverage coverage_report_title: JaCoCo github_token: ${{ secrets.GITHUB_TOKEN }} skip_check_run: false minimum_coverage: 40 fail_below_threshold: false publish_only_summary: false - name: Add Coverage Job Summary run: echo "${{ steps.jacoco_reporter.outputs.coverageSummary }}" >> $GITHUB_STEP_SUMMARY - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: cancelled() != true with: report_paths: '**/build/test-results/*/TEST-*.xml' このワークフローが実行されるとPullRequest上でテストの結果や現在のカバレッジ率を確認することができるようになります。 ワークフローの実行結果 jacoco-reporterの結果 最後に 今回行った取り組みは ユニットテストを書く カバレッジ率の可視化 CIで自動でテストが実行される環境構築 だけの最低限の環境を作っただけにすぎず、これだけではチームにユニットテストを書いていく文化が広がっていきません。 しかしながら、ユニットテストを作る環境を導入した事は開発チームにとって重要な足がかりとなります。 今後は、チーム全体でテストの重要性を認識しつつユニットテストを書く事が当たり前の文化を作っていく取り組みをしていきます。 また、セーフィーではエンジニアの採用を積極的に行っております。もし興味が出てきた際はぜひご応募いただけたらと思います。 safie.co.jp
こちらは Safie Engineers' Blog! Advent Calendar の18日目の記事です。 こんにちは。セーフィー株式会社でバックエンドエンジニアをしている河津です。 私たちの会社は2023年10月27-28日に、PyCon APAC 2023への出展を行いました。この記事では、出展までの準備や当日の様子について紹介したいと思います! PyConとは 用意したデザインアイテム 当日の様子 出展デモ Safie One Safie Pocket2 Plus Safie Connect 撤収と振り返り PyConとは プログラミング言語「Python」を中心としたボランティアによる非営利の年次カンファレンスです。 セーフィーは前回開催された PyCon JP 2022 から出展していますが、2023年はなんと10年ぶりの国際カンファレンスとなる PyCon APAC 2023 が日本で開催されることになりました。 2023-apac.pycon.jp PyCon APACの運営チームは、アジア太平洋地域における国または地域が主体となり、現在では、シンガポール、マレーシア、インドネシア、フィリピン、タイ、韓国、香港、ベトナム、日本、台湾、インド、バングラデシュが毎年交代して開催されるそうです。 世界中のPythonエンジニアにセーフィーの存在を認知いただくチャンス!ということで出展準備を進めていきました。 用意したデザインアイテム 前回出展時の反省点の一つに、ブースに来ていただいた方々に会社のことを知っていただくためにお見せする資料などの用意が不十分だったというものがありました。 商品説明などは販促用パンフレットなどを使うことができますが、来場するエンジニアの方々が興味ありそうなシステム構成や技術スタック、どのあたりにPythonが使われているの?などの技術的な質問に対して、口頭でしか回答できないというもどかしさがありました。 そのため今回は、システム構成や技術スタックなどを説明するためのA1サイズのパネルやパンフレットを準備しました。見やすい内容となるようにデザイナーの方々にデザインいただきました。 A1パネル パンフレット めちゃくちゃ素敵なデザインとなりました!このパネルとポスターがあればセーフィーすごいと思ってもらえるはず。 今回は国際カンファレンスなので海外のエンジニアの方々も多く来場すると予想し、英語バージョンのものも用意しました。 これで準備完了です。 当日の様子 展示前日の10/26日に荷物を搬入しブース設営しました。設営担当やブース担当などは社内のたくさんのエンジニアメンバーにご協力いただきまして、事前に対応シフトを組んで臨みました。 設営完了。いい感じです! あとは展示会場には出展企業が自由にメッセージを残せるボードがあるのですが、こちらに採用募集ポスターを掲載しておきます。 せっかくなのでイラストも描きました。パンフレットやブースにもフクロウ要素が入っているので、フクロウの会社と覚えてもらえればいいなという思いも込めております。 当日は国際カンファレンスなだけあり、海外からの来場者が多い印象でした。ある程度は予測していましたが、特に初日(10/27)は英語で話をされる方が日本人よりも多い印象でした。 ブースでサービス説明や技術要素の説明などの対応を行います。 出展デモ ブースに来場いただく方にセーフィーのことを知っていただくには、動いているプロダクト・サービスを触っていただくのが一番良いと考えまして、いくつかのカメラをブースに設置しました。 Safie One エッジAIアプリが入った屋内用のカメラ。 safie.jp 三脚を持参し少し高いところから撮影し、ブースに来場いただいた方々の人数をカウントする試みをしてみました。 実際に2日間でカウントされた結果がこちらです。人数は伏せていますが、時間帯によって来場者数の推移が見て取れます。初日の14時ごろが一番来場者が多いようですね。(ちなみに初日は平日でした) この試みはブースに来ていただいた方々にも一定ウケまして、セーフィーが提供しているプロダクトについて、映像の視聴だけでなくデータを使ったソリューションという観点で、一番イメージいただけたものになったのではないかと思います。 Safie Pocket2 Plus 胸ポケットにつけるなどして持ち運びが容易なウェアラブルカメラ。 safie.jp 会場の様子が遠隔でもわかるということを示すために、展示中たまにこちらのカメラを胸につけて、他社様のブースなどに行くなどしていました。 他社様のブースで話を聞いてる際に、「その胸につけてるものはなんですか?」と聞かれることが多く、そのままセーフィーの紹介とブースへの誘致を行うことができたので、話の接点を持つのにも一役買ったカメラでした。 Safie Connect HDMI出力された映像をセーフィーのクラウドにアップロードしどこでも映像を見れるようにできるルーター。 safie.jp 展示にはドローンごと持参し、ドローンの映像を遠隔で視聴するデモを行いました。ドローンを展示することで来場した方々にも覚えてもらいやすくなったのではないかと思います。 撤収と振り返り 社内のいろんな方のご協力のおかげで2日間のブース出展を無事に終えることができました。最後に荷物を梱包し直して撤収します。お疲れ様でした! その後の社内での振り返りとしては、 デモの内容が前回出展時よりもウケが良かったように感じた パネルを用意したため説明がしやすかった ブース担当者は椅子に座らず立つオペレーションだったため、来場した人に声をかけやすかった。結果ブースが賑やかだったと言われた などが良かった点として挙がりました。 一方で、 デモ用のディスプレイはもっと目立つように大きいものを準備すれば良かった 英語による配布物をもっと充実させておけば良かった もう少し企画っぽい出し物もあれば認知に貢献したかもしれない(他ブースでやられていたくじ引きやクイズなど) などの反省点なども挙がったため、次回出展する際には検討できるようにしたいなと思っております。 セーフィーでは継続的にエンジニアへ向けた認知拡大活動を行なっており、各種イベントへの出展・登壇や、このテックブログへの記事執筆もその活動の中の一つです。 今後も継続的な発信を行なっていきますので、よろしくお願いいたします。 セーフィーではプロダクト開発に関わる職種を積極採用中となりますため、興味を持たれた方はぜひ会社概要や募集職種なども見てみてくださいませ! speakerdeck.com open.talentio.com
メリー・クリスマス、セーフィーCTOの森本です。 こちらは Safie Engineers' Blog! Advent Calendar の25日目のエントリーです。 時間の経つのは早いもので、当社も少し前まで数十人でバタバタやっていたように感じていますが、それが今では400人を超え、いよいよ創業10年目に突入しました。 まだまだやりたいこと、やらなければならない事が山積みでそのために更に大きく成長していく必要も感じていますが、現在の姿も過去からの積み重ねですので、今後を考える上でもまずは年末のこのタイミングで今までの歴史を振り返ってみたいと思います。 はじめに 暗黒時代 エンジニア数〜10名 2014年〜2016年 2017年〜2018年 闇からの夜明け エンジニア数〜30名 2018年〜2020年 未来への飛躍 エンジニア数30〜100名 2021年、2022年 2023年 今後に向けて まとめ 最後に はじめに 上記は創業してから今日までのエンジニア人員数、対応カメラの台数、主要プロダクトを表した図です。 カメラ台数は2015年のサービス開始から2019年あたりまで中々伸びず苦しい時期を過ごしましたが、2019年頃からカメラ台数が一気に増加、 2021年〜2022年に一時的に成長が鈍化し、2023年から再度成長軌道に乗っています。 エンジニア人員数はと言うと、上記に合わせたわけではありませんが、同様に2019年ごろまで極めて少ない体制でやっており、少しづつ増加したものの大きな伸びはなく、2022年頃から大きく増員しています。 尚、全体に渡ってコンスタントにプロダクトを出せていますが、チャレンジングなプロダクトが出せるようになったのは2018年からという感じになっています。 engineers.safie.link 暗黒時代  エンジニア数〜10名 2015年5月に満を持してサービスを開始しましたが、中々カメラが売れずビジネス面で苦戦、且つプロダクトの品質もイマイチな状況でとにかくもがき苦しんだ時期でした。 本当に二度と戻りたくないです。 開発体制としてはエンジニア数名〜十名程度なので組織という概念も無く全員で日々のバグ修正や改善、新機能開発を同時に進めていました。 必然的にリポジトリはアプリケーション、バックエンド/インフラ、デバイスには分離しているものの、それぞれが単一のリポジトリで構成されていました。 当時はスピード感という観点でこの状態が最もやりやすかったのは事実です。後々この構成がボディーブローのように効いてきますが。 新機能開発も完全にエンジニアドリブンで、ほぼエンジニアだけでプロダクト開発を進めていました。 上記の通りの品質でしたが、とにかく出すことを優先して活動していました。 2014年〜2016年 創業メンバー2名(プラス業務委託2名程度)で全領域を回していました。 全システムや仕様が頭には入っていたのでテストの仕組みも薄い感じで、簡単に正常系と異常系をテストしてリリースしていました。(今だと考えられないです) 2017年〜2018年 ようやくエンジニアが増え始め、各領域に担当者を立てる事が出来ました。 またバックエンド/インフラは複数人体制(と言っても2名だが)が取れるようになりました。 闇からの夜明け  エンジニア数〜30名 2018年〜2020年 プロダクト、サービス品質の安定化、機能拡充、新商品の提供により急激にカメラの販売台数が伸び始めました。 エンジニア数の増加、プロジェクト数の増加に伴い、問題が発生するプロジェクトも散見されるような状態となり、エンジニアで全てを回していく体制から脱却し、PM/PdM専任者の配置を行いプロジェクトの安定化を図りました。 エンジニアも全員で保守運用や基本サービスの拡充と新プロダクト、プロダクトの開発を同時に行う体制から、分離して開発に当たる体制へ移行していきたいと組織変更を行いました。 エンジニアの総数が圧倒的に少なかったので、結果的に職能で分かれるような形になってしまいリポジトリは今までの構成をそのままキープして進めていました。 残念ながらサービス終了となってしまったものもありますが、様々なプロダクトを上記体制で世に送り出してきましたが、よくやれたなと、驚き、反省とともに振り返っています。 SafieVisitors(サービス終了済み) SafieEntrance(サービス終了済み) 未来への飛躍  エンジニア数30〜100名 2021年、2022年 事業成長とともに2021年の9月にIPOを行いました。 ビジネスが順調に成長し、様々な業界の顧客課題の深堀りを進める中、業界毎に課題が異なる為画一的な対応では厳しく、業界ごとに対応を分けていく必要があるということを痛感しました。 元々会社規模が拡大する中で、職能型組織では今後のスケールに耐えられないのではという思いもあり、2022年BU制度を採用し、業界軸に沿った組織体制に変更を行いました。 各BUにはPM/PdM、エンジニアも配置し、それぞれが単独で顧客とする業界向けのプロダクト開発を進めて行けるようにとの狙いがありました。 同時に認証基盤、デバイス管理基盤、録画配信基盤など共通部分はPUとして横串で配置することにより、業界ごとのプロダクト開発を進めつつ、基盤部分の強化を並列で進めたいという想いもありました。 例によってエンジニア数は十分ではありませんでしたが、採用の強化で対応して行くという事も考えていました。 残念ながら以下ような課題があり、組織として再検討が必要な状態となってしまいました。 採用が思ったとおりに進まずエンジニア数が不足したままとなってしまいました。(以下の理由により比較的経験のあるメンバーしか採用出来なかった事も原因だと感じています。) 組織や担当プロダクトを分割したものの、プロダクトの根幹にあるシステムやリポジトリが適切に分離されておらず、結局複数の組織が同じリポジトリを扱う状態が変わっていませんでした。 複雑なシステムが影響して新規参入メンバーのオンボーディングやキャッチアップに多大な時間がかかってしまう状態となっていました。 システム間の連携の複雑さに起因する細かい障害も度々発生してしていました。(大きな障害に繋がらなかったのは流石と思っています。) BU制への移行を通じて、今まで成長の影に隠れていた各種問題が一気に噴出したと感じました。上記の状態を招いてしまった事を反省しつつ、同時に今後の更なるスケールを実現するためにはこれらの課題解決が必須であるとも強く感じました。 2023年 サービス、事業は上記のような組織の急拡大や変更に伴う課題を抱え踊り場を迎えつつも伸長していました。 しかし組織としての課題を放置するわけにも行かず、開発組織は一旦2021年以前の状態に戻すことにしました。 勿論当時と比べエンジニア数そのものが増加していますし、業務システムやAIアプリケーション開発、データ分析を受け持つエンジニアもいましたので、規模そのものが大分大きくなっていました。 また、ただ戻す事はせず組織課題を解決し、更なる成長を目指すためにエンジニアリングオフィスを設置しました。 エンジニアリングオフィスにはEM陣にも兼務してもらい、以下の取組を進めて行きました。 採用、育成強化(イベントなど認知向上活動も) オンボーディングコンテンツ整備 エンジニアスキルテーブル、キャリアパスの整備 その他組織課題全般 qiita.com また、同時にプロダクト開発に加えシステムの強化、改善も明示的にロードマップに上げ取り組みを進めました。 本記事執筆中の現在、上記の取り組みにより開発組織は安定を取り戻し再度アクセルを踏める状態に推移出来てきたと感じています。 今後に向けて 2022年の踊り場を脱却し、事業は再成長軌道に乗って来たと実感していますが、将来の更なるスケールを実現するためには、BU制移行で露呈した課題の解決が必須だと感じています。 現状のシステムは、結局過去のシステム、リポジトリ構成を多少の修正をしつつ維持してしまっている所が多く、巨大、複雑すぎるために学習効率は元より開発効率や信頼性にも影響を与えてしまっています。また単一で分割に適していない為、組織を分割したとしても結果的に一つのシステムを複数の組織で同時に扱う事となり効率化が進みません。 これを抜本的に解決しなければ、職能型組織からの脱却や開発組織の効率化、拡大など将来の大きなスケールが実現できません。 システムの刷新を行い巨大なシステムを分割し、それぞれの依存度をできる限り小さくすることにより、分割した組織でもそれぞれに対応したシステムを集中して扱う事が出来、開発効率の改善が実現出来ると考えています。 2024年は上記の システムの刷新 対応を進め、確実に一つ以上の実績を出すことを目標に進めて行きます!! システム刷新については以下の記事でも触れられています。 engineers.safie.link まとめ こうやって整理し直してみると、色々な事があったなと感じるのと前もって分割可能なシステムを想定して作っておけばよかったと感じるところもあります。 勿論当時は一刻も早くプロダクトを世に送り出しビジネスとして成立する状態を作る事が最優先だったので、それを同時に行う事は簡単ではありませんでした。 会社には成長段階でそれぞれのステージが存在し、それぞれに適した状況があるというのも理解しており、最初から全てを見越して準備しておくことが今の成長に繋がったのかという疑問もあります。 いずれにせよ大きな理想は描きつつ、失敗も繰り返しながら着実には進化してきていると感じています。 ただ、眼前には今後に向けて超えなければならない システムの刷新 という大きな壁が立ちはだかっています。 最後に システムの刷新 は難易度が非常に高い取り組みですが、今後の成長に向けて我々の意志は決まっています。 一緒に取り組んでくれる人を絶賛募集していますので、興味が有る方はご応募よろしくお願いします!!!! https://safie.co.jp/teams/
はじめに セーフィー株式会社でバックエンド開発のテックリードをしております鈴木敦志と申します。セーフィー株式会社は創業から9年経過し、サービスの拡大と開発者の増加に伴う開発生産性の問題に直面しています。この問題の解消のため、職能横断型チームの再編成とコードベースの分割によるマイクロサービス化を進めています。 セーフィーのサービス・チーム構成 セーフィーのクラウドサービスはカメラ管理および映像閲覧のほか、業務システム、AI応用サービス、開発者向け機能および入退館管理システムなどで構成されており、サーバー/Webの開発者のほかモバイル/組み込み/QAエンジニアなど様々な職種が関わっています。チームは職能別に分かれており、チームごとに複数のプロジェクト (開発案件) に参加します。 開発チーム構成 開発組織の規模が増えるにつれ、様々な問題が明らかになってきました。 機能追加・改修にはたいていWeb・サーバーなど複数職種が関わってくるため、開発に伴い複数チーム間の調整が頻繁に発生します。各チームは複数プロジェクトを並列で掛け持ちすることになり、開発リードタイムの増加と認知的負荷の増加に繋がっていました。 特にバックエンド開発において、モノリシックなサーバーコンポーネントを複数チームで共有しているためコード変更やデプロイ作業の競合が頻繁に発生していました。こういった状況に対処するためにコードベースの分割が進められていましたが、各コンポーネントのオーナーシップが不明確であるためにいわゆる分散モノリスの状態になり改善には繋がりませんでした。 これらの問題を抜本的に解消するため、職能横断型チームの再編成とマイクロサービス化に取り組んでいます。 マイクロサービス マイクロサービス とは、個別にデプロイ可能なサービスの集合体としてシステムを構成することで多人数による並列開発を効率化する手法です。今回は下記方針でマイクロサービス化およびチーム再編成を進めていきます。 マイクロサービス境界を決定: マイクロサービスの導入にはどのようにサービス境界を定めるかが重要になりますが、今回はビジネス機能により分割し、必要以上に細かく分割しないようにしています チームおよびサービス境界の分割 チームの再編成: 各チーム内で開発業務が遂行できるよう企画・QA含め機能を集約し機能横断チームを編成します コンポーネントの分割と内部APIの整備: ソフトウェアコンポーネントの機能がマイクロサービス境界をまたいでいる場合、コードベース・デプロイ単位・DBを分割します。境界をまたいで機能提供を行う場合、gRPC等で内部APIを整備します。 モジュラーモノリスの検討 マイクロサービスに関連する手法としてコードベースおよびデプロイ単位は単一のままモジュールのオーナーシップを明確化するモジュラーモノリスも知られており、マイクロサービス移行の前段階として、あるいはマイクロサービスの複雑さを回避しつつ大規模開発を行うために用いられます。弊社においてはモジュール間参照の規律を保つのが難しいと判断したため、マイクロサービスを選択しました。 既存コードベースのマイクロサービス化 既存コードベースのうち特にバックエンドのソフトウェアはAPIサーバー、デバイス接続サーバー、タスクキュー、バッチサーバー等多数のサーバーで構成され、いわゆるモノリスあるいは分散モノリスとなっています。これらのソフトウェアの機能を保ったままマイクロサービス化を進めるため、以下の手順を取ります。 既存コードベースのうちマイクロサービス境界をまたぐものをチームごとにフォーク 自チームの担当範囲外の実装範囲外を削除またはプロキシに置き換え DBへの接続権限を変更し担当範囲外のテーブルへの書き込みを禁止 マイクロサービス境界をまたぐ機能提供をAPIプロキシまたはDBの参照からgRPC等の内部APIに置き換える DBサーバーを分割 例: カメラ管理と契約・決済情報の分離 場合によっては単純なAPI分割だけではサービスの分割ができず、再設計が必要な箇所があります。 例として、セーフィーではカメラごとに顧客とSaaS契約が締結され、契約プランにより使用できる機能が異なります。契約プランはフロントエンドチームの内製する管理ツールにより操作され、また社内の業務システム (Salesforce, Zuora等) と同期されています。チームをまたいだ複雑なデータの同期が行われ、またどこの情報が元データなのかがわかりづらくなっていました。 現在サーバー内では契約情報とそれにより有効化される機能が単一のRDBテーブルで表現されていますがこれを分離し、カメラの機能有効化API/ライフサイクル管理APIを業務システムチームに提供する形で連携することで、契約情報の一元管理を実現します。 まとめ セーフィーにおけるサービス規模・開発組織規模の増大に伴う開発生産性の問題に対応するため、既存システムのマイクロサービス移行を進めています。 現在セーフィー株式会社ではソフトウェアエンジニアを採用中です。マイクロサービスアーキテクチャや開発生産性の向上についてご興味のある方はぜひともご応募ください。 https://safie.co.jp/teams/