Blog AWS Indonesia

Membangun CI/CD pipeline untuk deploying custom machine learning models menggunakan layanan AWS

Amazon SageMaker adalah managed service yang menyediakan developer dan data scientist kemampuan untuk membangun, melatih, dan deploy machine learning (ML) dengan cepat. SageMaker menghilangkan tugas berat dari setiap langkah proses ML untuk mempermudah mengembangkan artefak ML berkualitas tinggi. AWS Serverless Application Model (AWS SAM) adalah framework open-source untuk membangun aplikasi serverless. AWS SAM menyediakan cara cepat untuk menulis fungsi, API, database, event source mappings, langkah-langkah dalam AWS Step Functions, dan banyak lagi.

Umumnya, workflow ML mengatur dan mengotomatiskan urutan task ML. Sebuah workflow mencakup pengumpulan data, pelatihan, pengujian, evaluasi manusia terhadap model ML, dan deployment model untuk inferensi.

Untuk continuous integration dan continuous delivery pipeline (CI/CD), AWS memiliki layanan Amazon SageMaker Pipelines, layanan CI/CD pertama yang mudah digunakan dan dirancang khusus untuk ML. Pipelines adalah alat orkestrasi native workflow untuk membangun pipeline ML yang memanfaatkan integrasi SageMaker secara langsung. Untuk informasi selengkapnya, lihat Membangun, mengotomatiskan, mengelola, dan scaling workflow ML menggunakan Amazon SageMaker Pipelines.

Dalam posting ini, saya menunjukkan kepada anda bagaiamana mengotomatiskan dan men-deploy custom machine learning models menggunakan integrasi layanan Amazon SageMaker, Step Functions, dan AWS SAM dengan pipeline CI/CD.

Untuk membangun pipeline ini, Anda juga harus megetahui layanan AWS berikut:

  • AWS CodeBuild – Layanan continuous integration yang dikelola sepenuhnya untuk compile source code, menjalankan pengujian, dan menghasilkan paket perangkat lunak yang siap deploy
  • AWS CodePipeline – Layanan continuous delivery yang membantu mengotomatiskan release pipelines Anda
  • Amazon Elastic Container Registry (Amazon ECR) – Registry container
  • AWS Lambda – Layanan yang memungkinkan Anda menjalankan kode tanpa menyediakan atau mengelola server. Anda hanya membayar untuk waktu komputasi yang Anda gunakan
  • Amazon Simple Storage Service (Amazon S3) – Layanan penyimpanan objek yang menawarkan skalabilitas, ketersediaan data, keamanan, dan kinerja terdepan di industri
  • AWS Step Functions –  Serverless orkestrator yang memudahkan untuk merangkai fungsi AWS Lambda dan berbagai layanan AWS lainnya

Gambaran umum solusi

Solusi dibagi menjadi dua bagian utama:

  • Gunakan AWS SAM untuk membuat workflow Step Functions dengan SageMaker – Step Functions baru-baru ini mengumumkan integrasi layanan secara native dengan SageMaker. Anda dapat menggunakan fitur ini untuk melatih model ML, deploy model ML, menguji hasil, dan mengekspos endpoint inferensi. Fitur ini juga menyediakan cara untuk menunggu persetujuan manusia sebelum konfigurasi dan deployment endpoint inferensi model ML dapat dilakukan.
  • Deploy model dengan CI/CD pipeline – Salah satu persyaratan SageMaker adalah source code custom model perlu disimpan sebagai Docker image dalam image registry seperti Amazon ECR. SageMaker kemudian mereferensikan Docker image ini untuk pelatihan dan inferensi. Untuk posting ini, kami membuat CI/CD pipeline menggunakan CodePipeline dan CodeBuild untuk membangun, men-tag, dan meng-upload Docker image ke Amazon ECR dan kemudian memulai workflow Step Functions untuk melatih dan men-deploy custom ML model di SageMaker, yang mereferensikan Docker image yang di-tag sebelumnya.

Diagram berikut menjelaskan gambaran umum alur MLOps CI/CD.

MLOps CodePipeline

