2일차 : 모델 프로토타입 개발
1. 모델 학습
우선, 내가 생각한 서버 구조는 다음과 같다.

- 클라이언트에서 서버(Controller)로 이미지를 전송하면 Controller에서 수신 데이터를 검증하고 Service에 이미지를 넘긴다.
- Service에서 이미지를 Model에서 필요로 하는 양식으로 전처리한다.
- Model에서 전처리한 이미지를 분석해 결과값을 반환한다.
- Service에서 제공받은 결과값을 토대로 유저에게 필요한 데이터를 반환한다.
- Controller에서 제공받은 결과를 유저에게 반환한다
이와같은 순서로 진행되도록 코드를 작성해보았다.
파일 구조
SERVER
├── app.py : 서버 실행 파일(Controller)
├── cat_dog_model.h5 : 모델(Model)
├── config.py : 전역변수 파일
├── image_preprocessing.py : 이미지 전처리 파일(Service)
├── train_model.py : 모델 학습, 생성 파일(Model)

모델 생성 코드
# train_model.py
import numpy as np
from quickdraw import QuickDrawDataGroup
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from sklearn.model_selection import train_test_split
import config
# ----------------------
# QuickDraw 데이터 로드 (고양이, 개 각각 10000개)
# ----------------------
print("QuickDraw 데이터 로딩 중 ...")
cat_data = QuickDrawDataGroup("cat", max_drawings=config.MAX_DRAWINGS)
dog_data = QuickDrawDataGroup("dog", max_drawings=config.MAX_DRAWINGS)
cat_drawings = cat_data.drawings
dog_drawings = dog_data.drawings
# ----------------------
# NumPy 변환
# ----------------------
X_cat = [np.array(d.get_image(stroke_width=2).resize((config.IMAGE_SIZE[0], config.IMAGE_SIZE[1])).convert("L")) for d in cat_drawings]
X_dog = [np.array(d.get_image(stroke_width=2).resize((config.IMAGE_SIZE[0], config.IMAGE_SIZE[1])).convert("L")) for d in dog_drawings]
X_cat = np.array(X_cat)
X_dog = np.array(X_dog)
# 레이블 생성 (고양이=0, 개=1)
y_cat = np.zeros(len(X_cat), dtype=np.int32)
y_dog = np.ones(len(X_dog), dtype=np.int32)
# 데이터 합치기
X = np.concatenate([X_cat, X_dog], axis=0)
y = np.concatenate([y_cat, y_dog], axis=0)
# 스케일링 및 차원 확장 (CNN 입력용)
X = X / 255.0
X = np.expand_dims(X, axis=-1) # (4000, 28, 28, 1)
# ----------------------
# 학습용/검증용 데이터 분할
# ----------------------
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)
# ----------------------
# CNN 모델 정의
# ----------------------
model = keras.Sequential([
layers.Conv2D(32, (3,3), activation='relu', input_shape=(config.IMAGE_SIZE[0],config.IMAGE_SIZE[1],1)),
layers.MaxPooling2D((2,2)),
layers.Conv2D(64, (3,3), activation='relu'),
layers.MaxPooling2D((2,2)),
layers.Flatten(),
layers.Dense(64, activation='relu'),
layers.Dense(2, activation='softmax') # 2개의 클래스 (cat/dog)
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.summary()
# ----------------------
# 모델 학습
# ----------------------
print("모델 학습 시작...")
history = model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=config.EPOCHS, batch_size=config.BATCH_SIZE)
# ----------------------
# 모델 저장
# ----------------------
model.save(config.MODEL_PATH)
print("모델이 'cat_dog_model.h5' 파일로 저장되었습니다!")
데이터 셋은 구글 Quick draw! 오픈소스를 사용하였다.
https://github.com/martinohanlon/quickdraw_python
GitHub - martinohanlon/quickdraw_python: An API for using the Google Quick, Draw! Data
An API for using the Google Quick, Draw! Data. Contribute to martinohanlon/quickdraw_python development by creating an account on GitHub.
github.com
이 코드를 미리 실행하면 cat_dog_model.h5(임시) 모델 파일이 만들어지고, 이후 서버에서 이 모델을 사용하면 된다.
서버 코드
# app.py
import uvicorn
import numpy as np
from fastapi import FastAPI, File, UploadFile
import tensorflow as tf
from tensorflow import keras
from image_preprocessing import preprocess_image # 이미지 전처리 함수
import config
# ----------------------
# 저장된 모델 로드
# ----------------------
print("모델 로딩 중...")
model = keras.models.load_model(config.MODEL_PATH)
classes = ["cat", "dog"]
print("모델 로드 완료!")
# ----------------------
# FastAPI 서버 시작
# ----------------------
app = FastAPI()
@app.post("/predict")
async def predict_endpoint(file: UploadFile = File(...)):
"""
- PNG, JPG 이미지를 업로드 받음
- `image_processor.py`의 `preprocess_image()`를 사용하여 변환
- 모델 예측 수행 후 JSON 형태로 결과 반환
"""
# 1) 파일 읽기
contents = await file.read()
# 2) `image_preprocessing.py`에서 전처리 수행
img_array = preprocess_image(contents)
# 3) 모델 예측
pred = model.predict(img_array)
pred_label = np.argmax(pred[0])
# 4) 결과 반환
return {
"filename": file.filename,
"predicted_class": classes[pred_label],
"confidence": float(pred[0][pred_label])
}
if __name__ == "__main__":
# 서버 실행
uvicorn.run(app, host="0.0.0.0", port=8000)
/predict 경로에 post 매핑을 하여 해당 url로 이미지를 포함하여 요청할 경우,
preprocess_image에서 전처리 후 model을 통해 결과를 예측한다.
이후 결과를 사용자에게 json으로 반환해준다.
이미지 전처리 코드
# image_preprocessing.py
import io
import numpy as np
from PIL import Image
from fastapi import HTTPException
import config
def preprocess_image(image_bytes: bytes, image_size=(config.IMAGE_SIZE[0], config.IMAGE_SIZE[1])):
"""
- 업로드된 이미지 파일의 바이트 데이터를 받아 처리
- 1. 흑백 변환
- 2. 크기 조정 (기본: 28x28)
- 3. NumPy 배열로 변환 후 정규화 (0~1)
- 4. CNN 입력 형태로 차원 확장
- 예외 발생 시 FastAPI의 HTTPException을 발생시켜 오류 반환
"""
try:
# 1) 이미지 로드 및 흑백 변환
img = Image.open(io.BytesIO(image_bytes)).convert('L')
# 2) 리사이즈 (모델 입력 크기와 동일해야 함)
img = img.resize(image_size)
# 3) NumPy 배열 변환 및 정규화
img_array = np.array(img) / 255.0 # 스케일링 (0~1)
# 4) CNN 입력 차원 확장 (28,28,1) -> (1,28,28,1)
img_array = np.expand_dims(img_array, axis=-1) # (28,28,1)
img_array = np.expand_dims(img_array, axis=0) # (1,28,28,1)
return img_array
except Exception as e:
raise HTTPException(status_code=400, detail=f"이미지 처리 오류: {str(e)}")
해당 함수에서는 이미지를 받아 모델에서 요구하는 형태로 전처리를 진행해준다.
전처리가 완료되면 해당 데이터를 반환한다.
전역변수
# config.py
# QuickDraw 데이터 설정
MAX_DRAWINGS = 10000 # 각 클래스(고양이, 개)별 샘플 개수
IMAGE_SIZE = (28, 28) # 이미지 크기 (너비, 높이)
# 학습 설정
BATCH_SIZE = 32
EPOCHS = 5
# 모델 저장 경로
MODEL_PATH = "cat_dog_model.h5"
점진적으로 데이터 횟수 및 이미지 크기 등을 올릴 예정이라 미리 전역변수로 할당하였다.
2. 실행 결과
1. 서버 실행

자잘한 경고가 떴지만 정상적으로 서버가 실행된다.

내가 그린 아주 귀여운 고양이 그림을 postman을 통해 서버에 전송해보았다.

결과가 매우 잘 나오는 것을 확인해볼 수 있다.
현재 각각 10000개의 데이터를 학습시켰고 모델 학습 시간은 대략 11초 정도가 걸리는 것 같다.
모델 학습 결과

분석 결과
| Epoch | Training Accuracy | Training Loss | Validation Accuracy | Validation Loss |
| 1 | 55.67% | 0.6730 | 72.70% | 0.5257 |
| 2 | 77.16% | 0.4743 | 78.37% | 0.4526 |
| 3 | 80.22% | 0.4359 | 81.70% | 0.4009 |
| 4 | 81.34% | 0.4089 | 82.50% | 0.3865 |
| 5 | 82.68% | 0.3843 | 82.17% | 0.3861 |
추후 방향
이제 제시어 데이터를 늘리고 내 노트북에서 버틸 수 있는 선에서 데이터 셋을 최대한 늘려볼 예정이다.
또한 이미지 픽셀크기와 데이터셋 개수, 손실함수 등을 변경해보며 최적의 모델을 구축해보고자 한다.
+) 배포 플랫폼 선정 및 예상 규모 측정이 필요할 것 같다.
참고자료
'인공지능' 카테고리의 다른 글
| [Python] 그림 분석 모델 개발 일지(3) - 데이터 셋 추가 (4) | 2025.03.19 |
|---|---|
| [Python] 그림 분석 모델 개발 일지(1) - 서버 기본 세팅 (4) | 2025.03.19 |