🎄

【AWS】Amazon Transcribe + Terraformで作る音声認識システム

2024/12/03に公開

https://qiita.com/advent-calendar/2024/ca-26th

はじめに

u-hyszkと申します!

本記事では、AWSが公開しているフルマネージド型の自動音声認識サービスである「Amazon Transcribe」を使って、AWS上にミニマルな自動音声認識システムを構築します。

システムを構築する際には、Infrastructure as Code(IaC)ソフトウェアツール「Terraform」を使って、インフラ構成を自動的に構築します。

また、Lambda関数をコンテナイメージから作成する方法についてもご紹介します。

https://aws.amazon.com/jp/transcribe/

https://www.terraform.io/

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-image.html

システムの概要

アーキテクチャ
https://github.com/u-hyszk/aws-transcriber-app/tree/master

本記事で作成するシステムのアーキテクチャは上記のとおりです。

このシステムの入力はS3バケット上にアップロードされた音声で、出力は認識結果が記述されたjsonファイルが同じバケット上にアップロードされます。具体的な処理の流れは以下の通りです。

  1. S3バケットのinputsフォルダに音声がアップロードされる
  2. 音声ファイルのアップロードを検知してLambda関数を呼び出す
  3. Lambda関数がAmazon Transcribeのjobを開始する
  4. 非同期でS3バケットのtranscriptionsフォルダに認識結果がアップロードされる

Lambda関数はECRにデプロイしたコンテナイメージを利用し、インフラの構築にはTerraformを使用します。

Amazon Transcribeの概要とメリット

Amazon Transcribeはフルマネージド型の自動音声認識サービスです。

音声認識システムを構築する際には、通常、大規模な深層学習モデル(Whisperfaster-whisperWavLMなど)を運用する必要があります。このため、モデルを低料金・低レイテンシで安定的に運用するための開発コストや、その運用コストが肥大化することが課題となります。

Amazon Transcribeのようなフルマネージド型のサービスを使用することによって、簡単に高品質な認識結果を得ることができるため、特にプロダクト開発初期の段階で有力な手段となると思われます。

example_aws_transcribe.py
# たった数行のコードで音声認識の結果を得ることができます
import boto3
transcribe = boto3.client("transcribe")
if __name__ == "__main__":
    transcribe.start_transcription_job(
        TranscriptionJobName="example_job",
        Media={"MediaFileUri": "media_file_uri_in_s3"},
        MediaFormat="wav",
        LanguageCode="ja-JP",
        OutputBucketName="output_s3_bucket_name"
    )
項目 説明
フォーマット mp3・mp4・wav・flac・ogg・amr・webm
言語 言語コード換算で39言語(日本語・英語を含む)
サンプルレート 8 ~ 48kHz
チャネル数 1 ~ 2
料金 リンク先を参照
無料利用枠 12か月間、1か月あたり60分

https://docs.aws.amazon.com/ja_jp/transcribe/latest/dg/how-input.html

https://docs.aws.amazon.com/ja_jp/transcribe/latest/APIReference/API_StartTranscriptionJob.html

https://aws.amazon.com/jp/transcribe/pricing/?nc=sn&loc=3

Terraformの概要とメリット

TerraformはHashiCorp社が提供しているInfrastructure as Code(IaC)ソフトウェアツールです。

HashiCorp Configuration Language(HCL)と呼ばれる宣言型言語でインフラを定義することで、AWS・GCP・Azureなどのクラウド上にインフラを展開することができます。

また、HCLではモジュールを作成することができるので、これを効果的に利用することで再利用性と保守性を向上させることができます。

AWS Cloud FormationやAWS Cloud Development KitなどのAWS依存のツールを利用する場合と比較して、他のクラウドベンダーのサービスの併用や移行も可能な点がTerraformの魅力です。

https://ja.wikipedia.org/wiki/Terraform

https://www.terraform.io/

Lambda関数をコンテナイメージから作成するメリット

