Data Analysis Study

딥러닝 분류(Classification) 이진분류 + 추가 학습

Solbi Lee 2025. 7. 20. 10:49

1. 코드의 목적과 역할 분석 

주어진 데이터(공부 시간, 커피 수)로 학생의 시험 결과(합격 or 불합격)를 예측하는 신경망 모델 구축.

역할 : 

- 데이터 학습을 통한 합격 확률 예측 

- 데이터 기반으로 합격과 불합격을 자동 분류 

- 실제 현업에서 쓰이는 이진 분류 문제의 기초적인 이해 

2. 필수 라이브러리 

- Numpy : 수치 데이터 배열 생성 및 처리 (np.array(

- TensorFlow/Keras 

    - sequential : 순차적으로 층(layer)을 추가하여 간편하게 모델 구축 

    - Dense : 각 층을 구성할 때 사용하는 기본 뉴런층 (완전 연결층)

3.  주요 기능 분석 

신경망 설계 : 입력층 (공부 시간, 커피 수 ) -> 은닉층(Dense/ReLu) -> 출력층 (Dense/Sigmoid)

모델 컴파일 : 손실함수 (binary_crossentropy), 최적화함수(adam), 평가지표(accuracy)

학습 및 예측 : 주어진 데이터로 학습 후 새로운 입력 데이터를 분류 및 확률 예측 

 

4. 코드 

# layer2.py (완료)
# sigmoid 활성화 함수

# 라이브러리 임포트
from tensorflow.keras.models import Sequential # 케라스의 순차 모델 클래스, 층을 차례로 쌓아 올리는 구조
from tensorflow.keras.layers import Dense # 완전 연결층. 입력 노드와 출력 노드가 모두 연결된 형태
import numpy as np #기본적인 수치 연산과 배열 생성, 조작을 담당

# 입력 데이터 (공부시간, 커피 수)
# 입력값 범위가 작아서 정규화를 생략해도 크게 무방하지 않지만 일반적으로는 MinMaxScaler 같은 전처리 권장
X = np.array([[1, 0], [2, 1], [3, 1], [4, 2], [5, 3]])

# 출력 데이터 (합격=1, 불합격=0), 이진분류
y = np.array([0, 0, 0, 1, 1])

# 모델 컴파일
model = Sequential() # 빈 시퀀셜 모델 객체 생성
model.add(Dense(4, input_dim=2, activation='relu')) # 은닉층
# 은닉 뉴런 4개, 입력 특성이 2차원임을 지정, 비선형성을 부여, 음수 값은 0으로 잘라 학습 속도 높임, 희소성 유도
model.add(Dense(1, activation='sigmoid')) # 출력층
# 하나의 값(합격 확률) 출력, 출력 범위 0~1 확률 해석에 적합하다.

# 모델 컴파일
model.compile(
    optimizer='adam', # 최적화 함수 (적응적 학습률을 사용하는 확률적 경사하강법 변형)
    loss='binary_crossentropy', # 손실 함수 (이진분류용 로그 손실 함수)
    metrics=['accuracy'] # 지표 : 정확도를 추가로 계산, 출력
)

# 모델 학습
# 전체 데이터 셋을 200번 반복 학습
# verbose=0 : 학습 진행 로그를 출력하지 않음
# verbose=1 로 하면 진척 바, 2로 하면 epoch별 요약 로그가 실시간으로 출력됨
model.fit(X, y, epochs=200, verbose=0)

# 예측 테스트
test_data = np.array([[3, 2], [5, 1], [1, 0]])
# 공부 3시간, 커피 2잔, 공부 5시간 커피 1잔, 공부 1시간 커피 0잔 데이터
predictions = model.predict(test_data) # 각 샘플의 합격 확률 (0~1 실수)

# 결과 출력
for i, pred in enumerate(predictions):
    print(f"입력:{test_data[i]}, 예측확률:{pred[0]:.4f}, \
        분류: {'합격' if pred[0]>=0.5 else '불합격'}")

 

# 결과 
입력:[3 2], 예측확률:0.5565,         분류: 합격
입력:[5 1], 예측확률:0.6189,         분류: 합격
입력:[1 0], 예측확률:0.4395,         분류: 불합격

# 합격 분류는 맞지만 모델은 아직 확신이 낮은 상태임 
# 불합격 분류 역시 0.44라는 확률은 낮은 자신감 
=> Classification(분류)  관점에서는 맞혔지만, Probablility(확률) 관점에서 보면 경계에 걸쳐있어 판별 기준 바로 위아래에서 왔다 갔다 하는 모습

=> 예측 확률이 0.56~0.62 정도로 낮게 나왔음. 모델의 신뢰도를 높이기 위한 개선 방안 필요

 

원인 분석 

1. 입력 스케일 불일치 

- 공부시간과 커피수를 정규화 없이 그대로 사용 

- 각 피처의 영향력이 왜곡되어, 학습이 덜 안정적일 수 있음 

 

2. 데이터 양, 다양성 부족 

- 5개의 샘플만으로 복잡한 경계면을 학습하기엔 정보가 충분치 않음 

 

3. 모델 구조, 하이퍼파라미터 

- 은닉층 4개 뉴런, epoch 200회로는 과소 적합 가능성 

- 학습률 기본값, 배치 크기 등도 미세 조정이 필요함 

 

4. 이진 분류 손실 특성 

- binary_crossentropy는 확률 예측 시 경계 근처 값(0.5±ε)에 덜 패널티를 줄 수 있음

 

 

# layer2_improved.py
# sigmoid 활성화 함수 기반 이진 분류 모델
# 입력 정규화(MinMaxScaler), 훈련/검증 데이터 분리, 하이퍼파리미터 튜닝, 조기 종료 콜백, 학습 로그 출력 

from tensorflow.keras.models import Sequential               # 케라스 순차 모델 클래스
from tensorflow.keras.layers import Dense                      # 전결합(Dense) 레이어
from tensorflow.keras.optimizers import Adam                    # Adam 옵티마이저
from tensorflow.keras.callbacks import EarlyStopping           # 조기 종료 콜백
from sklearn.preprocessing import MinMaxScaler                 # 입력 피처 정규화
import numpy as np                                             # 수치 연산용

# 1) 데이터 준비
# X: [공부시간, 커피 수], y: [불합격=0, 합격=1]
X = np.array([[1, 0],
              [2, 1],
              [3, 1],
              [4, 2],
              [5, 3]])
y = np.array([0, 0, 0, 1, 1])

# 2) 입력 데이터 정규화 (0~1 범위로 스케일링)
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)  # fit: min/max 계산, transform: 스케일링 적용

# 3) 모델 구성
model = Sequential()
model.add(Dense(8,                # 은닉층 뉴런 8개
                input_dim=2,      # 입력 특성 2차원 지정
                activation='relu' # ReLU 활성화로 비선형성 부여
               ))
model.add(Dense(1,                # 출력층 뉴런 1개 → 확률값 예측
                activation='sigmoid'  # Sigmoid 활성화로 0~1 확률 출력
               ))

# 4) 모델 컴파일: Adam 옵티마이저(학습률 조정), 이진분류 손실, 정확도 지표
optimizer = Adam(learning_rate=0.01)  # 기본 0.001 → 0.01로 조정해 빠른 수렴 시도
model.compile(optimizer=optimizer,
              loss='binary_crossentropy',
              metrics=['accuracy'])

# 5) EarlyStopping 콜백 설정: val_loss 기준 10회 연속 개선 없으면 학습 중단
es = EarlyStopping(monitor='val_loss',
                   patience=10,
                   restore_best_weights=True)

# 6) 모델 학습: 검증셋 20% 분리, 최대 500 epochs, EarlyStopping 적용
history = model.fit(X_scaled,       # 정규화된 입력
                    y,              # 레이블(0/1)
                    epochs=500,     # 최대 반복 횟수
                    validation_split=0.2,  # 20%는 검증용
                    callbacks=[es], # 조기 종료 콜백
                    verbose=1       # 학습 진행바 출력
                   )

# 7) 예측 테스트: 새로운 샘플도 동일한 스케일링 적용 후 예측
test_data = np.array([[3, 2],       # 공부 3h, 커피 2잔
                      [5, 1],       # 공부 5h, 커피 1잔
                      [1, 0]])      # 공부 1h, 커피 0잔
test_scaled = scaler.transform(test_data)  # 스케일 변환
predictions = model.predict(test_scaled)   # 확률 예측 (0~1)

# 8) 결과 출력: threshold=0.5로 분류, 소수점 4자리까지 표시
for i, prob in enumerate(predictions):
    label = '합격' if prob[0] >= 0.5 else '불합격'
    print(f"입력:{test_data[i]}, 예측확률:{prob[0]:.4f}, 분류: {label}")
    
    --
    
입력:[3 2], 예측확률:0.9353, 분류: 합격
입력:[5 1], 예측확률:0.5650, 분류: 합격
입력:[1 0], 예측확률:0.0001, 분류: 불합격