Rubyで文章生成AI「GPT-2」を実行して遊んでみた

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/5a8708e7-3e66-5124-80d3-983457143224.png" width="75%">

## はじめに

Python 以外の言語から AI にアクセスするのは難しすぎると考えている人が多いと思います。しかし、最近は、事前トレーニング済みのモデルが多数公開されており、さまざまなプラットフォームやプログラミング言語から AI 推論を簡単に実行できるようになっています。 ここでは、Ruby 言語から ONNX ランタイムを実行して英文を生成できる GPT-2 モデルで遊んでみたいと思います。 ONNX ランタイムを使用すると、推論は簡単に実行できます。

## 事前準備とインストール。

下記の３つのGemをインストールします。

* [onnxruntime](https://github.com/ankane/onnxruntime-ruby) - ONNX Runtime のRubyバインディングです。
* [tokenizers](https://github.com/ankane/tokenizers-ruby) - Hugging Face の提供しているTokenizerのRubyバインディングです。
* [numo-narray](https://github.com/ruby-numo/numo-narray) - Rubyの行列計算ライブラリです。NumPyと同じようなことができます。

```
gem install onnxruntime
gem install tokenizers
gem install numo-narray
```

onnxruntime gem は内部にプラットフォームごとのバイナリファイルを梱包済みですので、ONNX Runtimeを別途インストールする必要はなく、gemをインストールするだけで動作します。また、単数形の tokenizer は全く別のGemですのでご注意ください。

なお、tokenizersの拡張機能はRustで書かれているためインストールにはRustが実行できる環境が必要です。Windowsで動かしたい場合やRustがない環境では、tokenizersのかわりに[blingfire](https://github.com/ankane/blingfire-ruby)を使います。実装は[こんなかんじ](https://github.com/kojix2/LibUI/blob/main/examples/gpt2_notepad.rb)です。

## ONNX モデルの入手

ここではONNX公式が配布する[GPT-2のモデル](https://github.com/onnx/models/tree/main/text/machine_comprehension/gpt-2)を利用します。[リンク先](https://github.com/onnx/models/tree/main/text/machine_comprehension/gpt-2)からGPT-2-LM-HEADをダウンロードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/60d8bf1f-b76b-7473-895b-bf54787aaf12.png)上の画面で右クリックでダウンロードしようとするとリンク先はまだhtmlなので失敗します。一度リンクを踏んで、次のページの右上のDownloadボタンからモデルをダウンロードしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/ea4c4368-631a-09de-f10e-e381ae64d58a.png)


`gpt2-lm-head-10.onnx` をスクリプトと同じディレクトリに配置して下さい。[Netron](https://github.com/lutzroeder/Netron)というソフトウェアを使うと、ONNXモデルの中身を閲覧することができます。

## コードを書く

準備は完了しました。それでは実際に動かすRubyのコードを準備しましょう。

```ruby
require "tokenizers"
require "onnxruntime"
require "numo/narray"

s = "Why do cats want to ride on the keyboard?"

# トークナイザーを準備する。ここではgpt2を指定
tokenizer = Tokenizers.from_pretrained("gpt2")

# モデルを読み込む
model = OnnxRuntime::Model.new("gpt2-lm-head-10.onnx")

# 文字列を行列に変換
ids = tokenizer.encode(s).ids

# 単語を10回取得
10.times do
  o = model.predict({ input1: [[ids]] })
  o = Numo::DFloat.cast(o["output1"][0])
  ids << o[true, -1, true].argmax
end

# 結果を出力：行列から文字列へ
puts tokenizer.decode(ids)
```

下記の部分は、Rubyとしては見慣れない記法だと思います。これは numo-narray という NumPy と同様のことができるライブラリを利用しています。詳しい使い方を知りたい方は、[NArrayの簡単なつかい方](https://qiita.com/kojix2/items/0bc5efda0f65e58693a0)をご覧ください。

```ruby
  o = Numo::DFloat.cast(o["output1"][0])
  ids << o[true, -1, true].argmax
```

※ 実は行列操作の部分は、私も何をしているのか本質的にはわかっていなくて、**ひたすらNumPyのコードをnumo-narrayに直訳**しています。けれども、逆に言うと、あまりAIや数学の知識がなくても、公開されているAIのモデルは利用はできてしまいます。自動車の整備ができなくても、自動車が運転できるのと同じです。

## 実行してみる

`ruby` コマンドでも実行できますし、`irb` に上のコードをコピペしても動きます。

```
Why do cats want to ride on the keyboard?

The answer is that they do.
```

> なんでネコはキーボードに乗りたがるの？
> その答えは「そうしたいから」。

と表示されたら成功うまくいっています。

いかがでしたか？

今回実行したのは、Web上のAPIを呼び出しているわけではなく、実際に手元のマシンでDeep Learningを実行した内容になります。思ったより簡単にRubyからDeep Learning のモデルを呼び出せたかと思います。

それでは楽しいRubyライフを。この記事は以上です。