Lambda関数を作成する方法として、zipファイルアーカイブをデプロイする方法がありますが、以下のようなデメリットもあります。

デメリット 説明
外部パッケージの導入が難しい 外部パッケージの利用するには、各パッケージのzipファイルアーカイブも同時にデプロイする必要があり、手続きが煩雑である
デプロイできるファイルのサイズは最大で250MBまでの制限があるため、サイズが大きいパッケージは単純に導入できない
サイズが大きいパッケージをLambda関数のLayerとして取り入れる方法もあるが、Layer数は最大で5つに制限されているため、パッケージ数も5つに制限される
バージョン管理が煩雑 AWSのコンソール上でLambda関数が編集可能になるため、バージョン管理が煩雑になる

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/gettingstarted-limits.html#function-configuration-deployment-and-execution

そこで本記事では、システムの拡張性を考慮して、ECRのレポジトリに格納されているコンテナイメージからLambda関数を作成します。

これにより、

  • 外部パッケージもコンテナイメージに内包することで容易に外部パッケージを利用できる
  • コンテナイメージのサイズは10GBまでなら問題ない
  • ECRレポジトリのバージョン管理により、Lambda関数のバージョン管理を代替できる

といったメリットを享受することができます。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images-create.html#images-create-1

前準備

前提知識
  • AWSのコンソール上で基本的な操作ができること(特にIAM・S3)
  • Pythonの基本的な構文を理解していること
  • Dockerfileの基本的な仕様を理解していること
  • Terraformのinitplanapplydestroyを理解していること
開発環境
  • Darwin v23.6.0
  • aws-cli v2.21.2
  • Terraform v1.5.7
  • Docker v20.10.21
IAMユーザーの作成とポリシーのアタッチ

事前にIAMユーザーの作成・アクセスキーの発行を行い、AWS CLIのconfigureとして登録しておいてください。以下の記事が非常に分かりやすいかと思います。

https://zenn.dev/akkie1030/articles/aws-cli-setup-tutorial

また、作成したIAMユーザーには以下のポリシーを付与しておいてください。

  • AmazonEC2ContainerRegistryFullAccess
  • AmazonS3FullAccess
  • AmazonTranscribeFullAccess
  • AWSLambda_FullAccess
  • AWSLambdaRole
  • IAMFullAccess

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/access_policies_job-functions_create-policies.html

インフラの構築(Terraform)

まずはインフラから作成していきます。
インフラ構築ができたら中身のLambda関数などを作成します。

ディレクトリ構造

/
├─ lambda/
└─ infra
   ├─ envs
   │  └─ dev
   │     ├─ main.tf
   │     ├─ variables.tf
   │     └─ terraform.tfvars
   └─ modules
      ├─ iam/
      ├─ lambda_s3_handler/
      └─ s3
         ├─ variables.tf
         ├─ main.tf
         └─ outputs.tf

インフラ全体のディレクトリ構造は上記とおりです。
最も重要なファイルはdev直下のmain.tfであり、このファイルに以下のモジュールをインポートします。

  • iam: システムを動かすのに必要なIAMの権限設定
  • lambda_s3_handler: S3からのイベントを受け取って実行するLambda関数
  • s3: S3バケットの作成

各モジュールは以下の3つのファイルから構成されます。

  1. variables.tf: モジュールの引数を定義します
  2. main.tf: モジュール本体の処理を記述します
  3. outputs.tf: モジュールの返り値を定義します

また、dev直下のmain.tfには、同じくdev直下のvariables.tfおよびterraform.tfvarsに記述された変数が代入されます。

ディレクトリ構成は以下の文献を参考にしました。

https://cloud.google.com/docs/terraform/best-practices-for-terraform?hl=ja

メインファイル

まずはdev直下のmain.tfから作成していきます。コードは以下の通りです。

./infra/envs/dev/main.tf
# 1. プロバイダーの権限設定
provider "aws" {
  region                   = var.region  # 定義した変数
  shared_credentials_files = ["~/.aws/credentials"]
  profile                  = "default"  # FIXME
}

