학습일지/K-Digital Traing

[KDT] AIaaS 마스터클래스 11주차 - 머신러닝 입문

tierr 2025. 6. 4. 15:50

AI 에 대한 내용은 예제만 보고 이해하기가 어려웠기에, 아래 흐름대로 학습했다.

학습일지 작성 흐름

1. 어떤 모델에 대한 예제인지 이해하기

2. 코드에서 중요한 포인트를 찾고 이해하기

3. 궁금한 점에 대한 탐구


간단한 집값 예측 머신러닝 모델

예제)

더보기
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# 간단한 집값 예측 머신러닝 모델
def house_price_prediction():
    """
    집 크기를 기반으로 집값을 예측하는 머신러닝 모델
    """
    # 가상의 집 데이터 생성 (크기 -> 가격)
    np.random.seed(42)
    
    # house_sizes : 집 크기
    house_sizes = np.random.normal(100, 30, 1000)  # 평균 100평, 표준편차 30
    print(house_sizes)
    
    # house_prices : 집 가격
    # 크기가 클수록 가격이 높아지는 관계 + 노이즈
    house_prices = house_sizes * 50 + np.random.normal(0, 500, 1000) + 2000
    print(house_prices)

    # 데이터 전처리
    X = house_sizes.reshape(-1, 1)  # 2D 배열로 변환
    y = house_prices

    # 훈련용/테스트용 데이터 분할
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42
    )
    # train_test_split 함수 매개변수
    # X : 훈련 데이터
    # y : 훈련 데이터의 정답
    # test_size : 테스트 데이터 비율
    # random_state : 데이터 분할 시 랜덤 시드 설정

    # 머신러닝 모델 생성 및 훈련
    model = LinearRegression()
    # LinearRegression 클래스의 fit 메서드
    # X : 훈련 데이터
    # Y : 훈련 데이터의 정답
    model.fit(X_train, y_train)

    # 예측
    y_pred = model.predict(X_test)

    # 성능 평가
    mse = mean_squared_error(y_test, y_pred)
    r2 = r2_score(y_test, y_pred)

    print(f"평균 제곱 오차 (MSE): {mse:.2f}")
    print(f"결정 계수 (R²): {r2:.2f}")
    print(f"모델 계수 (기울기): {model.coef_[0]:.2f}")
    print(f"모델 절편: {model.intercept_:.2f}")

    # 새로운 집 크기에 대한 예측
    new_house_sizes = [80, 120, 150]
    for size in new_house_sizes:
        predicted_price = model.predict([[size]])[0]
        print(f"{size}평 집의 예상 가격: {predicted_price:.2f}만원")

    return model, X_test, y_test, y_pred


# 실행
model, X_test, y_test, y_pred = house_price_prediction()

예제 중요 포인트 설명

더보기

0. 라이브러리

import numpy as np                      # 수학 계산용 도구 (숫자 배열, 통계 등)
import matplotlib.pyplot as plt        # 시각화 (그래프 그리기용) — 여기선 사용 안됨
from sklearn.linear_model import LinearRegression   # 선형 회귀 모델
from sklearn.model_selection import train_test_split  # 훈련/테스트 데이터 나누기
from sklearn.metrics import mean_squared_error, r2_score  # 모델 성능 측정

1. 집 데이터 (더미 데이터)

np.random.seed(42)
house_sizes = np.random.normal(100, 30, 1000)
  • np.random.seed(42): 랜덤 결과를 항상 똑같이 나오게 설정 (실험 재현용).
  • np.random.normal(평균, 표준편차, 개수): 평균 100, 표준편차 30인 집 크기 1000개 생성.

👉  즉, 100평 전후의 다양한 집 크기가 들어있는 배열을 만든 것

house_prices = house_sizes * 50 + np.random.normal(0, 500, 1000) + 2000
  • 각 집 크기에 50만원을 곱하고 → 약간의 **노이즈(오차)**를 더하고 → 기본 가격 2000만원도 추가.
  • 예: 100평짜리 집 → 100 × 50 + (오차) + 2000 ≈ 7000~8000만원쯤 될 것.

