목차
관련글
saved_model 배포 방법을 한 번 시도해보고 읽어보길 권장
[머신러닝] 쿠버네티스에서 TensorFlow 모델 Triton 서버를 활용해서 서빙하기(saved_model)
목차 쿠버네티스에서 트리톤 이미지 파드로 띄우기kubectl create -f triton-pvc.yamlkubectl create -f triton-deployment.yaml```triton-pvc.yamlapiVersion: v1kind: PersistentVolumeClaimmetadata: name: triton-pvc namespace: ${네임스페이
jfbta.tistory.com
onnx 모델 생성하기
import torch
import numpy as np
from torchvision.datasets import ImageFolder
import torchvision.transforms as transforms
from torch.utils.data import Dataset, DataLoader
from torch.utils.data import random_split
import os
from PIL import Image
class CustomDataset(Dataset):
def __init__(self, root_dir, transform=None):
self.root_dir = root_dir
self.transform = transform
self.images = []
self.labels = []
# 이미지 파일 경로와 레이블 수집
for filename in os.listdir(root_dir):
if filename.endswith(".jpg"): # JPEG 이미지 파일만 포함
label = 0 if 'cat' in filename else 1 # 'cat'이면 0, 'dog'이면 1
self.images.append(os.path.join(root_dir, filename))
self.labels.append(label)
def __len__(self):
return len(self.images)
def __getitem__(self, idx):
img_path = self.images[idx]
image = Image.open(img_path).convert("RGB") # 이미지를 RGB 모드로 열기
label = self.labels[idx]
if self.transform:
image = self.transform(image)
return image, label
train_transforms = transforms.Compose([transforms.RandomRotation(30), # 랜덤 각도 회전
transforms.RandomResizedCrop(224), # 랜덤 리사이즈 크롭
transforms.RandomHorizontalFlip(), # 랜덤으로 수평 뒤집기
transforms.ToTensor(), # 이미지를 텐서로
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])]) #
dataset = CustomDataset(root_dir='C:/Users/Downloads/modelapi/model/onnx_model/data/train/test', transform=train_transforms) # 파일 디렉토리
trainset, validset = random_split(dataset, [3,1]) # 학습데이터를 학습과 검증데이터로 쪼갬
batch_size=10
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True)
validloader = torch.utils.data.DataLoader(validset, batch_size=batch_size, shuffle=True)
classes = ['cat', 'dog']
# 코드 작성
import torch.nn as nn
import torch.nn.functional as F
# 좀 더 심플한 네트워크
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# input image = 224 x 224 x 3
# 224 x 224 x 3 --> 112 x 112 x 32 maxpool
self.conv1 = nn.Conv2d(3, 32, 3, padding=1)
# 112 x 112 x 32 --> 56 x 56 x 64 maxpool
self.conv2 = nn.Conv2d(32, 64, 3, padding=1)
# 56 x 56 x 64 --> 28 x 28 x 128 maxpool
self.conv3 = nn.Conv2d(64, 128, 3, padding=1)
# maxpool 2 x 2
self.pool = nn.MaxPool2d(2, 2)
# 28 x 28 x 128 vector flat 256개
self.fc1 = nn.Linear(128 * 28 * 28, 256)
# 카테고리 2개 클래스
self.fc2 = nn.Linear(256, 2)
# dropout 적용
self.dropout = nn.Dropout(0.5)
def forward(self, x):
# conv1 레이어에 relu 후 maxpool. 112 x 112 x 32
x = self.pool(F.relu(self.conv1(x)))
# conv2 레이어에 relu 후 maxpool. 56 x 56 x 64
x = self.pool(F.relu(self.conv2(x)))
# conv3 레이어에 relu 후 maxpool. 28 x 28 x 128
x = self.pool(F.relu(self.conv3(x)))
# 이미지 펴기
x = x.view(-1, 128 * 28 * 28)
# dropout 적용
x = self.dropout(x)
# fc 레이어에 삽입 후 relu
x = F.relu(self.fc1(x))
# dropout 적용
x = self.dropout(x)
# 마지막 logsoftmax 적용
x = F.log_softmax(self.fc2(x), dim=1)
return x
model = Net() # 모델 생성
print(model) # 출력
# 코드
import torch.optim as optim
criterion = nn.NLLLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 코드 작성
# epochs 30
n_epochs = 10
valid_loss_min = np.Inf
for epoch in range(1, n_epochs+1):
# train, valid loss
print(epoch)
train_loss = 0.0
valid_loss = 0.0
# 모델 트레이닝
model.train()
# training set
for batch_idx, (data, target) in enumerate(trainloader, 1):
# cuda 사용
# 역전파 실행 전 gradient 0 초기화
optimizer.zero_grad()
# 모델 계산 후 output 저장
output = model(data)
# 로스율 계산
loss = criterion(output, target)
# 가중치 계산
loss.backward()
# 모델 parameter 업데이트
optimizer.step()
# 트레이닝 로스 계산
train_loss += loss.item()*data.size(0)
# 배치마다 트레이닝 손실 출력
if batch_idx % 10 == 0: # 예: 10번째 배치마다 출력
print(f" Batch {batch_idx}, Loss: {loss.item():.6f}")
# validation 모델
model.eval()
validation_iter = iter(validloader)
with torch.no_grad(): # validation 시 gradient 계산 안 함
for data, target in validation_iter:
# cuda 사용
# 모델 계산 후 output 저장
output = model(data)
# 로스율 계산
loss = criterion(output, target)
# validation 로스율 계산
valid_loss += loss.item()*data.size(0)
# 평균 로스율
train_loss = train_loss/len(trainloader.sampler)
valid_loss = valid_loss/len(validloader.sampler)
# training set, validation set 로스율 출력
print('Epoch: {} \tTraining Loss: {:.6f} \tValidation Loss: {:.6f}'.format(
epoch, train_loss, valid_loss))
# 로스율이 낮아지면 model_catdog.pt에 저장
if valid_loss <= valid_loss_min:
print('Validation loss decreased ({:.6f} --> {:.6f}). Saving model ...'.format(
valid_loss_min,
valid_loss))
torch.save(model.state_dict(), 'model_catdog.pt')
valid_loss_min = valid_loss
# 최종적으로 나온 모델을 onnx 파일로 저장
import onnx
dummy_input = torch.randn(1, 3, 224, 224) # 입력 크기에 맞게 조정
torch.onnx.export(model, dummy_input, "cat_dog_model.onnx",
input_names=["input_1"],
output_names=["output_1"],
opset_version=11,
dynamic_axes={"input_1": {0: "batch_size"}, "output_1": {0: "batch_size"}})
# torch.onnx.export(model, dummy_input, "cat_dog_model.onnx",
# input_names=["input_1"], output_names=["output_1"],
# opset_version=11)
'dataset = CustomDataset(root_dir='C:/Users/Downloads/modelapi/model/onnx_model/data/train/test', transform=train_transforms)' 이 경로에 있는 많은 양의 강아지, 고양이 이미지를 학습시키는 머신러닝 코드이다. 실행이 완료되는데 반나절 정도의 시간이 걸리며 완료되면 model.onnx 파일이 생성된다.
트리톤 서버에서 모델 디렉토리 Tree 형식으로 구조 파악하고 구성하기
```디렉토리 구조
/models
/onnx_model
/config.pbtxt
1
/model.onnx
```
config.pbtxt 작성하기
name: "onnx_model"
platform: "onnxruntime_onnx"
max_batch_size: 1
input [
{
name: "input_1"
data_type: TYPE_FP32
format: FORMAT_NCHW
dims: [ 3, 224, 224 ]
}
]
output [
{
name: "output_1"
data_type: TYPE_FP32
dims: [ 2 ]
}
]
dynamic_batching {
preferred_batch_size: [ 1 ]
max_queue_delay_microseconds: 100
}
이미지 크기를 224*224로 했더니 이미지를 json으로 변환해서 body에 입력하여 post api 호출했더니 다음과 같은 에러가 발생했다.
요청 실패: 413, <html>
<head><title>413 Request Entity Too Large</title></head>
<body>
<center><h1>413 Request Entity Too Large</h1></center>
<hr><center>nginx</center>
</body>
</html>
'<center>nginx</center>' 이 부분을 보니 ingress-nginx에서 size를 늘리는 설정이 필요한 것 같다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 100m
nginx.ingress.kubernetes.io/rewrite-target: /$2
creationTimestamp: "2024-09-23T06:30:26Z"
generation: 1
name: triton-ingress-21
namespace: "2"
resourceVersion: "52473063"
uid: 18374b2c-2759-49ea-b344-de5552b16951
spec:
ingressClassName: nginx
rules:
- http:
paths:
- backend:
service:
name: triton-svc-21
port:
number: 8000
path: /21(/|$)(.*)
pathType: Prefix
status:
loadBalancer:
ingress:
- ip: 10.10.12.123
'nginx.ingress.kubernetes.io/proxy-body-size: 100m' -> nginx에서 body size의 기본값은 1m 이다. 100m 까지 늘려준뒤 호출 해주면 정상적으로 동작된다.
이미지를 triton body 입력 형식에 맞는 json타입으로 변환 후 POST API 호출하여 - 고양이, 강아지 예측하기
import requests
import json
import numpy as np
from PIL import Image
import torchvision.transforms as transforms
# 입력데이터 전처리
def createInputData(image_path):
input_image = Image.open(image_path)
preprocess = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
input_tensor = preprocess(input_image).unsqueeze(0) # 배치 차원 추가
input_data_flat = input_tensor.numpy().flatten().tolist()
# JSON 형식으로 작성
request_json = {
"inputs": [
{
"name": "input_1", # 모델의 입력 이름
"shape": [1, 3, 224, 224], # 배치 크기를 포함한 입력 형태
"datatype": "FP32", # 데이터 타입
"data": input_data_flat # 플래트 형식의 데이터 배열
}
]
}
return request_json
# 응답 처리
def callAPI(input_data):
response = requests.post(url, json=input_data)
if response.status_code == 200:
outputs = response.json() # JSON 응답 파싱
data_values = outputs['outputs'][0]['data']
print(outputs)
class_names = ['고양이', '강아지']
result=class_names[np.argmax(data_values)]
print(f"예측 결과: {result}")
else:
print(f"요청 실패: {response.status_code}, {response.text}")
return requests.post(url, json=input_data)
image_path = 'cat.jpg'
input_data = createInputData(image_path)
# Triton API 호출을 위한 URL 설정
url = "http://10.10.12.123/21/v2/models/onnx_model/infer"
# API 호출 - 완전한 JSON 객체 전송
callAPI(input_data)
예측 결과: 고양이
'최근 포스팅' 카테고리의 다른 글
[쿠버네티스] externalTrafficPolicy 옵션으로 Ingress 접근 범위 설정하기 (0) | 2024.11.15 |
---|---|
[쿠버네티스] ingress-nginx 설치 (0) | 2024.11.14 |
[머신러닝] 쿠버네티스에서 pytorch 모델 Triton서버를 활용해서 서빙하기(model.pt) (0) | 2024.10.02 |
[도커] 로컬에 설치한 넥서스에 새로 빌드 후 이미지 push하기 (0) | 2024.09.23 |
[머신러닝] 쿠버네티스에서 TensorFlow 모델 Triton 서버를 활용해서 서빙하기(saved_model) (3) | 2024.09.23 |