# 2. プロバイダーとTerraform本体のバージョン設定
terraform {
  required_version = "~> 1.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}


# 3. システムを動かすのに必要なIAMの権限設定
module "iam" {
  source = "../../modules/iam"  # モジュールのディレクトリ
  # FIXME: IAMロールの名前
}

# 4. S3バケットの作成
module "s3" {
  source = "../../modules/s3"  # モジュールのディレクトリ
  # FIXME: バケットの名前
  # FIXME: (開発用のため)バケット削除時に中身も全削除するオプション
}

# 5. S3からのイベントを受け取って実行されるLambda関数
module "lambda_s3_handler" {
  source = "../../modules/lambda_s3_handler"  # モジュールのディレクトリ
  # FIXME: Lambda関数の名前
  # FIXME: Lambda関数のイメージの場所
  # FIXME: 監視対象のS3バケット
  # FIXME: 実行権限(Role)
  # FIXME: Lambda関数が実行される条件
}

最初の2つのブロックは、プロバイダーの権限やバージョンについての設定になります。
プロファイルはdefaultとしていますが、前準備でポリシーを付与したIAMユーザーのプロファイルに変更してください。

var.regionには、同じディレクトリ内にあるvariables.tfおよびterraform.tfvarsに定義した変数が入ります。
variables.tfでは変数とその型を定義し、terraform.tfvarsでその値を記述します。ここでは<REGION>と書かれている場所にリージョン(ex. ap-northeast-1)を記述してください。

./infra/envs/dev/variables.tf
variable "region" {
  type        = string
  description = "The region in which the resources will be created"
}
./infra/envs/dev/terraform.tfvars
region                       = "<REGION>"

残りの3ブロックはすべてモジュールになります。それぞれのモジュールの役割と想定される引数の種類をコメントで記載しています。
sourceには作成したいモジュールのファイル一式(main.tfvariables.tfoutputs.tf)が格納されたディレクトリのパスを記述します。

ここからはこれらのモジュールの中身を作成していきます。

IAM

iamモジュールでは、システムを動かすのに必要なIAMの権限設定を行います。

./infra/modules/iam/main.tf
# 1. 一時的な認証情報を引き受けるIAMロールの作成
resource "aws_iam_role" "lambda_role" {
  name = "${var.name}-lambda-role"

  assume_role_policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Action" : "sts:AssumeRole",
        "Principal" : {
          "Service" : ["lambda.amazonaws.com"]
        },
        "Effect" : "Allow"
      }
    ]
  })
}

# 2. システムの実行に必要なポリシーを定義
resource "aws_iam_policy" "lambda_policy" {
  name = "${var.name}-lambda-policy"

  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Action" : [
        "logs:CreateLogStream",
        "logs:CreateLogGroup",
        "logs:PutLogEvents"
        ],
        "Effect" : "Allow",
        "Resource" : "arn:aws:logs:*:*:*"
      },
      {
        "Action" : [
          "s3:GetObject",
          "s3:PutObject",
          "transcribe:StartTranscriptionJob",
          "transcribe:GetTranscriptionJob"
        ],
        "Effect" : "Allow",
        "Resource" : "*"
      }
    ]
  })
}

# 3.作成したIAMロールにポリシーを一括で付与する
resource "aws_iam_role_policy_attachment" "lambda_role_attachment" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = aws_iam_policy.lambda_policy.arn
}

1つ目のブロックでは一時的な認証情報を引き受けるロールをsts:AssumeRoleというポリシーを利用して作成しています。
2つ目のブロックでは、作成したロールに付与したいポリシーを宣言しています。ここではCloud Watchのログ・S3・Amazon Transcribeのアクセス権限を付与しています。
3つ目のブロックで、作成したロールにポリシーを付与しています。

モジュールの引数としてvariables.tfnameを定義します。これにより、main.tfからvar.nameとして参照でき、作成したロール・ポリシーを既存のものと区別できるようになります。

