Commit 8ad6abd8 authored by insun park's avatar insun park
Browse files

Update AI lecture materials: Linear Algebra and Calculus for ML

- Update course materials for Linear Algebra with NumPy (part 5.5)
- Update course materials for Calculus for ML (part 5.6)
- Add new README files for source code directories
- Improve formatting and remove unnecessary line breaks
- Update main AI lecture README
parent ffef1c7c
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
| 파트 (Part) | 주차 (Week) | 주제 (Topic) | 관련 문서 | 소스코드 | | 파트 (Part) | 주차 (Week) | 주제 (Topic) | 관련 문서 | 소스코드 |
|:---:|:---:|:---|:---|:---| |:---:|:---:|:---|:---|:---|
| **1** | 1-5주차 | **AI 개발을 위한 파이썬 마스터리** | [0. 소개](./courses/00_introduction/part_0_introduction.md)<br/>[0.1. AI의 역사](./courses/00_introduction/part_0.1_history_of_ai.md)<br/>[1. 개발환경](./courses/01_ai_development_environment/part_1_ai_development_environment.md)<br/>[2. 파이썬 핵심](./courses/02_python_core_syntax/part_2_python_core_syntax.md)<br/>[3. 컬렉션](./courses/03_python_collections/part_3_python_collections.md)<br/>[4. 객체지향](./courses/04_object_oriented_programming/part_4_object_oriented_programming.md)<br/>[5. AI 라이브러리](./courses/05_ai_core_libraries/part_5_ai_core_libraries.md)<br/>[5.5. 선형대수학](./courses/05.5_linear_algebra_with_numpy/part_5.5_linear_algebra_with_numpy.md)<br/>[5.6. 미적분학](./courses/05.6_calculus_for_ml/part_5.6_calculus_for_ml.md) | [Part 2](./source_code/02_python_core_syntax/)<br/>[Part 3](./source_code/03_python_collections/)<br/>[Part 4](./source_code/04_object_oriented_programming/)<br/>[Part 5](./source_code/05_ai_core_libraries/)<br/>[Part 5.5](./source_code/05.5_linear_algebra_with_numpy/) | | **1** | 1-5주차 | **AI 개발을 위한 파이썬 마스터리** | [0. 소개](./courses/00_introduction/part_0_introduction.md)<br/>[0.1. AI의 역사](./courses/00_introduction/part_0.1_history_of_ai.md)<br/>[1. 개발환경](./courses/01_ai_development_environment/part_1_ai_development_environment.md)<br/>[2. 파이썬 핵심](./courses/02_python_core_syntax/part_2_python_core_syntax.md)<br/>[3. 컬렉션](./courses/03_python_collections/part_3_python_collections.md)<br/>[4. 객체지향](./courses/04_object_oriented_programming/part_4_object_oriented_programming.md)<br/>[5. AI 라이브러리](./courses/05_ai_core_libraries/part_5_ai_core_libraries.md)<br/>[5.5. 선형대수학](./courses/05.5_linear_algebra_with_numpy/part_5.5_linear_algebra_with_numpy.md)<br/>[5.6. 미적분학](./courses/05.6_calculus_for_ml/part_5.6_calculus_for_ml.md) | [Part 2](./source_code/02_python_core_syntax/)<br/>[Part 3](./source_code/03_python_collections/)<br/>[Part 4](./source_code/04_object_oriented_programming/)<br/>[Part 5](./source_code/05_ai_core_libraries/)<br/>[Part 5.5](./source_code/05.5_linear_algebra_with_numpy/)<br/>[Part 5.6](./source_code/05.6_calculus_for_ml/) |
| **2** | 6-9주차 | **핵심 AI 모델 개발 및 서빙** | [6. 머신러닝](./courses/06_machine_learning/part_6_machine_learning.md)<br/>[7. 딥러닝](./courses/07_deep_learning/part_7_deep_learning.md)<br/>[7.1. RNN](./courses/07_deep_learning/part_7.1_recurrent_neural_networks.md)<br/>[7.2. Transformer](./courses/07_deep_learning/part_7.2_transformer_and_llm_principles.md)<br/>[7.3. LangChain](./courses/07_deep_learning/part_7.3_llm_application_development_with_langchain.md)<br/>[7.4. GNN](./courses/07_deep_learning/part_7.4_graph_neural_networks.md)<br/>[7.5. 강화학습](./courses/07_deep_learning/part_7.5_reinforcement_learning.md)<br/>[8. FastAPI 서빙](./courses/08_model_serving_with_fastapi/part_8_model_serving_with_fastapi.md)<br/>[9. 프로덕션 API](./courses/09_production_ready_api/part_9_production_ready_api.md) | [Part 6](./source_code/06_machine_learning/)<br/>[Part 7](./source_code/07_deep_learning/)<br/>[Part 8](./source_code/08_model_serving_with_fastapi/)<br/>[Part 9](./source_code/09_production_ready_api/) | | **2** | 6-9주차 | **핵심 AI 모델 개발 및 서빙** | [6. 머신러닝](./courses/06_machine_learning/part_6_machine_learning.md)<br/>[7. 딥러닝](./courses/07_deep_learning/part_7_deep_learning.md)<br/>[7.1. RNN](./courses/07_deep_learning/part_7.1_recurrent_neural_networks.md)<br/>[7.2. Transformer](./courses/07_deep_learning/part_7.2_transformer_and_llm_principles.md)<br/>[7.3. LangChain](./courses/07_deep_learning/part_7.3_llm_application_development_with_langchain.md)<br/>[7.4. GNN](./courses/07_deep_learning/part_7.4_graph_neural_networks.md)<br/>[7.5. 강화학습](./courses/07_deep_learning/part_7.5_reinforcement_learning.md)<br/>[8. FastAPI 서빙](./courses/08_model_serving_with_fastapi/part_8_model_serving_with_fastapi.md)<br/>[9. 프로덕션 API](./courses/09_production_ready_api/part_9_production_ready_api.md) | [Part 6](./source_code/06_machine_learning/)<br/>[Part 7](./source_code/07_deep_learning/)<br/>[Part 8](./source_code/08_model_serving_with_fastapi/)<br/>[Part 9](./source_code/09_production_ready_api/) |
| **3** | 10-13주차 | **프로덕션 MLOps 및 AI 심화** | [10. 전문가 과정](./courses/10_expert_path/part_10_expert_path.md)<br/>[11. MLOps](./courses/11_mlops/part_11_mlops.md)<br/>[12. 모델 최적화](./courses/12_model_optimization/part_12_model_optimization.md)<br/>[13. 생성형 AI](./courses/13_generative_ai/part_13_generative_ai.md)<br/>[14. AI 윤리](./courses/14_ai_ethics/part_14_ai_ethics.md) | [Part 11](./source_code/11_mlops/)<br/>[Part 12](./source_code/12_model_optimization/)<br/>[Part 13](./source_code/13_generative_ai/)<br/>[Part 14](./source_code/14_ai_ethics/) | | **3** | 10-13주차 | **프로덕션 MLOps 및 AI 심화** | [10. 전문가 과정](./courses/10_expert_path/part_10_expert_path.md)<br/>[11. MLOps](./courses/11_mlops/part_11_mlops.md)<br/>[12. 모델 최적화](./courses/12_model_optimization/part_12_model_optimization.md)<br/>[13. 생성형 AI](./courses/13_generative_ai/part_13_generative_ai.md)<br/>[14. AI 윤리](./courses/14_ai_ethics/part_14_ai_ethics.md) | [Part 11](./source_code/11_mlops/)<br/>[Part 12](./source_code/12_model_optimization/)<br/>[Part 13](./source_code/13_generative_ai/)<br/>[Part 14](./source_code/14_ai_ethics/) |
| **4** | 14-15주차 | **최종 캡스톤 프로젝트** | [15. 캡스톤](./courses/15_capstone_project/part_15_capstone_project.md) | (프로젝트 개별 진행) | | **4** | 14-15주차 | **최종 캡스톤 프로젝트** | [15. 캡스톤](./courses/15_capstone_project/part_15_capstone_project.md) | (프로젝트 개별 진행) |
......
# Part 5.5: NumPy로 배우는 선형대수학 # Part 5.5: NumPy로 배우는 선형대수학 - 직관적 이해 중심
이 파트에서는 머신러닝과 딥러닝의 수학적 기반이 되는 선형대수학의 핵심 개념을 NumPy를 활용하여 학습합니다. ## 🎯 학습 목표
## 📚 학습 자료 이번 파트에서는 **복잡한 수식보다는 시각화와 비유를 통해** 선형대수의 핵심 개념을 직관적으로 이해합니다.
- **[강의 노트](./part_5.5_linear_algebra_with_numpy.md)**: 선형대수학의 핵심 개념과 NumPy를 활용한 구현 방법에 대한 상세 설명을 제공합니다. ### 핵심 접근법
- **[핵심 용어집](../../glossary.md)**: 이 파트의 주요 용어와 개념을 정리했습니다. - **벡터** = "방향과 크기를 가진 화살표"
- **행렬** = "데이터를 변형하는 마법 상자"
- **고유벡터** = "변하지 않는 특별한 방향"
- **PCA** = "데이터의 핵심 방향 찾기"
## 💻 실습 코드 ## 📚 학습 내용
- **[예제 코드](../../source_code/05.5_linear_algebra_with_numpy/)**: 강의 내용을 직접 실행해볼 수 있는 코드입니다. ### 1. 벡터: 방향과 크기를 가진 화살표 🏹
- 벡터의 직관적 이해 (화살표 비유)
- 벡터 연산의 시각화 (이동 경로 합치기)
- 벡터 내적의 의미 (서로 얼마나 같은 방향인지)
## 📋 주요 학습 내용 ### 2. 행렬: 데이터를 변형하는 마법 상자 🎁
- 행렬 변환의 시각화 (회전, 크기 조절, 전단)
- 행렬곱의 직관적 의미 (복합 변환)
- 이미지 필터링으로 이해하는 행렬
1. **벡터와 행렬의 기본 개념** ### 3. 고유값과 고유벡터: 변하지 않는 특별한 방향 🧭
- 벡터의 연산: 덧셈, 뺄셈, 스칼라 곱, 내적, 외적 - 고유벡터의 시각화 (방향 유지)
- 행렬의 연산: 덧셈, 뺄셈, 곱셈, 전치, 행렬식, 역행렬 - PCA: 데이터의 핵심 방향 찾기
- 차원 축소의 직관적 이해
2. **고급 선형대수 개념** ### 4. 실제 활용 예제
- 고유값과 고유벡터 - 이미지 처리와 행렬
- 특이값 분해(SVD) - 추천 시스템과 행렬 분해
- 선형 변환 - 신경망 가중치 시각화
3. **머신러닝에서의 응용** ## 🛠️ 실습 도구
- 주성분 분석(PCA)
- 선형 회귀 - **Python + NumPy**: 실제 계산과 구현
- 딥러닝에서의 활용 - **Matplotlib**: 시각화로 직관적 이해
- **Jupyter Notebook**: 대화형 학습
- **실제 예제**: 이미지 처리, 데이터 분석 등
## 📖 학습 자료
### 강의 자료
- `part_5.5_linear_algebra_with_numpy.md`: 시각화 중심의 이론 설명
### 실습 코드
- `source_code/part_5.5_linear_algebra_with_numpy.py`: 11개의 시각화 예제
## 🎯 핵심 메시지
> **"왜 이걸 배워야 하는가?"** 에 집중합니다.
>
> 선형대수는 머신러닝의 "언어"입니다.
> 복잡한 수식보다는 **직관적 이해**와 **실제 활용**에 집중하세요!
## 🚀 다음 단계
이제 미적분을 배워서 **"어떻게 최적화할 것인가?"**를 알아보겠습니다!
--- ---
......
# Part 5.5: NumPy로 배우는 선형대수학 # Part 5.5: NumPy로 배우는 선형대수학 - 직관적 이해 중심
**⬅️ 이전 시간: [Part 5: AI 핵심 라이브러리](../05_ai_core_libraries/part_5_ai_core_libraries.md)** **⬅️ 이전 시간: [Part 5: AI 핵심 라이브러리](../05_ai_core_libraries/part_5_ai_core_libraries.md)**
**➡️ 다음 시간: [Part 5.6: 머신러닝/딥러닝을 위한 미적분](../05.6_calculus_for_ml/part_5.6_calculus_for_ml.md)** **➡️ 다음 시간: [Part 5.6: 머신러닝/딥러닝을 위한 미적분](../05.6_calculus_for_ml/part_5.6_calculus_for_ml.md)**
--- ---
<br>
> ## 1. 학습 목표 (Learning Objectives) > ## 🎯 학습 목표 (Learning Objectives)
> >
> 이번 파트가 끝나면, 여러분은 다음을 할 수 있게 됩니다. > 이번 파트가 끝나면, 여러분은 다음을 할 수 있게 됩니다.
> >
> - 머신러닝과 딥러닝의 수학적 기반이 되는 선형대수학의 핵심 개념을 이해할 수 있습니다. > - **직관적으로 이해하기**: 복잡한 수식보다는 시각화와 비유를 통해 선형대수의 핵심 개념을 직관적으로 이해할 수 있습니다.
> - NumPy를 사용하여 벡터와 행렬 연산을 수행하고 선형대수학 개념을 코드로 구현할 수 있습니다. > - **실습 중심 학습**: Python과 NumPy를 사용하여 개념을 직접 구현하고 시각화할 수 있습니다.
> - 벡터, 행렬, 텐서의 차이점을 설명하고 각각의 연산 방법을 NumPy로 구현할 수 있습니다. > - **실제 활용**: 머신러닝에서 선형대수가 어떻게 활용되는지 구체적인 예제를 통해 이해할 수 있습니다.
> - 고유값과 고유벡터, 특이값 분해(SVD)의 개념을 이해하고 NumPy로 계산할 수 있습니다.
> - 선형대수학 개념이 머신러닝과 딥러닝에서 어떻게 활용되는지 설명할 수 있습니다.
<br>
> ## 2. 핵심 요약 (Key Summary) > ## 🔑 핵심 접근법 (Key Approach)
>
> 이 파트에서는 머신러닝과 딥러닝의 기반이 되는 선형대수학의 핵심 개념들을 NumPy를 활용하여 학습합니다. 벡터와 행렬의 기본 연산부터 시작하여 내적, 외적, 행렬 분해, 고유값과 고유벡터, 특이값 분해(SVD)까지 다양한 선형대수학 개념을 직접 코드로 구현하며 이해합니다. 또한 이러한 개념들이 머신러닝 알고리즘과 딥러닝 모델에서 어떻게 활용되는지 실제 사례를 통해 학습합니다.
> >
> - **핵심 키워드**: `벡터(Vector)`, `행렬(Matrix)`, `텐서(Tensor)`, `내적(Dot Product)`, `외적(Cross Product)`, `행렬식(Determinant)`, `역행렬(Inverse Matrix)`, `고유값(Eigenvalue)`, `고유벡터(Eigenvector)`, `특이값 분해(SVD)`, `선형 변환(Linear Transformation)` > **"왜 이걸 배워야 하는가?"** 에 집중합니다.
> - 벡터 = "방향과 크기를 가진 화살표"
> - 행렬 = "데이터를 변형하는 마법 상자"
> - 고유벡터 = "변하지 않는 특별한 방향"
> - PCA = "데이터의 핵심 방향 찾기"
<br>
> ## 3. 도입: 머신러닝의 수학적 기반 (Introduction) > ## 🛠️ 사용 도구 (Tools)
> > - **Python + NumPy**: 실제 계산과 구현
> 머신러닝과 딥러닝의 세계로 본격적으로 들어가기 전에, 이 분야의 수학적 기반인 선형대수학을 이해하는 것은 매우 중요합니다. 선형대수학은 벡터와 행렬을 다루는 수학의 한 분야로, 데이터 표현, 변환, 분석의 핵심 도구입니다. > - **Matplotlib**: 시각화로 직관적 이해
> > - **Jupyter Notebook**: 대화형 학습
> > [!TIP] > - **실제 예제**: 이미지 처리, 데이터 분석 등
> > 본 파트의 모든 예제 코드는 `../../source_code/part_5.5_linear_algebra_with_numpy.py` 파일에서 직접 실행하고 수정해볼 수 있습니다.
>
> ### AI 프로젝트에서 선형대수학의 역할
>
> ```mermaid
> graph TD
> subgraph "선형대수학의 역할"
> A["<b>데이터 표현</b><br/>- 벡터, 행렬, 텐서로 데이터 표현<br/>- 고차원 데이터의 효율적 처리"]
> B["<b>모델 구현</b><br/>- 선형 회귀, PCA 등의 알고리즘<br/>- 신경망의 가중치와 활성화"]
> C["<b>최적화</b><br/>- 경사 하강법의 수학적 기반<br/>- 역전파 알고리즘"]
> D["<b>차원 축소</b><br/>- PCA, SVD를 통한 특성 추출<br/>- 데이터 압축과 시각화"]
> end
>
> A -->|"기반 제공"| B
> B -->|"효율적 계산"| C
> C -->|"성능 향상"| D
> D -->|"피드백"| A
> ```
>
> 이번 파트에서는 NumPy를 활용하여 선형대수학의 핵심 개념들을 직관적으로 이해하고, 이를 코드로 구현하는 방법을 배웁니다. 이론과 실습을 병행하여, 추상적인 수학 개념이 실제 머신러닝과 딥러닝에서 어떻게 활용되는지 명확하게 이해할 수 있을 것입니다.
--- ---
<br>
> ## 4. 벡터와 행렬: 기본 개념과 연산 ## 1. 벡터: 방향과 크기를 가진 화살표 🏹
>
> > **🎯 1일차 목표:** 벡터와 행렬의 기본 개념을 이해하고 NumPy로 연산하는 방법을 배웁니다. ### 1-1. 벡터란 무엇인가? (직관적 이해)
>
> ### 4-1. 벡터(Vector): 방향과 크기를 가진 양 > **💡 비유**: 벡터는 "화살표"입니다!
> > - **크기**: 화살표의 길이
> 벡터는 크기와 방향을 가진 양으로, 수학적으로는 숫자의 순서 있는 집합으로 표현됩니다. NumPy에서는 1차원 배열로 표현됩니다. > - **방향**: 화살표가 가리키는 방향
> > - **위치**: 화살표가 시작하는 점
> ```python
```python
import numpy as np import numpy as np
import matplotlib.pyplot as plt
# 벡터를 화살표로 시각화
def plot_vector(ax, start, vector, color='blue', label=''):
"""벡터를 화살표로 그리는 함수"""
ax.quiver(start[0], start[1], vector[0], vector[1],
angles='xy', scale_units='xy', scale=1, color=color, label=label)
# 그래프 설정
fig, ax = plt.subplots(figsize=(10, 8))
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.grid(True, alpha=0.3)
ax.set_aspect('equal')
# 원점
origin = np.array([0, 0])
# 다양한 벡터들
vectors = {
'v1': np.array([3, 2]), # 오른쪽 위로
'v2': np.array([-2, 1]), # 왼쪽 위로
'v3': np.array([0, -3]), # 아래로
'v4': np.array([4, 0]) # 오른쪽으로
}
colors = ['red', 'blue', 'green', 'purple']
# 벡터들을 화살표로 그리기
for i, (name, vector) in enumerate(vectors.items()):
plot_vector(ax, origin, vector, colors[i], name)
# 벡터의 크기 계산
magnitude = np.linalg.norm(vector)
print(f"{name}: 크기 = {magnitude:.2f}, 방향 = {vector}")
ax.set_xlabel('X축')
ax.set_ylabel('Y축')
ax.set_title('벡터 = 방향과 크기를 가진 화살표')
ax.legend()
plt.show()
```
### 1-2. 벡터 연산의 직관적 의미
#### 벡터 덧셈: "이동 경로 합치기"
```python
# 벡터 덧셈 시각화
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
# 첫 번째 그래프: 개별 벡터들
ax1.set_xlim(-5, 5)
ax1.set_ylim(-5, 5)
ax1.grid(True, alpha=0.3)
ax1.set_aspect('equal')
v1 = np.array([2, 1])
v2 = np.array([1, 3])
# 벡터 생성 plot_vector(ax1, origin, v1, 'red', 'v1')
v1 = np.array([1, 2, 3]) plot_vector(ax1, origin, v2, 'blue', 'v2')
v2 = np.array([4, 5, 6]) ax1.set_title('개별 벡터들')
ax1.legend()
# 벡터의 크기(norm) 계산 # 두 번째 그래프: 벡터 덧셈
magnitude_v1 = np.linalg.norm(v1) ax2.set_xlim(-5, 5)
print(f"v1의 크기: {magnitude_v1:.2f}") ax2.set_ylim(-5, 5)
ax2.grid(True, alpha=0.3)
ax2.set_aspect('equal')
# 벡터 덧셈과 뺄셈 # v1을 먼저 그리고, 그 끝점에서 v2를 그리기
plot_vector(ax2, origin, v1, 'red', 'v1')
plot_vector(ax2, v1, v2, 'blue', 'v2') # v1의 끝점에서 v2 시작
# 합 벡터 (원점에서 최종 위치까지)
v_sum = v1 + v2 v_sum = v1 + v2
v_diff = v1 - v2 plot_vector(ax2, origin, v_sum, 'green', 'v1 + v2', linewidth=3)
print(f"벡터 덧셈: {v_sum}")
print(f"벡터 뺄셈: {v_diff}")
# 스칼라 곱
scalar = 2
v_scaled = scalar * v1
print(f"{scalar} * v1 = {v_scaled}")
# 내적(Dot Product)
dot_product = np.dot(v1, v2)
print(f"v1 · v2 = {dot_product}")
# 외적(Cross Product) - 3차원 벡터에만 적용 가능
cross_product = np.cross(v1, v2)
print(f"v1 × v2 = {cross_product}")
```
> [!NOTE] 내적(Dot Product)의 기하학적 의미 ax2.set_title('벡터 덧셈: 이동 경로 합치기')
> ax2.legend()
> 두 벡터 A와 B의 내적은 `A·B = |A||B|cosθ`로 정의됩니다. 여기서 θ는 두 벡터 사이의 각도입니다.
> plt.tight_layout()
> - **θ = 0° (같은 방향)**: 내적은 최대값(두 벡터 크기의 곱) plt.show()
> - **θ = 90° (수직)**: 내적은 0
> - **θ = 180° (반대 방향)**: 내적은 최소값(두 벡터 크기의 곱의 음수) print(f"v1 = {v1}")
> print(f"v2 = {v2}")
> 이러한 특성 때문에 내적은 두 벡터의 유사도를 측정하는 데 자주 사용됩니다. 머신러닝에서 코사인 유사도(cosine similarity)가 바로 이 원리를 활용합니다. print(f"v1 + v2 = {v_sum}")
>
> ### 4-2. 행렬(Matrix): 2차원 데이터 구조
>
> 행렬은 숫자를 직사각형 형태로 배열한 것으로, 행(row)과 열(column)로 구성됩니다. NumPy에서는 2차원 배열로 표현됩니다.
>
> ```python
# 행렬 생성
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print(f"행렬 A:\n{A}")
print(f"행렬 B:\n{B}")
# 행렬 덧셈과 뺄셈
C = A + B
D = A - B
print(f"A + B =\n{C}")
print(f"A - B =\n{D}")
# 행렬 곱셈
E = np.matmul(A, B) # 또는 A @ B (Python 3.5 이상)
print(f"A × B =\n{E}")
# 전치 행렬(Transpose)
A_T = A.T
print(f"A의 전치 행렬:\n{A_T}")
# 행렬식(Determinant)
det_A = np.linalg.det(A)
print(f"A의 행렬식: {det_A}")
# 역행렬(Inverse Matrix)
inv_A = np.linalg.inv(A)
print(f"A의 역행렬:\n{inv_A}")
# 역행렬 검증: A × A^(-1) = I (단위 행렬)
I = np.matmul(A, inv_A)
print(f"A × A^(-1) =\n{np.round(I, decimals=10)}") # 부동소수점 오차 처리
``` ```
> [!NOTE] 행렬 곱셈의 의미 #### 벡터 내적: "서로 얼마나 같은 방향인지"
> ```python
> 행렬 곱셈은 선형 변환의 합성을 나타냅니다. 예를 들어, 행렬 A가 회전 변환을 나타내고 행렬 B가 크기 조절 변환을 나타낸다면, A×B는 "먼저 크기를 조절한 후 회전"하는 복합 변환을 나타냅니다. # 벡터 내적의 직관적 이해
> def cosine_similarity(v1, v2):
> 머신러닝에서 행렬 곱셈은 특히 중요합니다. 예를 들어, 신경망의 한 층에서 입력 벡터 x와 가중치 행렬 W의 곱 W×x는 각 뉴런의 가중 합(weighted sum)을 계산합니다. """두 벡터의 코사인 유사도 계산"""
dot_product = np.dot(v1, v2)
norm_v1 = np.linalg.norm(v1)
norm_v2 = np.linalg.norm(v2)
return dot_product / (norm_v1 * norm_v2)
# 다양한 각도의 벡터들
angles = [0, 45, 90, 135, 180] # 도
vectors = []
for angle in angles:
# 각도를 라디안으로 변환
rad = np.radians(angle)
# 단위 벡터 생성
v = np.array([np.cos(rad), np.sin(rad)])
vectors.append(v)
# 기준 벡터 (오른쪽 방향)
base_vector = np.array([1, 0])
print("벡터 간 유사도 (내적의 직관적 의미):")
print("-" * 50)
for i, angle in enumerate(angles):
similarity = cosine_similarity(base_vector, vectors[i])
print(f"각도 {angle}°: 유사도 = {similarity:.3f} ({'같은 방향' if similarity > 0.9 else '수직' if abs(similarity) < 0.1 else '반대 방향' if similarity < -0.9 else '대각선'})")
```
--- ---
<br>
> ## 5. 고급 선형대수학 개념과 NumPy 구현
>
> > **🎯 2일차 목표:** 고유값, 고유벡터, SVD 등 고급 선형대수학 개념을 이해하고 NumPy로 구현합니다. ## 2. 행렬: 데이터를 변형하는 마법 상자 🎁
>
> ### 5-1. 고유값(Eigenvalue)과 고유벡터(Eigenvector) ### 2-1. 행렬이란 무엇인가? (직관적 이해)
>
> 고유벡터는 선형 변환(행렬 곱셈)을 거쳐도 방향이 변하지 않는 특별한 벡터입니다. 고유값은 그 과정에서 벡터의 크기가 변하는 비율을 나타냅니다. > **💡 비유**: 행렬은 "데이터 변형기"입니다!
> > - **입력**: 원본 데이터 (벡터)
> 수학적으로, 정방행렬 A에 대해 Av = λv를 만족하는 벡터 v와 스칼라 λ가 있다면, v는 A의 고유벡터이고 λ는 대응하는 고유값입니다. > - **변형**: 행렬이 데이터를 어떻게 바꿀지 정의
> > - **출력**: 변형된 데이터 (새로운 벡터)
> ```python
# 예제 행렬 ```python
A = np.array([[4, -2], [1, 1]]) # 행렬 변환의 시각화
def plot_transformation(original_points, transformed_points, title):
# 고유값과 고유벡터 계산 """점들의 변환을 시각화"""
eigenvalues, eigenvectors = np.linalg.eig(A) fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
print(f"고유값: {eigenvalues}") # 원본 점들
print(f"고유벡터:\n{eigenvectors}") ax1.scatter(original_points[:, 0], original_points[:, 1], c='blue', alpha=0.6)
ax1.set_xlim(-3, 3)
# 검증: A × v = λ × v ax1.set_ylim(-3, 3)
for i in range(len(eigenvalues)): ax1.grid(True, alpha=0.3)
v = eigenvectors[:, i] ax1.set_aspect('equal')
Av = np.matmul(A, v) ax1.set_title('변환 전')
lambda_v = eigenvalues[i] * v
print(f"\n고유값 {eigenvalues[i]}에 대한 검증:") # 변환된 점들
print(f"A × v = {Av}") ax2.scatter(transformed_points[:, 0], transformed_points[:, 1], c='red', alpha=0.6)
print(f"λ × v = {lambda_v}") ax2.set_xlim(-3, 3)
ax2.set_ylim(-3, 3)
ax2.grid(True, alpha=0.3)
ax2.set_aspect('equal')
ax2.set_title('변환 후')
plt.suptitle(title)
plt.tight_layout()
plt.show()
# 원형 점들 생성
theta = np.linspace(0, 2*np.pi, 50)
circle_points = np.column_stack([np.cos(theta), np.sin(theta)])
# 1. 회전 변환 (90도 회전)
rotation_matrix = np.array([[0, -1], [1, 0]]) # 90도 회전
rotated_points = circle_points @ rotation_matrix.T
plot_transformation(circle_points, rotated_points, "회전 변환: 원형 → 회전된 원형")
# 2. 크기 조절 변환 (X축으로 2배 늘리기)
scaling_matrix = np.array([[2, 0], [0, 1]]) # X축 2배
scaled_points = circle_points @ scaling_matrix.T
plot_transformation(circle_points, scaled_points, "크기 조절: 원형 → 타원형")
# 3. 전단 변환 (X축 방향으로 기울이기)
shear_matrix = np.array([[1, 0.5], [0, 1]]) # X축 방향 전단
sheared_points = circle_points @ shear_matrix.T
plot_transformation(circle_points, sheared_points, "전단 변환: 원형 → 기울어진 원형")
``` ```
> [!NOTE] 고유값과 고유벡터의 응용 ### 2-2. 행렬곱의 직관적 의미
>
> 고유값과 고유벡터는 데이터 과학과 머신러닝에서 매우 중요한 역할을 합니다: ```python
> # 행렬곱의 시각화: 복합 변환
> - **주성분 분석(PCA)**: 데이터의 공분산 행렬의 고유벡터는 데이터의 주요 변동 방향을 나타냅니다. 이를 통해 차원 축소와 특성 추출이 가능합니다. fig, axes = plt.subplots(2, 2, figsize=(12, 10))
> - **PageRank 알고리즘**: Google의 초기 검색 알고리즘은 웹 그래프의 인접 행렬의 주요 고유벡터를 사용하여 웹페이지의 중요도를 계산했습니다.
> - **안정성 분석**: 동적 시스템의 안정성은 시스템 행렬의 고유값을 통해 분석할 수 있습니다. # 원본 점들
> original = circle_points
> ### 5-2. 특이값 분해(Singular Value Decomposition, SVD)
> # 변환 행렬들
> SVD는 어떤 행렬이든 세 개의 특별한 행렬의 곱으로 분해하는 방법입니다: A = UΣV^T. 여기서 U와 V는 직교 행렬이고, Σ는 대각 행렬입니다. A = np.array([[0.8, -0.6], [0.6, 0.8]]) # 회전 + 축소
> B = np.array([[1.5, 0], [0, 0.8]]) # X축 확대, Y축 축소
> SVD는 행렬이 정방행렬이 아니거나 역행렬이 존재하지 않는 경우에도 적용할 수 있어, 더 일반적인 행렬 분해 방법입니다.
> # 단계별 변환
> ```python step1 = original @ A.T
# 예제 행렬 (비정방행렬) step2 = step1 @ B.T
B = np.array([[3, 2, 2], [2, 3, -2]])
# SVD 계산 # 복합 변환 (A × B)
U, Sigma, VT = np.linalg.svd(B) combined = original @ (A @ B).T
print(f"U 행렬:\n{U}") # 시각화
print(f"특이값: {Sigma}") titles = ['원본', 'A 변환 후', 'B 변환 후', 'A×B 복합 변환']
print(f"V^T 행렬:\n{VT}") points_list = [original, step1, step2, combined]
for i, (ax, title, points) in enumerate(zip(axes.flat, titles, points_list)):
ax.scatter(points[:, 0], points[:, 1], c='blue', alpha=0.6)
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
ax.grid(True, alpha=0.3)
ax.set_aspect('equal')
ax.set_title(title)
# 원래 행렬 복원 plt.tight_layout()
# Sigma는 대각 요소만 반환하므로, 대각 행렬로 변환해야 함 plt.show()
Sigma_matrix = np.zeros(B.shape)
for i in range(min(B.shape)):
Sigma_matrix[i, i] = Sigma[i]
B_reconstructed = U @ Sigma_matrix @ VT print("행렬곱의 의미: 변환을 순서대로 적용")
print(f"\n복원된 행렬:\n{B_reconstructed}") print(f"A = {A}")
print(f"B = {B}")
print(f"A × B = {A @ B}")
``` ```
> [!NOTE] SVD의 응용 ---
>
> SVD는 머신러닝과 데이터 과학에서 다양하게 활용됩니다: <br>
>
> - **차원 축소**: PCA와 유사하게, SVD를 사용하여 데이터의 중요한 특성만 추출할 수 있습니다. ## 3. 고유값과 고유벡터: 변하지 않는 특별한 방향 🧭
> - **이미지 압축**: 큰 이미지 행렬에서 가장 중요한 특이값만 사용하여 이미지를 압축할 수 있습니다.
> - **추천 시스템**: Netflix와 같은 추천 시스템에서 사용자-아이템 행렬의 SVD를 통해 잠재 요인(latent factors)을 추출하여 추천에 활용합니다. ### 3-1. 고유벡터의 직관적 이해
> - **노이즈 제거**: 신호 처리에서 작은 특이값에 해당하는 성분을 제거하여 노이즈를 줄일 수 있습니다.
> > **💡 비유**: 고유벡터는 "변하지 않는 특별한 방향"입니다!
> ### 5-3. 주성분 분석(PCA)과 고유값 분해 > - 행렬이 데이터를 변형할 때, 특정 방향의 벡터는 방향이 바뀌지 않습니다
> > - 단지 크기만 변할 뿐입니다 (고유값만큼)
> PCA는 고차원 데이터의 분산을 최대한 보존하면서 저차원으로 축소하는 기법입니다. 이는 데이터 공분산 행렬의 고유값 분해를 통해 구현됩니다.
> ```python
> > [!NOTE] PCA와 고유값 분해의 관계 # 고유벡터 시각화
> > def visualize_eigenvectors(matrix, title):
> > 데이터의 공분산 행렬은 데이터가 각 차원에서 얼마나 흩어져 있는지, 그리고 차원 간에 어떤 상관관계가 있는지를 나타냅니다. """고유벡터를 시각화하는 함수"""
> > - **고유벡터**: 공분산 행렬의 고유벡터는 데이터가 가장 크게 분산된 방향(주성분)을 가리킵니다. 즉, 데이터의 '주요 축'을 나타냅니다. # 고유값과 고유벡터 계산
> > - **고유값**: 각 고유벡터에 해당하는 고유값은 해당 방향으로 데이터가 얼마나 많이 분산되어 있는지를 나타냅니다. 고유값이 클수록 더 중요한 주성분입니다. eigenvalues, eigenvectors = np.linalg.eig(matrix)
> >
> > 따라서, 가장 큰 고유값들을 가진 고유벡터(주성분)들로 원래 데이터를 투영(projection)하면, 원본 데이터의 분산을 최대한 유지하면서 차원을 축소할 수 있습니다. # 원형 점들
> theta = np.linspace(0, 2*np.pi, 100)
> ```python circle = np.column_stack([np.cos(theta), np.sin(theta)])
# PCA 예제
# 데이터 생성 # 변환 전후
transformed = circle @ matrix.T
# 고유벡터들
eigenvector1 = eigenvectors[:, 0]
eigenvector2 = eigenvectors[:, 1]
# 시각화
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
# 변환 전
ax1.scatter(circle[:, 0], circle[:, 1], c='lightblue', alpha=0.6, s=20)
ax1.quiver(0, 0, eigenvector1[0], eigenvector1[1], color='red', scale=3, label=f'고유벡터1 (λ={eigenvalues[0]:.2f})')
ax1.quiver(0, 0, eigenvector2[0], eigenvector2[1], color='green', scale=3, label=f'고유벡터2 (λ={eigenvalues[1]:.2f})')
ax1.set_xlim(-2, 2)
ax1.set_ylim(-2, 2)
ax1.grid(True, alpha=0.3)
ax1.set_aspect('equal')
ax1.set_title('변환 전: 고유벡터 방향')
ax1.legend()
# 변환 후
ax2.scatter(transformed[:, 0], transformed[:, 1], c='lightcoral', alpha=0.6, s=20)
# 변환된 고유벡터들
transformed_eigenvector1 = eigenvector1 @ matrix.T
transformed_eigenvector2 = eigenvector2 @ matrix.T
ax2.quiver(0, 0, transformed_eigenvector1[0], transformed_eigenvector1[1], color='red', scale=3, label=f'변환된 고유벡터1')
ax2.quiver(0, 0, transformed_eigenvector2[0], transformed_eigenvector2[1], color='green', scale=3, label=f'변환된 고유벡터2')
ax2.set_xlim(-2, 2)
ax2.set_ylim(-2, 2)
ax2.grid(True, alpha=0.3)
ax2.set_aspect('equal')
ax2.set_title('변환 후: 고유벡터는 방향이 유지됨')
ax2.legend()
plt.suptitle(title)
plt.tight_layout()
plt.show()
print(f"고유값: {eigenvalues}")
print(f"고유벡터:\n{eigenvectors}")
# 예제 행렬들
matrix1 = np.array([[2, 0], [0, 1]]) # 대각 행렬
matrix2 = np.array([[1, 0.5], [0.5, 1]]) # 대칭 행렬
visualize_eigenvectors(matrix1, "대각 행렬의 고유벡터")
visualize_eigenvectors(matrix2, "대칭 행렬의 고유벡터")
```
### 3-2. PCA: 데이터의 핵심 방향 찾기
```python
# PCA의 직관적 이해
np.random.seed(42) np.random.seed(42)
# 2D 데이터 생성 (상관관계가 있는 데이터)
mean = [0, 0] mean = [0, 0]
cov = [[5, 4], [4, 5]] # 공분산 행렬 cov = [[3, 2], [2, 3]] # 상관관계가 있는 공분산 행렬
data = np.random.multivariate_normal(mean, cov, 200) data = np.random.multivariate_normal(mean, cov, 200)
# 데이터 중앙 정렬 # 데이터 중앙 정렬
...@@ -254,138 +354,289 @@ X = data - data.mean(axis=0) ...@@ -254,138 +354,289 @@ X = data - data.mean(axis=0)
# 공분산 행렬 계산 # 공분산 행렬 계산
cov_matrix = np.cov(X.T) cov_matrix = np.cov(X.T)
print(f"공분산 행렬:\n{cov_matrix}")
# 고유값과 고유벡터 계산 # 고유값과 고유벡터 계산 (PCA)
eigenvalues, eigenvectors = np.linalg.eig(cov_matrix) eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)
# 고유값을 기준으로 내림차순 정렬 # 고유값 순으로 정렬
sorted_indices = np.argsort(eigenvalues)[::-1] sorted_indices = np.argsort(eigenvalues)[::-1]
sorted_eigenvalues = eigenvalues[sorted_indices] sorted_eigenvalues = eigenvalues[sorted_indices]
sorted_eigenvectors = eigenvectors[:, sorted_indices] sorted_eigenvectors = eigenvectors[:, sorted_indices]
print(f"고유값: {sorted_eigenvalues}") # 시각화
print(f"고유벡터:\n{sorted_eigenvectors}") fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
# 주성분 (PC) # 원본 데이터와 주성분
ax1.scatter(X[:, 0], X[:, 1], alpha=0.6, c='lightblue')
# 주성분 벡터 그리기
pc1 = sorted_eigenvectors[:, 0] pc1 = sorted_eigenvectors[:, 0]
pc2 = sorted_eigenvectors[:, 1] pc2 = sorted_eigenvectors[:, 1]
ax1.quiver(0, 0, pc1[0]*np.sqrt(sorted_eigenvalues[0]), pc1[1]*np.sqrt(sorted_eigenvalues[0]),
color='red', scale=10, label=f'PC1 (분산: {sorted_eigenvalues[0]:.2f})')
ax1.quiver(0, 0, pc2[0]*np.sqrt(sorted_eigenvalues[1]), pc2[1]*np.sqrt(sorted_eigenvalues[1]),
color='green', scale=10, label=f'PC2 (분산: {sorted_eigenvalues[1]:.2f})')
ax1.set_xlim(-4, 4)
ax1.set_ylim(-4, 4)
ax1.grid(True, alpha=0.3)
ax1.set_aspect('equal')
ax1.set_title('원본 데이터와 주성분 (PC1, PC2)')
ax1.legend()
# PCA 변환된 데이터
transformed_data = X @ sorted_eigenvectors
ax2.scatter(transformed_data[:, 0], transformed_data[:, 1], alpha=0.6, c='lightcoral')
ax2.set_xlim(-4, 4)
ax2.set_ylim(-4, 4)
ax2.grid(True, alpha=0.3)
ax2.set_aspect('equal')
ax2.set_title('PCA 변환 후: PC1이 가장 큰 분산을 가짐')
ax2.set_xlabel('PC1')
ax2.set_ylabel('PC2')
print(f"\n첫 번째 주성분 (PC1): {pc1}") plt.tight_layout()
print(f"두 번째 주성분 (PC2): {pc2}") plt.show()
# 데이터를 주성분으로 투영 (차원 축소) print("PCA의 직관적 의미:")
transformed_data = X @ sorted_eigenvectors print(f"PC1 (첫 번째 주성분): {pc1}")
print(f"PC2 (두 번째 주성분): {pc2}")
print(f"PC1 분산: {sorted_eigenvalues[0]:.2f} ({sorted_eigenvalues[0]/sum(sorted_eigenvalues)*100:.1f}%)")
print(f"PC2 분산: {sorted_eigenvalues[1]:.2f} ({sorted_eigenvalues[1]/sum(sorted_eigenvalues)*100:.1f}%)")
```
# 시각화 ---
<br>
## 4. 실제 활용: 이미지 처리와 데이터 분석 🖼️
### 4-1. 이미지 필터링으로 이해하는 행렬
```python
# 간단한 이미지 필터링 예제
from PIL import Image
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6)) # 간단한 이미지 생성 (체크무늬 패턴)
def create_checkerboard(size=50):
"""체크무늬 이미지 생성"""
img = np.zeros((size, size))
for i in range(size):
for j in range(size):
if (i // 10 + j // 10) % 2 == 0:
img[i, j] = 255
return img
# 필터 행렬들 (커널)
filters = {
'블러': np.array([[1/9, 1/9, 1/9], [1/9, 1/9, 1/9], [1/9, 1/9, 1/9]]),
'엣지 검출': np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]]),
'수평 엣지': np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]]),
'수직 엣지': np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]])
}
# 이미지 생성
original = create_checkerboard()
# 필터 적용 함수
def apply_filter(image, filter_kernel):
"""이미지에 필터 적용"""
from scipy import ndimage
return ndimage.convolve(image, filter_kernel, mode='constant', cval=0)
# 원본 데이터 # 시각화
plt.subplot(1, 2, 1) fig, axes = plt.subplots(2, 3, figsize=(15, 10))
plt.scatter(X[:, 0], X[:, 1], alpha=0.7) axes = axes.flatten()
# 주성분 벡터 그리기
origin = [0, 0] # 원본 이미지
plt.quiver(*origin, *pc1*np.sqrt(sorted_eigenvalues[0]), color='r', scale=5, label=f'PC1 (Variance: {sorted_eigenvalues[0]:.2f})') axes[0].imshow(original, cmap='gray')
plt.quiver(*origin, *pc2*np.sqrt(sorted_eigenvalues[1]), color='g', scale=5, label=f'PC2 (Variance: {sorted_eigenvalues[1]:.2f})') axes[0].set_title('원본 이미지')
plt.title('Original Data with Principal Components') axes[0].axis('off')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2') # 필터 적용 결과
plt.axis('equal') for i, (filter_name, filter_kernel) in enumerate(filters.items()):
plt.legend() filtered = apply_filter(original, filter_kernel)
axes[i+1].imshow(filtered, cmap='gray')
# 변환된 데이터 axes[i+1].set_title(f'{filter_name} 필터')
plt.subplot(1, 2, 2) axes[i+1].axis('off')
plt.scatter(transformed_data[:, 0], transformed_data[:, 1], alpha=0.7)
plt.title('Data Transformed by PCA')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.axis('equal')
plt.tight_layout() plt.tight_layout()
plt.show() plt.show()
print("이미지 필터링 = 행렬 곱셈의 실제 활용")
print("각 픽셀 주변의 값들을 행렬(커널)로 가중 평균하여 새로운 픽셀 값 계산")
```
### 4-2. 추천 시스템으로 이해하는 행렬 분해
```python
# 간단한 추천 시스템 시뮬레이션
np.random.seed(42)
# 사용자-아이템 평점 행렬 (5명 사용자, 4개 아이템)
# 0은 평점이 없는 경우
ratings = np.array([
[5, 3, 0, 1], # 사용자 1
[4, 0, 0, 1], # 사용자 2
[1, 1, 0, 5], # 사용자 3
[1, 0, 0, 4], # 사용자 4
[0, 1, 5, 4] # 사용자 5
])
print("사용자-아이템 평점 행렬:")
print(ratings)
print("\n행렬 분해의 직관적 의미:")
print("- 사용자 행렬: 각 사용자의 취향 (예: 액션 선호도, 로맨스 선호도)")
print("- 아이템 행렬: 각 아이템의 특성 (예: 액션 요소, 로맨스 요소)")
print("- 두 행렬의 곱으로 누락된 평점을 예측")
# 간단한 행렬 분해 (SVD의 직관적 이해)
# 실제로는 더 복잡한 알고리즘 사용
U = np.random.rand(5, 2) # 사용자 특성 행렬
V = np.random.rand(2, 4) # 아이템 특성 행렬
# 예측 평점
predicted = U @ V
print(f"\n예측된 평점 행렬:\n{predicted.round(2)}")
# 원본과 예측 비교
print(f"\n원본 평점 (0은 누락):\n{ratings}")
print(f"예측 평점:\n{predicted.round(2)}")
``` ```
--- ---
<br> <br>
> ## 6. 텐서(Tensor): 다차원 데이터의 표현 ## 5. 실습: 직접 만들어보는 선형대수 🛠️
>
> > **🎯 3일차 목표:** 텐서의 개념을 이해하고 NumPy로 다차원 데이터를 다루는 방법을 학습합니다. ### 5-1. 나만의 벡터 클래스 만들기
>
> ### 6-1. 텐서란 무엇인가? ```python
> class MyVector:
> 텐서는 벡터와 행렬을 일반화한 다차원 배열입니다. 딥러닝에서 텐서는 데이터를 표현하는 기본 단위로 사용됩니다. """직관적인 벡터 클래스"""
>
> - **0차원 텐서 (스칼라)**: 숫자 하나 (e.g., `5`) def __init__(self, x, y):
> - **1차원 텐서 (벡터)**: 숫자의 배열 (e.g., `[1, 2, 3]`) self.x = x
> - **2차원 텐서 (행렬)**: 숫자의 2차원 배열 (e.g., `[[1, 2], [3, 4]]`) self.y = y
> - **3차원 텐서**: 숫자의 3차원 배열 (e.g., 컬러 이미지 (높이, 너비, 채널))
> - **4차원 텐서**: 숫자의 4차원 배열 (e.g., 이미지 데이터의 미니배치 (배치 크기, 높이, 너비, 채널)) def __add__(self, other):
> """벡터 덧셈: 이동 경로 합치기"""
> ### 6-2. NumPy로 텐서 다루기 return MyVector(self.x + other.x, self.y + other.y)
>
> ```python def __mul__(self, scalar):
> # 3차원 텐서 생성 (2x3x4) """스칼라 곱: 크기 조절"""
> T = np.arange(24).reshape(2, 3, 4) return MyVector(self.x * scalar, self.y * scalar)
>
> print(f"3차원 텐서 T:\n{T}") def magnitude(self):
> print(f"\n텐서의 모양(shape): {T.shape}") """벡터의 크기"""
> print(f"텐서의 차원 수(ndim): {T.ndim}") return np.sqrt(self.x**2 + self.y**2)
> print(f"텐서의 총 요소 수(size): {T.size}")
> def dot(self, other):
> # 텐서 인덱싱 """내적: 서로 얼마나 같은 방향인지"""
> print(f"\nT[1, 0, 2] = {T[1, 0, 2]}") return self.x * other.x + self.y * other.y
>
> # 텐서 슬라이싱 def plot(self, ax, color='blue', label=''):
> print(f"\nT[:, 0, :] =\n{T[:, 0, :]}") # 각 행렬의 첫 번째 행 """벡터를 화살표로 그리기"""
> ``` ax.quiver(0, 0, self.x, self.y, color=color, scale=10, label=label)
>
> ### 6-3. 텐서 연산 def __str__(self):
> return f"Vector({self.x}, {self.y})"
> 텐서 연산은 기본적으로 요소별(element-wise)로 수행됩니다.
> # 사용 예제
> ```python v1 = MyVector(3, 2)
> T1 = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) v2 = MyVector(1, 4)
> T2 = np.array([[[9, 10], [11, 12]], [[13, 14], [15, 16]]])
> print(f"v1 = {v1}")
> # 텐서 덧셈 print(f"v2 = {v2}")
> T_sum = T1 + T2 print(f"v1 + v2 = {v1 + v2}")
> print(f"텐서 덧셈:\n{T_sum}") print(f"v1의 크기 = {v1.magnitude():.2f}")
> print(f"v1 · v2 = {v1.dot(v2)}")
> # 텐서 곱셈 (요소별)
> T_prod = T1 * T2 # 시각화
> print(f"\n텐서 곱셈 (요소별):\n{T_prod}") fig, ax = plt.subplots(figsize=(8, 8))
> ax.set_xlim(-5, 5)
> # 텐서 내적 (Tensor Dot Product) ax.set_ylim(-5, 5)
> # np.tensordot은 축을 지정하여 내적을 수행 ax.grid(True, alpha=0.3)
> # 예: (2, 2, 2) 텐서와 (2, 2, 2) 텐서의 마지막 두 축에 대해 내적 ax.set_aspect('equal')
> T_dot = np.tensordot(T1, T2, axes=([1, 2], [1, 2]))
> print(f"\n텐서 내적 (axes=([1,2],[1,2])):\n{T_dot}") v1.plot(ax, 'red', 'v1')
> ``` v2.plot(ax, 'blue', 'v2')
> (v1 + v2).plot(ax, 'green', 'v1 + v2')
> > [!NOTE] 딥러닝에서의 텐서 연산
> > 딥러닝 프레임워크(TensorFlow, PyTorch)에서는 복잡한 텐서 연산을 효율적으로 처리하기 위한 다양한 함수를 제공합니다. 특히 GPU를 활용한 병렬 처리를 통해 대규모 텐서 연산을 가속화합니다. ax.legend()
ax.set_title('나만의 벡터 클래스')
plt.show()
```
### 5-2. 간단한 신경망 가중치 시각화
```python
# 간단한 신경망 가중치를 행렬로 이해하기
def create_simple_network():
"""간단한 신경망 가중치 행렬 생성"""
# 입력층(2) -> 은닉층(3) -> 출력층(1)
W1 = np.random.randn(2, 3) * 0.1 # 입력층 -> 은닉층 가중치
W2 = np.random.randn(3, 1) * 0.1 # 은닉층 -> 출력층 가중치
return W1, W2
def visualize_weights(W1, W2):
"""신경망 가중치 시각화"""
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# W1 시각화 (2x3 행렬)
im1 = ax1.imshow(W1, cmap='RdBu', aspect='auto')
ax1.set_title('입력층 → 은닉층 가중치 (W1)')
ax1.set_xlabel('은닉층 뉴런')
ax1.set_ylabel('입력층 뉴런')
plt.colorbar(im1, ax=ax1)
# W2 시각화 (3x1 행렬)
im2 = ax2.imshow(W2, cmap='RdBu', aspect='auto')
ax2.set_title('은닉층 → 출력층 가중치 (W2)')
ax2.set_xlabel('출력층 뉴런')
ax2.set_ylabel('은닉층 뉴런')
plt.colorbar(im2, ax=ax2)
plt.tight_layout()
plt.show()
print("신경망 가중치 = 행렬의 실제 활용")
print(f"W1 모양: {W1.shape} (입력 2개 → 은닉층 3개)")
print(f"W2 모양: {W2.shape} (은닉층 3개 → 출력 1개)")
# 신경망 생성 및 시각화
W1, W2 = create_simple_network()
visualize_weights(W1, W2)
```
--- ---
<br> <br>
> ## 7. 마무리 및 다음 파트 예고 ## 6. 마무리: 선형대수의 핵심 메시지 🎯
>
> 이번 파트에서는 NumPy를 사용하여 선형대수학의 핵심 개념들을 살펴보았습니다. 벡터, 행렬, 텐서의 기본 연산부터 시작하여 고유값, 고유벡터, SVD와 같은 고급 주제까지 다루었습니다. 이러한 개념들은 머신러닝과 딥러닝 모델을 이해하고 구현하는 데 필수적인 수학적 도구입니다.
>
> 다음 파트에서는 드디어 본격적인 **머신러닝의 세계**로 들어갑니다. 선형 회귀, 로지스틱 회귀와 같은 기본적인 머신러닝 모델을 직접 구현하고 평가하며, 모델 학습의 전체 과정을 경험하게 될 것입니다. 이번 파트에서 배운 선형대수학 지식이 다음 파트의 모델들을 이해하는 데 어떻게 사용되는지 직접 확인할 수 있을 것입니다.
>
> > [!TIP]
> > **다음으로 무엇을 해야 할까요?**
> > - `source_code/part_5.5_linear_algebra_with_numpy.py` 파일의 코드를 직접 실행하고, 값을 바꿔보며 결과가 어떻게 변하는지 확인해보세요.
> > - 특히 SVD를 이용한 이미지 압축이나 PCA를 이용한 차원 축소 예제를 직접 다른 데이터에 적용해보세요.
> > - 궁금한 점이 있다면 언제든지 질문해주세요!
<br> ### 6-1. 우리가 배운 것들
1. **벡터**: 방향과 크기를 가진 화살표 → 데이터의 기본 단위
2. **행렬**: 데이터 변형기 → 모델의 핵심 구성 요소
3. **고유벡터**: 변하지 않는 특별한 방향 → 데이터의 핵심 특성
4. **행렬 분해**: 복잡한 데이터를 단순한 요소로 분해 → 차원 축소, 추천 시스템
### 6-2. 머신러닝에서의 활용
- **데이터 표현**: 벡터, 행렬, 텐서로 데이터 표현
- **모델 학습**: 가중치 행렬의 최적화
- **특성 추출**: PCA, SVD로 중요한 특성 찾기
- **차원 축소**: 고차원 데이터를 저차원으로 압축
### 6-3. 다음 단계
이제 미적분을 배워서 **"어떻게 최적화할 것인가?"**를 알아보겠습니다!
> **💡 핵심 메시지**:
> 선형대수는 머신러닝의 "언어"입니다.
> 복잡한 수식보다는 **직관적 이해**와 **실제 활용**에 집중하세요!
--- ---
......
# Part 5.6: 머신러닝을 위한 미적분 # Part 5.6: 머신러닝/딥러닝을 위한 미적분 - 직관적 이해 중심
딥러닝과 머신러닝의 학습 원리를 이해하기 위한 핵심 수학 개념인 미적분을 다룹니다. ## 🎯 학습 목표
## 학습 목표 이번 파트에서는 **복잡한 수식보다는 시각화와 비유를 통해** 미적분의 핵심 개념을 직관적으로 이해합니다.
- 미분의 개념과 기하학적 의미를 이해합니다. ### 핵심 접근법
- 머신러닝 학습에서 미분이 왜 중요한지, 특히 비용 함수와 경사 하강법의 관계를 이해합니다. - **미분** = "순간 변화율", "기울기"
- 편미분과 그래디언트의 개념을 파악합니다. - **기울기 하강법** = "산에서 내려오는 사람"
- Python의 `SymPy`, `NumPy` 라이브러리를 사용하여 미분 계수를 계산하는 방법을 실습합니다. - **편미분** = "여러 변수 중 하나만 바꾸기"
- **연쇄법칙** = "파이프라인을 통해 변화 전파"
## 내용 ## 📚 학습 내용
1. **[머신러닝/딥러닝을 위한 미적분](./part_5.6_calculus_for_ml.md)** ### 1. 미분: 순간 변화율과 기울기 📈
- 미분의 개념과 역할 - 미분의 직관적 이해 (기울기 시각화)
- 경사 하강법과 그래디언트 - 여러 점에서의 미분값 비교
- 연쇄 법칙과 역전파 - 수치 미분 vs 해석적 미분
- Python을 이용한 미분 실습 (기호적, 수치적)
- 적분의 기본 개념
본 파트를 통해 모델이 어떻게 오차로부터 학습하고 파라미터를 최적화하는지에 대한 수학적 원리를 이해할 수 있습니다. ### 2. 기울기 하강법: 산에서 내려오는 사람 🏔️
\ No newline at end of file - 기울기 하강법의 시각화
- 학습률의 중요성
- 다양한 최적화 알고리즘 비교
### 3. 편미분: 여러 변수 중 하나만 바꾸기 🔧
- 편미분의 3D 시각화
- 그래디언트 벡터의 의미
- 다변수 함수의 최적화
### 4. 연쇄법칙: 파이프라인을 통해 변화 전파 🔗
- 합성 함수의 미분
- 신경망의 역전파 이해
- 딥러닝의 핵심 원리
### 5. 실제 활용 예제
- 선형 회귀의 경사 하강법
- 간단한 신경망 구현
- 최적화 알고리즘 비교
## 🛠️ 실습 도구
- **Python + NumPy**: 실제 계산과 구현
- **Matplotlib**: 시각화로 직관적 이해
- **Jupyter Notebook**: 대화형 학습
- **실제 예제**: 선형 회귀, 신경망 등
## 📖 학습 자료
### 강의 자료
- `part_5.6_calculus_for_ml.md`: 시각화 중심의 이론 설명
### 실습 코드
- `source_code/linear_regression_gradient_descent.py`: 11개의 시각화 예제
## 🎯 핵심 메시지
> **"왜 이걸 배워야 하는가?"** 에 집중합니다.
>
> 미적분은 머신러닝의 "학습 엔진"입니다.
> 복잡한 수식보다는 **직관적 이해**와 **실제 구현**에 집중하세요!
## 🚀 다음 단계
이제 선형대수와 미적분을 모두 배웠으니, **실제 머신러닝 모델**을 만들어보겠습니다!
\ No newline at end of file
...@@ -3,176 +3,787 @@ ...@@ -3,176 +3,787 @@
--- ---
# Part 5.6: 머신러닝/딥러닝을 위한 미적분 # Part 5.6: 머신러닝/딥러닝을 위한 미적분 - 직관적 이해 중심
이전 파트에서 선형대수가 데이터와 모델을 표현하는 '언어'였다면, 미적분은 모델을 '학습'시키는 핵심 도구입니다. 특히 딥러닝에서 모델의 파라미터를 최적화하는 과정은 전적으로 미분의 원리에 기반합니다. **⬅️ 이전 시간: [Part 5.5: NumPy로 배우는 선형대수학](../05.5_linear_algebra_with_numpy/part_5.5_linear_algebra_with_numpy.md)**
**➡️ 다음 시간: [Part 6: 머신러닝 모델링과 평가](../06_machine_learning/part_6_machine_learning.md)**
## 1. 미분 (Differentiation)
### 1.1. 미분의 개념
미분(differentiation)은 아주 짧은 순간의 **변화율(rate of change)** 또는 함수의 특정 지점에서의 **접선의 기울기(slope of the tangent line)**를 구하는 과정입니다. ---
함수 $y = f(x)$가 있을 때, $x$의 값이 아주 조금($\Delta x$) 변할 때 $y$의 값($\Delta y$)이 얼마나 변하는지를 나타내는 비율 $\frac{\Delta y}{\Delta x}$을 생각할 수 있습니다. 여기서 $\Delta x$를 거의 0에 가깝게 만들었을 때의 극한값이 바로 **도함수(derivative)**이며, $f'(x)$ 또는 $\frac{dy}{dx}$로 표기합니다. <br>
$f'(x) = \lim_{\Delta x \to 0} \frac{f(x+\Delta x) - f(x)}{\Delta x}$ > ## 🎯 학습 목표 (Learning Objectives)
>
> 이번 파트가 끝나면, 여러분은 다음을 할 수 있게 됩니다.
>
> - **직관적으로 이해하기**: 복잡한 수식보다는 시각화와 비유를 통해 미적분의 핵심 개념을 직관적으로 이해할 수 있습니다.
> - **실습 중심 학습**: Python을 사용하여 미분과 기울기 하강법을 직접 구현하고 시각화할 수 있습니다.
> - **실제 활용**: 머신러닝에서 미적분이 어떻게 활용되는지 구체적인 예제를 통해 이해할 수 있습니다.
예를 들어, 함수 $f(x) = x^2$ 의 도함수는 $f'(x) = 2x$ 입니다. 이는 $x=1$에서의 접선의 기울기는 $2$, $x=2$에서의 접선의 기울기는 $4$라는 의미입니다. <br>
### 1.2. 머신러닝에서의 미분의 역할: 비용 함수 최소화 > ## 🔑 핵심 접근법 (Key Approach)
>
> **"왜 이걸 배워야 하는가?"** 에 집중합니다.
> - 미분 = "순간 변화율", "기울기"
> - 기울기 하강법 = "산에서 내려오는 사람"
> - 편미분 = "여러 변수 중 하나만 바꾸기"
> - 연쇄법칙 = "파이프라인을 통해 변화 전파"
머신러닝, 특히 딥러닝 모델의 학습 목표는 **비용 함수(Cost Function) 또는 손실 함수(Loss Function)의 값을 최소화**하는 것입니다. 비용 함수는 모델의 예측값과 실제 정답값의 차이(오차)를 나타내는 함수입니다. <br>
가장 널리 사용되는 최적화 알고리즘인 **경사 하강법(Gradient Descent)**은 바로 이 미분 개념을 사용합니다. > ## 🛠️ 사용 도구 (Tools)
> - **Python + NumPy**: 실제 계산과 구현
> - **Matplotlib**: 시각화로 직관적 이해
> - **Jupyter Notebook**: 대화형 학습
> - **실제 예제**: 선형 회귀, 신경망 등
1. 비용 함수를 모델의 파라미터(가중치 $W$, 편향 $b$)에 대해 편미분하여 **그래디언트(Gradient)**를 계산합니다. 그래디언트는 비용 함수가 가장 가파르게 증가하는 방향을 나타내는 벡터입니다. ---
2. 그래디언트의 **반대 방향**으로 파라미터를 조금씩 이동시킵니다. (가장 빠르게 감소하는 방향이므로)
3. 이 과정을 반복하면 비용 함수의 최솟값에 도달하게 되며, 이는 모델의 성능이 최적화되었음을 의미합니다.
> **그래디언트(Gradient)**: 다변수 함수에서 각 변수에 대한 편미분 값을 원소로 가지는 벡터. $\nabla f = (\frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, ..., \frac{\partial f}{\partial x_n})$ <br>
### 1.3. 기본 미분 공식 ## 1. 미분: 순간 변화율과 기울기 📈
자주 사용되는 미분 공식은 다음과 같습니다. ### 1-1. 미분이란 무엇인가? (직관적 이해)
* **상수**: $f(x) = c \implies f'(x) = 0$ > **💡 비유**: 미분은 "순간 변화율"입니다!
* **거듭제곱**: $f(x) = x^n \implies f'(x) = nx^{n-1}$ > - **기울기**: 곡선의 특정 점에서 접선의 기울기
* **지수 함수**: > - **변화율**: 아주 짧은 순간에 얼마나 빨리 변하는지
* $f(x) = e^x \implies f'(x) = e^x$ > - **방향**: 어느 방향으로 변하고 있는지
* $f(x) = a^x \implies f'(x) = a^x \ln(a)$
* **로그 함수**: $f(x) = \ln(x) \implies f'(x) = \frac{1}{x}$
* **시그모이드 함수**: $\sigma(x) = \frac{1}{1+e^{-x}} \implies \sigma'(x) = \sigma(x)(1-\sigma(x))$ (딥러닝에서 중요)
### 1.4. 연쇄 법칙 (Chain Rule) ```python
import numpy as np
import matplotlib.pyplot as plt
# 미분의 직관적 이해: 기울기 시각화
def plot_function_with_tangent(x, y, tangent_x, tangent_y, slope, title):
"""함수와 접선을 시각화"""
plt.figure(figsize=(10, 6))
# 함수 그리기
plt.plot(x, y, 'b-', linewidth=2, label='함수 f(x)')
# 접선 그리기
plt.plot(tangent_x, tangent_y, 'r--', linewidth=2, label=f'접선 (기울기: {slope:.2f})')
# 접점 표시
plt.scatter(tangent_x[len(tangent_x)//2], tangent_y[len(tangent_y)//2],
color='red', s=100, zorder=5, label='접점')
plt.grid(True, alpha=0.3)
plt.xlabel('x')
plt.ylabel('y')
plt.title(title)
plt.legend()
plt.show()
# 예제: f(x) = x² 함수의 미분
x = np.linspace(-3, 3, 100)
y = x**2
# x = 1에서의 접선
x0 = 1
y0 = x0**2
slope = 2 * x0 # f'(x) = 2x
# 접선 그리기
tangent_x = np.linspace(x0 - 1, x0 + 1, 50)
tangent_y = slope * (tangent_x - x0) + y0
plot_function_with_tangent(x, y, tangent_x, tangent_y, slope,
f"f(x) = x²의 미분: x = {x0}에서 기울기 = {slope}")
print(f"f(x) = x²")
print(f"f'({x0}) = {slope}")
print(f"x = {x0}에서 함수가 {slope}만큼 빠르게 증가하고 있습니다!")
```
연쇄 법칙은 **합성 함수(composite function)**를 미분하는 방법으로, 딥러닝의 역전파(Backpropagation) 알고리즘의 핵심 원리입니다. ### 1-2. 미분의 기하학적 의미
두 함수 $y=f(u)$와 $u=g(x)$가 합성된 함수 $y = f(g(x))$의 도함수는 각 함수의 도함수의 곱으로 나타낼 수 있습니다. ```python
# 여러 점에서의 기울기 시각화
def visualize_derivatives():
"""여러 점에서의 미분값을 시각화"""
x = np.linspace(-3, 3, 100)
y = x**2
# 여러 점에서의 접선
points = [-2, -1, 0, 1, 2]
plt.figure(figsize=(12, 8))
plt.plot(x, y, 'b-', linewidth=3, label='f(x) = x²')
colors = ['red', 'orange', 'green', 'blue', 'purple']
for i, x0 in enumerate(points):
y0 = x0**2
slope = 2 * x0
# 접선 그리기
tangent_x = np.linspace(x0 - 0.5, x0 + 0.5, 20)
tangent_y = slope * (tangent_x - x0) + y0
plt.plot(tangent_x, tangent_y, '--', color=colors[i], linewidth=2,
label=f'x = {x0}, 기울기 = {slope}')
plt.scatter(x0, y0, color=colors[i], s=100, zorder=5)
plt.grid(True, alpha=0.3)
plt.xlabel('x')
plt.ylabel('y')
plt.title('f(x) = x²의 여러 점에서의 기울기')
plt.legend()
plt.show()
print("미분의 직관적 의미:")
print("- 음수 기울기: 함수가 감소하고 있음")
print("- 0 기울기: 함수가 변하지 않음 (극점)")
print("- 양수 기울기: 함수가 증가하고 있음")
visualize_derivatives()
```
$\frac{dy}{dx} = \frac{dy}{du} \cdot \frac{du}{dx}$ ### 1-3. 수치 미분 구현하기
딥러닝 모델은 입력층부터 출력층까지 수많은 함수(레이어)가 연결된 거대한 합성 함수와 같습니다. 출력층에서 계산된 오차를 입력층 방향으로 다시 전파하며 각 레이어의 가중치에 대한 그래디언트를 계산할 때 이 연쇄 법칙이 사용됩니다. ```python
# 수치 미분의 직관적 이해
def numerical_derivative(func, x, h=1e-4):
"""수치 미분: 미분의 정의를 이용한 근사"""
return (func(x + h) - func(x - h)) / (2 * h)
## 2. Python을 이용한 미분 def compare_numerical_analytical():
"""수치 미분과 해석적 미분 비교"""
# 테스트 함수들
functions = {
'f(x) = x²': (lambda x: x**2, lambda x: 2*x),
'f(x) = x³': (lambda x: x**3, lambda x: 3*x**2),
'f(x) = sin(x)': (lambda x: np.sin(x), lambda x: np.cos(x))
}
x_test = 1.0
print("수치 미분 vs 해석적 미분 비교:")
print("-" * 50)
for name, (func, derivative) in functions.items():
numerical = numerical_derivative(func, x_test)
analytical = derivative(x_test)
print(f"{name}:")
print(f" 수치 미분: f'({x_test}) ≈ {numerical:.6f}")
print(f" 해석적 미분: f'({x_test}) = {analytical:.6f}")
print(f" 오차: {abs(numerical - analytical):.2e}")
print()
compare_numerical_analytical()
```
### 2.1. 기호적 미분 (Symbolic Differentiation) with `SymPy` ---
`SymPy`는 변수를 기호로 다루어 수학 공식을 그대로 사용하여 미분, 적분 등을 수행할 수 있는 라이브러리입니다. <br>
```python ## 2. 기울기 하강법: 산에서 내려오는 사람 🏔️
import sympy as sp
# 변수를 기호(symbol)로 선언 ### 2-1. 기울기 하강법의 직관적 이해
x = sp.Symbol('x')
# 함수 정의 > **💡 비유**: 기울기 하강법은 "산에서 내려오는 사람"입니다!
f = x**3 + 2*x**2 + x + 5 > - **현재 위치**: 함수의 특정 점
> - **기울기**: 어느 방향이 내리막인지
> - **한 걸음**: 학습률만큼 이동
> - **목표**: 가장 낮은 지점(최솟값) 찾기
# f를 x에 대해 미분 ```python
df_dx = sp.diff(f, x) # 기울기 하강법 시각화
def gradient_descent_visualization():
"""기울기 하강법의 과정을 시각화"""
# 목표 함수: f(x) = x² + 2
def f(x):
return x**2 + 2
def df(x):
return 2*x
# 기울기 하강법
x = 3.0 # 시작점
learning_rate = 0.1
steps = 20
x_history = [x]
y_history = [f(x)]
for i in range(steps):
gradient = df(x)
x = x - learning_rate * gradient
x_history.append(x)
y_history.append(f(x))
# 시각화
x_plot = np.linspace(-4, 4, 100)
y_plot = f(x_plot)
plt.figure(figsize=(12, 8))
# 함수 그리기
plt.plot(x_plot, y_plot, 'b-', linewidth=2, label='f(x) = x² + 2')
# 기울기 하강 과정
plt.plot(x_history, y_history, 'ro-', linewidth=2, markersize=8, label='기울기 하강 경로')
# 화살표로 이동 방향 표시
for i in range(len(x_history) - 1):
dx = x_history[i+1] - x_history[i]
dy = y_history[i+1] - y_history[i]
plt.arrow(x_history[i], y_history[i], dx, dy,
head_width=0.05, head_length=0.1, fc='red', ec='red', alpha=0.7)
plt.grid(True, alpha=0.3)
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title('기울기 하강법: 산에서 내려오는 과정')
plt.legend()
plt.show()
print("기울기 하강법 과정:")
for i, (x_val, y_val) in enumerate(zip(x_history, y_history)):
print(f"단계 {i}: x = {x_val:.4f}, f(x) = {y_val:.4f}")
gradient_descent_visualization()
```
print(f"함수 f(x) = {f}") ### 2-2. 학습률의 중요성
print(f"도함수 f'(x) = {df_dx}")
# 특정 지점(x=2)에서의 미분 계수 계산 ```python
derivative_at_2 = df_dx.subs(x, 2) # 학습률에 따른 기울기 하강법 비교
print(f"f'(2) = {derivative_at_2}") def compare_learning_rates():
"""다양한 학습률로 기울기 하강법 비교"""
def f(x):
return x**2 + 2
def df(x):
return 2*x
learning_rates = [0.01, 0.1, 0.5, 1.0]
colors = ['blue', 'green', 'orange', 'red']
plt.figure(figsize=(15, 10))
for i, lr in enumerate(learning_rates):
# 기울기 하강법
x = 3.0
steps = 20
x_history = [x]
y_history = [f(x)]
for step in range(steps):
gradient = df(x)
x = x - lr * gradient
x_history.append(x)
y_history.append(f(x))
# 경로 그리기
plt.subplot(2, 2, i+1)
x_plot = np.linspace(-4, 4, 100)
y_plot = f(x_plot)
plt.plot(x_plot, y_plot, 'b-', linewidth=2, label='f(x) = x² + 2')
plt.plot(x_history, y_history, 'o-', color=colors[i], linewidth=2,
markersize=6, label=f'학습률 = {lr}')
plt.grid(True, alpha=0.3)
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title(f'학습률 = {lr}')
plt.legend()
plt.tight_layout()
plt.show()
print("학습률의 영향:")
print("- 너무 작음 (0.01): 천천히 수렴")
print("- 적당함 (0.1): 빠르게 수렴")
print("- 너무 큼 (0.5, 1.0): 진동하거나 발산")
compare_learning_rates()
``` ```
### 2.2. 수치 미분 (Numerical Differentiation) with `NumPy` ---
`NumPy`는 기호적 미분을 직접 지원하지 않지만, 미분의 정의를 이용하여 근사값을 계산하는 **수치 미분**을 구현할 수 있습니다. <br>
$f'(x) \approx \frac{f(x+h) - f(x-h)}{2h}$ (중앙 차분) ## 3. 편미분: 여러 변수 중 하나만 바꾸기 🔧
여기서 $h$는 아주 작은 값(예: `1e-4`)입니다. ### 3-1. 편미분의 직관적 이해
> **💡 비유**: 편미분은 "여러 변수 중 하나만 바꾸기"입니다!
> - **다변수 함수**: 여러 입력을 받는 함수
> - **편미분**: 한 변수만 조금 바꿨을 때 함수가 얼마나 변하는지
> - **그래디언트**: 모든 변수에 대한 편미분을 모은 벡터
```python ```python
import numpy as np # 편미분의 시각화
def visualize_partial_derivatives():
"""2변수 함수의 편미분 시각화"""
# 예제 함수: f(x, y) = x² + y²
def f(x, y):
return x**2 + y**2
def df_dx(x, y):
return 2*x
def df_dy(x, y):
return 2*y
# 3D 시각화
from mpl_toolkits.mplot3d import Axes3D
x = np.linspace(-3, 3, 50)
y = np.linspace(-3, 3, 50)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
fig = plt.figure(figsize=(15, 5))
# 3D 표면
ax1 = fig.add_subplot(131, projection='3d')
surf = ax1.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8)
ax1.set_title('f(x, y) = x² + y²')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_zlabel('f(x, y)')
# x에 대한 편미분 (y = 1에서의 단면)
ax2 = fig.add_subplot(132)
y_fixed = 1
x_slice = np.linspace(-3, 3, 100)
z_slice = f(x_slice, y_fixed)
ax2.plot(x_slice, z_slice, 'b-', linewidth=2, label=f'f(x, {y_fixed})')
# 접선 그리기 (x = 1에서)
x0 = 1
z0 = f(x0, y_fixed)
slope = df_dx(x0, y_fixed)
tangent_x = np.linspace(x0 - 1, x0 + 1, 20)
tangent_z = slope * (tangent_x - x0) + z0
ax2.plot(tangent_x, tangent_z, 'r--', linewidth=2, label=f'∂f/∂x = {slope}')
ax2.scatter(x0, z0, color='red', s=100)
ax2.grid(True, alpha=0.3)
ax2.set_xlabel('x')
ax2.set_ylabel('f(x, y)')
ax2.set_title(f'∂f/∂x (y = {y_fixed})')
ax2.legend()
# y에 대한 편미분 (x = 1에서의 단면)
ax3 = fig.add_subplot(133)
x_fixed = 1
y_slice = np.linspace(-3, 3, 100)
z_slice = f(x_fixed, y_slice)
ax3.plot(y_slice, z_slice, 'g-', linewidth=2, label=f'f({x_fixed}, y)')
# 접선 그리기 (y = 1에서)
y0 = 1
z0 = f(x_fixed, y0)
slope = df_dy(x_fixed, y0)
tangent_y = np.linspace(y0 - 1, y0 + 1, 20)
tangent_z = slope * (tangent_y - y0) + z0
ax3.plot(tangent_y, tangent_z, 'r--', linewidth=2, label=f'∂f/∂y = {slope}')
ax3.scatter(y0, z0, color='red', s=100)
ax3.grid(True, alpha=0.3)
ax3.set_xlabel('y')
ax3.set_ylabel('f(x, y)')
ax3.set_title(f'∂f/∂y (x = {x_fixed})')
ax3.legend()
plt.tight_layout()
plt.show()
print("편미분의 직관적 의미:")
print(f"∂f/∂x = {df_dx(1, 1)}: x를 조금 바꾸면 함수가 {df_dx(1, 1)}배만큼 변함")
print(f"∂f/∂y = {df_dy(1, 1)}: y를 조금 바꾸면 함수가 {df_dy(1, 1)}배만큼 변함")
visualize_partial_derivatives()
```
# 함수 정의 ### 3-2. 그래디언트 벡터 시각화
def f(x):
return x**3 + 2*x**2 + x + 5
# 수치 미분 함수 구현 ```python
def numerical_derivative(func, x, h=1e-4): # 그래디언트 벡터의 시각화
return (func(x + h) - func(x - h)) / (2 * h) def visualize_gradient():
"""그래디언트 벡터를 시각화"""
def f(x, y):
return x**2 + y**2
def gradient(x, y):
return np.array([2*x, 2*y])
# 등고선 그리기
x = np.linspace(-3, 3, 50)
y = np.linspace(-3, 3, 50)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
plt.figure(figsize=(10, 8))
# 등고선
contours = plt.contour(X, Y, Z, levels=10, colors='blue', alpha=0.6)
plt.clabel(contours, inline=True, fontsize=8)
# 그래디언트 벡터들
x_points = np.linspace(-2, 2, 8)
y_points = np.linspace(-2, 2, 8)
for i, x_val in enumerate(x_points):
for j, y_val in enumerate(y_points):
grad = gradient(x_val, y_val)
# 벡터 크기 정규화
grad_norm = grad / np.linalg.norm(grad) * 0.3
plt.quiver(x_val, y_val, grad_norm[0], grad_norm[1],
color='red', alpha=0.7, scale=20)
plt.xlabel('x')
plt.ylabel('y')
plt.title('그래디언트 벡터: 가장 가파르게 증가하는 방향')
plt.grid(True, alpha=0.3)
plt.axis('equal')
plt.show()
print("그래디언트의 의미:")
print("- 방향: 함수가 가장 가파르게 증가하는 방향")
print("- 크기: 그 방향으로의 증가율")
print("- 기울기 하강법: 그래디언트의 반대 방향으로 이동")
visualize_gradient()
```
# x=2 에서의 미분 계수 계산 ---
derivative_at_2 = numerical_derivative(f, 2)
print(f"f(x) = x^3 + 2x^2 + x + 5") <br>
print(f"x=2 에서의 수치적 미분 계수: {derivative_at_2}")
# SymPy 결과(21)와 매우 유사한 값을 얻을 수 있습니다.
```
수치 미분은 구현이 간단하지만 근사값이기 때문에 오차가 발생할 수 있습니다. 반면 기호적 미분은 정확한 결과를 제공합니다. 딥러닝 프레임워크(TensorFlow, PyTorch)는 **자동 미분(Automatic Differentiation)**이라는 효율적이고 정확한 방식을 사용하여 그래디언트를 계산합니다. 자동 미분은 기호적 미분과 수치 미분의 장점을 결합한 방식입니다. ## 4. 연쇄법칙: 파이프라인을 통해 변화 전파 🔗
## 3. 적분 (Integration) ### 4-1. 연쇄법칙의 직관적 이해
적분은 미분의 역연산으로, 크게 **부정적분(indefinite integral)****정적분(definite integral)**으로 나뉩니다. > **💡 비유**: 연쇄법칙은 "파이프라인을 통해 변화 전파"입니다!
> - **합성 함수**: 여러 함수가 연결된 구조
> - **변화 전파**: 출력의 변화가 입력으로 거꾸로 전파
> - **역전파**: 딥러닝의 핵심 원리
* **부정적분**: 도함수가 주어졌을 때 원래 함수를 찾는 과정. $F'(x) = f(x)$일 때, $\int f(x)dx = F(x) + C$ (C는 적분 상수) ```python
* **정적분**: 함수 그래프의 특정 구간 아래의 **넓이**를 구하는 과정. $\int_{a}^{b} f(x)dx$ # 연쇄법칙의 시각화
def chain_rule_visualization():
"""연쇄법칙의 직관적 이해"""
# 예제: f(x) = sin(x²)
def f(x):
return np.sin(x**2)
def df_dx(x):
# 연쇄법칙: d/dx[sin(x²)] = cos(x²) * 2x
return np.cos(x**2) * 2*x
x = np.linspace(-2, 2, 100)
y = f(x)
# 수치 미분으로 검증
def numerical_derivative(func, x, h=1e-4):
return (func(x + h) - func(x - h)) / (2 * h)
x_test = 1.0
analytical = df_dx(x_test)
numerical = numerical_derivative(f, x_test)
plt.figure(figsize=(12, 8))
# 함수와 미분
plt.subplot(2, 1, 1)
plt.plot(x, y, 'b-', linewidth=2, label='f(x) = sin(x²)')
plt.grid(True, alpha=0.3)
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title('합성 함수: f(x) = sin(x²)')
plt.legend()
# 미분
plt.subplot(2, 1, 2)
dy_dx = df_dx(x)
plt.plot(x, dy_dx, 'r-', linewidth=2, label="f'(x) = cos(x²) * 2x")
plt.grid(True, alpha=0.3)
plt.xlabel('x')
plt.ylabel("f'(x)")
plt.title('연쇄법칙을 이용한 미분')
plt.legend()
plt.tight_layout()
plt.show()
print("연쇄법칙의 직관적 의미:")
print(f"f(x) = sin(x²)에서 x = {x_test}일 때:")
print(f" 해석적 미분: f'({x_test}) = {analytical:.6f}")
print(f" 수치 미분: f'({x_test}) ≈ {numerical:.6f}")
print(f" 오차: {abs(analytical - numerical):.2e}")
print()
print("연쇄법칙: d/dx[sin(x²)] = cos(x²) * d/dx[x²] = cos(x²) * 2x")
chain_rule_visualization()
```
머신러닝에서 적분은 주로 확률 및 통계 분야에서 확률 분포의 기댓값이나 분산을 계산하는 등 이론적인 부분에서 활용됩니다. ### 4-2. 간단한 신경망의 역전파
## 4. 머신러닝/딥러닝 적용 실제 예시 ```python
# 간단한 신경망으로 역전파 이해하기
def simple_neural_network():
"""간단한 신경망의 역전파 시각화"""
# 간단한 신경망: 입력 -> 은닉층 -> 출력
# f(x) = w2 * sigmoid(w1 * x + b1) + b2
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(x):
return sigmoid(x) * (1 - sigmoid(x))
# 가중치 초기화
w1, b1 = 2.0, 1.0
w2, b2 = 3.0, 0.5
def forward(x):
"""순전파"""
z1 = w1 * x + b1
a1 = sigmoid(z1)
z2 = w2 * a1 + b2
return z2, a1, z1
def backward(x, target, learning_rate=0.1):
"""역전파 (연쇄법칙)"""
# 순전파
output, a1, z1 = forward(x)
# 오차
error = output - target
# 역전파 (연쇄법칙)
# ∂E/∂w2 = ∂E/∂output * ∂output/∂w2
dE_dw2 = error * a1
# ∂E/∂w1 = ∂E/∂output * ∂output/∂a1 * ∂a1/∂z1 * ∂z1/∂w1
dE_dw1 = error * w2 * sigmoid_derivative(z1) * x
# ∂E/∂b1 = ∂E/∂output * ∂output/∂a1 * ∂a1/∂z1 * ∂z1/∂b1
dE_db1 = error * w2 * sigmoid_derivative(z1)
# ∂E/∂b2 = ∂E/∂output * ∂output/∂b2
dE_db2 = error
return dE_dw1, dE_db1, dE_dw2, dE_db2
# 학습 과정 시각화
x_train = np.array([0.5, 1.0, 1.5])
y_train = np.array([2.0, 3.0, 4.0])
print("간단한 신경망 학습 과정:")
print("-" * 50)
for epoch in range(5):
total_error = 0
for x, target in zip(x_train, y_train):
output, _, _ = forward(x)
error = output - target
total_error += error**2
# 그래디언트 계산
dw1, db1, dw2, db2 = backward(x, target)
print(f"입력: {x}, 목표: {target}, 출력: {output:.4f}, 오차: {error:.4f}")
print(f" 그래디언트: dw1={dw1:.4f}, db1={db1:.4f}, dw2={dw2:.4f}, db2={db2:.4f}")
print(f"에포크 {epoch+1}: 평균 오차 = {total_error/len(x_train):.4f}")
print()
simple_neural_network()
```
### 4.1. 선형 회귀 모델에서의 경사 하강법 ---
가장 간단한 머신러닝 모델 중 하나인 선형 회귀를 통해 그래디언트가 어떻게 계산되고 사용되는지 살펴보겠습니다. <br>
1. **모델 정의**: $y_{pred} = Wx + b$ ## 5. 실제 활용: 선형 회귀와 경사 하강법 📊
- $x$: 입력 데이터
- $W, b$: 학습시킬 파라미터 (가중치와 편향)
- $y_{pred}$: 모델의 예측값
2. **비용 함수(Cost Function) 정의**: 평균 제곱 오차(Mean Squared Error, MSE) ### 5-1. 선형 회귀 구현하기
- $J(W, b) = \frac{1}{N} \sum_{i=1}^{N} (y_{true}^{(i)} - y_{pred}^{(i)})^2 = \frac{1}{N} \sum_{i=1}^{N} (y_{true}^{(i)} - (Wx^{(i)} + b))^2$
- $y_{true}$: 실제 정답값
3. **그래디언트 계산 (편미분)** ```python
- 비용 함수 $J$를 최소화하는 $W$와 $b$를 찾기 위해, 각각에 대해 편미분을 수행합니다. # 선형 회귀의 경사 하강법 구현
- **$W$에 대한 편미분 $\frac{\partial J}{\partial W}$**: def linear_regression_gradient_descent():
- 연쇄 법칙에 따라, $J = \frac{1}{N}\sum u^2$, $u = y_{true} - v$, $v = Wx + b$ 로 볼 수 있습니다. """선형 회귀를 경사 하강법으로 학습"""
- $\frac{\partial J}{\partial W} = \frac{\partial J}{\partial u} \frac{\partial u}{\partial v} \frac{\partial v}{\partial W}$ np.random.seed(42)
- $\frac{\partial J}{\partial u} = \frac{1}{N}\sum 2u$, $\frac{\partial u}{\partial v} = -1$, $\frac{\partial v}{\partial W} = x$
- 따라서, $\frac{\partial J}{\partial W} = \frac{1}{N} \sum 2(y_{true} - (Wx+b)) \cdot (-1) \cdot x = -\frac{2}{N} \sum x(y_{true} - y_{pred})$ # 데이터 생성
- **$b$에 대한 편미분 $\frac{\partial J}{\partial b}$**: X = np.random.rand(100, 1) * 10
- $\frac{\partial J}{\partial b} = \frac{\partial J}{\partial u} \frac{\partial u}{\partial v} \frac{\partial v}{\partial b}$ y = 3 * X + 2 + np.random.randn(100, 1) * 0.5
- $\frac{\partial v}{\partial b} = 1$ 이므로,
- $\frac{\partial J}{\partial b} = \frac{1}{N} \sum 2(y_{true} - (Wx+b)) \cdot (-1) \cdot 1 = -\frac{2}{N} \sum (y_{true} - y_{pred})$ # 모델: y = w * x + b
w, b = 0.0, 0.0
learning_rate = 0.01
epochs = 100
# 학습 과정 기록
w_history = [w]
b_history = [b]
loss_history = []
for epoch in range(epochs):
# 예측
y_pred = w * X + b
# 손실 함수 (MSE)
loss = np.mean((y_pred - y)**2)
loss_history.append(loss)
# 그래디언트 계산
dw = np.mean(2 * (y_pred - y) * X)
db = np.mean(2 * (y_pred - y))
# 파라미터 업데이트
w = w - learning_rate * dw
b = b - learning_rate * db
w_history.append(w)
b_history.append(b)
# 결과 시각화
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
# 데이터와 학습된 선
ax1.scatter(X, y, alpha=0.6, label='데이터')
X_line = np.linspace(0, 10, 100).reshape(-1, 1)
y_line = w * X_line + b
ax1.plot(X_line, y_line, 'r-', linewidth=2, label=f'학습된 선: y = {w:.2f}x + {b:.2f}')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_title('선형 회귀 결과')
ax1.legend()
ax1.grid(True, alpha=0.3)
# 손실 함수 변화
ax2.plot(loss_history, 'b-', linewidth=2)
ax2.set_xlabel('에포크')
ax2.set_ylabel('손실 (MSE)')
ax2.set_title('손실 함수 수렴')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("선형 회귀 학습 결과:")
print(f"학습된 가중치: w = {w:.4f}")
print(f"학습된 편향: b = {b:.4f}")
print(f"실제 값: w = 3.0, b = 2.0")
print(f"최종 손실: {loss_history[-1]:.6f}")
linear_regression_gradient_descent()
```
4. **파라미터 업데이트** ### 5-2. 다양한 최적화 알고리즘 비교
- 계산된 그래디언트를 이용해 경사 하강법을 수행합니다. $\alpha$는 학습률(learning rate)입니다.
- $W := W - \alpha \frac{\partial J}{\partial W}$
- $b := b - \alpha \frac{\partial J}{\partial b}$
이 과정을 반복하면서 모델은 점차 오차를 줄여나가는 방향으로 $W$와 $b$를 업데이트하게 됩니다. ```python
# 다양한 최적화 알고리즘 비교
def compare_optimizers():
"""다양한 최적화 알고리즘 비교"""
def objective_function(x):
return x**2 + 2*x + 1
def gradient(x):
return 2*x + 2
# 최적화 알고리즘들
def gradient_descent(x0, learning_rate=0.1, epochs=50):
x = x0
history = [x]
for _ in range(epochs):
x = x - learning_rate * gradient(x)
history.append(x)
return history
def momentum_gradient_descent(x0, learning_rate=0.1, momentum=0.9, epochs=50):
x = x0
velocity = 0
history = [x]
for _ in range(epochs):
velocity = momentum * velocity - learning_rate * gradient(x)
x = x + velocity
history.append(x)
return history
# 최적화 실행
x0 = 5.0
gd_history = gradient_descent(x0)
mgd_history = momentum_gradient_descent(x0)
# 시각화
x_plot = np.linspace(-1, 6, 100)
y_plot = objective_function(x_plot)
plt.figure(figsize=(12, 8))
# 목적 함수
plt.subplot(2, 1, 1)
plt.plot(x_plot, y_plot, 'b-', linewidth=2, label='목적 함수: f(x) = x² + 2x + 1')
plt.plot(gd_history, [objective_function(x) for x in gd_history], 'ro-',
markersize=6, label='기본 경사 하강법')
plt.plot(mgd_history, [objective_function(x) for x in mgd_history], 'go-',
markersize=6, label='모멘텀 경사 하강법')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title('최적화 과정 비교')
plt.legend()
plt.grid(True, alpha=0.3)
# 수렴 과정
plt.subplot(2, 1, 2)
plt.plot([objective_function(x) for x in gd_history], 'r-', linewidth=2, label='기본 경사 하강법')
plt.plot([objective_function(x) for x in mgd_history], 'g-', linewidth=2, label='모멘텀 경사 하강법')
plt.xlabel('에포크')
plt.ylabel('f(x)')
plt.title('손실 함수 수렴 비교')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("최적화 알고리즘 비교:")
print(f"기본 경사 하강법 최종값: {gd_history[-1]:.6f}")
print(f"모멘텀 경사 하강법 최종값: {mgd_history[-1]:.6f}")
print(f"실제 최솟값: -1.0")
compare_optimizers()
```
> **실제 구현 예시:** ---
> 이 과정은 Python과 `NumPy`를 사용하여 직접 구현해볼 수 있습니다. 다음 예제 코드를 통해 그래디언트 계산과 파라미터 업데이트가 실제로 어떻게 작동하는지 확인해보세요.
> - **코드:** [`linear_regression_gradient_descent.py`](./linear_regression_gradient_descent.py)
### 4.2. 간단한 신경망과 역전파 (Backpropagation) <br>
더 복잡한 딥러닝 모델의 학습 원리인 역전파는 연쇄 법칙의 결정판입니다. ## 6. 마무리: 미적분의 핵심 메시지 🎯
- **순전파(Forward Propagation)**: 입력 데이터가 신경망을 거쳐 최종 출력(예측)을 만들어내는 과정입니다. ### 6-1. 우리가 배운 것들
- **역전파(Backward Propagation)**: 순전파 결과 계산된 오차(비용)를 출력층에서부터 입력층 방향으로 거꾸로 전파시키면서, 각 가중치 파라미터가 오차에 얼마나 기여했는지를(그래디언트) 연쇄 법칙을 이용해 계산하는 과정입니다.
예를 들어, `입력 -> 레이어1 -> 레이어2 -> 출력` 구조의 신경망에서 레이어1의 가중치에 대한 그래디언트는 다음과 같이 연쇄적으로 계산됩니다. 1. **미분**: 순간 변화율과 기울기 → 함수의 변화 방향과 속도
2. **기울기 하강법**: 산에서 내려오는 사람 → 최적화의 핵심 알고리즘
3. **편미분**: 여러 변수 중 하나만 바꾸기 → 다변수 함수의 최적화
4. **연쇄법칙**: 파이프라인을 통해 변화 전파 → 딥러닝의 역전파
` (최종 오차 / 출력) -> (출력 / 레이어2 출력) -> (레이어2 출력 / 레이어1 출력) -> (레이어1 출력 / 레이어1 가중치) ` ### 6-2. 머신러닝에서의 활용
$\frac{\partial \text{Error}}{\partial W_1} = \frac{\partial \text{Error}}{\partial \text{Output}} \cdot \frac{\partial \text{Output}}{\partial \text{Layer2_out}} \cdot \frac{\partial \text{Layer2_out}}{\partial \text{Layer1_out}} \cdot \frac{\partial \text{Layer1_out}}{\partial W_1}$ - **모델 학습**: 손실 함수의 최소화
- **파라미터 업데이트**: 그래디언트 기반 최적화
- **신경망 훈련**: 역전파 알고리즘
- **특성 선택**: 그래디언트 기반 특성 중요도
이렇게 계산된 그래디언트($\frac{\partial \text{Error}}{\partial W_1}$)를 가지고 경사 하강법을 적용하여 레이어1의 가중치($W_1$)를 업데이트합니다. 이 과정을 모든 가중치에 대해 수행하는 것이 바로 딥러닝의 학습입니다. ### 6-3. 다음 단계
--- 이제 선형대수와 미적분을 모두 배웠으니, **실제 머신러닝 모델**을 만들어보겠습니다!
이처럼 미적분, 특히 미분과 그래디언트의 개념은 머신러닝 모델이 데이터를 통해 '학습'하고 똑똑해지는 과정의 수학적 기반을 이룹니다. > **💡 핵심 메시지**:
> 미적분은 머신러닝의 "학습 엔진"입니다.
> 복잡한 수식보다는 **직관적 이해**와 **실제 구현**에 집중하세요!
--- ---
......
# NumPy로 배우는 선형대수학 실습 코드 # 선형대수 시각화 실습 코드
이 디렉토리에는 NumPy를 활용하여 선형대수학 개념을 실습할 수 있는 코드가 포함되어 있습니다. ## 🎯 목적
이 디렉토리에는 **시각화와 비유를 중심으로** 선형대수의 핵심 개념을 직관적으로 이해할 수 있는 실습 코드가 포함되어 있습니다.
## 📁 파일 구조 ## 📁 파일 구조
- **`part_5.5_linear_algebra_with_numpy.py`**: 선형대수학 개념을 NumPy로 구현한 주요 실습 코드 ```
05.5_linear_algebra_with_numpy/
├── README.md # 이 파일
└── part_5.5_linear_algebra_with_numpy.py # 11개의 시각화 예제
```
## 🚀 실행 방법
### 1. 환경 설정
```bash
pip install numpy matplotlib scipy
```
### 2. 코드 실행
```bash
python part_5.5_linear_algebra_with_numpy.py
```
## 📚 실습 내용
### 1. 벡터 시각화 🏹
- **파일**: `visualize_vectors()`
- **목적**: 벡터를 화살표로 시각화하여 방향과 크기 이해
- **핵심**: 벡터 = "방향과 크기를 가진 화살표"
### 2. 벡터 연산 🚶‍♂️
- **파일**: `vector_operations()`
- **목적**: 벡터 덧셈을 "이동 경로 합치기"로 이해
- **핵심**: 벡터 덧셈의 기하학적 의미
### 3. 벡터 유사도 📊
- **파일**: `vector_similarity()`
- **목적**: 내적을 통해 벡터 간 유사도 측정
- **핵심**: 내적 = "서로 얼마나 같은 방향인지"
### 4. 행렬 변환 🎁
- **파일**: `matrix_transformations()`
- **목적**: 행렬을 "데이터 변형기"로 이해
- **핵심**: 회전, 크기 조절, 전단 변환 시각화
### 5. 행렬곱 🔄
- **파일**: `matrix_multiplication()`
- **목적**: 행렬곱을 "복합 변환"으로 이해
- **핵심**: 변환의 순서적 적용
### 6. 고유벡터 🧭
- **파일**: `eigenvectors_visualization()`
- **목적**: 고유벡터를 "변하지 않는 특별한 방향"으로 이해
- **핵심**: 변환 후에도 방향이 유지되는 벡터
### 7. PCA 📈
- **파일**: `pca_visualization()`
- **목적**: PCA를 "데이터의 핵심 방향 찾기"로 이해
- **핵심**: 차원 축소의 직관적 이해
### 8. 이미지 필터링 🖼️
- **파일**: `image_filtering()`
- **목적**: 행렬을 이미지 처리에 활용
- **핵심**: 커널 필터링의 원리
### 9. 추천 시스템 💡
- **파일**: `recommendation_system()`
- **목적**: 행렬 분해를 추천 시스템에 활용
- **핵심**: 사용자-아이템 행렬의 분해
### 10. 벡터 클래스 🛠️
- **파일**: `my_vector_class()`
- **목적**: 나만의 벡터 클래스 구현
- **핵심**: 객체지향 프로그래밍과 벡터
### 11. 신경망 가중치 🧠
- **파일**: `neural_network_weights()`
- **목적**: 신경망 가중치를 행렬로 시각화
- **핵심**: 딥러닝에서의 행렬 활용
## 🎯 학습 팁
## 🚀 실습 방법 ### 시각화 중심 학습
- 각 예제를 실행한 후 그래프를 자세히 관찰하세요
- 파라미터를 바꿔보며 결과가 어떻게 변하는지 확인하세요
- 코드의 주석을 읽고 각 단계의 의미를 이해하세요
1. 먼저 필요한 패키지가 설치되어 있는지 확인합니다: ### 실습 중심 접근
```bash - 코드를 직접 수정해보세요
pip install numpy matplotlib - 새로운 예제를 추가해보세요
``` - 다른 데이터셋에 적용해보세요
2. 코드를 실행합니다: ## 🔧 커스터마이징
```bash
python part_5.5_linear_algebra_with_numpy.py
```
3. 특정 섹션만 실행하려면 코드 내 주석을 참고하여 해당 부분을 활성화하거나 비활성화합니다. ### 파라미터 조정
```python
# 벡터 크기 조정
vectors = {
'v1': np.array([5, 3]), # 더 큰 벡터
'v2': np.array([-3, 2]), # 다른 방향
}
## 📋 주요 실습 내용 # 학습률 조정
learning_rate = 0.05 # 더 작은 학습률
```
- **벡터 연산**: 벡터의 생성, 크기 계산, 덧셈, 뺄셈, 스칼라 곱, 내적, 외적 ### 새로운 예제 추가
- **행렬 연산**: 행렬의 생성, 덧셈, 뺄셈, 곱셈, 전치, 행렬식, 역행렬 ```python
- **고급 연산**: 고유값, 고유벡터, 특이값 분해(SVD) def my_custom_visualization():
- **시각화**: 벡터, 행렬, 선형 변환의 기하학적 표현 """나만의 시각화 함수"""
- **머신러닝 응용**: 주성분 분석(PCA), 선형 회귀의 구현 # 여기에 코드 작성
pass
```
## 🔍 학습 목표 ## 🎉 핵심 메시지
이 실습 코드를 통해 다음을 배우게 됩니다: > **"왜 이걸 배워야 하는가?"** 에 집중합니다.
- NumPy의 기본 사용법과 선형대수학적 연산 방법 >
- 추상적인 수학 개념을 코드로 구현하는 능력 > 선형대수는 머신러닝의 "언어"입니다.
- 선형대수학이 머신러닝과 딥러닝에 어떻게 적용되는지 이해 > 복잡한 수식보다는 **직관적 이해**와 **실제 활용**에 집중하세요!
--- ## 📖 추가 자료
[↩️ 전체 코드 목록으로 돌아가기](../README.md) - [3Blue1Brown - Essence of Linear Algebra](https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab)
\ No newline at end of file - [NumPy 공식 문서](https://numpy.org/doc/)
- [Matplotlib 튜토리얼](https://matplotlib.org/stable/tutorials/index.html)
\ No newline at end of file
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from matplotlib.image import imread from matplotlib.image import imread
from mpl_toolkits.mplot3d import Axes3D
def section_4_1():
print("### 4-1. 벡터(Vector) ###") def visualize_vectors():
# 벡터 생성 """1. 벡터를 화살표로 시각화"""
v1 = np.array([1, 2, 3]) print("=== 1. 벡터: 방향과 크기를 가진 화살표 ===")
v2 = np.array([4, 5, 6])
print(f"v1 = {v1}") def plot_vector(ax, start, vector, color='blue', label=''):
print(f"v2 = {v2}") """벡터를 화살표로 그리는 함수"""
ax.quiver(start[0], start[1], vector[0], vector[1],
# 벡터의 크기(norm) 계산 angles='xy', scale_units='xy', scale=1, color=color, label=label)
magnitude_v1 = np.linalg.norm(v1)
print(f"v1의 크기: {magnitude_v1:.2f}") # 그래프 설정
fig, ax = plt.subplots(figsize=(10, 8))
# 벡터 덧셈과 뺄셈 ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.grid(True, alpha=0.3)
ax.set_aspect('equal')
# 원점
origin = np.array([0, 0])
# 다양한 벡터들
vectors = {
'v1': np.array([3, 2]), # 오른쪽 위로
'v2': np.array([-2, 1]), # 왼쪽 위로
'v3': np.array([0, -3]), # 아래로
'v4': np.array([4, 0]) # 오른쪽으로
}
colors = ['red', 'blue', 'green', 'purple']
# 벡터들을 화살표로 그리기
for i, (name, vector) in enumerate(vectors.items()):
plot_vector(ax, origin, vector, colors[i], name)
# 벡터의 크기 계산
magnitude = np.linalg.norm(vector)
print(f"{name}: 크기 = {magnitude:.2f}, 방향 = {vector}")
ax.set_xlabel('X축')
ax.set_ylabel('Y축')
ax.set_title('벡터 = 방향과 크기를 가진 화살표')
ax.legend()
plt.show()
def vector_operations():
"""2. 벡터 연산의 직관적 의미"""
print("\n=== 2. 벡터 연산: 이동 경로 합치기 ===")
def plot_vector(ax, start, vector, color='blue', label=''):
ax.quiver(start[0], start[1], vector[0], vector[1],
angles='xy', scale_units='xy', scale=1, color=color, label=label)
# 벡터 덧셈 시각화
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
# 첫 번째 그래프: 개별 벡터들
ax1.set_xlim(-5, 5)
ax1.set_ylim(-5, 5)
ax1.grid(True, alpha=0.3)
ax1.set_aspect('equal')
v1 = np.array([2, 1])
v2 = np.array([1, 3])
plot_vector(ax1, [0, 0], v1, 'red', 'v1')
plot_vector(ax1, [0, 0], v2, 'blue', 'v2')
ax1.set_title('개별 벡터들')
ax1.legend()
# 두 번째 그래프: 벡터 덧셈
ax2.set_xlim(-5, 5)
ax2.set_ylim(-5, 5)
ax2.grid(True, alpha=0.3)
ax2.set_aspect('equal')
# v1을 먼저 그리고, 그 끝점에서 v2를 그리기
plot_vector(ax2, [0, 0], v1, 'red', 'v1')
plot_vector(ax2, v1, v2, 'blue', 'v2') # v1의 끝점에서 v2 시작
# 합 벡터 (원점에서 최종 위치까지)
v_sum = v1 + v2 v_sum = v1 + v2
v_diff = v1 - v2 plot_vector(ax2, [0, 0], v_sum, 'green', 'v1 + v2')
print(f"벡터 덧셈: {v_sum}")
print(f"벡터 뺄셈: {v_diff}") ax2.set_title('벡터 덧셈: 이동 경로 합치기')
ax2.legend()
# 스칼라 곱
scalar = 2
v_scaled = scalar * v1
print(f"{scalar} * v1 = {v_scaled}")
# 내적(Dot Product)
dot_product = np.dot(v1, v2)
print(f"v1 · v2 = {dot_product}")
# 외적(Cross Product) - 3차원 벡터에만 적용 가능
cross_product = np.cross(v1, v2)
print(f"v1 × v2 = {cross_product}")
print("-" * 30)
def section_4_2():
print("### 4-2. 행렬(Matrix) ###")
# 행렬 생성
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print(f"행렬 A:\n{A}")
print(f"행렬 B:\n{B}")
# 행렬 덧셈과 뺄셈
C = A + B
D = A - B
print(f"A + B =\n{C}")
print(f"A - B =\n{D}")
# 행렬 곱셈
E = np.matmul(A, B) # 또는 A @ B (Python 3.5 이상)
print(f"A × B =\n{E}")
# 전치 행렬(Transpose)
A_T = A.T
print(f"A의 전치 행렬:\n{A_T}")
# 행렬식(Determinant)
det_A = np.linalg.det(A)
print(f"A의 행렬식: {det_A}")
# 역행렬(Inverse Matrix)
inv_A = np.linalg.inv(A)
print(f"A의 역행렬:\n{inv_A}")
# 역행렬 검증: A × A^(-1) = I (단위 행렬)
I = np.matmul(A, inv_A)
print(f"A × A^(-1) =\n{np.round(I, decimals=10)}") # 부동소수점 오차 처리
print("-" * 30)
def section_5_1():
print("### 5-1. 고유값(Eigenvalue)과 고유벡터(Eigenvector) ###")
# 예제 행렬
A = np.array([[4, -2], [1, 1]])
print(f"행렬 A:\n{A}")
# 고유값과 고유벡터 계산
eigenvalues, eigenvectors = np.linalg.eig(A)
print(f"고유값: {eigenvalues}")
print(f"고유벡터:\n{eigenvectors}")
# 검증: A × v = λ × v
for i in range(len(eigenvalues)):
v = eigenvectors[:, i]
Av = np.matmul(A, v)
lambda_v = eigenvalues[i] * v
print(f"\n고유값 {eigenvalues[i]:.2f}에 대한 검증:")
print(f"A × v = {Av}")
print(f"λ × v = {lambda_v}")
print("-" * 30)
def section_5_2():
print("### 5-2. 특이값 분해(SVD) ###")
# 예제 행렬 (비정방행렬)
B = np.array([[3, 2, 2], [2, 3, -2]])
print(f"행렬 B:\n{B}")
# SVD 계산
U, Sigma, VT = np.linalg.svd(B)
print(f"U 행렬:\n{U}")
print(f"특이값: {Sigma}")
print(f"V^T 행렬:\n{VT}")
# 원래 행렬 복원
# Sigma는 대각 요소만 반환하므로, 대각 행렬로 변환해야 함
Sigma_matrix = np.zeros(B.shape)
for i in range(min(B.shape)):
Sigma_matrix[i, i] = Sigma[i]
B_reconstructed = U @ Sigma_matrix @ VT
print(f"\n복원된 행렬:\n{np.round(B_reconstructed, decimals=10)}")
print("-" * 30)
def section_6_1():
print("### 6-1. 선형 회귀와 행렬 연산 ###")
# 데이터 생성
np.random.seed(42)
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)
# X에 상수항(1)을 추가 plt.tight_layout()
X_b = np.c_[np.ones((100, 1)), X] plt.show()
# 정규 방정식으로 파라미터 계산 print(f"v1 = {v1}")
theta_best = np.linalg.inv(X_b.T @ X_b) @ X_b.T @ y print(f"v2 = {v2}")
print(f"v1 + v2 = {v_sum}")
def vector_similarity():
"""3. 벡터 내적: 서로 얼마나 같은 방향인지"""
print("\n=== 3. 벡터 내적: 유사도 측정 ===")
def cosine_similarity(v1, v2):
"""두 벡터의 코사인 유사도 계산"""
dot_product = np.dot(v1, v2)
norm_v1 = np.linalg.norm(v1)
norm_v2 = np.linalg.norm(v2)
return dot_product / (norm_v1 * norm_v2)
# 다양한 각도의 벡터들
angles = [0, 45, 90, 135, 180] # 도
vectors = []
for angle in angles:
# 각도를 라디안으로 변환
rad = np.radians(angle)
# 단위 벡터 생성
v = np.array([np.cos(rad), np.sin(rad)])
vectors.append(v)
# 기준 벡터 (오른쪽 방향)
base_vector = np.array([1, 0])
print("벡터 간 유사도 (내적의 직관적 의미):")
print("-" * 50)
for i, angle in enumerate(angles):
similarity = cosine_similarity(base_vector, vectors[i])
direction = '같은 방향' if similarity > 0.9 else '수직' if abs(similarity) < 0.1 else '반대 방향' if similarity < -0.9 else '대각선'
print(f"각도 {angle}°: 유사도 = {similarity:.3f} ({direction})")
def matrix_transformations():
"""4. 행렬: 데이터를 변형하는 마법 상자"""
print("\n=== 4. 행렬 변환 시각화 ===")
def plot_transformation(original_points, transformed_points, title):
"""점들의 변환을 시각화"""
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# 원본 점들
ax1.scatter(original_points[:, 0], original_points[:, 1], c='blue', alpha=0.6)
ax1.set_xlim(-3, 3)
ax1.set_ylim(-3, 3)
ax1.grid(True, alpha=0.3)
ax1.set_aspect('equal')
ax1.set_title('변환 전')
# 변환된 점들
ax2.scatter(transformed_points[:, 0], transformed_points[:, 1], c='red', alpha=0.6)
ax2.set_xlim(-3, 3)
ax2.set_ylim(-3, 3)
ax2.grid(True, alpha=0.3)
ax2.set_aspect('equal')
ax2.set_title('변환 후')
plt.suptitle(title)
plt.tight_layout()
plt.show()
print(f"계산된 파라미터: {theta_best.flatten()}") # 원형 점들 생성
print(f"실제 파라미터: [4, 3]") theta = np.linspace(0, 2*np.pi, 50)
circle_points = np.column_stack([np.cos(theta), np.sin(theta)])
# 1. 회전 변환 (90도 회전)
rotation_matrix = np.array([[0, -1], [1, 0]]) # 90도 회전
rotated_points = circle_points @ rotation_matrix.T
plot_transformation(circle_points, rotated_points, "회전 변환: 원형 → 회전된 원형")
# 2. 크기 조절 변환 (X축으로 2배 늘리기)
scaling_matrix = np.array([[2, 0], [0, 1]]) # X축 2배
scaled_points = circle_points @ scaling_matrix.T
plot_transformation(circle_points, scaled_points, "크기 조절: 원형 → 타원형")
# 3. 전단 변환 (X축 방향으로 기울이기)
shear_matrix = np.array([[1, 0.5], [0, 1]]) # X축 방향 전단
sheared_points = circle_points @ shear_matrix.T
plot_transformation(circle_points, sheared_points, "전단 변환: 원형 → 기울어진 원형")
def matrix_multiplication():
"""5. 행렬곱의 직관적 의미"""
print("\n=== 5. 행렬곱: 복합 변환 ===")
# 원본 점들
theta = np.linspace(0, 2*np.pi, 100)
original = np.column_stack([np.cos(theta), np.sin(theta)])
# 변환 행렬들
A = np.array([[0.8, -0.6], [0.6, 0.8]]) # 회전 + 축소
B = np.array([[1.5, 0], [0, 0.8]]) # X축 확대, Y축 축소
# 단계별 변환
step1 = original @ A.T
step2 = step1 @ B.T
# 복합 변환 (A × B)
combined = original @ (A @ B).T
# 시각화
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
titles = ['원본', 'A 변환 후', 'B 변환 후', 'A×B 복합 변환']
points_list = [original, step1, step2, combined]
for i, (ax, title, points) in enumerate(zip(axes.flat, titles, points_list)):
ax.scatter(points[:, 0], points[:, 1], c='blue', alpha=0.6)
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
ax.grid(True, alpha=0.3)
ax.set_aspect('equal')
ax.set_title(title)
plt.tight_layout()
plt.show()
print("행렬곱의 의미: 변환을 순서대로 적용")
print(f"A = {A}")
print(f"B = {B}")
print(f"A × B = {A @ B}")
def eigenvectors_visualization():
"""6. 고유벡터: 변하지 않는 특별한 방향"""
print("\n=== 6. 고유벡터 시각화 ===")
def visualize_eigenvectors(matrix, title):
"""고유벡터를 시각화하는 함수"""
# 고유값과 고유벡터 계산
eigenvalues, eigenvectors = np.linalg.eig(matrix)
# 원형 점들
theta = np.linspace(0, 2*np.pi, 100)
circle = np.column_stack([np.cos(theta), np.sin(theta)])
# 변환 전후
transformed = circle @ matrix.T
# 고유벡터들
eigenvector1 = eigenvectors[:, 0]
eigenvector2 = eigenvectors[:, 1]
# 시각화
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
# 변환 전
ax1.scatter(circle[:, 0], circle[:, 1], c='lightblue', alpha=0.6, s=20)
ax1.quiver(0, 0, eigenvector1[0], eigenvector1[1], color='red', scale=3,
label=f'고유벡터1 (λ={eigenvalues[0]:.2f})')
ax1.quiver(0, 0, eigenvector2[0], eigenvector2[1], color='green', scale=3,
label=f'고유벡터2 (λ={eigenvalues[1]:.2f})')
ax1.set_xlim(-2, 2)
ax1.set_ylim(-2, 2)
ax1.grid(True, alpha=0.3)
ax1.set_aspect('equal')
ax1.set_title('변환 전: 고유벡터 방향')
ax1.legend()
# 변환 후
ax2.scatter(transformed[:, 0], transformed[:, 1], c='lightcoral', alpha=0.6, s=20)
# 변환된 고유벡터들
transformed_eigenvector1 = eigenvector1 @ matrix.T
transformed_eigenvector2 = eigenvector2 @ matrix.T
ax2.quiver(0, 0, transformed_eigenvector1[0], transformed_eigenvector1[1],
color='red', scale=3, label=f'변환된 고유벡터1')
ax2.quiver(0, 0, transformed_eigenvector2[0], transformed_eigenvector2[1],
color='green', scale=3, label=f'변환된 고유벡터2')
ax2.set_xlim(-2, 2)
ax2.set_ylim(-2, 2)
ax2.grid(True, alpha=0.3)
ax2.set_aspect('equal')
ax2.set_title('변환 후: 고유벡터는 방향이 유지됨')
ax2.legend()
plt.suptitle(title)
plt.tight_layout()
plt.show()
print(f"고유값: {eigenvalues}")
print(f"고유벡터:\n{eigenvectors}")
# 예측 # 예제 행렬들
X_new = np.array([[0], [2]]) matrix1 = np.array([[2, 0], [0, 1]]) # 대각 행렬
X_new_b = np.c_[np.ones((2, 1)), X_new] matrix2 = np.array([[1, 0.5], [0.5, 1]]) # 대칭 행렬
y_predict = X_new_b @ theta_best
print(f"x=0, x=2에 대한 예측값: {y_predict.flatten()}") visualize_eigenvectors(matrix1, "대각 행렬의 고유벡터")
print("-" * 30) visualize_eigenvectors(matrix2, "대칭 행렬의 고유벡터")
def section_6_2(): def pca_visualization():
print("### 6-2. 주성분 분석(PCA)과 고유값 분해 ###") """7. PCA: 데이터의 핵심 방향 찾기"""
# 데이터 생성 print("\n=== 7. PCA 시각화 ===")
# 2D 데이터 생성 (상관관계가 있는 데이터)
np.random.seed(42) np.random.seed(42)
mean = [0, 0] mean = [0, 0]
cov = [[5, 4], [4, 5]] # 공분산 행렬 cov = [[3, 2], [2, 3]] # 상관관계가 있는 공분산 행렬
data = np.random.multivariate_normal(mean, cov, 200) data = np.random.multivariate_normal(mean, cov, 200)
# 데이터 중앙 정렬 # 데이터 중앙 정렬
...@@ -152,139 +296,285 @@ def section_6_2(): ...@@ -152,139 +296,285 @@ def section_6_2():
# 공분산 행렬 계산 # 공분산 행렬 계산
cov_matrix = np.cov(X.T) cov_matrix = np.cov(X.T)
print(f"공분산 행렬:\n{cov_matrix}")
# 고유값과 고유벡터 계산 # 고유값과 고유벡터 계산 (PCA)
eigenvalues, eigenvectors = np.linalg.eig(cov_matrix) eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)
# 고유값 기준으로 고유벡터 정렬 # 고유값 순으로 정렬
idx = eigenvalues.argsort()[::-1] sorted_indices = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[idx] sorted_eigenvalues = eigenvalues[sorted_indices]
eigenvectors = eigenvectors[:, idx] sorted_eigenvectors = eigenvectors[:, sorted_indices]
print(f"고유값: {eigenvalues}") # 시각화
print(f"고유벡터:\n{eigenvectors}") fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
# 주성분으로 데이터 투영 (차원 축소) # 원본 데이터와 주성분
n_components = 1 # 첫 번째 주성분만 사용 ax1.scatter(X[:, 0], X[:, 1], alpha=0.6, c='lightblue')
X_pca = X @ eigenvectors[:, :n_components] # 주성분 벡터 그리기
pc1 = sorted_eigenvectors[:, 0]
# 원래 차원으로 복원 pc2 = sorted_eigenvectors[:, 1]
X_recovered = X_pca @ eigenvectors[:, :n_components].T ax1.quiver(0, 0, pc1[0]*np.sqrt(sorted_eigenvalues[0]), pc1[1]*np.sqrt(sorted_eigenvalues[0]),
color='red', scale=10, label=f'PC1 (분산: {sorted_eigenvalues[0]:.2f})')
print(f"원본 데이터 차원: {X.shape}") ax1.quiver(0, 0, pc2[0]*np.sqrt(sorted_eigenvalues[1]), pc2[1]*np.sqrt(sorted_eigenvalues[1]),
print(f"축소된 데이터 차원: {X_pca.shape}") color='green', scale=10, label=f'PC2 (분산: {sorted_eigenvalues[1]:.2f})')
print(f"복원된 데이터 차원: {X_recovered.shape}") ax1.set_xlim(-4, 4)
print("-" * 30) ax1.set_ylim(-4, 4)
ax1.grid(True, alpha=0.3)
def section_6_3(): ax1.set_aspect('equal')
print("### 6-3. 신경망과 행렬 연산 ###") ax1.set_title('원본 데이터와 주성분 (PC1, PC2)')
def sigmoid(x): ax1.legend()
return 1 / (1 + np.exp(-x))
# PCA 변환된 데이터
# 입력 데이터 transformed_data = X @ sorted_eigenvectors
X = np.array([[0.1, 0.2, 0.3]]) ax2.scatter(transformed_data[:, 0], transformed_data[:, 1], alpha=0.6, c='lightcoral')
ax2.set_xlim(-4, 4)
# 첫 번째 층 가중치와 편향 ax2.set_ylim(-4, 4)
W1 = np.array([[0.1, 0.2, 0.3], ax2.grid(True, alpha=0.3)
[0.4, 0.5, 0.6], ax2.set_aspect('equal')
[0.7, 0.8, 0.9]]) ax2.set_title('PCA 변환 후: PC1이 가장 큰 분산을 가짐')
b1 = np.array([0.1, 0.2, 0.3]) ax2.set_xlabel('PC1')
ax2.set_ylabel('PC2')
# 두 번째 층 가중치와 편향
W2 = np.array([[0.1, 0.2, 0.3], plt.tight_layout()
[0.4, 0.5, 0.6]]) plt.show()
b2 = np.array([0.1, 0.2])
print("PCA의 직관적 의미:")
# 순전파 계산 print(f"PC1 (첫 번째 주성분): {pc1}")
z1 = X @ W1 + b1 print(f"PC2 (두 번째 주성분): {pc2}")
a1 = sigmoid(z1) print(f"PC1 분산: {sorted_eigenvalues[0]:.2f} ({sorted_eigenvalues[0]/sum(sorted_eigenvalues)*100:.1f}%)")
z2 = a1 @ W2 + b2 print(f"PC2 분산: {sorted_eigenvalues[1]:.2f} ({sorted_eigenvalues[1]/sum(sorted_eigenvalues)*100:.1f}%)")
a2 = sigmoid(z2)
def image_filtering():
print(f"입력: {X}") """8. 이미지 필터링으로 이해하는 행렬"""
print(f"첫 번째 층 출력: {a1}") print("\n=== 8. 이미지 필터링 예제 ===")
print(f"최종 출력: {a2}")
print("-" * 30) # 간단한 이미지 생성 (체크무늬 패턴)
def create_checkerboard(size=50):
def exercise_1(): """체크무늬 이미지 생성"""
print("### 연습문제 1: 이미지 압축 (SVD 활용) ###") img = np.zeros((size, size))
try: for i in range(size):
# 이미지 로드 (그레이스케일로 변환) for j in range(size):
# 실습을 위해 'image.jpg' 파일을 소스코드와 같은 디렉토리에 위치시켜주세요. if (i // 10 + j // 10) % 2 == 0:
img = imread('ai_lecture/source_code/image.jpg') img[i, j] = 255
if len(img.shape) == 3: # 컬러 이미지인 경우 return img
img = np.mean(img, axis=2) # 그레이스케일로 변환
# 필터 행렬들 (커널)
plt.figure(figsize=(12, 6)) filters = {
plt.subplot(1, 3, 1) '블러': np.array([[1/9, 1/9, 1/9], [1/9, 1/9, 1/9], [1/9, 1/9, 1/9]]),
plt.imshow(img, cmap='gray') '엣지 검출': np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]]),
plt.title('Original Image') '수평 엣지': np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]]),
plt.axis('off') '수직 엣지': np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]])
}
# SVD 계산
U, Sigma, VT = np.linalg.svd(img) # 이미지 생성
original = create_checkerboard()
# 다양한 수의 특이값으로 이미지 재구성
k = 50 # 사용할 특이값 수 # 필터 적용 함수
reconstructed = U[:, :k] @ np.diag(Sigma[:k]) @ VT[:k, :] def apply_filter(image, filter_kernel):
"""이미지에 필터 적용"""
plt.subplot(1, 3, 2) from scipy import ndimage
plt.imshow(reconstructed, cmap='gray') return ndimage.convolve(image, filter_kernel, mode='constant', cval=0)
plt.title(f'Reconstructed (k={k})')
plt.axis('off') # 시각화
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 잔차(Residual) 이미지 axes = axes.flatten()
residual = img - reconstructed
plt.subplot(1, 3, 3) # 원본 이미지
plt.imshow(residual, cmap='gray') axes[0].imshow(original, cmap='gray')
plt.title('Residual (Error)') axes[0].set_title('원본 이미지')
plt.axis('off') axes[0].axis('off')
# 필터 적용 결과
for i, (filter_name, filter_kernel) in enumerate(filters.items()):
filtered = apply_filter(original, filter_kernel)
axes[i+1].imshow(filtered, cmap='gray')
axes[i+1].set_title(f'{filter_name} 필터')
axes[i+1].axis('off')
plt.tight_layout()
plt.show()
print("이미지 필터링 = 행렬 곱셈의 실제 활용")
print("각 픽셀 주변의 값들을 행렬(커널)로 가중 평균하여 새로운 픽셀 값 계산")
def recommendation_system():
"""9. 추천 시스템으로 이해하는 행렬 분해"""
print("\n=== 9. 추천 시스템 시뮬레이션 ===")
# 간단한 추천 시스템 시뮬레이션
np.random.seed(42)
# 사용자-아이템 평점 행렬 (5명 사용자, 4개 아이템)
# 0은 평점이 없는 경우
ratings = np.array([
[5, 3, 0, 1], # 사용자 1
[4, 0, 0, 1], # 사용자 2
[1, 1, 0, 5], # 사용자 3
[1, 0, 0, 4], # 사용자 4
[0, 1, 5, 4] # 사용자 5
])
print("사용자-아이템 평점 행렬:")
print(ratings)
print("\n행렬 분해의 직관적 의미:")
print("- 사용자 행렬: 각 사용자의 취향 (예: 액션 선호도, 로맨스 선호도)")
print("- 아이템 행렬: 각 아이템의 특성 (예: 액션 요소, 로맨스 요소)")
print("- 두 행렬의 곱으로 누락된 평점을 예측")
# 간단한 행렬 분해 (SVD의 직관적 이해)
# 실제로는 더 복잡한 알고리즘 사용
U = np.random.rand(5, 2) # 사용자 특성 행렬
V = np.random.rand(2, 4) # 아이템 특성 행렬
# 예측 평점
predicted = U @ V
print(f"\n예측된 평점 행렬:\n{predicted.round(2)}")
# 원본과 예측 비교
print(f"\n원본 평점 (0은 누락):\n{ratings}")
print(f"예측 평점:\n{predicted.round(2)}")
def my_vector_class():
"""10. 나만의 벡터 클래스 만들기"""
print("\n=== 10. 나만의 벡터 클래스 ===")
class MyVector:
"""직관적인 벡터 클래스"""
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
"""벡터 덧셈: 이동 경로 합치기"""
return MyVector(self.x + other.x, self.y + other.y)
def __mul__(self, scalar):
"""스칼라 곱: 크기 조절"""
return MyVector(self.x * scalar, self.y * scalar)
def magnitude(self):
"""벡터의 크기"""
return np.sqrt(self.x**2 + self.y**2)
def dot(self, other):
"""내적: 서로 얼마나 같은 방향인지"""
return self.x * other.x + self.y * other.y
def plot(self, ax, color='blue', label=''):
"""벡터를 화살표로 그리기"""
ax.quiver(0, 0, self.x, self.y, color=color, scale=10, label=label)
def __str__(self):
return f"Vector({self.x}, {self.y})"
# 사용 예제
v1 = MyVector(3, 2)
v2 = MyVector(1, 4)
print(f"v1 = {v1}")
print(f"v2 = {v2}")
print(f"v1 + v2 = {v1 + v2}")
print(f"v1의 크기 = {v1.magnitude():.2f}")
print(f"v1 · v2 = {v1.dot(v2)}")
# 시각화
fig, ax = plt.subplots(figsize=(8, 8))
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.grid(True, alpha=0.3)
ax.set_aspect('equal')
v1.plot(ax, 'red', 'v1')
v2.plot(ax, 'blue', 'v2')
(v1 + v2).plot(ax, 'green', 'v1 + v2')
ax.legend()
ax.set_title('나만의 벡터 클래스')
plt.show()
def neural_network_weights():
"""11. 간단한 신경망 가중치 시각화"""
print("\n=== 11. 신경망 가중치 시각화 ===")
# 간단한 신경망 가중치를 행렬로 이해하기
def create_simple_network():
"""간단한 신경망 가중치 행렬 생성"""
# 입력층(2) -> 은닉층(3) -> 출력층(1)
W1 = np.random.randn(2, 3) * 0.1 # 입력층 -> 은닉층 가중치
W2 = np.random.randn(3, 1) * 0.1 # 은닉층 -> 출력층 가중치
return W1, W2
def visualize_weights(W1, W2):
"""신경망 가중치 시각화"""
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# W1 시각화 (2x3 행렬)
im1 = ax1.imshow(W1, cmap='RdBu', aspect='auto')
ax1.set_title('입력층 → 은닉층 가중치 (W1)')
ax1.set_xlabel('은닉층 뉴런')
ax1.set_ylabel('입력층 뉴런')
plt.colorbar(im1, ax=ax1)
# W2 시각화 (3x1 행렬)
im2 = ax2.imshow(W2, cmap='RdBu', aspect='auto')
ax2.set_title('은닉층 → 출력층 가중치 (W2)')
ax2.set_xlabel('출력층 뉴런')
ax2.set_ylabel('은닉층 뉴런')
plt.colorbar(im2, ax=ax2)
plt.tight_layout() plt.tight_layout()
plt.show() plt.show()
# 압축률과 오차 계산 print("신경망 가중치 = 행렬의 실제 활용")
original_size = img.shape[0] * img.shape[1] print(f"W1 모양: {W1.shape} (입력 2개 → 은닉층 3개)")
compressed_size = k * (img.shape[0] + img.shape[1] + 1) print(f"W2 모양: {W2.shape} (은닉층 3개 → 출력 1개)")
compression_ratio = original_size / compressed_size
error = np.linalg.norm(img - reconstructed) / np.linalg.norm(img) # 신경망 생성 및 시각화
W1, W2 = create_simple_network()
print(f"압축률: {compression_ratio:.2f}x") visualize_weights(W1, W2)
print(f"상대 오차: {error:.4f}")
def main():
except FileNotFoundError: """메인 함수: 모든 시각화 실행"""
print("\n오류: `ai_lecture/source_code/image.jpg` 파일을 찾을 수 없습니다.") print("🚀 선형대수 직관적 이해 - 시각화 중심 학습")
print("실습을 진행하려면 이미지를 해당 경로에 추가해주세요.") print("=" * 60)
print("-" * 30)
# 1. 벡터 시각화
def exercise_2(): visualize_vectors()
print("### 연습문제 2: 선형 시스템 해결 ###")
# 계수 행렬 A와 상수 벡터 b 정의 # 2. 벡터 연산
A = np.array([[2, 1, -1], vector_operations()
[-3, -1, 2],
[-2, 1, 2]]) # 3. 벡터 유사도
b = np.array([8, -11, -3]) vector_similarity()
# 선형 시스템 해결 # 4. 행렬 변환
x = np.linalg.solve(A, b) matrix_transformations()
print(f"해: x = {x[0]}, y = {x[1]}, z = {x[2]}") # 5. 행렬곱
matrix_multiplication()
# 검증
verification = A @ x # 6. 고유벡터
print(f"A @ x = {verification}") eigenvectors_visualization()
print(f"b = {b}")
print(f"검증 (A@x 와 b가 거의 같은가?): {np.allclose(A @ x, b)}") # 7. PCA
print("-" * 30) pca_visualization()
if __name__ == '__main__': # 8. 이미지 필터링
section_4_1() image_filtering()
section_4_2()
section_5_1() # 9. 추천 시스템
section_5_2() recommendation_system()
section_6_1()
section_6_2() # 10. 벡터 클래스
section_6_3() my_vector_class()
exercise_1()
exercise_2() # 11. 신경망 가중치
\ No newline at end of file neural_network_weights()
print("\n🎉 선형대수 학습 완료!")
print("핵심 메시지: 복잡한 수식보다는 직관적 이해와 실제 활용에 집중하세요!")
if __name__ == "__main__":
main()
\ No newline at end of file
# 미적분 시각화 실습 코드
## 🎯 목적
이 디렉토리에는 **시각화와 비유를 중심으로** 미적분의 핵심 개념을 직관적으로 이해할 수 있는 실습 코드가 포함되어 있습니다.
## 📁 파일 구조
```
05.6_calculus_for_ml/
├── README.md # 이 파일
└── linear_regression_gradient_descent.py # 11개의 시각화 예제
```
## 🚀 실행 방법
### 1. 환경 설정
```bash
pip install numpy matplotlib
```
### 2. 코드 실행
```bash
python linear_regression_gradient_descent.py
```
## 📚 실습 내용
### 1. 미분 시각화 📈
- **파일**: `visualize_derivatives()`
- **목적**: 미분을 "순간 변화율"과 "기울기"로 이해
- **핵심**: 접선의 기울기 = 미분값
### 2. 여러 점에서의 미분 📊
- **파일**: `visualize_multiple_derivatives()`
- **목적**: 함수의 여러 점에서 기울기 변화 관찰
- **핵심**: 미분의 기하학적 의미
### 3. 수치 vs 해석적 미분 🔍
- **파일**: `numerical_vs_analytical_derivatives()`
- **목적**: 두 미분 방법의 비교
- **핵심**: 수치 근사 vs 정확한 해
### 4. 기울기 하강법 🏔️
- **파일**: `gradient_descent_visualization()`
- **목적**: 기울기 하강법을 "산에서 내려오는 사람"으로 이해
- **핵심**: 최적화 과정의 시각화
### 5. 학습률 비교 ⚡
- **파일**: `compare_learning_rates()`
- **목적**: 학습률이 최적화에 미치는 영향
- **핵심**: 적절한 학습률의 중요성
### 6. 편미분 🔧
- **파일**: `partial_derivatives_visualization()`
- **목적**: 편미분을 "여러 변수 중 하나만 바꾸기"로 이해
- **핵심**: 3D 시각화로 다변수 함수 이해
### 7. 그래디언트 벡터 🧭
- **파일**: `gradient_vector_visualization()`
- **목적**: 그래디언트 벡터의 방향과 크기
- **핵심**: 가장 가파르게 증가하는 방향
### 8. 연쇄법칙 🔗
- **파일**: `chain_rule_visualization()`
- **목적**: 연쇄법칙을 "파이프라인을 통해 변화 전파"로 이해
- **핵심**: 합성 함수의 미분
### 9. 신경망 역전파 🧠
- **파일**: `simple_neural_network()`
- **목적**: 간단한 신경망의 역전파 과정
- **핵심**: 딥러닝의 핵심 원리
### 10. 선형 회귀 📊
- **파일**: `linear_regression_gradient_descent()`
- **목적**: 선형 회귀의 경사 하강법 구현
- **핵심**: 실제 머신러닝 모델 학습
### 11. 최적화 알고리즘 비교 🏁
- **파일**: `compare_optimizers()`
- **목적**: 다양한 최적화 알고리즘 비교
- **핵심**: 모멘텀의 효과
## 🎯 학습 팁
### 시각화 중심 학습
- 각 예제를 실행한 후 그래프를 자세히 관찰하세요
- 파라미터를 바꿔보며 결과가 어떻게 변하는지 확인하세요
- 코드의 주석을 읽고 각 단계의 의미를 이해하세요
### 실습 중심 접근
- 코드를 직접 수정해보세요
- 새로운 함수로 테스트해보세요
- 다른 최적화 문제에 적용해보세요
## 🔧 커스터마이징
### 파라미터 조정
```python
# 학습률 조정
learning_rate = 0.05 # 더 작은 학습률
# 함수 변경
def f(x):
return x**3 + 2*x + 1 # 다른 함수로 테스트
```
### 새로운 예제 추가
```python
def my_custom_optimization():
"""나만의 최적화 함수"""
# 여기에 코드 작성
pass
```
## 🎉 핵심 메시지
> **"왜 이걸 배워야 하는가?"** 에 집중합니다.
>
> 미적분은 머신러닝의 "학습 엔진"입니다.
> 복잡한 수식보다는 **직관적 이해**와 **실제 구현**에 집중하세요!
## 📖 추가 자료
- [3Blue1Brown - Calculus](https://www.youtube.com/playlist?list=PLZHQObOWTQDMsr9K-rj53DwVRMYO3t5Yr)
- [Khan Academy - Calculus](https://www.khanacademy.org/math/calculus-1)
- [MIT OpenCourseWare - Calculus](https://ocw.mit.edu/courses/mathematics/18-01sc-single-variable-calculus-fall-2010/)
\ No newline at end of file
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# --- 1. 데이터 생성 --- def visualize_derivatives():
# y = 2x + 1 의 관계를 가지는 데이터를 생성합니다. """1. 미분의 직관적 이해: 기울기 시각화"""
# 실제 데이터처럼 보이기 위해 약간의 노이즈를 추가합니다. print("=== 1. 미분: 순간 변화율과 기울기 ===")
np.random.seed(42)
X = 2 * np.random.rand(100, 1) def plot_function_with_tangent(x, y, tangent_x, tangent_y, slope, title):
y = 1 + 2 * X + np.random.randn(100, 1) """함수와 접선을 시각화"""
plt.figure(figsize=(10, 6))
# --- 2. 모델 파라미터 초기화 및 하이퍼파라미터 설정 ---
# 학습을 통해 찾아야 할 가중치(W)와 편향(b)을 랜덤 값으로 초기화합니다. # 함수 그리기
W = np.random.randn(1, 1) plt.plot(x, y, 'b-', linewidth=2, label='함수 f(x)')
b = np.random.randn(1, 1)
# 접선 그리기
# 학습률(learning_rate): 파라미터를 얼마나 업데이트할지 결정하는 값 plt.plot(tangent_x, tangent_y, 'r--', linewidth=2, label=f'접선 (기울기: {slope:.2f})')
learning_rate = 0.1
# 반복 학습 횟수(epochs) # 접점 표시
epochs = 100 plt.scatter(tangent_x[len(tangent_x)//2], tangent_y[len(tangent_y)//2],
color='red', s=100, zorder=5, label='접점')
n_samples = len(X)
print(f"초기 W: {W[0][0]:.3f}, 초기 b: {b[0][0]:.3f}") plt.grid(True, alpha=0.3)
plt.xlabel('x')
# --- 3. 경사 하강법 구현 --- plt.ylabel('y')
# 학습 과정에서 비용(cost)의 변화를 기록하기 위한 리스트 plt.title(title)
cost_history = [] plt.legend()
plt.show()
for i in range(epochs):
# 3-1. 예측값 계산 (Forward Pass) # 예제: f(x) = x² 함수의 미분
# y_pred = W * X + b x = np.linspace(-3, 3, 100)
y_pred = np.dot(X, W) + b y = x**2
# 3-2. 비용(Cost/Loss) 계산: 평균 제곱 오차(MSE) # x = 1에서의 접선
# cost = (1/N) * Σ(y_true - y_pred)^2 x0 = 1
cost = np.mean((y - y_pred)**2) y0 = x0**2
cost_history.append(cost) slope = 2 * x0 # f'(x) = 2x
# 3-3. 그래디언트 계산 (Backward Pass, 편미분) # 접선 그리기
# dJ/dW = -(2/N) * Σ X * (y_true - y_pred) tangent_x = np.linspace(x0 - 1, x0 + 1, 50)
# dJ/db = -(2/N) * Σ (y_true - y_pred) tangent_y = slope * (tangent_x - x0) + y0
# 문서의 수식과 비교해보세요. (여기서는 2를 곱하지 않고 평균 그래디언트를 사용)
dW = (1/n_samples) * np.dot(X.T, (y_pred - y)) plot_function_with_tangent(x, y, tangent_x, tangent_y, slope,
db = (1/n_samples) * np.sum(y_pred - y) f"f(x) = x²의 미분: x = {x0}에서 기울기 = {slope}")
# 3-4. 파라미터 업데이트 print(f"f(x) = x²")
# W = W - learning_rate * dW print(f"f'({x0}) = {slope}")
# b = b - learning_rate * db print(f"x = {x0}에서 함수가 {slope}만큼 빠르게 증가하고 있습니다!")
W = W - learning_rate * dW
b = b - learning_rate * db def visualize_multiple_derivatives():
"""2. 여러 점에서의 기울기 시각화"""
# 10번의 epoch마다 학습 결과 출력 print("\n=== 2. 여러 점에서의 미분값 ===")
if (i + 1) % 10 == 0:
print(f"Epoch {i+1:3d}: Cost={cost:.4f}, W={W[0][0]:.3f}, b={b[0][0]:.3f}") x = np.linspace(-3, 3, 100)
y = x**2
print(f"\n학습 완료!")
print(f"최종 W: {W[0][0]:.3f}, 최종 b: {b[0][0]:.3f} (실제 값: W=2, b=1)") # 여러 점에서의 접선
points = [-2, -1, 0, 1, 2]
# --- 4. 학습 결과 시각화 --- plt.figure(figsize=(12, 8))
plt.plot(x, y, 'b-', linewidth=3, label='f(x) = x²')
# 1. 비용(Cost) 감소 그래프
plt.figure(figsize=(12, 5)) colors = ['red', 'orange', 'green', 'blue', 'purple']
plt.subplot(1, 2, 1)
plt.plot(cost_history) for i, x0 in enumerate(points):
plt.title("Cost Reduction History") y0 = x0**2
plt.xlabel("Epoch") slope = 2 * x0
plt.ylabel("Cost (MSE)")
plt.grid(True) # 접선 그리기
tangent_x = np.linspace(x0 - 0.5, x0 + 0.5, 20)
# 2. 회귀선 시각화 tangent_y = slope * (tangent_x - x0) + y0
plt.subplot(1, 2, 2)
# 원본 데이터 산점도 plt.plot(tangent_x, tangent_y, '--', color=colors[i], linewidth=2,
plt.scatter(X, y, label='Original Data') label=f'x = {x0}, 기울기 = {slope}')
# 학습된 모델의 회귀선 plt.scatter(x0, y0, color=colors[i], s=100, zorder=5)
plt.plot(X, np.dot(X, W) + b, color='red', linewidth=3, label='Fitted Line')
plt.title("Linear Regression Fit") plt.grid(True, alpha=0.3)
plt.xlabel("X") plt.xlabel('x')
plt.ylabel("y") plt.ylabel('y')
plt.legend() plt.title('f(x) = x²의 여러 점에서의 기울기')
plt.grid(True) plt.legend()
plt.show()
plt.tight_layout()
plt.show() print("미분의 직관적 의미:")
\ No newline at end of file print("- 음수 기울기: 함수가 감소하고 있음")
print("- 0 기울기: 함수가 변하지 않음 (극점)")
print("- 양수 기울기: 함수가 증가하고 있음")
def numerical_vs_analytical_derivatives():
"""3. 수치 미분 vs 해석적 미분"""
print("\n=== 3. 수치 미분 vs 해석적 미분 ===")
def numerical_derivative(func, x, h=1e-4):
"""수치 미분: 미분의 정의를 이용한 근사"""
return (func(x + h) - func(x - h)) / (2 * h)
# 테스트 함수들
functions = {
'f(x) = x²': (lambda x: x**2, lambda x: 2*x),
'f(x) = x³': (lambda x: x**3, lambda x: 3*x**2),
'f(x) = sin(x)': (lambda x: np.sin(x), lambda x: np.cos(x))
}
x_test = 1.0
print("수치 미분 vs 해석적 미분 비교:")
print("-" * 50)
for name, (func, derivative) in functions.items():
numerical = numerical_derivative(func, x_test)
analytical = derivative(x_test)
print(f"{name}:")
print(f" 수치 미분: f'({x_test}) ≈ {numerical:.6f}")
print(f" 해석적 미분: f'({x_test}) = {analytical:.6f}")
print(f" 오차: {abs(numerical - analytical):.2e}")
print()
def gradient_descent_visualization():
"""4. 기울기 하강법: 산에서 내려오는 사람"""
print("\n=== 4. 기울기 하강법 시각화 ===")
# 목표 함수: f(x) = x² + 2
def f(x):
return x**2 + 2
def df(x):
return 2*x
# 기울기 하강법
x = 3.0 # 시작점
learning_rate = 0.1
steps = 20
x_history = [x]
y_history = [f(x)]
for i in range(steps):
gradient = df(x)
x = x - learning_rate * gradient
x_history.append(x)
y_history.append(f(x))
# 시각화
x_plot = np.linspace(-4, 4, 100)
y_plot = f(x_plot)
plt.figure(figsize=(12, 8))
# 함수 그리기
plt.plot(x_plot, y_plot, 'b-', linewidth=2, label='f(x) = x² + 2')
# 기울기 하강 과정
plt.plot(x_history, y_history, 'ro-', linewidth=2, markersize=8, label='기울기 하강 경로')
# 화살표로 이동 방향 표시
for i in range(len(x_history) - 1):
dx = x_history[i+1] - x_history[i]
dy = y_history[i+1] - y_history[i]
plt.arrow(x_history[i], y_history[i], dx, dy,
head_width=0.05, head_length=0.1, fc='red', ec='red', alpha=0.7)
plt.grid(True, alpha=0.3)
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title('기울기 하강법: 산에서 내려오는 과정')
plt.legend()
plt.show()
print("기울기 하강법 과정:")
for i, (x_val, y_val) in enumerate(zip(x_history, y_history)):
print(f"단계 {i}: x = {x_val:.4f}, f(x) = {y_val:.4f}")
def compare_learning_rates():
"""5. 학습률에 따른 기울기 하강법 비교"""
print("\n=== 5. 학습률의 중요성 ===")
def f(x):
return x**2 + 2
def df(x):
return 2*x
learning_rates = [0.01, 0.1, 0.5, 1.0]
colors = ['blue', 'green', 'orange', 'red']
plt.figure(figsize=(15, 10))
for i, lr in enumerate(learning_rates):
# 기울기 하강법
x = 3.0
steps = 20
x_history = [x]
y_history = [f(x)]
for step in range(steps):
gradient = df(x)
x = x - lr * gradient
x_history.append(x)
y_history.append(f(x))
# 경로 그리기
plt.subplot(2, 2, i+1)
x_plot = np.linspace(-4, 4, 100)
y_plot = f(x_plot)
plt.plot(x_plot, y_plot, 'b-', linewidth=2, label='f(x) = x² + 2')
plt.plot(x_history, y_history, 'o-', color=colors[i], linewidth=2,
markersize=6, label=f'학습률 = {lr}')
plt.grid(True, alpha=0.3)
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title(f'학습률 = {lr}')
plt.legend()
plt.tight_layout()
plt.show()
print("학습률의 영향:")
print("- 너무 작음 (0.01): 천천히 수렴")
print("- 적당함 (0.1): 빠르게 수렴")
print("- 너무 큼 (0.5, 1.0): 진동하거나 발산")
def partial_derivatives_visualization():
"""6. 편미분: 여러 변수 중 하나만 바꾸기"""
print("\n=== 6. 편미분 시각화 ===")
# 예제 함수: f(x, y) = x² + y²
def f(x, y):
return x**2 + y**2
def df_dx(x, y):
return 2*x
def df_dy(x, y):
return 2*y
# 3D 시각화
x = np.linspace(-3, 3, 50)
y = np.linspace(-3, 3, 50)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
fig = plt.figure(figsize=(15, 5))
# 3D 표면
ax1 = fig.add_subplot(131, projection='3d')
surf = ax1.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8)
ax1.set_title('f(x, y) = x² + y²')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_zlabel('f(x, y)')
# x에 대한 편미분 (y = 1에서의 단면)
ax2 = fig.add_subplot(132)
y_fixed = 1
x_slice = np.linspace(-3, 3, 100)
z_slice = f(x_slice, y_fixed)
ax2.plot(x_slice, z_slice, 'b-', linewidth=2, label=f'f(x, {y_fixed})')
# 접선 그리기 (x = 1에서)
x0 = 1
z0 = f(x0, y_fixed)
slope = df_dx(x0, y_fixed)
tangent_x = np.linspace(x0 - 1, x0 + 1, 20)
tangent_z = slope * (tangent_x - x0) + z0
ax2.plot(tangent_x, tangent_z, 'r--', linewidth=2, label=f'∂f/∂x = {slope}')
ax2.scatter(x0, z0, color='red', s=100)
ax2.grid(True, alpha=0.3)
ax2.set_xlabel('x')
ax2.set_ylabel('f(x, y)')
ax2.set_title(f'∂f/∂x (y = {y_fixed})')
ax2.legend()
# y에 대한 편미분 (x = 1에서의 단면)
ax3 = fig.add_subplot(133)
x_fixed = 1
y_slice = np.linspace(-3, 3, 100)
z_slice = f(x_fixed, y_slice)
ax3.plot(y_slice, z_slice, 'g-', linewidth=2, label=f'f({x_fixed}, y)')
# 접선 그리기 (y = 1에서)
y0 = 1
z0 = f(x_fixed, y0)
slope = df_dy(x_fixed, y0)
tangent_y = np.linspace(y0 - 1, y0 + 1, 20)
tangent_z = slope * (tangent_y - y0) + z0
ax3.plot(tangent_y, tangent_z, 'r--', linewidth=2, label=f'∂f/∂y = {slope}')
ax3.scatter(y0, z0, color='red', s=100)
ax3.grid(True, alpha=0.3)
ax3.set_xlabel('y')
ax3.set_ylabel('f(x, y)')
ax3.set_title(f'∂f/∂y (x = {x_fixed})')
ax3.legend()
plt.tight_layout()
plt.show()
print("편미분의 직관적 의미:")
print(f"∂f/∂x = {df_dx(1, 1)}: x를 조금 바꾸면 함수가 {df_dx(1, 1)}배만큼 변함")
print(f"∂f/∂y = {df_dy(1, 1)}: y를 조금 바꾸면 함수가 {df_dy(1, 1)}배만큼 변함")
def gradient_vector_visualization():
"""7. 그래디언트 벡터 시각화"""
print("\n=== 7. 그래디언트 벡터 ===")
def f(x, y):
return x**2 + y**2
def gradient(x, y):
return np.array([2*x, 2*y])
# 등고선 그리기
x = np.linspace(-3, 3, 50)
y = np.linspace(-3, 3, 50)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
plt.figure(figsize=(10, 8))
# 등고선
contours = plt.contour(X, Y, Z, levels=10, colors='blue', alpha=0.6)
plt.clabel(contours, inline=True, fontsize=8)
# 그래디언트 벡터들
x_points = np.linspace(-2, 2, 8)
y_points = np.linspace(-2, 2, 8)
for i, x_val in enumerate(x_points):
for j, y_val in enumerate(y_points):
grad = gradient(x_val, y_val)
# 벡터 크기 정규화
grad_norm = grad / np.linalg.norm(grad) * 0.3
plt.quiver(x_val, y_val, grad_norm[0], grad_norm[1],
color='red', alpha=0.7, scale=20)
plt.xlabel('x')
plt.ylabel('y')
plt.title('그래디언트 벡터: 가장 가파르게 증가하는 방향')
plt.grid(True, alpha=0.3)
plt.axis('equal')
plt.show()
print("그래디언트의 의미:")
print("- 방향: 함수가 가장 가파르게 증가하는 방향")
print("- 크기: 그 방향으로의 증가율")
print("- 기울기 하강법: 그래디언트의 반대 방향으로 이동")
def chain_rule_visualization():
"""8. 연쇄법칙: 파이프라인을 통해 변화 전파"""
print("\n=== 8. 연쇄법칙 시각화 ===")
# 예제: f(x) = sin(x²)
def f(x):
return np.sin(x**2)
def df_dx(x):
# 연쇄법칙: d/dx[sin(x²)] = cos(x²) * 2x
return np.cos(x**2) * 2*x
x = np.linspace(-2, 2, 100)
y = f(x)
# 수치 미분으로 검증
def numerical_derivative(func, x, h=1e-4):
return (func(x + h) - func(x - h)) / (2 * h)
x_test = 1.0
analytical = df_dx(x_test)
numerical = numerical_derivative(f, x_test)
plt.figure(figsize=(12, 8))
# 함수와 미분
plt.subplot(2, 1, 1)
plt.plot(x, y, 'b-', linewidth=2, label='f(x) = sin(x²)')
plt.grid(True, alpha=0.3)
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title('합성 함수: f(x) = sin(x²)')
plt.legend()
# 미분
plt.subplot(2, 1, 2)
dy_dx = df_dx(x)
plt.plot(x, dy_dx, 'r-', linewidth=2, label="f'(x) = cos(x²) * 2x")
plt.grid(True, alpha=0.3)
plt.xlabel('x')
plt.ylabel("f'(x)")
plt.title('연쇄법칙을 이용한 미분')
plt.legend()
plt.tight_layout()
plt.show()
print("연쇄법칙의 직관적 의미:")
print(f"f(x) = sin(x²)에서 x = {x_test}일 때:")
print(f" 해석적 미분: f'({x_test}) = {analytical:.6f}")
print(f" 수치 미분: f'({x_test}) ≈ {numerical:.6f}")
print(f" 오차: {abs(analytical - numerical):.2e}")
print()
print("연쇄법칙: d/dx[sin(x²)] = cos(x²) * d/dx[x²] = cos(x²) * 2x")
def simple_neural_network():
"""9. 간단한 신경망의 역전파"""
print("\n=== 9. 간단한 신경망 역전파 ===")
# 간단한 신경망: 입력 -> 은닉층 -> 출력
# f(x) = w2 * sigmoid(w1 * x + b1) + b2
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(x):
return sigmoid(x) * (1 - sigmoid(x))
# 가중치 초기화
w1, b1 = 2.0, 1.0
w2, b2 = 3.0, 0.5
def forward(x):
"""순전파"""
z1 = w1 * x + b1
a1 = sigmoid(z1)
z2 = w2 * a1 + b2
return z2, a1, z1
def backward(x, target, learning_rate=0.1):
"""역전파 (연쇄법칙)"""
# 순전파
output, a1, z1 = forward(x)
# 오차
error = output - target
# 역전파 (연쇄법칙)
# ∂E/∂w2 = ∂E/∂output * ∂output/∂w2
dE_dw2 = error * a1
# ∂E/∂w1 = ∂E/∂output * ∂output/∂a1 * ∂a1/∂z1 * ∂z1/∂w1
dE_dw1 = error * w2 * sigmoid_derivative(z1) * x
# ∂E/∂b1 = ∂E/∂output * ∂output/∂a1 * ∂a1/∂z1 * ∂z1/∂b1
dE_db1 = error * w2 * sigmoid_derivative(z1)
# ∂E/∂b2 = ∂E/∂output * ∂output/∂b2
dE_db2 = error
return dE_dw1, dE_db1, dE_dw2, dE_db2
# 학습 과정 시각화
x_train = np.array([0.5, 1.0, 1.5])
y_train = np.array([2.0, 3.0, 4.0])
print("간단한 신경망 학습 과정:")
print("-" * 50)
for epoch in range(5):
total_error = 0
for x, target in zip(x_train, y_train):
output, _, _ = forward(x)
error = output - target
total_error += error**2
# 그래디언트 계산
dw1, db1, dw2, db2 = backward(x, target)
print(f"입력: {x}, 목표: {target}, 출력: {output:.4f}, 오차: {error:.4f}")
print(f" 그래디언트: dw1={dw1:.4f}, db1={db1:.4f}, dw2={dw2:.4f}, db2={db2:.4f}")
print(f"에포크 {epoch+1}: 평균 오차 = {total_error/len(x_train):.4f}")
print()
def linear_regression_gradient_descent():
"""10. 선형 회귀의 경사 하강법"""
print("\n=== 10. 선형 회귀 경사 하강법 ===")
# 데이터 생성
np.random.seed(42)
X = np.random.rand(100, 1) * 10
y = 3 * X + 2 + np.random.randn(100, 1) * 0.5
# 모델: y = w * x + b
w, b = 0.0, 0.0
learning_rate = 0.01
epochs = 100
# 학습 과정 기록
w_history = [w]
b_history = [b]
loss_history = []
for epoch in range(epochs):
# 예측
y_pred = w * X + b
# 손실 함수 (MSE)
loss = np.mean((y_pred - y)**2)
loss_history.append(loss)
# 그래디언트 계산
dw = np.mean(2 * (y_pred - y) * X)
db = np.mean(2 * (y_pred - y))
# 파라미터 업데이트
w = w - learning_rate * dw
b = b - learning_rate * db
w_history.append(w)
b_history.append(b)
# 결과 시각화
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
# 데이터와 학습된 선
ax1.scatter(X, y, alpha=0.6, label='데이터')
X_line = np.linspace(0, 10, 100).reshape(-1, 1)
y_line = w * X_line + b
ax1.plot(X_line, y_line, 'r-', linewidth=2, label=f'학습된 선: y = {w:.2f}x + {b:.2f}')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_title('선형 회귀 결과')
ax1.legend()
ax1.grid(True, alpha=0.3)
# 손실 함수 변화
ax2.plot(loss_history, 'b-', linewidth=2)
ax2.set_xlabel('에포크')
ax2.set_ylabel('손실 (MSE)')
ax2.set_title('손실 함수 수렴')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("선형 회귀 학습 결과:")
print(f"학습된 가중치: w = {w:.4f}")
print(f"학습된 편향: b = {b:.4f}")
print(f"실제 값: w = 3.0, b = 2.0")
print(f"최종 손실: {loss_history[-1]:.6f}")
def compare_optimizers():
"""11. 다양한 최적화 알고리즘 비교"""
print("\n=== 11. 최적화 알고리즘 비교 ===")
def objective_function(x):
return x**2 + 2*x + 1
def gradient(x):
return 2*x + 2
# 최적화 알고리즘들
def gradient_descent(x0, learning_rate=0.1, epochs=50):
x = x0
history = [x]
for _ in range(epochs):
x = x - learning_rate * gradient(x)
history.append(x)
return history
def momentum_gradient_descent(x0, learning_rate=0.1, momentum=0.9, epochs=50):
x = x0
velocity = 0
history = [x]
for _ in range(epochs):
velocity = momentum * velocity - learning_rate * gradient(x)
x = x + velocity
history.append(x)
return history
# 최적화 실행
x0 = 5.0
gd_history = gradient_descent(x0)
mgd_history = momentum_gradient_descent(x0)
# 시각화
x_plot = np.linspace(-1, 6, 100)
y_plot = objective_function(x_plot)
plt.figure(figsize=(12, 8))
# 목적 함수
plt.subplot(2, 1, 1)
plt.plot(x_plot, y_plot, 'b-', linewidth=2, label='목적 함수: f(x) = x² + 2x + 1')
plt.plot(gd_history, [objective_function(x) for x in gd_history], 'ro-',
markersize=6, label='기본 경사 하강법')
plt.plot(mgd_history, [objective_function(x) for x in mgd_history], 'go-',
markersize=6, label='모멘텀 경사 하강법')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title('최적화 과정 비교')
plt.legend()
plt.grid(True, alpha=0.3)
# 수렴 과정
plt.subplot(2, 1, 2)
plt.plot([objective_function(x) for x in gd_history], 'r-', linewidth=2, label='기본 경사 하강법')
plt.plot([objective_function(x) for x in mgd_history], 'g-', linewidth=2, label='모멘텀 경사 하강법')
plt.xlabel('에포크')
plt.ylabel('f(x)')
plt.title('손실 함수 수렴 비교')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("최적화 알고리즘 비교:")
print(f"기본 경사 하강법 최종값: {gd_history[-1]:.6f}")
print(f"모멘텀 경사 하강법 최종값: {mgd_history[-1]:.6f}")
print(f"실제 최솟값: -1.0")
def main():
"""메인 함수: 모든 시각화 실행"""
print("🚀 미적분 직관적 이해 - 시각화 중심 학습")
print("=" * 60)
# 1. 미분 시각화
visualize_derivatives()
# 2. 여러 점에서의 미분
visualize_multiple_derivatives()
# 3. 수치 vs 해석적 미분
numerical_vs_analytical_derivatives()
# 4. 기울기 하강법
gradient_descent_visualization()
# 5. 학습률 비교
compare_learning_rates()
# 6. 편미분
partial_derivatives_visualization()
# 7. 그래디언트 벡터
gradient_vector_visualization()
# 8. 연쇄법칙
chain_rule_visualization()
# 9. 신경망 역전파
simple_neural_network()
# 10. 선형 회귀
linear_regression_gradient_descent()
# 11. 최적화 알고리즘 비교
compare_optimizers()
print("\n🎉 미적분 학습 완료!")
print("핵심 메시지: 복잡한 수식보다는 직관적 이해와 실제 구현에 집중하세요!")
if __name__ == "__main__":
main()
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment