Amazon Transcribe 日本語版で遊んでみた

# はじめに

この記事は[LIFULLその3 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/lifull3) の18日目の記事です。
Amazon Transcribeが日本語対応されました。遊んでみましょう。

検証ポイントは以下２点。

- **日本語の認識精度**
- **話者の認識精度**

音声ソースはこちら。[イチロー選手引退会見 ANNnewsCH](https://www.youtube.com/watch?v=nc1-3CRfMXA)

# 準備するもの

- AWS アカウント
- AWS CLI

# 手順
## 準備
AWS アカウント＆CLIのセットアップ、いつも通りなので割愛。

## S3 bucket
bucket作成、policy設定、Transcribeで利用する音声ファイル設置

```
aws s3 mb s3://${BUCKET}
aws s3api put-bucket-policy --bucket ${BUCKET} --policy file://policy.json
aws s3 cp ${SRC_FILE} s3://${BUCKET}/
```
```json:policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "transcribe.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::${BUCKET}/*"
        }
    ]
}
```

## Transcribe実行
### ジョブ 登録
```
aws transcribe start-transcription-job --cli-input-json file://${JOB}.json
```
```json:${JOB}.json
 {
    "TranscriptionJobName": "${JOB}",
    "LanguageCode": "ja-JP",
    "MediaFormat": "mp3",
    "Settings": {
      "ShowSpeakerLabels": true,
      "MaxSpeakerLabels": 2
    },
    "Media": {
      "MediaFileUri": "${SRC_FILE}"
    }
  }
```
※ MediaFileUri: 解析で利用するS3のURLを指定。Transcribeと同一リージョンであること。
※ ShowSpeakerLabels: 話者識別する場合に指定。
※ MaxSpeakerLabels: 話者識別最大数。2〜10まで指定可。諸々の事情で今回は2。

```json:レスポンス例
{
    "TranscriptionJob": {
        "TranscriptionJobName": "${JOB}",
        "TranscriptionJobStatus": "IN_PROGRESS",
        "LanguageCode": "ja-JP",
        "MediaFormat": "mp3",
        "Media": {
            "MediaFileUri": "${SRC_FILE}"
        },
        "CreationTime": 1576491921.134,
        "Settings": {
            "ShowSpeakerLabels": true,
            "MaxSpeakerLabels": 2
        }
    }
}
```

### ジョブ ステータス確認
```
aws transcribe list-transcription-jobs --job-name-contains "${JOB}"
```
変換完了している場合は、`"TranscriptionJobStatus": "COMPLETED"`

```json:レスポンス例
{
    "NextToken": "...",
    "TranscriptionJobSummaries": [
        {
            "TranscriptionJobName": "${JOB}",
            "CreationTime": 1576491921.134,
            "CompletionTime": 1576492002.115,
            "LanguageCode": "ja-JP",
            "TranscriptionJobStatus": "COMPLETED",
            "OutputLocationType": "SERVICE_BUCKET"
        }
    ]
}
```
### ジョブ 結果取得
```
aws transcribe get-transcription-job --transcription-job-name "${JOB}"
```

```json:レスポンス例
{
    "TranscriptionJob": {
        "TranscriptionJobName": "${JOB}",
        "TranscriptionJobStatus": "COMPLETED",
        "LanguageCode": "ja-JP",
        "MediaSampleRateHertz": 44100,
        "MediaFormat": "mp3",
        "Media": {
            "MediaFileUri": "${SRC_FILE}"
        },
        "Transcript": {
            "TranscriptFileUri": "https://s3.ap-northeast-1.amazonaws.com/aws-transcribe-ap-northeast-1-prod/XXXXXX/asrOutput.json?X-Amz-Security-Token=XXXXXX&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Signature=XXXXXX"
        },
        "CreationTime": 1576491921.134,
        "CompletionTime": 1576492002.115,
        "Settings": {
            "ShowSpeakerLabels": true,
            "MaxSpeakerLabels": 2,
            "ChannelIdentification": false,
            "ShowAlternatives": false
        }
    }
}
```

変換結果は`TranscriptFileUri`から取得。

```json:TranscriptFileUriの中身(抜粋)
{
  "jobName": "${JOB}",
  "accountId": ".....",
  "results": {
    "transcripts": [
      {
        "transcript": "一朗 選手 から 皆様 へ。 ご あいさつ が ござい ます こんな に いる の びっくり する わ そう です か いや この 遅い 時間 に お 集まり いただい て ありがとう ござい ます ま 今日 の ゲーム を 最後 に 日本 で 九 年 LENCE] アメリカ で ま 十 九 年 目 に 突入 ま し た ところ だっ た ん です けれど も 原因 生活 に 終止符 を 打ち 引退 する こと と なり まし た ま 最後 に この ユニフォーム を 着 て この 日 を 迎え られ た こと を ま 大変 幸せ に 感じ て い ます ま この 十 八 八 年 を 振り返る に は ま あまり に も ま 長い 時間 だっ た ので ここ で ま! 一つ 一つ 振り返る こと が 難しい という こと も あっ て ま ここ で は これ まで え 公園 と し て いただい た 片方 LENCE] へ の 感謝 の 思い そして 球団 関係 者 チームメート に関して は 申し上げ て 皆様 から の 御 質問 が あれ ば できる 限り お答え し、 たい と いう 風 に 思っ て い ます ありがとう ござい ます これ より 質疑 を お供 に 歌わ せ て いただき ます 質問 が 終わり の 方 は 挙手 にて お願い いたし ます こちら から 指名 を さ せ て 頂き マイク を お 持ち し ます 社名 お 名前 を 名乗っ て から 質問 を し て いただき ます よう お 願い いたし ます また 時間 に 限り が ござい ます ので。 一 回 に つき 伊藤 と さ せ て いただき ます では 質問 の ある 方 お 願い いたし ます 本番 前 の やっぱり 滅多 はい はい テレビ朝日 の 草薙 と 申し ます お疲れさま でし た え まず LENCE] この ケーキ として の せん 生活 に 終止符 を 撃つ こと を 決め た タイミング そして その 理由 を お 聞か せ! ください タイミング は です ね キャンプ 終盤 です ね 日本 に 戻っ て くる 何 日 前 です か ね? 何で 姉妹 と はっきり と お伝え でき ない です けれども 今 終盤 に 入っ た 時 です"
      }
    ],
    "speaker_labels": {
      "speakers": 2,
      "segments": [
        {
          "start_time": "10.64",
          "speaker_label": "spk_0",
          "end_time": "11.28",
          "items": [
            {
              "start_time": "10.64",
              "speaker_label": "spk_0",
              "end_time": "10.98"
            },
            {
              "start_time": "10.98",
              "speaker_label": "spk_0",
              "end_time": "11.28"
            }
          ]
        },
        {
          "start_time": "11.28",
          "speaker_label": "spk_1",
          "end_time": "11.54",
          "items": [
            {
              "start_time": "11.28",
              "speaker_label": "spk_1",
              "end_time": "11.54"
            }
          ]
        },
        (...)
      ]
    },
    "items": [
      {
        "start_time": "10.64",
        "end_time": "10.98",
        "alternatives": [
          {
            "confidence": "0.76",
            "content": "一朗"
          }
        ],
        "type": "pronunciation"
      },
      {
        "start_time": "10.98",
        "end_time": "11.28",
        "alternatives": [
          {
            "confidence": "0.999",
            "content": "選手"
          }
        ],
        "type": "pronunciation"
      },
      {
        "start_time": "11.28",
        "end_time": "11.54",
        "alternatives": [
          {
            "confidence": "0.999",
            "content": "から"
          }
        ],
        "type": "pronunciation"
      },
      (...)
    ]
  },
  "status": "COMPLETED"
}
```

#### TranscriptFileUriのフォーマットを少し解説

```json:TranscriptFileUriのフォーマット解説
{
  "results": {
    "transcripts": [
      {
        "transcript": "..." // 解析結果全文
      }
    ],
    "speaker_labels": { // 話者識別の場合のみ
      "speakers": 2,   //  話者数
      "segments": [    //  解析結果のリスト
        {
          "start_time": "10.64",
          "speaker_label": "spk_0",
          "end_time": "11.28",
          "items": [
            {
              "start_time": "10.64",    // 開始時間
              "speaker_label": "spk_0", // 話者識別子
              "end_time": "10.98"       // 終了時間
            }
          ]
        },
      ]
    },
    "items": [ // 品詞分解された単語のリスト
      {
        "start_time": "10.64",
        "end_time": "10.98",
        "alternatives": [
          {
            "confidence": "0.76", // 解析の信頼度(0〜1)
            "content": "一朗" // 解析した単語
          }
        ],
        "type": "pronunciation"
      },
    ]
  },
  "status": "COMPLETED"
}
```

#### 話者解析結果 一部抜粋

```
Speaker 0:一朗 選手
Speaker 1:から
Speaker 1:皆様 へ。
Speaker 1:ご あいさつ が ござい ます
Speaker 0:こんな に いる の
Speaker 0:びっくり する わ
Speaker 0:そう です か
Speaker 0:いや この 遅い 時間 に
Speaker 0:お 集まり いただい て ありがとう ござい ます
Speaker 0:ま 今日 の ゲーム を 最後 に
Speaker 0:日本 で 九 年 LENCE]
Speaker 0:アメリカ で ま 十 九 年 目 に
Speaker 0:突入 ま し た ところ だっ た ん です けれど も
Speaker 0:原因 生活 に
Speaker 0:終止符 を 打ち
Speaker 0:引退 する こと と
Speaker 0:なり まし た
Speaker 0:ま 最後 に この ユニフォーム を 着 て
Speaker 0:この 日 を 迎え られ た こと を ま 大変 幸せ に 感じ て い ます
Speaker 0:ま この 十 八 八 年
Speaker 0:を 振り返る に は
Speaker 0:ま あまり に も
Speaker 0:ま 長い 時間 だっ た ので
Speaker 0:ここ で ま! 一つ 一つ 振り返る こと が 難しい という こと も あっ て
Speaker 0:ま ここ で は
Speaker 0:これ まで え
Speaker 0:公園
Speaker 0:と し て いただい た
Speaker 0:片方 LENCE]
Speaker 0:へ の 感謝 の 思い
Speaker 0:そして
Speaker 0:球団 関係 者
Speaker 0:チームメート
Speaker 0:に関して は 申し上げ て
Speaker 0:皆様 から の 御
Speaker 0:質問 が
Speaker 0:あれ ば
Speaker 0:できる 限り
Speaker 0:お答え し、 たい と いう 風 に 思っ て い ます
Speaker 0:ありがとう ござい ます
Speaker 1:これ より 質疑 を お供 に 歌わ せ て いただき ます
Speaker 1:質問 が 終わり の 方 は 挙手 にて お願い いたし ます
Speaker 1:こちら から 指名 を さ せ て 頂き マイク を
Speaker 1:お 持ち し ます 社名 お 名前 を 名乗っ て から 質問 を し て いただき ます よう お 願い いたし ます
Speaker 1:また 時間 に 限り が ござい ます ので。 一 回 に つき 伊藤 と さ せ て いただき ます
Speaker 1:では 質問 の ある 方 お 願い いたし ます
```

# まとめ
検証に利用したソースは発声内容の聞き取りも比較的難しくなく、解析結果もまぁまぁ良い感じでした。
多少の誤変換はありますが、しっかり全容を理解できるレベルです。

話者の判定については、MaxSpeakerLabelsを適切に設定すると、それなりの精度が出ました。
ただし、適切な設定をせず、エイヤで10などと設定すると、ほぼ壊滅的な解析結果になります。
[公式 開発GUIDE](https://docs.aws.amazon.com/ja_jp/transcribe/latest/dg/how-diarization.html)でも`指定した話者の数が実際の入力オーディオの話者の数と一致すると、最高の結果が得られます。 `と記載されているので、そういう事なんでしょう。

# おまけ
## おまけ1
点字変換してみました。[参考](https://github.com/uhyo/tenji)

```
⢡⠊⠣⡘⣇⢜⡿⢂⠢⠱⣌⢇⢼⣖⢤⠀⠀⠠⢔⠂⠆⢢⡲⠠⢂⠠⢔⠠⢢⠆⣢⢲⢔⣠⡂⡆⠆⠲⡔⠠⣆⠄⢒⠦⢲⠲⡀⢴⠒⠠⡶⢲⢂⠆⡐⢔⡔⣇⡼⠆⣅⢮⣵⣌⡆⠔⢇⠎⣢⠦⠆⡢⠠⡢⠆⡶⠂⠦⠠⢂⡴⠒⠠⢔⠠⢢⠆⣢⢲⣢⠧⠊⣍⡔⡤⠠⢖⠤⣲⡄⡠⣅⡬⣑⡌⡆⣍⢇⢈⠠⡶⢡⠜⣅⢊⢠⢀⡆⢀⠢⢀⡲⢀⠒⢀⠢]⡤⠂⣶⠦⢂⡄⠠⡶⣢⠽⢡⠜⣅⢊⣿⡆⠓⢞⠱⡊⣢⢦⡢⡴⢔⠴⠠⡢⠄⡢⣠⠠⡶⢲⢖⠶⠠⡴⣴⡱⠊⣥⢞⢷⠎⡇⢾⡆⠇⢺⣩⡧⡊⡠⡷⡾⡦⣑⠘⣇⡘⢲⠲⢔⡴⡴⡂⠦⣢⢦⡢⣢⣅⡬⣑⡌⡆⢔⡔⡤⣐⡆⢄⣔⠤⣲⡄⡠⣷⢼⡶⢔⡔⣍⡠⣇⢪⠖⠢⠶⡢⢔⡴⡠⣢⠱⢞⠗⢺⡳⢊⢶⡆⣇⢬⠠⢦⡶⠆⣢⢲⣢⢔⡔⠽⢡⠮⢡⠮⣅⢊⡠⡷⠼⠦⣇⣨⠲⡆⣂⣢⠂⣢⠦⡆⣴⣢⠑⡼⠆⣅⢮⣵⣌⠠⡢⠄⡢⡔⠠⡶⢔⢔⠠⡶⣢⡤⢡⠊⡲⢡⠊⡲⡷⠼⠦⣇⣨⠲⢔⡴⠠⢂⢓⠎⢦⠆⡴⠆⠒⢔⡴⣴⠂⠄⡶⣢⢔⢔⠠⡶⣂⢔⠶⣢⠠⡶⠖⢁⢜⣥⠞⡴⢦⡶⠆⡢⠠⡢⠆⡢⣗⠨⣝⢠⢀⡆⢀⠢⢀⡲⢀⠒⢀⠢]⣀⣖⡔⣇⢬⠗⣮⡔⡣⢬⠆⢴⢦⡶⣗⡎⣥⡾⣵⢞⡃⡌⡵⣌⡤⡦⠤⣲⣶⠤⡴⡄⡆⣵⢞⢦⡶⣂⠡⢮⢦⠱⠚⠠⢖⡶⠱⣌⢇⢼⢂⠢⡔⣑⢪⡡⡨⣵⠾⠠⢂⠂⠶⠠⣂⠠⡶⢆⠲⢣⡘⠦⠔⡧⣬⠖⢦⢠⠀⡢⠆⡴⠆⠒⣳⠬⡆⡣⢬⠄⡶⠆⣢⢲⠂⠦⠠⢂⡴⠒⠠⢔⠠⢢⠆⣢⢲⢔⠶⡰⠦⡡⡨⡑⡸⡠⠔⡃⢜⡆⢃⣨⡀⢶⡶⠆⡢⠠⡢⢆⣢⢲⡡⡨⣵⠾⠠⢂⠇⢺⡀⠦⡔⣝⣂⠓⡾⡿⡆⡶⠔⡱⠜⠆⠆⡢⢦⣢⢲⢔⡦⠢⢂⠢⡷⡎⣕⠾⡠⢢⢶⡶⠕⡾⢆⡤⣢⠆⢒⡄⡠⠔⡷⢮⡦⢦⣢⢲⡗⡺⣕⠾⠔⣕⠾⠣⡚⡠⣕⠾⡕⢎⠄⡶⢂⠢⡡⡨⣵⠾⡠⢦⡶⠆⡢⠠⡢⢆⣢⢲⡰⠒⠔⡱⠜⠆⠆⡢⢦⣢⢲⣢⡢⣅⢮⣵⣌⡆⢣⡘⠦⠠⢂⠠⢔⠠⢢⠆⣢⢲⡔⠠⡶⢤⠀⠀⢡⠊⣥⠾⡆⡲⢆⠇⡊⢥⣚⡴⢢⢶⡶⠆⡢⠠⡢⢆⣢⢲⠠⡶⣂⡡⡨⣵⠾⡔⠂⠲⣝⠔⡱⠜⠆⠆⡢⢦⣢⢲⢇⢈⡕⡪⠣⡚⡔⡐⠄⢀⣂⠦⣇⣸⣕⣜⣂⠆⣂⠆⡤⡶⠶⠠⣆⡄⠵⢌⣍⡔⢛⢓⡐⠌⡴⠡⢮⢦⣢⢲⠔⡑⣎⠶⢢⣢⠠⡶⢦⡢⠖⣢⠠⢲⢠⢀⡆⢀⠢⢀⡲⢀⠒⢀⠢]⣀⢔⡔⡤⢖⠤⢆⡄⡴⢦⡶⡔⢶⣠⢷⠎⡇⢾⡆⠇⢺⣩⡧⡊⡠⣳⡜⡲⢔⡴⡠⡇⡾⣶⡢⡤⡢⠆⣦⣠⠠⢒⡄⢴⢦⡶⢴⡔⣗⠮⡣⠘⡠⠔⣵⣮⢂⢶⡤⢒⠠⡢⢢⠆⡤⡢⠆⣦⣠⠠⢒⡄⣂⠠⡶⢲⡖⡤⠐⢂⣠⢀⣒⡄⠇⢺⡕⢌⠠⡶⢲⡖⣍⢇⢈⡆⡵⢞⠄⡶⢒⠲⡃⢊⣍⠣⡚⠠⡶⢲⢂⡖⢄⡃⢊⠠⡶⣓⢮⣓⠨⡴⣂⠄⢆⠦⡴⠔⡃⡾⠖⠠⡶⢆⡂⠆⠠⡶⢲⢖⠶⠠⡴⣴⠧⠊⠇⢺⡕⢌⡆⠱⡊⠄⡢⣅⢮⠠⡶⢲
```



## おまけ2
手元にあったイロイロなファイルを食わせて遊んでみました。

- 漫才系：掛け合いが早いと、壊滅的な結果
- 専門用語多い系：医療系や法律系の専門用語が頻出すると、壊滅的な結果
- 音楽系：音声以外の音(ギター・ベース・ドラム等)が大きいとノイズ扱いされ、壊滅的な結果

このあたりは、日本語だけの問題ではなく英語なども共通の課題ですかね。

## おまけ3
手元にあったイロイロなファイルを食わせて遊んでいると、`LENCE]`という単語が時々出現します。
SILENCE？沈黙？バグ？

# 参考
[Amazon Transcribe 開発者ガイド ](https://docs.aws.amazon.com/ja_jp/transcribe/latest/dg/what-is-transcribe.html)
[Amazon Transcribe 話者の識別](https://docs.aws.amazon.com/ja_jp/transcribe/latest/dg/how-diarization.html)
[AWS CLI Reference transcribe](https://docs.aws.amazon.com/cli/latest/reference/transcribe/index.html#cli-aws-transcribe)