./infra/modules/iam/variables.tf
variable "name" {
  description = "Your name for IAM role prefix"
  type        = string
}

また、モジュールの出力としてAmazonリソースネーム(ARN)であるlambda_role_arnを定義します。
outputs.tfで定義された変数はmodule.iam.lambda_role_arnのような形でアクセスできるようになります。
これにより、他のモジュールやリソースからロールを参照できるようになります。

./infra/modules/iam/outputs.tf
output "lambda_role_arn" {
  value = aws_iam_role.lambda_role.arn
}

S3

s3モジュールでは、S3バケットの作成を行います。

./infra/modules/s3/main.tf
resource "aws_s3_bucket" "this"{
  bucket = var.name
  force_destroy = var.force_destroy  # (開発用のため)バケット削除時に中身も全削除するオプション
}

S3バケットのモジュールは非常にシンプルです。
今回は開発用の環境のため、バケット削除時に中身も全削除するオプションを変数で選択できるようにします。

モジュールの引数はS3バケットの名前であるnameとバケット削除時に中身も全て削除するオプションであるforce_destroyです。

./infra/modules/s3/variables.tf
variable "name" {
    description = "Your name for S3 bucket"
    type        = string
}

variable "force_destroy" {
    description = "A boolean that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error."
    type        = bool
    default     = false
}

モジュールの返り値はバケットの名前bucket_name、バケットのIDbucket_id、バケットのARNbucket_arnを定義します。
これらの値のうち、bucket_namebucket_arnはLambda関数を作成する際に使用します。

./infra/modules/s3/outputs.tf
output "bucket_name" {
    value = aws_s3_bucket.this.bucket
    description = "The name of the bucket"
}

output "bucket_id" {
    value = aws_s3_bucket.this.id
    description = "The name of the bucket"
}

output "bucket_arn" {
    value = aws_s3_bucket.this.arn
    description = "The ARN of the bucket"
}

Lambda

lambda_s3_handlerモジュールでは、S3からのイベントを受け取って実行されるLambda関数を作成します。

./infra/modules/lambda_s3_handler/main.tf
# 1. Lambda関数本体の定義
resource "aws_lambda_function" "s3_handler" {
  package_type  = "Image"
  function_name = var.function_name
  image_uri     = var.image_uri
  role          = var.role
}

# 2. S3バケットの通知設定
resource "aws_s3_bucket_notification" "s3_notification" {
  bucket = var.bucket_name
  lambda_function {
    lambda_function_arn = aws_lambda_function.s3_handler.arn
    events              = var.events
    filter_prefix       = var.filter_prefix
    filter_suffix       = var.filter_suffix
  }
}

# 3. S3に対してLambda関数を呼び出すことを許可する
resource "aws_lambda_permission" "allow_s3_invoke" {
  principal     = "s3.amazonaws.com"
  statement_id  = "AllowS3Invoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.s3_handler.function_name
  source_arn    = var.source_arn
}

1つ目のブロックではLambda関数本体を定義します。今回はコンテナイメージから作成するのでimage_uriを指定します。先ほど作成したIAMロールもここで必要になります。
2つ目のブロックでは、S3バケットのイベント通知設定を行います。bucketで指定したS3バケット内において、eventsfilter_prefixfilter_suffixの条件が満たされると、lambda_function_arnで指定したLambda関数が呼び出されます。それぞれのオプションの役割は以下の通りです。

  • events: 通知を発生させるイベントのリスト(ex: ["s3:ObjectCreated:*"])
  • filter_prefix: 指定した接頭辞の条件を満たす場合のみ通知する
  • filter_suffix: 指定した接尾辞の条件を満たす場合のみ通知する

3つ目のブロックで、S3に対してLambda関数を呼び出すことを許可します。

モジュールの引数としてはLambda関数の作成に必要なfunction_nameimage_uriroleの他に、イベント通知のために必要なbucket_namesource_arnを定義します。
また、オプション引数としてeventsfilter_prefixfilter_suffixを定義します。