Workflow tersebut mencakup langkah-langkah berikut:

  1. Data scientist bekerja untuk mengembangkan kode custom ML model menggunakan notebook lokal mereka atau notebook SageMaker. Mereka lalu melakukan commit dan push perubahan ke source code repository.
  2. Webhook pada repository kode memicu CodePipeline build di AWS Cloud.
  3. CodePipeline men-download source code dan memulai proses build.
  4. CodeBuild men-download file-file yang diperlukan dan mulai menjalankan perintah untuk build dan tag Docker container image lokal.
  5. CodeBuild push container image ke Amazon ECR. Container image di-tag dengan label unik yang berasal dari hash commit repository.
  6. CodePipeline memanggil Step Functions dan meneruskan URI Container image dan tag unik dari container image sebagai parameter ke Step Functions.
  7. Step Functions memulai workflow dengan memanggil SageMaker training job dan meneruskan parameter yang diperlukan.
  8. SageMaker download container image yang diperlukan dan memulai training job. Ketika job selesai, Step Functions mengarahkan SageMaker untuk membuat model dan menyimpan model di bucket S3.
  9. Step Functions memulai SageMaker Batch Transform Job pada data pengujian yang disediakan dalam bucket S3.
  10. Ketika batch transform job selesai, Step Functions mengirimkan email kepada pengguna menggunakan Amazon Simple Notification Service (Amazon SNS). Email ini mencakup detail batch transform job dan link ke hasil prediksi data pengujian yang disimpan dalam bucket S3. Setelah mengirim email, Step Function memasuki fase tunggu manual.
  11. Email yang dikirim oleh Amazon SNS memiliki link untuk menerima atau menolak hasil pengujian. Penerima dapat secara manual melihat hasil prediksi data pengujian di bucket S3. Jika mereka tidak puas dengan hasilnya, mereka dapat menolak perubahan untuk membatalkan workflow Step Functions.
  12. Jika penerima menerima perubahan, endpoint Amazon API Gateway memanggil fungsi Lambda dengan embedded token yang mereferensikan langkah Step Functions selanjutnya.
  13. Fungsi Lambda memanggil Step Functions untuk melanjutkan workflow.
  14. Step Functions melanjutkan workflow.
  15. Step Functions membuat konfigurasi SageMaker endpoint dan endpoint inferensi SageMaker.
  16. Ketika workflow berhasil, Step Functions mengirim email dengan link ke endpoint inferensi SageMaker.

Menggunakan AWS SAM untuk membuat workflow Step Functions dengan SageMaker

Di bagian pertama ini, Anda memvisualisasikan workflow Step Functions ML dengan mudah di Visual Studio Code dan men-deploy-nya ke environment AWS menggunakan AWS SAM. Anda menggunakan beberapa fitur dan integrasi layanan baru seperti dukungan AWS SAM untuk AWS Step Functions, dukungan bawaan Step Functions untuk integrasi dengan SageMaker, dan dukungan Step Functions untuk memvisualisasikan workflow secara langsung di VS Code.

Prasyarat

Sebelum memulai, pastikan Anda menyelesaikan prasyarat berikut:

Men-deploy template aplikasi

Untuk memulai, ikuti instruksi di GitHub untuk menyelesaikan persiapan aplikasi. Atau, Anda dapat beralih ke terminal dan memasukkan perintah berikut:

 git clone https://github.com/aws-samples/sam-sf-sagemaker-workflow.git

Anda akan menemukan struktur direktori sebagai berikut:

. sam-sf-sagemaker-workflow
|– cfn
|—- sam-template.yaml
| — functions
| —- api_sagemaker_endpoint
| —- create_and_email_accept_reject_links
| —- respond_to_links
| —- update_sagemakerEndpoint_API
| — scripts
| — statemachine
| —- mlops.asl.json

Kode telah dipecah menjadi subfolder dengan template utama SAM AWS berada di path cfn/sam-template.yaml .

Workflow Step Functions disimpan di folder statemachine/mlops.asl.json , dan fungsi Lambda lainnya yang digunakan disimpan dalam folder functions .

Untuk memulai dengan template AWS SAM, jalankan bash script berikut dari root folder:

