Programming/AI & ML

[OUTTA Alpha팀 Medical AI& 3D Vision 스터디] 딥러닝 1(파이토치2)

YeonJuJeon 2025. 2. 1. 21:21

1. 라이브러리 및 데이터셋 준비

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda, Compose
import matplotlib.pyplot as plt
import numpy as np
  1. torch, torch.nn: Pyorch의 핵심 라이브러리와 신경망 계층 구현을 위한 모듈.
  2. torch.utils.data.DataLoader: Dataset을 반복(iteration) 가능한 형태로 감싸는 클래스.
  3. torchvision.datasets: 유명한 공개 데이터셋(MNIST, CIFAR 등)을 쉽게 로드할 수 있는 모듈.
  4. torchvision.transforms: 이미지 전처리를 위한 기능(ToTensor, Normalize 등).
  5. matplotlib.pyplot: 시각화 라이브러리.
  6. numpy: 수치 계산 라이브러리.
# 공개 데이터셋에서 학습 데이터를 내려받음
training_data = datasets.MNIST(
    root = 'data',
    train=True,
    download=True,
    transform=ToTensor(),
)

test_data = datasets.MNIST(
    root = 'data',
    train=False,
    download=True,
    transform=ToTensor(),
)
  • MNIST: 28×28 크기의 흑백 손글씨 숫자(0~9) 이미지.
  • root='data': 데이터 저장 경로.
  • train=True/False: train용/테스트용 데이터 구분.
  • download=True: 해당 위치에 데이터 없으면 자동 다운로드.
  • transform=ToTensor(): PIL Image나 ndarray를 PyTorch Tensor로 변환 + [0,1] 범위로 스케일링.
batch_size = 64

train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)

for X, y in test_dataloader:
    print('Shape of X [N, C, H, W] : ', X.shape)
    print('Shape of y', y.shape, y.dtype)
    break
  1. DataLoader(…):
    • batch_size=64: 한 번에 64개 샘플을 묶어 반환.
    • 기본적으로 shuffle=False (학습용이면 보통 shuffle=True).
  2. for X, y in test_dataloader:: 첫 미니배치 데이터를 받아와 출력.
    • X.shape → [N, C, H, W] (배치 크기, 채널, 높이, 너비).
    • MNIST는 흑백 → C=1, 따라서 [64, 1, 28, 28].
    • y.shape → [N] (정답 라벨, 크기 64).
len(training_data)
  • len(training_data)는 MNIST 훈련 데이터셋의 전체 샘플 수(6만 장).

내용 정리

  • root: 데이터 저장 위치
  • train: 학습용인지 테스트용인지
  • download: 미존재 시 자동 다운로드
  • transform: 이미지 전처리 (여기서는 ToTensor).

2. GPU 사용 설정

device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'Using {device} device')
  • torch.cuda.is_available()로 GPU 사용 가능 여부 체크.
  • Colab에서 GPU 사용하려면 “런타임 유형 변경 → GPU”로 설정.

3. 모델 클래스 정의

class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 128),
            nn.ReLU(),
            nn.Dropout(0.2),
            nn.Linear(128, 10)
        )

    def forward(self, x):
        x = self.flatten(x)      # (N,1,28,28) → (N, 784)
        logits = self.linear_relu_stack(x)
        return logits
  1. nn.Module을 상속받아 사용자 정의 모델 구현.
  2. __init__:
    • nn.Flatten(): 입력 [N,1,28,28]을 [N,784]로 변경.
    • nn.Sequential(...): 순차적 레이어 묶음.
      • nn.Linear(784, 128) + nn.ReLU() + nn.Dropout(0.2) + nn.Linear(128,10)
  3. forward(self, x): 모델 순전파 로직.
    • flatten → linear_relu_stack
model = NeuralNetwork().to(device)
print(model)
model.__dict__
  • NeuralNetwork() 인스턴스 생성 후 .to(device) → GPU로 이동.
  • print(model) → 모델 구조(Flatten → Linear → ReLU → Dropout → Linear) 확인.
  • model.__dict__로 내부 속성 확인 가능.

Flatten, Dropout, Linear, ReLU

  • Flatten: 다차원 텐서를 1차원으로 펼침.
  • Linear(784, 128): 완전연결층, 입력 784 → 출력 128.
  • ReLU: 활성화 함수, 음수 → 0.
  • Dropout(0.2): 학습 시 20% 확률로 뉴런 비활성화하여 오버피팅 방지.
  • Linear(128, 10): 최종 분류 10개 클래스.

