Keras모델을 사용하는 Flask서버 개발하기
앞의 포스팅에서 Tensorflow 모델을 Keras 모델로 바꾸어 사용하는 방법을 알아보았습니다.
이제는 이 Keras 모델을 사용해 어떻게 서버를 만드는지 알아보도록 하겠습니다.
Flask의 기본 개념에 대한 내용은 생략합니다. 기본 개념은 공식문서를 참고하세요!
미리보기)
만들 서버의 모습입니다! 간단한 UI와 멋있는 사진 결과를 볼 수 있습니다!
입력으로 이미지의 URL을 받아 Keras 모델을 사용해 결과물을 얻고, 그 결과를 웹 화면에 보여주는 방식입니다.
순서는
- flask 코드 작성
- json 값을 이용해 html 화면에 결과 띄우기
로 진행하겠습니다.
flask 코드 작성
우선은 app.py
파일을 작성합니다. (flask를 실행할 파일)
app = flask.Flask(__name__)
@app.route("/", methods=["GET"])
def main():
return render_template('index.html')
main 함수에서는 index 페이지만 보여주도록 합니다.
@app.route("/model", methods=["GET"])
def model():
if request.method == "GET":
# 1. url을 입력으로 받아서
url = request.args.get('url')
# 2. url을 이미지로 변환한 후
image = url_to_image(url)
# 3. Keras 모델의 input으로 사용
result_file = test_model(image)
# 4. json 형태로 리턴
result_dict = {
'url' : url,
'result' : encode_image(result_file)
}
return json.dumps(result_dict)
이번에는 실제로 모델을 사용하는 함수를 작성하겠습니다.
1. 이미지의 url을 입력으로 받음
request.args.get('url')
이라고 하면, /model?url=
뒤에 들어가는 url값을 가져옵니다.
예를들어 /model?url=http://zum.com/ 이라는 입력이 들어오면 url 변수에는 ‘http://zum.com’ 이 들어갑니다.
2. 이미지의 url을 이미지 배열로 변환
def url_to_image(url):
resp = urllib.request.urlopen(url)
image = np.asarray(bytearray(resp.read()), dtype="uint8")
image = cv2.imdecode(image, cv2.IMREAD_COLOR)
return image
url을 읽어 array로 변환하고, 3차원 형태의 이미지 배열로 다시한번 변환하는 코드 입니다.
3. 이미지 배열을 모델의 input으로 사용해 모델 테스트
model.py
model = load_model('./model_complete.h5', custom_objects={'tf': tf})
model.load_weights('./model_weights.h5')
model._make_predict_function() # predict 할 때 매번 쓰기 위해 사용하는 함수
def test_model(image):
output = model.predict(image)
filename = 'temp.png'
cv2.imwrite(filename, image)
return filename
위 코드는 model.py
에 작성한 내용입니다. Flask에서 일반적으로 route를 가진 함수가 아니면 다른 파일로 분리해 작성하는 방식을 사용하기 때문에, 따로 분리해서 작성했습니다.
여기서 중요한 두 가지가 있는데, 첫번째로 중요한 것은 model.py
안에서 모델을 로드한다는 점입니다!model.py
내에서 모델을 로드하면, app.py
실행 시 맨 위에서 model.py
를 한번만 부르기 때문에 앱 실행동안 한번만 모델을 로드할 수 있습니다.
만약 app.py
내의 라우트를 가진 함수들 내에서 모델을 로드하면, 함수를 매번 호출할 때 마다 모델 파일을 읽기 때문에 매우 비효율적인 앱이 될 것입니다.
두 번째로는 model._make_predict_function()
부분입니다. model.predict()
를 하기 전에 호출을 해야 predict를 정상적으로 할 수 있습니다.
정확한 사용 이유는 공식 문서에도 잘 나와있지 않습니다. Keras github Issue들에도 여러 질문이 있지만, 아직 명확한 답변은 나와있지 않습니다. 답변이 추가되면 수정하겠습니다!
4. 결과를 json 형태로 리턴
result_dict = {
'url' : url,
'result' : encode_image(result_file)
}
return json.dumps(result_dict)
매번 이미지 결과를 얻어서 이미지로 저장을 하면 저장공간 상의 문제가 될 수도 있어, encode_image
라는 함수 내에서 이미지를 인코딩 한 후 인코딩 값만 리턴하고 파일은 삭제하도록 구현을 했습니다.
json
형식으로 리턴을 하면 서버에서 UI 없이 라우트로만 요청을 보내도 결과를 볼 수 있고, html에서 json형식의 결과를 사용하기 쉽다는 장점이 있습니다.
이제 서버개발은 모두 끝났습니다!python app.py
를 실행한 후 /model?url=[url주소]
를 입력하게 되면 json 형식의 결과를 볼 수 있습니다!~~~!~!
하지만 좀 더 편리하게 사용하기 위해 간단한 UI까지 개발해보도록 하겠습니다!
2. json 값을 이용해 html 화면에 결과 띄우기
우선 flask에서 html 파일을 사용하기 위해서는 정해진 디렉토리 규칙이 있습니다.
app.py
model.py
templates
- index.html
이런식으로 templates 폴더 내에 html 파일을 넣어주면 별다른 경로 지정 없이 flask가 html파일을 읽을 수 있습니다.
index.html
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>VIP</title>
</head>
<body>
<h2>saliency map을 구할 이미지의 URL을 입력하세요</h2>
<form class='url'>
<input type="text" name="url"/>
<button type="button" id="submit">submit</button>
</form>
<table id="result">
</table>
<script
src="https://code.jquery.com/jquery-3.4.1.js"
integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU="
crossorigin="anonymous"></script>
<script>
const Result = ({ url, result }) => `
<tr>
<td><img class="crop-result" src="${url}"></td>
<td><img class="crop-result" src="data:image/png;base64, ${result}"></td>
</tr>
`;
$(function() {
$('#submit').click(function() {
var input = $('form.url').serialize();
console.log(input);
$.ajax({
url: '/model',
data: input,
type: 'GET',
success: function(response) {
var result = JSON.parse(response);
console.log(result);
$('#result').html([result].map(Result).join(''));
},
error: function(error){
console.log(error);
}
});
});
});
</script>
</body>
이게 전부입니다..! easy..!
1. input tag로 입력값 받기
<form class='url'>
<input type="text" name="url"/>
<button type="button" id="submit">submit</button>
</form>
...
<script>
$(function() {
$('#submit').click(function() {
var input = $('form.url').serialize();
...
</script>
input tag에 name='url'
속성을 주었고, form tag의 기본 method는 GET방식이므로, input에 값을 넘겨주면 ?url=값
으로 넘어가게 됩니다.
이 때 값이 넘어가는 시점은 submit 버튼을 누를 때 입니다. 따라서 submit 버튼을 클릭할 때 처리 함수가 작동하도록 하면 됩니다.
script 태그 내의 input 변수에 ?url=값
까지 넘어간 상태 입니다.
2. 입력받은 값으로 요청 보내기
<script>
...
$.ajax({
url: '/model',
data: input,
type: 'GET',
...
</script>
이제 앞에서 flask로 작성한 함수에 요청을 보낼 차례 입니다.
ajax 형식에 맞게 작성만 하면 됩니다. 어떤 url로 보낼 것인지, 어떤 데이터를 보낼 것인지, 어떤 방식으로 보낼것인지 정의합니다.
위와 같이 작성하게 되면 /model?url=input 값
형태로 요청이 넘어갑니다.
3. 요청을 보낸 결과를 받아 파싱해서 뿌리기
<script>
...
success: function(response) {
var result = JSON.parse(response);
$('#result').html([result].map(Result).join(''));
},
error: function(error){
console.log(error);
}
...
</script>
요청을 보낸 결과를 json 형식으로 정의해 놓았기 때문에 JSON.parse
로 쉽게 값을 사용할 수 있습니다.
여기서 한 가지 봐야할 것은 json 형태의 array를 mapping 해서 사용하는 부분입니다.
<table id="result"></table>
<script>
const Result = ({ url, result }) => `
<tr>
<td><img class="crop-result" src="${url}"></td>
<td><img class="crop-result" src="data:image/png;base64, ${result}"></td>
</tr>
`;
</script>
mapping 시 사용한 Result라는 상수는 script의 윗 부분에서 정의했습니다.
이 상수 내에서는 json에서 사용하는 key값을 간단히 사용할 수 있습니다.
안에 사용할 key 값들을 나열해주고, ${ … }로 값을 사용해주면 됩니다!
그럼 이제 정말 끝입니다! (고양이 사진도 가능합니다!)