アソシエーション分析ができるAPIをlambdaとAPI-Gatewayで作った
はじめに
今は訳合ってサービスを閉じてしまっているのですが、スマレジプラットフォームアプリでlook-into-basketsというアプリを公開していました。
取引履歴から期間を指定してアソシエーション分析結果を表示するアプリだったのですが、
- フロントとバックが密結合だったので、分割したかった
- 「アソシエーション分析」というもの自体が割と使い勝手がよく、いろんなことに応用が効きそう
- 自作ブログのタグを分析すれば、僕の興味とかスキルセットとか見えるんじゃないか、とか
という理由から、 「じゃAPIにするか」 ということで、こんなリポジトリを作成しました。
実現方法
分析系のライブラリが揃っていることから、もともとPythonを使ってresponderというFWで実装していたので、
コードを流用すべく、今回もPythonで実装したかったです。
とすると現状あるサービスだとlambdaくらいしか選択肢がなさそうだったので、AWS上に構築することにしました。
使用技術
- aws-cli
- aws-gateway
- lambda (container image)
- Python3.7
- mlxtendライブラリ
分析機能の実装
分析周りはもともとlook-into-basketsで使っていたものと、下記のqiita記事を参考に作っています。
内容は上記記事がすべて説明してくれているので、上記記事とリポジトリを見てください。
唯一今回特殊な点として、分析結果は「AならばB」という感じで得られるのですが、AもBも本来はリスト形式(厳密にはtuple)です。
が、今考えている用途では扱いにくいので、AもBも単数のもののみ抽出するようにしています。
AWS-CLI周りの実装
はじめは至って普通のlambdaを使う予定だったので、DeveloperIO様などを参考に作っていました。
が、分析系なのでライブラリが重量級で、layerを使ってもlambdaの容量上限(256Mb)に収めることができませんでした。
ダメかと諦めつつある時に、docker imageでlambdaをあげると10Gbまで容量が増えるとのことだったので、そちらで作るようapp_stackを修正しています。
他に詰まった点として、lambdaをAPIで使う場合、レスポンスの型は指定されていて、それに背くとエラーになります。
pythonの場合、下記のような感じにする必要があります。
return {
"statusCode": 200,
"body": json.dumps({...})
}
挙動確認
試しにqiita APiで、ストックが10以上の最新記事300件(2023年1月時点)でつけられているタグをアソシエーション分析してみます。
curl --location --request GET 'https://qiita.com/api/v2/items?page=3&per_page=100&query=stocks%3A%3E10'
↓ レスポンスのフィールドは指定できないので、下記のようにmapでタグのみを抽出
↓ (postmanを使って確認しているのでpmオブジェクトが見えています)
console.log(JSON.stringify(pm.response.json().map(v => v.tags.map(t => t.name))))
↓ 抽出結果
["プロジェクト","エンジニア","エンジニアリング","エンジニアの心得"],
["ShellScript","Bash","国際化","Gettext","POSIX"],
["プロジェクト管理","エンジニアリング"],
["JavaScript","チーム開発","ゲーム制作","Phaser3"],
["Windows","コマンドプロンプト"],
["フロントエンド","Next.js","t3-stack"],
["ポエム","アプリ開発","Flutter","個人開発","flutter大学"],
["Svelte","prisma","Supabase","SvelteKit"],
...,
["Xcode","Swift","SwiftUI"]
↓ 出力結果をpostmanでリクエストに乗せてAPI実行
↓ リフト値とかは結構適当だけど、それなりに絞って出力
curl --location --request POST 'https://xxxxxx.amazonaws.com/prod/yyy/' \
--header 'Content-Type: application/json' \
--data-raw '{
"data": [
["プロジェクト","エンジニア","エンジニアリング","エンジニアの心得"],
["ShellScript","Bash","国際化","Gettext","POSIX"],
...,
["Qiita","Markdown","エディタ"],
["Xcode","Swift","SwiftUI"]
],
"min_support": 0.01,
"rule": {
"metric": "confidence",
"min_threshold": 0.2
},
"condition": {
"confidence": 0.5,
"lift": 1
}
}'
↓ レスポンスにはリフト値などが含まれているけれど、ここでは一旦タグの組み合わせのみ確認するため、
console.log(JSON.stringify(pm.response.json().map(v => [v.antecedents[0], v.consequents[0]])))
を挟んで出力
↓
[
[ "TypeScript", "JavaScript" ],
[ "新人プログラマ応援", "初心者" ],
[ "AI", "ChatGPT" ],
[ "ShellScript", "POSIX" ],
[ "POSIX", "ShellScript" ],
[ "Rails", "Ruby" ],
[ "Next.js", "React" ],
[ "ShellScript", "shell" ],
[ "shell", "ShellScript" ],
[ "C#", "Unity" ],
[ "Bash", "ShellScript" ],
[ "ShellScript", "Bash" ],
[ "UNIX", "shell" ],
[ "shell", "UNIX" ],
[ "UNIX", "ShellScript" ],
[ "ShellScript", "UNIX" ],
[ "Design", "デザイン" ],
[ "Web", "フロントエンド" ],
[ "POSIX", "shell" ],
[ "shell", "POSIX" ],
[ "UNIX", "POSIX" ],
[ "POSIX", "UNIX" ],
[ "深層学習", "Python" ],
[ "転職", "初心者" ],
[ "深層学習", "初心者" ],
[ "駆け出しエンジニア", "初心者" ],
[ "転職", "未経験エンジニア" ],
[ "linebot", "Line" ],
[ "Line", "linebot" ],
[ "Bash", "POSIX" ],
[ "POSIX", "Bash" ],
[ "Jupyter", "Python" ],
[ "UNIX", "Bash" ],
[ "Bash", "UNIX" ],
[ "Bash", "shell" ],
[ "shell", "Bash" ],
[ "C", "Elixir" ],
[ "CSS", "HTML" ],
[ "VSCode", "ChatGPT" ],
[ "Git", "GitHub" ],
[ "Node.js", "JavaScript" ],
[ "日本語訳", "JavaScript" ]
]
これらの組み合わせが関連度高いらしい。
大体言語とFWのセットっぽいけど、unix系のタグが人気?なのかな。
[ "VSCode", "ChatGPT" ]
とかは流行りですよね。
終わりに
それっぽい出力ができたので、細かい調整はありますが一旦は使えそうかな。
Discussion