TECH PLAY

セーフィー株式会社

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

246

この記事は Safie Engineers' Blog! Advent Calendar 2022 16日目の記事です。 こんにちは、開発本部 モバイルグループの池田です。 2016年入社以来、セーフィーのWebフロントエンドやモバイルアプリの開発に関わり、近年は Safie Viewer for Mobile ( iOS , Android )の開発チームリーダーとして、そして今年の5月ごろからは同プロダクトのPdM(プロダクトマネージャ)という立場でも活動しています。 この記事では、エンジニア出身のPdMがセーフィーのモバイル開発においてどのような取り組みをして、またどんな課題に直面していたのかを振り返ってみたいと思います。 セーフィーにおけるPdMの役割 B2B SaaSとモバイルアプリとPdM 今年の主な活動とアウトプット デザイナーとのチーム組成とFigma導入 VoC収集 ユーザーテスト UX改善リリース Map Viewerデモアプリ作成とPoC(進行中) まとめと課題 おわりに セーフィーにおけるPdMの役割 執行役員の植松さんと及川卓也さんの対談記事などでも触れられているように、セーフィーには多様なプロダクトが存在し、それぞれの開発プロジェクトやフェーズによってPdMの関わり方もさまざまです。 www.kandc.com セーフィーではターゲット業界ごとに区切られたビジネスユニットと呼ばれる各組織にプロダクト企画チームが散在しつつ、PdMオフィスというバーチャル組織としてPdM全員での情報共有を行っています。 私も開発組織に身を置きつつこちらにも所属させてもらい、メンバー育成のための座談会などにも参加するなど、ずっとエンジニアだけでやってきた身としては多くの刺激と学びがありました。 note.com そのようなPdM組織と開発チームを行き来しつつ、Safie Viewer for Mobile のプロダクトマネジメントを行なっていきます。 具体的には、機能開発の優先度設定、ユーザーニーズ調査、ロードマップ作成、技術的意思決定、開発進捗管理、品質と納期のマネジメント等々ですが、人によってはPdMの領域では無いと感じるものが含まれているかもしれません。 現時点で、私は開発チームも持ちながらPdMをやらせてもらっているため、 モノタロウさんの記事 にあった PdM兼任EM という動き方を意識しています。 tech-blog.monotaro.com 当初、CTO森本からモバイルのPdMどうですか、という話を受けたときには、 「まぁプロジェクト内にPM(Project Manager)もおるし、やることそんな変わらんやろ」 と軽く考えていましたが、PdMというものについて調べるほどにそこに求められる能力、資質におののくばかりでした。 https://ninjinkun.hatenablog.com/entry/the-product-management-triangle-ja PdMについてぐぐると必ず出てくる三角形。これらをバランス良く備えることがPdMには求められるそうです。 もちろん自分はそんなスーパーマンではないので、そこはチームの力をしっかり頼ろうと思いつつ、 現時点で自分としては、PdMとは「 プロダクトの方向性を示し、その成長に責任を持つ人 」という捉え方をしています。 B2B SaaSとモバイルアプリとPdM セーフィーはクラウド映像プラットフォームを提供しており、その大多数のユーザーは企業内での業務において、いわゆる B2B SaaS としてセーフィーのwebやアプリを利用しています。 さまざまな業界にその利用が広がっているセーフィーでは、カメラを1台だけ設置している個人ユーザーから、数百台のカメラが見えるチェーン店の管理者まで幅広いユーザーがサービスを利用しており、利用されるカメラデバイスのモデルも数百種類といった規模になってきています。 モバイルのシンプルさを保ちつつ、いかに多様なカメラ機能を利用するか、そのためにどういう取捨選択をするか、という課題は常に悩ましい問題です。 またB2B SaaSという点では、つい先日「 SaaSの解約理由の半数超が『操作性が良くない』 」というニュースがありましたが、企業向けSaaSにおいてこそ使い勝手は非常に重要です。 凝ったUIよりも、最速で見たいカメラ、見たい映像に辿り着けるわかりやすさと快適さがないと、あっさりと競合にその座を渡してしまうことになります。 またこれも(おそらく)B2Bあるあるとして、案件の多くが「 とりあえずPCで 」実現することのみを考えており、モバイルは後付けで話がやってくることが多いです。 そのあたりも社内外のステークホルダーとうまく連携し、「最初から」モバイルはどうあるべきかを一緒に考えていけるように、自分とチームのプレゼンスを上げていく必要もあると感じています。 ところでモバイルに関わるエンジニアであれば、 「シンプルでモダンで洒落たUIで、手触りがよくて驚きがあってユーザーに愛される、毎日思わず起動したくなるアプリ」 を、つくりたいと思う方は多いのではないでしょうか。少なくとも私はそう思います。 しかしながら、セーフィーが提供するサービスは(業務効率化やマーケティングにその用途が広がってきているとはいえ)やはりまず第一に「防犯・監視」、すなわち安心・安全を得ることにその目的があります。 設置した防犯カメラをアプリで毎日チェックするより、「今日も問題ありませんでした」「昨夜2:00の映像だけ確認が必要です」と、端的に教えてもらえるならその方がいいですよね。 企業生産性に寄与するB2B SaaSにおいては、アプリ利用時間やエンゲージメント率といったメトリクスが大きいことが単純に良いとは限らず、これもまた注意が必要です。 こういった多くの相反するニーズを理解し、プロダクトのビジョンすなわち「 誰の、どのような課題を解決するのか 」を示し、チームを実行に導くことが、PdMには求められます。(難しい。。。) 今年の主な活動とアウトプット PdMという立場ではなかった時期のものもありますが、モバイルアプリで今年行ったいくつかの活動・施策について振り返ってみます。 デザイナーとのチーム組成とFigma導入 1年ほど前、セーフィーのモバイル開発PJには専任のデザイナーがおらず、各案件から新規機能や画面の要件が発生すれば個別に画面案が出てくる、という状態でした。 これを「 開発チームと一緒にUX改善についてトライ&エラーができるメンバーが欲しい 」と、新たにデザインセンターにアサインをお願いし、改めてチームビルディングを行いました。 デザイナーはエンジニアチームと共に毎朝のデイリーmtgやレトロスペクティブ(振り返り)にも参加いただくなど緊密に連携してもらい、 開発・デザイン・企画そしてQAメンバーも交えてUX改善について議論する定例を設定し、 新規・既存画面によらず「より使いやすい」「よりわかりやすい」UIを目指して課題と改善案を出す活動を続けています。 またその過程でデザインツールを Figma に移行しました。 それまで案件ごとの画面仕様は都度 Adobe XD で共有され、またもっと抽象的なUX議論においては Miro を使っていたりしたのですが、えいやと 全画面 をFigmaに移行していただきました。 工数的に迷ったところもあったのですが、結果的にこれは非常に良い選択でした。デザイナーの尽力のおかげでiOS/Androidそれぞれの画面仕様がすっきりと整理され、開発とデザイナーだけでなく、QAとのコミュニケーションも楽になりました。また、デザイナーとしてもトレンドトップになりつつあったツールの経験が得られたことは良かったようです。(その後のAdobeによる買収はビックリしましたが) Figma導入については以前にモバイルチームの渡部さんも記事を書いてくれています。 engineers.safie.link VoC収集 もっとモバイルのユーザーとユースケースを知ろう、ということであらためて社内外にヒアリングを行いました。 しかし結論から言うとこれはまだあまりうまく進んでいません。 ここで拾えた営業メンバーの声をもとにリリースした改善があったり、一部お客様から現場のモバイル/タブレット利用について聞けたりはしましたが、アナリティクスにあらわれる数字(webよりモバイルの方が多い)ほどには、まだその利用シーンやユースケースの分布が掴めていない状態です。 いくつかの仮説はありますが、ここについては来年度にかけて、今年新たに立ち上がったデータ分析チームやUXリサーチチームとも連携して、さらにモバイルユーザーの解像度を上げていきたいと考えています。 note.com ユーザーテスト 主に新入社員をターゲットに、アプリをまだ使ったことのない人にユーザーテストを不定期に開催しています。 ユーザーテスト体験中の新参メンバー 「XXというカメラを見つける」 「XXさんの出社した映像を見つける」 「XXさんが冷蔵庫を開けたシーンをクリップ保存してDLする」 など、こちらの用意したシナリオに沿ったミッションにチャレンジしてもらいます。 そのアプリを使う様子をまたSafie Pocket2で録画して、見えてきた使いにくさやわかりにくさの課題に対して、仮説と解決案をチケット化して少しずつ改善に含めていきました。 UX改善リリース 上記のような活動を通じてタスク化した改善を多数リリースしてきました。 設定画面など目立たないところでも「わかりやすい」「使いやすい」を意識した変更が多く含まれています。 この他にもいろいろいろいろ Map Viewerデモアプリ作成とPoC(進行中) Flutter を使って、最低限のマップ機能を持つデモアプリを作成しました。 GPS搭載ウェアラブルカメラであるSafie Pocket2の発売以来、Safie Viewer(Web)の位置情報連携(マップビューアー)が進化してきました。 先月にはついに GPS非搭載のカメラも手動で位置情報が設定できるように なり、その利用範囲はますます広がっています。 モバイルでもカメラのGPS情報からマップアプリを開くことはできますが、それ以上のマップ対応も当然進めたいと考えています。 しかし、 実際にモバイルでマップ表示や軌跡表示を行うニーズが不明確 現状のViewer開発と並列で追加実装しながらUIの検証はかなり重い そもそも工数がない といった課題があって足踏みしていたところ、 「じゃあFlutterでマルチプラットフォームの別アプリで最速MVP作って検証しよう」 と、半ば勢いで宣言したところもありますが、強いAndroidエンジニアがこの夏に参画してくれたこともあって、3週間ほどで見事に形にしてくれました。 開発中デモアプリ これもFigmaと同じく、チームにFlutterというトレンド技術の知見を取り入れられてその効果や課題が見えたのも良かったです。 まだ社内でFBを受けている(そしてその対応工数にふたたび苦慮している)段階ではありますが、来年には何らかの形にして世に出したいと考えています。 まとめと課題 他にも勉強会の定期開催や、CI/CDの拡充や、ここにはちょっと間に合わなかった少し大型のリニューアルなども進行中なのですが、一旦このへんで置くことにします。そのうちメンバーが書いてくれることも期待しつつ、来年はこういう発信をもっと細かくやっていきたいとも思いますね。 振り返ってみて感じる課題としては、 VoC収集の不足 1年を通して、既存画面・機能のボトムアップ的な改善はかなり進みました。が、ある新しい機能の実現を考えようとなったときに必要な取捨選択を決めるための指針(=プロダクトビジョン)がまだ完全ではない、そのためにもっとユーザーの声を聞く必要はあると感じています データによる意思決定と検証 今もアナリティクスを追ってはいますが、もっと「これを出せばこう変わるはずだ」という仮説のもとにPDCAを回したり、何より最も需要なKPI(いわゆる North Star Metric )をプロダクトビジョンと共に定義して、来期はプロダクトの成長を追っていきたいところです ビジネスビジョン 台数が増えるだけ売上に直結するカメラデバイスや、オプションの付加価値であるAI機能などと違って、Viewerフロントエンドは数字を直接意識しにくいところがあります。上のメトリクスの話にも繋がりますが、どの改善がどのようにビジネス的な価値を向上させるかについてもっとセンサーを張り、仮説構築と検証を行なっていきたいと感じます いろいろ書いては来ましたが、実際のところ「普通の開発PM」的な領域をなかなか超えられていないな、という焦りもあります。 これらの課題を打ち倒して前に進むために、来年も頑張って参りたいと思います。 おわりに モバイルアプリPdMとしての1年をざっくり振り返ってみました。 ユーザーの多様性やプロダクトの複雑性からPdMには難しい悩みがありますが、それゆえの面白さやサービスを育てるやりがいもあると思います。興味を持った方はぜひ下記をご覧ください。 現在セーフィー株式会社では、ソフトウェアエンジニアそしてプロダクトマネージャーを積極採用中です! safie.co.jp
はじめに この記事は Safie Engineers' Blog! Advent Calendar 2022 23日目の記事です。 セーフィー株式会社 開発本部のソフトウェアエンジニア 斎藤です。 Safie のクラウド録画サービス の安定運用に寄与するべく、インフラ周りの構築・運用を主に担当しています。 今回は Amazon Aurora MySQL をバージョンアップグレード (5.6 -> 5.7) したお話です。 前回、私が執筆したブログ記事は GitHub Actions で小さな不便を解消してみた でした。約2年振りですね。 はじめに アップグレードする必要性 アップグレード手順候補案 インプレースアップグレード方式 レプリケーション && DNS 切り替え方式 検討する上での比較項目 実際に採用した手順 実際の作業イメージ 前提条件 step1 step2 step3 step4 step4 の詳細手順 万が一の rollback 手順 おわりに アップグレードする必要性 Preparing for Amazon Aurora MySQL-Compatible Edition version 1 end of life そもそも何故アップグレードするのかについては、上記記事の通りです。 Amazon Aurora MySQL バージョン 1(MySQL 5.6 互換) は、2023 年 2 月 28 日にサポートを終了する予定です。 そのため、Amazon Aurora MySQL バージョン 2(MySQL 5.7 互換) または Amazon Aurora MySQL バージョン 3 (MySQL 8.0 互換) にアップグレードする必要があります。 アップグレード手順候補案 インプレースアップグレード方式 https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Updates.MajorVersionUpgrade.html#AuroraMySQL.Upgrading.Sequence マネジメントコンソール もしくは (CLI/API) を使って簡単に 5.6 -> 5.7 へのアップグレードができます。 ただし、アップグレード中はクラスターエンドポイントに接続できないためダウンタイムが発生します。 ※弊社環境での事前検証だと約1時間でした。環境によってダウンタイム時間は前後します。 また、 rollback (切り戻し) したい場合、気軽に出来ないところがデメリットになります。 前バージョンのスナップショットからクラスターを復元するなど、時間が掛かります。 レプリケーション && DNS 切り替え方式 画像引用: https://aws.amazon.com/jp/blogs/database/performing-major-version-upgrades-for-amazon-aurora-mysql-with-minimum-downtime/ Blue/Green デプロイ手法で最後にアプリケーションの向き先を変える方法です。 ざっくり説明すると以下のイメージです。 現在動いている Aurora を Blue 環境とし、Blue からクローン、Green 環境を作ります Green (Older) をインプレースアップグレードします Blue とインプレースアップグレードした Green (Newer) でレプリケーションします 最後にアプリケーションの向き先をインプレースアップグレードしたクラスターに変更します この方法でもダウンタイムは少なからずどうしても発生します。 ただし、インプレースアップグレード方式よりもダウンタイムは短くできます。 検討する上での比較項目 ↓比較項目 インプレースアップグレード方式 レプリケーション && DNS 切り替え方式 手順がシンプルかどうか ✅ ❌ ダウンタイムの長さ ❌ ✅ 切り戻しがしやすいか ❌ ✅ 実際に採用した手順 レプリケーション && DNS 切り替え方式を採用 しました。 採用した理由は以下のとおりです。 ダウンタイムは極力短くしたい。 弊社サービスの性質上 、カメラの録画欠損はなるべくさせたくない。 こちらの理由が一番強い決め手となりました。 ※手順が少し複雑になる所は許容範囲としました。 実際の作業イメージ 前提条件 レプリケーションには AWS DMS Database Migration Service を利用します。 現在動いている Aurora MySQL の方で、 バイナリログ出力有効 (binlog_format=ROW 形式) になっていることが必須です。 https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.MySQL.html#CHAP_Source.MySQL.AmazonManaged Set the binlog_format parameter to "ROW". step1 から step3 までは事前準備です。 (既存サービスには影響ありません) 最後の step4 だけダウンタイムを伴う、停止メンテナンスが必要です。 step1 Clone して Clone 先クラスターをインプレースアップグレードします。 step2 AWS DMS (Database Migration Service) でレプリケーションします。 step3 rollback (切り戻し) 用の 5.6クラスターを追加します。 基本的には rollback は必要無いはずですが、念には念を入れて準備しました。 DMS を中継することで、5.7 -> 5.6 への (ダウングレード) レプリケーションも可能になります。 ※ AWS サポートに質問し、可能と回答頂きました。担当サポートの方、誠にありがとうございました。 step4 step3 までの事前準備が完了したら、いよいよダウンタイムを伴う停止メンテナンスを実施します。 (停止メンテナンス) DNS 切り替え CNAME 変更 (aurora56 -> 57) step4 の詳細手順 DNS 切り替えの手順イメージは、以下のとおりです。 API などのリクエストを 503 Service Unavailable 固定レスポンスにし、メンテナンス中にします。 古いクラスター (5.6) への書き込みを止めます。 (db-cluster-parameter-group 変更で read_only にします) 新しいクラスター (5.7) へのレプリケーション遅延が無いことを確認します。 DMS の CloudWatch メトリクスで確認できます。 CDCLatencySource CDCLatencyTarget これらの値が 0 になると、レイテンシー (レプリカラグ)が無いと判断出来ます。 DMS のレプリケーションを停止します。 Route53 ルーティング先変更します。 (5.6 -> 5.7) API などの 503 Service Unavailable 固定レスポンスを解除します。 古いクラスター (5.6) を reboot します。(各アプリケーションの接続を強制的に切断する意味合いです) 以上でメンテナンス完了です。 万が一の rollback 手順 今回は rollback (切り戻し) しませんでしたが、基本的には step4 と似たような手順で実施可能です。 現行クラスター (5.7) への書き込みを止めます。 (read_only にします) rollback クラスター (5.6) へのレプリケーション遅延が無いことを確認します。 DMS のレプリケーションを停止します。 Route53 ルーティング先変更します。 (5.7 -> 5.6-for-rollback) 現行クラスター (5.7) を reboot します。 おわりに 以上、Amazon Aurora MySQL をバージョンアップグレード (5.6 -> 5.7) したお話でした。 余談ですが、2022年11月27日に Blue/Green デプロイ手法でダウンタイムを最小限に抑えつつデータベースを更新する方法が一般公開されました。 New – Fully Managed Blue/Green Deployments in Amazon Aurora and Amazon RDS 次回、データベースのアップグレードをする際には、こちらの Blue/Green デプロイメントを試してみたいと思っています。 セーフィーでは、 夢を語りまきこみやりきる 方を歓迎します。 ご興味のある方のご連絡をお待ちしてます。 Safie Engineers' Blog! セーフィー株式会社
こんにちは。セーフィー株式会社 バックエンドエンジニアの村田 ( @naofumimurata )です。 この記事はセーフィー株式会社 Advent Calendar 2022 の12月15日の記事です! 本記事ではセーフィーにおけるdriftctlというツールを活用したIaC化推進に向けた取り組みについてご紹介したいと思います。 セーフィーのインフラ環境と課題 driftctlとは driftctlの使い方 準備 スキャンの実行 出力形式の変更 .driftignoreファイルによるスキャン対象からの除外 GitHub Actions による定期実行 ワークフローの解説 取り組みの結果 driftctlを使ってみて 良い点 カバレッジが出せる 「何をコード管理しないか」をコード管理できる 気をつける必要がある点 リソースが多い環境だとrate limit 超過のエラーで失敗する まとめ セーフィーのインフラ環境と課題 セーフィーではインフラ環境としてAWSを利用しており、 Terraform を利用してAWSリソースのコード管理を行なっています。基本的に新しく作られるものについてはTerraformで構築されるのでコード管理された状態になっているのですが、歴史的経緯(後からTerraformが導入された)によりコード管理できていないリソースがまだ残ってしまっているという課題があります。 また、そういったコード管理されていないリソースを見つけるには、Terraformコードベースの知識とセーフィーのインフラ構成の両方の知識が必要であるため、対応できる人がやるというような形になり中々コード化が進まないという課題もありました。 driftctlとは 前述のような課題の解決に役立ちそうなツールとして driftctl というものがあります。 https://github.com/snyk/driftctl driftctlはクラウド上のリソースと IaC (Infrastructure as Code) コードを比較し以下のリソースを検知することができます。 IaC管理されていないリソース IaC管理されているが実際の状態と差分が出てしまっているリソース 本記事の執筆現在 (2022/12/15) 、最新バージョンは v0.38.1 で、IaCツールは Terraformのみをサポートしており、クラウドプロバイダとしては AWS, GitHub, Azure, GCPをサポートしています。 また、READMEには以下の記載があり、まだbeta版であるということが注意書きされています。 ⚠️ This tool is still in beta state and will evolve in the future with potential breaking changes ⚠️ driftctlの使い方 準備 まず、以下を参考にインストールを行います。 https://docs.driftctl.com/0.38.0/installation その後、利用するクラウドプロバイダの認証情報を有効にします。 https://docs.driftctl.com/0.38.0/providers/aws/authentication スキャンの実行 以下のコマンドを実行するとスキャンが実施されます。 $ driftctl scan 特にオプションを指定しない場合、カレントディレクトリのHCLファイルから利用しているtfstateを検出し読み込みます。HCLファイルからtfstateの検出に失敗した場合、ローカルの terraform.tfstate ファイルを読み込もうとします。 tfstateを指定したい場合は --from オプション (環境変数で指定する場合は DCTL_FROM ) で指定することができます。 $ driftctl scan --from tfstate+s3://tfstate-bucket/terraform.tfstate ワイルドカードで複数tfstateをまとめて指定することもできます。 $ driftctl scan --from tfstate+s3://tfstate-bucket/*.tfstate 無事にスキャンが終わると、以下の様に結果がコンソールに出力されます。 Found missing resources: aws_s3_bucket: - test-bucket-1 Found resources not covered by IaC: aws_s3_bucket: - test-bucket-2 Found changed resources: - test-bucket-3 (aws_s3_bucket): ~ Versioning.0.Enabled: false => true Found 3 resource(s) - 33% coverage - 1 covered by IaC - 1 not covered by IaC - 1 missing on cloud provider - 1/1 changed outside of IaC 結果の内容は以下の様になっています。 IaC管理下のリソースに対するスキャン結果 IaCコードには存在するが実際のクラウド上には存在しないリソースの一覧 IaCコードと実際のクラウド上の設定で差分が出てしまっているリソースの一覧と差分 IaC管理されているリソースに対するスキャン結果 IaC管理されていないリソースの一覧 統計情報 カバレッジなど 出力形式の変更 デフォルトではスキャン結果はコンソールに出力されますが、 --output オプション (環境変数: DCTL_OUTPUT ) で出力形式を変更することができます。現在、利用できる出力形式はデフォルトのコンソール出力の他にJSON, HTMLがあります。 $ driftctl scan --output json://result.json $ driftctl scan --output html://output.html --only-managed オプション (環境変数: DCTL_ONLY_MANAGED ) を指定することで、IaC管理下のリソースに対するスキャン結果だけを出力させるができます。 $ driftctl scan --only-managed 逆に IaC管理されていないリソースに対するスキャン結果のみを出力したい場合は --only-unmanaged オプション (環境変数: DCTL_ONLY_MANAGED ) を指定します。 $ driftctl scan --only-unmanaged .driftignoreファイルによるスキャン対象からの除外 driftctlでは特定のリソースをスキャン対象から除外させる機能があり、ファイルに以下のような形式で除外パターンを記述しておくとスキャン対象から外すことができます。 除外パターンを記載しておくファイルは、デフォルトで .driftignore という名前のファイルが使われますが、オプションでファイル指定ができるので別の名前でも問題ありません。 aws_s3_bucket.test aws_instance.* 上記の除外パターンの意味としては、 test という名前のAWS S3バケットとAWS EC2インスタンスの全リソースを除外するという意味になります。 以上で、driftctlの基本的な使い方の紹介を終わります。 他のオプションや詳しい使い方については公式ドキュメントをご参照ください。 https://docs.driftctl.com/0.38.0/usage/ GitHub Actions による定期実行 実際にdriftctlを使ってスキャンした結果を元に改善を進めていく場合、定期的に改善の状況をチームで確認していく必要があるかと思います。都度、誰かがスキャンを実行して結果を共有するのでも良いですが、自動で実行できる仕組みがあると便利かと思い GitHub Actions で定期実行するワークフローを導入しました。 セーフィーでは複数のAWSアカウントがありますが、試験的に導入したかったため、ひとまず1つのAWSアカウントを対象にスキャンを実施するようにしました。 name : driftctl on : schedule : - cron : '0 12 1 * *' workflow_dispatch : jobs : driftctl : runs-on : ubuntu-latest permissions : id-token : write contents : write steps : - name : Checkout uses : actions/checkout@v3 - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v1-node16 with : role-to-assume : ${{ secrets.ASSUME_ROLE_ARN }} aws-region : ap-northeast-1 - name : Run uses : snyk/driftctl-action@v1 continue-on-error : true env : DCTL_ONLY_UNMANAGED : true DCTL_OUTPUT : "html://report.html" with : version : 0.38.0 - name : Chown run : sudo chown -R runner report.html - name : Upload artifact uses : actions/upload-artifact@v3 with : name : Scan Report path : report.html ワークフローの解説 ワークフローの内容は以下の通りです。 schedule トリガーイベントにより毎月月初に実行 or 手動起動で実行 AWS認証情報を有効にする driftctl を実行 公式のAction sync/driftctl-action を利用 環境変数 DCTL_ONLY_UNMANAGED でIaC管理外のリソースに対する結果のみを出力するように指定 環境変数 DCTL_OUTPUT でHTML形式で結果を出力するように指定 continue-on-error: true で失敗してもワークフローが続行されるように 1つでもIaC管理外のリソースがあると終了ステータスが1になり、そのままだとそこでワークフローが失敗してしまうため https://docs.driftctl.com/0.38.0/usage/cmd/scan-usage#exit-codes 結果ファイルをartifactに保存 このワークフローをTerraformコードを管理しているリポジトリに設定し、月一でスキャンした結果をチームで確認、分担して調査とTerraform化を進めていく運用を開始しました。 取り組みの結果 現在、取り組みを始めてから数ヶ月経ちました。まだ全てのリソースのIaC化までは実現できていませんが、着々とカバレッジの数値が上がっており、引き続きこのまま取り組んでいきたいと思っています。 これまでは対応できる人が個人のモチベーションで対応していくという属人的な方法で改善していたものが、ツールを活用し誰でも確認し対応できる形にできたので、かなり進めやすくなりました。 driftctlを使ってみて 今回、driftctlというツールを使っていくなかで個人的に良いと感じた点、また、ちょっと気をつけて使わないといけない点があったので簡単にまとめたいと思います。 良い点 カバレッジが出せる スキャン結果として、リソース全体のうちどのくらいIaC化できているかというカバレッジの値を出してくれるのですが、改善の状況を数値として確認できるのでとても便利です。 「何をコード管理しないか」をコード管理できる .driftignore ファイルでスキャン対象から除外するリソースを指定できる機能もとても良いと思いました。リソースによってはあえてTerraformで管理していないものもあり(アプリケーションから作成されるリソースだったりGitHub PR環境用にCIから作られるリソースなど)、そういったリソースをスキャン対象が外せるとともに、「何をコード管理していないか」という情報をコード管理できるのが便利です。 気をつける必要がある点 リソースが多い環境だとrate limit 超過のエラーで失敗する リソースが多い環境だとスキャンの途中でAWS APIのrate limit超過のエラーが発生し、スキャンに失敗してしまうことがあります。自分達の環境では、特にAWS Route53 Recordのリソースをスキャンしようとした場合にスキャンに失敗することが多かったため、不本意ながらスキャン対象から除外するように設定する必要がありました。この問題があるため、AWS APIを利用するアプリケーションが動く本番環境用のAWSアカウントなどで気軽にスキャンできないという別の問題も生まれています。 こちらの問題はissueとしても挙げられており、動向を注視しています。 https://github.com/snyk/driftctl/issues/1344 まとめ 今回は、driftctl というツールを活用したIaC化推進に向けた取り組みについてご紹介しました。driftctlはまだbeta版ということもあり、不足している機能もありますがとても便利なツールだと思うので興味を持たれた方はぜひ触ってみてください。取り組みを始めて数ヶ月経ち、まだ完全なIaC化までは実現できていませんが、着実に改善を進められていくことができています。本記事が同じような課題を持っている方の参考になれば幸いです。ここまで読んでいただき、ありがとうございました。
こちらは セーフィー株式会社 Advent Calendar 2022 の 14日目の記事になります。 はじめまして。セーフィーでインフラエンジニアをしている近江です。 セーフィーでは AWS の各サービスごとのコストを詳細に把握するため、"AWS Cost Categories" という AWS のサービスを使用しています。本記事では AWS Cost Categories とは何か、どのようなシーンで活用できるのか、実際の設定方法、気をつけるポイントについて書いていきたいと思います。 AWS Cost Categories とは コストカテゴリとは 利用料金 AWS Cost Categories を活用できるシーン コストカテゴリの作成とルール設定 ルールに継承された値 (INHERITED VALUE) を使用する 気をつけるポイント コストカテゴリで使用できるディメンション OR 条件は root レベルでは使えない コストカテゴリの削除 まとめ AWS Cost Categories とは AWS Cost Categories 概要ページ AWS Cost Categories は AWS Billing の請求関連のサービスの 1つです。 AWS によるクラウド財務管理 のページでは ビジネスロジックに沿ったコスト配分戦略の構築 のコストの整理を提供するサービスの 1つとして挙げられています。コストの整理の機能には他にコスト配分タグ (AWS cost allocation tags) があり、こちらは利用されることが多く、ご存じの方が多いかと思います。しかし AWS Cost Categories はそこまで知られているサービスではないのかなという印象です。 2022年11月現在、AWS Cost Categories では主に以下のような機能が提供されています。 独自に定義したルールからコスト情報をマッピングしてコストカテゴリを作成 分割料金ルールを使用して、コストカテゴリ値の間で料金を配分 コスト管理機能でコストカテゴリ別に使用量を表示 AWS Cost Categories サービスページより コストカテゴリとは AWS Cost Categories ではコストカテゴリというものを作成して AWS のコストをカテゴリで分割することが可能です。 コストカテゴリはユーザー側が細かく定義することが可能で、アカウント、タグ、サービス、料金タイプ、別のコストカテゴリなどを指定してルールを作成できます。 作成したコストカテゴリは AWS Cost Explorer、AWS Budgets、AWS Cost and Usage Report (CUR) などの他のコスト管理機能から使用できます。 Cost Explorer のグループ化としてコストカテゴリを指定した際の画面のキャプチャ セーフィーは複数の AWS アカウントがあり、一部のサービスは複数のアカウントを横断してリソースがあります。Cost Explorer のグループ化の条件としてコストカテゴリを使用することでサービスごとのコスト状況が一発で分かります。更にフィルターを追加してサービスごとのデータ通信量だけを把握したりも可能です。 利用料金 AWS Cost Categories 利用料金は無料です。 Q: Is there a cost associated with using AWS Cost Categories? This service is provided free of charge. AWS Cost Categories FAQs - Amazon Web Services より AWS Cost Categories を活用できるシーン 例えば、以下のような一括請求が有効な 3つの AWS アカウントがあり、コスト配分タグ CostAllocation がついたリソースのある環境があるとします。 このような複数アカウントの環境で、以下のようにプロジェクトごとにコストを集計したいという要件が上がったとします。 プロジェクトA コスト配分タグ CostAllocation の ServiceA と ServiceB がついているリソースが対象 プロジェクトB アカウントA のうち、コスト配分タグ CostAllocation がついていないリソースが対象 プロジェクトC アカウントC が対象 上記以外 このような要件で実際にコストを把握したい場合、一般的には以下のようにするかと思います。 Cost Explorer から条件を指定してプロジェクトごとに集計 新たにコスト配分タグを全てのリソースに付ける しかし Cost Explorer を使う場合はプロジェクトの数に応じて集計回数が増えてしまってかなりの手間になります。新たにコスト配分タグを付ける場合は既存のリソースへのタグの設定変更が必要になり、コスト配分タグを有効化するより前の集計ができないなどのデメリットがあります。 そこで登場するのがコストカテゴリです。 コストカテゴリを用いることで、コスト配分タグの新たな有効化や既存のリソースへのタグ追加をする必要はありません。また Cost Explorer でコストカテゴリを指定してグループ化することで、一度にプロジェクトごとにコストを把握することが可能になります。 コストカテゴリの作成とルール設定 ここでは先程の活用できるシーンの設定例を想定して実際にコストカテゴリを作っていきます。 請求ダッシュボード (AWS Billing Dashboard) ページの左側の Cost categories をクリックします。 コストカテゴリを作成 をクリックして作成します。 コストカテゴリの名前 (キー名) を設定するページが表示されます。 名前は今回 Test01 としました。 名前は後から変更不可なので注意してください。 またルックバック期間も設定できます。通常はコストカテゴリを作成した当月のみ遡って使用できますが、ルックバック期間を設定すると最大12ヶ月間遡ってコストカテゴリを使用することが可能になります。 AWS Cost Categories がルールの遡及適用のサポートを開始 設定して 次へ をクリックします。 コストカテゴリの値とルールを設定するルールビルダーのページが表示されます。 各プロジェクトを想定してルールを設定していきます。 値:ProjectA 条件 コスト配分タグ CostAllocation の値が ServiceA か ServiceB 一括請求の管理アカウントでは複数の AWS アカウントに設定されたコスト配分タグが対象です。 値: ProjectB 条件 アカウントID が 111111111111 (アカウントAを想定) コスト配分タグ CostAllocation が存在しない コストカテゴリではルールが上から処理され、コストに一度だけ適用されます。 ProjectA のルールでアカウントA に CostAllocation の値 ServiceA か ServiceB を持つリソースがあっても、ProjectB の値には反映されません。 値: ProjectC 条件 アカウントID が 3333333333333 (アカウントCを想定) デフォルト値 デフォルト値を設定すると、全てのルールに一致しないコストは設定した値が使用されます。デフォルト値を設定しない場合は NoValue 扱いになります。 設定したら 次へ をクリックします。 オプションの分割請求の定義のページが表示されます。 分割請求を設定することで、特定のコストカテゴリの値を別の値に振り分けることができます。 こちらは設定しても Cost Explorer などには反映されず、コストカテゴリのページ中のみ反映されるため注意してください。 注: 分割請求は AWS Cost Categories でのみ利用できます。この機能は、他のコスト管理機能 (例えば、Cost Explorer、Cost and Usage Reports) には表示されません。 ここまで設定し、 コストカテゴリを作成 をクリックすると作成されます。 コストカテゴリが作成されました。 ステータスが最初は 処理中 になります。 作成した日が月の後半だったりルックバック期間が長いと処理が完了するまでに数時間かかることもあります。気長に待ちましょう。 分類分けされたコストがある場合は以下のように色分けされて分かりやすく表示されます。 ルールに継承された値 (INHERITED VALUE) を使用する ルールタイプを 継承された値 (INHERITED VALUE) にすることで、値を自動的に生成することが可能です。 継承された値: このルールタイプにより、定義されたディメンション値からコストカテゴリの値を動的に継承するルールを定義する柔軟性が追加されます。 コスト配分タグ もしくは アカウント が指定可能です。 自動的に値を設定してくれるため、全ての値をルールにいちいち指定するような手間をなくすことができます。 気をつけるポイント コストカテゴリで使用できるディメンション ルールで指定するディメンションですが、Cost Explorer のものとは異なるようです。 例えばディメンションにサービスにした場合に値として AWSDataTransfer が指定できますが、これは Cost Explorer では指定できません。料金タイプの方も若干異なるようなので、設定する際には気をつけましょう。 OR 条件は root レベルでは使えない 複雑な条件を使いたい場合には JSON でルールを指定でき、OR 条件も使うことが可能になります。 しかし以下の JSON のような Rule 直下に OR を指定することはできません。 { " RuleVersion ": " CostCategoryExpression.v1 ", " Rules ": [ { " Type ": " REGULAR ", " Value ": " ProjectA ", " Rule ": { " Or ": [ { " Dimensions ": { " Key ": " LINKED_ACCOUNT ", " Values ": [ " 111111111111 " ] , " MatchOptions ": [ " EQUALS " ] } } , { " And ": [ { " Tags ": { " Key ": " CostAllocation ", " Values ": [ " ServiceB " ] , " MatchOptions ": [ " EQUALS " ] } } , { " Dimensions ": { " Key ": " LINKED_ACCOUNT ", " Values ": [ " 222222222222 " ] , " MatchOptions ": [ " EQUALS " ] } } ] } ] } } ] } こちらを JSON エディタに入力しても、以下の検証エラーが出てしまい、適用することができません。 検証エラー Failed to create Cost Category: Expression cannot contain OR on root level このような root レベルの OR が可能であれば、簡単にコストを足し合わせて算出できるのですが…。 できないため、コストカテゴリを入れ子にすることで回避することが可能です。 先に別のコストカテゴリで合算したいルール条件ごとに値を作成してコストカテゴリを作成する 別のコストカテゴリから先に作ったコストカテゴリをディメンションに指定し、値を全て指定する また、設定したい OR 条件と、全てに合致する適当なディメンションの条件で AND 条件を使うことでも無理やり回避可能です。しかしかなり分かりにくくなるためあまり使わないほうがいいかと思います。 コストカテゴリの削除 コスト配分タグと同じですが、コストカテゴリを削除しても Cost Explorer 上のコストカテゴリの選択欄からは消えません。 コストカテゴリのキー名は後から変えることが不可能なため、検証する際は注意が必要です。 まとめ AWS Cost Categories を利用することでコスト集計作業を効率化する事ができます。 「Cost Explorer のフィルターでタグから複数の値を選択しているけど面倒だし一つにまとめたい」 このようなシーンでもコストカテゴリを使って効率化可能なので、集計の手間を感じたら利用を検討してみてはいかがでしょうか。
はじめに こんにちは、セーフィーのイマドです。 この記事は Safie Engineers' Blog! Advent Calendar 13日目の記事です。 セーフィーでは「映像から未来をつくる」というビジョンのもと、AIなどの技術をクラウドや映像と紐付けることでさまざまな業界の不を覆すソリューションを日々模索しています。 そんな折、画像生成AIのStable Diffusionや人間のように自然な対話ができるChatGPTなど、様々な技術がソーシャルメディア上で話題になってきました。実際に使ってみるとその完成度に衝撃を受けたという人も多いのではないでしょうか、私もその一人です。 これらの最新技術は別に雲の上の話ではなく、実は全ての人が少し手を伸ばせば届く距離にあるわけです。 そこである考えが浮かんできました。 世の中にある最新技術や既存の技術要素を部品に見立てて、レゴのように組み合わせることで業界や顧客の課題を解決できる価値を簡単に作れてしまうのではないか… そしてあわよくば、プログラミングとか面倒なことはAIにまかせてしまいたい… AI「ボクタチ ハタラク アナタ ラクスル」 今回はそんな夢みたいな話を現実にしてくれるかもしれない組み合わせを見つけたのでご紹介します。 それが「 対話AIのChatGPTと、ローコードツールPipedreamの組み合わせ」 です。 次章からそれぞれを解説していきます。 はじめに 各種技術の紹介 OpenAI ChatGPT OpenAI API(GPT-3 text-davinci-003) Pipedream WEB上でコーディングが完結する。 価格 15分でAIチャットボットを作ってみる 背景 ざっくり構成を考えてみる STEP1:特定のチャンネルで特定の文字列を検知(Slack) STEP2:文字列から必要な部分だけを取り出す(Function) STEP3:回答文を生成する Open AIアカウント設定 APIキーの発行 Pipedreamの環境変数にAPIキーを設定 OpenAI APIへのリクエスト AIにキャラクターを付与する STEP4:回答をスレッドに投下する。 テスト/デプロイする 結果 おわりに 各種技術の紹介 OpenAI ChatGPT ChatGPTによるChatGPTの紹介(自己紹介) だそうです 以下からChatGPTのお試しをすることが可能です。(要OpenAIアカウント) https://chat.openai.com/chat こちらの ChatGPT使い方総まとめ を見てもらうとわかるように、質問への応答からコードの生成まで幅広くこなしてくれます。 この記事が公開される頃にはChat GPT系の記事が充実していると思われるので、詳しく知りたい方は他の記事も参照してみてください。 QiitaのChatGPT記事一覧 ちなみに、執筆当時(2022/12/06)まだ ChatGPTのAPIは公開されていません 。現在インターネット上で見かけるChatGPTのAPIで〜してみた系の記事は、後述するGPT-3の言語モデル”text-davinci-003”の可能性があります。 今回このChatGPTには、私がこれから行うアプリケーション開発のアシスタントになってもらいます。 OpenAI API(GPT-3 text-davinci-003) こちらはChatGPTと混同しがちですが、ChatGPTより前に開発された言語モデルです。 有料ですがAPIが公開されており使い勝手が良いので、 今回は部品の一つとして使わせてもらいます。 ChatGPTにtext-davinci-003とはなんですかと聞いてみている図 Pipedream www.youtube.com Pipedreamとは、ひとことで言えば「Zapier上でAWS Lambdaが使えるワークフローツール」です。 簡潔なインターフェースでZapierのように様々なサービスと簡単に接続するだけでなく、AWS LambdaのようにJavascript/Python/Goといった言語(とbash)で直接処理を記述/実行が可能です。 WEB上でコーディングが完結する。 これが、Pipedreamの最大の特徴であり、ZapierやAWS Lambdaとの違いになります。 たとえば、Zapierでもモジュールを作成することでnpmパッケージを扱うことができますが、ローカルでCLIを叩きながらデプロイしなければなりません。それがなんとPipedreamではWEB上でnpmパッケージが扱えます。 Javascript(node)を直接記述している Pythonでも科学計算用のライブラリをimport可能で、ちょっとした処理ならPipedreamだけで済みそうですね。 Pythonの科学計算用のライブラリを読み込んでいる 公式サイト https://pipedream.com/ 執筆している現在、日本語で検索すると産業用システムに対するマルウェアの方が出てきてしまう状況なので、これを機に日本語の記事が増えてくれることを望みます。 価格 Pipedreamの価格 https://pipedream.com/docs/pricing/ Pipedreamでは、Developer Tierとして、個人利用であれば無料で月10,000回の呼び出しが実行できるようになっています。 この呼び出しとは、ワークフロー内のステップ数ではなくワークフローが呼び出された回数のことです。そのため、5つのステップで構築したワークフローを実行した場合、それを1回の呼び出しとしてカウントします。 無料枠では333回呼び出し/日までという制限もありますが、Zapierの無料枠は月100回までと比べるとなんて太っ腹…!(組織だと66呼び出し/日でした) お試しなら良いですが、止まってはいけない運用をするのであれば有料版を検討してくださいね。 また、PipedreamはOSSとして公開されているため、これを自分のサーバーにデプロイして使ってみるのも良いかと思います。 https://github.com/PipedreamHQ/pipedream 15分でAIチャットボットを作ってみる 背景 弊社のSlackチャンネルの一部では、プライベートな一面を引き出し社員同士の理解を深めるという名目のもと、Colloという奇天烈カラクリロボットがユーザーに質問を投稿しています。 カラクリロボットによる執拗なSlack投稿 ですがこのCollo、毎日仕事の時間に質問してくるのです。 質問される側にとってはたまったものではありません。そのため、最近はみんなColloの質問を無視しがちになっています。 本来であれば、聞く時間をお昼時にするとか、みんなが興味をもってくれそうな質問をするとか、 もっと回答してもらいやすい方法 を考えるべきです。 そこで、社員をもうひとり用意し、一人が仕事をしている間に代わりに回答してもらうことで回答率100%を目指すことにしました。 いわゆるマルチプロセッサ ざっくり構成を考えてみる Pipedreamを使えば、以下のような構成で作成できそうです。Pipedreamでは、処理の単位をステップ。一連のステップの組み合わせをワークフローと呼んでいます。 ざっくりワークフロー構成 では、次節から具体的に組み上げていきます。 STEP1:特定のチャンネルで特定の文字列を検知(Slack) 質問文なりよ このステップでは、上のようなメンションを検知してワークフローが開始されるように、トリガーを設定していきます。 トリガーの選択 アプリからSlack→New Mention(Instant)を選択し、反応するチャンネルを指定。 アカウントの連携を済ませ、チャンネルを指定することで特定のチャネルへの投稿を検知することができるようになります。 トリガーの設計 ここでは、冒頭の「ねえねえ」をキーワードに指定しました。これで、「ねえねえ」を含むメッセージが指定されたチャンネル上で投稿されるとトリガーが発火されます。 ここで問題が発生しました。ignore botsをfalseにしても、なぜかColoの投稿だけ検知してくれません。他の機能ではちゃんとignore botsをfalseにしてもColoの投稿を検知してくれたので、バグかもしれません…。 →バグでした。 フォーラムに投稿 したところすぐに再現性のあるバグとして Issue が建てられました。めっちゃ対応早い この記事が公開される頃には修正されているかもしれませんね。 しかたないので、今回は別のやり方で実装してみました。 他の方法で実装してみる 「New Message in Channels(Instant)」で、特定のチャネルにきた投稿すべてを検知 「Filter」→「Continue based on Condition」を用い、検知されたメッセージのうち「ねえねえ」を含むものだけ次のステップに通す。 このやり方の欠点としては、すべての投稿に対してTriggerが実行されてしまうことで余計な呼び出しが発生してしまう点が挙げられます。 STEP2:文字列から必要な部分だけを取り出す(Function) 先程のステップで「ねえねえ」を含む投稿をトリガーにワークフローが実行されるようになりました。ここでもう一度、質問文を見てみましょう。 これは質問文なのか このうち1行目は要らないので削る必要がありますが、文字の処理はノーコードの範疇では難しそうですね。Pipedreamではプログラミング言語で直接処理を書くことができますが、面倒です。 ここは、AIに考えてもらいましょう。 AIに文字列処理のプログラムを書いてもらった いい感じのものが出てきました。 (今回は簡単な処理ですが、正規表現が必要なもっと複雑な処理でもChatGPTはよしなに生成してくれます。) 出力されたコードをPipedreamの様式に合うように少し改変します。ステップ名は”format_question”としました。 前のSlackのメンショントリガーステップからデータを取り出すために steps.{{step_name}}.event.text を指定しています。今回のstep_nameは”trigger"です。 export default defineComponent({ async run({ steps, $ }) { // 変数strに文字列を格納する var str = steps.trigger.event.text; // 改行コード(\n)で文字列を分割する var lines = str.split("\n"); // 2行目以降を抽出する var extracted = lines.slice(1); // 抽出した文字列を出力する return extracted.join("\n"); }, }) 抽出した文字列をreturnで返すことで、次のステップから format_question.$return_value で値を取り出すことができます。 このようにツールではカバーしきれない処理をAIに書かせるというのは、部分的にプログラムを記述できるローコードツールならではの使い方かもしれませんね。 STEP3:回答文を生成する ここでは、OpenAIのAPI(text-gpt-003)に質問内容を渡して回答文を生成してもらいます。 ※回答文を生成するAIはChatGPTではなくtext-davinci-003という自然言語モデルになります。ChatGPTはAPIが公開されていないので仕方ありません。 Open AIアカウント設定 OpenAIのAPIを使うためには、アカウント登録と有料アカウントへの変更が必要になります。 https://beta.openai.com/account/billing/overview 「Setup Paid account」からカード情報を入力してください。 https://openai.com/api/pricing/ 価格表(OpenAIの公式サイトより) 価格表にあるトークン(tokens)という単位ですが、入力プロンプトの文字数と出力された文字数を足し合わせた数がリクエストごとにカウントされ1,000トークンごとに所定の金額が課金されます。 今回質問に回答するのはDavinciという言語モデルなので、1,000トークン(文字)ごとに$0.02が必要になります。 APIキーの発行 以下のURLから、APIキーの発行が可能です https://beta.openai.com/account/api-keys 一度しか表示されないので、メモしておこう Pipedreamの環境変数にAPIキーを設定 Pipedreamでは、Enviroment Variablesから環境変数を設定することができます。 https://pipedream.com/settings/env-vars 「NEW ENVIROMENT VARIABLE」でキーを設置します。 環境変数を設定 今回は”OPENAI_APIKEY”という名前で環境変数を設定しました。 これで、ワークフローの中から{{process.env.OPENAI_APIKEY}}でキーを取り出す事ができるようになりました。 OpenAI APIへのリクエスト それでは、OpenAIのGPT-3 text-davinci-003”に回答文を生成してもらいましょう。 OpenAIのエンドポイントは https://api.openai.com/v1/completions です。 シンプルに回答してもらうだけなら、以下のようなリクエストをOpenAIにPOSTするだけで良いです。 POST https://api.openai.com/v1/completions Header:  Authorization:Bearer {{process.env.APIKEY}}  Content-Type:application/json Body: { "model": "text-davinci-003", "prompt": {{ここにプロンプトが入ります。}}, "max_tokens": 1000 } Bodyに指定しているパラメータの解説 model:今回使用するモデルを指定します。今回は現在公開されているAPIの中で一番高性能な”text-davinci-003”を指定して使います。 prompt:AIに入力するプロンプトになります。 max_tokens:回答に対する最大トークン数(≒文字数)を指定しています。text-davinci-003のmax_token数は4000なので、input/output合わせて4000を超えないように指定しましょう。 パラメータについては、 OpenAI API(GPT-3) 入門 (1) - 事始め が詳しかったです。 これをPipedreamに反映させると以下のようになります。 Authorizationに環境変数に保存したOpenAIキーを設定 Bodyの内容はこんな感じ 実行結果はこちら アットホームな職場です これで、AIに回答文を生成してもらうことができましたね。 AIにキャラクターを付与する GPT-3 text-davinci-003をそのまま使うと、当たり障りのない口調のキャラクターになってしまいます。 今回は質問された本人のように回答して欲しいので、弊社の従業員投票1位に輝いたことのある「とある社員」の偶像になってもらうことにしました。 今回利用したのはChatGPTとは違いますが、使い方は基本的には同じなはずなので ChatGPT使い方総まとめ を参考にプロンプトを生成してみました。本人のSlack投稿などをかき集め、キャラクターを作り上げています。(この記事は本人に許可を頂いて作成しています。) Pipedream上ではこのようになります AIにキャラクターを降臨させる儀式 STEP4:回答をスレッドに投下する。 最後に、生成された回答文をSlackに返しましょう。 「Slack」→「Reply to a Message thread」を選択してステップを作成していきます。 入力している内容は以下です。 Bot Username:Slack上で表示されるボット名 Icon(emoji):ボットのアイコンを好きな絵文字にすることができます。今回は社員のアイコンを絵文字にしてから指定しました。 Thread Timestamp:親メッセージのタイムスタンプを指定します。今回のトリガーとなったメッセージのタイムスタンプを持ってきました。 Channel:親メッセージのあるチャネルを指定します。今回はトリガーとなったメッセージからチャネルを紐付けています。 Text:ここに投稿内容を記載します。今回は、OpenAI APIから返却されたテキストを挿入しています。 `steps.post_request_openai.$return_value.choices[0].text` テスト/デプロイする 「Test」を押して、ちゃんと一連のワークフローが実行できるか試してみましょう。 自分でキーワードを発言して動作確認している様子 ちゃんと意図したとおりに動きましたでしょうか。 さて、このままでは新しい投稿があってもワークフローが実行されませんので、右上のDeployボタンを押して、変更を反映させます。デプロイが完了することでワークフローが有効になり、次回から自動で返信されるようになります。 結果 かがくのちからってすげー 見事、回答率100%を達成することができました。 用意しているプロンプトが「以下の質問に対して回答してください {質問文}」で終わるように作ってあるため、質問文が空になってしまったときはAIが勝手に質問文を生成して勝手に回答してくれています。意図していなかったのですが、これはこれで面白いですね。 突然の自分語り おわりに 今回は、AIの力を借りながらPipedreamというワークフローツールで社員のように回答してくれるAIチャットボットを共同開発してみました。 ChatGPTのコード生成は素晴らしく、ちょっとした処理ならコーディングする必要もないくらいプロセスを簡潔化してくれました。まだプログラム全体を代わりに作成してもらうことは難しいかも知れないですが、ワークフローツールのようなステップ単位の処理やFaaS(Function as a Service)などではすぐに実用可能な技術なのではないでしょうか。回答生成で使用したtext-davinci-003もAPIとして気軽に利用でき、一時期話題になっていたドラマの登場人物っぽいチャットボットが簡単に作れるようになったのだと実感しています。 また、PipedreamはZapierよりも高度なことができるローコードツールという印象です。今回は簡単な処理しかしていませんが、Pythonを使った複雑な科学計算なども扱えることから様々なシーンに応用できそうです。 個人的には、ノーコードツールでは役不足なところをプログラミングにより補完できる幅をもたせたものがローコードツールだと思っていますが、プログラミングが必要ということは専門的知識が必要であり私のようなプログラマではない半端者には難しいところがありました。 しかし、ChatGPTのような文字生成AIが誕生したことでローコードツールの敷居は非常に低くなり、既製品として用意されていないモジュールを自分でも作れるようになりました。ローコードツールの特性としてもChatGPTとの組み合わせは非常に相性が良いように思います。 世の中には素晴らしい技術やサービスが点在しています。それらをレゴのように組み立てることで、プログラマではない私達にも様々なことが実現できるという可能性を感じていただけたのではないでしょうか。 セーフィーでは、業界の不に対し既存の技術を組み合わせ仮説検証を高速化し、誰よりも早く本質的な価値を創りあげていく仲間を幅広く募っています。一緒に映像から未来をつくる夢物語を現実にしていきませんか(カジュアル面談やってます。ぜひお話ししましょう) https://safie.co.jp/teams/ ここまでお読みくださりありがとうございました。 最後にこの記事とプログラムを手伝って頂いたAIさんからひとこといただきましたので、こちらに掲載させていただきます。 お疲れ様です!
こんにちは、セーフィー株式会社の23卒、新卒エンジニア職入社予定の伊東です。 この記事は Safie Engineers' Blog! Advent Calendar 10日目の記事です。 私たち新卒一期生は入社前に5ヶ月間インターンシップを行ってきました。 このインターンシップの振り返りをしつつ皆さんにセーフィーという会社の教育環境や会社の雰囲気をお伝えしようと思います。 自己紹介 伊東 スキルセット セーフィーを選んだ理由 インターンで期待していたこと 土田 スキルセット セーフィーを選んだ理由 1.会社と事業のビジョンに共感できる 2.エンジニアとして成長できる環境 なぜインターンをしようと思ったか インターンで取り組んだこと Udemy講座 チーム開発 1. 仕様書作成(半月) 難しかった点 心がけた点 画面遷移図 ER図 2.開発環境構築(2週間) フロントエンド バックエンド ぶつかった壁 心がけたこと 3. 実装(2ヶ月) ぶつかった壁 4. 結合テストとデプロイ(1週間) 結合テスト デプロイ 完成したアプリケーション 1. ログイン画面 2. ユーザー一覧表示画面 3. ユーザー情報詳細表示画面 4. ユーザー情報変更画面 インターンで学んだこと 伊東 土田 まず、今回のテックブログは2人で書いたので、それぞれ自己紹介させてください。 自己紹介 伊東 改めまして、23卒、エンジニア職入社予定の伊東です。私は、大学では水産学部といういわゆる情報系ではない学部からエンジニアを目指してこの業界に入ってきました。 スキルセット python暦2年(濃縮すると1年ぐらい) 他社の長期インターンでバックエンド(FastAPI)を使用し、約7ヶ月働いていた 競技プログラミング(AtCoder)は茶色になったばかり 大学の研究でfortran90という超レガシー言語を3年ほど使用している(早くやめたい) セーフィーを選んだ理由 就活シーズン、昨今の意識高い系youtube、(ニュースピックス、 日経テレ東大学等)の動画を見すぎていつの間にかベンチャー思考になっていました。その中で、勢いのある会社を探していたところ、就活していた2021年12月当時に上場したばかりのセーフィーを見つけ、選考に進んだところ内定をいただけたのでそのまま承諾しました。 インターンで期待していたこと フロント、インフラを実際に触り、web開発がどのように成り立っているのか知りたいと思いました。 土田 こんにちは、もう一人のブログの筆者で23卒の新卒エンジニア職入社予定の土田です。 私は大学は工学部で、現在は大学院でロボットアームとAIについての研究に取り組んでいます。また、研究室は立ち上げ一期目のタイミングに入り、環境構築のところから卒業研究を実装するところまでをやり遂げる経験をしてきました。 スキルセット ロボットの研究をC++でゼロから実装(2年) AIをpythonで実装(1年) 趣味の範囲ででC#を用いたwindowsデスクトップアプリ開発 (1か月) セーフィーを選んだ理由 私はセーフィーに対して二つの点で魅力を感じました。 1.会社と事業のビジョンに共感できる セーフィーのビジョンである「映像から未来を作る」を聞いて私はテクノロジーが社会に本格実装する未来を想像し、自分もその未来を作る一員になりたいと強く思いました。 2.エンジニアとして成長できる環境 セーフィーの技術領域はWebのフロントエンド、サーバーに加えてAIエンジニア、組み込みエンジニアの領域もあり、エンジニアとしてのキャリアの幅の広さに大変魅力を感じました。また自ら積極的に開発事業の立ち上げもできる期待もあり、成長環境としても最高だと思いました。 なぜインターンをしようと思ったか 一流のエンジニアから実際の開発現場で活かせる知識を身につけたい これまで開発の知識をほとんど独学で身につけていましたが、知識の偏りを感じていたため、本場の環境でバランスよく矯正したいと思いました。 チーム開発を経験したい これまでは研究や趣味での個人開発しか経験がなかったのでチーム開発の中で自分がどう役に立てるかを見出したいと思いました。 インターンで取り組んだこと セーフィーの新卒エンジニア向けインターンは以下のような流れで行われました。 Udemy講座(1ヶ月半) チーム開発(3ヶ月) Udemy講座 上長の方に選んでいただいた、今後エンジニアとして知っておいて欲しい技術として、SQLやドキュメント作成の方法フロント、バックエンド、インフラの基礎を受講しました。実際に手を動かしながら、今後エンジニアとして必要な知識を大量にインプットしました。 チーム開発 チーム開発は、フルリモートで、来年から同期になる23卒エンジニア内定者の4名で行いました。開発が始まって最初に要件が伝えられました。 ユーザーとデバイスの紐付けを管理するためのwebサイトが欲しい ユーザー、デバイスはそれぞれ、グループに所属しているのでその紐付けもして欲しい この要件以外には特に制約はなく、どんなフレームワークを用いるかなどはチームで自由に決めることができました。 開発の流れは大まかに以下のようになります。 仕様書作成 開発環境構築 実装 テストとデプロイ ここから一つ一つチーム開発の過程を述べていきたいと思います。 1. 仕様書作成(半月) 作成した仕様書は以下の4つです。 画面遷移図(画面とその遷移を現した図) クラス図(バックエンドの設計) ER図(データベース設計) シーケンス図(バックとフロントの関係をみる図) 難しかった点 仕様書の作成を誰も経験したことがなくどんな内容を書くべきか分からなかった 心がけた点 仕様書作成の目的を「メンバー間の認識のずれを解消するため」とした とりあえずできる範囲で形にして上長から早めにフィードバックを得て何度も修正した 画面遷移図 ER図 2.開発環境構築(2週間) フロントエンドとバックエンドでGitHubのリポジトリを分けて環境構築を行いました。 フロントエンド Vue.jsを使用 Vue-cli環境でyarnを用いてパッケージ管理 Dockerファイルの作成 リンターとフォーマッターを用いるためにEslintとprettierを設定 ユニットテストのためにJestの環境設定 バックエンド APIはFastAPI, データベースはMySQLを使用 poetryを用いたパッケージ管理 Dockerファイルの作成 リンター・フォーマッターはflake8, mypy, black, isortなどを併用 ユニットテストはpytestを用いて実施 ぶつかった壁 Eslint、 prettier、JestとVue-cliとの接続 FastAPIとMySQLのDockerコンテナ間の接続 心がけたこと ネットの記事をそのまま応用するだけではうまくいかなかったので、ローカルでひたすら実験を繰り返しながら解決策を探る チーム間で丁寧にレビューし合い、参考になる記事をみんなで見つけてアイデアを出し合う 3. 実装(2ヶ月) 実装にあたって、Githubを用いた開発を行うこととしたため、始めに開発の流れをチーム内で共有しました(どのブランチにマージするかやissueの書き方等)。 また、インターンでの学びを最大化するためメンバーみんながフロントとバックエンドの実装の両方に携われるように役割分担しました。しかし、いざ開発を進めてみると予定工数から2ヶ月も超過することに、、、 ぶつかった壁 1.フォルダ構成、クラス名、関数名、変数名において個人の色がでてしまい、その都度、ルール決めをする必要があった(工数が超過した大きな要因となりました) 対策: 命名規則をpython, Vue.jsのそれぞれで調べ、根拠をもって統一 2.詰まった時に誰に聞けば良いかわからない 対策: 普段から開発以外の中でもコミュニケーションを取ってお互いの強い分野を把握 3.インターン終了の期限が迫っている 対策:チーム内でやらないことを話し合うようにし、必須ではない工程(例: ユニットテスト)を削った 4. 結合テストとデプロイ(1週間) 実装で大幅に工数が超過し、インターンの残り期間一週間というところまで追い込まれていました。そこで効率を重視するため結合テストを三人、デプロイ一人で役割を完全に分けてスピード重視の体制をとりました。 結合テスト テストケースを作成する(全部で150ケースほどになりました) テストケースを一つ一つ実行しバグがあれば新たにイシューを作成 イシューをもとにコードの修正 ぶつかった壁: テストケースについて開発者目線ばかりの視点になっていて、ユーザー目線で考えられていなかった 対策: テストケースを考えるうえで先に大まかな観点を決め、それがユーザー目線になっているかを徹底的にフィードバックを受けてそのあと詳細にテストケースを作成 デプロイ AWSのEC2インスタンスをフロントとバックエンド両方に構築し、EC2内でDockerコンテナを作成してその内部でフロントはnginx、 バックエンドはuvicornによってプログラムを動作させました。 ぶつかった壁: ここ数年でサードパーティークッキーの制限が厳しくなった影響で、デプロイの際にブラウザにクッキーが保存されなかった 対応: もともとhttpドメインを使用していたが、ssl証明書をAWSから取得しhttpsドメインを使用することでセキュリティ要件に対応できるようにした クッキーの設定をhttps専用に設定を変更する 完成したアプリケーション 開発の終了日ギリギリまでかかりましたが、何とか要件を満たしたアプリケーションを作成することができました。以下が完成したアプリケーションの概要になります。 1. ログイン画面 メールアドレスとパスワードで認証を行う画面です。 2. ユーザー一覧表示画面 データベース内に存在するユーザーの一覧を表示します。 3. ユーザー情報詳細表示画面 マウスで選択したユーザーアカウントの詳細情報を表示します。また、所属(ユーザーグループ)や紐づいているデバイスも表示します。 4. ユーザー情報変更画面 ユーザーのアカウント情報の更新や新規アカウントの作成を行う画面です。 インターンで学んだこと 伊東 このインターンでは当初私が求めていた以上のことを学ぶことができたと思っています。 1つ目は、0から開発を行う経験です。これは、今回のインターンで最も大きな学びになりました。要件から初めてシステムを作成することは、すでに存在するシステムをアップグレードするのとは別の知識や技術がいるということを知ることができました。 2つ目はチームで開発する際のノウハウです。今回のチームは全員同期であることから、フラットに質問、会話をすることができましたが、時には意見が対立することがありました。そのような中でも開発を進めていくためには、コーディング技術以外のコミュニケーションが大事であるということを再確認させられました。 土田 私は今回のインターンをやってよかった点が三つあります。 一つ目は開発現場で活かせる知識をバランス良く得られたことです。UdemyやデイリーMTGでの上長への質問によって、体系的なところから実際に現場で使われる範囲までバランスよく身につけられました。 二つ目はチーム開発をスムーズに進めるための知見を実践を通して得られたことです。納期に間に合う開発にするために工程を細かく分割しそれぞれ工数の予定を立てることが大切だと分かりました。また、実際どれほど工数を消化するのにかかったかを振り返ったうえで自分たちの工数の見積もりの甘さも実感したので、今後より改善していきたいと思います。 三つめは開発に没頭するとユーザー視点を忘れがちになることに気づけたことです。特に仕様書作成やテストケースを考える際に自分がユーザー目線になれていないことを実感しました。開発のことで頭がいっぱいのときでもこの機能やユーザーにとって本当に必要か?という問いを自分で立てていけるエンジニアになっていかねばと強く思いました。
こんにちは、セーフィー株式会社 サーバサイドエンジニアの河津です。 この記事は Safie Engineers' Blog! Advent Calendar 6日目の記事、また enebular Advent Calendar 23日目の記事です。 約1年前に、弊社から提供しているSafie APIとNode-REDを連携して、ローコードな仕組みで会議室の空き状況を見る試みを記事にさせていただきました。 engineers.safie.link 今回はその続編ということで、この1年でSafie APIに追加された機能を用いて、部屋に入退室された方の時間と体温を把握するような試みをしてみようと思います。特に体温を取得できるというのは、ご時世的にもかなり需要があるのではないでしょうか。 Safie APIやNode-REDについての説明も上記記事内でさせていただいています。この記事内では説明を省略しますが、もし良ければ上記記事を読んでいただけると、この記事もわかりやすくなるかもしれません! 今回作るもの Safie Entrance2とは フローの解説 injectノード functionノード(Safie APIへ送るリクエストパラメータ作成) http requestノード functionノード(Safie APIからレスポンスパラメータを受け取り加工する) slackノード フロー実行 まとめ 今回作るもの その日に部屋に入退室があったログを取得し、ログに含まれる入退室時間と体温情報を定期的にSlackに送るようなものを作ってみます。 入退室のログを確認するには、弊社から提供しているサービス「Safie Entrance2」を使用します。 Safie Entrance2とは Safie Entrance2は、顔認証端末を用いて自動解錠を行うことができる、クラウド型入退室管理サービスです。 顔認証なのでICカードなどと違い紛失やなりすましのリスクがないのと、データはクラウド管理されるためどこでも入退室履歴を確認することができます。 safie.link 入退室者の情報は、顔認証端末で解錠を行う際にサーバに溜まっていきます。溜まっていくデータは主に入退室した「日時」と、顔画像から画像処理にて割り出した「体温」が保存されます。 この情報を、Safie APIを用いて取得することができるようになりました。下記の「入退場一覧取得API」を使用します。 openapi.safie.link それでは、情報の取得ができるようNode-REDのフローを作っていこうと思います。 フローの解説 Node-REDで下記のようなフローを作ってみました。前回と同じように、今回もクラウドのNode-RED環境であるenebularというサービスを使いました。 www.enebular.com それぞれのノードの役割について解説していきます。 injectノード injectノードは手動でノードを始動させたり、一定間隔で実行させることができるノードです。 「inject実行」というボタンでフローを開始できるほか、実行間隔や繰り返しのあり・なしも設定することができます。 ノード同士で値を受け渡しする際は、 msg.payload というJavaScriptオブジェクトの中に値を作ってやりとりをしますが、今回はこのinjectノードから次のノードに値を受け渡すようなことはしないので、 msg.payload はデフォルト設定であるタイムスタンプを送るようになっています。 functionノード(Safie APIへ送るリクエストパラメータ作成) functionノードはノード間で受け渡しを行う msg.payload を加工することができるノードで、JavaScriptを用いて内部処理を記載することができます。 入退場取得APIはいくつかの条件で検索をかけることができますが、今回は特定の端末に紐づく入退場データを取得するために、 terminal_id を指定してデータを取得することにします。 http requestノード その名の通りHTTP Requestを行えるノードです。ここでは実際に入退場一覧取得APIが実行できるように、エンドポイントやアクセストークン情報などを入力していきます。 ※アクセストークンの取得方法については、こちらの記事に記載があります。 engineers.safie.link functionノード(Safie APIからレスポンスパラメータを受け取り加工する) 入退場取得一覧APIからは下記のようなレスポンスが返ってきます。 { total : 617 , offset : 0 , count : 20 , has_next: true , list : [ { detection_id: xxx , timestamp : " 2022-11-17T11:28:28+00:00 ", terminal : {} , // 割愛 result : false , person : null , temperature : 36.1 } ] } timestamp が入退場の日時であり、 temperature が体温の値です。 このレスポンス情報をSlackに送信するために加工を行います。 var list = msg.payload.list var response = "" list.forEach( function (obj, index) { var timestamp = obj.timestamp var temperature = obj.temperature response += "入退場時間:" + String (timestamp) + " 体温:" + String (temperature) + " \n " } ) msg.payload = response return msg; やってることとしては、入退場日時と体温情報をメッセージとともに1行の文字列にして、取得できるオブジェクト分改行しているだけです。 slackノード Slackに送信を行うことができるノードです。 SlackのWebhook URLと投稿したいチャンネル名を指定するだけで、 msg.payload の内容をSlack投稿してくれる便利ノードです。 Webhook URLの取得方法はこちらの記事を参考にさせていただきました。 qiita.com フロー実行 作成したフローを実行してみます。injectノードの「inject実行」をクリックすると・・・ このようにSlack送信できました! あとはinjectノードを一定間隔で実行するような形で設定すれば、「定期的に入退場データをSlackに送信してくれる仕組み」の完成です! まとめ 前回の記事を書く際にも感じていましたが、ローコードにシステムを作れるNode-REDとSafie APIは、かなり相性良いなと感じています。 この記事を書くためにNode-REDを色々と動かしていましたが、フローの組み始めからSlack投稿が完了するまで大体1時間かからないくらいで作成できました。(前回記事の知見を多数流用できたというのも大きいですが) プロトタイプ的に物作りをする上では便利ですね。 Safie APIは継続的に機能追加が行われていくため、しばらくしたらまた試してみたいなと思います!ここまでお読みいただきありがとうございました!
Safie Engineers’ Blog! Advent Calendar 12月5日を担当します。yokと申します。 Safie の本社には、「テックステージ」と呼ぶショールームスペースがあります。 (一般公開しているものではなく、打ち合わせなどでご来社いただいた方へのサービス紹介が主です。) テックステージ スマートプラグ スマートリモコン ここでは、Safie が取り扱うカメラの数々、最新機種 Safie One やそのAI 機能( Store People Detection Pack )、顔認証による入退管理( Safie Entrance2 )、 POS レジ連携 など、さまざまなサービスを体験できるようになっています。 タイミングによっては、検証を兼ねてリリース前の機能が展示されていることもあります。 Webサービスを展示しているため、多数のPCやディスプレイが稼働しています。 その他、市販のTVを8台繋いだ、手作り感のあるディスプレイウォールもあります。 これらのディスプレイをどう管理するか。以前のオフィスはモニターの台数が少なかったこともあり、つけっぱなしでもあまり気にならなかったのですが、これだけの台数になると、使わない時は消す必要があります。 オフィスのショールームに求められる要件としては できるだけ手間をかけず、自動的に起動・終了して欲しい 土日、祝日など営業していない日は使用しない(節電!) これらを満たすような電源管理が求められます。 あの手この手を試しましたが、、、最終的に「スマートホーム化」することにしました。 スマートプラグ コンセントをOn/Off することで、家電を無理矢理IoT 化するもの。自宅ではホットカーペットに愛用してます。 今回は SwitchBot 社の製品を使用しました。 https://www.switchbot.jp/products/switchbot-plug 公式のAPI で簡単にON/OFF 制御ができます。 https://github.com/OpenWonderLabs/SwitchBotAPI スマートリモコン インターネット経由で操作できるリモコン。こちらは M5Stack ATOM で自作しました。 TV のリモコン制御で困るのが、ON と OFF に同じリモコンコードが割り当てられているということです。つまり、現在ON なのか、OFF なのかわからないと、間違った制御をしてしまう可能性があります。 ネットの情報を調べて「何回か押す」というハックに辿り着きました。 参考: https://besma9.net/smart-remocon-tv-light/ メーカーや機種によって動作が異なるようで、今回制御したいTV は2回だとうまくいかず、「3回連続でON すると、ON状態でもOFF状態でも、必ず ONになる」ということがわかりました。 全部スマートプラグでもよかったのですが、PCとの相性で、「主電源を落とすとマルチディスプレイの設定が消える」箇所があり、一部リモコンでのON/OFF制御をおこなっています。 自作リモコン 3画面のそれぞれに確実に届くよう、IR信号を分配してTV の裏から操作するようにしています。 遠隔操作は Slackbot として実装し、専用のチャンネルで ("IR_ON"、"IR_OFF") といったメッセージを送るとON, OFF できるようにしました。 これらの制御をスクリプト化し、定期実行することで、要件を満たす制御が完成しました。 平日か土日祝かの判定も、スクリプト内で行っています。 ショールームのオープン当初は、出社が早いCEO や、遅くまでいるCFO が、手動でオン・オフしていたということもあって、自動化は課題でした。また、スマートプラグは消費電力が見えることもあって、あらためて節電意識が高まりました。 大画面TV の省電力はやばい。
こんにちは、セーフィーでバックエンドのエンジニアをしております神田です。 今回は、バックエンドのAPIを開発する際に使用しているフレームワークについてお話ししていこうと思います! API開発で使用しているフレームワークの紹介 Tornado FastAPI FastAPIを使用するまでの流れ FastAPIの何がよくて採用したのか 速い ドキュメントが自動生成される 実際に使ってみて感じること ドキュメント自動生成が本当に助かる 簡単にデータの堅牢性が保たれる まとめ API開発で使用しているフレームワークの紹介 まず初めに、現在使用しているフレームワークについて紹介します。 Tornado 公式リンク セーフィーのサービスができた当初(2014年)から使われています。 複数あるフレームワークの中から、APIのみで使用する前提で パフォーマンス 開発のしやすさ デバッグのしやすさ の観点から、当時最も条件を満たしているために採用されたそうです。 FastAPI 公式リンク FastAPI自体は2018年にリリースされ、近年人気を集めているフレームワークです。 セーフィーでは2020年以降に新たに作成されたAPIサーバーで使用されています。導入の理由はこれからお話しします! FastAPIを使用するまでの流れ もともとはtornadoですべてのAPIを開発していましたが、完全に新規のコンポーネントを開発することになり、せっかく新しく作るのであればtornadoにこだわる必要もないとのことで、その当時評判になりつつあったFastAPIをお試しで採用してみようという話になりました。 これが2020年ごろの話で、創業当初にはなかったFastAPIを導入するきっかけになりました。 FastAPIの何がよくて採用したのか 実際にFastAPIのどのようなところが魅力的で、導入に至ったのかをお話ししたいと思います。 速い 名前からもわかるように、FastAPIは速いことが特徴の1つで、公式ドキュメントにも「NodeJS や Go 並みのとても高いパフォーマンス 」と書かれています。 参考 Web Framework Benchmarks で確認すると、2020年時点ではFastAPIはかなり速いフレームワークであることがわかります。 Web Framework Benchmarksでベンチマークが示されているフレームワークの中で、FastAPIは全体4位、tornadoは29位でした。 表1. 1リクエストあたり20クエリでの1秒あたりのレスポンス(2022年時点) フレームワーク 1リクエストあたり20クエリでの1秒あたりのレスポンス FastAPI 12,991 Tornado 1,354 ドキュメントが自動生成される FastAPIでは作成したAPIのドキュメントが自動で生成されます。 ドキュメントを別途作成する手間がなくなるので、 ドキュメントの作成忘れ ドキュメント作成の手間 がなくなります。 実際に使ってみて感じること 現在、当初からあるコンポーネントはtornado、新規のコンポーネントはFastAPIで開発しているので、実際に開発していて両者にどのような違いがあり、FastAPIにどのような良さがあるかを2点お話ししようと思います。 ドキュメント自動生成が本当に助かる FastAPIの採用理由でも触れましたが、ドキュメントが自動生成されることで得られるメリットは想像以上でした。 tornadoでAPIを開発する場合、 仕様書のPRを作成 APIを実装するリポジトリでAPIを実装しPRを作成 APIに修正が入った場合も2つのリポジトリでPRを作成 という作業が必要になっています。 リポジトリが分かれている上、両者が連動されていないため、とくに、緊急に軽微な修正を行った際にドキュメントの修正漏れが発生してしまいます。 そうすると、APIを組み込むフロントエンドとモバイルの方が誤ったドキュメントを見ながら組み込んでしまい、うまく動作しないなどということが発生しかねません。。 FastAPIではこのような問題を全て手間なく解決してくれました! 簡単にデータの堅牢性が保たれる FastAPIはpydanticがベースになっていて、リクエスト・レスポンスのデータ型を簡単に定義することができます。 下記のようなAPIを考えます。(例のためなので内容に意味はありません) FastAPI @ router.get ( "/api/hoge" ) async def get_hoge ( id : Optional[ int ] = Query( max title= "id" ), name: str = Query(max_length= 200 , title= "名前" ) ): id, nameをリクエストパラメータにもつgetのAPIです。 idは数値で任意パラメータ、 nameは文字列で必須パラメータであることがわかります。これをtornadoで書くと、 Tornado class HogeHandler (self): def async_get (self): id = self.get_argument( "id" , None ) if not instance( id , int ): raise Exception () name = self.get_argument( "name" , None ) if name is None : raise Exception () if not instance(name, str ): raise Exception () if len (name) > 200 : raise Exception () 条件分岐をたくさん書かなければなりません。。 このように、FastAPIではデフォルトで簡単にバリデーションの記述ができます。また、FastAPIの方がどのようなバリデーションをしているのか一目で理解できる上、自前で用意する必要がないため、データの堅牢性が保たれ、予想外のエラー発生を防いでくれます! さらに、pydanticのBaseModelを使用すれば、ネストしたデータのバリデーションもできますし、レスポンスの型定義をすることで必要なデータを返却し忘れることもなくなります。 まとめ セーフィーでは、使い慣れた技術や手法を常に選ぶのではなく、その都度最適な技術を選定して開発を行っています。実際、FastAPIを導入することでたくさんの恩恵を受け作業効率も上がりました。 技術は日々進化するので、新しい技術情報にアンテナをはってより良いプロダクト・開発環境を作っていきたいな〜と思います!
サーバサイドエンジニアの松木 ( @tatsuma_matsuki ) です。Safie解析プラットフォームやSafie APIの開発を主に担当しています。 この記事は Safie Engineers' Blog! Advent Calendar 4日目の記事です。 解析プラットフォームでは、学習済みモデルおよびランタイムをSafieクラウド上に登録することで、Safieサービスで録画したカメラの映像・画像に対して、任意の推論処理を実行し、その実行結果をイベントとして保存することができます。 まだサービス公開はされていませんが、まずは社内エンジニアで利活用の推進したいという思いから、Safie解析プラットフォームを利用したアプリケーションの開発を実践しています。 私はサーバサイドのエンジニアで、機械学習の分野には明るくないのですが、今回解析プラットフォーム上でのアプリ構築を一から自力で開発可能にするために、学習済みモデルの作成と推論処理の実装を初めてやってみました! 結果として、なかなか高い精度を出すまではまだ少し時間がかかりそうですが、その作業・開発工程をこの記事で共有したいと思います。 開発するアプリケーション 画像や動画の収集は簡単! 学習(モデル作成) データの分割 データの拡張 ベースモデルの読み込み 学習の実行 推論処理の実行 精度の評価 AIモデル作成は簡単ではなかった 開発するアプリケーション まずは適当に思いついたものを開発してみよう!ということで、オフィスにいる弊社のCTOがカメラに写ったら検知して、通知(メール・Slack)を出すようにすることを目標としました。 弊社の五反田オフィスは、4F、5F、8Fの3フロアあり、分析対象として使えそうなカメラが10台弱ほど設置されています。様々なアングルや大きさで人が写りこみますが、全てのカメラで汎用的に使用できるアプリケーション(モデル)を作成できればベストです。 画像や動画の収集は簡単! Safie社内で学習用の画像を集めるのはとても簡単です! オフィス内にカメラが既に常時稼働しており、過去の映像も全て確認できるため、Safie Viewerからポチポチやるだけで画像がどんどん集まります。 Safie Viewerのストリーミング画面を開いて、右上のカメラのアイコンをクリックするとその時に写っている画像のスナップショットが取得できます。 実質1~2時間ほどで、様々なカメラから合計400枚ほどの学習用画像が集まりました。 学習(モデル作成) 今回開発するアプリケーションの場合、入力画像を「CTOが映っている」「CTOが写っていない」の二つに分類するモデルを作成する必要があります。 Efficientnetという有名な分類系のモデルをベースモデルとしてファインチューニングします。 arxiv.org また、以下のKaggleのnotebookを参考にして、学習処理部分を実装しました。 www.kaggle.com 以下では、その処理内容をざっくりと順を追って説明します。 データの分割 まず、 scikit-learn の train_test_split 関数を使って、集めた画像を用途ごとに分割します。 今回は、70%の画像を学習データ、15%の画像を評価データ、残りの15%をテスト用データとしてランダムに分割しました。 filepaths = [] labels = [] # `label_file_path`で指定したパスには、<画像ファイルのパス>,<ラベル> が列挙された # CSVファイルが置かれています。 with open (label_file_path) as f: reader = csv.reader(f) for row in reader: filepaths.append(row[ 0 ]) labels.append(row[ 1 ]) f_series = pd.Series(filepaths, name= "filepaths" ) l_series = pd.Series(labels, name= "labels" ) df = pd.concat([f_series, l_series], axis= 1 ) train_df, tmp_df = train_test_split( df, train_size= 0.7 , shuffle= True , random_state= 123 , stratify=df[ "labels" ] ) valid_df, test_df = train_test_split( tmp_df, train_size= 0.5 , shuffle= True , random_state= 123 , stratify=tmp_df[ "labels" ], ) データの拡張 深層学習ライブラリである Keras の ImageDataGenerator を使ってデータを拡張します。 gen = ImageDataGenerator( horizontal_flip= True , rotation_range= 20 , width_shift_range= 0.2 , height_shift_range= 0.2 , zoom_range= 0.2 , ) aug_img_count = 0 required = 100 # 作成する拡張画像の枚数 aug_gen = gen.flow_from_dataframe( df, x_col= "filepaths" , y_col= None , target_size=img_size, class_mode= None , batch_size= 1 , shuffle= False , save_to_dir=target_dir, # `target_dir`で指定したディレクトリに作成されます。 save_prefix= "aug-" , color_mode= "rgb" , save_format= "jpg" , ) while aug_img_count < required: images = next (aug_gen) aug_img_count += len (images) ベースモデルの読み込み 出力層付近を除いたモデルを読み込み( include_top=False )、2クラスの分類結果を出力するように置き換えます。今回はEfficientnet B3のモデルを利用しています。 base_model.trainable = True にするとファインチューニング、 False にすると転移学習となります。 base_model = tf.keras.applications.efficientnet.EfficientNetB3( include_top= False , weights= "imagenet" , input_shape=img_shape, pooling= "max" ) base_model.trainable = True x = base_model.output x = Dropout(rate= 0.4 , seed= 1 )(x) output = Dense(class_count, activation= "softmax" )(x) model = Model(inputs=base_model.input, outputs=output) model.compile( Adamax(learning_rate=lr), loss= "categorical_crossentropy" , metrics=[ "accuracy" ] ) 学習の実行 引数に、学習用データ( train_gen )と検証用データ( valid_gen )を指定して model.fit() を実行すると学習がスタートします。 model.fit( x=train_gen, epochs=epochs, verbose= 1 , callbacks=callbacks, validation_data=valid_gen, validation_steps= None , shuffle= False , initial_epoch= 0 , ) model.save(model_save_path) コンソールの出力を見ていると、 accuracy や val_accuracy の値が良くなっていくのが分かります。とりあえず、学習はうまく進んでいるようです。 Epoch 1/40 20/20 [==============================] - ETA: 0s - loss: 8.0606 - accuracy: 0.7800 validation loss of 9.0880 is 0.0000 % below lowest loss, saving weights from epoch 1 as best weights 20/20 [==============================] - 76s 3s/step - loss: 8.0606 - accuracy: 0.7800 - val_loss: 9.0880 - val_accuracy: 0.7000 Epoch 2/40 20/20 [==============================] - ETA: 0s - loss: 6.8788 - accuracy: 0.9050 validation loss of 7.7559 is 14.6582 % below lowest loss, saving weights from epoch 2 as best weights 20/20 [==============================] - 34s 2s/step - loss: 6.8788 - accuracy: 0.9050 - val_loss: 7.7559 - val_accuracy: 0.7000 Epoch 3/40 20/20 [==============================] - ETA: 0s - loss: 6.2107 - accuracy: 0.9225 validation loss of 6.1968 is 20.1025 % below lowest loss, saving weights from epoch 3 as best weights 20/20 [==============================] - 34s 2s/step - loss: 6.2107 - accuracy: 0.9225 - val_loss: 6.1968 - val_accuracy: 0.9167 Epoch 4/40 20/20 [==============================] - ETA: 0s - loss: 5.6444 - accuracy: 0.9350 validation loss of 5.6057 is 9.5382 % below lowest loss, saving weights from epoch 4 as best weights 20/20 [==============================] - 35s 2s/step - loss: 5.6444 - accuracy: 0.9350 - val_loss: 5.6057 - val_accuracy: 0.9500 Epoch 5/40 20/20 [==============================] - ETA: 0s - loss: 5.1150 - accuracy: 0.9500 validation loss of 4.9568 is 11.5753 % below lowest loss, saving weights from epoch 5 as best weights 20/20 [==============================] - 37s 2s/step - loss: 5.1150 - accuracy: 0.9500 - val_loss: 4.9568 - val_accuracy: 0.9833 Epoch 6/40 20/20 [==============================] - ETA: 0s - loss: 4.6689 - accuracy: 0.9675 validation loss of 4.5310 is 8.5913 % below lowest loss, saving weights from epoch 6 as best weights 20/20 [==============================] - 34s 2s/step - loss: 4.6689 - accuracy: 0.9675 - val_loss: 4.5310 - val_accuracy: 0.9833 Epoch 7/40 20/20 [==============================] - ETA: 0s - loss: 4.2913 - accuracy: 0.9900 validation loss of 4.2057 is 7.1782 % below lowest loss, saving weights from epoch 7 as best weights 20/20 [==============================] - 34s 2s/step - loss: 4.2913 - accuracy: 0.9900 - val_loss: 4.2057 - val_accuracy: 0.9500 Epoch 8/40 20/20 [==============================] - ETA: 0s - loss: 3.9923 - accuracy: 0.9750 validation loss of 3.9645 is 5.7369 % below lowest loss, saving weights from epoch 8 as best weights 20/20 [==============================] - 34s 2s/step - loss: 3.9923 - accuracy: 0.9750 - val_loss: 3.9645 - val_accuracy: 0.9500 Epoch 9/40 20/20 [==============================] - ETA: 0s - loss: 3.7193 - accuracy: 0.9725 validation loss of 3.6385 is 8.2214 % below lowest loss, saving weights from epoch 9 as best weights ... 推論処理の実行 テスト用のデータを model.predict() の引数に渡すことで推論を行うことができます。 推論の結果は、 [0.00226656, 0.99773353] というように、その画像がそれぞれのクラスである確率のようなものを表しているため、数値が最も大きいクラスを分類結果として使います。 image_generator = ImageDataGenerator().flow_from_dataframe( test_df, x_col= "filepaths" , y_col= "labels" , target_size=image_size, class_mode= "categorical" , color_mode= "rgb" , shuffle= False , batch_size=batch_size, ) results = [] predicts = model.predict(image_generator, verbose= 1 ) for i, p in enumerate (predicts): file = image_generator.filenames[i] label_index = image_generator.labels[i] predicted_index = np.argmax(p) results.append(( file , predicted_index, label_index, p)) print (results) 精度の評価 最初に用意した15%のテスト用画像(61枚)に対して評価してみた結果が以下の通りです。 label precision recall f1-score support 0 1.0000 0.8571 0.9231 21 1 0.9302 1.0000 0.9639 40 さくっとやった割に簡単に精度が出て、かなり驚きました。 AIモデル作成は簡単ではなかった しかし! 別途、別の人が用意したテスト用の画像に対して推論を実行して評価すると、precision 0.5, recall 0.8!!という残念な結果でした。ほとんどランダムで0, 1選択したのと変わらない!! 全くの未知の画像に対して精度を出すことの難しさがわかりました。。 おそらく私が集めた学習用データセット全体に対してかなり偏りがあるのではないかという考察をしているのですが、実際のアプリケーションとして使えるレベルにするにはもう少し試行錯誤が必要そうです。 精度を出すためにはもう少し勉強が必要そうですが、分類系の学習済みモデルの作成~推論処理の実装までを一通り経験することができました! Safieのサービスを利用すると画像収集の部分はかなり楽なので、その点は大きなメリットがありそうです。が、まだまだ手間であることには変わりないので、ラベル付きのCSVのような形である程度まとまった単位で画像ダウンロードできるようになるとなお良さそうです。この辺りはフロントチームと連携して利便性を向上させていきたいと思います。 推論の精度を上げて、無事にアプリケーションが完成しましたらまたこの記事で結果を共有したいと思います!
この記事は Safie Engineers’ Blog! Advent Calendar 2日目の記事です。 セーフィー株式会社テックリードの鈴木敦志です。 セーフィーでは開発者の積極採用を進めており、エンジニア組織の人数が2年間で約35名から約75名にまで成長しました。 開発者の人数増加に伴うチーム内のコラボレーションの問題に対応するため、アジャイル開発手法の一つであるスクラム開発をサーバー/インフラチーム内で導入し2年間ほど運用し、一定の成果を得られましたので経緯や実際の施策、結果などについて共有させていただきます。 スクラム導入前の課題 チーム内のコラボレーションが希薄 業務知識の属人化 スクラム導入 チームの分割 プロダクトバックログ スクラムイベントの実施 スクラム導入により解決された課題 チーム内コラボレーションの推進および知識移転の推進 解決されていない課題 機能横断型チーム プロダクトオーナー 今後の方針 さいごに スクラム導入前の課題 スクラム導入前の開発組織はおおよそ職能別のチームで構成されていました。 開発案件はプロジェクト単位に分割され、各プロジェクトごとに職能別チームから開発者を0~2名程度ずつ割り当てプロジェクトチームとしていました。 各チームメンバーはそれぞれ1~数個のプロジェクトに並列で参加することになります。 スクラム導入前のチーム構成 チーム内のコラボレーションが希薄 各開発者は職能別チームの中ではそれぞれ参加しているプロジェクトが異なり、プロジェクトチームの中では他メンバーと職能が異なるため必然的に単独で取り組むことになるため、チームメンバーとの協調作業が難しい状況でした。 例えば設計の相談やコードレビューの際も、相談を受ける側はプロジェクトの細かい要件や経緯を把握しているわけではないためどうしても形式的なものにならざるをえません。 業務知識の属人化 特定個人が特定のプロダクトやコードを触り続けることになるため、他のメンバーに知識が共有されづらくなります。 改修案件や質問対応、障害対応が集中しやすくなり、また転属や退職などでの混乱が発生しやすくなります。 スクラム導入 こういった状況を改善するため、サーバー/インフラ開発者内でスクラムの導入を行いました。 スクラムの導入には機能横断型チームの編成や開発案件の意思決定者としてのプロダクトオーナー制度の導入など組織構造の変更が必要になります。 スクラムの組織構成 今回は全社的な導入でなくあくまでサーバー/インフラ開発者の間でのボトムアップ的な導入であるため、こうした組織変更を伴う施策を行うことはできませんでした。 スクラムガイドでも否定的に言及されているスクラムの一部的な導入にとどまってしまい、いくつかのメリットを受けることができませんが、それらを抜きにしてもチーム内でのコラボレーション促進の効果を得られると思い導入を進めました。 実際に導入されたチーム構成 チームの分割 導入時点でサーバー/インフラチーム合計で11人のメンバーがおり、チームを2チームに分割しました (現在は4チーム)。 それぞれのチーム内の開発者一名が兼務でプロダクトオーナーとしてプロダクトバックログの管理およびステークホルダーとの協議を行います。 (このプロダクトオーナーは実際にプロジェクト/プロダクトに十分な権限を持つわけでなく、バックログの管理と各プロジェクトの会議体に出席し要件を整理する役割です) プロダクトバックログ プロダクトバックログおよびスプリントバックログはWrikeにより管理されています。 Wrikeはさほどスクラムに向いている感じではありませんが、前者共通のプロジェクト管理で使われていたためツールを合わせました。 スクラムイベントの実施 スプリントを2週間 (チームによっては1週間) とし下記スプリントイベントを実施しています。 スプリントプランニング (1回/スプリント) スプリントで行う開発の計画を行います デイリースクラム (毎日) 一日の作業の調整を行います スプリントレトロスペクティブ (1回/スプリント) KPT法で開発進捗やチームの状況について振り返りを行います バックログリファインメント (1回/スプリント) プロダクトバックログアイテムの詳細化を行います スクラム導入により解決された課題 チーム内コラボレーションの推進および知識移転の推進 開発者をプロジェクトに割り当てる習慣をやめ、スプリントプランニングおよびバックログリファインメントの場で開発項目を全員で議論し詳細化することで、チームの全員が各開発項目の実装を実装を進めることができるようになりました。 各人が別々のプロジェクトに割り当てられていた頃とくらべ、特定の技術や業務知識がある人とペアプロをしたり、各人の休暇や割り込み作業などにあわせてタスクを融通しあうなど、チーム内での協力と知識の移転を促進することができました。 チーム内でプランニングポーカーによるストーリーポイントの見積もりを行っており、見積り結果の値はまだ活用できていないものの、全員で見積りを行う行為自体が開発項目への理解を深めるのに役に立っていたように思います。 解決されていない課題 今回導入したスクラムは既存チーム内で完結するもののみで組織的なスクラム導入には至っていないため、スクラムにより解決が期待される課題の一部は残ったままです。 機能横断型チーム スクラムチームは機能横断型であり、必要な機能をすべて備えていることを求められます。 一方で現在の組織は職能別のチーム構成となっておりひとつの機能を実装するにはサーバーチーム、フロントエンドチーム、デバイスチームなど複数チーム間での情報共有や優先度や調整が必要になり、都度ロスが発生します。 プロダクトオーナー スクラムにおいてプロダクトオーナーはチームが開発するプロダクトに権限と責任をもつロールです。 現在の組織体制では開発案件の権限はプロジェクトマネージャーが持っており、スクラムのプロダクトオーナーに当てはめることはできません。 今回のスクラム導入の範囲ではチームの開発者のうち1名を便宜上プロダクトオーナーとし、各プロジェクトのミーティングへの出席やステークホルダーとの折衝およびプロダクトバックログの管理を行っています。 プロダクトに関する意思決定者がチームの近くにおらず判断に時間がかかりますし、開発業務と平行して複数プロジェクトのミーティングに出席するため非常に負荷が高くなります。 今後の方針 開発チームにスクラムを導入し、チーム内コラボレーションの促進および業務知識の属人化防止に一定の成果を得ることができました。 しかしボトムアップ的に始まったためスクラムのフレームワークすべてを導入できているわけではなく、チーム内でのスクラムイベントの実施などの一部のみにとどまっています。 スクラムのメリットを享受して組織課題を解決していくためには組織全体として対応していく必要があり、今後はスクラムの推進のための施策を進めていきます。 社内でのスクラムの普及 機能横断チームの編成 プロダクトオーナー制度の導入 専任スクラムマスター採用 各チームリーダーの認定スクラムマスター資格取得 さいごに スクラムにおいて一部の施策だけの導入は推奨されていませんが、とはいえ現場からボトムアップででもスクラム導入を進めていきたい場面もあると思います。 本件事例がそういった方々の役に立てば幸いです。 現在セーフィー株式会社ではソフトウェアエンジニアを積極採用中です。 興味のある方はぜひ下記ページよりご応募ください。 https://safie.co.jp/teams/
セーフィーCTOの森本です。 この記事は Safie Engineers' Blog! Advent Calendar 1日目の記事です 2020年2月より開始したセーフィーのテックブログですが、一時期運営が危機的な状況に陥ったこともありました。 しかし、有志からなる運営チームのガンバリにより2021年9月以降着実に更新を継続して行ってくれており、様々な会社さんからテックブログ継続の難しさを伺っている中、非常に喜ばしく感じている今日此頃となっています。 そんな中、運営チームが2022年の年末に向けてアドベントカレンダーをやろうと熱く提案してくれました。 月イチの更新すらままならなかった当初を知っているだけに、ホントに出来るのかという気持ちもありましたが、当社カルチャーでも「迷ったときはやってみる」と謳っていますのでこれはやるしか無いなと言うことでGOを掛けた次第です。 今回はそのトップバッターという事で、当社で今年初めから注力し開発している項目の一つである解析プラットフォームについて紹介致します。 セーフィーとは セーフィー解析プラットフォームについて 解析プラットフォームの今後 最後に セーフィーとは 既にテックブログで何度も紹介済みかとは思いますが、セーフィーはクラウド録画サービス「Safie(セーフィー)」を運営しています。 safie.jp カメラにより撮影した大量のデータをクラウド上に保存するとともに、必要に応じてユーザーへLive動画、過去動画を配信したり、動きや異常音が発生した場合にその旨を通知するような機能も提供しています。 インターネットに繋ぐだけで簡単に利用できますが、使いやすさやセキュリティを意識した作りとなっており、広くお客様にご利用頂いています。 現在17万台を超えるカメラを運用しており、20PBに迫るデータが当社システム上に蓄積されています。 Safieサービス概要 また、カメラから取得したデータを解析し、様々な現場の課題解決につなげるようなサービスも既に複数提供しています。 Safie AI People Count safie.jp Safie OneとStore People Detection Pack safie.jp engineers.safie.link engineers.safie.link このようなサービスを実現する上では、旧来の手法やAIを用いた画像解析技術の活用が必須となります。 最近はデバイス側で簡易的なAI処理が駆動できるような、所謂エッジAIカメラやエッジAIデバイスが販売されていますが、解析する内容によって必要なコンピューティングリソースも大きく異なり、ものによってデバイス側で実行出来ない事も多々あります。 また、エッジAIカメラはまだまだ普及している訳ではないので、AI機能を搭載していないカメラが大多数を占める状況となっていますが、当社ではデバイス、クラウド環境共に自分たちで開発、管理していますので、これらの画像解析をどちらでも適した環境で実現できるところに大きな強みがあります。 セーフィー解析プラットフォームについて 先程ご紹介したように、当社では複数の画像データ解析をベースとしたサービスを提供しています。 また、当社データを取得し画像解析を行うような連携サービスを提供しているパートナーさんも存在しています。 当社録画サービスをご利用中のお客様からご相談を頂く事も頻繁にあり、明確に画像データ解析のニーズの高まりを感じています。 一方で、現状では上記サービスそれぞれの開発に数ヶ月以上かかっている状況にあります。 例えば動画から特定の物体を検出するようなサービスを考えた場合にも、解析部分の開発だけでなく、データ入力IFの設計開発、解析エンジン駆動部分の設計開発、開発結果の保存部分、取得部分の設計開発、ものによっては結果を表示するUIの設計開発と多岐にわたる項目の開発が必要となります。 解析系のサービスはまだまだこれから市場開拓をしていく必要がある中で、もっと簡単にソリューション開発を実現出来なければ、大きく市場を広げて行く事は難しいと感じています。 上記の課題を解決するために、セーフィーでは解析プラットフォームの開発を進めています。 解析プラットフォームは主として以下の機能を提供します。 解析プラットフォームイメージ ①開発したAI等に基づく解析エンジンをセーフィーのクラウドシステムに登録 ②必要に応じ、セーフィークラウドシステム、セーフィー対応カメラに展開し解析を実行 ③解析結果をセーフィークラウドシステム上のデータベースに保存 ④解析結果をセーフィーAPI経由で取得し利用可能 現在システムの1stステップがほぼ完成しており、最初の解析サービスの開発を進めている状況となっています。 こちらの仕組みにより、自社、他社問わず解析エンジンだけ開発すればすぐに新しい解析サービスの提供が開始出来るようになると期待しています。 但し、画像解析系のサービスを広げていく上では他にも課題が存在しています。 解析系サービスでは精度が非常に重要な要素を占めますが、画像解析はカメラの設置された環境により精度が大きく影響を受けるという問題があります。 カメラの設置する画角や、設置環境の明るさ、設置環境に検知対象以外にどのようなものが映り込むかなど、様々な事柄により期待する精度が実現できない事があります。 これにより、設置コストやサポートコストが上がったりするだけでなく、結果的にお客様の期待値を裏切るような形になってしまうこともあり得ると考えており、解析サービスを広げていく上で合わせて解決が必要な問題と捉えています。 この問題を解決するために、セーフィーでは個別学習の仕組みの整備を検討しています。 個別再学習機能イメージ ⑤特定の環境下のカメラのデータを元に自動(目標)で個別学習を実行し、問題のある環境下でも精度の担保が出来るAIモデルを生成する この仕組みを合わせて整備する事により、設置コスト、サポートコストを抑える事が出来るだけでなくお客様の期待にきちんと向き合う事ができ、結果的に解析系サービスを広げていく事が本当の意味で実現出来ると期待しています。 解析プラットフォームの今後 上記にてご紹介した解析プラットフォームですが、現在も開発を継続しており、目下以下を目標に対応を進めています。 解析プラットフォームベースのソリューションを確実にリリースする パートナーさんにも解析プラットフォームベースのソリューション開発とリリースまで実化して頂く また、合わせて以下の項目も進めています。 ※解析プラットフォームそのものには含まないですが 多様なアプリケーション開発が簡単に行えるようセーフィーAPIやSDKの整備を進める AIモデル開発そのものがセーフィークラウドシステム内で完結して簡単に行えるような環境を整備する AIモデル開発まで一気通貫にサポート 解析プラットフォームにより、単一環境で容易に画像解析ベースのソリューションが構築できるようになるだけでなく、実運用時の環境要因による精度劣化を低減する事が出来るようになる事が期待できます。 ここまでやることにより本当の意味で解析サービスの利用が広がっていくのではと考えています。 最後に 上記解析プラットフォームの開発を更に進めると共に、2023年は上記を用いた解析サービスによる業界DXを確実に実現していきたいと考えています。 当社としては今後画像解析システムとの連携を更に強化していくべく開発業務に取り組んでいますが、まだまだエンジニアさんが足りていない状況です。ご興味がある方はお気軽にご連絡頂けますと幸いです。
こんにちは、セーフィー株式会社でサーバサイドのエンジニアをしている河津です。同時に、このSafie Engineers' Blog!の運営も行っています。 この度セーフィー株式会社 初の アドベントカレンダーを実施しようと思っており、告知も兼ねた記事を投稿させていただきます! 基本情報 アドベントカレンダーとは? 想定参加者は? 掲載媒体は? 執筆テーマは? 最後に 基本情報 セーフィー株式会社のアドベントカレンダーはこちらになります。 qiita.com なんと25枠全て埋まっており、初開催にしてかなりの盛り上がりを見せております! アドベントカレンダーとは? 元々の意味としては、クリスマスまでの日数をカウントダウンするために使われていたカレンダーで、12月1日からはじまり、25個ある「窓」を毎日1つずつ開けて中に入っている小さなお菓子やプレゼントを楽しむものになります。 このカレンダーに倣って、何か一つのテーマに対して技術記事を12/1から12/25までいろんな人が毎日投稿する、という風習がエンジニア界隈には存在しています。 そんな風習に、弊社も乗っかってみようという取り組みになります! 想定参加者は? セーフィー株式会社に勤める、プロダクト開発に関わる方々を対象にしています。 セーフィーには様々な職種のエンジニアが在籍しており、広い領域の技術話が集まってくるのではないかと(個人的に)期待しています! エンジニアの職種については、以前投稿したこちらの記事もご覧になってみてください。 engineers.safie.link また、「プロダクト開発に関わる方々」という言い方はエンジニアに限定していないというところもミソでして、デザイナー職種の方々、PdMの方々などからも広く記事を募集しております。 掲載媒体は? このブログ「Safie Engineers' Blog!」以外に、「Qiita」、「Zenn」、「note」での執筆もOKとしております。 なのでアドベントカレンダー期間中は、このブログ以外のところでもセーフィーの技術記事が上がっていくことになりますので、興味を持っていただける方は是非ともカレンダーをご確認ください。 執筆テーマは? 自由 としています。 元々このブログに掲載する場合でもそこまでテーマの縛りを設けているわけではありませんが、掲載媒体をSafie Engineers' Blog!にとどめない体制から、さらにフリーダムな記事が生まれるのではないかと思っています。 しっかりした技術話はもちろん、ライトめなものやポエム的なものまで歓迎としているので、ぜひ自由度の高い記事をお楽しみください。 最後に 是非ともセーフィー株式会社のアドベントカレンダーを、このブログ共々よろしくお願いいたします。 アドベントカレンダー終了時には、この記事に追記する形で振り返りの内容を掲載する予定ですのでお楽しみに! qiita.com
こんにちは!セーフィーでサーバーサイドエンジニアをしている神田です。 今回は2022/10/14、10/15に行われたPyCon JP 2022に参加した時のお話をしようと思います! 以下のアジェンダに沿って進めていきます! PyConはどのようなイベントか 参加企業の特徴 どんな人がイベントに参加していたか ブース運営の様子と感想 セッションに参加してみた感想 まとめ PyConはどのようなイベントか ざっくり言うと、Pythonユーザーが集まり、交流できるイベントです! さまざまな企業がブースを出していて、各プロダクトのどのような部分にPythonが使われているかなどお話を伺うことができます。 また、セッションではPythonのライブラリの活用方法やPythonを活かした簡単なプロダクトを作る、などといったお話を聞くことができます。 pycon2022 参加企業の特徴 ほぼ全ての企業ブースを回りましたが、メインでPythonを使用している企業もあれば、第二言語としてPythonを使用している企業もありました。活用方法もさまざまで、バックエンドの言語としてPythonを採用している企業もあれば、機械学習やデータ分析の部分でのみPythonを使用している企業もありました。 さまざまな企業ブースでお話を伺って、Pythonはさまざまな分野のエンジニアが選定する言語なのだな〜と改めて感じました。 今回は全部で22ブースありました。ブースでは企業の方とお話しするだけではなく、プロダクトを見せてもらったり、かわいく便利なノベルティをもらえたり、くじ引きやアンケートなども実施していてとても楽しかったです! どんな人がイベントに参加していたか Pythonユーザーの交流の場なので、Pythonが好き!という方がやはり多かったです。参加する前までは仕事でPythonを使っている人が大半だろうと思っていたのですが、案外仕事では使っておらず趣味で触っている程度という方もそれなりにいらっしゃいました。学習が難しくないPythonだからこそ、さまざまな人に興味を持ってもらえるイベントなのですね! また、中には学生も何人かいました。就活のためにPythonがどういうプロダクトで活用されているのか見にきたそうです。なんて意識の高い。。 ブース運営の様子と感想 セーフィーのブースはこんな感じでした! SafieOne と Pocket2 を展示して、Safie Viewerのマルチビューアーをモニターに映していました。 SafieOneについては下記の記事でも詳細をご確認いただけます! エッジAIカメラ「SafieOne」のアプリ「Store People Detection Pack」とは エッジAIカメラ「SafieOne」の画像認識 カメラのようなハードウェアを展示しているブースは少なく、これはなんだろう?と興味を持ってくれた方が多く立ち寄ってくださいました! エンジニア紹介資料 に沿って、セーフィーのクラウド録画がどのような現場で活用されているかを説明させていただきました。クラウド録画=防犯目的 のみと考えている方が非常に多かったのですが、実際には防犯以外の側面でも活用いただいていることを伝えられたのがよかったと思っています! お昼やセッションの合間の休憩時間には、ブース担当2人では足りないほどのにぎわいでした! ノベルティも好評で、特にステッカーはとても人気でした! セッションに参加してみた感想 2日目はブース担当ではなかったので、ほぼ1日セッションを聞いていました! 参加したセッションは以下です。 退屈なことはSlack botにやらせよう(ver.2) https://slides.takanory.net/slides/20221015pyconjp/#/ PyCon運営をする中で、運営のユーザーから何度も同じ質問に答える手間を無くそう!ということをテーマにSlackBotの簡単な作り方を学べるセッションでした。ユーモアあふれる話し方で聞き手をひきつけ、とても楽しく学ぶことができました! Pythonではじめる地理空間情報 geamapとleafmapを使用して簡単に地理情報を表示する方法について学べるセッションでした。もともと地理空間に関心があったので、ぜひ自分でも試してみようと思いました! データに関する堅牢性と可読性を向上させるpydanticとpanderaの活用方法の提案 データ分析においてデータのスキーマをpydanticとpanderaを用いて使いやすくする、といった内容のセッションでした。pydanticは業務でも使っていたのですが、QAセッションの中でさまざまなPythonユーザーのpydanticへの意見を聞くことができて、とても良い機会でした! はじめての量子コンピューター 量子コンピュータの基礎的な部分を学べるセッションでした。基礎とはいっても内容が難しかったのですが、非常に難しい計算をすばやく解くことができるということだけは理解できました。 どのセッションも匿名で質問を投稿することができ、質問したい人が多い項目から登壇者に答えていただくシステムでした。毎回質問が山ほど投げられており(わたしもたくさん投げていました)、非常に盛り上がっていました! セッションに参加したことで、今まで知らなかったPythonの活用方法を知ることができたのはもちろんのこと、イベントで代表に選ばれて登壇している方を直接目にしたり、多くの人が真剣にセッションを聞いている姿を見て、自分ももっと頑張りたいと思えたことがとてもよかったです! まとめ ブースの参加もセッションの参加も本当に楽しく、非常に充実した2日間を過ごすことができました。コロナウィルスの影響でここ数年はオフラインのイベントに参加できていませんでしたが、来場者の方や他の企業参加者の方と直接コミュニケーションをとることで得られるものも大きいな〜と感じました。 そしてなにより、PyConはPythonユーザーのためのイベントなので、初対面の人でもとりあえずPythonの話ができるため、人と話すのが苦手な自分でもたくさんの人と楽しくお話しできたことが本当に嬉しく、来年も絶対に参加したいなと思っています! また、さまざまな人から自社のプロダクトに興味を持っていただき、いいね!と言ってもらえることで日々の業務にやりがいを感じました!次回もたくさんの人と交流したいので、Pythonユーザーの皆さんはぜひPyConに参加してみてください!
サーバサイドエンジニアの松木 ( @tatsuma_matsuki ) です。 Safieでは、FastAPIを利用していくつかのサービスを開発しています。Safieのサービスの性質上、APIサーバで画像ファイルなどのオブジェクトを扱うことが多いです。 大きいサイズのオブジェクトをクライアントにダウンロードさせるAPIなどでは、FastAPIの StreamingResponse を使うのが便利ですが、このStreamingResponseの使い方を扱った良いリファレンスがネット上であまり見つからなかったので、この記事で実際にコードなどを示しながら実装例を共有していきたいと思います! StreamingResponse S3からオブジェクトをダウンロードして返す 同期・非同期イテレータでのダウンロード時間比較 他サービスAPIからオブジェクトをダウンロードして返す 単体テストの実装 まとめ StreamingResponse FastAPIのStreamingResponseは、HTTPのbodyとなるコンテンツをいくつかのチャンクに分割してストリームで返すために利用されるレスポンスクラスです。まずは、このStreamingResponseクラスのコンストラクタ引数についてまとめてみます。詳細は以下のリンクを参照ください。 https://github.com/encode/starlette/blob/0.21.0/starlette/responses.py#L224 引数 型 説明 content Iterator or AsyncIterable メインとなるオブジェクトのデータ(必須) status_code int ステータスコードを指定(デフォルトは200、省略可) headers Mapping レスポンスヘッダーを指定(省略可) media_type str 指定した値は、Content-Typeヘッダーの値として利用されます。headersでもContent-Typeを指定した場合は、headersの値が優先されます。(省略可) background BackgroundTask BackgroundTask を指定(省略可) メインとなるオブジェクトのデータはcontentの引数に渡します。content引数は、イテレータ、非同期イテレータ(もしくは、ジェネレータ、非同期ジェネレータ)を指定できますので、この仕様に合わせてcontentを渡してあげる必要があります。 S3からオブジェクトをダウンロードして返す オブジェクトを管理するオブジェクトストレージのサービスとしてまず思い浮かぶのはおそらくS3でしょう!APIサーバ上でS3からオブジェクトをダウンロードして、そのオブジェクトをクライアントに返すというような処理に、StreamingResponseが利用できます。 S3からオブジェクトをダウンロードして、それをStreamingResponseで返す処理は、例えば以下のような実装になります。S3からのオブジェクト取得には、 aiobotocore を利用しています。aiobotocoreは、botocoreとaiohttpによる 非同期の クライアント実装となっています。 StreamingResponseは、非同期イテレータと通常の同期イテレータの両方が使用可能なので、非同期ではないbotocoreを利用した場合とのダウンロード時間を比較した結果も後で紹介します。 ... async def s3_client ( settings: Settings = fastapi.Depends(get_settings), ) -> AsyncGenerator[aiobotocore.client.AioBaseClient, None ]: session = aiobotocore.session.AioSession() async with session.create_client( "s3" , endpoint_url=settings.aws_endpoint_url, aws_access_key_id=settings.aws_access_key_id, aws_secret_access_key=settings.aws_secret_access_key, verify=settings.aws_tls_verify, ) as client: yield client def s3_client_sync ( settings: Settings = fastapi.Depends(get_settings), ) -> botocore.client.BaseClient: session = botocore.session.Session() return session.create_client( "s3" , endpoint_url=settings.aws_endpoint_url, aws_access_key_id=settings.aws_access_key_id, aws_secret_access_key=settings.aws_secret_access_key, verify=settings.aws_tls_verify, ) @ router.get ( "/tasks/{task_id}/image" , summary= "Download Image" , response_class=StreamingResponse, ) async def get_image ( task_id: int = Path(..., title= "Task ID" ), settings: Settings = fastapi.Depends(get_settings), s3_client: aiobotocore.client.AioBaseClient = fastapi.Depends(s3_client), # s3_client_sync: botocore.client.BaseClient = fastapi.Depends(s3_client_sync), ): s3_bucket = settings.s3_image_bucket s3_key = f "test/{task_id}/image.jpg" try : image = await s3_client.get_object(Bucket=s3_bucket, Key=s3_key) # image = s3_client_sync.get_object(Bucket=s3_bucket, Key=s3_key) except Exception : raise HTTPException(status_code= 500 , detail= "Failed to get the image" ) return StreamingResponse( content=image[ "Body" ], # 非同期イテレータ media_type= "image/jpeg" , headers={ "content-length" : str (image[ "ContentLength" ]), "etag" : image[ "ETag" ]}, ) botocoreのget_object()のレスポンスはDict型で、"Body"キーの値(image["Body"])にオブジェクトのデータが返ってきます。 image["Body"]の型は aiobotocore.response.StreamingBody ですが、これは非同期イテレータとなっており、StreamingResponseのcontentにそのまま渡すことができます。 同期・非同期イテレータでのダウンロード時間比較 StreamingResponseは、通常の同期イテレータと非同期イテレータの両方に対応していますが、それぞれを利用した場合にどの程度オブジェクトのダウンロード時間が変わるのかを少し見てみたいと思います。 S3(今回はlocalstackを利用)上に10MBのオブジェクトをアップロードして、それを上記で実装したAPIでダウンロードします。 まずは、通常の同期イテレータ(botocore)を用いた場合の結果です。 $ time curl -sv http://localhost:30080/tasks/1/image -o image.jpg * Trying 127.0.0.1:30080... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 30080 (#0) > GET /tasks/1/image HTTP/1.1 > Host: localhost:30080 > User-Agent: curl/7.68.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < date: Wed, 28 Sep 2022 04:26:28 GMT < server: uvicorn < content-length: 10485760 < etag: "46f40f95feefb6ab49b2b3679b5ec0a2" < content-type: image/jpeg < { [1024 bytes data] * Connection #0 to host localhost left intact real 0m19.873s user 0m0.115s sys 0m0.300s 約20秒 ほどかかりました。次に、非同期イテレータ(aiobotocore)を用いた場合です。 $ time curl -sv http://localhost:30080/tasks/1/image -o image.jpg * Trying 127.0.0.1:30080... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 30080 (#0) > GET /tasks/1/image HTTP/1.1 > Host: localhost:30080 > User-Agent: curl/7.68.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < date: Wed, 28 Sep 2022 04:28:05 GMT < server: uvicorn < content-length: 10485760 < etag: "46f40f95feefb6ab49b2b3679b5ec0a2" < content-type: image/jpeg < { [3072 bytes data] * Connection #0 to host localhost left intact real 0m3.825s user 0m0.039s sys 0m0.198s こちらは 4秒弱 という結果でした!非同期イテレータをcontentに指定することでダウンロード時間がかなり早くなっています。 個人的には思ったよりも違いが出たので、API実装する上では、StreamingResponseに非同期のイテレータを渡すことが性能面でかなり重要になってきそうです。 他サービスAPIからオブジェクトをダウンロードして返す S3以外の実装例も紹介したいと思います。複数のサービス連携を実装する中では別サービスのAPIからオブジェクトをダウンロードして、それをStreamingResponseで返すようなシーンもありますので、その実装例も書いてみます。 ... async def get_exit_stack () -> AsyncGenerator[contextlib.AsyncExitStack, None ]: async with contextlib.AsyncExitStack() as stack: yield stack async def get_aiohttp_session () -> AsyncGenerator[aiohttp.ClientSession, None ]: async with aiohttp.ClientSession() as session: yield session @ router.get ( "/tasks/{task_id}/bytes/{size}" , summary= "Download bytes" , response_class=StreamingResponse, ) async def get_bytes ( task_id: int = Path(..., title= "Task ID" ), size: int = Path(..., title= "The number of Bytes to download" ), exit_stack: contextlib.AsyncExitStack = Depends(get_exit_stack), session: aiohttp.ClientSession = Depends(get_aiohttp_session), ): res = await exit_stack.enter_async_context( session.get(url=f "http://httpbin.org/stream-bytes/{size}" ) ) if res.status != 200 : raise HTTPException(status_code= 500 , detail= "Failed to download bytes" ) return StreamingResponse( content=res.content.iter_chunked( 1 * 1024 ), media_type=res.headers[ "content-type" ], ) HTTPクライアントの 非同期実装 である aiohttp を用いて、他サービスのAPIからオブジェクトを非同期でダウンロードする実装にしています。他サービスとして、今回は httpbin.org を利用しています。 aiohttp.ClientSessionでgetした際のレスポンス(ClientResponse)のcontentは、 aiohttp.streams.StreamReader 型となっています。これ自体非同期イテレータとして扱えるため、そのままStreamingResponseのcontentに渡してもOKですが、 iter_chunked(size: int) のメソッドを使って各イテレーションで扱うデータサイズを指定することもできます。 ちなみに、aiohttp.ClientSessionのレスポンスは、コンテキストマネージャーとして実装されており、そのままwith分を書くとwithブロックを抜けるタイミングでクローズされてしまい、contentをすべて読み込めないため、 contextlib.AsyncExitStack を用いて、レスポンスがクローズされるタイミングを遅らせています。 単体テストの実装 最後にmockを用いた単体テストの例も書いてみます。aiohttp.ClientSessionのメソッドをmockすることで他サービスからのResponseをmockしてテストを実装します。 ... class MockByteGenerator : async def iter_chunked (self, size: int ) -> AsyncGenerator[ bytes , None ]: for _ in range ( 5 ): yield b "x" * size class MockStreamResponse : def __init__ (self, content: Any, status: int , headers: Dict[ str , str ]): self.content = content self.status = status self.headers = headers async def __aenter__ (self): return self async def __aexit__ (self, exc_type, exc, tb): pass @ pytest.mark.asyncio async def test_get_bytes (test_client): with mock.patch( "aiohttp.ClientSession.get" ) as mock_get: mock_get.return_value = MockStreamResponse( content=MockByteGenerator(), status= 200 , headers={ "content-type" : "application/octet-stream" }, ) res = await test_client.get(f "/tasks/1/bytes/1" ) assert res.status_code == 200 assert res.content == b "x" * 1024 * 5 aiohttpのClientResponseをmockするclassであるMockStreamResponseは、ClientResponseと同じくコンテキストマネージャーとして実装する必要があり、 __aenter__ や __aexit__ のメソッドを追加しています。また、インスタンス変数として、contentを用意して、iter_chunked()のメソッドが非同期ジェネレータを返すように実装しています。 まとめ FastAPIでStreamingResponseを使って、オブジェクトをダウンロードするAPIの実装例を二つ書いてみました!StreamingResponseは、同期イテレータと非同期イテレータの両方をサポートしていますが、 非同期イテレータを渡してあげることで応答時間を改善することが可能です。S3クライアント、HTTPクライアント共に非同期の実装がすでに存在するので、それらを利用するだけで比較的容易に実装できるかと思います。これから、FastAPIを用いてAPI等を開発される方の参考になりますと幸いです!
こんにちは、フロントエンドエンジニアの沖です。 セーフィーには2022年1月にジョインし映像閲覧WebアプリであるSafie Viewer(以下Viewer)の開発を担当しています。 今回は、Viewerで利用しているフレームワークであるAngularのバージョンアップを9月末のリリース時に行ったのでその話をしたいと思います。 Viewerについて Viewerのアップデートサイクル 今回のアップデートによる変更点 Standalone Components Typed Forms 地味な変更点 アップデート作業について 今後試してみたい新機能 Standalone Components runInContext(Angular 14.1系) まとめ Viewerについて セーフィーのクラウド録画サービスは、カメラで撮影した映像をクライアントアプリケーションを用いて閲覧することが出来ます。クライアントアプリケーションはいくつかありますがその一つがViewerです。ViewerはWebブラウザで閲覧可能なWebアプリケーションとして提供されています。 機能としては、普通に映像が閲覧できる基本機能の他に複数のカメラの映像を同時に閲覧できるマルチビューアーだったりユーザーが独自の画面を作成し、閲覧できるダッシュボードだったり様々な機能があります。 このViewerでは下記の技術が導入されています。 言語:typescript フレームワーク:Angular CSS:Tailwind CSS Code Formatter:prettier Lint:eslint この度Viewerで利用しているフレームワークであるAngularを13から14へアップデートしました。 Viewerのアップデートサイクル Angularは約半年ごとにメジャーリリースが行われるので、セーフィーでは原則として新しいバージョンがリリースされてから1~2か月を目途にバージョンアップを行います。 Angular 13の時のIEサポート終了などインパクトが大きい変更がある場合にはリリーススケジュールが前後する可能性がありますが、出来るだけはやくバージョンアップできるようにしています。 マイナーバージョンについても随時Viewerのリリースサイクルに合わせてバージョンアップしています。 古いバージョンを使い続けることで安定性を担保するという考え方もありますが、最新バージョンに追従することで積極的に新しい機能を利用していくという面もセーフィーでは重要視しています。 今回のアップデートによる変更点 Angular 14の目玉と言ったらなんといってもStandalone Components(Standalone APIs)とTyped formsですね。 Standalone Components Standalone Componentsはmoduleを仲介しなくても直接呼び出せるcomponentで、今までmodule内で定義されていた暗黙的な依存がなくなるということで注目されている新機能です。 今まではNgModuleを定義してその中にcomponentと一緒に使うモジュール群をimportsに入れて定義しなければいけませんでしたが、NgModuleを介さずに直接componentを呼び出すことが出来るようになりました。 @Component({ selector: ‘sf-test’, template: ‘<div>standalone test</div>’, standalone: true // ここをtrueにすることでstandaloneとして扱うことが可能 }) export class TestComponent {} standaloneコンポーネントは直接呼び出せる一方で依存しているmoduleのimportを自身で行う必要があるため全てをstandaloneにするというより要所要所で利用していくのがいい印象です。 Typed Forms Typed formsはReactive Formsのvalueの型を指定できる機能です。 初期値を入れてFormControlを作成、もしくは型を指定してFormControlを定義しておくとそのControlのsetValueを呼ぶ際の型チェックやそのControlが属しているFormGroupのvalueChangesで型をつけることが出来ます。 const a = new FormControl(‘test’); a.setValue(true); // こういった場合にエラーとなってくれます。 地味な変更点 angular.jsonでのdefault projectが廃止になりました。 これにより今後ng buildやng serveの際に必ずprojectを指定する必要があります。 アップデート作業について 今回のアップデートは、あまり破壊的変更はなく新機能の追加がメインだったのでng updateで問題なくアップデートすることが出来ました。 default projectの部分は多くの人が作業が必要になるかと思われますが、それも大きな修正は必要ないため今回のアップデートは比較的気軽に出来るのではないかと思います。 今後試してみたい新機能 Standalone Components セーフィーのアプリケーションの性質上、ユーザーによって同じカメラを所有していたとしても利用できる機能が異なる場合があります。 現状もそのような場合にはLazyLoadingを利用して機能が必要のないユーザーに余計なモジュールをロードさせないような仕組みがあるのですが、その一部をStandalone Componentsに置き換えることで見通しのいいコードにできるのではないかと考えています。Developer previewが明けたら本格的に導入を考えたいと思います。 runInContext(Angular 14.1系) こちらは14.1の機能ですが、EnvironmentInjectorにrunInContextというメソッドが追加されました。この機能はrunInContextの中ではinjectメソッドを使うことによりEnvironmentInjectorでinject可能なサービスを取り出すことが出来るというものです。 今までは、DIで取り出すサービスを用いた処理は、InjectableなクラスやComponentなどAngularの管理下のクラス内で記述する必要がありましたが、これを用いることでサービスを用いたビジネスロジックもfunctionとして外に持たせることが可能となります。 const test = function() { const testService = inject(TestService); // testはただのfunctionですがこの中でinject出来ます。 } envirionmentInjector.runInContext(() => { test(); }); ビジネスロジックを外に出せるとテストの観点でも再利用の観点でもメリットが大きいので是非こちらは今後採用していこうと思っています。 まとめ 今回のAngularアップデートでは破壊的変更はあまりないものの新しい魅力的な機能が多数追加されているので、積極的にそれらを取り入れて使いやすく拡張性の高いアプリケーションにしていきたいと思っています。 フレームワークアップデートは場合によってはコストがかかる場合もありますが、保守性の向上やアプリケーションの最適化のためにも素早い追従が重要であると考えています。
こんにちは。セーフィー株式会社 モバイルエンジニアの渡部です。 今回は、モバイル開発のデザイン作成で活用しているツール「Figma」についてお話ししたいと思います。 Figmaとは 基本的な構成 Figma導入までのいきさつ Figmaで解決できたこと Figma vs XD XD Figma 最後に Figmaとは UIデザインやワイヤーフレーム作成のためのアプリケーションです。 デスクトップ版も用意されていますが、メインはWebアプリケーションとして提供されています。 動作が軽快で、リアルタイムでの共同編集が簡単に行えることが強みのツールです。 料金プランは現在3つ用意されており、安価なプランの順に、Starterプラン(無料)、Professionalプラン、Organizationプランがあります。 料金が上がるにつれ機能制限が無くなり、また扱える機能もより組織に向けた高度なものが増えていきます。 無料のStarterプランは作成できるファイル数に限りがありますが、編集権限を上限数無くユーザーに付与できます。 基本的な構成 画面サンプル キャンバス上にフレームを置き、フレーム上にボタンやテキスト等のパーツを追加していきます。上の画像だと、iPhone13をイメージした画面が2つキャンバス上に並んでいますが、これらがフレームです。 フレームやパーツの追加はツールバーから行います。繰り返し使うパーツはコンポーネント化(テンプレートのような機能。コンポーネント本体を編集すると、その複製も一括で編集される)もできます。 プロパティパネルでは、オブジェクトのスタイルを編集する他に、画像ファイルへの書き出しやプロトタイプ(モックのアニメーション)が作成できます。 また、ファイルに書き出すことなくその場で、選択中のオブジェクトのコード表記も確認できます。 Figma導入までのいきさつ モバイルチームでは従来、Adobe XDを用いてアプリデザインを行なっていました。 XDもUI/UXデザインツールとしては定番のアプリケーションです。 ツールを実際に活用するのはライセンスを割り当てられたデザイナーのみで、エンジニアはオンラインでの共有機能を使用して成果物の確認を行っていました。 しかし、XDを活用するにつれデザイナーとエンジニア双方から様々な要求と、それに伴う課題が挙がるようになりました。 成果物を手軽に共有したい XDの共有機能は、共有したいデータについてURLを発行することでオンライン上で閲覧が可能になるもので、データ作成者のみがURLを発行できます。 現状のチームの運用では、作成者と更新者が必ずしも一致しないので、URLをどこかに控えたり、発行を都度依頼する必要がありました。 また、データを更新すると、URLを据え置きにするか再発行するか選択できるはずなのですが、稀にその連携がうまくいかない場合が…… ファイル内のアートボード(アプリ画面)が増えても動作は軽いままがいい アプリ内デザインを一つのファイルに集約すると、それをXDで開く時点では問題無いのですが、共有URLで閲覧する際の動作が重くなります。 共有URLでの動作を軽くするために、ファイルを複数に分けていましたが、更新や管理が煩雑になりがちでした。 デザインデータとセットでコメントやメモを残したい 閲覧者もオンライン上のデータにコメントを残すことができますが、URLが削除されるとコメントも消える仕様でした。 完了したプロジェクトの共有URLは削除されることもあり、コメントを見返すことができないケースもありました。 軽微な修正をエンジニアも行えるように編集権限を与えたい 人数ごとにAdobeアカウントと利用料が必要となり、費用を考慮すると全員への付与は厳しいです。 これらを解決する代わりとなるツールを検討することになりました。 上記に加えて、検討の際に条件として上がったのは、 動作が軽快であること プロトタイプが作成できること お試しで運用がしやすいこと(無料プラン等が提供されている) などです。 結果、以上を満たして操作性や費用面を加味した上でFigmaに乗り換えることとなりました。 Figmaで解決できたこと Figmaに乗り換えてみて、先述した要求はかなり改善されました。 成果物を手軽に共有 原則としてブラウザ版を使用し、チーム全員に閲覧権限を付与することで、常に最新状態のデザインを確認できるようになりました。 例えばオンラインでのミーティングでも、各自がファイルを開きつつリアルタイムで変更点を話し合ったり編集することが可能になりました。 ファイル内のアートボード(アプリ画面)が増えても動作は軽いまま Figmaに移行するにあたって、XDで作成していたデータを1つのファイルにまとめました。下の画像のように、キャンバス内に全てのフレームを集約しています。 実際のファイルは更にフレームが増えていますが、読み込みも動作も軽快なままです。 ブラウザ上のファイル デザインデータとセットでコメントやメモを残す コメント機能も備わっており、デザイン決定までの経緯などをメモとして残せます。キャンバスを縮小してもコメントの存在を示すマーカーが表示され続けるので、どの部分にコメントが集中しているか視覚的に把握できます。 slackとの連携も可能で、自分へのメンションにも気付きやすいです。 編集権限に上限が無い 現在、モバイルチームではStarterプランを利用しています。 冒頭で書いた通りStarterプランは無料かつ編集権限に上限数が無いので、費用とアカウント数を気にせずエンジニアにも権限を付与できています。 実装過程で発生したデザイン上の軽微な修正や変更をエンジニアが行うことで、わざわざデザイナーに依頼してその作業待ちをすることが無くなり、全体の効率が上がりました。 また、当初は予想していなかった副次的に良かった点もありました。 採用・異動でチームの入れ替わりがあっても、仕様の共有が簡単になった 以前よりも成果物の共有と管理が楽になったことで、新しいメンバーにとっても仕様が把握しやすく、プロジェクトに参入しやすくなりました。 エンジニアのデザイン仕様に向ける関心が高くなった 編集を通じてエンジニアが直接的にデザインに関わる機会が増えたためか、当事者意識が高まった気がします。デザイナーとエンジニア双方が集まって意見を交わすことが増え、UIの検討が活発になりました。 エクスポート機能など、デザイン以外で便利な機能も多い 書き出しを介さないコード変換機能や画像出力機能など、デザイナーだけでなくエンジニアにとっても助かる機能が多々あります。 1ファイルが巨大になりがちな分、その一部分だけを扱う操作にも優れています。 Figma vs XD モバイルチームでは結果的にFigmaの特徴がうまく噛み合い活用できていますが、それまで活用していたXdにも利点はありました。XDのメリット、またどのようなケースであればそれを活かせるのかをFigmaと比較して考えてみました。 XD 編集権限はライセンス保持者のみに与え、閲覧者には簡易機能のみを許可できる 編集者と閲覧者で業務を明確に切り分けたい場合に有効。 成果物の共有ページ(共有URL)の機能がシンプル 操作が分かりやすいので、閲覧者が相当数いるチームでも活用しやすい。 他のAdobe製品と共通の操作性 チームが元々Adobe製品に馴染んでいる場合は導入しやすい。 プランによってはAdobe製品を横断して使用可能(CreativeCloud) UI/UX制作の他にも幅広くデザイン業務を行いたいケースでは便利。 Figma ユーザーに与えられる編集権限の上限数がない デザイナーに限らずメンバーが成果物に手を加えたい場合に有効。 コード変換機能やクリップボードへの直接書き出し機能など、ちょっとした機能が多数あり チームに開発者が多い場合、特に実装で役に立つ。 作成できるファイル数に限りがあるが無料プランあり 安価で運用したい少人数のチームにハマる。 そして、少し前になりますが、2022年9月16日にAdobeによるFigma買収が発表されました。驚きましたね… 個人的には、XDとFigmaは同じデザインツールといえどターゲットとするユーザーが異なると思っていたところだったので、両者の利点を生かしたままサービスが継続してくれればいいなと願っています。 最後に FigmaとXDの双方を使用してみてそれぞれの強みがあると実感しましたので、今後もツール選定の際は個々の特徴と自チームの目的が噛み合うか加味して決めていきたいなと思いました。 Figmaの特性である「共同編集に優れている」点は、実際使ってみることでデザインやUIに関わることの敷居を下げてくれ、非デザイナーがデザインに寄せる関心を高めることにも繋がっているなと、こうして振り返って思いました。 Figmaの理念は「すべての人がデザインを利用できるようにする」とのことで、まさしくそれを体感できたなと感じた次第です。 ここまで読んでいただきありがとうございました。
はじめに セーフィーで画像認識エンジニアをしている橋本です。本記事では、先日発売されたエッジAIカメラ SafieOne で利用可能なサービス 「Store People Detection Pack」 で開発した 画像認識システムとアルゴリズム について紹介したいと思います。 はじめに システムの概要 物体検出 学習時の工夫 量子化の工夫 トラッキング イベント発行と送信 まとめ 参考URL 「Store People Detection Pack」とは、小売業界の課題を解決するために作られた人物検出AIを使ったサービスです。 別記事 でも解説されていますので、是非合わせてご覧ください。 本サービスを利用することで、Webマーケティングでは当たり前に可視化できているコンバージョンにつながる顧客の行動を、リアルな店舗の映像から明らかでき、 小売業界での映像データの利活用が進む のではないかと期待しています。 Webサイトでは、どのようにページを遷移して商品を探し最終的な購入に至ったのかを把握できるため、「クリック率(逆に離脱率)がどのくらいなのか」といった情報から分析を行い、最適化が行いやすくなっています。一方で、リアルの店舗では、レジの売り上げ(POS)データで最終結果は分かるものの、購入に至るまでの経緯はブラックボックスになっています。 例えば、「今日は『かつ丼弁当』を食べよう」と思い立ってスーパーマーケットのお弁当コーナーに行き購入に至った目的買いのお客様と、店舗で様々なお弁当を検討して「かつ丼弁当」を購入したお客様の違いをPOSデータから見分けることはできません。本サービスを使えば 「かつ丼弁当」の前で立ち止まった人数を可視化でき、さらに録画映像を振り返って要因分析を行うことができます 。 システムの概要 本サービスの技術的な特徴は エッジAIを使った画像認識 を行っていることです。エッジAIというのは画像をアップロードしてサーバー上で推論するのではなく、カメラ毎に搭載されたプロセッサで推論するAIのことを指しています。一般的に、エッジデバイスには AIアクセラレータ が搭載されており、ニューラルネットワークを効率的に計算することができます。SafieOneの場合はQualcomm製のDSPが搭載されており、 SNPE(Snapdragon Neural Processing Engine)SDK を使ってネットワークの実行部分を実装しています。 本サービスがエッジAIを採用している理由としては、 低コストなリアルタイム処理が得意 デバイスごとに異なる賢さのAIをインストールできる(個別AI) の2点が挙げられます。 リアルタイム処理については、カメラの映像をデバイス内で直接処理できるのでアップロードが不要でレイテンシが少なく、専用アクセラレータを用いるためサーバー上の汎用ハードウェア(CPU、GPU)と比べてコストパフォーマンスに優れています。 また、個別AIについては、まだお見せできないのですが、 特定のユーザ・業種に対して専用のAIを簡単に作成してカメラ毎に異なる賢さを搭載できる ように開発を進めています。サーバー推論の場合はある程度のユーザ数で推論モデルと計算資源を共有することになるため、低コストな個別対応が難しいといった課題があるのですが、当社は自社でエッジAIカメラを販売しているため対応が行いやすくなっています。 システム全体の構成は上記のようになっています。SafieOneにはオプションを付けることで様々なアプリ(AI-App)がインストールできるようになっています。 PeopleDetection はエッジAIの本体でニューラルネットを使った画像認識を行うアプリです。 SafieEdgeAppFramework はSafieOneにインストールされるアプリのライフサイクル管理や、カメラ画像をアプリに送信する役割を持っています。 SafieClient はカメラハードウェアの制御、映像や検出結果(イベント)をサーバに送信するのが主な役割です。SafieEdgeAppFrameworkやSafieClientについては、 別記事 で解説されていますので是非合わせてご覧ください。 本サービスではエッジ側で画像認識結果をある程度集計して、ユーザが興味を持つ有益な事象(イベント)だけをサーバーに送って保存する仕組みになっています。ユーザーがSafieViewerを操作して検出結果の時系列グラフを見たいといったリクエストを送った場合には、サーバーに保存されているイベントを使って描画するという処理の流れになります。 エッジAI本体のPeopleDetectionは上のようなパイプラインで処理を行っています。物体検出( Object Detection )、追跡( Tracking )、イベント発行( Event Publishment )については次節以降で詳細を説明します。 物体検出 ライセンス: CC BY-SA 4.0 画像認識とは画像に含まれる意味や構造を抽出して、テーブルデータとして利活用できる状態にするプロセスのことを言います。画像認識のタスクはいろいろありますが、本サービスでは 物体検出 を利用しています。物体検出とは、 オブジェクトのカテゴリと位置と大きさを検出するタスク のことを言います。物体検出(object detection)は受け取った画像から、検出結果(raw detections)を計算して、次の追跡(tracking)処理に渡します。 SafieEdgeAppFrameworkにはビデオフレーム購読用のインターフェースが定められており、これをアプリ開発者が実装すると、当該メソッドが1 sec / 10 fps = 100ms間隔でcall backされて、アプリがカメラから画像を取得できる仕組みになっています。推論間隔が空いてしまう場合に、人物の動きが大きくなってトラッキングが不安定になるため、パイプライン全体のレイテンシの平均値と標準偏差を計測して、100msに収まるように ネットワークの選定とチューニング を行っています。 当社では、MobileNetV2-SSD、Yolov5、YoloXなどのアルゴリズム調査とチューニングを行い最も性能の良いネットワークと学習結果を採用しています。物体検出のアルゴリズムの進歩は早く、最近はPicoDet、Yolov7など新しいネットワークが登場していますので随時アップデートをしていきたいと考えています( ので エンジニアを鋭意募集 しております! )。 学習時の工夫 右画像のライセンス: CC BY-SA 3.0 画像認識の学習では、 推論環境と学習時で画像の分布を合わせることが重要 です。当社のカメラ(左)は天井に設置することが多く俯角が大きいことや、水平114° 垂直60°の広角レンズを使っているため、人物の見え方がCOCOのような汎用的なデータセット(右)とはかなり異なってきます。そこで、COCOをベースとしながら、 当社のドメインに合わせた画像を約21,000枚追加 したほか、画像回転のデータ拡張を強めにかけることで、パースによる人物の傾きに対応するようにしています。 量子化の工夫 エッジ実装では高速な演算と引き換えに、量子化誤差によって推論精度が低下することがあります。CPUでは32ビット浮動小数、GPUでhalf precisionを使った場合は16ビット浮動小数が利用できるため、丸め誤差を気にする必要はありませんが、SafieOneのDSPで 8ビット固定小数を利用した場合、256 段階の表現しかできないため考慮が必要 になります。量子化誤差を少なくするためのポイントは、 各レイヤーの出力の値域を絞る 出力の分散ごとにtensorを分ける ことです。 上の図はYolox-nanoの1・2番目の畳み込みレイヤーをnetronで可視化したものです。デフォルトのactivationは SiLU (左)となっていますが、上界がない(unboundedな)ため大きな値が出力される可能性があります。そうした場合に8bitで量子化するとステップ幅が大きすぎるためおおきな丸め誤差が発生し細かな諧調が失われる可能性があります。そこで、学習時に ReLU6 (右)など適当な閾値でクリップするようなactivationを使って値域を限定することで、学習時の性能(mAP)は下がりますが、8ビット変換時の量子化誤差を低減できました。 また、フレームワークに依存しますがchannel単位の量子化に対応していない場合(tensor単位の量子化しか対応していない場合)は注意が必要です。畳み込みの出力するtensorの分散がchannel毎に異なっていた場合、分散の大きなchannelにつられて量子化幅が決定され、分散の小さなchannelの情報が消えてしまいます。これを解決するには、分布が似ているチャネルごとにconvolutionの出力を分ける必要があります。 Yolox-nanoに416×416の画像を入力した場合に、一番大きな特徴量マップ(52×52)には、4チャネル(x, y, w, h)を出力する回帰用のヘッダ(左)、objectness(0~1)を出力する分類用のヘッダ(真ん中)、11クラスのスコア(0~1)を出力する分類用のヘッダ(右)が接続されています。回帰用のヘッダは値域が限定されないため、分類用のヘッダと同じtensorに纏めないほうが良いとされています。このネットワークでは、畳み込みは異なる経路で行っているため問題ないのですが、その後で1つのtensorにconcatする部分に注意する必要があります。1つのtensorにすることで量子化が再度行われ、丸め誤差が発生してしまうため、concatの前の出力をCPUの32ビット浮動小数点を使って計算することで精度の悪化を防ぐことができました。 物体検出では一般的に最終レイヤーで回帰と分類を行い、その後、デコード処理としてAddやMultiply、出力をまとめる目的でConcatenateが使われることが多いです。このような変換処理の度にtensor全体で再度量子化が行われ、丸め誤差が繰り返し発生してしまうため、 後処理の量子化実行には注意しておくと良い と思います。 トラッキング トラッキング処理は物体検出の結果(raw detections)を受け取って、人物IDのついた追跡結果(tracklets)を出力します。主な機能として、 同一人物に同じIDを割り当てる 検出漏れがあった場合に位置を推定して補間を行う を持っています。PeopleDetectionでは、軽量かつ性能も良いため SORTアルゴリズム を使っています。処理の流れは以下の通りです。 カルマンフィルタ を使って前フレームのtracklet(人物の位置と速度)から現フレームのtrackletを推定する 推定したtrackletとraw detectionを ハンガリアン法で二部マッチング する イベント発行と送信 イベント発行(Event Publishment)処理では、追跡処理で出力したtrackletsを受け取り、ユーザが興味を持つ有益な事象が発生した場合に、イベントとしてサーバに送信します。サーバーに送付するイベントは以下の3種類を定義しています。 設定した領域が滞留人数と時間の条件をみたした時に送信される PeopleDetectionEvent 設定した領域から人物が退出した時に送信される AreaLeaveEvent 設定した線分を人物が交差した時に送信される LineCrossEvent 上記のイベントがそれぞれ、 立ち入り検知 立ち入りカウント 通過人数カウント のサービスに利用されます。 PeopleDetectionEventは「 設定した領域で、N人以上、M秒以上の滞留が発生した場合 」にサーバーに送信されるイベントです。人物毎に滞留時間を計算することで、一時的にエリアに入った通行人を除くことができます。サンプル動画では3人以上10秒以上の条件を設定していますが、3人がエリアに入っただけでは条件をみたさず、最後に入った人物の滞留時間 t ≥ 10のときに条件が成立したと判定しているのが分かると思います。 このイベントが発生した時には、ユーザに通知を送る、Viewerのタイムラインにフラグとサムネイルを表示するといった処理が行われます。例えば、 レジ待ち行列が長くなったタイミングで通知を送ってレジ応援を呼ぶ といったオペレーションや、 映像の振り返りを効率化する といった活用方法を想定しています。 AreaLeaveEventは「 設定した領域から人物が退出したとき 」にサーバーに送信されるイベントで、イベントが発生した時刻のタイムスタンプと、人物がその領域に滞在した秒数を含んでいます。このデータをサーバで集計することで時間帯ごとにエリアに滞在していた人数を計算して、時系列グラフを表示できるようになっています。さらに、「N人以上、M秒以上」のクエリで絞り込むことで立ち止まりの人数だけを可視化することもできるため、 通行人と立ち止まりの比率を使ったファネル分析 といったご活用をしていただいています。 LineCrossEventは「 設定した線分を人物が交差したとき 」にサーバーに送信されるイベントで、イベントが発生した時刻のタイムスタンプと、線分を交差した方向を含んでいます。サンプル動画ではOut方向に2人、In方向に1人をカウントしています。 線分の前後でうろつく人物を過大にカウントするのを防止するため、線分を跨いだら同一人物に対して一定のクールダウン期間を設けるなど、フィールドテストで得られた知見から細かな改良を行ってきました。このデータもサーバーで集計されて時間帯ごとの入退場人数を計算して、時系列グラフを表示するのに使われます。 店舗入り口や通路に設置して人流の解析に使用する など、様々な用途でご活用をいただいています。 まとめ 本記事では、SafieOneで利用可能なサービス「Store People Detection Pack」で開発した画像認識システムやアルゴリズムについて紹介しました。当社のクラウドには、 15万台以上のカメラが常時接続されており、画像認識サービスを開発するのに恵まれた環境 だと思います。本記事で紹介したサービスの他にも、多様な映像サービスの考案と開発を進めていますので、 是非エントリーしてみてください 。 エントリーページ 参考URL かしこくなるAIカメラ Safie One(セーフィー ワン) SHIBUYA109渋谷店においてAIカメラ「Safie One」の実証実験を開始 セーフィー、ベルクと共同で「Safie One」を活用した実証実験を実施 セーフィー、エッジAIカメラ「Safie One」来店人数や混雑具合を可視化 | IT Leaders
こんにちは。セーフィー株式会社 バックエンドエンジニアの村田 ( @naofumimurata )です。 セーフィーには2020年9月に入社して今年の9月で2年が経ちました。入社後はインフラグループに配属となり、既存インフラの改善や新規サービスのインフラ設計/構築などサービスのインフラを支える仕事を担当していました。実は1年ほど前から自分の希望でチームを移動し、現在はサーバサイドの開発業務をメインで行っています。 今回はこれまでを振り返ってみて、サーバサイド開発に転向する上で大変だったことや良かったことなどを書いてみようと思います。 自己紹介 インフラグループでの仕事 スクラム開発の導入とサーバサイド開発へのチャレンジ なぜ転向したのか 実際やってみて 大変だったこと 良かったこと さいごに 自己紹介 まず、私自身の経歴について簡単にご紹介したいと思います。 大学院 CS専攻 を卒業 Web系企業に入社し SRE (Site Reliability Engineer) として2年半ほど勤務 セーフィーに入社 元々、低レイヤーの技術領域に関心があり、大学院ではデータベース管理システムの高性能化を研究していました。2018年に大学院を修士で卒業し、Web系企業に入社しました。入社後は SRE (Site Reliability Engineer) チームに配属され、サービスのインフラを管理する仕事を担当していました。2年半ほど経った頃に転職で今のセーフィーへと入社し現在に至ります。 インフラグループでの仕事 2020年9月にセーフィーに入社した後はインフラグループに配属となりました。インフラグループはセーフィーのクラウド録画サービスを支える全てのインフラの管理/改善などを行うチームで、主に以下の業務を行っています。 100万台規模のクライアントが接続するクラウド録画サービスを安定して提供できるアーキテクチャの設計・開発 サーバコストを抑えるための各種最適化 サービス・システムの監視やチューニング等の運用 参考: https://open.talentio.com/r/1/c/safie/pages/54192 セーフィーではインフラ環境としてAWSを利用しているのですが、自分が入社した当時はEC2インスタンスで稼働するアプリケーションのECS移行をまさに始めようとしている時期でした。入社直後はまずはその仕事を任せられることになり、いきなり重要な仕事を任せてもらえて嬉しかったことを覚えています。その後も既存インフラの改善から新規サービスのインフラ設計/構築、CI/CDの改善などいろんな仕事をやらせていただき、知識/技術を深めていくことができました。 スクラム開発の導入とサーバサイド開発へのチャレンジ インフラグループで1年ほど経った後、転機が訪れます。サーバ/インフラグループを中心に開発手法としてスクラムを導入することになり、チームの再編成が行われることになりました。これまでは人に対してプロジェクトを割り振るという開発スタイルだったために属人化や責任が集中してしまうといった問題があり、それを改善するべく認定スクラムマスターの資格を持ったメンバーを中心に導入することになりました。 参考: https://article.safie.link/safietimes/voice/2388 スクラムの導入にあたりチームが再編成されることになったのですが、その時の上司との1on1で、サーバサイド開発に挑戦したいという意向を伝えたところ(以前からそういう気持ちがあることを伝えてはいました)、いいタイミングだしサーバサイドの開発を行うチームに移るのが良いんじゃない?というお話しをいただき、はれてサーバサイドの開発を行うチームに異動することになりました。 なぜ転向したのか そもそもなんでサーバサイド開発をやりたかったの?という部分ですが、大きく以下の2つの動機がありました。 自分のキャリアに対する見識を広めたい 開発スキルを身につけ、セーフィーのシステム全体の理解度を高めたい まず1つめの動機についてですが、自分のエンジニアとしてのキャリアを考えるにあたって選択肢をなるべく増やせると良いかなと考えています。自分は今年でエンジニア歴5年目になりますが、これまでインフラの仕事しかしてきませんでした。元々、低レイヤーの技術領域が好きだったので、仕事内容に不満はなくそれなりに適性はあるのかなと思う一方で、ほんとにこの道が自分には合ってるのか?と悩む時もありました。色々悩んだ結果、そもそも他の技術領域をやったことないのに適性を判断するのは難しいので、色々やってみるのが良いんじゃないかという結論になりました。人生の時間は有限なので全ての領域にチャレンジするのは難しいですが、なるべく遠回りしていければ良いかなと思っています。 2つめの動機は単純で、開発を通してアプリケーション部分の理解を深めたいという思いです。これまでインフラチームでの仕事を進めるなかで、インフラ部分の知識や理解はついてきたのですが、各アプリケーションの具体的な動作やどう連携しているかについてはざっくりとしか把握できておらず、問題の調査がある段階までいくと難しくなったり、インフラに留まらない一歩踏み込んだ改善などができずにいました。開発業務を行わなくてもこれらの知識を深めることも可能だとは思いますが、やはり実際に自分で開発することで、より実感として得られるものがあるかなと考えました。 実際やってみて さて、実際にサーバサイドの開発業務を行うことになりましたが、いきなり全ての仕事をがらっと変えるというのではなく、ちょっとずつ開発の仕事をとっていくスタイルで進めていきました。徐々に開発業務の割合を増やしていき、現在はだいたい9割が開発業務という状態になっています。 ここでは、特に最初の頃を振り返ってみて大変だったこと、そして良かったことについて書いてみようと思います。 大変だったこと あたりまえですが、最初は技術面でキャッチアップすることが多くなかなか大変でした(今も苦労していますが)。これまで、TerraformやAnsibleといったいわゆるIaCのコードについては日常的に書いていましたが、アプリケーションのコードを書くという経験があまりなかったため、まず言語やフレームワークの使い方といった基本的なところから習得する必要がありました。 セーフィーのサーバサイドで稼働するアプリケーションには映像閲覧用のWeb/モバイルアプリケーション向けのAPIを提供するものや、カメラ映像を録画/配信を行うものなど様々なものがあります。前者ではPythonが使われており、後者ではJavaやGoなどが使われています。実際にサーバサイドの開発業務を始めるにあたり、まずは前者のPythonによるWeb APIの開発を行うこととなったため、まずはPythonの書籍を読みつつSQLやWeb APIの設計に関する書籍などを読んでいきました。具体的には、技術評論社の『Python 実践入門』、オライリー社の『Web API: The Good Parts』、『SQLアンチパターン』などです。カメラ映像の録画/配信を行う部分についても、少しずつ触れてきてはいますが、映像配信周りはかなり専門知識が求められる部分で難しく、その辺りのキャッチアップが自分の今後の課題です。 また、技術そのもの以外だと各アプリケーションのパッケージの構成やテストの書き方などについても最初キャッチアップするのに苦労したのを覚えています。このあたりは、歴史的な経緯でこうなってるみたいな部分もあり、背景を知るチームメンバーに教えてもらいつつ徐々に知識を付けていきました。 良かったこと まず、会社/チームからのサポートを得られたことが一番良かったことでした。未経験の技術領域にチャレンジするにあたって、自分のやりたい!という気持ちとは裏腹にチームとして受け入れてもらえるのか?という点が一つ懸念されることだと思いますが、その点が全く問題なかったのは良いことでした。短期的にみると、どうしてもパフォーマンスが下がってしまう可能性がありましたが、それよりも難しい課題に対してチャレンジすることを評価すると背中を押してもらえたので、会社からの評価が下がるといった不安なく仕事を進めることができました(実際、評価はむしろ上がりました)。 また、実際に開発業務をやるようになって、各アプリケーションの具体的な動作や連携方法などが以前よりもわかるようになり、システム全体の理解が深まったのも良かったです。これはそもそもの動機として挙げていた内の一つで、まだまだ把握できていないところは依然多いのですが、着実に達成されつつある実感があります。 さいごに 今回は、インフラ職からサーバサイド開発に転向している話をご紹介させていただきました。セーフィーに入社してまだ2年ですが、振り返ってみるといろいろ自由にやらせてもらっているなと感じました。まだ転向してから日が浅く、ちょっと道半ばな振り返りでしたが、同じようにキャリアについて悩んでいる方の参考になれば幸いです。ここまで読んでいただき、ありがとうございました。
こんにちは「Store People Detection Pack」のPdM(プロダクトマネージャー)の谷野です。 8/4にセーフィー初となるエッジAIカメラ、「 Safie One 」の製品発表を行いました! safie.co.jp 「SafieOne」では、エッジAIを利用する為の設定や結果を確認することができるブラウザアプリケーション「 AI-App(あいあっぷ) 」を使うことで、「映像×AIによる課題解決」ができる賢くなるカメラを目指しています。 その「AI-App」の第一弾として、「 Store People Detection Pack 」(AI-Appを使用できるようにするための、カメラと紐づくオプション)が小売り・飲食等向けに販売されます。 今回は、「AI-App」が目指すもの、そして「Store People Detection Pack」のコンセプトや機能に関して、PdM視点でご紹介させていただきます! 店舗におけるAIの役割 「Store People Detection Pack」のコンセプトとリリースまでの道のり Store People Detection Packの3つの機能 ①立ち入り検知 ②通過人数カウント ③立ち入りカウント 「AI-App」の今後 さいごに 店舗におけるAIの役割 セーフィーでは、「映像から未来をつくる」というビジョンのもと、防犯利用にとどまらずカメラが進化していく為のロードマップを5つのステップで提唱しています。「SafieOne」では、ステップ4の「映像×AIによる課題解決」を目指しています! 飲食や小売りの店舗様では、セーフィーのカメラを防犯用途はもちろんのこと、店舗運営のサポート、業務改善等にご活用いただいています。例えば、チェーン店等ではコンサルタントや本部の方が実際の店舗に訪問する+映像を確認することで、効率的かつ確度高く店舗の状態を把握したり、コンサルティングすることで、PDCAサイクルを早めることができます。 この映像を確認するきっかけとなるのが、下記のような場合です。 防犯:何かトラブルが発生した際に過去の映像を確認をする 業務改善:店舗のデータから気になる日時の映像を確認する 店舗でAIを使うと、リアルタイムにトラブルを発見出来たり、膨大な映像から知りたい映像にフラグが立てられ映像を探す時間が短縮されたり、検知結果の該当映像を確認することで新たな顧客の行動が発見されたり、店舗の既存システムでは取得できないデータを可視化したりすることができます。 店舗にとってAIは、店舗運営や課題解決に必要不可欠なものを見える化してくれるものだと思っています。 「Store People Detection Pack」のコンセプトとリリースまでの道のり 「Store People Detection Pack」は、誰もが手軽に店舗の課題解決につなげることができる機能とUI、価格であることをコンセプトとしています。 小売り・飲食のお客様から課題として多く声をいただいていたのが、主に下記3つです。「Store People Detection Pack」ではエッジAIの「人検出」を使い、それぞれを解決できる機能を盛り込んでいます。エッジAIは、カメラ側で人の検知を行いその情報を都度カメラからサーバーに送信します。そのため、利用者はリアルタイムに情報を受け取ることができます。 レジ混雑による機会損失をなくしたい →レジが混雑した場合に、検知を行い通知を受け取れる 棚レイアウトの効果検証手段がない →棚や商品の前に立ち止まった人数をカウントしグラフ化する 人員配置が適正かどうかわからない →店舗入口での入店、退店の人数をカウントしグラフ化する また、課題と共に多く声を頂いていたのが、価格です。複数のお客様から「課題を解決できるかもしれないAIカメラの提案を他社から受けたが、価格が非常に高く導入を見送った」と話しを伺い、「Store People Detection Pack」ではAIを手軽に使える価格での提供に努めました。 機能が形になってきた段階でフィールドテストを実施し、「店舗の課題が解決できる機能が盛り込まれているか」、「簡単に設定ができ、手軽に結果が取得できるか」等の確認を行いました。実は、初めてお客様に使ってもらった時、お客様だけでは、設定を完了することができませんでした。そのフィードバックを受け、現在は、設定時の遷移の仕方を改善することで、簡単に設定が完了できるようになっています。また、グラフも、知りたい情報が得られないとフィードバックがあり、グラフで表示される期間や集計単位がPOSデータと照らし合わせられる等、運用に合うようにるように改善を行っています。 また、「設定したエリアの中に立ち止まった人数をグラフ化する」という1つの側面においても、設計の試行錯誤がありました。設計にAIの画像処理側では、どの検知情報をどのタイミング(エリアに入ったorエリアを出たタイミングなど)でカメラからサーバーに送るか、サーバーでは時間をかけずにどう集計するか、フロントではどのように滞留時間でグラフを更新するかなど。 ここに記載の内容は本当に一例ですが、約7か月に渡るフィールドテストの期間を経て、機能、UI共に改善を繰り返し、リリースに至りました。 Store People Detection Packの3つの機能 SafieOneでは、エッジAIにて人を検知しています。「Store People Detection Pack」では上述した課題に応えるため、人検知を活用した以下3つの機能が盛り込まれています。 ①立ち入り検知 概要 :設定した条件に応じて、エリアに対して立ち入った人を検知し、通知する ユースケース :レジ混雑時に通知する、ディスプレイに立ち止まった人がいた場合に検知する、立ち入ってほしくないエリアに人が立ち入った場合に通知する ②通過人数カウント 概要 :設定したラインに対して、IN、OUTの人数をカウントしグラフ化する ユースケース :POSデータからの来店数ではなく、実際の入店数(日別や時間帯 別)から、人員配置の適正化をする ③立ち入りカウント 概要 :設定したエリアに対して、入った人数をカウントしグラフ化する 滞留時間での絞り込みが可能 ユースケース :特定の棚や商品前の立ち止まり人数で比較検証を行う ※画面は開発中のものです。仕様は予告なく変更される場合があります。 「AI-App」の今後 「Store People Detection Pack」では小売り・飲食のお客様から多く声をいただいた課題を解決できる機能を盛り込んでいますが、例えば、単に小売りと言っても業態はさまざまです。スーパーマーケット、百貨店、コンビニエンスストア、家電量販店、ドラックストア、ホームセンター、アパレル店など。抱えている課題も1つではありません。 今後、「AI-App」では、お客様の課題に応じたアプリケーションを準備していくことで、賢くなるカメラを目指しています。 さいごに 読んでいただきありがとうございました! 今回はPdM視点でAIアプリケーションに関してご紹介させていただきました。店舗の〇〇な課題を解決する為に作られたんだと伝わっていたら嬉しいです。 ご興味のある方やもっと技術的な内容が知りたいという方は、是非、お気軽にご連絡下さい。AI-Appは、引き続き、店舗の課題を解決できるアプリケーションを続々と!?作っていきますので、またこちらのブログにてお会いできる日を楽しみにしています。