Create MLを使って簡単にiOSアプリで機械学習を実装する方法

iOSアプリで機械学習を使って画像認識などをするときにCreate MLを用いると簡単に実装できるということで実際に触って見た。
Xcode以外で追加で必要なものは何もないのですごく楽な印象。

#1. 学習用モデルの作成
ここでは一から機械学習用モデルを作っていくが、Apple公式でもいくつかモデルが公開されているので[こちら](https://developer.apple.com/jp/machine-learning/models/)をチェックして使用したいものがあればダウンロードして使ってみると時間節約にもなると思う。
###学習データを作るためのプログラムを作成
「学習データを作るためのプログラムを作成」と聞くと一見難しく聞こえるが実はたった数行で作ることができる。
#####1. まずXcodeを起動しPlay Groundファイルを作成する。
<img width="473" alt="スクリーンショット 2019-09-19 10.41.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367053/645dec5d-b3d2-cbdd-e64f-4f7c6acacd67.png">
このプログラムファイルは今後も使い回せるので、「ImageRecognitionMLMaker」などとわかりやすい名前にしておくと良いだろう。
次にどのプラットフォームで作成するかを選ぶ画面が出てくると思うが、この後初めから書かれているコードは全て削除のでどれを選んでも問題はない。
<img width="1072" alt="スクリーンショット 2019-09-19 10.47.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367053/1a25389d-827d-3bf7-d16d-290204c7cb45.png">
このようにエディターが起動したら、最初から書かれているコードを全て削除し、以下のコードを入力する。

```swift
import CreateMLUI

let builder = MLImageClassifierBuilder()

builder.showInLiveView()
```
上記のコードを入力することで機械学習データ作成用の画面を起動することができる。（次のXcodeからはこのコードを入力しなくてもメニューから起動できるとのことなので期待）

コードを入力し終えたら、右上のボタンを押してアシスタントビューを表示する。
<img width="1512" alt="スクリーンショット 2019-09-19 10.54.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367053/0f39de18-7167-ed37-5616-4af44a2c0697.png">

最後に、右端の再生ボタンを押し実行すると機械学習作成用の画面が出てくる。
<img width="1512" alt="スクリーンショット 2019-09-19 10.56.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367053/7d1a6bc0-c107-ba4d-2611-be7b01f513a6.png">
この画面が出たら成功している。

###学習用の画像データを用意する。
ここまで完成したら次に学習用の画像データを用意する必要がある。
**画像を入れるフォルダー名はこの後、学習データに取り込まれるのできちんとした名前にしておくことをお勧めする。**
画像データは学習用とテスト用の二種類を用意する必要があり、学習用7割、テスト用3割にすると良いと言われている。
以下のようなフォルダー階層にすると良いだろう。
<img width="414" alt="スクリーンショット 2019-09-19 11.01.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367053/8673bb71-b1b9-4bb9-a034-d03c67986758.png">
なお、Testing Dataのフォルダー内にも同じ名前のフォルダーがあり中には異なる画像が入っている。
######写真を簡単に集める方法
ここでは詳しく使い方を紹介しないが[こちらのツール](https://co.bsnws.net/article/295)を使うと画像の収集が早くなるだろう。

ここまでできたら、先ほどのXcodeの画面の右側にあるImageClassifierの下にある点線内に学習用データのフォルダーごとドラックアンドドロップする。（上記の場合だとTraining Data）
すると、自動的に機械学習モデルを生成してくれる。
<img width="1512" alt="スクリーンショット 2019-09-19 11.09.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367053/d0c8471c-d183-a92c-0388-4ed64a4e7b48.png">
このような画面になったら完了。次にテスト用データ(上記の場合だとTesting Data)を先ほどと同じ要領で下の枠線にドラックアンドドロップする。全て完了すると学習モデルの正確性が表示されるはずだ。
全てが無事に終わったら、ImageClassifierの右隣にあるしたやじるしのようなボタンをクリックして今回作った学習モデルを保存する。

#2. アプリへの組み込み
ここまで終わったら、拡張子がmlmodelというファイルが出来上がっていると思う。
まずは、通常のアプリを作るときと同じ手順でXcodeプロジェクトを作成する。
次に、先ほど作成したmlmodelファイルを画像などを取り込む時と同じようにXcodeプロジェクトにドラックアンドドロップする。これで下準備は終わりだ。
次にアプリの見た目UIであるMainStoryBoardを作成するのだが、今回はテストのため以下のようにする。
<img width="588" alt="スクリーンショット 2019-09-19 11.29.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367053/56fdd2da-8d2f-e51a-e961-fb68ff7bad1e.png">
カメラボタン押すと写真を撮りそれをImage Viewに表示しそれが何かをNavigation Barに表示するものを作る。

まずは以下の3つをインポートしてほしい。

```swift
import UIKit
import CoreML
import Vision
```
CoreMLは機械学習全般のライブラリーでVisionは画像認識に特化したライブラリーである。

今からボタンを押すとカメラが起動し撮った写真をImage Viewに表示するプログラムを書くのだが、今回は機械学習をメインで取り扱うため詳しい説明は省略させてもらう。
以下のコードがImage Viewに画像を表示するコードである。

```swift
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    @IBOutlet var imageVIew: UIImageView!
    let imagePicker = UIImagePickerController()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        imagePicker.delegate = self
        imagePicker.sourceType = .camera
        imagePicker.allowsEditing = false
    }
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let userPickedImage = info[.originalImage] as? UIImage {
            imageVIew.image = userPickedImage
        }
        
        imagePicker.dismiss(animated: true, completion: nil)
    }

    @IBAction func cameraTapped() {
        present(imagePicker, animated: true, completion: nil)
    }
}
```

次に取得した画像を認識するのだが、CoreMLはUIImageを読み込めないため、CoreMLが認識できるCIImageというものに変換する。

```swift
guard let ciimage = CIImage(image: userPickedImage) else {
    fatalError("Could not convert UIImage into CIImage")
}
```
このコードをimagePicker.dismissの前に記入してほしい。
ではいよいよ、画像認識を行うコードを書いていきたいと思う。

```swift
func detect(image: CIImage) {
        guard let model = try? VNCoreMLModel(for: Pet().model) else {
            fatalError("Loading CoreML Model Failed")
        }
        
        let request = VNCoreMLRequest(model: model) { (request, error) in
            guard let results = request.results as? [VNClassificationObservation] else {
                fatalError("Model failed to process image")
            }
            
            if let firstResult = results.first {
                self.navigationItem.title = firstResult.identifier
            }
        }
        
        let handler = VNImageRequestHandler(ciImage: image)
        
        do {
            try handler.perform([request])
        } catch {
            print(error)
        }
    }
```
ここから要所をつまんで説明していきたいと思う。

この部分では先ほど取り込んだ機械学習モデルを読み込んでいる。私は学習モデル名をPetにしたためPet()となっているがここの名前は各々で変更してほしい。

```swift
guard let model = try? VNCoreMLModel(for: Pet().model) else {
    fatalError("Loading CoreML Model Failed")
}
```

この部分では学習モデルへ送るリクエストを定義している。先ほど作成したmodelを指定し、認識が完了した後にどのような動作をするかを指定している。今回はナビゲーションバーのタイトルを変更している。

```swift
let request = VNCoreMLRequest(model: model) { (request, error) in
            guard let results = request.results as? [VNClassificationObservation] else {
                fatalError("Model failed to process image")
            }

            if let firstResult = results.first {
                self.navigationItem.title = firstResult.identifier
            }
        }
```

最後に今作成したリクエストを実行している。エラーが発生する可能性があるためdo catchを使うことをお勧めする。リクエストは何個も指定でき配列を渡さないといけないので要素が一つの配列を指定している。

```swift
let handler = VNImageRequestHandler(ciImage: image)

        do {
            try handler.perform([request])
        } catch {
            print(error)
        }
```

最後に先ほど書いたUIImageをCIImageに変換したコードの下で作ったdetectの関数を呼び出して見て欲しい。
これ完成だが、カメラを使用しているのでinfo.plistからカメラへのアクセスを設定しておく必要がある。こちらに関しても記事がたくさんあるので説明は省略させていただく。
以上のことをして実行すると、無事に機械学習で画像認識ができるアプリを作成できたと思う。
以下はコードの全貌ある。

```swift
import UIKit
import CoreML
import Vision

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    @IBOutlet weak var imageVIew: UIImageView!
    let imagePicker = UIImagePickerController()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        imagePicker.delegate = self
        imagePicker.sourceType = .camera
        imagePicker.allowsEditing = false
    }
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let userPickedImage = info[.originalImage] as? UIImage {
            imageVIew.image = userPickedImage
            
            guard let ciimage = CIImage(image: userPickedImage) else {
                fatalError("Could not convert UIImage into CIImage")
            }
            detect(image: ciimage)
        }
        
        imagePicker.dismiss(animated: true, completion: nil)
    }
    
    func detect(image: CIImage) {
        guard let model = try? VNCoreMLModel(for: Pet().model) else {
            fatalError("Loading CoreML Model Failed")
        }
        
        let request = VNCoreMLRequest(model: model) { (request, error) in
            guard let results = request.results as? [VNClassificationObservation] else {
                fatalError("Model failed to process image")
            }
            
            if let firstResult = results.first {
                self.navigationItem.title = firstResult.identifier
            }
        }
        
        let handler = VNImageRequestHandler(ciImage: image)
        
        do {
            try handler.perform([request])
        } catch {
            print(error)
        }
    }
    
    @IBAction func cameraTapped(_ sender: UIBarButtonItem) {
        present(imagePicker, animated: true, completion: nil)
    }
}
```

#最後に
今回初めての投稿なので、何か分かりにくいことなどあればコメントで教えてもらえると助かります。