Git Product home page Git Product logo

heojae / foodimagerotationadmin Goto Github PK

View Code? Open in Web Editor NEW
0.0 2.0 0.0 47.39 MB

Food Image Rotation (음식이미지 회전) 이라는 주제에 대해서. 실제로 딥러닝(deeplearning)을 어떻게 도입하고, 이를 API(backend)로서 서버에 올리며, 웹(frontend) 를 통해서 올리는 과정을 구현하기 위해서 만든 프로젝트입니다.

Python 51.45% Cuda 13.66% C++ 21.79% Shell 1.31% JavaScript 10.84% HTML 0.21% CSS 0.66% Dockerfile 0.08%

foodimagerotationadmin's Introduction

Food Image Rotation Admin

이 레포의 목적은 Food Image Rotation(음식이미지 회전)이라는 주제에 대해서.

실제로 딥러닝(deeplearning)을 어떻게 도입하고, 이를 API(backend)로서 서버에 올리며, 웹(frontend) 를 통해서 올리는 과정을 구현하기 위해서 만든 프로젝트입니다.

Food Image Rotation(음식이미지 회전) 이라는 API 를 만들고, 이 API관리자(Admin) 가 웹을 통해서 관리하는 것을 구현할 것입니다.

초기 시안 기획 및 디자인 링크

결과물 시연 영상, Docker Version 시연 영상

run_demo_check_result


기술 정리 및 관련 요약

  • pytorch 를 통해서, Food Image Rotation 이라는 주제에 대해서, 딥러닝 학습한 것을 정리한 것입니다.

    다양한 환경에서, 학습을 하는 방법을 정리하여 두었습니다.

    deeplearning_flow

  • GRPCpython, Database 을 통해서, MSA(micro service architecture) 구조 형태이며, 서로 통신하는 형태로 구현되어 있습니다.

    backend_flow

  • GRPC-WEB,React, Redux ,antd 등을 활용하여, 웹 페이지가 구현되어 있습니다.

    frontend_flow

  • grpc 를 통해서, backendfrontend 가 서로 통신하기 위해서, 정의된 .proto 파일들입니다.

    protobuf 를 통한 통신 정리


시연 해보기

결과물 시연 영상, Docker Version 시연 영상

backendfrontend 를 통해 구현된 부분들을 docker compose up 들을 통해서, 실제로 돌릴 수 있습니다.

총 2개의 terminal 을 열고, 아래 명령어를 적어주시면, 됩니다.

그 뒤, localhost:3000/ 에 접속하여서, 확인을 해보시면 됩니다.

# /backend
docker-compose up  # -d # back ground 에서 돌리고 싶을 경우 

# /frontend
docker-compose up  # -d # back ground 에서 돌리고 싶을 경우

아래 gif 는 위의 경우를 시연하였을 때를 기록한 시연 영상입니다.

docker_wait_for_it

backend 에서 api_dl server 까지 다 완료되었을 때, 그 때, 실행을 시키면 됩니다.


Git Flow 를 통한 Branch 관리

https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow

https://gmlwjd9405.github.io/2018/05/11/types-of-git-branch.html

이 레포는 git-flow 를 활용하여서, branch 관리가 되어있습니다.

master, develop, feature, release, hotfix 등으로 관리되어 있습니다.

추후 관련 작업을 하게 된다면, 참고해주세요.

gir_flow

foodimagerotationadmin's People

Contributors

heojae avatar

Watchers

 avatar  avatar

foodimagerotationadmin's Issues

[DeepLearning] 데이터 전처리 및 생성방법

대주제 : 딥러닝 모델의 학습을 하고싶다.

소주제 : 데이터의 전처리 방법과 실행방법을 정리하고 싶다.

데이터 전처리 및 생성 방법 정리

1. 음식 이미지 모으기

데이터를 모으기 위해서는, 2가지 방법이 있습니다.

  • Crawler
  • 수 작업 (노가다)

예전에, Crawler 를 통해서, 데이터를 수집을 하였으나, (crawler.py 를 약 1년전에 구현을 했기에, 현재에는 쓰시는 것을 추천 드리지 않습니다.)

올바르지 못한 이미지의 비율이 많았기에(제가 정의를 한 클래스의 기준과 데이터 셋의 기준, 데이터의 분류와 클래스 선정의 이유 정리 )

손으로 하는 것이 더 빠르기에, 직접 손으로 모았습니다.

0도를 기준으로 한 폴더에(before_rotate) 에 모았습니다.

  • before_rotate(폴더 내부)

    아래와 같이, 제가 정의해놓은 분류에 맞는 데이터를 모았습니다.

    466개의 데이터를 구할 수 있었으며, 아래와 같이 생겼습니다.

접기/펼치기 버튼

스크린샷 2021-02-07 오전 10 38 27

2. 전처리 코드 실행시키기

deeplearning/preprocess_data 폴더를 보시면, 총 3가지 python 파일이 있습니다.

  1. rename.py

    사실 안해도 괜찮은 것이긴 하지만, 좀 더 간편하게 데이터에 대한 접근과 한글 naming 파일들을 가능한 쓰지 않기위해서, 다음과 같이 변형하였습니다.

    • 추후 리눅스 서버로 옮길 때, 한글의 경우 파일명이 깨질수 있기 때문에 이를 변형하는 것을 추천드립니다.
  2. make_rotated_images.py

    위에서, rename 된 이미지 파일들을 0, 90, 180, 270 도로 회전을 시키는 작업입니다.

    Image_size = 224 로서, efficient-b0 에 넣기위해서, 다음과 같이 바꾸었으며,

    추후 다른 모델들을 활용하고 싶으시다면, 아래를 참고해서, 작업해주시기를 바랍니다.

    https://github.com/lukemelas/EfficientNet-PyTorch/blob/master/efficientnet_pytorch/utils.py

접기/펼치기 버튼

스크린샷 2021-02-07 오전 10 58 06

폴더구조

폴더 구조는 아래와 같습니다.

data
	- after_rotate
		- train
			- degree_0
				- food1.jpg
				- food2.jpg
				- ...
			- degree_90
			- degree_180
			- degree_270
		- test
			- degree_0
			- degree_90
			- degree_180
			- degree_270
      
	- before_rotate
		- food1.jpg
		- food2.jpg
		- ...

before_rotate 에 있는 파일들을 아래와 같이 전처리 작업을 합니다.

접기/펼치기 버튼
0 90 180 270
  1. make_csv.py

    위 폴더 구조를 기반으로, 추후 DatasetDataLoader 를 통한 접근을 편리화 하기위해서, csv 파일로 만든다.

    train.csv

    train_or_test,degree_dir_name,image_name
    train,degree_270,1.jpg
    train,degree_90,1.jpg
    train,degree_0,1.jpg
    train,degree_180,1.jpg
    train,degree_270,10.jpg
    train,degree_90,10.jpg
    train,degree_0,10.jpg
    train,degree_180,10.jpg
    ...
  2. run_preprocess.sh

    위 파일들을 실행시키기 위한, 인자들을 간편하게 실행시키기 위해서 필요한 인자들을 넣어두었습니다.

    간편하게,

    # sh run_preprocess.sh
    python rename.py --dir_path ../data/before_rotate
    python make_rotated_images.py --before_dir_path ../data/before_rotate --save_dir_path ../data/after_rotate
    python make_csv.py --after_rotate_dir_path ../data/after_rotate \
                       --save_train_csv_path ../train.csv \
                       --save_test_csv_path ../test.csv \

    간편하게, 아래와 같이 실행시킬수 있습니다. 만약 폴더의 위치를 바꾸고 싶으시다면, 저 인자들을 수정하시길 바랍니다.

    sh run_preprocess.sh

[Backend] DL server 설계 및 구상도

대주제 : dl server 의 설계와 각각의 API 들의 설정이유들을 명시하고 싶다.

소주제 : multi threading 으로 서버를 정의한 이유 와 활용한 각 라이브러리들을 정리하고 싶다.

참고 이슈

User Server 설계 및 구상도

model version server 설계 및 구상도


API 설명

이 서버의 경우, 제공하고자 하는 api 는 아래 2가지 입니다.

서버를 시작할 때, 현재 사용중인 모델을 api_model_version GetUsingModelVersion 을 통해서, 현재 사용중인 모델의 정보를 미리 들고 와서, 모델을 Load 합니다.

AuthenticateUserAccessToken 미들 웨어
  • 유저 확인 (access_token)

    api_user 서버에 있는 Authenticate 에 요청을 보내서, access_token 을 기반으로,

    middlewate(intercepter) 로서, AuthenticateUserAccessToken(grpc.aio.ServerInterceptor) 을 통해서,

    미리 유저를 인증하는 형태로 구현을 해두었습니다.


Multi-Threading 을 선택한 이유.

우리가 흔히, python 에서는 GIL 때문에, multi-threadingmulti-processing 보다 느리다고 생각을 많이 합니다.

하지만, torch, numpy 라이브러리의 경우 C or C++ 단으로 내려가 연산을 진행을 하기 때문에,

흔히들 생각하는 것만큼 Multi-threadingMulti-Processing 보다 못하지 않고, 역으로 좋은 성과를 내는 것을 확인을 할 수 있었습니다.

예전에 인턴을 통한 경험에서, 한번 부딪혀 보았던 문제이고,

요기에서는 구현 하지는 않았지만, 예전에는 ABtool(Apache Benchmark tool) 을 통해, 1만개의 Request 를 경우들 마다 보내보면서,

그 결과를 수치로 정리를 하면서, 확인을 할 수 있었습니다.

model 한 번 호출을 하고, Heap 에 올린 상태에서, global model 을 통해서, 각각의 request 를 처리하는 것이, 용량적으로도 더 효율적으로 사용할 수 있을 것이므로, 충분히 좋은 결과를 낼 수 있을 것입니다.

