この記事は GMOアドマーケティング Advent Calendar 2023 18日目の記事です。
はじめに
こんにちは、GMOアドマーケティング GMOSSP開発担当の@KazuakiMです。
GMOSSPではAutoML Visionで広告画像の解析を行い、解析結果を広告審査に利用しています。
そしてAutoML Visionを利用している方はGoogleから下記メールが度々送られているため、ご存知だと思いますが 2024/01/23 にAutoML Visionの役目を終えます。
1 2 3 |
Your AutoML Vision Services will remain available through January 23, 2024, unless you discontinue your usage of AutoML Vision, or migrate to Vertex AI before January 23, 2024. |
私達は2022年からこの事実を把握しながら、ちゃんと温めてきました。
ついに年末という事で、流石に対応しないとまずい状況です。
VertexAIへの移行ページで余裕かなと信じていましたが、
現実はそんなに甘くなく、移行できないので1からモデル作って、利用しているバッチに埋め込む必要がありました。では早速VertexAI環境を構築していきます。VertexAIは以下の順番で生成していきます。
- データセットの作成
- モデルの作成
- エンドポイントの作成
まずはデータセットを作成していきます。
データセットの作成
VertexAIのダッシュボードから、データセットを作成していきます。
今回は画像解析を行う目的のため、画像タブでシンプルな単一ラベル解析で画像を解析していきます。
次にデータセットに画像情報をインポートしていきます。GCS経由でインポートするのが容易に大量学習するのに適しているような気がします。
CSVファイルをアップロードするのですが、内容は下記のような感じとなります。
1 2 3 4 |
gs://bucket/file/to/path/AAAAA.jpg,WeightLoss gs://bucket/file/to/path/BBBBB.jpg,WeightLoss gs://bucket/file/to/path/CCCCC.jpg,OK gs://bucket/file/to/path/DDDDD.jpg,OK |
1フィールド目に画像の保存されているgsutil URI
2フィールド目にラベル情報
という構成となります。
続行ボタンを押すとインポート処理が行われます。
次にモデルを作成をしていきます。
モデルの作成
データセット同様にVertexAIのダッシュボードから、モデルを作成していきます。
基本的にはデフォルトで大丈夫かと思います。
モデルの詳細
トレーニングオプション
コンピューティングと料金
費用にかかわるので利用用途に応じて調整します。
トレーニングが終わりましたら、次にエンドポイントを作成していきます。
エンドポイントの作成
データセット、モデル同様にVertexAIのダッシュボードから、エンドポイントを作成していきます。
作成ボタンを押し、
モデル設定を行います。
今度はエンドポイントが作られるまで待ちます。
アプリケーション開発
エンドポイントが出来ましたら、動作確認をしていきます。
GMOSSPではGo言語でバッチを作成しているため、ここではGo言語での動作確認例となります。
まずはエンドポイントが出来ているか確認します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// @see https://pkg.go.dev/cloud.google.com/go/aiplatform/apiv1#EndpointClient.GetEndpoint func getEndpoint(config config.GcpConfig, ctx context.Context, client *aiplatform.EndpointClient) (*aiplatformpb.Endpoint, error) { // @see https://pkg.go.dev/cloud.google.com/go/aiplatform/apiv1/aiplatformpb#GetEndpointRequest return client.GetEndpoint(ctx, &aiplatformpb.GetEndpointRequest{ Name: getEndpoint(config), }) } func getEndpoint(config config.GcpConfig) string { return fmt.Sprintf("projects/%s/locations/%s/endpoints/%s", config.ProjectID, config.VertexAI.Location, config.VertexAI.EndpointID) } /* 実行結果 name:"projects/<Project ID>/locations/<Location>/endpoints/<Endpoint ID>" display_name:"Sample" etag:"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" create_time:{seconds:1700534414 nanos:247907000} update_time:{seconds:1701149260 nanos:780809000} */ |
続いてモデルをデプロイし、その後エンドポイントの状況を確認します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
// @see https://pkg.go.dev/cloud.google.com/go/aiplatform/apiv1#EndpointClient.DeployModel func deployModel(config config.GcpConfig, ctx context.Context, client *aiplatform.EndpointClient) error { // @see https://pkg.go.dev/cloud.google.com/go/aiplatform/apiv1/aiplatformpb#DeployModelRequest op, err := client.DeployModel(ctx, &aiplatformpb.DeployModelRequest{ Endpoint: getEndpoint(config), // @see https://pkg.go.dev/cloud.google.com/go/aiplatform/apiv1/aiplatformpb#DeployedModel DeployedModel: &aiplatformpb.DeployedModel{ // @see https://pkg.go.dev/cloud.google.com/go/aiplatform/apiv1/aiplatformpb#DeployedModel_AutomaticResources PredictionResources: &aiplatformpb.DeployedModel_AutomaticResources{ // @see https://pkg.go.dev/cloud.google.com/go/aiplatform/apiv1/aiplatformpb#AutomaticResources AutomaticResources: &aiplatformpb.AutomaticResources{ MinReplicaCount: 1, MaxReplicaCount: 1, }, }, Model: getModelName(config), DisplayName: "Sample", }, }) if err != nil { return err } if _, err := op.Wait(ctx); err != nil { return err } //MEMO: //非常に遺憾ではありますが、deployの進捗状況を監視するAPIがなさそうで、 //いろいろ検証しましたが、とりあえず1分待たせる事で安定しました。 time.Sleep(time.Second * 60) return nil } func getModelName(config config.GcpConfig) string { return fmt.Sprintf("projects/%s/locations/%s/models/%s", config.ProjectID, config.VertexAI.Location, config.VertexAI.ModelID) } /* 実行結果 name:"projects/<Project ID>/locations/<Location>/endpoints/<Endpoint ID>" display_name:"Sample" deployed_models:{automatic_resources:{min_replica_count:1 max_replica_count:1} id:"<DeployedModel ID>" model:"projects/<Project ID>/locations/<Location>/models/<Model ID>" model_version_id:"1" display_name:"Sample" create_time:{seconds:1701336301 nanos:986347000} disable_container_logging:true} traffic_split:{key:"<key>" value:100} etag:"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" create_time:{seconds:1700534414 nanos:247907000} update_time:{seconds:1701336518 nanos:992597000} // Deployされているモデル情報が追加されています。 // 画面から確認するとエンドポイントにモデルがデプロイされています。 */ |
モデルがデプロイできているため、実際に画像解析を行ってみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
// aiplatformpb.PredictRequestでの実装を試みましたが、よく分からず、 // Endpointの詳細画面の上部"リクエストの例"にあるcurlによる解析リクエストです。 type ( //ImageBody ImageBodyJsonStruct struct { Instances []InstanceStruct `json:"instances"` Parameters ParameterStruct `json:"parameters"` } InstanceStruct struct { Content string `json:"content"` } ParameterStruct struct { ConfidenceThreshold float64 `json:"confidenceThreshold"` MaxPredictions int `json:"maxPredictions"` } //PredictResponse PredictResponseStruct struct { Url string Predictions []PredictionStruct `json:"predictions"` DeployedModelId string `json:"deployedModelId"` Model string `json:"model"` ModelDisplayName string `json:"modelDisplayName"` ModelVersionId string `json:"modelVersionId"` } PredictionStruct struct { Ids []string `json:"ids"` Confidences []float64 `json:"confidences"` DisplayNames []string `json:"displayNames"` } ) func requestPredict(config config.GcpConfig, credential, url string) (PredictResponseStruct, error) { //画像情報をBase64エンコードした文字列でVertexAIにリクエストするため、結局なんの画像を解析したのか分からなくなるため、構造体に格納しておきます。 predictResp := PredictResponseStruct{ Url: url, } resp, err := http.Get(url) if err != nil { return predictResp, err } defer resp.Body.Close() imageBytes, err := io.ReadAll(resp.Body) if err != nil { return predictResp, err } //MEMO: // 構造体([]InstanceStruct)的には複数画像をサポートしているが、2023/11/22現時点では // 複数画像の解析リクエストをするとエラーとなる bodyJson, err := json.Marshal(&ImageBodyJsonStruct{ Instances: []InstanceStruct{ { Content: base64.StdEncoding.EncodeToString(imageBytes), }, }, Parameters: ParameterStruct{ ConfidenceThreshold: 0.5, MaxPredictions: 5, }, }) if err != nil { return predictResp, err } req, err := http.NewRequest(http.MethodPost, getPredictEndpoint(config), bytes.NewReader(bodyJson)) if err != nil { return predictResp, err } req.Header.Set("Authorization", "Bearer "+credential) req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { return predictResp, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return predictResp, errors.New("http statuscode error: " + fmt.Sprint(res.StatusCode)) } predictRespJson, err := io.ReadAll(res.Body) if err != nil { return predictResp, err } if err = json.Unmarshal(predictRespJson, &predictResp); err != nil { return predictResp, err } return predictResp, nil } func getPredictEndpoint(config config.GcpConfig) string { return fmt.Sprintf("https://<Location>-aiplatform.googleapis.com:443/v1/projects/%s/locations/%s/endpoints/%s:predict", config.VertexAI.Location, config.ProjectNumber, config.VertexAI.Location, config.VertexAI.EndpointID) } /* 実行結果 {"URL" [{[<Predict ID>] [<Predict Confidence>] [<Predict DisplayName>]}] <DeployedModel ID> projects/<Project ID>/locations/us-central1/models/<Model ID> Sample 1} */ |
解析した結果が取得でき、妥当性的にも問題ない事が確認できます。
解析が無事行えたので、最後にデプロイしたモデルをアンデプロイします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// @see https://pkg.go.dev/cloud.google.com/go/aiplatform/apiv1#EndpointClient.UndeployModel func undeployModel(config config.GcpConfig, ctx context.Context, client *aiplatform.EndpointClient, deployedModelId string) (*aiplatformpb.UndeployModelResponse, error) { // @see https://pkg.go.dev/cloud.google.com/go/aiplatform/apiv1/aiplatformpb#UndeployModelRequest op, err := client.UndeployModel(ctx, &aiplatformpb.UndeployModelRequest{ Endpoint: getEndpoint(config), DeployedModelId: deployedModelId, }) if err != nil { return nil, err } return op.Wait(ctx) } /* 実行結果 DeployedModel IDはGetEndpointで取得できるので、 その情報を通知しています。画面から確認してもエンドポイントからモデルが消えている事が確認できると思います。 */ |
以上で、VertexAIへ移行する上で最低限必要なライブラリが揃ったと思います。
実運用する上でまだ足らないケースがあるかもしれませんが、期日が迫っているので、本日はここまでとします。
告知
明日はKONCEさんによる「AI搭載ターミナルwarpを使おう」です。
引き続き、GMOアドマーケティング Advent Calendar 2023 をお楽しみください!
■採用ページはこちら!
https://recruit.gmo-ap.jp/
■GMOアドパートナーズ 公式noteはこちら!
https://note.gmo-ap.jp/