def __init__()super().__init__()input_dim, output_dimdef forward()model2 = MyModel(X_train.size(-1), y_train.size(-1))
optimizer2 = optim.Adam(model2.parameters())
train_history_2, valid_history_2 = [], []
# 1. 반복 학습 (epoch 루프)
for i in range(n_epochs):
# 2. 훈련 데이터 셔플 → randperm, index_select
indices = torch.randperm(X_train.size(0))
X_ = torch.index_select(X_train, dim=0, index=indices) # X_train[indices]는 텐서가 엉킬 수 있어 추천하지 않습니다.
y_ = torch.index_select(y_train, dim=0, index=indices) #데이터 꼬임 방지
# 3. 미니배치로 분할 → split
X_ = X_.split(batch_size, dim=0)
y_ = y_.split(batch_size, dim=0)
# 훈련을 위한 변수 초기화
train_loss, valid_loss = 0, 0
y_pred_list = [] # 예측 결과를 리스트에 넣어 두지 않으면 다 날아가기 때문
# 4. 훈련
for X_i, y_i in zip(X_, y_):
y_pred_i = model2(X_i)
loss = F.binary_cross_entropy(y_pred_i, y_i)
# 최적화함수 초기화 → zero_grad
optimizer2.zero_grad()
# 오차역전파 진행 → backward
loss.backward()
# 최적화함수 업데이트 → step
optimizer2.step()
# 5. 평균 훈련 손실 계산
train_loss += float(loss)
train_loss = train_loss / len(X_) # 1 epoch에 대한 loss가 담기게 된다.
# 6. 검증
with torch.no_grad():
# 미니배치로 분할
X_ = X_valid.split(batch_size, dim=0)
y_ = y_valid.split(batch_size, dim=0)
# 예측 과정
for X_i, y_i in zip(X_, y_):
y_pred_i = model2(X_i)
loss = F.binary_cross_entropy(y_pred_i, y_i)
valid_loss += float(loss)
y_pred_list.append(y_pred_i) # 나중에 밖에서 평가할 때 쓰려고 저장하는 거라 검증에서만 넣음
# 7. 손실 기록
valid_loss = valid_loss / len(X_)
# 그래프 그리기 위해 값을 리스트에 담아두기
train_history_2.append(train_loss)
valid_history_2.append(valid_loss)
# 8. 진행 상황 출력
if (i+1) % print_interval == 0:
print(f"epoch: {i+1}, train_loss: {train_loss:.4e}, valid_loss: {valid_loss:.4e}, lowest_loss: {lowest_loss:.4e}")
# 9. 베스트 모델 저장
if valid_loss <= lowest_loss:
lowest_loss = valid_loss
lowest_epoch = i
best_model = deepcopy(model2.state_dict())
# 10. 조기 종료 (Early Stopping)
else:
if (early_stop > 0) and (lowest_epoch + early_stop < i+1):
print(f"{early_stop} epochs 동안 모델이 개선되지 않음")
break
# 11. 베스트 모델 복원
model2.load_state_dict(best_model)
# 12. 최종 성능 출력
print(f"{lowest_epoch+1} epochs에서 검증 데이터 최저 손실 값: {valid_loss}")

# 시각화
plt.figure(figsize=(7,3))
plt.grid()
plt.plot(range(1, len(train_history_2)+1), train_history_2, label="train_loss")
plt.plot(range(1, len(valid_history_2)+1), valid_history_2, label="valid_loss")
plt.legend()
plt.yscale("log")
plt.show()
# 과대적합 위험이 있음





