
Gemini Enterprise Agent Platform의 Experiments를 시도해 보았다
요약
Gemini Enterprise Agent Platform의 Experiments 기능을 활용하여 scikit-learn 모델의 학습 과정을 관리하는 방법을 다룹니다. SDK의 log_model을 사용하여 모델 직렬화, Cloud Storage 저장, ExperimentModel 연관 과정을 자동화하는 워크플로우를 소개합니다.
핵심 포인트
- log_model을 통해 모델 저장과 Experiment 연관을 자동화 가능
- ExperimentModel은 ML Metadata 상의 Artifact로 관리됨
- Cloud Build, Artifact Registry, Cloud Storage를 연동한 ML 파이프라인 구성
- 실험 조건(Parameter), 평가 지표(Metric), 모델(Artifact)의 체계적 추적
서론
Fusic의 레오나입니다.
이번에는 Gemini Enterprise Agent Platform Experiments를 사용하여, Custom Training으로 학습한 scikit-learn 모델을 비교했습니다. Gemini Enterprise Agent Platform은 기존 Vertex AI Platform에서 명칭이 변경된 서비스입니다. [1]
지난 블로그에서는 BigQuery의 공개 데이터를 사용하여 2건의 Custom Job을 실행하고, 학습 코드로부터 model.joblib를 Cloud Storage로 직접 저장했습니다.
이번에는 동일한 이진 분류(Binary Classification)의 연장선상에서, 모델 파일을 학습 코드에서 직접 업로드하는 대신 Agent Platform SDK for Python의 log_model을 사용하여 ExperimentModel을 생성하고 각 Run에 연관시킵니다.
ExperimentModel이란
Experiments에서는 동일한 목적을 가진 시도를 Experiment로 묶고, 개별 시도를 Run으로 기록합니다. Run에는 학습 조건을 Parameter, 평가값을 Metric, 모델을 Artifact로 연관시킬 수 있습니다. [2] 이번에 사용하는 log_model은 다음 두 가지 처리를 한꺼번에 수행합니다. [3]
- scikit-learn 모델을 직렬화(Serialize)하여 Cloud Storage에 저장
- 저장 위치 URI와 모델 정보를 가진 ExperimentModel을 현재의 Run에 연관
비슷한 이름의 저장소가 여러 개 있지만, 각각의 역할이 다릅니다.
| 저장소 | 이번에 저장하는 것 |
|---|---|
| Artifact Registry | Custom Training에서 사용하는 컨테이너 이미지 |
| Cloud Storage | model.pkl 및 입력 예시인 instance.yaml |
| ML Metadata | Cloud Storage의 URI 및 모델 정보를 가진 ExperimentModel |
| Model Registry | 이번에는 등록하지 않음 |
지난 블로그에서 구현했던 joblib.dump와 Cloud Storage로의 업로드 처리를 이번에는 log_model로 대체합니다. 모델 본체의 저장 위치는 Cloud Storage로 유지되지만, ExperimentModel을 Run에 연관시킴으로써 평가 결과로부터 대응하는 모델 파일을 추적할 수 있게 됩니다.
참고로, ExperimentModel은 ML Metadata 상의 Artifact입니다. Model Registry에 등록되는 모델과는 다릅니다.
구성
이번 구성에서는 Cloud Build로 학습용 컨테이너 이미지를 생성하여 Artifact Registry에 Push하고, Custom Job으로 CSV를 읽어 들여 모델을 학습합니다. 학습된 모델은 Cloud Storage에 저장하며, 학습 조건, 평가 지표, 실행 이력은 Experiments에 기록합니다. 또한, 학습 코드의 표준 출력(Standard Output)은 Cloud Logging으로 전송합니다.