만약, multi processing 을 해야한다면, 각각의 프로세스 마다, 모델을 정의해서 모델 파라미터를 로드 하지말고,

model 은 한번만 로드하여, 각 프로세스 공통으로 사용할 수 있도록 설계해야합니다.

참고 자료:

https://discuss.pytorch.org/t/can-pytorch-by-pass-python-gil/55498

https://www.youtube.com/watch?v=m2yeB94CxVQ&list=LL&index=2&t=48s

필요한 부분(model weight)만 서버에 올려야한다.

또한, .tar 형태로 모델을 정리할 경우, 간편할 수도 있지만, 이렇게 할 경우, 서버에 불필요한 메모리를 올릴 수도 있으므로,

extract_model_weight.py 를 통해서, model_weight parameter 를 추출을 해내어서, .pth, 필요한 부분의 파일(model weight) 만 서버에 로드합니다.

만약, 실제로 이 모델을 배포를 하게 된다면

AWSEC2t2.small(vCPUs:1, 메모리: 2GiB) instance 를 여러 개 만들어서, 개수를 증설을 하는 것이.

서버 한개의 스펙을 높이는 것보다 더 좋은 성능을 낼 수 있을 것입니다.

아마 비용적으로도, 더 좋은 결과를 낼 수 있을 것입니다.


Pytorch 에서 모델을 서버로 올릴 때, 유의해야하는 점

  • model.eval()

    model.eval() will notify all your layers that you are in eval mode, that way, batchnorm or dropout layers will work in eval mode instead of training mode.

    eval 모드에서는 dropout은 비활성화, 배치 정규화는 학습에서 저장된 파라미터를 사용

    model = load_model_weight_from_pth(model, model_file_name=model_file_name)
    model.eval()
  • with torch.no_grad()

    impacts the autograd engine and deactivate it. It will reduce memory usage and speed up computations but you won’t be able to backprop (which you don’t want in an eval script).

    오차 역전파에 사용하는 계산량을 줄여서 처리 속도를 높인다.

    with torch.no_grad():
        output = model(tensor_image)  # [1, 4], inference_time : 0.20484089851379395

https://discuss.pytorch.org/t/model-eval-vs-with-torch-no-grad/19615/5

with torch.no_grad() 자세히 이해하기

network train and eval

스크린샷 2021-02-12 오후 7 30 38
스크린샷 2021-02-12 오후 7 32 42


GRPC 에서, 이미지 Request and Response 정리해보기

제가 생각하기에는 이미지를 전송하는 방법은 총 2가지가 있습니다.

  1. Bytes 형태로 전송하는 방법
  2. string 으로 base64 Encoding 해서 전송하는 방법

물론, 1번이 웬만하면 시간과 메모리 측면에서 더 효율적이나, 2번도 가끔식 쓰이기 때문에,

2가지 방법을 다 정리하는게 도움이 될거 같아서, 정리하려고 합니다.

  1. Bytes 형태로 전송하는 방법

아래와 같이, pil image 를 BytesIO 에 담아서, 이를 pil -> bytes 로 변환하여, 전송하는 방법입니다.

client.py

image: PIL.Image.Image = Image.open("./sample/sample1.jpg")
image_file: BytesIO = BytesIO()
image.save(image_file, format="PNG")
image_bytes: bytes = image_file.getvalue()

# -----------------------------------------------------
# 아래 방식으로도 쓸 수 있습니다. 
image_bytes: bytes = open("./sample/sample1.jpg", "rb").read()
  
with open("./sample/sample1.jpg", "rb") as imageFile:
  image_bytes: bytes = imageFile.read()

server.py

client 에서 온 정보를 그대로, 다시, Image 로 만들어서 활용하시면 됩니다.

def convert_bytes_image2pil_image(bytes_image_content: bytes) -> PIL.Image.Image:
    image_file: BytesIO = BytesIO(bytes_image_content)
    image: PIL.Image.Image = Image.open(image_file)
    return image


def save_convert_bytes_image2pil_image(bytes_image_content: bytes, save_file_name="check.jpg") -> None:
    image_file: BytesIO = BytesIO(bytes_image_content)
    with open(save_file_name, "wb") as f:
        f.write(image_file.getbuffer())
  1. string 으로 base64 Encoding 해서 전송하는 방법

아래 처럼 base64 encoding decoding 을 활용해서, bytes -> string 으로 변환시켜서 구현할 수 있다.

하지만, 이를 변환하는 시간과 추가되는 용량(약 4/3 배 증가한다고 한다.) 을 고려한다면, 1번처럼 바로 보내는 방법이 더 cost 가 낮은 방법이다.

단지 Base64의 용도는 바이너리 파일들을 아스키코드로 표현할 수 있는 문자로 변환하는 것이 주 목적

client.py

image: PIL.Image.Image = Image.new('RGB', (224, 224), (127, 127, 127))
image_file: BytesIO = BytesIO()
image.save(image_file, format="PNG")
image_bytes: bytes = image_file.getvalue() 
b64image: str = base64.b64encode(image_bytes)
  

# -----------------------------------------------------
# 아래 방식으로도 쓸 수 있습니다. 
image_bytes: bytes = open("./sample/sample1.jpg", "rb").read()
  
with open("./sample/sample1.jpg", "rb") as imageFile:
  image_bytes: bytes = imageFile.read()

server.py

def convert_b64image2pil_image(b64image: str) -> PIL.Image.Image:
    image_bytes: bytes = base64.b64decode(b64image)
    image_file: BytesIO = BytesIO(image_bytes)

    image: PIL.Image.Image = Image.open(image_file)
    return image


def save_convert_b64image2pil_image(b64image: str, save_file_name="check.jpg") -> None:
    image_bytes: bytes = base64.b64decode(b64image)
    image_file: BytesIO = BytesIO(image_bytes)

    with open(save_file_name, "wb") as f:
        f.write(image_file.getbuffer())

참고자료

https://hyoje420.tistory.com/1

https://ghdwn0217.tistory.com/76

[DeepLearning] cpu, single gpu, multi gpu(data parallel, custom data parallel, distributed parallel, apex) 를 통한 학습

대주제 : 다양한 경우의 환경에서, 학습을 돌릴 수 있는 방법을 정리하고 싶다.

소주제 : cpu, single gpu, multi gpu(data parallel, custom data parallel, distributed parallel, apex) 의 각각의 환경에서, 학습을 돌리는 방법을 정리하고 싶다. + 각각의 GPU 상에 잡히는 메모리들을 실험하여 확인해보고 싶다.

작성 계기

당근 마켓, pytorch multi gpu 학습 제대로 하기

이 글을 읽고, 단순히, 학습코드를 구현하는 것보다는 좀 더 다양한 경우에 맞게 코드를 작성하는 것이 맞다고 생각하여, 시도하게 되었습니다.

또한, Multi GPU 를 다루는 방법에 대해서, 더 공부를 하고 싶었고,

이번 기회를 통해서, pytorchmulti gpu 를 다루는 방법에 대해서, 좀 더 깊게 이해할 수 있는 시간이 되었습니다.


참고자료

https://pytorch.org/tutorials/beginner/blitz/data_parallel_tutorial.html

https://pytorch.org/tutorials/intermediate/dist_tuto.html

https://pytorch.org/tutorials/intermediate/ddp_tutorial.html

참고 gihub

https://github.com/zhanghang1989/PyTorch-Encoding/blob/master/encoding/parallel.py

https://github.com/pytorch/examples/blob/master/imagenet/main.py

https://github.com/NVIDIA/apex/blob/master/examples/imagenet/main_amp.py

추천자료

https://yangkky.github.io/2019/07/08/distributed-pytorch-tutorial.html

Paper(좀더 심화적으로 이해하고 싶을 경우, 읽어보세요.)

https://arxiv.org/pdf/2006.15704.pdf


Train Acc 와 Test Acc 그리고 Batch Size 실험

cpu - only

  • batch_size =1

    아래와 같이, 학습의 속도가 더딘 것을 확인할 수 있으며,

    5 epoch 가 지난후,

    train_acc99 ~ 100에 가까이 수렴한 것에 비해,

    test_acc58-61 에 머물러 있는 것을 확인할 수 있었습니다.

분석

이에 대해서 분석해보았을 때, local minimum 에 빠졌다고, 판단할 수 있었습니다.

batch size =1 이기 때문에, 이미지 input 한개 한개 에 대해서, 많은 영향을 받을 수 밖에 없으며,

또한, 이와 같은 환경에서, 여러번 실험 해보았으나, 각 epoch 마다, accuracy 가 10-20% 이상 증가하고, 감소 하는것으로 보아

local minimum 에 빠져, 위와 같은 결과가 나온것이라 판단합니다.

접기/펼치기 버튼

스크린샷 2021-02-09 오후 11 00 38

Loaded pretrained weights for efficientnet-b0
Epoch: [0][   0/1596]	Time  0.281 ( 0.281)	Data  0.000 ( 0.000)	Loss 1.4905e+00 (1.4905e+00)	Acc@1   0.00 (  0.00)
Epoch: [0][ 200/1596]	Time  0.203 ( 0.181)	Data  0.000 ( 0.000)	Loss 1.3444e+00 (1.3959e+00)	Acc@1 100.00 ( 24.38)
Epoch: [0][ 400/1596]	Time  0.169 ( 0.181)	Data  0.000 ( 0.000)	Loss 1.1246e+00 (1.3934e+00)	Acc@1 100.00 ( 25.44)
Epoch: [0][ 600/1596]	Time  0.199 ( 0.180)	Data  0.000 ( 0.000)	Loss 1.2768e+00 (1.3892e+00)	Acc@1 100.00 ( 25.79)
Epoch: [0][ 800/1596]	Time  0.159 ( 0.180)	Data  0.000 ( 0.000)	Loss 1.3381e+00 (1.3823e+00)	Acc@1   0.00 ( 26.72)
Epoch: [0][1000/1596]	Time  0.160 ( 0.180)	Data  0.000 ( 0.000)	Loss 1.1534e+00 (1.3685e+00)	Acc@1 100.00 ( 29.97)
Epoch: [0][1200/1596]	Time  0.179 ( 0.180)	Data  0.000 ( 0.000)	Loss 7.6266e-01 (1.3463e+00)	Acc@1 100.00 ( 33.81)
Epoch: [0][1400/1596]	Time  0.190 ( 0.181)	Data  0.000 ( 0.000)	Loss 1.4622e+00 (1.3193e+00)	Acc@1   0.00 ( 36.83)
Test: [  0/184]	Time  0.089 ( 0.089)	Loss 1.2842e+00 (1.2842e+00)	Acc@1   0.00 (  0.00)
 * Acc@1 51.630
