Google の Magika を Python から C#に移植するまでの過程 (Day 7 / 7)

Google のファイル判定プログラム Magika を Python から C# に移植する過程を共有する記事の最終回です。

前回まででようやく一通りの Magika のコードを C# に移植が完了しました。最終回となる今回は、移植した Magika をビルドし、実際にファイル判定を行って動作を確認します。

## 目次
- [Day 1 : まずは Magika の中身を見てみよう](https://qiita.com/mkht/items/64b02b8648266d40380a)
- [Day 2 : C# で 概念実証コードを書いてみる](https://qiita.com/mkht/items/fa76b0238ed976d20ca9)
- [Day 3 : C# クラスライブラリとして Magika を移植していく](https://qiita.com/mkht/items/a7fa36c5446b5fad5092)
- [Day 4 : GitHub Copilot を使って作業効率アップ](https://qiita.com/mkht/items/2c2b23ceb0bc1df66599)
- [Day 5 : クラスライブラリとしての Magika を完成させる](https://qiita.com/mkht/items/9d7a8a16ed1d60e353fa)
- [Day 6 : コンソールアプリを作成する](https://qiita.com/mkht/items/0b2d1546cf8a25ab4cce)
- [Day 7 : 移植したMagikaをビルドし、動作確認する](https://qiita.com/mkht/items/f36d87afe50eae2be5f1)

## 移植した Magika をビルドし、動作確認する

### ビルド

完成したコードをビルドしてバイナリを作成します。ビルド時にターゲットフレームワークとプラットフォームを指定します。

```cmd
# クラスライブラリをビルド
cd .\magika
dotnet publish -c Release -f net6.0 -r win-x64

# コンソールアプリケーションをビルド
cd .\cli
dotnet publish -c Release -f net8.0 -r win-x64
```

コマンドからもわかるように実際にはビルドだけでなく、ビルド成果物を動作させるための依存ライブラリなどをまとめて配布できるようにする`publish`を行っています。

これで`.\magika\bin\Release\net6.0\win-x64`と`.\magia\cli\bin\Release\net8.0\win-x64`にそれぞれビルド成果物が出力されます。

> クラスライブラリを net6.0 でビルドしているのは PowerShell の古いバージョンで動作させるためです。ターゲットフレームワークの選択については [第2回]() で説明しています。


### 動作確認：クラスライブラリ

**注意： Windows では Visual C++ 2015-2022 Redistributable が必要です**

動作確認をする際に、実はひとつ初歩的な罠にハマりました。

Magika は 機械学習モデルの実行に ONNX Runtime を使っています。ONNX Runtime を動作させるために Windows の場合は Visual C++ 2015-2022 Redistributable が必要です。これをインストールしていないと、実行時にエラーが発生します。

これが必要であることは [ONNX Runtime のドキュメント](https://onnxruntime.ai/docs/install/) にきちんと書いてあるのですが、私はこれを見落としていて、ある PC では動作するのに別の PC では動作しない！という状況が発生し、原因に気がつくのに丸一日かかってしまいました。

前提条件をクリアしたうえで、クラスライブラリの Magika を PowerShell から呼び出すことで動作確認します。今回の動作確認では最新の PowerShell 7.4 を使っています。

```powershell
# magia.lib.dll を読み込む
> Add-Type -Path ".\magika\bin\Release\net6.0\win-x64\magika.lib.dll"

# Magika クラスのインスタンスを作成
> $magika = [magika.Magika]::new()

# ファイル判定を実行
> $Result = $magika.IdentifyPath("F:\sample.jpg")

# 結果を表示
> $Result | Format-List

path   : F:\sample.jpg
dl     : ModelOutputFields { ct_label = jpeg, score = 1, group = image, mime_type = image/jpeg, magic = JPEG image data
         , description = JPEG image data }
output : MagikaOutputFields { ct_label = jpeg, score = 1, group = image, mime_type = image/jpeg, magic = JPEG image dat
         a, description = JPEG image data }
```

`IdentifyPath`メソッドにファイルパスを渡して実行すると、ファイルの判定結果が返ってきます。この例では`sample.jpg`が JPEG 画像であることが判定できています。

もう少し PowerShell 的な使い方の例ということで、フォルダ内のすべてのファイルに対してファイル判定を実施し、結果を CSV 形式で出力する例を示します。

```powershell
> Get-ChildItem F:\tests_data -File -Recurse | % {$magika.IdentifyPath($_.FullName)} | % {[pscustomobject]@{Path = $_.path; FileType = $_.output.ct_label}} | ConvertTo-Csv

"Path","FileType"
"F:\tests_data\README.md","markdown"
"F:\tests_data\basic\code.css","css"
"F:\tests_data\basic\code.js","javascript"
"F:\tests_data\basic\code.py","python"
"F:\tests_data\basic\doc.docx","docx"
"F:\tests_data\basic\doc.epub","epub"
"F:\tests_data\basic\doc.html","html"
```

これは余談になりますが、PowerShell から .NET のクラスライブラリメソッドを呼び出す際に相対パスを使うと、PowerShell 側のカレントディレクトリと .NET 側のカレントディレクトリが一致せずに予期せぬ動作をする場合があるので注意してください。（これは不具合ではなく動作仕様です）

```powershell
#### 良くない例 ####
C:\> $magika = [magika.Magika]::new()
C:\> cd F:\tests_data
# F:\tests_data\sample.jpg を相対パスで指定したつもりですが、実際には C:\sample.jpg を見に行ってしまいます
F:\tests_data> $magika.IdentifyPath(".\sample.jpg")

#### 正しい例 ####
C:\> $magika = [magika.Magika]::new()
C:\> cd F:\tests_data
# .NET 側のカレントディレクトリを PowerShell 側のカレントディレクトリに合わせる
F:\tests_data> [Environment]::CurrentDirectory = $PWD
# これで意図通り F:\tests_data\sample.jpg を見に行きます
F:\tests_data> $magika.IdentifyPath(".\sample.jpg")
```

いい感じです。実際には同じことを Windows だけでなく Ubuntu の PowerShell でも試したりして、各プラットフォームで正しく動作することを確認します。


### 動作確認：コンソールアプリケーション

続いてコンソールアプリ版の Magika を動作させてみます。

```
> cd .\magika\cli\bin\Release\net8.0\win-x64
> magika.exe F:\sample.jpg

F:\sample.jpg: JPEG image data (image)
```

良いですね。CLI 版の Magika は様々なコマンドラインオプションがあるのでそれらも確認します。

![magika-cli.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177762/c806e955-9d7c-cf38-15ed-aed3139fb1de.png)

```
# --json で JSON 形式で出力
> magika.exe F:\tests_data\mitra\svg.svg --json

[
  {
    "path": "F:\\tests_data\\mitra\\svg.svg",
    "dl": {
      "ct_label": "svg",
      "score": 0.9999957,
      "group": "image",
      "mime_type": "image/svg+xml",
      "magic": "SVG Scalable Vector Graphics image",
      "description": "SVG Scalable Vector Graphics image data"
    },
    "output": {
      "ct_label": "svg",
      "score": 0.9999957,
      "group": "image",
      "mime_type": "image/svg+xml",
      "magic": "SVG Scalable Vector Graphics image",
      "description": "SVG Scalable Vector Graphics image data"
    }
  }
]
```

```
# --debug でデバッグ情報を出力
> magika.exe F:\tests_data\mitra\svg.svg --debug

INFO: Considering 1 files
DEBUG: Files: F:\tests_data\mitra\svg.svg
DEBUG: ONNX DL model loaded in 0.056 seconds
DEBUG: Processing input files and extracting features for 1 samples
DEBUG: First pass and features extracted in 0.007 seconds
DEBUG: DL input prepared in 0.001 seconds
DEBUG: DL raw prediction in 0.012 seconds
F:\tests_data\mitra\svg.svg: SVG Scalable Vector Graphics image data (image)
```

上々です。オリジナルの Python 版 Magika の出力結果と見比べて差異がないことも確認しましたが問題なさそうです。


## やり残したこと

今回の移植作業で、オリジナルの Python 版 Magika のほぼすべての機能を C# に移植することができました。しかし、以下の点については今後の課題として残しておきます。

### 外部モデルの読み込み機能
[第3回]()でも説明した通り、外部から機械学習モデルを読み込む機能は今回の移植作業ではオミットしました。主に C# で JSON を扱うのが大変だったからというのが理由ですが、移植作業を通して C# での JSON の扱い方にも慣れてきた感じがありますので、余裕があればこの機能も追加してみたいです。

### オリジナル版の変更点への追従
今回の移植作業では Python 版 Magika の初期リリースである v0.5.0 をベースにしていますが、その後もオリジナルの Magika は改良が続けられており、現在では内部処理にいくつか変更が加わっています。これらの変更点についても時間に余裕があれば追従していきたいです。

### テストコードの追加
今回完成したコードの動作確認はすべて手動で行いました。自動テストコードを追加しておけば、今後バグ修正をしたり機能の追加をした際にも短時間かつ確実な動作テストができるようになります。

オリジナルの Python 版 Magika にはテストコードが含まれているので、それを参考にしてテストコードを移植していく方法もあるでしょうし、GitHub Copilot にもテストコードの作成を実行させるコマンドがありますので、それを使ってテストコードを自動生成させてみるのも面白そうです。

### GitHub Actions による CI/CD
GitHub Actions を使うと、コードをプッシュした際に自動でビルドやテストを実行することができます。また、ビルド成果物をリリースとして公開することもできます。Windows、Linux、macOS の複数プラットフォームにまたがったビルドやテストを自動化することもできますので、これもやってみたいです。


## おわり

Python も C# にもそれほど明るくない私でも、一週間ちょっとの時間をかけてなんとか移植作業を完了することができました。移植作業を通じて、Python と C# の言語仕様やライブラリの違い、それぞれの言語での機械学習モデルの扱い方などを学ぶことができ、楽しかったです。

完成した C# 版 Magika のコードは以下の GitHub リポジトリで公開しています。興味のある方はぜひご覧ください。

https://github.com/mkht/Magika-CSharp