test_loss = 0
y_pred_test = []
with torch.no_grad():
# 미니배치로 분할
X_ = X_test.split(batch_size, dim=0)
y_ = y_test.split(batch_size, dim=0)
# 예측 과정
for X_i, y_i in zip(X_, y_):
y_pred_i = model2(X_i)
loss = F.binary_cross_entropy(y_pred_i, y_i)
test_loss += float(loss)
y_pred_test.append(y_pred_i)
test_loss = test_loss / len(X_)
y_pred_test = torch.cat(y_pred_test, dim=0)
# 정확도: 맞춘 데이터 수 / 전체 데이터 수
# True → 1, False → 0
# 맞춘 데이터 수 세기
corr_cnt = (y_test == (y_pred_test >= 0.5)).sum()
total_cnt = y_test.size(0)
accuracy = corr_cnt / total_cnt
print(f"accuracy: {accuracy}")
accuracy: 1.0
→ 과대적합일 가능성이 높다~
# 라이브러리 불러오기
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from copy import deepcopy
from tqdm import tqdm
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, classification_report
train = datasets.MNIST(
"./data"
, train=True
, download=True
, transform=transforms.Compose([
transforms.ToTensor()
])
)
test = datasets.MNIST(
"./data"
, train=False
, download=True
, transform=transforms.Compose([
transforms.ToTensor()
])
)
def plot(x) :
img = (np.array(x.detach().cpu(), dtype="float")).reshape(28, 28)
plt.figure(figsize=(3, 3))
plt.imshow(img, cmap="gray")
plt.show()
plot(train.data[0])

# 픽셀 값을 0~1 사이의 값으로 정규화하는 과정이 필요: 숫자 크기가 혼자 너무 크면 학습에 손해
X_data = train.data.float() / 255.
y_data = train.targets
X_test = test.data.float() / 255.
y_test = test.targets
print(X_data.shape, y_data.shape)
print(X_test.shape, y_test.shape)
torch.Size([60000, 28, 28]) torch.Size([60000])
torch.Size([10000, 28, 28]) torch.Size([10000])
# 28x28 2차원 데이터를 1차원으로 변경 → shape 변경: view()
X_data = X_data.view(X_data.size(0),-1) # '-1'이 의미하는 것: 알아서 해줘(남은 모두)
X_test = X_test.view(X_test.size(0),-1)
print(X_data.shape, y_data.shape)
print(X_test.shape, y_test.shape)
torch.Size([60000, 784]) torch.Size([60000])
torch.Size([10000, 784]) torch.Size([10000])
# 데이터 분리
X_train, X_valid, y_train, y_valid = train_test_split(X_data, y_data, test_size=0.2, random_state=22)
print(X_train.shape, y_train.shape)
print(X_valid.shape, y_valid.shape)
print(X_test.shape, y_test.shape)
torch.Size([48000, 784]) torch.Size([48000])
torch.Size([12000, 784]) torch.Size([12000])
torch.Size([10000, 784]) torch.Size([10000])
# 입력 크기 설정
input_size = X_train.size(-1)
# 출력 크기
# 클래스의 크기만큼 출력 크기를 설정해 주어야 함: 다중 분류니까
output_size = int(max(y_train)+1)
print(input_size, output_size)
784 10
y_train = y_train.long()
y_valid = y_valid.long()
y_test = y_test.long()