Epoch: [1][   0/1596]	Time  0.281 ( 0.281)	Data  0.000 ( 0.000)	Loss 2.7238e+00 (2.7238e+00)	Acc@1   0.00 (  0.00)
Epoch: [1][ 200/1596]	Time  0.173 ( 0.182)	Data  0.000 ( 0.000)	Loss 6.4961e-01 (7.2235e-01)	Acc@1 100.00 ( 79.10)
Epoch: [1][ 400/1596]	Time  0.174 ( 0.182)	Data  0.000 ( 0.000)	Loss 3.2509e-01 (6.6445e-01)	Acc@1 100.00 ( 81.80)
Epoch: [1][ 600/1596]	Time  0.206 ( 0.182)	Data  0.000 ( 0.000)	Loss 6.6027e-02 (6.1535e-01)	Acc@1 100.00 ( 83.19)
Epoch: [1][ 800/1596]	Time  0.182 ( 0.181)	Data  0.000 ( 0.000)	Loss 4.2142e-01 (5.5878e-01)	Acc@1 100.00 ( 84.52)
Epoch: [1][1000/1596]	Time  0.180 ( 0.181)	Data  0.000 ( 0.000)	Loss 6.2518e-01 (5.2224e-01)	Acc@1 100.00 ( 85.51)
Epoch: [1][1200/1596]	Time  0.175 ( 0.181)	Data  0.000 ( 0.000)	Loss 4.2768e-02 (5.0426e-01)	Acc@1 100.00 ( 85.93)
Epoch: [1][1400/1596]	Time  0.194 ( 0.181)	Data  0.000 ( 0.000)	Loss 6.1021e-02 (4.9043e-01)	Acc@1 100.00 ( 85.94)
Test: [  0/184]	Time  0.096 ( 0.096)	Loss 1.5377e-01 (1.5377e-01)	Acc@1 100.00 (100.00)
 * Acc@1 57.065
Epoch: [2][   0/1596]	Time  0.277 ( 0.277)	Data  0.000 ( 0.000)	Loss 7.8205e-02 (7.8205e-02)	Acc@1 100.00 (100.00)
Epoch: [2][ 200/1596]	Time  0.206 ( 0.183)	Data  0.000 ( 0.000)	Loss 1.6911e-01 (1.5629e-01)	Acc@1 100.00 ( 97.01)
Epoch: [2][ 400/1596]	Time  0.186 ( 0.183)	Data  0.000 ( 0.000)	Loss 4.7998e-02 (2.1530e-01)	Acc@1 100.00 ( 94.01)
Epoch: [2][ 600/1596]	Time  0.186 ( 0.183)	Data  0.000 ( 0.000)	Loss 1.1497e-02 (1.9376e-01)	Acc@1 100.00 ( 94.34)
Epoch: [2][ 800/1596]	Time  0.164 ( 0.183)	Data  0.000 ( 0.000)	Loss 9.5126e-03 (1.7779e-01)	Acc@1 100.00 ( 95.01)
Epoch: [2][1000/1596]	Time  0.183 ( 0.184)	Data  0.000 ( 0.000)	Loss 1.4673e-02 (1.7213e-01)	Acc@1 100.00 ( 94.91)
Epoch: [2][1200/1596]	Time  0.192 ( 0.184)	Data  0.000 ( 0.000)	Loss 8.1333e-02 (1.6600e-01)	Acc@1 100.00 ( 95.25)
Epoch: [2][1400/1596]	Time  0.197 ( 0.183)	Data  0.000 ( 0.000)	Loss 1.3153e-01 (1.8279e-01)	Acc@1 100.00 ( 94.93)
Test: [  0/184]	Time  0.101 ( 0.101)	Loss 1.5524e-02 (1.5524e-02)	Acc@1 100.00 (100.00)
 * Acc@1 58.696
Epoch: [3][   0/1596]	Time  0.281 ( 0.281)	Data  0.000 ( 0.000)	Loss 8.8166e-01 (8.8166e-01)	Acc@1   0.00 (  0.00)
Epoch: [3][ 200/1596]	Time  0.166 ( 0.180)	Data  0.000 ( 0.000)	Loss 2.4514e-03 (1.0979e-01)	Acc@1 100.00 ( 96.52)
Epoch: [3][ 400/1596]	Time  0.166 ( 0.181)	Data  0.000 ( 0.000)	Loss 8.4549e-02 (9.7078e-02)	Acc@1 100.00 ( 96.76)
Epoch: [3][ 600/1596]	Time  0.158 ( 0.181)	Data  0.000 ( 0.000)	Loss 4.6538e-02 (1.0783e-01)	Acc@1 100.00 ( 96.34)
Epoch: [3][ 800/1596]	Time  0.194 ( 0.181)	Data  0.000 ( 0.000)	Loss 2.5155e-03 (1.0136e-01)	Acc@1 100.00 ( 96.88)
Epoch: [3][1000/1596]	Time  0.170 ( 0.181)	Data  0.000 ( 0.000)	Loss 8.7595e-03 (9.6627e-02)	Acc@1 100.00 ( 97.00)
Epoch: [3][1200/1596]	Time  0.159 ( 0.181)	Data  0.000 ( 0.000)	Loss 2.9581e-01 (1.0998e-01)	Acc@1 100.00 ( 96.42)
Epoch: [3][1400/1596]	Time  0.196 ( 0.181)	Data  0.000 ( 0.000)	Loss 8.5525e-02 (1.0266e-01)	Acc@1 100.00 ( 96.79)
Test: [  0/184]	Time  0.088 ( 0.088)	Loss 1.2429e-01 (1.2429e-01)	Acc@1 100.00 (100.00)
 * Acc@1 57.065
Epoch: [4][   0/1596]	Time  0.285 ( 0.285)	Data  0.000 ( 0.000)	Loss 7.8266e-04 (7.8266e-04)	Acc@1 100.00 (100.00)
Epoch: [4][ 200/1596]	Time  0.158 ( 0.183)	Data  0.000 ( 0.000)	Loss 3.1997e-02 (6.3708e-02)	Acc@1 100.00 ( 99.00)
Epoch: [4][ 400/1596]	Time  0.178 ( 0.182)	Data  0.000 ( 0.000)	Loss 1.6696e-03 (7.3866e-02)	Acc@1 100.00 ( 98.00)
Epoch: [4][ 600/1596]	Time  0.190 ( 0.182)	Data  0.000 ( 0.000)	Loss 3.9160e-03 (6.4712e-02)	Acc@1 100.00 ( 98.34)
Epoch: [4][ 800/1596]	Time  0.180 ( 0.182)	Data  0.000 ( 0.000)	Loss 9.8097e-04 (6.5225e-02)	Acc@1 100.00 ( 98.25)
Epoch: [4][1000/1596]	Time  0.208 ( 0.181)	Data  0.000 ( 0.000)	Loss 9.1725e-04 (6.1493e-02)	Acc@1 100.00 ( 98.40)
Epoch: [4][1200/1596]	Time  0.200 ( 0.181)	Data  0.000 ( 0.000)	Loss 2.0247e-02 (6.2357e-02)	Acc@1 100.00 ( 98.25)
Epoch: [4][1400/1596]	Time  0.162 ( 0.181)	Data  0.000 ( 0.000)	Loss 9.7323e-04 (6.9955e-02)	Acc@1 100.00 ( 97.72)
Test: [  0/184]	Time  0.098 ( 0.098)	Loss 4.3770e-02 (4.3770e-02)	Acc@1 100.00 (100.00)
 * Acc@1 48.913
Epoch: [5][   0/1596]	Time  0.284 ( 0.284)	Data  0.000 ( 0.000)	Loss 2.0039e-03 (2.0039e-03)	Acc@1 100.00 (100.00)
Epoch: [5][ 200/1596]	Time  0.188 ( 0.180)	Data  0.000 ( 0.000)	Loss 2.7880e-03 (2.5582e-02)	Acc@1 100.00 ( 99.00)
Epoch: [5][ 400/1596]	Time  0.176 ( 0.180)	Data  0.000 ( 0.000)	Loss 9.5200e-03 (2.9331e-02)	Acc@1 100.00 ( 99.00)
Epoch: [5][ 600/1596]	Time  0.164 ( 0.180)	Data  0.000 ( 0.000)	Loss 6.2982e-04 (3.2622e-02)	Acc@1 100.00 ( 99.17)
Epoch: [5][ 800/1596]	Time  0.159 ( 0.180)	Data  0.000 ( 0.000)	Loss 1.1086e-01 (3.5885e-02)	Acc@1 100.00 ( 99.25)
Epoch: [5][1000/1596]	Time  0.175 ( 0.180)	Data  0.000 ( 0.000)	Loss 2.7320e-03 (4.1057e-02)	Acc@1 100.00 ( 99.00)
Epoch: [5][1200/1596]	Time  0.160 ( 0.180)	Data  0.000 ( 0.000)	Loss 3.0799e-04 (5.5722e-02)	Acc@1 100.00 ( 98.50)
Epoch: [5][1400/1596]	Time  0.166 ( 0.180)	Data  0.000 ( 0.000)	Loss 2.1393e-02 (6.7442e-02)	Acc@1 100.00 ( 98.07)
Test: [  0/184]	Time  0.103 ( 0.103)	Loss 3.9239e-02 (3.9239e-02)	Acc@1 100.00 (100.00)
 * Acc@1 61.957

  • batch_size = 16

    batch_size=1 과는 다르게, 빠른 속도로 학습이 되어가는 것을 확인할 수 있었습니다.

    5 epoch 를 학습하였지만,

    train_acc95 ~ 96에 가까이 수렴하였고,

    test_acc96~97 가까이 올라간 것을 확인할 수 있었습니다.