./infra/modules/lambda_s3_handler/variables.tf
# ---  Required  --- #

variable "function_name" {
    type = string
    description = "The name of the Lambda function"
}

variable "image_uri" {
    type = string
    description = "The URI of the container image"
}

variable "role" {
    type = string
    description = "The ARN of the IAM role that the Lambda function assumes when it executes"
}

variable "bucket_name" {
    type = string
    description = "The name of the S3 bucket to receive notifications"
}

variable "source_arn" {
    type = string
    description = "The ARN of the S3 bucket to receive notifications"
}

# --- Optional --- #

variable "events" {
    type = list(string)
    description = "A list of S3 event types"
    default = []
}

variable "filter_prefix" {
    type = string
    description = "The prefix filter"
    default = ""
}

variable "filter_suffix" {
    type = string
    description = "The suffix filter"
    default = ""
}

モジュールの返り値はLambda関数の名前function_nameとARNfunction_arnを定義します。

./infra/modules/lambda_s3_handler/outputs.tf
output "function_name" {
    value = aws_lambda_function.s3_handler.function_name
    description = "The name of the Lambda function"
}

output "function_arn" {
    value = aws_lambda_function.s3_handler.arn
    description = "The ARN of the Lambda function"
}

仕上げ

最後に作成した3つのモジュールをdev直下のmain.tfに反映させていきます。まずはmain.tfを編集します。

./infra/envs/dev/main.tf
module "iam" {
  source = "../../modules/iam"
+  name   = var.name
}

module "s3" {
  source = "../../modules/s3"
+  name       = var.name
+  force_destroy = true
}

module "lambda_s3_handler" {
  source = "../../modules/lambda_s3_handler"
+  function_name = var.name
+  image_uri     = var.s3_wav_transcriber_image_uri
+  role          = module.iam.lambda_role_arn
+  bucket_name = module.s3.bucket_name
+  source_arn    = module.s3.bucket_arn
+  events        = ["s3:ObjectCreated:*"] # オブジェクトが作成されたときに通知する
+  filter_prefix = "inputs/"              # 接頭辞が"inputs/"のときのみ通知する
+  filter_suffix = ".wav"                 # 接尾辞が".wav"のときのみ通知する
}

先ほど作成したモジュールの引数を埋めていきます。
lambda_s3_handlerのイベントはinputsフォルダの下に.wavで終わるファイルが作成されたときのみ通知されるように設定しました。

また、各リソースの名前は変数nameで柔軟に変えられるようにします。Lambda関数のコンテナイメージのURIs3_wav_transcriber_image_uriも変数にします。

./infra/envs/dev/variables.tf
variable "region" {
  type   = string
  description = "The region in which the resources will be created"
}

+variable "name" {
+  type   = string
+  description = "The name of the environment"
+}

+variable "s3_wav_transcriber_image_uri" {
+  type   = string
+  description = "The URI of the 's3_wav_transcriber' container image"
+}

各変数の値は先ほどと同様にterraform.tfvarsで与えます。<AWSアカウントID>はご自身のものを使用してください。

./infra/envs/dev/terraform.tfvars
region                       = "<REGION>"
+name                         = "aws-transcriber-dev"
+s3_wav_transcriber_image_uri = "<AWSアカウントID>.dkr.ecr.<REGION>.amazonaws.com/s3_wav_transcriber_dev:latest"

これでインフラの構築は完了です!

Lambda関数の作成

続いてLambda関数とそのコンテナイメージを作成していきます。

ディレクトリ構造

/
├─ infra/
└─ lambda
   └─ s3_wav_transcriber
      ├─ Dockerfile
      ├─ lambda_function.py
      └─ requirements.txt

全体のディレクトリ構成は上の通りです。Dockerfilelambda_function.pyを呼び出すコンテナイメージを作成することでLambda関数として動作させることができます。

Dockerfile

