5. 손글씨 도형 분류 FastAPI로 서빙
2025. 1. 20. 16:13ㆍLLM(Large Language Model)의 기초/딥러닝
1.손글씨 도형 분류하기 shape_classifier.py 로 저장한다.
예시 1)
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import Dataset, DataLoader
# 데이터 전처리를 위한 transform 정의
transform = transforms.Compose([
transforms.Resize((28, 28)), # 이미지 크기를 28x28로 조정
transforms.Grayscale(1), # 이미지를 그레이스케일로 변환 (채널 1개)
transforms.ToTensor(), # 이미지를 텐서 형태로 변환
transforms.RandomInvert(1), # 색상을 랜덤으로 반전
transforms.Normalize((0.5), (0.5)) # 텐서 값을 평균 0.5, 표준편차 0.5로 정규화
])
# 학습 데이터와 테스트 데이터 경로 설정
train_path = 'C:\\Chaeyeon\\Web\\ModelServing\\train'
test_path = 'C:\\Chaeyeon\\Web\\ModelServing\\test'
# ImageFolder를 이용해 데이터셋 로드 및 transform 적용
trainset = torchvision.datasets.ImageFolder(root=train_path, transform=transform)
testset = torchvision.datasets.ImageFolder(root=test_path, transform=transform)
# 클래스 인덱스와 라벨 매핑
class_map = {0: 'cir', 1: 'tri', 2: 'x'}
# 학습 데이터 로더 생성
loader = DataLoader(
dataset=trainset, # 학습 데이터셋
batch_size=64, # 배치 크기
shuffle=True # 데이터를 섞음
)
# 데이터 로더에서 한 배치를 가져와 이미지와 라벨 출력
imgs, labels = next(iter(loader))
print(imgs.shape, labels.shape) # 이미지와 라벨 텐서의 크기 출력
# 합성곱 신경망 정의
class ConvNeuralNetwork(nn.Module):
def __init__(self):
super(ConvNeuralNetwork, self).__init__()
self.flatten = nn.Flatten() # 출력 텐서를 평탄화
self.classifier = nn.Sequential(
nn.Conv2d(1, 28, kernel_size=3, padding='same'), # 첫 번째 합성곱 계층
nn.ReLU(), # 활성화 함수
nn.Conv2d(28, 28, kernel_size=3, padding='same'), # 두 번째 합성곱 계층
nn.ReLU(),
nn.MaxPool2d(kernel_size=2), # 맥스풀링 계층
nn.Dropout(0.25), # 드롭아웃 계층 (25% 비율로 노드 제거)
nn.Conv2d(28, 56, kernel_size=3, padding='same'), # 세 번째 합성곱 계층
nn.ReLU(),
nn.Conv2d(56, 56, kernel_size=3, padding='same'), # 네 번째 합성곱 계층
nn.ReLU(),
nn.MaxPool2d(kernel_size=2), # 맥스풀링 계층
nn.Dropout(0.25), # 드롭아웃 계층
)
self.Linear = nn.Linear(56 * 7 * 7, 3) # 완전연결 계층 (출력 클래스: 3개)
def forward(self, x):
x = self.classifier(x) # 입력을 합성곱 계층에 전달
x = self.flatten(x) # 출력 텐서를 평탄화
output = self.Linear(x) # 완전연결 계층을 통과시켜 최종 출력 생성
return output
# CUDA(또는 GPU) 사용 가능 여부 확인
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(device)
# 모델 초기화 및 장치로 이동
model = ConvNeuralNetwork().to(device)
print(model)
# 손실 함수 및 옵티마이저 정의
loss = nn.CrossEntropyLoss() # 다중 클래스 분류를 위한 손실 함수
optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam 옵티마이저 (학습률: 0.001)
# 학습 루프 함수 정의
def train_loop(train_loader, model, loss_fn, optimizer):
sum_losses = 0 # 에포크 전체 손실 합
sum_accs = 0 # 에포크 전체 정확도 합
for x_batch, y_batch in train_loader: # 배치 단위로 데이터 반복
x_batch = x_batch.to(device) # 데이터를 장치로 이동
y_batch = y_batch.to(device) # 라벨을 장치로 이동
y_pred = model(x_batch) # 모델을 통해 예측값 생성
loss = loss_fn(y_pred, y_batch) # 손실 계산
optimizer.zero_grad() # 이전 계산 그래디언트 초기화
loss.backward() # 역전파 수행
optimizer.step() # 모델 가중치 업데이트
sum_losses += loss # 배치 손실 합산
# 정확도 계산
y_prob = nn.Softmax(1)(y_pred)
y_pred_index = torch.argmax(y_prob, axis=1)
acc = (y_batch == y_pred_index).float().sum() / len(y_batch) * 100
sum_accs += acc
# 평균 손실과 정확도 계산
avg_loss = sum_losses / len(train_loader)
avg_acc = sum_accs / len(train_loader)
return avg_loss, avg_acc
# 학습 설정
epochs = 50
# 에포크 단위로 모델 학습
for i in range(epochs):
print(f"------------------------------------------------")
avg_loss, avg_acc = train_loop(loader, model, loss, optimizer)
print(f'Epoch {i:4d}/{epochs} Loss: {avg_loss:.6f} Accuracy: {avg_acc:.2f}%')
print("Done!")
# 테스트 데이터 로더 생성
test_loader = DataLoader(
dataset=testset, # 테스트 데이터셋
batch_size=32, # 배치 크기
shuffle=False # 데이터를 섞지 않음
)
# 테스트 함수 정의
def test(model, loader):
model.eval() # 모델을 평가 모드로 설정
sum_accs = 0 # 전체 정확도 합산
img_list = torch.Tensor().to(device) # 테스트 이미지 저장
y_pred_list = torch.Tensor().to(device) # 예측값 저장
y_true_list = torch.Tensor().to(device) # 실제 라벨 저장
for x_batch, y_batch in loader: # 테스트 배치 반복
x_batch = x_batch.to(device) # 데이터를 장치로 이동
y_batch = y_batch.to(device) # 라벨을 장치로 이동
y_pred = model(x_batch) # 모델로 예측 수행
y_prob = nn.Softmax(1)(y_pred) # 확률 계산
y_pred_index = torch.argmax(y_prob, axis=1) # 예측 라벨 생성
y_pred_list = torch.cat((y_pred_list, y_pred_index), dim=0) # 예측값 저장
y_true_list = torch.cat((y_true_list, y_batch), dim=0) # 실제 라벨 저장
img_list = torch.cat((img_list, x_batch), dim=0) # 이미지 저장
acc = (y_batch == y_pred_index).float().sum() / len(y_batch) * 100 # 정확도 계산
sum_accs += acc
avg_acc = sum_accs / len(loader) # 평균 정확도 계산
return y_pred_list, y_true_list, img_list, avg_acc
# 테스트 수행
y_pred_list, y_true_list, img_list, avg_acc = test(model, test_loader)
print(f'테스트 정확도는 {avg_acc:.2f}% 입니다.')
# 모델 가중치 및 전체 모델 저장
torch.save(model.state_dict(), 'model_weights.pth') # 가중치만 저장
torch.save(model, 'model.pt') # 전체 모델 저장
저장 후 돌리면,
Epoch 47/50 Loss: 0.015456 Accuracy: 99.61%
------------------------------------------------
Epoch 48/50 Loss: 0.014981 Accuracy: 99.48%
------------------------------------------------
Epoch 49/50 Loss: 0.028470 Accuracy: 98.44%
Done!
테스트 정확도는 93.75% 입니다.
이런식으로 표시가된다.
3. FastAPI를 활용하여 작성한 서버파일을 main.py로 저장
# FastAPI backend
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
import torch
from torchvision import transforms
from PIL import Image
import io
import torch.nn as nn
import torch.nn.functional as F
# 합성곱 신경망 모델 정의
class ConvNeuralNetwork(nn.Module):
def __init__(self):
super(ConvNeuralNetwork, self).__init__()
self.flatten = nn.Flatten() # 텐서를 평탄화하는 레이어
self.classifier = nn.Sequential(
nn.Conv2d(1, 28, kernel_size=3, padding='same'), # 첫 번째 합성곱 계층
nn.ReLU(), # 활성화 함수
nn.Conv2d(28, 28, kernel_size=3, padding='same'), # 두 번째 합성곱 계층
nn.ReLU(),
nn.MaxPool2d(kernel_size=2), # 맥스풀링 계층
nn.Dropout(0.25), # 드롭아웃 계층 (25% 비율로 노드 제거)
nn.Conv2d(28, 56, kernel_size=3, padding='same'), # 세 번째 합성곱 계층
nn.ReLU(),
nn.Conv2d(56, 56, kernel_size=3, padding='same'), # 네 번째 합성곱 계층
nn.ReLU(),
nn.MaxPool2d(kernel_size=2), # 맥스풀링 계층
nn.Dropout(0.25), # 드롭아웃 계층
)
self.Linear = nn.Linear(56 * 7 * 7, 3) # 완전 연결 계층 (출력 클래스: 3개)
def forward(self, x):
x = self.classifier(x) # 입력 데이터를 합성곱 계층에 전달
x = self.flatten(x) # 출력을 평탄화
output = self.Linear(x) # 완전 연결 계층 통과 후 최종 출력 생성
return output
# 모델 초기화 및 가중치 로드
model = ConvNeuralNetwork()
state_dict = torch.load('./model_weights.pth', map_location=torch.device('cpu')) # 저장된 가중치 로드
model.load_state_dict(state_dict) # 모델에 가중치 적용
model.eval() # 평가 모드 설정
# 클래스 라벨 정의
CLASSES = ['cir', 'tri', 'x']
# 이미지 전처리 함수 정의
def preprocess_image(image_bytes):
transform = transforms.Compose([
transforms.Resize((28, 28)), # 이미지 크기 조정
transforms.Grayscale(1), # 이미지를 그레이스케일로 변환
transforms.ToTensor(), # 이미지를 텐서로 변환
transforms.RandomInvert(1), # 색상 반전
transforms.Normalize((0.5), (0.5)) # 텐서 값을 정규화
])
image = Image.open(io.BytesIO(image_bytes)).convert('L') # 이미지 바이트를 열어서 그레이스케일로 변환
return transform(image).unsqueeze(0) # 텐서에 배치 차원을 추가하여 반환
# FastAPI 애플리케이션 생성
app = FastAPI()
# CORS 설정 (모든 도메인 허용)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 이미지 분류 API 엔드포인트 정의
@app.post("/classify")
async def classify_image(file: UploadFile = File(...)):
try:
# 업로드된 파일 읽기
image_bytes = await file.read()
print(f"Received file: {file.filename}, size: {len(image_bytes)} bytes")
# 이미지 전처리 수행
input_tensor = preprocess_image(image_bytes)
print(f"input tensor shape: {input_tensor.shape}")
# 모델을 사용하여 예측 수행
with torch.no_grad(): # 그래디언트 계산 비활성화
outputs = model(input_tensor) # 모델 출력 계산
print(f"Model outputs: {outputs}")
_, predicted = torch.max(outputs, 1) # 예측 클래스 인덱스 계산
label = CLASSES[predicted.item()] # 인덱스를 라벨로 변환
print(f"Predicted label: {label}")
# JSON 형식으로 결과 반환
return JSONResponse(content={"label": label})
except Exception as e:
# 에러 처리
print(f"Error: {e}")
return JSONResponse(content={"error": str(e)}, status_code=500)
4. Gradio를 활용하여 작성한 프론트 파일을 app.py로 저장
# Gradio frontend
import gradio as gr
import requests
import io
# 백엔드와 통신하여 이미지를 분류하는 함수 정의
def classify_with_backend(image):
url = "http://127.0.0.1:8080/classify" # FastAPI 백엔드 URL
image_bytes = io.BytesIO() # 이미지를 바이트 형태로 변환하기 위한 객체 생성
image.save(image_bytes, format="PNG") # 이미지를 PNG 형식으로 저장
image_bytes = image_bytes.getvalue() # 바이트 값 가져오기
response = requests.post(url, files={"file": ("image.png", image_bytes, "image/png")}) # 백엔드로 POST 요청 전송
if response.status_code == 200: # 응답 코드가 200일 경우
return response.json().get("label", "Error") # 응답에서 라벨 값을 반환
else: # 에러 발생 시
return "Error"
# Gradio 인터페이스 정의
iface = gr.Interface(
fn=classify_with_backend, # 이미지를 분류하는 함수
inputs=gr.Image(type="pil"), # 입력으로 이미지를 받음 (PIL 형식)
outputs="text", # 출력으로 텍스트를 반환
title="손글씨 도형 분류하기", # 인터페이스 제목
description="○, X, △ 이미지를 넣어주세요 !!" # 인터페이스 설명
)
# 인터페이스 실행
if __name__ == "__main__":
iface.launch() # Gradio 애플리케이션 실행
--->
app.py 를 돌릴시, 그림을 첨부하면 output에 그 모양이 나타난다
728x90
LIST
'LLM(Large Language Model)의 기초 > 딥러닝' 카테고리의 다른 글
6. Alien vs. Predator 데이터셋 (2) | 2025.01.21 |
---|---|
4. Alexnet 구현하기 (4) | 2025.01.20 |
4. 손글씨 도형 분류하기 (2) | 2025.01.16 |
3. CNN(Convolutional Neural Network, 합성곱 신경망) (2) | 2025.01.15 |
2. Multi-class Weather Dataset (2) | 2025.01.14 |