[부트캠프 휴강일] 손에 익을 때까지 - 다항 회귀부터 로지스틱 회귀까지

2026. 7. 20. 21:00·AI
반응형
wb_sunny 오늘 일상 공유

오늘은 부트캠프 휴강일이었지만 평소와 똑같이 하루를 보냈다. 아침에 일어나서 운동을 다녀왔고, 이후 프로그래머스 문제를 한 문제 풀었다. 나머지 시간은 다항 회귀부터 로지스틱 회귀까지 이론을 처음부터 다시 복습하는 데 썼다. 오늘 블로그는 프로그래머스 풀이와, 다항 회귀 ~ 로지스틱 회귀 복습 내용, 그리고 오늘 느낀 점을 정리해보려고 한다.

edit_note 오늘 코테 문제
문제크레인 인형뽑기 게임
플랫폼프로그래머스
핵심 요구사항board와 moves가 주어졌을 때, moves 순서대로 인형을 뽑아 바구니에 넣고 같은 인형이 연속으로 쌓이면 터뜨려서 사라진 인형 개수의 2배를 구하기
python
def solution(board, moves):
    answer = 0
    
    basket = []  # 뽑은 인형을 쌓아둘 바구니
    
    board_len = len(board)
    top_row = {j: -1 for j in range(board_len)}  # 각 열의 최상단 인형 위치 (-1: 없음)

    # 각 열마다 최상단 인형 위치를 한 번만 탐색
    for i in range(board_len):
        for j in range(board_len):
            if top_row[j] == -1 and board[i][j] != 0:
                top_row[j] = i
    
    for move in moves:
        col = move - 1  # 배열은 0부터 시작하므로 보정
        if top_row[col] == -1:  # 해당 열에 인형이 없으면 건너뛰기
            continue
        
        row = top_row[col]
        value = board[row][col]
        
        if basket and basket[-1] == value:  # 바구니 맨 위와 같은 인형이면 터뜨리기
            basket.pop()
            answer += 2
        else:
            basket.append(value)
        
        if row + 1 < board_len:  # 아래 칸이 board 범위 안이면 최상단 위치 갱신
            top_row[col] = row + 1
        else:
            top_row[col] = -1  # 범위를 벗어나면 더 이상 인형 없음
        
    return answer
풀이 접근법
처음에는 board를 다른 변수에 복사해서 moves에 접근할 때마다 매번 최상단 인형을 찾을 생각이었다. 그런데 그러면 매번 복사한 board를 훑으면서 인형을 지우는 방식이라 비효율적이라는 생각이 들었다. 조금 고민하다가, 각 열마다 인형이 있는 가장 위쪽 위치를 한 번만 찾아두고 값이 빠질 때마다 그 위치만 업데이트하는 방식으로 접근을 바꿨다. 이렇게 하니 board를 복사할 필요도 없고, board 자체를 크게 건드릴 필요도 없어졌다.
1
바구니와 최상단 위치 초기화
basket을 빈 배열로 초기화하고, 각 열의 인형 최상단 위치를 담을 top_row를 전부 -1(인형 없음)로 초기화한다.
2
각 열의 최상단 인형 위치를 한 번만 탐색
board를 훑으면서 각 열에서 처음 등장하는 0이 아닌 값의 행 번호를 top_row에 저장한다. 한 번만 계산해두고 이후 moves를 처리할 때는 이 값만 갱신한다.
3
moves를 순회하며 바구니에 쌓거나 터뜨리기
배열이 0부터 시작하니 col = move - 1로 변환한다. 해당 열의 top_row가 -1이면 인형이 없으니 넘어간다. 바구니의 맨 마지막 인형과 새로 뽑은 인형이 같으면 pop하고 answer += 2, 다르면 그냥 append한다. 인형을 뽑고 나면 그 아래 칸이 board 범위 안에 있을 때만 top_row를 한 칸 아래로 갱신하고, 범위를 벗어나면 -1로 바꿔서 더 이상 인형이 없다고 표시한다.
시간복잡도
최상단 위치를 초기화하는 이중 for문이 O(n²)(n = board_len), moves를 순회하는 부분은 O(m)(m = moves 길이)이고 안쪽 연산은 전부 딕셔너리 접근과 리스트 append/pop이라 O(1)이다. 합치면 O(n² + m)이 된다.
school 다항 회귀 → 로지스틱 회귀 복습
오늘은 이번 주 이론 진도인 다항 회귀부터 로지스틱 회귀까지를 처음부터 다시 훑었다. 실습 코드는 생각보다 쉬웠는데, 그만큼 복습이 필수적이라는 걸 느낀 하루였다.
다항 회귀 (Polynomial Regression)
직선으로는 데이터를 잘 표현하지 못할 수 있는데, 이럴 때 가설 함수를 곡선(2차, 3차 함수 등)으로 만들어주는 게 다항 회귀다. 속성이 1개면 원하는 차수만큼 속성을 늘려서 다중 선형 회귀처럼 풀면 되고, 속성이 여러 개인 다중 다항 회귀도 방식은 동일하다.
일반화된 수식 (n차 다항 회귀)
속성이 1개일 때 n차 다항 회귀의 가설 함수는 아래처럼 x의 거듭제곱 항들로 이루어진다. x, x², x³ ... xⁿ을 각각 새로운 속성 x1, x2, ... xn처럼 취급하면, 결국 입력 변수가 n개인 다중 선형 회귀와 완전히 같은 형태가 되어 똑같은 방식(경사 하강법, 정규 방정식)으로 θ를 구할 수 있다.
가설 함수 (n차 다항식)
hθ(x) = θ₀ + θ₁x + θ₂x² + ··· + θₙxⁿ
다중 선형 회귀로 치환 (x1 = x, x2 = x², ... xn = xⁿ)
hθ(x) = θ₀ + θ₁x₁ + θ₂x₂ + ··· + θₙxₙ
sklearn으로 구현하기
직접 x², x³ 같은 항을 하나하나 계산해서 열을 추가하지 않아도, scikit-learn의 PolynomialFeatures를 쓰면 원하는 차수만큼의 다항 속성을 자동으로 만들어준다. 이후는 다중 선형 회귀와 똑같이 LinearRegression으로 학습하면 된다.
python
from sklearn.preprocessing import PolynomialFeatures