👉 집 크기가 크면 가격이 올라가는 선형적 관계를 인위적으로 만든 것


2. 데이터 전처리

X = house_sizes.reshape(-1, 1)  # (1000,) → (1000, 1) 모양으로 바꾸기 (머신러닝용 포맷)
y = house_prices                # 정답 데이터는 그대로

3. 훈련/테스트 데이터 분리

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
  • 훈련용 80% / 테스트용 20%로 나눔.
  • random_state=42: 다시 실행해도 같은 결과가 나오도록 함.

4. 머신러닝 모델 만들고 훈련

model = LinearRegression()
model.fit(X_train, y_train)
  • LinearRegression()은 y = ax + b 형태의 모델을 학습하는 것.
  • fit()은 X(집 크기)와 y(집값)를 가지고 기울기 a와 절편 b를 학습함.

5. 테스트 데이터로 예측

y_pred = model.predict(X_test)

테스트용 집 크기(X_test)를 입력해서 예측된 집값(y_pred)을 구함.


6. 성능 평가

mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
  • MSE (Mean Squared Error): 예측값과 실제값 사이의 평균 제곱 오차. 작을수록 좋음.
  • R² (결정계수): 1에 가까울수록 모델이 잘 맞음.
print(f"평균 제곱 오차 (MSE): {mse:.2f}")
print(f"결정 계수 (R²): {r2:.2f}")

7. 새 집 크기에 대한 예

new_house_sizes = [80, 120, 150]
for size in new_house_sizes:
    predicted_price = model.predict([[size]])[0]
    print(f"{size}평 집의 예상 가격: {predicted_price:.2f}만원")
  • 80평, 120평, 150평 짜리 집의 예상 가격을 예측해서 출력

Q) 이해가 안가거나 궁금한 점

1. 평균 100, 표준편차 30란 무슨 의미?

  • 평균(mean) 100: 대부분의 값이 100 근처에 몰려 있음 (예: 집 크기가 100평쯤 많음)
  • 표준편차(std) 30: 평균에서 ±30 범위에 많은 값이 있음

예를 들어:

  • 평균이 100이고 표준편차가 30이면
    → 약 68%의 값이 70~130 사이에 있음
    → 약 95%의 값이 40~160 사이에 있음

📌 이걸로 "현실적인" 데이터를 흉내내는 것이 목적

집 크기가 10평짜리도, 200평짜리도 있지만 대부분은 100평 근처라고 가정하는 거죠.


2-1. random_state=42가 왜 필요한가?

머신러닝에서는 데이터를 "무작위로 섞어서" 훈련/테스트로 나누는데, 그냥 섞으면 매번 결과가 달라짐

그래서 random_state=42처럼 숫자를 주면, 무작위지만 항상 똑같이 섞이게 됨

 

→ 즉, 실험 재현 가능

  • random_state=1 → 섞는 방식 A
  • random_state=42 → 섞는 방식 B

👉 코드를 반복 실행할 때, 결과가 바뀌지 않게 하려면 random_state를 고정해두는 게 중요하다


2-2. 그럼 왜 random_state를 42로 지정하는가?

random_state=42는 꼭 42일 필요는 전혀 없다.

random_state=42 → 그냥 랜덤 시드를 고정하는 숫자
42 말고 13, 1234, 0, 999 등 어떤 숫자든 사용 가능하다.

참고) 그럼 왜 하필 42로 지정했나?