Dockerfile
# AWSのベースイメージを使用する
# NOTE: ARM64アーキテクチャの場合は`--platform linux/arm64`に変更
FROM --platform=linux/amd64 public.ecr.aws/lambda/python:3.9

# requirements.txtをコピーしてライブラリをインストール
COPY requirements.txt ${LAMBDA_TASK_ROOT}  # LAMBDA_TASK_ROOTはLambda側が定義する環境変数
RUN pip install -r requirements.txt

# Lambda関数本体をコピー
COPY lambda_function.py ${LAMBDA_TASK_ROOT}

# lambda_functions.pyのhandler関数を実行する
CMD [ "lambda_function.handler" ]

Dockerfileの中身は上の通りです。ほとんど公式のテンプレートに従ったものになっており、AWSのベースイメージから作成しています。
開発環境のプラットフォームによって先頭のFROMのオプションが変わることに注意してください。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-image.html#python-image-instructions

lambda関数本体(Python)

lambda_function.py
import datetime
import os
import re
from urllib.parse import unquote_plus

+import boto3

+transcribe = boto3.client("transcribe")


def handler(event, context):
    # 1. イベントの情報を取得する
+    input_bucket = event["Records"][0]["s3"]["bucket"]["name"]
+    input_key = unquote_plus(
+        event["Records"][0]["s3"]["object"]["key"], encoding="utf-8"
+    )
+    input_file_uri = f"s3://{input_bucket}/{input_key}"
    wav_name = os.path.splitext(os.path.basename(input_key))[0]
    print(f"Received event for {input_key} from bucket {input_bucket}.")

    # 2. ジョブの名称と出力先のバケット・ファイル名を決める
    output_bucket = input_bucket
    output_key = f"transcriptions/{wav_name}.json"
    output_file_uri = f"s3://{output_bucket}/{output_key}"
    timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
    transcription_job_name = f"transcription-{wav_name}-{timestamp}"
    transcription_job_name = re.sub(
        r"[^0-9a-zA-Z._-]", "_", transcription_job_name
    )  # Transcribe job name must be alphanumeric
    print(f"Output file URI: {output_file_uri}")

    # 3. 音声認識を行うジョブを開始する
    try:
+        transcribe.start_transcription_job(
+            TranscriptionJobName=transcription_job_name, # ジョブの名前
+            Media={"MediaFileUri": input_file_uri},  # 入力音声のURI
+            MediaFormat="wav",  # 入力音声のフォーマット
+            LanguageCode="ja-JP",  # 入力音声の言語
+            OutputBucketName=output_bucket,  # 認識結果の出力先となるバケット
+            OutputKey=output_key,  # 出力ファイルの名前
+        )
        print(f"Transcription job {transcription_job_name} started.")

        return {
            "statusCode": 200,
            "body": f"Transcription job {transcription_job_name} started for {input_key}.",
        }
    except Exception as e:
        print(
            f"Error starting transcription job for object {input_key} from bucket {input_bucket}."
            "Make sure they exist and your bucket is in the same region as this function."
        )
        raise e

細かく出力先やジョブの名称を変更していますが、重要なのは強調表示した「1. イベントの情報を取得する」部分とtranscribe.start_transcription_job(...)です。

handlerの引数となっているeventの中に、入力音声のバケットinput_bucketやファイル名input_keyに関する情報が含まれています。
これをinput_file_uriの形式にすることで、入力音声を参照できるようにします。

transcribe.start_transcription_job(...)では、S3バケットの中にある音声を取得し、認識結果のjsonファイルをS3バケットに格納するジョブを開始しています。

なお、今回は外部ライブラリとしてboto3を使用するので、requirements.txtに記載しておきます。

requirement.txt
boto3

これでLambda関数の作成も完了です!

デプロイ

作成したLambda関数とインフラ環境をデプロイしていきます。

ECRレポジトリの作成

ECRにコンテナイメージを格納するレポジトリを作成します。このレポジトリに先ほど作成したLambda関数のコンテナイメージをデプロイします。