polynomial_transformer = PolynomialFeatures(2)  # 2차항까지 다항 속성 생성
polynomial_data = polynomial_transformer.fit_transform(X)  # x, x^2 등 변환된 데이터
polynomial_feature_names = polynomial_transformer.get_feature_names_out(X.columns)  # 생성된 열 이름 확인

X = pd.DataFrame(polynomial_data, columns=polynomial_feature_names)

# 이후는 선형 회귀와 방식이 동일
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=5)
model = LinearRegression()
model.fit(X_train, y_train)
왜 분류에는 선형 회귀 대신 로지스틱 회귀?
분류 문제도 선형 회귀로 풀 수는 있다. 예를 들어 공부 시간으로 시험 통과 여부(0/1)를 예측할 때 최적선을 긋고 0.5를 기준으로 나누면 된다. 하지만 예외적으로 동떨어진 데이터가 하나만 추가돼도 최적선이 크게 기울어지면서 분류 기준이 확 바뀌어버린다. 선형 회귀는 이런 예외 데이터에 너무 민감해서, 분류에는 잘 쓰지 않는다.
시그모이드 함수와 가설 함수
로지스틱 회귀는 데이터에 가장 잘 맞는 직선이 아니라, 가장 잘 맞는 시그모이드 곡선을 찾는다. 시그모이드 함수는 어떤 값을 넣어도 항상 0과 1 사이의 결과를 낸다는 게 핵심 특징이다. x가 커질수록 1에 가까워지고, x가 작아질수록 0에 가까워진다. 그래서 예외적인 데이터가 있어도 선형 회귀만큼 민감하게 반응하지 않는다.
1 0.5 0 0 z g(z)
가설 함수는 선형 회귀의 결과(θᵀx)를 시그모이드 함수의 입력으로 넣는 형태다. 결과로 나온 0~1 사이의 값은 목표 변수가 1일 확률로 해석한다. 예를 들어 결과가 0.9라면 "이 데이터가 1(통과)일 확률이 90%"라는 뜻이고, 0.5를 기준으로 통과/탈락을 분류한다.
시그모이드 함수
g(z) = 11 + e−z
로지스틱 회귀 가설 함수 (일반화)
hθ(x) = g(θᵀx) = 11 + e−θᵀx
로그 손실과 경사 하강법
로지스틱 회귀는 선형 회귀처럼 평균 제곱 오차(MSE)를 손실 함수로 쓰지 않는다. 시그모이드 함수에 MSE를 그대로 적용하면 손실 함수가 울퉁불퉁해져서 경사 하강법으로 전역 최솟값을 찾기 어렵기 때문이다. 대신 로그 손실(log loss)을 사용하는데, 이 손실 함수를 미분해서 경사 하강법에 적용하면 신기하게도 선형 회귀의 경사 하강법 식과 형태가 거의 동일하게 나온다.
로그 손실 함수
J(θ) = − 1m mΣi=1 [y⁽ⁱ⁾ log(hθ(x⁽ⁱ⁾)) + (1−y⁽ⁱ⁾) log(1−hθ(x⁽ⁱ⁾))]
경사 하강법 업데이트 (θ 전체를 한 번에)
θ := θ − α · 1m Xᵀ(hθ(X) − y)
sklearn으로 구현하기
직접 시그모이드 함수나 경사 하강법을 구현하지 않아도, scikit-learn의 LogisticRegression을 쓰면 몇 줄로 학습부터 평가까지 끝낼 수 있다. 내부적으로는 로그 손실을 최소화하는 최적의 θ를 경사 하강법 계열 알고리즘(solver)으로 한 번에 계산해주고, 3개 이상 분류에도 동일하게 적용할 수 있다.
python
from sklearn.linear_model import LogisticRegression