#Create S3 buckets if required before executing the commands. 
S3_BUCKET=bucket-mlops #bucket to store AWS SAM template 
S3_BUCKET_MODEL=ml-models #bucket to store ML models 
STACK_NAME=sam-sf-sagemaker-workflow #Name of the AWS SAM stack 
sam build -t cfn/sam-template.yaml #AWS SAM build 
sam deploy --template-file .aws-sam/build/template.yaml \ 
--stack-name ${STACK_NAME} --force-upload \ 
--s3-bucket ${S3_BUCKET} --s3-prefix sam \ 
--parameter-overrides S3ModelBucket=${S3_BUCKET_MODEL} \ 
--capabilities CAPABILITY_IAM

Perintah sam build membangun semua fungsi dan membuat template AWS CloudFormation terakhir. Perintah sam deploy meng-upload file yang diperlukan ke bucket S3 untuk men-deploy atau memperbarui stack CloudFormation untuk membuat infrastruktur AWS yang diperlukan.

Setelah template selesai di-deploy dengan sukses, buka console CloudFormation. Pada tab Output, catat nilai MLOpsStateMachineArn untuk digunakan nanti.

Output tab CloudFormation Stack

Diagram berikut menunjukkan workflow yang dilakukan di Step Functions, menggunakan integrasi VS Code dengan Step Functions.

Flow dari StepFunctions untuk MLOps

Cuplikan JSON Amazon State Language berikut menjelaskan workflow yang divisualisasikan dalam diagram sebelumnya.

