Cloud Natural Language AutoMLを使って文章年齢判定アプリを作ろう!
クソアプリアドベントカレンダー2021の企画として、Cloud Natural Language AutoML
で「文章から書いた人の年齢を推測する」ネタアプリを開発してみたのですが、作ってから意外と AutoML
の価格が高いことに気がついてしまいました。
ということでアプリとしての公開は取りやめましたが、それだと引っ込みがつきません。そこで、サンプルソースコードの公開と、作り方を説明するLPを公開しました。
AutoMLで文章年齢を学習させる方法
AutoMLでテキストを分類する問題を解決するモデルを作るのが意外と簡単でした。指定の形式のCSVを作ってアップロードし、GUI上からポチポチするだけだったのです。その方法を順に説明していきます。
データの用意
データを用意します。以下のドキュメントをもとに、SpreadsheetなどでCSVを作ります。
https://cloud.google.com/natural-language/automl/docs/prepare#csv
CSVファイルには最低でも3つのカラムが必要です。左端のカラムは TRAIN/VALIDATION/TEST
のいずれかを入れますが、空でもOKです。
2つ目のカラムには分類したいテキストを入れます。本アプリケーションの場合、口コミサイトなどから年齢が特定できる投稿者のテキストを集めてきたり、Twitter上から年齢を知ることができる有名人のつぶやきを集めてきました。
用意するデータの個数は、分類したい区分ごとに最低10です。年齢を1の位まで限定して集めると、それらの分類×10の文章が必要で大変なので、20代、30代、40代といった年代ごとに区切ってテキストを集めました。以下は1行のサンプルです。
"","Cloud Natural Language AutoMLを使って文章年齢判定アプリを作ったけど、費用が思いのほか高かったです!",20
こういった行を、10代、20代、30代、40代、50代の4つの年代に対して、それぞれ最低10個ずつ合計50個強用意しました。
補足
一応補足ですが、現実問題文章だけで年齢を予測させるモデルを作るのはかなり大変だと思います。僕が集めた文章は長くても数百文字程度のものばかりでしたが、文字数がもう少し多いほうが良いかもしれないし、コピペ元をもっと年代らしさが出るところ(Tiktokコメントとか?しかしそれらは年齢を特定できない)にしなければ差異が出ないと思います。本記事の内容はAutoMLを実際に触ってみる題材として考えてください。
GCPコンソールでモデルを作成
GCPのコンソールからAutoML APIを有効にします。

有効にしたあと、コンソールからAutoMLテキストおよびドキュメント分類、に入ります。

CSVをアップロードします。

アップロードが終わると、以下のようにデータの分布が見れます。

トレーニングタブから、トレーニングを開始します。以下のスクショはすでに一度トレーニングしているので、その結果のスコアも表示されています。

トレーニングには3、4時間ほどかかりました。データ量は少ないですが、少ないだけに一定以上の精度に至るまでの学習期間が長くなってしまったのかもしれません。
最後にテストと使用というタブから、モデルをデプロイすると利用可能になります。簡単ですね。

ソースコードから予測APIを叩く
学習が終わったモデルに対して、実際に任意の文字列を与えて予測させるタスクの実行も簡単にできます。./pages/api/predict.ts
を開いてください。
import {PredictData} from "../../types/predict";
const projectId = process.env.GOOGLE_PROJECT_ID;
// @ts-ignore
const location = process.env.GOOGLE_PROJECT_LOCATION;
const modelId = process.env.GOOGLE_AUTOML_MODEL_ID;
const credentials = JSON.parse(
Buffer.from(process.env.GOOGLE_APPLICATION_CREDENTIALS as string, 'base64').toString()
);
// Imports the Google Cloud AutoML library
const {PredictionServiceClient} = require('@google-cloud/automl').v1;
// Instantiates a client
const client = new PredictionServiceClient({
projectId,
credentials
});
async function predict(content: string): Promise<PredictData> {
// Construct request
const request = {
name: client.modelPath(projectId, location, modelId),
payload: {
textSnippet: {
content: content,
mimeType: 'text/plain', // Types: 'text/plain', 'text/html'
},
},
};
const [response] = await client.predict(request);
const returnVal: PredictData = {
age: 0
}
for (const annotationPayload of response.payload) {
console.log(`Predicted class name: ${annotationPayload.displayName}`);
console.log(
`Predicted class score: ${annotationPayload.classification.score}`
);
returnVal.age += parseInt(annotationPayload.displayName) * annotationPayload.classification.score
}
returnVal.age = Math.floor(returnVal.age)
return returnVal
}
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<PredictData>
) {
const response = await predict(req.body.content)
res.status(200).json(response)
}
分類タスクですがたった1つの年齢をモデルは返してくるのではなく、事前に用意した各年代ごとに何%の確率か、を返します。なので各年代の確率を合計すると100%になるわけです。そのため、年代ごとの確率とその年代を合算するだけで、もっともそれらしい年代を特定することにしました。というのも、思ったより40代の可能性が80%!ほかは数%!みたいに極端にわかれず、割と分散するような結果ばかり得られてしまったので、足して割ってみるロジックを実装したわけです。
ただ本当は最も高い確率を示した年代を文章年齢として返したほうが良いと思うので、その場合は最もannotationPayload.classification.score
が高い年代を返して「あなたの文章は40代!」みたいにUI上で表示するのが良いと思います。
最後にフロントからAPIを叩けばOKです。
const getPredict: (content: string) => Promise<PredictData> = async (content) => {
const response = await axios.post<PredictData>('/api/predict', {
content
})
return response.data
}
所感
思ったより費用が高かった点ももちろんですが、実際にそれらしい予測を返すAIを作ろうと思うと、適したデータの用意や最終的にUIにどういうように結果をフィードバックするかといったところまで組み上げないといけないので、難しい点が多かったなと感じました。APIの費用さえお買い得だったら、Twitter連携して最新のツイートから年齢を予想するとかやりたかったのですが。自前でSVMなどを書いてデプロイして格安運用できるスキルが身についたら再チャレンジしてみたいです。