🎬 42는 SF 소설/영화 『은하수를 여행하는 히치하이커를 위한 안내서(The Hitchhiker's Guide to the Galaxy)』에서,
"삶, 우주, 모든 것의 궁극적인 해답" 이라고 농담처럼 나오는 숫자라고 한다.
그래서 많은 개발자들이 장난 삼아 random_state=42를 즐겨 쓰게 되었고, 관습처럼 퍼졌다는 이야기.


3. 아래 코드는 한세트인가?

  • model = LinearRegression()
  • model.fit(X_train, y_train)

그렇다.

model = LinearRegression()
  • "빈 모델 껍데기"를 만드는 단계
  • y = ax + b 형태의 선형 회귀 모델을 만들 준비
model.fit(X_train, y_train)
  • 위 모델에 실제 데이터를 넣어서 학습하는 단계
  • 즉, a (기울기)와 b (절편)을 자동으로 계산해서 모델을 완성

그래서 꼭 같이 써야 한다.


4. 표준편차는 랜덤인데 어떻게 정답이 있는가?

  • np.random.normal(0, 500, 1000)은 노이즈 (오차)
  • 즉, 집 가격을 완벽하게 예측할 수는 없고, 약간의 오차는 포함돼 있음

하지만

  • 전체적인 경향(trend)은 집 크기가 커질수록 가격도 올라간다는 걸 따르고,
  • 모델은 이 패턴을 최대한 "잘 따라가는" 방향으로 학습한다.

🎯 그래서 "정답"은 꼭 하나가 아니라,
→ "최대한 잘 맞추는 함수 y = ax + b를 찾는 것"이 목표

📌 즉, 모델은 완벽한 정답을 찾기보단, 평균적으로 잘 맞는 예측기를 만드는 것


5. 평균 제곱 오차, 결정 계수, 모델 계수, 모델 절편 이란 무슨의미인가?

예측 공식: y = a * x + b
- a: 모델 계수 (기울기) → 1평 늘면 얼마?
- b: 모델 절편 → 기본 가격
- MSE: 예측이 얼마나 틀렸나 (낮을수록 좋음)
- R²: 얼마나 잘 설명했나 (1에 가까울수록 좋음)

 

참고) 각각에 대한 좀 더 자세한 설명

더보기

1. 평균 제곱 오차 (MSE: Mean Squared Error)

예측값이 실제 정답과 얼마나 차이 나는지를 나타냄.

차이가 클수록 값이 커지고, 작을수록 값이 작다. 👉 MSE는 작을수록 좋은 모델

MSE = 평균( (예측값 - 실제값)² )

✅ 예시:
실제 집값: [7000, 8000, 6000]
예측값: [7100, 7900, 5900]

 

→ 오차: [100, -100, -100]
→ 제곱: [10000, 10000, 10000]
→ 평균: 10000


2. 결정 계수 (R²: R-squared)
모델이 데이터를 얼마나 잘 설명하는지 알려주는 지표
→ 1.0이면 완벽 예측, 0.0이면 무의미한 예측 👉 1에 가까울수록 좋은 모델

R² = 1 - (모델 오차 / 평균 오차)
  • 모델 오차 = 예측값과 실제값의 차이
  • 평균 오차 = 실제값 평균과의 차이

✅ 예시:
R² = 0.95 → 예측이 거의 다 맞았다!
R² = 0.3 → 예측이 실제 평균보다도 못 맞춤
R² = 1 → 완벽한 예측


3. 모델 계수 (기울기, model.coef_)
모델이 학습한 기울기 a 값
즉, 집 크기 1평 늘면 가격이 얼마 오르는지를 의미

model.coef_ = [49.5]

→ 평수 1 증가할 때 집값이 49.5만 원 증가한다는 뜻.


4. 모델 절편 (intercept, model.intercept_)

x(평수)가 0일 때 y(가격)는 얼마인가? → 시작점
기본적인 고정비용 같은 느낌

model.intercept_ = 2000

→ 평수가 0이라도 집값은 기본적으로 2000만 원이다.


MNIST 손글씨 숫자를 인식

예제)

더보기
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten, Conv2D, MaxPooling2D, Dropout
from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
# tensorflow.keras 인스톨 필요
# pip install tensorflow.keras
import numpy as np
import matplotlib.pyplot as plt