{
    "Comment": "This Step Function starts machine learning pipeline, once the custom model has been uploaded to ECR. Two parameters are expected by Step Functions are git commitID and the sagemaker ECR custom container URI",
    "StartAt": "SageMaker Create Training Job",
    "States": {
        "SageMaker Create Training Job": {
            "Type": "Task",
            "Resource": "arn:aws:states:::sagemaker:createTrainingJob.sync",
            "Parameters": {
                "TrainingJobName.$": "$.commitID",
                "ResourceConfig": {
                    "InstanceCount": 1,
                    "InstanceType": "ml.c4.2xlarge",
                    "VolumeSizeInGB": 20
                },
                "HyperParameters": {
                    "mode": "batch_skipgram",
                    "epochs": "5",
                    "min_count": "5",
                    "sampling_threshold": "0.0001",
                    "learning_rate": "0.025",
                    "window_size": "5",
                    "vector_dim": "300",
                    "negative_samples": "5",
                    "batch_size": "11"
                },
                "AlgorithmSpecification": {
                    "TrainingImage.$": "$.imageUri",
                    "TrainingInputMode": "File"
                },
                "OutputDataConfig": {
                    "S3OutputPath": "s3://${S3ModelBucket}/output"
                },
                "StoppingCondition": {
                    "MaxRuntimeInSeconds": 100000
                },
                "RoleArn": "${SagemakerRoleArn}",
                "InputDataConfig": [
                    {
                        "ChannelName": "training",
                        "DataSource": {
                            "S3DataSource": {
                                "S3DataType": "S3Prefix",
                                "S3Uri": "s3://${S3ModelBucket}/iris.csv",
                                "S3DataDistributionType": "FullyReplicated"
                            }
                        }
                    }
                ]
            },
            "Retry": [
                {
                    "ErrorEquals": [
                        "SageMaker.AmazonSageMakerException"
                    ],
                    "IntervalSeconds": 1,
                    "MaxAttempts": 1,
                    "BackoffRate": 1.1
                },
                {
                    "ErrorEquals": [
                        "SageMaker.ResourceLimitExceededException"
                    ],
                    "IntervalSeconds": 60,
                    "MaxAttempts": 1,
                    "BackoffRate": 1
                }
            ],
            "Catch": [
                {
                    "ErrorEquals": [
                        "States.ALL"
                    ],
                    "ResultPath": "$.cause",
                    "Next": "FailState"
                }
            ],
            "Next": "SageMaker Create Model"
        },
        "SageMaker Create Model": {
            "Type": "Task",
            "Resource": "arn:aws:states:::sagemaker:createModel",
            "Parameters": {
                "ExecutionRoleArn": "${SagemakerRoleArn}",
                "ModelName.$": "$.TrainingJobName",
                "PrimaryContainer": {
                    "ModelDataUrl.$": "$.ModelArtifacts.S3ModelArtifacts",
                    "Image.$": "$.AlgorithmSpecification.TrainingImage"
                }
            },
            "ResultPath": "$.taskresult",
            "Next": "SageMaker Create Transform Job",
            "Catch": [
                {
                "ErrorEquals": ["States.ALL" ],
                "Next": "FailState"
                }
            ]
        },
        "SageMaker Create Transform Job": {
            "Type": "Task",
            "Resource": "arn:aws:states:::sagemaker:createTransformJob.sync",
            "Parameters": {
                "ModelName.$": "$.TrainingJobName",
                "TransformInput": {
                    "SplitType": "Line",
                    "CompressionType": "None",
                    "ContentType": "text/csv",
                    "DataSource": {
                        "S3DataSource": {
                            "S3DataType": "S3Prefix",
                            "S3Uri": "s3://${S3ModelBucket}/iris.csv"
                        }
                    }
                },
                "TransformOutput": {
                    "S3OutputPath.$": "States.Format('s3://${S3ModelBucket}/transform_output/{}/iris.csv', $.TrainingJobName)" ,
                    "AssembleWith": "Line",
                    "Accept": "text/csv"
                },
                "DataProcessing": {
                    "InputFilter": "$[1:]"
                },
                "TransformResources": {
                    "InstanceCount": 1,
                    "InstanceType": "ml.m4.xlarge"
                },
                "TransformJobName.$": "$.TrainingJobName"
            },
            "ResultPath": "$.result",
            "Next": "Send Approve/Reject Email Request",
            "Catch": [
                {
                "ErrorEquals": [
                    "States.ALL"
                ],
                "Next": "FailState"
                }
            ]
        },
        "Send Approve/Reject Email Request": {
            "Type": "Task",
            "Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
            "Parameters": {
                "FunctionName": "${CreateAndEmailLinkFnName}",
                "Payload": {
                    "token.$":"$$.Task.Token",
                    "s3_batch_output.$":"$.result.TransformOutput.S3OutputPath"                                      
                }
            },
            "ResultPath": "$.output",
            "Next": "Sagemaker Create Endpoint Config",
            "Catch": [
                {
                    "ErrorEquals": [ "rejected" ],
                    "ResultPath": "$.output",
                    "Next": "FailState"
                }
            ]
            
            
        },
        "Sagemaker Create Endpoint Config": {
            "Type": "Task",
            "Resource": "arn:aws:states:::sagemaker:createEndpointConfig",
            "Parameters": {
                "EndpointConfigName.$": "$.TrainingJobName",
                "ProductionVariants": [
                    {
                        "InitialInstanceCount": 1,
                        "InitialVariantWeight": 1,
                        "InstanceType": "ml.t2.medium",
                        "ModelName.$": "$.TrainingJobName",
                        "VariantName": "AllTraffic"
                    }
                ]
            },
            "ResultPath": "$.result",
            "Next": "Sagemaker Create Endpoint",
            "Catch": [
                {
                  "ErrorEquals": [
                    "States.ALL"
                  ],
                  "Next": "FailState"
                }
              ]
        },
        "Sagemaker Create Endpoint": {
            "Type": "Task",
            "Resource": "arn:aws:states:::sagemaker:createEndpoint",
            "Parameters": {
                "EndpointName.$": "$.TrainingJobName",
                "EndpointConfigName.$": "$.TrainingJobName"
            },            
            "Next": "Send Email With API Endpoint",
            "Catch": [
                {
                  "ErrorEquals": [
                    "States.ALL"
                  ],
                  "Next": "FailState"
                }
              ]
        },
        "Send Email With API Endpoint": {
            "Type": "Task",
            "Resource": "${UpdateSagemakerEndpointAPI}",
            "Catch": [
                {
                  "ErrorEquals": [
                    "States.ALL"
                  ],
                  "Next": "FailState"
                }
              ],
             "Next": "SuccessState"
        },
        "SuccessState": {
            "Type": "Succeed"            
        },
        "FailState": {
            "Type": "Fail"          
        }               
                
    }
}

Proses Step Functions untuk membuat workflow SageMaker

Di bagian ini, kita membahas langkah-langkah terperinci yang terlibat dalam pembuatan workflow SageMaker menggunakan Step Functions.

Step Functions menggunakan commit ID yang diteruskan oleh CodePipeline sebagai pengidentifikasi unik untuk membuat SageMaker training job. Training job terkadang bisa memakan waktu lama untuk diselesaikan; untuk menunggu job, Anda menggunakan keterangan .sync saat membuat bagian resource dari SageMaker training job.

