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 バックアップのデータベースファイルです。さて、どうやって見つけるのでしょうか?バックアップディレクトリのフォルダを一つ一つ見ていると混乱するかもしれませんが、簡単です。検索を使い、右上の検索ボックスにこのファイル名を入力すればいいのです。注意点は、検索範囲が現在のフォルダであることです。
検索結果は以下の通りです。
このファイルを別の場所、例えばデスクトップにコピーし、SQLPro for SQLLite
などのデータベースソフトウェアで開きます。
このファイルを観察すると、電話番号と 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 がすべてフィルタリングされているのがわかります。
すべての行を選択し、右クリックしてExport result set as
を選択し、CSV
形式でエクスポートします。
これで必要な SMS サンプルを取得できました。
ゴミ SMS のトレーニング識別#
サンプルができたら、次にどうやってトレーニングして識別するかを考えます。Apple の CoreML を使用して識別するつもりですが、どうやって使うのでしょうか?サンプルのフォーマットの要件はどのようなものでしょうか?トレーニングにはどれくらいの時間がかかるのでしょうか?
まず、テキストトレーニングのCoreML
プロジェクトを作成します。Xcode を選択し、Open Developer Tool
をクリックしてCoreML
を開きます。以下の図のようにします。
次にフォルダを選択し、新しいNew Document
をクリックします。
次にText Classification
を選択します。
続いてプロジェクトの名前と説明を入力します。
右下の作成ボタンをクリックして、メイン画面に入ります。
Training Data
の詳細説明をクリックすると、CoreML
が要求するテキスト認識のフォーマットが表示されます。JSON
とCSV
ファイルがサポートされており、フォーマットは以下の通りです。
JSON フォーマットは以下の通りです。
// JSONファイル
[
{
"text": "映画は素晴らしかった!",
"label": "positive"
}, {
"text": "非常に退屈。眠ってしまった。",
"label": "negative"
}, {
"text": "まあまあでした。",
"label": "neutral"
} ...
]
CSV フォーマットは、1 列がtext
、もう 1 列がlabel
です。
text | label |
---|---|
これは普通の SMS です | label1 |
これはゴミ SMS です | label2 |
前のステップで SMS を CSV 形式にエクスポートしたので、ここでは上の図のフォーマットに変更するだけです。残る問題は、label にはどのような値があるかということです。
label の値を確認するためには、システムの SMS フィルタリングロジックを確認する必要があります。サポートされているフィルタリングカテゴリは何か?そうでなければ、自分が実現したい分類が、最後にシステムでサポートされていないことがわかると困ります。
SMS フィルタリングカテゴリ#
システムの SMS フィルタリングロジック#
SMS と MMS メッセージフィルタリングを参考にすると、開発者は新しいグループを作成する権限がなく、未知の連絡先からのSMS
またはMMS
に対して、指定されたカテゴリにフィルタリングすることしかできません。
ここで注意が必要なのは、文書によれば、SMS フィルタリングは iMessage や連絡先の SMS のフィルタリングをサポートしておらず、未知の連絡先からのSMS
とMMS
のみをサポートしているということです。
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
をクリックします。
トレーニングが完了したら、Preview をクリックして SMS テキストをシミュレートし、出力の予測を確認します。以下の図のように。
最後に、モデルをエクスポートして APP で使用します。
APP 開発#
新しいプロジェクトを作成し、new bing 生成画像を使用して APPIcon をデザインし、ChatGPT-4 を使って APP 名を生成します。そして、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
内ではsender
とmessageBody
を取得できるため、特定の電話番号に対して対応するルールを設定したい場合は、APP 内で対応するルールを設定し、Group を介して Extension に共有し、上記のメソッド内でルールをマッチングさせる必要があります。
まとめ#
上記の手順を通じて、皆さんも自分の SMS フィルタリング APP を開発できると信じています。
上記の手順は、固定のトレーニングモデルを使用してマッチングのロジックを実現するもので、手順は以下の通りです:
- SMS データセットを取得する
- CoreML を使用してデータセットをトレーニングし、モデルを生成する
- プロジェクト内でモデルを使用して判断する
この方法で生成されたモデルはデータが固定されており、モデルを更新するたびに再トレーニングしてインポートし、APP を更新する必要があります。より良い方法はないのでしょうか?
例えば、APP 内でトレーニングしながら更新できるのか?または、ローカルルールとローカルモデル、ネットワークモデルを組み合わせる方法はどうでしょうか?
仮に方案 1:
まず、APP 内でトレーニングしながら更新するという大まかな考え方は以下の通りです:
モデルを更新するには、データの内容とデータの分類を知る必要があります。したがって、APP 内でモデルをトレーニングするには、別の方法で分類を取得する必要があります。そうでなければ、モデルを使用して分類を取得し、その後再度モデルをトレーニングするのはあまり意味がありません。したがって、カスタムルールを使用してデータ分類を取得し、そのデータとデータ分類を使用してモデルを更新する方法は実行可能であるはずです。
仮に方案 2:
次に、より完全な方法を考えます。すなわち、ローカルルール、ローカルモデル、ネットワークモデルを組み合わせる方法です:
ロジックは、まずローカルルールでマッチングし、ローカルルールでマッチングできない場合はローカルモデルでマッチングし、ローカルモデルでもマッチングできない場合はサーバーにリクエストを送信します。サーバーには、継続的にトレーニングと更新を行うモデルがあり、対応する分類を取得します。最後に、毎回更新時にサーバーの最新のモデルをプロジェクトに更新します。
仮に方案 3:
方案 2 ではネットワークモデルが必要ですが、前提としてサーバーに継続的にトレーニングと更新を行うモデルがあると仮定しています。この仮定が存在しない場合、ローカルルールとローカルモデル、時折取得する更新データセットだけで、ローカルモデルをオンラインで更新する方法はあるのでしょうか?
現在、ローカルモデルは APP の主バンドルに直接追加されています。最初の起動時に APP と Extension の共有グループにコピーし、APP を開くたびにモデルに更新があるかどうかを確認し、更新があればこのディレクトリ内のモデルファイルをダウンロードして置き換えることを考えられます。Extension 内では、URL を介してこのディレクトリ内のモデルファイルを取得してフィルタリングを行います。
いくつかの方案のフローチャートは以下の通りです。
まとめは以下の通りです。