{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "93be1516",
   "metadata": {},
   "source": [
    "# Part 5.5: NumPy로 배우는 선형대수학 - 직관적 이해 중심\n",
    "\n",
    "**⬅️ 이전 시간: [Part 5: AI 핵심 라이브러리](../05_ai_core_libraries/part_5_ai_core_libraries.md)**\n",
    "**➡️ 다음 시간: [Part 5.6: 머신러닝/딥러닝을 위한 미적분](../05.6_calculus_for_ml/part_5.6_calculus_for_ml.md)**\n",
    "\n",
    "---\n",
    "\n",
    "## 🗺️ 학습 로드맵: 왜 여기서 선형대수를 배우나요?\n",
    "\n",
    "### 🎯 이번 파트의 목적\n",
    "이제 AI 핵심 라이브러리(NumPy, Pandas, Matplotlib)를 마스터했으니, **머신러닝의 수학적 기초**를 다져보겠습니다. 선형대수는 AI의 \"언어\"라고 할 수 있습니다.\n",
    "\n",
    "### 🔗 이전 파트와의 연결점\n",
    "- **Part 5 (AI Core Libraries)**: NumPy 배열을 다루는 방법을 배웠습니다\n",
    "- **이번 파트 (Linear Algebra)**: 그 NumPy 배열이 실제로는 **벡터와 행렬**이라는 것을 이해합니다\n",
    "- **다음 파트 (Calculus)**: 선형대수 + 미적분 = 머신러닝의 완성\n",
    "\n",
    "### 🚀 머신러닝에서의 활용 예시\n",
    "- **이미지 처리**: 픽셀 데이터를 행렬로 표현하고 변환\n",
    "- **추천 시스템**: 사용자-아이템 행렬을 분해하여 패턴 발견\n",
    "- **신경망**: 가중치를 행렬로 표현하고 최적화\n",
    "- **차원 축소**: PCA로 고차원 데이터를 저차원으로 압축\n",
    "\n",
    "### 📈 학습 진행도\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a80971b",
   "metadata": {},
   "outputs": [],
   "source": [
    "Part 5: AI Core Libraries ✅\n",
    "Part 5.5: Linear Algebra 🔄 (현재)\n",
    "Part 5.6: Calculus ⏳\n",
    "Part 6: Machine Learning ⏳"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7076e870",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "---\n",
    "\n",
    "> ## 🎯 학습 목표 (Learning Objectives)\n",
    ">\n",
    "> 이번 파트가 끝나면, 여러분은 다음을 할 수 있게 됩니다.\n",
    "> \n",
    "> - **직관적으로 이해하기**: 복잡한 수식보다는 시각화와 비유를 통해 선형대수의 핵심 개념을 직관적으로 이해할 수 있습니다.\n",
    "> - **실습 중심 학습**: Python과 NumPy를 사용하여 개념을 직접 구현하고 시각화할 수 있습니다.\n",
    "> - **실제 활용**: 머신러닝에서 선형대수가 어떻게 활용되는지 구체적인 예제를 통해 이해할 수 있습니다.\n",
    "\n",
    "\n",
    "> ## 🔑 핵심 접근법 (Key Approach)\n",
    "> \n",
    "> **\"왜 이걸 배워야 하는가?\"** 에 집중합니다.\n",
    "> - 벡터 = \"방향과 크기를 가진 화살표\"\n",
    "> - 행렬 = \"데이터를 변형하는 마법 상자\"\n",
    "> - 고유벡터 = \"변하지 않는 특별한 방향\"\n",
    "> - PCA = \"데이터의 핵심 방향 찾기\"\n",
    "\n",
    "\n",
    "> ## 🛠️ 사용 도구 (Tools)\n",
    "> - **Python + NumPy**: 실제 계산과 구현\n",
    "> - **Matplotlib**: 시각화로 직관적 이해\n",
    "> - **Jupyter Notebook**: 대화형 학습\n",
    "> - **실제 예제**: 이미지 처리, 데이터 분석 등\n",
    "\n",
    "---\n",
    "\n",
    "\n",
    "## 1. 벡터: 방향과 크기를 가진 화살표 🏹\n",
    "\n",
    "### 1-1. 벡터란 무엇인가? (직관적 이해)\n",
    "\n",
    "> **💡 비유**: 벡터는 \"화살표\"입니다!\n",
    "> - **크기**: 화살표의 길이\n",
    "> - **방향**: 화살표가 가리키는 방향\n",
    "> - **위치**: 화살표가 시작하는 점\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0f9b3b79",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 벡터를 화살표로 시각화\n",
    "def plot_vector(ax, start, vector, color='blue', label=''):\n",
    "    \"\"\"벡터를 화살표로 그리는 함수\"\"\"\n",
    "    ax.quiver(start[0], start[1], vector[0], vector[1], \n",
    "              angles='xy', scale_units='xy', scale=1, color=color, label=label)\n",
    "\n",
    "# 그래프 설정\n",
    "fig, ax = plt.subplots(figsize=(10, 8))\n",
    "ax.set_xlim(-5, 5)\n",
    "ax.set_ylim(-5, 5)\n",
    "ax.grid(True, alpha=0.3)\n",
    "ax.set_aspect('equal')\n",
    "\n",
    "# 원점\n",
    "origin = np.array([0, 0])\n",
    "\n",
    "# 다양한 벡터들\n",
    "vectors = {\n",
    "    'v1': np.array([3, 2]),    # 오른쪽 위로\n",
    "    'v2': np.array([-2, 1]),   # 왼쪽 위로  \n",
    "    'v3': np.array([0, -3]),   # 아래로\n",
    "    'v4': np.array([4, 0])     # 오른쪽으로\n",
    "}\n",
    "\n",
    "colors = ['red', 'blue', 'green', 'purple']\n",
    "\n",
    "# 벡터들을 화살표로 그리기\n",
    "for i, (name, vector) in enumerate(vectors.items()):\n",
    "    plot_vector(ax, origin, vector, colors[i], name)\n",
    "    \n",
    "    # 벡터의 크기 계산\n",
    "    magnitude = np.linalg.norm(vector)\n",
    "    print(f\"{name}: 크기 = {magnitude:.2f}, 방향 = {vector}\")\n",
    "\n",
    "ax.set_xlabel('X축')\n",
    "ax.set_ylabel('Y축')\n",
    "ax.set_title('벡터 = 방향과 크기를 가진 화살표')\n",
    "ax.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eb9b805d",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "### 1-2. 벡터 연산의 직관적 의미\n",
    "\n",
    "#### 벡터 덧셈: \"이동 경로 합치기\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3da5dd69",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 벡터 덧셈 시각화\n",
    "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))\n",
    "\n",
    "# 첫 번째 그래프: 개별 벡터들\n",
    "ax1.set_xlim(-5, 5)\n",
    "ax1.set_ylim(-5, 5)\n",
    "ax1.grid(True, alpha=0.3)\n",
    "ax1.set_aspect('equal')\n",
    "\n",
    "v1 = np.array([2, 1])\n",
    "v2 = np.array([1, 3])\n",
    "\n",
    "plot_vector(ax1, origin, v1, 'red', 'v1')\n",
    "plot_vector(ax1, origin, v2, 'blue', 'v2')\n",
    "ax1.set_title('개별 벡터들')\n",
    "ax1.legend()\n",
    "\n",
    "# 두 번째 그래프: 벡터 덧셈\n",
    "ax2.set_xlim(-5, 5)\n",
    "ax2.set_ylim(-5, 5)\n",
    "ax2.grid(True, alpha=0.3)\n",
    "ax2.set_aspect('equal')\n",
    "\n",
    "# v1을 먼저 그리고, 그 끝점에서 v2를 그리기\n",
    "plot_vector(ax2, origin, v1, 'red', 'v1')\n",
    "plot_vector(ax2, v1, v2, 'blue', 'v2')  # v1의 끝점에서 v2 시작\n",
    "\n",
    "# 합 벡터 (원점에서 최종 위치까지)\n",
    "v_sum = v1 + v2\n",
    "plot_vector(ax2, origin, v_sum, 'green', 'v1 + v2', linewidth=3)\n",
    "\n",
    "ax2.set_title('벡터 덧셈: 이동 경로 합치기')\n",
    "ax2.legend()\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(f\"v1 = {v1}\")\n",
    "print(f\"v2 = {v2}\")\n",
    "print(f\"v1 + v2 = {v_sum}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "949b5215",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "#### 벡터 내적: \"서로 얼마나 같은 방향인지\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1c287935",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 벡터 내적의 직관적 이해\n",
    "def cosine_similarity(v1, v2):\n",
    "    \"\"\"두 벡터의 코사인 유사도 계산\"\"\"\n",
    "    dot_product = np.dot(v1, v2)\n",
    "    norm_v1 = np.linalg.norm(v1)\n",
    "    norm_v2 = np.linalg.norm(v2)\n",
    "    return dot_product / (norm_v1 * norm_v2)\n",
    "\n",
    "# 다양한 각도의 벡터들\n",
    "angles = [0, 45, 90, 135, 180]  # 도\n",
    "vectors = []\n",
    "\n",
    "for angle in angles:\n",
    "    # 각도를 라디안으로 변환\n",
    "    rad = np.radians(angle)\n",
    "    # 단위 벡터 생성\n",
    "    v = np.array([np.cos(rad), np.sin(rad)])\n",
    "    vectors.append(v)\n",
    "\n",
    "# 기준 벡터 (오른쪽 방향)\n",
    "base_vector = np.array([1, 0])\n",
    "\n",
    "print(\"벡터 간 유사도 (내적의 직관적 의미):\")\n",
    "print(\"-\" * 50)\n",
    "for i, angle in enumerate(angles):\n",
    "    similarity = cosine_similarity(base_vector, vectors[i])\n",
    "    print(f\"각도 {angle}°: 유사도 = {similarity:.3f} ({'같은 방향' if similarity > 0.9 else '수직' if abs(similarity) < 0.1 else '반대 방향' if similarity < -0.9 else '대각선'})\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e1bb712",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "---\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "## 2. 행렬: 데이터를 변형하는 마법 상자 🎁\n",
    "\n",
    "### 2-1. 행렬이란 무엇인가? (직관적 이해)\n",
    "\n",
    "> **💡 비유**: 행렬은 \"데이터 변형기\"입니다!\n",
    "> - **입력**: 원본 데이터 (벡터)\n",
    "> - **변형**: 행렬이 데이터를 어떻게 바꿀지 정의\n",
    "> - **출력**: 변형된 데이터 (새로운 벡터)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9a32e640",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 행렬 변환의 시각화\n",
    "def plot_transformation(original_points, transformed_points, title):\n",
    "    \"\"\"점들의 변환을 시각화\"\"\"\n",
    "    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
    "    \n",
    "    # 원본 점들\n",
    "    ax1.scatter(original_points[:, 0], original_points[:, 1], c='blue', alpha=0.6)\n",
    "    ax1.set_xlim(-3, 3)\n",
    "    ax1.set_ylim(-3, 3)\n",
    "    ax1.grid(True, alpha=0.3)\n",
    "    ax1.set_aspect('equal')\n",
    "    ax1.set_title('변환 전')\n",
    "    \n",
    "    # 변환된 점들\n",
    "    ax2.scatter(transformed_points[:, 0], transformed_points[:, 1], c='red', alpha=0.6)\n",
    "    ax2.set_xlim(-3, 3)\n",
    "    ax2.set_ylim(-3, 3)\n",
    "    ax2.grid(True, alpha=0.3)\n",
    "    ax2.set_aspect('equal')\n",
    "    ax2.set_title('변환 후')\n",
    "    \n",
    "    plt.suptitle(title)\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n",
    "\n",
    "# 원형 점들 생성\n",
    "theta = np.linspace(0, 2*np.pi, 50)\n",
    "circle_points = np.column_stack([np.cos(theta), np.sin(theta)])\n",
    "\n",
    "# 1. 회전 변환 (90도 회전)\n",
    "rotation_matrix = np.array([[0, -1], [1, 0]])  # 90도 회전\n",
    "rotated_points = circle_points @ rotation_matrix.T\n",
    "plot_transformation(circle_points, rotated_points, \"회전 변환: 원형 → 회전된 원형\")\n",
    "\n",
    "# 2. 크기 조절 변환 (X축으로 2배 늘리기)\n",
    "scaling_matrix = np.array([[2, 0], [0, 1]])  # X축 2배\n",
    "scaled_points = circle_points @ scaling_matrix.T\n",
    "plot_transformation(circle_points, scaled_points, \"크기 조절: 원형 → 타원형\")\n",
    "\n",
    "# 3. 전단 변환 (X축 방향으로 기울이기)\n",
    "shear_matrix = np.array([[1, 0.5], [0, 1]])  # X축 방향 전단\n",
    "sheared_points = circle_points @ shear_matrix.T\n",
    "plot_transformation(circle_points, sheared_points, \"전단 변환: 원형 → 기울어진 원형\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16a7e3bf",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "### 2-2. 행렬곱의 직관적 의미\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d429913a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 행렬곱의 시각화: 복합 변환\n",
    "fig, axes = plt.subplots(2, 2, figsize=(12, 10))\n",
    "\n",
    "# 원본 점들\n",
    "original = circle_points\n",
    "\n",
    "# 변환 행렬들\n",
    "A = np.array([[0.8, -0.6], [0.6, 0.8]])  # 회전 + 축소\n",
    "B = np.array([[1.5, 0], [0, 0.8]])       # X축 확대, Y축 축소\n",
    "\n",
    "# 단계별 변환\n",
    "step1 = original @ A.T\n",
    "step2 = step1 @ B.T\n",
    "\n",
    "# 복합 변환 (A × B)\n",
    "combined = original @ (A @ B).T\n",
    "\n",
    "# 시각화\n",
    "titles = ['원본', 'A 변환 후', 'B 변환 후', 'A×B 복합 변환']\n",
    "points_list = [original, step1, step2, combined]\n",
    "\n",
    "for i, (ax, title, points) in enumerate(zip(axes.flat, titles, points_list)):\n",
    "    ax.scatter(points[:, 0], points[:, 1], c='blue', alpha=0.6)\n",
    "    ax.set_xlim(-2, 2)\n",
    "    ax.set_ylim(-2, 2)\n",
    "    ax.grid(True, alpha=0.3)\n",
    "    ax.set_aspect('equal')\n",
    "    ax.set_title(title)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"행렬곱의 의미: 변환을 순서대로 적용\")\n",
    "print(f\"A = {A}\")\n",
    "print(f\"B = {B}\")\n",
    "print(f\"A × B = {A @ B}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e63346d1",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "---\n",
    "\n",
    "<br>\n",
    "\n",
    "## 3. 고유값과 고유벡터: 변하지 않는 특별한 방향 🧭\n",
    "\n",
    "### 3-1. 고유벡터의 직관적 이해\n",
    "\n",
    "> **💡 비유**: 고유벡터는 \"변하지 않는 특별한 방향\"입니다!\n",
    "> - 행렬이 데이터를 변형할 때, 특정 방향의 벡터는 방향이 바뀌지 않습니다\n",
    "> - 단지 크기만 변할 뿐입니다 (고유값만큼)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5fcb348c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 고유벡터 시각화\n",
    "def visualize_eigenvectors(matrix, title):\n",
    "    \"\"\"고유벡터를 시각화하는 함수\"\"\"\n",
    "    # 고유값과 고유벡터 계산\n",
    "    eigenvalues, eigenvectors = np.linalg.eig(matrix)\n",
    "    \n",
    "    # 원형 점들\n",
    "    theta = np.linspace(0, 2*np.pi, 100)\n",
    "    circle = np.column_stack([np.cos(theta), np.sin(theta)])\n",
    "    \n",
    "    # 변환 전후\n",
    "    transformed = circle @ matrix.T\n",
    "    \n",
    "    # 고유벡터들\n",
    "    eigenvector1 = eigenvectors[:, 0]\n",
    "    eigenvector2 = eigenvectors[:, 1]\n",
    "    \n",
    "    # 시각화\n",
    "    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))\n",
    "    \n",
    "    # 변환 전\n",
    "    ax1.scatter(circle[:, 0], circle[:, 1], c='lightblue', alpha=0.6, s=20)\n",
    "    ax1.quiver(0, 0, eigenvector1[0], eigenvector1[1], color='red', scale=3, label=f'고유벡터1 (λ={eigenvalues[0]:.2f})')\n",
    "    ax1.quiver(0, 0, eigenvector2[0], eigenvector2[1], color='green', scale=3, label=f'고유벡터2 (λ={eigenvalues[1]:.2f})')\n",
    "    ax1.set_xlim(-2, 2)\n",
    "    ax1.set_ylim(-2, 2)\n",
    "    ax1.grid(True, alpha=0.3)\n",
    "    ax1.set_aspect('equal')\n",
    "    ax1.set_title('변환 전: 고유벡터 방향')\n",
    "    ax1.legend()\n",
    "    \n",
    "    # 변환 후\n",
    "    ax2.scatter(transformed[:, 0], transformed[:, 1], c='lightcoral', alpha=0.6, s=20)\n",
    "    # 변환된 고유벡터들\n",
    "    transformed_eigenvector1 = eigenvector1 @ matrix.T\n",
    "    transformed_eigenvector2 = eigenvector2 @ matrix.T\n",
    "    ax2.quiver(0, 0, transformed_eigenvector1[0], transformed_eigenvector1[1], color='red', scale=3, label=f'변환된 고유벡터1')\n",
    "    ax2.quiver(0, 0, transformed_eigenvector2[0], transformed_eigenvector2[1], color='green', scale=3, label=f'변환된 고유벡터2')\n",
    "    ax2.set_xlim(-2, 2)\n",
    "    ax2.set_ylim(-2, 2)\n",
    "    ax2.grid(True, alpha=0.3)\n",
    "    ax2.set_aspect('equal')\n",
    "    ax2.set_title('변환 후: 고유벡터는 방향이 유지됨')\n",
    "    ax2.legend()\n",
    "    \n",
    "    plt.suptitle(title)\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n",
    "    \n",
    "    print(f\"고유값: {eigenvalues}\")\n",
    "    print(f\"고유벡터:\\n{eigenvectors}\")\n",
    "\n",
    "# 예제 행렬들\n",
    "matrix1 = np.array([[2, 0], [0, 1]])  # 대각 행렬\n",
    "matrix2 = np.array([[1, 0.5], [0.5, 1]])  # 대칭 행렬\n",
    "\n",
    "visualize_eigenvectors(matrix1, \"대각 행렬의 고유벡터\")\n",
    "visualize_eigenvectors(matrix2, \"대칭 행렬의 고유벡터\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e2414b8",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "### 3-2. PCA: 데이터의 핵심 방향 찾기\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1c306f6f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# PCA의 직관적 이해\n",
    "np.random.seed(42)\n",
    "\n",
    "# 2D 데이터 생성 (상관관계가 있는 데이터)\n",
    "mean = [0, 0]\n",
    "cov = [[3, 2], [2, 3]]  # 상관관계가 있는 공분산 행렬\n",
    "data = np.random.multivariate_normal(mean, cov, 200)\n",
    "\n",
    "# 데이터 중앙 정렬\n",
    "X = data - data.mean(axis=0)\n",
    "\n",
    "# 공분산 행렬 계산\n",
    "cov_matrix = np.cov(X.T)\n",
    "\n",
    "# 고유값과 고유벡터 계산 (PCA)\n",
    "eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)\n",
    "\n",
    "# 고유값 순으로 정렬\n",
    "sorted_indices = np.argsort(eigenvalues)[::-1]\n",
    "sorted_eigenvalues = eigenvalues[sorted_indices]\n",
    "sorted_eigenvectors = eigenvectors[:, sorted_indices]\n",
    "\n",
    "# 시각화\n",
    "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))\n",
    "\n",
    "# 원본 데이터와 주성분\n",
    "ax1.scatter(X[:, 0], X[:, 1], alpha=0.6, c='lightblue')\n",
    "# 주성분 벡터 그리기\n",
    "pc1 = sorted_eigenvectors[:, 0]\n",
    "pc2 = sorted_eigenvectors[:, 1]\n",
    "ax1.quiver(0, 0, pc1[0]*np.sqrt(sorted_eigenvalues[0]), pc1[1]*np.sqrt(sorted_eigenvalues[0]), \n",
    "           color='red', scale=10, label=f'PC1 (분산: {sorted_eigenvalues[0]:.2f})')\n",
    "ax1.quiver(0, 0, pc2[0]*np.sqrt(sorted_eigenvalues[1]), pc2[1]*np.sqrt(sorted_eigenvalues[1]), \n",
    "           color='green', scale=10, label=f'PC2 (분산: {sorted_eigenvalues[1]:.2f})')\n",
    "ax1.set_xlim(-4, 4)\n",
    "ax1.set_ylim(-4, 4)\n",
    "ax1.grid(True, alpha=0.3)\n",
    "ax1.set_aspect('equal')\n",
    "ax1.set_title('원본 데이터와 주성분 (PC1, PC2)')\n",
    "ax1.legend()\n",
    "\n",
    "# PCA 변환된 데이터\n",
    "transformed_data = X @ sorted_eigenvectors\n",
    "ax2.scatter(transformed_data[:, 0], transformed_data[:, 1], alpha=0.6, c='lightcoral')\n",
    "ax2.set_xlim(-4, 4)\n",
    "ax2.set_ylim(-4, 4)\n",
    "ax2.grid(True, alpha=0.3)\n",
    "ax2.set_aspect('equal')\n",
    "ax2.set_title('PCA 변환 후: PC1이 가장 큰 분산을 가짐')\n",
    "ax2.set_xlabel('PC1')\n",
    "ax2.set_ylabel('PC2')\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"PCA의 직관적 의미:\")\n",
    "print(f\"PC1 (첫 번째 주성분): {pc1}\")\n",
    "print(f\"PC2 (두 번째 주성분): {pc2}\")\n",
    "print(f\"PC1 분산: {sorted_eigenvalues[0]:.2f} ({sorted_eigenvalues[0]/sum(sorted_eigenvalues)*100:.1f}%)\")\n",
    "print(f\"PC2 분산: {sorted_eigenvalues[1]:.2f} ({sorted_eigenvalues[1]/sum(sorted_eigenvalues)*100:.1f}%)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "918fd51e",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "---\n",
    "\n",
    "<br>\n",
    "\n",
    "## 4. 실제 활용: 이미지 처리와 데이터 분석 🖼️\n",
    "\n",
    "### 4-1. 이미지 필터링으로 이해하는 행렬\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6d2eea8e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 간단한 이미지 필터링 예제\n",
    "from PIL import Image\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 간단한 이미지 생성 (체크무늬 패턴)\n",
    "def create_checkerboard(size=50):\n",
    "    \"\"\"체크무늬 이미지 생성\"\"\"\n",
    "    img = np.zeros((size, size))\n",
    "    for i in range(size):\n",
    "        for j in range(size):\n",
    "            if (i // 10 + j // 10) % 2 == 0:\n",
    "                img[i, j] = 255\n",
    "    return img\n",
    "\n",
    "# 필터 행렬들 (커널)\n",
    "filters = {\n",
    "    '블러': np.array([[1/9, 1/9, 1/9], [1/9, 1/9, 1/9], [1/9, 1/9, 1/9]]),\n",
    "    '엣지 검출': np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]]),\n",
    "    '수평 엣지': np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]]),\n",
    "    '수직 엣지': np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]])\n",
    "}\n",
    "\n",
    "# 이미지 생성\n",
    "original = create_checkerboard()\n",
    "\n",
    "# 필터 적용 함수\n",
    "def apply_filter(image, filter_kernel):\n",
    "    \"\"\"이미지에 필터 적용\"\"\"\n",
    "    from scipy import ndimage\n",
    "    return ndimage.convolve(image, filter_kernel, mode='constant', cval=0)\n",
    "\n",
    "# 시각화\n",
    "fig, axes = plt.subplots(2, 3, figsize=(15, 10))\n",
    "axes = axes.flatten()\n",
    "\n",
    "# 원본 이미지\n",
    "axes[0].imshow(original, cmap='gray')\n",
    "axes[0].set_title('원본 이미지')\n",
    "axes[0].axis('off')\n",
    "\n",
    "# 필터 적용 결과\n",
    "for i, (filter_name, filter_kernel) in enumerate(filters.items()):\n",
    "    filtered = apply_filter(original, filter_kernel)\n",
    "    axes[i+1].imshow(filtered, cmap='gray')\n",
    "    axes[i+1].set_title(f'{filter_name} 필터')\n",
    "    axes[i+1].axis('off')\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"이미지 필터링 = 행렬 곱셈의 실제 활용\")\n",
    "print(\"각 픽셀 주변의 값들을 행렬(커널)로 가중 평균하여 새로운 픽셀 값 계산\")\n",
    "\n",
    "#### 🏢 Google - 이미지 검색 사례\n",
    "Google의 이미지 검색 시스템은 선형대수를 어떻게 활용할까요?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8667d9f0",
   "metadata": {},
   "source": [
    "python\n",
    "# Google 이미지 검색의 핵심: 특성 벡터와 유사도 계산\n",
    "def google_image_search_simulation():\n",
    "    \"\"\"Google 이미지 검색 시뮬레이션\"\"\"\n",
    "    \n",
    "    # 이미지 특성 벡터 (색상, 질감, 모양 등의 수치화된 특성)\n",
    "    image_features = {\n",
    "        'cat1.jpg': [0.8, 0.6, 0.9, 0.3, 0.7],  # 고양이 이미지\n",
    "        'cat2.jpg': [0.7, 0.5, 0.8, 0.4, 0.6],  # 다른 고양이\n",
    "        'dog1.jpg': [0.6, 0.8, 0.7, 0.5, 0.8],  # 강아지 이미지\n",
    "        'car1.jpg': [0.2, 0.1, 0.3, 0.9, 0.4],  # 자동차 이미지\n",
    "        'tree1.jpg': [0.4, 0.7, 0.5, 0.2, 0.9]  # 나무 이미지\n",
    "    }\n",
    "    \n",
    "    # 검색 쿼리: \"고양이\" 이미지 찾기\n",
    "    query_vector = np.array([0.8, 0.6, 0.9, 0.3, 0.7])  # 고양이 특성\n",
    "    \n",
    "    print(\"Google 이미지 검색 시뮬레이션\")\n",
    "    print(\"=\" * 50)\n",
    "    \n",
    "    # 각 이미지와의 유사도 계산 (코사인 유사도)\n",
    "    similarities = {}\n",
    "    for image_name, features in image_features.items():\n",
    "        similarity = np.dot(query_vector, features) / (np.linalg.norm(query_vector) * np.linalg.norm(features))\n",
    "        similarities[image_name] = similarity\n",
    "    \n",
    "    # 유사도 순으로 정렬\n",
    "    sorted_results = sorted(similarities.items(), key=lambda x: x[1], reverse=True)\n",
    "    \n",
    "    print(\"검색 결과 (유사도 순):\")\n",
    "    for i, (image, sim) in enumerate(sorted_results, 1):\n",
    "        print(f\"{i}. {image}: 유사도 {sim:.3f}\")\n",
    "    \n",
    "    print(\"\\n선형대수 활용:\")\n",
    "    print(\"- 이미지 특성 → 벡터로 표현\")\n",
    "    print(\"- 유사도 계산 → 벡터 내적 활용\")\n",
    "    print(\"- 검색 결과 정렬 → 수치 기반 순위 결정\")\n",
    "\n",
    "google_image_search_simulation()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3a63b6b5",
   "metadata": {},
   "outputs": [],
   "source": [
    "### 4-2. 추천 시스템으로 이해하는 행렬 분해"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc2c7b33",
   "metadata": {},
   "source": [
    "python\n",
    "# 간단한 추천 시스템 시뮬레이션\n",
    "np.random.seed(42)\n",
    "\n",
    "# 사용자-아이템 평점 행렬 (5명 사용자, 4개 아이템)\n",
    "# 0은 평점이 없는 경우\n",
    "ratings = np.array([\n",
    "    [5, 3, 0, 1],  # 사용자 1\n",
    "    [4, 0, 0, 1],  # 사용자 2\n",
    "    [1, 1, 0, 5],  # 사용자 3\n",
    "    [1, 0, 0, 4],  # 사용자 4\n",
    "    [0, 1, 5, 4]   # 사용자 5\n",
    "])\n",
    "\n",
    "print(\"사용자-아이템 평점 행렬:\")\n",
    "print(ratings)\n",
    "print(\"\\n행렬 분해의 직관적 의미:\")\n",
    "print(\"- 사용자 행렬: 각 사용자의 취향 (예: 액션 선호도, 로맨스 선호도)\")\n",
    "print(\"- 아이템 행렬: 각 아이템의 특성 (예: 액션 요소, 로맨스 요소)\")\n",
    "print(\"- 두 행렬의 곱으로 누락된 평점을 예측\")\n",
    "\n",
    "# 간단한 행렬 분해 (SVD의 직관적 이해)\n",
    "# 실제로는 더 복잡한 알고리즘 사용\n",
    "U = np.random.rand(5, 2)  # 사용자 특성 행렬\n",
    "V = np.random.rand(2, 4)  # 아이템 특성 행렬\n",
    "\n",
    "# 예측 평점\n",
    "predicted = U @ V\n",
    "print(f\"\\n예측된 평점 행렬:\\n{predicted.round(2)}\")\n",
    "\n",
    "# 원본과 예측 비교\n",
    "print(f\"\\n원본 평점 (0은 누락):\\n{ratings}\")\n",
    "print(f\"예측 평점:\\n{predicted.round(2)}\")\n",
    "\n",
    "#### 🏢 Netflix - 추천 시스템 사례\n",
    "Netflix의 추천 시스템은 행렬 분해를 어떻게 활용할까요?\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "819d4201",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Netflix 추천 시스템 시뮬레이션\n",
    "def netflix_recommendation_simulation():\n",
    "    \"\"\"Netflix 추천 시스템 시뮬레이션\"\"\"\n",
    "    \n",
    "    # 사용자-영화 평점 행렬 (실제로는 수백만 명의 사용자와 수만 개의 영화)\n",
    "    ratings_matrix = np.array([\n",
    "        # 액션  로맨스  코미디  스릴러  SF\n",
    "        [5,    1,     2,     4,     5],  # 사용자1 (액션/SF 선호)\n",
    "        [4,    2,     3,     5,     4],  # 사용자2 (스릴러 선호)\n",
    "        [1,    5,     4,     2,     1],  # 사용자3 (로맨스/코미디 선호)\n",
    "        [2,    4,     5,     1,     2],  # 사용자4 (코미디 선호)\n",
    "        [3,    3,     3,     3,     3]   # 사용자5 (중립적)\n",
    "    ])\n",
    "    \n",
    "    print(\"Netflix 추천 시스템 시뮬레이션\")\n",
    "    print(\"=\" * 50)\n",
    "    print(\"사용자-영화 평점 행렬:\")\n",
    "    print(ratings_matrix)\n",
    "    \n",
    "    # SVD를 통한 행렬 분해 (실제로는 더 복잡한 알고리즘 사용)\n",
    "    U, S, Vt = np.linalg.svd(ratings_matrix, full_matrices=False)\n",
    "    \n",
    "    # 잠재 요인 수 (실제로는 하이퍼파라미터 튜닝으로 결정)\n",
    "    k = 2\n",
    "    \n",
    "    # 차원 축소\n",
    "    U_k = U[:, :k]\n",
    "    S_k = np.diag(S[:k])\n",
    "    Vt_k = Vt[:k, :]\n",
    "    \n",
    "    # 재구성된 평점 행렬\n",
    "    reconstructed = U_k @ S_k @ Vt_k\n",
    "    \n",
    "    print(f\"\\n행렬 분해 결과 (k={k}):\")\n",
    "    print(\"사용자 특성 행렬 (U):\")\n",
    "    print(U_k.round(3))\n",
    "    print(\"\\n특이값 행렬 (S):\")\n",
    "    print(S_k.round(3))\n",
    "    print(\"\\n영화 특성 행렬 (V^T):\")\n",
    "    print(Vt_k.round(3))\n",
    "    \n",
    "    print(f\"\\n재구성된 평점 행렬:\")\n",
    "    print(reconstructed.round(2))\n",
    "    \n",
    "    # 새로운 사용자에 대한 추천\n",
    "    new_user = np.array([4, 1, 2, 5, 4])  # 액션/스릴러 선호\n",
    "    print(f\"\\n새로운 사용자 평점: {new_user}\")\n",
    "    \n",
    "    # 사용자 특성 추출\n",
    "    user_features = new_user @ Vt_k.T @ np.linalg.inv(S_k)\n",
    "    print(f\"사용자 특성: {user_features.round(3)}\")\n",
    "    \n",
    "    # 추천 점수 계산\n",
    "    recommendations = user_features @ S_k @ Vt_k\n",
    "    print(f\"추천 점수: {recommendations.round(2)}\")\n",
    "    \n",
    "    print(\"\\n선형대수 활용:\")\n",
    "    print(\"- 사용자 취향 → 벡터로 표현\")\n",
    "    print(\"- 영화 특성 → 벡터로 표현\")\n",
    "    print(\"- 행렬 분해 → 잠재 요인 발견\")\n",
    "    print(\"- 추천 점수 → 벡터 연산으로 계산\")\n",
    "\n",
    "netflix_recommendation_simulation()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9525b877",
   "metadata": {},
   "outputs": [],
   "source": [
    "---\n",
    "\n",
    "<br>\n",
    "\n",
    "## 5. 실습: 직접 만들어보는 선형대수 🛠️\n",
    "\n",
    "### 5-1. 나만의 벡터 클래스 만들기"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ae187fa",
   "metadata": {},
   "source": [
    "python\n",
    "class MyVector:\n",
    "    \"\"\"직관적인 벡터 클래스\"\"\"\n",
    "    \n",
    "    def __init__(self, x, y):\n",
    "        self.x = x\n",
    "        self.y = y\n",
    "    \n",
    "    def __add__(self, other):\n",
    "        \"\"\"벡터 덧셈: 이동 경로 합치기\"\"\"\n",
    "        return MyVector(self.x + other.x, self.y + other.y)\n",
    "    \n",
    "    def __mul__(self, scalar):\n",
    "        \"\"\"스칼라 곱: 크기 조절\"\"\"\n",
    "        return MyVector(self.x * scalar, self.y * scalar)\n",
    "    \n",
    "    def magnitude(self):\n",
    "        \"\"\"벡터의 크기\"\"\"\n",
    "        return np.sqrt(self.x**2 + self.y**2)\n",
    "    \n",
    "    def dot(self, other):\n",
    "        \"\"\"내적: 서로 얼마나 같은 방향인지\"\"\"\n",
    "        return self.x * other.x + self.y * other.y\n",
    "    \n",
    "    def plot(self, ax, color='blue', label=''):\n",
    "        \"\"\"벡터를 화살표로 그리기\"\"\"\n",
    "        ax.quiver(0, 0, self.x, self.y, color=color, scale=10, label=label)\n",
    "    \n",
    "    def __str__(self):\n",
    "        return f\"Vector({self.x}, {self.y})\"\n",
    "\n",
    "# 사용 예제\n",
    "v1 = MyVector(3, 2)\n",
    "v2 = MyVector(1, 4)\n",
    "\n",
    "print(f\"v1 = {v1}\")\n",
    "print(f\"v2 = {v2}\")\n",
    "print(f\"v1 + v2 = {v1 + v2}\")\n",
    "print(f\"v1의 크기 = {v1.magnitude():.2f}\")\n",
    "print(f\"v1 · v2 = {v1.dot(v2)}\")\n",
    "\n",
    "# 시각화\n",
    "fig, ax = plt.subplots(figsize=(8, 8))\n",
    "ax.set_xlim(-5, 5)\n",
    "ax.set_ylim(-5, 5)\n",
    "ax.grid(True, alpha=0.3)\n",
    "ax.set_aspect('equal')\n",
    "\n",
    "v1.plot(ax, 'red', 'v1')\n",
    "v2.plot(ax, 'blue', 'v2')\n",
    "(v1 + v2).plot(ax, 'green', 'v1 + v2')\n",
    "\n",
    "ax.legend()\n",
    "ax.set_title('나만의 벡터 클래스')\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "228e4d9f",
   "metadata": {},
   "outputs": [],
   "source": [
    "#### 🎯 과제 1: 벡터 클래스 확장하기\n",
    "기존 MyVector 클래스에 다음 메서드들을 추가해보세요:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8fe9e60b",
   "metadata": {},
   "source": [
    "python\n",
    "# 추가할 메서드들\n",
    "def angle(self, other):\n",
    "    \"\"\"두 벡터 사이의 각도 계산 (라디안)\"\"\"\n",
    "    # TODO: 구현해보세요!\n",
    "    pass\n",
    "\n",
    "def normalize(self):\n",
    "    \"\"\"단위 벡터로 정규화\"\"\"\n",
    "    # TODO: 구현해보세요!\n",
    "    pass\n",
    "\n",
    "def cross(self, other):\n",
    "    \"\"\"외적 계산 (3D 벡터용)\"\"\"\n",
    "    # TODO: 구현해보세요!\n",
    "    pass\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39d129d9",
   "metadata": {},
   "outputs": [],
   "source": [
    "### 5-2. 행렬 변환 시각화 과제"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22e85457",
   "metadata": {},
   "source": [
    "python\n",
    "# 2D 변환 행렬 구현\n",
    "class TransformationMatrix:\n",
    "    \"\"\"2D 변환 행렬 클래스\"\"\"\n",
    "    \n",
    "    @staticmethod\n",
    "    def rotation(angle_degrees):\n",
    "        \"\"\"회전 변환 행렬\"\"\"\n",
    "        angle_rad = np.radians(angle_degrees)\n",
    "        return np.array([\n",
    "            [np.cos(angle_rad), -np.sin(angle_rad)],\n",
    "            [np.sin(angle_rad), np.cos(angle_rad)]\n",
    "        ])\n",
    "    \n",
    "    @staticmethod\n",
    "    def scaling(sx, sy):\n",
    "        \"\"\"크기 조절 변환 행렬\"\"\"\n",
    "        return np.array([[sx, 0], [0, sy]])\n",
    "    \n",
    "    @staticmethod\n",
    "    def shear(sx, sy):\n",
    "        \"\"\"전단 변환 행렬\"\"\"\n",
    "        return np.array([[1, sx], [sy, 1]])\n",
    "\n",
    "def visualize_transformations():\n",
    "    \"\"\"다양한 변환을 시각화\"\"\"\n",
    "    # 원본 점들 (사각형)\n",
    "    points = np.array([\n",
    "        [-1, -1], [1, -1], [1, 1], [-1, 1], [-1, -1]\n",
    "    ])\n",
    "    \n",
    "    # 변환들\n",
    "    transformations = {\n",
    "        '회전 45도': TransformationMatrix.rotation(45),\n",
    "        'X축 2배 확대': TransformationMatrix.scaling(2, 1),\n",
    "        '전단 변환': TransformationMatrix.shear(0.5, 0)\n",
    "    }\n",
    "    \n",
    "    # 시각화\n",
    "    fig, axes = plt.subplots(2, 2, figsize=(12, 10))\n",
    "    axes = axes.flatten()\n",
    "    \n",
    "    # 원본\n",
    "    axes[0].plot(points[:, 0], points[:, 1], 'b-', linewidth=2)\n",
    "    axes[0].set_title('원본')\n",
    "    axes[0].set_xlim(-3, 3)\n",
    "    axes[0].set_ylim(-3, 3)\n",
    "    axes[0].grid(True, alpha=0.3)\n",
    "    axes[0].set_aspect('equal')\n",
    "    \n",
    "    # 변환들\n",
    "    for i, (name, matrix) in enumerate(transformations.items()):\n",
    "        transformed_points = points @ matrix.T\n",
    "        axes[i+1].plot(transformed_points[:, 0], transformed_points[:, 1], 'r-', linewidth=2)\n",
    "        axes[i+1].set_title(name)\n",
    "        axes[i+1].set_xlim(-3, 3)\n",
    "        axes[i+1].set_ylim(-3, 3)\n",
    "        axes[i+1].grid(True, alpha=0.3)\n",
    "        axes[i+1].set_aspect('equal')\n",
    "    \n",
    "    plt.tight_layout()\n",
    "    plt.show()\n",
    "\n",
    "visualize_transformations()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19ecbd16",
   "metadata": {},
   "outputs": [],
   "source": [
    "#### 🎯 과제 2: 복합 변환 구현하기\n",
    "여러 변환을 순서대로 적용하는 복합 변환을 구현해보세요:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1d4d27e4",
   "metadata": {},
   "source": [
    "python\n",
    "def composite_transformation():\n",
    "    \"\"\"복합 변환 구현\"\"\"\n",
    "    # TODO: 회전 + 확대 + 전단을 순서대로 적용하는 함수를 만들어보세요!\n",
    "    # 힌트: 행렬곱을 사용하여 변환들을 결합하세요\n",
    "    pass\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5182aae8",
   "metadata": {},
   "outputs": [],
   "source": [
    "### 5-3. 간단한 신경망 가중치 시각화"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83241f13",
   "metadata": {},
   "source": [
    "python\n",
    "# 간단한 신경망 가중치를 행렬로 이해하기\n",
    "def create_simple_network():\n",
    "    \"\"\"간단한 신경망 가중치 행렬 생성\"\"\"\n",
    "    # 입력층(2) -> 은닉층(3) -> 출력층(1)\n",
    "    W1 = np.random.randn(2, 3) * 0.1  # 입력층 -> 은닉층 가중치\n",
    "    W2 = np.random.randn(3, 1) * 0.1  # 은닉층 -> 출력층 가중치\n",
    "    \n",
    "    return W1, W2\n",
    "\n",
    "def visualize_weights(W1, W2):\n",
    "    \"\"\"신경망 가중치 시각화\"\"\"\n",
    "    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
    "    \n",
    "    # W1 시각화 (2x3 행렬)\n",
    "    im1 = ax1.imshow(W1, cmap='RdBu', aspect='auto')\n",
    "    ax1.set_title('입력층 → 은닉층 가중치 (W1)')\n",
    "    ax1.set_xlabel('은닉층 뉴런')\n",
    "    ax1.set_ylabel('입력층 뉴런')\n",
    "    plt.colorbar(im1, ax=ax1)\n",
    "    \n",
    "    # W2 시각화 (3x1 행렬)\n",
    "    im2 = ax2.imshow(W2, cmap='RdBu', aspect='auto')\n",
    "    ax2.set_title('은닉층 → 출력층 가중치 (W2)')\n",
    "    ax2.set_xlabel('출력층 뉴런')\n",
    "    ax2.set_ylabel('은닉층 뉴런')\n",
    "    plt.colorbar(im2, ax=ax2)\n",
    "    \n",
    "    plt.tight_layout()\n",
    "    plt.show()\n",
    "    \n",
    "    print(\"신경망 가중치 = 행렬의 실제 활용\")\n",
    "    print(f\"W1 모양: {W1.shape} (입력 2개 → 은닉층 3개)\")\n",
    "    print(f\"W2 모양: {W2.shape} (은닉층 3개 → 출력 1개)\")\n",
    "\n",
    "# 신경망 생성 및 시각화\n",
    "W1, W2 = create_simple_network()\n",
    "visualize_weights(W1, W2)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d8e4de2e",
   "metadata": {},
   "outputs": [],
   "source": [
    "#### 🎯 과제 3: 신경망 순전파 구현하기\n",
    "가중치 행렬을 사용하여 신경망의 순전파를 구현해보세요:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4603c79",
   "metadata": {},
   "source": [
    "python\n",
    "def forward_propagation(input_data, W1, W2, b1, b2):\n",
    "    \"\"\"신경망 순전파\"\"\"\n",
    "    # TODO: 입력 데이터를 신경망에 통과시켜 출력을 계산하세요!\n",
    "    # 힌트: z1 = input @ W1 + b1, a1 = sigmoid(z1), z2 = a1 @ W2 + b2, output = sigmoid(z2)\n",
    "    pass\n",
    "\n",
    "def sigmoid(x):\n",
    "    \"\"\"시그모이드 활성화 함수\"\"\"\n",
    "    return 1 / (1 + np.exp(-x))\n",
    "```\n",
    "\n",
    "### 5-4. 난이도별 실습 과제\n",
    "\n",
    "#### 🟢 초급 과제: 기본 개념 이해 및 구현\n",
    "1. **벡터 연산 계산기**: 두 벡터의 덧셈, 뺄셈, 내적, 외적을 계산하는 함수 구현\n",
    "2. **행렬 기본 연산**: 행렬 덧셈, 곱셈, 전치를 구현하는 함수 작성\n",
    "3. **단위 벡터 생성**: 주어진 방향의 단위 벡터를 생성하는 함수 구현\n",
    "\n",
    "#### 🟡 중급 과제: 실제 데이터 적용\n",
    "1. **이미지 회전**: NumPy 배열로 표현된 이미지를 행렬 변환으로 회전시키기\n",
    "2. **데이터 정규화**: 데이터셋을 평균 0, 표준편차 1로 정규화하는 함수 구현\n",
    "3. **거리 계산**: 여러 점들 간의 유클리드 거리를 행렬 연산으로 계산\n",
    "\n",
    "#### 🔴 고급 과제: 창의적 활용 및 확장\n",
    "1. **PCA 구현**: 고유값 분해를 사용하여 주성분 분석 직접 구현\n",
    "2. **행렬 분해**: SVD를 사용하여 이미지 압축 알고리즘 구현\n",
    "3. **신경망 시각화**: 가중치 행렬의 변화를 애니메이션으로 표현\n",
    "\n",
    "---\n",
    "\n",
    "<br>\n",
    "\n",
    "## 6. 마무리: 선형대수의 핵심 메시지 🎯\n",
    "\n",
    "### 6-1. 우리가 배운 것들\n",
    "\n",
    "1. **벡터**: 방향과 크기를 가진 화살표 → 데이터의 기본 단위\n",
    "2. **행렬**: 데이터 변형기 → 모델의 핵심 구성 요소\n",
    "3. **고유벡터**: 변하지 않는 특별한 방향 → 데이터의 핵심 특성\n",
    "4. **행렬 분해**: 복잡한 데이터를 단순한 요소로 분해 → 차원 축소, 추천 시스템\n",
    "\n",
    "### 6-2. 머신러닝에서의 활용\n",
    "\n",
    "- **데이터 표현**: 벡터, 행렬, 텐서로 데이터 표현\n",
    "- **모델 학습**: 가중치 행렬의 최적화\n",
    "- **특성 추출**: PCA, SVD로 중요한 특성 찾기\n",
    "- **차원 축소**: 고차원 데이터를 저차원으로 압축\n",
    "\n",
    "### 6-3. 다음 단계\n",
    "\n",
    "이제 미적분을 배워서 **\"어떻게 최적화할 것인가?\"**를 알아보겠습니다!\n",
    "\n",
    "> **💡 핵심 메시지**: \n",
    "> 선형대수는 머신러닝의 \"언어\"입니다. \n",
    "> 복잡한 수식보다는 **직관적 이해**와 **실제 활용**에 집중하세요!\n",
    "\n",
    "---\n",
    "\n",
    "## ✅ Part 5.5 완료 체크리스트\n",
    "\n",
    "### 🎯 달성 목표 확인\n",
    "다음 항목들을 모두 완료했다면 선형대수의 핵심을 이해한 것입니다!\n",
    "\n",
    "#### 📐 벡터 이해도\n",
    "- [ ] 벡터를 화살표로 직관적 이해\n",
    "- [ ] 벡터 덧셈의 기하학적 의미 설명 가능\n",
    "- [ ] 벡터 내적이 유사도를 측정한다는 것 이해\n",
    "- [ ] NumPy로 벡터 연산 구현 가능\n",
    "\n",
    "#### 🎁 행렬 이해도\n",
    "- [ ] 행렬을 데이터 변형기로 이해\n",
    "- [ ] 행렬곱을 복합 변환으로 설명 가능\n",
    "- [ ] 이미지 필터링에서 행렬이 어떻게 사용되는지 앎\n",
    "- [ ] NumPy로 행렬 연산 구현 가능\n",
    "\n",
    "#### 🧭 고유값과 고유벡터 이해도\n",
    "- [ ] 고유벡터가 변하지 않는 특별한 방향이라는 것 이해\n",
    "- [ ] PCA의 직관적 의미 이해\n",
    "- [ ] 차원 축소의 필요성과 방법 이해\n",
    "- [ ] 실제 데이터에 PCA 적용 가능\n",
    "\n",
    "#### 🛠️ 실습 완료도\n",
    "- [ ] 나만의 벡터 클래스 구현\n",
    "- [ ] 행렬 변환 시각화 코드 작성\n",
    "- [ ] 이미지 필터링 예제 실행\n",
    "- [ ] 신경망 가중치 시각화 이해\n",
    "\n",
    "### 🚀 다음 단계 준비 완료\n",
    "- [ ] 미적분 학습을 위한 기초 수학 준비\n",
    "- [ ] 기울기 하강법의 필요성 이해\n",
    "- [ ] 최적화 문제에 대한 관심과 동기 부여\n",
    "\n",
    "## 🎯 이제 할 수 있는 것들\n",
    "\n",
    "### 🚀 즉시 활용 가능한 기술\n",
    "- **이미지 필터링 알고리즘 구현**: 엣지 검출, 블러, 샤프닝 필터\n",
    "- **간단한 추천 시스템 만들기**: 사용자-아이템 행렬 분해\n",
    "- **데이터 차원 축소 (PCA) 적용**: 고차원 데이터를 저차원으로 압축\n",
    "- **신경망 가중치 시각화**: 행렬로 표현된 가중치 분석\n",
    "\n",
    "### 💼 포트폴리오 프로젝트 아이디어\n",
    "- **얼굴 인식 시스템**: 선형대수 기반 특성 추출 및 유사도 계산\n",
    "- **음악 추천 앱**: 행렬 분해를 활용한 사용자 취향 분석\n",
    "- **이미지 스타일 변환**: 행렬 변환을 통한 이미지 처리\n",
    "- **데이터 시각화 도구**: PCA를 활용한 다차원 데이터 시각화\n",
    "\n",
    "### 🔬 실무 적용 예시\n",
    "- **금융**: 포트폴리오 최적화, 위험 분석\n",
    "- **의료**: 의료 이미지 처리, 환자 데이터 분석\n",
    "- **게임**: 3D 그래픽 변환, 물리 시뮬레이션\n",
    "- **마케팅**: 고객 세분화, 추천 시스템\n",
    "\n",
    "> **💡 체크리스트 완료 후**: \n",
    "> 80% 이상 완료했다면 다음 파트(Part 5.6: 미적분)로 진행하세요!\n",
    "> 미완료 항목이 있다면 해당 섹션을 다시 학습하고 실습해보세요.\n",
    "\n",
    "---\n",
    "\n",
    "**⬅️ 이전 시간: [Part 5: AI 핵심 라이브러리](../05_ai_core_libraries/part_5_ai_core_libraries.md)**\n",
    "**➡️ 다음 시간: [Part 5.6: 머신러닝/딥러닝을 위한 미적분](../05.6_calculus_for_ml/part_5.6_calculus_for_ml.md)** "
   ]
  }
 ],
 "metadata": {},
 "nbformat": 4,
 "nbformat_minor": 5
}