まずはECRにAWS CLIを通じてログインしましょう。<AWSアカウントID><REGION>はご自身のものに変更してください。

aws ecr get-login-password \
    --region <REGION> \
    | docker login --username AWS --password-stdin <AWSアカウントID>.dkr.ecr.<REGION>.amazonaws.com

ログインに成功したらECRのレポジトリを作成します。今回はイメージタグの重複を許すMUTABLEの設定で作成します。

aws ecr create-repository \
    --repository-name s3_wav_transcriber_dev \
    --region <REGION> \
    --image-scanning-configuration scanOnPush=true \
    --image-tag-mutability MUTABLE

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/container-considerations.html

レポジトリの作成ができたらAWSコンソール上で確認しましょう。

ECRレポジトリの作成確認
ECRレポジトリの作成確認

問題なく作成できてますね!

イメージのbuild・push

続いて先ほど作成したDockerfilelambda_function.pyを利用してコンテナイメージのビルドを行います。
イメージの名前にはECRのURIを使用します。今回はタグにlatestを使用します。

docker build ./lambda/s3_wav_transcriber -t <AWSアカウントID>.dkr.ecr.<REGION>.amazonaws.com:latest

ビルドに成功したら、イメージをECRにプッシュしましょう。

docker push <AWSアカウントID>.dkr.ecr.<REGION>.amazonaws.com:latest

ECRレポジトリへのコンテナイメージのプッシュ確認
ECRレポジトリへのコンテナイメージのプッシュ確認

こちらも問題なくプッシュできています!

terraform apply

最後にTerraformで定義したインフラ環境を適用します。

まずは初期化を行います。-chdirオプションで実行するディレクトリを変更しています。

terraform -chdir=./infra/envs/dev init

続いてplanで構築されるリソースに問題がないことを確認します。

terraform -chdir=./infra/envs/dev plan

最後にapplyで実際に環境を適用します。

terraform -chdir=./infra/envs/dev apply

S3バケットとLambda関数の作成ができていることを確認しましょう。

S3バケットの作成確認
S3バケットの作成確認

Lambda関数の作成確認
Lambda関数の作成確認

実行確認

構築したシステムが正常に動作するか確認します。

S3バケットにinputsフォルダを作成し、音声ファイル(u_greeting.wav)をアップロードします。

S3のinputsフォルダに音声を入力
S3のinputsフォルダに音声を入力

すると、S3バケット内にtranscriptionsフォルダが自動的に作成されます。

S3にtranscriptionsフォルダが作成されている
S3にtranscriptionsフォルダが作成されている

フォルダの中身を確認すると、一時ファイルの他にu_greeting.jsonというファイルが格納されています。

jsonファイルが作成されている
jsonファイルが作成されている

このファイルの中身は以下のようになっています。

jsonファイルの中身
jsonファイルの中身

これで正常に音声認識が行われたことを確認できました👋

音声認識の結果
音声認識の結果

片付け

作業が終わったら全てのリソースを破棄し、余計な出費を減らしましょう。

terraform -chdir=./infra/envs/dev destroy
aws ecr delete-repository \
    --repository-name s3_wav_transcriber_dev \
    --region <REGION> \
    --force

おわりに

本記事では、Amazon TranscribeとTerraformを使って、AWS上にミニマルな自動音声認識システムを構築しました。
考えられる改善点としては(1)CI/CDの整備(2)Step functionsの利用(3)Map Distributionの利用による並列化(4)VPCやサブネットを利用したセキュリティ向上などが挙げられると思います。

機械学習エンジニア/データサイエンティストとして働く予定ですが、インフラ・バックエンド周りも強くなって、より良い意思決定に繋げたいですね...!

ご覧いただきありがとうございました🎄

参考文献

本記事で作成したアーキテクチャは以下の記事を参考にしました。

https://aws.amazon.com/jp/blogs/news/asahi-transcription-system-with-serverless/

https://aws.amazon.com/jp/blogs/news/aws-hands-on-for-beginners-04/

Discussion