nn.BCELoss(): binary cross entropy lossF.binary_cross_entropy()nn.CrossEntropyLoss()를 쓰는 것을 적극 권장nn.Sequential() 활용# 다중분류 → 출력층의 활성화함수를 작성하지 않고 학습 시 손실함수 nn.CrossEntropyLoss()를 사용하는 것을 권장함
model = nn.Sequential(
nn.Linear(input_size, 500)
, nn.ReLU()
, nn.Linear(500, 400)
, nn.ReLU()
, nn.Linear(400, 300)
, nn.ReLU()
, nn.Linear(300, 200)
, nn.ReLU()
, nn.Linear(200, 100)
, nn.ReLU()
, nn.Linear(100, 50)
, nn.ReLU()
, nn.Linear(50, output_size)
# , nn.LogSoftmax(dim=1)
)
class MyModel(nn.Module):
def __init__(self, input_dim, output_dim):
super().__init__()
self.linear1 = nn.Linear(input_dim, 500)
self.linear2 = nn.Linear(500, 400)
self.linear3 = nn.Linear(400, 300)
self.linear4 = nn.Linear(300, 200)
self.linear5 = nn.Linear(200, 100)
self.linear6 = nn.Linear(100, 50)
self.linear7 = nn.Linear(50, output_dim)
self.act_relu = nn.ReLU()
def forward(self, x):
h = self.act_relu(self.linear1(x))
h = self.act_relu(self.linear2(h))
h = self.act_relu(self.linear3(h))
h = self.act_relu(self.linear4(h))
h = self.act_relu(self.linear5(h))
h = self.act_relu(self.linear6(h))
y = self.linear7(h) # 🔥 LogSoftmax 제거
return y
model1 = MyModel(input_size, output_size)
device = torch.device("cpu")
print("GPU 사용 가능 여부:", tqdmorch.cuda.is_available())
if torch.cuda.is_available():
device = torch.device("cuda:0")
print("사용 중인 디바이스:", device)
# GPU 연결 됐으면 GPU로 모델 변경
model = model.to(device)
# gpu로 데이터 복사
X_train = X_train.to(device)
X_valid = X_valid.to(device)
X_test = X_test.to(device)
y_train = y_train.to(device)
y_valid = y_valid.to(device)
y_test = y_test.to(device)
# device 확인
device
GPU 사용 가능 여부: True
사용 중인 디바이스: cuda:0
device(type='cuda', index=0)
cuda:0으로 지정했기 때문# 최적화 함수
optimizer = optim.Adam(model.parameters())
# 손실 함수 → 출력층 활성화 함수 X
loss_func = nn.CrossEntropyLoss()
# 학습 파라미터 설정
n_epochs = 1000
batch_size = 256
print_interval = 10
lowest_loss = np.inf
best_model = None
early_stop = 50
lowest_epoch = np.inf
# 학습 코드 구현: 학습 반복문
train_history, valid_history = [], []
# 1. 반복 학습 (epoch 루프)
for i in range(n_epochs):
# 2. 훈련 데이터 셔플
indices = torch.randperm(X_train.size(0)).to(device) # 인덱스 gpu 복사
X_ = torch.index_select(X_train, dim=0, index=indices)
y_ = torch.index_select(y_train, dim=0, index=indices)
# 3. 미니배치로 분할
X_ = X_.split(batch_size, dim=0)
y_ = y_.split(batch_size, dim=0)
# 변수 초기화
train_loss, valid_loss = 0, 0
y_pred = []
# 4. 훈련
for X_i, y_i in zip(X_, y_):
y_i_pred = model(X_i)
loss = loss_func(y_i_pred, y_i)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss += float(loss)
# 5. 평균 훈련 손실 계산
train_loss = train_loss / len(X_)
# 6. 검증
with torch.no_grad():
X_ = X_valid.split(batch_size, dim=0)
y_ = y_valid.split(batch_size, dim=0)
for X_i, y_i in zip(X_, y_):
y_i_pred = model(X_i)
loss = loss_func(y_i_pred, y_i)
valid_loss += float(loss)
y_pred.append(y_i_pred)
valid_loss = valid_loss / len(X_)
# 7. 손실 기록
train_history.append(train_loss)
valid_history.append(valid_loss)
# 8. 진행 상황 출력
if (i+1)%print_interval == 0:
print(f"epoch: {i+1}, valid_loss: {valid_loss:.4e}, train_loss: {train_loss:.4e}, lowest_loss: {lowest_loss:.4e}")
# 9. 베스트 모델 저장
if valid_loss <= lowest_loss:
lowest_loss = valid_loss
lowest_epoch = i
best_model = deepcopy(model.state_dict())
# 10. 조기 종료 (Early Stopping)
if (early_stop > 0) and (lowest_epoch + early_stop < i+1):
print(f"{early_stop} epochs 동안 모델이 개선되지 않음")
break
# 11. 베스트 모델 복원
model.load_state_dict(best_model)
# 12. 최종 성능 출력
print(f"{lowest_epoch + 1} epochs에서 가장 낮은 검증손실값: {lowest_loss}")
epoch: 10, valid_loss: 1.2144e-01, train_loss: 2.3562e-02, lowest_loss: 9.6213e-02
epoch: 20, valid_loss: 1.3192e-01, train_loss: 1.1287e-02, lowest_loss: 9.6213e-02
epoch: 30, valid_loss: 1.2563e-01, train_loss: 7.9283e-03, lowest_loss: 9.6213e-02
epoch: 40, valid_loss: 1.1135e-01, train_loss: 5.5960e-03, lowest_loss: 9.6213e-02
epoch: 50, valid_loss: 1.4229e-01, train_loss: 5.8550e-03, lowest_loss: 9.6213e-02
50 epochs 동안 모델이 개선되지 않음
9 epochs에서 가장 낮은 검증손실값: 0.09621332807743803
# 손실 곡선(train_loss, valid_loss)
plt.figure(figsize = (7,3))
plt.grid(True)
plt.plot(range(1,len(train_history)+1), train_history, label = "train loss")
plt.plot(range(1,len(valid_history)+1), valid_history, label = "valid loss")
plt.legend()
plt.show()

