Word2vecモデルをAWS Lambdaで動かしてみる


## はじめに


これは，[mediba Advent Calendar 2020](https://qiita.com/advent-calendar/2020/mediba) 23日目の記事です．


medibaの野崎です．
現在は，バックエンドエンジニアとして [au Webポータル](https://portal.auone.jp/) などのプロダクトの担当し，バックエンドアプリケーションやインフラ範囲の運用を行っています．
また，兼務でテクノロジーセンターにて，TechLeadチームにも所属しています．
※参考：[2020年度エンジニア組織について | mediba Creator × Engineer Blog](https://ceblog.mediba.jp/post/622861990882328576/2020%E5%B9%B4%E5%BA%A6%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E7%B5%84%E7%B9%94%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)


---

[AWS Lambda の新機能 – コンテナイメージのサポート | Amazon Web Services ブログ](https://aws.amazon.com/jp/blogs/news/new-for-aws-lambda-container-image-support/)

今月発表されたAWS Lambdaのコンテナイメージサポートされました．
`Lambda 関数を最大 10 GB のコンテナイメージとしてパッケージ化し、デプロイできるようになりました。`とあるように， これまでより大きなサイズのパッケージが出来るようになり，
機械学習のモデルや自然言語処理（NLP）で利用する辞書データなどをコンテナに含めることが出来そうです．

そこで本記事では，訓練済みのWord2vecモデルをコンテナ内に含めAWS Lambda上で動かしてみます．
作る機能としては，入力したワードに対して類似したワードとその類似度（0~1の範囲の値）を返却するもので，これをAPI経由で呼び出せるようにします．
訓練済みのWord2vecモデルは，類似したワードを返却する部分で使います．

![スクリーンショット 2020-12-17 14.23.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889583/2898ac93-fc85-cddc-80a6-63037b4478dd.png)

全体像としては，図のような形になります．

## 背景

なぜ今回このような検証を行うかを背景ですが，
[au Webポータル](https://portal.auone.jp/)ではニュース記事の配信を行っているプロダクトがあり，
この機能としてニュース記事に対して自然言語処理（NLP）を施して提供しているものいくつかあります．

現在このシステムはEC2上で構築されていますが，
今後はコンテナ化あるいはサーバレス化への変更を検討しています．
この検討の一環として，掲題のような内容を思い立ちました．

## 実施した手順

[AWS サーバーレスアプリケーションモデル - アマゾン ウェブ サービス](https://aws.amazon.com/jp/serverless/sam/)
今回はAWS Lambdaのフレームワークとして，SAMを利用します．

### 1. sam init

以下のバージョンで進めていきます．

```
$ sam --version
SAM CLI, version 1.13.2
```

samプロジェクトの初期化を行います．

```
$ sam init
Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 1
What package type would you like to use?
	1 - Zip (artifact is a zip uploaded to S3)
	2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 2

Which base image would you like to use?
	1 - amazon/nodejs12.x-base
	2 - amazon/nodejs10.x-base
	3 - amazon/python3.8-base
	4 - amazon/python3.7-base
	5 - amazon/python3.6-base
	6 - amazon/python2.7-base
	7 - amazon/ruby2.7-base
	8 - amazon/ruby2.5-base
	9 - amazon/go1.x-base
	10 - amazon/java11-base
	11 - amazon/java8.al2-base
	12 - amazon/java8-base
	13 - amazon/dotnetcore3.1-base
	14 - amazon/dotnetcore2.1-base
Base image: 3

Project name [sam-app]: sam-wiki-entity-vectors
```

以下のようなプロジェクトが出来ます．
※以降の作業で含めるディレクトリ，ファイルも記載してあります．

```bash

.
├── README.md
├── __init__.py
├── events
│   └── event.json
├── hello_world
│   ├── Dockerfile
│   ├── __init__.py
│   ├── app.py
│   ├── requirements.txt
│   └── tohoku_entity_vector         // 2. モデルの取得で作成するディレクトリ
│       ├── entity_vector.model.bin
│       └── entity_vector.model.txt
├── samconfig.toml                   // 7. sam deployで作成される
├── template.yaml
└── tests
    ├── __init__.py
    └── unit
        ├── __init__.py
        └── test_handler.py
```

### 2. モデルの取得

今回は東北大学 乾・岡崎研究室にて公開されている訓練済みのモデルを使わせていただきます．

[日本語 Wikipedia エンティティベクトル](http://www.cl.ecei.tohoku.ac.jp/~m-suzuki/jawiki_vector/)

上記サイトより以下の2つのファイルを取得し，samプロジェクト内に`tohoku_entity_vector`ディレクトリを作成しこれらを格納します．

- バイナリファイル (`entity_vector.model.bin`) 
- テキストファイル (`entity_vector.model.txt`) 

```bash

$ du -h hello_world/tohoku_entity_vector
2.6G
```
これらのファイルサイズは`2.6GB`ありました．

### 3. コードの変更

プロジェクト内の以下のファイルを変更します．

#### requirements.txt

以下を追加します，

```
gensim
```

Gensimは,大規模コーパスを用いたトピックモデリング,文書インデックス作成,類似性検索のためのPythonライブラリです.


#### Dockerfile

COPY箇所を以下のように修正します．
`tohoku_entity_vector/`もcopy対象に加えて，モデルもDockerコンテナに含めます．

```docker

COPY app.py requirements.txt tohoku_entity_vector/ ./
```

#### app.py

以下のように修正します．

```python

import os
import json

from gensim.models import KeyedVectors


def lambda_handler(event, context):

    word = event.get('queryStringParameters').get('word')

    model = KeyedVectors.load_word2vec_format(
        './entity_vector.model.bin', binary=True)
    result = model.most_similar('[' + word + ']')

    return {
        "statusCode": 200,
        "body": json.dumps(result, indent=2, ensure_ascii=False),
    }
```

`most_similar`は，入力したワードに対して類似したワードとその類似度(0~1)を類似度順に返却します．

[gensim.models.Word2Vec.most_similar](https://tedboy.github.io/nlps/generated/generated/gensim.models.Word2Vec.most_similar.html)

#### template.yaml

`Globals`の項，メモリとタイムアウト値を変更します．

```yaml

Globals:
  Function:
    Timeout: 900
    MemorySize: 10240
```

[Lambda quotas - AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html)

どちらも上記クォータの上限まで上げてみます．

### 4. sam build

dockerを起動した状態で，以下のコマンドを実行します．

```bash

$ sam build
```

### 5.ローカルでの動作の確認

ローカル環境にて以下のコマンドで，apiのテストが行えます．

```bash
$ sam local start-api
```

ブラウザで以下のようにリクエストします．

<img width="500" " src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889583/865510a8-5426-a876-8960-5532b3a76133.png">


クエリパラメータで入力した`川崎フロンターレ`という入力に対して，
類似したワードとして，`ジュビロ磐田` `大分トリニータ`といったワードとその類似度が期待通り返却されました．大丈夫そうです．


### 6.ECRリポジトリの作成

以下のコマンドで，DockerイメージをPUSHする用のECRリポジトリを作成します．

```
aws ecr create-repository --repository-name repository-name
```

### 7.sam deploy

```
#初回
$ sam deploy --guided

#2回目以降
$ sam deploy
```

初回のデプロイでは，`--guided`オプションをつけることによりデプロイ設定を選択していきます．`samconfig.toml`が作成されます．
2回目以降のデプロイでは，このファイルを元にデプロイされます．

`deploy`コマンドで，ECRへのimageのpushとAWS Lambdaへの関数のデプロイが行われます．


### 8.動作の確認

まずはECRのイメージを確認します．
PUSHされており，1.8GBほどあることが確認できます．

<img width="600" " src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889583/ff04e304-9307-13ce-0fa4-4dda7aa0ebcf.png">

次に，APIの確認を行います．
今度はクエリパラメータで入力した`FCバルセロナ`という入力を行い，
類似したワードとして，`レアル・マドリード` `ACミラン`といったワードとその類似度が期待通り返却されました．期待通りです．
APIのレイテンシは10sec程度でした.

<img width="700" " src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889583/0eee8fa8-ba85-dd7d-9509-32a871b76122.png">


## 機械学習システムのデザインパターン

本記事を執筆して知ったのですが，
メルカリ様にて，機械学習システムのデザインパターンを公開されていました．
[機械学習システムの設計パターンを公開します。 | メルカリエンジニアリング](https://engineering.mercari.com/blog/entry/ml-system-design/)
具体的なパターン別に，ユースケースや利点難点などが整理されています．

今回の構成は，[Web single pattern | ml-system-design-pattern](https://mercari.github.io/ml-system-design-pattern/Serving-patterns/Web-single-pattern/design_ja.html)
こちらのパターンになりそうです．

## まとめ

- Word2vecモデルをAWS Lambdaで動かしてみました
- Word2vecモデルをコンテナに内包出来るようになり，アプリケーションの開発が単純になりました
    - これまでAWS Lambdaでサイズな大きなファイルを扱う場合は，EFSなどのサービス連携が必須でした
- AWS Lambdaの`関数のメモリ割り当て`を，どのように最適化するか検討が必要です

## 参考記事

- [Word2Vecを理解する - Qiita](https://qiita.com/g-k/items/69afa87c73654af49d36#next)