Elixir Nx 周辺の更新

## はじめに

過去の記事で何度か Elixir の Nx 周辺技術を紹介してきました

https://qiita.com/RyoWakabayashi/items/45a7daccf064b8720ad0

https://qiita.com/RyoWakabayashi/items/dfb5495b9c25eff710a2

https://qiita.com/RyoWakabayashi/items/a06ae639337726be2e9d

https://qiita.com/RyoWakabayashi/items/f7960ba642dfbaa05a9c

しかし、 Nx は現在進行形でどんどん進化しています

古いバージョンで動いていたコードが既に動かなくなっていたり、非推奨になっていたりします

というわけで、現状最新である以下のバージョンで各記事の処理を書き直してみました

- Livebook: 0.7.1
- Kino: 0.7.0
- Nx: 0.4.0
- Explorer: 0.3.1
- Evision: 0.1.14

各変更箇所のエッセンスだけ抜き出して紹介します

実装したコードの全量はこちら

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

## Evision のインストールオプション

インストールオプションのデフォルト値が変わっていました

- 0.1.6 以前
  - 何も指定しなければコンパイルしてインストール、
  - 環境変数に `EVISION_PREFER_PRECOMPILED=true` を指定している場合はコンパイル済のものをインストール

- 0.1.7 以降
  - 何も指定しなければコンパイル済のものをインストール
  - 環境変数に `EVISION_PREFER_PRECOMPILED=false` を指定している場合はコンパイルしてインストール

## Evision マトリックスの画像表示

なんと言っても便利になったのは Evision のマトリックスがそのまま画像として表示できるようになったことです

また、 `!` を付けないでマトリックスのみが返ってくるようになりました

- `!` の仕様変更
  - 旧: `{:ok, mat} = Evision.imread(lenna)` または `mat = Evision.imread!(lenna)`
  - 新: `mat = Evision.imread(lenna)`

### Evision 画像表示 旧バージョン実装例

画像を読み込んだあと、バイナリに変換して Kino に渡す必要がありました

```elixir
defmodule Helper do
  def show_image(mat) do
    Evision.imencode!(".png", mat)
    |> IO.iodata_to_binary()
    |> Kino.Image.new(:png)
  end
end

{:ok, mat} = Evision.imread(lenna)
Helper.show_image(mat)
```

![old_evision.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/f87ceb7f-e319-bcfb-ea24-c6a0a4973d2a.jpeg)

### Evision 画像表示 新バージョン実装例

単純に画像ファイルを読み込むだけで画像が表示できるようになりました

実行結果に `Raw` `Image` `Numeric` のタブが表示され、 `Image` タブで画像を見ることができます

```elixir
mat = Evision.imread(lenna)
```

![evision_kino.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/85b94feb-d5af-3cca-ee3a-6a3de3a9ce91.gif)

## Evision の仕様変更

Nx のテンソルと Evision のマトリックス間での変換処理が変更されています

- Nx -> Evision
  - 旧: `Evision.Nx.to_mat()`
  - 新: `Evision.Mat.from_nx()`

- Evision -> Nx
  - 旧: `Evision.Nx.to_nx()`
  - 新: `Evision.Mat.to_nx()`

また、引数として List を渡していた箇所が Tuple に変更されています

- 引数の仕様変更
  - 旧: `Evision.warpAffine!(affine, [512, 512])`
  - 新: `Evision.warpAffine(affine, {512, 512})`

### Evision 画像加工 旧バージョン実装例

```elixir
{:ok, affine} =
  [
    [1, 0, 100],
    [0, 1, 50]
  ]
  |> Nx.tensor(type: {:f, 32})
  |> Evision.Nx.to_mat()

lenna
|> Evision.imread!()
|> Evision.warpAffine!(affine, [512, 512])
|> Helper.show_image()
```

### Evision 画像加工 新バージョン実装例

```elixir
affine =
  [
    [1, 0, 100],
    [0, 1, 50]
  ]
  |> Nx.tensor(type: {:f, 32})
  |> Evision.Mat.from_nx()

lenna
|> Evision.imread()
|> Evision.warpAffine(affine, {512, 512})
```

## Evision.Backend

実は Evision が Nx バックエンドとして動くようになっており、
`Evision.Mat.to_nx()` を使うと Evision バックエンドが使われます

ただし、一部の関数がサポートされていないため、以下のようなコードを実行するとエラーになります

エラーになるコード

```elixir
predictions
|> Enum.map(fn prediction ->
　　Evision.Mat.to_nx(prediction)
end)
|> Nx.concatenate()
```

エラーメッセージ

```
operation concatenate is not yet supported on Evision.Backend
Please use another backend like Nx.BinaryBackend or Torchx.Backend.
```

エラーを回避するためには、他のバックエンドを使用するよう明示しないといけません

エラー回避コード

```elixir
predictions
|> Enum.map(fn prediction ->
　　Evision.Mat.to_nx(prediction, Nx.BinaryBackend)
end)
|> Nx.concatenate()
```

## Explorer の仕様変更

一部の関数が非推奨になり、代わりの関数を使うようになりました

- フィルター
  - 旧: `DataFrame.filter()`
  - 新: `DataFrame.filter_with()`

- 加工
  - 旧: `DataFrame.mutate()`
  - 新: `DataFrame.mutate_with()`

### Explorer 旧バージョン実装例

`mutate` で「人口（千人）」列を float に cast しています
`filter` で「都道府県」列が「東京都」の行だけを抽出しています

```elixir
population_df
|> DataFrame.mutate("人口（千人）": &Series.cast(&1["人口（千人）"], :float))
|> DataFrame.filter(&Series.equal(&1["都道府県"], "東京都"))
```

### Explorer 新バージョン実装例

`mutate_with` で「人口（千人）」列を float に cast しています
`filter_with` で「都道府県」列が「東京都」の行だけを抽出しています

```elixir
population_df
|> DataFrame.mutate_with(fn df ->
  [
    "人口（千人）": Series.cast(df["人口（千人）"], :float)
  ]
end)
|> DataFrame.filter_with(&Series.equal(&1["都道府県"], "東京都"))
|> DataFrame.select(["年齢層", "性別", "人口（千人）"])
```

### mutate_with の問題

`DataFrame.mutate_with` では `Series.transform` を使用できません

エラーになるコード

```elixir
trim_comma = fn input ->
  String.replace(input, ",", "")
end

population_df
|> DataFrame.mutate_with(fn df ->
  [
    "人口（千人）": Series.transform(df["人口（千人）"], trim_comma)
  ]
end)
```

エラーメッセージ

```
cannot perform operation on an Explorer.Backend.LazySeries
```

この問題は公式 Issue にもでているので、 Explorer 0.4 では何がしかの代替策が出てくるはずです

https://github.com/elixir-nx/explorer/issues/355

## AxonOnnx について

AxonOnnx の 0.2.1 ではまだ Nx の 0.4 を使えません

## おわりに

**今が熱い** 技術はどんどん進化するので、定期的にアップデートしていかないといけませんね