JSON SageMaker

Ketika training job selesai, Step Functions membuat dan menyimpan model dalam S3 bucket.

Step Functions kemudian menggunakan batch transform step untuk mengevaluasi dan menguji model, berdasarkan data batch yang awalnya disediakan oleh data scientist di dalam S3 bucket. Ketika langkah evaluasi selesai, hasilnya disimpan dalam S3 bucket.

Step Functions kemudian memasuki tahap persetujuan manual. Untuk membuat state ini, Anda menggunakan callback URLs. Untuk menerapkan state ini di Step Functions, gunakan keterangan .waitForTaskToken saat memanggil resource Lambda dan berikan token ke fungsi Lambda.

Fungsi Lambda menggunakan Amazon SNS atau Amazon Simple Email Service (Amazon SES) untuk mengirim email ke pihak yang berlangganan. Anda perlu menambahkan alamat email Anda ke topik SNS untuk menerima email accept/reject saat pengujian.

JSON SageMaker 2

Anda akan menerima email seperti pada screenshot layar berikut, dengan link ke data yang disimpan di S3 bucket. Data ini telah ditransformasikan secara batch menggunakan custom ML models yang dibuat pada langkah sebelumnya oleh SageMaker. Anda dapat memilih Accept atau Reject berdasarkan temuan Anda.

SNS SageMaker Email

Jika Anda memilih Reject, Step Functions berhenti menjalankan workflow. Jika Anda puas dengan hasilnya, pilih Accept, yang memicu API link. Link ini meneruskan embedded token ke API Gateway endpoint atau Lambda endpoint sebagai parameter request untuk maju ke langkah Step Functions berikutnya.

Lihat kode Python berikut:

import json
import boto3
sf = boto3.client('stepfunctions')
def lambda_handler(event, context):
    type= event.get('queryStringParameters').get('type')
    token= event.get('queryStringParameters').get('token')    
    
    if type =='success':
        sf.send_task_success(
        taskToken=token,
        output="{}"
    )
    else:
        sf.send_task_failure(
        taskToken=token
        
    )

    

    return {
        'statusCode': 200,
        'body': json.dumps('Responded to Step Function')
    }

Step Functions kemudian membuat konfigurasi endpoint SageMaker unik dan men-deploy-nya sebagai endpoint inferensi. Anda dapat melakukan ini dengan Lambda menggunakan nilai resource khusus, seperti yang ditunjukkan pada screenshot layar berikut.

JSON SageMaker 3

Ketika SageMaker endpoint siap, email dikirim ke subscriber dengan link ke API endpoint inferensi SageMaker.

SNS SageMaker 2

Deploy model dengan CI/CD Pipeline

Di bagian ini, Anda menggunakan CI/CD pipeline untuk men-deploy custom ML model.

Pipeline akan mulai dijalankan segera setelah pipeline tersebut mendeteksi pembaruan ke source code custom model. Pipeline men-download source code dari repository, membangun dan men-tag Docker image, dan meng-upload Docker image ke Amazon ECR. Setelah meng-upload Docker image, pipeline memicu workflow Step Functions untuk melatih dan men-deploy custom model ke SageMaker. Terakhir, pipeline mengirim email ke pengguna yang telah berlangganan dengan detil tentang endpoint inferensi SageMaker.

Kami menggunakan Scikit Bring Your Own Container untuk membuat custom container image dan menggunakan iris dataset untuk melatih dan menguji model.

Saat workflow Step Functions Anda siap, buat pipeline Anda menggunakan kode yang disediakan dalam GitHub repo.

Setelah Anda download kode dari repo, struktur direktori akan terlihat seperti berikut:

. codepipeline-ecr-build-sf-execution
| — cfn
| —- params.json
| —- pipeline-cfn.yaml
| — container
| —- descision_trees
| —- local_test
| —- .dockerignore
| —- Dockerfile
| — scripts

Dalam file params.json di folder /cfn , berikan token GitHub Anda, nama repo, dan ARN dari state machine Step Function yang Anda buat sebelumnya.

JSON SageMaker 4

Anda sekarang membuat service dan resources yang diperlukan untuk CI/CD pipeline. Untuk membuat CloudFormation stack, jalankan kode berikut:

