# ── Gradio 설치 ────────────────────────── import gradio as gr import torch import torch.nn as nn import torchvision.transforms as transforms from PIL import Image from datasets import load_dataset # ── 클래스 이름 가져오기 ────────────────── dataset = load_dataset("food101", split="train[:1%]") class_names = dataset.features['label'].names # ── 모델 정의 (똑같이 붙여넣기) ─────────── class BottleneckBlock(nn.Module): expansion = 4 def __init__(self, in_channels, mid_channels, stride=1): super().__init__() self.conv1 = nn.Conv2d(in_channels, mid_channels, kernel_size=1, bias=False) self.bn1 = nn.BatchNorm2d(mid_channels) self.conv2 = nn.Conv2d(mid_channels, mid_channels, kernel_size=3, stride=stride, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(mid_channels) self.conv3 = nn.Conv2d(mid_channels, mid_channels * self.expansion, kernel_size=1, bias=False) self.bn3 = nn.BatchNorm2d(mid_channels * self.expansion) self.relu = nn.ReLU(inplace=True) self.shortcut = nn.Sequential() if stride != 1 or in_channels != mid_channels * self.expansion: self.shortcut = nn.Sequential( nn.Conv2d(in_channels, mid_channels * self.expansion, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(mid_channels * self.expansion) ) def forward(self, x): identity = x out = self.relu(self.bn1(self.conv1(x))) out = self.relu(self.bn2(self.conv2(out))) out = self.bn3(self.conv3(out)) out += self.shortcut(identity) out = self.relu(out) return out class ResNet50(nn.Module): def __init__(self, num_classes=101): super().__init__() self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = nn.BatchNorm2d(64) self.relu = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) self.layer1 = self._make_layer( 64, 64, blocks=3, stride=1) self.layer2 = self._make_layer(256, 128, blocks=4, stride=2) self.layer3 = self._make_layer(512, 256, blocks=6, stride=2) self.layer4 = self._make_layer(1024, 512, blocks=3, stride=2) self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) self.fc = nn.Linear(2048, num_classes) def _make_layer(self, in_channels, mid_channels, blocks, stride): layers = [BottleneckBlock(in_channels, mid_channels, stride=stride)] for _ in range(1, blocks): layers.append(BottleneckBlock(mid_channels * 4, mid_channels)) return nn.Sequential(*layers) def forward(self, x): x = self.maxpool(self.relu(self.bn1(self.conv1(x)))) x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) x = self.avgpool(x) x = torch.flatten(x, 1) x = self.fc(x) return x # ── 모델 불러오기 ───────────────────────── device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = ResNet50(num_classes=101) model.load_state_dict( torch.load('resnet50_food101.pth', map_location=device) ) model = model.to(device) model.eval() # ── 전처리 ──────────────────────────────── transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize( mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ) ]) # ── 예측 함수 ───────────────────────────── def predict(image): # PIL 이미지 → 텐서 변환 img_tensor = transform(image).unsqueeze(0).to(device) # unsqueeze(0) = (3,224,224) → (1,3,224,224) 배치 차원 추가 with torch.no_grad(): output = model(img_tensor) probs = torch.softmax(output, dim=1) # 점수 → 확률 top5 = probs.topk(5) # 상위 5개 # 결과 딕셔너리로 반환 result = {} for i in range(5): label = class_names[top5.indices[0][i].item()] prob = top5.values[0][i].item() result[label] = prob return result # ── Gradio 인터페이스 ────────────────────── demo = gr.Interface( fn=predict, # 예측 함수 inputs=gr.Image(type="pil"), # 입력: 이미지 outputs=gr.Label(num_top_classes=5), # 출력: 상위 5개 클래스 title="🍔 Food-101 분류기", description="음식 사진을 올리면 어떤 음식인지 맞춰줘요! (101가지 음식 분류)", examples=[], # 예시 이미지 (있으면 추가) ) demo.launch()