접기/펼치기 버튼

스크린샷 2021-02-09 오후 11 05 33

Loaded pretrained weights for efficientnet-b0
Epoch: [0][  0/100]	Time  1.444 ( 1.444)	Data  0.000 ( 0.000)	Loss 1.4199e+00 (1.4199e+00)	Acc@1  18.75 ( 18.75)
Epoch: [0][ 50/100]	Time  1.174 ( 1.123)	Data  0.000 ( 0.000)	Loss 1.2743e+00 (1.3255e+00)	Acc@1  43.75 ( 41.05)
Test: [ 0/12]	Time  0.564 ( 0.564)	Loss 8.6686e-01 (8.6686e-01)	Acc@1  68.75 ( 68.75)
 * Acc@1 69.022
Epoch: [1][  0/100]	Time  1.677 ( 1.677)	Data  0.000 ( 0.000)	Loss 8.6776e-01 (8.6776e-01)	Acc@1  81.25 ( 81.25)
Epoch: [1][ 50/100]	Time  1.143 ( 1.115)	Data  0.000 ( 0.000)	Loss 5.6067e-01 (7.1666e-01)	Acc@1  81.25 ( 73.41)
Test: [ 0/12]	Time  0.598 ( 0.598)	Loss 4.9185e-01 (4.9185e-01)	Acc@1  81.25 ( 81.25)
 * Acc@1 81.522
Epoch: [2][  0/100]	Time  1.692 ( 1.692)	Data  0.000 ( 0.000)	Loss 3.8097e-01 (3.8097e-01)	Acc@1  87.50 ( 87.50)
Epoch: [2][ 50/100]	Time  1.118 ( 1.122)	Data  0.000 ( 0.000)	Loss 3.8521e-01 (4.6015e-01)	Acc@1  87.50 ( 82.60)
Test: [ 0/12]	Time  0.614 ( 0.614)	Loss 3.4018e-01 (3.4018e-01)	Acc@1  81.25 ( 81.25)
 * Acc@1 86.957
Epoch: [3][  0/100]	Time  1.736 ( 1.736)	Data  0.000 ( 0.000)	Loss 3.0147e-01 (3.0147e-01)	Acc@1  81.25 ( 81.25)
Epoch: [3][ 50/100]	Time  1.133 ( 1.150)	Data  0.000 ( 0.000)	Loss 2.3493e-01 (2.7778e-01)	Acc@1  93.75 ( 91.30)
Test: [ 0/12]	Time  0.617 ( 0.617)	Loss 2.4183e-01 (2.4183e-01)	Acc@1  93.75 ( 93.75)
 * Acc@1 96.196
Epoch: [4][  0/100]	Time  1.718 ( 1.718)	Data  0.000 ( 0.000)	Loss 2.1611e-01 (2.1611e-01)	Acc@1  93.75 ( 93.75)
Epoch: [4][ 50/100]	Time  1.141 ( 1.155)	Data  0.000 ( 0.000)	Loss 1.3752e-01 (1.6329e-01)	Acc@1 100.00 ( 94.85)
Test: [ 0/12]	Time  0.609 ( 0.609)	Loss 2.2088e-01 (2.2088e-01)	Acc@1  93.75 ( 93.75)
 * Acc@1 96.739
Epoch: [5][  0/100]	Time  1.724 ( 1.724)	Data  0.000 ( 0.000)	Loss 1.6129e-01 (1.6129e-01)	Acc@1  93.75 ( 93.75)
Epoch: [5][ 50/100]	Time  1.146 ( 1.158)	Data  0.000 ( 0.000)	Loss 9.8415e-02 (1.5447e-01)	Acc@1 100.00 ( 95.22)
Test: [ 0/12]	Time  0.620 ( 0.620)	Loss 1.5108e-01 (1.5108e-01)	Acc@1  93.75 ( 93.75)
 * Acc@1 96.196

single-gpu

  • batch_size = 32, epoch =20

    아래와 같이, test_accuracy 가 이미 충분히 수렴을 한것을 확인할 수 있었습니다.

    train_acc99 에 가까이 수렴하는 등, noise 를 고려한다면, 이미 충분히 수렴을 하였고,

    test_acc99.457 에 수렴한 것으로 보아, 더 이상 학습은 무의미하다고 판단을 하였기에

    앞으로는, GPU Memory 할당을 중점적으로 살펴보겠습니다.
접기/펼치기 버튼

스크린샷 2021-02-09 오후 11 23 36

[release 0.1] 결과물 시연 영상

대주제 : FIRA 에 대해서, frontendbackend 모두 열고, 그에 해당하는 GIF 로 저장하고 싶다.

소주제 : 각 기능을 GIF 를 통해서 시연하고 싶다.

Release 0.1

Login

login


RunDemo - CheckResult

run_demo_check_result


RunDemo - FixData

run_demo_fix_data


DataCollect

data_collect


Model Version

model_vresion

[FrontEnd] 프론트 시작전 폴더 구조 설계

대주제 : 프론트 부분을 시작하기 전에, 미리 폴더 설계를 마치고, 시작하고 싶다.

소주제 : 각각의 경우에 대한 설계를 하고 싶다.

page 에 대한 설계

src/

page 
	- Login
		- index.js
		- title.js
		- LoginMainBody
			- index.js
			- LoginMainForm.js
			- LoginMainTitle.js

	- Tool
		- ToolHeader
			- index.js
			- profile.js
			- sidebar.js
	  - api
	  	- client.js
	  	- user.js
	  	
		- ToolMiddle
			- index.js
			- ToolSidebar
				- index.js
				- item.js
			- ToolMain
				- index.js
				- RunDemo
					- index.js
					- Dropzone.js
					- ChoiceMode.js
					- Output
						- LineTitle
						- LineCheckResult
						- LineFixData
						- LineItem
					- api
						- client.js
						- inference.js
				
				- DataCollect 
					- index.js
					- ChoiceDataset.js
					- Output 
						- LineTitle.js
						- LineImageInfo.js
						- LineItem.js
					- SaveDataset.js
					- api
						- client.js
						- dataset.js
						- user_fix_image.js

				- ModelVersion
					- index.js
					- Output
						- LineTitleHeader
						- LineTitle
						- LineUsingModelVersion
						- LineAllModelVersion
						- LineItem
					- api
						- client.js
						- model_version.js
		

ActionTypes 구상해보기

ReactRedux-store 통해서 값들을 저장할 수 있는 데. 이를 어떤식으로 구조를 잡고, 어떠한, action 이 필요한지 정의하고 싶어서 이를 만들게 되었습니다.

  • user

    const initialState = {
        pk: 0,
        email: "",
        profile_image: "",
        access_token: ""
    }
    • SET_USER_INFO

      login 할 때와 cookie 를 통한 유저정보 들고 올 때 사용예정

  • Tool Choice

    const initialState = {
        mode: "RunDemo"
    }
    • SET_TOOL_MODE

      RunDemo, DataCollect, ModelVersion 중에 하나 선택하기

  • RunDemo

    const initialState = {
        mode: "CheckResult",
        file_list: [],
        fix_file_list: [],
        is_mode_change: false, 
        is_file_list_change: false, 
        is_fix_file_list_change: false, 
    }
    • SET_RUN_DEMO_MODE

      CheckResult, FixData 중에 하나 선택한다.

      is_mode_changetrue 로 변경한다. (나머지는 false)

    • SET_RUN_DEMO_FILE_LIST

      RunDemo - CheckResult, DropZone 에 올린 파일들을 저장하기 위한 형태이다.

      매번 DropZone 에 새로운 파일들을 올릴때 마다, 이부분은 Reset 되고, 새로 추가된다.

      is_file_list_change true 로 변경한다. (나머지는 false)

    • SET_RUN_DEMO_CONVERT_FILE_IN_FILE_LIST_TO_FIX_FILE_LIST

      FILE_LIST 에 있던 한개의 FILE 을 선택해서, FIX_FILE_LIST 에 추가한다.

      is_fix_file_list_change true 로 변경한다. (나머지는 false)

  • ModelVersion 과 DatasetInfo

    이 두 부분은 굳이 redux-store 를 통해서, 저장해야할 필요가 없을 것이라 판단되어, 작성하지 않았습니다.

    그때에는 굳이 저장할 필요가 없을 것이라 생각하여 굳이 고려를 해주지는 않았으나, 생각해보니, 있으면 좋았습니다.

    기본 베이스는 비슷하나, 하다보니, 좀 더 효율적인 방법에 대해서 고려하게 되었고, 아래에 바로 정리하였습니다.

[Backend] Gunicorn 의 gthread 상태일때의, multi worker 와 multi thread 의 결과에 대한 실험 및 분석

대주제 : Gunicorn 의 mutli worker 와 multi thread 에 관하여 실험을 해보고 싶어서, 실험을 해보게 되었습니다.

소주제 : Gunicornmutli worker 와 multi thread 때에 따라 request 를 처리하는 방향에 대해서 정리하고 싶었습니다.

Gunicorn 이란?

gunicorn 이란 python Server 를 배포하기위해서, 사용되는 WSGI 중에 하나입니다.

다양한 모드(sync, gthread, gevent, tornado, eventlet) 들을 통해 다양한 경우를 지원을 해준다는 장점이 있어서, 많이들 사용하는 WSGI 입니다.

https://docs.gunicorn.org/en/stable/


왜 실험을 하고 싶은 것인가?

예전에 딥러닝 모델을 API 로서, 올리는 과정에서 gunicorn 을 활용하였습니다.

그런데, 생각보다, 성능이 기대한 만큼 처리를 하지 못하고, 리퀘스트가 늘어나면 어느정도 에서는 엄청 느려진다는 것을 볼 수 있었습니다.

물론, 딥러닝연산 이 많은 CPU 연산량 이 필요한 작업이 맞지만, 어느정도 한계가 느껴진다는 것을 느낄 수 있었고,

process(worker) 를 늘릴수록, 기대한 만큼의 수치가 나오지 않는다는 것 또한 확인할 수 있었습니다.

물론, 제가 Gunicorn 의 내부 알고리즘을 전체를 다 분석을 할 수도 없었고, 당시 할당받은 서버의 스펙내부에서 최대한 성능을 이끌어 내고 싶었기에, AB(Apache Benchmark)test 를 활용하여서, 가장 좋은 결과가 나오는 경우를 찾아내었지만,

gunicorn 자체의 내부 알고리즘이 processcalculation time 이 늘어 날때마다, 어떻게 달라지는지는 확인하지 않았기에, 이를 대략적으로 확인하고 싶어 이를 실험하기로 하였습니다.


실험전 알고들어 가야하는 부분

우선, gunicorn 에 대해서, 완벽하게 제가 알지 못합니다.

각각의 경우에 대한 실험 또한, 간단하게 만든 실험 파일들을 기반으로, gunicorn 의 환경설정 값을 변화시켜서 만든 것일 뿐이고,

다음번에 돌렸을 때에는, 언제나 다른 결과가 나올수 있으며, 아래에 나온 결과 값들도 정확한 것은 아닙니다.

하지만, 대체적으로 worker x thread == 한번에 처리가능한 request 의 수 가 이론적으로 생각했을 때와는 달라질 수 있다는 것을 염두해주시고, 봐주시면 감사하겠습니다.

저는 개인적으로, 딥러닝을 올렸을 때, 어떻게 하면 효율적으로 할 수 있을까에 대해서, 생각하였는데, sleep time == 내 서버(API) 가 대략적으로 필요한 시간이라 생각해주시고, 본인의 환경에 대해서 생각해주세요.

(물론 CPU 연산량으로 인한 처리속도 지연도 생각해보셔야합니다.)


gthread 만 선택해서, 한 이유

https://docs.gunicorn.org/en/stable/settings.html#worker-class

  • sync 의 경우, 직관적으로 처리되고 있는 것을 확인할 수 있으나, 그만큼 연산량 + 메모리 추가로 인한 응답 시간 이 많이 커지고 있다는 것을 확인할 수 있었음.

  • CPU 연산량이 많은 딥러닝 API 에서는 gthread 가 비교적 제일 좋은 선택지라고 생각하며,

    worker=1 일때에는, multi thread 를 해도, docs에 있는 설명과 비슷하게 리퀘스트가 균등하게 처리된느 것을 확인할 수 있었으나, workerthread 가 늘어날 수 록, 예상과는 맞지 않는 모습이 보여 아래와 같이 실험을 하였습니다.


실험에 대한 설정
  • r -> client가 request 날린 총합 개수, 기본값 800

  • A, B, C -> 각 프로세스(=worker)당 리퀘스트를 받은 숫자를 큰수대로 나열한 것

  • client 16개 가 동시에 요청을 날리는 경우이다.

worker = 2

sleep(time)\thread t=1 t=2 t=3 t=4
2 A=524, B=276 A=513, B=287 A=587, B=213 A=568, B=232
1 A=491, B=309 A=658, B=142 A=564, B=236 A=576, B=224
0.1 A=667, B=133 A=647, B=153 A=677, B=123 A=541, B=259
0.1 r=8000 A=6810, B=1190 A=6919, B=1081 A=6163, B=1837 A=4898, B=3102
0.01 A=566, B=234 A=435, B=365 A=509, B=291 A=453, B=347
0.01 r= 8000 A=4493, B=3507 A=4135, B=3865 A=4599, B=3401 A=4290, B=3710

worker = 3

sleep(time)\thread t=1 t=2 t=3 t=4
2 A=488,B=193,C=119 A=588, B=128, C=114 A=446, B=203, C=151 A=519, B=147, C=134
1 A=377, B=176, C=247 A=520, B=152, C=128 A=516, B=148, C=136 A=513, B=161, C=126
0.1 A=630, B=123, C=46 A=396, B=237, C=167 A=361, B=280, C=159 A=339, B=263, C=198
0.1 r=8000 A=5878, B=1399, C=723 A=5472, B=1580, C=948 A=3922, B=2588, C=1490 A=3663, B=2750, C=1587
0.01 A=371, B=235, C=194 A=308, B=257, C=235 A=338, B=239, C=223 A=305, B=276, C=239
0.01 r=8000 A=3813, B=2119, C=2068 A=3067, B=2589, C=2344 A=3100, B=2565, C=2335 A=2829, B=2689, C=2482

실험 결과에 대한 정리

위의 결과를 기반으로 볼 때, 생각보다는 multi worker 를 통해서, 많은 양의 request 들을 공평하게 나누지 못하고

동시 request , request 처리시간 에 따라서 프로세스의 사용비율이 달라진다는 것을 확인할 수 있었습니다.

  • 동시 request 의 수의 증가에 따라서, 비교적 worker process 들이 공평하게 request 를 처리하고 있는 것을 확인할 수 있습니다.
  • request 처리시간이 감소함에 따라서, 비교적 worker process 들이 공평하게 request 를 처리하고 있는 것을 확인할 수 있습니다.

동시 request 수가 적거나 , request 처리시간이 클경우 이 두가지 경우에 대해서는

workerxthread 의 수만큼 동시에 request 를 처리하는 것이 아닌, 일종의 노는 process 가 존재하여, 특정 process 에 대해서 request 가 몰리는 경우가 생길 수 있어,

worker=1 일때에 비해서, 메모리 추가 양 대비, 성과가 별로일 수 있으니, 이를 참고하시면서, 개발해주시면 좋겠습니다.

[Backend] User server 설계 및 구상도

대주제 : User server 의 설계와 각각의 API 들의 설정이유들을 명시하고 싶다.

소주제 : db 설계 와 활용한 각 라이브러리들을 정리하고 싶다.

API 설명

  • Login

    Front 에서, 요청을 날려, 유저인지 확인하고, 유저의 정보를 돌려주는 API 입니다.

  • AuthenticateGetUserInfo

    프론트에서 Login 을 통해서, 얻은 access_token 값을, 브라우저 Cookie 로 저장하고, 이를 통해서, Login 을 대신해서,

    유저의 정보를 들고 올 수 있도록, 구현한 API 입니다.

    redis 에 올리는 편이 좀 더, 빠른 접근이 가능하였겠지만, 다양한 라이브러리들을 활용하고 싶었기에, 올리지는 않았습니다.

  • Authenticate

    access_token 을 통한, 유저 인증용으로 만든 API 입니다.

    redis 를 활용하여, access_token_list 에 접근하여, 빠르게 처리 가능하도록 구현해두었으며,

    다른 서버의 모든 API 들이, 이 user server - Authenticate 을 접근하여, 처리를 할 예정이기에,

    이와 같이 redis 로 빠른 처리가 필요하다고 생각하였습니다.


DB 설계도

아래와 같이, User 하나의 테이블로 이루어져있으며, 기본적인 정보들만, 포함하고 있습니다.
erdcloud 를 활용하여 그렸습니다.

스크린샷 2021-02-11 오후 7 51 12


라이브러리

asyncio , uvloop, grpc, databases 등을 통하여, 만들었으며,

비동기(async) 형태로 구현하였습니다.

async 하게,Server 를 구현하기 위해서, FastAPI 의 DB(databases) 사용방식을 많이 활용하였고, 그를 기반으로 코드를 많이 구성하였습니다.

그리고, grpc-python 과 관련된 자료들을 읽고, 동기와 비동기, 그리고 여라 방식으로, GRPC-Python 서버를 구현하는 방법들을 참조하여 구현하였습니다.

GRPC - Python

FastAPI and Databases, SqlAlchemy

UVloop (async io를 더 빠르게)

Redis

[Backend] Model Version Server 설계 및 구상도

대주제 : Model Version server 의 설계와 각각의 API 들의 설정이유들을 명시하고 싶다.

소주제 : db 설계 와 활용한 각 라이브러리들을 정리하고 싶다.

참고 이슈

User Server 설계 및 구상도

만든이유

dl server 에서, 단순히 모델을 load 하고, inference 하는 것만 해도 괜찮도록 만들기 위해서, 고안한 서버입니다.

dl server 에서는 오직, model 의 inference 작업에만 하고, 그외에 DB 작업과 관련된 것은 배제하였습니다.


API 설명

아래 3개의 API 모두, Service-Side Interceptor 를 활용해서,

https://grpc.github.io/grpc/python/grpc.html#service-side-interceptor

user server - authenticate 에게 요청을 보내서, 유저 인증을 하게 됩니다.

  • GetUsingModelVersion

    현재 사용중인, 모델의 정보를 들고 온다.

  • GetAllModelVersion

    모든 모델의 정보를 들고온다.

  • Change

    현재 사용중인 모델의 버젼을, 다른 모델로 바꾸어 준다.

    그리고, dl serverload model api 를 활용하여, 새로운 모델을 load 하게한다.


DB 설계도

아래와 같이, ModelVersion 하나의 테이블로 이루어져있으며, 기본적인 정보들만, 포함하고 있습니다.
erdcloud 를 활용하여 그렸습니다.

스크린샷 2021-02-12 오전 12 26 50


라이브러리

User Server 설계 및 구상도 의 라이브러리를 기본으로 하였지만, 추가로 활용한 라이브러리에서, 해결을 하는데 어려움이 있었기에 정리하고 싶습니다.

처음 grpc 에 접하게 된다면, 아래와 같은 내용들은 흔하지 않은 내용이기에, 도움이 될 수 있을 것이라 생각합니다.

  • Server Side Interceptor

    처음에는, grpc python 에서, middleware 와 관련된 부분이 있을 것이라 생각하였지만,

    쉽게 찾아볼 수 없어서, documentation 을 읽어보면서, 발견 할 수 있었던 부분입니다.

    아래와 같이, interceptors 에 추가하여, 할 수 있으며,

    아래의 AuthenticateUserAccessToken 말고도, 다른 미들웨어(interceptor) 또한, 아래처럼 구현하여 추가할 수 있습니다.

class AuthenticateUserAccessToken(grpc.aio.ServerInterceptor):

    def __init__(self, free_pass_method_name_list: List[str]):

        def abort(ignored_request, context: grpc.aio.ServicerContext) -> None:
            context.abort(grpc.StatusCode.UNAUTHENTICATED, 'Invalid signature')

        self._abort_handler = grpc.unary_unary_rpc_method_handler(abort)
        self.free_pass_method_name_list = free_pass_method_name_list

    async def intercept_service(
            self, continuation: Callable[[grpc.HandlerCallDetails], Awaitable[
                grpc.RpcMethodHandler]],
            handler_call_details: grpc.HandlerCallDetails
    ) -> grpc.RpcMethodHandler:

        method_name = handler_call_details.method.split('/')[-1]
        if method_name in self.free_pass_method_name_list:
            return await continuation(handler_call_details)

        for key, value in handler_call_details.invocation_metadata:
            if key == settings.token_header:
                user_authenticated, _ = await authenticate(access_token=value)
                if user_authenticated:
                    return await continuation(handler_call_details)
                break
        return self._abort_handler
      
 # --------------------------------
# app.py
server = grpc.aio.server(interceptors=(AuthenticateUserAccessToken(free_pass_method_name_list=[]),))

스크린샷 2021-02-12 오전 10 56 36

스크린샷 2021-02-12 오전 10 18 26

[DeepLearning] Inference 때의 GPU 상의 메모리 할당 분석 및 정리

대주제 : 딥러닝 연산시 GPU 위에 얼마나 많은 양의 메모리가 쌓이는지 분석할 필요가 있다.

소주제 : 딥러닝 연산시 GPU 에 올라가는 memory 양을 분석하고, train modeinference mode 를 비교하여, 결과물을 낸다.

참고자료

https://www.sicara.ai/blog/2019-28-10-deep-learning-memory-usage-and-pytorch-optimization-tricks

위 글을 보면서, 학습을 할 때의 GPU 상에 memory 가 쌓이는 순서 및 release 되는 방향에 대해서, 수치적으로 이해를 할 수 있었습니다.

하지만, 실제로 torch 에서는 cache 에 대해서도 고려를 해주어야할 필요도 있고, with torch.no_grad() 를 통해서, inference 를 할 때 또한, 생각을 해주어야 할 필요가 있습니다.

https://pytorch.org/docs/stable/notes/cuda.html#cuda-memory-management

그렇기에, 이를 참고하여, 실제로 traininference 를 할 때, GPU memorytorch memory 에서는 어떠하게 다르며 이를 분석하고자, 아래와 같은 실험을 하게되었습니다. 대부분의 model infernce APIwith torch.no_grad() 를 사용을 많이하게 되어있는 데, 이를 수치적으로 어떠한 만큼 효율을 낼 수 있는지 보여주고 싶습니다.

[Backend] Dataset Server 설계 및 구상도

대주제 : Dataset Info Server 의 설계와 각각의 API 들의 설정이유들을 명시하고 싶다.

소주제 : db 설계 와 활용한 라이브러리들을 정리하고 싶다.

참고 이슈

User Server 설계 및 구상도

Model Version Server 설계 및 구상도

DL Server 설계 및 구상도


API 설명

아래 API 모두, Service-Side Interceptor 를 활용해서,

https://grpc.github.io/grpc/python/grpc.html#service-side-interceptor

user server - authenticate 에게 요청을 보내서, 유저 인증을 하게 됩니다.

  • GetDatasetInfoList

    모든 DatasetInfo 의 정보를 들고 온다.

  • GetImageInfoList

    전체 ImageInfo 정보들을 들고온다.

  • GetChooseImageInfoList

    선택된, Dataset pk 에 포함되는, Image Info 들의 정보를 들고 온다.

  • CreateDatasetInfo

    새로운 DatasetInfo 를 생성한다.

    기존의 untrained Dataset 을 -> 새로운 이름을 주어주고,

    아예 또 새로운 untrained Dataset 을 생성하여, 무에서 시작한다.

  • RemoveImage

    해당 image info pkImageInfo 를 삭제한다.

  • SaveUserFixImage

    model 이 잘못 예측한, 이미지에 대한 정보와 user가 수정한 정보를 담아서, 그에 대한 정보를 함께 저장합니다.

    이는 media 폴더에 저장하게 될 예정이며, media server 를 통해서, 접근이 가능합니다.


DB 설계도

아래와 같이, 2개의 테이블로 이루어져있으며, 기본적인 정보들만, 포함하고 있습니다.
erdcloud 를 활용하여 그렸습니다.

스크린샷 2021-02-12 오후 11 45 06


Media Server

python http.server 를 활용하여, 이미지와 같은 파일들을 간편하게, 접근할 수 있는 media server 를 구현하였습니다.

Media server 동작시키기

아래를 media 폴더에서 동작 시키면, Front 에서도, media폴더 내부에 있는 파일에 접근할 수 있습니다.

cd media
python -m http.server 50050 &
# http://localhost:50050/admin/profile.jpg

라이브러리

아래에서, 거의 동일한 라이브러리를 활용해서, 구현하였습니다.

User Server 설계 및 구상도

Model Version Server 설계 및 구상도

DL Server 설계 및 구상도

[DeepLearning] 딥러닝 모델의 선택이유

대주제 : 학습 시킬 모델을 정하고 싶다.

소주제 : Efficient Net 을 선택하게 된 배경과 그 이유를 정리하고 싶다.

모델선정

데이터의 분류와 클래스 선정의 이유 정리

위 링크에서 확인할 수 있듯이, 아래 두가지를 결정하였습니다.

딥러닝 모델의 용도 : Classification

데이터의 Class 종류 : Class => [0, 90, 180, 270]

  • 고려 사항

    low inference time and low model parameter

    이 모델은 추후, 서버에 올려 API 로써 구현을 해야하며, 그를 위해서는 빠른 Inference Time 이 필요합니다.

    또한, 이 Food Image Rotaion 를 학습을 시키는 것도, 큰 모델을 사용할 필요없는 것이라 판단을 하였기에,

    작은 모델을 통해, 빠른 Inference Time 을 제공할 수 있는 모델을 쓰는 것이 좋을 것이라 생각합니다.

    (만약 필요로 하다면, 추후 모델의 크기를 늘려 성능을 확대할 수도 있습니다.)

  • 모델 종류

    위의 사항에 맞는 모델은 아래 두가지라 생각합니다.

    두 모델의 특성과 결과를 비교해보았을 떄, 성능적으로도 Efficeint Net 이 더 좋다는 것을 확인할 수 있었고

    (예전에 각각의 모델에 대해서, Multimodal CNN 으로 까지 구현하여, 실험해보았고, 그 결과는 공개할 예정이 없습니다.),

    추후 성능의 향상을 위해서, 모델의 크기도 쉽게 늘릴 수 있으니,

    Efficient Net 을 선택하였습니다.

결론

우선, 모델의 크기가 제일 작은 efficient-b0 모델을 선택하여, 구현할 예정입니다.

추후 성능을 증가하고 싶을 경우, 모델의 크기를 늘려, 구현하는 것을 추천 드립니다.

실험적으로, b0 - b7 까지, Colab 을 통해서, 돌려 보았고, 각각의 Inference Time 을 확인할 수 있었습니다.

이를 기반으로, 분석하였을 떄, 만약 성능과 inference time 을 교환한다면 B4 정도까지 추천드립니다.

그 이후의 경우, inference time 이 너무 증가하게 되기에, API 로서는 추천 드리지 않습니다.

또한, Mutlmodal Cnn 형태로 구현할 경우는 연산해야하는 것이 거의 배가 되기 때문에, inference time 또한 많이 느려지게 됩니다.

이를 참고하여, 구현하는 것이 좋을 것입니다.

[Protobuf] 필요한 API 대한 정리와 통신 정리

대주제 : Proto Buf 를 통해, 필요한 API 를 정리하고 싶다.

소주제 : 프론트와 서버간의 통신 뿐만 아니라, 서버와 서버간의 모든 필요한 통신에 대해서, 정리하고 싶다.

  • user.proto
service User {
  // email 과 password 를 받아, 인물의 정보를 돌려준다.  
  rpc Login(LoginInfo) returns (UserInfo) {}; 
  
  // access_token 을 header 에 담아, 올바른 access_token 인지 판단한다. (서버 <-> 서버, 유저 인증용으로 사용될 예정)
  rpc Authenticate(Empty) returns (Empty) {};
  
  // access_token 을 통해, 유저의 정보를 돌려준다. (서버 <-> 프론트, 브라우저 Cookie 를 통해 유저 인증용으로 사용될 예정)
  rpc AuthenticateGetUserInfo(Empty) returns (UserInfo) {}; 
}

user server 를 제외하고, 다른 서버들의 API 들은 프론트에서, request를 받을 때,

rpc Authenticate(Empty) returns (Empty) {} 을 통해, requestuser 인증을 한다.


  • inference.proto
service InferenceFoodImage {
  // image(binary) -> base64 encode -> ascii string 을 통해서, 이미지를 string 으로 받고, 
  // 결과를 돌려보내준다. 
  rpc Inference(FoodB64Image) returns (InferenceResult) {};
  
  // model 의 path 를 보내줌으로서, dl_server 에서, DB가 없더라도, 단순히 모델의 경로값만 받아서, 새로운 모델을 로드한다. 
  rpc LoadModel(ModelPath) returns (Empty){};
}

  • model_version.proto
service ModelVersion {
  // 현재 사용중인 모델의 버젼에 대한 정보를 보내준다. 
  rpc GetUsingModelVersion(Empty) returns (ModelVersionInfo) {};
  
  // 모든 모델의 버젼에 대한 정보들을 들고온다. 
  rpc GetAllModelVersion(Empty) returns (stream ModelVersionInfo) {};
  
  // 선택한 모델의 pk 를 바탕으로, 사용중인 모델의 버젼을 바꾼다. 
  rpc Change(SelectedModelVersion) returns (Empty) {};
}

  • dataset.proto
service Dataset {
  // 모든 데이터 셋의 정보를 들고온다. 
  rpc GetDatasetInfoList(Empty) returns (stream DatasetInfo) {};
  
  // 모든 이미지 정보를 들고온다. 
  rpc GetImageInfoList(Empty) returns (stream ImageInfo) {};
  
  // 선택된 데이터 셋에 포함된 이미지 정보를 들고온다. 
  rpc GetChooseImageInfoList(SelectedDatasetInfo) returns (stream ImageInfo) {};
  
  // 선택된 이미지의 정보를 제거한다. 
  rpc RemoveImage(SelectedImageInfo) returns (Empty){};
  
  // 기존의, no_named_dataset 에 새로운 이름을 부여해주고, 새로운 no_named_dataset 을 생성한다. 
  rpc CreateDatasetInfo(NewDatasetInfo) returns (Empty){};
}
  • user_fix_image.proto
service UserFixImage {
  // 사용자가 수정한 정보를 바탕으로, no_named_dataset 에 새로운 이미지를 추가한다. 
  rpc SaveUserFixImage(UserFixedB64ImageInfo) returns (Empty) {};
}

Docker 를 통한 컨테이너화 구현 구상

대주제 : 도커를 통해서, 이때까지 만들어 왔던 것들을 도커라이즈화 하면서 구현하고 싶다.

소주제 : BackendFrontend 부분을 Docker 로 컨테이너화를 하고 싶고, 그에 대한 구상을 정리하고 싶다.

현재 상황

우선, backend, frontend, deeplearning, proto 모두, release-0.1 버젼까지는 구현을 완료한 상태입니다.

backendfrontend 이 두가지를 대상으로 컨테이너화를 할 예정입니다.

결과로 나온 이슈 : #43 (comment)


현재 상황 - Backend

  1. 서버를 실행 시키기 위해서, 켜야 하는게 너무 많다.

    전체적으로, 서버를 실행을 시키기 위해서는 user, model version, dl, dataset, media(static) , redis

    총 6개의 서버를 켜서 작업을 해왔는데, 이를 docker-compose를 통해서, 효율화를 해줄 필요가 있다.

  2. DBsql lite 에서, mysql 로 각각의 DB 를 만들어줄 필요가 있다.

    현재, 개발용으로 간편하게 바로 고치기 위해서, sql alchemysql lite3DB 를 구현하였지만,

    이제는 각각의 port 번호를 매핑을 하여, 각각의 mysql 을 구현해줄 필요가 있다.

현재 상황 - Frontend

  1. build 된 파일들을 전송해줄 필요가 있다.

    npm run build 를 통해서, 생성된 파일들을 보내주어야 하는데,

    python3 -m http.server 3000 을 통해서, 단순하게 보낼수는 있겠지만,

    nginx 를 통해서, 이를 구현해두고 싶다.


앞으로의 구현 예상도 - Backend

  1. proto 파일들을 생성한 상태에서 올린다.

    user, model version, dl, dataset 서버들에서도,

    proto 파일들을 생성할 때, app.py에서, import absolute_path 가 맞지 않으므로, proto 파일들을 생성하고,

    import 경로까지 완벽하게 맞게 설정되었다고 생각하고 접근한다.

  2. 각각의 DB 를 생성한다.

    uesr -> /mysql Docker file 연결

    model version -> /mysql Dockerfile 연결

    dataset-> /mysql

  3. static 파일 serve

    dataset-> nginx(static file serving)

  4. redis 서버

    user -> redis-server 를 구현해야한다.

전체적인 Docker Config 설정

아래 처럼 서로 연동될 수 있도록, 설정을 하고 진행한다. 기존의 설정과 거의 차이가 안나는 형태로 진행하여, 간단하게

settings = DockerSettings() or DevSettings() 을 바꾸는 정도에서 간편하게 선택할 수 있도록 한다.

class DockerSettings(BaseSettings):
    env: str = "prod"

    user_api_listen_port: str = "[::]:50051"
    dl_api_listen_port: str = "[::]:50052"
    model_version_api_listen_port: str = "[::]:50053"
    dataset_api_listen_port: str = "[::]:50054"
    media_server_listen_port: str = "[::]:50050"

    user_api_listen_addr: str = "api-user:50051"
    dl_api_listen_addr: str = "api-dl:50052"
    model_version_api_listen_addr: str = "api-model_version:50053"
    dataset_api_listen_addr: str = "api-dataset:50054"
    media_server_listen_addr: str = "nginx-media"

    user_db_end_point: str = "mysql://root:fira_user_password@mysql-user/fira_user"
    model_version_db_end_point: str = "mysql://root:fira_model_version_password@mysql-model_version/fira_model_version"
    dataset_db_end_point: str = "mysql://root:fira_dataset_password@mysql-dataset/fira_dataset"
    redis_end_point: str = "redis://redis-server:6379/0?encoding=utf-8"

    token_header: str = "access_token"
    access_token: str = "f9e4a020-6bfd-11eb-8572-0800200c9a66"

    # api_user 에만 필요한 정보
    admin_email: str = "[email protected]"
    admin_password: str = "1234"
    admin_profile_image: str = "admin/profile.jpg"

앞으로의 구현 예상도 - Frontend

npm run build 를 기반으로 생성된, 파일들을 nginx 를 통해서, 전달되는 형태로 구현할 예정입니다.

[Design] 기초 디자인 구현도

대주재 : FIRA 기초 디자인을 구상해보고 싶다.

소주제 : Figma 를 활용하여, 기초 디자인을 구현하겠다.

필요한 기능

아래 기능들을 각각의 페이지로, 디자인 할 필요가 있다.

  • Login (관리자 로그인)
  • Run Demo - CHECK RESULT ( 모델이 처리한 결과를 보여주는 화면 )
  • Run Demo - FIX DATA (잘못 고쳐진 이미지를 수정하여서, 저장하는 화면)
  • Model Version - 학습한 모델을 버젼을 나누어서, 다른 모델들을 선택할 수 있는 화면
  • Dataset Save - FIX DATA 에서 수정된 이미지들을 저장해서, 그것을 Dataset 에 따라, 웹으로 보여주는 화면

시안 디자인 Demo

아래 디자인들을 FIGMA 를 통해서, 디자인 되었습니다.

아래 디자인들은 단순히, 시안이며, 실제로 구현할 때에는

antd or semantic ui 등등을 통해서, 바꾸어서 구현될 예정입니다.

  • Login Page
접기/펼치기 버튼

스크린샷 2021-02-06 오후 7 27 19

  • Run Demo (Check Result(Infernce) ) Page
접기/펼치기 버튼

스크린샷 2021-02-06 오후 7 27 35

  • Run Demo (Fix Data ) Page
접기/펼치기 버튼

스크린샷 2021-02-06 오후 7 28 00

  • Model Version Page
접기/펼치기 버튼

스크린샷 2021-02-06 오후 7 28 39

  • Model Version
접기/펼치기 버튼

스크린샷 2021-02-06 오후 7 29 13



시안 다자인 내부 구조

위와 같이 시안 디자인을 구현하는 것도 좋았지만,

나중에 html 로 나타낼 때, className 과 같은 부분등 체계적으로 설계를 해놓으면, 좋겠다고 생각하여, 아래와 같이 생각을 하며, 구현하였습니다.

물론, 대략적인 div className 구현도라서, 추후 변경 될 수 있습니다.

[DeepLearning] 데이터의 분류와 클래스 선정의 이유 정리

대주제 : 데이터의 분류 및 클래스들을 정리하고 싶다.

소주제 : 많은 양의 데이터를 보고 얻은 생각들을 정리하고 싶다. + 모델의 클래스를 정리하고 싶다

1. 이미지 회전(Rotation) 이라는 방법에 대한 고려

0 ~ 360 도 회전

처음 회전이라는, 주제를 들었을 떄, 0 - 360 => range(0, 360, 1) 전체 로 다 회전 할 수 있을 거라는 생각이 들었습니다.

실제로 간단하게, 모델을 만들어서, 학습 데이터를 적게해서 하였을 떄에는 크게 틀리지 않고도, 예측을 할 수 있었으니까요.

하지만, (0, 90, 180, 270) 을 제외한 각도로 회전을 할 경우, 아래와 같이 검은 색 부분들이 생기게 됩니다.

이러한 경우를 해결할 수 있는 방법에 대해서, 생각을 해보았으나,

1. Inpating 2. 내부 이미지 대체 3. 내부 이미지 Crop

위 3가지 방법 모두, 현실적으로는 도입하기 어려운 방법이기에, 검은색 부분을 채우지 않고, 이미지 원래의 온전함을 보존하기 위해서는