def mnist_deep_learning():
    """
    MNIST 손글씨 숫자를 인식하는 딥러닝 모델
    """
    # 1. 데이터 로드 및 전처리
    (X_train, y_train), (X_test, y_test) = mnist.load_data()
    # mnist : 손글씨 데이터셋
    # X_train : 훈련 데이터 (28x28 픽셀 이미지)
    # y_train : 훈련 데이터의 레이블 (0~9)
    # X_test : 테스트 데이터 (28x28 픽셀 이미지)
    # y_test : 테스트 데이터 레이블 (0~9)

    # x_train_one 데이터 파일로 저장
    x_train_one = X_train[0]
    plt.imsave('x_train_one.png', x_train_one, cmap='gray')

    # 데이터 정규화 (0~255 -> 0~1)
    X_train = X_train.reshape(-1, 28, 28, 1).astype('float32') / 255.0
    # reshape(-1, 28, 28, 1) : 28x28 픽셀 이미지를 1채널로 변환
    # -1 : 데이터 개수를 자동으로 계산
    # 28 : 이미지 높이
    # 28 : 이미지 너비
    # 1 : 채널 수 (흑백 이미지이므로 1)
    # astype('float32') : 데이터 타입을 float32로 변환
    # / 255.0 : 0~255 범위를 0~1 범위로 정규화

    X_test = X_test.reshape(-1, 28, 28, 1).astype('float32') / 255.0

    # 레이블 원-핫 인코딩
    y_train = to_categorical(y_train, 10)
    # to_categorical(y_train, 10) : 레이블을 원-핫 인코딩으로 변환
    # 10 : 카테고리 수 (0~9)
    # 값이 1이면, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
    # 값이 2이면, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0
    # 값이 3이면, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0

    y_test = to_categorical(y_test, 10)

    # 2. 딥러닝 모델 구성 (CNN)
    # CNN : 컨볼루션 신경망
    # 컨볼루션 레이어 : 이미지 데이터에 대한 컨볼루션 연산을 수행
    # 풀링 레이어 : 컨볼루션 레이어의 출력을 줄이는 역할
    # 완전 연결층 : 컨볼루션 레이어와 풀링 레이어의 출력을 받아 최종 출력을 생성

    model = Sequential([
        # 첫 번째 컨볼루션 블록
        Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
        MaxPooling2D((2, 2)),

        # 두 번째 컨볼루션 블록
        Conv2D(64, (3, 3), activation='relu'),
        MaxPooling2D((2, 2)),

        # 세 번째 컨볼루션 블록
        Conv2D(64, (3, 3), activation='relu'),

        # 완전 연결층
        Flatten(),                         # Flatten() : 컨볼루션과 풀링 레이어의 출력을 일차원 벡터로 변환
        Dense(64, activation='relu'),      # 64개의 뉴런을 가진 완전 연결층
        Dropout(0.5),                      # 과적합 방지 Dropout
        Dense(10, activation='softmax')    # 10개 숫자 분류 (출력층)
    ])

    # 3. 모델 컴파일
    model.compile(
        optimizer='adam',
        loss='categorical_crossentropy',
        metrics=['accuracy']
    )

    # optimizer='adam' : 옵티마이저 (Adam)
    # loss='categorical_crossentropy' : 손실 함수 (원-핫 인코딩 레이블에 대한 손실)
    # metrics=['accuracy'] : 평가 지표 (정확도)

    # 4. 모델 훈련
    print("모델 훈련을 시작합니다...")
    history = model.fit(
        X_train, y_train,
        batch_size=128,
        epochs=5,
        validation_data=(X_test, y_test),
        verbose=1
    )

    # batch_size=128 : 배치 크기 (128개의 데이터씩 묶어서 처리)
    # epochs=5 : 훈련 반복 횟수 (5번 반복)
    # validation_data=(X_test, y_test) : 검증 데이터셋
    # verbose=1 : 훈련 과정 출력

    # 5. 모델 평가
    test_loss, test_accuracy = model.evaluate(X_test, y_test, verbose=0)
    print(f"\n최종 테스트 정확도: {test_accuracy:.4f}")

    # 6. 예측 예시
    sample_idx = np.random.randint(0, len(X_test), 5)
    predictions = model.predict(X_test[sample_idx])  # predictions : 예측 결과

    print("\n예측 결과:")
    for i, idx in enumerate(sample_idx):
        predicted_digit = np.argmax(predictions[i])        # 예측 결과 중 가장 높은 값의 인덱스
        actual_digit = np.argmax(y_test[idx])              # 실제 레이블 중 가장 높은 값의 인덱스
        confidence = np.max(predictions[i]) * 100          # 신뢰도를 백분율로 변환

        print(f"샘플 {i+1}: 예측={predicted_digit}, 실제={actual_digit}, 신뢰도={confidence:.1f}%")
    
    return model, history