aws cloudformation create-stack --stack-name codepipeline-ecr-build-sf-execution --template-body file://cfn/pipeline-cfn.yaml --parameters file://cfn/params.json --capabilities CAPABILITY_NAMED_IAM

Atau, untuk memperbarui stack, jalankan kode berikut:

aws cloudformation update-stack --stack-name codepipeline-ecr-build-sf-execution --template-body file://cfn/pipeline-cfn.yaml --parameter file://cfn/params.json --capabilities CAPABILITY_NAMED_IAM

CloudFormation template men-deploy sebuah CodePipeline pipeline ke akun AWS Anda. Pipeline akan mulai berjalan segera setelah perubahan kode di-push ke repo. Setelah source code ter-download oleh tahapan pipeline, CodeBuild membuat Docker image dan menandainya dengan commit ID dan timestamp saat ini sebelum mem-push image ke Amazon ECR. CodePipeline kemudian pindah ke tahap berikutnya untuk memicu langkah Step Functions (yang telah Anda buat sebelumnya).

Ketika Step Functions selesai, email akhir dibuat dengan link ke URL API Gateway yang mengarah kepada endpoint inferensi SageMaker yang baru dibuat.

CodePipeline SageMaker

Menguji workflow

Untuk menguji workflow Anda, selesaikan langkah-langkah berikut:

  1. Mulai tahapan build pada CodePipeline dengan melakukan commit kode ke folder codepipeline-ecr-build-sf-execution/container.
  2. Pada console CodePipeline, periksa apakah pipeline sedang bertransisi melalui berbagai tahap seperti yang diharapkan.

Ketika pipeline mencapai status akhirnya, ia menjalankan workflow Step Functions, yang mengirim email untuk persetujuan.

  1. Setujui email untuk melanjutkan workflow Step Functions.

Saat endpoint SageMaker siap, Anda akan menerima email lain dengan link ke endpoint inferensi API.

Email Confirmation SageMaker Endpoint

Untuk menguji iris dataset, Anda dapat mencoba mengirim satu data point ke endpoint inferensi.

  1. Salin link endpoint inferensi dari email dan berikan ke bash variable INFERENCE_ENDPOINT seperti yang ditunjukkan dalam kode berikut.
INFERENCE_ENDPOINT=https://XXXX.execute-api.us-east-1.amazonaws.com/v1/invokeSagemakerAPI?sagemaker_endpoint=d236eba5-09-03-2020-18-29-15

curl --location --request POST ${INFERENCE_ENDPOINT}  --header 'Content-Type: application/json' --data-raw '{  "data": "4.5,1.3,0.3,0.3"
}'
{"result": "setosa"}

curl --location --request POST ${INFERENCE_ENDPOINT}  --header 'Content-Type: application/json' --data-raw '{
  "data": "5.9,3,5.1,1.8"
}'
{"result": "virginica"}

Dengan mengirim data yang berbeda, kita mendapatkan serangkaian hasil inferensi yang berbeda.

Pembersihan

Untuk menghindari tagihan biaya terus-menerus, hapus resources yang dibuat pada langkah-langkah sebelumnya dengan menghapus CloudFormation templates. Selain itu, pada SageMaker console, hapus model, konfigurasi endpoint, dan endpoint inferensi yang tidak digunakan.

Kesimpulan

Posting ini mendemonstrasikan cara membuat pipeline ML untuk model ML SageMaker custom menggunakan integrasi beberapa layanan AWS terbaru.

Anda dapat memperluas pipeline ML ini lebih lanjut dengan menambahkan lapisan autentikasi dan enkripsi saat mengirim link persetujuan. Anda juga dapat menambahkan lebih banyak langkah ke CodePipeline atau Step Functions yang dianggap perlu untuk alur kerja proyek Anda.

Contoh file tersedia di GitHub repo. Untuk menjelajahi fitur terkait SageMaker dan bacaan lebih lanjut, lihat referensi berikut ini:

Artikel ini diterjemahkan dari artikel asli berjudul “Build a CI/CD pipeline for deploying custom machine learning models using AWS services” yang ditulis oleh Sachin Doshi.

Fernandito

Fernandito

Fernandito is a Solutions Architect at Amazon Web Services based in Indonesia. His main focus is Machine Learning technologies, and has helped several customers to adopt Machine Learning to solve their business outcomes. Watching YouTube is his hobby.