SwiftでCoreMLの学習モデルを自作してみた

# CoreMLとは？
CoreMLは、Appleが提供する機械学習フレームワークで、iOSアプリに機械学習モデルを統合するための仕組みを提供します。これにより、デバイス上での高速な推論が可能となり、プライバシーを保護しながらオフラインでも動作するAI機能を実装できます。

本記事では、Swiftコードを用いてCoreMLモデルを自作し、感情分析を行うサンプルを紹介します。

# CoreMLモデルの自作

CoreMLモデルは[Appleの提供する事前学習済みモデル](https://developer.apple.com/jp/machine-learning/models/)を用いるか、既存の学習済みモデルをPythonを用い .mlmodel ファイルに変換して使うことが一般的なようです。
しかし、今回はSwiftで既存の学習モデルをトレーニングし、CoreMLモデルを作成しました。

### 学習モデル
そもそも学習モデルとはどういうものなのかというのを補足すると、機械学習によって訓練（トレーニング）されたデータとアルゴリズムの集合体であり、誤解を恐れずに言うと、ある種のプログラムやデータベースのようなものを指します。

### モデルの具体的なイメージ
例：画像分類モデル
1. 訓練データ
犬や猫などの画像データを数千枚用意し、それぞれに「犬」「猫」のラベルを付ける。
1. 学習（トレーニング）
AIアルゴリズム（ニューラルネットワークなど）に画像とラベルを繰り返し学習させることで、特徴（耳の形や体のシルエットなど）を認識できるようにする。
1. 出力されたモデル
トレーニングの結果、未知の画像が与えられたときに「犬」または「猫」と分類できるファイルが生成される。このファイルがCore MLの **モデル** です（拡張子は.mlmodel）。

### モデルの役割
Core MLのモデルは、次の2つの要素を含んでいます
1. パラメータ（知識）
学習結果として得られた係数やパターン（例：犬の耳の形や猫の目の位置などの特徴量）。
1. アルゴリズム
入力データを解析し、パラメータを基に結果を予測する仕組み（ニューラルネットワークや決定木など）。
このモデルは学習の過程でチューニングされたものであり、 **ユーザーがコードを書く必要はありません。** アプリはこのモデルにデータを渡すだけで推論結果を得られます。

### モデルの自作

では、前置きはこれくらいにして、実際にモデルを自作してみたいと思います。
今回作成したのは、文章を入力し、それがポジティブ・ネガティブ・ニュートラルのどれに分類されるかを判別する感情分析モデルです。
基本的にはApple公式ドキュメントを参照しています。
https://developer.apple.com/documentation/CreateML/creating-a-text-classifier-model
ただ、このドキュメントだけではモデル作成までに至らなかったので、ポイントを併せて紹介できればと思います。

具体的な流れは以下の通りです。

1. データセットの準備
1. モデル出力用のプロジェクト作成
1. モデル出力用のプログラム作成
1. サンプルプロジェクトでモデルをプレビューする

### 実装手順
##### 1. データセットの準備
感情分析用のデータセットとして、以下のようなjson形式のデータを用意しました。

```
[
    {
        "text": "今日は特にこれといった出来事はなく、いつも通りの日常を過ごした。",
        "label": "neutral"
    },
    {
        "text": "美味しいケーキを食べて、心が満たされた一日だった。",
        "label": "positive"
    },
    {
        "text": "大切にしていた物をなくしてしまい、心が痛い。",
        "label": "negative"
    },
    ...
]
```

##### 2. モデル出力用のプロジェクト作成
Xcodeで新規プロジェクトを作成する際に「Command Line Tool」を選択します。
![スクリーンショット 2025-02-20 22.49.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/8615fde6-4914-4528-bd7d-98d642d90a60.png)

##### 3. モデル出力用のプログラム作成
先ほど作成したプロジェクト内に ModelTrainer.swift を新規作成します。

```Swift
import Foundation
import CreateML
import TabularData

struct ModelTrainer {
    func trainAndSaveModel() {
        do {
            // モデルトレーニングとそのトレーニングしたモデルを保存する処理を描く
        } catch {
            // 例外処理
        }
    }
}
```
続いて用意したjsonを読み込み、DataFrameという形式に変換します
```Swift
let currentDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
let bundleURL = URL(fileURLWithPath: "JSONMocks.bundle", relativeTo: currentDirectoryURL)
guard let bundle = Bundle(url: bundleURL),
      let url = bundle.url(forResource: "sentiment", withExtension: "json") else {
    print("JSONファイルが見つかりません")
    return
}
let dataFrame = try DataFrame(contentsOfJSONFile: url)
```
ここで注意が必要なのが、このプロジェクトにはBundleがないということです。
そのため、jsonファイルをバンドルさせるためのターゲットを追加してあげる必要があります。
![スクリーンショット 2025-02-20 23.08.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/4e71beb2-985f-4a81-93dc-f81d70e20563.png)
そして作成したバンドルに用意したjsonを追加してあげます。
![スクリーンショット 2025-02-20 23.09.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/63a51af8-0f0c-440e-ba7c-48b18663166b.png)
これでバンドルのURLが取得できるようになったので、そこからファイルのURLを取得し、DataFrameに渡してあげています。

続いて読み込んだデータをトレーニングデータとテストデータに分割してあげます。
公式のドキュメントを読むと8:2にするといいと書いていたので、そのようにします。
```Swift
let (trainingData, testingData) = dataFrame.stratifiedSplit(
    on: "label",
    by: 0.8
)
```
さらに、トレーニング用のパラメータを設定してあげます。
ここら辺も公式ドキュメントのままです。言語だけ日本語に変えました。
```Swift
let parameters = MLTextClassifier.ModelParameters(
    validation: .split(strategy: .automatic),
    algorithm: .transferLearning(.bertEmbedding, revision: 1),
    language: .japanese
)
```
ここまでで準備ができたので、いよいよモデルのトレーニング処理を書いていきます。
すでに元となるテキスト分類の学習モデルがあるので、これを使って感情分類のトレーニングを行い、その結果のモデルを出力させるということをしていきます。
![スクリーンショット 2025-02-20 23.24.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/0de3e75a-d00a-4176-b33f-e8611f1e36d9.png)
まずは準備したトレーニング用のデータとパラメータを渡して MLTextClassifier のインスタンスを作成します。MLTextClassifier は trainingData を渡してインスタンスを生成した時点で、内部的にトレーニングが行われます。
```Swift
let classifier = try MLTextClassifier(
    trainingData: trainingData,
    textColumn: "text",
    labelColumn: "label",
    parameters: parameters
)
```
これで感情分析用のモデルができたはずなので、今度はこのモデルを保存する処理を書きます。
```Swift
let metadata = MLModelMetadata(
    author: "gdate",
    shortDescription: "感情分析モデル",
    version: "1.0"
)
let saveURL = FileManager.default.temporaryDirectory.appendingPathComponent("SentimentClassifier.mlmodel")
try classifier.write(to: saveURL, metadata: metadata)
            
print("モデルを保存しました: \(saveURL)")
```
最後にmain.swiftでこのModelTrainerを実行させる処理を書き、ビルドすればmlmodelが出力されます。
```Swift
let trainer = ModelTrainer()
trainer.trainAndSaveModel()
```

##### 4. サンプルプロジェクトでモデルをプレビューする
まず、エクスポートした SentimentClassifier.mlmodel をXcodeのプロジェクトに追加します。すると以下のような画面になると思います。
![スクリーンショット 2025-02-20 23.55.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/e943fe4e-cf3f-41a1-a1ac-9710937dc3b4.png)
タブにPreviewというのがあると思うのでそちらをクリックします。
![スクリーンショット 2025-02-20 23.56.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/5f37f2d9-e64b-4540-aff1-5e8136fc4ca4.png)
この入力欄に文章を入力してみると、作ったモデルが感情分析をしてくれます。
![スクリーンショット 2025-02-21 0.00.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457333/05f8ef10-8ec7-4f3c-8744-8f3688e396a4.png)

与えるデータを変えることで結果も異なりますので、用途に応じたデータを用意し、学習させてあげることで色々な使い道がありそうだなと思いました :smile: 

# 参考
https://developer.apple.com/documentation/CreateML/creating-a-text-classifier-model
https://developer.apple.com/jp/documentation/createml/creating_a_text_classifier_model/