# 테스트 데이터로 모델 평가
test_loss = 0
y_pred_test = []
# 검증
with torch.no_grad():
X_ = X_test.split(batch_size, dim = 0)
y_ = y_test.split(batch_size, dim = 0)
for X_i, y_i in zip(X_, y_):
y_pred_i = model(X_i)
loss = loss_func(y_pred_i, y_i)
test_loss += loss
y_pred_test.append(y_pred_i)
# 손실 기록
test_loss = test_loss / len(X_)
# 기록 합치기
y_pred_test = torch.cat(y_pred_test, dim = 0)
# 다중 분류 정확도 계산
# torch.argmax(y_pred_test, dim=1)
# 1개의 입력 데이터에 대한 정답 카테고리별 확률 → 비교 → 최댓값 → label
corr_cnt = (y_test == torch.argmax(y_pred_test, dim=1)).sum()
total_cnt = float(y_test.size(0))
corr_cnt/total_cnt
# 정확도 0.9760
tensor(0.9760, device='cuda:0')
# gpu로 할당된 데이터를 cpu로 가져와서 출력해줘야 한다
pd.DataFrame(
confusion_matrix(
y_test.cpu() # gpu → cpu: numpy ndarray 값을 넣어야 하기 때문
, torch.argmax(y_pred_test, dim=1).cpu() # gpu → cpu
)
, index = [f"true_{i}" for i in range(10)]
, columns = [f"pred_{i}" for i in range(10)]
)
# 예측과 실제값의 데이터 세트 개수를 표로 표현

from sklearn.metrics import classification_report
# GPU → CPU
y_true = y_test.cpu()
y_pred = torch.argmax(y_pred_test.cpu(), dim=1)
report = classification_report(y_true, y_pred)
print(report)
precision recall f1-score support
0 0.98 0.99 0.98 980
1 0.99 0.99 0.99 1135
2 0.98 0.97 0.98 1032
3 0.99 0.97 0.98 1010
4 0.95 0.99 0.97 982
5 0.99 0.96 0.97 892
6 0.97 0.98 0.98 958
7 0.97 0.99 0.98 1028
8 0.97 0.96 0.96 974
9 0.98 0.95 0.96 1009
accuracy 0.98 10000
macro avg 0.98 0.98 0.98 10000
weighted avg 0.98 0.98 0.98 10000

































3*3


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from tqdm import tqdm
from copy import deepcopy
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import dataloader
from sklearn.model_selection import train_test_split
device = torch.device("cpu") # 기본값 cpu로 설정
print("GPU 사용 여부:", torch.cuda.is_available())
if torch.cuda.is_available():
device = torch.device("cuda:0")
print("현재 사용 중인 디바이스:", device)
GPU 사용 가능 여부: True
사용 중인 디바이스: cuda:0
# 데이터 불러오기
train = datasets.MNIST(
"./data"
, train=True
, download=True
, transform=transforms.Compose([
transforms.ToTensor()
])
)
test = datasets.MNIST(
"./data"
, train=False
, transform=transforms.Compose([
transforms.ToTensor()
])
)
randperm, index_select, split으로 수작업 해주지 않아도 됨# drop_last: 배치 사이즈로 데이터를 분리했을 때 마지막 배치가 맞지 않으면(개수가 부족하면) 제외
train_loader = torch.utils.data.DataLoader(
dataset=train
, batch_size=100
, shuffle=True
, drop_last=True
)
test_loader = torch.utils.data.DataLoader(
dataset=test
, batch_size=100
, shuffle=True
, drop_last=True
)
print(f"훈련 데이터의 총 배치 수: {len(train_loader)}")
print(f"테스트 데이터의 총 배치 수: {len(test_loader)}")
훈련 데이터의 총 배치 수: 600
테스트 데이터의 총 배치 수: 100