4. Loss 함수 & Optimizer 설정

loss_fn = nn.CrossEntropyLoss()  
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
  1. CrossEntropyLoss: 소프트맥스 + 로그우도 손실 결합.
    • 라벨이 정수(0~9)인 경우 직접 사용 가능.
  2. Adam 옵티마이저
    • model.parameters()는 모델의 학습 가능 파라미터(m, b, weights 등).
    • lr=1e-3: 학습률.

5. 훈련 함수(train)

def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    for batch, (X, y) in enumerate(dataloader):
        X, y = X.to(device), y.to(device)

        pred = model(X)
        print("pred[0]:", pred[0])
        print("y[0]:", y[0])
        loss = loss_fn(pred, y)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if batch % 100 == 0:
            loss_val = loss.item()
            current = batch * len(X)
            print(f"loss: {loss_val:>7f} [{current:>5d}/{size:>5d}]")
  1. size = len(dataloader.dataset) → 총 샘플 수 (ex. 60000).
  2. for batch, (X, y) in enumerate(dataloader): → 미니배치 반복.
  3. X, y = X.to(device), y.to(device) → GPU로 전송.
  4. pred = model(X) → forward 계산 (로짓).
  5. loss = loss_fn(pred, y) → CrossEntropyLoss 계산.
  6. 역전파
    • optimizer.zero_grad()로 기울기 초기화
    • loss.backward()로 기울기 계산
    • optimizer.step()로 파라미터 업데이트
  7. if batch % 100 == 0: ... → 특정 배치마다 학습 상황 출력.
  • pred[0]는 첫 샘플에 대한 로짓. 예: tensor([-18.9012, -13.6001, ...], grad_fn=...)
  • y[0]는 첫 샘플의 실제 라벨, 예: tensor(5, ...)

6. 테스트 함수(test)

def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    model.eval()
    test_loss, correct = 0, 0

    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
  1. model.eval(): 평가 모드(배치정규화, 드롭아웃 등 학습 비활성화).
  2. with torch.no_grad(): → 기울기 계산 안 함.
  3. 누적 손실: test_loss += loss_fn(pred, y).item()
  4. 정확도: (pred.argmax(1) == y) → True(1)/False(0).
    • .sum().item()로 맞춘 개수 계산, correct /= size로 비율.
  5. 최종 평균 손실, 정확도 출력.

7. 실제 학습 루프

epochs = 10
for t in range(epochs):
    print(f'Epoch {t+1}----------------------------')
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model, loss_fn)
Epoch 10\----------------------------
loss: 0.010015 [    0/60000]
loss: 0.010355 [ 6400/60000]
loss: 0.016117 [12800/60000]
loss: 0.019655 [19200/60000]
loss: 0.011582 [25600/60000]
loss: 0.011333 [32000/60000]
loss: 0.003123 [38400/60000]
loss: 0.018862 [44800/60000]
loss: 0.028464 [51200/60000]
loss: 0.009460 [57600/60000]
10000
Test Error: 
 Accuracy: 97.8%, Avg loss: 0.074867
  1. 총 10 에폭 반복.
  2. train(...): 한 에폭 동안 모든 train_dataloader 배치 처리.
  3. test(...): 한 에폭 종료 후 테스트 데이터셋 성능 평가.

중간 디버그용 train 함수

# train 함수 중간 디버깅
def train(dataloader, model, loss_fn, optimizer):
  size = len(dataloader.dataset)
  for batch, (X, y) in enumerate(dataloader):
    X, y = X.to(device), y.to(device)

    pred = model(X)
    print("pred[0] : ", pred[0])
    print("pred.argmax(1)[0] : ", pred.argmax(1)[0])
    print("y[0] : ", y[0])

    loss = loss_fn(pred, y)
    break

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    ...
pred[0] :  tensor([-18.9012, -13.6001,  -4.5011,   8.7944, -32.9612,  15.3064, -31.3961,
        -10.8590, -11.8794, -11.1282], device='cuda:0',
       grad_fn=<SelectBackward0>)
pred.argmax(1)[0] :  tensor(5, device='cuda:0')
y[0] :  tensor(5, device='cuda:0')
  • 이 코드는 중간에 break가 있어 첫 배치만 확인. 이후 훈련 중단.
  • print(...)로 첫 번째 배치 예측값과 정답을 확인하는 디버그 목적.

8. 손글씨 이미지 예측(three.png)

(예시 코드, MNIST 학습 후 사용자 이미지 입력 테스트)

