Livebook で Bumblebee の BERT による文章の穴埋めを実行する

## はじめに

Bumblebee の公式サンプルに従ってモデルを実行してみるシリーズです

今回は BERT と言う自然言語処理技術を使って、文章の穴埋めを実行します

このシリーズの記事

- [画像分類: ResNet50](https://qiita.com/RyoWakabayashi/items/b9b4799683af5aaa29dd)
- [画像生成: Stable Diffusion](https://qiita.com/RyoWakabayashi/items/8070f6bc372015be6a8d)
- 文章の穴埋め: BERT　(ここ)
- [文章の判別: BERTweet](https://qiita.com/RyoWakabayashi/items/1f8624d717fb2b1dbdc0)
- [文章の生成: GPT2](https://qiita.com/RyoWakabayashi/items/6a780c2858ec952fa1e1)
- [質疑応答: RoBERTa](https://qiita.com/RyoWakabayashi/items/316d0c24020468d07f18)
- [固有名詞の抽出: bert-base-NER](https://qiita.com/RyoWakabayashi/items/82ac1dddfdddc8a2f79d)

Bumblebee の公式サンプル

https://github.com/elixir-nx/bumblebee/blob/main/notebooks/examples.livemd

実装の全文はこちら

https://github.com/RyoWakabayashi/elixir-learning/blob/main/livebooks/bumblebee/fill_mask.livemd

## 実行環境

- MacBook Pro 13 inchi
  - 2.4 GHz クアッドコアIntel Core i5
  - 16 GB 2133 MHz LPDDR3
- macOS Ventura 13.0.1
- Rancher Desktop 1.6.2
  - メモリ割り当て 12 GB
  - CPU 割り当て 6 コア

Livebook 0.8.0 の Docker　イメージを元にしたコンテナで動かしました

https://hub.docker.com/layers/livebook/livebook/0.8.0/images/sha256-6ef3fdd439fd72af9ae01d58e947664a46a8493f031e179cc0e91c08ddac02e5?context=explore

コンテナ定義はこちらを参照

https://github.com/RyoWakabayashi/elixir-learning

## セットアップ

必要なモジュールをインストールして EXLA.Backend で Nx が動くようにします

```elixir
Mix.install(
  [
    {:bumblebee, "~> 0.1"},
    {:nx, "~> 0.4"},
    {:exla, "~> 0.4"},
    {:kino, "~> 0.8"}
  ],
  config: [nx: [default_backend: EXLA.Backend]]
)
```

コンテナで動かしている場合、キャッシュディレクトリーを指定した方が都合がいいです

※詳細は[この記事](https://qiita.com/RyoWakabayashi/items/8070f6bc372015be6a8d)を見てください

```elixir
cache_dir = "/tmp/bumblebee_cache"
```

## モデルのダウンロード

モデルファイルを Haggin Face からダウンロードしてきて読み込みます

必要な場合は `cache_dir` を指定します

```elixir
{:ok, bert} =
  Bumblebee.load_model({
    :hf,
    "bert-base-uncased",
    cache_dir: cache_dir
  })
```

```elixir
{:ok, tokenizer} =
  Bumblebee.load_tokenizer({
    :hf,
    "bert-base-uncased",
    cache_dir: cache_dir
  })
 ```

tokenizer は文章をテンソルに変換するための前処理定義です

## サービスの提供

`Bumblebee.Text.fill_mask` で文章穴埋めサービスを提供します

```elixir
serving = Bumblebee.Text.fill_mask(bert, tokenizer)
```

## マスクされた文章の準備

テキスト入力を作り、文章を入力します

AI に穴埋めさせたいところを `[MASK]` にしてください

```elixir
text_input = Kino.Input.text("マスクされた文章", default: "The most important thing in life is [MASK].")
```

![スクリーンショット 2022-12-13 15.13.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/84c930ee-f999-2643-d943-b272a7e3f5d9.png)

入力された文章を取得します

```elixir
text = Kino.Input.read(text_input)
```

![スクリーンショット 2022-12-13 15.13.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/533127e7-75f8-3c54-7e57-cb42916bc5b4.png)

## 推論の実行

推論してデータテーブルに結果を表示します

```elixir
serving
|> Nx.Serving.run(text)
|> then(&Kino.DataTable.new(&1.predictions))
```

![スクリーンショット 2022-12-13 15.14.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/7e67396c-1722-7175-6d6c-b3e673175e84.png)

## 他のモデル

Hugging Face のリポジトリーIDを変えれば他のモデルでも実行できます

```elixir
serve_model = fn repository_id ->
  {:ok, model} =
    Bumblebee.load_model({
      :hf,
      repository_id,
      cache_dir: cache_dir
    })

  {:ok, tokenizer} =
    Bumblebee.load_tokenizer({
      :hf,
      repository_id,
      cache_dir: cache_dir
    })

  Bumblebee.Text.fill_mask(model, tokenizer)
end
```

```elixir
"albert-base-v2"
|> serve_model.()
|> Nx.Serving.run(text)
|> then(&Kino.DataTable.new(&1.predictions))
```

![スクリーンショット 2022-12-13 15.19.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/e953fa77-3994-e2c5-09a9-b95865795f3d.png)

```elixir
"roberta-base"
|> serve_model.()
|> Nx.Serving.run(text)
|> then(&Kino.DataTable.new(&1.predictions))
```

![スクリーンショット 2022-12-13 15.22.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/b33b9cb3-cf30-e16b-55a1-9437a58ca3b1.png)

モデルによって意見が違っていますね

## まとめ

テキスト系の AI も簡単に動かせました