# 실행
model, history = mnist_deep_learning()

 


머신러닝(ML) 수학 기초

선형대수 기초

# 고유값/고유벡터 예제
import numpy as np

def eigenvalue_example():
    """고유값/고유벡터 예제 - 비전공자를 위한 상세 설명 포함"""

    # 🔍 비전공자를 위한 직관적 설명
    print("수학적 의미:")
    print("∙ A @ v = λ @ v (A: 행렬, v: 고유벡터, λ(lambda): 고유값)")
    print("∙ 행렬 A를 곱해도 벡터 v의 방향은 그대로, 크기만 λ배 변함")

    # 간단한 2x2 행렬
    A = np.array([[4, 2],
                  [1, 3]])
    
    # 고유값과 고유벡터 계산
    eigenvalues, eigenvectors = np.linalg.eig(A)

    print(f"\n계산 결과:")
    print(f"고유값: {eigenvalues}")
    print(f"고유벡터:")
    for i, (val, vec) in enumerate(zip(eigenvalues, eigenvectors.T)):
        print(f"  λ{i+1} = {val:.3f}, v{i+1} = [{vec[0]:.3f}, {vec[1]:.3f}]")

    try:
        import matplotlib.pyplot as plt

        # 윈도우 한글 폰트 설정
        plt.rc('font', family='Malgun Gothic')

        # 시각화를 위한 벡터들 생성
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

        # 원래 벡터들 (여러 방향)
        original_vectors = np.array([
            [1, 0],     # x축 방향
            [0, 1],     # y축 방향
            [1, 1],     # 대각선
            eigenvectors[:, 0],  # 고유벡터1
            eigenvectors[:, 1]   # 고유벡터2
        ])

        # 변환된 벡터들
        transformed_vectors = np.array([A @ v for v in original_vectors])

        colors = ['red', 'blue', 'green', 'orange', 'purple']
        labels = ['x축 방향', 'y축 방향', '대각선', '고유벡터1', '고유벡터2']

        # 변환 전 (왼쪽 그래프)
        ax1.set_xlim(-3, 3)
        ax1.set_ylim(-3, 3)
        ax1.grid(True, alpha=0.3)
        ax1.axhline(y=0, color='k', linestyle='--', alpha=0.3)
        ax1.axvline(x=0, color='k', linestyle='--', alpha=0.3)

        for vec, color, label in zip(original_vectors, colors, labels):
            ax1.arrow(0, 0, vec[0], vec[1], head_width=0.1, head_length=0.1,
                      fc=color, ec=color, label=label)

        ax1.set_title('변환 전: 원래 벡터들')
        ax1.legend(fontsize=8)
        ax1.set_aspect('equal')

        # 변환 후 (오른쪽 그래프)
        ax2.set_xlim(-6, 6)
        ax2.set_ylim(-6, 6)
        ax2.grid(True, alpha=0.3)
        ax2.axhline(y=0, color='k', linestyle='--', alpha=0.3)
        ax2.axvline(x=0, color='k', linestyle='--', alpha=0.3)

        for vec, color, label in zip(transformed_vectors, colors, labels):
            ax2.arrow(0, 0, vec[0], vec[1], head_width=0.2, head_length=0.2,
                      fc=color, ec=color, label=label)

        ax2.set_title('변환 후: 행렬 A를 곱한 결과')
        ax2.legend(fontsize=8)
        ax2.set_aspect('equal')

        plt.tight_layout()
        plt.show()

        print(f"\n그래프 해석:")
        print(f"∙ 주황색과 보라색 화살표(고유벡터)는 방향이 그대로!")
        print(f"∙ 다른 벡터들은 방향이 바뀜")
        print(f"∙ 고유벡터는 길이만 변함 (고유값만큼 늘어남)")

    except ImportError:
        print("matplotlib이 설치되지 않아 그래프를 그릴 수 없습니다.")
        print("pip install matplotlib 명령으로 설치 후 다시 시도해보세요.")

    return A, eigenvalues, eigenvectors


