TECH PLAY

セーフィー株式会社

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

221

インフラグループ所属の佐伯です。 2023年4月20日、21日に開催された AWS Summit Tokyo で「18 万台のカメラが接続する Safie のモダナイゼーションへの取り組み」というタイトルで登壇してきました。 登壇資料 概要 補足 伝えたかったこと 登壇を終えて 登壇資料 概要 サービスが成長するにつれてトイルが増えつつある状況のなか、トイル削減のためにチームで取り組んだ改善内容についていくつかピックアップし内容と工夫した点などについて紹介させていただきました。 補足 P31〜36 の「Amazon RDS のデータ共有に AWS Lake Formation を採用」については全てを紹介すると時間が足りない可能性があったため、内容のほとんどを省略していますが、内容は去年の Safie Engineers' Blog! Advent Calendar に投稿した以下記事となります。 Step Functions と Lake Formation を使った異なる AWS アカウントへの Aurora のデータ共有 伝えたかったこと トイルはサービスの成長に伴い増える性質のものですが、サービスローンチ時の構成を維持するのみではトイルは増え続けてしまう可能性があるので、塩漬けにせず日々改善しましょう!というのが伝えたかったことです。 登壇を終えて 登壇慣れしていないにも関わらず、練習不足もあり本番ではかなり緊張してしまい噛み噛みだった、且つ作成したスライドを読むのに必死だったと記憶しており(動画も恥ずかしくてまともに見れていない)、反省しています...。この規模のイベントで登壇する機会がまたあるかはわかりませんが次回はがんばります。
アバター
はじめに Doxygen実行用コンテナの作成 AWS S3を使った静的ウェブサイトのホスティング GitHub Actions むすび はじめに こんにちは。AI Vision グループでリードエンジニアを務めている橋本です。この記事では、コードドキュメントの生成を GitHub Actions と Doxygen で自動化することで、手間をかけずにドキュメントを作成する仕組みを紹介したいと思います。以下のスクリーンショットは作成するドキュメントのイメージです。今回はC++で実装されたアプリケーションを対象に実施していますが、その他の言語であってもこの手法が参考になると思います。 みなさんは、ドキュメントが整備されておらず、コードの理解や、チームメンバーとのコミュニケーションに難しさを感じたことはありませんか?例えば、新たにプロジェクトに配属され、適切なドキュメントがないために既存の実装を解読しなくてはならず、キャッチアップに骨が折れる思いをしたなど。 そんなときに、ドキュメントが整備されていると、コードの理解がとても容易になります。実装仕様書などのドキュメントを別途作成することもできますが、コードは更新したのにドキュメントの更新を忘れてしまい、実装とドキュメントが乖離するいった問題が発生しがちです。また、毎回手作業でドキュメントを更新するのは面倒ですね。 そこで、ソースコードから自動的にドキュメントを生成するDoxygenというツールを使うと効率的です。Doxygen は、C++、Java、Python等、多数の言語をサポートし、コード内のコメントを抽出して、構造化された形式で参照ドキュメントを生成することができます。ドキュメントには、クラスやメソッドの使用方法、インターフェース、クラスの継承関係、ヘッダファイルの依存関係、コールグラフなどを含めることができます。 ソースコードから自動的にドキュメントを生成すると、最新のコードと対応したドキュメントが常に維持できるのでとても便利です。コードとドキュメント(docstring)が一体になっているので、コードレビューと同時にドキュメントがチェックできるという利点もあります。 今回の自動化で処理される内容は、次の通りです。 ブランチがmainにマージされたことをトリガーにして GitHub Actions workflow を起動する Doxygen を使ってソースからHTMLを生成する 生成した HTML を AWS S3 にアップロードし、S3の静的webサイトのホスティングを利用して公開する 次のセクションから、構築手順を具体的に説明していきます。 Doxygen実行用コンテナの作成 このセクションでは、Doxygen を実行してHTMLを生成するDockerコンテナの作成について説明します。Dockerfile は こちら から閲覧できます。 はじめに、必要なaptパッケージをインストールします。Doxygenでグラフを描画するために、graphviz パッケージが必要です。 # Install apt packages RUN apt-get update && apt-get install -y --no-install-recommends \ graphviz wget git make \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* 次に、Doxygen をインストールします。今回は、Ubuntu 22.04 LTS のパッケージリポジトリでインストール可能な最新バージョン 1.9.1 ではなく、より新しいバージョン 1.9.3 をGitHubから直接取得しています。Doxygen 1.9.1 では、一部の可視化(数式、ツリー表示)が非対応となっているので、ここでは1.9.3を選択しています。 # Install Doxygen 1.9.3 RUN wget -q --no-check-certificate \ https://github.com/doxygen/doxygen/releases/download/Release_1_9_3/doxygen-1. 9 . 3 .linux.bin.tar.gz \ && tar -zxvf doxygen-1. 9 . 3 .linux.bin.tar.gz \ && cd doxygen-1. 9 . 3 && make install && cd .. \ && rm -r doxygen-1. 9 . 3 .linux.bin.tar.gz doxygen-1. 9 . 3 最後に、カスタム CSS をクローンします。カスタムCSSを適用することで、モダンでオシャレなドキュメントを生成することが可能です。ここでは、 doxygen-awesome-css を利用します。 # Clone CSS for Doxygen RUN git config --global http.sslVerify false \ && git clone https://github.com/jothepro/doxygen-awesome-css.git Dockerfile を使用してイメージをビルドし、作成したイメージを ECR にプッシュします。 AWS S3を使った静的ウェブサイトのホスティング このセクションでは、生成したHTMLドキュメントをS3にホストする方法について説明します。 S3バケットを新しく作成し、静的ウェブサイトホスティングの設定に移動し、次のように設定を行います。記載されていない項目についてはデフォルトのままで問題ありません。 設定項目 値 静的ウェブサイトホスティング 有効にする ホスティングタイプ 静的ウェブサイトをホストする インデックスドキュメント index.html 次に、アクセス許可の設定をします。今回は、社内ネットワークからのみ閲覧できるようにするため、特定のIPアドレスからのみ明示的にアクセスを許可し、それ以外のIPアドレスからは暗黙に拒否するようにします。以下のJSONをバケットポリシーとして適用します。 { " Version ": " 2012-10-17 ", " Id ": " AccessToDoxygenBucket ", " Statement ": [ { " Sid ": " AllowIP ", " Effect ": " Allow ", " Principal ": " * ", " Action ": " s3:* ", " Resource ": [ " arn:aws:s3:::your-doxygen-bucket ", " arn:aws:s3:::your-doxygen-bucket/* " ] , " Condition ": { " IpAddress ": { " aws:SourceIp ": " xxx.xxx.xxx.xxx/xx " } } } ] } バケットポリシーの設定が完了したら、ブロックパブリックアクセスの設定に移動し、「パブリックアクセスをすべてブロック」のチェックを外すことで、バケットへのパブリックアクセスを許可します。これで、webサイトを公開するための設定は完了です。 次に、GitHub Actions が ECRからDockerイメージをpullできるように、以下のようにポリシーを作成します。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Sid ": " AllowECRAuthorization ", " Effect ": " Allow ", " Action ": [ " ecr:GetAuthorizationToken " ] , " Resource ": " * " } , { " Sid ": " AllowPullFromECR ", " Effect ": " Allow ", " Action ": " ecr:* ", " Resource ": " arn:aws:ecr:your-region-name:xxxxxxxxxxxx:repository/your-repos-name " } ] } 加えて、GitHub Actions が S3にリソースをアップロードするためのポリシーも作成します。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Sid ": " AllowS3Sync ", " Effect ": " Allow ", " Action ": [ " s3:PutObject ", " s3:GetObject ", " s3:ListBucket ", " s3:DeleteObject ", " s3:GetBucketLocation " ] , " Resource ": [ " arn:aws:s3:::your-doxygen-bucket/* ", " arn:aws:s3:::your-doxygen-bucket " ] } ] } 上記で作成した2つのポリシーをIAMロールにアタッチします。IAMロールのARNを次のセクションで説明する GitHub Actions workflow で使用します。 GitHub Actions このセクションでは、GitHub Actions の処理について説明します。ここで紹介するスクリプトは こちら から閲覧できます。 まず、GitHubのworkflow が実行される条件を設定します。今回は、featureブランチがmain ブランチにマージされたときに実行したいため、push をイベントトリガーに設定します。また、featureブランチの変更を一時的に確認することをを想定して、手動でイベントをトリガーできるように workflow_dispatch も設定します。 on : push : branches : - main workflow_dispatch : 続いて、ジョブの定義を行います。OpenID Connect トークンを発行し、それを用いてAWSの認証を行うため、id-token スコープには write 権限が必要です。また、contents スコープについては、read 権限があれば十分です。 jobs : doxygen : permissions : id-token : write contents : read ここからは、steps の内容を順番に見ていきましょう。リポジトリのチェックアウト、AWSとECRの認証を済ませ、Docker イメージのpull や S3へのアップロードを行う準備をします。 steps : - name : Checkout uses : actions/checkout@v3 - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v2 with : aws-region : your-aws-region role-to-assume : ${{ secrets.YOUR_ARN_FOR_DOXYGEN_ROLE }} role-session-name : PublishDocsWithDoxygen - name : Login to ECR id : login-ecr uses : aws-actions/amazon-ecr-login@v1 次の、Pull image ステップでは、ECRから Docker イメージを取得します。このときのイメージ名と場所は環境変数で定義されています。取得したイメージ名を後続の処理で利用するため $GITHUB_OUTPUT ファイルに追加しておきます。 - name : Pull image id : pull_image env : ECR_REGISTRY : ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY : $YOUR_ECR_REPOSITORY_NAME IMAGE_NAME : $ECR_REGISTRY/$ECR_REPOSITORY:latest run : | docker pull ${{ env.IMAGE_NAME }} echo "IMAGE_NAME=${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT その次の、Build document ステップでは、Doxygenを実行してHTMLを生成します。 - name : Pull image id : pull_image env : ECR_REGISTRY : ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY : $YOUR_ECR_REPOSITORY_NAME IMAGE_NAME : $ECR_REGISTRY/$ECR_REPOSITORY:latest run : | docker pull ${{ env.IMAGE_NAME }} echo "IMAGE_NAME=${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT ここでは、カレントディレクトリが your-repo-name となっているため、一つ上のディレクトリに移動し、ディレクトリ構造を次のように整形します。リポジトリで管理しているDoxyfile(Doxygen設定ファイル)とリポジトリのロゴ(.png)をリポジトリと並列になるように移動します。 . ├── repo_logo.png # リポジトリのロゴ ├── Doxyfile # Doxygen設定ファイル └── your-repo-name # チェックアウトしたリポジトリ ホストのディレクトリ構造が準備ができたら、Dockerコンテナを起動して doxygenを実行します。カレントディレクトリをコンテナにマウントすると、コンテナのディレクトリ構造は次のようになります。Doxygenを実行すると、HTMLが /workspace/data/html 配下に生成されるようにDoxyfileで出力先を設定しています。 /workspace ├── data │ ├── repo_logo.png # リポジトリのロゴ │ ├── Doxyfile # Doxygenの設定ファイル │ └── your-repo-name # チェックアウトしたリポジトリ └── doxygen-awesome-css # カスタムCSS 最後のステップとして、生成したドキュメントをS3にアップロードします。 - name : Push document run : | cd .. aws s3 sync --delete --region ap-northeast-1 ./html s3://repo-document/ むすび この記事では、GitHub Actions と Doxygen を活用し、コードドキュメントを自動生成する方法を詳細に解説しました。使用したスクリプトは、 こちら で公開していますのでご活用ください。また、出力されるドキュメントの例としては、 Doxygen Awesome の Examples が参考になると思います。 紹介した方法によって、コードの理解が容易になるほか、ドキュメンテーションの維持管理も効率化できるため、開発チームがコーディングにより多くの時間を割くことが可能になり、製品開発のスピードと品質が向上することを期待しています。今後も、さまざまなツールや技術を活用した結果を紹介できればと思います。
アバター
サーバーサイドエンジニアの松木 ( @tatsuma_matsuki ) です。 セーフィーではいくつかのサービスでFastAPIを使った開発を行っています。FastAPIでは、Pydanticというライブラリを使ってリクエスト・レスポンスのモデルのバリデーションなどを実装することができます。このPydanticというライブラリですが、近いうちにメジャーバージョンアップのリリースが予定されており、これによりモデルのバリデーション処理が高速化されることがアナウンスされています! 以下のページによると、Pydantic v2はv1に比べて4倍~50倍の性能向上があると書かれているので、これは期待してしまいます。 https://docs.pydantic.dev/latest/blog/pydantic-v2/#performance そして、Pydanticを利用するFastAPIでも2023/6/21に 0.100.0-beta1 がリリースされ、このリリースでは、Pydantic v2を利用することができるようになっています!この記事の執筆時点では、Pydantic・FastAPIともにまだβ版ではありますが、試しに FastAPI + Pydantic v1 と FastAPI + Pydantic v2の性能を比較 してみましたので、その内容をこの記事で共有したいと思います。 Pydantic BaseModel Pydantic v2でモデルのバリデーションが高速化 環境セットアップ アプリケーションの実装 Locustで性能測定 性能測定の結果 まとめ Pydantic BaseModel PydanticのBaseModelについて簡単に説明します。BaseModelを継承したクラスでデータのモデルを定義することで、クラス変数に定義した型ヒントとインスタンス作成時に指定された値の型を比較し、正しい型なのかどうかをバリデーションしてくれます。型ヒント以外にも、独自のバリデーションルールを定義することもできます。以下がPydantic BaseModelを利用したバリデーションの例です。 from pydantic import BaseModel class Service (BaseModel): service_id: int name: str # 独自のバリデーションルール @ validator ( "name" ) def name_has_service (cls, v): if "Service" not in v: raise ValueError ( "name must have 'Service'" ) return v Service(service_id= 1 , name= "Service A" ) # > OK Service(service_id= "a" , name= "Service A" ) # > ValidationError Service(service_id= 1 , name= "A" ) # > ValidationError FastAPIを使っていると、必然的にこのPydantic BaseModelを使うことになるので、Pydantic は FastAPIで使うもの、というイメージを持たれるかもしれないですが、(私自身初めはそうでした)Pydantic の BaseModelは、FastAPI以外でも使うと結構便利です。 バリデーション以外にも、標準出力やdict型と相互変換、JSONシリアライズなどが簡単にできる他、インスタンス同士の比較もサクッとできてしまいます(単体テストなどを書く際に便利)。 from pydantic import BaseModel class Service (BaseModel): service_id: int name: str service_1 = Service(service_id= 1 , name= "Service A" ) print (service_1) # > service_id=1 name='Service A' print (service_1.dict()) # > {'service_id': 1, 'name': 'Service A'} print (service_1.json()) # > {"service_id": 1, "name": "Service A"} service_2 = Service(service_id= 1 , name= "Service A" ) print (service_1 == service_2) # > True service_3 = Service.parse_obj({ "service_id" : 1 , "name" : "Service A" }) print (service_1 == service_3) # > True Pydantic v2でモデルのバリデーションが高速化 Pydantic v2では、バリデーション部分の実装をRustで書き直し、 pydantic-core という別のパッケージに分離されています。これにより、v1と比較して、4~50倍バリデーション処理が高速化されたと Pydantic公式ページ で言及されています。 この記事では、このPydantic v1 → v2の性能向上がFastAPIで実装したAPIの応答時間にどの程度影響を与えるのかを調べるために、簡単なアプリケーション(APIサーバー)を実装して性能を測ってみます。 環境セットアップ 実際に簡単なAPIを実装して性能を比較するために、まずは環境のセットアップです。Python 3.10.6の環境で、まずはfastapiの最新バージョンをインストールします。 $ poetry add fastapi pydantic Using version ^0.98.0 for fastapi Using version ^1.10.9 for pydantic Updating dependencies Resolving dependencies... (0.2s) Package operations: 8 installs, 0 updates, 0 removals • Installing exceptiongroup (1.1.1) • Installing idna (3.4) • Installing sniffio (1.3.0) • Installing anyio (3.7.0) • Installing typing-extensions (4.6.3) • Installing pydantic (1.10.9) • Installing starlette (0.27.0) • Installing fastapi (0.98.0) Writing lock file 続いて、別の環境でpre-releaseのfastapiおよびpydanticをインストールします。pydanticが2.0b3、fastapiが0.100.0b1となっています。また、pydantic-coreというパッケージが増えていることも分かります。 $ poetry add fastapi pydantic --allow-prereleases Using version ^0.100.0b1 for fastapi Using version ^2.0b3 for pydantic Updating dependencies Resolving dependencies... (1.3s) Package operations: 10 installs, 0 updates, 0 removals • Installing exceptiongroup (1.1.1) • Installing idna (3.4) • Installing sniffio (1.3.0) • Installing typing-extensions (4.6.3) • Installing annotated-types (0.5.0) • Installing anyio (3.7.0) • Installing pydantic-core (0.39.0) • Installing pydantic (2.0b3) • Installing starlette (0.27.0) • Installing fastapi (0.100.0b1) Writing lock file アプリケーションの実装 今回は単純にあるオブジェクトのリストを返すようなAPIをアプリケーションとして簡単に実装します。今回Pydantic v2で性能向上が期待できるのはBaseModelによる変数のバリデーションの部分ですので、複雑な情報を持ったオブジェクトのリストを返すようなAPIが一番効果が大きいのではないかと予想をしています。 以下のような Service というモデルのリストを返すAPIを実装しました。conintを使ったり、datetimeを使ったりして少し複雑にしてみましたが、比較的シンプルな仕様のAPIかと思います。 Pydantic v1用の実装は以下の通りです。 from datetime import datetime from fastapi import FastAPI, Query from pydantic import BaseModel, Field, conint app = FastAPI() class Service (BaseModel): service_id: int = Field(..., title= "Service ID" ) name: str = Field(..., title= "Name of the service" ) version_number: conint(ge= 1 ) = Field(..., title= "Version of the service" ) version_name: str = Field(..., title= "Version name of the service" ) created_on: datetime = Field(..., title= "Date of the service was created" ) updated_on: datetime = Field(..., title= "Date of the service was updated" ) class ServiceList (BaseModel): offset: int = Field(..., title= "Offset of the list" ) count: int = Field(..., title= "Length of the list" ) total: int = Field(..., title= "Total count of the available services" ) services: list [Service] = Field(..., title= "List of the services" ) @ app.get ( "/services" ) async def get_services ( offset: int = Query(default= 0 , ge= 0 , title= "Offset of the list" ), ) -> ServiceList: services = [] for i in range ( 1 , 20 ): services.append( { "service_id" : i, "name" : f "service-{i}" , "version_number" : i, "version_name" : f "v{i}" , "created_on" : datetime.now(), "updated_on" : datetime.now(), } ) return ServiceList( offset=offset, count= len (services), total= len (services), services=[Service.parse_obj(s) for s in services], ) ただサーバー側でリストを適当に作って ServiceList のモデルとして返しているだけですので、負荷としてはほとんどがモデルのバリデーション部分だけなのではないかと思っています。 次のコードがPydantic v2用のコードです。ほとんど同じですが、Pydantic v2では、BaseModelのparse_obj() のメソッドの名前が変わっているため、そこだけ少し修正しています。 from datetime import datetime from fastapi import FastAPI, Query from pydantic import BaseModel, Field, conint app = FastAPI() class Service (BaseModel): service_id: int = Field(..., title= "Service ID" ) name: str = Field(..., title= "Name of the service" ) version_number: conint(ge= 1 ) = Field(..., title= "Version of the service" ) version_name: str = Field(..., title= "Version name of the service" ) created_on: datetime = Field(..., title= "Date of the service was created" ) updated_on: datetime = Field(..., title= "Date of the service was updated" ) class ServiceList (BaseModel): offset: int = Field(..., title= "Offset of the list" ) count: int = Field(..., title= "Length of the list" ) total: int = Field(..., title= "Total count of the available services" ) services: list [Service] = Field(..., title= "List of the services" ) @ app.get ( "/services" ) async def get_services ( offset: int = Query(default= 0 , ge= 0 , title= "Offset of the list" ), ) -> ServiceList: services = [] for i in range ( 1 , 21 ): services.append( { "service_id" : i, "name" : f "service-{i}" , "version_number" : i, "version_name" : f "v{i}" , "created_on" : datetime.now(), "updated_on" : datetime.now(), } ) return ServiceList( offset=offset, count= len (services), total= len (services), services=[Service.model_validate(s) for s in services], # parse_obj()は使えない ) Pydantic v1 → v2はかなり非互換のある変更が含まれており、移行には少なからずコードへの修正も合わせて必要になると思います。PydanticのページにMigration Guideが用意されていますので、詳しくは以下のページを参照ください。 https://docs.pydantic.dev/dev-v2/migration/ Locustで性能測定 さっそく、上で実装した簡易的なアプリケーションをLocustを使って性能測定したいと思います。 Locustのコードは以下のように書きました。(ChatGPTでほぼ同じコードが一瞬で生成できます) # main.py from locust import HttpUser, task, constant_throughput class ServicesList (HttpUser): host = "http://localhost:8001" wait_time = constant_throughput( 1 ) @ task ( 1 ) def get_services (self): with self.client.get( "/services" , catch_response= True ) as response: if response.status_code != 200 : 以下のようにPydantic v1, v2を使ったアプリケーションをそれぞれ起動します。 $ poetry add uvicorn[standard] ... $ poetry run uvicorn main:app --host 0.0.0.0 --reload --port 8000 INFO: Will watch for changes in these directories: ['/tmp/fastapi-0.100.0-beta1'] INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) INFO: Started reloader process [14005] using WatchFiles INFO: Started server process [14050] INFO: Waiting for application startup. INFO: Application startup complete. Locustのコードは、以下のように実行します。引数でユーザー数を指定することで、リクエスト数をコントロールします。 $ poetry run locust -f main.py --headless --users 10 --spawn-rate 1 --csv=outputs/fastapi_pydantic_v1 -t 1m ... Response time percentiles (approximated) Type Name 50% 66% 75% 80% 90% 95% 98% 99% 99.9% 99.99% 100% # reqs --------|--------------------------------------------------------------------------------|--------|------|------|------|------|------|------|------|------|------|------|------ GET /services 13 16 18 20 27 31 38 41 45 45 45 555 --------|--------------------------------------------------------------------------------|--------|------|------|------|------|------|------|------|------|------|------|------ 性能測定の結果 測定結果です!今回は、FastAPIのサーバーの起動は、ローカルのLaptop PC上で行っています。 以下が測定結果をグラフ化した結果です。横軸はLocust実行時に指定した設定したユーザー数で、縦軸が応答時間(ミリ秒)です。得られた結果を各統計値(中央値、90パーセンタイル値、95パーセンタイル値、99パーセンタイル値)毎にラインにして描いてみました。 性能測定結果比較 結果は一目瞭然ですね!ざっくり言うと、 2~3倍高速化 されていると思います!モデルのバリデーション部分が4~50倍の性能向上があるという話でしたので、今回のようなバリエーション処理以外ほぼ何もない実装だともっと劇的に向上するかも?と思っていましたが、思ったほどではなかったです。 もちろん、今回の結果はあくまで一例であり、実際に商用運用されているアプリケーションではどうなるかやってみないと分かりませんが、見積もりとして2倍以上の改善が見込めるのであれば、多少苦労してでも、Pydantic v2に移行する価値はありそうです! まとめ 今回は、簡単なAPIを実装して応答時間を測定してみましたが、実サービスのAPIでは、データベース等へのアクセスが挟まるので、実際にはその部分が支配的になり、応答時間に対してはあまり効果が出ないかも?とは思っていますが、FastAPIは基本そのようなI/Oを非同期で処理するので、APIサーバーでの負荷は結構下がるのかもしれません。早く実サービスでも試してみたいです! Pydantic v1 → v2へのバージョンアップは非互換のある変更を多く含んでいますので、今後慎重にバージョンアップしていきたいと思います!
アバター
こんにちは。 セーフィーで知財のマネージャーをしている渡辺と申します。 セーフィーでは、2022年1月に知財部門を立ち上げ、それと同時に私が入社して知財の体制づくりを進めてきました。 そこから約1年半が経ち、知財の基本的な機能は整ってきて、最近では特許もいくつか登録されてきましたので、今回はそれら特許からセーフィーの技術のトレンドを読み解いてみようと思います。 トレンド① ◆特許7279241 ◆特許7265072 ◆特許7227423/特許7271803 トレンド② ◆特許7159503 さいごに トレンド① では早速、具体的な特許を見ていきましょう! ◆ 特許7279241 この件は、新製品の Safie One に搭載されたAI-Appの1つである「 立ち入り検知 」について狙ったものです。 特許のポイントは、「対象エリアに留まっている人の①数②時間を設定し、①②の条件を満たす場合に、その時のサムネイルを表示する(請求項1)/タイムライン上にオブジェクトを表示する(請求項7)」という点です。 ◆ 特許7265072 この件は、小売店の レジ返金不正 に関する現場の確認作業を効率化するためのものです。 特許のポイントは、「返金処理が行われたタイミングで、レジ前の顧客領域内に人がいなかった場合、タイムライン上にオブジェクトを表示する」という点です。 ◆ 特許7227423 / 特許7271803 この件は、飲食店の デシャップに出された料理 に関する現場の確認作業を効率化するためのものです。 特許のポイントは、「飲食店の所定領域に料理が置かれた場合、タイムライン上に料理の種類に応じたオブジェクトを表示する(特許7227423)/その時の写真を撮影し料理の種類ごとにフォルダ分けして格納する(特許7271803)」という点です。 ・・・もう気付いたかもしれませんが、これらは「タイムライン上にオブジェクトを表示する」という点で共通しています。 そこから見えてくるのは、「撮り溜めた映像の中で、ユーザーが見たい映像にいかに素早く&簡単にアクセスできるか」という技術のトレンドです。 トレンド② ただ、セーフィーの技術はこれだけではありません。 ◆ 特許7159503 この件は、強風や振動等でカメラの設置角度が変わってしまった場合でも、AIモデルを用いて対象を検出できるようにするためのものです。 特許のポイントは、「映像データに基づいてカメラの設置角度を推定し、その設置角度に基づいて検出モデルを生成する」という点です。 その他、実は出願済のものはまだ色々あるのですが・・・未だ公開されていないため、また別の機会にということで(笑) さいごに さて、ご覧いただいて如何だったでしょうか? クラウドカメラと聞くと、きっと多くの人が「特許なんてあまり生れないだろう」と思われるでしょうが、セーフィーではここ約1年半で上記の様な特許が確実に生まれています。 その理由は、クラウドカメラの用途が監視から現場のDXへと進化しつつある中で、セーフィーの社員一人一人が「現場の負」としっかり向き合い、その解決へ向けて「泥臭い試行錯誤」をしているからだと思います。 このプロセス、実は発明が創出されるプロセスそのものなので、私は入社当初から「セーフィーでは新しいものが必ず生まれる筈だ」と感じていました。 知財担当として、その仮説を特許を取ることにより証明し、社員に自信と希望を持ってもらうことが、自分に課された使命の1つだと思ってやってきました。 ちょっと真面目な話になってしまいましたが・・・日々、新しいものが生まれる環境で、熱量を持った社員の方々と一緒に仕事できて、とにかく楽しいです。 今自分がやっている特許の権利化のイメージを料理に例えるなら、良い食材が目の前にあるので、その良さを最大限に引き出すべく、味付けは塩コショウのみで余計なことはせず、火入れは徹底するという感じでしょうか(笑) このブログをご覧の皆様の中で、もし将来セーフィーのエンジニアになる方がいらっしゃいましたら、その時は是非一緒に特許を楽しみましょう! セーフィーでは、エンジニアが特許に関わる際には、なるべく本業である開発の邪魔にならないよう配慮しつつ、一方で特許の楽しみを味わえるような仕掛けもしていますので、ご安心ください。
アバター
こんにちは、はじめまして。セーフィー株式会社 業務システムグループの大山です。 業務系システムの開発・運用を行っております。 今回はセーフィーで使用している「DataSpider Cloud」についてお話しさせていただきます。 DataSpider Cloudとは できること 使い方 例 1. データ読み取り(クエリー) 2. CSVファイル書き込み 3. マッピング 4. 完成 最後に DataSpider Cloudとは DataSpiderは複数のシステム同士を「つなぐ」ことに長けているツールです。 異なるシステム同士のデータを連携するには、各システムを理解している専門の技術者が必要でしたが、DataSpider Cloudを使用すると専門的な知識やプログラミング技術がなくても、ポイント&クリックで設定ができます。 開発や運用がしやすい事から、内製化しやすい、というメリットもあります。 できること DataSpiderはとにかく様々なシステムをAPI経由で連携することができます。 (Salesforce、AWS、Google、Microsoft Azure、Microsoft Dynamics365等) クラウドシステム間はもちろん、オンプレミスとクラウドをDataSpider Cloudを経由して連携し、DataSpider内でデータを移行することも可能です。 使い方 例 Salesforceからデータをエクスポートし、CSVファイルに書き出し DataSpider Cloudの設定はデザイナ上で行います。こちらはデザイナ画面です。①のツールパレットからツールを探し、②のキャンバス上へドラッグ&ドロップして追加していきます。 キャンバスには「Start」と「End」がデフォルトで存在し、ツールパレットから追加したツールは、「Start」と「End」の間に配置し、最終的にすべてつなげる必要があります。 1. データ読み取り(クエリー) ツールパレットから「データ読み取り(クエリー)」を選択して、キャンバス上にドラッグします。 ドロップすると、データ読み取り処理に関する詳細設定画面が表示されます。 「接続先」にはデータ読み取り先のSalesforce組織を選択します。 「テーブル名」にはデータを読み取る対象オブジェクトを指定します。 「スキーマの作成」から、データを読み取る対象項目を選択します。 特定条件のデータのみ読み取りたい場合は、「SOQLの作成」から条件を設定します。 設定が完了したら、「完了」をクリックして「データ読み取り(クエリー)」が1つ完成です。 2. CSVファイル書き込み 読み取ったデータをCSVファイルに書き込むため、次はツールパレットから「CSVファイル書き込み」を選択して、キャンバス上にドラッグします。 ドロップすると、データ読み取り処理に関する詳細設定画面が表示されます。 「ファイル」には、CSVファイルの置き場所を指定します。 「列一覧」には、CSVファイルのヘッダーを作成します。「追加」ボタンで列を追加し、「列名」に各列のヘッダー名を入力します。 「1行目に列名を挿入」にチェックを入れます。 ※定期的に同じ処理を行う設定で、同じファイルに行を追加したい場合、「追加書き込み」や「ファイルが存在する場合は列名を挿入しない」にもチェックを入れます。 必要な設定が完了したら「完了」ボタンをクリックし、「CSVファイル書き込み」も完成です。 3. マッピング 「データ読み取り(クエリー)」と「CSVファイル書き込み」を設定しましたが、まだそれぞれ単体で存在しているだけなので、接続作業が必要です。 ツール同士を接続する時も、ドラッグ&ドロップで簡単にできます。 「データ読み取り(クエリー)」を「CSVファイル書き込み」にドラッグ&ドロップすると接続され、間にマッピング用のツールが自動で出現します。 「マッピング」をクリックすると、マッピングの設定画面が開きます。 「入力元」は「データ読み取り(クエリー)」、「出力先」は「CSVファイル書き込み」の情報になります。 「入力元」の「row」配下と、「出力先」の「row」配下が線でつながっていますが、これは「データ読み取り(クエリー)」で読み取る項目と、「CSVファイル書き込み」で書き込むヘッダーが自動マッピングされていることを表します。 ※「CSVファイル書き込み」のヘッダー名を、読み取り項目のAPI名と合わせておくと、自動マッピングされやすくなります。 ※自動マッピングされていない箇所があった場合、手動でのマッピングも可能です。その際は「入力先」から「出力先」の方向にドラッグ&ドロップします。 4. 完成 マッピング内容が問題なければ、最後に「Start」から「データ読み取り(クエリー)」、「CSVファイル書き込み」から「End」の方向につなげると、1つの処理設定が完成しました! 設定した処理は、即時実行はもちろん、「マイトリガー」という機能で実行スケジュールを設定することも可能です。 最後に 今回は非常にシンプルな処理のご案内で恐縮ですが、DataSpider Cloudは本当に様々な処理設定が可能です! 慣れてくると、複数のサービスが絡む複雑な設定もできるようになるので、使えば使うほどおもしろく感じるツールだと思います! ・・・と大きなことを言いましたが、私もまだまだ勉強中です。 セーフィーには、私のようなローコード開発経験者レベルから、高度な開発スキルを持つスペシャリストまで、幅広いエンジニアが在籍しています。 セーフィーではエンジニアを募集しています! 興味を持っていただけたらぜひ採用サイトも覗いてみてください。 safie.co.jp
アバター
こんにちは。セーフィーで画像認識エンジニアをやっている木村Y(緑コーダー)です。 セーフィーには競技プログラミング愛好者が複数在籍しており、社内勉強会の一つとして競技プログラミングの勉強会が開催されています! 競技プログラミングとは? 概要 会の内容 最後に 競技プログラミングとは? 競技プログラミングについて簡単に触れておきます。 競技プログラミングとは、決められた時間内で、出題中の与えられた要求を解決するコードを記述する競技です。問題は通常、アルゴリズムやデータ構造の知識をテストするために、数学、グラフ理論、文字列処理、動的計画法などの分野から選ばれます。 競技プログラミングのコンテストの代表的なものとして AtCoder 社が主催するものがあります。この勉強会では、このAtCoderのコンテストの過去問を中心に扱っています。 概要 毎週火曜日の業務終了後に勉強会を行っています。会議室を借りて行っていますが、リモート参加ももちろんOK。 青色コーダーの現主催者から、最近興味を持ち競プロを始めたばかりの方まで、さまざまな習熟度の方たちが参加しております。 また、今年からセーフィーには新卒の1期生が入社しましたが、すでにさっそく何人かの方に参加してもらっています(競プロ文化の浸透を感じます)。去年と比較して参加者が続々と増えてきており、盛り上がりが増してきている実感があります。 回によってまちまちですが、毎回3〜6名の参加者がおり、その内訳もデバイスやフロント、自分のような画像認識エンジニアまで、様々な分野のエンジニアが参加しています(過去には経理部の競プロerの方にも参加していただきました)。 このように競プロの問題を解いてみたい!という方なら、熟練者や初心者に関わらず、またエンジニア・非エンジニアに関わらず、誰でもウェルカムとなっています。 会の内容 基本的な会の流れとしては、AtCoderの過去のBeginer Contestから選んだ1つの回をA問題から順番に解いていくことが多いです(A問題が最も簡単で、B問題、C問題・・と徐々に難しくなってゆくのがAtCoderコンテストの特徴です)。 A問題 D問題 解いている問題について語りながら、雑談もしながら、時には無言で集中モードになりながら、ゆるく解いてゆきます。 参加者の習熟度によって解くスピードはまちまちになりますが、初学者が悩んでいる部分に、熟練者がヒントを出しながら進んでいく光景がよく見られます。さまざまなレベルの参加者で、一緒に考えて楽しんでいこうというスタンスが強いと思います。 このようにして問題を解いてゆき、ちょうど良い頃合い(疲れ具合)を見て、ホワイトボードなどを使って1~2問解説を行って終了となる流れが多いです。「自分はこうやって解いた」という議論も盛り上がりを見せます。 初心者の方にとっては新しい問題にチャレンジする機会や熟練者から教えてもらう良い機会になりますし、すでに問題を解いたことのある熟練者にとっても、(なかなかやる気の起きない)復習の良い機会になり、新しい言語でチャレンジしてみることもできます。問題の解説をすることでアルゴリズムへの理解が改めて深まったり、初心者の解き方を見て新しい視点が得られることもあるかもしれません。 毎回解く問題については決まっているわけではないので、この問題を解いてみたい!という希望に応じてフレキシブルに変更して行っています。また「動的計画法」など、テーマを絞って問題を解いてゆくこともあります。 最後に 様々な種類が存在するエンジニアの中で、競技プログラミングというのは共通言語になりうる数少ない題材の一つだと思っており、他部署のエンジニアとの交流を深めるとても良い機会にもなっていると思います。 おかげさまで参加者が徐々に増えてきており、さらに会を盛り上げるためにも、今後はテーマ別の強化週間を増やしたり、Heuristic Contestも取り上げてみたりなど、新しい試みを取り入れていければと思ったりしています。 熟練度によって非常に差がつきやすいという性質を持つ競技なので、初心者が参加しやすく、かつ熟練者にとって有意義なものにするために工夫してゆきたいです。 競プロ経験者の方を心待ちにしておりますので、興味のある方は採用ページをぜひチェックしてみてください! safie.co.jp
アバター
こんにちは。セーフィー株式会社に所属するサーバサイドエンジニアの河津です。 セーフィーにはクラウドカメラやユーザーアカウントを一括管理できる統合環境である「 Safie Manager 」というサービスがあり、主にエンタープライズのお客様にご活用いただいています。 safie.jp エンタープライズ企業のお客様に対してもっと使いやすく、もっと効率的な管理ができるよう日々開発をしており、2023年2月には「効率的な管理」を実現させるための機能として、 ディレクトリ連携機能のリリース を行いました。 今回は、ディレクトリ連携機能をどのように開発していったか、またそれを実現させる「SCIM」というものについての記事を執筆してみました。 ディレクトリ連携とは Safie Managerのディレクトリ連携 SCIMについて シーケンス エンドポイント設定 インターフェース例 負荷対策について まとめ ディレクトリ連携とは 昨今様々な企業で、社員情報を管理するためにActive Directoryのような仕組みを用いているケースが多くなってきていると思います。 Active Directory (アクティブディレクトリ) とはマイクロソフトによって開発されたオンプレミスにおけるディレクトリ・サービス・システムのことです。2013年にはそのクラウドコンピューティング版である Azure Active Directory が誕生しましたが、こちらを導入されている企業様は多いのではないでしょうか。 azure.microsoft.com Active Directory(以下AD)で主に行えることとしてはユーザー認証とアクセス制御ですが、ADで構築した制御設定(誰がどの機能を触れるか)を他サービスにも反映させたい(=管理の手間をAD上の設定だけにしたい)という需要が生まれました。 ADの設定をそのまま他サービスに連携する、というのがディレクトリ連携のざっくりした説明になります。 Safie Managerのディレクトリ連携 Safie Managerとは、「誰が」「どのカメラを」見ることができるかを統合管理するためのアプリケーションです。 ユーザーとカメラそれぞれを管理する際、一人一人に対してカメラの視聴権限を割り振っていくのは手間になってしまうため、ユーザーとカメラそれぞれをグループごとにまとめて、グループ同士を紐づけることで「Aユーザーグループに所属しているユーザーは、A'デバイスグループ所属のカメラ全てが見れる」といった権限制御を行うことができます。 このグループの中に別のグループを作ることができ、まるでディレクトリ階層のような構造で管理を行うことができるのですが、Azure ADなども同様な階層構造でユーザー管理を行えるものであり、おそらくですがユーザーの所属部署情報や担当現場店舗ごとにグループを切る使い方が多いのではないかと思っています。 Azure ADでのグループ情報およびそこに所属しているユーザーを、Safie Managerにグループ情報ごと同期(プロビジョニング)できますというのが、Safie Managerのディレクトリ連携機能の概要になります。 ※Safie Managerの階層構造権限などの詳細な仕様については割愛させていただきますが、もし興味ある方はまずはぜひ 資料のご請求 からお願いいたします。 SCIMについて いざディレクトリ連携を行うとなった場合、システム管理者の立場から見ると、どのような設定を行えば良いのでしょうか。ここで使用されるのが SCIM というものになります。 SCIMとは、Identity情報(ユーザーの認証情報や権限)を自動プロビジョニングするためのプロトコルです。プロトコルと言うとHTTPのような通信規則が真っ先に思いつくかと思いますが、SCIMは通信規則ではなく標準規格です。「こういったインターフェースでやりとりしてくださいね」という取り決めのことです。 以前 SAML認証を用いたSSO(シングルサインオン)を実装する記事 を書きました。 SSO(シングルサインオン)機能を実装するための取り決めとしてSAMLを使用する ディレクトリ連携機能を実装するための取り決めとしてSCIMを使用する 上記のような関係性となります。 SCIMはRFCにも仕様が定義されているため、SCIMを用いたディレクトリ連携の機能を開発していく上で困った時はこちらを確認すると良いと思います。 tex2e.github.io 詳細な仕様は上記RFCをご確認いただくとして、この記事では私たちセーフィーがSCIMを取り扱った際にやったことや感じたことなどについて記載させていただこうと思います。 シーケンス セーフィーがディレクトリ連携機能のサポートをしているIdP(Identify Provider)は、執筆時点ではAzure Active Directory(以下AzureAD)のみとなります。他IdPの場合では差異があるかもしれませんが、AzureADからセーフィーにプロビジョニングされるシーケンスを記載します。 上記シーケンスは Microsoft公式のドキュメント から抜粋させていただいております。 SP(Service Provider)側はユーザー情報の追加や更新・削除ができるCRUDのAPIを用意しておき、IdP側で情報の更新が起きた際に随時APIを叩くようなシーケンスとなります。 SCIMでのプロビジョニング実行が行われる際、一番初めにGETのAPIが実行されます。このGET APIの実行結果により、 IdP側で持っている情報と違いがないため何もしない IdP側には情報があるがSP側には無いため、POST APIを実行しSPに情報を作る IdP側とSP側で情報が異なる(例: 名前が違う)ため、PATCH APIを実行し情報を更新する IdP側には情報がないがSP側には情報があるため、DELETE APIを実行し情報を削除する 上記のような処理に分岐していきます。 エンドポイント設定 SCIMで同期可能な情報は、ユーザー情報とグループ情報です。(他の情報もありますがここでの説明は割愛します) SPで実装したSCIM用のエンドポイントをIdPに登録することで、IdPから一定のタイミングで同期のリクエストが送られてきます。 情報登録を行える画面はこのようになっています。 登録できるエンドポイントは1つです。このエンドポイントを起点に、 /Users とリソース名が指定されたエンドポイントがユーザー操作の、 /Groups と指定されたエンドポイントがグループを操作するエンドポイントになります。 また、認証時のトークンを指定することができるため、SP側で用意するエンドポイントではトークン認証の仕組みを用意する必要があります。 トークンは Authorization: Bearer xxx 形式で送られてきます。また、Content-typeヘッダが Content-Type: application/scim+json となってリクエストが来るのも特徴です。 インターフェース例 SP側で用意したエンドポイントに対して、IdP側の情報変更があった際に都度SPにAPIリクエストが送られるというのがSCIMの概要ですが、SCIMはこのAPIリクエストの形式およびレスポンス形式についても取り決めがあります。 SCIMを行う上でのリクエスト・レスポンス形式(インターフェース)例はRFCに記載されているのと、IdPの仕様ドキュメントとしても記載されている場合が多いと思います。AzureADの場合は下記のドキュメントをもとにインターフェースの設計と実装を行いました。 learn.microsoft.com ここからはインターフェースの例をいくつか紹介します。 まずPOST /Usersのケースの例を記載します。ユーザーを同期するために必要となる情報は、 AzureAD上のユニークID ユーザー名 メールアドレス などを必須項目としており、下記のようなリクエストを受け付けられるようにしています。 { "externalId": "1234-5678-90ab-cdef", "emails": [{"primary": True, "type": "work", "value": "test@user.com"}], "displayName": "test user", } ちなみに externalId などのキー名については別の名前を使用することもできます。 AzureADには属性情報のマッピングを行う機能があり、この画面にて「Azure AD内のどの情報が」「どのキー名でやりとりされるか」を定義することが可能です。この画面からユーザー情報とSCIMのキーを紐付けます。 この辺りの設定は、IdPを操作できるお客様自身に設定いただくことになるため、基本的にはデフォルトとして設定されている内容を利用しつつ、どうしても設定いただく必要がある箇所についてはマニュアルに記載しつつお渡しするようにしています。 また、SCIMでプロビジョニングできる情報はユーザー情報だけではなく、グループの情報もプロビジョニングすることができます。グループが登録される際は、POST /Groupsに下記のようなリクエストが送られます。 { "schemas": [ "urn:ietf:params:scim:schemas:core:2.0:Group", "http://schemas.microsoft.com/2006/11/ResourceManagement/ADSCIM/2.0/Group", ], "externalId": "1234-5678-90ab-cdef", "displayName": "test group name", "meta": { "resourceType": "Group", }, } グループ自体の登録だけでなく、グループの階層配下にユーザーやグループを追加することもでき、その場合はPATCH /Groupsに下記のようなリクエストが送られます。 { "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ { "op": "Add", "path": "members", "value": [{"$ref": None, "value": "1234-5678-90ab-cdef"}], } ], } valueの中のIDがユーザーIDの場合はユーザーの追加、グループIDの場合はグループの追加となります。 負荷対策について 例えばIdPに10万件のユーザーを一度に登録した際、ディレクトリ連携されているとその10万件はSPに同期されます。 一気に10万件のリクエストが来るとなると負荷対策的に少し不安になりますが、その辺りはどうなっているのでしょうか。 Azure ADについては、送られるリクエストの間隔が仕様として決められています。 learn.microsoft.com 正常系についてはこの仕様通りの間隔でリクエストが来るはずです。後はそれ以上のリクエストが来る場合(誰かが悪意を持って実行しまくること)を考慮し、例えばリクエスト状況をサーバキャッシュで保持しつつ処理毎に判断し、上記仕様より高い頻度でリクエストが来ている場合は処理前段でエラーとするようにしておけば、少なくともDB負荷については対策の一つとなりそうです。 まとめ ここまでSCIMやディレクトリ連携について記載しましたが、試行錯誤しながら進めていった上で感じたことなどを記載します。 インターフェース仕様はRFCなどで定められているものの、開発を進める上ではIdPにエンドポイントを設定しつつ、どのようなリクエストが来るかログを見ながら仕様を固めていく方が間違いない。 実際のリクエストで判別がつきづらいケース(エラーの場合など)は、RFCの仕様に則って実装すると良い。 IdP側の仕様や挙動によって想定外の工数が発生する恐れがあるため、余裕を持った開発およびQAスケジュールが取れると安心だと思います。 SCIMについてはドキュメントも少ないと思われますので、この記事がどなたかの役に立てば幸いです。 セーフィーではエンジニアの採用を積極的に行っております。もし興味が出てきた際はぜひご応募いただけたらと思います。 safie.co.jp
アバター
こんにちは、セーフィー株式会社でサーバサイドのエンジニアをしている河津です。同時に、このSafie Engineers' Blog!の運営も行っています。 2022年12月にセーフィー株式会社初のアドベントカレンダーを実施し、大盛況のまま終わりました。 (だいぶ遅くなってしまいましたが、)この記事では、その様子について振り返りを行いたいと思います! 実施したアドベントカレンダーの基本概要は以前記事にしてまとめましたので、もし良ければこちらもご覧ください! engineers.safie.link 基本情報 モチベーション 運営側で意識したこと 執筆記事のピックアップ ピックアップその1:画像認識コンペについて(CTO森本のフリー素材化) ピックアップその2:PoC系の記事 ピックアップその3:サーバ・インフラに関わる記事 ピックアップその4:開発マネジメント系の記事 ピックアップその5:インターンシップについて 振り返り 基本情報 アドベントカレンダーはQiitaの機能を用いて行いました。 qiita.com 開催前から25枠全てが埋まり、一つの欠けもなく全ての記事が執筆・公開されました!改めて執筆いただいた方々には感謝しております。 モチベーション テックブログの更新などは少しずつ進めていたものの、アドベントカレンダーの開催は今回初となります。 他のテックカンパニーでは当たり前のように開催されており、アドベントカレンダーを開催しないこと自体がセーフィーにとってマイナスになると感じていました。 2022年度になってテックブログ運営チームの体制がようやく整ってきたため、満を持して開催することができました。 運営側で意識したこと まずは「開催して運営する」ことを目標とし、25枠埋めることは目標としませんでした。どちらかというと開催すること自体が最重要と思っていたのと、運営する上での課題は走ってみてフィードバックを集め来年に改善していくことを意識しました。(その上で25枠埋まったのはかなり嬉しいことでした) なるべく運営側の負荷がかかりすぎないよう、あえてレビューフローを短縮しリスクチェックのみとし、運営側が走りきれる体制を心がけました。(後の振り返りで、記事のクオリティアップのためにレビューは厚めにした方が良いかもというフィードバックを得ています。このフィードバックも、まずは実験的にやってみたことで得られたものだと思っています) 執筆記事のピックアップ ここからは、実際にアドベントカレンダーに寄せられたいくつかの記事についてピックアップしたいと思います。 ピックアップその1:画像認識コンペについて(CTO森本のフリー素材化) 執筆いただいた記事の中で、セーフィー社内で行った画像認識コンペをテーマに取り扱った記事が3記事ほど集まりました。 コンペの内容は、オフィス内に設置されているカメラにCTO森本が映ったらそれを検知し、メールやSlackなどに通知するまでを目標に開発を行う、というものです。 いずれも解析モデル構築について試行錯誤している記事になっています。 engineers.safie.link qiita.com qiita.com CTOの顔画像を自由に使って進めている雰囲気がとても良いと思い一番最初にピックアップさせていただきました。 ※アドベントカレンダーの記事ではないですが、最近も顔はめパネル作成をテーマにした記事内でCTOの画像を使用しつつ技術解説しています。 engineers.safie.link ピックアップその2:PoC系の記事 アドベントカレンダーに限らず、セーフィーのテックブログ内にはサービス内で使用されている技術や導入検討している技術の話などが多く集まる傾向にありますが、今回のアドベントカレンダーではそれに限らない様々な角度からの記事が集まりました。 そのうちの一つがPoC系の記事であり、「 whisper 」を用いた文字起こしや、今話題の「 ChatGPT 」について「やってみた/触ってみた」系の記事が集まりました。 いずれも多くの方に読まれた記事となりました。 qiita.com engineers.safie.link ピックアップその3:サーバ・インフラに関わる記事 AWSに関するインフラ周りをテーマにした記事が多く集まる傾向にありました。その中で特にAmazon Aurora MySQLのバージョンアップの話、AWSのコスト集計作業を効率化する話がぞれぞれ多く読まれた記事となったため、ピックアップさせていただきます。 engineers.safie.link engineers.safie.link ピックアップその4:開発マネジメント系の記事 テックブログには技術的な内容をテーマにしたものが集まる傾向にありますが、今回は開発マネジメント系の記事もいくつか集まりました。 マネジメント系と一括りに話しましたが、「スクラム開発」「プロダクトマネジメント」「エンジニアリングマネジメント」「新規事業開発」など様々な観点にフォーカスされた記事が集まりました。 engineers.safie.link engineers.safie.link qiita.com qiita.com ピックアップその5:インターンシップについて 2023年新卒入社予定でインターンシップに参加いただいた学生の方に、インターンシップの内容について記事にしていただきました。 志望理由やインターンシップで行ったこと・試行錯誤したポイント、学んだことなどを執筆しており、セーフィーへの新卒採用エントリーを検討している学生の方々には参考になる内容なのではないかと思っています。 engineers.safie.link 振り返り 初回での開催であり、運営側もノウハウがない中実施したため、いくつか運営側の課題が出てきました。一部を抜粋すると、 Qiitaのカレンダーを使用したが、Qiitaアカウントを持たない人に対して代理でURL登録を行う運用が手間になったため、 Adventar などの使用を検討する 参加ガイドラインの存在が伝わりきらず、ガイドラインの内容が長く分かりにくいからではないかという意見が挙がったためガイドラインの見直しを行う 告知と執筆者募集については早めに(10月初旬ごろには)行った方が良い などの意見が上がったため、改善につなげていきたいと思っています。 また、2023年度も実施を検討していますため、ぜひ楽しみにお待ちいただけたらと思います! セーフィーではエンジニアの採用を積極的に行っております。もし興味が出てきた際はぜひご応募いただけたらと思います。 safie.co.jp
アバター
こんにちは、セーフィー フロントエンドエンジニアの沖です。 こちらの記事でも紹介されてますが、セーフィーには様々な種類のエンジニアが在籍しています。 engineers.safie.link 組み込みソフトウェアエンジニア サーバーサイド / インフラ・SREエンジニア AI / 画像処理 エンジニア フロントエンドエンジニア iOS / Androidエンジニア 業務システムエンジニア QAエンジニア データエンジニア 各エンジニアは個人での技術研鑽はもちろんのことチームによっては定期的に勉強会を開いてその知見を共有しています。 チーム毎に勉強会の特色や目的が異なっていたりするので今回はその違いやどんな勉強会があるかについてお話したいと思います。 サーバーチームの勉強会 イメージングチームの勉強会 開発関連の勉強会 論文調査共有会 Webフロントエンドチームの勉強会 モバイルチームの勉強会 まとめ サーバーチームの勉強会 サーバーチームはサーバーサイド / インフラ・SREエンジニアが所属しているチームです。 サーバーチームでは、メンバー各々が持つ開発知見を共有しチーム全体の技術力を底上げすることを目的として隔週で勉強会が開かれています。 2名ずつ発表担当者を決めて、当日までにスライドを作成し約30分の持ち時間の中で発表と質疑応答をします。発表自体は20分で終了し、残り10分で質疑応答とすることが多いです。 こちらが勉強会の1例です。 「WebRTCについて」 内容:WebRTCの概要や通信の仕組み(シーケンス)などの解説。セーフィーの中でWebRTCは、カメラからのライブ映像を低遅延に閲覧するためや、セーフィーで提供しているウェアラブルクラウドカメラ「Pocket2」で使用できるグループ通話機能で使用されています。 開発に携わっている人以外のメンバーへの知見共有の意図もあり実施されました。 「Rustについて」 内容:Rust言語の概要やHello Worldまでの流れ、Rustの特徴である所有権についての解説。 セーフィーでは現状Rustでの開発はありませんが、新しい技術のキャッチアップの意図で開催されました。 「ディレクトリ連携について」 内容:Safie Managerというプロダクトに追加されたディレクトリ連携機能についての共有と、SCIMというものを用いて実装をした話をしました。 勉強会の様子 「Open Policy Agentについて」 内容:Open Policy Agentというポリシーエンジンを実験的に触ってみて、特徴や使用感などをデモと一緒に解説しました。 勉強会の様子 サーバーチームの勉強会では、実際に利用している技術の共有はもちろん、プロダクトには利用していない技術も勉強会のネタとして上がることが多いです。 イメージングチームの勉強会 イメージングチームは画像認識エンジニアが所属しているチームです。 イメージングチームで実施されている勉強会は2つあります。 1つは開発関連の勉強会、もう1つは論文調査共有会です。 開発関連の勉強会 開発関連の勉強会は、2週間に1回の頻度で開催されています。 テーマは、実際に開発しているプロダクトの内容や、各種ツールの紹介等が多くなっています。 例えば、以下のような勉強会が行われています。 「Yoloxの量子化とsnpeの使い方」 内容:SafieOne、AI-App: Store People Detection Pack(以降SPDP)で検討したネットワークであるyoloxの量子化技術、そしてSPDPで利用されるSNPE SDKの利用方法についての紹介 論文調査共有会 論文調査共有会も2週間に1回の頻度で開催されています。 画像認識エンジニアとして論文を読む習慣をつけることで最新の技術動向にキャッチアップすることを目的としています。 忙しい通常業務の中でも読む習慣をつけることが目的のため、共有のための資料を作るというようなことはしません。 紹介後には質疑応答があり、紹介者が分からなかった場合には皆で読みながら解決していきます。 以下が勉強会の1例です。 論文タイトル:「Rethinking Atrous Convolution for Semantic Image Segmentation」 内容:CVPR2022の2017年のセマンティックセグメンテーションの論文で、DeepLabV3として知られています。 こちらは業務でセマンティックセグメンテーションを扱う機会があり、その一環として扱いました。 イメージングチームの勉強会には論文調査共有会というアカデミックな内容が含まれているのが特徴です。 勉強会のネタだし会の様子 Webフロントエンドチームの勉強会 Webフロントエンドチームは、フロントエンドエンジニアが所属しているチームです。 既に実装されている機能の共有やチーム内のコミュニケーションの活性化を目的に毎週開催されています。 所属メンバーが順番に発表者となり話したいテーマを話していく形で進めています。内容の難易度は基本的に不問で発表すること自体を重要視しています。 スライドに関しては、発表者が要否を判断して発表を行いますが、スライドよりは実際のコードを見ながら議論するケースが多いです。 こちらが勉強会の1例です。 「認証の仕組み」 内容:フロントエンドで認証情報をどのように扱っているかの解説。 実際の実装を見ながらCORSやCSPなどの周辺知識を共有しながらディスカッションを実施しました。 「セーフィー独自で作成されたライブラリの説明」 内容:実装を簡略化するためにセーフィー内で実装されているライブラリの中身に関する説明。 利用する時の考慮点はもちろん内部実装を深堀ってディスカッションを行いました。 コードを映しながらディスカッション Webフロントエンドチームの勉強会では、実装している社員が変わっていくということを前提に既存のコードの深掘りを行うことが多いです。 メンバーによっては知っていることの復習になるケースもありますが、それは許容し発表することのハードルを下げるようにしています。 テーマは業務外の内容になることもありますが、比較的業務で利用している技術になることが多いです。 モバイルチームの勉強会 モバイルチームは、iOS / Androidエンジニアが所属しているチームです。 業務内外問わず個人が得た技術的知見をチームに共有することを目的に隔週で開催されています。 所属メンバーが順番に発表者となりテーマを決めてスライドを作成し20~30分で発表します。 その後質疑応答が10分~20分ほどあることが多いです。 どのようなテーマを取り上げるかはメンバーに任されています。 こちらが勉強会の1例です。 「RxSwiftについて」 内容:プロジェクトにRxSwiftを取り入れたタイミングで、実装者による初学者向けの解説を行いました。 「KMM(Kotlin Multiplatform Mobile)について」 内容:KMM自体を知らないメンバーにも分かりやすく概要から説明。 実際にチーム内で活用するならどうするか、どういったメリットがあるのか等を交えたディスカッションも行いました。 モバイルチームにはiOSエンジニアとAndroidエンジニアがいますが、勉強会は自分の担当しているOS以外の知見を広げられるいい機会となっています。 まとめ セーフィーの社内では数多くの勉強会が開かれていますが、チーム毎に課題と思っていることや求められているものが異なるため、勉強会の目的ややり方にも特徴が出ています。 また、勉強会自体も昔からあるものから新しく始まったものまで様々です。 セーフィーには、技術的にモチベーションが高いエンジニアが数多く在籍しているので、もし記事を読んでセーフィーに興味を持っていただいた方はぜひご応募ください。 article.safie.link
アバター
こんにちは。「 Safie Pocket2 」のプロダクトマネージャーをしている坂元です。 本記事では2022年11月に全てのSafieカメラで利用可能となった位置情報連携機能について紹介したいと思います。 位置情報連携機能とは 据え置きカメラにまで機能を拡大する理由 直感的なUI リリース後の反響 最後に 位置情報連携機能とは 位置情報連携機能は、2020年10月にGPSを搭載するウェアラブルカメラ「Safie Pocket2」に搭載された機能です。 Safie Pocket2が取得した位置情報をマップビューアー(Safie Viewerの地図アプリ)上に表示して、映像データと紐づけて表示することができます。 移動による位置情報の変遷を移動軌跡として表示が可能なため、Safie Pocket2の軌跡を追いながら映像を確認することが可能となりました。 この位置情報連携機能により、ユーザーはこれまでの時間軸による映像管理だけでなく、位置情報による映像管理が可能となり、映像データの利活用の幅を広げられます。 Safie Pocket2と位置情報連携機能は多くの現場で活用いただいています。 ある現場では、現場従事者の移動軌跡と映像データを、効率的な導線を定着させるための新人教育に活用されています。また、遠隔パトロール中のトラブル発生時には、管理者が位置情報を即座に共有し、応援を現場に向かわせるといったユースケースで採用されています。 Safie Pocket2の位置情報連携機能(イメージ) 据え置きカメラにまで機能を拡大する理由 Safie Pocket2のようにウェアラブルカメラは移動を伴うので、変化する位置情報と映像データの紐づけが、いかに有用かは想像しやすいと思います。 今回、移動することがほぼない「据え置きカメラ」になぜ位置情報連携機能を開発したかの背景を説明します。  映像データ+位置情報による映像データの活用を広げる Safie Pocket2で映像データに位置情報を紐づけることで、映像データの利活用の幅が大きく広がりました。据え置きカメラでも同じことを再現できると考えています。 管理者は据え置きカメラの位置情報と映像を基に、現場従事者にこれまで以上に適切な指示を出すことが可能になります。 例えば、河川管理の監視カメラ映像に位置情報と画角情報を紐づけるとします。すると災害発生時に、設置位置や、上流から見た映像か・下流から見た映像かを瞬時に判断して、適切な意思決定ができるようになります。 カメラの複数台管理を効率化させる Safie Viewerではカメラの映像を見たいときにはカメラ一覧からデバイスを選択する必要があります。 ユーザーはデバイス名を任意で付けることができるので、そのカメラを特定しやすいデバイス名を付けています。 複数のカメラを管理する場合、デバイス名で管理されている しかし、複数台のカメラを導入している場合、デバイス名は「〇〇工事現場南門( 北西側 から撮影)」「△△ビル カメラ 5号機 」のように、方角やナンバリング等でカメラを管理していることが分かりました。 カメラ設置した本人であればこれらのデバイス名でカメラを容易に特定できますが、現場を知らない場合、どこに設置され、どの画角で撮影しているカメラかを特定することが難しく、コミュニケーションコストが発生していたと推察しています。 こうしたカメラの複数台管理のコストを軽減させるために、地図上にカメラ設置位置の表示、画角の情報を表示させる手法は有効だと考えました。 直感的なUI Safie Pocket2と異なり、据え置きカメラの多くにはGPS機能が搭載されていない為、自動的に位置情報を取得することができません。 ユーザーが手動で位置情報を設定する必要がある為、カメラの位置情報をいかに簡単に設置させることに拘りました。 話はそれますが、Safieを選んでいただく理由でプロダクトの直感的な操作がよかったと言う声を多くいただきます。今回も多分に漏れず直感的に操作できるUIを目指しました。 カメラの位置と向きを設定する管理画面 マップビューアー(Safie Viewerの地図アプリ)の画面(イメージ) 管理画面の地図上にて、クリック&ドラックでカメラの位置を設定 カメラの位置は地図上をクリックすると表示されるカメラアイコンを 地図上でドラックするだけで容易に設定できます。 カメラの向きの簡単設定が可能 カメラ向きはマーカーをクリックして円周上で動かすと、マップ上にあるカメラの向きが連動して変わります。 マップビューアー内のカメラの視認性向上 機能の適用拡大により表示されるカメラの数が増えることから、地図上のカメラアイコンの表示内容やデザインを見直し、視認性を向上させました。   リリース後の反響 2022年11月のリリース後の1か月間で、位置情報連携機能の利用者は前月より 約60% 増えており(※)、据え置きカメラでの利用が進んでいます。 特に、建設現場等の屋外現場向けのSafie GOシリーズのユーザーの利用が多い傾向にあります。これは、広域の屋外現場に複数台のカメラを設置している建設業や土木業のニーズにマッチしている結果であると推察しています。 (※2022年10月と12月のマップビューアー利用者数の実績より) 最後に 2023年1月にSafie GOシリーズにGPS機能を持つ据え置きカメラ Safie GO PTZ Plus が追加されました。 これにより自動でカメラの設置位置を取得しマップビューアー上に表示することが可能となりました。(23年1月時点でSafie GO PTZ Plus以外の据え置きカメラは手動設定による位置情報設定が必要です) 「映像から未来をつくる」がセーフィーのビジョンです。 映像データの価値を上げるために、映像デバイスやAI解析技術の開発が進んでいます。今回の映像データ+位置情報もまた映像データの価値を上げ、ユーザーが映像データの利活用の幅を広げる一手であると思います。 今後もさらなる提供価値を高めていくため、映像データ+位置情報のブラッシュアップを継続していきます。
アバター
こんにちは!エンジニアの大林です。 先日セーフィーのエンジニアにアンケートを取ったので、今回はその内容をお届けします。 アンケートのテーマは「新卒エンジニアにおすすめの〇〇は?」ということで、以下の3つについて聞いてみました! 技術本 技術以外の本 本以外の学習媒体 新卒エンジニアにおすすめの技術本は? 新卒エンジニアにおすすめの技術以外の本は? 新卒エンジニアにおすすめの本以外の学習媒体は? 最後に 新卒エンジニアにおすすめの技術本は? たくさんの意見が寄せられましたが、中でも多かったのが以下の2冊でした。 1. リーダブルコード / Dustin Boswell、Trevor Foucher リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice) 作者: Dustin Boswell , Trevor Foucher オライリージャパン Amazon 美しいコードを見ると感動する。優れたコードは見た瞬間に何をしているかが伝わってくる。そういうコードは使うのが楽しいし、自分のコードもそうあるべきだと思わせてくれる。本書の目的は、君のコードを良くすることだ。(本書「はじめに」より) \エンジニアからのおすすめの声/ 仕事で書くコードは自分以外の人も読めて保守性が高いことは非常に重要です。 この本を読むことで他人が読めるコードを書くための書き方を理解できます。 読みやすいコードの何たるかを学べる。どんなエンジニアでも役に立つ! 最低限のコードルールを知っておいてほしいから。 コーディングする上でのTIPSが豊富! 私もエンジニアとして仕事を始める前にこの本を読みました。当たり前ですが、仕事でコードを書いていると、自分が書いたコードをチームのメンバーや未来の自分が読んだり、そのプログラムに改修を加えたりするので、コードの読みやすさがいかに大切かを実感します。読み物としてもとても読みやすい本だと思います! 2. Webを支える技術 / 山本陽平 Webを支える技術 ―― HTTP,URI,HTML,そしてREST WEB+DB PRESS plus 作者: 山本 陽平 技術評論社 Amazon 本書のテーマは,Webサービスの実践的な設計です。まずHTTPやURI,HTMLなどの仕様を歴史や設計思想を織り交ぜて解説します。そしてWebサービスにおける設計課題,たとえば望ましいURI,HTTPメソッドの使い分け,クライアントとサーバの役割分担,設計プロセスなどについて,現時点のベストプラクティスを紹介します。 \エンジニアからのおすすめの声/ Webの基礎が理解できるから。 開発全般で意識すること、周辺知識を理解できる! CSの基本的な内容。 この本を挫折しそうになった人には「プロになるためのWeb技術入門」がおすすめ! こちらの本もエンジニアとして仕事を始める前に読みましたが、文系出身でコンピュータサイエンスを全く学んでこなかった私には難しいと感じる部分もありました。ただ、業務をしていく中で「あ、これってあの本で言ってたアレのことか!」という風に点と点がつながる瞬間があったりして、たくさんインプットすることの大事さを感じていました。 また、この2冊ともをおすすめしたエンジニアからは「初学者であれば、平易で広い概念から入って、その後さらに専門領域の書籍に移っていくのが良さそう」という意見が出ていました! そのほかにもたくさんの技術本が挙がったのでジャンルごとにご紹介します。 様々なエンジニアが集まるセーフィーらしく、幅広いジャンルがおすすめとして挙がりました。 サーバー Amazon Web Services 基礎からのネットワーク&サーバー構築 Linuxとpthreadsによるマルチスレッドプログラミング入門 Linuxのしくみ SQL 第2版 ゼロからはじめるデータベース操作 SQLアンチパターン SREの探求 新しいLinuxの教科書 *達人に学ぶDB設計徹底指南書 言語・コーディング C++のためのAPIデザイン CODE COMPLETE 完全なプログラミングを目指して EffectivePython コーディングを支える技術 プログラミングTypeScript 実践ソフトウェアエンジニアリング 設計 Clean Architecture UNIXという考え方 オブジェクト指向でなぜつくるのか オブジェクト指向のこころ テスト駆動設計 データ指向アプリケーションデザイン ―信頼性、拡張性、保守性の高い分散システム設計の原理 コンピュータサイエンス CPUの創りかた RISC-VとChiselで学ぶはじめてのCPU自作 コンピュータシステムの理論と実装 プロセッサを支える技術 Web・ネットワーク Real World HTTP プロになるためのWeb技術入門 その他 JSTQB(資格) Team Geek ゼロからつくるDeepLearning ビッグデータを支える技術 達人プログラマー 新卒エンジニアにおすすめの技術以外の本は? こちらもかなり幅広く集まったので、一部ピックアップしてご紹介します。 ユニコーン企業のひみつ 起業のファイナンス 経営に終わりはない 武器としての決断思考 伝え方が9割 失敗の科学 失敗の本質 イシューからはじめよ イノベーションのジレンマ amazonのすごい会議 問いかけの作法 予想通りに不合理 若手育成の教科書 起業や経営に関わる内容のものが多くてベンチャーらしいなと思いました。また、急速に組織が大きくなっていることや、新卒採用を始めたばかり(23卒から)なことなどが影響していそうな本が多かったのもセーフィーらしいのかなと感じました! 新卒エンジニアにおすすめの本以外の学習媒体は? 本以外で学習するための媒体として何を活用しているかを聞きました。 podcast rebuild.fm fukabori.fm Udemy のような音声や動画で利用するものから、 プロコン(チーム形式) ハッカソン Atcoder 人! など、エンジニア同士のコミュニケーションを重視する声もありました。 最後に 新卒エンジニアに向けてのメッセージも募集しました! 技術本を読み漁ることも大切ですが、仕事に慣れない内はIPAの資格や業務関連資格を取得し、知識の体系化に努めると良いと思います。仕事に少し慣れてきたら、テック系の勉強会やイベントに参加すると良いと思います。社外の優秀なエンジニアから刺激を受け、勉強や仕事のモチベーションに繋がります。 誰でも初めは若葉マークと思っていますので失敗したことに対して落ち込みすぎないようにすると気が楽ですよ。挑戦してみての失敗は致し方なしですし、ボスが大体責任取ってくれますので今のうちに失敗をいっぱいしましょ! 数多ある企業の中で、せっかくできたご縁ですので一緒にいい仕事をしましょう! わからないことがあればなんでも聞いてください!技術トークしましょう! 周囲の常識や固定観念にとらわれずに、自分らしく伸び伸びとやってください! セーフィーではいろいろな技術領域を学ぶことのできるエンジニアとして成長できる環境です。ぜひセーフィーで成長し、新しい価値を社会にどんどん提供していきましょう。 セーフィーでは新卒エンジニアを募集しています! 興味を持っていただけたらぜひ採用サイトも覗いてみてください。 safie.co.jp
アバター
こんにちは。セーフィーで画像認識エンジニアをやっている柏木です。 今回はセーフィーで行った PoC (Proof of Concept) の一つである、商品棚の Semantic Segmentation について紹介いたします! 背景と課題 Semantic Segmentation DeepLabV3 データセットとアノテーション MMSegmentationを使った学習 データセットClassの作成 データセットConfigの作成 学習・テストConfigの作成 環境 学習 評価結果 終わりに 背景と課題 大手スーパーマーケット様より、商品棚の欠品状況を解析したいとのお話がありました。欠品の状況が解析できれば、品出しのタイミングを最適化し、機会損失を削減することができます。イメージングチームではこれらの課題を解決すべく、PoCを行ってみることとしました! こちらが実際の商品棚の写真になります。 Semantic Segmentation 欠品状況を解析するためのアルゴリズムとして、 Semantic Segmentation を選定しました。Semantic Segmentationとは、画素ごとにカテゴリ付けを行うニューラルネットワークのアルゴリズムです。自動運転や、医療用途で利用されています。 Semantic Segmentationは 境界をはっきり出すことができる 重なりがある物体や不定形の物体に強い ことが特徴のアルゴリズムになります。 別のアルゴリズム案として物体検出も考えましたが、重なった商品や小さな商品を検出するのが難しいことから、今回はSemantic Segmentationを採用しました。 DeepLabV3 DeepLabV3 はGoogleが2017年に発表したSemantic Segmentationアルゴリズムです。Atrous畳み込みを直列に何層も重ね、またAtrous rateを変えて並列に繋げたことが新規性のネットワークとなっています。論文は『 Rethinking Atrous Convolution for Semantic Image Segmentation 』です。 セーフィーではエッジAI機能を搭載したカメラである『 Safie One 』を提供しています。そこで、Safie Oneでも動作可能な軽量なモデルとしてDeepLabV3を使用することとしました。 DeepLabV3の公式リポジトリはTensorflow実装になっています。セーフィーではPytorchの知見が多いので、Pytorch実装である MMSegmentation を使用しました。こちらのリポジトリはDeepLabV3に限らず多くのモデルが実装されており、バックボーンも豊富で使いやすいです。 データセットとアノテーション データセットはスーパーマーケット様より提供いただきました。モデル自体が軽量ということや、固定カメラにおける利用を想定していることから多くの画像のバリエーションは不要と判断しました。そこで、Train(学習用)・Validation(検証用)・Test(テスト用)含めて200枚程度としました。 アノテーションツールは弊社が日頃から利用させていただいている『 FastLabel 』を使用しました。FastLabelは物体検出、物体追跡、Segmentationなど豊富なタスクをアノテーションすることができます。クラウドベースのサービスのため、アノテーションした結果をチーム内で共有することも容易にできます。またUIが日本語なので使いやすいです。 FastLabelが出力するアノテーション形式は選ぶことができます。今回はMMSegmentationで利用することが容易だったマスク画像を使用しました。 アノテーションにおけるクラスは background package person wagon の4種類としました。packageが実際の商品を表すカテゴリです。backgroundとpackageさえあれば欠品状況は解析できるのですが、人やワゴンが映り込むことでオクルージョン(隠れ)が生じることがあり、そのような状況は検出不能と扱う必要があるためのでperson、wagonと別カテゴリを用意しました。 マスク画像(黒がbackground, 赤がpackage) MMSegmentationを使った学習 データセットClassの作成 独自データセットで学習するためには、まずそのデータセットClassを作る必要があります。 mmseg/datasets/以下にデータセットの設定ファイルを作成します。 class SaladDataset (CustomDataset): """Salad dataset.""" CLASSES = ( 'background' , 'package' , 'person' , 'wagon' ) PALETTE = [[ 0 , 0 , 0 ], [ 0 , 0 , 192 ], [ 0 , 192 , 0 ], [ 192 , 0 , 0 ]] def __init__ (self, **kwargs): super ().__init__(img_suffix= '.jpg' , seg_map_suffix= '.png' , **kwargs) おもにクラス(カテゴリ)の名前CLASSES、推論結果の画像出力時の色であるPALETTEを変更すれば大丈夫です。 作成したら mmseg/datasets/init.py に作成したクラスを追加します。 データセットConfigの作成 configs/base/datasets/以下に学習・テストの設定ファイルを作成します。 dataset_type = 'SaladDataset' data_root = 'data/salad' img_norm_cfg = dict ( mean=[ 121.13 , 118.48 , 115.98 ], std=[ 3.14 , 2.97 , 3.15 ], to_rgb= True ) crop_size = ( 512 , 512 ) train_pipeline = [ dict ( type = 'LoadImageFromFile' ), dict ( type = 'LoadAnnotations' ), dict ( type = 'Resize' , img_scale=( 2048 , 512 ), ratio_range=( 0.5 , 2.0 )), dict ( type = 'RandomCrop' , crop_size=crop_size, cat_max_ratio= 0.75 ), dict ( type = 'RandomFlip' , prob= 0.5 ), dict ( type = 'PhotoMetricDistortion' ), dict ( type = 'Normalize' , **img_norm_cfg), dict ( type = 'Pad' , size=crop_size, pad_val= 0 , seg_pad_val= 255 ), dict ( type = 'DefaultFormatBundle' ), dict ( type = 'Collect' , keys=[ 'img' , 'gt_semantic_seg' ]), ] ~~ 省略 ~~ data = dict ( samples_per_gpu= 4 , workers_per_gpu= 4 , train= dict ( type = 'SaladDataset' , data_root= '/workspace/mmsegmentation/data/salad' , img_dir= 'train/images' , ann_dir= 'train/annotations' , pipeline=train_pipeline), 特に dataset_typeの指定 data_rootの指定 data augmentationのパイプライン(train_pipeline, test_pipeline) train, val, testのtype、data_rootの指定 に注意してください。 学習・テストConfigの作成 configs/ユーザー名/以下にネットワークモデルの設定ファイルを作成します。 _base_ = [ '../_base_/models/deeplabv3_r50-d8.py' , '../_base_/datasets/salad_data.py' , '../_base_/default_runtime.py' , '../_base_/schedules/schedule_20k.py' ] model = dict ( backbone= dict ( type = 'ResNet' , depth= 18 ), decode_head= dict (in_channels= 512 , channels= 128 , num_classes= 4 ), auxiliary_head= dict (in_channels= 256 , channels= 64 , num_classes= 4 )) 特に データセット設定ファイルの指定('../base/datasets/salad_data.py') クラス数(num_classes) 学習スケジュール('../base/schedules/schedule_20k.py') の指定に注意してください。 環境 Dockerfileからイメージを作成し、コンテナを立ち上げます。ただ元のDockerfileではmmsegmentationをgitでcloneしてインストールしていますが、今回は独自データセットを利用するためにコードを変更しています。そのため、git cloneではなく現在のディレクトリをマウントするようにする必要があります。また、データセットがあるフォルダをマウントしてください。 学習 学習はtools/train.pyで可能です。今までの設定で準備は終わっているので、学習・テストのconfigファイルを引数で指定すればOKです! 学習時間はGPUのNVIDIA GeForce RTX2080 Tiを使用して1時間ほどで終わりました。 評価結果 tools/test.pyで評価することができます。推論だけではなく評価もスクリプトに含まれているので、そのまま表示されます。 IoU は正解の領域と推論した領域の重なり具合を評価する指標です。一方で Accuracy はカテゴリを正確に推測できている画素の割合の指標になります。 Class IoU Acc background 95.31 97.35 package 85.38 92.61 person 87.1 97.14 wagon 78.65 86.24 クラス別の値をみるとbackgroundとpersonで高い値が出ています。一方wagonは少し苦手なようです。 aAcc はすべての画像でAccuracyを出したときの値、 mIoU は画像ごとのIoUの平均値、 mAcc は画像ごとのAccuracyの平均値です。 aAcc mIoU mAcc 96.13 86.61 93.33 精度という観点でみると9割以上を達成してます。勿論、今後さらなる改善に向けた精度向上は必要ですが、欠品検知という課題解決の目的では十分な性能と言っていいのではないかと考えています。 終わりに 今回はセーフィーで行っている、商品棚のSemantic Segmentationに関する取り組みを紹介しました。これ以外にもお客さんの課題を解決するためのPoCをどんどん行っていく予定ですので、興味がある方は是非チェックしてみてください! また一緒に働く仲間も募集しています!採用ページは こちら にあるので、よろしくお願いします!
アバター
データ分析基盤グループでデータエンジニアをしている平川です。 近年注目されてきているDataVaultに関して、全3回(予定)で記事を書かせていただく予定です。 第1回の記事では、DataVaultとは何なのか?どんな特徴があるのかを書いていきます。 参考までに、1~3回の内容を紹介しておきます。(内容は変わる可能性が大いにありますのでお許しください🙇‍♂️) 第1回: DataVaultってなに?どんな特徴があるの? ← 今回はここ 第2回: dbtvaultを使って実際にDataVaultモデリングでテーブルを作ってみた 第3回: BusinessVaultの使い所や特徴的なSatelliteの利用におけるハマったところや良いところ これまでのデータモデリング手法 3NF ディメンショナルモデリング DataVaultとは何か? Hub Link Satellite モデリングにおける注意点 DataVaultの優れている点と課題 DataVaultの優れている点 監査能力の高さ 取得するデータが増える場合のような変更に対する柔軟性の高さ 短いリリースサイクルでDWHを更新することの容易さ DataVaultの課題点 クエリがJOINだらけになる 初期構築時に「正しく」Hubを設計することが簡単ではない まとめ/次回予告 Hub Link Satellite 参考資料 これまでのデータモデリング手法 分析基盤構築において、 データウェアハウス(DWH) 層で行うデータモデリングは、アプリケーション開発のデータモデリングとは目的が異なります。 アプリケーション開発におけるデータモデリングの主な目的はシステムを円滑に動かすことです。 一方、DWH層でのデータモデリングはデータ分析のしやすさやデータの履歴化が目的になります。 両者の目的は異なるため、必要となるデータモデリングにも違いが現れます。 次に、これまでDWH層で使われてきたデータモデリングについて紹介します。 主要なデータモデリング手法には大きく2つあり、William Inmon氏が提唱した 第3正規形(3NF) によるモデリングとRalph Kimball氏が提唱した ディメンショナルモデリング があります。 今回はこれらのデータモデリングが主テーマではないですが、それぞれの特徴や課題を簡単にまとめておきます。 3NF 3NFによるモデリングは、アプリケーション開発でも使われている3NFと同じ意味です。3NFはテーブルの数が膨大なDWHの実装で使われることが多いです。 3NFの目的は、各テーブルの関係や情報を可能な限り詳細に保持することにあります。データソースであるアプリケーション全体のデータモデルをDWHに構築します。 3NFの問題点としては、作成するテーブルの数が膨大であることで、初期構築に多くの労力と期間が必要となることと新規パラメータの追加などへの柔軟性が低いことです。そのためユーザからの新たなパラメータの追加など柔軟性が高いとは言えません。 ディメンショナルモデリング ディメンショナルモデリングは、データソース(アプリケーション側のデータや別システムのデータ)をディメンションテーブルとファクトテーブルに分割して作成します。 ディメンションテーブルには、商品名などの属性情報を格納し、ファクトテーブルには売上などの数値情報が格納されます。 ディメンショナルモデリングはアプリケーション開発におけるデータモデリングとは構造が大きく異なり、データ分析に特化した構造になります。 ディメンショナルモデルの例 ディメンショナルモデリング手法をもとに構築されたテーブルは、データがディメンショナルとファクトで分割されていることで、データの理解がしやすくなります。 さらに、分析する際に実行するクエリも単純になります。一方でデータの変更対応には多くの時間やコストがかかります。 DWH層だけでなく、データマートの層においてもディメンショナルモデルが使われることが多いです。 これまで使用されてきた2つのデータモデリング手法には初期構築やデータの追加に多くの時間が取られるという課題があります。 データを使って意思決定を行いたいユーザは、分析基盤に変化するビジネスロジックに対する迅速な対応を期待します。 迅速で柔軟な対応のためには、リリースサイクルが短い方が利点があります。 柔軟性に欠けるという課題に対して、近年ではDataVaultと呼ばれるモデリング手法が注目されてきています。 DataVaultとは何か? DataVaultデータモデリングは、2000年頃にDan Linstedt氏が提唱したデータモデリング手法で現在では、進化を遂げてDataVault2.0(DV2.0)と呼ばれています。 DV2.0に基づいて作られたテーブルは、キー情報・テーブル間の関係情報・属性情報をそれぞれ格納する3つのテーブルに分割されます。 データを3つのテーブルに分離したことで、ビジネスロジックの変更に伴うデータの追加のような変化に対する柔軟さと俊敏さを手にしています。テーブルには一部の例外を除きデータをインサートするだけなので、データを履歴化して持つことができます。 さらに、特定の日付のデータを取得することが可能なことから監査の面でもメリットがあります。 データを分離しているという点ではディメンショナルモデリングと似ている部分もありますが、DV2.0では、テーブル間の関係情報も別テーブルで管理している点が、ディメンショナルモデリングとは異なっています。 DV2.0に基づいて作成される特徴的な3つのテーブルを紹介していきます。 Hub Hubテーブルはビジネスにおける中心となる実体・存在(エンティティ)を表現します。 Hubテーブルにはビジネスキーが格納されています。ビジネスキーは、エンティティをユニークにするキーのことを指します。ナチュラルキーとも呼ばれます。Hubテーブルに含まれるビジネスキーはビジネスの中心となるものなので、不変ないしは、ほぼ変化しないものが選ばれます。 テーブルを構成する要素は、ビジネスキーとそのhash key、さらにデータをロードした日付(or 日時)とどのデータソースからデータが来たかを示すカラムで構成されます。どのデータソースからデータが来ているかというカラムをRecord Sourceと呼び、元テーブルの名前やシステム自体の名前を格納します。 dbtvaultというdbtのパッケージのドキュメントでは、Record Sourceには元テーブルの名前を格納しています。 最後に簡単なテーブル構造の例を挙げておきます。 column type example product_pk binary 27634ff8002b12e75d98e07ccd005d18 product_name varchar Water loaded_on date 2022-12-01 record_source varchar products Link Linkテーブルの役割は全てのテーブル間の関係を表現することです。 テーブル構成は関係する2つのテーブルのhash keyと2つのテーブルのビジネスキーをconcatしてhash化したキーとHubテーブル同様にロードした日付とRecord Sourceで構成されます。 テーブルの構成は以下のようになります。 column type example order_product_pk binary 6fd0207c2d9ce3dcaddf870e96721a4b order_pk binary c4ca4238a0b923820dcc509a6f75849b product_pk binary 27634ff8002b12e75d98e07ccd005d18 loaded_on date 2022-12-01 record_source varchar orders Satellite Satelliteテーブルには、対象のエンティティの全ての属性情報が格納されます。 具体的には、商品というエンティティがあれば、値段や製造日のようなデータが格納されます。Satelliteテーブルはビジネスキーのhash keyとロードされた日付(日時)を主キーとします。 テーブルの構成は以下のようになります。 column type example product_pk binary 27634ff8002b12e75d98e07ccd005d18 hashdiff binary 1e6e0a04d20f50967c64dac2d639a577 price number 100 loaded_on date 2022-12-01 record_source varchar products Satelliteテーブルの特徴的なカラムは上記図にもあるhashdiffです。hashdiffカラムは属性情報を全てconcatしhash化した値です。 hashdiffの生成には全ての属性情報を使うため、属性情報に変更があった場合、hashdiffの値は変化します。 全ての属性情報のカラムを見るのではなく、hashdiffカラムだけを見ればよいため変更の検知が容易になります。 上図で示した例は属性情報のカラムが一つだけですが、属性情報が大量にあるエンティティを扱う可能性もあります。属性情報が大量にある場合、データの種類や変更頻度などでSatelliteテーブルを分割することも可能です。 Hub/Link/Satelliteテーブルの関係性を図にすると以下のようになります。 RawVaultの例 モデリングにおける注意点 DV2.0モデリングを進めていく際は、ビジネスの中心となるエンティティを最初に定義する必要があります。 なので今回紹介したHub > Link > Satelliteの順番でモデリングしていくことが推奨されます。 また、Hub/Link/Satelliteの3つのテーブルをまとめてRawVaultとも呼びます。 今回紹介したテーブル例ですとソースシステムのテーブルをただ3つのテーブルに分割しているだけですが、モデリングする際にソースシステムのテーブルのカラムを単純にHub/Link/Satelliteに分割することは良い手段とは言えません。 DataVaultを提唱したDan Linstedt氏曰く(元のブログポストはすでに消えてしまっているようです...) "Data vault modeling was, is, and always will be ABOUT THE BUSINESS. And if the Data Vault you have in place today is not currently about the business, then unfortunately you've hired the wrong people, and those people need to go back to school and relearn what Data Vault really means. Or you've build the wrong solution, and you need to fix it-immediately." と言っています。 ざっくりいうと、DataVaultモデリングは常にビジネスに関わるものでなければいけないということです。 ビジネスロジックなどを考えずにソースシステムのデータをそのままDataVault風に分割するのは適当とは言えません。 ここまで、DataVaultを構成するテーブルについて簡単に紹介させていただきました。DataVaultのメリットの片鱗は少し見えてきたかもしれませんが、次の章でDataVaultの優れている点をまとめていきます。 さらに、構築する上で課題となる点も紹介していきます DataVaultの優れている点と課題 DataVaultモデリングの優れている点はいくつかあると思いますが、個人的に他のモデリング手法よりも優れていると思われる点を3点ほど紹介させていただきます。 DataVaultの優れている点 監査能力の高さ DataVaultモデリングで作成されたテーブルにはインサートしかしません。(一部例外はありますが...) よって、過去の指定の時点のデータの状態を保持することが可能になります。インサートしかしないため、データの履歴化も可能になっています。 取得するデータが増える場合のような変更に対する柔軟性の高さ DV2.0モデリングに基づいて作られたテーブルはHub/Link/Satelliteに分割されています。 新規に追加するデータが、既に作成しているHubの属性情報であれば、Satelliteテーブルを作成するだけでデータ追加の対応が完了します。既にあるテーブル自体を拡張したりする場合と比べると考慮しないといけない点が減っています。 短いリリースサイクルでDWHを更新することの容易さ テーブルの設計方針が明確なため、新しいデータを取り込むことが他のモデリング手法と比べると容易なため変更へのコストが低くなります。そのため短い開発サイクルでリリースをすることが可能になります。そのため、ユーザー(データを使って意思決定をしたい人たち)が望んでいるビジネスロジックの変更に対する迅速な対応が実現できます。 紹介した以外にも、まだまだDV2.0の優れている点はあると思います。 一方で、残念ながらDV2.0にも課題があります。 DataVaultの課題点 クエリがJOINだらけになる 1つのエンティティから最低でも3つのテーブルが作成されるため、3NFやディメンショナルモデルと比べてテーブルが多くなります。 2つのエンティティ、例えば注文と商品というエンティティがあるとすると、商品情報と注文情報の両方を取得するには、以下のようなSQLを書く必要があります。 select order_name, product_name, order_created_at, price from hub_orders ho inner join link_order_products lop on ho.order_hk = lop.order_hk inner join hub_products hp on hp.product_hk = lop.product_hk inner join sat_orders so on so.order_hk = ho.order_hk inner join sat_products sp on hp.product_hk = sp.product_hk where sat_orders.loaded_at = ( select max(loaded_at) from sat_orders so2 where so.order_hk = so2.order_hk ) and sat_products.loaded_at = ( select max(loaded_at) from sat_products sp2 where sp.product_hk = sp.product_hk ) 上記のようなJOINだらけのクエリが生成されてしまい、可読性が高くありません。 なので、今回紹介したHub/Link/Satelliteテーブルだけを使って分析するのはクエリ自体は単純ですが、記述がかなり大変になります。 JOINが多くなってしまう課題への対処方法の一つはBridgeテーブルを作成することです。BridgeテーブルはHub/Link/Satelliteの各要素を統合してまとめたテーブルになります。 つまり、分析する際に毎回実行するようなデータの統合処理は先にやってしまおうという考え方です。 また、1つのHubに複数のSatelliteが付随している際に、それぞれのSatelliteテーブルの任意の時点のデータを抽出するというクエリも複雑になります。 この課題に対しては、PIT(Point In Time)テーブルの作成をすることが有効な手段と言えます。 データの統合や特定の期間のデータだけを取り出すような、ビジネスロジックの追加やデータを解釈する必要がある場合は、RawVaultとは別のテーブルを作成すると問題を解消できます。 Bridge TableやPIT TableのようなBusiness Vaultについての紹介は第3回でさせていただければと思います。 初期構築時に「正しく」Hubを設計することが簡単ではない 元のデータソースのテーブルをHub/Link/Satelliteに分けるだけで、見た目的にはRawVaultは構築できます。 DV2.0はビジネスに関するものであるという考え方からすると、単純にソーステーブルのデータを3つのテーブルに分割することは、必ずしも良い手段とは言えません。 DV2.0は、カラムの追加や取り込むシステムに対する柔軟性は高いですが、柔軟性が高いと言える状態にするためには、設計原理に基づいてRawVaultを設計する必要があります。 一見簡単に構築できそうですが、ビジネス理解やドメイン知識の理解がないとダメで、要求される知識が多く、初期構築が難しくなると思います。 設計・構築の難しさはモブプログラミングのような形式のモブモデリングである程度対処できるかもしれません。 まとめ/次回予告 本記事では、DataVaultモデリングに関する投稿の第1回として、モデリングの特徴や構築されるテーブルについて簡単に紹介させていただきました。 今回紹介したHub/Link/Satelliteがどんなカラムを持っているか最後に振り返っておきます。 Hub ビジネスキー/ナチュラルキーのhash化した値 ビジネスキー/ナチュラルキー ロードした日時/日付 元ソースの名称 Link Hubのビジネスキー/ナチュラルキーをconcatしてhash化した値 Hubのhash key その1 Hubのhash key その2 ロードした日時/日付 元ソースの名称 Satellite ビジネスキー/ナチュラルキーのhash化した値 属性情報を全てconcatしてhash化した値 属性情報(価格や登録日など) ロードした日時/日付 元ソースの名称 次回の記事では、これら3つのテーブルをdbtvaultというdbtのパッケージを使って構築する方法や実際に構築する際に困った点などについて紹介できたらと思います。 最後に、本記事を書くにあたり様々な書籍・ブログポスト・登壇資料などを参考にさせていただきました。より詳しい内容を知りたいという方はぜひ参考資料を見てみてください。 参考資料 Building a Scalable Data Warehouse with Data Vault 2.0 The Elephant in the Fridge: Guided Steps to Data Vault Success through Building Business-Centered Models The Data Vault Guru: a pragmatic guide on building a data vault Data Vault as a Modeling Concept for the Data Warehouse dbtvault入門 DataVault2.0をご紹介
アバター
こんにちは。セーフィー株式会社エンジニアの伊藤です。 今回の話はややフロントエンドエンジニア向けの内容となるのですが、UI/UX に関わる話もありエンジニアの方でなくてもお楽しみいただける内容となっていると思います。気軽に読んでいただけると幸いです。 さて、突然ですが、エンジニアのみなさんはユーザーが入力した画像のうち特定の部分だけを切り出したいと思ったことはないでしょうか?セーフィーでは SafieEntrance2 という顔認証技術を使った入退場の管理システムの開発において、そんな場面に遭遇しました。 SafieEntrance2 の課題 良さげな画像切り出しライブラリ Cropperjs Croppie まとめ おまけ 画像切り出し Canvas API マウス操作による切り出し範囲の変更 タッチ操作による切り出し範囲の変更 終わりに SafieEntrance2 の課題 SafieEntrance2 とは顔認証技術を使った入退場の管理システムで、自分の顔を鍵代わりにしてドアを開けることができるサービスです。こちらのサービスをオフィスに導入すれば、うっかりデスクに社員証を置いたまま外に出てしまったら誰か来るまでドアの前でそわそわしながら待たねばならない、という全ての社会人が抱えている悩みが過去のものとなります。気になった方はこちらの リンク を会社の偉い方に共有してあげてください。 SafieEntrance2 ではユーザーが利用登録するときに Web アプリ上で顔画像を登録します。この時にある程度の大きさでしっかりと正面を向いて顔が写っていないと顔認証の精度が落ちてしまいます。こちらとしては、免許証やパスポートの証明写真のように、ばっちり正面を向いた写真を登録してほしいのですが、何も意識せずに自撮りした写真は、引きで撮影されて顔が小さく映っていたり、写真の中に余計なものが映り込んでいたりします。また画像はスマホで撮影されるパターンが多く、そのままだと解像度が大きすぎるといった問題もありました。 さて、この課題を解決するために、下の図のように登録時に画像の上にガイド用の線を重ねて表示することで、観光地で顔はめパネルを見つけたらどうしても写真が撮りたくなる心理をつつき、顔認証に適した画像を登録させることにしました。 実際の手順は下記の通りで、顔写真の画像を撮影または選択した後に、PCの場合はマウス操作で、スマートフォンの場合はスワイプ・ピンチ操作で、ちょうどよい場所までユーザーの手で画像を移動してもらい、そこだけ切り出して顔写真を登録するという流れです。 ここでまさに画像の特定の範囲だけを切り出すという処理を実装する必要がありました。 良さげな画像切り出しライブラリ というわけで、上記のような機能を実装するにあたって、良さげなライブラリを探します。 Cropperjs とりあえず一番有名そうなのが cropperjs でした。実際の動作はリンク先のサンプルを動かすとわかりやすいと思います。 https://fengyuanchen.github.io/cropperjs/ こちらは画像の中から切り出したい範囲をボックスを動かして指定するという方法になります。よく見かける UI ではありますが、今回やりたいのとはちょっと違います。ただでさえ小さなスマホの画面の上で、小さく写った顔をなぞるようにしてボックスを指定するという形になるので、細かい指先の操作が要求されそうです。顔はめパネルに自ら顔をあてに行くというよりは、自分の顔の所に顔はめパネルの方を動かして持ってくるという感じです。というわけでこちらのライブラリは使いませんでした。 Croppie 続いて使えそうなのが Croppie でした。こちらもリンク先のサンプルを動かしていただくと実際の動作がわかりやすいです。 https://foliotek.github.io/Croppie/ こちらの方は切り出す範囲は動かずに、後ろに写った画像の方が動くというスタイルの UI になっております。目指したかったのはこちらの動きに近いです。しかもいろいろ機能もあってかゆいところにも手が届きそうです。 まとめ 今回は画像を切り出したいときに使える良さげなライブラリの紹介でした。世の中には便利なものがたくさんありますね。ただ、この内容だとフロントエンドエンジニア以外の人にとっては何にも面白くないので、こちらの技術を応用して顔はめパネルアプリを作ってみました。 顔はめパネル こんな感じの顔ハメ写真が作れます! 以上、現場からでした。 おまけ ちなみに実際はどうしたかというと、今回紹介したライブラリは利用せずに、全て自前での実装となりました。というのも、細かい動きを実現しようとしたときにライブラリが対応しておらず、やりたいことが実現できなさそうだったのです。ここからはライブラリを使わずに Croppie 的な動きを再現するにはどうすればよいのか、興味のある人だけ続きを読んでみてください。 まずは見本となるシンプルなアプリを用意しましたので、実際に動作を確認しつつ、デベロッパーツールでコードを見ながら読み進めると良いと思います。 https://safiepublic.github.io/kaohame/image.html 画像切り出し Canvas API 画像切り出しには Canvas の drawImage が使えます。 https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) こちらの API は、図が示すように画像の切り出したい範囲を引数の sx, sy, sWidth, sHeight で指定するだけのシンプルなものとなっています。各パラメータをユーザーの操作に合わせていかに更新していくかがポイントとなります。 sx, sy はどこから切り出すかのパラメータなので、マウスドラッグやタッチ操作の動きにあわせて良しなに更新すれば良さそうです。sWidth, sHeight はどれだけの範囲を切り出すかを指定するパラメータですが、最終的に切り出した画像のアスペクト比(画像の縦と横の比率)は固定する方針とするので、拡大率を scale としてひとつだけパラメータを持っておけばよいです。というわけで下記の値をグローバル変数として持っておきます。 let sx = 0 let sy = 0 let scale = 1 描画先では Canvas の全領域に描画することになるので、dx, dy はゼロで、dWidth, dHeight は描画先の Canvas のサイズで固定できます。 const dx = 0 const dy = 0 const dWidth = 500 const dHeight = 500 このように定義すると sWidth, sHeight は scale を使って下記のようにあらわすことができます。 const sWidth = dWidth / scale const sHeight = dHeight / scale こちらについては下の図を見てもらえるとわかりやすいかと思います。下図のように拡大率 (scale) が大きくなればなるほど切り出す範囲を狭くしていく必要があるため、数式の分母の方に scale が来ています。逆に scale が小さくなると、切り出し範囲は広くなり、より全体を見渡せる画像が結果として得られます。scale はマウスホイール操作やタッチのピンチ操作にあわせて更新することで、よくある画像ビューワーの UI を再現できます。 なんだかんだ描画している部分のコードは下記のような感じでとてもシンプルに書けます。 function draw() { const ctx = canvas.getContext( '2d' ) ctx.fillStyle = 'rgb(0, 0, 0)' ctx.fillRect(0, 0, canvas.width, canvas.height) const sWidth = dWidth / scale const sHeight = dHeight / scale ctx.drawImage(srcImage, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) } マウス操作による切り出し範囲の変更 続いてマウス操作で切り出す位置を更新しているコードを見ていきます。 切り出し位置 (sx, sy) の変更はマウスドラッグで行いますので、ドラッグ中かどうかを示すフラグを変数 (isMouseDown) として保持してする必要があります。またマウスの移動量を知る必要があるので、マウスの位置もグローバル変数として持っておきます。 let isMouseDown = false let mousePosX = null let mousePosY = null マウスイベントの mousedown, mouseup, mouseleave を拾って、isMouseDown フラグを On/Off を操作します。また mousedown のタイミングではマウスがクリックされた位置を記憶しておきます。 canvas.addEventListener( 'mousedown' , (e) => { isMouseDown = true mousePosX = e.offsetX mousePosY = e.offsetY } ) canvas.addEventListener( 'mouseup' , (e) => { isMouseDown = false } ) canvas.addEventListener( 'mouseleave' , (e) => { isMouseDown = false } ) mousemove ではドラッグ中の場合のみ、sx, sy を更新しますが、この時、画面上のマウスの移動量をそのまま sx, sy に反映させるのではなく、拡大率に応じて画面上の移動量を入力画像上の移動量に換算するところがポイントです。 canvas.addEventListener( 'mousemove' , (e) => { const newMousePosX = e.offsetX const newMousePosY = e.offsetY if (isMouseDown) { const mouseDiffX = mousePosX - newMousePosX const mouseDiffY = mousePosY - newMousePosY const canvasDiffX = mouseDiffX / canvas.clientWidth * dWidth const canvasDiffY = mouseDiffY / canvas.clientHeight * dHeight const imageDiffX = canvasDiffX / scale const imageDiffY = canvasDiffY / scale const newSx = sx + imageDiffX const newSy = sy + imageDiffY sx = Math.min(Math.max(newSx, 0), maxSx()) sy = Math.min(Math.max(newSy, 0), maxSy()) draw() } mousePosX = newMousePosX mousePosY = newMousePosY } ) 例えば、マウスが画面上を 100px 動いたとしても、倍率2倍で表示されていた場合、入力画像上では 50px (= 10 / 2) しか動いていません。 このように拡大率に応じて移動量を調整しないと、拡大時はマウス操作よりも大きく画像が動いてすべったような挙動になり、逆に縮小時はドラッグしてもなかなか画像が移動しないということになってしまいます。これを補正しているのが上記の処理です。 次はマウスホイール操作による拡大&縮小のコードです。 canvas.addEventListener( 'mousewheel' , (e) => { e.preventDefault() const prevScale = scale const newScale = e.deltaY > 0 ? scale * 0.95 : scale * 1.05 scale = Math.max(newScale, minScale()) const focusX = mousePosX / canvas.clientWidth * dWidth const focusY = mousePosY / canvas.clientHeight * dHeight const newSx = sx + focusX * (1 / prevScale - 1 / scale) const newSy = sy + focusY * (1 / prevScale - 1 / scale) sx = Math.min(Math.max(newSx, 0), maxSx()) sy = Math.min(Math.max(newSy, 0), maxSy()) draw() } ) ホイールの操作に合わせて scale を更新すればよいのですが、コードを見ると同時に sx, sy も更新しています。これはマウスカーソルがある位置にフォーカスしてズームを行いたいからです。こちらも下の図を見てもらうとわかりやすいと思いますが、マウスポインタが指している位置を動かさずに拡大&縮小を行おうとすると、切り出しの開始位置の座標である sx, sy も一緒に動かす必要があるのです。 マウスポインタがある位置の画像上の座標(imagePosX, imagePosY) を数式で表すと次のようになります。 imagePosX = sx + mousePosX / scale imagePosY = sy + mousePosY / scale ここで、拡大の前後でマウスポインタの指している位置が変わらないように sx, sy を調整するので、拡大前のsx, sy, scale を prevSx, prevSy, prevScale とし、拡大後の sx, sy, scale を newSx, newSy, newScale とすると、拡大の前後で次の式が成り立ちます。 prevSx + mousePosX / prevScale = newSx + mousePosX / newScale prevSy + mousePosY / prevScale = newSy + mousePosY / newScale こちらの式を newSx, newSy について解くと次のようになり、補正後の位置が求まります。 newSx = prevSx - mousePosX * ( 1 / prevScale - 1 / newScale ) newSy = prevSy - mousePosY * ( 1 / prevScale - 1 / newScale ) サンプルのソースコードでは Canvas 上の位置の補正も入っているので上記の式とはちょっと違うのですが大体こんな感じです。ちなみに Croppie はこのあたりの処理がややあまく、拡大&縮小は常に画像の中心にフォーカスして行われるようになっていました。 タッチ操作による切り出し範囲の変更 スマホの場合はタッチ操作になるので、タッチイベントを拾って処理することになります。少し長いのでちょっとずつ見ていくことにします。 touchmove イベントでタッチした点の座標を配列で受け取ることができます。ただし、取得できる座標は canvas 上の座標ではなくウィンドウ全体の座標となっているので、canvas の位置を差し引いて、canvas 上の座標に変換します。 // タッチの位置を計算する const canvasRect = canvas.getClientRects() [ 0 ] const newTouches = [] for ( let i = 0; e.touches.length > i; ++i) { newTouches.push( { x: e.touches [ i ] .clientX - canvasRect.left, y: e.touches [ i ] .clientY - canvasRect. top , } ) } 続いて画像の移動処理ですが、2本以上の指で操作されることもあるので、戦略としては全てのタッチ点の平均位置を計算して、平均位置の移動量を画像の移動量とすることにしています。これによって2本指以上でも操作が可能です。処理のはじめにタッチ点の数が違う場合に処理を中断している理由は、途中でタッチの点数がかわることによって、平均位置が急激に変化して画像が一瞬で大きく動くということを防ぐためです。 // タッチ数が異なる場合はタッチの位置だけ記憶してリターン if (touches.length !== newTouches.length) { touches = newTouches return } // 全てのタッチの平均位置を計算する const prevTouchMeanPos = { x:0, y:0 } const newTouchMeanPos = { x:0, y:0 } for ( let i = 0; touches.length > i; ++i) { prevTouchMeanPos.x += touches [ i ] .x prevTouchMeanPos.y += touches [ i ] .y newTouchMeanPos.x += newTouches [ i ] .x newTouchMeanPos.y += newTouches [ i ] .y } prevTouchMeanPos.x /= touches.length prevTouchMeanPos.y /= touches.length newTouchMeanPos.x /= newTouches.length newTouchMeanPos.y /= newTouches.length // 全てのタッチの平均位置が動いた距離を視点の移動量とする const touchDiffX = prevTouchMeanPos.x - newTouchMeanPos.x const touchDiffY = prevTouchMeanPos.y - newTouchMeanPos.y const canvasDiffX = touchDiffX / canvas.clientWidth * dWidth const canvasDiffY = touchDiffY / canvas.clientHeight * dHeight const imageDiffX = canvasDiffX / scale const imageDiffY = canvasDiffY / scale let newSx = sx + imageDiffX let newSy = sy + imageDiffY タッチが2点以上の場合は同時に拡大&縮小も行います。こちらの戦略としては、全てのタッチのうち最も離れた2点の距離の変化を拡大率の変化量とすることにします。これにより3本以上の指で操作されたとしてもピンチ操作ができます。またこの時に2点間の中心の位置にフォーカスして拡大&縮小するように sx, sy も更新しており、注目しているポイントに寄っていく動きが実現できます。 // 2 点タッチ以上の場合、拡大縮小も行う if (touches.length >= 2) { // 全てのタッチのうち最も離れた2点の距離の変化を拡大率の変化量とする let prevMaxDistance = 0 for ( let i = 0; touches.length > i; ++i) { for ( let j = i+1; touches.length > j; ++j) { const p1 = touches [ i ] const p2 = touches [ j ] prevMaxDistance = Math.max(prevMaxDistance, calcDistance(p1, p2)) } } let newMaxDistance = 0 for ( let i = 0; newTouches.length > i; ++i) { for ( let j = i+1; newTouches.length > j; ++j) { const p1 = newTouches [ i ] const p2 = newTouches [ j ] newMaxDistance = Math.max(newMaxDistance, calcDistance(p1, p2)) } } const prevScale = scale const newScale = scale * newMaxDistance / prevMaxDistance scale = Math.max(newScale, minScale()) // スケールの変化に合わせて画像の位置更新 const focusX = prevTouchMeanPos.x / canvas.clientWidth * dWidth const focusY = prevTouchMeanPos.y / canvas.clientHeight * dHeight newSx += focusX * (1 / prevScale - 1 / scale) newSy += focusY * (1 / prevScale - 1 / scale) } ちなみに Croppie はタッチ処理で少し雑な部分がありました。ソースコードを覗いた感じですと、タッチ点を 2 点以上検知した場合は拡大&縮小のみを行う動きとなっており、ピンチ操作しながら移動することができません。またピンチ操作の時に配列の最初の 2 点を使ってスケールを計算しており、3 本以上の指で操作した時に思った通りに動かないことがあります。 https://github.com/Foliotek/Croppie/blob/master/croppie.js 終わりに 長くなりましたが、以上が処理の全容になります。 今回お話ししたような内容は、OS に標準で入っているような画像ビューワーでも当たり前のように実装されている処理でもありますし、Photoshop のような画像編集ツールや draw.io や figma 等のドローイングツールを作ろうとしたときにも必要な処理となります。案外身近なところで出くわすものですが、ちゃんとした解説を見かけたことがなかったので、様々ある実装方法のひとつとしてみなさんの参考になれば幸いです。 最後までお付き合いいただきありがとうございました。
アバター
この記事は Safie Engineers’ Blog! Advent Calendar 2022 22日目の記事です。 こんにちは,あるいはこんばんわ 第1開発部QCDグループ 小山と申します。 アドベントカレンダーへの投稿に声がけいただけましたので 今回はQCDグループとしては”初”の投稿となりますので よく聞かれるQCDグループって何しているのという点と これからやりたいこと,目指したいことを紹介させていただきます。 一緒に開発に加わっている方にはほ~~んという内容なのは恐縮です。 QCDグループについて チーム名は分かった,でもQCDって何しているの? テスト業務ってなにしてるの? テスト計画書とテスト仕様書について テスト計画書とは テスト仕様書とは 次回予告 最後に QCDグループについて QCDグループのQCDとは”Quality(品質)、Cost(コスト)、Delivery(納期)”を 示しています。 本当はQCD”M(マネジメント)”がついていたのですが 組織編成の中で失われた文字に・・・ ただQCDグループのメンバーとしては ”QCDをマネージメントするグループ”の一員 ということを意識したうえで日々のQA(品質保証)業務に取り組んでいます。 チーム名は分かった,でもQCDって何しているの? QCD(社内ではQAチームと言われたり)は 製品のソフトに関する”品質保証”を行っています。 品質保証といっても様々な活動があると思いますが 現在はソフトのテストを通してお客様へ提供することができる/ 満足していただける クオリティになっているか確認することが”現在の”主な日々の業務となっています。 前職などでテスト業務を中心に活動する組織に在籍されていた方や 社内でも営業よりの方からすると QCDってグループははテストだけを行っているの?と思われがちですが 他にも品質を向上させるために行っていることはあります。 ただ一旦,イメージのしやすいテスト業務に焦点を当てて,つらつら書かせていただけます。 テスト業務ってなにしてるの? テスト業務ってテストを作る?実施する?イメージがつきづらい と思われるかもしれませんが,テストケースの作成から終了報告までは 意外と道のりがあります。   1,要件定義の確認,仕様把握   2,テスト計画(テスト計画書)の作成   3,テスト詳細仕様書(テストマップ)の作成   4,グループ内レビュー   5,開発メンバーとのレビューと確認      ~ここからテスト期間(約2週間)~   6,テストの実施(リグレッションテスト含む)   7,不具合報告   8,修正されたチケットの修正確認   9,上記の対応終了後,終了報告       ~テスト終了報告→次の製品開発スタートまで(約2週間)   10,追加,修正仕様のリグレッションテストへの反映   11,不具合分析とフィードバック   12,自動テストのメンテナンス 担当者ごとに粒度や形式の違いはありますが 概ね上記の活動を2名~3名で分担して行っております。 期間は定期的にリリースしているプロダクトなら 2週間前後,新規開発のものはそれ以上の期間で 市場に出せるか否か,セーフィーの製品としてふさわしい品質か否か を確認しています 中々聞かない言葉としては, テスト計画書とテスト仕様書でしょうか テスト計画書とテスト仕様書について テスト計画書とは 実施するテストの目的・方向性、スケジュール、体制などの要件を整理し、まとめるドキュメント その他にも実際のテストで想定される不具合管理や、プロジェクトに関わるメンバー同士のコミュニケーション方法などについて具体的にまとめます 引用元 テスト計画書の役割・目的とは|記載すべき要件や漏れを防ぐポイントを紹介! | テクバン株式会社 過去PJのテスト計画書 弊社ではこのようなものを作成して プロジェクト側に展開しています。 テスト仕様書とは テストマップとは、テスト対象システムにおける設計単位(機能、画面、状態など)と、実施するテスト観点の対応表 引用元: テストマップとは?テストを俯瞰し、抜け漏れ防止やテストケース数のバランス調整を行おう【ソフトウェア開発・テスト用語 】| Qbook テストマップ 引用したテストマップからはアレンジはしていますが, 追加される機能に対し,どの端末で,どういった観点でテストを行うか をマトリクス表にして記載しています。 テスト粒度の確認+実装時の観点の抜け漏れに気づく機会を テスト期間前に設けて,テスト期間中の修正工数の削減を目指すものとなります。 とここまで書いてきましたが このままだとテストだけやっている部署だと思われてしまう可能性があることに 気づいたため,テスト以外の業務についてもざっくり追記します 要件定義段階からPJへ参加し仕様策定時から 考慮漏れや改善案を提案する テスト実施中ではなく 動的に検証を行う前の段階で 他プロダクトへの影響や問題が発生してしまう点などを 指摘し解決できるような仕様を提案する 開発期間中にでたチケットを分析し,フィードバックを行う とこんなことも実はやっています (自分が十分にやれているかといえば疑問ですがorz) 上記のようにテストを行って品質を担保する業務以外でも プロダクトの品質を向上できる取り組みを行っている/行おうとしているのが セーフィーのQCD(M)グループとなります。 テストだけやっているのではなく テストも含めそれ以外でも積極的に関わっています。 セーフィーとしていいものを世の中に出すために 提案や小言を言っているんだな  期間通りにリリースできるようにしっかり計画だてて 品質を保証するための行動を行ってくれているんだなと 認識いただけますと幸いです。 また次回以降に書きたいな/書ければな と思っている お題目を先に宣言させていただきます 次回予告 リリース判定会って何やっているの テスト管理ツールの導入まで 品質基準の策定について 全社にまたがる品質向上への取り組み AIの品質保証 セーフィーのQAエンジニアってどんなことができる人 BitriseとMagicpodを連携させて見た結果,○○に! 最後に 品質に携わる業務を行っているものとしては お客様に私たちの存在を意識されないことがいい(意識される=バグ,不具合がある) と思っています。 同じプロダクトにかかわる人たちからは ”QCD”の人たちがいないと困る,いるとなんか開発が上手くいくよね と思われる存在,部署として認知されてたいです。 もちろんAIに関する品質保証等はまだ確立されていない領域なので そういったところで自分たちなりのやり方や挑戦したこと というのはゆくゆく発信していきたいです。 またQAエンジニアからプロジェクトマネージャー等の 新領域に挑戦する人をグループからも輩出したいと思っています。 (これは僕もやってみたい) まだまだ日々の業務に追われてしまっていて そういった挑戦に手を付けられていないので これから取り組んでいきたいと思います。 以上となります, セーフィーのQAエンジニアという仕事について ざっくり知っていただけると嬉しいです! ありがとうございました。 みなさん,よいクリスマスを!!(ケンタッキー食べたいですね。)
アバター
この記事は 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さんからひとこといただきましたので、こちらに掲載させていただきます。 お疲れ様です!
アバター