今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

短信フィルタリング APP 開発

SMS フィルタリング APP 開発#

本文は搜狐技術製品 - 短信フィルタリング APP 開発に掲載されています。

自分の SMS フィルタリング APP を開発したいと思っていましたが、具体的な実施ができずにいました。今やっと心を落ち着けて、開発しながら全体の開発プロセスを記録しています。

ゴミ SMS サンプル#

最初の問題は、ゴミ SMS をフィルタリングするためには、まずどれがゴミ SMS なのかを識別する必要があるということです。どうやって識別するのでしょうか?

以前、鋼管のカウントを識別するための経験を参考に、CoreML を使って Text モデルをトレーニングすることに決めました。しかし、問題は、モデルをトレーニングするための SMS データセットをどうやって入手するかということです。

最初はインターネットでゴミ SMS のサンプルを探そうと思いましたが、長い間探しても見つからなかったので、自分や家族の携帯電話にある SMS を使うことにしました。携帯電話の SMS は一般的に削除されないため、数千件あり、ゴミ SMS、勧誘、広告などが豊富にあります。

そこで問題は、iPhone の SMS をどうやってエクスポートするかということです。

ここでもかなり調べましたが、見つけたサードパーティのソフトウェアはほとんどが有料でした。最終的に無料でエクスポートする方法を見つけました。

まず、暗号化せずに携帯電話をコンピュータにバックアップします。以下の図のように、Back up all the data on your iPhone to this Macを選択し、Back Up Nowをクリックしてバックアップが完了するのを待ちます。バックアップが完了したら、Manage Backupsをクリックします。

バックアップ画面

Manage Backupsをクリックすると、以下のような画面が表示され、バックアップの記録が表示されます。右クリックしてShow In Finderを選択し、フォルダを開きます。

管理バックアップ

次に、バックアップのディレクトリが開かれ、3d0d7e5fb2ce288813306e4d4636395e047a3d28という名前のファイルを見つける必要があります。このファイルが SMS バックアップのデータベースファイルです。さて、どうやって見つけるのでしょうか?バックアップディレクトリのフォルダを一つ一つ見ていると混乱するかもしれませんが、簡単です。検索を使い、右上の検索ボックスにこのファイル名を入力すればいいのです。注意点は、検索範囲が現在のフォルダであることです。

バックアップフォルダ

検索結果は以下の通りです。

SMS バックアップデータベースファイル

このファイルを別の場所、例えばデスクトップにコピーし、SQLPro for SQLLiteなどのデータベースソフトウェアで開きます。

SMS データベースファイルを開く

このファイルを観察すると、電話番号と SMS 記録が異なるテーブルに分かれていることがわかります。必要な内容を取得するために SQL を記述する必要があります。SQL の内容は以下の通りで、バックアップからメッセージを抽出するための SQLを参考にしています。上の図のQueryを選択し、以下のコマンドを入力します。

SELECT datetime(message.date, 'unixepoch', '+31 years', '-6 hours') as Timestamp, handle.id, message.text,
    case when message.is_from_me then 'From me' else 'To me' end as Sender
FROM message, handle WHERE message.handle_id = handle.ROWID AND message.text NOT NULL;

右上の実行ボタンをクリックすると、SMS がすべてフィルタリングされているのがわかります。
SMS フィルタリング

すべての行を選択し、右クリックしてExport result set as を選択し、CSV形式でエクスポートします。

SMS をエクスポート

これで必要な SMS サンプルを取得できました。

ゴミ SMS のトレーニング識別#

サンプルができたら、次にどうやってトレーニングして識別するかを考えます。Apple の CoreML を使用して識別するつもりですが、どうやって使うのでしょうか?サンプルのフォーマットの要件はどのようなものでしょうか?トレーニングにはどれくらいの時間がかかるのでしょうか?

まず、テキストトレーニングのCoreMLプロジェクトを作成します。Xcode を選択し、Open Developer ToolをクリックしてCoreMLを開きます。以下の図のようにします。

XcodeDeveloperTool

次にフォルダを選択し、新しいNew Documentをクリックします。

New Document

次にText Classificationを選択します。

Text Classification

続いてプロジェクトの名前と説明を入力します。

プロジェクト説明

右下の作成ボタンをクリックして、メイン画面に入ります。

メイン画面

Training Dataの詳細説明をクリックすると、CoreMLが要求するテキスト認識のフォーマットが表示されます。JSONCSVファイルがサポートされており、フォーマットは以下の通りです。

フォーマット

JSON フォーマットは以下の通りです。

// JSONファイル
[
    {
        "text": "映画は素晴らしかった!",
        "label": "positive"
    }, {
        "text": "非常に退屈。眠ってしまった。",
        "label": "negative"
    }, {
        "text": "まあまあでした。",
        "label": "neutral"
    } ...
]