if __name__ == "__main__":
    eigenvalue_example()

 

미적분학 기초

import numpy as np
import matplotlib.pyplot as plt

# 경사하강법으로 최소값 찾기
def simple_gradient_descent():
    
    # 목적 함수: f(x) = (x-5)^2 + 3 (최소값: x=5일 때 y=3)
    def objective_function(x):
        return (x - 5)**2 + 3

    # 도함수: f'(x) = 2(x - 5)
    def gradient(x):
        return 2 * (x - 5)

    # 경사하강법 구현
    # 기울기를 이용해 최소값을 찾는 반복적 방법

    x = 0               # 시작점
    learning_rate = 0.1
    iterations = 20

    print("경사하강법으로 f(x) = (x-5)^2 + 3 의 최소값 찾기")
    print("시작점: x = 0, 학습률: 0.1")

    for i in range(iterations):
        # 현재 함수값과 경사 계산
        current_value = objective_function(x)
        current_gradient = gradient(x)

        print(f"반복 {i+1:2d}: x = {x:7.4f}, f(x) = {current_value:7.4f}, 경사 = {current_gradient:7.4f}")

        # x 업데이트 (학습률 * 경사만큼 이동)
        x = x - learning_rate * current_gradient

        # 수렴 조건 (기울기가 거의 0에 가까우면 중단)
        if abs(current_gradient) < 1e-6:
            print("수렴!")
            break

    print(f"\n최종 결과: x = {x:.6f}, f(x) = {objective_function(x):.6f}")
    print(f"이론적 최소값: x = 5.000000, f(x) = 3.000000")

# 실행
simple_gradient_descent()

 

미분

import numpy as np
import matplotlib.pyplot as plt

def numerical_derivative(f, x, h=1e-7):  # h=10^-7 (아주 작은 변화량)
    """
    수치적 미분 계산
    f: 함수
    x: 미분할 점
    h: 아주 작은 변화량
    """
    return (f(x + h) - f(x)) / h

    # 위 식은 전진 차분, 더 정확한 방법은 중앙 차분
    # 중앙 차분 식: (f(x+h) - f(x-h)) / (2h)

def derivative_definition_demo():
    """미분의 정의를 이용한 극한 계산 시연"""

    # 함수 정의: f(x) = x^2
    def f(x):
        return x**2

    # x = 2에서의 미분값 계산
    a = 2
    h_values = [1, 0.1, 0.01, 0.001, 0.0001, 0.00001]

    print("미분의 정의: f'(2) = lim[h→0] (f(2+h) - f(2)) / h")
    print("함수 f(x) = x^2")

    for h in h_values:
        diff_quotient = (f(a + h) - f(a)) / h
        print(f"h = {h:8.5f}: [f(2+{h}) - f(2)] / {h} = {diff_quotient:.6f}")

    # 해석적 미분값 (정확한 값)
    analytical_derivative = 2 * a  # f(x) = x^2의 도함수는 2x
    print(f"\n정확한 미분값: f'(2) = {analytical_derivative}")

    # 수치적 미분값
    numerical_result = numerical_derivative(f, a)
    print(f"수치적 미분값: f'(2) ≈ {numerical_result:.6f}")

    return h_values, [f(a + h) for h in h_values]