# ... 생략: 이미지 읽기, 흑백 변환, 배경 반전, 0~1 스케일링
image = torch.as_tensor(image).to(device).reshape(1, 1, 28, 28)
model.eval()
predict = model(image)
print('model이 예측한 값은 {}입니다.'.format(predict.argmax(1).item()))
  1. model.eval(): 평가 모드.
  2. image.shape = [1,1,28,28].
  3. predict = model(image) → 로짓.
  4. predict.argmax(1).item() → 예측된 클래스(0~9).

9. FashionMNIST 예시

training_data = datasets.FashionMNIST(
    root="data", train=True, download=True, transform=ToTensor()
)
test_data = datasets.FashionMNIST(
    root="data", train=False, download=True, transform=ToTensor()
)

labels_map = {
    0: "T-Shirt", 1: "Trouser", 2: "Pullover", 3: "Dress",
    4: "Coat", 5: "Sandal", 6: "Shirt", 7: "Sneaker",
    8: "Bag", 9: "Ankle Boot",
}
  • FashionMNIST: 옷, 신발 등 10개 카테고리의 흑백 이미지(28x28).
  • labels_map로 인덱스 → 문자 라벨 대응.
figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3
for i in range(1, cols*rows + 1):
    sample_idx = torch.randint(len(training_data), size=(1,)).item()
    img, label = training_data[sample_idx]
    figure.add_subplot(rows, cols, i)
    plt.title(labels_map[label])
    plt.axis("off")
    plt.imshow(img.squeeze(), cmap='gray')
plt.show()
  1. 무작위 샘플 9개 표시 (3×3).
  2. img.squeeze()로 (1,28,28)을 (28,28)으로 변형.
  3. labels_map[label]으로 라벨 이름 추출.

DataLoader

train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=False)
  • FashionMNIST도 train/test를 batch=64로 묶음.
  • shuffle=True: 학습 시 무작위 배치.
train_features, train_labels = next(iter(train_dataloader))
print(f'Feature batch shape: {train_features.size()}')
print(f'Labels batch shape: {train_labels.size()}')
img = train_features[0].squeeze()
label = train_labels[0]
plt.imshow(img, cmap='gray')
plt.show()
print(f'Label: {label}')
print(labels_map[int(label)])
print(label.numpy())

  1. next(iter(train_dataloader)): 첫 배치(이미지, 라벨)만 가져옴.
  2. img.shape → [28,28], label은 정수 라벨.
  3. 시각화: plt.imshow(img, cmap='gray').

Iteration 예시

list1 = [1,2,3,4,5]
iterator = iter(list1)
a = next(iterator)  # 1
a = next(iterator)  # 2
a = next(iterator)  # 3
a1 = next(iterator) # 1
  • next(iter(...))가 호출될 때마다 다음 요소 반환.
  • DataLoader도 유사하게 작동 → 각 배치 순회.

정리

  1. 데이터셋 로드: datasets.MNIST(...), datasets.FashionMNIST(...)
  2. DataLoader배치 구성 → train_dataloader, test_dataloader
  3. 모델 정의: class NeuralNetwork(nn.Module)
    • Flatten → Linear → ReLU → Dropout → Linear
  4. 손실 / 최적화 함수 설정: CrossEntropyLoss, Adam
  5. train(...) 함수:
    • 각 배치에 대해 forward → loss 계산 → backward → optimizer.step
    • 100단계마다 중간 손실 출력
  6. test(...) 함수:
    • model.eval() + no_grad() → 평가 모드, 손실/정확도 측정
  7. 학습 루프(for t in range(epochs):)
    • train(...) 후 test(...)
  8. 추가 예시:
    • 직접 업로드한 이미지(three.png) → 전처리(흑백, 28x28, 반전) → model(image) 예측
    • FashionMNIST로 데이터셋 바꿔 테스트

1. Python에서 super()란 무엇인가?

  • super()
    • 부모(슈퍼) 클래스임시 객체를 반환하여, 부모 클래스 메서드에 접근할 수 있게 함.
    • 예: super().__init__() → 부모 클래스의 생성자 실행.
    • super().some_method() → 부모 클래스의 some_method 호출.
  • super(SubClass, self) vs super()
    • 파이썬 3에서는 대부분 super()로 간단히 사용.
    • 내부적으로 super(SubClass, self)는 SubClass의 MRO(메서드 해석 순서)에 따라 상위 클래스를 탐색.
    • 다중 상속이나 할머니 클래스가 있는 경우, super(명시적 하위클래스, self)와 super()의 탐색 범위가 달라질 수 있음.
  • 상속 예시
    • Square는 Rectangle을 상속받고, super().__init__(length, length)로 부모 클래스의 초기화 로직 실행.
    • area(), perimeter() 등을 그대로 상속 및 재사용 가능.