CSV フォーマットは、1 列がtext、もう 1 列がlabelです。

textlabel
これは普通の SMS ですlabel1
これはゴミ SMS ですlabel2

前のステップで SMS を CSV 形式にエクスポートしたので、ここでは上の図のフォーマットに変更するだけです。残る問題は、label にはどのような値があるかということです。

label の値を確認するためには、システムの SMS フィルタリングロジックを確認する必要があります。サポートされているフィルタリングカテゴリは何か?そうでなければ、自分が実現したい分類が、最後にシステムでサポートされていないことがわかると困ります。

SMS フィルタリングカテゴリ#

システムの SMS フィルタリングロジック#

SMS と MMS メッセージフィルタリングを参考にすると、開発者は新しいグループを作成する権限がなく、未知の連絡先からのSMSまたはMMSに対して、指定されたカテゴリにフィルタリングすることしかできません。

ここで注意が必要なのは、文書によれば、SMS フィルタリングは iMessage や連絡先の SMS のフィルタリングをサポートしておらず、未知の連絡先からのSMSMMSのみをサポートしているということです。

SMS フィルタリングは、ローカル判断フィルタリングとサーバー判断フィルタリングに分かれます。示意図は以下の通りです。

SMS ローカルフィルタリング

SMS サーバーフィルタリング

文書によれば、たとえサーバーフィルタリングであっても、APP は直接ネットワークにアクセスできず、システムが設定されたサーバーとやり取りします。また、App Extension は共有グループを介してデータを書き込むことができないため、SMS は App Extension 内でのみ取得でき、保存やアップロードはできず、プライバシーとセキュリティが保証されます。サーバーフィルタリングの詳細な実装については、メッセージフィルタアプリ拡張の作成を参考にしてください。

次に、サポートされているフィルタリングタイプ、ILMessageFilterActionを見てみましょう。

大分類は 5 種類サポートされています:

  • none
    情報が不十分で判断できず、情報を表示するか、さらにサーバーに判断を依頼します。
  • allow
    通常通り情報を表示します。
  • junk
    通常通り情報を表示せず、ゴミ SMS カテゴリに表示します。
  • promotion
    通常通り情報を表示せず、プロモーション情報カテゴリに表示します。
  • transation
    通常通り情報を表示せず、取引情報カテゴリに表示します。

これらはさらに細分化され、ILMessageFilterSubActionが存在します。具体的な意味についてはILMessageFilterSubActionを参照してください。

  • none
  • promotion のサポートされるサブカテゴリは
    • others
    • offers
    • coupons
  • transation のサポートされるサブカテゴリは
    • others
    • finance
    • orders
    • reminders
    • health
    • weather
    • carrier
    • rewards
    • publicServices

ここでは大分類のみを処理し、具体的なサブ分類の詳細なフィルタリングは行わないため、トレーニングする label の値は以下のように明確になります:

  • allow
  • junk
  • promotion
  • transation

次に、エクスポートした SMS のCSVファイルに対して、各 SMS に対応する label を追加します。ここでは手作業で行う必要があります。サンプルのサイズと label の定義が、後の識別の正確性を決定します。また、後のサブ分類の実現のために、現実的に行うことをお勧めします。例えば、promotion の中のものを junk に分けないようにしましょう。。。

各 SMS サンプルにラベルを付けたら、Create MLにインポートしてトレーニングし、必要なモデルを生成します。手順は以下の通りです。

まず、データセットをインポートします。

データセットをインポート

次に左上のTrainをクリックします。

トレーニング Train

トレーニングが完了したら、Preview をクリックして SMS テキストをシミュレートし、出力の予測を確認します。以下の図のように。

効果検証

最後に、モデルをエクスポートして APP で使用します。

モデルをエクスポート

APP 開発#

新しいプロジェクトを作成し、new bing 生成画像を使用して APPIcon をデザインし、ChatGPT-4 を使って APP 名を生成します。そして、Message Filter Extensionターゲットを追加します。以下の図のように。

Message Filter Extension ターゲット

MessageFilterExtension.swiftの中には、Apple が基本的なフレームワークを実装してくれているのが見えます。必要なのは、フレームワークに対応する // TODO: の部分にフィルタリングロジックを追加することだけです。

次に、トレーニング結果セットをプロジェクトにインポートします。注意点は、ターゲットを主プロジェクトとMessage Filter Extensionのターゲットにチェックを入れることです。このターゲット内でモデルを使用してフィルタリングを実現する必要があります。

具体的な使用方法は以下の通りです。


import Foundation
import IdentityLookup
import CoreML

import IdentityLookup

enum SMSFilterActionType: String {
    case transation
    case promotion
    case allow
    case junk
    
    func formatFilterAction() -> ILMessageFilterAction {
        switch self {
        case .transation:
            return ILMessageFilterAction.transaction
        case .promotion:
            return ILMessageFilterAction.promotion
        case .allow:
            return ILMessageFilterAction.allow
        case .junk:
            return ILMessageFilterAction.junk
        }
    }
}

struct SMSFilterUtil {
    static func filter(with messageBody: String) -> ILMessageFilterAction {
        var filterAction: ILMessageFilterAction = .none
        let configuration = MLModelConfiguration()
        do {
            let model = try SmsClassifier(configuration: configuration)
            let resultLabel = try model.prediction(text: messageBody).label
            if let resultFilterAction = SMSFilterActionType(rawValue: resultLabel)?.formatFilterAction() {
                filterAction = resultFilterAction
            }
        } catch {
            print(error)
        }
        return filterAction
    }
}

次に、MessageFilterExtension.Swiftの中のofflineAction(for queryRequest: ILMessageFilterQueryRequest) -> (ILMessageFilterAction, ILMessageFilterSubAction)メソッドを呼び出します。以下のように。

  @available(iOSApplicationExtension 16.0, *)
  private func offlineAction(for queryRequest: ILMessageFilterQueryRequest) -> (ILMessageFilterAction, ILMessageFilterSubAction) {
      guard let messageBody = queryRequest.messageBody else {
          return (.none, .none)
      }
      let action = MWSMSFilterUtil.filter(with: messageBody)
      return (action, .none)
  }

ここで注意が必要なのは、APP の最低バージョン設定です。ILMessageFilterSubActionは iOS 16 以上のデバイスでのみサポートされており、ILMessageFilterSubActionは iOS 14 以上です。

より詳細なSubActionフィルタリングを実現したい場合は、上記の SMS データセットの label をより詳細な label に変更し、モデルをトレーニングして判断に使用します。

また、ILMessageFilterQueryRequest内ではsendermessageBodyを取得できるため、特定の電話番号に対して対応するルールを設定したい場合は、APP 内で対応するルールを設定し、Group を介して Extension に共有し、上記のメソッド内でルールをマッチングさせる必要があります。

まとめ#

上記の手順を通じて、皆さんも自分の SMS フィルタリング APP を開発できると信じています。

上記の手順は、固定のトレーニングモデルを使用してマッチングのロジックを実現するもので、手順は以下の通りです:

  1. SMS データセットを取得する
  2. CoreML を使用してデータセットをトレーニングし、モデルを生成する
  3. プロジェクト内でモデルを使用して判断する

この方法で生成されたモデルはデータが固定されており、モデルを更新するたびに再トレーニングしてインポートし、APP を更新する必要があります。より良い方法はないのでしょうか?
例えば、APP 内でトレーニングしながら更新できるのか?または、ローカルルールとローカルモデル、ネットワークモデルを組み合わせる方法はどうでしょうか?

仮に方案 1:

まず、APP 内でトレーニングしながら更新するという大まかな考え方は以下の通りです:

モデルを更新するには、データの内容とデータの分類を知る必要があります。したがって、APP 内でモデルをトレーニングするには、別の方法で分類を取得する必要があります。そうでなければ、モデルを使用して分類を取得し、その後再度モデルをトレーニングするのはあまり意味がありません。したがって、カスタムルールを使用してデータ分類を取得し、そのデータとデータ分類を使用してモデルを更新する方法は実行可能であるはずです。

仮に方案 2:

次に、より完全な方法を考えます。すなわち、ローカルルール、ローカルモデル、ネットワークモデルを組み合わせる方法です:

ロジックは、まずローカルルールでマッチングし、ローカルルールでマッチングできない場合はローカルモデルでマッチングし、ローカルモデルでもマッチングできない場合はサーバーにリクエストを送信します。サーバーには、継続的にトレーニングと更新を行うモデルがあり、対応する分類を取得します。最後に、毎回更新時にサーバーの最新のモデルをプロジェクトに更新します。

仮に方案 3:

方案 2 ではネットワークモデルが必要ですが、前提としてサーバーに継続的にトレーニングと更新を行うモデルがあると仮定しています。この仮定が存在しない場合、ローカルルールとローカルモデル、時折取得する更新データセットだけで、ローカルモデルをオンラインで更新する方法はあるのでしょうか?

現在、ローカルモデルは APP の主バンドルに直接追加されています。最初の起動時に APP と Extension の共有グループにコピーし、APP を開くたびにモデルに更新があるかどうかを確認し、更新があればこのディレクトリ内のモデルファイルをダウンロードして置き換えることを考えられます。Extension 内では、URL を介してこのディレクトリ内のモデルファイルを取得してフィルタリングを行います。

いくつかの方案のフローチャートは以下の通りです。

SMS APP フィルタリングフローチャート

まとめは以下の通りです。

SMS フィルタリング APP 開発プロセス

参考#

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。