def plot_derivative_concept():
    """미분의 기하학적 의미 시각화"""

    # 함수와 점 정의
    def f(x):
        return x**2

    x = np.linspace(0, 4, 100)  # 0부터 4까지 100개의 점 생성
    y = f(x)

    # 특정 점에서의 접선
    a = 2                       # 접선을 그릴 x 값
    slope = 2 * a               # f'(2) = 4, 접선의 기울기

    tangent_x = np.linspace(1, 3, 100)  # 접선 x 좌표 범위
    tangent_y = f(a) + slope * (tangent_x - a)  # 접선 방정식: y = f(a) + f'(a)(x - a)

    plt.rc('font', family='Malgun Gothic')
    plt.figure(figsize=(10, 6))

    # 함수 그래프
    plt.plot(x, y, 'b-', linewidth=2, label='f(x) = x²')

    # 접점 표시
    plt.plot(a, f(a), 'ro', markersize=8, label=f'접점 ({a}, {f(a)})')

    # 접선
    plt.plot(tangent_x, tangent_y, 'r--', linewidth=2,
             label=f'접선 (기울기 = {slope})')

    # 할선들 (기울기가 점점 접선에 가까워짐)
    h_values = [1, 0.5, 0.2]
    colors = ['orange', 'green', 'purple']

    for i, h in enumerate(h_values):
        x_end = a + h
        y_end = f(x_end)

        secant_slope = (f(x_end) - f(a)) / h

        secant_x = np.array([a, x_end])
        secant_y = np.array([f(a), y_end])

        plt.plot(secant_x, secant_y, '--', color=colors[i],
                 linewidth=1.5, label=f'할선 (h={h}, 기울기={secant_slope:.1f})')

        # 끝점 표시
        plt.plot(x_end, y_end, 'o', color=colors[i], markersize=6)

    plt.xlabel('x')
    plt.ylabel('f(x)')
    plt.title('미분의 기하학적 의미: 할선이 접선으로 수렴')
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.show()

 

편미분

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

class PartialDerivatives:
    """편미분 계산 클래스"""

    @staticmethod
    def numerical_partial_derivative(f, x, y, var='x', h=1e-7):
        """
        수치적 편미분 계산
        f: 함수 (x, y를 받음)
        x, y: 편미분할 점
        var: 'x' 또는 'y' (어느 변수에 대해 편미분할지)
        h: 아주 작은 변화량
        """
        if var == 'x':
            return (f(x + h, y) - f(x - h, y)) / (2 * h)
        elif var == 'y':
            return (f(x, y + h) - f(x, y - h)) / (2 * h)

    @staticmethod
    def gradient(f, x, y, h=1e-7):
        """경사 벡터 계산"""
        df_dx = PartialDerivatives.numerical_partial_derivative(f, x, y, 'x', h)
        df_dy = PartialDerivatives.numerical_partial_derivative(f, x, y, 'y', h)
        return np.array([df_dx, df_dy])

def partial_derivative_examples():
    """편미분 예제들"""

    # 예제 함수: f(x, y) = x^2*y + 3x*y^2 + 5x + 2y + 7
    def f(x, y):
        return x**2 * y + 3 * x * y**2 + 5 * x + 2 * y + 7

    # 해석적 편미분
    def df_dx_analytical(x, y):
        return 2 * x * y + 3 * y**2 + 5

    def df_dy_analytical(x, y):
        return x**2 + 6 * x * y + 2

    # 테스트 점
    x, y = 2, 3

    print(f"함수: f(x, y) = x²y + 3xy² + 5x + 2y + 7")
    print(f"점 ({x}, {y})에서의 편미분:\n")

    # ∂f/∂x 계산
    analytical_x = df_dx_analytical(x, y)
    numerical_x = PartialDerivatives.numerical_partial_derivative(f, x, y, 'x')

    print(f"∂f/∂x:")
    print(f"  해석적: {analytical_x}")
    print(f"  수치적: {numerical_x:.6f}")
    print(f"  오차: {abs(analytical_x - numerical_x):.2e}")