model = LogisticRegression(solver='saga', max_iter=2000)  # solver: 최적화 알고리즘, max_iter: 최대 반복 횟수
model.fit(X_train, y_train)  # 학습 (최적의 theta를 찾는다)

model.predict(X_test)  # 예측 클래스(0 또는 1) 반환
model.score(X_test, y_test)  # 정확도 반환
functions 분류 모델 평가 지표
지금까지는 정확도만으로 분류 모델을 평가했는데, 데이터가 불균형하거나 특정 클래스 판단이 중요한 문제에서는 정확도만으로 진짜 성능을 파악하기 어렵다. 그래서 혼동 행렬을 기반으로 한 여러 평가 지표들을 같이 정리해봤다.
혼동 행렬 (Confusion Matrix)
예측 결과와 실제 정답을 조합해 네 가지 경우의 수로 정리한 표다.
TP실제 Positive를 Positive로 정확히 예측
FP실제 Negative인데 Positive로 잘못 예측
FN실제 Positive인데 Negative로 잘못 예측
TN실제 Negative를 Negative로 정확히 예측
정확도 (Accuracy)
전체 예측 중 맞은 비율. 클래스가 불균형하면(예: 10,000개 중 10개만 불량) 전부 정상으로 예측해도 99.9%가 나올 수 있어서 이 지표만으로는 부족할 수 있다.
Accuracy
Accuracy = TP + TNTP + TN + FP + FN
정밀도 (Precision)
Positive로 예측한 것 중 실제로 Positive인 비율. False Positive를 줄이는 데 중점을 둔다.
Precision
Precision = TPTP + FP
재현율 (Recall)
실제 Positive 중 Positive로 정확히 예측된 비율. False Negative를 줄이는 데 중점을 두며, 질병 진단처럼 놓치면 안 되는 문제에서 중요하다.
Recall
Recall = TPTP + FN
특이도 (Specificity)
실제 Negative 중 Negative로 정확히 예측한 비율. 정밀도와 마찬가지로 False Positive에 관심을 두지만, 관점이 실제 Negative 데이터 기준이라는 점이 다르다.
Specificity
Specificity = TNTN + FP
F1 점수
정밀도와 재현율의 조화 평균. 둘 중 하나라도 낮으면 전체 점수가 크게 떨어지기 때문에, 클래스 불균형이 크거나 정밀도·재현율이 둘 다 중요할 때 사용한다.
F1 Score
F1 = 2 · Precision · RecallPrecision + Recall
ROC-AUC 점수
임계값(threshold)을 0.1, 0.3, 0.7 등으로 바꿔가며 계산한 FPR(실제 Negative를 Positive로 잘못 판단한 비율)과 TPR(재현율)의 쌍을 점으로 이은 게 ROC 곡선이다. 곡선이 왼쪽 위로 치우칠수록 Positive/Negative를 잘 구분하는 모델이고, 이 곡선 아래 면적이 AUC다. AUC가 1에 가까울수록 좋은 모델, 0.5에 가까우면 무작위 분류와 다를 게 없다는 뜻이다.
scikit-learn으로 계산하기
python
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix

# 예측 결과
y_pred = model.predict(X_test)

# 정확도
accuracy = accuracy_score(y_test, y_pred)
print("accuracy: ", accuracy)