class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width
    def area(self):
        return self.length * self.width

class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)
  • 부모의 부모(할머니) 상속 예시
    • super(Square, self).area() → Square의 직접 부모(Rectangle) 탐색
    • super().area() → Cube 기준으로 MRO를 거쳐 Square의 area 탐색.

2. Numpy에서 sum 함수와 axis 이해

: Numpy에서 np.sum 함수의 axis 이해

 

Numpy에서 np.sum 함수의 axis 이해

Numpy의 sum은 유용한 함수입니다. 그러나 처음 sum 함수를 사용할 때 axis 파라미터가 무엇을 의미하는지 혼동되는 것이 사살입니다. axis의 의미를 정리합니다.

taewan.kim

  • 배열(텐서)의 차원(Dimension)
    • 1차원(벡터), 2차원(행렬), 3차원(텐서), …
    • 예: (4,2,4) 형태의 3차원 배열 → Row=4, Column=2, Depth=4.
  • v.sum(axis=None)
    • 모든 원소를 단순 합산 → 스칼라 반환.
  • axis=0
    • 가장 바깥 차원(ROW)합산해 줄임.
    • 결과 shape에서 row가 사라지므로 (2,4)로 감소.
  • axis=1
    • 두 번째 축(컬럼)을 합산. (4,4) 형태 → row별 column들을 하나로 합침.
  • axis=2
    • 세 번째 축(depth)을 합산. (4,2) 형태 → 각 row·column마다 depth의 합.
  • 일반적 개념
    • axis=n은 n번째 축(0부터 시작)을 기준으로, 그 축을 합산 대상으로 삼아 제거.
    • 다차원 배열을 다룰 때 axis 설정은 “어떤 차원을 합쳐서 없앨 것인가?”라고 보면 됨.

3. TensorFlow vs. PyTorch 비교

  • 모델 정의 방식

TensorFlow(Keras):

class MyModel(tf.keras.Model):
    def __init__(self):
        super().__init__()
        # layers 정의
    def call(self, x):
        # forward pass
        return ...
model = MyModel()

 

PyTorch:

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        # layers 정의
    def forward(self, x):
        return ...
model = NeuralNetwork().to(device)
  • 차이점:
    • Keras: 자동으로 GPU 설정(Colab 등).
    • PyTorch: model.to(device)로 명시적 GPU 할당.
  • 컴파일
    • Keras: model.compile(optimizer, loss, metrics=...)
    • PyTorch:
      • loss_fn = nn.CrossEntropyLoss()
      • optimizer = torch.optim.Adam(model.parameters(), lr=...)
  • 학습(fit) vs. custom training loop
    • Keras: model.fit(x_train, y_train, epochs=...)로 간단 호출.
    • PyTorch: 수동 반복문으로 batch 처리, forward, backward, optimizer step을 명시.
# Pytorch
def train(dataloader, model, loss_fn, optimizer):
    for X,y in dataloader:
        pred = model(X)
        loss = loss_fn(pred, y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
  • 평가
    • Keras: model.evaluate(x_test, y_test)
    • PyTorch: 결과적으로 PyTorch는 더 자유도 높고 세부 제어 가능하지만, 직접 코드 작성 많음.
model.eval()
with torch.no_grad():
    # forward & calculate accuracy
  • 정리
    • TensorFlow는 고수준 API(fit, compile 등) 제공해 편의성 높음.
    • PyTorch는 직관적이고 명시적(forward, backward)하며 연구/디버깅에 유리.

최종 요약

  • super(): 파이썬에서 상속 시, 부모 클래스 메서드/생성자 등을 호출하기 위한 임시 객체. 다중 상속 시 super(하위클래스, self) 형태로 부모/조상 클래스를 유연히 찾을 수 있음.
  • Numpy sum(axis=...): 다차원 배열에서 특정 축(차원)을 합산 기준으로 삼아 제거. 예) axis=0은 row 합산, axis=1은 column 합산 등.
  • TensorFlow vs. PyTorch:
    • 모델 정의, 컴파일, 학습 방식이 조금 다름.
    • Keras는 fit(), compile() 중심의 간단 API; PyTorch는 직접 training loop를 작성(자유도 높음).
    • 두 프레임워크 모두 Layer, Optimizer, Loss 개념은 유사하나, API 사용 패턴에서 차이 발생.