0 , 90, 180, 270 => range(0, 360, 90) 으로만 회전함이 필요한 것을 알 수 있었습니다.

그렇기에, 클래스를 0 , 90, 180, 270 4가지로 선정하였습니다.



2. 데이터 분류에 대한 생각 (학습 데이터 분포의 기준에 대한 정립)

저는 음식 사진들을 보았을 때, 총 3가지 경우가 있다고 판단하였습니다.

  • 회전 하였을 때, 구분 가능한 것 ( 정면 뷰, front -view, 판별 가능한 경우)

    사진을 정면에서 찍었을 때, 대략 0-80 도 정도 쯤에서, 각도를 유지하며 찍은 경우, 음식의 주축선이 0-30도 정도 안에 포함되는 경우.

  • 회전 하였을 때, 구분 불가능 한 것 ( 수직 뷰, Perpendicular - view, 애매한 경우)

    사진을 수직(90도)으로 찍은 경우이다.

    도시락 같은 경우, 직사각형으로 찍혔을 때, 가로든 세로든, 둘다 크게 이상하다고 생각되지 않았으며,

    초밥 같은 경우도 마찬가지였습니다.

    이와 같은 경우는 회전을 하였을 때, 분리하기 힘들다는 생각을 많이하였습니다.

  • 0도에서 사진을 찍었지만, 음식의 주축선이 45도 인 경우, (애매한 경우)

    회전 하였지만, 90도 정도 차이로, 두 경우 모두 사람이 보기에 비슷한 경우 ( 45도 view)

    음식의 주축선이 약 45도 쯤에 위치한 경우이다.

    이경우는 좌 or 우로 90도를 회전해도, 0도를 유지해도 애매하다는 생각이 많이 들었습니다.

    물론, 이러한 경우, point Detection 을 구현하여,

    warping Trasnaform 을 통해 해결할 수 있는 경우가 될 수 있을 것이라 생각합니다.

    하지만, 배경을 warp transform 을 통해서 처리할 경우, 이상하게 변질 될 수 있으며,

    사진을 업로드한 사람들의 의도를 빗나갈 수 있다는 생각에, 사용하지 않는게 더 안정적이라고 생각합니다.

    그래서, 고려 대상에는 포함 시킬 수 있지만, 실 서비스에서는 사용하지 않는 것이 좋다고 생각합니다.

    warp transform 및 여러 cv transform 케이스

    https://m.blog.naver.com/PostView.nhn?blogId=pckbj123&logNo=100205803400&proxyReferer=https:%2F%2Fwww.google.com%2F

    https://darkpgmr.tistory.com/79

    만약 point Detection 을 하게된다면, 참조할 논문 sample

    https://openaccess.thecvf.com/content_cvpr_2018_workshops/papers/w9/DeTone_SuperPoint_Self-Supervised_Interest_CVPR_2018_paper.pdf

    https://cseweb.ucsd.edu/~mkchandraker/pdf/cvpr16_warpnet.pdf

저는 이러한 경우를 해결 할 수 있는 방법이 Confidence 라고 생각하였습니다.

애매한 경우 들을 학습 데이터에서 제거하고(학습을 시키지 않고), 판별가능한 경우들을 학습을 시킨다면,

학습 시킨 데이터의 분포에 대해서는 높은 Confidence 를 낼 수있을 것이라 생각하였고,

학습을 하지 않은(애매한) 분포에 대해서는, 낮은 Confidence 를 낼 것이라 생각하였습니다.

물론, 완벽하게 되지는 않겠지만, 4개의 Class 에 대해서는 threshold0.9 이상으로 높여도 괜찮다고 생각하였습니다.

만약 Confidencethreshold 이하라면, 사람이 판단을 하여, 수정하는 것을 생각하며, 진행하였습니다.

Confidence 를 통해서, 이런 문제를 해결할 수 있을 것이라 생각합니다.

[DeepLearning] Food Image Rotation Task 선정의 이유

대주제 : 딥러닝 Task 선정의 이유에 대해서 정리하고 싶다.

소주제 : 왜 Food Image Rotation 이 필요로 하며, 이 Task 선정 이유와 Food 라는 도메인에 적용하게된 이유를 설명하고 싶다.

  • Task 선정에 대한 이유

모든 사진이 올바른 각도로 저장이 되지 않는다

많은 사람들이 사진을 옆으로 찍어서, 스마트폰 내부 알고리즘으로 통해서, 그를 보정하는 작업을 거치게 됩니다.

하지만, 모든 사진들이 올바르게 보정이 되지는 않으며, 이는 대부분의 스마트폰 사용자들은 큰 신경을 쓰지 않습니다.

사진 찍을 때 모습

사진을 돌렸을 때 모습

수정하지 않고 올렸을 때, SNS 상의 모습

하지만, 이러한 상태로 SNS 에 올리는 경우가 많이, 존재합니다.

그렇기에, 이를 딥러닝을 통해서 해결할 수 있다고 판단을 하였기에 Task 로써 선정하였습니다.



  • Food 라는 특정한 범위로 정한 이유

인스타 그램 이나 SNS 들을 살펴보면서, 음식이라는 사진이 많이 있다는 것을 확인 할 수 있었습니다.

Food(음식)이라는 사진 자체가 많은 SNS 에서 사용하고,

딥러닝으로 적용하기 좋은 Domain 이라 생각하였습니다.

음식이라는 도메인을 해결한다면, 실질적으로 많은 도움을 줄 수 있을 것이라 생각하였기에, 이를 선택하였습니다.

스크린샷 2021-02-06 오후 9 20 59

[Backend] async server run 방식 수정

대주제 : async Server 를 보다 효율적으로 디자인하고 싶다.

소주제 : async Server 의 singal handler 를 추가하여, 보다 안전하게 서버를 종료하고 싶다.

수정하게 된 계기

처음 async server 에 대해서, 참조를 했던 코드는 GRPC python example 에서 참조하여, 구현하여 아래와 같이 구현하였으나,

try:
    await server.wait_for_termination()
except KeyboardInterrupt:
    redis.close()
    await redis.wait_closed()  # Coroutine waiting until underlying connections are closed.
    await database.disconnect()

    await server.stop(0)

try exceptionasyncio 의 EventLoop 단에서 잡혀서, 저 아래까지 내려오지 않는 것을 확인할 수 있었고,

서버를 종료할 떄, DB 에 대한 컨넥션이 제대로 종료가 안되는 점을 발견하면서, 이 부분에 대해서 수정할 필요가 있다는 것을 느꼈습니다.

스크린샷 2021-03-01 오후 11 30 25

또한, 서버(grpc server)는 종료가 되었으나, python 프로세스는 종료되지 않은 것을 확인하여, 이를 수정할 필요성을 느꼈습니다.


해결 방법 (signal handler 추가)

FastAPIdjango, flask 등 대다수의 라이브러리들은 before shut down 과 같은 타이밍에 처리하는 함수들이 있기에,

asyncio 에서도 이는 존재할것이라 생각하였습니다.

그래서 찾은 것이 loop.add_signal_handler 이였습니다.

async def shut_down_server(signal, loop):
    global redis, server

    redis.close()
    await redis.wait_closed()  # Coroutine waiting until underlying connections are closed.
    await database.disconnect()
    await server.stop(0)

    loop.stop()
    
# ------------------------------------
# 이전 방식
# asyncio.run(serve())

# 현재방식
loop = asyncio.get_event_loop()

signals = (signal.SIGHUP, signal.SIGTERM, signal.SIGINT)
for s in signals:
    loop.add_signal_handler(s, lambda s=s: asyncio.create_task(shut_down_server(s, loop)))

try:
    loop.run_until_complete(serve())
finally:
    loop.close()

asyncio.run() 의 경우, 함수 내부에서, loop = events.new_event_loop() 를 통해서, 새로운 EvenetLoop 를 생성하기에 loop 에 대한 signal_handler 를 지정할 수 없기에, 쓸 수 없었습니다.

접기/펼치기 버튼
def run(main, *, debug=False):
    """Execute the coroutine and return the result.

    This function runs the passed coroutine, taking care of
    managing the asyncio event loop and finalizing asynchronous
    generators.

    This function cannot be called when another asyncio event loop is
    running in the same thread.

    If debug is True, the event loop will be run in debug mode.

    This function always creates a new event loop and closes it at the end.
    It should be used as a main entry point for asyncio programs, and should
    ideally only be called once.

    Example:

        async def main():
            await asyncio.sleep(1)
            print('hello')

        asyncio.run(main())
    """
    if events._get_running_loop() is not None:
        raise RuntimeError(
            "asyncio.run() cannot be called from a running event loop")

    if not coroutines.iscoroutine(main):
        raise ValueError("a coroutine was expected, got {!r}".format(main))

    loop = events.new_event_loop()
    try:
        events.set_event_loop(loop)
        loop.set_debug(debug)
        return loop.run_until_complete(main)
    finally:
        try:
            _cancel_all_tasks(loop)
            loop.run_until_complete(loop.shutdown_asyncgens())
        finally:
            events.set_event_loop(None)
            loop.close()

위와 같은 형태로 수정을 하였으며, 아래처럼 무언가가 올바르게 DB 와의 연결이 종료되고, python 프로세스 또한 안전하게 종료된것을 확인할 수 있었습니다.

스크린샷 2021-03-02 오전 12 06 56

그리고, 위에서, 아래와 같은 경고문이 떠서 확인을 해보았는데,

app.py:123: DeprecationWarning: The loop argument is deprecated since Python 3.8, and scheduled for removal in Python 3.10.
  loop.run_until_complete(serve())

스크린샷 2021-03-01 오후 11 10 46

위의 사진에 있는, asyncio 함수들중 loop 를 추가 인자로 받는 것이 앞으로 사라진다는 의미였고, 저 warning 에 대해서는 걱정하지 않으셔도 됩니다.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.