AWS Amplify iOSでテキストの解釈を行う #reinvent
Amplify iOSではCoreMLフレームワークをサポート!
re:Invent 2019の期間中、ネイティブアプリ用の新しいSDKAmplify iOSとAmplify Androidがプレビューにて公開されました。
【速報】モバイル向けの新しいSDK「Amplify iOS & Amplify Android」が公開されました! #reinvent
その中で、iOSでは Prediction(予測機能) について CoreMLフレームワークとの組み合わせ が実装されています。ML/AI系の機能を、AWSサービスとiOS Frameworkを組み合わせ、非常に高い精度の結果セットが得られるようになっています。
CoreMLフレームワークおよびCoreML VisionフレームワークはiOSの機械学習用のフレームワークで、学習モデルなどを扱う際に開発者が専門的な知識を必要とせずに扱えるように補助するフレームワークです。CPU、GPU、ニューラルエンジンを活用し、予測の作成、モデルのトレーニングや微調整をすべてユーザーのデバイス上で行うようになっています。
本記事では、Amplify iOSを使ってテキストの解釈を試してみました。AWSサービスとしてはAmazon Comprehendを利用する形となります。Amplify iOSを通して使うことで、ローカル(オフライン)での実行も可能になります。
インストール
それではまずはインストールしていきます。なお、AmplifyのiOSアプリ向けプロジェクトはすでに作成済みの前提で進めます。また、今回は対話形式でのインストールが必要なためAmplify CLIを利用します。
まずは predictions
というプラグインを追加します。
$ amplify add predictions
対話形式で、どのような機能を作りたいか問われます。
? Please select from one of the categories below : Interpret ? What would you like to identify? : Interpret Text ? Provide a friendly name for your resource : interpretText9b0e560f ? What kind of interpretation would you like? : All ? Who should have access? : Auth and Guest users
以下のように分類されているので、自分の作りたい機能に合わせて設定します。本記事では Interpret Text
を選択しています。
Interpret Text
を選択すると What kind of interpretation would you like?
と、どのような種類の解釈を行いたいのか問われます。今回は All
としましたが、より限定的な使い方が設定できるようです。
? What kind of interpretation would you like? Language Entity Keyphrase Sentiment Syntax ❯ All
また、最後の Who should have access?
は Auth and Guest users
を選び、Cognito User Poolsの作成を行うようにします。これはAWSリソースへのアクセスを行うため必須になります。
Successfully added resource identifyTexte454f02b locally
ローカルでの設定が完了したので amplify push
でAWSリソースを作成します。
$ amplify push | Category | Resource name | Operation | Provider plugin | | ----------- | --------------------- | --------- | ----------------- | | Predictions | interpretText9b0e560f | Create | awscloudformation | | Api | amplifyDatasource | No Change | awscloudformation | | Auth | amplifysample93d65ae8 | No Change | awscloudformation |
以上でAWSリソースの作成は完了です。
次に AWSPredictionsPlugin
というPodを追加します。Podfile
全体としては以下のようになります。
target 'AmplifySample' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for AmplifySample pod 'amplify-tools' pod 'Amplify' pod 'AWSPluginsCore' pod 'AWSPredictionsPlugin' pod 'AWSMobileClient', '~> 2.12.0' pod 'AmplifyPlugins/AWSAPIPlugin' end
最後に pod install
を実行して終わりです。
$ pod install --repo-update
実装
今回はテキストの自然言語処理を行いたいだけなので、iOSアプリでは画面なしで実装します。
まずは AppDelegate
でセットアップする処理を書きます。
import UIKit import Amplify import AWSPredictionsPlugin import AmplifyPlugins @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let apiPlugin = AWSAPIPlugin(modelRegistration: AmplifyModels()) let predictionsPlugin = AWSPredictionsPlugin() do { try Amplify.add(plugin: apiPlugin) try Amplify.add(plugin: predictionsPlugin) try Amplify.configure() print("Amplify initialized") } catch { print("Failed to configure Amplify \(error)") } return true } }
次に ViewController
で画像のテキスト解析を行う処理を実装します。interpret()
というメソッドを用意しました。文言はAmazon Comprehendの説明文を拝借しています。
import UIKit import Amplify class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() interpret() } func interpret() { let text = "Amazon Comprehend is a natural language processing (NLP) service that uses machine learning to find insights and relationships in text. No machine learning experience required." let options = PredictionsInterpretRequest.Options(defaultNetworkPolicy: .offline, pluginOptions: nil) _ = Amplify.Predictions.interpret(text: text, options: options, listener: { (event) in switch event { case .completed(let result): print(result) case .failed(let error): print(error) default: break } }) } }
試してみる
それでは実行してみましょう。コンソールログに以下のように出力されるかと思います。
InterpretResult( keyPhrases: nil, sentiment: Optional(Amplify.Sentiment(predominantSentiment: Amplify.SentimentType.negative, sentimentScores: nil)), entities: Optional([ Amplify.EntityDetectionResult(type: Amplify.EntityType.person, targetText: "Amazon Comprehend", score: nil, range: Range(Swift.String.Index(_rawBits: 1)..<Swift.String.Index(_rawBits: 1114112))) ]), language: Optional(Amplify.LanguageDetectionResult(languageCode: Amplify.LanguageType.english, score: nil)), syntax: Optional([ Amplify.SyntaxToken(tokenId: 0, text: "Amazon", range: Range(Swift.String.Index(_rawBits: 1)..<Swift.String.Index(_rawBits: 393216)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.noun, score: nil)), Amplify.SyntaxToken(tokenId: 1, text: "Comprehend", range: Range(Swift.String.Index(_rawBits: 458752)..<Swift.String.Index(_rawBits: 1114112)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.noun, score: nil)), Amplify.SyntaxToken(tokenId: 2, text: "is", range: Range(Swift.String.Index(_rawBits: 1179648)..<Swift.String.Index(_rawBits: 1310720)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.verb, score: nil)), Amplify.SyntaxToken(tokenId: 3, text: "a", range: Range(Swift.String.Index(_rawBits: 1376256)..<Swift.String.Index(_rawBits: 1441792)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.determiner, score: nil)), Amplify.SyntaxToken(tokenId: 4, text: "natural", range: Range(Swift.String.Index(_rawBits: 1507328)..<Swift.String.Index(_rawBits: 1966080)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.adjective, score: nil)), Amplify.SyntaxToken(tokenId: 5, text: "language", range: Range(Swift.String.Index(_rawBits: 2031616)..<Swift.String.Index(_rawBits: 2555904)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.noun, score: nil)), Amplify.SyntaxToken(tokenId: 6, text: "processing", range: Range(Swift.String.Index(_rawBits: 2621440)..<Swift.String.Index(_rawBits: 3276800)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.noun, score: nil)), Amplify.SyntaxToken(tokenId: 7, text: "NLP", range: Range(Swift.String.Index(_rawBits: 3407872)..<Swift.String.Index(_rawBits: 3604480)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.noun, score: nil)), Amplify.SyntaxToken(tokenId: 8, text: "service", range: Range(Swift.String.Index(_rawBits: 3735552)..<Swift.String.Index(_rawBits: 4194304)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.noun, score: nil)), Amplify.SyntaxToken(tokenId: 9, text: "that", range: Range(Swift.String.Index(_rawBits: 4259840)..<Swift.String.Index(_rawBits: 4521984)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.pronoun, score: nil)), Amplify.SyntaxToken(tokenId: 10, text: "uses", range: Range(Swift.String.Index(_rawBits: 4587520)..<Swift.String.Index(_rawBits: 4849664)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.verb, score: nil)), Amplify.SyntaxToken(tokenId: 11, text: "machine", range: Range(Swift.String.Index(_rawBits: 4915200)..<Swift.String.Index(_rawBits: 5373952)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.noun, score: nil)), Amplify.SyntaxToken(tokenId: 12, text: "learning", range: Range(Swift.String.Index(_rawBits: 5439488)..<Swift.String.Index(_rawBits: 5963776)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.verb, score: nil)), Amplify.SyntaxToken(tokenId: 13, text: "to", range: Range(Swift.String.Index(_rawBits: 6029312)..<Swift.String.Index(_rawBits: 6160384)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.particle, score: nil)), Amplify.SyntaxToken(tokenId: 14, text: "find", range: Range(Swift.String.Index(_rawBits: 6225920)..<Swift.String.Index(_rawBits: 6488064)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.verb, score: nil)), Amplify.SyntaxToken(tokenId: 15, text: "insights", range: Range(Swift.String.Index(_rawBits: 6553600)..<Swift.String.Index(_rawBits: 7077888)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.noun, score: nil)), Amplify.SyntaxToken(tokenId: 16, text: "and", range: Range(Swift.String.Index(_rawBits: 7143424)..<Swift.String.Index(_rawBits: 7340032)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.conjunction, score: nil)), Amplify.SyntaxToken(tokenId: 17, text: "relationships", range: Range(Swift.String.Index(_rawBits: 7405568)..<Swift.String.Index(_rawBits: 8257536)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.noun, score: nil)), Amplify.SyntaxToken(tokenId: 18, text: "in", range: Range(Swift.String.Index(_rawBits: 8323072)..<Swift.String.Index(_rawBits: 8454144)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.preposition, score: nil)), Amplify.SyntaxToken(tokenId: 19, text: "text", range: Range(Swift.String.Index(_rawBits: 8519680)..<Swift.String.Index(_rawBits: 8781824)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.noun, score: nil)), Amplify.SyntaxToken(tokenId: 20, text: "No", range: Range(Swift.String.Index(_rawBits: 8912896)..<Swift.String.Index(_rawBits: 9043968)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.determiner, score: nil)), Amplify.SyntaxToken(tokenId: 21, text: "machine", range: Range(Swift.String.Index(_rawBits: 9109504)..<Swift.String.Index(_rawBits: 9568256)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.noun, score: nil)), Amplify.SyntaxToken(tokenId: 22, text: "learning", range: Range(Swift.String.Index(_rawBits: 9633792)..<Swift.String.Index(_rawBits: 10158080)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.verb, score: nil)), Amplify.SyntaxToken(tokenId: 23, text: "experience", range: Range(Swift.String.Index(_rawBits: 10223616)..<Swift.String.Index(_rawBits: 10878976)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.noun, score: nil)), Amplify.SyntaxToken(tokenId: 24, text: "required", range: Range(Swift.String.Index(_rawBits: 10944512)..<Swift.String.Index(_rawBits: 11468800)), partOfSpeech: Amplify.PartOfSpeech(tag: Amplify.SpeechType.verb, score: nil)) ]))
結果は InterpretResult 型で返されます。それぞれ次のような意味があります。
要素 | 説明 |
---|---|
keyPhrases |
テキスト内の重要なフレーズ |
sentiment |
感情の解析結果 |
entities |
文章の属するエンティティタイプと信頼度 |
language |
テキストの主要言語の解析結果 |
syntax |
一語一語の解析情報 |
language
を見てみると Amplify.LanguageType.english
と日本語で解釈されていることが分かります。
明示的にオフラインで使いたい場合は、実行する際のオプション設定を変更します。
let options = PredictionsInterpretRequest.Options(defaultNetworkPolicy: .offline, pluginOptions: nil) _ = Amplify.Predictions.interpret(text: text, options: options, listener: { (event) in switch event { case .completed(let result): print(result) case .failed(let error): print(error) default: break } })
iOSのフレームワークと統合されたAmplify
CoreML Vision FrameworkとAWSのML/AI系サービスの統合によって、かなりパワフルに働くようになりました。
モバイルアプリの場合は常に安定した通信環境が得られるとは限りませんので、本記事でご紹介したような機能を使うアプリの場合は非常に有効な手段だと思います。
Predictionsに含まれる機能は他にもあるので、試していきたいと思います。