# 정밀도 (다중 클래스의 경우 average 옵션 필요)
precision = precision_score(y_test, y_pred, average='macro')
print("precision: ", precision)

# 재현율
recall = recall_score(y_test, y_pred, average='macro')
print("recall: ", recall)

# F1 점수
f1 = f1_score(y_test, y_pred, average='macro')
print("f1 score: ", f1)

# 혼동 행렬
conf_matrix = confusion_matrix(y_test, y_pred)
print("\nconfusion matrix:\n", conf_matrix)
특이도는 sklearn에 바로 계산하는 함수가 없어서, 혼동 행렬 값을 가지고 직접 tn / (tn + fp)로 계산해야 한다. ROC-AUC는 model.predict_proba(X_test)로 얻은 확률값을 roc_curve, roc_auc_score에 넣어서 구한다.
오늘 한 줄 정리
✓다항 회귀는 가설 함수를 곡선으로 만들어 복잡한 관계를 학습하고, 로지스틱 회귀는 그 결과를 시그모이드에 통과시켜 0~1 사이 확률로 바꿔 분류에 사용한다. 손실 함수만 로그 손실로 바뀔 뿐, 경사 하강법의 형태는 선형 회귀와 거의 같다.
✓정확도만으로는 불균형 데이터에서 성능을 제대로 못 볼 수 있어서, 문제 특성에 맞게 정밀도(오탐 최소화), 재현율(놓침 최소화), F1(둘의 균형), ROC-AUC(전반적 구분 능력)를 함께 고려해야 한다.
nights_stay 오늘 하루 회고

오늘은 휴강일이라 여유 있게 복습에 시간을 쏟은 날이었다. 다항 회귀부터 로지스틱 회귀까지 실습 코드를 직접 다시 짜보니 생각보다 쉽게 손에 붙어서, 복습이 정말 필수적이라는 걸 다시 느꼈다. 이번 주 진행되는 이론도 놓치지 않고 복습하고, 부트캠프 수업 때 했던 실습도 다시 한번 손에 익혀야겠다는 생각이 들었다. 최근에 자신감이 조금 떨어져 있었는데, 오늘 복습하면서 이해가 되고 손에 익는 걸 느끼니 다시 자신감이 붙었다. 앞으로 이어질 내용들도 두렵지 않고 잘 해낼 수 있을 것 같은 하루였다.

반응형

'AI' 카테고리의 다른 글

[부트캠프 휴강일] 이론과 실습을 연결해보자 - 선형 회귀와 다중 선형 회귀  (0) 2026.07.18
[부트캠프 10일차] 예측이 틀렸을 때 - 손실 함수부터 K-폴드까지  (0) 2026.07.15
[부트캠프 8일차] 선형대수학  (1) 2026.07.13
[부트캠프 5일차] 첫 멘토링 후기  (0) 2026.07.08
[부트캠프 3일차] 시간복잡도와 DataFrame 전처리 정리  (0) 2026.07.06
'AI' 카테고리의 다른 글
  • [부트캠프 휴강일] 이론과 실습을 연결해보자 - 선형 회귀와 다중 선형 회귀
  • [부트캠프 10일차] 예측이 틀렸을 때 - 손실 함수부터 K-폴드까지
  • [부트캠프 8일차] 선형대수학
  • [부트캠프 5일차] 첫 멘토링 후기
Riu
Riu
안녕하세요 iOS 개발자를 꿈꾸는 Riu입니다. Github: woolnd
  • Riu
    Riu 개발노트
    Riu
  • 전체
    오늘
    어제
    • 분류 전체보기 (44) N
      • AI (16) N
      • iOS 개발 (21)
        • SwiftUI (9)
        • UIKit (6)
        • Combine (5)
        • Architecture (1)
      • 알고리즘 (1)
      • 회고록 (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • Github
  • 공지사항

  • 인기 글

  • 태그

    Combine
    UIKit
    코드잇스프린트
    ios
    AI엔지니어취업
    앗차!
    코드잇스프린트잇
    ios개발
    SWIFT
    막자알림서비스
    SWIF
    AI엔지니어
    구름톤유니브
    cleanArchitecture
    회고록
    figma
    SwiftUI
    나만의todo
    Ai
    AI부트캠프
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
Riu
[부트캠프 휴강일] 손에 익을 때까지 - 다항 회귀부터 로지스틱 회귀까지
상단으로

티스토리툴바