1. SavedModel로 내보내기
학습시킨 모델을 tf.saved_model.save()함수로 SavedModel 포맷으로 내보낼 수 있다.
모델과 이름, 버전을 포함한 경로를 전달하면 이 함수는 이 경로에 모델의 계산 그래프와 학습결과 가중치를 저장한다.
model_version = "0001"
model_name = "medisharp_pill_image_model"
model_path = os.path.join(model_name, model_version)
tf.saved_model.save(model, model_path)
그러면
- saved_model.pb: 계산 그래프를 정의
- variables: 변수값을 담고있는 폴더로, 많은 개수의 가중치를 담은 모델의 경우 변수값이 여러개의 파일로 나뉘어 저장 될 수 있다.
- assets: 부가적인 데이터가 들어있는 폴더를 포함하지만 내 경우 사용하지 않았다.
이런 형식으로 저장된다.
2. 저장된 모델 보기
텐서플로는 SavedModel을 검사할 수 있는 saved_model_cli 명령줄 도구를 제공한다. 한 번 보자.
$ export ML_PATH="/Users/jeonghyeonjeong/Desktop/medisharp/medisharp-server/cnn/model"
$ cd ML_PATH
$ saved_model_cli show --dir medisharp_pill_image_model/0001 --all
3. Tensorflow Serving 설치하기
도커를 켜고 로그인을 한 후 Docker Desktop is running상태가 되면
공식 tf서빙 도커 이미지
를 다운로드한다.
$ docker pull tensorflow/serving
그리고 이 이미지를 실행하기위해 도커 컨테이너
를 만든다.
$ docker run -it --rm -p 8500:8500 -p 8501:8501 \
-v "/Users/jeonghyeonjeong/medisharp_pill_image_model:/models/medisharp_pill_image_model" \
-e MODEL_NAME=medisharp_pill_image_model \
tensorflow/serving
※ [Docker] 각 명령 옵션이 의미하는 바
-it: 인터랙티브 모드로 컨테이너를 만들고, 서버의 출력을 화면에 나타낸다. Ctrl+C로 중지할 수 있다.
--rm: 중지할 때 컨테이너를 삭제함으로써 시스템이 지저분해지지 않게 한다. (이미지는 삭제되지 않는다.)
-p 8500:8500: 도커 엔진이 호스트 시스템의 TCP포트 8500번을 컨테이너의 TCP 8500번으로 포워딩한다. 기본적으로 TF서빙은 이 포트를 사용해 gRPC API를 제공한다.
-p 8501:8501: 호스트 시스템의 TCP포트 8501번을 컨테이너의 TCP 8501번으로 포워딩한다. 기본적으로 TF서빙은 이 포트를 사용해 REST API를 제공한다.
-v "/Users/jeonghyeonjeong/medisharp_pill_image_model:/models/medisharp_pill_image_model" : 호스트 시스템의 /Users/jeonghyeonjeong/medisharp_pill_image_model 디렉터리를 컨테이너의 /models/medisharp_pill_image_model경로에 연결한다.
-e MODEL_NAME=medisharp_pill_image_model: TF서빙이 어떤 모델을 서빙할지 알 수 있도록 컨테이너의 MODEL_NAME 환경 변수를 설정한다. 기본적으로 /models 디렉터리에서 모델을 찾고, 자동으로 최신 버전을 서빙한다.
tensorflow/serving: 실행할 이미지명
※이 과정에서 아래와 같은 "docker: Error response from daemon: Mounts denied:" 에러가 발생한다면?
맥에서 docker container로 복사할 때는, 정해진 디렉토리인 /Users, /Volumes, /private, /tmp 가 기본으로 filesharing에 지정되어 있다. 기본 디렉토리 외에 다른 디렉토리를 docker container로 복사하려고 할 때 이런 에러가 발생할 수 있다.
해결 방법은 공식문서에 잘 나와있다 :)) docs.docker.com/docker-for-mac/
여기까지 정상적으로 잘 되었다면 이제 python에서 이 서버를 호출하여서 REST API로 TF서빙에 쿼리를 날려볼 수 있다.
4. REST API로 TF 서빙에 쿼리하기
1) client로 부터 파일전송된 이미지로 먼저 쿼리를 위한 JSON 데이터를 만든다.
import json
import requests
def prepare_image(image, target):
if image.mode != "RGB":
image = image.convert("RGB")
image = image.resize(target)
image = img_to_array(image)
image = np.expand_dims(image, axis=0)
return image
image = request.files["image"].read()
image = Image.open(io.BytesIO(image))
image = prepare_image(image, target=(224, 224))
input_data_json = json.dumps({
"signature_name": "serving_default", #호출할 함수 시그니처의 이름
"instances": image.tolist() #입력 데이터 (JSON 포멧이므로 numpy 배열을 리스트형식으로 변환해야 한다. tolist()를 해주지 않으면 Object of type 'ndarray' is not JSON serializable 라는 에러가 발생)
})
2) 이제 이 input_data_json을 HTTP POST 메서드로 TF서빙에 전송한다.
SERVER_URL = 'http://localhost:8501/v1/models/medisharp_pill_image_model:predict'
response = requests.post(SERVER_URL, data=input_data_json)
response.raise_for_status() #에러 발생시 예외 발생시킴
response = response.json()
print("response is: ", response)
"""
출력결과는 이런 식으로, 응답(response)은 "predictions" 키 하나를 가진 딕셔너리로, 이 키에 해당하는 값은 예측의 리스트이다.
response is: {'predictions': [[0.00994166359, 0.00910509098,
...
]}
"""
응답은 "predictions" 키 하나를 가진 딕셔너리로, 이 키에 해당하는 값은 예측의 리스트이다.
3) 그리고 이 중 예측확률이 가장 큰 것으로 client로 결과를 응답해준다.
output_data = response["predictions"]
pred_class = np.argmax(output_data, axis=-1)
prediction_result = class_list[int(pred_class)]
print("prediction: ", class_list[int(pred_class)])
response_object = {
'status': 'OK',
'message': 'Successfully predict image class.',
'prediction': prediction_result
}
return response_object, 200
4) postman으로 테스트해 보면~!!
요청에 따른 결과를 잘 응답해준다! tf서빙 성공!
전체 코드는 <더보기> 클릭!
@api.route('/image')
class PredictMedicineName(Resource):
def post(self):
"""카메라로 촬영한 이미지를 서버로 보내오고, 학습된 모델에서 예측결과를 client에게 전달해주는 API"""
try:
class_list = get_class_list()
try:
token = request.headers.get('Authorization')
decoded_token = jwt.decode(token, jwt_key, jwt_alg)
user_id = decoded_token['id']
if decoded_token:
file = request.files['image']
if file.filename == '':
response_object = {
'status': 'Bad Request',
'message': 'No Selected File.',
}
return response_object, 400
elif file and file.filename:
#Ready for the Data
image = request.files["image"].read()
image = Image.open(io.BytesIO(image))
image = prepare_image(image, target=(224, 224))
# input
input_data_json = json.dumps({
"signature_name": "serving_default",
"instances": image.tolist()
})
SERVER_URL = 'http://localhost:8501/v1/models/medisharp_pill_image_model:predict'
response = requests.post(SERVER_URL, data=input_data_json)
response.raise_for_status() #에러 발생시 예외 발생시킴
response = response.json() #응답은 "predictions" 키 하나를 가진 딕셔너리로, 이 키에 해당하는 값은 예측의 리스트
output_data = response["predictions"]
pred_class = np.argmax(output_data, axis=-1)
prediction_result = class_list[int(pred_class)]
print("prediction: ", class_list[int(pred_class)])
response_object = {
'status': 'OK',
'message': 'Successfully predict image class.',
'prediction': prediction_result
}
return response_object, 200
except Exception as e:
print("predict pill name 401 error: ", e)
response_object = {
'status': 'fail',
'message': 'Provide a valid auth token.',
}
return response_object, 401
except Exception as e:
response_object = {
'status': 'Internal Server Error',
'message': 'Some Internal Server Error occurred.',
}
return response_object, 500
이제 기존에 서버 코드에 직접 tflite모델을 저장하고, 그걸 서버 시작때마다 load()해오는 코드(아래 코드)가 없어도 된다 ㅎㅎ.
#Load TFLite model and allocate tensors.
# def load_model():
# global interpreter
# currdir = os.getcwd()
# print("currdir: ", currdir)
# modeldir = os.path.join(currdir+"/cnn/model/medisharp_tflite_model.tflite")
# interpreter = tf.lite.Interpreter(model_path=modeldir)
# print(("* Loading Keras model and Flask starting server..."
# "please wait until server has fully started"))
# load_model()
물론 배포환경에서 확인을 해봐야겠지만 말이다. :))
또 대량의 데이터를 전송할 때는 gRPC API를 사용하는게 훨씬 좋다고 한다.
Image Classification on Tensorflow Serving with gRPC or REST Call for Inference
을 참고해서 이 역시 시도해 보면 좋겠다.
reference
핸즈온 머신러닝 개정2판 19장 대규모 텐서플로 모델 훈련과 배포
How to Serve Machine Learning Models with TensorFlow Serving and Docker
'Project > [약올림] Final Project' 카테고리의 다른 글
[Milestone 그 이후] Hosting the Docker container on Heroku for TFserving (0) | 2021.01.28 |
---|---|
[Milestone 그 이후] Docker 개념 잡기 및 Quick Start (0) | 2021.01.22 |
💊 약올림 모바일 앱 서비스 README ⏰ (2) | 2021.01.22 |
[Milestone Week 4(마지막 마무리)] Push Notification/개인정보 관리/로그아웃 (2) | 2021.01.22 |
[Milestone Week 3] 복약 정보 제공 및 관리 기능 (0) | 2021.01.22 |