머신러닝
딥러닝
텐서플로
머신러닝 모델 생성, 배포, 운영 과정
출처: 챗gpt
- 모델 학습:
- 데이터 준비: 데이터를 수집하고, 전처리 과정을 통해 깨끗하고 사용 가능한 형태로 만듦.
- 모델 선택 및 학습: 사용 목적에 맞는 알고리즘을 선택하고, 데이터에 대해 학습시킴.
- 모델 평가: 테스트 데이터를 사용하여 모델의 성능을 평가하고, 필요 시 하이퍼파라미터 튜닝 수행.
- 모델 저장:
- 학습된 모델을 파일로 저장. 일반적인 파일 형식으로는 TensorFlow의 경우 SavedModel 형식 등이 있음.
- 모델 배포 준비:
- API 서버 구축: 모델을 사용할 수 있도록 API 서버를 구축. Flask나 FastAPI 같은 프레임워크를 사용하여 쉽게 개발할 수 있음.
- 모델 로드: 저장된 모델을 API 서버에서 로드하고, 입력 데이터를 받아 예측 결과를 반환하는 엔드포인트를 만듦.
- 모델 배포:
- 클라우드 서비스 사용: 모델을 클라우드 환경에 배포. AWS, GCP, Azure 등의 클라우드 플랫폼을 사용할 수 있음.
- 컨테이너화: Docker 같은 컨테이너 기술을 사용하여 애플리케이션을 컨테이너화하고, Kubernetes를 통해 관리할 수 있음.
- 모니터링 및 유지보수:
- 로깅과 모니터링: 모델이 배포된 후 성능 모니터링과 로깅을 통해 예측 결과와 오류를 확인.
- 모델 업데이트: 새로운 데이터와 피드백을 기반으로 모델을 재학습시키고 업데이트.
텐서플로 서빙(TF 서빙) 사용하기
텐서플로 모델을 훈련한 후에는 tf.keras 모델이면 predict() 메서드를 호출하여 파이썬 코드에서도 쉽게 사용할 수 있다. 그러나 시스템이 점점 커지면 이 모델을 작은 서비스로 감싸야 할 때가 필요하다. 이 서비스의 역할은 예측을 만드는 것이며 나머지 시스템은 이 서비스에 쿼리를 한다. 이는 모델과 나머지 시스템을 분리하여 모델 버전을 쉽게 바꾸거나 나머지 시스템에 독립적으로 필요에 따라 규모를 늘리고 A/B 테스트를 수행할 수 있다. 또한 모든 소프트웨어 구성 요소가 동일한 모델 버전을 사용하도록 만들고 테스트와 개발 등을 단순화한다.
텐서플로 서빙(이하 TF 서빙)을 사용하게 되면 이런 서비스를 구성하는 모든 기술을 사용할 수 있다. TF 서빙은 높은 부하를 처리할 수 있고 여러 모델을 서비스하며 모델 저장소에서 자동으로 최신 버전의 모델을 배포하는 등의 작업을 수행할 수 있다.
https://www.tensorflow.org/tfx/guide/serving?hl=ko
◆ MNIST 모델 훈련하여 TF 서빙으로 배포하기
tf.keras를 사용해 MNIST 모델을 훈련하여 TF 서빙으로 배포하는 프로젝트를 진행할 때, 가장 먼저 해야 할 일은 모델을 빌드하고 학습한 다음 텐서플로의 SavedModel 포맷으로 내보내는 것이다.
1) MNIST 데이터 세트를 로드하여 스케일을 조정하고, 데이터셋 분할(훈련, 검증, 테스트)
from pathlib import Path
# MNIST 데이터 세트 로드 및 분할
mnist = tf.keras.datasets.mnist.load_data()
(X_train_full, y_train_full), (X_test, y_test) = mnist
X_valid, X_train = X_train_full[:5000], X_train_full[5000:]
y_valid, y_train = y_train_full[:5000], y_train_full[5000:]
2) MNIST 모델 구축, 컴파일 및 훈련
# MNIST 모델 구축 및 훈련(이미지 전처리도 처리)
tf.random.set_seed(42)
tf.keras.backend.clear_session()
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=[28, 28], dtype=tf.uint8),
tf.keras.layers.Rescaling(scale=1 / 255),
tf.keras.layers.Dense(100, activation="relu"),
tf.keras.layers.Dense(10, activation="softmax")
])
model.compile(loss="sparse_categorical_crossentropy",
optimizer=tf.keras.optimizers.SGD(learning_rate=1e-2),
metrics=["accuracy"])
model.fit(X_train, y_train, epochs=10, validation_data=(X_valid, y_valid))
▶ 일반적으로 내보낼 최종 모델에 모든 전처리 층을 포함하는 것이 좋다. 이렇게 하면 제품으로 배포했을 때 원래 형태 그대로 데이터를 주입할 수 있다. 또 모델을 사용하는 애플리케이션 내에서 전처리를 별도로 관리할 필요가 없다.
모델 안에서 전처리 단계를 처리하면 나중에 모델을 업데이트하기가 훨씬 수월하고 모델과 필요한 전처리 단계가 맞지 않는 문제를 피할 수 있다.
3) 텐서플로의 SavedModel 포맷으로 내보내기
model_name = "my_mnist_model"
model_version = "0001"
model_path = Path(model_name) / model_version
model.save(model_path, save_format="tf")
▶ 모델을 저장하는 방법은 model.save()를 호출하면 된다.
모델의 버전을 관리하려면 모델 버전마다 서브디렉터리를 만들기만 하면 된다.
파일 트리를 살펴본다.
sorted([str(path) for path in model_path.parent.glob("**/*")])
[각 파일의 용도]
- my_mnist_model\\0001: 모델 버전 디렉터리. 여러 모델 버전을 관리할 때 각 버전 번호로 구분된다.
- my_mnist_model\\0001\\assets: 모델의 메타데이터나 기타 사용자 정의 파일을 저장하는 디렉터리. 일반적으로 빈 폴더로 남아있을 수 있다.
- my_mnist_model\\0001\\keras_metadata.pb: Keras 모델과 관련된 추가 메타데이터를 포함하는 파일. 예를 들어, 모델 컴파일 정보, 커스텀 객체 등을 담고 있을 수 있다.
- my_mnist_model\\0001\\saved_model.pb: 전체 모델 구조와 그래프를 포함하는 프로토콜 버퍼 형식의 파일. 모델의 연산 그래프와 관련된 모든 정보가 포함되어 있다.
- my_mnist_model\\0001\\variables: 모델의 가중치 및 변수 데이터를 저장하는 디렉터리.
- my_mnist_model\\0001\\variables\\variables.data-00000-of-00001: 실제로 학습된 모델의 가중치 값들이 저장된 바이너리 파일. 큰 모델의 경우 여러 개의 파일로 나누어질 수 있다.
- my_mnist_model\\0001\\variables\\variables.index: 가중치와 변수의 인덱스 정보를 포함하는 파일. variables.data-* 파일과 함께 사용되어 변수들을 효율적으로 로드할 수 있도록 돕는다.
▶이 파일들은 텐서플로우 모델의 구조, 가중치, 메타데이터 등을 포함하며, 모델을 재사용하거나 배포할 때 필요하다.
텐서플로에는 SavedModel을 검사할 수 있는 saved_model_cli 명령줄 인터페이스가 있다.
!saved_model_cli show --dir $model_path
▶ 이 출력의 의미는 SavedModel은 하나 이상의 메타그래프를 포함하는데, 메타그래프란 계산 그래프에 입력과 출력 이름, 타입, 크기를 포함한 몇 가지 함수 시그니처 정의가 추가된 것을 말한다. 각 메타그래프는 일련의 태그로 구분되는데,
- 'train': 훈련 연산을 포함한 전체 계산 그래프가 포함된 메타그래프
- 'serve', 'gpu': 일부 GPU 관련 연산을 포함하여 예측 연산만 포함된 계산 그래프를 포함하는 메타그래프
- 'eval': 평가 단계에서 필요한 메타 데이터가 포함하는 메타 그래프 (평가 단계에서 사용)
- 'cpu': 모델이 CPU에서 실행될 때 필요한 메타 데이터를 포함하는 메타 그래프
이 'serve' 태그 세트를 살펴본다.
!saved_model_cli show --dir $model_path --tag_set serve
▶ 이 메타그래프애는 초기화 함수'__saved_model_init_op'와 기본 서빙 함수'serving_default'라는 두 가지 시그니처 정의가 포함되어 있다.
케라스 모델을 저장할 때 기본 서빙 함수는 예측을 수행하는 모델의 call() 메서드이다. 이 서빙 함수에 관해 자세히 알아본다.
!saved_model_cli show --dir $model_path --tag_set serve \
--signature_def serving_default
▶ 함수의 입력은 'flatten_input', 출력은 'dense_1' 로, 이는 케라스 모델의 입력 및 출력 층의 이름에 해당한다.
입력 및 출력 데이터의 타입(dtype)과 크기(shape)도 확인할 수 있다.
4) 텐서플로 서빙 설치하고 시작하기
TF 서빙을 설치하는 방법은 여러가지이다. (하이퍼링크 접속)
- 시스템 패키지 매니저 사용
- 도커 이미지 사용
- 소스에서 설치
필자의 경우엔 TensorFlow Serving Python API PIP 패키지를 설치하려고 한다. (아나콘다프롬프트 + 가상환경)
conda install qiqiao::tensorflow_serving_api
※ 주의! 설치를 하고나면 일부 모듈의 버전이 다르다면서 경고 메시지가 뜨는데, 무시해도 된다.
(괜히 이거에 맞춰 모듈 버전 바꿨다가 코드 실행이 안 되서 다 삭제하고 원래 버전으로 삭제하느라 시간 버렸다.)
가상환경에 설치를 해야 했기 때문에, 주피터노트북을 종료한 뒤, 아나콘다프롬프트에서 설치를 하고, 해당 노트북을 재실행했다. 모델을 다시 훈련하고 저장할 필요 없이, 기존에 저장한 모델을 불러와야 한다.
from pathlib import Path
# 모델 로드 경로
model_name = "my_mnist_model"
model_version = "0001"
model_path = Path(model_name) / model_version
# 모델 로드
loaded_model = tf.keras.models.load_model(model_path)
모델 로드 후, 기존에 수행했던 파일 트리 확인, 메타그래프(태그) 확인 등을 수행하니 제대로 잘 나온다.
5) 서버 시작 및 실행
서버를 시작하는 명령에는 모델의 기본 디렉터리의 절대 경로(0001이 아닌 my_mnist_model의 경로)가 필요하므로 MODEL_DIR 환경 변수에 저장해둔다.
import os
os.environ["MODEL_DIR"] = str(model_path.parent.absolute())
서버를 실행한다.
%%bash --bg
tensorflow_model_server \
--port=8500 \
--rest_api_port=8501 \
--model_name=my_mnist_model \
--model_base_path="${MODEL_DIR}" >my_server.log 2>&1
[코드 설명]
- %%bash --bg 매직 명령: 샐을 배시(bash) 스크립트로 백그라운드에서 실행된다.
- my_server.log 2 >&1: 표준 출력과 표준에러를 my_server.log 파일로 리다이렉션 한다.
- TF 서빙이 백그라운드에서 실행 중이며 해당 로그는 my_server.log 에 저장된다.
- MNIST 모델 (버전1)을 로드했으며 포트 8500과 8501애서 각각 gRPC와 REST 요청을 기다리고 있다.
6-1) REST API로 TF 서빙에 쿼리하기
이제 서버를 실행했으므로 REST API를 사용하여 쿼리해본다.
쿼리를 만들기 위해 호출할 함수 시그니처의 이름과 입력 데이터가 포함되어야 하는데, JSON 포맷으로 요청해야 하므로 넘파이 배열인 입력 이미지를 파이썬 리스트로 바꿔야 한다.
※ 쿼리하다: 데이터베이스 등에서 원하는 정보를 검색하기 위해 요청하는 것
import json
X_new = X_test[:3] # 새로운 숫자 이미지 3개를 분류한다고 가정
request_json = json.dumps({
'signature_name': 'serving_default',
'instances': X_new.tolist(),
})
※ 모델을 로드해도, 데이터셋 로드, 분할, 훈련은 다시 해야한다. (하지 않으면 X_test 변수가 정의되지 않았다는 에러 발생)
이 요청 데이터(request_json)를 HTTP POST 메서드로 TF 서빙에 전송하는데, request 라이브러리를 사용해 쉽게 처리할 수 있다.
import requests
server_url = "http://localhost:8501/v1/models/my_mnist_model:predict"
response = requests.post(server_url, data=request_json)
response.raise_for_status() # 오류 발생 시 예외 발생
response = response.json()
※ 만약 아래와 같은 에러가 나왔다면,
ConnectionError: HTTPConnectionPool(host='localhost', port=8501):
Max retries exceeded with url: /v1/models/my_mnist_model:
predict (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001D56F526948>:
Failed to establish a new connection: [WinError 10061]
대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다'))
TensorFlow Serving 서버에 대한 HTTP 연결이 거부되었음을 의미하므로, 다른 방식으로 접근해야 한다.
저자가 한 가지 명시한 방법인 도커(Docker)를 사용하여 실행하기로 한다.
◆ 도커 컨테이너에서 TF 서빙 실행하기
우선, 도커 데스크탑(Docker Desktop)이 없다면 아래 글 참고하여 해당 파일부터 설치해야 한다.
도커 설치 후, 주피터노트북을 실행한 뒤 TF 서빙 이미지를 다운로드한다.
# 도커 이미지 다운로드
!docker pull tensorflow/serving
그리고 도커 컨테이너 내에서 서버를 시작하기 위해 파워쉘(PowerShell) 또는 명령어 프롬프트를 켜고 아래의 명령어를 입력한다.
* -v 뒤의 경로는 'my_mnist_model'의 실제 경로이다.
docker run --rm -v "C:\Users\niceq\Documents\DataScience\Hands_ML\my_mnist_model:/models/my_mnist_model" -p 8500:8500 -p 8501:8501 -e MODEL_NAME=my_mnist_model tensorflow/serving
이제 주피터노트북으로 돌아와서 아래 명령어를 시작하면 이상없이 진행된다.
import requests
server_url = "http://localhost:8501/v1/models/my_mnist_model:predict"
response = requests.post(server_url, data=request_json)
response.raise_for_status() # 오류 발생 시 예외 발생
response = response.json()
연결 문제를 해결했으므로, 앞서 한 작업을 이어서 진행
응답은 'predictions' 키 하나를 가진 딕셔너리로, 이 키에 해당하는 값은 예측의 리스트이다.
이 예측은 파이썬 리스트이므로 넘파이 배열로 변환하고 소수점 셋째 자리에서 반올림한다.
y_proba = np.array(response['predictions'])
y_proba.round(2)
▶ 이 모델은 첫 번째 이미지가 7이라고 99% 확신하고,
두 번째 이미지는 2번이라고 99%, 세 번째 이미지는 1번이라고 98% 확신한다.
◆ REST API 장단점
- 장점
- HTTP 프로토콜의 인프라를 그대로 사용하므로 REST API 사용을 위한 별도의 인프라를 구축할 필요가 없다.
- HTTP 프로토콜의 표준을 최대한 활용하여 여러 추가적인 장점을 함께 가져갈 수 있게 해준다.
- 거의 모든 클라이언트 애플리케이션이 다른 것에 의존하지 않고 REST API를 사용할 수 있다.
- REST API 메시지가 의도하는 바를 명확하게 나타내므로 의도하는 바를 쉽게 파악할 수 있다.
- 서버와 클라이언트 역할을 명확하게 분리한다.
- 단점
- JSON 기반이므로 텍스트를 사용하고 매우 장황하다.
- 큰 넘파이 배열을 전송할 때 응답 속도를 느리게 하고 네트워크 대역폭을 많이 사용한다.
6-2) gRPC API로 TF 서빙에 쿼리하기
★ gRPC(출처: 위키백과)
gRPC(gRPC Remote Procedure Calls)는 구글이 최초로 개발한 오픈 소스 원격 프로시저 호출 (RPC) 시스템이다. 전송을 위해 HTTP/2를, 인터페이스 정의 언어로 프로토콜 버퍼를 사용하며 인증, 양방향 스트리밍 및 흐름 제어, 차단 및 비차단 바인딩, 취소 및 타임아웃 등의 기능을 제공한다. 수많은 언어를 대상으로 크로스 플랫폼 클라이언트 및 서버 바인딩을 생성한다. 가장 흔한 사용 시나리오에는 마이크로서비스 스타일 아키텍처의 서비스 연결, 모바일 장치, 브라우저 클라이언트를 백엔드 서비스에 연결하는 일 등이 포함된다.
gRPC API는 직렬화된 PredictRequest 프로토콜 버퍼를 입력으로 기대하고 직렬화된 PredictResponse 프로토콜 버퍼를 출력한다. 이 프로토콜은 앞서 설치한 tensorflow-serving-api에 포함되어 있다.
6-2-1) 먼저 요청을 만든다.
from tensorflow_serving.apis.predict_pb2 import PredictRequest
request = PredictRequest()
request.model_spec.name = model_name
request.model_spec.signature_name = "serving_default"
input_name = model.input_names[0] # == "flatten_input"
request.inputs[input_name].CopyFrom(tf.make_tensor_proto(X_new))
[코드 설명]
- 이 코드는 PredictRequest 프로토콜 버퍼를 만들고 필수 필드를 채운다.
- 여기에는 앞서 정의한 모델 이름, 호출할 함수의 시그니처 이름, Tensor 프로토콜 버퍼 형식으로 변환한 입력 데이터가 포함된다.
- tf.make_tensor_proto() 함수는 주어진 텐서나 넘파이 배열(여기서는 X_new)을 기반으로 Tensor 프로토콜 버퍼를 만든다.
6-2-2) 서버로 요청을 보내고 응답을 받는다.
import grpc
from tensorflow_serving.apis import prediction_service_pb2_grpc
channel = grpc.insecure_channel('localhost:8500')
predict_service = prediction_service_pb2_grpc.PredictionServiceStub(channel)
response = predict_service.Predict(request, timeout=10.0)
[코드 설명]
- 이 코드는 gRPC를 import 한 다음 localhost에서 TCP 포트 8500번으로 gRPC 통신 채널을 만든다.
- 이 채널에 대해 gRPC 서비스를 만들고 이를 사용해 10초 타임아웃이 설정된 요청을 보낸다.
- 이 요청은 동기 호출이 아니므로, 응답을 받거나 타임아웃이 지날 때까지 멈춰있을 것이다.
- 여기서는 보안 채널을 사용하지 않으나, gRPC와 TF 서빙은 SSL/TLS 기반의 보안 채널도 제공한다.
PredictResponse 프로토콜 버퍼를 텐서로 바꾸면 응답을 텐서로 반환한다.
output_name = model.output_names[0]
outputs_proto = response.outputs[output_name]
y_proba = tf.make_ndarray(outputs_proto)
y_proba.round(2)
7) 새로운 버전의 모델 배포하기
새로운 버전의 모델을 만들어 SavedModel 포맷으로 내보낸다.
그전에 새로운 모델을 빌드, 컴파일 및 훈련하고
# 새로운 MNIST 모델 버전 빌드 및 훈련
np.random.seed(42)
tf.random.set_seed(42)
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=[28, 28], dtype=tf.uint8),
tf.keras.layers.Rescaling(scale=1 / 255),
tf.keras.layers.Dense(50, activation="relu"),
tf.keras.layers.Dense(50, activation="relu"),
tf.keras.layers.Dense(10, activation="softmax")
])
model.compile(loss="sparse_categorical_crossentropy",
optimizer=tf.keras.optimizers.SGD(learning_rate=1e-2),
metrics=["accuracy"])
history = model.fit(X_train, y_train, epochs=10,
validation_data=(X_valid, y_valid))
이번에는 my_mnist_model/0002 디렉터리에 저장한다.
model_version = "0002"
model_path = Path(model_name) / model_version
model.save(model_path, save_format="tf")
파일 트리를 다시 한 번 살펴보면 0002 버전이 잘 저장된 것을 확인할 수 있다.
sorted([str(path) for path in model_path.parent.glob("**/*")])
TF 서빙을 사용하면 새로운 모델을 배포하는 것이 아주 쉽다. 또한 버전 2 모델이 원하는 대로 작동하지 않는다면 my_mnist_model/0002 디렉터리를 삭제하여 버전 1로 롤백할 수 있다.
다음 내용
[출처]
핸즈 온 머신러닝
텐서플로 사이트
https://easyhomputer.tistory.com/38 [6_oHji:티스토리]
위키백과
'[파이썬 Projects] > <파이썬 머신러닝>' 카테고리의 다른 글
[머신러닝]텐서플로 모델 훈련과 배포: 버텍스 AI (실패 및 보류) (0) | 2024.12.07 |
---|---|
[머신러닝] 도커(Docker) 설치하기 (1) | 2024.12.07 |
[머신러닝] 군집: 군집 사례 (1) | 2024.11.16 |
[머신러닝] 군집: k-평균 알고리즘 훈련하기 (0) | 2024.11.16 |
[머신러닝] 머신러닝 기반 분석 모형 선정 (1) | 2024.11.16 |