본 블로그의 아키텍처
검증 방법
검증 조건은 지난 기사와 동일합니다.
| 항목 | 값 |
|---|---|
| 리전 (Region) | us-central1 |
| ... |
원 데이터 32,561행과 학습용 및 평가용 데이터의 합계 사이의 차이는 3,142행입니다. 이 3,142행은 이번에 게재하는 평가값 산출 대상에는 포함하지 않았습니다.
2건의 Custom Job에는 동일한 입력 CSV, 이미지 다이제스트(Image Digest), 머신 유형, solver, max_iter, random_state를 지정했습니다. 변경한 것은 class_weight뿐입니다.
| 항목 | Run A | Run B |
|---|---|---|
class_weight | None | balanced |
solver | liblinear | liblinear |
max_iter | 1,000 | 1,000 |
random_state | 42 | 42 |
class_weight="balanced"를 사용하면 학습 데이터 내의 클래스 빈도에 반비례하는 가중치가 자동으로 계산됩니다. [4]
이번 양성 클래스(Positive Class)인 >50K
를 놓치지 않을 가능성이 높아지는 반면, 오탐(False Positive)이 증가하여 정밀도(Precision)가 저하될 가능성도 있습니다. 따라서 재현율(Recall)만으로는 채택 여부를 판단하지 않습니다.
채택 조건은 실행 전에 다음과 같이 정했습니다.
| 조건 | 판정 |
|---|---|
| Recall | Run A보다 높음 |
| ... |
구현
Step 1: Terraform으로 권한 추가하기
이전 블로그에서 사용했던 학습용 서비스 계정(Service Account)에는 Cloud Storage에 모델을 저장할 권한은 부여했지만, Experiments와 ML Metadata에 기록하기 위한 권한은 부여하지 않았습니다.
그래서 이번에는 학습용 서비스 계정에 roles/aiplatform.user를 추가했습니다.
resource "google_project_iam_member" "training_experiment_user" {
project = var.project_id
role = "roles/aiplatform.user"
...
Step 2: 학습 조건을 Custom Job으로 전달하기
학습용 컨테이너 이미지(Container Image)를 조건마다 따로 만들면, 비교 대상이 아닌 코드나 의존성(Dependency)의 차이가 개입될 가능성이 있습니다. 그래서 2개의 Custom Job에서 동일한 이미지 다이제스트(Image Digest)를 사용하고, CLASS_WEIGHT만 환경 변수로 전환했습니다.
sha256:45c76a86eee9bfef49988f8f5beac0d26922d542a9d1355e936a799968eea23e
다음은 Run B의 Custom Job에 전달한 주요 환경 변수입니다.
env:
- name: "EXPERIMENT_NAME"
value: "gcp5-census-income"
...
학습 코드에서도 Cloud Storage 상의 CSV로부터 SHA-256을 계산하여, 환경 변수에 지정한 값과 일치하지 않으면 학습을 중단합니다. Run에 동일한 해시(Hash) 값을 기록할 뿐만 아니라, 실제로 읽어들인 파일이 예상한 파일과 일치하는지도 확인했습니다.
Step 3: Run을 생성하거나 재개하기
새로운 Run을 생성하는 경우에는 resume=False를, 기존 Run을 재개하는 경우에는 resume=True를 지정합니다. resume=True를 지정했을 때 대상 Run이 존재하지 않는 경우에는 NOT_FOUND가 발생했습니다. 공식 문서에서도 resume=True는 이전에 시작한 Run을 재개하기 위한 지정으로 설명되어 있습니다.[5] Custom Job 재시도 시에도 동일한 Run 이름을 사용할 수 있도록, 먼저 기존 Run의 재개를 시도하고 존재하지 않을 때만 새로운 Run을 생성합니다.
from google.api_core.exceptions import NotFound
try:
experiment_run = aiplatform.start_run(
...
Step 4: Parameter와 Metric 준비하기
Run에 기록할 비교 조건과 평가치를 준비합니다. 이번에는 학습 종료 시점의 값을 비교하기 위해, 평가치는 요약 지표(Summary Metric)로 기록합니다.[6]
params = {
"class_weight": raw_class_weight,
"solver": "liblinear",
...
Parameter, Metric, ExperimentModel은 다음 Step 5에서 동일한 Run의 컨텍스트(Context) 내에 모아서 기록합니다.
Step 5: ExperimentModel을 Run에 연관시키기
이전 블로그에서는 학습 코드 내에서 joblib.dump를 사용하여 model.joblib을 생성하고 Cloud Storage로 직접 업로드했습니다. 이번에는 해당 처리를 삭제하고 대신 log_model을 호출합니다.
start_run을 컨텍스트 매니저(Context Manager)로 사용했을 경우, with 블록을 벗어나면 Run이 종료됩니다. 따라서 log_params, log_metrics, log_model은 동일한 with experiment_run 블록 내에서 실행합니다.
with experiment_run:
aiplatform.log_params(params)
aiplatform.log_metrics(summary_metrics)
...
Agent Platform SDK for Python 1.161.0에서는 최상위 레벨의 aiplatform.log_model()의 반환값이 None이 되었습니다. 따라서 본 검증에서는 log_model의 반환값을 ExperimentModel로 사용하지 않고, 저장 후 Artifact ID를 지정하여 get_experiment_model()로 재취득하고 있습니다. 이 동작은 SDK 버전에 따라 달라지므로, SDK를 업데이트한 경우에는 반환값의 사양을 다시 확인해야 합니다. 또한, ExperimentModel이 존재하는 것과 현재 Run에 연관되어 있는 것은 별개로 확인해야 합니다. 첫 번째 실행이 ExperimentModel 생성 후 Run에 대한 연관 설정이 완료되기 전에 중단된 경우, ExperimentModel만 남을 가능성이 있습니다. 따라서 처리 마지막에 get_experiment_models()를 사용하여 대상 ExperimentModel이 현재 Run에 연관되어 있는지도 확인하고 있습니다.
ExperimentModel만 남은 부분적 실패를 검출한 경우에는 기존 Artifact를 정리한 후 재실행하거나, 새로운 Artifact ID를 지정하여 재생성합니다. 추가로, input_example을 저장하기 위해서는 PyYAML이 필요했습니다. 초기 컨테이너 이미지에는 PyYAML을 포함하지 않았기 때문에 다음과 같은 에러와 함께 실패했습니다.
ImportError: PyYAML is not installed and is required for saving input examples.
그 후, 학습용 컨테이너 이미지의 의존성에 PyYAML==6.0.3을 추가했습니다.
검증 결과
Custom Job과 Run
최종적으로 실행한 2건의 Custom Job은 모두 JOB_STATE_SUCCEEDED로 완료되었습니다. Experiments 측의 Run도 COMPLETE 상태입니다.
| 항목 | Run A | Run B |
|---|---|---|
| Run 이름 | gcp5-none-20260720t042949z-r4 | gcp5-balanced-20260720t042949z-r4 |
| Custom Job ID | 4192023879171964928 | 7560153450491674624 |
| Job 상태 | JOB_STATE_SUCCEEDED | JOB_STATE_SUCCEEDED |
| Run 상태 | COMPLETE | COMPLETE |
| Job 투입부터 완료까지 | 165.871초 | 140.020초 |
| 학습 코드 | 3.310초 | 3.342초 |
Cloud Logging에서는 다음 5가지 로그 이벤트를 두 Custom Job 모두에서 순서대로 확인할 수 있었습니다.
start
data_loaded
evaluation
experiment_model
completed
평가값
평가값은 다음과 같습니다.
| 지표 | Run A | Run B | Run B와 Run A의 차이 |
|---|---|---|---|
| Accuracy | 0.840670 | 0.789213 | -0.051457 |
| ... |
차이값은 표에 표시하기 전 반올림하지 않은 평가값으로부터 산출하였으며, 소수점 6자리까지 표시했습니다. Run B에서는 Recall이 0.563859에서 0.841033으로 향상되었습니다. F1도 0.617560에서 0.645464로 향상되었으며, Precision은 사전에 정한 0.5 이상을 유지하고 있습니다. 따라서 Run B는 다음 채택 조건을 모두 충족했습니다.
| 조건 | 결과 | 판정 |
|---|---|---|
| Recall | Run A보다 높음 | 달성 |
| ... | 달성 | |
| ExperimentModel | Run에 1건 연관됨 | 달성 |
한편, Precision (정밀도)은 0.682566에서 0.523689로, Accuracy (정확도)는 0.840670에서 0.789213으로 저하되었습니다. ROC-AUC는 거의 동일합니다. 따라서 Run B가 모든 평가 지표에서 Run A보다 우수하다는 의미는 아닙니다. Run B는 오검출(False Positive)이 늘어나는 대신 양성 클래스(Positive Class)를 놓치는 경우를 줄이는 특성을 갖게 되었다고 해석할 수 있습니다. 이번 데이터 분할 및 평가 조건에서는 Recall (재현율)을 중시하는 채용 조건을 충족했기 때문에, Run B를 채용 후보로 선정했습니다.
ExperimentModel
각 Run에서 ExperimentModel을 가져온 결과, 두 Run 모두에 각각 1건씩 연관되어 있었습니다.
| 항목 | Run A | Run B |
|---|---|---|
| Artifact ID | gcp5-none-20260720t042949z-r4-model | gcp5-balanced-20260720t042949z-r4-model |
| Schema Title | google.ExperimentModel | google.ExperimentModel |
| Schema Version | 0.0.1 | 0.0.1 |
| Framework | sklearn | sklearn |
| Framework Version | 1.5.2 | 1.5.2 |
| Model Class | sklearn.pipeline.Pipeline | sklearn.pipeline.Pipeline |
| 모델 파일 | model.pkl | model.pkl |
| 파일 크기 | 3,952바이트 | 3,962바이트 |
| SHA-256 | f81a574df4fd8c1965d3682e630ccde1adb364addf508200d91a69c436e42046 | d994a2d76b0a523c68872f7dd4263135e75d9d3d2f860d43b614183ac6dae4af |
Cloud Storage에는 Run별 디렉토리에 model.pkl과 471바이트의 instance.yaml이 저장되었습니다.
ExperimentModel의 URI도 각각 동일한 디렉토리를 가리키고 있습니다.
gs://reona-sato-gcp4-903876386173/experiment-models/
├── gcp5-none-20260720t042949z-r4-model/
│ ├── instance.yaml
...
모델을 Artifact (아티팩트)로 기록했다고 해서 모델 본체가 ML Metadata 내에만 저장되는 것은 아닙니다. 모델 본체는 Cloud Storage에 저장되며, ML Metadata 상의 ExperimentModel이 Cloud Storage 상의 모델 파일과 Run을 연관시킵니다.
이를 통해 Experiments 상의 평가 결과로부터 해당 평가값에 대응하는 학습된 모델을 추적할 수 있게 되었습니다.
리소스 상태
이번에 추가한 IAM 바인딩 등 Terraform으로 관리하던 검증용 리소스는 삭제했습니다. Custom Job에서 사용된 학습용 VM은 Custom Job 완료 후 자동으로 삭제됩니다.
한편, Agent Platform SDK for Python을 통해 생성한 다음 리소스들은 Terraform 관리 대상이 아닙니다.
- Experiment
- Run
- ExperimentModel
- Cloud Storage 상의
model.pkl - Cloud Storage 상의
instance.yaml
이들은 force_destroy = true가 설정되어 있지 않은 경우 terraform destroy만으로는 삭제되지 않으므로, 불필요해진 경우에는 별도로 삭제해야 합니다.
마치며
Agent Platform SDK for Python의 log_model을 사용하여, Custom Training으로 학습한 scikit-learn 모델을 Cloud Storage에 저장하고 ExperimentModel로서 각 Run에 연관시킬 수 있었습니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기