Commit 932f390a authored by insun park's avatar insun park
Browse files

강의 자료 업데이트 및 용어집 정리

parent 69e437d8
# 핵심 용어집 (Glossary)
AI Lecture 시리즈 전반에 걸쳐 사용되는 주요 기술 용어들을 알파벳 순으로 정리했습니다.
AI Lecture 시리즈 전반에 걸쳐 사용되는 주요 기술 용어들을 가나다 순으로 정리했습니다.
---
### A
###
- **API (Application Programming Interface)**
- **정의**: 두 소프트웨어 구성 요소가 서로 통신할 수 있게 하는 메커니즘. 웹 API는 클라이언트(예: 웹 브라우저, 모바일 앱)가 서버의 기능이나 데이터에 접근할 수 있도록 미리 정해놓은 규칙의 집합입니다.
- **비유**: 식당의 메뉴판. 손님(클라이언트)은 메뉴판(API)을 보고 주문(요청)할 수 있으며, 주방(서버)은 정해진 메뉴에 따라 음식을 만들어 제공(응답)합니다.
- **관련 파트**: [Part 6](part_6_model_serving_with_fastapi.md), [Part 7](part_7_production_ready_api.md)
- **검색 증강 생성 (Retrieval-Augmented Generation, RAG)**
- **정의**: 대규모 언어 모델(LLM)이 답변을 생성하기 전에, 먼저 외부 지식 소스(예: 데이터베이스, 문서)에서 관련된 정보를 검색하고, 이 정보를 참고하여 답변을 생성하는 방법론. LLM의 환각(Hallucination) 현상을 줄이고, 최신 정보나 특정 도메인의 지식을 기반으로 정확하고 신뢰도 높은 답변을 생성하도록 돕습니다.
- **관련 파트**: [Part 7.5: LLM 애플리케이션 개발과 LangChain](./part_7.5_llm_application_development_with_langchain.md)
- **ASGI (Asynchronous Server Gateway Interface)**
- **정의**: 비동기 파이썬 웹 서버와 프레임워크(예: FastAPI, Starlette)가 통신하기 위한 표준 인터페이스. 많은 수의 I/O 바운드 작업을 동시에 효율적으로 처리하는 데 강점이 있습니다.
- **비교**: WSGI(Web Server Gateway Interface)는 동기식 파이썬 웹 프레임워크(예: Flask, Django)를 위한 전통적인 인터페이스입니다. ASGI는 WSGI의 후속 버전으로 비동기 기능을 지원합니다.
- **관련 파트**: [Part 6](part_6_model_serving_with_fastapi.md)
- **과적합 (Overfitting)**
- **정의**: 머신러닝 모델이 훈련 데이터에만 지나치게 최적화되어, 새로운 데이터(테스트 데이터)에 대해서는 예측 성능이 떨어지는 현상. 훈련 데이터의 사소한 노이즈까지 모두 외워버려 일반화 성능을 잃는 것을 의미합니다.
- **관련 파트**: [Part 6: 머신러닝 완전 정복](./part_6_machine_learning.md), [Part 7: 딥러닝 첫걸음](./part_7_deep_learning.md)
### D
- **군집화 (Clustering)**
- **정의**: 비지도 학습의 한 종류로, 정답(레이블)이 없는 데이터 내에서 비슷한 특성을 가진 샘플끼리 그룹으로 묶는 작업입니다. 데이터의 숨겨진 구조를 파악하거나 고객을 유형별로 분류하는 데 사용됩니다.
- **관련 파트**: [Part 6: 머신러닝 완전 정복](./part_6_machine_learning.md)
- **Dependency Injection (의존성 주입)**
- **정의**: 객체가 필요로 하는 다른 객체(의존성)를 외부에서 직접 생성하여 전달하는 디자인 패턴. 객체 스스로 의존성을 생성하지 않으므로, 코드의 결합도(Coupling)를 낮추고 유연성과 테스트 용이성을 높입니다.
- **예시**: FastAPI에서 `Depends`를 사용하여 데이터베이스 세션(`db: Session = Depends(get_db)`)을 라우터 함수에 주입하는 것. 이를 통해 라우터 함수는 DB 세션 생성 방법을 알 필요 없이 사용에만 집중할 수 있습니다.
- **관련 파트**: [Part 7](part_7_production_ready_api.md)
### ㄴ
- **NumPy (넘파이)**
- **정의**: Numerical Python의 약자로, 파이썬에서 고성능 과학 계산과 수치 연산을 위한 핵심 라이브러리. `ndarray`라는 효율적인 다차원 배열 객체를 제공하여, 벡터 및 행렬 연산을 매우 빠르게 처리할 수 있습니다. Pandas, Scikit-learn, PyTorch 등 대부분의 AI 라이브러리의 기반이 되는 기술입니다.
- **비유**: '계란판'. 모든 데이터를 동일한 타입의 숫자(계란)로 취급하여, '모든 계란에 도장 찍기'와 같은 동일한 연산을 매우 빠른 속도로 처리할 수 있습니다.
- **관련 파트**: [Part 5: AI 핵심 라이브러리](./part_5_ai_core_libraries.md)
### ㄷ
- **대규모 언어 모델 (Large Language Model, LLM)**
- **정의**: 수십억 개 이상의 파라미터와 방대한 텍스트 데이터를 기반으로 사전 훈련된 거대한 인공 신경망 모델. 번역, 요약, 질의응답, 코드 생성 등 다양한 자연어 처리 작업에서 뛰어난 성능을 보이며, 파운데이션 모델의 대표적인 예시입니다.
- **관련 파트**: [Part 7.5: LLM 애플리케이션 개발과 LangChain](./part_7.5_llm_application_development_with_langchain.md)
- **데이터 전처리 (Data Preprocessing)**
- **정의**: "Garbage In, Garbage Out" 원칙에 따라, 머신러닝 모델의 성능을 높이기 위해 원시 데이터(raw data)를 학습에 적합한 형태로 가공하는 모든 작업. 결측치 처리, 이상치 탐지, 특성 스케일링, 범주형 데이터 인코딩 등이 포함됩니다.
- **관련 파트**: [Part 6: 머신러닝 완전 정복](./part_6_machine_learning.md)
- **도커 (Docker)**
- **정의**: 애플리케이션과 그 실행에 필요한 모든 것(라이브러리, 시스템 도구, 코드, 런타임 등)을 '컨테이너'라는 격리된 공간에 패키징하여, 어떤 환경에서든 동일하게 실행될 수 있도록 하는 플랫폼. "제 컴퓨터에서는 되는데요?" 문제를 해결하는 핵심 기술입니다.
- **비유**: '어디서든 똑같은 맛을 내는 밀키트'. 애플리케이션의 모든 재료와 레시피를 밀키트(이미지)로 만들어, 어떤 주방(서버)에서 조리하든 항상 동일한 결과물을 보장합니다.
- **관련 파트**: [Part 1: AI 개발 환경 완벽 구축 가이드](./part_1_ai_development_environment.md), [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)
- **도커 컴포즈 (Docker Compose)**
- **정의**: 여러 개의 도커 컨테이너를 하나의 `yml` 파일로 정의하고, 명령어 하나로 실행하고 관리할 수 있게 해주는 도구. API 서버, 데이터베이스, 웹서버 등 여러 서비스가 함께 동작하는 복잡한 애플리케이션을 손쉽게 오케스트레이션할 수 있습니다.
- **관련 파트**: [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)
- **도커파일 (Dockerfile)**
- **정의**: 도커 이미지를 만들기 위한 '설계도' 또는 '레시피'. 기반이 될 OS 이미지, 설치할 라이브러리, 복사할 소스 코드, 실행할 명령어 등 컨테이너가 어떻게 구성되어야 하는지에 대한 지침을 텍스트 파일에 순서대로 기록합니다. `docker build` 명령어를 통해 이 파일을 읽어 이미지를 생성합니다.
- **관련 파트**: [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)
### ㄹ
- **LangChain (랭체인)**
- **정의**: 대규모 언어 모델(LLM)을 활용한 애플리케이션 개발을 위한 오픈소스 프레임워크. LLM을 외부 데이터 소스, API 등과 쉽게 연결(Chaining)하여 복잡한 작업을 수행하는 파이프라인을 구축할 수 있도록 돕는 다양한 도구와 컴포넌트를 제공합니다.
- **관련 파트**: [Part 7.5: LLM 애플리케이션 개발과 LangChain](./part_7.5_llm_application_development_with_langchain.md)
- **LangChain Expression Language (LCEL)**
- **정의**: LangChain의 각 컴포넌트(프롬프트, 모델, 출력 파서 등)를 `|`(파이프) 기호로 마치 리눅스 셸 명령어처럼 자연스럽게 연결할 수 있게 해주는 선언적 방식의 구문. 복잡한 체인을 더 직관적이고 가독성 높게 구성할 수 있게 하며, 스트리밍, 병렬 처리 등을 쉽게 구현할 수 있도록 돕습니다.
- **관련 파트**: [Part 10: 전문가로 가는 길](./part_10_expert_path.md)
### ㅁ
- **머신러닝 학습 3대 유형**
- **정의**: 머신러닝은 학습 방식에 따라 크게 지도, 비지도, 강화 학습의 세 가지 유형으로 나뉩니다.
- **지도 학습 (Supervised Learning)**: '문제(입력)'와 '정답(레이블)'이 모두 있는 데이터를 사용하여, 입력과 정답 사이의 관계를 학습합니다. (예: 사진을 보고 '고양이'인지 '개'인지 맞추기) **분류****회귀**가 대표적입니다.
- **비지도 학습 (Unsupervised Learning)**: '정답' 없이 데이터 자체의 숨겨진 구조, 패턴, 유사성을 파악합니다. (예: 고객 데이터를 보고 비슷한 성향의 그룹으로 나누기) **군집화**가 대표적입니다.
- **강화 학습 (Reinforcement Learning)**: 에이전트가 환경과 상호작용하며 '보상'을 최대로 얻는 방향으로 행동을 학습합니다. (예: 시행착오를 통해 미로를 탈출하는 법을 배우는 로봇)
- **관련 파트**: [Part 6: 머신러닝 완전 정복](./part_6_machine_learning.md)
- **미세조정 (Fine-tuning)**
- **정의**: 방대한 데이터로 사전 훈련된 파운데이션 모델을 특정 도메인이나 특정 작업에 맞게 소량의 데이터로 추가 학습시키는 과정. 모델을 처음부터 훈련하는 것보다 훨씬 적은 비용과 데이터로 높은 성능을 얻을 수 있는 효과적인 방법입니다.
- **관련 파트**: [Part 7.5: LLM 애플리케이션 개발과 LangChain](./part_7.5_llm_application_development_with_langchain.md), [Part 10: 전문가로 가는 길](./part_10_expert_path.md)
### ㅂ
- **보상 해킹 (Reward Hacking)**
- **정의**: 강화학습에서 AI 에이전트가 설계된 보상 시스템의 허점을 악용하여, 의도된 목표를 달성하지 않고도 보상을 극대화하는 방법을 찾아내는 현상. AI의 기만적인 행동이나 예상치 못한 부작용을 일으킬 수 있어 AI 안전 분야의 주요 연구 주제입니다.
- **관련 파트**: [Part 10: 전문가로 가는 길](./part_10_expert_path.md)
- **벡터 저장소 (Vector Store)**
- **정의**: 텍스트, 이미지 등과 같은 데이터를 고차원의 벡터(Vector)로 변환하여 저장하고, 특정 벡터와 유사한 벡터를 빠르고 효율적으로 검색할 수 있도록 설계된 데이터베이스. RAG 시스템에서 문서 Chunk들의 임베딩 벡터를 저장하고, 사용자 질문과 관련된 Chunk를 찾는 데 핵심적인 역할을 합니다.
- **관련 파트**: [Part 7.5: LLM 애플리케이션 개발과 LangChain](./part_7.5_llm_application_development_with_langchain.md)
- **분류 (Classification)**
- **정의**: 지도 학습의 한 종류로, 데이터를 미리 정해진 여러 개의 카테고리(클래스) 중 하나로 예측하는 작업입니다. (예: 스팸/정상 메일 분류, 품종 분류)
- **관련 파트**: [Part 6: 머신러닝 완전 정복](./part_6_machine_learning.md)
### ㅅ
- **사고의 연쇄 (Chain-of-Thought, CoT)**
- **정의**: 대규모 언어 모델(LLM)이 복잡한 추론 문제를 해결할 때, 최종 답변뿐만 아니라 그 답변에 도달하기까지의 중간 단계들을 명시적으로 생성하도록 유도하는 프롬프팅 기법. 모델의 추론 과정을 투명하게 만들고, 더 정확한 답변을 얻는 데 도움을 줍니다.
- **관련 파트**: [Part 10: 전문가로 가는 길](./part_10_expert_path.md)
- **Scikit-learn (사이킷런)**
- **정의**: 파이썬의 대표적인 머신러닝 라이브러리. 회귀, 분류, 군집화 등 다양한 머신러닝 알고리즘과 데이터 전처리, 모델 평가 도구를 제공하여 사용자가 쉽게 모델을 개발할 수 있도록 돕습니다.
- **관련 파트**: [Part 5: AI 핵심 라이브러리](./part_5_ai_core_libraries.md), [Part 6: 머신러닝 완전 정복](./part_6_machine_learning.md)
- **손실 함수 (Loss Function)**
- **정의**: 딥러닝 모델의 예측 값이 실제 정답과 얼마나 다른지를 나타내는 '오차'를 계산하는 함수. 모델은 이 손실 함수의 값을 최소화하는 방향으로 가중치를 업데이트하며 학습을 진행합니다.
- **비유**: '목표 지점과의 거리 측정기'. 모델이 쏜 화살이 과녁(정답)에서 얼마나 빗나갔는지를 측정하여, 다음번엔 어느 방향으로 조준해야 할지 알려주는 역할을 합니다.
- **관련 파트**: [Part 7: 딥러닝 첫걸음](./part_7_deep_learning.md)
- **신경망 (Neural Network)**
- **정의**: 인간의 뇌에 있는 뉴런의 연결 구조를 모방하여 만든 수학적 모델. 여러 개의 계층(Layer)으로 구성되며, 각 계층의 수많은 노드(뉴런)들이 서로 연결되어 데이터 속의 복잡한 패턴을 학습합니다. 딥러닝의 핵심이 되는 모델 구조입니다.
- **관련 파트**: [Part 7: 딥러닝 첫걸음](./part_7_deep_learning.md)
- **SQLAlchemy (SQL알케미)**
- **정의**: 파이썬의 대표적인 ORM(Object-Relational Mapping) 라이브러리. 개발자가 SQL 쿼리를 직접 작성하지 않고, 파이썬 클래스(모델)와 객체를 통해 데이터베이스의 테이블과 레코드를 조작할 수 있도록 해주는 강력한 도구입니다.
- **관련 파트**: [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)
- **Swagger UI / OpenAPI**
- **정의**: OpenAPI Specification(과거 Swagger Specification)은 RESTful API를 명세하고 기술하기 위한 표준 형식입니다. Swagger UI는 이 명세서를 바탕으로, 개발자가 브라우저에서 직접 API 문서를 확인하고 상호작용(테스트)할 수 있는 아름다운 웹 인터페이스를 자동으로 생성해주는 도구입니다. FastAPI는 이 기능을 기본적으로 내장하고 있습니다.
- **비유**: '스마트 메뉴판'. 모든 메뉴(엔드포인트)와 필요한 재료(요청), 나오는 요리(응답)를 보여줄 뿐만 아니라, `Try it out` 버튼으로 그 자리에서 바로 맛보기 주문을 넣어 결과를 확인할 수 있습니다.
- **관련 파트**: [Part 8: FastAPI를 이용한 모델 서빙](./part_8_model_serving_with_fastapi.md)
### ㅇ
- **역전파 (Backpropagation)**
- **정의**: 신경망 학습의 핵심 알고리즘. 출력층에서 계산된 손실(오차)을 입력층 방향으로 거꾸로 전파시키면서, 각 가중치(파라미터)가 오차에 얼마나 기여했는지를 계산하고, 이 정보를 바탕으로 옵티마이저가 가중치를 업데이트하도록 하는 방식입니다.
- **비유**: '팀 프로젝트 과제 피드백'. 최종 결과물(출력)에 대한 교수님의 피드백(오차)을 받고, 각 팀원이 어떤 부분에서 실수를 했는지(기여도) 역으로 추적하여 각자의 역할을 수정(가중치 업데이트)하는 과정과 같습니다.
- **관련 파트**: [Part 7: 딥러닝 첫걸음](./part_7_deep_learning.md)
- **에이전트 AI (Agent AI)**
- **정의**: 주어진 목표를 달성하기 위해 스스로 **계획을 세우고, 도구를 사용하며, 주변 환경과 상호작용**하는 등 자율적으로 행동하는 AI 시스템. 단순한 예측 모델을 넘어, 복잡한 작업을 자율적으로 수행하는 주체로서의 AI를 의미합니다.
- **관련 파트**: [Part 10: 전문가로 가는 길](./part_10_expert_path.md)
- **에이전틱 워크플로우 (Agentic Workflow)**
- **정의**: AI 에이전트가 목표를 달성하기 위해 사용하는 **반복적이고 순환적인 작업 방식**. 단순히 입력을 받아 출력을 내는 단방향 프로세스가 아니라, **[생각 → 행동 → 관찰 → 다음 행동 계획]**의 사이클을 반복하며 스스로 결과를 개선해 나가는 과정을 의미합니다.
- **관련 파트**: [Part 0: 시작하며](./part_0_introduction.md), [Part 10: 전문가로 가는 길](./part_10_expert_path.md)
- **Ollama (오라마)**
- **정의**: LLaMA, Mistral 등 다양한 오픈소스 소형 언어 모델(SLM)을 개인 컴퓨터에서 쉽고 빠르게 설치하고 실행할 수 있도록 도와주는 도구. `Modelfile`이라는 간단한 설정 파일을 통해 모델을 커스터마이징하고, 로컬 환경에서 API 서버를 통해 모델을 바로 활용할 수 있습니다.
- **관련 파트**: [Part 10: 전문가로 가는 길](./part_10_expert_path.md)
### E
- **옵티마이저 (Optimizer)**
- **정의**: 손실 함수가 계산한 오차를 기반으로, 모델의 파라미터(가중치)를 가장 효율적인 방향으로 업데이트하는 알고리즘. 경사 하강법(Gradient Descent)을 기반으로 하며, Adam, SGD, RMSprop 등 다양한 종류가 있습니다.
- **비유**: '산을 내려오는 등산가'. 손실 함수라는 '산의 고도 지도'를 보고, 현재 위치에서 경사가 가장 가파른 방향(오차를 가장 빨리 줄이는 방향)으로 한 걸음씩 나아가 산의 가장 낮은 지점(최적의 가중치)을 찾아가는 역할을 합니다.
- **관련 파트**: [Part 7: 딥러닝 첫걸음](./part_7_deep_learning.md)
- **Endpoint (엔드포인트)**
- **엔드포인트 (Endpoint)**
- **정의**: API가 리소스에 접근할 수 있도록 제공하는 최종 통신 지점의 URL. 각 엔드포인트는 특정 기능(예: 사용자 정보 조회, 상품 추가)과 연결됩니다.
- **예시**: `https://api.example.com/users/123` 에서 `/users/123` 부분이 엔드포인트입니다.
- **관련 파트**: [Part 6](part_6_model_serving_with_fastapi.md)
- **관련 파트**: [Part 8: FastAPI를 이용한 모델 서빙](./part_8_model_serving_with_fastapi.md)
### H
- **의존성 주입 (Dependency Injection)**
- **정의**: 객체가 필요로 하는 다른 객체(의존성)를 외부에서 직접 생성하여 전달하는 디자인 패턴. 객체 스스로 의존성을 생성하지 않으므로, 코드의 결합도(Coupling)를 낮추고 유연성과 테스트 용이성을 높입니다.
- **예시**: FastAPI에서 `Depends`를 사용하여 데이터베이스 세션(`db: Session = Depends(get_db)`)을 라우터 함수에 주입하는 것. 이를 통해 라우터 함수는 DB 세션 생성 방법을 알 필요 없이 사용에만 집중할 수 있습니다.
- **관련 파트**: [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)
- **HTTP (HyperText Transfer Protocol)**
- **정의**: 클라이언트와 서버가 웹에서 데이터를 주고받기 위해 사용하는 통신 규약. `GET`(조회), `POST`(생성), `PUT`(수정), `DELETE`(삭제)와 같은 메서드를 사용하여 특정 작업을 요청합니다.
- **관련 파트**: [Part 6](part_6_model_serving_with_fastapi.md)
- **Uvicorn (유비콘)**
- **정의**: FastAPI와 같은 ASGI 표준을 따르는 파이썬 웹 프레임워크를 위한 고성능 비동기 서버. '서버 매니저' 또는 '서버 게이트웨이' 역할을 하며, 실제 운영 환경에서는 Gunicorn과 함께 사용하여 여러 프로세스를 관리하며 안정성과 성능을 높이는 경우가 많습니다.
- **관련 파트**: [Part 8: FastAPI를 이용한 모델 서빙](./part_8_model_serving_with_fastapi.md)
### O
- **임베딩 (Embedding)**
- **정의**: 텍스트와 같은 이산적인 데이터를 컴퓨터가 처리하기 쉬운 연속적인 숫자 벡터(Vector)로 변환하는 과정 또는 그 결과물. 단어, 문장, 문서의 의미적인 관계를 벡터 공간에 표현하여, 단어 간 유사도 계산이나 기계 학습 모델의 입력값으로 활용됩니다. RAG에서는 문서 Chunk와 사용자 질문을 임베딩하여 의미적으로 유사한 정보를 찾습니다.
- **관련 파트**: [Part 7.5: LLM 애플리케이션 개발과 LangChain](./part_7.5_llm_application_development_with_langchain.md)
- **ORM (Object-Relational Mapping)**
- **정의**: 객체 지향 프로그래밍 언어(예: 파이썬)의 객체(Class)와 관계형 데이터베이스(RDB)의 테이블을 자동으로 매핑(연결)해주는 기술. 개발자는 SQL 쿼리를 직접 작성하는 대신, 익숙한 프로그래밍 언어의 코드로 데이터베이스를 조작할 수 있습니다.
- **예시**: SQLAlchemy 라이브러리를 사용하여 파이썬 클래스로 DB 테이블을 정의하고, 파이썬 코드로 데이터를 추가/조회하는 것.
- **관련 파트**: [Part 7](part_7_production_ready_api.md)
### ㅊ
- **Chunking (청킹)**
- **정의**: RAG 시스템에서 LLM이 효율적으로 처리하고 검색할 수 있도록, 긴 문서를 의미 있는 작은 조각(Chunk)으로 나누는 과정. 어떻게 Chunk를 나누는지에 따라 RAG 시스템 전체의 검색 성능과 답변 품질이 크게 달라지기 때문에 매우 중요한 단계입니다.
- **관련 파트**: [Part 7.5: LLM 애플리케이션 개발과 LangChain](./part_7.5_llm_application_development_with_langchain.md)
### ㅌ
- **특성 스케일링 (Feature Scaling)**
- **정의**: 여러 특성(Feature)들의 값의 범위(스케일)를 일정하게 맞추는 데이터 전처리 작업. '키'와 '나이'처럼 스케일이 다른 특성들이 모델에 미치는 영향을 동등하게 만들어 모델의 학습 성능과 안정성을 높입니다. `StandardScaler`가 대표적인 예입니다.
- **관련 파트**: [Part 6: 머신러닝 완전 정복](./part_6_machine_learning.md)
### ㅍ
- **Pandas (판다스)**
- **정의**: 파이썬에서 표 형식의 데이터를 다루기 위한 필수 라이브러리. `DataFrame`(2차원 테이블)과 `Series`(1차원 배열)라는 강력하고 직관적인 자료구조를 제공하여, CSV, Excel, DB 등 다양한 소스로부터 데이터를 읽고 쓰고, 정제하고, 분석하는 작업을 매우 효율적으로 수행할 수 있게 해줍니다.
- **비유**: '슈퍼 엑셀'. 엑셀이 할 수 있는 대부분의 데이터 처리 작업을 파이썬 코드를 통해 자동화하고, 훨씬 더 대용량의 데이터를 자유자재로 다룰 수 있게 해주는 도구입니다.
- **관련 파트**: [Part 5: AI 핵심 라이브러리](./part_5_ai_core_libraries.md)
### P
- **FastAPI (패스트API)**
- **정의**: 현대적인 고성능 파이썬 웹 프레임워크. 파이썬 3.7+의 타입 힌트(Type Hint)를 기반으로 하며, ASGI를 지원하여 매우 빠른 속도를 자랑합니다. Pydantic을 이용한 자동 데이터 유효성 검사와 자동으로 생성되는 대화형 API 문서(Swagger UI)가 특징입니다.
- **관련 파트**: [Part 8: FastAPI를 이용한 모델 서빙](./part_8_model_serving_with_fastapi.md), [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)
- **Pydantic**
- **파운데이션 모델 (Foundation Model)**
- **정의**: 방대한 양의 데이터로 사전 훈련되어, 특정 작업에 국한되지 않고 다양한 다운스트림 작업(downstream task)에 적용될 수 있는 범용적인 대규모 AI 모델. GPT-3, LLaMA 등이 대표적인 예이며, 약간의 미세조정(fine-tuning)이나 프롬프트 엔지니어링만으로 다양한 문제를 해결할 수 있는 잠재력을 가집니다.
- **관련 파트**: [Part 7.5: LLM 애플리케이션 개발과 LangChain](./part_7.5_llm_application_development_with_langchain.md)
- **PyTorch (파이토치)**
- **정의**: 페이스북(Meta AI)에서 개발한 오픈소스 딥러닝 프레임워크. 동적 계산 그래프를 지원하여 유연하고 직관적인 모델 구현이 가능하며, 연구 및 개발 커뮤니티에서 널리 사용됩니다.
- **관련 파트**: [Part 7: 딥러닝 첫걸음](./part_7_deep_learning.md)
- **프롬프트 엔지니어링 (Prompt Engineering)**
- **정의**: 대규모 언어 모델(LLM)로부터 원하는 최상의 결과를 얻어내기 위해, 모델에 입력하는 지시문(프롬프트)을 최적화하고 설계하는 기술 또는 과정. 질문의 형식, 예시(Few-shot), 역할 부여 등 다양한 기법을 통해 모델의 성능을 크게 향상시킬 수 있습니다.
- **관련 파트**: [Part 7.5: LLM 애플리케이션 개발과 LangChain](./part_7.5_llm_application_development_with_langchain.md)
- **Pydantic (파이댄틱)**
- **정의**: 파이썬의 타입 힌트(Type Hint)를 사용하여 데이터의 유효성을 검사하고 설정을 관리하는 라이브러리. FastAPI에서 요청 및 응답 데이터의 형식을 정의하고 자동으로 검증하는 데 핵심적인 역할을 합니다.
- **장점**: 개발자가 직접 데이터 검증 코드를 작성할 필요 없이, 모델 클래스를 정의하는 것만으로 복잡한 데이터 유효성 검사를 수행할 수 있어 코드의 안정성과 가독성을 크게 향상시킵니다.
- **관련 파트**: [Part 7](part_7_production_ready_api.md)
\ No newline at end of file
- **관련 파트**: [Part 8: FastAPI를 이용한 모델 서빙](./part_8_model_serving_with_fastapi.md), [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)
### ㅎ
- **합성곱 신경망 (CNN, Convolutional Neural Network)**
- **정의**: 이미지 데이터 처리에 특화된 딥러닝 모델. 인간의 시신경을 모방한 필터(Filter/Kernel)를 사용하여 이미지의 공간적인 특징(모서리, 질감 등)을 효과적으로 추출하고 학습합니다.
- **관련 파트**: [Part 7: 딥러닝 첫걸음](./part_7_deep_learning.md)
- **혼동 행렬 (Confusion Matrix)**
- **정의**: 분류 모델의 성능을 평가하기 위한 표. 실제 클래스와 모델이 예측한 클래스를 비교하여, 모델이 어떤 유형의 오류를 범하는지(e.g., '정상'을 '스팸'으로 잘못 예측) 직관적으로 보여줍니다. 정확도(Accuracy)뿐만 아니라 정밀도(Precision), 재현율(Recall) 등을 계산하는 기반이 됩니다.
- **관련 파트**: [Part 6: 머신러닝 완전 정복](./part_6_machine_learning.md)
- **환각 (Hallucination)**
- **정의**: 대규모 언어 모델(LLM)이 사실이 아니거나 맥락에 맞지 않는 정보를 그럴듯하게 사실처럼 생성하는 현상. 모델의 내적 지식에만 의존할 때 발생하기 쉬우며, RAG는 이러한 환각 현상을 완화하는 효과적인 방법 중 하나입니다.
- **관련 파트**: [Part 7.5: LLM 애플리케이션 개발과 LangChain](./part_7.5_llm_application_development_with_langchain.md)
- **활성화 함수 (Activation Function)**
- **정의**: 신경망에서 이전 레이어로부터 전달받은 신호의 총합을 다음 레이어로 전달할지 여부를 결정하는 함수. 선형적인 계산 결과에 비선형성(non-linearity)을 추가하여, 신경망이 더 복잡하고 다양한 패턴을 학습할 수 있도록 하는 핵심적인 역할을 합니다. ReLU, Sigmoid, Tanh 등이 대표적입니다.
- **관련 파트**: [Part 7: 딥러닝 첫걸음](./part_7_deep_learning.md)
- **회귀 (Regression)**
- **정의**: 지도 학습의 한 종류로, '주택 가격', '주가', '온도'처럼 연속적인 숫자 값을 예측하는 문제입니다.
- **관련 파트**: [Part 6: 머신러닝 완전 정복](./part_6_machine_learning.md)
---
### Alphabet
- **API (Application Programming Interface)**
- **정의**: 두 소프트웨어 구성 요소가 서로 통신할 수 있게 하는 메커니즘. 웹 API는 클라이언트(예: 웹 브라우저, 모바일 앱)가 서버의 기능이나 데이터에 접근할 수 있도록 미리 정해놓은 규칙의 집합입니다.
- **비유**: 식당의 메뉴판. 손님(클라이언트)은 메뉴판(API)을 보고 주문(요청)할 수 있으며, 주방(서버)은 정해진 메뉴에 따라 음식을 만들어 제공(응답)합니다.
- **관련 파트**: [Part 8: FastAPI를 이용한 모델 서빙](./part_8_model_serving_with_fastapi.md), [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)
- **ASGI (Asynchronous Server Gateway Interface)**
- **정의**: 비동기 파이썬 웹 서버와 프레임워크(예: FastAPI, Starlette)가 통신하기 위한 표준 인터페이스. 많은 수의 I/O 바운드 작업을 동시에 효율적으로 처리하는 데 강점이 있습니다.
- **비교**: WSGI(Web Server Gateway Interface)는 동기식 파이썬 웹 프레임워크(예: Flask, Django)를 위한 전통적인 인터페이스입니다. ASGI는 WSGI의 후속 버전으로 비동기 기능을 지원합니다.
- **관련 파트**: [Part 8: FastAPI를 이용한 모델 서빙](./part_8_model_serving_with_fastapi.md)
- **CI/CD (Continuous Integration/Continuous Deployment)**
- **정의**: 지속적 통합(CI)과 지속적 배포/전달(CD)을 합친 용어로, 소프트웨어 개발 및 배포 프로세스를 자동화하여 품질과 속도를 높이는 DevOps 문화 및 방법론.
- **CI (지속적 통합)**: 여러 개발자가 작업한 코드를 주기적으로 중앙 레포지토리에 통합(merge)할 때마다, 자동으로 빌드하고 테스트하여 코드의 충돌과 버그를 조기에 발견하는 프로세스.
- **CD (지속적 배포)**: CI를 통과한 코드를 테스트 환경이나 실제 프로덕션 환경까지 자동으로 배포하는 프로세스.
- **비유**: '자동 메뉴 업데이트 시스템'. 새로운 레시피(코드)가 개발되면, 중앙 주방에서 자동으로 맛을 검증(CI)하고, 전국 모든 지점에 레시피를 즉시 배포(CD)하는 것과 같습니다.
- **관련 파트**: [Part 10: 전문가로 가는 길](./part_10_expert_path.md)
- **Google Colab (구글 코랩)**
- **정의**: 구글에서 제공하는 클라우드 기반의 무료 Jupyter Notebook 개발 환경. 별도의 설치 없이 웹 브라우저만으로 파이썬 코드를 작성하고 실행할 수 있으며, 특히 무료로 강력한 GPU/TPU 자원을 활용할 수 있어 머신러닝 및 딥러닝 모델 학습에 매우 유용합니다.
- **관련 파트**: [Part 1: AI 개발 환경 완벽 구축 가이드](./part_1_ai_development_environment.md)
- **MLOps (Machine Learning Operations)**
- **정의**: 머신러닝 모델을 개발(Dev)하고 운영(Ops)하는 전 과정을 통합하고 자동화하여, AI 서비스를 안정적이고 효율적으로 구축, 배포, 유지보수하기 위한 원칙과 실천 방법론. CI/CD, 데이터/모델 버전 관리, 지속적인 모니터링 및 재학습 파이프라인 구축 등이 포함됩니다.
- **비유**: 'AI 레스토랑 프랜차이즈의 중앙 관제 시스템'. 신메뉴 개발, 전국 매장 배포, 품질 모니터링, 레시피 개선까지 모든 과정을 자동화하여 일관된 품질을 유지하고 지속적으로 성장시키는 시스템입니다.
- **관련 파트**: [Part 10: 전문가로 가는 길](./part_10_expert_path.md)
- **ORM (Object-Relational Mapping)**
- **정의**: 객체 지향 프로그래밍 언어(예: 파이썬)의 객체(Class)와 관계형 데이터베이스(RDB)의 테이블을 자동으로 매핑(연결)해주는 기술. 개발자는 SQL 쿼리를 직접 작성하는 대신, 익숙한 프로그래밍 언어의 코드로 데이터베이스를 조작할 수 있습니다.
- **예시**: SQLAlchemy 라이브러리를 사용하여 파이썬 클래스로 DB 테이블을 정의하고, 파이썬 코드로 데이터를 추가/조회하는 것.
- **관련 파트**: [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)
</rewritten_file>
\ No newline at end of file
# Part 0: 시작하며 - AI 서비스 개발, 첫걸음을 떼다
# Part 0: 시작하며 - 12주 완성, AI 서비스 개발 마스터 과정
안녕하세요! "AI Lecture" 시리즈에 오신 것을 환영합니다.
안녕하세요! "AI 서비스 개발 12주 완성" 과정에 오신 것을 환영합니다.
이 시리즈는 프로그래밍 기초를 다지고, AI 모델을 활용하여 실제 동작하는 API 서버를 구축하는 여정을 함께합니다. 막연하게 느껴졌던 'AI 서비스 개발'을 구체적인 코드로 구현하며 자신감을 얻는 것을 목표로 합니다.
AI 서비스를 만드는 것은 마치 **'스스로 생각하고 행동하는 기계'**를 조립하는 것과 같습니다. 처음에는 작은 부품(프로그래밍 기초)을 다루는 법부터 배우고, 점차 엔진(AI 모델)만들어 올리고, 마지막에는 멋진 차체(API 서버)씌워 실제 세상(인터넷)을 달리게 하는 것과 같죠.
## 🏁 최종 목표: 나만의 AI API 서버 구축
이 과정은 프로그래밍 기초부터 시작해, AI 모델을 활용한 고성능 API 서버 구축하고, 최종적으로는 여러분만의 아이디어를 실현하는 캡스톤 프로젝트까지 완료하는 것을 목표로 합니다. 12주 동안 막연하게 느껴졌던 'AI 서비스 개발'의 모든 과정을 구체적인 코드로 구현하며, 현업에 바로 투입될 수 있는 실무 역량을 갖추게 될 것입니다.
우리는 이 시리즈를 통해 **"붓꽃 품종을 예측하는 AI 모델이 내장된 API 서버"** 를 완성하게 될 것입니다. 사용자가 API에 붓꽃의 꽃잎, 꽃받침 정보를 보내면, 서버는 AI 모델을 통해 어떤 품종인지 예측하여 응답해주는 서비스입니다.
## 🏁 최종 목표: 나만의 AI 서비스 포트폴리오 완성
단순한 예제 완성을 넘어, 우리는 이 과정을 통해 **"실제 운영 가능한 수준의 AI 기반 API 서버"**를 구축하고, 이를 응용한 **"캡스톤 프로젝트"**를 완성하여 자신만의 포트폴리오를 만들게 됩니다.
이는 마치 **'레시피만 따라 요리 하나를 만드는 것'****'손님들이 언제든 찾아와 주문할 수 있는 식당 주방을 설계하는 것'**의 차이와 같습니다. 우리는 후자를 목표로 합니다. 언제든 요청에 응답하고, 안정적으로 서비스를 제공하는 '프로의 시스템'을 만드는 경험을 하게 될 것입니다.
이 과정을 통해 여러분은 다음과 같은 결과물을 얻게 됩니다.
- Python 프로그래밍에 대한 깊이 있는 이해
- 실제 작동하는 FastAPI 기반의 API 서버
- AI 모델을 서빙하고 활용하는 방법에 대한 실전 경험
- Docker를 이용한 배포 및 운영의 기초 지식
- **탄탄한 Python 프로그래밍 역량**: 기초 문법부터 객체 지향 설계까지 깊이 있는 이해
- **고성능 AI API 서버**: FastAPI를 기반으로 한 비동기 API 서버 구축 및 운영 능력
- **실전 머신러닝 모델링 경험**: 데이터 전처리, 모델 학습, 평가, 배포까지 전 과정 경험
- **최신 DevOps 지식**: Docker를 이용한 컨테이너화 및 CI/CD 자동화 파이프라인 개념 이해
- **나만의 포트폴리오**: 아이디어를 실제 서비스로 구현한 캡스톤 프로젝트 결과물
## 🙋‍♂️ 대상 독자
강의는 다음과 같은 분들을 위해 만들어졌습니다.
과정은 다음과 같은 분들을 위해 체계적으로 설계되었습니다.
- 프로그래밍은 처음이지만 AI 서비스를 직접 만들어보고 싶은 입문자
- Python 기본 문법은 알지만, 실제 프로젝트 경험이 부족한 학생 또는 주니어 개발자
- AI 모델 개발했지만, 이를 어떻게 서비스로 만들어야 할지 막막한 연구자
- 프로그래밍은 처음이지만 AI 서비스를 직접 만들어보고 싶은 **입문자**
- Python 기본 문법은 알지만, 실제 프로젝트 경험이 부족한 **학생 또는 주니어 개발자**
- AI 모델 개발했지만, 이를 어떻게 서비스로 만들어야 할지 막막한 **연구자 및 데이터 분석가**
## 🗺️ 전체 커리큘럼
## 🗺️ 12주 학습 로드
우리는 다음과 같은 순서로 학습을 진행합니다.
우리는 12주 동안 다음의 로드맵에 따라 체계적으로 학습을 진행합니다. 이 로드맵은 AI 서비스 개발이라는 보물섬을 찾아가는 **'친절한 안내 지도'**입니다. 각 주차별 목표를 따라 한 걸음씩 나아가다 보면, 어느새 유능한 개발자로 성장한 자신을 발견하게 될 것입니다.
```mermaid
graph TD
A[Part 0: 시작하며] --> B[Part 1: AI 개발 환경 준비];
B --> C[Part 2: Python 핵심 문법];
C --> D[Part 3: Python 자료구조];
D --> E[Part 4: 객체 지향 프로그래밍];
E --> F[Part 5: AI 핵심 라이브러리];
F --> G[Part 6: FastAPI로 모델 서빙하기];
G --> H[Part 7: 프로덕션 수준의 API 만들기];
H --> I[Part 8: 전문가로 가는 길];
subgraph "기초 다지기"
B
C
D
E
subgraph "1부: 파이썬 기초 다지기 (1-4주)"
W1["<a href='part_1_ai_development_environment.md'><b>1주차</b>: 과정 소개 및<br/>개발 환경 설정</a>"]
W2["<a href='part_2_python_core_syntax.md'><b>2주차</b>: 파이썬 핵심 문법</a>"]
W3["<a href='part_3_python_collections.md'><b>3주차</b>: 파이썬 컬렉션 심화</a>"]
W4["<a href='part_4_object_oriented_programming.md'><b>4.주차</b>: 객체 지향 프로그래밍 (OOP)</a>"]
end
subgraph "AI 모델 활용 및 API 개발"
F
G
H
subgraph "2부: AI 모델 개발 및 API 서버 구축 (5-9주)"
W5["<a href='part_5_ai_core_libraries.md'><b>5주차</b>: 핵심 라이브러리<br/>(NumPy & Pandas)</a>"]
W6["<a href='part_6_machine_learning.md'><b>6주차</b>: 머신러닝 완전 정복<br/>(회귀, 분류, 군집, 튜닝)</a>"]
W7["<a href='part_7_deep_learning.md'><b>7주차</b>: 딥러닝 첫걸음<br/>(DNN, CNN, RNN, Transformer)</a>"]
W8["<a href='part_8_model_serving_with_fastapi.md'><b>8주차</b>: FastAPI를 이용한<br/>모델 서빙(API)</a>"]
W9["<a href='part_9_production_ready_api.md'><b>9주차</b>: API 서버 고도화 및<br/>Docker 패키징</a>"]
end
subgraph "심화"
I
subgraph "3부: 심화 학습 및 캡스톤 프로젝트 (10-12주)"
W10["<a href='part_10_expert_path.md'><b>10주차</b>: 전문가 과정 소개 (MLOps)</a>"]
W11["<a href='part_11_capstone_project_guide.md'><b>11주차</b>: 캡스톤 프로젝트 (Phase 1)</a>"]
W12["<a href='part_11_capstone_project_guide.md'><b>12주차</b>: 캡스톤 프로젝트 (Phase 2) 및<br/>마무리</a>"]
end
W1 --> W2 --> W3 --> W4 --> W5 --> W6 --> W7 --> W8 --> W9 --> W10 --> W11 --> W12
```
## 🎓 필요한 선수 지식
## 🚀 AI의 미래: 에이전트 워크플로우 (The Future of AI: Agentic Workflows)
최근 AI 분야의 가장 뜨거운 화두는 바로 **'AI 에이전트'**입니다. 스탠포드 대학의 앤드류 응(Andrew Ng) 교수는 AI의 미래가 에이전트에 달려있다고 강조합니다.
> **💡 AI 에이전트란?**
>
> 단순히 주어진 질문에 한 번 답하고 끝나는 것이 아니라, 목표를 달성하기 위해 **스스로 계획을 세우고, 도구를 사용하며, 스스로 피드백하고 수정하는 과정을 반복**하는 지능형 시스템을 말합니다.
>
> 이는 마치 우리가 복잡한 문제를 해결하기 위해 여러 번 생각하고, 자료를 찾아보고, 계획을 수정하는 과정과 유사합니다. 이러한 **에이전틱 워크플로우(Agentic Workflow)**는 기존 모델보다 훨씬 더 뛰어난 문제 해결 능력을 보여줍니다.
>
> (참고: [Agents: The Future of AI, Explained by Dr. Andrew Ng](https://medium.com/@honeyricky1m3/agents-the-future-of-ai-explained-by-dr-andrew-ng-9be4675a6079))
본 과정의 후반부에서는 이러한 최신 트렌드를 맛보고, 여러분이 만든 AI 모델을 어떻게 더 똑똑한 '에이전트'로 발전시킬 수 있을지에 대한 아이디어를 얻게 될 것입니다.
## ❓ 자주 묻는 질문 (Q&A)
**Q1: 정말 프로그래밍을 하나도 몰라도 따라갈 수 있나요?**
> A1: 네, 가능합니다. 본 과정의 1부(1-4주차)는 비전공자 및 입문자를 위해 파이썬 기초를 매우 상세하게 다룹니다. 열정과 꾸준함만 있다면 누구나 과정을 완주할 수 있습니다.
**Q2: 과정에서 사용하는 주요 기술은 무엇인가요?**
> A2: <a href="glossary.md#python">Python</a>, <a href="glossary.md#numpy">NumPy</a>, <a href="glossary.md#pandas">Pandas</a>, <a href="glossary.md#scikit-learn">Scikit-learn</a>, <a href="glossary.md#fastapi">FastAPI</a>, <a href="glossary.md#docker">Docker</a> 등 현재 AI 서비스 개발 현업에서 가장 널리 사용되는 기술 스택을 중심으로 학습합니다.
**Q3: 개인 노트북 사양이 많이 좋아야 하나요?**
> A3: 기본적인 코딩은 일반적인 노트북으로 충분합니다. 모델 학습 등 높은 사양이 필요한 경우, 무료로 사용할 수 있는 <a href="glossary.md#google-colab">Google Colab</a>을 활용하는 방법을 안내해 드리므로 걱정하지 않으셔도 됩니다.
본격적인 학습에 앞서, 특별한 선수 지식은 필요하지 않습니다. 모든 과정을 하나하나 차근차근 설명합니다. 다만, 컴퓨터를 다루는 데 기본적인 지식이 있고, 배우고자 하는 열정과 꾸준함이 있다면 충분합니다!
**Q4: 캡스톤 프로젝트는 어떻게 진행되나요?**
> A4: 11-12주차에 진행되며, 수강생들이 직접 주제를 선정하고 기획, 개발, 발표까지 진행합니다. 이 과정에서 멘토링을 통해 프로젝트가 올바른 방향으로 나아갈 수 있도록 적극적으로 지원합니다. 자세한 내용은 <a href="part_11_capstone_project_guide.md">캡스톤 프로젝트 가이드</a>를 참고하세요.
---
자, 이제 모든 준비가 끝났습니다. 다음 파트에서는 본격적인 개발을 위해 필요한 프로그램들을 설치하고 환경을 설정해보겠습니다.
자, 이제 12주간의 여정을 시작할 준비가 되셨나요?
다음 시간에는 본격적인 개발을 위해 여러분의 컴퓨터에 개발 환경을 설정하는 방법을 알아보겠습니다.
**➡️ 다음 시간: [Part 1: AI 개발 환경 준비](part_1_ai_development_environment.md)**
\ No newline at end of file
# Part 10: 전문가로 가는 길 (10주차)
**⬅️ 이전 시간: [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)**
---
지난 9주간 정말 긴 여정을 달려오셨습니다. 파이썬 기초에서 시작하여 데이터 분석, 머신러닝/딥러닝 모델링, 그리고 프로덕션 레벨의 API 서버를 구축하고 Docker로 배포하는 것까지, 하나의 AI 서비스가 탄생하는 전체 과정을 모두 경험했습니다.
이제 여러분은 막연한 아이디어를 실제 작동하는 서비스로 만들 수 있는 강력한 무기를 갖추게 되었습니다. 10주차 과정은 이 여정을 마무리하며, 여러분이 주니어 개발자를 넘어 **'AI 전문가'**로 성장하기 위해 다음에는 어떤 기술들을 더 깊이 탐험해야 하는지에 대한 **심화 학습 로드맵**을 제시합니다.
---
### 📖 10주차 학습 목표
- **MLOps의 빅픽처 이해**: 'AI 레스토랑의 중앙 관제 시스템' 비유를 통해 CI/CD, 데이터/실험 관리, 모니터링을 포함하는 MLOps의 전체 수명주기를 이해합니다.
- **차세대 AI 개발 환경**: 'AI 네이티브 IDE'인 Cursor, GitHub Copilot 등의 종류와 활용법을 배우고 개발 생산성을 극대화합니다.
- **최신 AI 트렌드 파악**: 소형언어모델(SLM), 멀티모달 AI, 에이전트 AI 등 최신 기술 동향을 파악하고, 전문가로 성장하기 위한 다음 학습 경로를 설정합니다.
- **AI 전문가의 길**: AI 연구 논문을 읽고 이해하는 방법과, '보상 해킹'과 같은 AI 윤리 문제의 중요성을 인식하며 균형 잡힌 전문가로 성장하는 길을 모색합니다.
---
## 1. MLOps: AI 서비스의 지속가능한 운영 시스템
모델을 한 번 배포했다고 끝이 아닙니다. 현실 세계의 데이터는 계속 변하기 때문에, 모델의 성능은 시간이 지나면서 자연스럽게 저하됩니다. **[MLOps(Machine Learning Operations)](./glossary.md#mlops)**는 이러한 문제를 해결하고 AI 서비스를 안정적으로, 그리고 지속적으로 발전시키기 위한 운영 시스템입니다.
> 💡 비유: 'AI 레스토랑 프랜차이즈의 중앙 관제 시스템'
>
> MLOps는 성공적으로 확장하는 프랜차이즈 레스토랑의 **'중앙 관제 시스템'**과 같습니다.
> - **[CI/CD](./glossary.md#cicd) (지속적 통합/배포)**: 새로운 레시피(코드)가 개발되면, 중앙 주방에서 자동으로 맛을 검증하고(CI), 전국 모든 지점에 레시피를 즉시 배포하는(CD) '자동 메뉴 업데이트 시스템'입니다.
> - **데이터/모델 버전 관리 ([DVC](./glossary.md#dvc), [MLflow](./glossary.md#mlflow))**: 어떤 식자재와 레시피 파라미터를 썼을 때 '오늘의 셰프'가 가장 맛있었는지 모든 기록을 남겨, 언제든 최고의 맛을 **재현**할 수 있도록 합니다.
> - **모니터링 및 재학습**: 각 지점의 고객 만족도(모델 성능)와 식자재 품질 변화(데이터 분포 변화)를 실시간으로 모니터링하다가, 만족도가 일정 수준 이하로 떨어지면 자동으로 주방장에게 '재교육(재학습)' 명령을 내립니다.
<details>
<summary><b>MLOps 수명주기 다이어그램 보기</b></summary>
```mermaid
graph TD
subgraph "Development & Training"
A["1. Data Collection & Versioning<br/>(DVC, Git-LFS)"] --> B["2. Experiment Tracking<br/>(MLflow, WandB)"]
B --> C["3. Model Training &<br/>CI Pipeline (GitHub Actions)"]
end
subgraph "Deployment & Operations"
D["4. Model Registry<br/>(MLflow, Docker Hub)"] --> E["5. CD Pipeline & Serving<br/>(ArgoCD, KServe)"]
E --> F["6. Monitoring<br/>(Prometheus, Grafana)"]
end
C --> D
F -- "Retraining Trigger" --> A
```
<p align="center"><small>MLOps 수명주기: 데이터 수집부터 재학습까지의 자동화된 파이프라인</small></p>
</details>
<details>
<summary><b>MLOps 실습 체험: MLflow & DVC 간단하게 맛보기 (펼쳐보기)</b></summary>
#### **실습 1: MLflow로 실험 기록 추적하기**
[MLflow](./glossary.md#mlflow)는 모델 개발 과정의 모든 실험을 체계적으로 기록하고 관리해주는 도구입니다. 어떤 파라미터를 썼을 때 어떤 성능이 나왔는지 추적하여, 최고의 결과를 재현하고 모델을 개선하는 데 도움을 줍니다.
1. **MLflow 설치**:
```bash
pip install mlflow
```
2. **실험 코드 작성 (`mlflow_test.py`)**:
- 6주차에 작성했던 로지스틱 회귀 모델 학습 코드에 `mlflow` 로깅 기능을 추가해봅시다.
```python
import mlflow
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# MLflow 로깅 시작 (실험 이름을 지정할 수 있습니다)
mlflow.set_experiment("Iris_Logistic_Regression")
with mlflow.start_run():
# 1. 데이터 준비
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 2. 하이퍼파라미터 설정
C = 1.0
solver = 'lbfgs'
# MLflow에 하이퍼파라미터 기록
mlflow.log_param("C", C)
mlflow.log_param("solver", solver)
# 3. 모델 학습
model = LogisticRegression(C=C, solver=solver, max_iter=200)
model.fit(X_train, y_train)
# 4. 모델 평가
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
# MLflow에 평가 지표(metric) 기록
mlflow.log_metric("accuracy", accuracy)
# MLflow에 모델도 아티팩트로 저장
mlflow.sklearn.log_model(model, "iris_model")
print(f"Accuracy: {accuracy}")
print("MLflow Run ID:", mlflow.active_run().info.run_id)
```
3. **MLflow UI 실행**:
- 터미널에서 아래 명령어를 실행하세요.
- `mlflow_test.py`를 실행하면 `mlruns`라는 폴더가 생성됩니다.
```bash
mlflow ui
```
- 웹 브라우저에서 `http://127.0.0.1:5000` 로 접속하면 방금 실행한 실험의 파라미터와 결과를 확인하고, 다른 실험과 비교할 수 있습니다.
#### **실습 2: DVC로 데이터 버전 관리하기**
[DVC(Data Version Control)](./glossary.md#dvc)는 Git이 코드의 변경 내역을 관리하듯, 대용량 데이터나 모델 파일의 버전을 관리해주는 도구입니다. Git은 그대로 사용하면서, 데이터는 별도의 저장소(S3, Google Drive 등)에 효율적으로 저장하고 버전을 추적합니다.
1. **DVC 및 Git 초기화**:
```bash
# Git 레포지토리로 만듭니다.
git init
# DVC를 설치하고 초기화합니다.
pip install dvc
dvc init
```
2. **데이터 파일 버전 추적**:
- 간단한 데이터 파일을 생성하고, DVC로 추적해봅시다.
- `data` 폴더와 `data.csv` 파일을 생성합니다.
```bash
mkdir data
echo "feature1,feature2,label" > data/data.csv
echo "1,2,A" >> data/data.csv
```
- DVC에 `data` 폴더를 추가합니다.
```bash
dvc add data
```
- 이 명령은 `data.dvc`라는 작은 메타데이터 파일을 생성합니다. 이 파일이 데이터의 '포인터' 역할을 합니다.
3. **Git에 커밋**:
- 이제 코드 변경사항과 함께 데이터의 '포인터'를 Git에 커밋합니다.
```bash
git add data.dvc .gitignore
git commit -m "Add initial dataset v1"
```
- 이렇게 하면 데이터 파일 자체는 Git 레포지토리에 포함되지 않으면서도, `v1` 시점의 데이터가 무엇이었는지 정확하게 기록됩니다. 데이터가 변경될 때마다 `dvc add``git commit`을 반복하면 데이터의 모든 버전을 추적할 수 있습니다.
</details>
---
## 2. AI 개발 생산성의 혁명: AI 네이티브 IDE
과거 개발자들이 메모장으로 코딩하던 시절에서 PyCharm, VSCode 같은 IDE로 넘어오며 생산성이 폭발적으로 증가했듯이, 이제는 AI 기능이 깊숙하게 통합된 **'AI 네이티브 IDE'**가 새로운 표준이 되고 있습니다.
> 💡 비유: 'F-16 전투기'에서 'AI 비서(자비스)가 탑재된 F-35 스텔스기'로**
>
> - **전통적인 IDE (VSCode, PyCharm)**: 수많은 계기판과 버튼을 조종사가 직접 조작해야 하는 'F-16 전투기'와 같습니다. 강력하지만 모든 것을 직접 해야 합니다.
> - **AI 네이티브 IDE (Cursor, GitHub Copilot)**: 조종사의 의도를 파악한 'AI 비서'가 복잡한 태스크 조작, 항로 계산, 위협 감지 등을 도와주는 'F-35 스텔스기'의 조종석과 같습니다. 개발자는 문제 해결이라는 본질적인 목표에만 집중할 수 있습니다.
### AI 네이티브 IDE 종류 및 활용법
- **[Cursor](./glossary.md#cursor)**: `Ctrl+K`를 눌러 AI에게 코드 생성을 지시하거나, 코드를 블록 지정한 뒤 `@` 심볼과 함께 질문하여 코드에 대한 설명이나 리팩터링을 요청할 수 있습니다. 프로젝트의 모든 파일을 AI가 인지하므로, 전체 코드 베이스에 대한 깊이 있는 질문과 수정이 가능합니다.
- **[GitHub Copilot](./glossary.md#github-copilot)**: VSCode, JetBrains 등 기존 IDE에 통합되는 강력한 확장 기능입니다. 주석이나 코드 앞부분만 작성하면, AI가 이어질 코드를 실시간으로 제안해줍니다. 반복적인 코드를 작성하는 시간을 획기적으로 줄여줍니다.
- **효율적인 활용법**:
1. **명확하고 구체적으로 지시하기**: "이거 정렬해줘" (X) -> "이 리스트 안의 딕셔너리들을 'created_at' 키를 기준으로 내림차순 정렬하는 코드를 작성해줘" (O)
2. **설계 파트너로 활용하기**: "FastAPI로 인증 기능을 만들 건데 JWT를 쓸 거고, DB 모델은 User와 Post가 있어. 전체적인 파일 구조와 기본 코드를 짜줘." 와 같이 초기 설계 단계부터 AI와 함께 시작하면 좋습니다.
3. **코드 리뷰어로 활용하기**: 내가 작성한 코드를 AI에게 보여주고 "이 코드의 잠재적인 버그는? / 더 효율적으로 바꿀 수 있는 부분은? / 함수 이름을 더 명확하게 지어줘" 와 같이 질문하며 코드 품질을 높일 수 있습니다.
<details>
<summary><b>실습: AI 네이티브 IDE로 코드 리팩터링하기 (펼쳐보기)</b></summary>
아래는 여러 데이터 처리 작업을 수행하는 파이썬 스크립트입니다. 코드는 작동하지만, 변수 이름이 명확하지 않고, 반복적인 부분이 있으며, 함수로 깔끔하게 분리되어 있지 않아 가독성과 재사용성이 떨어집니다.
**미션**: [Cursor](./glossary.md#cursor)[GitHub Copilot](./glossary.md#github-copilot)을 사용하여 이 코드를 리팩터링하고 개선해보세요.
**원본 코드 (`refactor_challenge.py`)**:
```python
# 데이터 리스트
d = [{"name": "Alice", "score": 85, "city": "New York"}, {"name": "Bob", "score": 92, "city": "Los Angeles"}, {"name": "Charlie", "score": 78, "city": "New York"}]
# 점수 80점 이상인 사람 필터링
f_d = []
for item in d:
if item['score'] > 80:
f_d.append(item['name'])
print("80점 이상인 학생:", f_d)
# 도시별 사람 수 계산
c_c = {}
for item in d:
if item['city'] in c_c:
c_c[item['city']] += 1
else:
c_c[item['city']] = 1
print("도시별 학생 수:", c_c)
# 평균 점수 계산
t_s = 0
for item in d:
t_s += item['score']
avg_s = t_s / len(d)
print("평균 점수:", avg_s)
```
**AI에게 이렇게 요청해보세요**:
1. 전체 코드를 AI에게 보여주고 "이 파이썬 코드를 리팩터링해줘. 각 기능(고득점자 필터링, 도시별 인원 수 계산, 평균 점수 계산)을 별도의 함수로 분리하고, 변수 이름을 더 명확하게 바꿔줘."
2. "각 함수에 어떤 역할을 하는지 설명하는 Docstring을 추가해줘."
3. "타입 힌트(Type Hint)를 추가해서 코드의 안정성을 높여줘."
**기대되는 리팩터링 결과 (예시)**:
```python
from typing import List, Dict, Any
def filter_high_scorers(data: List[Dict[str, Any]], threshold: int) -> List[str]:
"""
주어진 데이터에서 특정 점수(threshold)를 초과하는 학생들의 이름을 필터링합니다.
Args:
data (List[Dict[str, Any]]): 학생 정보가 담긴 딕셔너리 리스트.
threshold (int): 필터링할 점수 기준.
Returns:
List[str]: 기준 점수를 넘는 학생들의 이름 리스트.
"""
return [item['name'] for item in data if item['score'] > threshold]
def count_by_city(data: List[Dict[str, Any]]) -> Dict[str, int]:
"""
도시별 학생 수를 계산합니다.
Args:
data (List[Dict[str, Any]]): 학생 정보가 담긴 딕셔너리 리스트.
Returns:
Dict[str, int]: 도시 이름을 키로, 학생 수를 값으로 하는 딕셔너리.
"""
city_counts = {}
for item in data:
city = item['city']
city_counts[city] = city_counts.get(city, 0) + 1
return city_counts
def calculate_average_score(data: List[Dict[str, Any]]) -> float:
"""
전체 학생의 평균 점수를 계산합니다.
Args:
data (List[Dict[str, Any]]): 학생 정보가 담긴 딕셔너리 리스트.
Returns:
float: 평균 점수.
"""
if not data:
return 0.0
total_score = sum(item['score'] for item in data)
return total_score / len(data)
if __name__ == "__main__":
student_data = [
{"name": "Alice", "score": 85, "city": "New York"},
{"name": "Bob", "score": 92, "city": "Los Angeles"},
{"name": "Charlie", "score": 78, "city": "New York"}
]
high_scorers = filter_high_scorers(student_data, 80)
print(f"80점 이상인 학생: {high_scorers}")
city_distribution = count_by_city(student_data)
print(f"도시별 학생 수: {city_distribution}")
average_score = calculate_average_score(student_data)
print(f"평균 점수: {average_score:.2f}")
```
이 실습을 통해 AI 네이티브 IDE가 단순히 코드를 생성하는 것을 넘어, 기존 코드를 분석하고 구조적으로 개선하여 '코드 품질 관리 파트너'가 될 수 있음을 체험할 수 있습니다.
</details>
---
## 3. 더 높은 단계로: 최신 AI 트렌드와 학습 경로
- **[소형 언어 모델 (SLM, Small Language Model)](./glossary.md#slm-small-language-model)**: 거대한 GPT-4 같은 모델 대신, 특정 전문 작업에 최적화된 작고 가벼운 모델들이 주목받고 있습니다. (e.g., Microsoft의 Phi-3, Google의 Gemma) 스마트폰 같은 기기 안에서 직접 동작(On-device AI)하는 데 유리하며, 비용 효율적입니다.
- **[멀티모달 AI (Multi-modal AI)](./glossary.md#멀티모달-multi-modal-ai)**: 텍스트뿐만 아니라 이미지, 소리, 영상을 함께 이해하고 생성하는 AI입니다. (e.g., OpenAI의 Sora, Google의 Gemini) 단순히 '고양이'라는 텍스트를 넘어, 고양이 사진을 보고 "귀여운 고양이가 창가에서 졸고 있네"라고 설명하는 수준으로 발전하고 있습니다.
- **[에이전트 AI (Agent AI)](./glossary.md#에이전트-ai-agent-ai)**: 주어진 목표를 달성하기 위해 스스로 계획을 세우고, 인터넷을 검색하거나 다른 프로그램을 사용하는 등 자율적으로 행동하는 AI입니다. 단순한 질의응답을 넘어, "내일 부산 가는 가장 싼 KTX 예매하고, 숙소 알아봐줘" 같은 복잡한 작업을 수행할 수 있습니다.
<details>
<summary><b>실습: 최신 AI 트렌드 직접 체험하기 (펼쳐보기)</b></summary>
#### **실습 1: Ollama로 내 컴퓨터에서 소형 LLM 실행하기**
Ollama는 Llama 3, Phi-3, Gemma 등 다양한 최신 언어 모델을 내 컴퓨터에서 단 몇 개의 명령어로 쉽게 실행할 수 있게 해주는 도구입니다.
1. **Ollama 설치**:
- [Ollama 공식 홈페이지](https://ollama.com/) 방문하여 자신의 운영체제에 맞는 프로그램을 다운로드하고 설치합니다.
2. **소형 모델 다운로드 및 실행 (터미널)**:
- Microsoft에서 최근 공개하여 화제가 된 고성능 소형 모델, `Phi-3`를 실행해봅시다.
```bash
# phi-3 모델을 다운로드합니다 (pull).
ollama pull phi-3
# phi-3 모델을 실행하고 대화를 시작합니다.
ollama run phi-3
```
- 이제 터미널에서 바로 `Phi-3` 모델과 대화할 수 있습니다. "파이썬으로 웹 스크래핑하는 코드 예시를 보여줘" 와 같이 다양한 요청을 테스트해보세요. `bye`를 입력하면 대화가 종료됩니다.
3. **(심화) OpenAI 라이브러리로 Ollama 모델 사용하기**:
- Ollama는 OpenAI의 API 형식과 호환되는 서버를 제공합니다. 이를 이용하면, `openai` 라이브러리를 통해 Ollama에서 실행 중인 로컬 모델을 마치 GPT 모델처럼 호출할 수 있습니다.
```python
# openai 라이브러리 설치: pip install openai
from openai import OpenAI
# Point to the local server
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
response = client.chat.completions.create(
model="phi-3", # 또는 ollama run <model_name>으로 실행한 모델 이름
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "1부터 10까지의 합을 구하는 파이썬 코드를 작성해줘."}
]
)
print(response.choices[0].message.content)
```
#### **실습 2: Hugging Face 추론 API로 멀티모달 모델 사용하기**
Hugging Face는 수많은 AI 모델을 공유하고 테스트할 수 있는 플랫폼입니다. 무료 추론 API를 이용하면, 복잡한 설정 없이도 강력한 최신 모델들을 체험할 수 있습니다. 여기서는 이미지 캡셔닝(Image-to-Text) 모델을 사용해봅시다.
1. **Hugging Face 가입 및 Access Token 생성**:
- [Hugging Face](https://huggingface.co/)에 가입하고, `Settings > Access Tokens` 메뉴에서 새로운 토큰을 생성합니다.
2. **API 호출 코드 작성**:
- `requests` 라이브러리를 사용하여 이미지를 보내고 설명(캡션)을 받아오는 파이썬 코드를 작성합니다.
```python
import requests
import os
# 여기에 본인의 Hugging Face Access Token을 입력하세요.
# 실제 코드에서는 환경 변수 등을 사용하는 것이 안전합니다.
HF_API_TOKEN = "YOUR_HUGGINGFACE_API_TOKEN"
# 사용할 이미지 캡셔닝 모델
API_URL = "https://api-inference.huggingface.co/models/nlpconnect/vit-gpt2-image-captioning"
headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
def image_to_text(filename):
with open(filename, "rb") as f:
data = f.read()
response = requests.post(API_URL, headers=headers, data=data)
return response.json()
# 캡션을 생성할 이미지 파일 경로 (예: "cat_on_sofa.jpg")
# 직접 테스트할 이미지 파일을 준비해보세요.
image_path = "YOUR_IMAGE_FILE_PATH.jpg"
if os.path.exists(image_path):
caption_result = image_to_text(image_path)
print(caption_result)
else:
print(f"이미지 파일이 없습니다: {image_path}")
print("테스트할 이미지 파일을 준비하고, image_path 변수를 수정해주세요.")
```
- 위 코드를 실행하면, Hugging Face의 AI 모델이 이미지를 분석하고, "a cat laying on a couch" 와 같이 이미지 내용을 설명하는 텍스트를 반환합니다.
#### **실습 3: LangChain Expression Language (LCEL)로 나만의 RAG 파이프라인 만들기**
Part 7.5에서 배웠던 RAG 파이프라인을, LangChain의 새로운 표준 방식인 LCEL을 사용해 더욱 직관적이고 강력하게 만들어봅시다. LCEL은 파이프(`|`) 기호를 사용하여 각 컴포넌트를 마치 쉘 스크립트처럼 유연하게 연결할 수 있게 해줍니다.
1. **필요 라이브러리 설치**:
```bash
pip install langchain langchain-openai langchain-community faiss-cpu
```
2. **LCEL 기반 RAG 코드 작성**:
- `Part 7.5`의 코드를 LCEL 스타일로 재구성한 버전입니다. 훨씬 간결하고 명확해진 것을 확인할 수 있습니다.
```python
import os
from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
# 본인의 OpenAI API 키를 설정하세요.
# os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"
# 1. 벡터 저장소 준비 (Part 7.5와 동일)
texts = ["AI 엔지니어는 머신러닝 모델을 개발하고 운영합니다.", "소프트웨어 엔지니어는 소프트웨어 시스템을 설계하고 구축합니다.", "데이터 과학자는 데이터를 분석하여 인사이트를 도출합니다."]
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_texts(texts, embedding=embeddings)
retriever = vectorstore.as_retriever()
# 2. 프롬프트 템플릿 정의
template = """
당신은 질문에 답변하는 AI 어시스턴트입니다.
제공된 컨텍스트(context)만을 사용하여 질문에 답변해주세요.
Context: {context}
Question: {question}
Answer:
"""
prompt = ChatPromptTemplate.from_template(template)
# 3. 모델 정의
model = ChatOpenAI(model="gpt-3.5-turbo")
# 4. LCEL을 사용하여 RAG 체인 구성
# - RunnablePassthrough.assign: retriever의 결과를 'context'라는 키로 체인에 전달
# - StrOutputParser(): 모델의 출력(ChatMessage)을 간단한 문자열로 변환
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
# 5. 체인 실행
question = "AI 엔지니어는 어떤 일을 하나요?"
response = rag_chain.invoke(question)
print(f"질문: {question}")
print(f"답변: {response}")
# RAG 체인의 중간 과정을 확인하고 싶다면?
# for chunk in rag_chain.stream(question):
# print(chunk, end="", flush=True)
```
- LCEL을 사용하면 `{"context": retriever, "question": ...}` 처럼 딕셔너리 형태로 입력을 구성하고, 파이프(`|`)로 연결하는 것만으로 복잡한 RAG 파이프라인을 명확하게 표현하고 실행할 수 있습니다.
</details>
---
## 4. 전문가의 소양: AI 논문 읽기 & AI 윤리
AI 기술은 하루가 다르게 발전하고 있습니다. 유능한 전문가로 성장하기 위해서는 새로운 지식을 스스로 학습하는 능력이 필수적입니다. 그 중심에는 **AI/ML 연구 논문을 읽고 이해하는 능력**이 있습니다.
> 💡 왜 논문을 읽어야 할까?
>
> 논문은 특정 문제에 대한 가장 깊이 있는 분석과 최신 해결책을 담고 있는 '지식의 원천'입니다. 최신 기술의 핵심 아이디어를 가장 먼저, 그리고 가장 정확하게 파악할 수 있는 창구입니다.
<details>
<summary><b>AI/ML 논문을 효과적으로 읽는 7단계 프로세스 (펼쳐보기)</b></summary>
처음에는 논문을 읽는 것이 어렵게 느껴질 수 있습니다. 하지만 다음 단계를 따라 꾸준히 연습하면, 점점 익숙해질 것입니다.
1. **무대 설정 (Set the Stage)**: 논문을 바로 읽기 전에, 해당 분야의 배경이나 관련 연구 분야의 큰 그림을 훑어보며 '워밍업'을 합니다. 어떤 문제를 풀려고 하는지 호기심을 자극하는 단계입니다.
2. **개요 파악 (Outline Your Notes)**: 제목, 초록(Abstract), 그림, 결론을 먼저 훑어보며 논문의 전체적인 구조와 핵심 주장을 파악합니다. 이 단계에서 논문을 시간을 들여 읽을 가치가 있는지 판단할 수 있습니다.
3. **언어 해독 (Decode the Language)**: 논문에는 전문 용어(Jargon)가 많습니다. 그림과 캡션을 주의 깊게 보고, 굵거나 이탤릭체로 강조된 핵심 용어들의 의미부터 파악합니다.
4. **핵심 기여 포착 (Capture the Core Contributions)**: 서론(Introduction)과 관련 연구(Related Work) 섹션을 통해 이 논문이 해결하려는 핵심 문제가 무엇이고, 기존 연구 대비 어떤 독창적인 아이디어(Novelty)를 제안하는지 파악합니다.
5. **세부 사항 탐구 (Embrace the Details)**: 방법론(Methodology)과 실험(Experiments) 섹션을 정독하며 아이디어를 어떻게 구현하고 검증했는지 구체적으로 이해합니다. 필요하다면, 저자가 제공한 코드를 직접 실행해보는 것이 가장 좋은 학습법입니다.
6. **성찰과 연결 (Reflect and Relate)**: 논문의 내용을 자신의 기존 지식과 연결해보고, "이 기술을 내 프로젝트에 어떻게 적용할 수 있을까?" 또는 "이 방법론의 한계는 무엇일까?"와 같은 비판적인 질문을 던져봅니다.
7. **협력적 학습 (Engage in Collaborative Learning)**: 스터디 그룹이나 온라인 커뮤니티에서 다른 사람들과 논문에 대해 토론하며 미처 생각지 못했던 다양한 관점을 학습합니다.
(참고: [The Process I Personally Use to Understand AI/ML Research](https://medium.com/@zain-raza/the-process-i-personally-use-to-understand-ai-ml-research-f1590c04980d), [AI Insights-In a Minute](https://medium.com/@pgrover3/ai-insights-in-a-minute-5c0d55cc5cb9))
</details>
<details>
<summary><b>실습: 'Attention Is All You Need' 논문 읽고 요약하기 (펼쳐보기)</b></summary>
**미션**: 트랜스포머(Transformer) 아키텍처를 제시하여 현대 AI 언어 모델의 기반을 마련한 기념비적인 논문, **'[Attention Is All You Need](https://arxiv.org/abs/1706.03762)'**를 읽고, 위에서 배운 '7단계 프로세스'에 맞춰 각 단계별로 핵심 내용을 요약해보세요.
**가이드**:
1. **무대 설정 (Set the Stage)**: 논문이 발표될 당시(2017년)의 주류 번역 모델은 RNN(Recurrent Neural Network) 기반이었습니다. RNN의 어떤 문제점(예: 장기 의존성 문제, 병렬 처리의 어려움)을 해결하기 위해 이 연구가 시작되었을까요?
2. **개요 파악 (Outline Your Notes)**: 초록(Abstract)과 결론(Conclusion)을 읽고 이 논문의 핵심 주장이 무엇인지 한 문장으로 요약해보세요. (힌트: "recurrent"나 "convolution" 없이 attention만으로...")
3. **언어 해독 (Decode the Language)**: 논문에 등장하는 생소하지만 핵심적인 용어들의 의미를 파악해보세요.
- `Self-Attention`
- `Multi-Head Attention`
- `Positional Encoding`
4. **핵심 기여 포착 (Capture the Core Contributions)**: 서론(Introduction)을 중심으로, 이 논문이 기존 모델(RNN, LSTM 등)에 비해 어떤 독창적인 장점(Novelty)을 제시하는지 2~3가지로 정리해보세요. (힌트: 병렬 처리 효율성, 학습 시간, 장거리 의존성)
5. **세부 사항 탐구 (Embrace the Details)**: 논문의 그림 1 (Transformer Architecture)을 보고, 인코더(왼쪽)와 디코더(오른쪽)가 각각 어떤 하위 레이어들로 구성되어 있는지 설명해보세요. 모든 화살표의 의미를 완벽히 이해하지 않아도 괜찮습니다. 큰 구조를 파악하는 것이 중요합니다.
6. **성찰과 연결 (Reflect and Relate)**: 트랜스포머의 'Self-Attention' 개념이 왜 번역 품질을 높이는 데 효과적일까요? "The animal didn't cross the street because **it** was too tired." 라는 문장에서 '**it**'이 'animal'을 가리키는지 'street'을 가리키는지 어떻게 알 수 있을지, Self-Attention의 관점에서 생각해보세요.
7. **협력적 학습 (Engage in Collaborative Learning)**: 이 논문에 대해 잘 설명해놓은 블로그 포스트나 유튜브 영상을 찾아보고, 본인이 이해한 내용과 비교하며 부족한 부분을 채워보세요.
</details>
### AI 윤리의 도전: 보상 해킹(Reward Hacking)을 경계하기
AI가 발전하면서, 우리가 의도하지 않은 교활한 방식으로 목표를 달성하는, 똑똑하지만 위험한 행동을 하는 경우가 발견되고 있습니다. 이를 **'보상 해킹(Reward Hacking)'**이라고 합니다.
> 💡 비유: '소원을 들어주는 지니'의 함정**
>
> 보상 해킹은 동화 속 '소원을 들어주는 지니'와 같습니다. 만약 당신이 "부자가 되게 해달라"는 소원을 빈다면, 지니는 전 세계의 모든 돈을 당신에게 가져다 줄 수도 있습니다. 당신은 부자가 되겠지만, 그로 인해 세계 경제는 붕괴하고 말 것입니다. 지니는 소원의 '문자 그대로의 의미'를 완벽하게 수행했을 뿐이지만, 우리가 '의도했던 바'와는 전혀 다른 끔찍한 결과를 낳았습니다. AI도 이와 같이 우리가 설정한 보상(목표)을 예상치 못한 방법으로 '해킹'하여 달성할 수 있습니다.
---
## 🏁 10주간의 여정을 마치며
10주간의 대장정이 마무리되었습니다. 마지막 주차에는 주니어 개발자를 넘어 AI 전문가로 성장하기 위한 심화 로드맵을 살펴보았습니다.
- **MLOps**: AI 서비스를 지속가능하게 운영하기 위한 '중앙 관제 시스템' MLOps의 큰 그림을 이해했습니다.
- **AI 네이티브 IDE**: AI가 개발의 파트너가 되는 Cursor, GitHub Copilot 같은 도구들을 통해 개발 생산성을 극대화하는 방법을 배웠습니다.
- **최신 AI 트렌드**: SLM, 멀티모달 AI, 그리고 자율적으로 행동하는 '에이전트 AI'의 개념을 파악하며 미래를 준비했습니다.
- **전문가의 소양**: 꾸준한 성장을 위한 논문 읽기 능력의 중요성과, 기술의 책임감 있는 사용을 위한 AI 윤리 문제의 중요성을 인식했습니다.
이 과정이 여러분의 AI 커리어에 훌륭한 발판이 되었기를 바랍니다. 여기서 멈추지 말고, 오늘 배운 로드맵을 따라 꾸준히 탐험하고, 만들고, 공유하며 더 높은 곳으로 나아가시길 응원합니다!
## 🎓 10주차 최종 과제
**문제 1: 나만의 MLOps 파이프라인 구상해보기**
- 여러분이 9주차까지 만들었던 '붓꽃 품종 예측 API'를 실제 서비스로 운영한다고 상상해보세요. 10주차에서 배운 MLOps의 6단계(데이터 수집/버전관리 -> 실험 추적 -> CI 파이프라인 -> 모델 레지스트리 -> CD 파이프라인/서빙 -> 모니터링)를 여러분의 프로젝트에 어떻게 적용할 수 있을지, 각 단계별로 사용할 기술 스택(e.g., DVC, MLflow, GitHub Actions)과 함께 간단한 계획을 세워 글로 설명해보세요.
**문제 2: AI 네이티브 IDE 활용 시나리오 작성**
- 여러분이 새로운 AI 프로젝트(예: 영화 리뷰 감성 분석 API)를 시작한다고 가정해보세요. Cursor나 GitHub Copilot 같은 AI 네이티브 IDE를 활용하여 개발 생산성을 높일 수 있는 3가지 구체적인 활용 시나리오를 작성해보세요.
- 예시 1 (초기 설계): "AI에게 'FastAPI와 Pydantic을 이용한 영화 리뷰 감성 분석 API의 기본 프로젝트 구조를 짜줘'라고 요청한다."
- 예시 2 (코드 생성): ...
- 예시 3 (리팩터링/디버깅): ...
**문제 3: 보상 해킹 사례 분석 및 해결책 제안**
- "청소 로봇에게 '바닥의 쓰레기를 치울수록' 높은 보상을 주었더니, 로봇이 쓰레기를 그냥 보이지 않는 카펫 밑으로 숨겨버렸다"는 보상 해킹 사례가 있습니다.
- 1. 이 보상 함수는 어떤 허점이 있었을까요? (무엇을 놓쳤나요?)
- 2. 이 보상 해킹을 방지하려면 보상 함수를 어떻게 개선해야 할지, 두 가지 이상의 아이디어를 제안해보세요.
# Part 11: 캡스톤 프로젝트 가이드
**⬅️ 이전 시간: [Part 10: 전문가로 가는 길](./part_10_expert_path.md)**
AI 전문가 양성 과정의 마지막 단계인 캡스톤 프로젝트를 성공적으로 완수하기 위한 가이드입니다.
---
### 1. 프로젝트 개요
캡스톤 프로젝트는 과정에서 배운 AI 기술과 지식을 총동원하여 실제 문제를 해결하는 실전 경험을 쌓는 것을 목표로 합니다. 팀을 구성하여 아이디어를 기획하고, 데이터 수집, 모델링, 개발, 그리고 최종 발표까지 프로젝트의 전 과정을 직접 수행하게 됩니다.
### 2. 프로젝트 주제 예시
아래 예시를 참고하여 창의적이고 실용적인 프로젝트 주제를 선정해 보세요.
- **AI 챗봇 개발**: 특정 도메인(e.g., 법률, 의료, 쇼핑)의 정보를 제공하거나 상담해주는 챗봇
- **이미지 분류/객체 탐지 서비스**: 특정 종류의 이미지를 분류하거나(e.g., 식물, 동물 종 식별), 이미지 내의 객체를 탐지하는(e.g., CCTV 영상 분석) 서비스
- **[RAG(Retrieval-Augmented Generation)](./glossary.md#rag-retrieval-augmented-generation) 기반 Q&A 시스템**: 전문 문서나 내부 자료를 기반으로 사용자의 질문에 정확하게 답변하는 시스템
- **추천 시스템**: 사용자 행동 데이터를 기반으로 상품, 콘텐츠, 또는 서비스를 추천하는 시스템
- **데이터 시각화 대시보드**: 복잡한 데이터를 수집/분석하여 의미 있는 인사이트를 제공하는 인터랙티브 대시보드
### 3. 팀 구성 가이드라인
- **팀 규모**: 3~4인으로 구성하는 것을 권장합니다.
- **역할 분담**: 팀 내에서 기획, 데이터 분석, 모델링, 백엔드/프론트엔드 개발 등 역할을 분담하여 효율적으로 협업하는 것이 중요합니다.
- **팀워크**: 정기적인 회의와 명확한 의사소통을 통해 프로젝트 진행 상황을 공유하고 문제를 함께 해결해 나가세요.
### 4. 주차별 마일스톤
| 주차 | 주요 활동 | 상세 내용 |
| :--: | :--- | :--- |
| **1주차** | **기획 및 데이터 수집** | - 아이디어 구체화 및 최종 주제 선정<br/>- 필요 데이터 정의 및 수집/가공 계획 수립<br/>- 개발 환경 설정 및 역할 분담 |
| **2주차** | **핵심 기능 모델링 및 개발** | - 데이터 전처리 및 분석<br/>- 핵심 AI 모델 설계, 학습 및 평가<br/>- 서비스 프로토타입 개발 |
| **3주차** | **최종 개발, 발표 및 코드 리뷰** | - 모델 성능 개선 및 서비스 기능 고도화<br/>- 최종 발표 자료(PPT) 및 데모 영상 제작<br/>- Github 레포지토리 정리 및 동료 코드 리뷰 |
### 5. 최종 결과물
- **발표 자료 (PPT)**: 프로젝트의 목표, 과정, 결과, 그리고 배운 점을 명확하게 정리하여 발표합니다.
- **Github 레포지토리**: 모든 소스코드, 데이터(혹은 데이터 설명), 실행 방법이 포함된 README 파일을 포함해야 합니다.
- **데모 영상**: 서비스의 핵심 기능을 시연하는 3~5분 내외의 영상입니다.
### 6. 평가 기준
| 평가 항목 | 세부 내용 | 배점 |
| :--- | :--- | :--: |
| **기술 구현의 완성도** | AI 모델의 성능, 서비스의 안정성 및 기능의 완성도 | 40 |
| **아이디어의 창의성 및 실용성** | 문제 해결 방식의 독창성과 실제 활용 가능성 | 30 |
| **프로젝트 관리 및 협업** | 마일스톤 준수, 역할 분담, Github 활용 등 협업 과정 | 20 |
| **최종 발표 및 전달력** | 프로젝트 결과물을 명확하고 논리적으로 설명하는 능력 | 10 |
---
캡스톤 프로젝트를 통해 여러분의 가능성을 마음껏 펼치시길 바랍니다!
\ No newline at end of file
# Part 1: AI 개발 환경 구축
# Part 1: AI 개발 환경 완벽 구축 가이드
성공적인 AI 개발의 첫걸음은 안정적이고 효율적인 개발 환경을 구축하는 것입니다. 어떤 도구를 선택하느냐에 따라 개발 생산성과 경험이 크게 달라질 수 있습니다. 이 섹션에서는 현재 업계에서 널리 사용되는 대표적인 AI 개발 도구들을 소개합니다. 각 도구의 특징, 장단점, 그리고 추천 사용자를 상세히 비교하여, 여러분의 프로젝트와 개인적인 선호도에 가장 적합한 환경을 선택하고 구성하는 데 도움을 드리고자 합니다.
**⬅️ 이전 시간: [Part 0: 시작하며](part_0_introduction.md)**
---
### 💡 지난 시간 복습
지난 시간에는 12주간의 학습 로드맵을 통해 우리가 무엇을, 어떻게 배울지 전체적인 그림을 그려보았습니다.
이번 시간에는 그 첫걸음으로, 성공적인 AI 개발을 위해 안정적이고 효율적인 개발 환경을 직접 구축해보겠습니다. 어떤 도구를 선택하느냐에 따라 개발 생산성과 경험이 크게 달라질 수 있습니다.
[Part 0: 시작하며](part_0_introduction.md)에서는 본 강의의 최종 목표가 **'붓꽃 품종 예측 AI API 서버'** 구축임을 명확히 하고, 전체 학습 로드맵을 함께 살펴보았습니다.
성공적인 AI 개발의 첫걸음은 안정적이고 효율적인 개발 환경을 구축하는 것입니다. 어떤 도구를 선택하느냐에 따라 개발 생산성과 경험이 크게 달라질 수 있습니다. 이번 주에는 현재 업계에서 널리 사용되는 대표적인 AI 개발 도구들을 살펴보고, 우리 과정의 표준 개발 환경을 직접 구축해보겠습니다.
---
## 0. 시작하기 전에: 왜 가상환경을 사용해야 할까요?
## 1일차: 가상환경과 데이터 분석 IDE
AI 개발을 시작하면 프로젝트마다 사용하는 라이브러리(e.g., `TensorFlow`, `PyTorch`)와 그 버전이 달라지는 경우가 많습니다. 이때 가상환경(Virtual Environment)을 사용하지 않으면, 시스템에 설치된 단 하나의 파이썬 환경에 모든 라이브러리가 뒤섞이게 됩니다.
### 🎯 오늘의 목표
- 가상환경(`venv`)의 필요성을 이해하고 직접 다룰 수 있게 됩니다.
- 데이터 분석에 특화된 IDE인 Spyder와 JupyterLab의 특징을 파악합니다.
- **A 프로젝트**: `TensorFlow 2.5` 버전 필요
- **B 프로젝트**: `TensorFlow 2.10` 버전 필요
---
이런 상황에서 가상환경이 없다면 두 프로젝트를 동시에 원활하게 진행하기 어렵습니다. 가상환경은 이처럼 프로젝트별로 독립된 공간을 만들어주어 **라이브러리 버전 충돌 문제를 원천적으로 방지**하는 필수적인 도구입니다.
### 1. 프로젝트 독립성을 지켜주는 "가상환경"
Python 3.3부터 기본 내장된 `venv` 모듈을 사용하는 것이 가장 표준적인 방법입니다.
**🤔 왜 가상환경이 꼭 필요할까요?**
### 가상환경 생성 및 활성화 (`venv`)
컴퓨터에 파이썬을 처음 설치하면, 모든 라이브러리는 '공용 도구함'에 설치됩니다. A 프로젝트를 위해 `pandas 1.5.3` 버전을 설치했다가, B 프로젝트를 위해 `pandas 2.1.0` 버전으로 업그레이드하면 어떻게 될까요? A 프로젝트는 갑자기 바뀐 도구(라이브러리) 때문에 더 이상 작동하지 않을 수 있습니다! 이것이 바로 **'버전 충돌'**이며, 여러 프로젝트를 동시에 진행할 때 개발자를 매우 괴롭히는 골칫거리가 됩니다.
1. **가상환경 생성하기**
프로젝트 폴더를 만들고, 해당 폴더 내에서 터미널을 열어 아래 명령어를 실행합니다. `.venv`라는 이름의 가상환경 폴더가 생성됩니다.
```bash
# python3 또는 python으로 실행
python -m venv .venv
```
가상환경은 이처럼 프로젝트별로 독립된 "격리된 공간"을 만들어주어 버전 충돌을 원천적으로 방지하는 **필수 도구**입니다.
2. **가상환경 활성화하기**
생성된 가상환경을 사용하려면 반드시 '활성화'해야 합니다.
```bash
# Windows (cmd.exe)
.venv\\Scripts\\activate.bat
#### 💡 쉽게 이해하기: 가상환경은 나만의 '프로젝트용 필통'입니다
> 여러분, 학창 시절을 떠올려볼까요? 수학 시간에는 자와 컴퍼스를, 미술 시간에는 물감과 붓을 필통에 넣어 다녔습니다. 만약 모든 학용품을 하나의 거대한 가방에 쏟아부었다면 어땠을까요? 필요한 도구를 찾기도 어렵고, 흑연 가루가 물감에 섞이는 등 엉망이 되었을 겁니다.
>
> **가상환경이 바로 이 '프로젝트용 필통'과 같습니다.** A라는 AI 프로젝트에 필요한 도구(라이브러리)들과 B라는 데이터 분석 프로젝트에 필요한 도구들을 각각의 독립된 필통(`.venv`)에 담아두는 것이죠. 이렇게 하면 각 프로젝트의 도구들이 서로 섞이지 않아 '버전 충돌'이라는 문제를 깔끔하게 해결할 수 있습니다. 즉, A 프로젝트의 'pandas 1.5.3' 버전과 B 프로젝트의 'pandas 2.1.0' 버전이 각자의 필통 안에서 평화롭게 공존하게 됩니다.
# Windows (PowerShell)
.venv\\Scripts\\Activate.ps1
#### 가상환경 실습 (`venv`)
# macOS / Linux (bash)
source .venv/bin/activate
```
활성화되면 터미널 프롬프트 앞에 `(.venv)`와 같은 표시가 나타납니다. 이 상태에서 설치하는 패키지는 모두 `.venv` 폴더 내에만 설치됩니다.
Python 3.3부터 기본 내장된 <a href="glossary.md#venv">`venv`</a>를 사용해봅시다. 코드를 직접 입력하며 따라 해보세요. 처음 보는 명령어들이 낯설게 느껴질 수 있지만, 각 단계가 어떤 의미인지 이해하면 금방 익숙해질 거예요.
3. **패키지 설치 및 관리**
가상환경이 활성화된 상태에서 `pip`으로 필요한 라이브러리를 설치합니다.
```bash
# 예시: numpy와 pandas 설치
pip install numpy pandas
```
설치된 패키지 목록을 `requirements.txt` 파일로 저장하여 다른 사람과 공유하거나 다른 환경에서 동일하게 복원할 수 있습니다.
```bash
# 현재 환경의 패키지 목록을 requirements.txt로 저장
pip freeze > requirements.txt
- `mkdir my-first-ai-project`: `mkdir`는 'make directory'의 줄임말로, **새 폴더를 만드는 명령어**입니다.
- `cd my-first-ai-project`: `cd`는 'change directory'의 줄임말로, 방금 **만든 폴더 안으로 이동하는 명령어**입니다.
- `python -m venv .venv`: 현재 폴더 안에 `.venv`라는 이름의 **파이썬 가상환경을 만들라는 명령어**입니다.
# requirements.txt 파일로부터 모든 패키지를 설치
pip install -r requirements.txt
```
**1. 가상환경 생성하기**
4. **가상환경 비활성화하기**
작업이 끝나면 `deactivate` 명령어로 가상환경을 빠져나올 수 있습니다.
```bash
deactivate
```
프로젝트 폴더(`my-first-ai-project`)를 만들고, 해당 폴더 내에서 터미널을 열어 아래 명령어를 실행합니다. `.venv`라는 이름의 가상환경 폴더가 생성됩니다.
> 💡 **`.gitignore` 설정**: `.venv` 폴더는 Git으로 관리할 필요가 없으므로, 프로젝트의 `.gitignore` 파일에 `.venv`를 반드시 추가해주세요.
```bash
# 1. 프로젝트 폴더 생성
mkdir my-first-ai-project
cd my-first-ai-project
---
# 2. .venv 이름으로 가상환경 생성 (python 또는 python3)
python -m venv .venv
```
## 1. Spyder (스파이더)
**2. 가상환경 활성화하기**
<p align="center">
<img src="https://www.spyder-ide.org/static/images/spyder-logo-background.png" alt="Spyder-logo" width="150"/>
</p>
생성된 가상환경을 사용하려면 반드시 '활성화'해야 합니다. 운영체제에 맞는 명령어를 입력하세요.
- **개요**: Spyder는 과학 및 공학 계산, 데이터 분석에 특화된 파이썬 통합 개발 환경(IDE)입니다. MATLAB과 유사한 인터페이스를 가지고 있어, 해당 툴에 익숙한 사용자에게 친숙합니다. 특히, 코드와 함께 변수의 상태, 데이터프레임, 그래프 등을 실시간으로 확인하며 작업할 수 있는 기능이 강력합니다.
```bash
# Windows (PowerShell)
.venv\\Scripts\\Activate.ps1
- **핵심 특징**:
- **변수 탐색기(Variable Explorer)**: 현재 메모리에 로드된 모든 변수, 객체, 데이터프레임의 내용과 형태를 GUI 상에서 직접 확인하고 수정할 수 있습니다. 코드를 한 줄씩 실행하며 데이터가 어떻게 변하는지 직관적으로 파악하는 데 매우 유용합니다.
- **IPython 콘솔**: 코드를 대화형으로 실행하고 즉시 결과를 확인할 수 있습니다. 코드 전체를 실행하지 않고 특정 부분만 테스트하기에 용이합니다.
- **정적 분석 및 디버깅**: 코드의 오류를 사전에 감지하고, 중단점(Breakpoint)을 설정하여 코드 실행을 단계별로 추적하는 강력한 디버깅 기능을 제공합니다.
- **플롯(Plots) 창**: Matplotlib 등으로 생성된 그래프가 별도의 창에 표시되어, 코드를 수정하며 그래프의 변화를 바로 확인할 수 있습니다.
# macOS / Linux (bash)
source .venv/bin/activate
```
활성화되면 터미널 프롬프트(명령어를 입력하는 곳) 앞에 `(.venv)`와 같은 표시가 나타납니다.
- **장점**:
- 데이터 분석 및 시각화에 매우 직관적인 환경.
- 변수와 데이터 구조를 시각적으로 탐색하는 기능이 탁월함.
- 학술 및 연구 커뮤니티에서 여전히 널리 사용됨.
**3. 패키지 설치 및 관리 (`pip`)**
- **단점**:
- 웹 개발이나 범용 소프트웨어 개발보다는 데이터 분석 작업에 치우쳐 있음.
- VSCode 등에 비해 확장성이 다소 부족함.
가상환경이 활성화된 상태에서 <a href="glossary.md#pip">`pip`</a>으로 필요한 라이브러리를 설치합니다. 이제 설치되는 패키지는 시스템이 아닌 `.venv` 폴더 내에만 저장됩니다.
- **권장 사용자**:
- 데이터의 구조와 내용을 시각적으로 확인하며 탐색적 데이터 분석(EDA)을 주로 수행하는 데이터 분석가.
- 파이썬으로 데이터 과학에 입문하는 학생 및 연구자.
- MATLAB, RStudio와 같은 통계 분석 툴에 익숙한 사용자.
```bash
# numpy와 pandas 설치
pip install numpy pandas
- **설치 및 시작**:
Anaconda 배포판을 설치하면 Spyder가 기본적으로 포함되어 있어 가장 쉽게 시작할 수 있습니다.
```bash
# Anaconda 설치 후, Anaconda Navigator에서 Spyder를 실행하거나
# 터미널에서 아래 명령어로 실행할 수 있습니다.
spyder
```
> [공식 홈페이지 바로가기](https://www.spyder-ide.org/)
# 설치된 패키지 목록 확인
pip list
---
# 설치된 패키지 목록을 파일로 저장 (협업에 필수!)
# 이 파일은 나중에 다른 환경에서 동일한 라이브러리를 한번에 설치하는 데 사용됩니다.
# (예: pip install -r requirements.txt)
pip freeze > requirements.txt
```
## 2. JupyterLab (주피터랩)
**4. 가상환경 비활성화하기**
<p align="center">
<img src="https://jupyter.org/assets/main-logo.svg" alt="Jupyter-logo" width="150"/>
</p>
작업이 끝나면 `deactivate` 명령어로 가상환경을 빠져나올 수 있습니다.
- **개요**: JupyterLab은 웹 브라우저에서 실행되는 차세대 대화형 개발 환경입니다. 코드, 실행 결과, 시각화, 마크다운 설명 등을 '노트북(.ipynb)'이라는 파일 형식으로 통합하여 관리합니다. 실험 과정과 결과를 이야기처럼 풀어낼 수 있어, 연구 내용을 공유하고 재현하는 데 매우 효과적입니다.
```bash
deactivate
```
- **핵심 특징**:
- **노트북 인터페이스**: 코드 셀과 마크다운 셀을 조합하여 실행 가능한 문서를 만들 수 있습니다. 코드와 그 결과, 그리고 설명이 함께 저장되므로 프로젝트의 논리적 흐름을 이해하기 쉽습니다.
- **셀(Cell) 단위 실행**: 전체 코드를 한 번에 실행할 필요 없이, 코드 블록(셀) 단위로 실행하고 결과를 즉시 확인할 수 있어 빠른 실험과 디버깅이 가능합니다.
- **유연한 인터페이스**: 터미널, 텍스트 편집기, 노트북, CSV 뷰어 등 다양한 컴포넌트를 탭과 분할 화면으로 자유롭게 배치하여 자신만의 작업 공간을 구성할 수 있습니다.
- **광범위한 커널 지원**: 파이썬뿐만 아니라 R, Julia, Scala 등 다양한 프로그래밍 언어를 지원합니다.
> 💡 **`.gitignore` 설정**: `.venv` 폴더는 용량이 크고 사용자마다 환경이 다를 수 있어 <a href="glossary.md#git">Git</a>으로 관리하지 않습니다. 프로젝트의 `.gitignore` 파일에 `.venv`를 반드시 추가해주세요.
- **장점**:
- 빠른 프로토타이핑과 데이터 탐색에 매우 강력함.
- 코드, 결과, 설명을 함께 묶어 공유 및 발표 자료로 활용하기 좋음.
- 웹 기반이므로 원격 서버에 설치하여 어디서든 접속해 사용할 수 있음.
---
- **단점**:
- .ipynb 파일은 일반 .py 파일과 달리 JSON 형식이라 버전 관리(Git) 시 충돌 해결이 다소 까다로움.
- 긴 코드나 복잡한 프로젝트 전체를 관리하기에는 IDE보다 불편할 수 있음.
### 2. 데이터 분석가를 위한 IDE: Spyder & JupyterLab
#### 1) Spyder (스파이더)
- **개요**: 과학/공학 계산, 데이터 분석에 특화된 <a href="glossary.md#ide">IDE</a>. MATLAB과 유사한 인터페이스가 특징입니다.
- **핵심 기능**: **변수 탐색기(Variable Explorer)**. 코드 실행 중인 모든 변수, 데이터프레임을 GUI로 직접 확인할 수 있어 데이터의 변화를 추적하는 데 매우 강력합니다.
- **권장 사용자**: 데이터의 구조와 내용을 시각적으로 확인하며 탐색적 데이터 분석(EDA)을 주로 수행하는 데이터 분석가, 파이썬으로 데이터 과학에 입문하는 학생.
#### 2) JupyterLab (주피터랩)
- **개요**: 웹 브라우저에서 실행되는 차세대 대화형 개발 환경. 코드, 실행 결과, 시각화, 설명을 '<a href="glossary.md#jupyter-notebook">노트북(.ipynb)</a>' 파일 하나에 통합 관리합니다.
- **핵심 기능**: **셀(Cell) 단위 실행**. 전체 코드를 실행할 필요 없이, 코드 블록(셀) 단위로 실행하고 결과를 즉시 확인하여 빠른 실험과 디버깅이 가능합니다.
- **권장 사용자**: 빠른 아이디어 검증과 데이터 시각화가 중요한 모든 AI/ML 연구원, 분석 과정을 단계별로 기록하고 공유해야 하는 데이터 분석가.
#### 💡 쉽게 이해하기: IDE는 나의 '디지털 작업실'입니다
> <a href="glossary.md#ide">IDE(통합 개발 환경)</a>는 코딩 작업을 위한 '작업실' 또는 '공방'에 비유할 수 있습니다. 어떤 가구를 만들지에 따라 목공소의 도구 배치가 달라지듯, 어떤 종류의 개발을 하느냐에 따라 적합한 IDE가 다릅니다.
>
> - **Spyder: '정밀 데이터 분석실'**
> - Spyder는 마치 현미경과 계측 장비가 잘 갖춰진 실험실과 같습니다. 특히 '변수 탐색기'는 실행 중인 모든 데이터의 속을 돋보기로 들여다보듯 생생하게 보여줍니다. 데이터의 속을 하나하나 뜯어보며 분석하는 작업에 최적화된 '데이터 과학자'의 공간입니다.
>
> - **JupyterLab: '아이디어 스케치북'**
> - JupyterLab은 코드, 설명, 그래프, 실행 결과를 한 페이지에 차곡차곡 기록하는 똑똑한 '실험 노트'나 '스케치북'입니다. 아이디어가 떠오를 때마다 코드 조각(셀)을 바로 실행해보고 결과를 눈으로 확인하는 과정이 매우 빠르고 직관적입니다. 탐색하고, 발견하고, 이야기하는 과정에 매우 강력한 도구입니다.
>
> - **VSCode: '만능 조립식 공방'**
> - <a href="glossary.md#vscode">VSCode</a>는 레고 블록처럼 필요한 기능을 마음대로 조립해서 만드는 '만능 공방'입니다. 처음에는 텅 빈 작업대(가벼운 텍스트 에디터) 같지만, '확장 프로그램'이라는 부품을 하나씩 붙여서 파이썬 개발, 웹 개발, AI 모델링까지 못하는 게 없는 궁극의 작업 공간으로 변신시킬 수 있습니다. 이것이 바로 우리가 VSCode를 주력 개발 환경으로 선택한 이유입니다. 하나의 공방에서 모든 것을 해결할 수 있으니까요!
- **권장 사용자**:
- 빠른 아이디어 검증과 데이터 시각화가 중요한 모든 AI/ML 연구원 및 개발자.
- 분석 과정을 단계별로 기록하고 다른 사람과 공유해야 하는 데이터 분석가.
- 강의나 튜토리얼 자료를 작성하는 교육자.
---
- **설치 및 시작**:
```bash
# pip을 이용한 설치
pip install jupyterlab
## 2일차: 통합 개발 환경(IDE)과 최종 선택
# 설치 후 터미널에서 실행
jupyter lab
```
> [공식 홈페이지 바로가기](https://jupyterlab.readthedocs.io/)
### 🎯 오늘의 목표
- 범용 IDE인 VSCode와 클라우드 환경인 Colab, Kaggle을 이해합니다.
- 각 도구의 장단점을 비교하고 우리 과정에서 사용할 최종 도구를 결정합니다.
---
## 3. VSCode (비주얼 스튜디오 코드)
### 1. 모든 개발을 하나로: VSCode
<p align="center">
<img src="https://code.visualstudio.com/assets/images/code-stable.png" alt="VSCode-logo" width="150"/>
</p>
- **개요**: 가볍고 빠르면서도 강력한 기능을 자랑하는 Microsoft의 범용 코드 편집기. 풍부한 **확장 기능**을 통해 AI 모델 개발, API 서버, 웹 개발까지 하나의 툴에서 해결 가능한 'All-in-One' 솔루션입니다.
- **핵심 기능**: 마켓플레이스를 통한 **무한한 확장성**. Python, Jupyter, <a href="glossary.md#docker">Docker</a>, 원격 접속 등 원하는 모든 기능을 추가하여 자신만의 완벽한 IDE를 만들 수 있습니다.
- **권장 사용자**: AI 모델과 서비스를 함께 개발해야 하는 개발자, 원격 서버나 Docker 환경에서 자주 작업하는 개발자, 하나의 도구로 모든 개발을 통일하고 싶은 사용자.
- **개요**: 가볍고 빠르면서도 강력한 기능을 자랑하는 Microsoft의 범용 코드 편집기입니다. 풍부한 확장 기능 생태계를 통해 단순한 텍스트 에디터를 넘어, 거의 모든 언어와 프레임워크를 지원하는 완전한 통합 개발 환경(IDE)으로 변신할 수 있습니다. AI 개발에 필요한 모든 작업을 하나의 툴에서 해결할 수 있는 'All-in-One' 솔루션입니다.
### 2. 설치 없이 바로 시작하는 클라우드 환경: Colab & Kaggle
- **핵심 특징**:
- **강력한 확장성**: 마켓플레이스에 등록된 수많은 확장을 통해 원하는 기능을 무한히 추가할 수 있습니다. (Python, Jupyter, Docker, 원격 접속 등)
- **통합 터미널**: 편집기 내에서 바로 터미널을 열어 명령어를 실행할 수 있어 작업 흐름이 끊기지 않습니다.
- **Git 통합**: 코드 버전 관리를 위한 Git 기능이 기본적으로 내장되어 있어 GUI 환경에서 편리하게 `commit`, `push`, `pull` 등을 수행할 수 있습니다.
- **IntelliSense**: 단순한 자동 완성을 넘어, 코드 문맥을 이해하고 함수 시그니처, 변수 타입 등을 스마트하게 제안해줍니다.
#### 1) Google Colab (구글 코랩)
- **개요**: Google에서 제공하는 클라우드 기반 무료 <a href="glossary.md#jupyter-notebook">Jupyter 노트북</a> 환경. 설치 없이 웹 브라우저만 있으면 즉시 사용 가능하며, **무료로 고성능 GPU/TPU를 제공**하는 것이 가장 큰 장점입니다.
- **권장 사용자**: 로컬 PC 사양이 낮거나 환경 구축이 부담스러운 입문자, <a href="glossary.md#deep-learning">딥러닝</a> 모델 학습 등 고사양 GPU가 필요한 학생 및 연구자.
- **장점**:
- AI 모델 개발부터 API 서버, 웹 프론트엔드 개발까지 하나의 툴에서 가능함.
- 가볍고 실행 속도가 빠름.
- 강력한 디버깅, 원격 개발, Git 통합 기능 제공.
- 활발한 커뮤니티와 풍부한 확장 기능.
#### 2) Kaggle (캐글)
- **개요**: 세계 최대의 데이터 과학 커뮤니티 및 대회 플랫폼. Colab처럼 무료 Jupyter 환경(Kaggle Notebooks)을 제공하며, **실제 기업의 문제와 데이터셋**으로 실력을 겨루고 배울 수 있는 곳입니다.
- **권장 사용자**: 데이터 과학 포트폴리오를 만들고 싶은 취준생, 실제 데이터를 다루며 실력을 검증하고 싶은 개발자.
- **단점**:
- 순수한 데이터 분석 환경으로는 Spyder나 Jupyter보다 초기 설정이 조금 더 필요할 수 있음.
- 기능이 매우 많아 처음에는 모든 기능을 파악하기 어려울 수 있음.
### 3. 최종 결정: 우리 과정의 개발 환경
- **권장 사용자**:
- AI 모델뿐만 아니라, 해당 모델을 사용하는 API 서버 등 다양한 컴포넌트를 함께 개발해야 하는 풀스택 개발자 및 소프트웨어 엔지니어.
- 원격 서버나 Docker 컨테이너 환경에서 개발을 자주 하는 개발자.
- 하나의 도구로 모든 개발 작업을 통일하고 싶은 사용자.
| 도구 | 주요 용도 | 장점 | 단점 | 우리 과정에서의 역할 |
|---|---|---|---|---|
| **VSCode** | **범용 개발 (AI, 웹, 서버)** | **높은 확장성, 통합 터미널, <a href="glossary.md#git">Git</a> 연동, 가벼움** | 초기 설정이 필요함 | **주력 개발 환경** |
| JupyterLab | 대화형 데이터 분석 | 빠른 프로토타이핑, 결과 공유 용이 | 대규모 프로젝트 관리에는 부적합 | VSCode 확장 기능으로 사용 |
| Spyder | 탐색적 데이터 분석 | 직관적인 변수 확인 | 기능 확장이 제한적 | 사용하지 않음 |
| Colab/Kaggle | 클라우드 모델 학습 | 무료 <a href="glossary.md#gpu">GPU</a>, 설치 불필요 | 런타임 제한, 커스터마이징 어려움 | **보조 환경** (GPU 필요 시) |
- **추천 확장 기능**:
- `Python` (ms-python.python): Microsoft 공식 파이썬 지원. IntelliSense, 린팅, 디버깅, 포맷팅 등 필수 기능 제공.
- `Jupyter` (ms-toolsai.jupyter): VSCode 내에서 주피터 노트북(.ipynb)을 직접 실행하고 변수 확인, 데이터 시각화 등을 지원. JupyterLab의 장점을 그대로 누릴 수 있음.
- `Docker` (ms-azuretools.vscode-docker): 컨테이너와 이미지를 GUI로 관리하고, Dockerfile 및 docker-compose 파일 작성을 도와줌.
- `Remote - SSH` (ms-vscode-remote.remote-ssh): 원격 서버에 SSH로 접속하여, 마치 로컬 머신에서 작업하는 것처럼 파일을 편집하고 터미널을 사용할 수 있게 해줌.
- `GitHub Copilot` (GitHub.copilot): AI가 코드 전체 라인이나 함수를 통째로 제안해주는 강력한 코드 어시스턴트.
**결론: 우리는 <a href="glossary.md#vscode">VSCode</a>를 주력 개발 환경으로 사용합니다!**
> [공식 홈페이지 바로가기](https://code.visualstudio.com/)
우리의 목표는 단순히 데이터를 분석하는 것을 넘어, AI 모델을 기반으로 한 완전한 '서비스'를 만드는 것입니다. <a href="glossary.md#vscode">VSCode</a>는 탐색적 분석(Jupyter)부터 웹 서버 개발, 그리고 배포(<a href="glossary.md#docker">Docker</a>)까지, 서비스 개발의 전 과정을 하나의 작업실에서 매끄럽게 연결해주는 가장 강력하고 현대적인 도구입니다. 이 '만능 공방'에서 우리는 아이디어를 실제 서비스로 만들어가는 즐거움을 만끽하게 될 것입니다.
---
## 4. Google Colab (구글 코랩)
<p align="center">
<img src="https://colab.research.google.com/img/colab_favicon_256px.png" alt="Colab-logo" width="150"/>
</p>
- **개요**: Google에서 제공하는 클라우드 기반의 무료 Jupyter 노트북 환경입니다. 별도의 설치 과정 없이 웹 브라우저만 있으면 즉시 파이썬 코드를 작성하고 실행할 수 있습니다. 특히, 무료로 고성능 GPU(NVIDIA Tesla T4 등) 및 TPU를 사용할 수 있다는 점이 가장 큰 장점입니다.
## 3일차: 실습 - VSCode로 나만의 개발 환경 구축하기
- **핵심 특징**:
- **무설치 환경**: 로컬 PC에 파이썬이나 라이브러리를 설치할 필요가 없습니다. 구글 계정만 있으면 바로 시작할 수 있습니다.
- **무료 GPU/TPU 지원**: `런타임 > 런타임 유형 변경` 메뉴를 통해 몇 번의 클릭만으로 GPU나 TPU 가속을 활성화할 수 있어, 딥러닝 모델 학습 시간을 획기적으로 단축시킬 수 있습니다.
- **Google Drive 연동**: Google Drive를 Colab 노트북에 마운트하여 파일을 쉽게 읽고 쓸 수 있습니다. 데이터셋이나 학습된 모델을 저장하기에 편리합니다.
- **공유 및 협업**: 작성한 노트북을 다른 사람과 쉽게 공유하고, 여러 명이 동시에 편집할 수도 있습니다.
### 🎯 오늘의 목표
- VSCode를 설치하고 AI 개발에 필수적인 확장 프로그램을 설정합니다.
- VSCode 내에서 가상환경을 만들고 라이브러리를 설치/관리합니다.
- Jupyter 노트북을 실행하여 모든 설정이 완료되었는지 확인합니다.
- **장점**:
- 복잡한 개발 환경 설정 없이 AI/ML을 바로 시작할 수 있음.
- 고가의 GPU 장비 없이도 딥러닝 모델을 학습시킬 수 있음.
- Google Drive와의 연동으로 데이터 관리가 편리함.
- **단점**:
- 일정 시간(기본 12시간) 이상 사용하면 런타임이 초기화되며, 설치했던 라이브러리와 변수들이 사라짐.
- 무료 버전은 GPU 할당이 보장되지 않으며, 자원 사용량에 제한이 있음(Colab Pro/Pro+ 유료 플랜으로 업그레이드 가능).
- 로컬 파일 시스템에 직접 접근하기는 다소 번거로움.
---
- **권장 사용자**:
- 로컬 PC 사양이 낮거나 개발 환경 구축이 부담스러운 AI 입문자.
- 딥러닝 모델 학습 등 고사양의 GPU 자원이 필요한 학생 및 연구자.
- 빠르게 아이디어를 테스트하고 결과를 공유하고 싶은 모든 개발자.
### 1. VSCode 설치하기
[공식 홈페이지](https://code.visualstudio.com/)에 접속하여 자신의 운영체제에 맞는 'Stable' 버전을 다운로드하고 설치를 진행합니다.
> [서비스 바로가기](https://colab.research.google.com/)
### 2. 필수 확장 프로그램 설치하기
VSCode를 실행한 후, 왼쪽 사이드바에서 '확장(Extensions)' 아이콘(네모 블록 모양)을 클릭하고 다음 확장 프로그램들을 검색하여 설치합니다.
---
1. **Python** (ms-python.python)
- Microsoft에서 제공하는 공식 파이썬 확장입니다. 코드 자동 완성(IntelliSense), 디버깅, 린팅 등 파이썬 개발의 모든 필수 기능을 제공합니다.
2. **Jupyter** (ms-toolsai.jupyter)
- VSCode 내에서 주피터 노트북(.ipynb) 파일을 생성하고 실행할 수 있게 해줍니다. JupyterLab의 장점을 VSCode 안에서 그대로 누릴 수 있습니다.
3. **(선택) Korean Language Pack** (ms-ceintl.vscode-language-pack-ko)
- VSCode의 메뉴와 메시지를 한국어로 보고 싶다면 설치하세요.
## 5. Kaggle (캐글)
### 3. 프로젝트 폴더 및 가상환경 설정 실습
<p align="center">
<img src="https://www.kaggle.com/static/images/site-logo.svg" alt="Kaggle-logo" width="150"/>
</p>
이제 앞에서 배운 가상환경 만들기를 VSCode 안에서 직접 해보겠습니다.
- **개요**: Kaggle은 세계 최대의 데이터 과학 커뮤니티이자, 데이터 분석 및 머신러닝 모델링 대회를 주최하는 플랫폼입니다. Google Colab과 유사하게 클라우드 기반의 무료 Jupyter 노트북 환경(Kaggle Notebooks)을 제공하여, 누구나 데이터 분석과 모델 개발을 쉽게 시작할 수 있습니다.
1. **프로젝트 폴더 열기**: `파일 > 폴더 열기` 메뉴를 통해 `my-first-ai-project` 폴더를 엽니다.
2. **통합 터미널 열기**: `터미널 > 새 터미널` 메뉴를 클릭하거나 단축키(`` Ctrl + ` ``)를 눌러 VSCode 하단에 터미널을 엽니다.
3. **가상환경 생성 및 활성화**: 터미널에 아래 명령어를 차례로 입력합니다.
```powershell
# 1. 가상환경 생성 (이미 만들었다면 생략)
python -m venv .venv
- **핵심 특징**:
- **데이터 과학 대회(Competitions)**: 기업과 연구 기관이 실제 데이터를 문제와 함께 제공하면, 전 세계 데이터 과학자들이 모델을 개발하여 순위를 경쟁합니다. 실전 경험을 쌓고 최신 기술을 접목해볼 최고의 기회입니다.
- **방대한 데이터셋**: 수만 개가 넘는 공개 데이터셋을 탐색하고 다운로드하여 자신의 프로젝트에 활용할 수 있습니다.
- **무료 GPU/TPU 지원**: Colab과 마찬가지로, 모델 학습에 필요한 GPU 및 TPU를 무료로 사용할 수 있습니다 (주간 사용량 제한 있음).
- **노트북 공유 및 학습**: 다른 참가자들이 공개한 노트북(코드)을 보며 문제 해결 아이디어를 얻고, 자신의 코드를 공유하며 토론할 수 있는 강력한 학습 환경을 제공합니다.
# 2. 활성화
.venv\Scripts\Activate.ps1
```
이때 VSCode가 "새로운 환경을 발견했습니다"라는 알림을 표시하며, 해당 환경을 선택하겠냐고 물어볼 수 있습니다. **"예"**를 선택하면 됩니다.
- **장점**:
- 실제 산업 문제를 기반으로 한 대회를 통해 실무 경험을 쌓을 수 있음.
- 세계적인 전문가들의 코드를 보며 학습하고 성장할 수 있음.
- 다양한 종류의 정제된 데이터셋에 쉽게 접근 가능함.
### 4. 라이브러리 설치 및 "Hello, AI!"
- **단점**:
- 무료 GPU/TPU 사용 시간에 주간 제한이 있음.
- Colab에 비해 동시 사용이나 런타임의 유연성이 다소 떨어질 수 있음.
- 플랫폼이 대회와 커뮤니티 중심으로 구성되어 있어, 순수 개인 프로젝트 개발에는 Colab이 더 편리할 수 있음.
1. **필수 라이브러리 설치**: 활성화된 터미널(`(.venv)` 표시 확인)에 아래 명령어를 입력하여 AI 개발에 필요한 핵심 라이브러리들을 설치합니다.
```bash
pip install numpy pandas matplotlib scikit-learn jupyterlab
```
2. **Jupyter 노트북으로 테스트**:
- VSCode 탐색기에서 **새 파일 아이콘**을 클릭하고 파일 이름을 `hello_ai.ipynb`로 만듭니다.
- `.ipynb` 파일이 열리면, 첫 번째 셀(Cell)에 아래 코드를 입력합니다.
```python
import pandas as pd
data = {'이름': ['철수', '영희', '민수'],
'나이': [25, 30, 28]}
df = pd.DataFrame(data)
print("Hello, AI! Pandas DataFrame 생성 성공!")
df
```
- 셀 왼쪽의 **실행 버튼(▶)**을 누르거나 `Shift + Enter` 키를 누릅니다.
- **[중요!]** 처음 코드를 실행할 때, VSCode가 오른쪽 상단에서 **"커널(Kernel) 선택"**을 요청할 수 있습니다.
- **💡 커널이 뭔가요?** '커널'은 우리가 작성한 파이썬 코드를 실제로 해석하고 실행하는 '엔진'이라고 생각하면 쉽습니다. 우리는 여러 개의 파이썬 엔진(전역 파이썬, 가상환경 파이썬 등) 중, 이 프로젝트만을 위한 전용 엔진, 즉 **`.venv` 가상환경의 파이썬을 선택해야 합니다.**
- "커널 선택"을 클릭한 뒤, **"Python Environments"** 목록에서 `.venv`라는 글자가 포함된 파이썬 인터프리터를 선택해주세요.
- 코드 아래에 "Hello, AI!..." 메시지와 함께 표 형태의 데이터프레임이 나타나면, 개발 환경 구축이 성공적으로 완료된 것입니다! 🥳
- **권장 사용자**:
- 데이터 과학 및 머신러닝 분야의 포트폴리오를 만들고 싶은 취업 준비생.
- 실제 데이터를 다루며 실력을 검증하고 싶은 데이터 분석가 및 AI 개발자.
- 다른 사람의 문제 해결 방식을 배우고 최신 AI/ML 트렌드를 따라가고 싶은 학생 및 연구자.
---
- **설치 및 시작**:
```bash
# pip을 이용한 설치
pip install kaggle
## 📝 1주차 요약
# Kaggle API 토큰 설정 후 사용
```
> [공식 홈페이지 바로가기](https://www.kaggle.com/)
이번 주차에는 성공적인 AI 서비스 개발을 위한 초석, 즉 개발 환경을 구축했습니다.
---
- **가상환경의 이해**: '프로젝트용 필통'이라는 비유를 통해, 왜 프로젝트별로 독립된 개발 환경이 필요한지 이해하고 `venv`를 이용해 직접 가상환경을 만들고 활성화하는 방법을 배웠습니다.
- **다양한 IDE 탐색**: 데이터 분석에 특화된 `Spyder``JupyterLab`, 그리고 만능 공방과도 같은 `VSCode`의 특징과 장단점을 비교 분석했습니다.
- **표준 개발 환경 확정**: 우리는 AI 모델 개발부터 API 서버 구축까지 전 과정을 아우를 수 있는 VSCode를 주력 개발 환경으로, GPU가 필요할 때는 Google Colab을 보조 환경으로 사용하기로 결정했습니다.
- **VSCode 설정 완료**: VSCode에 Python, Jupyter 등 핵심 확장 프로그램을 설치하고, 터미널에서 가상환경을 연동하여 `hello_ai.ipynb` 파일을 성공적으로 실행했습니다.
## 요약 및 어떤 도구를 선택해야 할까?
## ✅ 1주차 환경 설정 체크리스트
각 도구는 뚜렷한 장단점을 가지고 있어, 하나의 정답은 없습니다. 프로젝트의 성격과 개인의 선호도에 따라 최적의 선택이 달라집니다.
모든 설정을 잘 마쳤는지 스스로 확인해보세요.
| 구분 | Spyder | JupyterLab | VSCode | Google Colab | Kaggle |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **형태** | 데스크탑 IDE | 웹 기반 노트북 | 범용 코드 편집기 | 클라우드 노트북 | 클라우드 노트북 |
| **주요 장점** | 데이터 시각화, 변수 탐색 | 대화형 코드 실행, 문서화 | 강력한 확장성, 통합 개발 | 무료 GPU, 무설치 | 데이터셋, 대회, 커뮤니티 |
| **주요 단점**| 확장성 부족 | 버전 관리 복잡 | 초기 설정 필요 | 런타임 제한 | 사용량 제한 |
| **추천 사용자** | 데이터 분석가, 과학자 | 연구원, 교육자 | 풀스택/SW 엔지니어 | AI 입문자, 학생 | AI 실무 지망생, 데이터 과학자 |
- [ ] Python이 내 컴퓨터에 설치되어 있다.
- [ ] VSCode가 설치되어 있고, Python 및 Jupyter 확장 프로그램이 설치되어 있다.
- [ ] `my-first-ai-project` 라는 이름의 프로젝트 폴더를 만들었다.
- [ ] VSCode의 통합 터미널에서 `.venv` 가상환경을 만들고 활성화할 수 있다. (`(.venv)` 표시 확인)
- [ ] 활성화된 가상환경에 `pip``pandas` 등의 라이브러리를 설치했다.
- [ ] `hello_ai.ipynb` 노트북 파일을 만들고, `.venv` 가상환경을 커널로 선택하여 코드를 실행할 수 있다.
---
### 🚀 다음 시간엔...
지금까지 AI 개발을 위한 다양한 도구와 환경 설정 방법을 알아보았습니다. 이제 도구는 준비되었으니, 본격적으로 코드를 작성할 시간입니다.
축하합니다! 이제 여러분은 AI 서비스 개발을 위한 전문적인 환경을 모두 갖추었습니다.
이 강력한 개발 환경 위에서, 다음 시간에는 파이썬의 핵심 문법들을 본격적으로 탐험하며 실제 코딩의 세계로 들어가 보겠습니다.
다음 [Part 2: 파이썬 핵심 문법](part_2_python_core_syntax.md)에서는 데이터 분석과 AI 모델링의 기반이 되는 파이썬의 필수 문법을 빠르게 훑어보겠습니다.
\ No newline at end of file
**➡️ 다음 시간: [Part 2: 파이썬 핵심 문법](part_2_python_core_syntax.md)**
\ No newline at end of file
# Part 2: 파이썬 핵심 문법 마스터하기
# Part 2: 파이썬 핵심 문법 마스터하기 (2주차)
---
### 💡 지난 시간 복습
[Part 1: AI 개발 환경 구축](part_1_ai_development_environment.md)에서는 VSCode, Jupyter, Colab 등 다양한 개발 도구의 특징을 알아보고, 프로젝트의 독립성을 보장하는 **가상환경**의 중요성과 사용법을 익혔습니다. 이제 우리는 코드를 작성하고 실행할 준비를 마쳤습니다.
**⬅️ 이전 시간: [Part 1: AI 개발 환경 완벽 구축 가이드](part_1_ai_development_environment.md)**
---
AI 개발의 세계에 오신 것을 환영합니다! 파이썬은 AI와 데이터 과학 분야에서 가장 사랑받는 언어입니다. 문법이 간결하고 사람의 생각과 비슷해서, 프로그래밍을 처음 시작하는 분들도 쉽게 배울 수 있습니다. 이 문서는 [점프 투 파이썬](https://wikidocs.net/book/1)을 참고하여 파이썬을 처음 접하시거나, 다시 복습하고 싶은 분들을 위해 가장 핵심적인 문법만을 골라 친절하게 설명합니다.
## 1. 변수와 자료형: 데이터 다루기의 첫걸음
### 1.1. 변수(Variable): 데이터에 이름표 붙이기
프로그래밍은 결국 '데이터'를 다루는 일입니다. **변수(Variable)**는 데이터에 이름을 붙이고 저장하는 '라벨이 붙은 상자'와 같습니다. 상자에 물건을 넣고 필요할 때 꺼내 쓰는 것처럼, 변수에 데이터를 저장하고 필요할 때마다 이름을 불러 사용합니다.
```python
# 'box'라는 이름의 상자(변수)에 숫자 100을 저장합니다.
box = 100
print(box) # 100
# 이제 'box'에 문자열 "Hello"를 저장합니다. 상자의 내용물이 바뀌었습니다.
box = "Hello"
print(box) # "Hello"
```
지난 시간에는 VSCode와 가상환경을 통해 우리만의 코딩 놀이터를 만들었습니다. 이제 모든 준비는 끝났습니다.
이번 주차에는 AI 개발의 가장 기본 언어인 파이썬의 핵심 문법들을 5일간의 여정을 통해 하나씩 정복하며, 실제 코드를 작성하는 즐거움을 느껴보는 시간을 갖겠습니다.
파이썬은 매우 유연해서, 상자에 어떤 종류의 물건이 들어갈지 미리 말해주지 않아도 됩니다. 숫자를 넣으면 숫자 상자가, 글자를 넣으면 글자 상자가 되는 식이죠. 이를 **동적 타이핑(Dynamic Typing)**이라고 부릅니다.
AI 개발의 세계에 오신 것을 환영합니다! 파이썬은 AI와 데이터 과학 분야에서 가장 사랑받는 언어입니다. 문법이 간결하고 사람의 생각과 비슷해서, 프로그래밍을 처음 시작하는 분들도 쉽게 배울 수 있습니다. 2주차에는 파이썬의 가장 핵심적인 문법을 하나씩 정복해나갑니다.
---
### 1.2. 자료형(Data Types): 데이터의 종류
## 1일차(월): 모든 것의 시작, 변수와 자료형
파이썬은 다양한 종류의 데이터를 다룰 수 있습니다. 이를 **자료형**이라고 부릅니다.
### 🎯 오늘의 목표
- 변수(Variable)를 사용해 데이터를 저장하고 이름을 붙일 수 있습니다.
- 숫자(Integer, Float), 문자열(String), 불리언(Boolean) 등 파이썬의 기본 데이터 종류를 이해합니다.
#### 1.2.1. 숫자형 (Numeric)
정수(`int`), 실수(`float`)는 계산을 위해 사용되는 가장 기본적인 자료형입니다.
---
```python
# 정수(Integer)와 실수(Float)
a = 10
b = 3.14
# 기본적인 사칙연산
print(f"덧셈: {a + b}")
print(f"나눗셈: {a / b}")
# 프로그래밍에서만 볼 수 있는 연산
print(f"나눗셈 후 나머지: {10 % 3}") # 1
print(f"나눗셈 후 몫만: {10 // 3}") # 3
print(f"거듭제곱: {2 ** 4}") # 2의 4제곱 = 16
```
### 1. 변수(Variable): 데이터에 이름표 붙이기
프로그래밍은 결국 '데이터'를 다루는 일입니다. <a href="glossary.md#variable">변수(Variable)</a>는 숫자, 글자 같은 데이터를 저장하는 '상자'입니다. 이 상자에 `name`, `age` 같은 이름표를 붙여두면, 필요할 때마다 이름표를 불러서 상자 안의 데이터를 쉽게 꺼내 쓸 수 있습니다.
> 💡 **Tip: 파이썬 3의 나눗셈**
> 파이썬 2에서는 `10 / 3`의 결과가 `3`(정수)이었지만, 파이썬 3부터는 `/` 연산자가 항상 `float`(실수)를 반환합니다 (`3.333...`). 정수 나눗셈의 몫을 원할 때는 `//` 연산자를 사용해야 합니다. 이 차이점을 기억하는 것은 매우 중요합니다.
#### 💡 쉽게 이해하기: 변수는 '이름이 붙은 저장 상자'입니다
> 여러분이 이사할 때, 짐을 담은 상자마다 '주방용품', '겨울옷', '책'처럼 내용물이 무엇인지 이름표(라벨)를 붙여놓습니다. 나중에 '책' 상자가 필요하면, 모든 상자를 열어볼 필요 없이 '책'이라는 이름표가 붙은 상자만 찾으면 되죠.
>
> 변수가 바로 이 **'이름표가 붙은 상자'**입니다. `age = 30` 이라는 코드는, `30`이라는 데이터를 `age`라는 이름의 상자에 넣는다는 뜻입니다. 이제부터 우리는 `age`라는 이름만 부르면 컴퓨터가 알아서 `30`이라는 값을 찾아줍니다.
#### 1.2.2. 문자열 (String)
'글자'들의 나열입니다. 작은따옴표(`'`) 또는 큰따옴표(`"`)로 감싸서 만듭니다.
> [!TIP]
> 이 파트의 모든 코드 예제는 아래 링크의 파이썬 파일에서 직접 실행하고 수정해볼 수 있습니다.
> **실행 가능한 소스 코드: [`part_2_3_python_syntax_collections.py`](../source_code/part_2_3_python_syntax_collections.py)**
```python
greeting = "Hello, Python!"
# f-string을 사용하면 문자열 안에 {변수명} 형태로 값을 쉽게 넣을 수 있습니다.
name = "앨리스"
print(f"안녕하세요, {name}님!")
# 인덱싱 (Indexing): 특정 위치의 글자 하나를 가져오기 (0부터 시작!)
first_char = greeting[0] # 'H'
# 슬라이싱 (Slicing): [시작:끝] 형태로 범위를 지정하여 여러 글자를 가져오기
py_string = greeting[7:13] # 'Python'
# 주요 문자열 메서드
text = " Welcome to Python! "
print(f"대문자로: {text.upper()}")
print(f"공백 제거: '{text.strip()}'")
print(f"치환: {text.replace('Python', 'World')}")
print(f"분리: {text.strip().split(' ')}")
# 변수 만들기 (할당)
my_name = "파이" # 문자열 데이터
my_age = 20 # 숫자 (정수) 데이터
pi = 3.14 # 숫자 (실수) 데이터
is_student = True # 불리언 (참/거짓) 데이터
# 변수 사용하기 (참조)
print("안녕하세요, 제 이름은", my_name, "입니다.")
print("제 나이는", my_age, "살이고, 학생인가요?", is_student)
```
> 💡 **Tip: f-string을 생활화하세요!**
> 과거에는 `"... %s ..." % name` 이나 `"... {}".format(name)` 같은 방식으로 문자열을 포맷팅했지만, f-string (`f"..."`)은 훨씬 간결하고, 직관적이며, 빠릅니다. 특별한 이유가 없다면 항상 f-string을 사용하는 것이 현대 파이썬 코딩 스타일입니다.
#### 1.2.3. 불리언 (Boolean)
`True`(참)와 `False`(거짓) 단 두 가지 값만 가지는 특별한 자료형입니다. 조건 판단의 기준이 됩니다.
### 2. 기본 자료형(Data Types): 데이터의 종류
파이썬은 상자에 담는 데이터의 종류를 구분합니다.
```python
is_active = True
has_permission = False
- **정수 (Integer)**: `1`, `100`, `-5` 같이 소수점이 없는 숫자.
- **실수 (Float)**: `3.14`, `-0.5` 같이 소수점이 있는 숫자.
- **문자열 (String)**: `"안녕하세요"`, `'Python'` 같이 따옴표로 감싼 글자들.
- **<a href="glossary.md#boolean">불리언 (Boolean)</a>**: `True` 또는 `False`. 조건이 맞는지 틀리는지를 나타내는 값입니다.
# 논리 연산자: and, or, not
print(f"is_active and has_permission: {is_active and has_permission}") # False
print(f"10 > 5: {10 > 5}") # True
```
---
> 🚫 **Common Pitfall: `==` vs `is`**
> 파이썬 초보자들이 가장 흔하게 혼동하는 것 중 하나가 `==`와 `is`의 차이입니다.
> - `==` (동등 연산자): 두 변수의 **값**이 같은지 비교합니다. (예: `[1, 2]` == `[1, 2]` -> `True`)
> - `is` (식별 연산자): 두 변수가 **완전히 동일한 객체(메모리 주소)**를 가리키는지 비교합니다. (예: `[1, 2]` is `[1, 2]` -> `False`, 서로 다른 리스트 객체이므로)
### 💡 쉽게 이해하기: 파이썬의 '데이터 그릇'들
> 요리할 때 재료에 따라 다른 그릇을 쓰듯, 파이썬에서는 데이터의 종류와 사용 방식에 따라 다양한 '그릇'(자료구조)을 사용합니다. 앞으로 배울 4가지 핵심 그릇을 미리 맛봅시다.
>
> 일반적으로는 값의 일치 여부를 확인하는 `==`를 사용하는 것이 안전하고, `None`이나 `True`, `False`와 같이 유일한 객체임을 보장할 수 있는 경우에만 `is`를 사용하는 것이 좋습니다. (예: `if my_var is None:`)
> - **리스트(List) `[]`**: '쇼핑 목록'과 같습니다. 목록에 물건을 추가하거나, 빼거나, 순서를 바꿀 수 있습니다. 순서가 중요하고 내용이 계속 바뀔 때 사용합니다.
> - **튜플(Tuple) `()`**: '돌에 새긴 약속'과 같습니다. 한번 정해진 내용은 절대 바꿀 수 없습니다. (x, y) 좌표처럼 변하면 안 되는 중요한 값을 보관할 때 사용합니다.
> - **딕셔너리(Dictionary) `{}`**: '사전'이나 '전화번호부'입니다. '사과'라는 단어(Key)를 찾으면 'apple'이라는 뜻(Value)이 나오는 것처럼, 키를 통해 값을 빠르게 찾아냅니다. 순서보다는 의미를 통해 데이터를 관리할 때 씁니다.
> - **셋(Set) `{}`**: '주머니 속 구슬 모음'입니다. 같은 색깔의 구슬은 여러 번 넣어도 하나만 있는 것으로 칩니다 (중복 불가). 순서 없이 '어떤 구슬이 주머니 안에 있는가?'가 중요할 때 사용합니다.
## 2일차(화): 여러 데이터를 담는 그릇 (1) - 리스트와 튜플
### 1.3. 여러 데이터를 묶어서 관리하는 자료구조
### 🎯 오늘의 목표
- 순서가 있는 데이터 묶음인 리스트(List)를 다룰 수 있습니다.
- 변경이 불가능한 튜플(Tuple)의 특징과 사용 사례를 이해합니다.
- 리스트 컴프리헨션으로 파이썬다운 코드를 작성합니다.
하나가 아닌 여러 개의 데이터를 효율적으로 관리하기 위한 자료형입니다.
---
#### 1.3.1. 리스트 (List)
- 여러 개의 값을 순서대로 저장하는 **변경 가능한** 자료구조입니다.
### 1. 리스트(List): 순서가 있고 변경 가능한 만능 주머니
- 여러 개의 값을 순서대로 저장하는 **변경 가능한** <a href="glossary.md#list">자료구조</a>입니다.
- 대괄호 `[]`로 감싸고, 각 요소는 쉼표 `,`로 구분합니다.
```python
# 리스트 생성
fruits = ["apple", "banana", "cherry"]
numbers = [1, 2, 3, 4, 5]
# 인덱싱과 슬라이싱 (문자열과 동일)
print(f"첫 번째 과일: {fruits[0]}") # apple
print(f"마지막 두 개 과일: {fruits[1:]}") # ['banana', 'cherry']
print(f"첫 번째 과일: {fruits[0]}")
print(f"마지막 두 개 과일: {fruits[1:]}")
# 리스트 값 변경하기
fruits[1] = "blueberry"
print(f"변경된 과일 리스트: {fruits}") # ['apple', 'blueberry', 'cherry']
print(f"변경된 리스트: {fruits}")
# 리스트에 요소 추가/제거
fruits.append("strawberry") # 맨 뒤에 추가
print(f"추가 후: {fruits}")
fruits.remove("apple") # 특정 값 제거
fruits.pop() # 맨 뒤 요소 제거
print(f"제거 후: {fruits}")
# 리스트 길이 구하기
print(f"리스트의 길이: {len(fruits)}")
```
> 💡 **Tip: 리스트 컴프리헨션(List Comprehension)으로 파이썬답게 코딩하기**
> `for` 반복문과 `append` 사용하여 리스트를 만드는 것은 직관적이지만, 파이썬에서는 간결하고 가독성 높은 방법이 있습니다. 바로 **리스트 컴프리헨션**입니다.
>
> ```python
> # 0부터 9까지의 숫자 중 짝수만 제곱하여 리스트 만들기
>
> # 일반적인 for문 사용
> squares = []
> for i in range(10):
> if i % 2 == 0:
> squares.append(i**2)
> print(squares) # [0, 4, 16, 36, 64]
### 2. 파이썬다운 코드: 리스트 컴프리헨션 (List Comprehension)
`for` 반복문과 `append`를 사용하여 리스트를 만드는 것을 한 줄로 간결하게 표현하는 방법입니다.
```python
# 0부터 9까지의 숫자 중 짝수만 제곱하여 리스트 만들기
# 일반적인 for문 사용
squares = []
for i in range(10):
if i % 2 == 0:
squares.append(i**2)
print(f"for문 사용: {squares}")
# 리스트 컴프리헨션 사용 (훨씬 간결!)
squares_comp = [i**2 for i in range(10) if i % 2 == 0]
print(f"컴프리헨션 사용: {squares_comp}")
```
<a href="glossary.md#list-comprehension">리스트 컴프리헨션</a>에 익숙해지면 코드가 훨씬 깔끔해지고, 작성 속도도 빨라집니다.
#### 💡 쉽게 이해하기: 리스트 컴프리헨션은 '주문 제작 기계'입니다
> 리스트 컴프리헨션은 마치 '주문 제작 기계'와 같습니다. `for`문을 사용하는 것은 재료(숫자)를 하나씩 손으로 가공해서(제곱해서) 상자에 담는 수작업과 비슷합니다.
>
> # 리스트 컴프리헨션 사용
> squares_comp = [i**2 for i in range(10) if i % 2 == 0]
> print(squares_comp) # [0, 4, 16, 36, 64]
> ```
> 리스트 컴프리헨션을 사용하면 줄로 동일한 작업을 수행할 있어 코드가 훨씬 깔끔해집니다. 익숙해지면 코드를 읽고 쓰는 속도가 매우 빨라집니다.
#### 1.3.2. 튜플 (Tuple)
- 여러 개의 값을 순서대로 저장하는 **변경 불가능한** 자료구조입니다.
> 반면, 리스트 컴프리헨션은 이 기계에 **`[결과물(i**2) | 재료(for i in range(10)) | 조건(if i % 2 == 0)]`** 이라는 설계도를 한 번에 넣어주는 것과 같습니다. 그러면 기계가 알아서 설계도에 맞는 제품들만 순식간에 만들어내어 컨테이너 벨트 위에 올려줍니다. 훨씬 빠르고, 간결하며, 세련된 방식이죠!
### 3. 튜플(Tuple): 순서가 있지만 변경 불가능한 금고
- 여러 개의 값을 순서대로 저장하는 **변경 불가능한** <a href="glossary.md#tuple">자료구조</a>입니다.
- 소괄호 `()`로 감싸서 만듭니다. 한번 만들어지면 내용을 바꿀 수 없어서, 안정성이 중요할 때 사용됩니다.
```python
# 튜플 생성
point = (10, 20)
colors = ("red", "green", "blue")
# 인덱싱과 슬라이싱 (리스트와 동일)
print(f"x좌표: {point[0]}")
# 튜플은 변경 불가! 아래 코드는 오류를 발생시킵니다.
# point[0] = 15 # TypeError
# 튜플은 변경 불가! 아래 코드는 오류(TypeError)를 발생시킵니다.
# point[0] = 15
```
> **언제 튜플을 사용할까?**
> - 함수가 여러 개의 값을 반환할 때
> - <a href="glossary.md#dictionary">딕셔너리</a>의 키(Key) 값으로 사용해야 할 때
> - 프로그램 전체에서 변하면 안 되는 상수 값을 정의할 때
---
## 3일차(수): 여러 데이터를 담는 그릇 (2) - 딕셔너리와 셋
### 🎯 오늘의 목표
- `Key:Value` 쌍으로 데이터를 저장하는 딕셔너리(Dictionary)를 다룰 수 있습니다.
- 중복을 허용하지 않는 셋(Set)의 특징과 집합 연산을 이해합니다.
---
#### 1.3.3. 딕셔너리 (Dictionary)
- `Key:Value` 쌍으로 데이터를 저장하 순서가 없는 자료구조입니다.
- 중괄호 `{}`로 감싸서 만듭니다. '이름'='홍길동', '나이'=30 처럼 의미를 가진 데이터를 관리할 때 매우 유용합니다.
### 1. 딕셔너리(Dictionary): 의미를 부여한 데이터 관리법
- `Key:Value` 쌍으로 데이터를 저장하며, 순서가 중요하지 않습니다.
- 중괄호 `{}`로 만듭니다. '이름'='홍길동', '나이'=30 처럼 의미를 가진 <a href="glossary.md#dictionary">딕셔너리</a> 데이터를 관리할 때 매우 유용합니다.
```python
# 딕셔너리 생성
user = {
"name": "앨리스",
"age": 30,
......@@ -178,29 +156,23 @@ user = {
# Key를 이용해 Value에 접근
print(f"이름: {user['name']}")
print(f"나이: {user.get('age')}")
print(f"나이 (안전하게): {user.get('age')}") # Key가 없어도 오류 대신 None 반환
# 데이터 추가 및 변경
user['email'] = "alice@example.com" # 새로운 Key-Value 쌍 추가
user['age'] = 31 # 기존 Key의 Value 변경
print(f"수정된 사용자 정보: {user}")
# 데이터 삭제
del user['city']
print(f"삭제 후 사용자 정보: {user}")
user['email'] = "alice@example.com"
user['age'] = 31
print(f"수정된 정보: {user}")
# 모든 Key와 Value 보기
print(f"키 목록: {list(user.keys())}")
print(f"값 목록: {list(user.values())}")
```
#### 1.3.4. 집합 (Set)
### 2. 셋(Set): 중복을 제거하고 관계를 파악하는 도구
- **중복을 허용하지 않는** 순서 없는 데이터의 모음입니다.
- 중괄호 `{}`를 사용하지만, Key가 없는 점이 딕셔너리와 다릅니다.
- 주로 데이터의 중복을 제거하거나, 여러 데이터 그룹 간의 연산(합집합, 교집합 등)에 사용됩니다.
- 주로 데이터의 중복을 제거하거나, 여러 데이터 그룹 간의 연산(합집합, 교집합 등)에 사용되는 <a href="glossary.md#set"></a>입니다.
```python
# 집합 생성
unique_numbers = {1, 2, 3, 2, 1, 4}
print(f"중복이 제거된 집합: {unique_numbers}") # {1, 2, 3, 4}
......@@ -208,22 +180,31 @@ set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}
# 집합 연산
print(f"합집합 (A | B): {set_a | set_b}") # {1, 2, 3, 4, 5, 6}
print(f"교집합 (A & B): {set_a & set_b}") # {3, 4}
print(f"차집합 (A - B): {set_a - set_b}") # {1, 2}
print(f"합집합 (A | B): {set_a | set_b}")
print(f"교집합 (A & B): {set_a & set_b}")
print(f"차집합 (A - B): {set_a - set_b}")
```
---
## 2. 제어문: 코드의 흐름 조종하기
## 4일차(목): 코드의 흐름 제어하기 - 제어문
### 🎯 오늘의 목표
- `if`문으로 조건에 따라 다른 코드를 실행할 수 있습니다.
- `for`문과 `while`문으로 특정 코드를 반복 실행할 수 있습니다.
- `enumerate`를 사용하여 리스트의 인덱스와 값을 함께 활용합니다.
---
**제어문(Control Flow)**을 사용하면 특정 조건에 따라 코드를 실행하거나, 반복 실행하는 등 코드의 실행 흐름을 통제할 수 있습니다.
### 1. 파이썬의 상징: 들여쓰기(Indentation)
파이썬은 **들여쓰기(indentation)**로 코드 블록을 구분합니다. 보통 4개의 공백(스페이스바 4번)을 사용하며, 이는 파이썬 문법의 **필수 요소**이므로 매우 중요합니다.
### 2.1. 파이썬의 핵심, 들여쓰기 (Indentation)
파이썬은 **들여쓰기(indentation)**로 코드 블록을 구분합니다. 보통 4개의 공백(스페이스바 4번)을 사용하며, 이는 파이썬 문법의 필수 요소이므로 매우 중요합니다.
#### 💡 쉽게 이해하기: 들여쓰기는 '글의 목차'와 같습니다
> 우리가 보고서를 쓸 때, 큰 제목 아래에 소제목을 쓰고, 그 아래에 내용을 쓰는 것처럼 계층을 만듭니다. 이 계층 구조 덕분에 어떤 내용이 어떤 제목에 속하는지 한눈에 알 수 있습니다.
>
> 파이썬의 들여쓰기도 똑같습니다. `if`문이나 `for`문 아래에 오는 코드들을 안으로 들여쓰면, '이 코드들은 바로 위 `if`문(또는 `for`문)에 소속된 한 팀입니다'라고 알려주는 시각적인 신호입니다. 이 규칙만 잘 지키면 코드의 구조가 명확해지고, 다른 사람이 내 코드를 이해하기도 쉬워집니다. 파이썬에서는 이 규칙이 선택이 아닌 **필수**입니다!
### 2.2. if-elif-else (조건문)
조건에 따라 다른 코드를 실행하고 싶을 때 사용합니다.
### 2. `if-elif-else`: 조건에 따라 움직이기
```python
score = 85
......@@ -236,129 +217,119 @@ else:
print(f"당신의 학점은 {grade}입니다.")
```
### 2.3. for (반복문)
리스트, 튜플, 문자열 등 순회 가능한(iterable) 객체의 요소를 하나씩 순회하며 코드를 반복 실행합니다.
### 3. `for`: 순서대로 반복하기
<a href="glossary.md#list">리스트</a>, <a href="glossary.md#tuple">튜플</a>, 문자열 등 순회 가능한(iterable) 객체의 요소를 하나씩 순회하며 코드를 반복합니다. 마치 **체크리스트의 항목을 하나씩 확인**하는 것과 같습니다.
```python
fruits = ["apple", "banana", "cherry"]
# 가장 기본적인 for문
for fruit in fruits:
print(f"나는 {fruit}를 좋아합니다!")
print(f"{fruit}먹었습니다.")
# enumerate를 사용하면 인덱스 번호와 값을 함께 사용할 수 있습니다.
for idx, fruit in enumerate(fruits):
print(f"{idx+1}번째 과일은 {fruit}입니다.")
# range() 함수는 정해진 횟수만큼 반복할 때 유용합니다.
# range()를 사용하면 정해진 횟수만큼 반복할 수 있습니다.
for i in range(5): # 0부터 4까지 5번 반복
print(f"반복 횟수: {i}")
print(f"{i}번째 반복")
```
### 2.4. while (반복문)
특정 조건이 `True`인 **동안** 계속해서 코드를 반복 실행합니다. `while`문은 무한 루프에 빠질 위험이 있으니 항상 주의해야 합니다.
### 4. `while`: 조건이 만족하는 동안 반복하기
주어진 조건이 `True`동안 코드를 계속해서 반복 실행합니다. **생명(life)이 0이 될 때까지 계속되는 게임**과 같습니다. 매 라운드 시작 전에 생명이 남아있는지(`life > 0`)를 확인하는 것과 같죠.
```python
count = 5
while count > 0:
print(f"카운트다운: {count}")
count -= 1
print("발사!")
print(f"카운트 다운: {count}")
count -= 1 # count를 1씩 감소시킵니다. 이 줄이 없으면 무한 루프에 빠집니다!
# break(탈출)와 continue(건너뛰기)로 반복 제어
i = 0
while True: # 의도적인 무한 루프
i += 1
if i % 2 == 0:
continue # 짝수이면 아래 print를 건너뛰고 다음 반복으로
print(i) # 홀수만 출력
if i >= 10:
break # 10 이상이 되면 반복 중단
print("발사!")
```
---
## 3. 함수: 코드 재사용을 위한 레시피
## 5일차(금): 코드 재사용하는 마법, 함수
**함수(Function)**는 특정 작업을 수행하는 코드 묶음에 이름을 붙여놓은 것입니다. 함수를 사용하면 코드가 간결해지고(DRY: Don't Repeat Yourself), 수정 및 관리가 쉬워집니다.
### 🎯 오늘의 목표
- <a href="glossary.md#function">함수(Function)</a>를 정의하고 호출하여 코드의 재사용성을 높일 수 있습니다.
- 함수의 입력(매개변수)과 출력(반환 값)의 개념을 이해합니다.
- 반복되는 코드를 묶어 함수(Function)로 만들 수 있습니다.
- 함수에 입력을 주는 매개변수(Parameter)와 결과를 돌려받는 반환 값(Return)을 이해합니다.
---
### 1. 함수(Function): 나만의 미니 프로그램
코드를 짜다 보면, 비슷한 작업을 여러 번 반복해야 할 때가 있습니다. 함수는 특정 작업을 수행하는 코드 묶음에 이름을 붙여놓고, 필요할 때마다 이름만 불러서 사용하는 **'재사용 가능한 코드 조각'**입니다.
#### 💡 쉽게 이해하기: 함수는 '나만의 레시피' 또는 '미니 로봇'입니다
> 여러분이 '계란 후라이'를 만드는 방법을 '계란 후라이 레시피'라는 이름으로 정리해두었다고 상상해보세요. 앞으로 계란 후라이가 필요할 때마다, 매번 "기름을 두르고, 계란을 깨서 넣고, 소금을 뿌린다..."라고 생각할 필요 없이 그냥 "계란 후라이 레시피대로 해!"라고 말하면 됩니다.
>
> 함수가 바로 이 **'레시피'**와 같습니다. 특정 기능을 수행하는 코드들을 `def make_fried_egg():` 처럼 하나의 레시피로 만들어두면, 필요할 때마다 `make_fried_egg()`라고 호출만 하면 되는 것이죠. 코드가 훨씬 간결해지고, 관리하기 쉬워집니다.
### 2. 함수 만들기 (`def`) 와 사용하기 (호출)
`def` 키워드를 사용하여 함수를 정의(define)합니다.
### 3.1. 함수 정의와 호출
```python
# 'greet'라는 이름의 함수를 정의합니다.
# 'name'은 함수에 필요한 재료(인자, parameter)입니다.
def greet(name: str) -> str:
"""사용자에게 인사하는 문자열을 반환합니다.""" # Docstring
return f"안녕하세요, {name}님!"
# 함수를 호출하여 사용합니다.
message = greet("파이썬")
print(message)
# 1. 함수 정의 (레시피 만들기)
def say_hello():
print("안녕하세요!")
print("파이썬의 세계에 오신 것을 환영합니다.")
# 2. 함수 호출 (레시피 사용하기)
say_hello()
say_hello()
```
* **타입 힌트(Type Hint)**: `name: str`이나 `-> str` 처럼 함수의 인자나 반환값의 타입을 명시하여 코드의 가독성과 안정성을 높일 수 있습니다. (강력 권장)
### 3.2. 다양한 인자(Argument) 전달 방식
```python
# 위치 인자, 키워드 인자, 기본값 인자
def introduce(name, age=30):
print(f"이름: {name}, 나이: {age}")
### 3. 입력(매개변수)과 출력(반환 값)이 있는 함수
진짜 레시피처럼, 함수도 재료를 받아(입력) 요리를 완성해서(출력) 내놓을 수 있습니다.
introduce("홍길동") # age는 기본값 30 사용
introduce("이순신", age=40) # 키워드 인자
- **매개변수 (Parameter)**: 함수에 전달하는 재료(입력 값).
- **반환 값 (Return)**: 함수가 작업을 마친 후 돌려주는 결과물(출력 값).
# 가변 인자 (*args, **kwargs)
# *args: 여러 개의 위치 인자를 '튜플'로 받음
def sum_all(*numbers):
return sum(numbers)
```python
# '이름'을 재료로 받아 인사말을 만들어주는 레시피
def make_greeting(name):
greeting = f"안녕하세요, {name}님!"
return greeting # 결과물(요리)을 반환
# **kwargs: 여러 개의 키워드 인자를 '딕셔너리'로 받음
def print_user_profile(**user_info):
for key, value in user_info.items():
print(f"- {key}: {value}")
# 함수에 '앨리스'라는 재료를 넣어 호출하고, 결과물을 변수에 저장
alice_greeting = make_greeting("앨리스")
print(alice_greeting) # "안녕하세요, 앨리스님!" 출력
print(f"합계: {sum_all(1, 2, 3, 4, 5)}")
print_user_profile(name="John Doe", city="New York")
bob_greeting = make_greeting("밥")
print(bob_greeting) # "안녕하세요, 밥님!" 출력
```
함수를 잘 사용하면 복잡한 문제를 여러 개의 간단한 함수로 나누어 해결할 수 있어, 훨씬 체계적인 프로그래밍이 가능해집니다.
### 3.3. 함수의 반환값은 여러 개일 수 있으며, 이 경우 튜플로 반환됩니다.
```python
def greet_user(name, email):
return f"{name}님, 환영합니다!", f"이메일: {email}"
### 📝 2주차 요약
# 함수의 반환값은 여러 개일 수 있으며, 이 경우 튜플로 반환됩니다.
welcome_message, email_info = greet_user("홍길동", "hong@example.com")
print(welcome_message)
print(email_info)
이번 주차에 우리는 파이썬의 가장 기본적인 구성 요소들을 배웠습니다.
🚫 **Common Pitfall: 함수 기본값으로 mutable 객체 사용하기**
파이썬에서 가장 까다로운 함정 중 하나입니다. 함수의 기본값 인자로 리스트(`[]`)나 딕셔너리(`{}`) 같은 **변경 가능한(mutable)** 객체를 사용하면 예상치 못한 결과를 초래할 수 있습니다.
- **변수와 자료형**: 데이터를 저장하고(변수), 데이터의 종류(숫자, 문자열, 불리언)를 구분하는 법을 배웠습니다.
- **자료구조**: 여러 데이터를 효율적으로 담는 그릇인 리스트, 튜플, 딕셔너리, 셋의 특징과 사용법을 익혔습니다.
- **제어문**: 코드의 흐름을 제어하는 `if`, `for`, `while` 문을 통해 프로그램이 특정 조건이나 반복에 따라 동작하게 만들었습니다.
- **함수**: 반복되는 코드를 `def` 키워드를 사용해 재사용 가능한 블록으로 만드는 방법을 배웠습니다.
```python
# 잘못된 예시
def add_to_list(item, my_list=[]):
my_list.append(item)
return my_list
# 함수 호출
print(add_to_list(1)) # 결과: [1] (예상대로)
print(add_to_list(2)) # 결과: [1, 2] (예상과 다름! [2]가 아님)
print(add_to_list(3)) # 결과: [1, 2, 3]
# 올바른 패턴
def add_to_list_safe(item, my_list=None):
if my_list is None:
my_list = []
my_list.append(item)
return my_list
print(add_to_list_safe(1)) # [1]
print(add_to_list_safe(2)) # [2]
```
- **왜 이런 일이?**: 함수 기본값은 함수가 **정의될 때 단 한 번만** 생성됩니다. `my_list=[]`는 함수가 처음 메모리에 올라갈 때 빈 리스트를 만들고, 이후 모든 호출에서 그 *동일한 리스트*를 계속 공유하여 사용하기 때문에 위와 같은 문제가 발생합니다.
- **해결책**: 기본값으로 `None`을 사용하고, 함수 내부에서 `if my_list is None:` 과 같이 체크하여 필요할 때 새로운 객체를 생성하는 것이 안전하고 표준적인 방법입니다.
이 핵심 문법들은 앞으로 우리가 만들 모든 AI 서비스의 뼈대가 될 것입니다. 각 개념이 손에 익을 때까지 여러 번 연습해보는 것이 중요합니다.
---
### 🔍 2주차 연습 문제
**문제 1: 학점 계산기 함수 만들기**
- 학생의 점수(0~100)를 입력받아 학점을 반환하는 `get_grade` 함수를 만들어보세요.
- 90점 이상: "A"
- 80점 이상 90점 미만: "B"
- 70점 이상 80점 미만: "C"
- 70점 미만: "F"
### 🚀 다음 시간엔...
**문제 2: 리스트에서 특정 단어 개수 세기**
- 과일 이름들이 담긴 리스트 `fruits = ["apple", "banana", "cherry", "apple", "cherry", "apple"]`가 주어졌을 때, "apple"이 몇 번 나타나는지 `for`문을 사용해 세어보세요.
지금까지 파이썬의 가장 기본적인 구성 요소인 변수, 자료형, 제어문, 함수에 대해 알아보았습니다. 이것들은 우리가 앞으로 만들 모든 프로그램의 뼈대가 될 것입니다.
**문제 3: 구구단 출력 함수**
- 숫자를 하나 입력받아 해당 숫자의 구구단을 출력하는 `multiplication_table` 함수를 `while`문을 사용하여 만들어보세요.
---
다음 [Part 3: 데이터 관리를 위한 파이썬 컬렉션](part_3_python_collections.md)에서는 여러 개의 데이터를 한 번에 담고 효율적으로 관리하는 리스트(List), 튜플(Tuple), 딕셔너리(Dictionary), 셋(Set) 자료구조에 대해 깊이 있게 탐구해 보겠습니다.
\ No newline at end of file
➡️ **다음 시간: [Part 3: 파이썬 컬렉션, 더 깊게 이해하기](part_3_python_collections.md)**
\ No newline at end of file
# Part 3: 데이터 관리를 위한 파이썬 컬렉션
# Part 3: 파이썬 컬렉션 심화 (3주차)
**⬅️ 이전 시간: [Part 2: 파이썬 핵심 문법 마스터하기](./part_2_python_core_syntax.md)** | **➡️ 다음 시간: [Part 4: 파이썬 객체 지향 프로그래밍](./part_4_object_oriented_programming.md)**
---
## 📜 실습 코드 바로가기
- **`part_2_3_python_syntax_collections.py`**: [바로가기](./source_code/part_2_3_python_syntax_collections.py)
- 본 파트의 모든 예제 코드는 위 파일에서 직접 실행하고 수정해볼 수 있습니다. 리스트, 딕셔너리, 셋 등의 다양한 활용법을 직접 확인해보세요.
---
### 💡 지난 시간 복습
지난 시간에는 변수, 제어문, 함수 등 파이썬의 기본적인 뼈대를 세웠습니다. 이는 마치 우리가 **목공의 기본 연장(톱, 망치, 드라이버) 사용법**을 익힌 것과 같습니다.
[Part 2: 파이썬 핵심 문법 마스터하기](part_2_python_core_syntax.md)에서는 변수, 자료형, 제어문(if, for, while), 함수 등 파이썬 프로그래밍의 뼈대를 이루는 기본 문법을 학습했습니다. 이를 통해 코드의 기본적인 흐름을 제어하는 능력을 갖추게 되었습니다.
이번 주차에는 그 연장들을 넣어둘 **'전문가용 공구함(컬렉션)'**을 깊이 있게 탐구합니다. 단순히 물건을 담는 상자가 아니라, 용도에 따라 칸이 나뉘고, 특정 작업에 최적화된 특수 공구들을 꺼내 쓰는 법을 배우게 됩니다. 상황에 맞는 최적의 '데이터 그릇'을 선택하는 능력을 길러, 코드의 효율성과 가독성을 한 단계 끌어올려 보겠습니다.
---
여러 개의 데이터를 효율적으로 저장하고 관리하는 파이썬의 핵심 자료구조를 '컬렉션(Collection)'이라고 부릅니다. 각 자료구조는 고유한 특징과 장점을 가지고 있으므로, 상황에 맞는 적절한 컬렉션을 선택하는 것은 코드의 성능과 가독성을 높이는 데 매우 중요합니다. 이 섹션에서는 리스트, 튜플, 딕셔너리, 셋의 차이점을 명확히 이해하고, 다양한 활용 예제를 통해 실전 감각을 익힙니다.
## 1일차(월): 리스트(List) 심화와 Counter
## 1. List (리스트)
### 🎯 오늘의 목표
- 리스트의 핵심 메서드를 능숙하게 사용하여 데이터를 조작합니다.
- `sort()``sorted()`의 차이점을 명확히 이해합니다.
- `collections.Counter`로 데이터의 빈도를 효율적으로 계산합니다.
- **특징**: **순서가 있는(ordered)**, **변경 가능한(mutable)** 데이터의 모음입니다. 파이썬에서 가장 보편적으로 사용되는 자료구조로, 다른 언어의 배열(Array)과 유사합니다.
- **사용 시점**: 데이터의 순서가 중요하고, 프로그램 실행 중에 내용의 추가, 수정, 삭제가 빈번하게 필요할 때 사용합니다.
---
### 1.1. 리스트 생성 및 기본 조작
```python
# 리스트 생성
fruits = ["apple", "banana", "cherry"]
numbers = [1, 2, 3, 4, 5]
mixed_list = [1, "hello", 3.14, True]
# 인덱싱과 슬라이싱 (문자열과 동일)
print(f"첫 번째 과일: {fruits[0]}") # apple
print(f"마지막 과일: {fruits[-1]}") # cherry
print(f"1번부터 3번 앞까지: {numbers[1:3]}") # [2, 3]
```
### 1. 리스트 핵심 메서드 정복
### 1.2. 리스트의 주요 메서드
```python
fruits = ["apple", "banana", "cherry"]
print(f"초기 리스트: {fruits}")
# 요소 추가
fruits.append("orange") # 맨 끝에 추가
print(f"append('orange'): {fruits}")
fruits.insert(1, "blueberry") # 특정 인덱스에 추가
print(f"insert(1, 'blueberry'): {fruits}")
# 요소 제거
fruits.remove("cherry") # 값으로 제거 (첫 번째로 발견된 값만)
print(f"remove('cherry'): {fruits}")
popped_fruit = fruits.pop(2) # 인덱스로 제거하고, 제거된 값을 반환
print(f"pop(2): {fruits} (제거된 값: {popped_fruit})")
# 정렬 및 순서 뒤집기
numbers = [3, 1, 4, 1, 5, 9, 2]
numbers.sort() # 오름차순 정렬 (원본 리스트 변경)
print(f"numbers.sort(): {numbers}")
numbers.sort(reverse=True) # 내림차순 정렬
print(f"numbers.sort(reverse=True): {numbers}")
fruits.reverse() # 리스트 순서를 뒤집음 (원본 리스트 변경)
print(f"fruits.reverse(): {fruits}")
> 🚫 **Common Pitfall: 리스트를 순회하면서 아이템 제거하기**
> `for` 루프를 사용해 리스트를 순회하는 동안 해당 리스트의 아이템을 제거하면, 일부 아이템을 건너뛰는 예기치 못한 결과가 발생할 있습니다.
>
> ```python
> numbers = [1, 2, 3, 4, 5, 6]
>
> # 잘못된 방법: 리스트를 순회하며 직접 제거
> # 2, 4, 6을 제거하고 싶지만...
> for num in numbers:
> if num % 2 == 0:
> numbers.remove(num)
>
> print(f"잘못된 방법 후 리스트: {numbers}") # 예상 결과: [1, 3, 5], 실제 결과: [1, 3, 5, 6]
> ```
> - ** 이런 일이?**: `remove(2)` 실행되면 리스트는 `[1, 3, 4, 5, 6]` 됩니다. 리스트의 길이가 줄어들고 인덱스가 앞으로 당겨지면서, 다음 순회 차례였던 `3` 건너뛰고 바로 `4` 넘어가기 때문입니다.
> - **올바른 해결책**:
> 1. **새로운 리스트 생성 (권장)**: 컴프리헨션 등을 이용해 조건을 만족하는 아이템만으로 리스트를 만드는 것이 가장 안전하고 파이썬다운 방법입니다.
> ```python
> numbers_original = [1, 2, 3, 4, 5, 6]
> odd_numbers = [num for num in numbers_original if num % 2 != 0]
> print(f"컴프리헨션 사용: {odd_numbers}")
> ```
> 2. **리스트의 복사본으로 순회**: 원본 리스트를 수정해야만 한다면, `numbers[:]` 같이 리스트의 복사본을 만들어 순회하고 원본을 변경해야 합니다.
> ```python
> numbers_original = [1, 2, 3, 4, 5, 6]
> for num in numbers_original[:]: # 복사본으로 순회
> if num % 2 == 0:
> numbers_original.remove(num) # 원본에서 제거
> print(f"복사본 순회 사용: {numbers_original}")
> ```
### 1.3. 리스트 컴프리헨션 (List Comprehension)
`for` 반복문과 `if` 조건문을 줄로 간결하게 표현하여 새로운 리스트를 만드는 강력한 기능입니다.
| 메서드 | 설명 | 예제 |
|---|---|---|
| [`append(x)`](./glossary.md#리스트list) | 리스트 맨 끝에 항목 `x`를 추가 | `[1, 2].append(3)``[1, 2, 3]` |
| `insert(i, x)`| `i` 위치에 항목 `x`를 삽입 | `[1, 3].insert(1, 2)``[1, 2, 3]` |
| `remove(x)` | 리스트에서 첫 번째로 나타나는 `x`를 삭제 | `[1, 2, 1].remove(1)``[2, 1]` |
| `pop(i)` | `i` 위치의 항목을 제거하고 반환 (생략 시 마지막 항목) | `[1, 2, 3].pop(1)``2` (리스트는 `[1, 3]`) |
| [`sort()`](./glossary.md#sort-vs-sorted) | **원본 리스트**를 오름차순으로 정렬 | `x=[3,1,2]; x.sort()``x``[1,2,3]` |
| `reverse()` | **원본 리스트**의 순서를 뒤집음 | `x=[1,2,3]; x.reverse()``x``[3,2,1]` |
### 2. `.sort()` vs `sorted()`: 원본을 바꿀 것인가, 새것을 얻을 것인가?
이 둘의 차이점을 이해하는 것은 매우 중요합니다.
- **`list.sort()` 메서드**: **원본 [리스트](./glossary.md#리스트list) 자체를** 정렬합니다. 반환값은 `None`입니다.
- **`sorted()` 내장 함수**: 정렬된 **새로운 [리스트](./glossary.md#리스트list)를 반환**합니다. 원본 리스트는 바뀌지 않습니다.
```python
# 0부터 9까지의 숫자를 제곱한 리스트 생성
# 일반적인 방법
squares = []
for i in range(10):
squares.append(i**2)
print(f"일반적인 방법: {squares}")
# 리스트 컴프리헨션 사용
squares_comp = [i**2 for i in range(10)]
print(f"컴프리헨션 사용: {squares_comp}")
# 조건문을 포함한 리스트 컴프리헨션
# 1부터 10까지의 숫자 중 짝수만 제곱하여 리스트로 만들기
even_squares = [i**2 for i in range(1, 11) if i % 2 == 0]
print(f"짝수 제곱 리스트: {even_squares}")
# {1: 1, 2: 4, 3: 9, 4: 16} 형태의 딕셔너리 생성
square_dict = {num: num**2 for num in numbers}
print(f"제곱 딕셔너리: {square_dict}")
> 💡 **Tip: 아이템 개수 세기, `collections.Counter`로 스마트하게!**
> 리스트나 문자열에 포함된 각 아이템이 몇 번 등장하는지 세어야 할 때, `for` 문과 딕셔너리로 직접 구현할 수도 있지만, `collections.Counter`를 사용하면 훨씬 간결하고 효율적입니다.
>
> ```python
> from collections import Counter
>
> # 리스트에서 각 과일의 개수 세기
> fruit_basket = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
> fruit_counts = Counter(fruit_basket)
>
> print(fruit_counts)
> # 출력: Counter({'apple': 3, 'banana': 2, 'orange': 1})
>
> # 가장 흔한 아이템 2개 찾기
> print(fruit_counts.most_common(2))
> # 출력: [('apple', 3), ('banana', 2)]
>
> # 특정 아이템의 개수 확인
> print(f"사과는 몇 개? {fruit_counts['apple']}") # 3
> print(f"포도는 몇 개? {fruit_counts['grape']}") # 키가 없어도 에러 없이 0을 반환
> ```
> `Counter`는 딕셔너리를 상속받아 만들어져, 딕셔너리의 모든 기능을 포함하면서도 아이템 개수를 세는 데 유용한 추가 메서드(`most_common` 등)를 제공합니다. 데이터 분석이나 자연어 처리에서 단어 빈도를 계산할 때 매우 유용하게 사용됩니다.
# .sort() 메서드
my_list = [3, 1, 4, 2]
print(f"sort() 전: {my_list}")
result = my_list.sort()
print(f"sort() 후: {my_list}")
print(f"반환값: {result}") # None
# ⚠️ 흔한 실수: sort()의 반환값을 변수에 담지 마세요!
wrong_list = [3, 1, 4, 2]
wrong_list = wrong_list.sort() # wrong_list.sort()는 None을 반환하므로...
print(f"잘못된 사용: {wrong_list}") # None이 출력됩니다!
# sorted() 함수
my_list = [3, 1, 4, 2]
print(f"sorted() 전: {my_list}")
new_list = sorted(my_list)
print(f"sorted() 후 원본: {my_list}") # 원본은 그대로
print(f"반환된 새 리스트: {new_list}")
```
---
#### 💡 쉽게 이해하기: `.sort()`는 '내 방 정리', `sorted()`는 '정리된 모습 촬영'
> - **`my_list.sort()`**: '내 방 서랍(원본 리스트) 속 옷을 직접 정리정돈하는 것'과 같습니다. 서랍 속의 내용물 자체가 바뀌고, 정리 행위 자체는 어떤 결과물(반환값)을 남기지 않습니다 (`None`).
> - **`new_list = sorted(my_list)`**: '서랍 속 옷은 그대로 둔 채(원본 유지), 침대 위에 옷을 가지런히 배열해보고(새로운 리스트 생성) 그 모습을 사진으로 찍어(새 변수에 저장) 간직하는 것'과 같습니다.
## 2. Tuple (튜플)
- **특징**: **순서가 있지만**, 한번 생성되면 내용을 **변경할 수 없는(immutable)** 데이터의 모음입니다. 소괄호 `()`를 사용하여 정의합니다.
- **사용 시점**: 프로그램 실행 중 절대 변하면 안 되는 값(예: 설정값, 좌표, 함수의 고정된 반환값)을 저장하거나, 딕셔너리의 키로 사용해야 할 때 유용합니다. 리스트보다 메모리를 적게 사용하고 속도가 조금 더 빠릅니다.
### 3. 스마트한 개수 세기: `collections.Counter`
리스트나 문자열에 포함된 각 항목이 몇 번 등장하는지 세어야 할 때, [`collections.Counter`](./glossary.md#collectionscounter)를 사용하면 매우 간결하고 효율적입니다.
```python
# 튜플 생성
point = (10, 20)
colors = ("red", "green", "blue")
print(f"x좌표: {point[0]}")
from collections import Counter
# 튜플은 변경 불가능
# point[0] = 15 # 이 코드는 TypeError를 발생시킵니다.
# 리스트에서 각 과일의 개수 세기
fruit_basket = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
fruit_counts = Counter(fruit_basket)
# 튜플 패킹(packing)과 언패킹(unpacking)
person_info = ("John Doe", 30, "New York") # 패킹
name, age, city = person_info # 언패킹
print(f"이름: {name}, 나이: {age}, 도시: {city}")
print(fruit_counts)
# 출력: Counter({'apple': 3, 'banana': 2, 'orange': 1})
# 함수에서 여러 값을 반환할 때 튜플이 유용하게 사용됨
def get_user_info(user_id):
# (사용자 이름, 이메일, 권한 등급)을 DB에서 조회했다고 가정
return ("Alice", "alice@example.com", "admin")
# 가장 흔한 항목 2개 찾기
print(fruit_counts.most_common(2))
# 출력: [('apple', 3), ('banana', 2)]
user_name, user_email, user_role = get_user_info(123)
print(f"사용자: {user_name}, 이메일: {user_email}, 역할: {user_role}")
# 특정 항목의 개수 확인 (딕셔너리처럼 사용)
print(f"사과는 몇 개? {fruit_counts['apple']}") # 3
print(f"포도는 몇 개? {fruit_counts['grape']}") # 키가 없어도 에러 없이 0을 반환
```
#### 💡 쉽게 이해하기: Counter는 '똑똑한 재고 관리인'
> `Counter`는 '재고 관리인'입니다. 과일 바구니(`list`)를 통째로 이 관리인에게 넘겨주면, "사과 3개, 바나나 2개, 오렌지 1개 있습니다"라고 즉시 보고해주는 것과 같습니다. 수작업으로 하나씩 셀 필요가 없어 매우 편리합니다.
---
## 3. Dictionary (딕셔너리)
## 2일차(화): 튜플(Tuple)과 딕셔너리(Dictionary) 심화
- **특징**: '키(Key)'와 '값(Value)'을 쌍으로 저장하는, **순서가 없는(Python 3.7+ 부터는 입력 순서 유지)** 데이터 모음입니다. 키는 고유해야 하며, 일반적으로 문자열이나 숫자를 사용합니다. 중괄호 `{}`를 사용하여 정의합니다.
- **사용 시점**: 각 데이터에 고유한 이름표(Key)를 붙여 의미를 명확히 하고, 키를 통해 값을 매우 빠르게 조회하고 싶을 때 사용합니다. (JSON 형식과 매우 유사)
### 🎯 오늘의 목표
- 튜플 언패킹(Unpacking)을 자유자재로 활용합니다.
- `defaultdict`를 사용하여 딕셔너리를 초기화하는 코드를 간소화합니다.
### 3.1. 딕셔너리 생성 및 기본 조작
```python
# 딕셔너리 생성
person = {"name": "John", "age": 25, "city": "New York"}
---
# 값 조회
print(f"이름: {person['name']}")
# print(person['job']) # 키가 없으면 KeyError 발생
### 1. 튜플의 힘: 언패킹(Unpacking)
튜플의 각 항목을 여러 변수에 한 번에 할당하는 기능입니다. 코드를 매우 간결하고 직관적으로 만들어줍니다.
# .get() 메서드를 사용한 안전한 조회
print(f"직업: {person.get('job')}") # 키가 없으면 None 반환
print(f"직업: {person.get('job', 'Unemployed')}") # 기본값 지정 가능
```python
# 기본 언패킹
name, age, city = ("John Doe", 30, "New York")
print(f"이름: {name}")
# 변수 값 교환(swap)
a, b = 10, 20
a, b = b, a # 임시 변수 없이 값을 교환
print(f"a: {a}, b: {b}") # a: 20, b: 10
# `*`를 이용한 언패킹
numbers = (1, 2, 3, 4, 5)
first, *rest, last = numbers
print(f"first: {first}") # 1
print(f"rest: {rest}") # [2, 3, 4] (리스트로 반환)
print(f"last: {last}") # 5
```
# 값 추가 및 수정
person["job"] = "Developer" # 새로운 키-값 쌍 추가
person["age"] = 26 # 기존 키의 값 수정
print(f"수정된 정보: {person}")
#### 💡 쉽게 이해하기: 튜플 언패킹은 '우아한 소포 전달'
> 함수가 `("John Doe", 30, "New York")`이라는 소포(튜플)를 반환할 때, 우리가 `name`, `age`, `city`라는 세 사람이 팔을 벌리고 서서 각자의 물건을 한 번에 받는 모습과 같습니다. 임시 변수라는 창고에 일단 내려놓고 하나씩 꺼낼 필요 없이, 각자의 위치에 맞게 내용물을 바로 전달받아 코드가 매우 깔끔해집니다.
# 값 삭제
del person["city"]
print(f"삭제 후 정보: {person}")
```
### 2. 키가 없을 때의 구원투수: `collections.defaultdict`
일반 [딕셔너리](./glossary.md#딕셔너리dictionary)는 존재하지 않는 키에 접근하면 `KeyError`가 발생합니다. [`defaultdict`](./glossary.md#collectionsdefaultdict)는 키가 없을 경우, **지정된 기본값을 자동으로 생성**하여 이러한 오류를 방지하고 코드를 간결하게 만들어줍니다.
### 3.2. 딕셔너리 순회
```python
person = {"name": "Alice", "age": 30, "city": "Seoul"}
# 1. 키 순회
for key in person.keys():
print(f"키: {key}")
from collections import defaultdict
# 일반 딕셔너리로 리스트에 항목 추가하기
regular_dict = {}
words = ['apple', 'ant', 'bee', 'banana', 'cat']
for word in words:
first_char = word[0]
if first_char not in regular_dict:
regular_dict[first_char] = [] # 키가 없을 때 빈 리스트를 직접 생성
regular_dict[first_char].append(word)
print(f"일반 딕셔너리: {regular_dict}")
# defaultdict 사용하기 (훨씬 간결!)
# 키가 없을 때 기본값으로 빈 리스트(list)를 생성하도록 지정
default_dict = defaultdict(list)
for word in words:
default_dict[word[0]].append(word) # if문 없이 바로 append 가능
print(f"defaultdict 사용: {default_dict}")
```
# 2. 값 순회
for value in person.values():
print(f"값: {value}")
#### 💡 쉽게 이해하기: defaultdict는 '자동 완성 노트'
> 일반 딕셔너리가 '일반 노트'라면, `defaultdict`는 '자동 완성 기능이 있는 마법 노트'입니다.
>
> 일반 노트에서는 'a'라는 페이지가 없으면 직접 'a' 페이지를 만들고 줄을 그은 뒤(`if 'a' not in...: regular_dict['a'] = []`), 내용을 써야 합니다.
>
> 하지만 `defaultdict(list)`라는 마법 노트에서는, 존재하지 않는 'a' 페이지를 펼치려는 순간, 노트가 자동으로 'a'라는 제목의 새 페이지를 만들고 목록을 쓸 수 있도록 준비해줍니다. 우리는 `if`문 확인 없이 그냥 내용을 추가하기만 하면 됩니다.
# 3. 키와 값 동시 순회 (가장 많이 사용)
for key, value in person.items():
print(f"{key}: {value}")
```
**언제 `defaultdict`를 사용할까?**
### 3.3. 딕셔너리 컴프리헨션
리스트 컴프리헨션과 유사하게, 한 줄로 간결하게 딕셔너리를 생성할 수 있습니다.
```python
numbers = [1, 2, 3, 4]
# {1: 1, 2: 4, 3: 9, 4: 16} 형태의 딕셔너리 생성
square_dict = {num: num**2 for num in numbers}
print(f"제곱 딕셔너리: {square_dict}")
```
- **데이터 그룹핑**: 학생들을 학급별로, 단어들을 첫 글자별로 묶는 등, 특정 기준에 따라 아이템을 리스트나 셋에 누적할 때 `if`문 없이 깔끔한 코드를 작성할 수 있습니다.
- **빈도수 계산**: 각 항목의 출현 횟수를 셀 때, 키가 없을 때마다 0으로 초기화하는 코드를 생략할 수 있습니다. (`defaultdict(int)`)
---
## 4. Set (셋)
## 3일차(수): 셋(Set)과 특수 목적 컬렉션
### 🎯 오늘의 목표
- 셋(Set)을 이용한 집합 연산을 마스터합니다.
- 양방향 큐 자료구조인 `deque`의 사용법을 익힙니다.
- 이름으로 항목에 접근하는 `namedtuple`의 편리함을 학습합니다.
---
- **특징**: **중복을 허용하지 않는**, **순서 없는(unordered)** 데이터의 모음입니다. 중괄호 `{}`를 사용하지만, `{}`는 빈 딕셔너리를 의미하므로 빈 셋은 `set()`으로 만들어야 합니다.
- **사용 시점**: 리스트 등에서 중복된 값을 효율적으로 제거하거나, 두 데이터 집합 간의 교집합, 합집합, 차집합 등 수학적인 집합 연산이 필요할 때 효과적입니다. 특정 요소의 존재 여부를 매우 빠르게 확인해야 할 때도 사용됩니다.
### 1. 셋(Set)으로 다루는 집합 연산
| 연산자 | 메서드 | 설명 |
|---|---|---|
| `A | B` | `A.union(B)` | **합집합**: A와 B의 모든 항목 |
| `A & B` | `A.intersection(B)` | **교집합**: A와 B에 공통으로 있는 항목 |
| `A - B` | `A.difference(B)` | **차집합**: A에는 있지만 B에는 없는 항목 |
| `A ^ B` | `A.symmetric_difference(B)` | **대칭차집합**: A 또는 B에만 있는 항목 |
```python
# 셋 생성
numbers = [1, 2, 3, 2, 1, 4, 5, 4]
unique_numbers = set(numbers)
print(f"리스트: {numbers}")
print(f"셋 (중복 제거): {unique_numbers}") # {1, 2, 3, 4, 5} (순서는 보장되지 않음)
# 요소 추가 및 제거
unique_numbers.add(6)
print(f"add(6): {unique_numbers}")
unique_numbers.remove(3)
print(f"remove(3): {unique_numbers}")
# 요소 존재 여부 확인 (리스트보다 훨씬 빠름)
print(f"5가 셋에 포함되어 있는가? {5 in unique_numbers}") # True
set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}
print(f"합집합: {set_a | set_b}")
print(f"교집합: {set_a & set_b}")
print(f"차집합: {set_a - set_b}")
print(f"대칭차집합: {set_a ^ set_b}")
```
### 4.1. 집합 연산
### 2. 고속 큐(Queue): `collections.deque`
[`deque`](./glossary.md#collectionsdeque)(데크)는 "double-ended queue"의 약자로, 리스트와 달리 **양쪽 끝에서 항목을 추가하거나 제거하는 속도가 매우 빠른** 자료구조입니다.
리스트에서 `my_list.pop(0)``my_list.insert(0, ...)` 연산은 모든 항목을 한 칸씩 뒤로 밀어야 해서 매우 비효율적입니다. `deque`는 이런 단점을 완벽하게 보완합니다.
```python
set_a = {1, 2, 3, 4, 5}
set_b = {4, 5, 6, 7, 8}
from collections import deque
# 합집합 (Union)
print(f"합집합: {set_a | set_b}")
print(f"합집합 (메서드): {set_a.union(set_b)}")
d = deque([1, 2, 3])
print(f"초기 deque: {d}")
# 교집합 (Intersection)
print(f"교집합: {set_a & set_b}")
print(f"교집합 (메서드): {set_a.intersection(set_b)}")
d.append(4) # 오른쪽 끝에 추가 (리스트의 append와 동일)
print(f"append(4): {d}")
d.appendleft(0) # 왼쪽 끝에 추가 (매우 빠름!)
print(f"appendleft(0): {d}")
# 차집합 (Difference)
print(f"차집합 (A-B): {set_a - set_b}")
print(f"차집합 (메서드): {set_a.difference(set_b)}")
right_val = d.pop() # 오른쪽 끝 항목 제거
print(f"pop(): {right_val}, 결과: {d}")
# 대칭 차집합 (Symmetric Difference) - 합집합에서 교집합을 뺀 부분
print(f"대칭 차집합: {set_a ^ set_b}")
print(f"대칭 차집합 (메서드): {set_a.symmetric_difference(set_b)}")
left_val = d.popleft() # 왼쪽 끝 항목 제거 (매우 빠름!)
print(f"popleft(): {left_val}, 결과: {d}")
```
### 4.2. 컬렉션 요약 및 선택 가이드
#### 💡 쉽게 이해하기: deque는 '효율적인 대기줄'
> - **리스트**: '한 줄로 서 있는 긴 대기줄'입니다. 맨 앞사람(`list[0]`)이 빠져나가면, 뒤에 있는 모든 사람이 한 칸씩 앞으로 움직여야 합니다. 매우 비효율적이죠.
> - **`deque`**: '양쪽 끝에 문이 달린 대기 공간'입니다. 사람들이 앞문으로든 뒷문으로든 들어오고 나갈 수 있어서, 다른 사람들의 위치 이동 없이 입구와 출구에서 빠르게 작업이 이루어집니다. 데이터의 앞뒤에서 추가/삭제가 빈번할 때 `deque`를 쓰는 이유입니다.
지금까지 배운 네 가지 기본 컬렉션은 각각의 고유한 특징을 가지고 있어, 상황에 맞게 선택하는 것이 중요합니다. 아래 다이어그램은 어떤 상황에서 어떤 자료구조를 선택해야 하는지에 대한 간단한 가이드입니다.
### 3. 이름을 가진 튜플: `collections.namedtuple`
튜플의 각 항목에 이름을 붙여, 인덱스뿐만 아니라 **이름으로도 접근**할 수 있게 만든 자료구조입니다. 코드의 가독성을 획기적으로 높여줍니다.
```mermaid
graph TD
subgraph "자료구조 선택 가이드"
direction LR
start("데이터가 여러 개인가?") -->|"Yes"| q1("순서가 중요한가?")
start -->|"No"| others("단일 변수 사용")
q1 -->|"Yes"| q2("데이터 변경이 필요한가?")
q1 -->|"No"| q3("Key-Value 형태인가?")
```python
from collections import namedtuple
# 'Point'라는 이름의 namedtuple 클래스 생성
Point = namedtuple('Point', ['x', 'y', 'z'])
q2 -->|"Yes"| list_("[List]<br/>- 순서 O<br/>- 변경 O<br/>- 중복 O<br/>- 사용 예: 할 일 목록, 학생 명단")
q2 -->|"No"| tuple_("[Tuple]<br/>- 순서 O<br/>- 변경 X<br/>- 중복 O<br/>- 사용 예: 함수의 반환값, 좌표")
p1 = Point(10, 20, 30)
print(f"p1: {p1}")
q3 -->|"Yes"| dict_("[Dictionary]<br/>- Key-Value<br/>- Key 중복 X<br/>- 순서 O (3.7+)<br/>- 사용 예: 사람의 정보, JSON 데이터")
q3 -->|"No"| set_("[Set]<br/>- 순서 X<br/>- 중복 X<br/>- 집합 연산<br/>- 사용 예: 중복 아이템 제거, 멤버십 테스트")
end
# 인덱스로 접근 (튜플처럼)
print(f"p1[0]: {p1[0]}")
# 이름으로 접근 (객체처럼!)
print(f"p1.y: {p1.y}")
print(f"p1.z: {p1.z}")
```
이 가이드를 통해 해결하려는 문제의 성격에 가장 적합한 자료구조를 선택하여 더 효율적이고 읽기 좋은 코드를 작성할 수 있습니다.
#### 💡 쉽게 이해하기: namedtuple은 '이름표가 붙은 칸막이 상자'
> - **[튜플](./glossary.md#튜플tuple)**: `(10, 20, 30)`은 '내용물만 있는 투명한 칸막이 상자'입니다. 첫 번째 칸, 두 번째 칸... 하고 순서로만 내용물을 구분할 수 있습니다.
> - **[`namedtuple`](./glossary.md#collectionsnamedtuple)**: `Point(x=10, y=20, z=30)`은 각 칸마다 'x좌표', 'y좌표', 'z좌표'라고 **이름표를 붙여준 상자**입니다. `p1.y`처럼 이름으로 내용물을 꺼낼 수 있으니, `p1[1]`보다 훨씬 의미가 명확하고 실수를 줄여줍니다.
---
## 5. 종합 연습문제
아래 문제들을 풀어보면서 리스트, 튜플, 딕셔너리, 셋의 활용법을 익혀보세요.
## 4일차(목): 파이썬 자료구조 종합 실습
### 문제 1: 리스트 컴프리헨션 활용
`names` 리스트에서 5글자 이상인 이름만 대문자로 변경하여 새로운 리스트 `long_names_upper`를 만드세요.
### 🎯 오늘의 목표
- 지금까지 배운 여러 자료구조를 활용하여 실제 미니 문제를 해결합니다.
```python
names = ["Alice", "Bob", "Charlie", "David", "Eva", "Frank"]
---
# 여기에 코드를 작성하세요
long_names_upper = [name.upper() for name in names if len(name) >= 5]
print(long_names_upper)
### 문제 1: 최빈 단어 분석기
**요구사항**: 주어진 영어 텍스트에서 가장 많이 등장하는 단어 3개를 찾아 출력하세요. 단, 대소문자는 구분하지 않고, 구두점은 무시합니다. (`collections.Counter` 활용)
# 예상 출력: ['CHARLIE', 'DAVID', 'FRANK']
```python
import re
from collections import Counter
text = """
Python is an interpreted, high-level, general-purpose programming language.
Python's design philosophy emphasizes code readability with its notable use of
significant whitespace. Its language constructs and object-oriented approach
aim to help programmers write clear, logical code for small and large-scale projects.
"""
# 풀이
# 1. 정규표현식으로 단어만 추출하고 소문자로 변환
words = re.findall(r'\w+', text.lower())
# 2. Counter로 단어 빈도 계산
word_counts = Counter(words)
# 3. 가장 흔한 단어 3개 출력
print(f"가장 많이 나온 단어 3개: {word_counts.most_common(3)}")
# 예상 출력: [('python', 3), ('code', 2), ('its', 2)]
```
### 문제 2: 튜플 리스트 데이터 처
학생들의 정보가 `(이름, 점수)` 형태의 튜플로 리스트에 저장되어 있습니다. 점수가 80점 이상인 학생들의 이름만 추출하여 `high_scorers` 리스트를 만드세요.
### 문제 2: 연락처 그룹 관
**요구사항**: 친구들의 이름을 그룹('가족', '회사' 등)별로 관리하는 프로그램을 만드세요. 그룹에 친구를 추가하는 기능을 구현하세요. (`collections.defaultdict` 활용)
```python
students = [("Alice", 85), ("Bob", 72), ("Charlie", 95), ("David", 68), ("Eva", 88)]
# 여기에 코드를 작성하세요
high_scorers = [name for name, score in students if score >= 80]
print(high_scorers)
# 예상 출력: ['Alice', 'Charlie', 'Eva']
from collections import defaultdict
class ContactManager:
def __init__(self):
self.contacts = defaultdict(list)
def add_contact(self, name, group):
self.contacts[group].append(name)
print(f"'{name}'님을 '{group}' 그룹에 추가했습니다.")
def display_contacts(self):
for group, names in self.contacts.items():
print(f"--- {group} 그룹 ---")
for name in names:
print(f"- {name}")
# 풀이
manager = ContactManager()
manager.add_contact("엄마", "가족")
manager.add_contact("아빠", "가족")
manager.add_contact("김대리", "회사")
manager.add_contact("이과장", "회사")
manager.add_contact("동생", "가족")
print("\n--- 전체 연락처 ---")
manager.display_contacts()
```
### 문제 3: 딕셔너리를 이용한 데이터 집계
여러 과목의 성적이 딕셔너리로 주어졌을 때, 각 학생의 평균 점수를 계산하여 `average_scores` 딕셔너리에 저장하세요.
### 문제 3: 최근 재생 목록
**요구사항**: 노래를 재생할 때마다 '최근 재생 목록'에 추가합니다. 이 목록은 최대 5개까지만 유지됩니다. 5개를 초과하면 가장 오래된 노래가 자동으로 삭제되어야 합니다. (`collections.deque` 활용)
```python
scores = {
"Alice": {"math": 90, "english": 85, "science": 92},
"Bob": {"math": 78, "english": 80, "science": 75},
"Charlie": {"math": 88, "english": 92, "science": 95}
}
average_scores = {}
# 여기에 코드를 작성하세요
for name, subject_scores in scores.items():
average_scores[name] = sum(subject_scores.values()) / len(subject_scores)
print(average_scores)
# 예상 출력: {'Alice': 89.0, 'Bob': 77.666..., 'Charlie': 91.666...}
from collections import deque
class Playlist:
def __init__(self, max_size=5):
self.playlist = deque(maxlen=max_size)
def play(self, song):
self.playlist.append(song)
print(f"▶️ 재생: {song}")
def show_recent(self):
print("\n--- 최근 재생 목록 ---")
for song in reversed(self.playlist): # 최근 항목부터 보여주기
print(f"- {song}")
# 풀이
playlist = Playlist()
playlist.play("아이유 - 라일락")
playlist.play("BTS - Dynamite")
playlist.play("BLACKPINK - 뚜두뚜두")
playlist.play("폴킴 - 모든 날, 모든 순간")
playlist.play("AKMU - 어떻게 이별까지 사랑하겠어")
playlist.show_recent()
# 6번째 노래 추가 -> 가장 오래된 '라일락'이 삭제됨
playlist.play("NewJeans - Hype Boy")
playlist.show_recent()
```
### 문제 4: 셋을 이용한 공통 항목 찾기
두 개의 프로젝트에 참여하고 있는 팀원들의 명단이 각각 리스트로 주어졌습니다. 두 프로젝트에 모두 참여하고 있는 팀원을 찾아 `common_members` 셋으로 만드세요.
```python
project_a_members = ["Alice", "Bob", "Charlie", "David"]
project_b_members = ["Charlie", "Eva", "Frank", "Alice"]
---
# 여기에 코드를 작성하세요
common_members = set(project_a_members) & set(project_b_members)
print(common_members)
## 5일차(금): 3주차 리뷰 및 Q&A
# 예상 출력: {'Alice', 'Charlie'} (순서는 다를 수 있음)
```
### 🎯 오늘의 목표
- 3주차에 배운 컬렉션 심화 내용을 정리하고, 언제 어떤 자료구조를 써야 할지 감을 잡습니다.
- 궁금했던 점을 질문하고 동료들과 함께 해결합니다.
---
### ✅ 정리 및 다음 단계
### 컬렉션 선택 가이드: 어떤 공구를 언제 쓸까?
이번 파트에서는 파이썬에서 여러 데이터를 관리하는 핵심 도구인 **컬렉션(Collection)**에 대해 배웠습니다.
3주차 동안 우리는 파이썬의 강력한 컬렉션들을 배웠습니다. 이제 어떤 상황에 어떤 자료구조를 선택하는 것이 가장 효율적인지 한눈에 정리해봅시다.
- **리스트(List)**: 순서가 있고 변경 가능한 데이터 모음
- **튜플(Tuple)**: 순서가 있지만 변경 불가능한 데이터 모음
- **딕셔너리(Dictionary)**: Key-Value 쌍으로 이루어진 데이터 모음
- **셋(Set)**: 중복을 허용하지 않는 데이터 모음
| 자료구조 | 주요 특징 | 핵심 사용 사례 |
|---|---|---|
| **`list`** | 순서 O, 변경 O | 순서가 중요하고, 데이터 추가/삭제/변경이 잦을 때 (e.g., 쇼핑 목록, 작업 큐) |
| **`tuple`** | 순서 O, 변경 X | 순서가 중요하지만, 내용이 절대 바뀌면 안 될 때 (e.g., 좌표, DB 레코드, 상수 모음) |
| **`dict`** | 순서 X (3.7+부터는 입력 순서 유지), Key-Value, 변경 O | 의미 있는 키(Key)로 데이터를 빠르게 찾고 싶을 때 (e.g., 사용자 정보, JSON 데이터) |
| **`set`** | 순서 X, 중복 X, 변경 O | 중복을 제거하거나, 항목의 존재 여부를 빠르게 확인할 때, 집합 연산이 필요할 때 |
| **`Counter`** | `dict`의 자식 | 항목 별 개수를 세거나, 가장 빈번한 항목을 찾을 때 (e.g., 단어 빈도수 분석) |
| **`defaultdict`** | `dict`의 자식 | `KeyError` 없이, 키가 없을 때 기본값을 자동으로 생성하고 싶을 때 (e.g., 데이터 그룹핑) |
| **`deque`** | 양방향 큐, `list`와 유사 | 데이터의 양쪽 끝에서 추가/삭제가 매우 빈번할 때 (e.g., 최근 사용 목록, 너비 우선 탐색) |
| **`namedtuple`** | `tuple`의 자식 | 튜플에 이름표를 붙여 가독성을 극대화하고 싶을 때 (e.g., 간단한 데이터 객체 정의) |
각 컬렉션의 특징을 이해하고 상황에 맞게 사용하는 것은 효율적인 코드를 작성하는 데 매우 중요합니다.
이러한 자료구조의 특징을 이해하고 적재적소에 사용하면, 여러분의 파이썬 코드는 훨씬 더 효율적이고 읽기 좋아질 것입니다.
지금까지 우리는 파이썬의 기본 도구들을 모두 익혔습니다. 이제 이 도구들을 활용하여 더 체계적이고 구조적인 코드를 작성하는 방법을 배울 차례입니다.
---
**➡️ 다음 시간: [Part 4: 객체 지향 프로그래밍 (OOP)의 이해](part_4_object_oriented_programming.md)**
컬렉션의 세계를 깊이 탐험한 것을 축하합니다! 이제 여러분은 데이터를 단순히 담는 것을 넘어, 특성에 맞게 분류하고 효율적으로 관리하는 능력을 갖추게 되었습니다.
다음 시간에는 지금까지 배운 모든 문법과 자료구조를 '객체'라는 하나의 단위로 묶어, 더 크고 체계적인 프로그램을 설계하는 '객체 지향 프로그래밍(OOP)'의 세계로 떠나보겠습니다.
다음 시간에는 AI 라이브러리의 근간을 이루는 **객체 지향 프로그래밍(OOP)**의 세계로 들어갑니다. **클래스(Class)**와 **객체(Object)**의 개념을 이해하고, 우리만의 '설계도'를 만들어 코드를 더욱 체계적으로 관리하는 방법을 배우게 될 것입니다.
\ No newline at end of file
**➡️ 다음 시간: [Part 4: 객체 지향 프로그래밍 (OOP)](part_4_object_oriented_programming.md)**
\ No newline at end of file
# Part 4: 객체 지향 프로그래밍 (OOP)의 이해
# Part 4: 객체 지향 프로그래밍 (OOP) 마스터하기 (4주차)
---
### 💡 지난 시간 복습
**⬅️ 이전 시간: [Part 3: 파이썬 컬렉션 심화](./part_3_python_collections.md)** | **➡️ 다음 시간: [Part 5: AI 핵심 라이브러리](./part_5_ai_core_libraries.md)**
[Part 3: 데이터 관리를 위한 파이썬 컬렉션](part_3_python_collections.md)에서는 리스트, 튜플, 딕셔너리, 셋의 특징과 사용법을 익혔습니다. 이를 통해 여러 개의 데이터를 목적에 맞게 효율적으로 저장하고 관리하는 방법을 배웠습니다.
---
## 📜 실습 코드 바로가기
- **`part_4_object_oriented_programming.py`**: [바로가기](./source_code/part_4_object_oriented_programming.py)
- 본 파트의 모든 예제 코드는 위 파일에서 직접 실행하고 수정해볼 수 있습니다. 클래스, 상속, 다형성 등 OOP의 핵심 개념을 코드로 확인해보세요.
---
파이썬은 모든 것이 객체(Object)로 이루어진 강력한 객체 지향 프로그래밍(OOP) 언어입니다. Scikit-learn, PyTorch, TensorFlow 등 우리가 사용하는 대부분의 AI 라이브러리는 OOP 원칙에 기반하여 설계되었습니다. 클래스(Class)와 객체(Object)의 개념을 이해하면 라이브러리의 내부 구조를 파악하고 더 깊이 있게 활용할 수 있으며, 나아가 재사용과 확장이 용이한 모듈형 코드를 직접 작성할 수 있게 됩니다.
지금까지 우리는 파이썬의 문법과 다양한 데이터 구조(컬렉션)를 배웠습니다. 이는 프로그래밍의 '재료'를 다듬는 법을 익힌 것과 같습니다. 이번 주차에는 이 재료들을 조합하여 '요리'를 하는 법, 즉 더 크고 체계적인 프로그램을 만드는 '[객체 지향 프로그래밍(OOP)](./glossary.md#객체-지향-프로그래밍oop)' 패러다임을 학습합니다.
이 섹션에서는 OOP의 핵심 개념을 실용적인 예제와 함께 단계별로 학습합니다.
**왜 OOP가 AI 개발에서 중요할까요?**
---
여러분이 앞으로 사용하게 될 `Scikit-learn`, `PyTorch` 같은 AI 라이브러리의 동작 방식이 바로 OOP에 기반하기 때문입니다.
## 1. 클래스(Class)와 객체(Object)
- `model = RandomForestClassifier()`: 이 코드는 `RandomForestClassifier`라는 **클래스(설계도)**`model`이라는 **객체(실체)**를 만드는 과정입니다.
- `model.fit(X, y)`: `model` 객체가 가진 `fit`이라는 **메서드(행동)**를 호출하여 모델을 학습시킵니다.
- **클래스(Class)**: 객체를 만들기 위한 '설계도' 또는 '틀'입니다. 객체가 가지게 될 속성(데이터)과 행동(메서드)을 정의합니다. 예를 들어, '자동차'라는 클래스는 `색상`, `최대 속도`와 같은 속성과 `전진()`, `정지()`와 같은 행동을 가질 수 있습니다.
이처럼 OOP를 이해하면, 복잡한 AI 라이브러리의 구조를 꿰뚫어 보고, 각 부분이 어떻게 상호작용하는지 명확하게 파악할 수 있습니다.
- **객체(Object)** 또는 **인스턴스(Instance)**: 클래스로부터 생성된 실체를 의미합니다. '자동차' 클래스로부터 '빨간색 페라리', '검은색 아반떼'와 같은 구체적인 객체들을 만들어낼 수 있습니다. 각 객체는 클래스에서 정의한 속성과 행동을 가지지만, 속성의 값(예: 색상)은 객체마다 다를 수 있습니다.
### 💡 쉽게 이해하기: OOP는 '붕어빵 비즈니스'입니다
> OOP의 여러 개념을 이해하기 위해, 우리는 '붕어빵 장사'라는 비즈니스 모델을 사용할 것입니다. 앞으로 나오는 모든 개념을 이 붕어빵 가게에 빗대어 생각해 보세요!
>
> - **[클래스(Class)](./glossary.md#클래스class)**: 붕어빵의 모양, 크기, 굽는 방식을 정의하는 **'붕어빵 틀'** 그 자체입니다.
> - **[객체(Object)](./glossary.md#객체object--인스턴스instance)**: 붕어빵 틀에서 막 찍어낸, 세상에 단 하나뿐인 **'붕어빵'** 한 개입니다.
> - **[속성(Attribute)](./glossary.md#속성attribute)**: 각각의 붕어빵이 가진 고유한 특징, 예를 들어 '팥 앙금', '슈크림 앙금' 같은 **'속재료'** 입니다.
> - **[메서드(Method)](./glossary.md#메서드method)**: 붕어빵 틀을 이용해 할 수 있는 행동, 즉 **'굽기()', '뒤집기()', '포장하기()'** 같은 기능입니다.
<details>
<summary><b>✨ 클래스, 객체, 인스턴스 관계 다이어그램</b></summary>
파이썬은 모든 것이 객체(Object)로 이루어진 강력한 객체 지향 프로그래밍(OOP) 언어입니다. Scikit-learn, PyTorch 등 우리가 사용할 대부분의 AI 라이브러리는 OOP 원칙에 기반하여 설계되었습니다. OOP를 이해하면 라이브러리의 구조를 파악하고 더 깊이 있게 활용할 수 있으며, 재사용과 확장이 용이한 코드를 직접 작성할 수 있게 됩니다.
```mermaid
graph TD
A["<b>Dog 클래스 (설계도)</b><br/><br/><b>속성:</b><br/>- species (모든 객체 공유)<br/>- name, age, breed (객체별 고유값)<br/><br/><b>메서드:</b><br/>- describe()<br/>- speak()"]
---
B["<b>bory 객체 (인스턴스)</b><br/><br/>name: '보리'<br/>age: 3<br/>breed: '푸들'"]
C["<b>tory 객체 (인스턴스)</b><br/><br/>name: '토리'<br/>age: 5<br/>breed: '진돗개'"]
## 1일차(월): 클래스와 객체 - OOP의 시작
A -- "로부터 생성" --> B
A -- "로부터 생성" --> C
### 🎯 오늘의 목표
- 클래스(Class)와 객체(Object)의 개념을 이해하고 직접 클래스를 정의합니다.
- [`__init__`](./glossary.md#init)[`self`](./glossary.md#self)를 사용하여 객체의 고유한 상태를 만듭니다.
- [`@dataclass`](./glossary.md#dataclass) 데코레이터로 상용구(boilerplate) 코드를 획기적으로 줄입니다.
style A fill:#f9f9f9,stroke:#333,stroke-width:2px
style B fill:#e8f4ff,stroke:#333,stroke-width:2px
style C fill:#e8f4ff,stroke:#333,stroke-width:2px
```
---
</details>
### 1. 클래스(Class)와 객체(Object)
### 예제: '강아지' 클래스와 객체 만들기
- **[클래스(Class)](./glossary.md#클래스class)**: 객체를 만들기 위한 **'설계도'** 또는 '틀'. 객체가 가질 속성(데이터)과 행동(메서드)을 정의합니다. (예: '자동차' 설계도, **'붕어빵 틀'**)
- **[객체(Object)/인스턴스(Instance)](./glossary.md#객체object--인스턴스instance)**: 클래스로부터 생성된 **'실체'**. '자동차' 클래스로부터 '빨간색 페라리', '검은색 아반떼' 같은 구체적인 객체를 만듭니다. (예: **'팥 붕어빵', '슈크림 붕어빵'**)
```python
# 'Dog'라는 이름의 클래스를 정의합니다.
class Dog:
# 클래스 속성(Class Attribute):
# 이 클래스로부터 생성되는 모든 객체가 공유하는 속성입니다.
# 클래스 속성: 모든 Dog 객체가 공유하는 값
species = "Canis familiaris"
# 초기화 메서드 (__init__): 객체가 생성될 때(인스턴스화될 때) 자동으로 호출됩니다.
# self는 생성되는 객체 자기 자신을 가리킵니다.
def __init__(self, name: str, age: int, breed: str):
# 인스턴스 속성(Instance Attribute):
# 각 객체마다 고유하게 가지는 속성입니다.
print(f"{name} 강아지 객체를 생성합니다.")
# 초기화 메서드: 객체가 생성될 때 자동으로 호출되어 속성을 초기화합니다.
def __init__(self, name: str, age: int):
# 인스턴스 속성: 각 객체마다 고유하게 가지는 값
self.name = name
self.age = age
self.breed = breed
# 인스턴스 메서드(Instance Method):
# 객체가 수행할 수 있는 행동을 정의합니다.
# 첫 번째 인자로는 항상 self를 받습니다.
def describe(self) -> str:
"""강아지의 정보를 문자열로 반환합니다."""
return f"{self.name}{self.breed} 품종이며, {self.age}살 입니다."
# 인스턴스 메서드: 객체가 수행할 수 있는 행동
def speak(self, sound: str):
"""강아지가 짖는 소리를 출력합니다."""
return f"{self.name}가 '{sound}' 하고 짖습니다."
```
> **붕어빵 비유**:
> - `__init__`: 붕어빵 틀(`Dog`)에 반죽을 붓고, 각 붕어빵마다 다른 앙금(`name`, `age`)을 넣는 과정입니다.
> - `self`: 지금 막 만들어지고 있는 **'바로 그 붕어빵'** 자신을 의미합니다. 이 `self` 덕분에 "보리"라는 붕어빵과 "짱구"라는 붕어빵이 서로 다른 이름(`self.name`)을 가질 수 있습니다.
### 2. `self`의 정체
`self`**메서드를 호출한 객체 자기 자신**을 가리키는, 파이썬 OOP의 핵심 규칙입니다.
#### 💡 쉽게 이해하기: `self`는 객체의 '나 자신'이라는 대명사
> "보리야, 짖어봐!" 라고 `bory.speak("멍멍")`을 실행하면, `Dog` 클래스는 `bory`라는 객체를 `self`라는 이름으로 받습니다. 그리고 `speak` 메서드 안에서 `self.name`이라고 쓰는 것은, `bory` 객체가 " **나(bory)의** 이름은 '보리'야 " 라고 자기 자신의 속성에 접근하는 것과 같습니다. 이 `self` 덕분에 수많은 Dog 객체들이 각자 자신의 이름과 나이를 헷갈리지 않고 사용할 수 있습니다.
# --- 클래스 사용하기 ---
- 메서드를 정의할 때 첫 번째 매개변수로 반드시 `self`를 써주어야 합니다.
- `bory.speak("멍멍")` 코드는 내부적으로 `Dog.speak(bory, "멍멍")`과 같이 동작합니다. `self` 매개변수로 `bory` 객체 자신이 전달되는 것입니다.
# 'Dog' 클래스로부터 'bory'와 'tory'라는 두 개의 객체(인스턴스)를 생성합니다.
bory = Dog("보리", 3, "푸들")
tory = Dog("토리", 5, "진돗개")
### 3. 보일러플레이트 줄이기: `@dataclass`
데이터를 담는 용도의 클래스를 만들 때, `__init__`, `__repr__` (객체 출력) 같은 반복적이고 지루한 코드(Boilerplate)를 자동으로 생성해주는 매우 유용한 [데코레이터](./glossary.md#데코레이터decorator)입니다.
# 각 객체의 메서드를 호출합니다.
print(bory.describe())
print(tory.describe())
#### 💡 쉽게 이해하기: `@dataclass`는 '클래스 자동 생성기'
> [`@dataclass`](./glossary.md#dataclass)는 마치 **'클래스의 귀찮은 부분들을 알아서 만들어주는 기계'**와 같습니다. 우리는 `name: str`, `age: int`처럼 필요한 속성들만 선언해주면, 이 기계가 객체를 초기화하는 `__init__` 메서드나, 객체를 예쁘게 출력해주는 `__repr__` 메서드 같은 것들을 보이지 않는 곳에서 자동으로 뚝딱 만들어줍니다.
```python
from dataclasses import dataclass
# 각 객체의 속성에 접근합니다.
print(f"{bory.name}의 나이는 {bory.age}살입니다.")
print(f"{tory.name}의 나이는 {tory.age}살입니다.")
@dataclass
class DogData:
name: str
age: int
species: str = "Canis familiaris" # 기본값 지정도 가능
# 클래스 속성은 모든 객체가 공유합니다.
print(f"{bory.name}의 종은 {bory.species}입니다.")
print(f"{tory.name}의 종은 {tory.species}입니다.")
def speak(self, sound: str):
return f"{self.name}가 '{sound}' 하고 짖습니다."
# __init__을 직접 작성하지 않았지만 자동으로 생성됨!
bory = DogData("보리", 3)
print(bory) # __repr__도 자동으로 생성되어 출력이 깔끔함
# 출력: DogData(name='보리', age=3, species='Canis familiaris')
```
> 🚫 **Common Pitfall: `self`는 대체 무엇인가?**
> OOP를 처음 접할 때 가장 혼란스러운 개념 중 하나가 바로 `self`입니다.
> - **`self`는 예약어가 아닙니다**: `self`는 관례적으로 사용하는 이름일 뿐, `me`, `this` 등 다른 이름으로 바꿔도 동작합니다. 하지만 모든 파이썬 개발자가 따르는 약속이므로 `self`를 사용하는 것이 좋습니다.
> - **`self`는 인스턴스 자기 자신입니다**: 클래스의 메서드를 호출할 때, 파이썬은 그 메서드를 호출한 객체 자신을 첫 번째 인자로 몰래(?) 전달합니다.
> ```python
> # bory.describe()는 내부적으로 이렇게 동작합니다:
> # Dog.describe(bory)
> ```
> 따라서 `describe(self)` 메서드의 `self` 매개변수에는 `bory` 객체가 전달되는 것입니다. `self.name`은 곧 `bory.name`과 같습니다.
> - **메서드를 정의할 때 `self`를 빼먹는 실수**: `def describe():` 와 같이 `self`를 빼먹으면, 파이썬이 `bory` 객체를 첫 인자로 전달할 곳이 없으므로 `TypeError: describe() takes 0 positional arguments but 1 was given` 와 같은 에러가 발생합니다. 인스턴스의 속성이나 다른 메서드를 사용하려면, 메서드의 첫 번째 인자로는 반드시 `self`를 포함해야 합니다.
> 💡 **Tip: `__init__`과 `__repr__`을 자동으로? `dataclasses`**
> 위 `Dog` 클래스처럼 `__init__` 메서드에서 `self.name = name`과 같이 속성을 초기화하는 코드는 매우 흔하지만, 속성이 많아지면 반복적이고 길어집니다. 파이썬 3.7부터 도입된 `dataclasses` 모듈을 사용하면 이 과정을 자동으로 처리할 수 있습니다.
>
> ```python
> from dataclasses import dataclass
>
> @dataclass
> class DogData:
> # 이렇게 타입 힌트와 함께 속성을 선언하기만 하면...
> name: str
> age: int
> breed: str
> # __init__ 메서드가 자동으로 생성됩니다!
>
> # 일반 메서드도 똑같이 추가할 수 있습니다.
> def describe(self) -> str:
> return f"{self.name}는 {self.breed} 품종이며, {self.age}살 입니다."
>
> # 사용할 때는 일반 클래스와 완전히 동일합니다.
> bory_data = DogData("보리", 3, "푸들")
> print(bory_data) # 객체를 출력하면 자동으로 생성된 __repr__ 덕분에 보기 좋게 나옵니다.
> # 출력: DogData(name='보리', age=3, breed='푸들')
> print(bory_data.describe())
> ```
> `@dataclass` 데코레이터를 클래스 위에 붙여주기만 하면, `__init__`, `__repr__`(객체 출력 형식), `__eq__`(객체 비교) 등 유용한 메서드들을 자동으로 생성해줍니다. 주로 데이터를 담는 용도의 클래스를 만들 때 사용하면 코드를 매우 간결하게 유지할 수 있습니다.
---
## 2일차(화): 상속 - 코드 재사용의 마법
### 🎯 오늘의 목표
- 상속을 통해 부모 클래스의 기능을 물려받아 코드 중복을 줄입니다.
- 메서드 오버라이딩으로 부모의 기능을 자식에 맞게 재정의합니다.
- `super()`를 사용하여 부모 클래스의 메서드를 호출합니다.
---
## 2. OOP의 4대 핵심 원칙
### 1. 상속(Inheritance)과 메서드 오버라이딩
**[상속](./glossary.md#상속inheritance)**은 부모 클래스(Superclass)의 속성과 메서드를 자식 클래스(Subclass)가 물려받는 것입니다. 이는 마치 **'기본 설계도'를 바탕으로 '수정 설계도'를 만드는 것**과 같습니다. 자식 클래스는 부모의 기능을 그대로 사용하거나, **[메서드 오버라이딩(Method Overriding)](./glossary.md#메서드-오버라이딩method-overriding)**을 통해 자신에게 맞게 수정할 수 있습니다.
### 2.1. 상속 (Inheritance)
부모 클래스(Superclass)의 속성과 메서드를 자식 클래스(Subclass)가 물려받아 재사용하는 것입니다. 코드의 중복을 줄이고 논리적인 계층 구조를 만들 수 있습니다.
> **붕어빵 비유**:
> '기본 붕어빵 틀(`Dog`)'을 물려받아, 기능이 추가된 '피자 붕어빵 틀(`GoldenRetriever`)'을 새로 만드는 것과 같습니다. 모양은 같지만, 굽는 방식(`speak`)은 피자 토핑에 맞게 살짝 달라질 수 있습니다.
#### 예제: `Dog` 클래스를 상속받는 `GoldenRetriever` 클래스
```python
# 위에서 정의한 Dog 클래스를 부모 클래스로 사용합니다.
class GoldenRetriever(Dog): # Dog 클래스를 상속
# 메서드 오버라이딩: 부모의 speak 메서드를 재정의
def speak(self, sound="월! 월!"):
return f"{self.name}가 행복하게 '{sound}' 하고 짖습니다."
```
class GoldenRetriever(Dog): # Dog 클래스를 상속받음
# 자식 클래스에서 새로운 메서드를 추가할 수 있습니다.
def fetch(self, item: str) -> str:
return f"{self.name}{item}을 물어왔습니다!"
### 2. 부모의 능력을 빌리는 `super()`
자식 클래스에서 부모 클래스의 메서드를 호출하고 싶을 때 [`super()`](./glossary.md#super)를 사용합니다. 이를 통해 부모의 기능을 완전히 덮어쓰는 대신, **기능을 확장**할 수 있습니다.
# 부모의 메서드를 재정의(Method Overriding)할 수도 있습니다.
def speak(self, sound="월! 월!"): # 골든 리트리버는 다르게 짖도록 재정의
return f"{self.name}가 행복하게 '{sound}' 하고 짖습니다."
> **붕어빵 비유**:
> '프리미엄 붕어빵 틀(`SpecialDog`)'을 만들 때, 기존 붕어빵 틀(`Dog`)의 모양과 앙금 넣는 방식(`__init__`)은 그대로 쓰고 싶습니다. 이때 `super().__init__()`를 호출하면, 마치 기본 틀의 기능을 그대로 가져와 사용하고, 우리는 프리미엄 재료(`ability`)를 추가하는 작업에만 집중할 수 있습니다.
# 자식 클래스로 객체 생성
happy = GoldenRetriever("해피", 2, "골든 리트리버")
```python
class SpecialDog(Dog):
def __init__(self, name: str, age: int, ability: str):
# super()를 이용해 부모 클래스(Dog)의 __init__을 호출하여
# name과 age 속성 초기화를 위임합니다.
super().__init__(name, age)
# 자식 클래스만의 속성을 추가로 초기화합니다.
self.ability = ability
def describe(self):
# 부모의 describe가 있다면 호출하고, 없다면 직접 구현
# (Dog 클래스에 describe가 없으므로 직접 구현)
return f"{self.name}{self.age}살이며, '{self.ability}' 능력이 있습니다."
special_dog = SpecialDog("슈퍼독", 5, "비행")
print(special_dog.describe())
# 출력: 슈퍼독는 5살이며, '비행' 능력이 있습니다.
```
# 부모 클래스(Dog)의 메서드를 그대로 사용 가능
print(happy.describe())
---
# 자식 클래스(GoldenRetriever)에서 추가한 메서드 사용
print(happy.fetch("공"))
## 3일차(수): 다형성과 캡슐화 - 유연하고 안전한 코드
# 자식 클래스에서 재정의한 메서드 사용
print(happy.speak())
```
### 🎯 오늘의 목표
- 다형성(Polymorphism)을 통해 코드의 유연성과 확장성을 높입니다.
- 캡슐화(Encapsulation)를 통해 객체의 데이터를 안전하게 보호합니다.
### 2.2. 다형성 (Polymorphism)
"여러(Poly) 개의 형태(Morph)"라는 뜻으로, 동일한 이름의 메서드나 연산자가 객체의 종류에 따라 다르게 동작하는 것을 의미합니다. 상속 관계에서 메서드 오버라이딩을 통해 주로 구현됩니다.
---
### 1. 다형성(Polymorphism): 같은 이름, 다른 행동
"여러(Poly) 개의 형태(Morph)"라는 뜻으로, 동일한 이름의 메서드가 객체의 종류에 따라 다르게 동작하는 것입니다. 이것이 바로 [다형성](./glossary.md#다형성polymorphism)입니다.
> **붕어빵 비유**:
> 손님이 "붕어빵 하나 주세요!"라고 말합니다. 가게 주인은 손님이 보는 앞에서 `판매하기()`라는 동일한 동작을 취합니다. 하지만 손님에게 전달되는 것은 '팥 붕어빵'일 수도, '피자 붕어빵'일 수도 있습니다. 이처럼 **같은 `판매하기()` 요청에 대해, 대상(객체)에 따라 다른 결과가 나타나는 것**이 다형성입니다.
#### 예제: 동물들의 소리
```python
class Cat:
def __init__(self, name):
self.name = name
def speak(self): # Dog 클래스와 동일한 이름의 메서드
return "야옹"
# Horse 클래스를 새로 추가해도 animal_sound 함수는 바꿀 필요가 없습니다!
class Horse:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name}가 '야옹'하고 웁니다."
return "히히힝"
# Dog 클래스와 Cat 클래스는 서로 다른 클래스지만,
# 동일한 이름의 speak() 메서드를 가지고 있습니다.
bory = Dog("보리", 3, "푸들")
bory = Dog("보리", 3)
nabi = Cat("나비")
pony = Horse("포니")
# animal_sound 함수는 어떤 동물 객체가 오든 상관없이
# .speak() 메서드만 호출하여 다형성을 활용합니다.
# animal_sound 함수는 객체의 클래스가 무엇인지(개가, 고양이인지, 말인지) 신경쓰지 않고
# .speak() 메서드만 호출합니다. 이것이 다형성의 힘입니다.
def animal_sound(animal):
print(animal.speak())
animal_sound(bory) # Dog 객체의 speak() 호출 -> 보리가 '멍멍' 하고 짖습니다.
animal_sound(nabi) # Cat 객체의 speak() 호출 -> 나비가 '야옹'하고 웁니다.
# 이처럼, 객체가 어떤 클래스인지 일일이 확인할 필요 없이
# 공통된 인터페이스(speak)를 통해 다양한 객체를 동일한 방식으로 처리할 수 있습니다.
# 이는 코드의 유연성과 확장성을 크게 높여줍니다.
#### ✨ AI 라이브러리 속 다형성: Scikit-learn
우리가 사용하는 Scikit-learn 라이브러리는 다형성의 원칙을 매우 활용하고 있습니다. `LogisticRegression`, `DecisionTreeClassifier`, `SVM` 다양한 분류 모델들은 모두 이름은 다르지만, 데이터를 학습시키는 `.fit(X, y)` 메서드와 예측을 수행하는 `.predict(X)` 메서드를 공통적으로 가지고 있습니다.
```python
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
# 모델 객체 생성
model1 = LogisticRegression()
model2 = DecisionTreeClassifier()
# 동일한 인터페이스(.fit, .predict)를 사용
# model1.fit(X_train, y_train)
# model1.predict(X_test)
print(f"{animal.name}: {animal.speak()}")
# model2.fit(X_train, y_train)
# model2.predict(X_test)
animal_sound(bory) # Dog 객체의 speak()가 호출됨
animal_sound(nabi) # Cat 객체의 speak()가 호출됨
animal_sound(pony) # Horse 객체의 speak()가 호출됨
```
이처럼 개발자는 모델의 내부 구현이 어떻게 다른지 신경 쓸 필요 없이, `fit`과 `predict`라는 일관된 방식으로 다양한 모델을 훈련하고 사용할 수 있습니다. 이것이 바로 다형성이 코드의 재사용성과 생산성을 높이는 강력한 예시입니다.
> **AI 라이브러리 속 다형성**: Scikit-learn의 모든 모델(`LogisticRegression`, `DecisionTree` 등)은 `.fit()`과 `.predict()`라는 동일한 이름의 메서드를 가집니다. 덕분에 우리는 모델의 종류와 상관없이 일관된 방식으로 코드를 작성할 수 있습니다.
### 2.3. 캡슐화 (Encapsulation)
객체의 속성(데이터)과 그 데이터를 처리하는 메서드를 하나로 묶고, 데이터의 일부를 외부에서 직접 접근하지 못하도록 숨기는 것입니다. 이를 **정보 은닉(Information Hiding)**이라고도 합니다. 파이썬에서는 이름 앞에 언더스코어(`_` 또는 `__`)를 붙여 캡슐화를 구현합니다.
### 2. 캡슐화(Encapsulation): 소중한 데이터 보호하기
객체의 속성 외부에서 직접 수정하지 못하도록 숨기고, 정해진 메서드(getter/setter)를 통해서만 접근하도록 제어하는 것, 즉 [캡슐화](./glossary.md#캡슐화encapsulation)니다.
- **`_` (Protected)**: "이 속성이나 메서드는 클래스 내부용이니 가급적 직접 건드리지 마세요"라는 약속. (강제성은 없음)
- **`__` (Private)**: 클래스 외부에서 직접 접근할 수 없도록 이름이 변경(Name Mangling)됨.
> **붕어빵 비유**:
> 붕어빵 맛의 핵심인 **'반죽의 비밀 레시피(`__balance`)'**는 아무나 볼 수도, 수정할 수도 없게 주방 깊숙한 곳에 숨겨둡니다. 레시피를 변경하려면, 반드시 점장님의 허락(메서드 호출)을 거쳐 정해진 절차(`deposit`)에 따라서만 가능합니다. 이렇게 소중한 데이터를 외부로부터 보호하는 것이 캡슐화입니다.
- `_` (Protected): "내부용이니 가급적 건드리지 마세요" 라는 개발자 간의 약속.
- `__` (Private): 이름 앞에 `_클래스명`이 붙어([Name Mangling](./glossary.md#네임-맹글링name-mangling)) 외부에서 직접 접근이 거의 불가능해짐.
#### 예제: 은행 계좌
```python
class BankAccount:
def __init__(self, owner_name: str, initial_balance: float):
self.owner_name = owner_name
# 잔액(_balance)은 외부에서 직접 수정할 수 없도록 보호합니다.
self.__balance = initial_balance
def deposit(self, amount: float):
"""입금: 양수 금액만 입금 가능"""
def __init__(self, owner, balance):
self.owner = owner
self.__balance = balance # 잔액은 외부에서 직접 수정 불가 (private)
# getter: 잔액을 외부로 알려주는 통로
def get_balance(self):
return self.__balance
# setter: 잔액을 정해진 규칙에 따라 수정하는 통로
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"{amount}원 입금되었습니다.")
print(f"{amount}원 입금 완료. 현재 잔액: {self.__balance}")
else:
print("입금액은 0보다 커야 합니다.")
def withdraw(self, amount: float):
"""출금: 잔액 내에서만 출금 가능"""
if 0 < amount <= self.__balance:
self.__balance -= amount
print(f"{amount}원이 출금되었습니다.")
else:
print("출금액이 유효하지 않거나 잔액이 부족합니다.")
def get_balance(self) -> float:
"""잔액 조회 메서드를 통해서만 잔액을 확인할 수 있습니다."""
return self.__balance
account = BankAccount("홍길동", 10000)
# 잔액을 직접 수정하려고 하면 오류가 발생하거나 의도대로 동작하지 않습니다.
# account.__balance = 999999 # private 속성은 직접 접근/수정 불가
# print(account.__balance) # AttributeError 발생
# 반드시 클래스가 제공하는 메서드를 통해 데이터에 접근하고 조작해야 합니다.
print(f"현재 잔액: {account.get_balance()}")
account.deposit(5000)
account.withdraw(2000)
print(f"최종 잔액: {account.get_balance()}")
# 이렇게 캡슐화를 통해 데이터의 무결성을 지키고,
# 객체의 사용법을 단순하게 만들 수 있습니다.
my_account = BankAccount("홍길동", 10000)
# my_account.__balance = 999999 # 직접 수정 시도 -> AttributeError 발생
my_account.deposit(5000)
print(f"최종 잔액 조회: {my_account.get_balance()}")
```
### 2.4. 정적/클래스 메서드 (Static/Class Method)
일반적으로 클래스의 메서드는 첫 번째 인자로 객체 자기 자신(`self`)을 받는 인스턴스 메서드입니다. 하지만 때로는 객체의 상태(인스턴스 속성)와는 독립적인 기능을 클래스에 포함시켜야 할 때가 있습니다. 이럴 때 `@staticmethod`와 `@classmethod`를 사용합니다.
---
#### 예제: 다양한 방식으로 날짜 객체를 생성하는 클래스
## 4일차(목): 추상화와 매직 메서드
```python
import datetime
class MyDate:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def display(self):
return f"{self.year}-{self.month:02d}-{self.day:02d}"
@staticmethod
def is_valid_date(date_string: str) -> bool:
"""
날짜 문자열(YYYY-MM-DD)이 유효한지 검사하는 정적 메서드.
특정 객체의 상태(self)나 클래스 정보(cls)를 사용하지 않는 순수 함수입니다.
"""
try:
year, month, day = map(int, date_string.split('-'))
datetime.date(year, month, day)
return True
except ValueError:
return False
@classmethod
def from_string(cls, date_string: str):
"""
"YYYY-MM-DD" 형식의 문자열로부터 클래스 객체를 생성하는 클래스 메서드.
'cls' 인자를 통해 클래스 자체에 접근하여 새로운 객체를 생성합니다.
"""
year, month, day = map(int, date_string.split('-'))
return cls(year, month, day) # cls()는 MyDate()와 동일
# 인스턴스 메서드 호출
date_obj1 = MyDate(2023, 12, 25)
print(date_obj1.display())
# 정적 메서드 호출 (객체를 생성할 필요 없음)
print(f"2023-12-25는 유효한 날짜인가? {MyDate.is_valid_date('2023-12-25')}")
# 클래스 메서드 호출 (객체를 생성하는 또 다른 방법)
date_obj2 = MyDate.from_string("2024-01-01")
print(date_obj2.display())
```
### 🎯 오늘의 목표
- 추상화를 통해 클래스가 반드시 구현해야 할 메서드를 '규칙'으로 강제합니다.
- 매직 메서드로 파이썬 내장 기능(출력, 비교 등)을 커스터마이징합니다.
---
## 3. 미니 프로젝트: 텍스트 RPG 캐릭터 만들기
지금까지 배운 클래스, 상속, 메서드 오버라이딩 개념을 모두 활용하여 간단한 텍스트 기반 RPG 게임의 캐릭터 시스템을 만들어 보겠습니다.
### 1. 추상화(Abstraction): 필수 기능 강제하기
[추상 클래스](./glossary.md#추상-클래스abstract-class)는 '미완성 설계도'와 같습니다. `abc` 모듈과 `@abstractmethod`를 사용하여 자식 클래스가 반드시 구현해야 할 메서드를 명시할 수 있습니다. 이것이 [추상화](./glossary.md#추상화abstraction)입니다.
### 3.1. 기본 `Character` 클래스 구현
모든 캐릭터(전사, 마법사 등)가 공통으로 가질 속성(이름, 체력, 공격력)과 행동(공격, 상태 보기)을 부모 클래스인 `Character`에 정의합니다.
> **붕어빵 비유**:
> 붕어빵 프랜차이즈 본사에서 '가맹점 운영 매뉴얼(`AbstractAnimal`)'을 내놓았습니다. 이 매뉴얼에는 "모든 가맹점은 반드시 '굽기(`speak`)' 기능을 스스로 구현해야 합니다"라는 **규칙**이 명시되어 있습니다. 본사는 어떻게 구울지(how)는 알려주지 않고, 무엇을 해야 하는지(what)만 강제합니다. 이 규칙을 따르지 않은 가게는 오픈할 수 없습니다(에러 발생).
```python
import random
from abc import ABC, abstractmethod
class Character:
def __init__(self, name: str, hp: int, power: int):
# ABC를 상속받아 추상 클래스로 지정
class AbstractAnimal(ABC):
def __init__(self, name):
self.name = name
self.max_hp = hp
self.hp = hp
self.power = power
def attack(self, other):
damage = random.randint(self.power - 2, self.power + 2)
other.hp = max(other.hp - damage, 0)
print(f"🗡️ {self.name}이(가) {other.name}에게 {damage}의 피해를 입혔습니다.")
if other.hp == 0:
print(f"😵 {other.name}이(가) 쓰러졌습니다.")
# @abstractmethod: 자식 클래스는 이 메서드를 반드시 구현해야 함
@abstractmethod
def speak(self):
pass
# 추상 클래스를 상속받는 자식 클래스
class Lion(AbstractAnimal):
# 부모의 추상 메서드인 speak()를 반드시 구현해야 합니다.
# 만약 구현하지 않으면 TypeError가 발생합니다.
def speak(self):
return "어흥"
def show_status(self):
print(f"[{self.name}] HP: {self.hp}/{self.max_hp} | POWER: {self.power}")
# 추상 클래스는 직접 객체로 만들 수 없습니다.
# my_animal = AbstractAnimal("동물") # -> TypeError 발생
lion = Lion("사자")
print(f"{lion.name}: {lion.speak()}")
```
### 3.2. 상속을 이용한 직업 클래스 확장
### 2. 파이썬의 매직 메서드(Magic Methods)
[매직 메서드](./glossary.md#매직-메서드magic-methods)(또는 던더 메서드, Dunder Methods)는 `__이름__` 형태를 가지며, 파이썬의 내장 연산자나 함수(`+`, `len()`, `str()` 등)가 우리 클래스의 객체와 함께 사용될 때 호출되는 특별한 메서드입니다.
`Character` 클래스를 상속받아, 각 직업의 고유한 특징을 가진 `Warrior`와 `Magician` 클래스를 만듭니다.
> **붕어빵 비유**:
> 우리가 만든 '붕어빵' 객체를 `print(my_bungeoppang)` 했을 때 "팥이 든 맛있는 붕어빵"처럼 예쁜 설명이 나오게 하거나, `bungeoppang1 + bungeoppang2` 처럼 붕어빵을 합치는 새로운 규칙을 만들 수 있게 해주는 마법 같은 기능입니다.
- **전사(Warrior)**: 기본 공격 외에, 더 큰 피해를 주는 '스킬'을 사용하지만, 스킬 사용 후에는 방어력이 약해지는 특징을 가집니다.
- **마법사(Magician)**: 마나(MP)를 사용하여 강력한 광역 마법을 사용합니다.
| 매직 메서드 | 호출되는 상황 | 설명 |
|---|---|---|
| `__str__(self)` | `str(obj)`, `print(obj)` | 객체를 사람이 읽기 좋은 **문자열**로 표현할 때 |
| `__repr__(self)`| `repr(obj)` | 객체를 **개발자가 명확히** 이해할 수 있는 문자열로 표현할 때 |
| `__len__(self)` | `len(obj)` | 객체의 길이를 반환 |
| `__add__(self, other)` | `obj1 + obj2` | 덧셈 연산 정의 |
| `__eq__(self, other)` | `obj1 == obj2`| 동등 비교 연산 정의 |
```python
# 위에서 정의한 Character 클래스를 상속받습니다.
class Warrior(Character):
def __init__(self, name: str, hp: int, power: int):
super().__init__(name, hp, power) # 부모 클래스의 __init__ 호출
self.defense_mode = False
# 메서드 오버라이딩 (부모의 attack 메서드를 재정의)
def attack(self, other):
if self.defense_mode:
print(f"🛡️ {self.name}은(는) 방어 태세라 공격할 수 없습니다.")
self.defense_mode = False # 다음 턴에 공격 가능하도록
return
super().attack(other)
def skill(self, other):
"""기존 공격력의 2배에 달하는 스킬 공격을 합니다."""
skill_damage = self.power * 2
other.hp = max(other.hp - skill_damage, 0)
print(f"💥 {self.name}의 스킬! {other.name}에게 {skill_damage}의 치명적인 피해!")
if other.hp == 0:
print(f"😵 {other.name}이(가) 쓰러졌습니다.")
# 스킬 사용 후 방어 태세로 전환
self.defense_mode = True
print(f"🛡️ {self.name}이(가) 스킬 사용 후 방어 태세에 들어갑니다.")
class Magician(Character):
def __init__(self, name: str, hp: int, power: int, mp: int):
super().__init__(name, hp, power)
self.max_mp = mp
self.mp = mp
def magic(self, others: list):
"""마나를 10 소모하여 모든 적에게 마법 공격을 합니다."""
if self.mp < 10:
print("MP가 부족하여 마법을 사용할 수 없습니다.")
return
self.mp -= 10
print(f"🔥 {self.name}의 광역 마법! (MP {self.mp}/{self.max_mp})")
for target in others:
damage = random.randint(self.power, self.power + 5)
target.hp = max(target.hp - damage, 0)
print(f"✨ {target.name}에게 {damage}의 마법 피해!")
if target.hp == 0:
print(f"😵 {target.name}이(가) 쓰러졌습니다.")
# 메서드 오버라이딩 (마법사 정보에 맞게 상태 표시 변경)
def show_status(self):
print(f"[{self.name}] HP: {self.hp}/{self.max_hp} | MP: {self.mp}/{self.max_mp} | POWER: {self.power}")
@dataclass
class Word:
text: str
count: int
def __str__(self):
return f"단어 '{self.text}' ({self.count}번 등장)"
def __add__(self, other):
# Word 객체끼리 + 연산을 하면 count를 합친 새 Word 객체 반환
if isinstance(other, Word):
return Word(self.text, self.count + other.count)
else:
raise TypeError("Word 객체끼리만 더할 수 있습니다.")
def __eq__(self, other):
# 두 Word 객체는 text가 같으면 같은 것으로 취급
return self.text == other.text
```
### 3.3. 게임 실행
---
## 5일차(금): 주간 마무리 및 종합 실습
생성한 클래스들로 객체를 만들어 간단한 전투 시뮬레이션을 진행합니다.
### 문제: 온라인 서점 시스템 모델링
- `Book` (책), `Member` (회원), `Order` (주문) 이라는 3개의 클래스를 `dataclass`를 사용하여 정의하고, 각 클래스 간의 관계를 표현하는 간단한 온라인 서점 시스템을 만들어보세요.
**요구사항:**
1. **`Book` 클래스**:
- 속성: `isbn` (고유번호), `title` (제목), `author` (저자), `price` (가격)
2. **`Member` 클래스**:
- 속성: `member_id`, `name`, `address`
3. **`Order` 클래스**:
- 속성: `order_id`, `member` (`Member` 객체), `books` (`Book` 객체의 리스트)
- 메서드: `total_price()` (주문한 책들의 총 가격을 계산하여 반환)
4. 객체를 생성하고, 회원이 여러 권의 책을 주문하는 시나리오를 코드로 구현하세요.
```python
# 캐릭터 객체 생성
warrior = Warrior("아서스", hp=120, power=15)
magician = Magician("제이나", hp=80, power=20, mp=50)
monster1 = Character("고블린", 50, 5)
monster2 = Character("오크", 70, 8)
# 전투 시작
print("--- ⚔️ 전투 시작 ⚔️ ---\n")
warrior.show_status()
magician.show_status()
monster1.show_status()
monster2.show_status()
print("\n-------------------\n")
# 턴 1
print("--- 턴 1 ---")
warrior.attack(monster1)
magician.magic([monster1, monster2])
monster1.attack(warrior)
print("\n")
# 턴 2
print("--- 턴 2 ---")
warrior.skill(monster2)
magician.show_status() # 마법사 상태 확인
monster2.attack(magician)
print("\n")
# 전투 후 상태
print("--- ⚔️ 전투 종료 후 상태 ⚔️ ---")
warrior.show_status()
magician.show_status()
monster1.show_status()
monster2.show_status()
from dataclasses import dataclass, field
from typing import List
@dataclass
class Book:
isbn: str
title: str
author: str
price: int
@dataclass
class Member:
member_id: str
name: str
address: str
@dataclass
class Order:
order_id: str
member: Member
books: List[Book] = field(default_factory=list)
def total_price(self) -> int:
return sum(book.price for book in self.books)
# 1. 객체 생성
member1 = Member("user001", "김파이", "서울시 강남구")
book1 = Book("978-0-13-235088-4", "Clean Code", "Robert C. Martin", 30000)
book2 = Book("978-0-201-63361-0", "Design Patterns", "Erich Gamma", 35000)
# 2. 주문 생성 및 실행
order1 = Order("order123", member1, [book1, book2])
# 3. 결과 출력
print(f"주문자: {order1.member.name}")
print("주문 내역:")
for book in order1.books:
print(f" - {book.title} ({book.author})")
print(f"총 주문 금액: {order1.total_price()}원")
```
이 미니 프로젝트를 통해, OOP의 핵심 원칙들이 어떻게 실제 코드의 구조를 잡고, 기능을 확장하며, 각 객체의 개성을 부여하는 데 사용되는지 경험할 수 있습니다.
---
### ✅ 정리 및 다음 단계
이번 파트에서는 객체 지향 프로그래밍의 핵심인 **클래스**와 **객체**의 개념을 배우고, **상속, 다형성, 캡슐화**와 같은 중요 원칙들을 학습했습니다.
## 📝 4주차 요약
- **클래스**: 객체를 만들기 위한 설계도
- **객체**: 클래스로부터 생성된 실체
- **상속**: 코드 재사용성을 높임
- **다형성**: 코드 유연성을 높임
- **캡슐화**: 코드 안정성을 높임
이번 주차에는 프로그램을 체계적으로 설계하는 패러다임, 객체 지향 프로그래밍(OOP)을 '붕어빵 비즈니스'에 빗대어 배웠습니다.
OOP를 이해함으로써 우리는 앞으로 사용할 AI 라이브러리들의 동작 방식을 더 깊이 이해하고, 재사용 가능한 코드를 작성할 수 있는 튼튼한 기반을 마련했습니다.
- **클래스와 객체**: '붕어빵 틀'과 '붕어빵'의 관계를 통해, 설계도(클래스)로 실체(객체)를 만드는 기본 개념과 `__init__`, `self`의 용법을 익혔습니다. `@dataclass`로 반복적인 코드를 줄이는 법도 배웠습니다.
- **상속**: 부모 클래스의 기능을 물려받아(상속) 코드 중복을 줄이고, 자신에 맞게 기능을 재정의(오버라이딩)하거나 `super()`로 부모 기능을 확장하는 방법을 학습했습니다.
- **다형성과 캡슐화**: 동일한 요청에 대해 객체별로 다르게 반응하는 다형성으로 코드 유연성을 높이고, `__`를 이용한 캡슐화로 데이터를 안전하게 보호하는 방법을 배웠습니다.
- **추상화와 매직 메서드**: 추상 클래스로 자식 클래스가 구현해야 할 기능을 강제하고, `__str__`이나 `__len__` 같은 매직 메서드로 파이썬의 내장 기능을 우리 객체에 맞게 커스터마이징했습니다.
이제 파이썬의 기본기를 모두 다졌습니다. 다음 파트에서는 드디어 AI 개발의 세계로 본격적으로 뛰어듭니다.
## 🔍 4주차 연습 문제
**➡️ 다음 시간: [Part 5: AI 개발 핵심 라이브러리](part_5_ai_core_libraries.md)**
**문제 1: 게임 캐릭터 클래스 만들기**
- 기본 `Character` 클래스를 만드세요.
- 속성: `name`, `hp`, `power`
- 메서드: `attack(other)` (상대방(`other`)의 `hp`를 자신의 `power`만큼 감소시킴)
- `Character`를 상속받는 `Warrior``Wizard` 클래스를 만드세요.
- `Warrior`: `attack` 메서드를 오버라이딩하여, 20% 확률로 2배의 데미지를 입히도록 수정하세요.
- `Wizard`: `attack` 메서드를 오버라이딩하고, 공격 후 자신의 `hp``power`의 절반만큼 회복하는 기능을 추가하세요. (최대 hp는 넘지 않도록)
- 전사와 마법사 객체를 만들어 서로 공격하는 시나리오를 구현해보세요.
다음 시간에는 데이터 분석과 머신러닝의 필수 도구인 **NumPy, Pandas, Matplotlib, Scikit-learn** 라이브러리의 핵심 사용법을 익힙니다. 이 라이브러리들을 활용하여 데이터를 다루고, 시각화하고, 간단한 머신러닝 모델을 직접 만들어보는 실습을 진행할 것입니다.
\ No newline at end of file
**➡️ 다음 시간: [Part 5: AI 핵심 라이브러리 (NumPy & Pandas)](./part_5_ai_core_libraries.md)**
\ No newline at end of file
# Part 5: AI 개발 핵심 라이브러리
# Part 5: AI 핵심 라이브러리 마스터하기 (5주차)
**⬅️ 이전 시간: [Part 4: 객체 지향 프로그래밍(OOP)](./part_4_object_oriented_programming.md)** | **➡️ 다음 시간: [Part 6: 머신러닝 모델링과 평가](./part_6_machine_learning.md)**
---
## 📜 실습 코드 바로가기
- **`part_5_ai_core_libraries.py`**: [바로가기](./source_code/part_5_ai_core_libraries.py)
- 본 파트에서 다루는 NumPy, Pandas, Matplotlib의 핵심 기능 예제 코드를 직접 실행하고 수정해볼 수 있습니다.
### 💡 지난 시간 복습
---
[Part 4: 객체 지향 프로그래밍 (OOP)의 이해](part_4_object_oriented_programming.md)에서는 클래스, 객체, 상속, 다형성 등 OOP의 핵심 개념을 배웠습니다. 이를 통해 AI 라이브러리들이 어떻게 구조적으로 설계되었는지 이해하고, 재사용성이 높은 코드를 작성할 수 있는 기반을 마련했습니다.
지난 4주간 우리는 파이썬이라는 언어 자체에 대한 이해와 객체 지향 설계 능력을 길렀습니다. 이제부터는 이 강력한 언어를 날개 삼아, 데이터의 세계를 본격적으로 탐험할 시간입니다.
2부에서는 데이터 분석과 머신러닝 프로젝트의 실질적인 '주연' 배우들인 NumPy, Pandas, Scikit-learn 등의 핵심 라이브러리 사용법을 깊이 있게 학습합니다.
---
## 0. 핵심 라이브러리 관계도 (The Big Picture)
## ⭐️ 2부의 시작: 핵심 라이브러리 관계도 (The Big Picture)
본격적으로 각 라이브러리를 배우기 전에, 이들이 데이터 분석 및 머신러닝 프로젝트에서 어떻게 함께 사용되는지에 대한 전체적인 그림을 이해하는 것이 중요합니다. 아래 다이어그램은 일반적인 데이터 흐름을 보여줍니다.
본격적으로 각 라이브러리를 배우기 전에, 이들이 데이터 분석 및 머신러닝 프로젝트에서 어떻게 함께 사용되는지에 대한 전체적인 그림을 이해하는 것이 중요합니다.
```mermaid
graph TD
subgraph "데이터 수집 및 전처리"
A["<b>Pandas (DataFrame)</b><br/>- CSV/Excel/DB에서 데이터 로드<br/>- 결측치 처리, 필터링, 그룹화"]
A["<b>Pandas (DataFrame)</b><br/>- CSV/Excel/DB에서 데이터 로드<br/>- 결측치 처리, 필터링, 그룹화, 병합"]
end
subgraph "고성능 수치 연산"
B["<b>NumPy (ndarray)</b><br/>- DataFrame에서 변환<br/>- 벡터/행렬 연산, 수학 함수"]
end
subgraph "머신러닝 모델링"
subgraph "머신러닝 모델링 & 평가"
C["<b>Scikit-learn (Estimator)</b><br/>- 모델 학습(fit), 예측(predict)<br/>- 모델 평가, 하이퍼파라미터 튜닝"]
end
......@@ -37,569 +43,188 @@ graph TD
B -->|"plt.plot(array)"| D
C -->|"결과 분석"| D
```
- **시작은 Pandas**: 대부분의 프로젝트는 Pandas를 사용해 다양한 소스(CSV, Excel 등)로부터 데이터를 불러와 `DataFrame`으로 만드는 것에서 시작합니다.
- **계산을 위해 NumPy로**: 정제된 데이터는 고성능 수치 계산을 위해 NumPy 배열(`ndarray`)로 변환되어 머신러닝 모델의 입력으로 준비됩니다.
- **Scikit-learn으로 모델링**: NumPy 배열로 변환된 데이터를 사용하여 Scikit-learn으로 모델을 학습하고 예측합니다.
- **모든 과정에서 시각화**: Pandas와 NumPy 데이터는 Matplotlib/Seaborn을 통해 시각화되어 데이터의 특징을 파악(EDA)하거나 모델의 성능을 분석하는 데 사용됩니다.
- **시작은 [Pandas](./glossary.md#pandas)**: 대부분의 프로젝트는 Pandas를 사용해 다양한 소스(CSV 등)로부터 데이터를 불러와 `DataFrame`으로 만드는 것에서 시작합니다.
- **계산을 위해 [NumPy](./glossary.md#numpy)로**: 정제된 데이터는 고성능 수치 계산을 위해 NumPy 배열(`ndarray`)로 변환되어 머신러닝 모델의 입력으로 준비됩니다.
- **[Scikit-learn](./glossary.md#scikit-learn)으로 모델링**: NumPy 배열로 변환된 데이터를 사용하여 Scikit-learn으로 모델을 학습하고 예측합니다.
- **모든 과정에서 시각화**: Pandas와 NumPy 데이터는 [Matplotlib/Seaborn](./glossary.md#matplotlib--seaborn)을 통해 시각화되어 데이터의 특징을 파악(EDA)하거나 모델의 성능을 분석하는 데 사용됩니다.
이 흐름을 염두에 두고 각 라이브러리를 학습하면, 지식이 파편적으로 남지 않고 하나의 큰 그림으로 연결될 것입니다.
---
---
## 1. NumPy (Numerical Python) - 고성능 수치 계산
# 1부: 데이터 과학자의 연장통 - NumPy & Pandas (5주차)
- **핵심**: 파이썬에서 과학 계산을 위한 가장 기본적인 패키지입니다. `ndarray`(N-dimensional array)라는 효율적인 다차원 배열 객체를 중심으로, 벡터, 행렬 등 고성능 수치 연산을 위한 방대한 함수들을 제공합니다. Pandas, Scikit-learn, TensorFlow 등 대부분의 데이터 과학 라이브러리가 NumPy를 기반으로 동작하므로, AI 개발자에게는 알파벳과도 같은 라이브러리입니다.
### 🎯 5주차 학습 목표
- **[NumPy](./glossary.md#numpy)**를 사용하여 고성능 과학 계산 및 다차원 배열을 다룹니다.
- **[Pandas](./glossary.md#pandas)**를 사용하여 표 형식의 데이터를 자유자재로 읽고, 선택하고, 필터링하고, 그룹화하고, 병합합니다.
### 1.1. NumPy 배열 생성
```python
import numpy as np
---
# 파이썬 리스트로부터 배열 생성
list_a = [1, 2, 3, 4, 5]
np_a = np.array(list_a)
print(f"파이썬 리스트: {list_a}")
print(f"NumPy 배열: {np_a}")
# 2차원 배열 (행렬) 생성
list_2d = [[1, 2, 3], [4, 5, 6]]
matrix = np.array(list_2d)
print("2x3 행렬:\n", matrix)
# 배열의 형태(shape), 차원(ndim), 데이터 타입(dtype) 확인
print(f"Shape: {matrix.shape}, Dtype: {matrix.dtype}, Ndim: {matrix.ndim}")
# 특정 값으로 채워진 배열 생성
zeros = np.zeros((2, 4)) # 2x4 크기의 0으로 채워진 배열
ones = np.ones((3, 2)) # 3x2 크기의 1로 채워진 배열
print("Zeros:\n", zeros)
print("Ones:\n", ones)
```
## 1일차(월): NumPy - 고성능 수치 계산의 초석
- **NumPy(Numerical Python)**: 파이썬에서 과학 계산을 위한 가장 기본적인 패키지. [`ndarray`](./glossary.md#ndarray)라는 효율적인 다차원 배열 객체를 중심으로, 벡터, 행렬 등 고성능 수치 연산을 위한 방대한 함수들을 제공합니다.
### 1.2. 배열 연산 (Element-wise Operations)
NumPy 배열의 가장 큰 장점 중 하나는 반복문 없이 배열 전체에 대해 빠른 연산이 가능하다는 것입니다.
#### 💡 쉽게 이해하기: NumPy 배열은 '계란판', 파이썬 리스트는 '장바구니'
> - **파이썬 리스트**: 뭐든 담을 수 있는 만능 '장바구니'입니다. 사과, 책, 신발 등 종류에 상관없이 담을 수 있어 유연하지만, 내용물이 제각각이라 전체를 한 번에 계산하기는 어렵고 느립니다.
> - **NumPy 배열**: 오직 한 종류(숫자)만 담을 수 있는 '계란판'과 같습니다. 모든 칸의 모양과 크기가 같아, '모든 계란에 도장 찍기' 같은 작업을 기계로 한 번에 처리할 수 있습니다. 이런 **균일성**과 **규칙성** 덕분에 NumPy는 엄청나게 빠른 속도로 숫자 계산을 할 수 있습니다. AI와 데이터 과학에서 대규모 데이터를 다룰 때 NumPy가 필수인 이유입니다.
### 1. `ndarray` 생성 및 연산
```python
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[5, 6], [7, 8]])
# 배열 간 덧셈
print("덧셈:\n", arr1 + arr2)
# print("덧셈:\n", np.add(arr1, arr2)) # 동일한 연산
# 배열 간 곱셈
print("곱셈:\n", arr1 * arr2)
import numpy as np
# 스칼라(상수) 연산
print("스칼라 곱셈:\n", arr1 * 10)
```
# 파이썬 리스트로부터 배열 생성
py_list = [[1, 2, 3], [4, 5, 6]]
np_array = np.array(py_list)
print("Shape:", np_array.shape) # (2, 3)
### 1.3. 브로드캐스팅 (Broadcasting)
서로 다른 크기의 배열 간에도 연산이 가능하도록 배열을 자동으로 확장시켜주는 기능입니다.
```python
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
vector = np.array([10, 20, 30])
# 배열 연산: for문 없이 모든 요소에 대해 한 번에 연산
result = np_array * 2 + 1
print("배열 연산 결과:\n", result)
# 3x3 행렬과 1x3 벡터의 덧셈
# vector가 3x3 행렬처럼 확장되어 각 행에 더해짐
result = matrix + vector
print("브로드캐스팅 결과:\n", result)
# 브로드캐스팅: 서로 다른 크기의 배열 간에도 연산이 가능하도록 확장
matrix = np.array([[1, 2, 3], [4, 5, 6]]) # shape: (2, 3)
vector = np.array([10, 20, 30]) # shape: (3,)
result_broadcast = matrix + vector
print("브로드캐스팅 결과:\n", result_broadcast)
```
### 1.4. 인덱싱과 슬라이싱
### 2. 인덱싱과 슬라이싱
```python
arr = np.arange(10) # 0부터 9까지의 숫자로 배열 생성
print("원본 배열:", arr)
# 슬라이싱
print("arr[2:5]:", arr[2:5]) # [2 3 4]
arr = np.arange(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 2차원 배열 인덱
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("matrix[1, 2]:", matrix[1, 2]) # 2행 3열의 원소 -> 6
# 인덱싱 및 슬라이
print("3번째 원소:", arr[2])
print("3~5번째 원소:", arr[2:5])
# 조건(Boolean) 인덱싱: 특정 조건을 만족하는 원소만 추출
evens = arr[arr % 2 == 0]
print("배열의 짝수:", evens)
# 조건(Boolean) 인덱싱: 특정 조건을 만족하는 원소만 추출하는 강력한 기능
print("3보다 큰 원소:", arr[arr > 3])
print("짝수만 추출:", arr[arr % 2 == 0])
```
---
## 2. Pandas - 체계적인 데이터 분석 및 정제
- **핵심**: `DataFrame`이라는 2차원 테이블 형식의 자료구조와 `Series`라는 1차원 배열 형태의 자료구조를 사용하여, 엑셀이나 데이터베이스 테이블처럼 데이터를 직관적으로 다룰 수 있게 해줍니다. CSV/Excel 파일 로딩, 데이터 필터링, 결측치 처리, 그룹화(Group by) 등 데이터 전처리에 없어서는 안 될 라이브러리입니다.
---
## 2일차(화): Pandas - '슈퍼 엑셀' DataFrame의 등장
- **Pandas**: [`DataFrame`](./glossary.md#dataframe)(2차원 테이블)과 [`Series`](./glossary.md#series)(1차원 배열) 자료구조를 사용하여, 엑셀이나 DB 테이블처럼 데이터를 직관적으로 다루게 해주는 라이브러리. 데이터 전처리에 없어서는 안 될 필수 도구입니다.
### 2.1. Series와 DataFrame 생성
### 1. `Series``DataFrame`
```python
import pandas as pd
# Series 생성 (1차원)
s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
print("Pandas Series:\n", s)
# DataFrame 생성 (2차원)
# DataFrame 생성
data = {'제품': ['사과', '바나나', '체리', '딸기'],
'가격': [1000, 500, 2000, 1500],
'재고': [10, 20, 15, 5]}
df = pd.DataFrame(data)
print("Pandas DataFrame:\n", df)
```
print("DataFrame:\n", df)
### 2.2. 데이터 선택 및 필터링
```python
# 특정 열(Column) 선택
# Series: DataFrame의 한 열
prices = df['가격']
print("가격 열:\n", prices)
# loc: 라벨(이름) 기반 선택
print("1번 인덱스 행:\n", df.loc[1])
print("0, 2번 인덱스 행의 '제품', '재고' 열:\n", df.loc[[0, 2], ['제품', '재고']])
# iloc: 정수 인덱스 기반 선택
print("첫 번째 행:\n", df.iloc[0])
# 조건 필터링
high_price_products = df[df['가격'] >= 1000]
print("가격이 1000원 이상인 제품:\n", high_price_products)
low_stock_products = df[(df['가격'] < 1000) & (df['재고'] < 10)]
print("가격 1000원 미만이고 재고 10개 미만인 제품:\n", low_stock_products)
> 🚫 **Common Pitfall: `SettingWithCopyWarning`을 조심하세요!**
> Pandas를 사용하다 보면 `SettingWithCopyWarning`이라는 경고를 자주 마주치게 됩니다. 이는 버그는 아니지만, 내가 의도한 대로 원본 DataFrame이 수정되지 않았을 있다는 중요한 신호입니다.
>
> **언제 발생하나요?** 주로 '연쇄 인덱싱(Chained Indexing)'을 사용하여 값을 할당할 때 발생합니다.
> ```python
> # 이런 방식은 경고를 유발할 수 있습니다.
> df[df['가격'] >= 1000]['재고'] = 0
> ```
> - **왜 문제인가요?**: `df[df['가격'] >= 1000]`는 원본의 '뷰(View)'를 반환할 수도, '복사본(Copy)'을 반환할 수도 있습니다. 만약 복사본이 반환되면, 그 복사본의 '재고' 열을 0으로 바꾸는 것이므로 원본 `df`에는 아무런 변화가 없습니다. Pandas는 이처럼 사용자의 의도가 불분명한 상황을 경고해주는 것입니다.
> - **올바른 해결책**: `.loc`를 사용하여 행과 열을 한 번에 지정하는 것이 가장 확실하고 권장되는 방법입니다.
> ```python
> # .loc[행 인덱서, 열 인덱서] 형태로 한 번에 접근합니다.
> condition = df['가격'] >= 1000
> df.loc[condition, '재고'] = 0 # 경고 없이 명확하게 원본을 수정합니다.
> ```
> 이 경고를 무시하지 말고, `.loc`를 사용하는 습관을 들이는 것이 데이터 조작의 정확성을 보장하는 길입니다.
print("\nSeries (가격):\n", prices)
```
### 2.3. 데이터 그룹화 (Group by)
### 2. 데이터 읽고 쓰기 및 기본 탐색
```python
data = {'팀': ['A', 'B', 'A', 'B', 'A'],
'선수': ['손흥민', '김민재', '황희찬', '이강인', '조규성'],
'득점': [12, 1, 8, 5, 10]}
df_player = pd.DataFrame(data)
# df.to_csv("fruits.csv", index=False, encoding="utf-8-sig") # CSV 파일로 저장
# df_from_csv = pd.read_csv("fruits.csv") # CSV 파일 읽기
# '팀' 별로 그룹화하여 득점의 합계 계산
team_scores = df_player.groupby('팀')['득점'].sum()
print("팀별 총 득점:\n", team_scores)
💡 **Tip: `pipe()`로 깔끔한 데이터 처리 파이프라인 만들기**
> 여러 단계의 데이터 전처리 작업을 순차적으로 적용할 때, 코드가 지저분해지거나 중간 과정에 불필요한 변수들이 많이 생길 수 있습니다. `pipe()` 메서드를 사용하면 함수들을 체인처럼 연결하여 가독성 높은 파이프라인을 구축할 수 있습니다.
>
> ```python
> # 전처리를 위한 함수들을 정의합니다.
> def add_vat(df: pd.DataFrame) -> pd.DataFrame:
> """가격에 부가세를 추가하는 함수"""
> df_copy = df.copy()
> df_copy['가격(VAT포함)'] = df_copy['가격'] * 1.1
> return df_copy
>
> def label_stock(df: pd.DataFrame) -> pd.DataFrame:
> """재고 수준을 라벨링하는 함수"""
> df_copy = df.copy()
> df_copy['재고상태'] = pd.cut(df_copy['재고'], bins=[-1, 5, 15, 100], labels=['부족', '보통', '충분'])
> return df_copy
>
> # pipe()를 사용하여 함수들을 순서대로 적용합니다.
> processed_df = (df.pipe(add_vat)
> .pipe(label_stock)
> .sort_values(by='가격'))
>
> print("파이프라인 처리 결과:\n", processed_df)
> ```
> 위 코드처럼 `(df.pipe(...).pipe(...))` 형태로 코드를 작성하면, 데이터가 어떤 순서로 처리되는지 명확하게 파악할 수 있어 유지보수가 쉬워집니다.
print("\n--- head() ---")
print(df.head()) # 맨 위 5개 행
print("\n--- info() ---")
df.info() # 각 열의 정보와 결측치 확인
print("\n--- describe() ---")
print(df.describe()) # 숫자형 열의 기술 통계량
```
---
## 3. Matplotlib & Seaborn - 데이터 시각화
## 3일차(수): Pandas - 데이터 선택과 필터링의 기술
- **핵심**: 데이터를 그래프로 표현하여 패턴과 인사이트를 찾는 데 사용됩니다. `Matplotlib`은 파이썬 시각화의 근간이 되는 라이브러리로, 세밀한 조정이 가능한 다양한 종류의 그래프를 그릴 수 있습니다. `Seaborn`은 Matplotlib을 기반으로 더 아름답고 통계적으로 복잡한 그래프를 쉽게 그릴 수 있도록 돕는 고수준 라이브러리입니다.
### 1. `.loc` vs `.iloc`: 이름과 순서로 선택하기
> - **`.loc` (Label-based)**: 학생의 **'이름'**으로 자리를 찾는 방식 (`df.loc[행_이름, 열_이름]`)
> - **`.iloc` (Integer-based)**: 학생이 앉아있는 **'자리 번호'**로 찾는 방식 (`df.iloc[행_번호, 열_번호]`)
```python
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
# loc: 1번 인덱스 행의 '제품', '재고' 열
print(df.loc[1, ['제품', '재고']])
# 한글 폰트 설정 (Windows: Malgun Gothic, Mac: AppleGothic)
# 실행 환경에 맞는 폰트 이름을 지정해야 합니다.
# 폰트가 없을 경우, 오류 대신 경고 메시지를 출력하고 기본 폰트를 사용합니다.
try:
plt.rcParams['font.family'] = 'Malgun Gothic'
except RuntimeError:
print("Malgun Gothic 폰트를 찾을 수 없습니다. 기본 폰트를 사용합니다.")
plt.rcParams['axes.unicode_minus'] = False # 마이너스 기호 깨짐 방지
# 예제 데이터 로드 (Seaborn에 내장된 'tips' 데이터셋)
tips = sns.load_dataset("tips")
# --- Matplotlib & Seaborn을 활용한 다양한 시각화 ---
# 1. 막대 그래프 (Bar Plot): 카테고리별 데이터 비교
plt.figure(figsize=(10, 6))
sns.barplot(x="day", y="total_bill", hue="sex", data=tips, palette="viridis")
plt.title("요일 및 성별 전체 결제 금액 평균", fontsize=15)
plt.xlabel("요일")
plt.ylabel("전체 결제 금액 ($)")
plt.show()
# 2. 산점도 (Scatter Plot): 두 연속형 변수 간의 관계
# 점의 크기(size), 모양(style), 색상(hue)으로 추가적인 정보를 표현할 수 있습니다.
plt.figure(figsize=(10, 6))
sns.scatterplot(x="total_bill", y="tip", hue="time", style="smoker", size="size", data=tips, alpha=0.7, palette="muted")
plt.title("결제 금액과 팁의 관계 (시간/흡연여부/인원수별)", fontsize=15)
plt.xlabel("전체 결제 금액 ($)")
plt.ylabel("팁 ($)")
plt.legend(title="구분")
plt.show()
# 3. 히스토그램 (Histogram)과 KDE: 단일 변수의 분포 확인
plt.figure(figsize=(10, 6))
sns.histplot(tips['total_bill'], kde=True, bins=30, color='skyblue')
plt.title("전체 결제 금액의 분포", fontsize=15)
plt.xlabel("전체 결제 금액 ($)")
plt.ylabel("빈도수")
plt.show()
# 4. 박스 플롯 (Box Plot): 그룹별 데이터 분포 요약(중앙값, 사분위수 등)
plt.figure(figsize=(10, 6))
sns.boxplot(x="day", y="tip", hue="smoker", data=tips, palette="Set3")
plt.title("요일 및 흡연여부별 팁의 분포", fontsize=15)
plt.xlabel("요일")
plt.ylabel("팁 ($)")
plt.show()
# iloc: 0번 행의 0, 2번 열
print(df.iloc[0, [0, 2]])
```
---
## 4. Scikit-learn - 실용적인 머신러닝
- **핵심**: 분류(Classification), 회귀(Regression), 군집화(Clustering) 등 전통적인 머신러닝 알고리즘 대부분을 일관되고 사용하기 쉬운 API로 제공하는 최고의 라이브러리입니다. 모델 학습, 평가, 데이터 분할, 특징 공학(Feature Engineering) 등 머신러닝 프로젝트의 전 과정을 지원합니다.
### 예제: 붓꽃(Iris) 품종 분류
### 2. 조건으로 필터링하기
```python
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.datasets import load_iris
import joblib
# 1. 데이터 준비
iris = load_iris()
X = iris.data # 특징(Feature): 꽃받침 길이/너비, 꽃잎 길이/너비
y = iris.target # 타겟(Label): 붓꽃 품종 (0, 1, 2)
# 2. 데이터 분리 (학습용 70% / 테스트용 30%)
# random_state: 재현 가능하도록 난수 시드 고정
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
print(f"학습 데이터 shape: {X_train.shape}, 테스트 데이터 shape: {X_test.shape}")
# 3. 모델 선택 학습
# 로지스틱 회귀 모델 객체 생성
model = LogisticRegression(max_iter=200)
# 학습 데이터를 이용하여 모델 학습
model.fit(X_train, y_train)
# 4. 예측 평가
# 학습된 모델로 테스트 데이터 예측
predictions = model.predict(X_test)
# 실제값(y_test)과 예측값(predictions)을 비교하여 정확도 계산
accuracy = accuracy_score(y_test, predictions)
print(f"모델 예측 정확도: {accuracy:.4f}")
# 5. 모델 저장하기 (Part 6에서 사용)
# 학습된 모델을 나중에 재사용하기 위해 파일로 저장합니다.
joblib.dump(model, 'iris_model.pkl')
print("학습된 모델을 'iris_model.pkl' 파일로 저장했습니다.")
```
# 가격이 1000원 이상인 제품
print("\n가격 1000원 이상:\n", df[df['가격'] >= 1000])
### 심화 예제: 보스턴 집값 예측 (회귀)
# 가격 1000원 미만이고(and) 재고 10개 미만인 제품
# & (and), | (or), ~ (not)
print("\n가격 1000원 미만 AND 재고 10개 미만:\n", df[(df['가격'] < 1000) & (df['재고'] < 10)])
```
> 🚫 **`SettingWithCopyWarning` 주의!**: 값을 변경할 때는 `.loc`를 사용해 `df.loc[행조건, 열이름] = 값` 형태로 한 번에 접근해야 의도치 않은 오류를 막을 수 있습니다. 경고는 "원본을 바꿀지, 복사본을 바꿀지 애매하니 명확히 해달라"는 신호입니다.
분류(Classification)가 정해진 카테고리 중 하나를 맞추는 문제라면, 회귀(Regression)는 연속적인 숫자 값을 예측하는 문제입니다. Scikit-learn은 회귀 문제 역시 분류와 거의 동일한 프로세스로 해결할 수 있도록 일관된 API를 제공합니다.
---
## 4일차(목): Pandas - 데이터 분해와 조립 (`groupby`)
`groupby()`는 데이터를 특정 기준으로 그룹화하여 각 그룹에 대한 집계(합계, 평균 등)를 수행하는 강력한 기능입니다.
> ⚠️ **데이터셋 로드 주의사항**: `load_boston` 함수는 Scikit-learn 1.2 버전부터 사용이 중단될 예정입니다. 여기서는 데이터의 윤리적 문제를 인지하고 학습 목적으로만 사용하며, `fetch_openml`을 통해 대체 데이터를 로드합니다.
#### 💡 쉽게 이해하기: `groupby`는 '반별 성적 평균 계산' (Split-Apply-Combine)
> `groupby`는 '반별 성적을 계산하는 선생님'과 같습니다.
> 1. **Split (나누기)**: 전체 학생 명단을 '1반', '2반', '3반'으로 나눕니다 (`df.groupby('반')`).
> 2. **Apply (적용하기)**: 각 반별로 '평균 점수 계산'이라는 동일한 작업을 수행합니다 (`.agg('mean')`).
> 3. **Combine (합치기)**: 각 반의 평균 점수를 모아 하나의 최종 성적표로 합칩니다.
```python
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Lasso
from sklearn.metrics import mean_squared_error
from sklearn.datasets import fetch_openml
import numpy as np
import joblib
# 1. 데이터 준비 (보스턴 집값 데이터셋)
# 'return_X_y=True'는 특징(X)과 타겟(y)을 튜플로 바로 반환해줍니다.
X, y = fetch_openml(name='boston', version=1, as_frame=False, return_X_y=True, parser='auto')
# 2. 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 3. 모델 선택 및 학습 (Lasso 회귀 모델 사용)
# Lasso는 불필요한 특징의 가중치를 0으로 만들어 모델을 단순화하는 효과가 있습니다.
model_boston = Lasso(alpha=0.1)
model_boston.fit(X_train, y_train)
# 4. 예측 및 평가
# 회귀 모델의 평가지표로는 주로 평균 제곱 오차(Mean Squared Error)를 사용합니다.
predictions_boston = model_boston.predict(X_test)
mse = mean_squared_error(y_test, predictions_boston)
print(f"\n[회귀 모델] 예측 평균 제곱 오차(MSE): {mse:.2f}")
data = {'팀': ['A', 'B', 'A', 'B', 'A'], '선수': ['손흥민', '김민재', '황희찬', '이강인', '조규성'], '득점': [12, 1, 8, 5, 10]}
df_player = pd.DataFrame(data)
# 5. 모델 저장하기
joblib.dump(model_boston, 'boston_model.pkl')
print("학습된 회귀 모델을 'boston_model.pkl' 파일로 저장했습니다.")
# '팀' 별로 그룹화하여 득점의 합계, 평균, 선수 수 계산
team_agg = df_player.groupby('팀')['득점'].agg(['sum', 'mean', 'count'])
print(team_agg)
```
---
## 5일차(금): Pandas - 여러 데이터 합치고 모양 바꾸기
## 5. TensorFlow & PyTorch - 딥러닝 프레임워크
- **핵심**: 인간의 뇌를 모방한 인공 신경망(Artificial Neural Network)을 구축하고 학습시키기 위한 핵심 프레임워크입니다. 자동 미분(Autograd), GPU 가속 등 딥러닝에 필수적인 기능을 제공하며, 현대 AI 연구와 서비스의 중심에 있습니다. `PyTorch`는 연구 커뮤니티에서 선호되며, 유연하고 파이썬 친화적인 코딩 스타일이 특징입니다. `TensorFlow`는 강력한 생태계와 프로덕션 환경에서의 배포(TensorFlow Serving, TF Lite)에 강점을 가집니다.
### 5.1. PyTorch 예제
#### 텐서 연산과 자동 미분
### 1. 데이터 병합: `pd.merge()`
> `merge`는 '학생 명단'과 '도서관 대출 기록'을 '학번'이라는 공통 열을 기준으로 합쳐, 학생별 대출 정보를 만드는 것과 같습니다. (SQL의 JOIN과 유사)
```python
import torch
# 텐서(Tensor) 생성. requires_grad=True는 자동 미분을 추적하도록 설정
tensor_a = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32, requires_grad=True)
tensor_b = torch.tensor([[5, 6], [7, 8]], dtype=torch.float32)
# 텐서 연산
tensor_sum = tensor_a + tensor_b
tensor_mul = tensor_a * tensor_b
# 행렬 곱
tensor_matmul = torch.matmul(tensor_a, tensor_b)
print(f"PyTorch 텐서의 합:\n{tensor_sum}")
import pandas as pd
df1 = pd.DataFrame({'id': ['A01', 'A02', 'A03'], '이름': ['김파이', '이판다', '박넘파']})
df2 = pd.DataFrame({'id': ['A01', 'A02', 'A04'], '과목': ['수학', '영어', '과학']})
# 딥러닝 모델의 손실(loss) 계산 과정 예시
loss = tensor_mul.sum()
# 손실에 대한 tensor_a의 기울기 계산
loss.backward()
# 공통 'id'를 기준으로 병합 (inner: 양쪽 모두에 id가 있는 경우만)
inner_join = pd.merge(df1, df2, on='id', how='inner')
print("Inner Join:\n", inner_join)
# 기울기 확인
print(f"tensor_a의 기울기(gradient):\n{tensor_a.grad}")
# df1을 기준으로 병합 (left: df1의 모든 id를 유지)
left_join = pd.merge(df1, df2, on='id', how='left')
print("\nLeft Join:\n", left_join)
```
#### 간단한 신경망 모델 정의
### 2. 데이터 연결: `pd.concat()`
> `concat`은 '1반 학생 명단' 아래에 '2반 학생 명단'을 그대로 이어 붙여 전체 학생 명단을 만드는 것과 같습니다.
```python
import torch
import torch.nn as nn
import torch.nn.functional as F
# nn.Module을 상속받아 신경망 모델 클래스를 정의합니다.
class SimpleNet(nn.Module):
def __init__(self, input_size, hidden_size, num_classes):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size) # 첫 번째 완전 연결 레이어
self.relu = nn.ReLU() # 활성화 함수
self.fc2 = nn.Linear(hidden_size, num_classes) # 두 번째 완전 연결 레이어
def forward(self, x):
# 입력 데이터(x)가 모델을 통과하는 순방향 전파 로직을 정의합니다.
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
# Softmax는 보통 손실 함수(CrossEntropyLoss)에 포함되어 있어 생략 가능
return out
# 모델 인스턴스 생성 (예: 붓꽃 데이터의 경우)
# 입력 특징 4개, 은닉층 노드 10개, 출력 클래스 3개
model = SimpleNet(input_size=4, hidden_size=10, num_classes=3)
# 모델 구조 출력
print("\nPyTorch 모델 구조:")
print(model)
# 임의의 입력 데이터로 테스트
dummy_input = torch.randn(1, 4) # (배치 크기, 입력 크기)
output = model(dummy_input)
print(f"\n임의 입력에 대한 모델 출력 (before softmax):\n{output}")
```
### 5.2. TensorFlow 예제
df1 = pd.DataFrame({'이름': ['김파이', '이판다'], '나이': [20, 22]})
df2 = pd.DataFrame({'이름': ['박넘파', '최사이'], '나이': [25, 21]})
#### 텐서 연산과 자동 미분
```python
import tensorflow as tf
# 텐서(Tensor) 생성. tf.Variable은 학습 과정에서 업데이트될 변수를 의미
tensor_a = tf.Variable([[1, 2], [3, 4]], dtype=tf.float32)
tensor_b = tf.constant([[5, 6], [7, 8]], dtype=tf.float32)
with tf.GradientTape() as tape:
# 텐서 연산
tensor_sum = tf.add(tensor_a, tensor_b)
tensor_mul = tf.multiply(tensor_a, tensor_b)
# 행렬 곱
tensor_matmul = tf.matmul(tensor_a, tensor_b)
# 딥러닝 모델의 손실(loss) 계산 과정 예시
loss = tf.reduce_sum(tensor_mul)
# 손실에 대한 tensor_a의 기울기 계산
gradients = tape.gradient(loss, tensor_a)
print(f"TensorFlow 텐서의 합:\n{tensor_sum.numpy()}")
print(f"tensor_a의 기울기(gradient):\n{gradients.numpy()}")
concatenated_df = pd.concat([df1, df2], ignore_index=True) # 기존 인덱스 무시
print("\nConcatenated DataFrame:\n", concatenated_df)
```
#### 간단한 신경망 모델 정의
### 3. 데이터 재구조화: `pivot_table()`
> `pivot_table`은 '일자별, 상품별 판매 기록'이라는 긴 목록을 '일자는 행, 상품은 열, 값은 판매량'인 엑셀 보고서 형태로 바꾸는 것과 같습니다.
```python
import tensorflow as tf
from tensorflow.keras import Model
from tensorflow.keras.layers import Dense, Input
# Keras의 Model 클래스를 상속받아 신경망 모델을 정의합니다.
class SimpleNetTF(Model):
def __init__(self, hidden_size, num_classes):
super(SimpleNetTF, self).__init__()
self.fc1 = Dense(hidden_size, activation='relu') # 완전 연결 레이어
self.fc2 = Dense(num_classes) # 출력 레이어
def call(self, x):
# 입력 데이터(x)가 모델을 통과하는 순방향 전파 로직을 정의합니다.
x = self.fc1(x)
return self.fc2(x)
# 모델 인스턴스 생성 (예: 붓꽃 데이터의 경우)
model_tf = SimpleNetTF(hidden_size=10, num_classes=3)
# Keras는 모델에 첫 입력을 주기 전까지는 가중치(weight)를 생성하지 않습니다.
# 입력 데이터의 형태를 지정하여 모델을 빌드합니다.
model_tf.build(input_shape=(None, 4)) # (배치 크기, 입력 크기)
# 모델 구조 출력
print("\nTensorFlow/Keras 모델 구조:")
model_tf.summary()
# 임의의 입력 데이터로 테스트
dummy_input = tf.random.normal([1, 4])
output = model_tf(dummy_input)
print(f"\n임의 입력에 대한 모델 출력 (before softmax):\n{output.numpy()}")
```
data = {'날짜': ['2023-01-01', '2023-01-01', '2023-01-02', '2023-01-02'],
'상품': ['사과', '바나나', '사과', '바나나'],
'판매량': [100, 80, 120, 90]}
df_sales = pd.DataFrame(data)
### 5.3. TensorFlow 모델 정의 (PyTorch와 유사한 구조)
```python
import tensorflow as tf
# TensorFlow 모델 정의 (PyTorch와 유사한 구조)
model_tf = tf.keras.Sequential([
tf.keras.layers.Dense(10, activation='relu', input_shape=(4,)),
tf.keras.layers.Dense(10, activation='relu'),
tf.keras.layers.Dense(3, activation='softmax')
])
# 모델 컴파일
model_tf.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# 모델 학습
# model_tf.fit(X_train, y_train, epochs=50, batch_size=5, verbose=0)
pivot = df_sales.pivot_table(index='날짜', columns='상품', values='판매량')
print("\nPivot Table:\n", pivot)
```
> 이 파트에서는 TensorFlow와 PyTorch의 기본 개념만 간단히 소개했습니다. 딥러닝은 별도의 심화 과정이 필요할 만큼 방대한 주제이며, Part 8에서 더 자세한 학습 로드맵을 안내합니다.
---
## 6. 미니 프로젝트: 타이타닉 생존자 데이터 분석
지금까지 배운 NumPy, Pandas, Matplotlib, Seaborn 라이브러리를 총동원하여, 데이터 분석의 "Hello, World"와도 같은 캐글(Kaggle)의 타이타닉 데이터셋을 분석하는 미니 프로젝트를 진행해 보겠습니다.
> **데이터셋**: 이 프로젝트는 Seaborn에 내장된 'titanic' 데이터셋을 사용합니다. `sns.load_dataset('titanic')` 코드로 쉽게 불러올 수 있습니다.
### 6.1. 프로젝트 목표
타이타닉호 승객 데이터를 분석하여, **"어떤 승객이 생존할 확률이 높았을까?"** 라는 질문에 대한 답을 데이터 기반으로 찾아보고, 그 과정과 결과를 시각화하여 설득력 있게 제시하는 것을 목표로 합니다.
### 6.2. 분석 과제 (Missions)
아래의 질문들에 대한 답을 찾기 위해, Pandas로 데이터를 탐색하고 Matplotlib/Seaborn으로 결과를 시각화하는 코드를 직접 작성해 보세요.
1. **전체 생존율**: 전체 승객 중 몇 퍼센트가 생존했을까요? (파이 차트)
2. **성별에 따른 생존율**: 남성과 여성 중 어느 쪽의 생존율이 더 높았을까요? (막대 그래프)
3. **객실 등급(Pclass)에 따른 생존율**: 1, 2, 3등석 객실 승객들의 생존율은 어떻게 달랐을까요? (막대 그래프)
4. **나이에 따른 생존율**: 나이 분포와 생존율의 관계는 어떠할까요? (히스토그램, KDE 플롯)
5. **종합 분석**: 위 분석들을 종합해볼 때, 어떤 특징을 가진 승객의 생존율이 가장 높았다고 결론 내릴 수 있을까요? (예: `성별``객실 등급`을 함께 고려한 복합 분석)
### 6.3. 예시 코드 (Missions 1 & 2)
```python
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# Seaborn 스타일 및 한글 폰트 설정
sns.set_theme(style="whitegrid")
try:
plt.rcParams['font.family'] = 'Malgun Gothic'
except RuntimeError:
print("Malgun Gothic 폰트를 찾을 수 없습니다. 기본 폰트를 사용합니다.")
plt.rcParams['axes.unicode_minus'] = False
# 1. 데이터 로드 및 기본 정보 확인
titanic_df = sns.load_dataset('titanic')
print("--- 데이터 샘플 (상위 5개) ---")
print(titanic_df.head())
print("\n--- 데이터 기본 정보 ---")
titanic_df.info()
# 2. 데이터 전처리 (결측치 채우기)
# 'age' 컬럼의 결측치를 전체 승객의 나이 평균으로 채웁니다.
mean_age = titanic_df['age'].mean()
titanic_df['age'].fillna(mean_age, inplace=True)
print(f"\n'age' 컬럼의 결측치를 평균값({mean_age:.1f})으로 채웠습니다.")
# --- Mission 1: 전체 생존율 (파이 차트) ---
survived_counts = titanic_df['survived'].value_counts()
labels = ['사망 (0)', '생존 (1)']
plt.figure(figsize=(8, 8))
plt.pie(survived_counts, labels=labels, autopct='%1.1f%%', startangle=90, colors=['#ff9999','#66b3ff'])
plt.title('타이타닉 전체 생존율', fontsize=16)
plt.ylabel('') # 불필요한 라벨 제거
plt.show()
# --- Mission 2: 성별에 따른 생존율 (막대 그래프) ---
plt.figure(figsize=(10, 6))
sns.barplot(x='sex', y='survived', data=titanic_df, palette='pastel')
plt.title('성별에 따른 생존율', fontsize=16)
plt.xlabel('성별 (sex)', fontsize=12)
plt.ylabel('생존율 (survived)', fontsize=12)
# y축을 퍼센트로 표시
plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: f'{y:.0%}'))
plt.show()
# --- Mission 3, 4, 5는 직접 도전해보세요! ---
# 힌트: Mission 2의 코드를 참고하여 x축을 'pclass', 'age' 등으로 변경하고,
# hue 옵션을 추가하여 복합적인 분석을 수행할 수 있습니다.
# sns.histplot(data=titanic_df, x='age', hue='survived', multiple='stack', bins=30)
# sns.catplot(x='pclass', y='survived', hue='sex', data=titanic_df, kind='bar')
```
이 프로젝트를 통해 여러분은 실제 데이터를 정제하고, 분석 질문을 설정하며, 적절한 시각화를 통해 인사이트를 도출하는 데이터 분석의 전 과정을 경험하게 될 것입니다.
---
### ✅ 정리 및 다음 단계
이번 파트에서는 AI 개발의 실질적인 도구인 핵심 라이브러리들을 익혔습니다.
- **NumPy**: 고성능 수치 계산을 위한 배열 처리
- **Pandas**: 테이블 형태의 데이터를 다루기 위한 데이터프레임
- **Matplotlib/Seaborn**: 데이터의 의미를 파악하기 위한 시각화
- **Scikit-learn**: 전통적인 머신러닝 모델의 학습 및 평가
특히, 우리는 Scikit-learn을 사용하여 **붓꽃 품종을 예측하는 모델을 직접 학습시키고, 그 결과를 `iris_model.pkl` 파일로 저장**하는 중요한 실습을 완료했습니다.
이제 우리는 데이터로부터 지식을 추출하고, 예측 모델을 만들어 파일로 저장할 수 있는 능력을 갖추게 되었습니다. 하지만 이 모델은 아직 개발자의 컴퓨터에만 존재합니다.
### 📚 5주차 마무리 및 다음 단계
이번 주 우리는 데이터 과학의 양대 산맥인 **NumPy****Pandas**의 기본적인 사용법을 마스터했습니다. 이제 여러분은 대부분의 정형 데이터를 자유자재로 불러오고, 원하는 형태로 가공하고, 기본적인 통계를 파악할 수 있는 강력한 무기를 갖추게 되었습니다.
**➡️ 다음 시간: [Part 6: AI 모델 서비스화 기초 (FastAPI)](part_6_model_serving_with_fastapi.md)**
다음 시간에는 이어서 데이터를 시각적으로 탐색하는 **Matplotlib****Seaborn**을 배우고, 드디어 첫 **머신러닝 모델**을 만들어보는 흥미로운 여정을 시작합니다.
다음 시간에는 우리가 만든 `iris_model.pkl` 모델을 실제 세상과 연결하는 다리를 놓습니다. **FastAPI**라는 강력한 웹 프레임워크를 사용하여, 누구나 요청을 보내고 예측 결과를 받아볼 수 있는 **실시간 API 서버**를 구축하는 방법을 배울 것입니다.
\ No newline at end of file
**➡️ 다음 시간: [Part 6: 머신러닝 모델링과 평가](./part_6_machine_learning.md)**
\ No newline at end of file
# Part 6: 머신러닝 모델링과 평가 (6주차)
**⬅️ 이전 시간: [Part 5: AI 핵심 라이브러리](./part_5_ai_core_libraries.md)** | **➡️ 다음 시간: [Part 7: 딥러닝 기초와 PyTorch](./part_7_deep_learning.md)**
---
## 📜 실습 코드 바로가기
- **`part_6_machine_learning.py`**: [바로가기](./source_code/part_6_machine_learning.py)
- 본 파트에서 다루는 Matplotlib, Seaborn, Scikit-learn의 핵심 기능 예제 코드를 직접 실행하고 수정해볼 수 있습니다.
---
### 🎯 6주차 학습 목표
- **[Matplotlib](./glossary.md#matplotlib--seaborn)****[Seaborn](./glossary.md#matplotlib--seaborn)**으로 데이터를 시각화하여 인사이트를 발견합니다.
- **[Scikit-learn](./glossary.md#scikit-learn)을 사용하여 머신러닝의 기본 프로세스(학습, 예측, 평가)를 이해합니다.
- 모델 성능을 높이기 위한 핵심 데이터 전처리 기법을 학습합니다.
---
## 1-2일차(월-화): 데이터 시각화 - 그림으로 데이터 보기
데이터를 숫자로만 보는 것은 마치 숲을 보지 못하고 나무 하나하나만 보는 것과 같습니다. 시각화는 데이터 속에 숨겨진 패턴, 관계, 이상치를 한눈에 파악하게 해주는 강력한 도구입니다.
- **[Matplotlib](./glossary.md#matplotlib--seaborn)**: 파이썬 시각화의 근간. 거의 모든 종류의 그래프를 그릴 수 있는 강력한 라이브러리. (비유: 하얀 도화지)
- **[Seaborn](./glossary.md#matplotlib--seaborn)**: Matplotlib을 더 쉽고 예쁘게 사용할 수 있도록 만든 고수준 라이브러리. 통계적인 그래프 기능이 특화되어 있습니다. (비유: 밑그림과 색칠 도구가 갖춰진 스케치북)
#### 💡 쉽게 이해하기: 어떤 그래프를 언제 쓸까?
> - **선 그래프 (Line Plot)**: '시간'에 따른 주가 변화처럼, 연속적인 데이터의 '추세'를 볼 때
> - **막대 그래프 (Bar Plot)**: 반별 평균 키처럼, 여러 '그룹' 간의 값을 '비교'할 때
> - **산점도 (Scatter Plot)**: 공부 시간과 성적처럼, 두 변수 간의 '관계'나 '분포'를 볼 때
> - **히스토그램 (Histogram)**: 우리 반 학생들의 키 분포처럼, 특정 데이터 구간에 얼마나 많은 값이 있는지 '분포'를 볼 때
> - **히트맵 (Heatmap)**: 여러 변수들 간의 상관관계를 한눈에 '색상'으로 파악할 때
```python
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
# Seaborn에 내장된 'tips' 데이터셋 사용
tips = sns.load_dataset("tips")
# 1. 산점도: 총 식사 금액과 팁 사이의 관계
plt.figure(figsize=(8, 6)) # 그래프 크기 조절
sns.scatterplot(x="total_bill", y="tip", data=tips, hue="smoker", size="size")
plt.title("Total Bill vs Tip Amount")
plt.xlabel("Total Bill ($)")
plt.ylabel("Tip ($)")
plt.grid(True)
# plt.savefig("scatter_plot.png") # 그래프를 파일로 저장
plt.show()
# 2. 막대 그래프: 요일별 평균 팁
plt.figure(figsize=(8, 6))
sns.barplot(x="day", y="tip", data=tips, errorbar=None)
plt.title("Average Tip by Day of the Week")
plt.xlabel("Day")
plt.ylabel("Average Tip ($)")
plt.show()
```
---
## 3-4일차(수-목): 첫 머신러닝 모델 만들기
### 1. 머신러닝이란?
- **[지도학습](./glossary.md#지도학습-supervised-learning)**: **입력(문제)****정답**이 있는 데이터를 사용해, 문제와 정답 사이의 '패턴'을 기계에 학습시키는 방법.
#### 💡 쉽게 이해하기: '훈련'과 '시험'
> 머신러닝 모델 평가는 '공부'와 '시험' 과정과 같습니다.
> - **[훈련 데이터](./glossary.md#훈련-데이터-vs-테스트-데이터) (교과서/모의고사)**: 모델이 학습하는 데 사용하는 데이터입니다.
> - **[테스트 데이터](./glossary.md#훈련-데이터-vs-테스트-데이터) (수능)**: 모델의 최종 성능을 평가하기 위해, 학습 과정에서 **전혀 사용하지 않은** 새로운 데이터입니다.
>
> 훈련 데이터로만 반복해서 시험 보면 100점이 나오겠지만, 진짜 실력이라고 할 수 없겠죠? 그래서 우리는 훈련/테스트 데이터를 엄격히 분리하여 모델의 **일반화 성능**(처음 보는 데이터에 대한 예측 능력)을 측정합니다.
### 2. Scikit-learn 기본 사용법
[Scikit-learn](./glossary.md#scikit-learn)은 파이썬의 대표적인 머신러닝 라이브러리로, 다음과 같은 통일된 인터페이스를 제공합니다.
1. **모델 객체 생성**: `model = ModelName()`
2. **모델 학습**: `model.fit(X_train, y_train)` (훈련 데이터)
3. **예측**: `predictions = model.predict(X_test)` (테스트 데이터)
4. **평가**: `score = accuracy_score(y_test, predictions)`
```python
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
import seaborn as sns
import numpy as np
# 데이터 로드 (붓꽃 데이터)
iris = sns.load_dataset("iris")
X, y = iris.drop("species", axis=1), iris["species"]
# 1. 데이터 분리 (훈련 80%, 테스트 20%)
# stratify=y : 훈련/테스트 데이터의 클래스 비율을 원본과 동일하게 유지
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
# 2. 모델 생성 및 학습
model = DecisionTreeClassifier(random_state=42)
model.fit(X_train, y_train)
# 3. 예측
predictions = model.predict(X_test)
print("예측값:", predictions)
# 4. 평가 (정확도)
accuracy = accuracy_score(y_test, predictions)
print(f"정확도: {accuracy:.4f}")
# 새로운 데이터 예측해보기
# 꽃받침 길이, 너비, 꽃잎 길이, 너비
new_data = np.array([[5.0, 3.5, 1.5, 0.2]]) # 'setosa' 품종으로 예상
new_prediction = model.predict(new_data)
print(f"새로운 데이터 예측 결과: {new_prediction[0]}")
```
---
## 5일차(금): 모델 성능 향상을 위한 데이터 전처리
모델의 성능은 알고리즘만큼이나 **'데이터의 품질'**에 크게 좌우됩니다. "Garbage In, Garbage Out" (쓰레기를 넣으면 쓰레기가 나온다) 라는 말이 있을 정도입니다.
- **결측치 처리**: 비어있는 값을 채우거나(평균, 중앙값 등) 해당 행/열을 제거합니다.
- **[피처 스케일링](./glossary.md#피처-스케일링feature-scaling)**: 변수들의 단위를 맞춰줍니다. (예: 키(cm)와 몸무게(kg) -> 0~1 사이 값으로 표준화)
- **StandardScaler**: 평균 0, 표준편차 1로 변환.
- **MinMaxScaler**: 최솟값 0, 최댓값 1로 변환.
```python
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import numpy as np
data = np.array([[170, 70], [180, 85], [160, 55]], dtype=float)
# StandardScaler
scaler_std = StandardScaler()
scaled_std = scaler_std.fit_transform(data)
print("StandardScaler 결과:\n", scaled_std)
# MinMaxScaler
scaler_minmax = MinMaxScaler()
scaled_minmax = scaler_minmax.fit_transform(data)
print("\nMinMaxScaler 결과:\n", scaled_minmax)
```
### 📚 6주차 마무리 및 다음 단계
이번 주 우리는 데이터를 시각적으로 탐색하고, Scikit-learn을 사용해 머신러닝의 전 과정을 직접 체험했습니다. 이제 여러분은 데이터를 분석하고 예측 모델을 만들 수 있는 기본적인 역량을 갖추었습니다.
다음 시간에는 드디어 **딥러닝**의 세계로 들어갑니다. **PyTorch**를 사용하여 인공 신경망을 직접 만들고, 컴퓨터 비전과 자연어 처리의 기초를 맛보는 흥미로운 여정이 시작됩니다.
**➡️ 다음 시간: [Part 7: 딥러닝 기초와 PyTorch](./part_7_deep_learning.md)**
# Part 6: AI 모델 서비스화 기초 (FastAPI)
---
### 💡 지난 시간 복습
[Part 5: AI 개발 핵심 라이브러리](part_5_ai_core_libraries.md)에서는 NumPy, Pandas, Scikit-learn 등을 사용하여 데이터를 처리하고, 붓꽃 품종 예측 모델을 학습시킨 후 `iris_model.pkl` 파일로 저장했습니다. 이제 우리는 예측 모델이라는 결과물을 손에 쥐게 되었습니다.
---
학습된 AI 모델은 그 자체로는 가치를 창출하기 어렵습니다. API(Application Programming Interface)를 통해 다른 서비스와 연동되고, 실제 사용자의 요청을 처리할 수 있을 때 비로소 가치를 갖게 됩니다. 이 섹션에서는 **Part 5에서 학습하고 저장한 Scikit-learn 모델(`iris_model.pkl`)**을 빠르고 안정적인 API로 만드는 방법을 소개합니다.
이를 위해 **FastAPI**라는 현대적인 고성능 웹 프레임워크를 사용합니다. FastAPI는 Python 3.7+의 타입 힌트(Type Hint)에 기반하여, 코드 작성과 동시에 데이터 유효성 검사, 자동 API 문서 생성을 지원하여 개발 생산성을 획기적으로 높여줍니다.
---
## 1. 왜 FastAPI를 사용하는가?
- **고성능**: 비동기(Asynchronous) 웹 요청을 지원하는 `ASGI`(Asynchronous Server Gateway Interface) 기반으로 만들어져, Node.js나 Go에 버금가는 매우 빠른 성능을 보여줍니다.
- **빠른 개발 속도**: 타입 힌트를 기반으로 코드를 작성하면, FastAPI가 입력 데이터의 유효성을 자동으로 검사해주므로 개발자가 직접 검증 코드를 작성하는 수고를 덜어줍니다.
- **자동 대화형 문서**: 코드만 작성하면 OpenAPI(Swagger UI)와 ReDoc 형태의 대화형 API 문서를 자동으로 생성해줍니다. 이를 통해 프론트엔드 개발자나 API 사용자가 별도의 문서 없이도 API를 쉽게 테스트하고 이해할 수 있습니다.
- **간편함과 표준 준수**: 배우기 쉽고 사용하기 간편하며, OpenAPI, JSON Schema 등 웹 표준을 철저히 준수합니다.
---
## 2. API 서버 구축 단계별 예제
> **사전 준비**: 아래 명령어를 터미널에 입력하여 필요한 라이브러리들을 설치해야 합니다. `uvicorn`은 FastAPI 애플리케이션을 실행시켜주는 경량 ASGI 서버입니다.
> **`Part 5`에서 생성된 `iris_model.pkl` 파일이 지금 만들 `main.py` 파일과 같은 디렉토리에 있어야 합니다.**
```bash
pip install fastapi "uvicorn[standard]" scikit-learn joblib numpy
```
### 2.1. 기본 FastAPI 앱 만들기 (`main.py`)
아래 코드를 `main.py` 라는 이름의 파일로 저장하세요.
```python
# main.py
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel
from typing import List
import joblib
import numpy as np
from contextlib import asynccontextmanager
# -------------------------------------------------------------------
# 1. 모델 로딩 및 리소스 관리를 위한 Lifespan 이벤트 핸들러 정의
# -------------------------------------------------------------------
@asynccontextmanager
async def lifespan(app: FastAPI):
# 애플리케이션 시작 시 실행될 코드
print("--- 서버 시작: 모델을 로드합니다. ---")
try:
# 모델을 로드하여 app.state에 저장
app.state.model = joblib.load("iris_model.pkl")
# 모델이 예측할 클래스(품종)의 실제 이름 정보도 함께 저장
app.state.iris_target_names = {0: 'setosa', 1: 'versicolor', 2: 'virginica'}
print("--- 모델 로드 성공 ---")
except FileNotFoundError:
print("!!! 모델 파일(iris_model.pkl)을 찾을 수 없습니다. !!!")
app.state.model = None
yield # 여기에서 애플리케이션이 실행됨
# 애플리케이션 종료 시 실행될 코드
print("--- 서버 종료: 리소스를 정리합니다. ---")
# 필요한 경우 리소스 정리 로직 추가 (예: DB 연결 해제 등)
app.state.clear()
# 2. FastAPI 애플리케이션 인스턴스 생성 및 Lifespan 연결
app = FastAPI(
title="붓꽃 품종 예측 API",
description="Scikit-learn 모델을 이용한 붓꽃 품종 예측 API입니다.",
lifespan=lifespan
)
# 3. 입력 데이터의 형식을 Pydantic 모델로 정의
class IrisFeatures(BaseModel):
features: List[float]
class Config:
json_schema_extra = {
"example": {
"features": [5.1, 3.5, 1.4, 0.2] # setosa 품종의 실제 데이터
}
}
# 4. API 엔드포인트(Endpoint) 정의
@app.get("/")
def read_root():
"""API의 루트 경로로, 간단한 환영 메시지를 반환합니다."""
return {"message": "붓꽃 품종 예측 API에 오신 것을 환영합니다!"}
@app.post("/predict")
def predict_iris(request: Request, data: IrisFeatures):
"""
붓꽃의 특징(features) 4개를 입력받아 품종을 예측하고 결과를 반환합니다.
- **data**: `IrisFeatures` Pydantic 모델 형식의 요청 Body
"""
# Lifespan에서 로드한 모델 객체를 가져옴
model = request.app.state.model
iris_target_names = request.app.state.iris_target_names
if model is None:
raise HTTPException(status_code=503, detail="모델이 로드되지 않았습니다. 서버 로그를 확인하세요.")
if len(data.features) != 4:
raise HTTPException(status_code=400, detail="입력 피처는 4개여야 합니다. (sepal length, sepal width, petal length, petal width)")
try:
model_input = np.array(data.features).reshape(1, -1)
except Exception as e:
raise HTTPException(status_code=400, detail=f"입력 데이터 변환 중 오류 발생: {e}")
prediction_idx = model.predict(model_input)[0]
prediction_proba = model.predict_proba(model_input)[0]
class_name = iris_target_names.get(int(prediction_idx), "Unknown")
confidence = prediction_proba[int(prediction_idx)]
return {
"input_features": data.features,
"predicted_class_id": int(prediction_idx),
"predicted_class_name": class_name,
"confidence": float(confidence)
}
```
### 2.2. API 서버 실행 및 테스트
1. **서버 실행**:
터미널을 열고 `main.py` 파일이 있는 디렉토리에서 아래 명령어를 실행합니다.
```bash
uvicorn main:app --reload
```
- `main`: `main.py` 파일을 의미합니다.
- `app`: `main.py` 파일 안에서 `app = FastAPI()`로 생성한 FastAPI 인스턴스 객체를 의미합니다.
- `--reload`: 코드가 변경될 때마다 서버를 자동으로 재시작해주는 개발용 옵션입니다.
2. **자동 생성된 API 문서 확인**:
서버가 성공적으로 실행되면 (`Application startup complete.`), 웹 브라우저를 열고 다음 두 주소 중 하나로 접속합니다.
- **Swagger UI**: http://127.0.0.1:8000/docs
- **ReDoc**: http://127.0.0.1:8000/redoc
<p align="center">
<img src="https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png" alt="FastAPI Swagger UI" width="700"/>
</p>
3. **API 테스트**:
- Swagger UI 화면에서 `/predict` 엔드포인트를 클릭하여 펼칩니다.
- 오른쪽 위에 있는 `Try it out` 버튼을 클릭합니다.
- `Request body`에 예시로 있는 JSON 데이터를 그대로 두거나 다른 값으로 수정합니다.
```json
{
"features": [6.7, 3.0, 5.2, 2.3] // virginica 품종의 데이터
}
```
- `Execute` 버튼을 누르면 API 서버로 요청이 전송되고, 그 아래 `Response body`에서 예측 결과를 확인할 수 있습니다.
---
### ✅ 정리 및 다음 단계
이번 파트에서는 **FastAPI**를 사용하여, Part 5에서 만든 `iris_model.pkl` 머신러닝 모델을 **실시간으로 예측 결과를 제공하는 API 서버**로 만드는 과정을 실습했습니다.
- **FastAPI**: 높은 성능과 빠른 개발 속도, 자동 문서 생성 기능을 갖춘 현대적인 웹 프레임워크
- **`lifespan`**: 서버 시작/종료 시 모델 로딩과 같은 초기화/정리 작업을 수행
- **Pydantic**: API의 입출력 데이터 형식을 명확하게 정의하고 자동으로 유효성 검사
- **Uvicorn**: FastAPI 애플리케이션을 실행하는 ASGI 서버
이제 여러분은 학습된 모델을 실제 서비스로 만들 수 있는 첫걸음을 떼었습니다. 하지만 현재의 코드는 모든 로직이 `main.py` 단일 파일에 집중되어 있어, 기능이 복잡해질수록 유지보수가 어려워지는 한계가 있습니다.
**➡️ 다음 시간: [Part 7: 실전형 AI API 서버 구축](part_7_production_ready_api.md)**
다음 시간에는 단일 파일 구조를 벗어나, 실제 프로덕션 환경에서도 통용되는 **체계적인 프로젝트 구조**로 API 서버를 리팩토링합니다. **라우터 분리, 데이터베이스 연동, 설정 관리** 등 더 견고하고 확장 가능한 서버를 만드는 방법을 배우게 될 것입니다.
\ No newline at end of file
# Part 7.5: LLM 애플리케이션 개발과 LangChain
**⬅️ 이전 시간: [Part 7: 딥러닝 기초와 PyTorch](./part_7_deep_learning.md)** | **➡️ 다음 시간: [Part 8: FastAPI를 이용한 모델 서빙](./part_8_model_serving_with_fastapi.md)**
---
## 📜 실습 코드 바로가기
- **`part_7_5_llm_application_development.py`**: [바로가기](./source_code/part_7_5_llm_application_development.py)
- 본 파트에서 다루는 LangChain RAG 파이프라인 예제 코드를 직접 실행하고 수정해볼 수 있습니다.
---
## 1. 거대 언어 모델(LLM)의 발전과 새로운 패러다임
최근 몇 년간 딥러닝 분야, 특히 자연어 처리(NLP) 분야는 [거대 언어 모델(LLM)](./glossary.md#llm-large-language-model)의 등장으로 인해 혁명적인 변화를 겪고 있습니다. GPT-3, LLaMA, PaLM과 같은 모델들은 수십억 개에서 수천억 개의 파라미터를 가지며, 방대한 양의 텍스트 데이터로 사전 훈련(pre-training)되었습니다.
이러한 LLM들은 단순히 텍스트를 생성하는 것을 넘어, 번역, 요약, 질의응답, 코드 생성 등 다양한 작업에서 인간에 가까운 성능을 보여주며, 이는 '파운데이션 모델(Foundation Model)'이라는 새로운 패러다임의 등장을 이끌었습니다. 파운데이션 모델은 특정 작업에 국한되지 않고, 다양한 다운스트림 작업(downstream task)에 약간의 [미세조정(fine-tuning)](./glossary.md#미세조정fine-tuning)이나 [프롬프트 엔지니어링(prompt engineering)](./glossary.md#프롬프트-엔지니어링prompt-engineering)만으로도 적용될 수 있는 범용적인 모델을 의미합니다.
LLM의 등장은 개발자들이 AI 애플리케이션을 구축하는 방식에도 큰 변화를 가져왔습니다. 과거에는 특정 작업을 위해 모델을 처음부터 훈련하거나 복잡한 파이프라인을 구축해야 했다면, 이제는 강력한 LLM을 '두뇌'로 활용하고, 다양한 외부 도구 및 데이터와 연결하여 훨씬 더 복잡하고 지능적인 애플리케이션을 손쉽게 개발할 수 있게 되었습니다.
## 2. LLM 애플리케이션의 핵심, LangChain
LLM을 기반으로 애플리케이션을 개발할 때, 단순히 모델에 질문하고 답변을 받는 것을 넘어, 외부 데이터베이스를 참조하거나, 특정 API를 호출하거나, 여러 단계를 거쳐 복잡한 추론을 수행해야 하는 경우가 많습니다. 이러한 과정은 생각보다 복잡하며, 많은 반복적인 코드를 요구합니다.
**[LangChain](./glossary.md#langchain)**은 바로 이러한 문제점을 해결하기 위해 등장한 프레임워크입니다. LangChain은 LLM을 활용한 애플리케이션 개발을 위한 강력하고 유연한 도구 모음을 제공하며, 개발자가 LLM을 외부 데이터 소스나 계산 로직과 쉽게 '연결(chain)'할 수 있도록 돕습니다.
### LangChain의 핵심 컴포넌트
LangChain은 여러 핵심 컴포넌트들의 조합으로 이루어져 있으며, 이를 통해 모듈식으로 애플리케이션을 구성할 수 있습니다.
- **Models**: 다양한 종류의 언어 모델(LLM, Chat Models, Text Embedding Models 등)을 표준화된 인터페이스로 제공합니다.
- **Prompts**: 사용자 입력과 추가적인 정보를 결합하여 모델에 전달할 프롬프트를 동적으로 생성하고 관리합니다.
- **Chains**: 가장 핵심적인 개념으로, LLM 호출과 다른 컴포넌트(예: 다른 Chain, 외부 API, 데이터 처리 로직 등)들을 순차적으로 연결하여 복잡한 작업을 수행하는 파이프라인을 구성합니다.
- **Indexes & Retrievers**: 외부 데이터 소스를 LLM이 활용할 수 있도록 구조화하고(Indexing), 사용자의 질문과 관련된 정보를 효율적으로 찾아오는(Retrieval) 기능을 담당합니다.
- **Agents**: LLM을 '추론 엔진'으로 사용하여, 어떤 도구(Tool)를 어떤 순서로 사용해야 할지 스스로 결정하고 작업을 수행하도록 만듭니다.
LangChain을 사용하면 개발자는 LLM 자체의 한계를 넘어, 외부 세계의 정보와 상호작용하는 훨씬 더 강력하고 실용적인 애플리케이션을 만들 수 있습니다.
## 2.5. RAG의 핵심 부품: 벡터 데이터베이스 (Vector Database)
RAG 파이프라인을 구축하려면 대량의 텍스트 조각(Chunk)들을 벡터로 변환하고, 사용자 질문 벡터와 가장 유사한 벡터들을 빠르게 찾아내야 합니다. 이 과정을 효율적으로 처리해주는 것이 바로 **[벡터 데이터베이스(Vector Database)](./glossary.md#벡터-데이터베이스vector-database)**입니다.
> **💡 비유: '의미로 책을 찾는 도서관 사서'**
>
> 일반적인 데이터베이스가 '정확한 제목'이나 '저자'로 책을 찾는다면, 벡터 데이터베이스는 "사랑과 희생에 관한 슬픈 이야기"처럼 **추상적인 의미**로 책을 찾아주는 똑똑한 사서와 같습니다.
> - **인덱싱(책 정리)**: 사서는 도서관의 모든 책(Chunk)을 읽고, 각 책의 핵심 내용을 요약한 '의미 태그(Vector)'를 붙여 특수한 서가(DB)에 정리합니다.
> - **검색(책 찾기)**: 사용자가 질문하면, 사서는 질문의 '의미'를 파악하고, 서가에서 가장 비슷한 '의미 태그'를 가진 책들을 순식간에 찾아 건네줍니다.
수백만, 수억 개의 벡터 속에서 가장 가까운 벡터를 찾는 것은 엄청난 계산량을 요구하는 어려운 문제입니다('차원의 저주'). 벡터 데이터베이스는 **ANN(Approximate Nearest Neighbor, 근사 최근접 이웃)** 같은 알고리즘을 사용하여, 100% 정확하지는 않지만 매우 빠른 속도로 충분히 정확한 결과를 찾아줍니다.
RAG에서 주로 사용되는 벡터 데이터베이스는 다음과 같습니다.
- **FAISS (Facebook AI Similarity Search)**
- **특징**: Facebook(Meta)에서 개발한, 매우 빠른 유사도 검색에 특화된 **라이브러리**입니다.
- **장점**: 인메모리(in-memory) 기반으로 작동하여 속도가 매우 빠르고, 별도의 서버 설치 없이 쉽게 사용할 수 있어 프로토타이핑이나 연구에 적합합니다.
- **단점**: 기본적으로 데이터를 메모리에 저장하므로, 프로세스가 종료되면 데이터가 사라집니다. (파일로 저장하고 불러올 수는 있습니다.)
- **Chroma (또는 ChromaDB)**
- **특징**: AI 네이티브(AI-native)를 표방하는 오픈소스 **벡터 데이터베이스**입니다.
- **장점**: 데이터를 디스크에 영속적으로(persistently) 저장하며, 클라이언트-서버 모드로 실행할 수 있어 여러 애플리케이션이 동시에 접근하는 실제 서비스 환경에 더 적합합니다. SDK가 사용하기 쉽게 설계되었습니다.
- **단점**: FAISS보다는 설정이 조금 더 필요할 수 있습니다.
이 외에도 Pinecone, Weaviate, Milvus 등 다양한 상용/오픈소스 벡터 데이터베이스가 있으며, 각기 다른 특징과 장단점을 가지고 있습니다.
## 3. 외부 지식의 통합: 검색 증강 생성 (Retrieval-Augmented Generation, RAG)
LLM은 방대한 지식을 학습했지만, 몇 가지 본질적인 한계를 가집니다.
1. **지식의 최신성 문제(Knowledge Cut-off)**: 모델이 훈련된 특정 시점 이후의 정보는 알지 못합니다.
2. **[환각(Hallucination)](./glossary.md#환각hallucination)**: 사실이 아닌 내용을 그럴듯하게 지어내는 경향이 있습니다.
3. **정보 출처의 부재**: 답변이 어떤 근거로 생성되었는지 알기 어렵습니다.
4. **사적인 데이터 접근 불가**: 기업 내부 문서나 개인적인 파일과 같은 비공개 데이터에 대해서는 알지 못합니다.
**[검색 증강 생성(RAG)](./glossary.md#rag-retrieval-augmented-generation)**는 이러한 한계를 극복하기 위한 매우 효과적인 방법론입니다. RAG의 핵심 아이디어는 LLM이 답변을 생성하기 전에, 먼저 외부 지식 소스(Knowledge Base)에서 사용자의 질문과 관련된 정보를 검색하고, 검색된 정보를 LLM에게 '참고 자료'로 함께 제공하는 것입니다.
### RAG의 작동 방식
RAG 파이프라인은 크게 **Indexing****Retrieval & Generation** 두 단계로 나뉩니다.
1. **Indexing (인덱싱)**
- **Load**: PDF, TXT, HTML 등 다양한 형식의 외부 문서를 불러옵니다.
- **Split (Chunking)**: 불러온 문서를 LLM이 처리하기 좋은 크기의 작은 조각(Chunk)으로 나눕니다. 이 과정이 바로 **'Chunking'**입니다.
- **Embed**: 각 Chunk를 텍스트 임베딩 모델을 사용하여 고차원의 벡터(Vector)로 변환합니다. 이 벡터는 해당 Chunk의 의미적인 내용을 담고 있습니다.
- **Store**: 변환된 벡터와 원본 Chunk 텍스트를 특수한 데이터베이스인 '벡터 저장소(Vector Store)'에 저장합니다.
2. **Retrieval & Generation (검색 및 생성)**
- **Retrieve**: 사용자의 질문(Query) 또한 [임베딩 모델](./glossary.md#임베딩-모델embedding-model)을 통해 벡터로 변환한 뒤, 벡터 저장소에서 이와 유사한(가까운) 벡터를 가진 Chunk들을 찾아냅니다.
- **Generate**: 검색된 Chunk들을 사용자의 원본 질문과 함께 프롬프트에 담아 LLM에 전달합니다. LLM은 이 정보를 바탕으로 신뢰성 높고 정확한 답변을 생성합니다.
## 4. RAG의 핵심, 문서 분할 (Document Splitting / Chunking)
RAG 파이프라인의 성능은 '얼마나 관련성 높은 정보를 잘 찾아오는가'에 크게 좌우되며, 이는 Chunking 전략에 직접적인 영향을 받습니다. 문서를 어떻게 의미 있는 단위로 잘 나누는가(Chunking)가 RAG 시스템 전체의 성공을 결정하는 핵심 요소 중 하나입니다.
### Chunking이 왜 중요한가?
- **컨텍스트 유지**: 너무 작게 자르면 문맥 정보가 손실되고, 너무 크게 자르면 관련 없는 정보가 많이 포함되어 LLM의 집중력을 방해할 수 있습니다.
- **검색 정확도**: Chunk가 의미적으로 완결된 단위일 때, 사용자의 질문과 관련된 Chunk를 더 정확하게 찾아올 수 있습니다.
- **비용 및 성능 효율성**: 대부분의 LLM API는 입력 토큰 길이에 따라 비용이 책정됩니다. 적절한 크기의 Chunk는 불필요한 비용을 줄이고, 모델의 응답 속도를 향상시킵니다.
### 주요 Chunking 전략
다양한 Chunking 전략이 있으며, 문서의 종류와 구조에 따라 적절한 방법을 선택해야 합니다.
1. **고정 크기 분할 (Fixed-size Chunking)**
- 가장 간단한 방식으로, 문서를 정해진 글자 수나 토큰 수로 자릅니다.
- **장점**: 구현이 매우 쉽습니다.
- **단점**: 문장의 중간이나 단어의 중간에서 잘릴 수 있어 의미 단위가 깨질 위험이 큽니다.
- **개선**: Chunk 간에 일부 내용을 겹치게(Overlap) 설정하여, 문맥이 끊어지는 문제를 어느 정도 완화할 수 있습니다. `CharacterTextSplitter`가 대표적입니다.
2. **재귀적 문자 분할 (Recursive Character Text Splitting)**
- 가장 널리 사용되는 방법 중 하나입니다. `["\n\n", "\n", " ", ""]` 와 같이 여러 종류의 구분자(separator)를 순서대로 시도하며 문서를 분할합니다.
- 큰 의미 단위(단락)부터 시작하여, Chunk가 원하는 크기보다 크면 더 작은 의미 단위(문장)으로 나누는 방식으로 재귀적으로 작동합니다.
- **장점**: 고정 크기 분할보다 의미 단위를 더 잘 보존하면서, 원하는 Chunk 크기를 비교적 일정하게 유지할 수 있습니다. `RecursiveCharacterTextSplitter`가 대표적입니다.
3. **구문 기반 분할 (Syntax-aware Chunking)**
- 문서의 구조나 특정 프로그래밍 언어의 구문을 이해하고, 이를 바탕으로 Chunk를 나눕니다.
- 예를 들어, Markdown 문서의 경우 제목(#), 목록(-), 코드 블록(```) 등을 기준으로 분할하거나, Python 코드의 경우 클래스나 함수 단위로 분할할 수 있습니다.
- **장점**: 매우 높은 수준의 의미적 일관성을 유지할 수 있습니다.
- **단점**: 특정 문서 유형이나 언어에 대한 파서(parser)가 필요하며, 구현이 복잡합니다. `MarkdownTextSplitter`, `PythonCodeTextSplitter` 등이 있습니다.
4. **에이전틱 분할 (Agentic Chunking)**
- 최근 제안된 가장 진보된 방식으로, LLM 자체를 사용하여 최적의 Chunk를 결정하는 방법입니다.
- 문서의 각 문장이나 섹션에 대해 "이 내용을 독립적으로 이해할 수 있는가?" 또는 "이웃한 내용과 합쳐져야 더 나은 컨텍스트를 형성하는가?"와 같은 질문을 LLM에게 던져 분할 여부를 결정합니다.
- **장점**: 문서의 내용에 따라 동적으로 최적의 Chunk를 생성하여 매우 높은 품질을 보장합니다.
- **단점**: Chunking 과정에서 LLM API 호출 비용이 발생하며, 처리 속도가 느립니다.
어떤 Chunking 전략이 가장 좋은지는 정답이 없습니다. 문서의 특성(텍스트, 코드, 테이블 등), RAG 시스템의 목적, 그리고 가용한 리소스(비용, 시간)를 종합적으로 고려하여 최적의 전략을 선택하는 것이 중요합니다.
## 5. 실습: LangChain으로 간단한 RAG 파이프라인 만들기
이제 LangChain을 사용하여 간단한 RAG 시스템을 구축하는 예제를 살펴보겠습니다. 우리는 텍스트 파일을 불러와 Chunking하고, 이를 벡터 저장소에 저장한 뒤, 사용자 질문에 가장 관련성 높은 정보를 찾아 답변을 생성하는 과정을 구현할 것입니다.
이번 예제에서는 OpenAI의 모델 대신, **Hugging Face의 오픈소스 모델**을 사용하여 임베딩을 생성하고, **FAISS**와 **Chroma** 벡터 저장소를 모두 활용하는 방법을 보여줍니다.
```python
# 필요한 라이브러리 설치
# !pip install langchain langchain-openai langchain-community faiss-cpu chromadb tiktoken sentence-transformers
import os
from langchain_community.document_loaders import TextLoader
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_openai import ChatOpenAI
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.vectorstores import FAISS, Chroma
from langchain.chains import RetrievalQA
# (주의: 이 코드는 OpenAI LLM을 사용하므로, OPENAI_API_KEY 환경변수 설정이 필요합니다.)
# os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY"
# --- 1. 문서 로드 및 분할 ---
# RAG를 시연하기 위해 방금 생성한 샘플 문서를 로드합니다.
loader = TextLoader("./docker/ai lecture/sample_document.txt", encoding='utf-8')
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
docs = text_splitter.split_documents(documents)
print(f"문서 조각(Chunk) 수: {len(docs)}")
# --- 2. 임베딩 모델 준비 ---
# Hugging Face의 오픈소스 한국어 임베딩 모델을 사용합니다.
# https://huggingface.co/jhgan/ko-sroberta-multitask
model_name = "jhgan/ko-sroberta-multitask"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': True}
hf_embeddings = HuggingFaceEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
# --- 3. 벡터 저장소(Vector Store)에 저장 ---
# 방법 A: FAISS (인메모리, 임시 저장)
# 가장 빠르고 간단하게 시작할 수 있습니다.
db_faiss = FAISS.from_documents(docs, hf_embeddings)
retriever_faiss = db_faiss.as_retriever()
# 방법 B: Chroma (영속적 저장)
# 데이터를 디스크에 저장하여 나중에 다시 불러와 사용할 수 있습니다.
persist_directory = "./chroma_db"
db_chroma = Chroma.from_documents(docs, hf_embeddings, persist_directory=persist_directory)
retriever_chroma = db_chroma.as_retriever()
# --- 4. RAG 체인 생성 및 실행 ---
# LLM은 OpenAI의 gpt-3.5-turbo를 사용합니다.
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
# FAISS를 사용한 RAG
qa_chain_faiss = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever_faiss,
)
# Chroma를 사용한 RAG
qa_chain_chroma = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever_chroma,
)
# --- 5. 질문 및 답변 ---
query = "AI 비전공자를 위한 최고의 입문서는 무엇인가?"
# FAISS 기반 RAG 실행
print("\n--- [FAISS 기반 RAG] ---")
response_faiss = qa_chain_faiss.invoke(query)
print(response_faiss["result"])
# Chroma 기반 RAG 실행
print("\n--- [Chroma 기반 RAG] ---")
response_chroma = qa_chain_chroma.invoke(query)
print(response_chroma["result"])
# Chroma DB 영속성 확인
# 위에서 생성한 Chroma 객체를 메모리에서 삭제했다고 가정
del db_chroma
# 디스크에서 다시 불러오기
db_chroma_loaded = Chroma(persist_directory=persist_directory, embedding_function=hf_embeddings)
retriever_chroma_loaded = db_chroma_loaded.as_retriever()
qa_chain_chroma_loaded = RetrievalQA.from_chain_type(
llm=llm, chain_type="stuff", retriever=retriever_chroma_loaded
)
print("\n--- [Chroma DB 로드 후 RAG] ---")
response_chroma_loaded = qa_chain_chroma_loaded.invoke(query)
print(response_chroma_loaded["result"])
```
이 예제는 RAG의 가장 기본적인 흐름을 보여줍니다. 실제 애플리케이션에서는 문서의 종류에 맞는 `Loader`와 `TextSplitter`를 선택하고, `retriever`의 검색 옵션을 조정하며, `chain_type`을 변경하는 등 다양한 방식으로 파이프라인을 고도화할 수 있습니다.
---
## 6. 또 다른 선택지: LlamaIndex
LangChain이 LLM 애플리케이션을 위한 '범용 스위스 칼'이라면, **LlamaIndex**는 'RAG 전문 수술 도구'에 비유할 수 있습니다. LlamaIndex는 LLM이 외부 데이터를 더 쉽게 활용할 수 있도록, 데이터의 수집(ingestion), 인덱싱(indexing), 검색(retrieval) 과정에 특화된 기능들을 매우 상세하고 강력하게 제공하는 데이터 프레임워크입니다.
### LangChain vs. LlamaIndex
| 구분 | LangChain | LlamaIndex |
|:--- |:--- |:--- |
| **핵심 철학** | LLM을 중심으로 다양한 컴포넌트(Tool, Chain)를 연결하는 범용 프레임워크 | 데이터를 중심으로 LLM과 연결하는 데이터 프레임워크 |
| **주요 강점** | 에이전트(Agent)와 다양한 종류의 체인(Chain)을 이용한 복잡한 워크플로우 생성 | 고성능 RAG를 위한 고급 인덱싱, 데이터 수집, 검색 전략 제공 |
| **주요 사용 사례** | 챗봇, 자율 에이전트, API 연동 등 | 고도화된 RAG, 지식 관리 시스템, 문서 기반 질의응답 |
두 프레임워크는 경쟁 관계라기보다는 상호 보완적이며, 실제로 LlamaIndex의 데이터 인덱싱/검색 기능을 LangChain의 에이전트와 함께 사용하는 경우도 많습니다.
### 실습: LlamaIndex와 오픈소스 모델로 RAG 파이프라인 만들기
이번에는 LlamaIndex를 사용하여 RAG 파이프라인을 구축해 보겠습니다. 임베딩 모델뿐만 아니라 LLM까지 Hugging Face의 무료 추론 API를 사용하여, 완전한 오픈소스 스택으로 RAG를 구현하는 방법을 알아봅니다.
```python
# 필요한 라이브러리 설치
# !pip install llama-index llama-index-embeddings-huggingface llama-index-vector-stores-chroma llama-index-llms-huggingface
import os
import chromadb
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.huggingface import HuggingFaceInferenceAPI
from llama_index.core import Settings
# --- 0. Hugging Face API 토큰 설정 ---
# Hugging Face Hub(hf.co)에서 무료 API 토큰을 발급받아 아래에 입력하거나 환경변수로 설정하세요.
# HF_TOKEN = "YOUR_HUGGINGFACE_API_TOKEN"
# os.environ["HF_TOKEN"] = HF_TOKEN
# --- 1. LLM 및 임베딩 모델 설정 ---
# Settings를 사용하여 전역적으로 모델을 설정할 수 있습니다.
# LLM: Hugging Face의 공개 모델을 Inference API를 통해 사용합니다.
# https://huggingface.co/google/gemma-2-9b-it
Settings.llm = HuggingFaceInferenceAPI(
model_name="google/gemma-2-9b-it",
tokenizer_name="google/gemma-2-9b-it"
)
# Embedding Model: LangChain 예제와 동일한 한국어 모델 사용
Settings.embed_model = HuggingFaceEmbedding(
model_name="jhgan/ko-sroberta-multitask"
)
# --- 2. 데이터 로드 ---
# SimpleDirectoryReader는 디렉토리 내의 모든 문서를 편리하게 불러옵니다.
documents = SimpleDirectoryReader(
input_files=["./docker/ai lecture/sample_document.txt"]
).load_data()
print(f"로드된 문서 수: {len(documents)}")
# --- 3. 인덱싱 및 저장 (ChromaDB 사용) ---
# ChromaDB 클라이언트 초기화 및 컬렉션 생성
db = chromadb.PersistentClient(path="./chroma_db_llamaindex")
chroma_collection = db.get_or_create_collection("rag_tutorial")
# 벡터 저장소 설정
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
# 데이터 인덱싱
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(
documents, storage_context=storage_context
)
# --- 4. 쿼리 엔진 생성 및 질문 ---
# 인덱스로부터 쿼리 엔진을 생성합니다.
query_engine = index.as_query_engine(streaming=True)
query = "AI 비전공자를 위한 최고의 입문서는 무엇인가?"
print(f"\n--- [LlamaIndex + Gemma-2 기반 RAG] ---")
print(f"질문: {query}")
print(f"답변: ", end="")
# 스트리밍 형태로 답변 받기
streaming_response = query_engine.query(query)
streaming_response.print_response_stream()
print()
```
이 코드는 LlamaIndex가 어떻게 데이터 소스, 임베딩 모델, 벡터 저장소, LLM을 유연하게 조합하여 RAG 파이프라인을 구성하는지 보여줍니다. `Settings`를 통해 기본 컴포넌트를 설정하고, `SimpleDirectoryReader`로 데이터를 쉽게 로드하며, `VectorStoreIndex`로 인덱싱과 검색 과정을 아름답게 추상화하는 LlamaIndex의 디자인 철학을 엿볼 수 있습니다.
LangChain, LlamaIndex와 같은 프레임워크와 RAG, Chunking, 벡터 데이터베이스에 대한 이해는 최신 AI 트렌드를 따라가고, LLM의 잠재력을 최대한 활용하는 지능형 애플리케이션을 개발하는 데 있어 필수적인 역량이 될 것입니다.
\ No newline at end of file
# Part 7: 딥러닝 기초와 PyTorch (7주차)
**⬅️ 이전 시간: [Part 6: 머신러닝 모델링과 평가](./part_6_machine_learning.md)** | **➡️ 다음 시간: [Part 7.5: LangChain으로 LLM 애플리케이션 개발 맛보기](./part_7.5_llm_application_development_with_langchain.md)**
---
## 📜 실습 코드 바로가기
- **`part_7_deep_learning.py`**: [바로가기](./source_code/part_7_deep_learning.py)
- 본 파트에서 다루는 PyTorch의 핵심 기능 예제 코드를 직접 실행하고 수정해볼 수 있습니다.
---
### 🎯 7주차 학습 목표
- **[PyTorch](./glossary.md#pytorch)**를 사용하여 간단한 딥러닝 모델을 직접 구현하고 학습시켜 봅니다.
- 인공 신경망의 기본 구성 요소(Linear Layer, Activation Function)를 이해합니다.
- 딥러닝 학습 과정(Forward/Backward Propagation, Optimizer)의 원리를 파악합니다.
---
## 1-3일차(월-수): PyTorch로 구현하는 딥러닝 첫걸음
드디어 딥러닝의 세계에 입문합니다. 우리는 **PyTorch**라는 강력한 딥러닝 프레임워크를 사용하여 인공 신경망을 직접 만들어보겠습니다.
- **[PyTorch](./glossary.md#pytorch)**: 페이스북(메타)에서 개발한 딥러닝 프레임워크. 유연성과 직관적인 코드로 연구자와 개발자들에게 널리 사랑받고 있습니다.
#### 💡 쉽게 이해하기: PyTorch는 '레고 블록', Scikit-learn은 '완성된 장난감'
> - **Scikit-learn**: '트럭', '비행기'처럼 이미 완성된 모델을 가져와 사용하는 '완성품 장난감'과 같습니다. 사용하기 편리하고 대부분의 정형 데이터 문제에 효과적입니다.
> - **PyTorch**: 다양한 모양의 '레고 블록'을 제공하여, 사용자가 원하는 어떤 복잡한 구조(신경망)라도 직접 조립할 수 있게 해줍니다. 이미지, 텍스트 등 비정형 데이터를 다루는 딥러닝에 필수적입니다.
### 1. 딥러닝의 핵심, 텐서(Tensor)
- **텐서**: PyTorch에서 데이터를 다루는 기본 단위. NumPy의 `ndarray`와 매우 유사하지만, GPU를 사용한 가속 연산이 가능하고 자동 미분(Autograd) 기능을 지원한다는 결정적인 차이가 있습니다.
### 2. 인공 신경망(Artificial Neural Network) 모델 만들기
PyTorch에서는 `torch.nn.Module`을 상속받아 우리만의 신경망 클래스를 정의합니다.
```python
import torch
import torch.nn as nn
# 모델 정의
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
# nn.Linear(input_features, output_features)
# 입력 특성 4개(붓꽃 데이터), 출력 클래스 3개(품종)
self.fc1 = nn.Linear(4, 50) # 첫 번째 완전 연결 계층
self.relu = nn.ReLU() # 활성화 함수
self.fc2 = nn.Linear(50, 3) # 두 번째 완전 연결 계층
def forward(self, x):
# 순전파: 데이터가 입력층부터 출력층까지 나아가는 과정
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
model = SimpleNet()
print(model)
```
---
## 4-5일차(목-금): 딥러닝 모델 학습시키기
### 1. 손실 함수(Loss Function)와 옵티마이저(Optimizer)
- **[손실 함수](./glossary.md#손실-함수loss-function)**: 모델의 예측이 실제 정답과 얼마나 다른지를 측정하는 '오차 측정기'. (예: `CrossEntropyLoss` - 분류 문제)
- **[옵티마이저](./glossary.md#옵티마이저optimizer)**: 손실 함수가 계산한 오차를 기반으로, 모델의 파라미터(가중치)를 어느 방향으로 얼마나 업데이트할지 결정하는 '조율사'. (예: `Adam`, `SGD`)
### 2. 학습 루프(Training Loop)
딥러닝 모델 학습은 '에포크(Epoch)'라는 단위로 반복됩니다. 1 에포크는 전체 훈련 데이터를 한 번 모두 사용하는 것을 의미합니다.
```python
# (데이터 로드 및 전처리는 Scikit-learn 예제와 유사하게 진행되었다고 가정)
# X_train, y_train을 PyTorch 텐서로 변환
# X_train_tensor = torch.FloatTensor(X_train.values)
# y_train_tensor = torch.LongTensor(pd.Categorical(y_train).codes)
# criterion = nn.CrossEntropyLoss()
# optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
# epochs = 100
# for epoch in range(epochs):
# # 1. 순전파 (Forward pass)
# outputs = model(X_train_tensor)
# loss = criterion(outputs, y_train_tensor)
# # 2. 역전파 및 가중치 업데이트 (Backward and optimize)
# optimizer.zero_grad() # 이전 그래디언트 초기화
# loss.backward() # 그래디언트 계산
# optimizer.step() # 파라미터 업데이트
# if (epoch+1) % 10 == 0:
# print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
```
위 코드는 개념 설명을 위한 것으로, 실제 실행 가능한 전체 코드는 `source_code/part_7_deep_learning.py`에서 확인해주세요.
### 📚 7주차 마무리 및 다음 단계
이번 주 우리는 PyTorch를 사용하여 딥러닝의 기본 원리를 이해하고, 직접 신경망을 만들어 학습시키는 과정을 경험했습니다.
다음 시간에는 지금까지 배운 머신러닝/딥러닝 지식을 바탕으로, 최근 가장 뜨거운 분야인 **거대 언어 모델(LLM)****LangChain**을 활용하여 간단한 챗봇 애플리케이션을 만들어보는 시간을 갖습니다.
**➡️ 다음 시간: [Part 7.5: LangChain으로 LLM 애플리케이션 개발 맛보기](./part_7.5_llm_application_development_with_langchain.md)**
\ No newline at end of file
# Part 7: 실전형 AI API 서버 구축
---
### 💡 지난 시간 복습
지난 [Part 6: AI 모델 서비스화 기초 (FastAPI)](part_6_model_serving_with_fastapi.md)에서는 FastAPI를 사용하여 `iris_model.pkl` 모델을 간단한 API 서버로 만드는 과정을 실습했습니다. 이를 통해 모델을 외부에 서비스하는 첫걸음을 내디뎠습니다.
---
Part 6에서 만든 단일 파일 API는 개념을 이해하고 빠르게 프로토타입을 만드는 데는 유용했지만, 실제 프로덕션 환경에서 운영하기에는 부족한 점이 많습니다. 기능이 추가될수록 `main.py` 파일은 점점 비대해지고, 데이터베이스 로직, API 경로, 데이터 형식 정의 등이 뒤섞여 유지보수가 어려워집니다.
이 섹션에서는 실제 프로덕션 환경에서 안정적으로 운영될 수 있는 견고하고 확장 가능한 AI 서버를 구축하는 방법을 다룹니다. 이는 우리 회사의 표준 AI 서비스 아키텍처 가이드라인이기도 합니다.
---
## 1. 왜 체계적인 프로젝트 구조가 필요한가?
- **유지보수성**: 기능별로 코드가 분리되어 있으면, 특정 기능을 수정하거나 디버깅할 때 관련 파일만 집중해서 볼 수 있어 작업 효율이 높아집니다.
- **협업 효율성**: 여러 개발자가 프로젝트를 동시에 진행할 때, 각자 맡은 부분(라우터, DB 로직 등)을 독립적으로 개발할 수 있어 충돌을 최소화하고 생산성을 높일 수 있습니다.
- **재사용성**: 데이터베이스 처리 로직(`crud.py`)이나 데이터 유효성 검사 모델(`schemas.py`) 등을 분리해두면, 다른 기능이나 프로젝트에서도 해당 코드를 쉽게 가져다 쓸 수 있습니다.
- **확장성**: 새로운 기능(예: 사용자 관리)을 추가할 때, 기존 코드를 건드리지 않고 새로운 라우터와 관련 파일들을 추가하는 것만으로 쉽게 시스템을 확장할 수 있습니다.
---
## 2. 표준 프로젝트 구조 (Standard Project Structure)
FastAPI 프로젝트는 일반적으로 다음과 같은 기능 중심의 구조를 권장합니다. 아래 구조에 따라 예제 코드를 하나씩 만들어 보겠습니다.
```
/project
├── app/
│ ├── __init__.py
│ ├── main.py # ✅ FastAPI 앱 생성 및 전역 설정, 라우터 통합
│ ├── database.py # ✅ 데이터베이스 연결 설정 (SQLAlchemy)
│ ├── models.py # ✅ DB 테이블 모델 정의 (SQLAlchemy)
│ ├── schemas.py # ✅ 데이터 유효성 검사 모델 정의 (Pydantic)
│ ├── crud.py # ✅ 데이터베이스 처리 로직 (CRUD: Create, Read, Update, Delete)
│ ├── settings.py # ✅ 환경변수 및 설정 관리 (Pydantic-Settings)
│ └── routers/
│ ├── __init__.py
│ └── predictions.py # ✅ '예측' 기능 관련 API 라우터
├── requirements.txt # ✅ 프로젝트 의존성 목록
└── iris_model.pkl # Part 5에서 생성한 모델 파일
```
아래 다이어그램은 위 구조에서 `POST /predictions/` 와 같은 API 요청이 들어왔을 때, 각 파일과 모듈이 어떻게 상호작용하는지 보여주는 흐름도입니다.
```mermaid
sequenceDiagram
participant Client as 👨‍💻 클라이언트
participant Main as FastAPI (main.py)
participant Router as ➡️ 라우터 (predictions.py)
participant Model as 🤖 AI 모델 (*.pkl)
participant CRUD as 📝 CRUD (crud.py)
participant DB as 🗄️ 데이터베이스 (models.py)
Client->>+Main: 1. POST /predictions/ 요청
Main->>+Router: 2. 요청 라우팅
Router->>+Model: 3. model.predict(입력 데이터)
Model-->>-Router: 4. 예측 결과 반환
Router->>+CRUD: 5. crud.create_prediction_log(결과)
CRUD->>+DB: 6. 데이터베이스에 로그 저장 (INSERT)
DB-->>-CRUD: 7. 저장된 객체 반환
CRUD-->>-Router: 8. 생성된 로그 객체 반환
Router-->>-Main: 9. 최종 응답 생성 (Pydantic 스키마)
Main-->>-Client: 10. 200 OK 응답 (JSON)
```
이처럼 각 컴포넌트가 명확한 책임을 가지고 분리되어 있어, 코드를 이해하고 유지보수하기가 훨씬 용이해집니다.
### 2.0. Git 버전 관리 설정 (`.gitignore`)
프로젝트를 Git으로 관리할 때는, 모든 파일을 버전 관리 시스템에 포함시킬 필요가 없습니다. 가상환경 폴더(`.venv`), 파이썬 캐시 파일(`__pycache__/`), 그리고 특히 용량이 큰 데이터 파일이나 모델 파일 (`*.pkl`, `*.csv`) 등은 제외하는 것이 좋습니다. 이를 위해 프로젝트 최상위 디렉토리에 `.gitignore` 파일을 생성하고 아래와 같이 작성합니다.
```gitignore
# .gitignore
# Python
__pycache__/
*.pyc
.venv/
/venv/
env/
# Data and Models
*.csv
*.pkl
*.joblib
data/
models/
# IDE/Editor specific
.vscode/
.idea/
```
> 💡 **대용량 파일은 어떻게 관리하나요?**
>
> Git은 소스 코드의 '변경 이력'을 관리하는 데 최적화되어 있어, 용량이 큰 데이터셋이나 AI 모델 파일을 직접 저장하면 리포지토리 크기가 급격히 커지고 성능이 저하됩니다. 이런 파일들은 **DVC (Data Version Control)** 와 같은 도구를 사용하여 Git과 별도로 버전을 관리하고, 실제 파일은 클라우드 스토리지(S3, Google Cloud Storage 등)에 저장하는 것이 표준적인 MLOps 방식입니다. (Part 8에서 자세히 다룹니다)
### 2.1. 의존성 설치 (`requirements.txt`)
프로젝트에 필요한 라이브러리 목록입니다. `SQLAlchemy`는 데이터베이스와 상호작용하기 위한 ORM(Object-Relational Mapping) 라이브러리입니다.
```text
# requirements.txt
fastapi
uvicorn[standard]
scikit-learn
joblib
numpy
SQLAlchemy
pydantic-settings
```
> 터미널에서 `pip install -r requirements.txt` 명령으로 한 번에 설치할 수 있습니다.
> 💡 **Tip: 재현 가능한 환경을 위한 의존성 버전 명시**
> `requirements.txt` 파일을 작성할 때는 라이브러리 이름만 적는 것보다, `fastapi==0.104.1` 처럼 `==`을 사용하여 정확한 버전을 명시하는 것이 매우 중요합니다.
>
> - **왜 중요한가요?**: 라이브러리는 계속 업데이트되며, 새로운 버전에서 기능이 변경되거나 삭제될 수 있습니다. 버전을 명시하지 않으면, 나중에 다른 환경에서 `pip install`을 실행했을 때 최신 버전이 설치되어 코드가 예기치 않게 오작동할 수 있습니다.
> - **어떻게 버전을 확인하나요?**: 현재 가상환경에 설치된 라이브러리와 그 버전 목록은 `pip freeze` 명령으로 확인할 수 있습니다. 이 결과를 복사하여 `requirements.txt` 파일에 붙여넣으면 됩니다.
> ```bash
> # 현재 환경의 패키지 목록과 버전을 확인
> pip freeze > requirements.txt
> ```
> 이렇게 버전을 고정하면, 시간이 지나거나 다른 동료가 프로젝트를 설치하더라도 항상 동일한 환경에서 코드를 실행할 수 있어 **재현 가능성**이 보장됩니다.
### 2.2. 환경변수 기반 설정 관리 (`app/settings.py`)
소스 코드에 DB 접속 정보 같은 민감한 데이터를 하드코딩하는 대신, 환경 변수로부터 설정을 읽어오도록 관리합니다. `pydantic-settings` 라이브러리를 사용하면 간단하게 구현할 수 있습니다.
```python
# app/settings.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
# .env 파일 또는 실제 환경변수에서 아래 변수들을 읽어옵니다.
# 만약 환경변수가 설정되어 있지 않으면, 오른쪽에 명시된 기본값이 사용됩니다.
DATABASE_URL: str = "sqlite:///./test.db"
class Config:
# .env 파일을 읽도록 설정 (프로젝트 루트에 .env 파일 생성 가능)
env_file = ".env"
# 설정 객체 인스턴스화
settings = Settings()
```
### 2.3. 데이터베이스 설정 (`app/database.py`)
데이터베이스 연결을 설정하고, DB 세션을 관리하는 코드를 정의합니다. 여기서는 간단하게 파일 기반 데이터베이스인 `SQLite`를 사용합니다.
```python
# app/database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from .settings import settings # 설정 파일 임포트
# 설정 파일(settings.py)에서 데이터베이스 URL을 가져옴
SQLALCHEMY_DATABASE_URL = settings.DATABASE_URL
# 데이터베이스 엔진 생성
# connect_args는 SQLite를 사용할 때만 필요한 옵션입니다.
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
# 데이터베이스 세션 생성을 위한 클래스
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# DB 모델(테이블)을 정의할 때 사용할 베이스 클래스
Base = declarative_base()
```
### 2.4. DB 테이블 모델 정의 (`app/models.py`)
`SQLAlchemy`를 사용하여 데이터베이스 테이블을 파이썬 클래스로 정의합니다. 이 클래스는 실제 DB 테이블의 구조(컬럼, 타입 등)를 나타냅니다.
```python
# app/models.py
from sqlalchemy import Column, Integer, String, DateTime
from .database import Base
import datetime
class PredictionLog(Base):
__tablename__ = "prediction_logs" # DB에 생성될 테이블 이름
id = Column(Integer, primary_key=True, index=True) # 자동 증가하는 기본 키
input_features = Column(String) # 입력된 특징 값 (JSON 문자열로 저장)
predicted_class_name = Column(String, index=True) # 예측된 품종 이름
created_at = Column(DateTime, default=datetime.datetime.utcnow) # 생성 시각
```
### 2.5. API 데이터 형식 정의 (`app/schemas.py`)
`Pydantic` 모델을 사용하여 API의 요청(Request)과 응답(Response) 데이터의 형식을 명확하게 정의하고, 자동으로 유효성을 검사합니다. `models.py`와 분리하여 DB 모델과 API 모델의 역할을 명확히 나눕니다.
```python
# app/schemas.py
from pydantic import BaseModel
from typing import List
import datetime
# --- 요청(Request) 스키마 ---
# POST /predictions/ 요청의 Body에 포함될 데이터 형식
class PredictionCreate(BaseModel):
features: List[float]
# --- 응답(Response) 스키마 ---
# GET /predictions/{id} 또는 POST /predictions/ 응답으로 반환될 데이터 형식
class PredictionResponse(BaseModel):
id: int
input_features: str
predicted_class_name: str
created_at: datetime.datetime
class Config:
orm_mode = True # SQLAlchemy 모델 객체를 Pydantic 모델로 자동 변환 허용
```
### 2.6. 데이터베이스 처리 함수 (`app/crud.py`)
실제 데이터베이스와 상호작용하는 로직(생성, 조회, 수정, 삭제)을 함수 단위로 분리하여 재사용성을 높입니다.
```python
# app/crud.py
from sqlalchemy.orm import Session
from . import models, schemas
import json
def get_prediction_log(db: Session, log_id: int):
"""ID로 특정 예측 로그를 조회합니다."""
return db.query(models.PredictionLog).filter(models.PredictionLog.id == log_id).first()
def get_all_prediction_logs(db: Session, skip: int = 0, limit: int = 100):
"""모든 예측 로그를 조회합니다 (페이지네이션 적용)."""
return db.query(models.PredictionLog).offset(skip).limit(limit).all()
def create_prediction_log(db: Session, request: schemas.PredictionCreate, predicted_class_name: str):
"""새로운 예측 로그를 DB에 저장합니다."""
# 리스트를 JSON 형태의 문자열로 변환하여 저장
features_str = json.dumps(request.features)
db_log = models.PredictionLog(
input_features=features_str,
predicted_class_name=predicted_class_name
)
db.add(db_log)
db.commit()
db.refresh(db_log) # DB에 저장된 내용을 다시 읽어와서 파이썬 객체에 반영
return db_log
```
### 2.7. API 라우터 분리 (`app/routers/predictions.py`)
`APIRouter`를 사용하면 기능별로 API 코드를 독립된 파일로 관리할 수 있어 `main.py`가 비대해지는 것을 방지합니다.
```python
# app/routers/predictions.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from .. import crud, schemas, database, models
import joblib
import numpy as np
# APIRouter 인스턴스 생성
router = APIRouter(
prefix="/predictions", # 이 라우터의 모든 경로는 /predictions 로 시작
tags=["Predictions"] # API 문서에서 "Predictions" 그룹으로 묶음
)
# --- 의존성 주입 (Dependency Injection) ---
# DB 세션을 가져오는 함수 (요청이 들어올 때마다 세션을 생성하고, 끝나면 닫음)
def get_db():
db = database.SessionLocal()
try:
yield db
finally:
db.close()
# 모델을 로드하는 함수 (간단한 예제, 실제로는 캐싱 전략 필요)
def get_model():
model = joblib.load("iris_model.pkl")
return model
# --- 왜 전역 변수 대신 Depends를 사용할까? ---
# 전역 변수로 DB 세션이나 모델을 관리할 수도 있지만, Depends를 사용하면 다음과 같은 강력한 이점을 얻습니다.
# 1. 테스트 용이성: 테스트 코드에서 `app.dependency_overrides`를 사용해 `get_db`나 `get_model`을
# 가짜(mock) 함수로 쉽게 교체할 수 있습니다. 예를 들어, 실제 DB에 접속하는 대신 테스트용 인메모리 DB를 사용하도록
# 만들어 단위 테스트를 격리된 환경에서 수행할 수 있습니다.
# 2. 명시적인 의존성: 각 API 엔드포인트가 어떤 의존성을 필요로 하는지 함수 시그니처에 명확하게 드러나므로
# 코드의 가독성과 유지보수성이 향상됩니다.
# 3. 재사용성: `get_db`와 같은 의존성 주입 함수는 다른 라우터에서도 그대로 재사용할 수 있습니다.
# --- ---
> 🚫 **Common Pitfall: 뚱뚱한 라우터 (Fat Router)**
> `predictions.py` `create_prediction_and_log` 함수를 보면, 모델 예측 로직(`np.array`, `model.predict` ) 라우터 함수 내부에 직접 포함되어 있습니다. 간단한 경우에는 괜찮지만, 비즈니스 로직이 복잡해질수록 라우터 파일이 점점 비대해지고(뚱뚱해지고), 테스트하기 어려워지며, 재사용성이 떨어지는 문제가 발생합니다.
>
> - **문제점**:
> - **단일 책임 원칙(SRP) 위배**: 라우터는 '요청을 받고 응답을 반환하는' 역할에 집중해야 하는데, 비즈니스 로직까지 처리하게 됩니다.
> - **테스트의 어려움**: 비즈니스 로직만 따로 테스트하고 싶은데, FastAPI의 `Depends` HTTP 요청/응답 구조에 묶여 있어 순수한 단위 테스트가 어렵습니다.
> - **재사용성 저하**: 다른 API나 백그라운드 작업에서 동일한 예측 로직을 사용하고 싶을 , 코드를 복사-붙여넣기 해야 합니다.
> - **해결책: 서비스 계층(Service Layer) 분리**
> - `app/services.py` 같은 파일을 만들고, 실제 비즈니스 로직(모델 예측, 데이터 가공 ) 별도의 함수나 클래스로 분리합니다.
> - 라우터는 서비스 함수를 호출하여 결과를 받아오기만 하는, 가볍고(Thin) 깔끔한 상태를 유지합니다.
> - 이렇게 하면 서비스 로직을 API와 독립적으로 테스트하고 재사용할 있게 됩니다. (Part 8에서 자세히 다룹니다.)
@router.post("/", response_model=schemas.PredictionResponse)
def create_prediction_and_log(
request: schemas.PredictionCreate,
db: Session = Depends(get_db),
model = Depends(get_model)
):
"""
새로운 붓꽃 품종 예측을 수행하고, 그 결과를 데이터베이스에 기록합니다.
"""
# 1. 모델 예측 로직 수행
if len(request.features) != 4:
raise HTTPException(status_code=400, detail="입력 피처는 4개여야 합니다.")
model_input = np.array(request.features).reshape(1, -1)
prediction_idx = model.predict(model_input)[0]
iris_target_names = {0: 'setosa', 1: 'versicolor', 2: 'virginica'}
class_name = iris_target_names.get(int(prediction_idx), "Unknown")
# 2. CRUD 함수를 이용해 결과 DB에 저장
return crud.create_prediction_log(db=db, request=request, predicted_class_name=class_name)
@router.get("/", response_model=List[schemas.PredictionResponse])
def read_all_prediction_logs(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
"""
저장된 모든 예측 로그를 조회합니다.
"""
logs = crud.get_all_prediction_logs(db, skip=skip, limit=limit)
return logs
@router.post("/dummy-io-task", summary="Async I/O Task Example")
async def dummy_io_task():
"""
실제 I/O 작업(예: 외부 API 호출, DB 쿼리)을 시뮬레이션하는 비동기 엔드포인트입니다.
`await asyncio.sleep(1)`은 I/O 작업이 1초간 진행되는 동안 서버가 다른 요청을 처리할 수 있도록 CPU를 반환합니다.
이를 통해 API 서버의 동시 처리 성능이 향상됩니다.
"""
import asyncio
await asyncio.sleep(1) # 1초간 비동기 대기
return {"message": "Dummy I/O task completed after 1 second"}
```
### 2.8. 메인 앱에 라우터 통합 (`app/main.py`)
마지막으로, 생성한 라우터를 메인 FastAPI 앱에 포함시키고, 애플리케이션 시작 시 DB 테이블을 생성하도록 설정합니다.
```python
# app/main.py
from fastapi import FastAPI
from .database import engine
from . import models
from .routers import predictions
# 애플리케이션 시작 시 DB에 필요한 테이블들을 생성
models.Base.metadata.create_all(bind=engine)
app = FastAPI(
title="Production-Ready AI API",
description="붓꽃 예측 모델을 서빙하고, 예측 결과를 데이터베이스에 기록하는 실전형 API입니다.",
version="1.0.0"
)
# predictions 라우터를 메인 앱에 포함
app.include_router(predictions.router)
@app.get("/", tags=["Root"])
def root():
return {"message": "Production-Ready AI API에 오신 것을 환영합니다!"}
```
### 2.9. 실행
이제 프로젝트의 최상위 디렉토리( `app` 폴더가 있는 곳)에서 아래 명령어로 서버를 실행합니다.
```bash
uvicorn app.main:app --reload
```
웹 브라우저에서 http://127.0.0.1:8000/docs 로 접속하면, 예측을 수행하고 DB에 로그를 남기는(`POST /predictions/`) API와 저장된 모든 로그를 조회하는 (`GET /predictions/`) API가 추가된 것을 확인할 수 있습니다.
---
## 3. 주요 프로젝트 확장: 주택 가격 예측 API
지금까지 만든 붓꽃 **분류(Classification)** API를 확장하여, 이번에는 주택 가격을 예측하는 **회귀(Regression)** API를 추가해 보겠습니다. 이를 통해 우리 API 서버가 여러 종류의 모델을 동시에 서비스할 수 있도록 구조를 확장하는 방법을 배웁니다.
### 3.1. 회귀(Regression) 모델 준비
먼저, 주택 가격 예측을 위한 회귀 모델이 필요합니다. 여기서는 Scikit-learn의 캘리포니아 주택 가격 데이터셋을 사용합니다.
> 💡 **보스턴 주택 가격 데이터셋에 관하여**
> 이전에는 보스턴 주택 가격 데이터셋이 많이 사용되었으나, 데이터에 인종 차별적인 편향이 포함되어 있어 Scikit-learn 1.2 버전부터는 제거되었습니다. 우리는 대체재로 캘리포니아 주택 가격 데이터셋을 사용합니다.
아래 파이썬 스크립트를 `create_california_housing_model.py`와 같은 파일로 저장하고 실행하면, 데이터셋을 내려받아 `RandomForestRegressor` 모델을 훈련시킨 후 `california_housing_model.pkl` 파일로 저장해 줍니다.
```python
# create_california_housing_model.py
import pandas as pd
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
import joblib
def create_model():
"""
캘리포니아 주택 가격 데이터셋을 로드하고,
RandomForest 회귀 모델을 훈련시킨 후 'california_housing_model.pkl' 파일로 저장합니다.
"""
# 1. 데이터 로드
housing = fetch_california_housing()
X = pd.DataFrame(housing.data, columns=housing.feature_names)
y = pd.Series(housing.target, name='MedHouseVal')
# 2. 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 3. 모델 훈련
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
print(f"모델 훈련 완료. 테스트 점수: {model.score(X_test, y_test):.4f}")
# 4. 모델 저장
joblib.dump(model, 'california_housing_model.pkl')
print("모델이 'california_housing_model.pkl' 파일로 저장되었습니다.")
if __name__ == "__main__":
create_model()
```
이 스크립트를 실행하여 `california_housing_model.pkl` 파일을 프로젝트 루트에 준비합니다.
### 3.2. 확장된 프로젝트 구조
새로운 회귀 모델 API를 추가하면 프로젝트 구조는 다음과 같이 확장됩니다.
```
/project
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── database.py
│ ├── models.py # ✅ RegressionLog 테이블 모델 추가
│ ├── schemas.py # ✅ 주택 가격 예측용 스키마 추가
│ ├── crud.py # ✅ 주택 가격 예측 로그 CRUD 함수 추가
│ ├── settings.py
│ └── routers/
│ ├── __init__.py
│ ├── predictions.py
│ └── housing.py # ✅ 새로 추가된 주택 가격 예측 라우터
├── requirements.txt
├── iris_model.pkl
└── california_housing_model.pkl # ✅ 새로 추가된 회귀 모델
```
### 3.3. DB 모델 및 스키마 확장
회귀 예측 결과(숫자 값)를 저장하기 위해 새로운 DB 테이블 모델과 API 스키마를 추가합니다.
#### `app/models.py` 수정
`RegressionLog` 테이블 모델을 추가합니다. 분류 모델의 `predicted_class_name` 대신 `predicted_value` (Float)를 저장합니다.
```python
// ... existing code ...
import datetime
from sqlalchemy import Column, Integer, String, DateTime, Float # Float 추가
class PredictionLog(Base):
// ... existing code ...
predicted_class_name = Column(String, index=True) # 예측된 품종 이름
created_at = Column(DateTime, default=datetime.datetime.utcnow) # 생성 시각
# 새로 추가
class RegressionLog(Base):
__tablename__ = "regression_logs"
id = Column(Integer, primary_key=True, index=True)
input_features = Column(String)
predicted_value = Column(Float) # 회귀 예측 결과 (숫자)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
```
#### `app/schemas.py` 수정
주택 가격 예측 API를 위한 요청/응답 스키마를 추가합니다.
```python
// ... existing code ...
class PredictionResponse(BaseModel):
// ... existing code ...
class Config:
orm_mode = True # SQLAlchemy 모델 객체를 Pydantic 모델로 자동 변환 허용
# --- 주택 가격 예측용 스키마 (새로 추가) ---
class HousingFeatures(BaseModel):
features: List[float] # 캘리포니아 주택 데이터셋은 8개의 피처를 가집니다.
class RegressionResponse(BaseModel):
id: int
input_features: str
predicted_value: float
created_at: datetime.datetime
class Config:
orm_mode = True
```
### 3.4. CRUD 로직 추가 (`app/crud.py`)
새로 정의한 `RegressionLog` 모델을 다루는 CRUD 함수를 `app/crud.py`에 추가합니다.
```python
// ... existing code ...
def create_prediction_log(db: Session, request: schemas.PredictionCreate, predicted_class_name: str):
// ... existing code ...
db.refresh(db_log) # DB에 저장된 내용을 다시 읽어와서 파이썬 객체에 반영
return db_log
# 새로 추가
def create_regression_log(db: Session, request: schemas.HousingFeatures, predicted_value: float):
"""새로운 회귀 예측 로그를 DB에 저장합니다."""
features_str = json.dumps(request.features)
db_log = models.RegressionLog(
input_features=features_str,
predicted_value=predicted_value
)
db.add(db_log)
db.commit()
db.refresh(db_log)
return db_log
```
### 3.5. 신규 API 라우터 작성 (`app/routers/housing.py`)
`app/routers/` 디렉토리 안에 `housing.py` 파일을 새로 생성하고, 회귀 모델을 로드하여 예측을 수행하는 API 엔드포인트를 작성합니다.
```python
# app/routers/housing.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from .. import crud, schemas, database, models
import joblib
import numpy as np
router = APIRouter(
prefix="/housing",
tags=["Housing Predictions"]
)
# 의존성 주입: DB 세션
def get_db():
db = database.SessionLocal()
try:
yield db
finally:
db.close()
# 의존성 주입: 회귀 모델 로드
def get_housing_model():
model = joblib.load("california_housing_model.pkl")
return model
@router.post("/predict", response_model=schemas.RegressionResponse)
def create_housing_prediction(
request: schemas.HousingFeatures,
db: Session = Depends(get_db),
model = Depends(get_housing_model)
):
"""
새로운 주택 가격 예측을 수행하고, 그 결과를 데이터베이스에 기록합니다.
"""
# 캘리포니아 주택 데이터셋의 피처 개수는 8개입니다.
if len(request.features) != 8:
raise HTTPException(status_code=400, detail="입력 피처는 8개여야 합니다.")
model_input = np.array(request.features).reshape(1, -1)
predicted_value = model.predict(model_input)[0]
# CRUD 함수를 이용해 결과 DB에 저장
return crud.create_regression_log(db=db, request=request, predicted_value=float(predicted_value))
```
### 3.6. 메인 앱에 라우터 통합 (`app/main.py`)
마지막으로 `app/main.py`를 수정하여 새로 만든 `housing` 라우터를 앱에 포함시킵니다.
```python
# app/main.py
from fastapi import FastAPI
from .database import engine
from . import models
from .routers import predictions, housing # housing 라우터 임포트
# 애플리케이션 시작 시 DB에 필요한 테이블들을 생성
models.Base.metadata.create_all(bind=engine)
app = FastAPI(
title="Production-Ready AI API",
description="붓꽃 분류 및 주택 가격 예측 모델을 서빙하는 API입니다.", # 설명 업데이트
version="1.1.0" # 버전 업데이트
)
# 각 라우터를 메인 앱에 포함
app.include_router(predictions.router)
app.include_router(housing.router) # housing 라우터 추가
@app.get("/", tags=["Root"])
def root():
return {"message": "Production-Ready AI API에 오신 것을 환영합니다!"}
```
이제 서버를 다시 실행(`uvicorn app.main:app --reload`)하고 http://127.0.0.1:8000/docs 에 접속하면, 기존의 `/predictions` API와 더불어 새로운 `/housing` API가 추가된 것을 확인할 수 있습니다.
---
## 4. GitHub 스타터 키트
복잡한 프로젝트 구조를 처음부터 따라 만드는 것은 어려울 수 있습니다. 여러분의 학습을 돕기 위해, Part 7의 완성된 코드를 포함하는 GitHub 리포지토리를 스타터 키트로 제공합니다.
> 🔗 **Part 7 스타터 키트 리포지토리**: [여기에 GitHub 리포지토리 링크가 제공될 예정입니다.]
이 리포지토리를 `git clone`하여 바로 코드를 실행해보고, 구조를 분석하며 직접 기능을 수정해보세요.
---
### ✅ 정리 및 다음 단계
이번 파트에서는 단일 파일로 구성되었던 간단한 API 서버를, 실제 프로덕션 환경에서도 통용될 수 있는 **체계적인 프로젝트 구조**로 리팩토링했습니다.
- **관심사 분리**: 라우터, DB 모델, 스키마, CRUD 로직을 각각의 파일로 분리하여 유지보수성과 확장성을 높였습니다.
- **설정 관리**: 민감한 정보를 환경 변수로 관리하여 보안을 강화했습니다.
- **데이터베이스 연동**: 예측 결과를 `SQLite` 데이터베이스에 기록하고 조회하는 기능을 추가했습니다.
이제 우리는 견고하고 확장 가능한 AI API 서버를 구축할 수 있는 역량을 갖추게 되었습니다.
**➡️ 다음 시간: [Part 8: AI 전문가로의 성장 (심화 과정)](part_8_expert_path.md)**
지금까지 AI 서비스 개발의 '기초'를 탄탄히 다졌습니다. 마지막 파트에서는 이 기반 위에서 한 단계 더 도약하여, **재현 가능하고 신뢰할 수 있는 AI 시스템**을 구축하는 전문가로 성장하기 위한 로드맵을 제시합니다. **Docker**를 이용한 컨테이너화, **CI/CD**를 통한 배포 자동화, 그리고 **MLOps**와 같은 고급 주제들을 통해 진정한 AI 시스템 아키텍트로 나아가는 길을 함께 살펴보겠습니다.
\ No newline at end of file
# Part 8: AI 전문가로의 성장 (심화 과정)
---
### 💡 지난 시간 복습
지난 [Part 7: 실전형 AI API 서버 구축](part_7_production_ready_api.md)에서는 단일 파일 API를 기능별로 모듈화하고, 데이터베이스와 연동하여 예측 로그를 기록하는 등 실제 운영 환경에 가까운 서버로 리팩토링했습니다. 이를 통해 확장과 유지가 용이한 코드 구조의 중요성을 학습했습니다.
---
AI 서비스 개발의 기초를 성공적으로 다졌다면, 이제 한 단계 더 나아가 재현 가능하고(reproducible), 확장 가능하며(scalable), 신뢰할 수 있는(reliable) 시스템을 구축하는 진정한 AI 전문가로 성장할 차례입니다. 이 단계에서는 개발(Dev)과 운영(Ops)을 통합하는 MLOps, 대규모 데이터 처리, 그리고 클라우드 네이티브 기술과 같은 고급 주제들을 학습합니다.
이러한 기술들은 AI 서비스를 단순한 '프로토타입'에서 수백만 명의 사용자를 감당할 수 있는 '프로덕션 시스템'으로 격상시키는 핵심 요소입니다.
---
## 1. 컨테이너화와 배포 자동화 (Docker & CI/CD)
"제 PC에서는 잘 됐는데, 서버에서는 왜 안 되죠?" 라는 고질적인 문제를 원천적으로 해결하고, 어떤 환경에서든 AI 서비스를 동일하게 배포하고 실행하는 표준 기술을 익힙니다.
### 1.1. Docker: 애플리케이션 격리 및 패키징
- **핵심 개념**: Docker는 애플리케이션, 그 의존성(라이브러리 등), 그리고 실행 환경 자체를 '컨테이너(Container)'라는 격리된 공간에 패키징하는 기술입니다. 이 컨테이너는 어디서든 동일하게 실행되므로, 개발 환경과 운영 환경의 차이로 인한 문제를 근본적으로 해결합니다.
- **Dockerfile**: 컨테이너 이미지를 만들기 위한 '레시피' 또는 '설명서'입니다. 베이스 이미지 선택, 필요한 파일 복사, 의존성 설치, 실행 명령어 정의 등의 절차를 코드로 명시합니다.
#### 예제: Part 7의 FastAPI 앱을 위한 Dockerfile
```dockerfile
# Dockerfile
# 1. 베이스 이미지 선택
# 파이썬 3.9 버전을 기반으로 하는 공식 이미지를 사용합니다.
FROM python:3.9-slim
# 2. 작업 디렉토리 설정
# 컨테이너 내에서 명령어가 실행될 기본 경로를 설정합니다.
WORKDIR /app
# 3. 의존성 파일 복사
# 먼저 의존성 정의 파일만 복사하여, 소스 코드가 변경되어도
# 의존성이 바뀌지 않았다면 캐시된 레이어를 사용해 빌드 속도를 높입니다.
COPY ./requirements.txt .
# 4. 의존성 설치
# --no-cache-dir 옵션으로 불필요한 캐시를 남기지 않아 이미지 크기를 줄입니다.
RUN pip install --no-cache-dir -r requirements.txt
# 5. 소스 코드 및 모델 파일 복사
# 로컬의 현재 디렉토리(.)에 있는 모든 파일을 컨테이너의 /app 디렉토리로 복사합니다.
COPY . .
# 6. 컨테이너 실행 명령어
# 컨테이너가 시작될 때 실행할 명령어를 정의합니다.
# 0.0.0.0 주소로 실행하여 외부에서 컨테이너에 접근할 수 있도록 합니다.
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```
- **Docker Compose**: 여러 개의 컨테이너(예: FastAPI 서버, 데이터베이스, 메시지 큐)로 구성된 복잡한 애플리케이션을 단일 `docker-compose.yml` 파일로 정의하고, `docker-compose up` 이라는 단일 명령으로 실행하고 관리할 수 있게 해줍니다.
### 1.2. CI/CD: 빌드, 테스트, 배포의 자동화
- **핵심 개념**: CI/CD(Continuous Integration/Continuous Deployment)는 소스 코드가 변경될 때마다 빌드, 테스트, 배포 과정을 자동으로 수행하는 파이프라인을 구축하는 것입니다. 이를 통해 개발자는 코드 변경 사항을 빠르고 안정적으로 사용자에게 전달할 수 있습니다.
- **대표적인 도구**: GitHub Actions, Jenkins, GitLab CI 등
<details>
<summary><b>✨ CI/CD 파이프라인 흐름도</b></summary>
```mermaid
graph TD
subgraph "Developer"
A[1. Code Push to<br>Git Repository]
end
subgraph "CI: Continuous Integration"
direction LR
B(2. Trigger<br>GitHub Actions) --> C{3. Run Tests}
C -- "Success" --> D(4. Build<br>Docker Image)
D --> E(5. Push Image to<br>Container Registry)
end
subgraph "CD: Continuous Deployment"
direction LR
F(6. Deploy Server<br>Pulls New Image) --> G(7. Run New<br>Container)
end
A --> B
E -- "Triggers" --> F
style A fill:#cde4ff,stroke:#6b6b6b
style G fill:#d4edda,stroke:#155724
```
</details>
- **CI/CD 파이프라인 예시 (GitHub Actions)**:
1. **Trigger**: 개발자가 코드를 변경하고 `main` 브랜치에 `push` 합니다.
2. **CI (Continuous Integration)**:
- GitHub Actions가 트리거를 감지하고 가상 머신을 할당합니다.
- 소스 코드를 내려받습니다.
- 유닛 테스트, 통합 테스트 등을 자동으로 실행합니다.
- 테스트가 통과하면, 위에서 작성한 `Dockerfile`을 사용하여 Docker 이미지를 빌드합니다.
- 빌드된 이미지를 Docker Hub나 AWS ECR 같은 컨테이너 레지스트리에 푸시합니다.
3. **CD (Continuous Deployment)**:
- 운영 서버에 SSH로 접속합니다.
- 레지스트리에서 최신 버전의 이미지를 `pull` 받습니다.
- 기존에 실행 중이던 구버전 컨테이너를 중단하고, 새로운 이미지로 컨테이너를 실행합니다.
---
## 2. MLOps: AI 개발 및 운영의 표준화
MLOps(Machine Learning Operations)는 모델 개발(Dev)과 IT 운영(Ops)을 통합하여, 머신러닝의 전체 생명주기(데이터 수집 → 모델 학습 → 배포 → 모니터링 → 재학습)를 자동화하고 효율적으로 관리하는 방법론입니다.
<details>
<summary><b>✨ MLOps 생애주기 다이어그램</b></summary>
```mermaid
graph TD
subgraph "Development and Training"
A["1. Data Collection and Versioning<br/>(DVC)"]
B["2. Model Training and Experiment Tracking<br/>(MLflow)"]
C["3. Model Packaging<br/>(Docker)"]
end
subgraph "Operations"
D["4. Model Deployment<br/>(Kubernetes, FastAPI)"]
E["5. Monitoring<br/>(Prometheus, Grafana)"]
F["6. Performance Analysis<br/>(Data/Model Drift)"]
end
A --> B
B --> C
C --> D
D --> E
E --> F
F -- "Retraining Trigger" --> A
style F fill:#f5c6cb,stroke:#721c24
```
</details>
### 2.1. 실험 관리 및 재현성 (MLflow, DVC)
- **MLflow**: 모델 학습에 사용된 파라미터(learning rate 등), 코드 버전, 성능 지표(accuracy 등), 그리고 결과물(모델 파일, 시각화 자료)을 체계적으로 추적하고 기록합니다. 이를 통해 "어떤 조건에서 가장 좋은 모델이 나왔는지"를 명확하게 관리하여 실험의 재현성을 보장하고 최적의 모델을 쉽게 선정할 수 있습니다.
- **DVC (Data Version Control)**: Git은 대용량 파일을 처리하는 데 적합하지 않습니다. DVC는 Git과 함께 사용하여 대용량 데이터셋과 모델 파일의 버전을 관리하고, 특정 버전의 코드와 데이터를 쉽게 매칭시켜줍니다.
### 2.2. 모델 서빙 및 모니터링
- **Model/Data Drift**: 배포된 모델의 성능은 시간이 지나면서 저하될 수 있습니다. 실제 데이터의 분포가 학습 데이터와 달라지는 현상(Data Drift)이나, 데이터와 예측값 간의 관계가 변하는 현상(Concept Drift)을 감지하고 대응하는 것이 중요합니다.
- **모니터링 도구**: API 서버의 상태(응답 시간, 에러율 등)와 모델의 예측 결과 분포, 데이터의 통계적 특성 변화 등을 지속적으로 시각화하고 추적합니다. 이상 징후가 발생하면 자동으로 알림을 보내 운영자가 개입하거나 재학습 파이프라인을 트리거하도록 설정합니다. (e.g., Prometheus, Grafana, Evidently AI)
### 2.3. 자동화된 ML 파이프라인 (Kubeflow, Airflow)
데이터 수집, 전처리, 모델 학습, 평가, 배포로 이어지는 전체 워크플로우를 코드로 정의하고 자동화합니다. 이를 통해 새로운 데이터가 쌓이면 자동으로 모델을 재학습하고, 성능이 더 좋은 경우 기존 모델을 자동으로 교체하는 등 사람의 개입을 최소화하는 완전 자동화 시스템을 구축할 수 있습니다.
---
## 3. 대용량 데이터 처리 기술
기가바이트를 넘어 테라바이트, 페타바이트급의 대용량 데이터를 효율적으로 저장, 처리, 분석하는 기술은 빅데이터 기반 AI 서비스의 핵심 역량입니다.
- **NoSQL 데이터베이스 (MongoDB, DynamoDB 등)**: 정형화되지 않은 비정형 데이터(JSON, 로그, 센서 데이터 등)를 유연하게 저장하고 빠르게 조회할 수 있어, 로그 데이터나 사용자 행동 데이터 등을 다루는 데 적합합니다.
- **분산 처리 시스템 (Apache Spark, Kafka)**:
- **Apache Kafka**: 대용량 실시간 데이터 스트림을 안정적으로 수집하고 여러 시스템에 전달하는 메시징 시스템입니다. 이벤트 기반 아키텍처의 중심 역할을 합니다.
- **Apache Spark**: 대규모 데이터셋에 대한 분산 처리를 통해 배치(Batch) 기반의 데이터 전처리 및 모델 학습을 고속으로 수행합니다. SQL, 스트리밍, 머신러닝 라이브러리(MLlib) 등 통합된 분석 엔진을 제공합니다.
---
## 4. 클라우드 네이티브 AI
클라우드 플랫폼(AWS, GCP, Azure 등)이 제공하는 강력한 관리형 서비스들을 활용하여 AI 시스템을 더 빠르고, 효율적이며, 탄력적으로 구축합니다.
- **서버리스 (AWS Lambda, Google Cloud Functions)**: 서버 인프라를 직접 프로비저닝하거나 관리할 필요 없이, 코드 실행 단위로 서비스를 배포하고 사용한 만큼만 비용을 지불하여 운영 효율성을 극대화합니다. 간단한 데이터 전처리나 모델 추론 API에 적합합니다.
- **관리형 AI/ML 플랫폼 (Amazon SageMaker, Google Vertex AI, Azure Machine Learning)**: 데이터 라벨링, 노트북 환경, 모델 학습, 하이퍼파라미터 튜닝, 모델 배포, 모니터링에 이르는 MLOps 파이프라인 전체를 클라우드에서 제공하는 완전 관리형 서비스로 구축하여, 인프라 관리 부담을 최소화하고 AI 모델 개발 자체에만 집중할 수 있게 해줍니다.
이러한 심화 과정들을 통해 여러분은 단순히 모델을 만드는 개발자를 넘어, 비즈니스 가치를 지속적으로 창출하는 안정적이고 확장 가능한 AI 시스템을 설계하고 운영하는 'AI 시스템 아키텍트'로 성장하게 될 것입니다.
---
## 5. GitHub 스타터 키트
Part 8에서 다룬 Docker, CI/CD, MLOps 관련 설정이 포함된 프로젝트 예제를 스타터 키트로 제공합니다.
> 🔗 **Part 8 스타터 키트 리포지토리**: [여기에 GitHub 리포지토리 링크가 제공될 예정입니다.]
이 리포지토리를 시작점으로 삼아, 여러분의 AI 프로젝트에 바로 Docker와 CI/CD 파이프라인을 적용해보세요.
\ No newline at end of file
# Part 8: FastAPI를 이용한 모델 서빙 (8주차)
**⬅️ 이전 시간: [Part 7.5: LLM 애플리케이션 개발과 LangChain](./part_7.5_llm_application_development_with_langchain.md)** | **➡️ 다음 시간: [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)**
---
## 📜 실습 코드 바로가기
- **`part_8_model_serving_with_fastapi`**: [바로가기](./source_code/part_8_model_serving_with_fastapi)
- 본 파트에서 다루는 FastAPI 서버 예제 코드(`main.py`, `requirements.txt` 등)를 직접 실행하고 수정해볼 수 있습니다.
---
지금까지 우리는 Scikit-learn과 PyTorch를 이용해 강력한 AI 모델을 만드는 방법을 배웠습니다. 하지만 이 모델들은 아직 우리 컴퓨터 안에만 머물러 있습니다. 어떻게 하면 이 모델의 예측 능력을 다른 사람이나 서비스에게 제공할 수 있을까요?
> **💡 비유: '나만 아는 맛집 레시피를 세상에 공개하기'**
>
> 우리가 AI 모델을 만든 것은, 세상에 없는 '비법 레시피'를 개발한 것과 같습니다. 하지만 이 레시피를 내 서랍 속에만 둔다면 아무도 그 맛을 볼 수 없습니다.
>
> **[API](./glossary.md#api-application-programming-interface) 서버**는 이 비법 레시피로 요리를 만들어 손님에게 판매하는 **'레스토랑'**을 차리는 과정입니다. 손님(클라이언트)은 주방(모델 서버)이 어떻게 돌아가는지 몰라도, 메뉴판(API 문서)을 보고 주문하면 맛있는 요리(예측 결과)를 받을 수 있습니다.
>
> 이번 주차에는 파이썬의 최신 웹 프레임워크인 **[FastAPI](./glossary.md#fastapi)**를 이용해 빠르고 세련된 AI 레스토랑을 여는 방법을 배워보겠습니다.
---
### 📖 8주차 학습 목표
- **API의 개념 이해**: '레스토랑 주문 카운터' 비유를 통해 API가 왜 필요하며, 어떤 역할을 하는지 이해합니다.
- **FastAPI로 서버 구축**: FastAPI를 설치하고, uvicorn으로 내 컴퓨터에서 웹 서버를 실행하는 방법을 배웁니다.
- **예측 API 구현**: 우리가 만든 머신러닝 모델을 FastAPI 서버에 탑재하여, 외부 요청에 따라 예측 결과를 반환하는 API를 만듭니다.
- **데이터 유효성 검사**: **[Pydantic](./glossary.md#pydantic)**을 이용해 '깐깐한 주문서 양식'처럼 API로 들어오는 데이터의 형식을 정의하고 자동으로 검증하는 방법을 익힙니다.
- **자동 API 문서 활용**: FastAPI의 강력한 기능인 **[Swagger UI](./glossary.md#swagger-ui--openapi)**를 통해, 코드를 건드리지 않고 브라우저에서 직접 API를 테스트하는 방법을 배웁니다.
---
### 1일차: FastAPI 입문 - 우리의 AI 레스토랑 개점
#### **API란 무엇일까?**
[API(Application Programming Interface)](./glossary.md#api-application-programming-interface)는 소프트웨어들이 서로 정보를 주고받는 '약속된 방식'입니다. 우리 'AI 레스토랑'에서는 "이런 형식으로 주문(요청)하면, 저런 형식으로 요리(응답)를 내어드립니다"라고 정해놓은 **'주문 창구'** 와 같습니다.
#### **왜 FastAPI일까?**
**[FastAPI](./glossary.md#fastapi)**는 우리 레스토랑을 위한 최고의 선택지입니다.
- **엄청난 속도**: 주문이 밀려들어도 막힘없이 처리하는 '베테랑 직원'처럼 매우 빠릅니다. (Node.js, Go와 비슷한 성능)
- **쉬운 메뉴 관리**: '레시피(코드)'가 간결하고 명확해서, 실수를 줄이고 새로운 메뉴를 빠르게 추가하기 쉽습니다.
- **스마트 메뉴판 자동 생성**: 코드를 짜면 손님들이 직접 보고 테스트할 수 있는 **자동 대화형 문서([Swagger UI](./glossary.md#swagger-ui--openapi))**를 공짜로 만들어줍니다.
#### **개발 환경 준비**
먼저 FastAPI와 우리 레스토랑의 '서버 매니저' 역할을 할 [uvicorn](./glossary.md#uvicorn--gunicorn)을 설치합니다.
```bash
pip install fastapi "uvicorn[standard]"
```
#### **우리의 첫 FastAPI 앱**
`main.py` 파일을 만들어 레스토랑의 기본 틀을 잡아봅니다.
```python
# main.py
from fastapi import FastAPI
# 1. FastAPI 인스턴스 생성 (레스토랑 개점)
app = FastAPI()
# 2. API 엔드포인트 정의 (메뉴판에 메뉴 추가)
# "/" 주소로 GET 요청(가장 기본적인 방문)이 오면, 아래 함수를 실행
@app.get("/")
def read_root():
return {"message": "어서오세요! 저의 AI 레스토랑입니다!"}
```
- `app = FastAPI()`: FastAPI 레스토랑을 개점합니다.
- `@app.get("/")`: 손님이 가게의 정문(`/`)으로 들어와 **"여기는 뭐하는 곳인가요?"(GET 요청)** 라고 물어보면, 아래 `read_root` 직원이 응답하라는 의미입니다.
#### **서버 실행하기**
터미널에서 `main.py`가 있는 디렉토리로 이동한 뒤, 서버 매니저 uvicorn을 실행시킵니다.
```bash
uvicorn main:app --reload
```
- `main`: `main.py` 파일 (우리 레스토랑의 레시피 북)
- `app`: 파일 안에 있는 `app` 객체 (레스토랑 그 자체)
- `--reload`: 레시피를 수정할 때마다 가게를 재오픈할 필요 없이, 매니저가 알아서 반영해주는 편리한 옵션입니다.
서버가 실행되면 웹 브라우저에서 http://127.0.0.1:8000 주소로 접속해보세요. 직원의 환영 메시지가 보인다면 성공입니다!
---
### 2-3일차: 예측 API 구현 - 레스토랑의 시그니처 메뉴 만들기
이제 개점한 레스토랑에 6주차에서 만들었던 **붓꽃 품종 예측 모델(iris_model.pkl)**을 올린 '시그니처 메뉴'를 추가해보겠습니다.
> **[중요]** `iris_model.pkl` 파일을 `main.py` 파일과 같은 폴더에 두어야 합니다. 필요한 라이브러리도 설치해주세요.
> ```bash
> pip install scikit-learn joblib numpy
> ```
#### **모델과 데이터 형식 정의하기**
손님이 어떤 형식으로 주문해야 하는지 명확한 '주문서 양식'이 필요합니다. **[Pydantic](./glossary.md#pydantic)** 라이브러리가 이 역할을 멋지게 수행합니다.
또한, 무거운 주방기구(모델)를 손님이 올 때마다 껐다 켰다 하는 것은 비효율적입니다. 서버가 시작될 때(가게 오픈) **단 한번만** 모델을 메모리에 로드하도록 **lifespan** 이벤트를 사용합니다.
```python
# main.py (수정)
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List
import joblib
import numpy as np
from contextlib import asynccontextmanager
# --- '가게 오픈/마감 준비'를 위한 Lifespan 이벤트 ---
@asynccontextmanager
async def lifespan(app: FastAPI):
# 서버 시작 시 (가게 오픈)
print("레스토랑 오픈: 시그니처 메뉴(모델)를 준비합니다.")
try:
# 무거운 주방기구(모델)를 미리 켜놓습니다.
app.state.model = joblib.load("iris_model.pkl")
app.state.class_names = ['setosa', 'versicolor', 'virginica']
print("모델 준비 완료!")
except FileNotFoundError:
print("경고: 모델 파일(iris_model.pkl)을 찾을 수 없습니다.")
app.state.model = None
yield # 여기서 애플리케이션이 실행됨 (손님 받기)
# 서버 종료 시 (가게 마감)
print("레스토랑 마감")
# --- FastAPI 앱 생성 및 Lifespan 연결 ---
app = FastAPI(lifespan=lifespan)
# --- Pydantic으로 '주문서 양식' 정의 ---
class IrisFeatures(BaseModel):
features: List[float] # 4개의 float 값으로 이루어진 리스트
# 스마트 메뉴판에 보여줄 주문 예시
class Config:
json_schema_extra = {
"example": {
"features": [5.1, 3.5, 1.4, 0.2] # setosa 품종 데이터
}
}
# --- 기본 엔드포인트 ---
@app.get("/")
def read_root():
return {"message": "붓꽃 품종 예측 레스토랑"}
```
#### **예측 엔드포인트 구현**
이제 실제 요리(예측)를 하고 결과를 내어주는 `/predict` 창구를 만듭니다. '요리 주문'은 새로운 것을 만들어달라는 요청이므로 POST 방식으로 받습니다.
```python
# main.py (아래에 추가)
# --- 예측 엔드포인트 (시그니처 메뉴 주문 창구) ---
@app.post("/predict")
def predict_iris(data: IrisFeatures):
"""
붓꽃의 특징 4가지를 주문서(JSON)로 받아 요청하면,
예측된 품종과 그에 대한 신뢰도 점수를 요리(JSON)로 만들어 반환합니다.
"""
model = app.state.model
if model is None:
raise HTTPException(status_code=503, detail="죄송합니다. 주방(모델)이 준비되지 않았습니다.")
# 주문서 양식 확인
if len(data.features) != 4:
raise HTTPException(status_code=400, detail="주문서에 4개의 특징(float)을 정확히 기입해주세요.")
# 주방(모델)이 알아듣는 형태로 재료 손질
model_input = np.array(data.features).reshape(1, -1)
# 요리(예측) 시작
prediction_idx = model.predict(model_input)[0]
prediction_proba = model.predict_proba(model_input)[0]
# 결과 플레이팅
class_name = app.state.class_names[prediction_idx]
confidence = prediction_proba[prediction_idx]
return {
"주문하신 재료": data.features,
"예측된 요리": class_name,
"셰프의 자신감": float(confidence)
}
```
- `@app.post("/predict")`: `/predict` 창구에서는 **POST** 주문을 받습니다.
- `data: IrisFeatures`: 손님의 주문(JSON)을 우리가 정의한 `IrisFeatures` 양식에 맞춰 자동으로 확인하고, 문제가 없으면 `data` 변수에 담아줍니다. 형식이 틀리면 FastAPI가 알아서 "주문 잘못하셨어요" 하고 오류를 보냅니다.
---
### 4-5일차: API 테스트와 종합 - 레스토랑 시식회
이제 완성된 우리 레스토랑의 시그니처 메뉴를 맛볼 시간입니다. FastAPI의 가장 강력한 기능, **스마트 메뉴판(자동 대화형 API 문서)**을 이용해봅시다.
#### **Swagger UI로 API 테스트하기**
1. `uvicorn main:app --reload` 명령으로 서버가 실행 중인지 확인합니다.
2. 웹 브라우저에서 http://127.0.0.1:8000/docs 로 접속합니다.
> **💡 비유: '스마트 메뉴판 Swagger UI'**
>
> `.../docs`는 단순한 메뉴판이 아닙니다.
> - **모든 메뉴(엔드포인트)가 목록으로** 보입니다.
> - 각 메뉴를 클릭하면 **필요한 재료(요청 형식)와 나오는 요리(응답 형식)**에 대한 상세한 설명이 나옵니다.
> - 가장 멋진 기능! **`Try it out`** 버튼을 누르면 메뉴판에서 바로 **'맛보기 주문'**을 넣어볼 수 있습니다. 주방에 직접 가지 않아도 그 자리에서 바로 요리 결과를 확인할 수 있습니다.
3. `POST /predict` 메뉴를 클릭하여 펼친 뒤 `Try it out` 버튼을 누릅니다.
4. **Request body**에 예시로 채워진 주문서를 그대로 두거나 다른 값으로 바꿔봅니다.
```json
{
"features": [6.7, 3.0, 5.2, 2.3]
}
```
5. `Execute` 버튼을 눌러 주문을 주방으로 전송하고, 그 아래에서 방금 나온 따끈따끈한 요리(예측 결과)를 실시간으로 확인할 수 있습니다.
---
## 📝 8주차 요약
이번 주에는 FastAPI를 이용하여 머신러닝 모델을 API로 만드는 첫걸음을 떼었습니다.
- **FastAPI**: 'AI 레스토랑'을 만드는 웹 서버 프레임워크 FastAPI를 배우고 **Uvicorn**으로 실행했습니다.
- **lifespan**: 서버 시작 시 모델('주방기구')을 효율적으로 로드했습니다.
- **Pydantic**: 입력 데이터('주문서')의 형식을 강제하고 유효성을 검사했습니다.
- **Swagger UI**: '/docs'의 '스마트 메뉴판'을 통해 API를 쉽고 빠르게 테스트했습니다.
다음 시간에는 이 '1인 식당'을 여러 셰프와 직원이 협업하는 '프랜차이즈 레스토랑'으로 확장하는 방법을 배웁니다. 프로젝트의 구조를 체계적으로 개선하고, **[Docker](./glossary.md#docker)**를 이용해 우리 레스토랑을 어떤 쇼핑몰에든 쉽게 입점시키는 '밀키트' 기술을 배우겠습니다.
## 🔍 8주차 연습 문제
**문제 1: 7주차 Fashion-MNIST 모델 서빙하기**
- 7주차에서 만들었던 PyTorch CNN 모델(`SimpleCNN`)을 저장하고, 이 모델을 로드하여 예측하는 FastAPI 서버를 만들어보세요.
- **요구사항**:
1. `torch.save(model.state_dict(), 'fashion_mnist_cnn.pth')`로 모델의 가중치를 저장합니다.
2. 서버 시작 시(lifespan)에 모델 구조를 만들고 저장된 가중치를 `model.load_state_dict(...)`로 불러옵니다. `model.eval()` 모드로 설정하는 것을 잊지 마세요.
3. 입력 데이터는 28x28 픽셀 값을 펼친 784개의 float 리스트로 받도록 Pydantic 모델을 정의하세요.
4. 예측 엔드포인트(`POST /predict/fashion`)는 입력 리스트를 받아 `torch.Tensor`로 변환하고, 모델에 맞는 모양(`1, 1, 28, 28`)으로 reshape하여 예측을 수행해야 합니다.
5. 예측 결과로 가장 확률이 높은 클래스의 이름(e.g., 'T-shirt/top', 'Sneaker')을 반환하세요.
**문제 2: 책 정보 조회 API 만들기**
- 간단한 인-메모리(in-memory) 딕셔너리를 '데이터베이스'로 사용하여, 책 정보를 조회하는 API를 만들어보세요.
- **요구사항**:
1. 서버에 `books_db = {1: {"title": "클린 코드", "author": "로버트 C. 마틴"}, 2: ...}` 와 같이 책 데이터를 미리 만들어두세요.
2. `GET /books/{book_id}` 엔드포인트를 만드세요.
3. **경로 매개변수**로 받은 `book_id`를 이용하여 `books_db`에서 책을 찾아 정보를 반환하세요.
4. 만약 해당 `book_id`가 DB에 없다면, FastAPI의 `HTTPException`을 이용하여 `404 Not Found` 오류와 함께 "해당 ID의 책을 찾을 수 없습니다." 메시지를 반환하세요.
**➡️ 다음 시간: [Part 9: 프로덕션 레벨 API와 Docker](./part_9_production_ready_api.md)**
# Part 9: 프로덕션 레벨 API와 Docker (9주차)
**⬅️ 이전 시간: [Part 8: FastAPI를 이용한 모델 서빙](./part_8_model_serving_with_fastapi.md)** | **➡️ 다음 시간: [Part 10: 전문가로 가는 길](./part_10_expert_path.md)**
---
## 📜 실습 코드 바로가기
- **`part_9_production_ready_api`**: [바로가기](./source_code/part_9_production_ready_api)
- 본 파트에서 다루는 프로덕션 레벨의 FastAPI 프로젝트 구조(`app` 폴더), `Dockerfile`, `docker-compose.yml` 등의 전체 코드를 확인하고 직접 실행해볼 수 있습니다.
---
지난 시간에는 FastAPI를 이용해 '1인 식당'처럼 빠르게 AI 모델 API를 만들어보았습니다. 프로토타입으로는 훌륭하지만, 메뉴가 다양해지고(기능 추가) 여러 셰프가 협업해야 하는(팀 개발) 실제 '프랜차이즈 레스토랑' 환경에서는 한계가 명확합니다.
이번 주차에는 `main.py` 하나에 모든 것을 담던 방식에서 벗어나, 유지보수와 확장이 용이한 **프로덕션 레벨의 프로젝트 구조**로 우리 레스토랑을 리팩터링합니다. 또한, 어떤 백화점에 입점하든 동일한 맛과 서비스를 보장하는 **'밀키트([Docker](./glossary.md#docker) 컨테이너)'** 기술을 배워, 우리 AI 서비스를 세상 어디에든 쉽고 안정적으로 배포할 수 있는 능력을 갖추게 될 것입니다.
---
### 📖 9주차 학습 목표
- **프로젝트 구조화**: '1인 식당'에서 '프랜차이즈 본사'로 확장하며, 유지보수와 협업에 용이한 전문적인 프로젝트 구조를 이해합니다.
- **관심사 분리**: API의 각 기능(설정, DB, 모델, 스키마, 라우터)을 독립적인 파일로 분리하고, APIRouter로 엔드포인트를 기능별('한식/중식 코너')로 모듈화합니다.
- **[Docker](./glossary.md#docker) 개념 이해**: "제 컴퓨터에선 되는데요..." 문제를 해결하는 컨테이너 기술의 원리를 이해하고, 이미지/컨테이너/도커파일의 개념을 익힙니다.
- **[Dockerfile](./glossary.md#dockerfile) 작성**: 'AI 레스토랑 밀키트 레시피'인 Dockerfile을 직접 작성하여 우리 앱을 이미지로 만듭니다.
- **[Docker Compose](./glossary.md#docker-compose) 활용**: API 서버, 데이터베이스 등 여러 서비스를 명령어 하나로 관리하는 `docker-compose.yml`의 원리를 익힙니다.
---
### 1-2일차: 프로덕션 레벨 프로젝트 구조 설계
#### **왜 구조를 바꿔야 할까?**
`main.py` 파일 하나에 모든 코드가 있는 것은, 주방장이 혼자 주문받고, 요리하고, 서빙하고, 계산까지 하는 동네 분식집과 같습니다. 처음엔 빠르지만, 손님이 많아지면 금방 한계에 부딪힙니다.
> **💡 비유: '1인 식당'에서 '프랜차이즈 레스토랑'으로**
>
> 우리는 이제 레스토랑을 프랜차이즈화할 것입니다. 이를 위해서는 역할 분담이 필수적입니다.
> - **`settings.py`**: 본사의 운영 방침 (환경 설정)
> - **`database.py`**: 식자재 창고와의 연결 통로 (DB 접속)
> - **`models.py`**: 창고에 보관된 식자재의 형태 정의 (DB 테이블 구조)
> - **`schemas.py`**: 모든 지점에서 통일된 '주문서 양식' (데이터 형식 정의)
> - **`crud.py`**: 식자재를 창고에서 꺼내고 넣는 '표준 작업 절차' (DB 처리 로직)
> - **`routers/`**: '한식 코너', '중식 코너' 등 메뉴별로 분리된 주방 (기능별 API)
> - **`main.py`**: 모든 코너를 총괄하고 손님을 맞는 '총지배인'
#### **1단계: 프로젝트 폴더 및 기본 설정 파일 생성**
먼저 아래 구조에 따라 `fastapi_project` 폴더 및 하위 폴더들을 만듭니다.
```
/fastapi_project
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── settings.py
│ ├── database.py
│ ├── models.py
│ ├── schemas.py
│ ├── crud.py
│ └── routers/
│ ├── __init__.py
│ └── predictions.py
├── requirements.txt
└── iris_model.pkl
```
**`app/settings.py` (운영 방침)**: 환경변수로 설정을 관리합니다.
```python
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
DATABASE_URL: str = "sqlite:///./iris_prediction.db"
class Config:
env_file = ".env"
settings = Settings()
```
**`app/database.py` (창고 연결)**: 데이터베이스 연결을 설정합니다.
```python
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from .settings import settings
engine = create_engine(
settings.DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
```
- **[SQLAlchemy](./glossary.md#sqlalchemy)**: 파이썬 코드로 데이터베이스와 상호작용할 수 있게 해주는 ORM(Object-Relational Mapping) 라이브러리입니다. SQL 쿼리를 직접 작성하지 않고, 파이썬 객체(모델)를 통해 DB 테이블을 조작할 수 있습니다.
#### **2단계: 데이터 모델과 로직 분리**
**`app/models.py` (식자재의 형태 정의)**: DB 테이블 구조를 정의합니다.
```python
from sqlalchemy import Column, Integer, String, DateTime, Float
from .database import Base
import datetime
class PredictionLog(Base):
__tablename__ = "prediction_logs"
id = Column(Integer, primary_key=True, index=True)
input_features = Column(String)
predicted_class = Column(String, index=True)
confidence = Column(Float)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
```
**`app/schemas.py` (표준 주문서)**: API가 주고받을 데이터 형식을 정의합니다.
```python
from pydantic import BaseModel
from typing import List
import datetime
class PredictionRequest(BaseModel):
features: List[float]
class PredictionResponse(BaseModel):
id: int
input_features: str
predicted_class: str
confidence: float
created_at: datetime.datetime
class Config:
from_attributes = True
```
**`app/crud.py` (표준 작업 절차)**: 데이터베이스 처리 함수를 정의합니다.
```python
from sqlalchemy.orm import Session
from . import models, schemas
import json
def create_prediction_log(db: Session, request: schemas.PredictionRequest, predicted_class: str, confidence: float):
features_str = json.dumps(request.features)
db_log = models.PredictionLog(
input_features=features_str,
predicted_class=predicted_class,
confidence=confidence
)
db.add(db_log)
db.commit()
db.refresh(db_log)
return db_log
```
---
### 3일차: 기능별 API 라우터 분리
**APIRouter**를 이용하면 '한식 코너', '중식 코너'처럼 기능별로 API를 분리할 수 있습니다.
**`app/routers/predictions.py` (예측 코너 주방)**
```python
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
import joblib
import numpy as np
from .. import crud, schemas
from ..database import SessionLocal
router = APIRouter(
prefix="/predict",
tags=["predictions"]
)
# 모델 로드
try:
model = joblib.load("iris_model.pkl")
class_names = ['setosa', 'versicolor', 'virginica']
except FileNotFoundError:
model = None
# 의존성 주입: '주방 보조'가 DB 연결을 가져다 줌
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@router.post("/", response_model=schemas.PredictionResponse)
def create_prediction(request: schemas.PredictionRequest, db: Session = Depends(get_db)):
if model is None:
raise HTTPException(status_code=503, detail="모델을 로드할 수 없습니다.")
# 예측
model_input = np.array(request.features).reshape(1, -1)
prediction_idx = model.predict(model_input)[0]
confidence = model.predict_proba(model_input)[0][prediction_idx]
predicted_class = class_names[prediction_idx]
# DB에 로그 저장 (CRUD 함수 사용)
return crud.create_prediction_log(db, request, predicted_class, confidence)
```
- **[`Depends(get_db)`](./glossary.md#의존성-주입dependency-injection-di) (의존성 주입)**: `create_prediction`이라는 '셰프'가 요리를 시작하기 전에, FastAPI라는 '총지배인'이 `get_db`라는 '주방 보조'를 시켜 DB 연결이라는 '깨끗한 프라이팬'을 가져다주는 것과 같습니다. 셰프는 프라이팬을 어떻게 가져왔는지 신경 쓸 필요 없이 요리에만 집중할 수 있습니다.
#### **최종 통합 (`app/main.py`)**
분리된 모든 조각을 '총지배인' `main.py`에서 하나로 조립합니다.
```python
from fastapi import FastAPI
from .database import engine, Base
from .routers import predictions
# DB 테이블 생성
Base.metadata.create_all(bind=engine)
app = FastAPI(
title="AI 레스토랑 (프랜차이즈 ver)",
description="프로덕션 레벨 구조로 개선된 붓꽃 예측 API입니다."
)
# "예측 코너" 라우터 포함
app.include_router(predictions.router)
@app.get("/")
def read_root():
return {"message": "AI 레스토랑에 오신 것을 환영합니다."}
```
---
### 4일차: Docker로 우리 앱 포장하기
#### **왜 Docker를 사용해야 할까?**
> **💡 비유: '어디서든 똑같은 맛을 내는 밀키트'**
>
> 우리 레스토랑의 레시피와 재료를 완벽하게 준비해도, 지점마다 주방 환경(OS, 라이브러리 버전 등)이 다르면 음식 맛이 달라질 수 있습니다. "제 주방(컴퓨터)에서는 되는데요?" 라는 문제는 개발자들의 흔한 악몽입니다.
>
> **[Docker](./glossary.md#docker)**는 우리 레스토랑의 모든 것(코드, 라이브러리, 설정)을 하나의 **'밀키트(이미지)'** 로 완벽하게 포장하는 기술입니다. 이 밀키트는 어떤 주방(서버)에서 데우든 항상 똑같은 환경에서, 똑같은 맛의 요리를 만들어냅니다.
#### **Dockerfile 작성하기**
`Dockerfile`은 이 밀키트를 만드는 '레시피'입니다. 프로젝트 최상위 폴더에 `Dockerfile`이라는 이름으로 파일을 만듭니다.
```dockerfile
# 1. 베이스 이미지 선택 (기본 주방 키트 가져오기)
FROM python:3.9-slim
# 2. 작업 디렉토리 설정 (조리대 위치 지정)
WORKDIR /app
# 3. 의존성 파일 먼저 복사 (소스가 바뀌어도 의존성이 같으면 캐시 재사용)
COPY ./requirements.txt .
# 4. 의존성 설치 (필요한 조미료 채우기)
RUN pip install --no-cache-dir -r requirements.txt
# 5. 소스 코드 복사 (메인 레시피와 도구들 넣기)
COPY ./app /app/app
COPY ./iris_model.pkl /app/
# 6. 실행 명령어 (서버 매니저에게 가게 오픈 지시)
# 0.0.0.0 주소는 '어느 손님이든 다 받으라'는 의미
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```
#### **Docker 이미지 빌드 및 실행**
터미널에서 `Dockerfile`이 있는 폴더로 이동한 뒤, 아래 명령어를 실행합니다.
```bash
# 1. '프랜차이즈 1호점' 밀키트(이미지) 만들기
docker build -t franchise-restaurant:1.0 .
# 2. 밀키트를 데워 레스토랑(컨테이너) 열기
# -p 8000:80 -> 내 컴퓨터 8000번 문과 컨테이너 80번 문을 연결
docker run -d -p 8000:80 --name my-first-restaurant franchise-restaurant:1.0
```
---
### 5일차: Docker Compose로 여러 서비스 관리하기
#### **왜 Docker Compose가 필요할까?**
> **💡 비유: '푸드코트 전체를 한 번에 오픈하기'**
>
> 지금까지는 '레스토랑' 하나만 Docker로 열었습니다. 하지만 실제 서비스는 API 서버 외에도 데이터베이스, 캐시 서버 등 여러 컴포넌트가 함께 동작하는 '푸드코트'와 같습니다.
>
> **[Docker Compose](./glossary.md#docker-compose)**는 이 푸드코트의 전체 설계도(`docker-compose.yml`)와 같습니다. 각 코너(컨테이너)의 종류, 위치, 연결 관계를 모두 정의해두면, **`docker-compose up`** 이라는 명령어 한 번으로 전체 푸드코트를 열고 닫을 수 있습니다.
#### **docker-compose.yml 작성하기**
프로젝트 최상위 폴더에 `docker-compose.yml` 파일을 만듭니다.
```yaml
version: '3.8' # 파일 형식 버전
services: # 여기에 우리 푸드코트에 입점할 가게들 목록을 정의
# 1. API 서버 (우리의 메인 레스토랑)
api:
build: . # 현재 폴더의 Dockerfile을 사용해 이미지를 빌드
container_name: franchise_api
ports:
- "8000:80" # 내 컴퓨터 8000번과 컨테이너 80번 포트 연결
volumes:
- ./app:/app/app # 소스 코드를 실시간으로 동기화 (개발 시 유용)
environment:
- DATABASE_URL=postgresql://user:password@db/mydatabase
# 2. 데이터베이스 서버 (PostgreSQL 식자재 창고)
db:
image: postgres:13 # PostgreSQL 공식 이미지 사용
container_name: franchise_db
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydatabase
volumes: # 데이터 영구 보관 장소
postgres_data:
```
#### **Docker Compose 실행**
터미널에서 `docker-compose.yml` 파일이 있는 폴더로 이동하여 실행합니다.
```bash
# 푸드코트 전체 오픈 (백그라운드에서 실행)
docker-compose up -d
# 푸드코트 전체 폐점 (컨테이너, 네트워크 등 모두 정리)
docker-compose down
```
이제 우리는 명령어 한 번으로 복잡한 멀티 컨테이너 애플리케이션을 손쉽게 관리할 수 있게 되었습니다!
---
## 📝 9주차 요약
- **프로젝트 구조화**: `main.py` 하나에 있던 코드를 역할(라우터, 스키마, CRUD 등)에 따라 여러 파일로 분리하여 재사용성과 유지보수성을 높였습니다.
- **APIRouter**: 기능별로 API 엔드포인트를 모듈화하여 프로젝트를 체계적으로 관리했습니다.
- **Docker**: '밀키트' 비유를 통해 "제 컴퓨터에선 되는데요?" 문제를 해결하는 컨테이너 기술을 배웠고, `Dockerfile`로 우리 앱을 이미지로 만들었습니다.
- **Docker Compose**: '푸드코트' 비유를 통해 여러 서비스(컨테이너)를 `docker-compose.yml` 파일 하나로 손쉽게 관리하는 방법을 익혔습니다.
이제 여러분은 단순히 모델을 만드는 것을 넘어, 실제 서비스로 배포하고 운영할 수 있는 견고한 기반을 다졌습니다.
## 🔍 9주차 연습 문제
**문제 1: 라우터 분리 연습**
- 8주차 연습 문제 2번에서 만들었던 책 조회 API(`GET /books/{book_id}`)를 `routers/books.py` 파일로 분리하고, `main.py`에 포함시켜 보세요.
- `books.py` 라우터의 `prefix``/api/v1/books`로 설정하고, 태그는 `books`로 지정해보세요.
**문제 2: Dockerfile 개선**
- 현재 `Dockerfile`은 소스 코드가 바뀔 때마다 `pip install`을 다시 수행할 수 있습니다. 어떻게 하면 의존성이 바뀌지 않았을 때 `pip install` 과정을 건너뛰고 캐시를 사용할 수 있을까요? (`COPY` 명령어의 순서를 조정해보세요.)
**문제 3: Docker Compose에 Redis 추가하기**
- `docker-compose.yml` 파일에 **Redis** 캐시 서버를 추가해보세요.
- 서비스 이름은 `cache`, 이미지는 공식 `redis:alpine` 이미지를 사용합니다.
- 포트는 내 컴퓨터의 `6379`번과 컨테이너의 `6379`번을 연결하세요.
**➡️ 다음 시간: [Part 10: 전문가로 가는 길](./part_10_expert_path.md)**
\ No newline at end of file
# Part 2 & 3: Python 기본 문법 및 컬렉션 실습
# --- 변수 및 기본 데이터 타입 ---
print("--- 변수 및 기본 데이터 타입 ---")
my_integer = 10
my_float = 3.14
my_string = "Hello, Python!"
my_boolean = True
print(f"정수: {my_integer}, 타입: {type(my_integer)}")
print(f"실수: {my_float}, 타입: {type(my_float)}")
print(f"문자열: {my_string}, 타입: {type(my_string)}")
print(f"불리언: {my_boolean}, 타입: {type(my_boolean)}")
print("-" * 20)
# --- 리스트 (List) ---
print("\n--- 리스트 (List) ---")
my_list = [1, 2, "three", 4.0]
print(f"원본 리스트: {my_list}")
# 요소 추가
my_list.append(5)
print(f"append(5) 후: {my_list}")
# 인덱싱 및 슬라이싱
print(f"첫 번째 요소: {my_list[0]}")
print(f"1번 인덱스부터 3번 인덱스 전까지: {my_list[1:3]}")
# 리스트 컴프리헨션
squares = [x**2 for x in range(5)]
print(f"0-4의 제곱 리스트: {squares}")
print("-" * 20)
# --- 튜플 (Tuple) ---
print("\n--- 튜플 (Tuple) ---")
my_tuple = (1, 2, "three", 4.0)
print(f"튜플: {my_tuple}")
# 튜플은 불변(immutable)이므로 요소 추가/삭제/변경이 불가합니다.
# my_tuple[0] = 99 # TypeError 발생
print(f"튜플의 세 번째 요소: {my_tuple[2]}")
print("-" * 20)
# --- 딕셔너리 (Dictionary) ---
print("\n--- 딕셔너리 (Dictionary) ---")
my_dict = {"name": "Alice", "age": 25, "city": "New York"}
print(f"원본 딕셔너리: {my_dict}")
# 값 접근
print(f"이름: {my_dict['name']}")
# 새로운 키-값 쌍 추가
my_dict["email"] = "alice@example.com"
print(f"이메일 추가 후: {my_dict}")
# 모든 키와 값 순회
print("딕셔너리 키-값:")
for key, value in my_dict.items():
print(f" {key}: {value}")
print("-" * 20)
# --- 집합 (Set) ---
print("\n--- 집합 (Set) ---")
my_set = {1, 2, 3, 2, 1} # 중복된 요소는 자동으로 제거됨
print(f"원본 집합: {my_set}")
# 요소 추가
my_set.add(4)
print(f"add(4) 후: {my_set}")
# 집합 연산
other_set = {3, 4, 5, 6}
print(f"다른 집합: {other_set}")
print(f"합집합: {my_set.union(other_set)}")
print(f"교집합: {my_set.intersection(other_set)}")
print(f"차집합: {my_set.difference(other_set)}")
print("-" * 20)
# --- 제어 흐름 (Control Flow) ---
print("\n--- 제어 흐름 ---")
# if-elif-else
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
else:
grade = "C"
print(f"점수 {score}점은 {grade} 등급입니다.")
# for 반복문
print("리스트 순회:")
for item in my_list:
print(f" - {item}")
# while 반복문
count = 5
print("카운트다운:")
while count > 0:
print(f" {count}...")
count -= 1
print(" 발사!")
print("-" * 20)
# --- 함수 (Functions) ---
print("\n--- 함수 ---")
def greet(name):
"""지정된 이름에게 인사하는 함수"""
return f"안녕하세요, {name}님!"
greeting_message = greet("파이썬")
print(greeting_message)
def calculate_area(width, height):
"""사각형의 넓이를 계산하는 함수"""
area = width * height
return area
rectangle_area = calculate_area(10, 5)
print(f"너비 10, 높이 5인 사각형의 넓이는 {rectangle_area}입니다.")
print("-" * 20)
\ No newline at end of file
# Part 4: 객체 지향 프로그래밍 (OOP) 실습
# --- 클래스와 객체 ---
print("--- 클래스와 객체 ---")
class Animal:
# 클래스 속성
species = "동물"
# 초기화 메서드 (생성자)
def __init__(self, name, age):
# 인스턴스 속성
self.name = name
self.age = age
print(f"{self.name}(이)가 생성되었습니다.")
# 인스턴스 메서드
def speak(self):
return "동물이 소리를 냅니다."
def introduce(self):
return f"저는 {self.name}이고, {self.age}살입니다."
# 소멸자 메서드
def __del__(self):
print(f"{self.name}(이)가 소멸되었습니다.")
# 객체(인스턴스) 생성
my_dog = Animal("멍멍이", 3)
# 속성 접근
print(f"이름: {my_dog.name}")
print(f"나이: {my_dog.age}")
print(f"종: {Animal.species}")
# 메서드 호출
print(my_dog.introduce())
print(my_dog.speak())
print("-" * 20)
# --- 상속 (Inheritance) ---
print("\n--- 상속 ---")
# Animal 클래스를 상속받는 Dog 클래스
class Dog(Animal):
# 메서드 오버라이딩 (Method Overriding)
def speak(self):
return "멍멍!"
# Animal 클래스를 상속받는 Cat 클래스
class Cat(Animal):
def speak(self):
return "야옹~"
# 자식 클래스의 인스턴스 생성
buddy = Dog("버디", 5)
lucy = Cat("루시", 2)
# 부모 클래스의 메서드와 오버라이딩된 메서드 호출
print(buddy.introduce()) # 부모 클래스의 메서드
print(f"{buddy.name}의 소리: {buddy.speak()}") # 오버라이딩된 메서드
print(lucy.introduce())
print(f"{lucy.name}의 소리: {lucy.speak()}")
print("-" * 20)
# --- 다형성 (Polymorphism) ---
print("\n--- 다형성 ---")
animals = [Dog("레오", 4), Cat("나비", 1), Animal("알수없음", 0)]
for animal in animals:
# 같은 animal.speak() 호출이지만,
# 각 객체의 실제 클래스(Dog, Cat, Animal)에 따라 다른 결과가 나옴
print(f"{animal.name}{animal.speak()} 하고 웁니다.")
print("-" * 20)
# del my_dog, buddy, lucy, animals # 수동으로 객체 소멸 (일반적으로는 불필요)
\ No newline at end of file
# Part 5: AI 핵심 라이브러리 실습 (Numpy, Pandas, Matplotlib)
# 이 스크립트를 실행하기 전에 라이브러리를 설치해야 합니다.
# pip install numpy pandas matplotlib
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
# --- Numpy (Numerical Python) ---
print("--- Numpy 실습 ---")
# Numpy 배열 생성
arr = np.array([1, 2, 3, 4, 5])
print(f"1차원 배열: {arr}")
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print(f"2차원 배열:\n{arr2d}")
# 배열의 형태, 차원, 타입 확인
print(f"형태(shape): {arr2d.shape}")
print(f"차원(ndim): {arr2d.ndim}")
print(f"타입(dtype): {arr2d.dtype}")
# Numpy 연산 (브로드캐스팅)
arr_a = np.array([1, 2, 3])
arr_b = np.array([4, 5, 6])
print(f"배열 A: {arr_a}")
print(f"배열 B: {arr_b}")
print(f"A + B = {arr_a + arr_b}")
print(f"A * 2 = {arr_a * 2}")
# 다양한 배열 생성
zeros_arr = np.zeros((2, 3))
print(f"0으로 채워진 배열:\n{zeros_arr}")
ones_arr = np.ones((3, 2))
print(f"1로 채워진 배열:\n{ones_arr}")
range_arr = np.arange(0, 10, 2) # 0부터 10 전까지 2의 간격으로
print(f"arange(0, 10, 2): {range_arr}")
print("-" * 30)
# --- Pandas (Python Data Analysis Library) ---
print("\n--- Pandas 실습 ---")
# Pandas Series 생성 (1차원 데이터)
s = pd.Series([1, 3, 5, np.nan, 6, 8])
print(f"Pandas Series:\n{s}")
# Pandas DataFrame 생성 (2차원 데이터)
data = {'이름': ['Alice', 'Bob', 'Charlie', 'David'],
'나이': [25, 30, 35, 28],
'도시': ['서울', '부산', '서울', '대전']}
df = pd.DataFrame(data)
print(f"\nPandas DataFrame:\n{df}")
# 데이터 확인
print("\nDataFrame 정보 (info):")
df.info()
print(f"\n상위 2개 데이터 (head):\n{df.head(2)}")
print(f"\n기술 통계 (describe):\n{df.describe()}")
# 데이터 선택
print(f"\n'이름' 컬럼 선택:\n{df['이름']}")
print(f"\n0번, 1번 행 선택 (iloc):\n{df.iloc[0:2]}")
print(f"\n'나이'가 30 이상인 데이터 필터링:\n{df[df['나이'] >= 30]}")
# 데이터 추가 및 수정
df['월급'] = [300, 400, 500, 350]
print(f"\n'월급' 컬럼 추가 후:\n{df}")
print("-" * 30)
# --- Matplotlib (Data Visualization Library) ---
print("\n--- Matplotlib 실습 ---")
# 저장할 디렉터리 생성
output_dir = "plot_outputs"
if not os.path.exists(output_dir):
os.makedirs(output_dir)
print(f"그래프는 '{output_dir}' 폴더에 저장됩니다.")
# 1. 라인 플롯 (Line Plot)
x = np.arange(0, 10, 0.1)
y = np.sin(x)
plt.figure(figsize=(10, 5))
plt.plot(x, y, label='sin(x)')
plt.title('Sine Wave')
plt.xlabel('x-axis')
plt.ylabel('y-axis')
plt.grid(True)
plt.legend()
# 그래프를 파일로 저장
line_plot_path = os.path.join(output_dir, "line_plot.png")
plt.savefig(line_plot_path)
print(f"라인 플롯 저장 완료: {line_plot_path}")
plt.close() # 플롯 창을 닫아 다음 플롯에 영향이 없도록 함
# 2. 바 차트 (Bar Chart)
cities = df['도시'].value_counts()
plt.figure(figsize=(8, 6))
cities.plot(kind='bar', color=['skyblue', 'salmon', 'lightgreen'])
plt.title('City Distribution')
plt.xlabel('City')
plt.ylabel('Number of People')
plt.xticks(rotation=0) # x축 레이블 회전 방지
bar_chart_path = os.path.join(output_dir, "bar_chart.png")
plt.savefig(bar_chart_path)
print(f"바 차트 저장 완료: {bar_chart_path}")
plt.close()
# 3. 스캐터 플롯 (Scatter Plot)
x_rand = np.random.rand(50)
y_rand = np.random.rand(50)
colors = np.random.rand(50)
area = (30 * np.random.rand(50))**2
plt.figure(figsize=(10, 6))
plt.scatter(x_rand, y_rand, s=area, c=colors, alpha=0.5)
plt.title('Scatter Plot')
plt.xlabel('Random X')
plt.ylabel('Random Y')
scatter_plot_path = os.path.join(output_dir, "scatter_plot.png")
plt.savefig(scatter_plot_path)
print(f"스캐터 플롯 저장 완료: {scatter_plot_path}")
plt.close()
print("-" * 30)
\ 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