def visualize_partial_derivatives():
    """편미분과 경사 시각화"""

    # 함수 정의: f(x, y) = x² + y²
    def f(x, y):
        return x**2 + y**2

    # 해석적 편미분
    def df_dx(x, y): return 2 * x  # ∂f/∂x
    def df_dy(x, y): return 2 * y  # ∂f/∂y

    # 그리드 생성
    x = np.linspace(-3, 3, 100)
    y = np.linspace(-3, 3, 100)
    X, Y = np.meshgrid(x, y)  # 2차원 격자 생성
    Z = f(X, Y)               # 함수값 계산

    # 편미분 계산
    DX = df_dx(X, Y)  # ∂f/∂x
    DY = df_dy(X, Y)  # ∂f/∂y

    plt.rc('font', family='Malgun Gothic')  # 한글 폰트
    fig = plt.figure(figsize=(15, 5))       # 그림 사이즈 설정

    # 1. 3D surface plot
    ax1 = fig.add_subplot(131, projection='3d')
    surf = ax1.plot_surface(X, Y, Z, alpha=0.6, cmap='viridis')

    # 축 설정 및 제목
    ax1.set_xlabel('x')                     # x축 라벨
    ax1.set_ylabel('y')                     # y축 라벨
    ax1.set_zlabel('f(x, y)')               # z축 라벨
    ax1.set_title('함수: f(x, y) = x² + y²')  # 제목

    # 2. 등고선 + 경사 벡터
    ax2 = fig.add_subplot(132)
    contour = ax2.contour(X, Y, Z, levels=20)  # 등고선 20개 생성
    ax2.clabel(contour, inline=True, fontsize=8)  # 등고선 라벨

    # 경사 벡터 필드 일부 시각화
    step = 10
    ax2.quiver(X[::step, ::step], Y[::step, ::step],
               DX[::step, ::step], DY[::step, ::step],
               alpha=0.8, color='red', scale=50)

    ax2.set_xlabel('x')
    ax2.set_ylabel('y')
    ax2.set_title('등고선과 경사 벡터')
    ax2.grid(True, alpha=0.3)

    # 3. 한 변수를 고정한 단면 편미분 시각화
    ax3 = fig.add_subplot(133)

    # x = 1 고정, y에 따른 함수값 변화
    y_line = np.linspace(-3, 3, 100)
    x_fixed = 1
    z_line = f(x_fixed, y_line)

    ax3.plot(y_line, z_line, 'b-', linewidth=2,
             label=f'f({x_fixed}, y)')

    # 특정 점에서 접선
    y_point = 1.5
    z_point = f(x_fixed, y_point)
    slope = df_dy(x_fixed, y_point)  # 접선 기울기 (∂f/∂y)

    # 접선 방정식: z = f(a) + f'(a)(y - a)
    y_tangent = np.linspace(y_point - 0.5, y_point + 0.5, 100)
    z_tangent = z_point + slope * (y_tangent - y_point)

    ax3.plot(y_tangent, z_tangent, 'r--', linewidth=2,
             label=f'접선 (기울기 = {slope:.1f})')
    ax3.plot(y_point, z_point, 'ro', markersize=8,
             label=f'점 ({x_fixed}, {y_point})')

    # 접선 그리기 (앞서 정의됨)
    ax3.plot(y_tangent, z_tangent, 'r--', linewidth=2,
             label=f'접선 (기울기 = {slope:.1f})')

    # 점 그리기
    ax3.plot(y_point, z_point, 'ro', markersize=8,
             label=f'점 ({x_fixed}, {y_point})')

    # 축 및 제목 설정
    ax3.set_xlabel('y')
    ax3.set_ylabel(f'f({x_fixed}, y)')
    ax3.set_title(f'x = {x_fixed}에서 ∂f/∂y')  # 제목
    ax3.legend()                              # 범례
    ax3.grid(True, alpha=0.3)                 # 격자

    plt.tight_layout()  # 서브플롯 간 간격 자동 정리
    plt.show()          # 화면에 그래프 표시

if __name__ == "__main__":
    partial_derivative_examples()  # 편미분 값 비교 (수치 vs 해석)
    visualize_partial_derivatives()  # 3D 그래프 및 벡터 시각화

본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.