Python Complete Guide

초급부터 고급까지: 파이썬의 모든 것

1. 파이썬이란?

파이썬(Python)은 1991년 네덜란드 CWI의 귀도 반 로섬(Guido van Rossum)이 발표한 고급 프로그래밍 언어입니다. 파이썬이라는 이름은 자신이 좋아하던 코미디 쇼 "몬티 파이썬의 날아다니는 서커스"에서 따왔다고 합니다.

파이썬은 TIOBE Index에서 꾸준히 1위를 차지하며, 전 세계에서 가장 인기 있는 프로그래밍 언어로 자리 잡았습니다. 웹 개발(Django, Flask), 데이터 과학(NumPy, Pandas), 인공지능/머신러닝(TensorFlow, PyTorch), 자동화 스크립팅, 게임 개발, IoT 등 거의 모든 소프트웨어 분야에서 활용됩니다. 특히 간결하고 읽기 쉬운 문법은 프로그래밍 입문자부터 숙련 개발자까지 폭넓은 사용자층을 형성하는 핵심 이유입니다.

파이썬의 주요 특징

파이썬은 다른 프로그래밍 언어와 비교하여 독보적인 특징을 가지고 있습니다. 인터프리터 언어로서 컴파일 과정 없이 코드를 한 줄씩 즉시 실행할 수 있고, 동적 타이핑(Dynamic Typing)으로 변수 선언 시 자료형을 명시하지 않아도 됩니다. 또한 멀티 패러다임을 지원하여 절차적, 객체지향, 함수형 프로그래밍 스타일을 모두 사용할 수 있습니다. 특히 크로스 플랫폼 지원으로 Windows, macOS, Linux 등 어디서나 동일한 코드를 실행할 수 있다는 점이 큰 장점입니다.

인터프리터 언어

소스 코드를 기계어로 미리 컴파일하지 않고, 인터프리터가 한 줄씩 해석하여 실행합니다. 코드를 작성하고 즉시 실행 결과를 확인할 수 있어 개발 속도가 빠릅니다. 대화형 셸(REPL)에서 실시간으로 코드를 테스트할 수 있는 것도 이 특징 덕분입니다.

동적 타이핑

변수 선언 시 자료형을 명시하지 않아도 됩니다. x = 10이라고 쓰면 자동으로 정수형으로 인식하고, x = "hello"로 바꾸면 문자열형으로 변환됩니다. 이로 인해 코드가 간결해지지만, 런타임 타입 에러에 주의해야 합니다.

멀티 패러다임

절차적 프로그래밍(함수 단위 순차 실행), 객체지향 프로그래밍(클래스와 상속), 함수형 프로그래밍(lambda, map, filter, reduce)을 모두 지원합니다. 문제의 성격에 따라 가장 적합한 스타일을 선택할 수 있습니다.

풍부한 표준 라이브러리

"배터리가 포함된(Batteries Included)" 철학에 따라, 파일 I/O, 네트워킹, 정규식, JSON 처리, 데이터베이스 연동, 멀티스레딩 등을 별도 설치 없이 표준 라이브러리만으로 처리할 수 있습니다. 200개 이상의 모듈이 기본 제공됩니다.

크로스 플랫폼

Windows, macOS, Linux, Unix 등 주요 운영체제에서 동일한 코드를 수정 없이 실행할 수 있습니다. "Write once, run anywhere" 원칙을 실현하여 개발 환경에 구애받지 않습니다.

거대한 생태계

PyPI(Python Package Index)에 50만 개 이상의 서드파티 패키지가 등록되어 있으며, pip install 한 줄로 설치할 수 있습니다. 웹, AI, 데이터, 보안, 게임 등 거의 모든 분야의 라이브러리가 존재합니다.

Python vs 다른 언어 코드 비교 — "Hello, World!" 출력 Python print("Hello, World!") 1줄 Java public class Hello { public static void main(String[] args) { System.out.println(... 5줄 C #include <stdio.h> int main() { printf("Hello..."); return 0; } 4줄 주요 언어 특성 비교 특성 Python Java C/C++ 타이핑 동적 정적 정적 실행 방식 인터프리터 컴파일+VM 네이티브 컴파일 학습 난이도 쉬움 보통 어려움
Python 활용 분야 Python 범용 프로그래밍 언어 TIOBE #1 (2024~) 웹 개발 Django, Flask, FastAPI Instagram, Pinterest, Spotify REST API, GraphQL, WebSocket 데이터 과학 NumPy, Pandas, SciPy Jupyter Notebook 기반 분석 통계, 시각화, 탐색적 분석 AI / 머신러닝 TensorFlow, PyTorch scikit-learn, Hugging Face ChatGPT, Stable Diffusion 자동화 Selenium, BeautifulSoup 업무 자동화, 웹 크롤링 PDF/Excel 자동 처리 데이터 시각화 Matplotlib, Seaborn Plotly, Bokeh (인터랙티브) 대시보드, 차트, 지도 시각화 DevOps / 클라우드 Ansible, Terraform CDK AWS Boto3, Docker SDK CI/CD, 모니터링, IaC 게임 / IoT Pygame, MicroPython Raspberry Pi, Arduino 프로토타이핑, 센서 제어

파이썬의 핵심 철학 (PEP 20)

파이썬의 설계 원칙은 PEP 20 (The Zen of Python)에 담겨 있으며, 인터프리터에서 import this를 실행하면 확인할 수 있습니다. 이 철학은 코드 작성 시 가독성과 단순함을 최우선으로 두라는 메시지를 전달합니다.

  • "아름다운 게 추한 것보다 낫다." (Beautiful is better than ugly)
  • "명시적인 것이 암시적인 것보다 낫다." (Explicit is better than implicit)
  • "단순함이 복잡함보다 낫다." (Simple is better than complex)
  • "복잡함이 난해한 것보다 낫다." (Complex is better than complicated)
  • "가독성은 중요하다." (Readability counts)

"인생은 너무 짧다. 그래서 파이썬이 필요하다." ("Life is short, You need Python.")

파이썬의 역사

파이썬은 1989년 크리스마스 주간에 귀도 반 로섬이 취미 프로젝트로 시작하여, 30년 이상의 역사를 거치며 세계에서 가장 영향력 있는 프로그래밍 언어로 성장했습니다. 각 주요 버전은 언어의 방향성을 결정짓는 중요한 이정표가 되었습니다.

Python 역사 타임라인 1991 — Python 0.9.0 귀도 반 로섬이 최초 공개. 예외 처리, 함수, 모듈 시스템 포함. ABC 언어의 후계자로 설계. 들여쓰기 기반 문법 도입. 1994 — Python 1.0 lambda, map, filter, reduce 등 함수형 프로그래밍 도구 추가. Lisp/Haskell 커뮤니티의 영향. comp.lang.python 뉴스그룹 활성화. 2000 — Python 2.0 리스트 컴프리헨션, 가비지 컬렉터(순환 참조 감지), 유니코드 지원. 커뮤니티 주도 개발(PEP 프로세스) 시작. BeOpen.com으로 이전. 2008 — Python 3.0 (혁명적 변화) 하위 호환성을 깨는 대규모 개편. print 함수화, 유니코드 기본 문자열. 정수 나눗셈 변경, bytes/str 분리. 2.x → 3.x 마이그레이션 시작. 2020 — Python 2.x EOL (지원 종료) Python 2.7 최종 릴리스. 모든 프로젝트가 Python 3로 전환 권장. pip, Django, NumPy 등 주요 패키지 Python 2 지원 중단. 2023 — Python 3.12 / TIOBE 1위 성능 대폭 개선(3.11 대비 최대 5% 추가 향상). 타입 힌트 강화. TIOBE Index 사상 최초 1위 달성. AI 붐과 함께 사용자 급증.

파이썬 실행 구조

파이썬 코드가 실행되는 내부 과정을 이해하면 성능 최적화와 디버깅에 큰 도움이 됩니다. 파이썬은 순수 인터프리터 언어가 아니라, 먼저 소스 코드를 바이트코드로 컴파일한 뒤 가상 머신(PVM)에서 실행하는 하이브리드 방식을 사용합니다. .pyc 파일은 이 바이트코드가 캐싱된 것이며, __pycache__ 디렉터리에 저장됩니다.

Python 코드 실행 과정 소스 코드 .py 파일 사람이 읽는 코드 컴파일러 Lexer + Parser AST 생성 + 최적화 바이트코드 .pyc 파일 중간 표현 (IR) PVM Python VM 바이트코드 해석 실행 결과 Output CPython (기본) C로 구현된 표준 인터프리터 GIL로 스레드 안전성 보장 PyPy (JIT) JIT 컴파일로 고속 실행 반복 코드에서 수십 배 향상 Cython / Numba 네이티브 코드로 컴파일 C 수준 성능 달성 가능

파이썬 설치 가이드

파이썬은 python.org에서 공식 배포판을 다운로드하거나, 운영체제별 패키지 관리자를 통해 설치할 수 있습니다. 데이터 과학 용도라면 Anaconda 또는 Miniconda가 편리합니다. 설치 후 터미널에서 python --version을 입력하여 정상 설치를 확인하세요.

# Windows — python.org에서 다운로드 후 설치 # 설치 시 "Add Python to PATH" 체크 필수! # macOS — Homebrew 사용 brew install python # Ubuntu / Debian sudo apt update sudo apt install python3 python3-pip python3-venv # 설치 확인 python3 --version # Python 3.x.x pip3 --version # pip xx.x.x
가상 환경 권장: 프로젝트마다 독립된 패키지 환경을 만들어야 의존성 충돌을 방지할 수 있습니다. python -m venv myenv로 가상 환경을 생성하고, source myenv/bin/activate(Linux/Mac) 또는 myenv\Scripts\activate(Windows)로 활성화합니다. 최근에는 uvpoetry 같은 최신 패키지 매니저도 많이 사용됩니다.

파이썬 생태계와 커뮤니티

파이썬의 강력함은 언어 자체뿐만 아니라 거대한 생태계와 활발한 커뮤니티에서 비롯됩니다. PyPI(Python Package Index)에는 50만 개 이상의 패키지가 등록되어 있으며, GitHub에서 가장 많이 사용되는 언어 중 하나입니다. PyCon, DjangoCon 등의 컨퍼런스가 전 세계에서 열리고 있으며, Stack Overflow에서도 파이썬 관련 질문이 가장 활발합니다.

Python 생태계 구조 애플리케이션 (웹, AI, 자동화, 데이터 분석, IoT, 게임 ...) 웹 프레임워크 Django, Flask, FastAPI AI / 데이터 TensorFlow, Pandas, scikit-learn DevOps / 테스트 Ansible, pytest, Selenium PyPI (500,000+ 패키지)   |   pip / uv / poetry / conda 표준 라이브러리 (200+ 모듈): os, sys, json, re, http, unittest, typing, asyncio ... Python 인터프리터 (CPython / PyPy / GraalPy / ...)

PyPI (Python Package Index)

파이썬의 공식 서드파티 패키지 저장소입니다. pip install 패키지명 한 줄로 전 세계 개발자가 만든 50만 개 이상의 패키지를 즉시 설치할 수 있습니다. 웹 프레임워크, AI 라이브러리, 이미지 처리, 암호화 등 거의 모든 기능의 패키지가 존재합니다.

Jupyter Notebook

코드, 시각화, 설명 텍스트를 하나의 문서에 결합할 수 있는 대화형 개발 환경입니다. 데이터 과학, 머신러닝 연구, 교육 분야에서 사실상 표준 도구로 사용됩니다. Google Colab은 클라우드에서 무료로 Jupyter 환경을 제공합니다.

커뮤니티와 거버넌스

PSF(Python Software Foundation)가 언어 발전을 관리하며, PEP(Python Enhancement Proposals) 프로세스를 통해 새로운 기능이 제안되고 채택됩니다. 귀도 반 로섬 은퇴 후 Steering Council(5인 위원회)이 최종 결정을 내립니다.

주요 IDE / 에디터

VS Code(가장 인기, Python 확장 제공), PyCharm(JetBrains, 전문 IDE), Jupyter(대화형 분석), Vim/Neovim(터미널 기반). 각 도구는 자동 완성, 디버깅, 린팅, 가상환경 관리 등을 지원합니다.

초급자 가이드 - 파이썬을 왜 배워야 할까?

파이썬은 프로그래밍을 처음 접하는 분에게 가장 권장되는 언어입니다. 그 이유는:

  • 쉬운 문법: 영어 문장을 읽듯 코드를 읽을 수 있습니다. 예를 들어 if age >= 18: print("성인")은 "만약 나이가 18 이상이면 '성인'을 출력하라"와 같습니다.
  • 설치 후 바로 시작: python.org에서 설치 후 터미널에 python을 입력하면 바로 코드를 실행할 수 있습니다.
  • 방대한 학습 자료: 한국어 강좌, 책, 유튜브 영상 등 풍부한 자료가 있어 독학이 용이합니다.
  • 높은 취업 수요: 데이터 분석, AI, 웹 개발, 자동화 등 거의 모든 IT 분야에서 파이썬 개발자를 찾고 있습니다.

처음 시작하는 팁: Python을 설치한 후 터미널(또는 명령 프롬프트)에서 python을 입력하면 대화형 모드(REPL)가 시작됩니다. 여기서 print("Hello!")를 입력해보세요. 이것이 여러분의 첫 번째 파이썬 프로그램입니다!

중급자 가이드 - 파이썬의 실무 활용

기본 문법을 익혔다면, 파이썬이 실무에서 어떻게 활용되는지 이해하는 것이 중요합니다:

  • 웹 개발: Django(대규모 서비스, Instagram/Pinterest 사용), Flask(경량 API), FastAPI(고성능 비동기 API)로 백엔드를 구축합니다.
  • 데이터 엔지니어링: Apache Airflow(워크플로 자동화), PySpark(대규모 데이터 처리)로 데이터 파이프라인을 구축합니다.
  • DevOps/자동화: Ansible, Fabric으로 서버 관리를 자동화하고, Selenium으로 웹 테스트를 자동화합니다.
  • 패키지 관리: 실무에서는 venv 대신 poetryuv를 사용하여 의존성을 더 체계적으로 관리하는 추세입니다.
고급자 가이드 - 파이썬의 내부 구조와 한계

파이썬의 내부 동작 원리를 이해하면 더 효율적인 코드를 작성할 수 있습니다:

  • CPython 바이트코드: Python 소스코드는 먼저 바이트코드(.pyc)로 컴파일되고, 이후 CPython VM이 이를 해석 실행합니다. dis 모듈로 바이트코드를 확인할 수 있습니다. (import dis; dis.dis(함수명))
  • GIL의 실제 영향: GIL은 CPU-bound 작업에서만 병목이 됩니다. I/O-bound 작업(네트워크, 파일)에서는 GIL이 자동 해제되어 멀티스레딩이 효과적입니다. CPU-bound는 multiprocessing이나 C 확장 모듈을 사용하세요.
  • 메모리 관리: CPython은 참조 카운팅(Reference Counting)과 세대별 가비지 컬렉터(Generational GC)를 사용합니다. 순환 참조는 GC가 처리하지만, __del__ 메서드가 있으면 수집이 지연될 수 있습니다.
  • 성능 한계 극복: 성능이 중요한 부분은 Cython, ctypes, pybind11로 C/C++ 확장을 만들거나, Numba JIT 컴파일러를 사용하여 수십~수백 배 속도 향상이 가능합니다.

파이썬 인터프리터 종류

파이썬은 언어 사양(specification)과 구현체(implementation)가 분리되어 있습니다. 동일한 파이썬 코드를 다양한 인터프리터에서 실행할 수 있으며, 각 인터프리터는 다른 플랫폼이나 성능 요구사항에 최적화되어 있습니다. 이 분리 덕분에 다양한 실행 환경(JVM, .NET, 브라우저 등)에서 파이썬 코드를 재사용할 수 있습니다.

Python 인터프리터 생태계 Python 소스 코드 (.py) CPython C 기반 | 표준 구현체 python.org 기본 PyPy JIT | 고속 실행 수~수십 배 빠름 Jython JVM | Java 통합 Java 라이브러리 호출 IronPython .NET CLR | C# 통합 .NET 생태계 연동 Brython JS 기반 | 브라우저용 웹 프론트엔드 실행 하나의 Python 소스 코드를 다양한 플랫폼에서 실행할 수 있습니다

CPython

C로 작성된 공식 인터프리터. Reference 구현체로 사실상 표준이며, python.org에서 다운로드하는 것이 바로 CPython입니다. GIL(Global Interpreter Lock)을 사용하는데, 이는 한 번에 하나의 스레드만 Python 바이트코드를 실행하도록 제한하는 뮤텍스입니다. 메모리 관리의 안전성을 보장하지만, CPU 집약적 작업에서 멀티스레드 병렬 실행이 제한되는 단점이 있습니다.

PyPy

JIT(Just-In-Time) 컴파일러를 내장한 고성능 인터프리터. 반복 연산이 많은 코드에서 CPython 대비 수~수십 배 빠른 성능을 제공합니다.

Jython

JVM(Java Virtual Machine) 위에서 실행되는 인터프리터. Java 라이브러리를 직접 호출할 수 있어 Java 생태계와의 통합에 유용합니다.

IronPython

.NET CLR(Common Language Runtime) 위에서 실행되며, C#이나 VB.NET 등 .NET 라이브러리와 상호 운용이 가능합니다.

Brython

JavaScript로 구현된 브라우저용 인터프리터. HTML 페이지 내에서 Python 코드를 직접 실행할 수 있어 웹 프론트엔드 개발에 활용됩니다.

Python 2.x vs 3.x 차이점

권장: 이제 Python 3.x 이상을 기준으로 학습하세요. Python 2.x는 2020년 1월 1일에 공식 지원이 종료(EOL)되었으며, 보안 패치도 더 이상 제공되지 않습니다. 모든 주요 라이브러리(Django, NumPy, Pandas 등)가 Python 2 지원을 중단했습니다.
Python 2 vs Python 3 주요 차이 Python 2.x (지원 종료) print "hello" 5 / 2 = 2 (정수 나눗셈) u"유니코드 문자열" xrange() (메모리 절약) raw_input() (사용자 입력) except Error, e: (구문) has_key() 딕셔너리 메서드 Python 3.x (현재 표준) print("hello") 함수 호출 5 / 2 = 2.5 (실수 나눗셈) "기본이 유니코드 문자열" range() (이터레이터 반환) input() (항상 문자열 반환) except Error as e: (구문) key in dict (in 연산자)

Python 3.14 새로운 기능 (2025년 10월 출시)

Python 3.14는 성능 개선, 새로운 문법, 디버깅 도구 등 다양한 혁신을 포함합니다. 특히 GIL 제거를 향한 Free-threaded 모드와 Template Strings는 파이썬 생태계에 큰 변화를 가져올 핵심 기능입니다.

PEP 750: Template Strings

t"..." 접두사로 템플릿 문자열 생성. f-string과 달리 즉시 평가되지 않아 SQL 인젝션 방지 등 보안이 필요한 곳에 활용됩니다.

PEP 734: Multiple Interpreters

하나의 프로세스 내에서 여러 독립적인 인터프리터를 실행하여 GIL 제약 없이 진정한 병렬 처리를 구현할 수 있습니다.

PEP 649: Deferred Annotations

타입 어노테이션이 런타임에 필요할 때까지 평가를 지연시켜 순환 참조 문제를 해결하고 모듈 로딩 속도를 개선합니다.

PEP 768: Debugger Interface

외부 프로세스에서 안전하게 디버거를 연결할 수 있는 표준 인터페이스를 제공하여 프로덕션 환경의 디버깅을 용이하게 합니다.

PEP 784: Zstandard

Facebook이 개발한 고성능 Zstandard 압축 알고리즘을 표준 라이브러리에 포함하여 별도 설치 없이 빠른 데이터 압축/해제가 가능합니다.

Free-threaded Mode

GIL(Global Interpreter Lock)을 비활성화하여 멀티코어 CPU를 최대한 활용하는 진정한 멀티스레딩 실행이 가능합니다. 실험적 기능으로 점진적 안정화 중입니다.

주요 CLI 옵션

파이썬 인터프리터는 명령줄에서 다양한 옵션을 지원합니다. 이 옵션들을 활용하면 디버깅, 최적화, 스크립트 실행 방식을 세밀하게 제어할 수 있습니다.

옵션설명
python인자 없이 실행: 대화형 인터프리터
-d디버그 출력
-O최적화 모드 (assert 제거, __pycache__에 최적화된 .pyc 생성)
-v자세한 출력 (import 추적)
-c "cmd"명령어 실행
-S시작 시 import site 건너뛰기

파이썬 환경변수

환경변수를 통해 파이썬 인터프리터의 동작을 전역적으로 설정할 수 있습니다. 모듈 검색 경로, 인코딩 설정 등을 OS 수준에서 지정합니다.

변수설명
PYTHONPATH모듈 파일 위치
PYTHONSTARTUP인터프리터 시작 시 실행 파일
PYTHONHOME모듈 검색 경로
PYTHONCASEOK대소문자 구분 안 함 (Windows)

예약어 (Keywords)

다음 예약어는 변수나 식별자로 사용할 수 없습니다 (Python 3.10+ 기준):

False, None, True, and, as, assert, async, await, break, class, continue, def, del, elif, else, except, finally, for, from, global, if, import, in, is, lambda, match, case, nonlocal, not, or, pass, raise, return, try, while, with, yield

연산자 우선순위

여러 연산자가 하나의 표현식에 함께 사용될 때, 어떤 연산이 먼저 수행되는지를 결정하는 규칙입니다. 숫자가 작을수록 우선순위가 높습니다. 괄호 ()를 사용하면 우선순위를 명시적으로 지정할 수 있습니다. 할당 연산자(=, += 등)는 표현식이 아닌 문(statement)이므로 이 표에 포함되지 않습니다.

우선순위연산자설명
1 (최고)(expr), [list], {dict}, {set}괄호, 리터럴
2x[i], x.attr, x()인덱싱, 속성 참조, 호출
3await xAwait 표현식
4**지수 (거듭제곱)
5+x, -x, ~x단항 양수, 음수, 비트 NOT
6*, /, //, %, @곱셈, 나눗셈, 나머지, 행렬곱
7+, -덧셈, 뺄셈
8<<, >>비트 시프트
9&비트 AND
10^비트 XOR
11|비트 OR
12==, !=, <, <=, >, >=, is, is not, in, not in비교, 식별, 멤버십
13not논리 NOT
14and논리 AND
15or논리 OR
16if – else조건 표현식
17lambda람다 표현식
18 (최저):=Walrus 연산자

2. 기초 문법

초급

파이썬의 기초 문법은 다른 언어에 비해 직관적이고 자연어에 가깝게 설계되어 있습니다. 중괄호({}) 대신 들여쓰기로 코드 블록을 구분하고, 세미콜론(;) 없이도 한 줄이 하나의 문장이 됩니다. 이런 설계 덕분에 파이썬 코드는 마치 영어 문장을 읽는 것처럼 읽히며, 초보자도 빠르게 프로그래밍의 핵심 개념을 익힐 수 있습니다.

초급자 가이드 - 기초 문법의 핵심

파이썬 기초 문법에서 가장 중요한 3가지를 기억하세요:

  • 들여쓰기가 곧 문법: C, Java에서는 중괄호 {}로 코드 블록을 구분하지만, 파이썬은 들여쓰기(보통 스페이스 4칸)로 구분합니다. 탭과 스페이스를 섞으면 에러가 납니다!
  • 변수 선언이 필요 없다: int x = 5;처럼 타입을 명시하지 않고, 그냥 x = 5로 작성하면 됩니다. 파이썬이 자동으로 타입을 결정합니다.
  • 세미콜론이 필요 없다: 한 줄이 하나의 문장입니다. 줄바꿈 자체가 구분자 역할을 합니다.

흔한 실수: if문 뒤에 콜론(:)을 빼먹는 것입니다. if x > 0:처럼 반드시 콜론을 붙여야 합니다.

중급자 가이드 - PEP 8 코딩 스타일

실무에서는 PEP 8 스타일 가이드를 준수하는 것이 중요합니다. 팀 협업 시 코드 일관성을 유지하는 핵심 규칙:

  • 네이밍 규칙: 변수/함수는 snake_case, 클래스는 PascalCase, 상수는 UPPER_SNAKE_CASE
  • 한 줄 길이: 최대 79자(PEP 8 권장). 현대적 프로젝트에서는 100~120자까지 허용하기도 합니다.
  • import 순서: 표준 라이브러리 → 서드파티 → 로컬 모듈 순서로 작성하고, 그룹 사이에 빈 줄을 넣습니다.
  • 자동 도구: black(코드 포매터), isort(import 정렬), flake8(린터), ruff(올인원 린터)를 사용하면 스타일을 자동으로 맞출 수 있습니다.
고급자 가이드 - 파이썬 실행 모델의 이해

파이썬의 실행 과정을 깊이 이해하면 디버깅과 최적화에 도움이 됩니다:

  • 모든 것이 객체: 정수 42도, 함수 print도, 모듈 os도 모두 객체입니다. id()로 메모리 주소를, type()으로 타입 객체를 확인할 수 있습니다.
  • 변수는 이름표(label): 파이썬 변수는 C의 변수와 다릅니다. 값을 담는 "상자"가 아니라 객체를 가리키는 "이름표"입니다. a = [1,2,3]; b = a에서 ab는 같은 리스트 객체를 가리키며, id(a) == id(b)입니다.
  • 정수 캐싱(Integer Interning): CPython은 -5~256 범위의 정수를 미리 캐싱합니다. 이 범위의 정수는 is 비교가 True이지만, 그 밖의 정수는 False일 수 있습니다. 값 비교에는 항상 ==를 사용하세요.
  • 문자열 인터닝(String Interning): 식별자처럼 보이는 문자열(영문/숫자/밑줄로만 구성)은 자동으로 인터닝되어 동일 객체를 재사용합니다.

Hello, World! 출력하기

프로그래밍의 첫걸음은 언제나 "Hello, World!"를 출력하는 것입니다.

print("Hello, World!") # 결과: Hello, World!
💡 팁: 파이썬에서 print()는 함수의 일종입니다. 괄호 안에 넣은 내용을 화면에 출력해줍니다.

변수와 주석

변수는 데이터를 저장하는 공간입니다. 주석은 코드에 설명을 추가하는 데 사용됩니다.

# 변수 선언의 예 name = "민준" age = 25 height = 175.5 print(name) # 민준 print(age) # 25

주석 (Comments)과 독스트링 (Docstrings)

#으로 시작하는 주석(comment)은 인터프리터가 완전히 무시하는 메모입니다. 반면, 삼중 따옴표(""" 또는 ''')로 작성하는 독스트링(docstring)은 실제로는 문자열 리터럴이며, 함수·클래스·모듈의 첫 줄에 오면 __doc__ 속성에 저장되어 help()로 조회할 수 있습니다. 독스트링은 주석처럼 보이지만 런타임에 존재하는 문자열이라는 점이 핵심 차이입니다.

# 한 줄 주석입니다 (인터프리터가 무시) def greet(name): """인사말을 반환합니다. 이것은 독스트링(docstring)으로, 함수의 __doc__ 속성에 저장됩니다. help(greet)로 확인할 수 있습니다. """ return f"안녕, {name}!" print(greet.__doc__) # 독스트링 출력

여러 줄로 구문 작성

긴 코드를 여러 줄에 걸쳐 작성할 수 있습니다. 백슬래시(\)를 사용하거나 괄호([], {}, ()) 안에서는 자동으로 줄이 연결됩니다. 세미콜론(;)으로 한 줄에 여러 구문을 작성할 수도 있지만, PEP 8에서는 권장하지 않습니다.

# \(백슬래시)로 줄 연결 a = 1 + \ 2 + \ 3 # [], {}, ()는 자동으로 연결 numbers = [1, 2, 3, 4, 5] # 한 줄에 여러 구문 (세미콜론) x = 1; y = 2; print(x + y)

식별자 (Identifiers)

식별자는 변수, 함수, 클래스 등에 붙이는 이름입니다. 파이썬의 명명 규칙은 PEP 8을 따르며, 언더스코어의 개수에 따라 특별한 의미를 가집니다.

  • 영문자, 숫자, _(밑줄) 사용 가능 (숫자로 시작 불가)
  • 대소문자 엄격히 구분
  • Python 3.x부터 한글 변수명 사용 가능
  • _var : 관례적으로 내부 사용(private)을 의미하며, from module import *에서 제외됩니다.
  • __var : 이름 맹글링(name mangling) 적용. 클래스 내에서 _ClassName__var로 변환되어 하위 클래스와의 이름 충돌을 방지합니다.
  • __var__ : 던더(dunder, double underscore) 또는 매직 메서드/속성이라 불립니다. __init__, __str__, __len__ 등 Python이 특별한 용도로 예약한 이름입니다. 직접 새로 정의하지 않는 것이 좋습니다.
  • var_ : Python 예약어와의 충돌을 피하기 위한 관례 (예: class_, type_)

들여쓰기 (Indentation)

중요: 파이썬은 들여쓰기로 블록을 구분합니다. 콜론(:) 뒤에 들여쓰기를 하세요.
if x > 0: print("양수") # 4칸 들여쓰기 if x > 10: print("큰 양수") # 8칸 들여쓰기

첫 줄 Shebang과 인코딩

Unix/Linux 환경에서 스크립트를 직접 실행하려면 첫 줄에 Shebang(#!)을 추가합니다. 인코딩 선언(# -*- coding: utf-8 -*-)은 Python 3에서는 기본이 UTF-8이므로 생략 가능하지만, Python 2 호환이나 명시적 선언이 필요할 때 사용합니다.

#!/usr/bin/env python # -*- coding: utf-8 -*- print("안녕하세요")
name "민준" 메모리 공간 (객체) 변수 (이름표) 값 (객체) 참조

Walrus 연산자 (:=)

Python 3.8부터 도입된 Walrus 연산자(:=)는 표현식 내에서 변수에 값을 할당하면서 동시에 그 값을 반환합니다. := 기호가 바다코끼리(walrus)의 눈과 엄니를 닮았다고 하여 이 이름이 붙었습니다. 조건문이나 반복문에서 중복 호출을 줄이는 데 유용합니다.

# 기존 방식 result = some_function() if result: print(result) # Walrus 연산자 사용 if (result := some_function()): print(result) # 리스트 comprehension에서의 사용 if (n := int("42")) > 40: print(f"숫자는 {n}입니다")

3. 자료형 (Data Types)

초급

파이썬은 동적 타이핑(Dynamic Typing) 언어로, 변수를 선언할 때 자료형을 명시하지 않아도 됩니다. 인터프리터가 대입된 값에 따라 자동으로 자료형을 결정합니다. 파이썬의 모든 데이터는 객체(object)이며, type() 함수로 자료형을 확인할 수 있습니다. 기본 자료형은 크게 숫자형(int, float, complex), 시퀀스형(str, list, tuple), 매핑형(dict), 집합형(set, frozenset), 불리언형(bool), None으로 분류됩니다.

Python 자료형 숫자형 int, float complex, bool 시퀀스형 str, list tuple, range 매핑형 dict 집합형 set frozenset 기타 None, bytes bytearray 불변(Immutable): int, str, tuple, frozenset 가변(Mutable): list, dict, set, bytearray

숫자 (Numbers)

파이썬은 정수(int), 실수(float), 복소수(complex) 세 가지 숫자 자료형을 지원합니다. 정수는 크기 제한이 없어 아무리 큰 수도 표현할 수 있으며(메모리가 허용하는 한), 2진수(0b), 8진수(0o), 16진수(0x) 리터럴도 사용 가능합니다.

# 정수 (Integer) integer = 42 # 실수 (Float) float_num = 3.14 scientific = 3.4e10 # 지수 표기 # 복소수 (Complex) complex_num = 3 + 4j print(complex_num.real) # 3.0 (실수부) print(complex_num.imag) # 4.0 (허수부) print(complex_num.conjugate()) # (3-4j) (켤레복소수) # 8진수 (0o으로 시작) octal = 0o34 # 16진수 (0x로 시작) hex_num = 0xFF

문자열 (String)

문자열은 작은따옴표('...') 또는 큰따옴표("...")로 생성하며, 삼중 따옴표("""...""")로 여러 줄 문자열을 만들 수 있습니다. 파이썬 3에서 문자열은 기본적으로 유니코드(Unicode)이므로 한글 등 다국어 처리가 자연스럽습니다. f-string(Python 3.6+)을 사용하면 변수를 문자열에 직접 삽입할 수 있습니다.

single = '작은따옴표' double = "큰따옴표" multi_line = """여러 줄 문자열""" # 문자열 포맷팅 name = "철수" print(f"안녕하세요, {name}님!") # 안녕하세요, 철수님!

문자열 주요 메서드

파이썬 문자열은 불변(immutable) 객체이므로 모든 메서드는 원본을 수정하지 않고 새로운 문자열을 반환합니다. 다음은 가장 자주 사용되는 문자열 메서드들입니다.

메서드설명
.upper()대문자 변환
.lower()소문자 변환
.strip()좌우 공백 제거
.split()분할 (리스트 반환)
.join()합치기
.replace()치환
.find()위치 찾기 (-1 if not found)
.index()위치 찾기 (오류 if not found)
.count()개수 세기
.startswith()시작 문자열 확인
.endswith()끝 문자열 확인
.isalpha()영문자 여부
.isdigit()숫자 여부
.splitlines()줄바꿈으로 분할
.zfill()0으로 패딩

문자열 슬라이싱

슬라이싱은 [시작:끝:간격] 문법으로 문자열의 일부를 추출합니다. 인덱스는 0부터 시작하며, 음수 인덱스는 끝에서부터 역순으로 셉니다. 끝 인덱스는 해당 위치를 포함하지 않습니다.

s = "Hello Python" s[0] # 'H' (인덱싱) s[0:5] # 'Hello' (슬라이싱, 끝 제외) s[6:] # 'Python' (6부터 끝까지) s[:5] # 'Hello' (처음부터 5까지) s[-1] # 'n' (마지막 문자) s[0:10:2] # 'HloPto' (step 2) s[::-1] # 'nohtyP olleH' (역순)

Escape 문자

이스케이프 문자는 백슬래시(\)와 함께 사용되어 줄바꿈, 탭 등 특수 문자를 표현합니다. 이스케이프 시퀀스를 무시하려면 문자열 앞에 r(raw string)을 붙입니다. 예: r"C:\new"

코드설명
\n줄바꿈
\t
\\백슬래시
\'작은따옴표
\"큰따옴표
\r캐리지 리턴
\b백스페이스

리스트와 튜플

리스트(list)는 순서가 있고 수정 가능한(mutable) 시퀀스로 []로 생성합니다. 튜플(tuple)은 순서가 있지만 수정 불가능한(immutable) 시퀀스로 ()로 생성합니다. 튜플은 변경되면 안 되는 데이터(좌표, DB 레코드 등)에 사용하며, 딕셔너리의 키로도 활용할 수 있습니다.

# 리스트 (수정 가능) - [] fruits = ["사과", "바나나", "딸기"] fruits[0] = "포도" # 가능 # 튜플 (수정 불가능) - () coordinates = (10, 20, 30) # coordinates[0] = 5 # 오류 발생!

리스트 주요 메서드

리스트는 가변(mutable) 객체이므로 메서드를 통해 원본을 직접 수정할 수 있습니다. append(), insert() 등은 리스트 자체를 변경하고 None을 반환한다는 점에 주의하세요.

메서드설명예시
.append(x)끝에 추가[1,2].append(3) → [1,2,3]
.extend(iterable)iterable 추가[1].extend([2,3]) → [1,2,3]
.insert(i, x)위치에 삽입[1,3].insert(1,2) → [1,2,3]
.remove(x)첫 번째 x 제거[1,2,3].remove(2) → [1,3]
.pop([i])위치 값 제거/반환[1,2,3].pop() → 3
.clear()모두 제거[1,2].clear() → []
.index(x)첫 번째 x 인덱스[1,2,3].index(2) → 1
.count(x)x 개수[1,2,2].count(2) → 2
.sort(*, key, reverse)정렬[3,1,2].sort() → [1,2,3]
.reverse()역순[1,2,3].reverse() → [3,2,1]
.copy() shallow 복사[1,2].copy() → [1,2]

List Comprehension (리스트 내포)

리스트 컴프리헨션은 한 줄로 리스트를 생성하는 파이썬의 강력한 문법입니다. [표현식 for 변수 in 반복가능객체 if 조건] 형식으로 작성하며, for 루프보다 간결하고 일반적으로 더 빠릅니다. 중첩 컴프리헨션으로 다차원 리스트도 생성할 수 있습니다.

# 기본 squares = [x**2 for x in range(5)] # [0, 1, 4, 9, 16] # 조건 포함 evens = [x for x in range(10) if x % 2 == 0] # [0, 2, 4, 6, 8] # 중첩 matrix = [[j for j in range(3)] for i in range(3)]

딕셔너리 (Dictionary)

딕셔너리는 키-값(key-value) 쌍으로 데이터를 저장하는 자료형입니다. {}로 생성하며, 키를 통해 O(1) 시간복잡도로 값에 접근할 수 있습니다. Python 3.7부터 삽입 순서가 보장되며, 키는 불변(hashable) 객체만 사용 가능합니다.

# 딕셔너리 생성 (키-값 쌍) person = {"이름": "민준", "나이": 25, "도시": "서울"} # 값 접근 print(person["이름"]) # 민준 print(person.get("나이")) # 25 (키 없을 때 None 반환) print(person.get("직업", "미정")) # 미정 (기본값 지정) # 추가 / 수정 person["직업"] = "개발자" person.update({"나이": 26, "취미": "독서"}) # 삭제 del person["취미"] person.pop("직업", None) # 키 없어도 오류 없음 # 순회 for key in person.keys(): print(key) for value in person.values(): print(value) for key, value in person.items(): print(f"{key}: {value}") # 딕셔너리 컴프리헨션 squares = {x: x**2 for x in range(5)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} # 딕셔너리 병합 (Python 3.9+) d1 = {"a": 1} d2 = {"b": 2} merged = d1 | d2 # {"a": 1, "b": 2}

집합 컴프리헨션 (Set Comprehension)

리스트 컴프리헨션과 동일한 문법이지만 {}를 사용하여 중복 없는 집합을 생성합니다. 소괄호 ()를 사용하면 제너레이터 표현식이 되어 한 번에 모든 값을 메모리에 저장하지 않고 필요할 때 하나씩 생성하므로 메모리 효율적입니다.

# 집합 컴프리헨션 unique_squares = {x**2 for x in [-2, -1, 0, 1, 2]} # {0, 1, 4} # 제너레이터 표현식 (메모리 효율) gen = (x**2 for x in range(10)) # next(gen)으로 하나씩 꺼냄
List (리스트) [] "사과" "바나나" "딸기" ✏️ 수정 가능 Tuple (튜플) () "사과" "바나나" "딸기" 🔒 수정 불가 Dict (사전) {"key": "value"} "이름": "민준" "나이": 25 "도시": "서울" 🔑 키-값 쌍

집합 (Set)

집합(set)은 중복을 허용하지 않고 순서가 없는 자료형입니다. 수학의 집합 연산(합집합, 교집합, 차집합, 대칭차집합)을 지원하며, 멤버십 테스트(in)가 리스트보다 훨씬 빠릅니다(O(1)). 빈 집합은 set()으로 생성합니다({}는 빈 딕셔너리).

# 집합 (중복 불가, 순서 없음) s = {1, 2, 3} s.add(4) # 추가 s.remove(2) # 제거 # 집합 연산 a = {1, 2, 3} b = {2, 3, 4} print(a | b) # 합집합: {1, 2, 3, 4} print(a & b) # 교집합: {2, 3} print(a - b) # 차집합: {1}

None (Null)

None은 파이썬의 '값이 없음'을 나타내는 특별한 싱글턴 객체입니다. 다른 언어의 null에 해당하며, 함수가 명시적으로 값을 반환하지 않을 때도 None이 반환됩니다. None 비교는 == 대신 is를 사용하는 것이 권장됩니다.

x = None print(x is None) # True print(x == None) # True (하지만 is를 권장)

불변 vs 가변 객체

파이썬 객체는 생성 후 값을 변경할 수 있는지에 따라 가변(mutable)불변(immutable)으로 나뉩니다. 불변 객체를 수정하려 하면 새로운 객체가 생성되며, 가변 객체는 원본이 직접 변경됩니다. 이 차이는 함수 인자 전달, 복사, 딕셔너리 키 사용 등에서 중요합니다.

  • 불변: int, float, str, tuple, frozenset
  • 가변: list, dict, set
초급자 가이드 - 자료형 선택 기준

어떤 자료형을 사용해야 할지 고민될 때 참고하세요:

  • 숫자를 다룰 때int(정수), float(소수점). 대부분의 경우 이 두 가지로 충분합니다.
  • 글자/문장을 다룰 때str(문자열). 작은따옴표든 큰따옴표든 상관없습니다.
  • 여러 값을 순서대로 저장할 때list(리스트). 가장 자주 사용하는 자료형입니다.
  • 이름표(키)로 값을 찾을 때dict(딕셔너리). 전화번호부처럼 "이름 → 번호"의 관계를 저장합니다.
  • 참/거짓을 나타낼 때bool. True 또는 False 값만 가집니다.

타입 변환: int("42")는 문자열을 정수로, str(42)는 정수를 문자열로, float("3.14")는 문자열을 실수로 변환합니다.

중급자 가이드 - 자료형의 성능과 실무 패턴

자료형마다 연산별 시간복잡도가 다릅니다. 올바른 자료형 선택이 성능에 큰 영향을 미칩니다:

  • 리스트 vs 집합: in 연산에서 리스트는 O(n), 집합(set)은 O(1)입니다. 멤버십 검사가 빈번하면 set을 사용하세요.
  • 딕셔너리 컴프리헨션: {k: v for k, v in pairs}로 딕셔너리를 간결하게 생성할 수 있습니다.
  • 언패킹(Unpacking): a, b, *rest = [1, 2, 3, 4, 5]에서 a=1, b=2, rest=[3,4,5]입니다.
  • 딕셔너리 병합(3.9+): merged = dict1 | dict2로 두 딕셔너리를 병합할 수 있습니다.
  • defaultdict 활용: collections.defaultdict(list)를 사용하면 키 존재 여부를 확인하지 않고도 안전하게 값을 추가할 수 있습니다.
고급자 가이드 - 자료형의 내부 구현

CPython의 자료형 구현을 이해하면 성능 최적화에 도움이 됩니다:

  • 리스트 내부: CPython의 리스트는 포인터 배열(array of pointers)로 구현됩니다. append()는 평균 O(1)이지만, 배열이 가득 차면 약 1.125배 크기로 재할당합니다. insert(0, x)는 모든 요소를 이동하므로 O(n)입니다.
  • 딕셔너리 내부: CPython 3.6+에서 딕셔너리는 compact dict 구현을 사용합니다. 해시 테이블 + 삽입 순서 배열로 구성되어, 삽입 순서가 보장되면서도 이전보다 20-25% 메모리를 절약합니다.
  • 문자열 인코딩: CPython 3.3+에서 문자열은 내용에 따라 Latin-1(1바이트), UCS-2(2바이트), UCS-4(4바이트) 중 가장 효율적인 인코딩을 자동 선택합니다(PEP 393 Flexible String Representation).
  • __slots__: 클래스에 __slots__를 정의하면 __dict__ 대신 고정 크기 배열을 사용하여 인스턴스당 수십 바이트의 메모리를 절약합니다. 수백만 개의 객체를 생성할 때 유용합니다.
  • array 모듈: 동일 타입의 숫자만 저장할 때 array.array를 사용하면 리스트 대비 메모리를 4-8배 절약합니다.

4. 제어문

초급 중급

제어문은 프로그램의 실행 흐름을 제어하는 구문입니다. 조건문(if-elif-else)으로 조건에 따라 다른 코드를 실행하고, 반복문(for, while)으로 코드를 반복 실행합니다. Python 3.10부터는 Match-Case(구조적 패턴 매칭)를 통해 복잡한 데이터 구조의 형태를 매칭하여 분기할 수 있습니다. break, continue, pass 등의 키워드로 반복 흐름을 세밀하게 제어합니다.

조건문 (if-elif-else)

조건문은 주어진 조건의 참/거짓에 따라 코드 실행 흐름을 분기합니다. if로 시작하고, 추가 조건은 elif(else if의 축약), 모든 조건에 해당하지 않는 경우 else를 사용합니다. 파이썬에는 C/Java의 삼항 연산자 대신 값1 if 조건 else 값2 형태의 조건부 표현식이 있습니다.

score = 85 if score >= 90: grade = "A" elif score >= 80: grade = "B" elif score >= 70: grade = "C" else: grade = "F" print(f"학점: {grade}") # 학점: B

반복문 (for, while)

for 문은 시퀀스(리스트, 문자열, range 등)의 각 요소를 순회하며 실행합니다. while 문은 조건이 참인 동안 반복 실행합니다. range(n)은 0부터 n-1까지의 정수 시퀀스를 생성하며, range(start, stop, step)으로 시작값, 끝값, 간격을 지정할 수 있습니다.

# for 반복문 for i in range(5): print(i) # 0, 1, 2, 3, 4 # while 반복문 count = 0 while count < 3: print(f"카운트: {count}") count += 1

Match-Case (Structural Pattern Matching)

Python 3.10에서 도입된 구조적 패턴 매칭은 C/Java의 switch-case보다 훨씬 강력합니다. 단순 값 비교뿐 아니라 튜플, 리스트, 클래스 인스턴스 등 데이터 구조의 형태를 매칭하고, 매칭된 부분을 변수에 바인딩할 수 있습니다. 와일드카드 _는 기본(default) 케이스에 사용됩니다.

def http_status(status): match status: case 200: return "OK" case 404: return "Not Found" case 500: return "Server Error" case _: return "Unknown" # 패턴 매칭 with tuple/list def location(point): match point: case (0, 0): return "원점" case (x, 0): return f"x축 위: {x}" case (x, y): return f"점 ({x}, {y})"

Break, Continue, Else (반복문)

break는 반복문을 즉시 종료하고, continue는 현재 반복을 건너뛰고 다음 반복으로 진행합니다. 파이썬의 독특한 기능으로, for/while 뒤에 else 블록을 붙일 수 있으며, 이는 break 없이 반복이 정상 완료되었을 때만 실행됩니다.

for i in range(10): if i == 3: continue # 다음 반복으로 if i == 7: break # 반복 종료 print(i) else: print("완료") # break로 나가면 실행 안됨
시작 조건 확인? Yes 실행 No 종료 반복
💡 팁: range() 함수는 숫자 시퀀스를 생성합니다. range(5)는 0부터 4까지입니다.
초급자 가이드 - 제어문의 일상적 비유

제어문을 일상생활에 비유하면 쉽게 이해할 수 있습니다:

  • if문 = 교차로: "비가 오면 우산을 챙기고, 아니면 그냥 나간다" → if 비: 우산() else: 외출()
  • for문 = 체크리스트: "장바구니의 각 물건에 대해 가격을 확인한다" → for 물건 in 장바구니: 가격확인(물건)
  • while문 = 반복 알람: "목표에 도달할 때까지 계속 시도한다" → while not 목표달성: 시도()

주의: while True:는 무한 루프를 만듭니다. 반드시 break로 탈출 조건을 설정하세요. 무한 루프에 빠지면 Ctrl+C로 중단할 수 있습니다.

중급자 가이드 - 파이썬다운(Pythonic) 반복 패턴

인덱스 기반 반복 대신 파이썬다운 반복 패턴을 사용하세요:

  • enumerate(): 인덱스가 필요할 때 for i, v in enumerate(리스트):를 사용합니다. for i in range(len(리스트)):는 비-파이썬적(unpythonic)입니다.
  • zip(): 두 리스트를 동시에 순회할 때 for a, b in zip(리스트1, 리스트2):를 사용합니다.
  • 삼항 연산자: result = "짝수" if x % 2 == 0 else "홀수"로 간결하게 조건 분기합니다.
  • any() / all(): if any(x > 10 for x in numbers):로 "하나라도 만족하면", if all(x > 0 for x in numbers):로 "모두 만족하면"을 표현합니다.
  • for-else 실전 활용: 소수 판별 시 for i in range(2, n): 반복 후 else에서 "소수임"을 확인하는 패턴이 대표적입니다.
고급자 가이드 - 반복 성능 최적화와 Match-Case 심화

대규모 데이터 처리 시 반복문 성능을 최적화하는 기법과 패턴 매칭 고급 활용법:

  • 컴프리헨션 vs for 루프: 리스트 컴프리헨션은 일반 for 루프보다 약 20-30% 빠릅니다. C 레벨에서 최적화된 LIST_APPEND 바이트코드를 사용하기 때문입니다.
  • 제너레이터 표현식: 메모리 절약이 필요하면 [...] 대신 (...)를 사용하세요. sum(x**2 for x in range(10**7))은 리스트를 생성하지 않아 메모리를 절약합니다.
  • itertools 활용: itertools.chain()으로 여러 이터러블을 연결하고, itertools.islice()로 슬라이싱하면 대용량 데이터를 메모리 효율적으로 처리할 수 있습니다.
  • Match-Case 클래스 패턴: case Point(x=0, y=y):처럼 클래스 인스턴스의 속성을 매칭하고 추출할 수 있습니다. __match_args__를 정의하면 위치 기반 매칭도 가능합니다.
  • Match-Case 가드(Guard): case x if x > 0:처럼 if 가드를 사용하여 패턴 매칭에 추가 조건을 걸 수 있습니다.

5. 함수 (Functions)

초급 중급

함수는 특정 작업을 수행하는 코드 블록을 하나로 묶어 이름을 부여한 것입니다. 함수를 사용하면 동일한 코드를 여러 번 작성할 필요 없이 호출만으로 재사용할 수 있고, 프로그램을 논리적 단위로 분리하여 가독성과 유지보수성을 크게 높일 수 있습니다. 파이썬에서 함수는 일급 객체(First-class Object)로, 변수에 할당하거나 다른 함수의 인자로 전달하는 등 유연하게 활용할 수 있습니다. 또한 lambda, 클로저(closure), 데코레이터(decorator), 제너레이터(generator) 등 다양한 고급 기법을 지원합니다.

기본 함수 정의

함수는 def 키워드로 정의하며, 함수명 뒤 괄호 안에 매개변수를 선언합니다. 함수 본문 첫 줄에 문자열을 작성하면 docstring(문서 문자열)이 되어 help() 함수로 확인할 수 있습니다. return으로 값을 반환하며, 생략 시 None이 반환됩니다.

def greet(name): """인사말을 출력하는 함수""" return f"안녕하세요, {name}님!" result = greet("민준") print(result) # 안녕하세요, 민준님!

매개변수와 반환값

매개변수에 기본값(default value)을 지정하면 호출 시 해당 인자를 생략할 수 있습니다. 기본값이 있는 매개변수는 반드시 기본값이 없는 매개변수 뒤에 위치해야 합니다. 여러 값을 반환하려면 튜플로 반환하고 언패킹하여 받을 수 있습니다.

def add_numbers(a, b=0): """두 수를 더하는 함수 (기본값 지원)""" return a + b add_numbers(5, 3) # 8 add_numbers(5) # 5 (b의 기본값 0 사용)

*args와 **kwargs

*args는 위치 인자를 튜플로 묶어서 받고, **kwargs는 키워드 인자를 딕셔너리로 묶어서 받습니다. 이를 통해 함수가 임의 개수의 인자를 처리할 수 있습니다. 반대로, 호출 시 *리스트**딕셔너리를 사용하면 인자를 풀어서(unpacking) 전달할 수 있습니다.

def print_all(*args, **kwargs): print("args:", args) print("kwargs:", kwargs) print_all(1, 2, 3, name="민준", age=25) # args: (1, 2, 3) # kwargs: {'name': '민준', 'age': 25}

Lambda (익명 함수)

lambda는 이름 없는 한 줄짜리 함수를 생성합니다. lambda 매개변수: 표현식 형식이며, sorted(), map(), filter() 등 함수를 인자로 받는 고차 함수와 함께 자주 사용됩니다. 복잡한 로직에는 일반 def 함수가 권장됩니다.

# lambda 함수 square = lambda x: x ** 2 print(square(5)) # 25 # 정렬에 사용 pairs = [(1, "one"), (3, "three"), (2, "two")] pairs.sort(key=lambda x: x[0]) # [(1, 'one'), (2, 'two'), (3, 'three')]

고급 함수 기법

클로저(Closure)는 외부 함수의 변수를 기억하는 내부 함수이며, 상태를 유지하는 함수를 만들 때 유용합니다. 데코레이터(Decorator)@ 문법으로 함수를 감싸서 기존 기능을 변경하지 않으면서 새로운 동작을 추가하는 패턴입니다. (더 실전적인 데코레이터 활용은 아래 데코레이터 (Decorators) 섹션에서 다룹니다.)

# 클로저 (Closure) def make_counter(): count = [0] def counter(): count[0] += 1 return count[0] return counter c = make_counter() print(c()) # 1 print(c()) # 2 # 데코레이터 def my_decorator(func): def wrapper(*args, **kwargs): print("Before") result = func(*args, **kwargs) print("After") return result return wrapper @my_decorator def say_hello(): print("Hello!")

일급 시민 (First-class Citizens)

파이썬에서 함수는 일급 객체(First-class Object)입니다. 이는 함수를 변수에 할당하고, 다른 함수의 인자로 전달하고, 함수의 반환값으로 사용할 수 있다는 의미입니다. 이 특성이 데코레이터, 콜백, 함수형 프로그래밍 등 고급 패턴의 기반이 됩니다.

# 함수를 변수에 할당 my_func = print my_func("Hello") # 함수를 인자로 전달 def apply(func, x): return func(x) def double(x): return x * 2 print(apply(double, 5)) # 10 # 함수 반환 def get_adder(n): return lambda x: x + n add5 = get_adder(5) print(add5(3)) # 8

함수 Annotations

함수 어노테이션은 매개변수와 반환값의 타입 정보를 메타데이터로 기록합니다. 런타임에는 강제되지 않지만, mypy와 같은 정적 분석 도구가 타입 오류를 사전에 검출하는 데 활용합니다. __annotations__ 속성으로 어노테이션 정보를 확인할 수 있습니다.

def greet(name: str, times: int = 1) -> str: return "Hello! " * times + name # Annotations 확인 print(greet.__annotations__) # {'name': , 'times': , 'return': }

제너레이터 (Generator) - 함수에서

제너레이터는 yield 키워드를 사용하여 값을 하나씩 느리게(lazy) 생성하는 특수 함수입니다. 모든 값을 메모리에 한꺼번에 저장하지 않으므로 대용량 데이터 처리에 매우 효율적입니다. next()로 다음 값을 수동으로 가져오거나, for 문에서 자동으로 순회할 수 있습니다.

def fibonacci(n): a, b = 0, 1 for _ in range(n): yield a a, b = b, a + b for num in fibonacci(10): print(num) # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 # next()로 수동 가져오기 gen = fibonacci(5) print(next(gen)) # 0 print(next(gen)) # 1
입력 (Input) 매개변수 함수 (Function) 실행 로직 출력 (Output) 반환값

데코레이터 (Decorators)

데코레이터는 기존 함수를 감싸서(wrapping) 원본 코드를 수정하지 않고 로깅, 인증, 성능 측정 등 횡단 관심사(Cross-cutting Concerns)를 추가하는 디자인 패턴입니다. @decorator_name 문법은 func = decorator_name(func)의 축약형입니다. functools.wraps를 사용하면 원본 함수의 메타데이터가 보존됩니다.

def timer_decorator(func): def wrapper(*args, **kwargs): import time start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__} 실행 시간: {end-start:.2f}초") return result return wrapper @timer_decorator def slow_function(): import time time.sleep(1) return "완료" slow_function()

컨텍스트 매니저 (Context Manager)

with 문은 리소스(파일, 네트워크 연결, 락 등)의 획득과 해제를 자동으로 관리합니다. 예외가 발생하더라도 반드시 리소스가 정리되므로, try-finally보다 안전하고 간결합니다. __enter____exit__ 메서드를 구현하거나 contextlib.contextmanager 데코레이터로 커스텀 컨텍스트 매니저를 만들 수 있습니다.

with open("test.txt", "w") as f: f.write("안녕하세요!") # 파일이 자동으로 닫힘

async/await (비동기 프로그래밍)

비동기 프로그래밍은 I/O 대기 시간(네트워크 요청, 파일 읽기 등)에 다른 작업을 수행하여 효율성을 극대화합니다. async def로 코루틴(coroutine) 함수를 정의하고, await로 비동기 작업의 완료를 기다립니다. asyncio는 Python 표준 라이브러리의 비동기 I/O 프레임워크로, asyncio.run()으로 이벤트 루프를 시작하며, asyncio.gather()로 여러 코루틴을 동시에 실행할 수 있습니다.

import asyncio async def fetch_data(): print("데이터 가져오는 중...") await asyncio.sleep(2) return {"data": "some data"} async def main(): result = await fetch_data() print(result) asyncio.run(main())
초급자 가이드 - 함수를 쉽게 이해하기

함수를 일상생활에 비유하면:

  • 함수 = 레시피: "재료(매개변수)를 넣으면 요리(결과)가 나오는 레시피"입니다. def 볶음밥(밥, 계란): return 완성된_볶음밥
  • 왜 함수를 만드나?: 같은 코드를 10번 반복해서 쓰면 수정할 때 10곳을 다 고쳐야 합니다. 함수로 만들면 1곳만 고치면 됩니다.
  • return vs print: return은 값을 "돌려주는 것"이고, print는 화면에 "보여주는 것"입니다. return한 값은 변수에 저장하거나 다른 연산에 사용할 수 있습니다.

초보자 팁: 함수 이름은 동사로 시작하세요. calculate_total(), get_user_name(), send_email()처럼 "무엇을 하는지" 이름에 담으면 읽기 좋습니다.

중급자 가이드 - 함수 설계 원칙과 실무 패턴

좋은 함수를 작성하기 위한 실무 원칙:

  • 단일 책임 원칙: 하나의 함수는 하나의 작업만 수행해야 합니다. 함수 설명에 "그리고"가 들어가면 분리를 고려하세요.
  • 가변 기본값 함정: def f(items=[])는 모든 호출이 같은 리스트를 공유합니다! def f(items=None): items = items or []로 작성하세요.
  • 키워드 전용 인자: def f(*, name, age)에서 * 뒤의 인자는 반드시 키워드로 전달해야 합니다. API 설계 시 실수를 방지합니다.
  • 위치 전용 인자(3.8+): def f(x, y, /)에서 / 앞의 인자는 위치로만 전달할 수 있습니다.
  • functools.wraps: 데코레이터 작성 시 반드시 @functools.wraps(func)를 사용하여 원본 함수의 __name__, __doc__ 등 메타데이터를 보존하세요.
고급자 가이드 - 함수의 내부 동작과 고급 패턴

함수 객체의 내부 구조와 고급 활용 패턴:

  • 함수 객체 속성: 함수는 __code__(바이트코드), __closure__(클로저 변수), __defaults__(기본값), __globals__(전역 네임스페이스) 등의 속성을 가집니다.
  • LEGB 규칙: 변수 탐색 순서는 Local → Enclosing → Global → Built-in입니다. nonlocal은 Enclosing 스코프의 변수를 수정할 때, global은 Global 스코프의 변수를 수정할 때 사용합니다.
  • 제너레이터 send(): gen.send(value)로 제너레이터에 값을 전달할 수 있어 양방향 통신이 가능합니다. 이는 코루틴(coroutine)의 기초입니다.
  • yield from: yield from iterable은 서브 제너레이터에게 제어권을 위임합니다. 재귀적 제너레이터나 코루틴 체이닝에 사용됩니다.
  • 데코레이터 팩토리: 인자를 받는 데코레이터는 3단 중첩 함수입니다. @retry(max_attempts=3)처럼 설정 가능한 데코레이터를 만들 수 있습니다.
  • asyncio 동시성: asyncio.gather()로 여러 코루틴을 동시에 실행하고, asyncio.Semaphore로 동시 실행 수를 제한할 수 있습니다.

6. 객체지향 프로그래밍 (OOP)

중급 고급

객체지향 프로그래밍(OOP)은 관련 데이터와 동작을 하나의 객체(Object)로 묶어 프로그램을 설계하는 패러다임입니다. 캡슐화(Encapsulation)로 데이터를 보호하고, 상속(Inheritance)으로 코드를 재사용하며, 다형성(Polymorphism)으로 같은 인터페이스에 다른 구현을 제공합니다. 파이썬은 모든 것이 객체이며, 클래스 기반 OOP를 class 키워드, 특수 메서드(dunder methods), @property, ABC, Protocol 등으로 풍부하게 지원합니다.

클래스와 객체

클래스는 객체를 만들기 위한 설계도(blueprint)이고, 객체는 클래스로부터 생성된 실체(instance)입니다. __init__ 메서드는 생성자로, 객체 초기화 시 자동 호출됩니다. self는 인스턴스 자신을 가리키며, 모든 인스턴스 메서드의 첫 번째 매개변수입니다.

class Dog: """개를 나타내는 클래스""" def __init__(self, name, age): self.name = name self.age = age def bark(self): return f"{self.name}가 멍멍짖습니다!" # 객체 생성 my_dog = Dog("멍이", 3) print(my_dog.bark()) # 멍이가 멍멍짖습니다!

상속 (Inheritance)

상속은 기존 클래스(부모/기반 클래스)의 속성과 메서드를 새로운 클래스(자식/파생 클래스)가 물려받는 메커니즘입니다. 코드 재사용을 촉진하고, 자식 클래스에서 부모 메서드를 오버라이드(재정의)하여 다형성을 구현할 수 있습니다.

class Animal: def __init__(self, name): self.name = name def speak(self): pass class Cat(Animal): # Animal 상속 def speak(self): return f"{self.name}가 야옹합니다!" class Dog(Animal): # Animal 상속 def speak(self): return f"{self.name}가 멍멍합니다!"

다중 상속

파이썬은 하나의 클래스가 여러 부모 클래스를 동시에 상속받는 다중 상속을 지원합니다. 이를 통해 여러 기능을 조합한 클래스를 만들 수 있으며, 이런 패턴을 믹스인(Mixin)이라 합니다. 다중 상속 시 메서드 충돌은 MRO(Method Resolution Order) 규칙에 따라 해결됩니다.

class Flyable: def fly(self): return "날고 있습니다!" class Swimmable: def swim(self): return "수영하고 있습니다!" class Duck(Flyable, Swimmable): def quack(self): return "꽥꽥!" duck = Duck() print(duck.fly()) # 날고 있습니다! print(duck.swim()) # 수영하고 있습니다! print(duck.quack()) # 꽥꽥!

MRO (Method Resolution Order)

MRO는 다중 상속에서 메서드를 찾는 순서를 결정하는 알고리즘입니다. 파이썬은 C3 선형화(C3 Linearization) 알고리즘을 사용하며, 클래스.__mro__ 또는 클래스.mro()로 순서를 확인할 수 있습니다. super()는 이 MRO 순서에 따라 다음 클래스의 메서드를 호출합니다.

print(Duck.__mro__) # (, , ...)

super()와 메서드 오버라이드

super()는 부모 클래스의 메서드를 호출할 때 사용합니다. 자식 클래스에서 부모와 같은 이름의 메서드를 정의하면 오버라이드(재정의)되며, 부모의 기능을 확장하려면 super().메서드()를 호출한 후 추가 로직을 작성합니다.

class Animal: def __init__(self, name): self.name = name def speak(self): return "소리" class Dog(Animal): def speak(self): return f"{self.name}가 멍멍합니다!" class Cat(Animal): def speak(self): return f"{self.name}가 야옹합니다!"

특수 메서드 (Dunder Methods)

특수 메서드(매직 메서드)는 이름 양쪽에 이중 밑줄(__)이 있어 던더(Dunder) 메서드라 불립니다. 연산자 오버로딩(__add__, __eq__), 문자열 표현(__repr__, __str__), 컨테이너 프로토콜(__len__, __getitem__) 등 파이썬의 내장 동작을 커스터마이즈할 수 있습니다.

class Vector: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) def __repr__(self): return f"Vector({self.x}, {self.y})" def __len__(self): return 2 def __getitem__(self, index): return [self.x, self.y][index] v1 = Vector(1, 2) v2 = Vector(3, 4) print(v1 + v2) # Vector(4, 6)

속성 (Properties)

@property 데코레이터를 사용하면 메서드를 속성처럼 접근할 수 있으며, getter/setter/deleter를 통해 값의 유효성 검증이나 계산된 속성을 구현할 수 있습니다. 이는 Java의 getter/setter 패턴보다 훨씬 파이썬다운(Pythonic) 방식입니다.

class Circle: def __init__(self, radius): self._radius = radius # private 속성 @property def radius(self): return self._radius @radius.setter def radius(self, value): if value < 0: raise ValueError("반지름은 0 이상이어야 합니다") self._radius = value @property def area(self): return 3.14 * self._radius ** 2 c = Circle(5) print(c.area) # 78.5 c.radius = 10 print(c.area) # 314.0

클래스 변수 vs 인스턴스 변수

클래스 변수는 클래스 본문에 직접 선언되어 모든 인스턴스가 공유하는 변수입니다. 인스턴스 변수self를 통해 선언되어 각 인스턴스마다 독립적인 값을 가집니다. 클래스 변수에 가변 객체(리스트 등)를 사용할 때는 의도치 않은 공유에 주의해야 합니다.

class Dog: species = "Canis familiaris" # 클래스 변수 def __init__(self, name): self.name = name # 인스턴스 변수 print(Dog.species) # Canis familiaris dog1 = Dog("Max") dog2 = Dog("Buddy") print(dog1.name, dog2.name) # Max Buddy

정적 메서드와 클래스 메서드

@staticmethod는 인스턴스(self)나 클래스(cls)에 접근하지 않는 유틸리티 함수를 클래스 내에 정의할 때 사용합니다. @classmethod는 첫 번째 인자로 클래스 자체(cls)를 받아 팩토리 메서드나 대안 생성자를 만들 때 활용됩니다.

class MyClass: value = 10 @staticmethod def static_method(): return "정적 메서드" @classmethod def class_method(cls): return f"클래스 메서드: {cls.value}" print(MyClass.static_method()) # 정적 메서드 print(MyClass.class_method()) # 클래스 메서드: 10

Private 멤버 (Encapsulation)

파이썬에는 진정한 private 접근 제한이 없지만, 언더스코어 관례로 접근을 제어합니다. _변수는 '내부용'이라는 관례적 표시이고, __변수(이중 밑줄)는 네임 맹글링(Name Mangling)이 적용되어 _클래스명__변수로 변환됩니다. 이를 통해 하위 클래스와의 이름 충돌을 방지합니다.

class BankAccount: def __init__(self, balance): self.__balance = balance # name mangling: _BankAccount__balance @property def balance(self): return self.__balance def deposit(self, amount): if amount > 0: self.__balance += amount def withdraw(self, amount): if amount <= self.__balance: self.__balance -= amount return True return False account = BankAccount(1000) # account.__balance # 오류! print(account.balance) # 1000
Animal (부모) Cat (자식) speak()_override Dog (자식) speak()_override

Type Hints (타입 힌트)

Python 3.5에서 도입된 타입 힌트는 함수와 변수의 예상 타입을 명시적으로 선언합니다. 런타임에 강제되지 않지만, IDE의 자동완성 지원, mypy를 통한 정적 타입 검사, 코드 문서화 역할을 합니다. typing 모듈의 List, Dict, Optional, Union 등으로 복잡한 타입을 표현할 수 있습니다.

def greet(name: str) -> str: return f"안녕하세요, {name}님!" def add_numbers(a: int, b: int) -> int: return a + b # 복잡한 타입 from typing import List, Dict, Optional def process_data( items: List[int], config: Optional[Dict[str, str]] = None ) -> Dict[str, int]: return {"sum": sum(items), "count": len(items)}

Data Classes

Python 3.7에서 도입된 @dataclass 데코레이터는 __init__, __repr__, __eq__ 등을 자동 생성하여 데이터 저장용 클래스를 간결하게 정의합니다. field()로 기본값, 비교 제외 등 세밀한 설정이 가능하며, frozen=True 옵션으로 불변 데이터 클래스를 만들 수 있습니다.

from dataclasses import dataclass, field @dataclass class User: name: str age: int email: str = "unknown@example.com" active: bool = field(default=True) # 사용 user = User("민준", 25) print(user) # User(name='민준', age=25, ...)

NamedTuple

NamedTuple은 일반 튜플의 불변성과 인덱스 접근을 유지하면서, 필드에 이름으로 접근할 수 있는 자료형입니다. 간단한 데이터 레코드를 표현할 때 유용하며, 메모리 효율이 딕셔너리보다 좋습니다. typing.NamedTuple을 사용하면 타입 힌트도 추가할 수 있습니다.

from collections import namedtuple Point = namedtuple("Point", ["x", "y"]) p = Point(10, 20) print(p.x, p.y) # 10 20 print(p[0], p[1]) # 10 20

Enum (열거형)

Enum은 관련된 상수들을 하나의 클래스로 그룹화하여 타입 안전한 상수 집합을 만듭니다. 매직 넘버 대신 의미 있는 이름을 사용하여 코드 가독성을 높이고, 잘못된 값 사용을 방지합니다. auto()로 값을 자동 할당할 수도 있습니다.

from enum import Enum, auto class Color(Enum): RED = 1 GREEN = 2 BLUE = 3 print(Color.RED) # Color.RED print(Color.RED.name) # RED print(Color.RED.value) # 1

ABC (추상 베이스 클래스)

추상 베이스 클래스(ABC)는 @abstractmethod로 선언된 메서드를 반드시 구현하도록 계약(contract)을 강제합니다. ABC를 직접 인스턴스화하면 에러가 발생하며, 상속받은 클래스가 추상 메서드를 구현하지 않으면 역시 인스턴스화할 수 없습니다. 인터페이스 역할을 합니다.

from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self): pass class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): return 3.14 * self.radius ** 2

Protocol (구조적 타이핑)

Python 3.8에서 도입된 Protocol은 ABC와 달리 명시적 상속 없이 특정 메서드/속성을 가진 객체를 타입으로 인식합니다. 이를 구조적 타이핑(Structural Typing) 또는 덕 타이핑(Duck Typing)의 정적 버전이라 합니다. Go 언어의 인터페이스와 유사한 개념입니다.

from typing import Protocol class Drawable(Protocol): def draw(self) -> str: ... class Circle: def draw(self) -> str: return "Circle drawn" # 타입 체크에서 Drawable로 처리됨 def render(shape: Drawable) -> None: print(shape.draw())
OOP 고급 개념 메타클래스 type, __new__ 디스크립터 __get__, __set__ 다중상속 / MRO C3 선형화 ABC / Protocol 추상화, 덕 타이핑
초급자 가이드 - 클래스를 왜, 언제 사용할까?

클래스는 처음에 어렵게 느껴질 수 있지만, 핵심은 간단합니다:

  • 클래스 = 붕어빵 틀: 틀(클래스)로 붕어빵(객체)을 찍어냅니다. 틀 하나로 여러 개의 붕어빵을 만들 수 있습니다.
  • 언제 클래스를 만드나?: 관련된 데이터(속성)와 기능(메서드)을 하나로 묶고 싶을 때입니다. 예: "학생" 데이터(이름, 성적)와 기능(성적 계산, 학년 올리기).
  • self란?: 클래스 안에서 "나 자신"을 가리키는 참조입니다. self.name은 "이 객체의 이름"이라는 뜻입니다.
  • __init__이란?: 객체가 생성될 때 자동으로 호출되는 초기화 함수입니다. 생성자(constructor)라고도 합니다.

팁: 처음에는 클래스 없이 함수만으로 프로그래밍해도 됩니다. 코드가 복잡해지고 관련 데이터와 함수가 많아질 때 클래스 도입을 고려하세요.

중급자 가이드 - OOP 설계 원칙(SOLID)

유지보수 가능한 코드를 위한 SOLID 원칙:

  • S - 단일 책임(SRP): 클래스는 하나의 이유로만 변경되어야 합니다. "사용자 인증"과 "이메일 전송"을 같은 클래스에 넣지 마세요.
  • O - 개방-폐쇄(OCP): 확장에는 열려있고, 수정에는 닫혀있어야 합니다. 새 기능 추가 시 기존 코드를 수정하지 않고 상속/합성으로 확장하세요.
  • L - 리스코프 치환(LSP): 자식 클래스는 부모 클래스를 대체할 수 있어야 합니다.
  • I - 인터페이스 분리(ISP): 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 분리하세요.
  • D - 의존성 역전(DIP): 구체 클래스가 아닌 추상(ABC/Protocol)에 의존하세요.

실무 팁: 파이썬에서는 "상속보다 합성(Composition over Inheritance)"을 선호합니다. 다중 상속의 복잡성을 피하고 Mixin 패턴을 활용하세요.

고급자 가이드 - 메타클래스와 디스크립터

파이썬 OOP의 깊은 내부 메커니즘:

  • 메타클래스(Metaclass): 클래스의 클래스입니다. class MyMeta(type):로 정의하며, 클래스 생성 과정을 제어합니다. __new__에서 클래스를 생성하고, __init__에서 초기화하며, __init_subclass__는 3.6+에서 더 간단한 대안을 제공합니다.
  • 디스크립터(Descriptor): __get__, __set__, __delete__를 구현한 객체로, 속성 접근을 가로챕니다. @property, @classmethod, @staticmethod가 모두 디스크립터로 구현되어 있습니다.
  • __new__ vs __init__: __new__는 인스턴스를 생성(메모리 할당)하고, __init__은 생성된 인스턴스를 초기화합니다. 불변 객체(tuple, str 등)를 커스터마이즈할 때 __new__를 오버라이드해야 합니다.
  • __init_subclass__(3.6+): 메타클래스 없이 서브클래스 생성을 후킹할 수 있습니다. 플러그인 시스템, 자동 등록 등에 유용합니다.
  • __class_getitem__(3.7+): MyClass[int]처럼 클래스를 제네릭하게 사용할 수 있게 해주며, typing.Generic의 기반입니다.

7. 예외 처리 (Exception Handling)

초급 중급

예외(Exception)는 프로그램 실행 중 발생하는 오류 상황을 나타내는 객체입니다. 파이썬의 예외 처리 메커니즘을 사용하면 오류가 발생하더라도 프로그램이 비정상적으로 종료되지 않고, 적절한 복구 로직을 수행한 뒤 계속 실행할 수 있습니다. 파이썬은 ZeroDivisionError, TypeError, ValueError, FileNotFoundError60개 이상의 내장 예외 클래스를 제공하며, 이들은 모두 BaseException을 최상위 부모로 하는 계층 구조를 이룹니다. try-except-else-finally 구문으로 예외를 처리하고, raise로 의도적으로 예외를 발생시킬 수 있습니다.

BaseException Exception ValueError TypeError KeyError OSError 사용자 정의 FileNotFoundError

try-except 구조

try 블록에서 예외가 발생하면 해당 예외 타입과 일치하는 except 블록이 실행됩니다. 예외를 잡지 않으면 프로그램이 비정상 종료됩니다. except Exception as e로 예외 객체를 변수에 바인딩하여 에러 메시지를 확인할 수 있습니다.

try: result = 10 / 0 except ZeroDivisionError: print("0으로 나눌 수 없습니다!")

여러 예외 처리

여러 except 절을 사용하여 예외 타입별로 다른 처리를 할 수 있습니다. else 블록은 예외가 발생하지 않았을 때만 실행되고, finally 블록은 예외 발생 여부와 관계없이 항상 실행됩니다. finally는 파일 닫기, 리소스 해제 등 정리 작업에 사용합니다.

try: num = int("abc") except ValueError: print("값 오류 발생") except TypeError: print("타입 오류 발생") finally: print("항상 실행")

사용자 정의 예외

Exception 클래스를 상속하여 애플리케이션에 맞는 커스텀 예외를 만들 수 있습니다. 도메인별로 예외 계층을 설계하면 호출 코드에서 세밀한 에러 처리가 가능합니다. raise 키워드로 예외를 직접 발생시키며, raise ... from ...으로 예외 체이닝도 가능합니다.

class CustomError(Exception): def __init__(self, message): self.message = message super().__init__(self.message) raise CustomError("사용자 정의 오류")
try 블록 예외? except 블록 finally
초급자 가이드 - 예외 처리가 필요한 이유

프로그램은 예상치 못한 상황에서 멈출 수 있습니다. 예외 처리는 이런 상황을 대비하는 안전장치입니다:

  • 사용자 입력 오류: 숫자를 입력해야 하는데 문자를 입력하면 ValueError가 발생합니다.
  • 파일이 없을 때: 존재하지 않는 파일을 열려고 하면 FileNotFoundError가 발생합니다.
  • 0으로 나누기: 10 / 0ZeroDivisionError를 일으킵니다.

핵심 패턴: "시도(try)해보고, 실패(except)하면 대안을 실행한다"가 기본입니다. 처음에는 try-except만 기억하면 충분합니다!

주의: except:처럼 예외 타입을 지정하지 않으면 모든 예외를 잡아 디버깅이 어려워집니다. 가능하면 구체적인 예외 타입을 명시하세요.

중급자 가이드 - 예외 처리 모범 사례

실무에서의 예외 처리 가이드라인:

  • EAFP vs LBYL: 파이썬은 "허락을 구하기보다 용서를 구하는(EAFP)" 스타일을 선호합니다. if key in dict:(LBYL) 대신 try: dict[key] except KeyError:(EAFP)를 사용하세요.
  • else 블록 활용: else는 예외가 발생하지 않았을 때만 실행됩니다. try 블록을 최소화하고, 성공 시 로직을 else에 넣으면 어디서 예외가 발생했는지 명확해집니다.
  • 예외 체이닝: raise NewError() from original_error로 원인 예외를 보존합니다. __cause__ 속성으로 원인을 추적할 수 있습니다.
  • logging 활용: 예외 발생 시 logging.exception("메시지")를 사용하면 스택 트레이스가 자동으로 기록됩니다.
  • contextlib.suppress: 특정 예외를 무시할 때 with suppress(FileNotFoundError): os.remove("temp.txt")가 try-except보다 간결합니다.
고급자 가이드 - 예외의 내부 구조와 고급 패턴

예외 시스템의 고급 활용법:

  • ExceptionGroup (3.11+): ExceptionGroup으로 여러 예외를 동시에 발생시키고, except* 구문으로 선택적으로 처리할 수 있습니다. 비동기 프로그래밍에서 여러 작업의 예외를 한꺼번에 처리할 때 유용합니다.
  • sys.exc_info(): 현재 처리 중인 예외의 타입, 값, 트레이스백을 튜플로 반환합니다. 예외 정보를 세밀하게 조작하거나 로깅할 때 사용합니다.
  • traceback 모듈: traceback.format_exc()로 스택 트레이스를 문자열로 얻고, traceback.print_exc()로 출력합니다. 커스텀 에러 리포팅에 활용됩니다.
  • 예외 계층 설계: 라이브러리를 설계할 때 기본 예외 클래스를 만들고(class MyLibError(Exception)), 세부 예외를 파생시킵니다. 사용자가 except MyLibError:로 모든 라이브러리 예외를 잡을 수 있습니다.
  • __traceback__ 관리: 예외 객체의 __traceback__ 속성은 트레이스백 객체를 참조합니다. 메모리 누수를 방지하려면 del e.__traceback__ 또는 raise e from None을 사용하세요.

8. 모듈과 패키지 (Modules & Packages)

초급 중급

모듈(Module)은 파이썬 코드(함수, 클래스, 변수 등)가 담긴 .py 파일 하나를 의미합니다. 패키지(Package)는 관련 모듈들을 디렉터리 구조로 묶어 체계적으로 관리하는 방법입니다. 파이썬은 "배터리 포함(Batteries Included)" 철학에 따라 os, sys, json, datetime, pathlib 등 300개 이상의 표준 라이브러리 모듈을 제공하며, PyPI(Python Package Index)를 통해 50만 개 이상의 서드파티 패키지를 pip로 쉽게 설치할 수 있습니다. 이러한 방대한 생태계가 파이썬의 강력한 경쟁력입니다.

모듈 가져오기

import 문으로 모듈을 가져옵니다. import 모듈은 전체 모듈을 가져오고, from 모듈 import 함수는 특정 항목만 가져옵니다. as를 사용하면 별칭을 지정할 수 있어 코드를 간결하게 만듭니다. from 모듈 import *는 이름 충돌 위험이 있으므로 일반적으로 권장되지 않습니다.

# 전체 모듈 가져오기 import math print(math.pi) # 3.141592653589793 print(math.sqrt(16)) # 4.0 # 특정 함수만 가져오기 from math import pi, sqrt print(sqrt(25)) # 5.0 # 별칭 사용 import numpy as np import pandas as pd

표준 라이브러리 모듈

파이썬은 "배터리 포함(Batteries Included)" 철학에 따라 방대한 표준 라이브러리를 제공합니다. 별도 설치 없이 바로 사용할 수 있는 주요 모듈들입니다.

📅 datetime

날짜와 시간 처리

🔢 random

랜덤 숫자 생성

📊 json

JSON 데이터 처리

🌐 urllib

네트워크 요청

주요 내장 함수 (Built-in Functions)

파이썬은 별도 import 없이 어디서든 사용 가능한 내장 함수(built-in functions)를 약 70개 이상 제공합니다. 데이터 변환, 입출력, 반복 처리 등 프로그래밍의 기본 작업을 담당하는 핵심 도구입니다.

함수설명
print()출력
input()입력
len()길이
range()범위 생성
enumerate()인덱스와 값
zip()여러 시퀀스 병합
map()함수 적용
filter()조건 필터링
sorted()정렬
reversed()역순
sum(), min(), max()합계, 최소, 최대
abs()절대값
round()반올림
type()타입 확인
isinstance()타입 확인
dir()속성 목록
help()도움말

사용자 정의 모듈

모든 .py 파일은 모듈로 사용할 수 있습니다. 다른 파일에서 import하면 해당 파일의 함수, 클래스, 변수를 사용할 수 있습니다. if __name__ == "__main__": 구문을 사용하면 모듈이 직접 실행될 때만 특정 코드가 실행되도록 할 수 있습니다.

# mymodule.py def greet(name): return f"안녕하세요, {name}!" # main.py import mymodule print(mymodule.greet("민준"))

패키지 설치 (pip)

pip는 파이썬의 공식 패키지 관리자로, PyPI(Python Package Index)에서 30만 개 이상의 패키지를 설치할 수 있습니다. requirements.txt 파일로 프로젝트의 의존성을 관리하며, pip freeze > requirements.txt로 현재 설치된 패키지 목록을 내보낼 수 있습니다.

# 패키지 설치 pip install numpy pandas matplotlib # 특정 버전 pip install numpy==1.24.0 #requirements.txt 사용 pip install -r requirements.txt # 패키지 목록 pip list # 패키지 검색 pip search "keyword" # 패키지 제거 pip uninstall package_name

가상 환경 (Virtual Environment)

가상 환경은 프로젝트마다 독립된 파이썬 패키지 공간을 만들어 의존성 충돌을 방지합니다. 프로젝트 A가 Django 3.x, 프로젝트 B가 Django 4.x를 사용하더라도 각각의 가상 환경에서 충돌 없이 실행할 수 있습니다. venv는 파이썬 3.3부터 표준 라이브러리에 포함되어 있습니다.

# venv 생성 python -m venv myenv # 활성화 (Windows) myenv\Scripts\activate # 활성화 (Linux/Mac) source myenv/bin/activate # 비활성화 deactivate

주요 표준 라이브러리

파이썬의 표준 라이브러리는 시스템 상호작용, 병렬 처리, 파일 관리, 로깅 등 실무에서 자주 필요한 기능을 포괄합니다. 다음은 가장 많이 사용되는 표준 라이브러리 모듈입니다.

sys

시스템 매개변수 및 함수. 인터프리터와 직접 상호작용합니다.

os

운영체제 상호작용

subprocess

프로세스 실행

threading

스레드 기반 병렬처리

multiprocessing

프로세스 기반 병렬처리

pathlib

경로 객체 지향 처리

argparse

명령행 인자 파싱

logging

로깅 프레임워크

tempfile

임시 파일/디렉토리

shutil

고수준 파일 operations

mypackage/ 패키지 폴더 __init__.py module1.py module2.py
초급자 가이드 - 모듈과 패키지를 쉽게 이해하기

모듈과 패키지를 일상에 비유하면:

  • 모듈 = 도구 상자: 다른 사람이 만든 기능(도구)을 가져다 쓰는 것입니다. import math하면 수학 관련 함수들을 모두 사용할 수 있습니다.
  • pip = 앱 스토어: 전 세계 개발자들이 만든 패키지를 무료로 설치할 수 있는 저장소입니다.
  • 가상 환경 = 작업 폴더: 프로젝트마다 독립된 공간을 만들어 패키지 버전 충돌을 방지합니다.

if __name__ == "__main__": 이해하기: 이 코드는 "이 파일이 직접 실행될 때만 아래 코드를 실행해라"라는 뜻입니다. 다른 파일에서 import할 때는 실행되지 않습니다. 모든 스크립트의 실행 진입점에 사용합니다.

중급자 가이드 - 프로젝트 구조와 의존성 관리

실무 프로젝트의 구조와 의존성 관리 전략:

  • 프로젝트 레이아웃: src/ 레이아웃이 모범 사례입니다. src/mypackage/에 소스코드를, tests/에 테스트를, 루트에 pyproject.toml을 배치합니다.
  • pyproject.toml: PEP 517/518에 따라 setup.py를 대체하는 현대적 프로젝트 설정 파일입니다. 빌드 시스템, 의존성, 도구 설정을 하나의 파일에 통합합니다.
  • 의존성 관리 도구: pip + requirements.txt가 기본이지만, poetry는 의존성 해결과 패키징을 통합하고, uv는 Rust로 작성되어 초고속 설치를 제공합니다.
  • 상대 vs 절대 import: 패키지 내에서 from . import module(상대 import)을 사용하면 패키지 이름 변경 시에도 안전합니다. 실행 스크립트에서는 절대 import를 사용합니다.
고급자 가이드 - import 시스템의 내부 동작

파이썬 import 시스템의 내부 메커니즘:

  • import 과정: (1) sys.modules 캐시 확인 → (2) sys.meta_path의 finder 검색 → (3) sys.path 경로에서 모듈 찾기 → (4) 모듈 로딩 및 실행 → (5) sys.modules에 캐싱
  • 커스텀 Importer: importlib.abc.MetaPathFinderimportlib.abc.Loader를 구현하면 데이터베이스, 네트워크, ZIP 파일 등에서 모듈을 로드할 수 있습니다.
  • 순환 import: A가 B를 import하고 B가 A를 import하면 순환 참조가 발생합니다. 해결법: (1) import를 함수 내부로 이동, (2) 공통 모듈 분리, (3) 구조 재설계
  • __all__: 모듈에 __all__ = ["func1", "Class1"]을 정의하면 from module import *에서 내보낼 항목을 제한합니다.
  • namespace package: __init__.py 없이도 패키지를 만들 수 있습니다(PEP 420). 여러 디렉토리에 분산된 패키지를 하나로 합칠 때 사용합니다.

9. 파일 입출력 (File I/O)

초급 중급

파일 입출력(File I/O)은 프로그램의 데이터를 디스크에 영구적으로 저장하고, 저장된 데이터를 다시 읽어오는 핵심 기능입니다. 프로그램이 종료되면 메모리의 데이터는 사라지지만, 파일에 기록한 데이터는 유지됩니다. 파이썬은 open() 내장 함수와 with 컨텍스트 매니저를 통해 안전하고 간결한 파일 처리를 지원하며, 텍스트 파일뿐 아니라 JSON, CSV, 바이너리 파일, pickle 직렬화 등 다양한 형식을 다룰 수 있습니다. Python 3.4 이후에는 pathlib 모듈로 경로를 객체 지향적으로 다루는 방법도 제공합니다.

Python 프로그램 write read open() 파일 핸들러 파일 .txt .json .csv .pkl with 문 자동 close() 파일은 열기(open) → 읽기/쓰기 → 닫기(close) 순서로 처리

파일 읽기

파일 읽기는 open() 함수로 파일을 열고 read(), readline(), readlines() 메서드로 내용을 가져옵니다. with 문을 사용하면 파일이 자동으로 닫히므로 리소스 누수를 방지할 수 있습니다. 한글 파일은 encoding="utf-8"을 명시하는 것이 안전합니다.

# 전체 읽기 with open("file.txt", "r", encoding="utf-8") as f: content = f.read() print(content) # 줄 단위 읽기 with open("file.txt", "r") as f: for line in f: print(line.strip()) # readlines()로 목록 가져오기 with open("file.txt") as f: lines = f.readlines() # seek()로 위치 이동 with open("file.txt") as f: f.seek(0) # 처음으로 print(f.read(10)) # 첫 10자

파일 쓰기

"w" 모드는 파일을 새로 생성하거나 기존 내용을 덮어쓰고, "a" 모드는 기존 내용 끝에 추가합니다. write()는 문자열 하나를 쓰고, writelines()는 문자열 리스트를 한꺼번에 씁니다. 줄바꿈은 자동 추가되지 않으므로 필요 시 \n을 직접 포함해야 합니다.

# 새 파일 작성 (w: overwrite, a: append) with open("output.txt", "w", encoding="utf-8") as f: f.write("첫 번째 줄\n") f.write("두 번째 줄\n") # 여러 줄 쓰기 lines = ["줄 1", "줄 2", "줄 3"] with open("output.txt", "w") as f: f.writelines("\n".join(lines))

파일 모드 (Modes)

open() 함수의 두 번째 인자로 파일 모드를 지정합니다. 모드를 조합하여 사용할 수 있으며(예: "rb"는 바이너리 읽기), 기본값은 텍스트 읽기("rt")입니다.

모드설명
r읽기 (기본)
w쓰기 (덮어쓰기)
a추가 (끝에)
r+읽기+쓰기
w+읽기+쓰기 (덮어쓰기)
a+읽기+추가
b바이너리 모드
t텍스트 모드 (기본)

pickle (객체 직렬화)

pickle은 파이썬 객체를 바이트 스트림으로 변환(직렬화)하여 파일에 저장하고, 나중에 다시 파이썬 객체로 복원(역직렬화)합니다. 리스트, 딕셔너리, 클래스 인스턴스 등 거의 모든 파이썬 객체를 저장할 수 있지만, 신뢰할 수 없는 출처의 pickle 파일은 보안 위험이 있으므로 주의해야 합니다.

import pickle # 객체 저장 data = {"name": "민준", "age": 25} with open("data.pkl", "wb") as f: pickle.dump(data, f) # 객체 읽기 with open("data.pkl", "rb") as f: loaded_data = pickle.load(f)

pathlib (객체 지향 경로)

Python 3.4에서 도입된 pathlib은 파일 경로를 객체 지향적으로 다룹니다. / 연산자로 경로를 결합하고, 메서드 체이닝으로 파일 존재 확인, 읽기/쓰기, 패턴 검색 등을 간결하게 수행합니다. os.path보다 직관적이며 현대 파이썬에서 권장됩니다.

from pathlib import Path p = Path("myproject") # 경로 생성 (p / "src" / "main.py") # 파일 존재 확인 p.exists() p.is_file() p.is_dir() #.glob()로 파일 찾기 for f in Path(".").glob("*.py"): print(f) # 파일 읽기 content = Path("file.txt").read_text() # 파일 쓰기 Path("new.txt").write_text("Hello")

tempfile (임시 파일)

tempfile 모듈은 시스템의 임시 디렉토리에 안전하게 임시 파일/디렉토리를 생성합니다. with 문과 함께 사용하면 블록 종료 시 자동으로 삭제되어 정리 걱정이 없습니다. 파일명 충돌을 자동으로 방지합니다.

import tempfile # 임시 파일 with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: f.write("임시 내용") temp_name = f.name # 임시 디렉토리 with tempfile.TemporaryDirectory() as tmpdir: print(tmpdir)

JSON 파일 처리

JSON(JavaScript Object Notation)은 웹 API, 설정 파일 등에서 가장 널리 사용되는 데이터 교환 형식입니다. json.dump()로 파일에 쓰고, json.load()로 읽습니다. ensure_ascii=False를 지정하면 한글이 유니코드 이스케이프 없이 그대로 저장됩니다.

import json # 쓰기 data = {"name": "민준", "age": 25} with open("data.json", "w") as f: json.dump(data, f, ensure_ascii=False, indent=2) # 읽기 with open("data.json", "r") as f: data = json.load(f)

CSV 파일 처리

CSV(Comma-Separated Values)는 표 형태의 데이터를 텍스트로 저장하는 형식으로, 엑셀이나 데이터베이스와의 데이터 교환에 널리 사용됩니다. csv.writer/csv.reader로 기본 읽기/쓰기를, csv.DictWriter/csv.DictReader로 헤더 기반 처리를 할 수 있습니다.

import csv # CSV 쓰기 with open("data.csv", "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["이름", "나이"]) writer.writerow(["민준", 25]) writer.writerow(["서연", 23]) # CSV 읽기 with open("data.csv", "r", encoding="utf-8") as f: reader = csv.reader(f) for row in reader: print(row)
💡 팁: 항상 with문을 사용하여 파일을 다루세요. 파일이 자동으로 닫힙니다.
초급자 가이드 - 파일 다루기의 기본 패턴

파일 작업에서 가장 중요한 3가지 패턴:

  • 반드시 with문 사용: with open("파일") as f: 패턴은 파일을 안전하게 열고 닫습니다. with 없이 f = open("파일")로 열면, 예외 발생 시 파일이 닫히지 않아 데이터 손실이 생길 수 있습니다.
  • 인코딩 명시: 한글이 포함된 파일은 반드시 encoding="utf-8"을 지정하세요. 지정하지 않으면 운영체제 기본 인코딩이 사용되어 Windows에서 한글 깨짐이 발생합니다.
  • 파일 모드 구분: "r"(읽기), "w"(쓰기, 기존 내용 삭제!), "a"(추가, 기존 내용 유지). "w" 모드는 기존 파일을 완전히 덮어쓰므로 주의하세요!
중급자 가이드 - 실무 파일 처리 패턴

실무에서 자주 사용하는 파일 처리 패턴과 주의사항:

  • 대용량 파일 처리: for line in f:는 한 줄씩 읽어 메모리 효율적입니다. f.read()는 전체 내용을 메모리에 올리므로 대용량 파일에서는 피하세요.
  • pathlib 우선 사용: os.path 대신 pathlib.Path를 사용하세요. Path("dir") / "file.txt"로 경로를 결합하고, .read_text(), .write_text()로 간결하게 읽기/쓰기할 수 있습니다.
  • 원자적 쓰기: 파일 쓰기 중 프로그램이 중단되면 데이터가 손상될 수 있습니다. 임시 파일에 먼저 쓴 후 os.replace()로 이름을 바꾸면 안전합니다.
  • 구조화된 데이터: 단순 텍스트보다 JSON(설정 파일, API), CSV(표 데이터), YAML(설정 파일), TOML(프로젝트 설정)을 목적에 맞게 선택하세요.
고급자 가이드 - 고성능 파일 I/O

대규모 데이터를 효율적으로 처리하기 위한 고급 I/O 기법:

  • mmap (메모리 매핑): mmap.mmap()은 파일을 메모리에 매핑하여 랜덤 접근 성능을 극대화합니다. 수 GB 파일에서 특정 위치의 데이터를 빠르게 읽을 때 유용합니다.
  • io.BufferedReader/Writer: 버퍼 크기를 조절하여 I/O 성능을 최적화합니다. SSD에서는 더 큰 버퍼(예: 1MB)가 효율적입니다.
  • aiofiles: asyncio와 함께 비동기 파일 I/O를 수행합니다. 많은 파일을 동시에 처리할 때 이벤트 루프를 블로킹하지 않습니다.
  • orjson / ujson: 표준 json 모듈보다 3-10배 빠른 JSON 처리를 제공합니다. 대량의 JSON 데이터를 다룰 때 유용합니다.
  • Parquet / HDF5: 대규모 데이터 분석에서는 CSV 대신 Apache Parquet(컬럼 기반, 압축)이나 HDF5(계층적 데이터)를 사용하면 10-100배 빠른 읽기/쓰기가 가능합니다.

10. 정규표현식 (Regular Expressions)

중급 고급

정규표현식(regex)은 문자열 패턴을 정의하는 특수한 표기법으로, 데이터 검증, 텍스트 검색/치환, 로그 파싱 등에 널리 사용됩니다. 파이썬의 re 모듈이 정규표현식 기능을 제공하며, 패턴 문자열은 r"..."(raw string)로 작성하여 백슬래시 이스케이프 문제를 방지합니다.

기본 매칭

re.search()는 문자열 전체에서 패턴을 검색하고, re.match()는 문자열의 시작 부분에서만 매칭합니다. 매칭에 성공하면 Match 객체를 반환하고, 실패하면 None을 반환합니다.

import re # 매칭 여부 확인 pattern = r"\d+" text = "내 번호는 010-1234-5678입니다" if re.search(pattern, text): print("숫자 발견!")

패턴 매칭 함수

re 모듈은 다양한 매칭 함수를 제공합니다. findall()은 모든 매칭을 리스트로 반환하고, sub()은 매칭된 부분을 대체하며, split()은 패턴을 기준으로 문자열을 분할합니다. compile()로 패턴을 미리 컴파일하면 반복 사용 시 성능이 향상됩니다.

# findall: 모든 매칭 찾기 result = re.findall(r"\d+", "123 abc 456 def 789") print(result) # ['123', '456', '789'] # sub: 대체 new_text = re.sub(r"\d+", "X", "123 abc 456") print(new_text) # X abc X # split: 분리 words = re.split(r"\s+", "hello world python") print(words) # ['hello', 'world', 'python']

자주 사용하는 패턴

정규표현식의 핵심 메타문자들입니다. 소문자(\d, \w, \s)는 해당 문자를 매칭하고, 대문자(\D, \W, \S)는 그 반대를 매칭합니다. {n,m}으로 반복 횟수를 정밀하게 지정할 수 있습니다.

패턴의미예시
\d숫자0-9
\w단어 문자a-z, A-Z, 0-9, _
\s공백스페이스, 탭, 줄바꿈
.모든 문자줄바꿈 제외
*0회 이상ab* = a, ab, abb...
+1회 이상ab+ = ab, abb...
?0회 또는 1회ab? = a, ab
^시작^hello
$world$

이메일 검증 예제

정규표현식의 실전 활용 예시입니다. ^$로 문자열 전체가 패턴에 맞는지 확인합니다. 실제 서비스에서는 email-validator 같은 전용 라이브러리 사용이 더 안정적입니다.

email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" emails = ["test@example.com", "invalid-email", "user@domain.co.kr"] for email in emails: if re.match(email_pattern, email): print(f"{email}: 유효함") else: print(f"{email}: 무효함")
정규표현식 패턴 구조 예시: \d{3}-\d{4}-\d{4} 입력: "010-1234-5678" \d{3} 숫자 3자리 010 - 리터럴 - \d{4} 숫자 4자리 1234 - 리터럴 - \d{4} 숫자 4자리 5678 Match! "010-1234-5678" \d = 숫자(0-9) {n} = 정확히 n회 반복 리터럴 = 해당 문자 그대로 매칭
초급자 가이드 - 정규표현식 첫걸음

정규표현식이 어렵게 느껴진다면, 가장 자주 사용하는 3가지만 먼저 익히세요:

  • \d = 숫자 한 글자 (0-9). \d+는 "숫자 1개 이상", \d{3}은 "숫자 정확히 3개"
  • \w = 글자 한 글자 (영문, 숫자, _). \w+는 "단어 1개 이상"
  • . = 아무 글자 1개 (줄바꿈 제외). .*는 "아무 글자 0개 이상"

실전 예시: 전화번호 찾기는 r"\d{3}-\d{4}-\d{4}", 이메일 찾기는 r"\w+@\w+\.\w+"로 시작할 수 있습니다.

팁: r"..."(raw string)을 반드시 사용하세요! r을 빼면 \n이 줄바꿈으로 해석됩니다.

중급자 가이드 - 정규표현식 실전 패턴

실무에서 빈번히 사용되는 정규표현식 패턴과 기법:

  • 그룹 캡처: r"(\d{3})-(\d{4})-(\d{4})"에서 괄호 안 내용을 match.group(1), match.group(2)로 개별 추출합니다.
  • 이름 붙은 그룹: r"(?P<area>\d{3})-(?P<mid>\d{4})"로 이름을 부여하면 match.group("area")로 접근할 수 있습니다.
  • 비탐욕적(non-greedy) 매칭: .*는 최대한 많이 매칭(탐욕적)하지만, .*?는 최소한만 매칭합니다. HTML 태그 추출 시 중요합니다.
  • 전방/후방 탐색: (?=패턴)(전방 긍정), (?<=패턴)(후방 긍정)으로 패턴 주변을 조건으로 사용하되 결과에 포함하지 않습니다.
  • re.compile(): 같은 패턴을 반복 사용할 때 pattern = re.compile(r"...")으로 미리 컴파일하면 약 30% 성능이 향상됩니다.
고급자 가이드 - 정규표현식 성능과 대안

정규표현식의 성능 함정과 대안 기법:

  • 재앙적 백트래킹(Catastrophic Backtracking): (a+)+$같은 패턴은 "aaaaaaaaX" 입력에서 지수적 시간이 걸립니다. 중첩된 반복 한정자를 피하고, regex 모듈의 POSIX 매칭이나 타임아웃을 활용하세요.
  • 원자적 그룹(3rd party): regex 모듈(pip install regex)은 원자적 그룹 (?>...), 유니코드 프로퍼티 \p{Han}, 중첩 집합 등 표준 re보다 강력한 기능을 제공합니다.
  • 정규표현식 대안: 단순 문자열 작업은 str.startswith(), str.endswith(), in 연산자가 정규표현식보다 10-100배 빠릅니다. 항상 정규표현식이 필요한지 먼저 검토하세요.
  • re.VERBOSE 플래그: re.VERBOSE(또는 re.X)를 사용하면 패턴에 줄바꿈과 주석을 넣을 수 있어 복잡한 패턴의 가독성이 크게 향상됩니다.

11. 테스트 (Testing)

중급 고급

테스트는 코드가 의도한 대로 동작하는지 자동으로 검증하는 코드입니다. 단위 테스트(Unit Test)는 개별 함수/메서드를 독립적으로 검증하고, 통합 테스트는 여러 컴포넌트 간의 상호작용을 검증합니다. TDD(Test-Driven Development) 방법론에서는 테스트를 먼저 작성한 후 구현 코드를 작성합니다.

단위 테스트 (unittest)

unittest는 파이썬 표준 라이브러리에 포함된 테스트 프레임워크로, TestCase 클래스를 상속하여 테스트를 작성합니다. assertEqual, assertTrue, assertRaises 등의 단언(assert) 메서드를 제공하며, setUp/tearDown으로 테스트 전후 초기화/정리를 수행합니다.

import unittest class TestMathOperations(unittest.TestCase): def test_add(self): self.assertEqual(2 + 2, 4) def test_divide(self): self.assertEqual(10 / 2, 5) def test_negative(self): self.assertTrue(-5 < 0) if __name__ == "__main__": unittest.main()

pytest 사용

pytest는 파이썬에서 가장 인기 있는 서드파티 테스트 프레임워크입니다. unittest보다 간결한 문법(단순 assert 사용), 강력한 fixture 시스템, 풍부한 플러그인 생태계를 제공합니다. 테스트 파일은 test_로 시작하는 이름을 사용합니다.

# pytest 설치 pip install pytest # 테스트 파일 작성 (test_*.py) # test_calculator.py def test_addition(): assert 1 + 1 == 2 def test_subtraction(): assert 5 - 3 == 2 # 실행 pytest test_calculator.py -v

테스트.raises()

pytest.raises()는 특정 예외가 발생하는지 검증하는 컨텍스트 매니저입니다. 예외가 발생하지 않거나 다른 타입의 예외가 발생하면 테스트가 실패합니다. 예외 처리 로직을 테스트할 때 필수적인 도구입니다.

import pytest def test_divide_by_zero(): with pytest.raises(ZeroDivisionError): 1 / 0 def test_value_error(): with pytest.raises(ValueError): int("not a number")

Fixture와 Mock

Fixture는 테스트 실행 전에 필요한 데이터나 환경을 준비하는 함수입니다. @pytest.fixture로 정의하고 테스트 함수의 매개변수로 받습니다. Mock은 외부 의존성(DB, API 등)을 가짜 객체로 대체하여 테스트를 독립적으로 실행할 수 있게 합니다. unittest.mock@patch 데코레이터가 자주 사용됩니다.

import pytest from unittest.mock import Mock, patch @pytest.fixture def sample_data(): return {"name": "테스트", "value": 100} def test_with_fixture(sample_data): assert sample_data["value"] == 100 @patch("module.function") def test_mock(mock_func): mock_func.return_value = "mocked" result = mock_func() assert result == "mocked"
💡 팁: TDD(Test-Driven Development) 방식: 테스트를 먼저 작성하고, 그 다음에 실제 코드를 구현하세요.
1. 테스트 작성 2. 실패 확인 3. 코드 구현
초급자 가이드 - 왜 테스트를 작성해야 할까?

테스트 코드는 처음에 "추가 작업"처럼 느껴지지만, 장기적으로 시간을 절약해줍니다:

  • 자동 검증: 코드를 수정할 때마다 수동으로 확인하지 않아도 됩니다. 테스트를 실행하면 모든 기능이 정상인지 자동으로 확인됩니다.
  • 버그 방지: 한 곳을 고치면서 다른 곳이 깨지는 "회귀 버그"를 방지합니다.
  • 문서 역할: 테스트 코드를 보면 함수가 어떻게 사용되는지, 어떤 결과를 기대하는지 알 수 있습니다.

시작 팁: pytest로 시작하세요. assert 1 + 1 == 2처럼 간단한 assert 문만으로 테스트를 작성할 수 있습니다. 파일 이름을 test_로 시작하면 pytest가 자동으로 찾아 실행합니다.

중급자 가이드 - 테스트 전략과 커버리지

효과적인 테스트를 위한 실무 전략:

  • 테스트 피라미드: 단위 테스트(많이) → 통합 테스트(적당히) → E2E 테스트(적게) 비율로 작성합니다.
  • AAA 패턴: 테스트는 Arrange(준비) → Act(실행) → Assert(검증) 구조로 작성합니다.
  • pytest-cov: pytest --cov=mypackage로 테스트 커버리지를 측정합니다. 80% 이상이 일반적인 목표입니다.
  • Parametrize: @pytest.mark.parametrize("input,expected", [(1,1), (2,4)])로 여러 입력에 대해 같은 테스트를 반복합니다.
  • conftest.py: 여러 테스트 파일에서 공유하는 fixture를 conftest.py에 정의하면 자동으로 사용 가능합니다.
고급자 가이드 - 테스트 자동화와 고급 기법

대규모 프로젝트를 위한 고급 테스트 기법:

  • Property-based Testing: hypothesis 라이브러리로 무작위 입력을 자동 생성하여 엣지 케이스를 발견합니다. @given(st.integers())로 정수 범위의 모든 값에 대해 속성을 검증합니다.
  • Snapshot Testing: syrupy 또는 pytest-snapshot으로 복잡한 출력(JSON, HTML 등)의 스냅샷을 저장하고, 변경 사항을 자동 감지합니다.
  • CI/CD 통합: GitHub Actions, GitLab CI에서 push마다 자동으로 pytest를 실행하고, 커버리지를 Codecov에 보고합니다.
  • Mutation Testing: mutmut으로 소스코드를 의도적으로 변형(mutation)하여 테스트가 이를 감지하는지 확인합니다. 테스트의 품질을 측정하는 방법입니다.
  • Test Doubles 구분: Stub(정해진 값 반환), Mock(호출 검증), Fake(간단한 구현), Spy(호출 기록)를 목적에 맞게 사용합니다.

12. 데이터 과학 도구

중급 고급

파이썬은 데이터 과학 분야에서 가장 널리 사용되는 언어입니다. NumPy로 고성능 수치 연산을, Pandas로 데이터 조작을, Matplotlib/Seaborn으로 시각화를 수행합니다. 이 생태계는 R, MATLAB 등의 대안보다 범용성과 확장성이 뛰어나 산업 표준으로 자리잡았습니다.

데이터 수집 CSV, API, DB 전처리 Pandas, NumPy 탐색적 분석 Matplotlib 모델링 scikit-learn 평가 / 배포 검증, 서빙 데이터 과학 워크플로우 (Data Science Pipeline) 각 단계는 반복적(iterative)으로 수행됩니다

AI 개발 환경 설정 & 설치

데이터 과학과 AI 개발을 위해 필요한 핵심 패키지들을 설치합니다. GPU를 활용한 딥러닝 학습에는 CUDA 호환 GPU와 드라이버가 필요하며, 가상 환경에서 패키지를 관리하는 것이 권장됩니다.

# 필수 패키지 설치 pip install numpy pandas matplotlib seaborn # scikit-learn pip install scikit-learn # TensorFlow (GPU 자동 포함) pip install tensorflow # PyTorch (https://pytorch.org 에서 플랫폼별 명령어 확인) pip install torch torchvision torchaudio # Hugging Face Transformers pip install transformers datasets # 기타 유용한 패키지 pip install opencv-python pillow albumentations
# requirements.txt 예시 numpy>=1.24.0 pandas>=2.0.0 matplotlib>=3.7.0 seaborn>=0.12.0 scikit-learn>=1.3.0 tensorflow>=2.13.0 torch>=2.0.0 transformers>=4.30.0 opencv-python>=4.8.0
# GPU 확인 import tensorflow as tf print(tf.config.list_physical_devices("GPU")) import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))

NumPy (수치 계산)

NumPy(Numerical Python)는 파이썬 과학 계산의 핵심 라이브러리입니다. C로 구현된 다차원 배열 객체 ndarray를 제공하며, 반복문 없이 벡터화 연산으로 수십~수백 배의 성능을 냅니다. Pandas, scikit-learn, TensorFlow, PyTorch 등 거의 모든 AI/데이터 라이브러리의 기반입니다.

설치: pip install numpy  |  확인: python -c "import numpy; print(numpy.__version__)"

배열 생성 (Array Creation)

import numpy as np # 리스트/튜플에서 생성 a1 = np.array([1, 2, 3]) # 1차원 (ndim=1) a2 = np.array([[1, 2], [3, 4]]) # 2차원 (ndim=2) a3 = np.array([[[1, 2], [3, 4]]]) # 3차원 (ndim=3) # 특수 배열 생성 np.zeros((3, 4)) # 0으로 채운 3×4 행렬 np.ones((2, 3)) # 1로 채운 2×3 행렬 np.full((3, 3), 7) # 7로 채운 3×3 행렬 np.eye(4) # 4×4 단위 행렬(항등행렬) np.empty((2, 2)) # 초기화 없이 메모리 할당 (속도 빠름) # 수열 배열 np.arange(0, 10, 2) # [0 2 4 6 8] — range와 유사, 실수 step 가능 np.linspace(0, 1, 5) # [0. 0.25 0.5 0.75 1.] — 균등 간격 n개 np.logspace(0, 3, 4) # [1. 10. 100. 1000.] — 로그 균등 # 타입 지정 np.array([1, 2, 3], dtype=np.float32) # float32 (메모리 절반) np.zeros(5, dtype=np.int64) # int64 np.array([True, False], dtype=np.bool_) # bool

배열 속성 (ndarray Attributes)

a = np.array([[1, 2, 3], [4, 5, 6]]) a.ndim # 2 — 차원 수 a.shape # (2, 3) — (행, 열) 크기 a.size # 6 — 전체 요소 수 a.dtype # dtype('int64') — 요소 데이터 타입 a.itemsize # 8 — 요소 하나의 바이트 크기 a.nbytes # 48 — 전체 바이트 크기 (size × itemsize) a.T # 전치 행렬 shape=(3, 2) a.real # 실수부 (복소수 배열일 때) a.imag # 허수부

인덱싱 & 슬라이싱 (Indexing & Slicing)

a = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]]) # 기본 인덱싱 a[0, 1] # 20 — (0행, 1열) a[-1, -1] # 90 — 마지막 요소 # 슬라이싱 [행 start:stop:step, 열 start:stop:step] a[0:2, 1:3] # [[20,30],[50,60]] a[:, 0] # [10 40 70] — 0번 열 전체 a[1, :] # [40 50 60] — 1번 행 전체 a[::2, ::2] # [[10,30],[70,90]] — 2칸 간격 # 팬시 인덱싱 (Fancy Indexing) — 인덱스 목록으로 선택 idx = [0, 2] a[idx] # [[10,20,30],[70,80,90]] — 0행, 2행 a[:, idx] # 0열, 2열 선택 # 불리언 인덱싱 (Boolean Indexing) mask = a > 50 a[mask] # [60 70 80 90] — 조건 만족 요소 a[a % 20 == 0] # 20의 배수만 # 인덱스 위치 찾기 np.where(a > 50) # 조건 위치 (행 배열, 열 배열) 반환 np.argmax(a), np.argmin(a) # 최대/최소 위치 (flatten 기준) np.argmax(a, axis=0) # 열별 최대값 위치

형태 변환 (Reshaping)

a = np.arange(12) # [0 1 2 ... 11] # reshape — 데이터 공유(뷰), 변경 시 원본도 변경 a.reshape(3, 4) # 3행 4열 a.reshape(2, -1) # -1은 자동 계산 → (2, 6) a.reshape(-1, 1) # 열 벡터 (12, 1) a.reshape(1, -1) # 행 벡터 (1, 12) # 차원 추가/제거 a[np.newaxis, :] # (1, 12) — 앞에 차원 추가 a[:, np.newaxis] # (12, 1) — 뒤에 차원 추가 np.expand_dims(a, axis=0) # (1, 12) np.squeeze(a.reshape(1,12)) # 크기 1인 차원 제거 → (12,) # 평탄화 b = a.reshape(3, 4) b.ravel() # 1차원 뷰 반환 (원본 공유) b.flatten() # 1차원 복사본 반환 (독립) # 전치 (Transpose) b.T # shape (3,4) → (4,3) np.transpose(b, (1, 0)) # 축 순서 지정

브로드캐스팅 (Broadcasting)

브로드캐스팅 규칙: 두 배열의 shape를 오른쪽에서 비교해서, ① 같거나 ② 한쪽이 1이면 연산 가능합니다. 크기 1인 차원이 자동으로 확장됩니다.
# 스칼라 브로드캐스팅 a = np.array([1, 2, 3]) a + 10 # [11 12 13] a * 2 # [2 4 6] # 2D ↔ 1D 브로드캐스팅 m = np.array([[1, 2, 3], # shape (2, 3) [4, 5, 6]]) v = np.array([10, 20, 30]) # shape (3,) → 자동으로 (1,3)→(2,3) m + v # [[11,22,33],[14,25,36]] # 열 벡터 ↔ 행 벡터 col = np.array([[1], [2], [3]]) # shape (3, 1) row = np.array([10, 20]) # shape (2,) → (1, 2) col + row # shape (3, 2) — 외적처럼 확장 # [[11,21],[12,22],[13,23]] # 실용: 정규화 (각 열을 평균 0으로) data = np.random.randn(100, 5) data -= data.mean(axis=0) # 열별 평균 빼기 data /= data.std(axis=0) # 열별 표준편차 나누기

수학 함수 (Math Functions)

a = np.array([1.0, 4.0, 9.0, 16.0]) # 기본 수학 np.sqrt(a) # [1. 2. 3. 4.] np.square(a) # 제곱 np.abs(np.array([-1, 2, -3])) # 절대값 [1 2 3] np.power(a, 0.5) # 거듭제곱 (sqrt와 동일) # 지수/로그 np.exp(np.array([0, 1, 2])) # e^x: [1. 2.718 7.389] np.log(a) # 자연로그 np.log2(a) # 밑 2 로그 np.log10(a) # 밑 10 로그 # 삼각함수 (라디안 기준) angles = np.linspace(0, np.pi, 5) np.sin(angles) np.cos(angles) np.tan(angles) np.deg2rad(180) # 도→라디안 (= np.pi) np.rad2deg(np.pi) # 라디안→도 (= 180.0) # 반올림 x = np.array([1.4, 1.5, 2.5, 3.6]) np.round(x) # [1. 2. 2. 4.] (은행가 반올림) np.floor(x) # [1. 1. 2. 3.] (내림) np.ceil(x) # [2. 2. 3. 4.] (올림) np.trunc(x) # [1. 1. 2. 3.] (0 방향 절단) # 누적 연산 a = np.array([1, 2, 3, 4]) np.cumsum(a) # [1 3 6 10] — 누적 합 np.cumprod(a) # [1 2 6 24] — 누적 곱 np.diff(a) # [1 1 1] — 차분

통계 함수 (Statistical Functions)

a = np.array([[2, 4, 6], [8, 10, 12]]) # 기본 통계 — axis=None(전체), axis=0(열별), axis=1(행별) np.sum(a) # 42 np.sum(a, axis=0) # [10 14 18] — 열별 합 np.sum(a, axis=1) # [12 30] — 행별 합 np.mean(a) # 7.0 np.median(a) # 7.0 np.std(a) # 표준편차 np.var(a) # 분산 np.min(a), np.max(a) # 2, 12 np.ptp(a) # peak-to-peak (max-min) = 10 # 퍼센타일 / 분위수 np.percentile(a, 25) # 1사분위수 (Q1) np.percentile(a, [25, 50, 75]) # Q1, Q2, Q3 np.quantile(a, 0.9) # 90번째 백분위 # 상관 계수 / 공분산 x = np.array([1, 2, 3, 4]) y = np.array([2, 4, 5, 8]) np.corrcoef(x, y) # 상관 계수 행렬 np.cov(x, y) # 공분산 행렬 # 히스토그램 counts, edges = np.histogram(a.ravel(), bins=4)

선형대수 (Linear Algebra — np.linalg)

A = np.array([[3, 1], [1, 2]]) b = np.array([9, 8]) # 행렬 곱 np.dot(A, A) # 행렬 곱셈 (2D 권장) A @ A # @ 연산자 (Python 3.5+, 권장) np.matmul(A, A) # matmul (배치 연산 지원) # np.linalg 함수 np.linalg.det(A) # 행렬식(determinant) np.linalg.inv(A) # 역행렬 np.linalg.solve(A, b) # Ax=b 연립방정식 풀기 (inv 대신 권장) np.linalg.norm(b) # 벡터 노름(크기) — L2 norm np.linalg.norm(b, 1) # L1 norm np.linalg.norm(A, "fro") # Frobenius norm # 고유값 분해 (Eigendecomposition) eigenvalues, eigenvectors = np.linalg.eig(A) # 특이값 분해 (SVD) U, S, Vt = np.linalg.svd(A) # QR 분해 / LU 분해 Q, R = np.linalg.qr(A) # 랭크 / 대각 / 대각합 np.linalg.matrix_rank(A) # 행렬 랭크 np.diag(A) # 대각 요소 추출 np.diag([1, 2, 3]) # 대각 행렬 생성 np.trace(A) # 대각합 (trace)

난수 생성 (Random — np.random)

rng = np.random.default_rng(seed=42) # 권장: 새 Generator API # 연속 분포 rng.random((3, 4)) # [0, 1) 균등 분포 rng.uniform(-1, 1, 5) # [-1, 1) 균등 분포 rng.normal(0, 1, (3, 3)) # 정규 분포 (평균=0, 표준편차=1) rng.exponential(2, 10) # 지수 분포 rng.beta(2, 5, 10) # 베타 분포 rng.poisson(3, 10) # 포아송 분포 # 이산 분포 rng.integers(0, 10, 5) # [0, 10) 정수 (구 randint) rng.choice([10, 20, 30], 5, replace=True) # 복원 추출 rng.choice([10, 20, 30], 2, replace=False) # 비복원 추출 # 셔플 / 순열 arr = np.arange(10) rng.shuffle(arr) # 제자리 셔플 (in-place) rng.permutation(arr) # 셔플된 새 배열 반환 # 레거시 API (구버전 호환) np.random.seed(42) # 재현성 고정 (전역 상태 — 비권장) np.random.rand(3, 3) # [0,1) 균등 np.random.randn(3, 3) # 표준 정규 분포

배열 결합 & 분할 (Concatenation & Splitting)

a = np.array([[1, 2], [3, 4]]) b = np.array([[5, 6], [7, 8]]) # 결합 (concatenate) np.concatenate([a, b], axis=0) # 행 방향 → (4,2) np.concatenate([a, b], axis=1) # 열 방향 → (2,4) np.vstack([a, b]) # 행 쌓기 (axis=0), shape 달라도 열수 같으면 OK np.hstack([a, b]) # 열 쌓기 (axis=1) np.dstack([a, b]) # 깊이 쌓기 (axis=2) # 새 축으로 쌓기 np.stack([a, b], axis=0) # → (2,2,2): 새 축(0) 생성 np.stack([a, b], axis=2) # → (2,2,2): 새 축(2) 생성 # 분할 (split) arr = np.arange(12).reshape(4, 3) np.split(arr, 2, axis=0) # 행 방향 2등분 → [shape(2,3), shape(2,3)] np.vsplit(arr, 2) # vsplit = split(..., axis=0) np.hsplit(arr, 3) # 열 방향 3등분 → [shape(4,1), ...] np.array_split(arr, 3, axis=0) # 불균등 분할도 가능

정렬 & 탐색 (Sorting & Searching)

a = np.array([3, 1, 4, 1, 5, 9, 2, 6]) # 정렬 np.sort(a) # [1 1 2 3 4 5 6 9] — 새 배열 a.sort() # in-place 정렬 np.sort(a)[:-1] # 내림차순 슬라이싱 # 간접 정렬 (인덱스 반환) np.argsort(a) # 정렬 시 원래 인덱스 순서 a[np.argsort(a)] # 정렬된 배열 (argsort 활용) # 2D 정렬 m = np.array([[3,1],[4,2]]) np.sort(m, axis=0) # 열별 정렬 np.sort(m, axis=1) # 행별 정렬 # 탐색 np.searchsorted([1,3,5], 4) # 삽입 위치 → 2 (이진 탐색) np.nonzero(a) # 0이 아닌 요소 위치 np.where(a > 4, a, 0) # 조건 기반 대체 # 집합 연산 np.unique(a) # 중복 제거 + 정렬 np.unique(a, return_counts=True) # 각 값의 빈도 np.intersect1d([1,2,3], [2,3,4]) # 교집합 np.union1d([1,2], [2,3]) # 합집합 np.setdiff1d([1,2,3], [2,3]) # 차집합 np.isin(a, [1,3,5]) # 멤버십 검사 (bool 배열)

파일 I/O

arr = np.arange(12).reshape(3, 4) # 이진 형식 (.npy / .npz) — 빠르고 정확, dtype 보존 np.save("array.npy", arr) # 단일 배열 저장 loaded = np.load("array.npy") # 불러오기 np.savez("data.npz", x=arr, y=arr*2) # 여러 배열 f = np.load("data.npz") f["x"], f["y"] # 키로 접근 np.savez_compressed("data.npz", arr=arr) # 압축 저장 # 텍스트 형식 (.txt / .csv) — 사람이 읽을 수 있음 np.savetxt("data.csv", arr, delimiter=",", fmt="%.4f") loaded_txt = np.loadtxt("data.csv", delimiter=",") # genfromtxt — 결측치/헤더 처리 가능 data = np.genfromtxt("data.csv", delimiter=",", skip_header=1, filling_values=0)

벡터화 & 성능 최적화

# np.vectorize — 스칼라 함수를 배열 함수로 래핑 def my_func(x): return x ** 2 + 1 if x > 0 else 0 vfunc = np.vectorize(my_func) vfunc(np.array([-1, 0, 2, 3])) # [0, 0, 5, 10] # np.where — 조건 분기 (벡터화된 if-else) a = np.array([-3, -1, 0, 2, 4]) np.where(a > 0, a, 0) # ReLU: [0 0 0 2 4] np.where(a > 0, "pos", "non-pos") # 문자열도 가능 # np.select — 다중 조건 conditions = [a < 0, a == 0, a > 0] choices = ["neg", "zero", "pos"] np.select(conditions, choices, default="?") # 메모리 레이아웃 — C order(행 우선) vs F order(열 우선) c_arr = np.ascontiguousarray(arr) # C-contiguous (행 연속) f_arr = np.asfortranarray(arr) # F-contiguous (열 연속) arr.flags # 메모리 레이아웃 정보 # 뷰 vs 복사 — 메모리 공유 여부 확인 view = arr[0:2] # 뷰 (메모리 공유) copy = arr[0:2].copy() # 독립 복사본 view.base is arr # True (뷰 확인)

NumPy vs 순수 Python 성능 비교

작업순수 PythonNumPy속도 차이
1백만 요소 합산sum(list)np.sum(arr)~10–50배 빠름
요소별 곱셈list comprehensionarr * arr~20–100배 빠름
행렬 곱이중 for 루프A @ B~100배+ 빠름
메모리 사용int 객체 ~28Bint64 8B~3배 적음
조건 필터list comprehensionarr[mask]~10배 빠름
주의: 반복문 안에서 NumPy 배열을 요소 하나씩 접근하면 오히려 느립니다. NumPy는 벡터화 연산으로 사용할 때 최고 성능을 발휘합니다. 루프 대신 브로드캐스팅, np.where, np.vectorize, ufunc를 활용하세요.

Pandas (데이터 처리)

Pandas는 표 형태의 데이터를 다루는 파이썬의 핵심 라이브러리입니다. Series(1차원)와 DataFrame(2차원) 두 가지 자료구조를 중심으로, 데이터 로딩·정제·변환·집계·시각화까지 데이터 분석의 전 과정을 지원합니다.

설치: pip install pandas  |  권장: pip install pandas openpyxl pyarrow (Excel·Parquet 지원 포함)

Series — 1차원 자료구조

import pandas as pd import numpy as np # 생성 s = pd.Series([10, 20, 30, 40]) s = pd.Series([10, 20, 30], index=["a", "b", "c"]) # 레이블 인덱스 s = pd.Series({"x": 1, "y": 2, "z": 3}) # 딕셔너리에서 s = pd.Series(np.arange(5), dtype="float32") # 속성 s.values # numpy 배열 s.index # 인덱스 s.dtype # 데이터 타입 s.shape # (n,) s.name # 시리즈 이름 # 인덱싱 s["a"] # 레이블 접근 s[["a", "c"]] # 여러 레이블 s[s > 15] # 불리언 필터 # 연산 — 인덱스 기준으로 정렬 후 연산 (align) s1 = pd.Series([1, 2, 3], index=["a", "b", "c"]) s2 = pd.Series([10, 20], index=["b", "c"]) s1 + s2 # a: NaN, b: 22, c: 33 (불일치 → NaN) s1.add(s2, fill_value=0) # NaN 대신 0으로 채워 연산 # 유용한 메서드 s.value_counts() # 값별 빈도 s.unique() # 고유값 배열 s.nunique() # 고유값 개수 s.sort_values() # 값 기준 정렬 s.apply(lambda x: x ** 2) # 요소별 함수 적용 s.map({10: "low", 20: "mid", 30: "high"}) # 값 매핑

DataFrame 생성

# 딕셔너리 → DataFrame (가장 일반적) df = pd.DataFrame({ "name" : ["Alice", "Bob", "Charlie"], "age" : [25, 30, 35], "score": [85.0, 90.5, 78.3], "pass" : [True, True, False] }) # 리스트 of 딕셔너리 df = pd.DataFrame([ {"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}, ]) # NumPy 배열에서 df = pd.DataFrame(np.random.randn(5, 3), columns=["A", "B", "C"], index=pd.date_range("2024-01", periods=5, freq="ME")) # 빈 DataFrame 후 열 추가 df = pd.DataFrame() df["x"] = [1, 2, 3] # 인덱스 지정 df.index.name = "id"

기본 속성 & 탐색

df.shape # (행수, 열수) df.dtypes # 각 열의 타입 df.index # 행 인덱스 df.columns # 열 이름 목록 df.values # numpy 배열 df.size # 전체 요소 수 df.memory_usage(deep=True) # 열별 메모리 사용량 # 데이터 미리보기 df.head(5) # 상위 5행 (기본값) df.tail(3) # 하위 3행 df.sample(5) # 무작위 5행 df.sample(frac=0.1) # 10% 무작위 # 요약 정보 df.info() # 타입·결측치·메모리 df.describe() # 수치형 통계 요약 (count/mean/std/min/Q/max) df.describe(include="all") # 범주형 포함 df.describe(percentiles=[.1, .5, .9]) # 퍼센타일 지정 # 열 탐색 df["age"].value_counts() # 값별 빈도 df["age"].value_counts(normalize=True) # 비율 df["age"].nunique() # 고유값 개수 df["age"].unique() # 고유값 배열

인덱싱 & 선택 (loc / iloc / query)

방법기준예시특징
df["col"]열 이름df["age"]Series 반환
df[["c1","c2"]]열 이름 목록df[["name","age"]]DataFrame 반환
.loc[행, 열]레이블 기반df.loc[0, "age"]슬라이스 끝 포함
.iloc[행, 열]위치(정수) 기반df.iloc[0:3, 1:3]슬라이스 끝 미포함
.at[행, 열]단일 레이블df.at[0, "age"]스칼라, 빠름
.iat[행, 열]단일 위치df.iat[0, 1]스칼라, 가장 빠름
.query()문자열 표현식df.query("age > 25")가독성 좋음
# loc — 레이블 기반 (끝 인덱스 포함) df.loc[0] # 인덱스 0인 행 df.loc[0:3] # 인덱스 0~3행 (3 포함) df.loc[0, "age"] # 스칼라 df.loc[0:3, ["name", "score"]] # 슬라이스 + 여러 열 df.loc[df["age"] > 25] # 불리언 마스크 df.loc[df["age"] > 25, "name"] # 조건 행 + 특정 열 # iloc — 위치 기반 (끝 인덱스 미포함) df.iloc[0] # 첫 번째 행 df.iloc[0:3] # 0, 1, 2행 df.iloc[0:3, 1:3] # 0~2행, 1~2열 df.iloc[[0, 2, 4], [1, 3]] # 팬시 인덱싱 df.iloc[:, :-1] # 마지막 열 제외 전체 # query — SQL 스타일 (변수는 @var) threshold = 80 df.query("age > 25 and score >= @threshold") df.query("name in ['Alice', 'Bob']") df.query("score.between(80, 95)") # 불리언 마스크 조합 mask = (df["age"] > 25) & (df["score"] >= 80) df[mask] df[~mask] # 반전

데이터 타입 변환

# astype — 기본 타입 변환 df["age"] = df["age"].astype("int32") # 메모리 절반 df["score"] = df["score"].astype("float32") df["flag"] = df["flag"].astype(bool) # 수치 변환 — 오류 처리 pd.to_numeric(df["col"], errors="coerce") # 변환 실패 → NaN pd.to_numeric(df["col"], errors="ignore") # 실패 시 원본 유지 pd.to_numeric(df["col"], downcast="integer") # 가능한 작은 int로 # 날짜 변환 df["date"] = pd.to_datetime(df["date"]) df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d") df["date"] = pd.to_datetime(df["date"], errors="coerce") # 카테고리 타입 — 반복 문자열에 효율적 df["grade"] = df["grade"].astype("category") df["grade"].cat.categories # 카테고리 목록 df["grade"].cat.codes # 정수 코드 pd.Categorical(df["grade"], categories=["C","B","A"], ordered=True) # 순서 있는 카테고리 # 일괄 타입 최적화 df = df.convert_dtypes() # Pandas 2.0+: 최적 타입 자동 추론

결측치 처리 (Missing Data)

# 결측치 확인 df.isnull() # 요소별 bool df.isnull().sum() # 열별 결측치 수 df.isnull().sum() / len(df) # 결측 비율 df.notnull() # 반대 # 결측치 제거 df.dropna() # 결측 있는 행 전체 제거 df.dropna(axis=1) # 결측 있는 열 제거 df.dropna(subset=["name", "age"]) # 특정 열 기준 df.dropna(thresh=3) # 유효값이 3개 미만인 행 제거 df.dropna(how="all") # 모두 NaN인 행만 제거 # 결측치 채우기 df.fillna(0) # 모든 NaN → 0 df["score"].fillna(df["score"].mean()) # 평균으로 df["score"].fillna(df["score"].median()) # 중앙값으로 df["grade"].fillna(df["grade"].mode()[0]) # 최빈값으로 df.fillna({"age": 0, "name": "unknown"}) # 열별 다른 값 df["col"].ffill() # 앞 값으로 전파 (forward fill) df["col"].bfill() # 뒤 값으로 전파 (backward fill) # 보간 (Interpolation) df["price"].interpolate(method="linear") # 선형 보간 df["price"].interpolate(method="spline", order=2) # 스플라인 df["price"].interpolate(limit=2) # 최대 2개만

데이터 정제 & 변환

# 열/행 이름 변경 df.rename(columns={"name": "Name", "age": "Age"}) df.rename(index={0: "first"}) df.columns = df.columns.str.lower().str.replace(" ", "_") # 일괄 소문자+언더스코어 # 열/행 추가·삭제 df["bonus"] = df["score"] * 0.1 # 새 열 추가 df.insert(2, "rank", df["score"].rank()) # 위치 지정 삽입 df.drop(columns=["bonus"]) # 열 삭제 df.drop(index=[0, 2]) # 행 삭제 df.pop("bonus") # 열 제거 후 반환 # 중복 처리 df.duplicated() # 중복 행 여부 bool df.duplicated(subset=["name"]) # 특정 열 기준 df.drop_duplicates() # 중복 제거 df.drop_duplicates(subset=["name"], keep="last") # 마지막 남기기 # 값 치환 df["grade"].replace("A", "Excellent") df["grade"].replace({"A": 4, "B": 3, "C": 2}) df.replace(np.nan, 0) # 전체 NaN → 0 # apply — 행/열 단위 함수 적용 df["score_scaled"] = df["score"].apply(lambda x: (x - 50) / 50) df.apply(np.sum, axis=0) # 열별 합 df.apply(np.sum, axis=1) # 행별 합 df[["age","score"]].apply(lambda row: row["age"] * row["score"], axis=1) # map — Series 요소별 매핑 df["grade"].map({"A": 4, "B": 3, "C": 2}) # 구간 분류 pd.cut(df["score"], bins=[0,60,80,100], labels=["F","B","A"]) pd.qcut(df["score"], q=4) # 4분위로 자동 분류 # 인덱스 재설정 df.reset_index(drop=True) # 0,1,2... 재번호 df.set_index("name") # 열 → 인덱스 df.set_index(["year", "month"]) # 멀티 인덱스

정렬 & 순위

# 값 기준 정렬 df.sort_values("age") # 오름차순 df.sort_values("age", ascending=False) # 내림차순 df.sort_values(["grade", "score"], ascending=[True, False]) # 복합 정렬 df.sort_values("score", na_position="first") # NaN 앞에 배치 # 인덱스 기준 정렬 df.sort_index() df.sort_index(ascending=False) # 순위 (rank) df["rank"] = df["score"].rank(ascending=False) # 내림차순 순위 df["score"].rank(method="dense") # 동점 시 건너뜀 없음 df["score"].rank(method="min") # 동점 시 최소 순위 df["score"].rank(pct=True) # 백분위 순위 (0~1) # 상위/하위 N개 df.nlargest(5, "score") # 상위 5개 df.nsmallest(3, "age") # 하위 3개

그룹화 & 집계 (GroupBy)

# 기본 groupby grp = df.groupby("grade") grp["score"].mean() # 학점별 평균 grp["score"].agg(["mean", "std", "count"]) # 여러 집계 # 다중 키 그룹화 df.groupby(["dept", "grade"])["score"].mean() # agg — 열별로 다른 집계 함수 df.groupby("grade").agg( avg_score=("score", "mean"), max_age =("age", "max"), count =("name", "count") ) # transform — 원래 shape 유지 (그룹 통계 → 각 행에 브로드캐스트) df["grade_avg"] = df.groupby("grade")["score"].transform("mean") df["score_z"] = df.groupby("grade")["score"].transform( lambda x: (x - x.mean()) / x.std() # 그룹 내 z-score ) # filter — 조건 만족 그룹만 남기기 df.groupby("grade").filter(lambda g: g["score"].mean() >= 80) # pivot_table — 엑셀 피벗과 동일 pd.pivot_table(df, values="score", index="dept", columns="grade", aggfunc="mean", fill_value=0) # crosstab — 빈도 교차표 pd.crosstab(df["dept"], df["grade"], margins=True) pd.crosstab(df["dept"], df["grade"], values=df["score"], aggfunc="mean")

데이터 결합 (Merge / Join / Concat)

# concat — 같은 구조의 DataFrame 이어 붙이기 pd.concat([df1, df2]) # 행 방향 (axis=0, 기본) pd.concat([df1, df2], ignore_index=True) # 인덱스 재번호 pd.concat([df1, df2], axis=1) # 열 방향 pd.concat([df1, df2], join="inner") # 공통 열만 # merge — SQL JOIN과 동일 개념 pd.merge(df_left, df_right, on="id") # INNER JOIN pd.merge(df_left, df_right, on="id", how="left") # LEFT JOIN pd.merge(df_left, df_right, on="id", how="right") # RIGHT JOIN pd.merge(df_left, df_right, on="id", how="outer") # FULL OUTER JOIN pd.merge(df_left, df_right, left_on="user_id", right_on="id") # 다른 열 이름 pd.merge(df_left, df_right, on=["year", "month"]) # 복합 키 pd.merge(df_left, df_right, on="id", suffixes=("_left", "_right")) # 중복 열명 접미사 # join — 인덱스 기준 병합 (merge의 편의 래퍼) df1.join(df2, how="left") df1.join(df2, lsuffix="_l", rsuffix="_r")

시계열 (Time Series)

# DatetimeIndex 생성 dates = pd.date_range("2024-01-01", periods=12, freq="ME") # 월말 dates = pd.date_range("2024-01-01", "2024-12-31", freq="D") # 일별 dates = pd.bdate_range("2024-01-01", periods=10) # 영업일 # dt 접근자 — datetime 열 속성 df["date"] = pd.to_datetime(df["date"]) df["year"] = df["date"].dt.year df["month"] = df["date"].dt.month df["weekday"] = df["date"].dt.day_name() df["quarter"] = df["date"].dt.quarter df["is_month_end"] = df["date"].dt.is_month_end # resample — 시간 단위 리샘플링 ts = df.set_index("date")["price"] ts.resample("ME").mean() # 월별 평균 (다운샘플링) ts.resample("D").ffill() # 일별로 확장 (업샘플링, 앞 값으로) ts.resample("QE").agg({"price": ["mean", "max"]}) # 분기별 집계 # rolling / expanding — 이동 통계 ts.rolling(window=7).mean() # 7일 이동 평균 ts.rolling(window=7, min_periods=1).std() # 최소 1개부터 계산 ts.expanding().mean() # 누적 평균 ts.ewm(span=7).mean() # 지수 가중 이동 평균 # shift / diff — 시차 & 변화량 ts.shift(1) # 1기간 뒤로 (lag) ts.shift(-1) # 1기간 앞으로 (lead) ts.diff(1) # 1기간 차분 ts.pct_change() # 전기 대비 변화율

문자열 처리 (str 접근자)

s = df["name"] # 문자열 Series # 검색 & 판별 s.str.contains("li", case=False) # 대소문자 무시 포함 여부 s.str.startswith("A") # 시작 여부 s.str.endswith("e") # 끝 여부 s.str.match(r"^[A-Z]") # 정규식 매치 s.str.fullmatch(r"[A-Za-z]+") # 전체 일치 # 변환 s.str.upper() # 대문자 s.str.lower() # 소문자 s.str.strip() # 양쪽 공백 제거 s.str.replace(" ", "_", regex=False) # 치환 s.str.replace(r"\s+", "_", regex=True) # 정규식 치환 # 분리 & 추출 s.str.split("_") # 분리 → 리스트 s.str.split("_", expand=True) # DataFrame으로 확장 s.str.split("_").str[0] # 첫 번째 요소 s.str.extract(r"(\d+)") # 정규식 캡처 그룹 # 길이 & 카운트 s.str.len() # 문자열 길이 s.str.count("a") # 특정 문자 개수 s.str.zfill(5) # 앞에 0 채우기

파일 I/O

# CSV df = pd.read_csv("data.csv", encoding="utf-8", index_col="id", parse_dates=["date"], dtype={"age": "int32"}, na_values=["-", "N/A", "?"], chunksize=10_000) # 대용량: 청크 단위 읽기 df.to_csv("out.csv", index=False, encoding="utf-8-sig") # BOM for Excel # Excel (openpyxl 필요) df = pd.read_excel("data.xlsx", sheet_name="Sheet1", header=1) df.to_excel("out.xlsx", index=False, sheet_name="Result") # 여러 시트를 한 파일에 쓰기 with pd.ExcelWriter("report.xlsx", engine="openpyxl") as writer: df1.to_excel(writer, sheet_name="Summary", index=False) df2.to_excel(writer, sheet_name="Detail", index=False) # JSON df = pd.read_json("data.json", orient="records") df.to_json("out.json", orient="records", force_ascii=False) # Parquet — 컬럼형 이진, 대용량에 권장 (pyarrow 필요) df = pd.read_parquet("data.parquet") df.to_parquet("out.parquet", compression="snappy") # SQL (SQLAlchemy) from sqlalchemy import create_engine engine = create_engine("sqlite:///mydb.db") df = pd.read_sql("SELECT * FROM users WHERE age > 20", engine) df.to_sql("result", engine, if_exists="replace", index=False) # 대용량 CSV 청크 처리 chunks = pd.read_csv("big.csv", chunksize=50_000) result = pd.concat( chunk[chunk["age"] > 25] for chunk in chunks )

성능 최적화

# 1. dtype 다운캐스팅 — 메모리 대폭 절감 df["age"] = pd.to_numeric(df["age"], downcast="integer") # int64→int8 df["score"] = pd.to_numeric(df["score"], downcast="float") # float64→float32 df["grade"] = df["grade"].astype("category") # 반복 문자열 # 2. iterrows 대신 벡터화 # 느림 (절대 금지): for idx, row in df.iterrows(): df.at[idx, "bonus"] = row["score"] * 0.1 # 빠름: df["bonus"] = df["score"] * 0.1 # 3. inplace=True — Pandas 2.0+ 에서 더 이상 성능 이점 없음, 지양 권장 # 구버전 관용: df.dropna(inplace=True) # 권장 (재할당): df = df.dropna() # 4. copy vs view — SettingWithCopyWarning 방지 subset = df[df["age"] > 25].copy() # 명시적 복사 subset["bonus"] = 100 # 안전하게 수정 가능 # 5. pipe — 메서드 체인 가독성 result = ( df .dropna(subset=["score"]) .query("age >= 20") .assign(bonus=lambda x: x["score"] * 0.1) .groupby("grade") .agg(avg=("score", "mean")) .reset_index() )

Pandas vs SQL 대응표

SQLPandas
SELECT col FROM tdf["col"]
SELECT * FROM t WHERE age > 25df.query("age > 25")
ORDER BY score DESCdf.sort_values("score", ascending=False)
GROUP BY gradedf.groupby("grade")
COUNT(*)df.groupby("grade").size()
INNER JOINpd.merge(df1, df2, on="id")
LEFT JOINpd.merge(df1, df2, on="id", how="left")
UNION ALLpd.concat([df1, df2])
DISTINCTdf.drop_duplicates()
LIMIT 10df.head(10)
Pandas 2.0 주요 변경:
  • inplace=True는 복사를 방지하지 않습니다 — 재할당(df = df.method()) 권장
  • Copy-on-Write(CoW) 기본 활성화 예정 — .copy()를 명시적으로 사용
  • freq="M"freq="ME" (월말), "QE" (분기말) 등 별칭 변경

데이터 전처리 핵심

데이터 전처리는 머신러닝 파이프라인에서 가장 중요한 단계로, 모델 성능의 80% 이상을 좌우합니다. 표준화(Z-score)는 평균 0, 표준편차 1로 변환하고, 정규화(Min-Max)는 0~1 범위로 스케일링합니다. 원핫 인코딩은 범주형 변수를 수치로 변환하며, 데이터 증강(Data Augmentation)은 학습 데이터를 인위적으로 확장하여 과적합을 방지합니다.

import numpy as np from sklearn.preprocessing import StandardScaler, MinMaxScaler, OneHotEncoder # 표준화 (Z-score) scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 정규화 (0-1) minmax = MinMaxScaler() X_normalized = minmax.fit_transform(X) # 원-핫 인코딩 encoder = OneHotEncoder(sparse_output=False) X_encoded = encoder.fit_transform(X categorical.reshape(-1, 1)) # 결측치 처리 # 평균/중앙값 대체 from sklearn.impute import SimpleImputer imputer = SimpleImputer(strategy="mean") X_imputed = imputer.fit_transform(X) # 데이터 증강 (이미지) from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen = ImageDataGenerator( rotation_range=20, width_shift_range=0.2, height_shift_range=0.2, horizontal_flip=True )

Matplotlib (시각화)

Matplotlib은 파이썬의 대표적인 시각화 라이브러리로, 선 그래프·막대·산점도·히스토그램·3D 플롯 등 거의 모든 유형의 차트를 지원합니다. 두 가지 인터페이스가 있으며, 복잡한 레이아웃에는 OOP 방식(fig, ax)을 권장합니다.

설치: pip install matplotlib  |  Jupyter: %matplotlib inline 또는 %matplotlib widget

pyplot vs OOP 인터페이스

구분pyplot (MATLAB 스타일)OOP (권장)
생성plt.figure()fig, ax = plt.subplots()
플롯plt.plot(x, y)ax.plot(x, y)
레이블plt.xlabel("X")ax.set_xlabel("X")
범례plt.legend()ax.legend()
저장plt.savefig("f.png")fig.savefig("f.png")
적합한 경우빠른 탐색, 단일 차트복잡한 레이아웃, 재사용

Figure & Axes 구조

import matplotlib.pyplot as plt import numpy as np # Figure: 전체 캔버스 / Axes: 개별 좌표계(차트 한 개) fig, ax = plt.subplots(figsize=(8, 5)) # 단일 Axes # Figure 속성 fig.set_size_inches(10, 6) fig.set_dpi(150) fig.suptitle("전체 제목", fontsize=16, fontweight="bold") # Axes 속성 (set_* 메서드로 일괄 설정 가능) ax.set_title("차트 제목", fontsize=14) ax.set_xlabel("X축 레이블") ax.set_ylabel("Y축 레이블") ax.set_xlim(0, 10) ax.set_ylim(-1, 1) ax.set_xscale("log") # 로그 스케일 ax.grid(True, linestyle="--", alpha=0.5) ax.set_aspect("equal") # 가로세로 비율 1:1 # 여러 속성 한 번에 ax.set(title="제목", xlabel="X", ylabel="Y", xlim=(0,10)) plt.tight_layout() # 레이아웃 자동 조정 (겹침 방지) plt.show()

선 그래프 (Line Plot)

x = np.linspace(0, 2 * np.pi, 200) fig, ax = plt.subplots(figsize=(10, 4)) # 기본 — label은 legend()에서 표시 ax.plot(x, np.sin(x), label="sin") ax.plot(x, np.cos(x), label="cos") # 스타일 지정 ax.plot(x, np.sin(x), color="#E63946", # HEX, 이름("red"), RGB튜플 모두 가능 linewidth=2.5, # 선 굵기 linestyle="--", # 선 스타일: -, --, -., : marker="o", # 마커: o, s, ^, *, +, x, D markersize=5, markevery=10, # 10개마다 마커 표시 alpha=0.8, # 투명도 0~1 label="sin (styled)") # 단축 포맷 문자열 "색상마커선스타일" ax.plot(x, np.cos(x), "g^--") # 초록 삼각 점선 ax.plot(x, -np.sin(x), "rs:") # 빨강 사각 점선 # 음영 영역 ax.fill_between(x, np.sin(x), 0, where=np.sin(x) > 0, alpha=0.2, color="blue", label="양수 영역") # 수평/수직 보조선 ax.axhline(y=0, color="black", linewidth=0.8) # 수평선 ax.axvline(x=np.pi, color="gray", linestyle=":") # 수직선 ax.axhspan(0.5, 1.0, alpha=0.1, color="green") # 수평 영역 ax.legend(loc="upper right") # 범례 위치 plt.show()

산점도 (Scatter Plot)

rng = np.random.default_rng(42) x = rng.normal(0, 1, 200) y = x * 0.8 + rng.normal(0, 0.5, 200) colors = rng.uniform(0, 1, 200) # 색상 값 (컬러맵 매핑) sizes = rng.uniform(20, 200, 200) # 마커 크기 fig, ax = plt.subplots() sc = ax.scatter(x, y, c=colors, # 색상 배열 또는 단색 cmap="viridis", # 컬러맵 s=sizes, # 마커 크기 alpha=0.7, edgecolors="white", linewidths=0.5) fig.colorbar(sc, ax=ax, label="값") # 컬러바 # 추세선 (회귀선) z = np.polyfit(x, y, 1) p = np.poly1d(z) ax.plot(np.sort(x), p(np.sort(x)), "r--", linewidth=2, label="추세선") ax.legend() plt.show()

막대 그래프 (Bar Chart)

categories = ["Python", "Java", "C++", "JS", "Go"] values_2023 = [32, 17, 13, 14, 9] values_2024 = [35, 16, 12, 15, 11] fig, axes = plt.subplots(1, 3, figsize=(14, 4)) # ① 단순 막대 x = np.arange(len(categories)) axes[0].bar(categories, values_2024, color="steelblue", edgecolor="white", width=0.6) axes[0].set_title("단순 막대") # ② 그룹 막대 w = 0.35 axes[1].bar(x - w/2, values_2023, w, label="2023", color="#457b9d") axes[1].bar(x + w/2, values_2024, w, label="2024", color="#e63946") axes[1].set_xticks(x) axes[1].set_xticklabels(categories) axes[1].legend() axes[1].set_title("그룹 막대") # ③ 수평 막대 axes[2].barh(categories, values_2024, color="coral") axes[2].set_title("수평 막대") # 막대 위에 값 표시 for ax, vals in zip(axes[:2], [values_2024, values_2024]): for bar in ax.patches: ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.3, str(int(bar.get_height())), ha="center", va="bottom", fontsize=9) plt.tight_layout() plt.show()

히스토그램 & KDE (분포)

rng = np.random.default_rng(0) data1 = rng.normal(0, 1, 1000) data2 = rng.normal(3, 1.5, 600) fig, axes = plt.subplots(1, 2, figsize=(12, 4)) # 기본 히스토그램 axes[0].hist(data1, bins=40, # 구간 수, 또는 "auto","fd","scott" density=True, # 정규화 (면적=1) alpha=0.6, color="steelblue", edgecolor="white", label="그룹A") axes[0].hist(data2, bins=40, density=True, alpha=0.6, color="coral", edgecolor="white", label="그룹B") # 정규 분포 PDF 오버레이 from scipy.stats import norm xr = np.linspace(-4, 8, 300) axes[0].plot(xr, norm.pdf(xr, 0, 1), "b-", linewidth=2) axes[0].legend() axes[0].set_title("히스토그램 (density=True)") # 2D 히스토그램 x2 = rng.normal(0, 1, 3000) y2 = x2 * 0.7 + rng.normal(0, 0.5, 3000) h = axes[1].hist2d(x2, y2, bins=40, cmap="Blues") fig.colorbar(h[3], ax=axes[1], label="빈도") axes[1].set_title("2D 히스토그램") plt.tight_layout() plt.show()

박스 플롯 & 바이올린 플롯

rng = np.random.default_rng(1) data = [rng.normal(mu, 1, 100) for mu in [0, 1, 2, 3]] labels = ["A", "B", "C", "D"] fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) # 박스 플롯 — Q1/Q2/Q3, 수염(1.5IQR), 이상치 bp = ax1.boxplot(data, labels=labels, patch_artist=True, # 박스 채우기 notch=True, # 중앙값 노치 showfliers=True, # 이상치 표시 medianprops=dict(color="red", linewidth=2)) colors = ["#a8dadc", "#457b9d", "#1d3557", "#e63946"] for patch, color in zip(bp["boxes"], colors): patch.set_facecolor(color) ax1.set_title("박스 플롯") # 바이올린 플롯 — 분포 모양까지 시각화 vp = ax2.violinplot(data, positions=range(1, 5), showmeans=True, showmedians=True, showextrema=True) for body in vp["bodies"]: body.set_alpha(0.7) ax2.set_xticks(range(1, 5)) ax2.set_xticklabels(labels) ax2.set_title("바이올린 플롯") plt.tight_layout() plt.show()

파이 & 도넛 차트

sizes = [35, 25, 20, 15, 5] labels = ["Python", "Java", "C++", "JS", "기타"] explode = (0.05, 0, 0, 0, 0) # 첫 조각 강조 colors = ["#3776ab", "#e76f51", "#2a9d8f", "#e9c46a", "#adb5bd"] fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) # 파이 차트 wedges, texts, autotexts = ax1.pie( sizes, labels=labels, explode=explode, colors=colors, autopct="%1.1f%%", # 퍼센트 표시 형식 startangle=90, # 시작 각도 counterclock=False, # 시계 방향 shadow=True ) for at in autotexts: at.set_fontsize(9) ax1.set_title("파이 차트") # 도넛 차트 — wedgeprops로 구멍 ax2.pie(sizes, labels=labels, colors=colors, autopct="%1.1f%%", startangle=90, counterclock=False, wedgeprops=dict(width=0.6, edgecolor="white")) ax2.text(0, 0, "언어\n점유율", ha="center", va="center", fontsize=13) ax2.set_title("도넛 차트") plt.tight_layout() plt.show()

히트맵 & 등고선 (Heatmap & Contour)

# 상관 행렬 히트맵 import pandas as pd rng = np.random.default_rng(0) df = pd.DataFrame(rng.normal(0, 1, (100, 5)), columns=list("ABCDE")) corr = df.corr() fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5)) im = ax1.imshow(corr, cmap="coolwarm", vmin=-1, vmax=1) fig.colorbar(im, ax=ax1) # 각 셀에 값 표시 for i in range(len(corr)): for j in range(len(corr)): ax1.text(j, i, f"{corr.iloc[i,j]:.2f}", ha="center", va="center", fontsize=8) ax1.set_xticks(range(len(corr))) ax1.set_yticks(range(len(corr))) ax1.set_xticklabels(corr.columns) ax1.set_yticklabels(corr.columns) ax1.set_title("상관 행렬 히트맵") # 등고선 플롯 xg, yg = np.meshgrid(np.linspace(-3, 3, 100), np.linspace(-3, 3, 100)) z = np.sin(xg) * np.cos(yg) cf = ax2.contourf(xg, yg, z, levels=20, cmap="RdBu_r") ax2.contour(xg, yg, z, levels=10, colors="black", linewidths=0.5) fig.colorbar(cf, ax=ax2) ax2.set_title("등고선 플롯") plt.tight_layout() plt.show()

서브플롯 레이아웃 (Subplots Layout)

# ① subplots — 균일 그리드 fig, axes = plt.subplots(2, 3, figsize=(14, 8), sharex=True, # X축 공유 sharey=False, constrained_layout=True) # tight_layout 대안 axes[0, 0].set_title("(0,0)") # [행, 열] 접근 axes.flat[3].set_title("index 3") # 1D 인덱스 for ax in axes.ravel(): # 전체 순회 ax.plot(np.random.randn(50)) # ② subplot_mosaic — 문자 레이아웃 (Matplotlib 3.3+) layout = [ ["A", "A", "B"], ["C", "D", "B"], ] fig, axd = plt.subplot_mosaic(layout, figsize=(12, 6), constrained_layout=True) axd["A"].set_title("A — 가로 2칸") axd["B"].set_title("B — 세로 2칸") # ③ GridSpec — 세밀한 비율 제어 from matplotlib.gridspec import GridSpec fig = plt.figure(figsize=(12, 6)) gs = GridSpec(2, 3, figure=fig, hspace=0.4, wspace=0.3) ax_top = fig.add_subplot(gs[0, :]) # 0행 전체 ax_bl = fig.add_subplot(gs[1, :2]) # 1행 0~1열 ax_br = fig.add_subplot(gs[1, 2]) # 1행 2열 ax_top.set_title("넓은 상단 차트")

텍스트 & 주석 (Text & Annotation)

fig, ax = plt.subplots(figsize=(9, 5)) x = np.linspace(0, 10, 100) ax.plot(x, np.sin(x)) # 텍스트 (좌표계 기준) ax.text(1.5, 0.9, "피크 근처", fontsize=11, color="red", ha="center", va="bottom", fontweight="bold") # 텍스트 (Figure 비율 기준 — 0~1) fig.text(0.5, 0.01, "출처: 예시 데이터", ha="center", fontsize=8, color="gray") # 화살표 주석 ax.annotate("최대값", xy=(np.pi/2, 1.0), # 화살표 끝(가리키는 곳) xytext=(2.5, 0.6), # 텍스트 위치 fontsize=11, arrowprops=dict( arrowstyle="->", color="darkred", lw=1.5 )) # 수식 (LaTeX) ax.set_title(r"$f(x) = \sin(x)$", fontsize=14) ax.text(7, 0.5, r"$\frac{d}{dx}\sin x = \cos x$", fontsize=12) # 테두리 박스 텍스트 ax.text(5, -0.8, "박스 텍스트", bbox=dict(boxstyle="round,pad=0.3", facecolor="wheat", alpha=0.8)) plt.show()

축 & 눈금 설정

fig, axes = plt.subplots(1, 2, figsize=(12, 4)) # 로그 스케일 x = np.logspace(0, 4, 50) axes[0].semilogy(x, x**2) # Y축 로그 axes[0].set_xscale("log") # X축도 로그 axes[0].set_title("로그-로그 스케일") # 눈금 커스터마이징 ax = axes[1] x2 = np.linspace(0, 2*np.pi, 100) ax.plot(x2, np.sin(x2)) # 주 눈금 ax.set_xticks([0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi]) ax.set_xticklabels(["0", r"$\pi/2$", r"$\pi$", r"$3\pi/2$", r"$2\pi$"]) ax.tick_params(axis="x", labelsize=12, rotation=0) # 보조 눈금 from matplotlib.ticker import MultipleLocator, FuncFormatter ax.xaxis.set_minor_locator(MultipleLocator(np.pi/4)) ax.yaxis.set_major_formatter(FuncFormatter(lambda v, _: f"{v:.1f}")) # 그리드 ax.grid(True, which="major", linestyle="-", alpha=0.5) ax.grid(True, which="minor", linestyle=":", alpha=0.3) ax.set_title("눈금 커스터마이징") plt.tight_layout() plt.show()

스타일 & 테마 (Style & rcParams)

# 사용 가능한 스타일 목록 print(plt.style.available) # 주요: "seaborn-v0_8", "ggplot", "fivethirtyeight", # "dark_background", "bmh", "Solarize_Light2" # 스타일 적용 (전역) plt.style.use("seaborn-v0_8-whitegrid") # 블록 안에서만 임시 적용 with plt.style.context("dark_background"): fig, ax = plt.subplots() ax.plot(np.random.randn(50)) plt.show() # rcParams — 전역 기본값 설정 plt.rcParams.update({ "font.family" : "Malgun Gothic", # 한글 폰트 (Windows) "font.size" : 11, "axes.titlesize" : 14, "axes.labelsize" : 12, "lines.linewidth": 2, "figure.dpi" : 120, "axes.unicode_minus": False, # 한글 폰트 마이너스 깨짐 방지 }) # 한글 폰트 (macOS/Linux) import matplotlib.font_manager as fm # fm.findSystemFonts(fontpaths=None) # 설치 폰트 목록 plt.rcParams["font.family"] = "NanumGothic" # macOS 예시 # 설정 초기화 plt.rcdefaults()

컬러맵 (Colormap)

# 주요 컬러맵 분류 # 순차형: viridis, plasma, inferno, magma, Blues, Reds, YlOrRd # 발산형: coolwarm, RdBu, bwr, seismic, PiYG # 정성형: tab10, tab20, Set1, Set2, Set3, Paired, Accent # 반전: viridis_r, Blues_r (뒤에 _r) # 컬러맵에서 색상 추출 cmap = plt.get_cmap("tab10") colors = [cmap(i) for i in range(5)] # 5가지 색상 # 정규화 (Normalize) from matplotlib.colors import Normalize, LogNorm, BoundaryNorm norm = Normalize(vmin=0, vmax=100) # 선형 norm = LogNorm(vmin=1, vmax=10000) # 로그 norm = BoundaryNorm([0,30,70,100], ncolors=3) # 구간별 # ScalarMappable — colorbar 독립 추가 import matplotlib.cm as cm sm = cm.ScalarMappable(cmap="viridis", norm=Normalize(0, 1)) sm.set_array([]) fig.colorbar(sm, ax=ax, label="값")

3D 플롯

from mpl_toolkits.mplot3d import Axes3D # noqa (import 필요) fig = plt.figure(figsize=(14, 5)) # ① 3D 곡면 플롯 ax1 = fig.add_subplot(131, projection="3d") xg, yg = np.meshgrid(np.linspace(-3, 3, 60), np.linspace(-3, 3, 60)) zg = np.sin(np.sqrt(xg**2 + yg**2)) surf = ax1.plot_surface(xg, yg, zg, cmap="viridis", alpha=0.9, rstride=2, cstride=2) fig.colorbar(surf, ax=ax1, shrink=0.6) ax1.set_title("3D 곡면") # ② 3D 산점도 ax2 = fig.add_subplot(132, projection="3d") rng = np.random.default_rng(0) xs, ys, zs = rng.normal(0, 1, (3, 200)) ax2.scatter(xs, ys, zs, c=zs, cmap="plasma", s=20, alpha=0.7) ax2.set_title("3D 산점도") # ③ 3D 막대 ax3 = fig.add_subplot(133, projection="3d") xpos = [0, 1, 2, 0, 1, 2] ypos = [0, 0, 0, 1, 1, 1] zpos = np.zeros(6) dz = rng.integers(5, 20, 6) ax3.bar3d(xpos, ypos, zpos, 0.8, 0.8, dz, color="steelblue", alpha=0.8) ax3.set_title("3D 막대") plt.tight_layout() plt.show()

파일 저장 (savefig)

fig, ax = plt.subplots() ax.plot([1, 2, 3], [4, 5, 6]) # 기본 저장 — 확장자가 형식 결정 fig.savefig("chart.png") fig.savefig("chart.pdf") fig.savefig("chart.svg") # 벡터 (웹/인쇄에 좋음) # 고품질 PNG fig.savefig("chart_hq.png", dpi=300, # 해상도 (화면 72~96, 인쇄 300+) bbox_inches="tight", # 여백 자동 조정 (필수) pad_inches=0.1, # 테두리 여백 facecolor="white", # 배경색 transparent=False) # 투명 배경 # 투명 PNG (배경 없음) fig.savefig("chart_transparent.png", dpi=150, bbox_inches="tight", transparent=True) # 메모리 버퍼로 (웹 서버 응답 등) import io buf = io.BytesIO() fig.savefig(buf, format="png", dpi=150, bbox_inches="tight") buf.seek(0) image_bytes = buf.getvalue() # bytes 객체 plt.close(fig) # 메모리 해제 (루프·서버에서 필수)

Seaborn 연계 (통계 시각화)

# pip install seaborn import seaborn as sns # Seaborn은 Matplotlib 위에 구축 — ax 인자로 위치 제어 fig, axes = plt.subplots(2, 2, figsize=(12, 10)) df = sns.load_dataset("penguins") # 내장 데이터셋 # 분포 + KDE sns.histplot(df["flipper_length_mm"].dropna(), kde=True, ax=axes[0,0]) # 범주별 박스 sns.boxplot(x="species", y="body_mass_g", data=df, palette="Set2", ax=axes[0,1]) # 산점도 + 회귀선 sns.regplot(x="flipper_length_mm", y="body_mass_g", data=df, scatter_kws={"alpha":0.4}, ax=axes[1,0]) # 히트맵 (상관 행렬) num_df = df.select_dtypes(include="number") sns.heatmap(num_df.corr(), annot=True, fmt=".2f", cmap="coolwarm", ax=axes[1,1]) plt.tight_layout() plt.show()

주요 차트 선택 가이드

목적차트 유형주요 함수
시간에 따른 추이선 그래프ax.plot()
두 변수 관계산점도ax.scatter()
범주별 수치 비교막대 그래프ax.bar() / ax.barh()
분포 형태히스토그램 / KDEax.hist()
분포 요약 + 이상치박스 / 바이올린ax.boxplot() / ax.violinplot()
비율 구성파이 / 도넛ax.pie()
행렬 / 상관관계히트맵ax.imshow() / sns.heatmap()
지리 / 연속 표면등고선ax.contourf()
3차원 데이터3D 곡면 / 산점도ax.plot_surface()
초급자 가이드 - 데이터 과학 시작하기

데이터 과학은 어렵게 느껴질 수 있지만, 단계적으로 접근하면 됩니다:

  • Jupyter Notebook: 코드를 한 줄씩 실행하고 결과를 바로 볼 수 있는 대화형 환경입니다. pip install jupyterjupyter notebook으로 시작하세요.
  • 학습 순서: NumPy(배열 다루기) → Pandas(표 데이터 다루기) → Matplotlib(그래프 그리기) 순서로 학습하는 것이 좋습니다.
  • NumPy = 빠른 계산기: 파이썬 리스트보다 수십 배 빠른 수치 계산을 제공합니다. import numpy as np; arr = np.array([1, 2, 3])로 시작하세요.
  • Pandas = 엑셀 on 파이썬: CSV 파일을 읽고, 필터링하고, 집계하는 작업을 코드로 합니다. import pandas as pd; df = pd.read_csv("data.csv")

팁: Google Colab을 사용하면 설치 없이 브라우저에서 바로 데이터 과학을 시작할 수 있습니다.

중급자 가이드 - 데이터 분석 워크플로우

실무 데이터 분석의 표준 워크플로우:

  • 1. 데이터 수집: pd.read_csv(), pd.read_sql(), API 호출 등으로 데이터를 가져옵니다.
  • 2. 탐색적 데이터 분석(EDA): df.describe(), df.info(), df.corr()로 데이터의 특성을 파악합니다.
  • 3. 전처리: 결측치 처리(fillna(), dropna()), 이상치 제거, 스케일링(StandardScaler), 인코딩(LabelEncoder, OneHotEncoder)을 수행합니다.
  • 4. 시각화: Seaborn의 heatmap(), pairplot()으로 패턴을 시각적으로 탐색합니다.
  • 메모리 최적화: df.astype({"col": "int32"})로 타입을 다운캐스트하면 메모리를 50-80% 절약합니다. category 타입은 반복 문자열에 효과적입니다.
고급자 가이드 - 대규모 데이터 처리

Pandas의 한계를 넘어서는 대규모 데이터 처리 기법:

  • Polars: Rust로 작성된 차세대 DataFrame 라이브러리로, Pandas보다 5-50배 빠릅니다. Lazy evaluation과 멀티코어 병렬 처리가 기본 내장되어 있습니다.
  • Dask: Pandas와 동일한 API로 코어 수를 넘는 대용량 데이터를 분산 처리합니다. dask.dataframe은 메모리를 초과하는 데이터도 처리 가능합니다.
  • NumPy 벡터화: for 루프 대신 배열 연산을 사용하세요. np.where(arr > 0, arr, 0)처럼 조건부 연산도 벡터화합니다. ufunc을 직접 만들려면 np.vectorize() 또는 Numba @vectorize를 사용합니다.
  • GPU 가속: CuPy(NumPy 호환)와 cuDF(Pandas 호환)로 NVIDIA GPU에서 연산을 수행하면 100배 이상의 속도 향상이 가능합니다.
  • Apache Arrow: 언어 간 데이터 교환의 표준 메모리 포맷입니다. Pandas 2.0+에서 Arrow 백엔드를 사용하면 문자열 연산이 크게 빨라집니다.

13. 머신러닝

중급 고급

머신러닝(Machine Learning)은 데이터로부터 패턴을 학습하여 예측이나 의사결정을 자동화하는 AI의 핵심 분야입니다. 지도 학습(분류, 회귀), 비지도 학습(군집화, 차원 축소), 강화 학습 세 가지 범주로 나뉘며, 파이썬에서는 scikit-learn이 전통적 ML의 표준 라이브러리입니다.

전체 데이터 Dataset 훈련 데이터 테스트 데이터 모델 학습 fit(X_train, y_train) 예측 predict(X_test) 평가 score, metrics 지도 학습 (Supervised) 분류(Classification), 회귀(Regression) 비지도 학습 (Unsupervised) 군집화(Clustering), 차원 축소 강화 학습 (RL) 에이전트, 보상 기반 학습 scikit-learn의 fit → predict → score 패턴으로 통일된 머신러닝 워크플로우

scikit-learn (머신러닝)

scikit-learn은 파이썬의 대표적인 머신러닝 라이브러리로, 분류·회귀·클러스터링·차원 축소·전처리·모델 선택 등 전통적 ML 알고리즘을 일관된 Estimator API로 제공합니다. fit / transform / predict 세 메서드 패턴만 익히면 모든 모델을 같은 방식으로 사용할 수 있습니다.

설치: pip install scikit-learn  |  버전 확인: sklearn.__version__

Estimator API 핵심 패턴

# 모든 scikit-learn 객체는 동일한 인터페이스 # fit(X, y) — 학습 (파라미터 추정) # transform(X) — 변환 (전처리기) # fit_transform(X) — 학습 + 변환 (훈련 데이터에만) # predict(X) — 예측 (지도학습 모델) # predict_proba(X) — 클래스 확률 반환 # score(X, y) — 기본 평가 지표 (분류: accuracy, 회귀: R²) # 중요 규칙: fit은 반드시 훈련 데이터로만, transform은 전체에 적용 from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_sc = scaler.fit_transform(X_train) # 훈련: 평균·표준편차 학습 + 변환 X_test_sc = scaler.transform(X_test) # 테스트: 학습된 값으로만 변환 # ❌ X_test_sc = scaler.fit_transform(X_test) # 데이터 누수(leakage)!

내장 데이터셋 & 데이터 분할

from sklearn.datasets import ( load_iris, # 붓꽃 분류 (150×4, 3클래스) load_digits, # 손글씨 숫자 (1797×64, 10클래스) load_breast_cancer, # 유방암 (569×30, 이진분류) load_boston, # 보스턴 집값 — deprecated, 아래 대체 fetch_california_housing, # 캘리포니아 주택가 (회귀) make_classification, # 합성 분류 데이터 make_regression, # 합성 회귀 데이터 make_blobs, # 클러스터링용 합성 데이터 make_moons, make_circles, # 비선형 경계 데이터 ) # 데이터 로드 iris = load_iris() X, y = iris.data, iris.target # numpy 배열 iris.feature_names # ['sepal length (cm)', ...] iris.target_names # ['setosa', 'versicolor', 'virginica'] # 합성 데이터 생성 X, y = make_classification( n_samples=1000, n_features=20, n_informative=10, # 실제 유효 특성 수 n_redundant=5, n_classes=3, random_state=42 ) # 데이터 분할 from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, # 테스트 비율 (0~1) random_state=42, # 재현성 stratify=y # 클래스 비율 유지 (분류에서 권장) ) # 3-way 분할 (훈련 / 검증 / 테스트) X_tr, X_tmp, y_tr, y_tmp = train_test_split(X, y, test_size=0.3, stratify=y, random_state=42) X_val, X_test, y_val, y_test = train_test_split(X_tmp, y_tmp, test_size=0.5, stratify=y_tmp, random_state=42)

전처리 — 스케일링 (Scaling)

스케일러변환 공식특징 / 적합 상황
StandardScaler(x−μ)/σ평균 0, 표준편차 1. 이상치에 민감. 대부분의 모델에 기본
MinMaxScaler(x−min)/(max−min)[0,1] 범위. 이상치에 취약. 신경망·이미지
RobustScaler(x−중앙값)/IQR이상치에 강건. 이상치 많은 데이터
MaxAbsScalerx/|max(x)|[-1,1]. 희소 행렬(Sparse matrix)에 적합
Normalizer행 단위 L2 정규화각 샘플을 단위 벡터로. 텍스트·코사인 유사도
from sklearn.preprocessing import ( StandardScaler, MinMaxScaler, RobustScaler, MaxAbsScaler, Normalizer, PowerTransformer, QuantileTransformer ) # PowerTransformer — 비정규 분포를 정규분포에 가깝게 pt = PowerTransformer(method="yeo-johnson") # 음수 허용 pt = PowerTransformer(method="box-cox") # 양수만 X_train_pt = pt.fit_transform(X_train) # QuantileTransformer — 균등/정규 분포로 매핑 qt = QuantileTransformer(output_distribution="normal", random_state=42) X_train_qt = qt.fit_transform(X_train)

전처리 — 인코딩 (Encoding)

from sklearn.preprocessing import ( LabelEncoder, OrdinalEncoder, OneHotEncoder, LabelBinarizer ) # LabelEncoder — 타깃(y) 인코딩. 순서 있는 이진 변수 le = LabelEncoder() y_enc = le.fit_transform(["cat", "dog", "cat", "fish"]) # → [0, 1, 0, 2] le.inverse_transform([0, 2]) # → ['cat', 'fish'] # OrdinalEncoder — 특성(X) 순서형 인코딩 oe = OrdinalEncoder(categories=[["low", "mid", "high"]]) oe.fit_transform([["low"], ["high"], ["mid"]]) # → [[0.],[2.],[1.]] # OneHotEncoder — 명목형(순서 없는) 인코딩 (권장) ohe = OneHotEncoder(sparse_output=False, # dense 배열 반환 handle_unknown="ignore", # 미지 카테고리 → 0 drop="first") # 더미 변수 함정 방지 X_ohe = ohe.fit_transform(X_cat) ohe.get_feature_names_out() # 새 열 이름

전처리 — 결측치 & 특성 생성

from sklearn.impute import SimpleImputer, KNNImputer, IterativeImputer from sklearn.preprocessing import PolynomialFeatures from sklearn.feature_selection import VarianceThreshold # SimpleImputer — 통계값으로 채우기 imp_mean = SimpleImputer(strategy="mean") # 수치형 (기본) imp_median = SimpleImputer(strategy="median") # 이상치 있을 때 imp_mode = SimpleImputer(strategy="most_frequent") # 범주형 imp_const = SimpleImputer(strategy="constant", fill_value=0) # KNNImputer — K-최근접 이웃으로 추정 knn_imp = KNNImputer(n_neighbors=5) # IterativeImputer — 반복 회귀 추정 (MICE 알고리즘) from sklearn.experimental import enable_iterative_imputer # noqa iter_imp = IterativeImputer(max_iter=10, random_state=42) # PolynomialFeatures — 다항 특성 생성 (비선형 관계 포착) poly = PolynomialFeatures(degree=2, include_bias=False, interaction_only=False) # [a, b] → [a, b, a², ab, b²] X_poly = poly.fit_transform(X_train) poly.get_feature_names_out(["x1", "x2"]) # VarianceThreshold — 분산 0인 특성(상수) 제거 vt = VarianceThreshold(threshold=0.01) X_sel = vt.fit_transform(X)

지도학습 — 회귀 (Regression)

from sklearn.linear_model import ( LinearRegression, Ridge, Lasso, ElasticNet, # 정규화 선형 모델 HuberRegressor, # 이상치에 강건 BayesianRidge, ) from sklearn.tree import DecisionTreeRegressor from sklearn.ensemble import ( RandomForestRegressor, GradientBoostingRegressor, HistGradientBoostingRegressor, # 대용량·결측치 허용 AdaBoostRegressor, ) from sklearn.svm import SVR from sklearn.neighbors import KNeighborsRegressor # 선형 회귀 lr = LinearRegression(fit_intercept=True) lr.fit(X_train, y_train) lr.coef_ # 계수 lr.intercept_ # 절편 # Ridge (L2 정규화) — 다중공선성, 과적합 완화 ridge = Ridge(alpha=1.0) # alpha ↑ → 정규화 강도 ↑ # Lasso (L1 정규화) — 특성 선택 효과 (계수 → 0) lasso = Lasso(alpha=0.1, max_iter=5000) # ElasticNet (L1+L2) — Lasso와 Ridge 혼합 en = ElasticNet(alpha=0.1, l1_ratio=0.5) # GradientBoosting — 순차 부스팅, 높은 정확도 gbr = GradientBoostingRegressor( n_estimators=200, learning_rate=0.05, max_depth=4, subsample=0.8, random_state=42 ) gbr.fit(X_train, y_train)

지도학습 — 분류 (Classification)

from sklearn.linear_model import LogisticRegression, SGDClassifier from sklearn.svm import SVC, LinearSVC from sklearn.neighbors import KNeighborsClassifier from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import ( RandomForestClassifier, GradientBoostingClassifier, HistGradientBoostingClassifier, AdaBoostClassifier, ExtraTreesClassifier, ) from sklearn.naive_bayes import GaussianNB, MultinomialNB from sklearn.discriminant_analysis import LinearDiscriminantAnalysis # 로지스틱 회귀 lr = LogisticRegression(C=1.0, # C=1/alpha (C↑ → 정규화 약해짐) penalty="l2", # "l1","l2","elasticnet" solver="lbfgs", # 다중분류: "lbfgs","saga" multi_class="auto", max_iter=1000) # SVM (Support Vector Machine) svc = SVC(C=1.0, kernel="rbf", # "linear","poly","rbf","sigmoid" gamma="scale", # "scale"=1/(n_features*X.var()) probability=True) # predict_proba 활성화 (속도 저하) # K-최근접 이웃 knn = KNeighborsClassifier(n_neighbors=5, weights="distance", # "uniform" or "distance" metric="minkowski") # "euclidean","manhattan" # 랜덤 포레스트 rf = RandomForestClassifier( n_estimators=200, # 트리 수 (많을수록 안정, 느려짐) max_depth=None, # None이면 완전 성장 max_features="sqrt", # 분기 시 고려 특성 수 min_samples_leaf=1, class_weight="balanced", # 클래스 불균형 처리 n_jobs=-1, # 모든 CPU 코어 사용 random_state=42 ) rf.fit(X_train, y_train) rf.feature_importances_ # 특성 중요도 # Gradient Boosting hgb = HistGradientBoostingClassifier( max_iter=200, learning_rate=0.05, max_leaf_nodes=31, min_samples_leaf=20 ) # HistGradientBoosting은 결측치(NaN)를 자체 처리

비지도학습 — 클러스터링

from sklearn.cluster import ( KMeans, MiniBatchKMeans, DBSCAN, HDBSCAN, AgglomerativeClustering, SpectralClustering, ) # K-Means km = KMeans(n_clusters=3, init="k-means++", # 초기 중심 선택 (수렴 빠름) n_init=10, # 시작 횟수 (최선 결과 반환) max_iter=300, random_state=42) km.fit(X) km.labels_ # 각 샘플의 클러스터 번호 km.cluster_centers_ # 클러스터 중심 좌표 km.inertia_ # Within-cluster sum of squares (낮을수록 좋음) # 최적 K 찾기 — 엘보우 방법 inertias = [] for k in range(1, 11): inertias.append(KMeans(n_clusters=k, random_state=42, n_init=10) .fit(X).inertia_) # DBSCAN — 밀도 기반, 이상치 자동 감지 db = DBSCAN(eps=0.5, # 이웃 반경 min_samples=5, # 핵심 포인트 최소 이웃 수 metric="euclidean") db.fit(X) db.labels_ # -1 = 이상치(noise) # 계층적 클러스터링 agg = AgglomerativeClustering(n_clusters=3, linkage="ward") # "average","complete","single" agg.fit(X)

비지도학습 — 차원 축소

from sklearn.decomposition import PCA, TruncatedSVD, NMF from sklearn.manifold import TSNE from sklearn.preprocessing import StandardScaler # PCA (Principal Component Analysis) pca = PCA(n_components=2) # 고정 차원 수 pca = PCA(n_components=0.95) # 분산 95% 보존에 필요한 차원 수 pca = PCA(n_components="mle") # MLE로 자동 결정 X_pca = pca.fit_transform(StandardScaler().fit_transform(X)) pca.explained_variance_ratio_ # 각 PC의 설명 분산 비율 pca.components_ # 주성분 벡터 (로딩) print(f"보존 분산: {pca.explained_variance_ratio_.sum():.3f}") # TruncatedSVD — 희소 행렬에 적합 (PCA 대안) svd = TruncatedSVD(n_components=50) X_svd = svd.fit_transform(X_sparse) # t-SNE — 시각화 전용 (2D/3D), 비볼록 최적화 tsne = TSNE(n_components=2, perplexity=30, # 이웃 수 목표 (5~50) n_iter=1000, learning_rate="auto", init="pca", # PCA 초기화 (안정적) random_state=42) X_tsne = tsne.fit_transform(X) # fit_transform만 제공 (transform 없음)

모델 평가 지표 (Metrics)

from sklearn.metrics import ( # 회귀 mean_squared_error, root_mean_squared_error, mean_absolute_error, mean_absolute_percentage_error, r2_score, explained_variance_score, # 분류 accuracy_score, balanced_accuracy_score, precision_score, recall_score, f1_score, classification_report, confusion_matrix, ConfusionMatrixDisplay, roc_auc_score, roc_curve, average_precision_score, log_loss, # 클러스터링 silhouette_score, davies_bouldin_score, adjusted_rand_score, ) # ─── 회귀 평가 ──────────────────────────────────────────── mse = mean_squared_error(y_test, y_pred) rmse = root_mean_squared_error(y_test, y_pred) # sklearn 1.4+ mae = mean_absolute_error(y_test, y_pred) mape = mean_absolute_percentage_error(y_test, y_pred) # 비율 오차 r2 = r2_score(y_test, y_pred) # 1.0 = 완벽, 음수 = 기준선 이하 # ─── 분류 평가 ──────────────────────────────────────────── acc = accuracy_score(y_test, y_pred) bacc = balanced_accuracy_score(y_test, y_pred) # 클래스 불균형 시 권장 prec = precision_score(y_test, y_pred, average="weighted") rec = recall_score(y_test, y_pred, average="macro") f1 = f1_score(y_test, y_pred, average="macro") # average: "binary","micro","macro","weighted","samples" # 전체 리포트 print(classification_report(y_test, y_pred, target_names=iris.target_names)) # 혼동 행렬 cm = confusion_matrix(y_test, y_pred) ConfusionMatrixDisplay(cm, display_labels=iris.target_names).plot() # ROC-AUC (이진 분류) y_prob = clf.predict_proba(X_test)[:, 1] auc = roc_auc_score(y_test, y_prob) fpr, tpr, thresholds = roc_curve(y_test, y_prob) # ROC-AUC (다중 분류) y_prob_multi = clf.predict_proba(X_test) auc_multi = roc_auc_score(y_test, y_prob_multi, multi_class="ovr", average="macro")

교차 검증 (Cross Validation)

from sklearn.model_selection import ( cross_val_score, cross_validate, KFold, StratifiedKFold, RepeatedStratifiedKFold, LeaveOneOut, GroupKFold, ) # cross_val_score — 단일 지표 scores = cross_val_score( RandomForestClassifier(random_state=42), X, y, cv=5, # 5-fold scoring="f1_macro", n_jobs=-1 ) print(f"F1: {scores.mean():.4f} ± {scores.std():.4f}") # cross_validate — 여러 지표 + 훈련 점수 cv_res = cross_validate( RandomForestClassifier(random_state=42), X, y, cv=5, scoring=["accuracy", "f1_macro", "roc_auc_ovr"], return_train_score=True ) # StratifiedKFold — 클래스 비율 유지 (분류 기본 권장) skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) for fold, (tr_idx, val_idx) in enumerate(skf.split(X, y)): X_tr, X_val = X[tr_idx], X[val_idx] y_tr, y_val = y[tr_idx], y[val_idx] # 모델 학습/평가 # RepeatedStratifiedKFold — k-fold를 n번 반복 rskf = RepeatedStratifiedKFold(n_splits=5, n_repeats=3, random_state=42) # → 총 15번 평가로 분산 감소 # LeaveOneOut — 소규모 데이터 loo_scores = cross_val_score(clf, X, y, cv=LeaveOneOut(), scoring="accuracy")

하이퍼파라미터 튜닝

from sklearn.model_selection import ( GridSearchCV, RandomizedSearchCV, HalvingGridSearchCV ) from scipy.stats import randint, uniform, loguniform # GridSearchCV — 격자 탐색 (전수 조사) param_grid = { "n_estimators" : [100, 200, 300], "max_depth" : [None, 5, 10], "max_features" : ["sqrt", "log2"], } grid = GridSearchCV( RandomForestClassifier(random_state=42), param_grid, cv=5, scoring="f1_macro", n_jobs=-1, verbose=1 ) grid.fit(X_train, y_train) grid.best_params_ # 최적 파라미터 grid.best_score_ # 교차 검증 최고 점수 grid.best_estimator_.predict(X_test) # RandomizedSearchCV — 무작위 탐색 (시간 절약) param_dist = { "n_estimators" : randint(50, 500), "max_depth" : [None, 5, 10, 20], "max_features" : uniform(0.1, 0.9), "min_samples_leaf": randint(1, 10), } rand = RandomizedSearchCV( RandomForestClassifier(random_state=42), param_dist, n_iter=50, # 시도 횟수 cv=5, scoring="f1_macro", n_jobs=-1, random_state=42 ) rand.fit(X_train, y_train) # HalvingGridSearchCV — 예산 절반씩 할당 (빠름, sklearn 0.24+) halving = HalvingGridSearchCV( RandomForestClassifier(random_state=42), param_grid, cv=5, factor=2, resource="n_samples", random_state=42 )

파이프라인 (Pipeline)

from sklearn.pipeline import Pipeline, make_pipeline from sklearn.compose import ColumnTransformer, make_column_selector # 기본 파이프라인 — 전처리 + 모델을 하나로 pipe = Pipeline([ ("scaler", StandardScaler()), ("pca", PCA(n_components=10)), ("clf", RandomForestClassifier(random_state=42)), ]) pipe.fit(X_train, y_train) pipe.predict(X_test) pipe.score(X_test, y_test) # make_pipeline — 이름 자동 지정 (간편 버전) pipe = make_pipeline(StandardScaler(), LogisticRegression()) # Pipeline + GridSearchCV — 파이프라인 파라미터 탐색 param_grid = { "pca__n_components" : [5, 10, 20], "clf__n_estimators" : [100, 200], "clf__max_depth" : [None, 5], } # 단계명__파라미터명 형식 grid_pipe = GridSearchCV(pipe, param_grid, cv=5, n_jobs=-1) # ColumnTransformer — 열 유형별 전처리 num_features = ["age", "income", "score"] cat_features = ["gender", "city", "grade"] preprocessor = ColumnTransformer(transformers=[ ("num", StandardScaler(), num_features), ("cat", OneHotEncoder(handle_unknown="ignore"), cat_features), ], remainder="drop") # 나머지 열 처리: "drop","passthrough" full_pipe = Pipeline([ ("prep", preprocessor), ("clf", RandomForestClassifier(n_jobs=-1, random_state=42)), ]) full_pipe.fit(X_train_df, y_train)

특성 선택 (Feature Selection)

from sklearn.feature_selection import ( SelectKBest, SelectPercentile, f_classif, chi2, mutual_info_classif, # 분류 점수 함수 f_regression, mutual_info_regression, # 회귀 점수 함수 RFE, RFECV, # 재귀적 특성 제거 SelectFromModel, # 모델 기반 선택 ) # 통계 기반 — 상위 K개 특성 선택 skb = SelectKBest(score_func=f_classif, k=10) X_new = skb.fit_transform(X_train, y_train) skb.get_support() # 선택된 특성 bool 마스크 skb.scores_ # 각 특성의 점수 skb.get_feature_names_out() # 선택된 특성 이름 (sklearn 1.0+) # RFE — 재귀적 특성 제거 rfe = RFE(estimator=LogisticRegression(), n_features_to_select=10, step=1) rfe.fit(X_train, y_train) rfe.ranking_ # 특성 순위 (1 = 선택됨) rfe.support_ # 선택된 특성 bool # RFECV — 교차 검증으로 최적 특성 수 자동 결정 rfecv = RFECV(LogisticRegression(), cv=5, scoring="accuracy", n_jobs=-1) rfecv.fit(X_train, y_train) rfecv.n_features_ # 선택된 최적 특성 수 # 모델 기반 — 트리/선형모델 feature_importances_ 활용 sfm = SelectFromModel(RandomForestClassifier(n_estimators=100, random_state=42), threshold="median") # 중앙값 이상 중요도만 선택 sfm.fit(X_train, y_train) X_selected = sfm.transform(X_train)

앙상블 (Ensemble)

from sklearn.ensemble import ( VotingClassifier, VotingRegressor, StackingClassifier, StackingRegressor, BaggingClassifier, ) # VotingClassifier — 여러 모델의 다수결 / 평균 voting = VotingClassifier(estimators=[ ("lr", LogisticRegression(max_iter=1000)), ("svc", SVC(probability=True)), ("rf", RandomForestClassifier(n_estimators=100, random_state=42)), ], voting="soft", # "hard"=다수결, "soft"=확률 평균 (일반적으로 성능 우수) n_jobs=-1) voting.fit(X_train, y_train) # StackingClassifier — 메타 학습기 (2단계) estimators = [ ("lr", LogisticRegression(max_iter=1000)), ("rf", RandomForestClassifier(n_estimators=100, random_state=42)), ("svc", SVC(probability=True)), ] stacking = StackingClassifier( estimators=estimators, final_estimator=LogisticRegression(), # 메타 학습기 cv=5, passthrough=False # True이면 원본 특성도 메타 학습기에 전달 ) # BaggingClassifier — 부트스트랩 앙상블 bag = BaggingClassifier( estimator=DecisionTreeClassifier(), n_estimators=100, max_samples=0.8, # 훈련 샘플 비율 max_features=0.8, # 특성 비율 bootstrap=True, n_jobs=-1, random_state=42 )

모델 저장 & 불러오기

import joblib import pickle # joblib — sklearn 모델에 권장 (numpy 배열 효율적) joblib.dump(model, "model.joblib") # 저장 loaded_model = joblib.load("model.joblib") # 불러오기 loaded_model.predict(X_test) # 압축 저장 joblib.dump(model, "model.joblib.gz", compress=3) # pickle — 표준 라이브러리 with open("model.pkl", "wb") as f: pickle.dump(model, f) with open("model.pkl", "rb") as f: loaded = pickle.load(f) # 전처리기 + 모델 파이프라인 통째로 저장 joblib.dump(full_pipe, "pipeline.joblib") # 모델 메타 정보 확인 print(loaded_model.get_params()) # 파라미터 딕셔너리

주요 알고리즘 선택 가이드

알고리즘유형장점단점 / 주의
LinearRegression / Logistic선형빠름, 해석 쉬움비선형 관계 취약
Ridge / Lasso / ElasticNet정규화 선형과적합 완화, 특성 선택(Lasso)alpha 튜닝 필요
DecisionTree트리해석 쉬움, 전처리 불필요과적합 심함
RandomForest배깅 앙상블안정적, 중요도 제공, 병렬화메모리 사용량, 느린 예측
GradientBoosting / HistGB부스팅높은 정확도, 결측치 허용(Hist)튜닝 파라미터 많음
SVM / SVC커널고차원 강점, 마진 최적화대용량 느림, 확률 느림
KNN거리 기반구현 간단, 파라미터 적음예측 느림, 고차원 취약
K-Means클러스터링빠름, 확장성K 사전 지정, 구형 군집만
DBSCAN밀도 클러스터링이상치 감지, K 불필요고차원·대용량 어려움
PCA차원 축소빠름, 노이즈 제거비선형 관계 포착 불가

모델 평가 및 검증

모델의 성능을 객관적으로 측정하는 것은 ML 파이프라인의 필수 과정입니다. 정확도(Accuracy)는 전체 정답률, 정밀도(Precision)는 양성 예측의 정확성, 재현율(Recall)은 실제 양성의 검출률을 나타냅니다. F1 Score는 정밀도와 재현율의 조화 평균이며, 교차 검증(Cross Validation)은 과적합을 방지하기 위해 데이터를 여러 폴드로 나누어 반복 평가합니다.

from sklearn.metrics import ( accuracy_score, precision_score, recall_score, f1_score, confusion_matrix, classification_report, roc_auc_score ) # 분류 метрик accuracy = accuracy_score(y_test, y_pred) precision = precision_score(y_test, y_pred, average="weighted") recall = recall_score(y_test, y_pred, average="weighted") f1 = f1_score(y_test, y_pred, average="weighted") # 혼동 행렬 cm = confusion_matrix(y_test, y_pred) # 리포트 print(classification_report(y_test, y_pred)) # ROC-AUC roc_auc = roc_auc_score(y_test, y_proba, multi_class="ovr") # 교차 검증 from sklearn.model_selection import cross_val_score scores = cross_val_score(model, X, y, cv=5, scoring="accuracy")
초급자 가이드 - 머신러닝이란 무엇인가?

머신러닝을 일상에 비유하면 쉽게 이해할 수 있습니다:

  • 지도 학습 = 선생님이 있는 학습: 정답(라벨)이 있는 데이터로 학습합니다. 예: 스팸/정상 메일을 구분하기 위해 이미 분류된 메일 데이터로 훈련합니다.
  • 비지도 학습 = 스스로 패턴 찾기: 정답 없이 데이터의 구조를 발견합니다. 예: 고객 구매 패턴을 분석하여 비슷한 고객끼리 그룹화합니다.
  • 회귀 vs 분류: 회귀는 "집 가격은 얼마?"처럼 연속적인 숫자를 예측하고, 분류는 "스팸인가 아닌가?"처럼 카테고리를 예측합니다.

시작 팁: scikit-learn의 내장 데이터셋(load_iris(), load_digits())으로 실습을 시작하세요. 3줄의 코드로 머신러닝 모델을 만들 수 있습니다!

중급자 가이드 - 모델 선택과 튜닝

실무에서 효과적인 모델 개발 전략:

  • 기준 모델(Baseline): 복잡한 모델 전에 간단한 모델(LogisticRegression, DecisionTree)로 기준 성능을 측정하세요.
  • 교차 검증(Cross-Validation): cross_val_score()로 모델의 일반화 성능을 평가합니다. k-fold 교차 검증이 표준입니다.
  • 하이퍼파라미터 튜닝: GridSearchCV(완전 탐색)나 RandomizedSearchCV(랜덤 탐색)로 최적 파라미터를 찾습니다. Optuna는 베이지안 최적화로 더 효율적입니다.
  • 파이프라인: Pipeline([("scaler", StandardScaler()), ("model", SVC())])로 전처리와 모델을 하나로 묶으면 데이터 누출(data leakage)을 방지합니다.
  • 특성 중요도: 트리 기반 모델의 feature_importances_나 SHAP 값으로 어떤 특성이 예측에 영향을 주는지 분석합니다.
고급자 가이드 - MLOps와 프로덕션 배포

머신러닝 모델을 프로덕션에 배포하고 운영하기 위한 고급 전략:

  • 실험 추적: MLflow, Weights & Biases(W&B)로 하이퍼파라미터, 메트릭, 모델을 체계적으로 기록하고 비교합니다.
  • 모델 서빙: FastAPI + uvicorn으로 REST API를 구축하거나, TorchServe, TensorFlow Serving으로 모델을 배포합니다. ONNX Runtime으로 프레임워크 독립적인 추론도 가능합니다.
  • 피처 스토어: Feast 등의 피처 스토어로 특성 엔지니어링을 재사용하고 학습/서빙 간 일관성을 보장합니다.
  • 모델 모니터링: 데이터 드리프트, 개념 드리프트를 감지하여 모델 성능 저하를 조기에 발견합니다. Evidently, Whylogs 등의 도구를 활용합니다.
  • 앙상블 기법: Gradient Boosting(XGBoost, LightGBM, CatBoost)은 대부분의 정형 데이터 문제에서 최고 성능을 제공합니다. 스태킹(Stacking)으로 여러 모델을 결합하면 추가 성능 향상이 가능합니다.

14. 딥러닝

고급 전문가

딥러닝(Deep Learning)은 다층 신경망을 사용하여 데이터의 복잡한 패턴을 학습하는 머신러닝의 하위 분야입니다. 이미지 인식, 자연어 처리, 음성 인식, 생성 AI 등에서 혁신적인 성과를 이루고 있으며, TensorFlow와 PyTorch가 양대 프레임워크입니다.

입력층 Input Layer x1 x2 x3 은닉층 1 Hidden Layer 은닉층 2 Hidden Layer 출력층 Output Layer y1 y2 다층 퍼셉트론(MLP) 구조 - 각 연결선은 학습 가능한 가중치(weight)를 가짐 은닉층이 깊어질수록(Deep) 더 복잡한 패턴을 학습할 수 있음

TensorFlow/Keras (딥러닝)

TensorFlow는 Google이 개발한 오픈소스 딥러닝 프레임워크이며, Keras는 TensorFlow의 고수준 API로 모델을 직관적으로 구축·학습·배포할 수 있습니다. TensorFlow 2.x부터 Keras가 기본 통합되어 tf.keras로 사용합니다.

설치: pip install tensorflow  |  GPU: pip install tensorflow[and-cuda]  |  확인: python -c "import tensorflow as tf; print(tf.__version__); print(tf.config.list_physical_devices('GPU'))"

텐서(Tensor) 기본 연산

import tensorflow as tf import numpy as np # 텐서 생성 a = tf.constant([[1, 2], [3, 4]], dtype=tf.float32) # 불변 텐서 b = tf.Variable([[5, 6], [7, 8]], dtype=tf.float32) # 변경 가능 (학습 가중치용) z = tf.zeros([3, 4]) # 영행렬 o = tf.ones([2, 3]) # 1행렬 r = tf.random.normal([3, 3], mean=0.0, stddev=1.0) # 정규분포 난수 e = tf.eye(3) # 단위행렬 # 기본 수학 연산 (요소별) c = a + b # tf.add(a, b) c = a * b # tf.multiply(a, b) — 요소별 곱 c = a @ b # tf.matmul(a, b) — 행렬 곱 c = tf.reduce_sum(a) # 전체 합 → 스칼라 10 c = tf.reduce_mean(a, axis=0) # 열 평균 → [2. 3.] # Shape 조작 t = tf.constant([[1, 2, 3], [4, 5, 6]]) t.shape # TensorShape([2, 3]) r = tf.reshape(t, [3, 2]) # (2,3) → (3,2) r = tf.reshape(t, [-1]) # 평탄화 → (6,) r = tf.expand_dims(t, axis=0) # (2,3) → (1,2,3) — 배치 차원 추가 r = tf.squeeze(r) # 크기 1인 차원 제거 r = tf.transpose(t) # 전치 (2,3) → (3,2) # 타입 변환 & NumPy 호환 f = tf.cast(t, tf.float32) # int → float n = t.numpy() # 텐서 → NumPy 배열 t = tf.convert_to_tensor(n) # NumPy → 텐서

Sequential API

from tensorflow import keras from tensorflow.keras import layers # Sequential 모델 — 레이어를 순서대로 쌓는 가장 간단한 방식 model = keras.Sequential([ layers.Dense(64, activation="relu", input_shape=(784,)), layers.Dense(32, activation="relu"), layers.Dropout(0.3), # 과적합 방지: 30% 뉴런 비활성화 layers.Dense(10, activation="softmax") ]) model.summary() # 모델 구조 및 파라미터 수 확인 # add()로 레이어 추가 (동적 구성) model = keras.Sequential() model.add(layers.Dense(128, activation="relu", input_shape=(784,))) model.add(layers.BatchNormalization()) # 배치 정규화 model.add(layers.Dense(64, activation="relu")) model.add(layers.Dropout(0.5)) model.add(layers.Dense(10, activation="softmax")) # 모델 컴파일 model.compile( optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"] ) # 학습 history = model.fit( X_train, y_train, epochs=10, batch_size=32, validation_split=0.2, verbose=1 # 0=무출력, 1=진행바, 2=에포크 요약 ) # 학습 기록 활용 history.history["loss"] # 에포크별 손실값 리스트 history.history["val_accuracy"] # 에포크별 검증 정확도 # 평가 및 예측 test_loss, test_acc = model.evaluate(X_test, y_test) predictions = model.predict(X_test) # (N, 10) 확률 배열 predicted_classes = tf.argmax(predictions, axis=1) # 최대 확률 클래스

Functional API

# Functional API — 다중 입력/출력, 잔차 연결 등 복잡한 구조 지원 # 기본 사용 inputs = keras.Input(shape=(784,), name="input") x = layers.Dense(64, activation="relu")(inputs) x = layers.Dropout(0.3)(x) outputs = layers.Dense(10, activation="softmax", name="output")(x) model = keras.Model(inputs=inputs, outputs=outputs) # 잔차 연결 (Residual / Skip Connection) inputs = keras.Input(shape=(256,)) x = layers.Dense(256, activation="relu")(inputs) x = layers.BatchNormalization()(x) x = layers.Dense(256, activation="relu")(x) x = layers.Add()([x, inputs]) # 잔차 연결: 입력을 출력에 더함 outputs = layers.Dense(10, activation="softmax")(x) res_model = keras.Model(inputs, outputs) # 다중 입력 / 다중 출력 text_input = keras.Input(shape=(100,), name="text") meta_input = keras.Input(shape=(5,), name="meta") x1 = layers.Dense(64, activation="relu")(text_input) x2 = layers.Dense(16, activation="relu")(meta_input) merged = layers.Concatenate()([x1, x2]) # 특성 결합 shared = layers.Dense(32, activation="relu")(merged) category_out = layers.Dense(5, activation="softmax", name="category")(shared) score_out = layers.Dense(1, name="score")(shared) multi_model = keras.Model( inputs=[text_input, meta_input], outputs=[category_out, score_out] ) multi_model.compile( optimizer="adam", loss={"category": "sparse_categorical_crossentropy", "score": "mse"}, loss_weights={"category": 1.0, "score": 0.5} )

Model Subclassing

# 커스텀 모델 클래스 — 최대 유연성, 연구용에 적합 class MyModel(keras.Model): def __init__(self, num_classes=10): super().__init__() self.dense1 = layers.Dense(128, activation="relu") self.bn = layers.BatchNormalization() self.dropout = layers.Dropout(0.3) self.dense2 = layers.Dense(num_classes, activation="softmax") def call(self, inputs, training=False): x = self.dense1(inputs) x = self.bn(x, training=training) # training 플래그 전달 중요 x = self.dropout(x, training=training) return self.dense2(x) model = MyModel(num_classes=10) model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") # 커스텀 레이어 class ScaledDense(layers.Layer): def __init__(self, units, scale=1.0): super().__init__() self.units = units self.scale = scale def build(self, input_shape): self.w = self.add_weight(shape=(input_shape[-1], self.units), initializer="random_normal") self.b = self.add_weight(shape=(self.units,), initializer="zeros") def call(self, inputs): return tf.matmul(inputs, self.w) * self.scale + self.b

주요 레이어 비교

레이어용도주요 파라미터입력 shape
Dense완전연결 (MLP)units, activation(batch, features)
Conv2D이미지 특징 추출filters, kernel_size, strides, padding(batch, H, W, C)
MaxPooling2D공간 다운샘플링pool_size, strides(batch, H, W, C)
LSTM시계열/순차 데이터units, return_sequences, dropout(batch, timesteps, features)
GRU경량 순환 레이어units, return_sequences(batch, timesteps, features)
Embedding정수→밀집 벡터 변환input_dim, output_dim(batch, sequence_length)
BatchNormalization학습 안정화, 가속momentum, epsilon임의
Dropout과적합 방지rate (0~1)임의
Flatten다차원→1차원 변환없음(batch, ...)
GlobalAveragePooling2D특성맵 평균 풀링없음(batch, H, W, C)

옵티마이저(Optimizer)

# 문자열 지정 (기본 하이퍼파라미터) model.compile(optimizer="adam", loss="mse") # 객체로 지정 (하이퍼파라미터 커스터마이징) model.compile( optimizer=keras.optimizers.Adam(learning_rate=1e-3, weight_decay=1e-4), loss="mse" ) # 주요 옵티마이저 비교 keras.optimizers.SGD(learning_rate=0.01, momentum=0.9) # 안정적, 일반화 우수 keras.optimizers.Adam(learning_rate=1e-3) # 가장 범용, 빠른 수렴 keras.optimizers.AdamW(learning_rate=1e-3, weight_decay=1e-4) # Adam + 가중치 감쇠 keras.optimizers.RMSprop(learning_rate=1e-3) # RNN에 적합 # 학습률 스케줄러 lr_schedule = keras.optimizers.schedules.CosineDecay( initial_learning_rate=1e-3, decay_steps=10000, alpha=1e-5 # 최소 학습률 ) optimizer = keras.optimizers.Adam(learning_rate=lr_schedule)

손실 함수(Loss Function)

작업손실 함수출력 활성화레이블 형태
이진 분류binary_crossentropysigmoid0 또는 1
다중 분류 (정수 레이블)sparse_categorical_crossentropysoftmax0, 1, 2, ...
다중 분류 (원핫 레이블)categorical_crossentropysoftmax[0,1,0,...] 원핫
회귀mse (mean_squared_error)없음 (linear)연속값
회귀 (이상치 강건)huber없음연속값
다중 레이블binary_crossentropysigmoid[1,0,1,...] 다중
# 커스텀 손실 함수 def custom_mse(y_true, y_pred): return tf.reduce_mean(tf.square(y_true - y_pred)) model.compile(optimizer="adam", loss=custom_mse) # 클래스 가중치 (불균형 데이터) model.fit(X_train, y_train, class_weight={0: 1.0, 1: 5.0}) # 소수 클래스에 높은 가중치

메트릭(Metrics)

# 내장 메트릭 model.compile( optimizer="adam", loss="sparse_categorical_crossentropy", metrics=[ "accuracy", keras.metrics.Precision(name="precision"), keras.metrics.Recall(name="recall"), keras.metrics.AUC(name="auc"), ] ) # 커스텀 메트릭 class F1Score(keras.metrics.Metric): def __init__(self, name="f1_score", **kwargs): super().__init__(name=name, **kwargs) self.precision = keras.metrics.Precision() self.recall = keras.metrics.Recall() def update_state(self, y_true, y_pred, sample_weight=None): self.precision.update_state(y_true, y_pred, sample_weight) self.recall.update_state(y_true, y_pred, sample_weight) def result(self): p = self.precision.result() r = self.recall.result() return 2 * (p * r) / (p + r + keras.backend.epsilon()) def reset_state(self): self.precision.reset_state() self.recall.reset_state()

콜백(Callbacks)

# 주요 콜백 — 학습 과정을 자동으로 제어 callbacks = [ # 검증 손실이 5 에포크 동안 개선되지 않으면 학습 중단 keras.callbacks.EarlyStopping( monitor="val_loss", patience=5, restore_best_weights=True ), # 최적 모델 자동 저장 keras.callbacks.ModelCheckpoint( "best_model.keras", monitor="val_loss", save_best_only=True ), # 검증 손실 정체 시 학습률 감소 keras.callbacks.ReduceLROnPlateau( monitor="val_loss", factor=0.5, patience=3, min_lr=1e-6 ), # TensorBoard 로그 keras.callbacks.TensorBoard(log_dir="./logs", histogram_freq=1), ] model.fit(X_train, y_train, epochs=100, callbacks=callbacks, validation_split=0.2) # 커스텀 콜백 class PrintLR(keras.callbacks.Callback): def on_epoch_end(self, epoch, logs=None): lr = self.model.optimizer.learning_rate print(f"\n에포크 {epoch+1} 학습률: {lr:.6f}")

커스텀 학습 루프 (GradientTape)

# tf.GradientTape — 학습 과정을 완전히 제어 model = MyModel() optimizer = keras.optimizers.Adam(1e-3) loss_fn = keras.losses.SparseCategoricalCrossentropy() train_acc = keras.metrics.SparseCategoricalAccuracy() @tf.function # 그래프 모드로 컴파일 → 성능 향상 def train_step(x, y): with tf.GradientTape() as tape: predictions = model(x, training=True) loss = loss_fn(y, predictions) gradients = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(gradients, model.trainable_variables)) train_acc.update_state(y, predictions) return loss # 학습 루프 for epoch in range(10): train_acc.reset_state() for x_batch, y_batch in train_dataset: loss = train_step(x_batch, y_batch) print(f"에포크 {epoch+1}, 손실: {loss:.4f}, 정확도: {train_acc.result():.4f}")

모델 저장 및 로드

# ① Keras 형식 (권장) — 모델 구조 + 가중치 + 옵티마이저 상태 model.save("my_model.keras") loaded_model = keras.models.load_model("my_model.keras") # ② SavedModel 형식 (TF Serving 배포용) model.save("saved_model_dir") # 디렉토리로 저장 loaded_model = keras.models.load_model("saved_model_dir") # ③ 가중치만 저장/로드 (모델 구조는 코드로 재생성) model.save_weights("weights.weights.h5") model.load_weights("weights.weights.h5") # ④ TFLite 변환 (모바일/엣지 배포) converter = tf.lite.TFLiteConverter.from_saved_model("saved_model_dir") converter.optimizations = [tf.lite.Optimize.DEFAULT] # 양자화 tflite_model = converter.convert() with open("model.tflite", "wb") as f: f.write(tflite_model) # ⑤ ONNX 변환 # pip install tf2onnx # python -m tf2onnx.convert --saved-model saved_model_dir --output model.onnx

tf.data 입력 파이프라인

# tf.data.Dataset — 효율적인 데이터 로딩 & 전처리 파이프라인 # NumPy 배열에서 생성 dataset = tf.data.Dataset.from_tensor_slices((X_train, y_train)) # 파이프라인 구성 (체이닝) train_ds = ( dataset .shuffle(buffer_size=10000) # 데이터 섞기 .batch(32) # 배치 크기 .map(lambda x, y: (x / 255.0, y), # 전처리 (정규화) num_parallel_calls=tf.data.AUTOTUNE) .prefetch(tf.data.AUTOTUNE) # GPU 연산 중 다음 배치 준비 .cache() # 첫 에포크 후 메모리 캐시 ) # 이미지 디렉토리에서 데이터셋 생성 train_ds = keras.utils.image_dataset_from_directory( "data/train", image_size=(224, 224), batch_size=32, label_mode="categorical", # "int", "categorical", "binary" validation_split=0.2, subset="training", seed=42 ) # CSV 파일에서 생성 csv_ds = tf.data.experimental.make_csv_dataset( "data.csv", batch_size=32, label_name="target", num_epochs=1 ) # 커스텀 map 함수 (데이터 증강) def augment(image, label): image = tf.image.random_flip_left_right(image) image = tf.image.random_brightness(image, max_delta=0.2) return image, label train_ds = train_ds.map(augment, num_parallel_calls=tf.data.AUTOTUNE)

GPU 메모리 관리

# GPU 메모리 점진적 할당 (필수 설정 — OOM 방지) gpus = tf.config.list_physical_devices("GPU") for gpu in gpus: tf.config.experimental.set_memory_growth(gpu, True) # 또는 메모리 한도 설정 tf.config.set_logical_device_configuration( gpus[0], [tf.config.LogicalDeviceConfiguration(memory_limit=4096)] # 4GB ) # 혼합 정밀도 학습 (float16 + float32) — 속도 2배, 메모리 절감 keras.mixed_precision.set_global_policy("mixed_float16") # 멀티 GPU 분산 학습 strategy = tf.distribute.MirroredStrategy() # 단일 머신, 다중 GPU with strategy.scope(): model = keras.Sequential([...]) model.compile(optimizer="adam", loss="sparse_categorical_crossentropy")
주의사항: set_memory_growth는 프로그램 시작 시 GPU 사용 전에 호출해야 합니다. 혼합 정밀도 사용 시 모델의 마지막 출력 레이어는 반드시 dtype="float32"로 지정하세요. model(x, training=True/False)에서 training 플래그를 정확히 전달해야 Dropout과 BatchNormalization이 올바르게 동작합니다.

TensorFlow/Keras 핵심 정리

작업API / 메서드비고
간단한 모델 구축keras.Sequential선형 스택 구조
복잡한 모델 구축keras.Model (Functional)다중 입출력, 잔차 연결
최대 유연성 모델Model Subclassing동적 로직, 연구용
학습model.fit()콜백으로 제어
세밀한 학습 제어tf.GradientTapeGAN, 강화학습 등
데이터 파이프라인tf.data.Datasetshuffle→batch→prefetch
모델 저장model.save(".keras")Keras 형식 권장
배포 변환TFLiteConverter모바일/엣지용
성능 향상@tf.function그래프 모드 컴파일
메모리 절감mixed_float16Ampere+ GPU 권장
분산 학습MirroredStrategy멀티 GPU 자동 분배
학습 모니터링TensorBoard 콜백tensorboard --logdir ./logs

PyTorch (딥러닝)

PyTorch는 Meta(Facebook)가 개발한 오픈소스 딥러닝 프레임워크로, 동적 계산 그래프(Define-by-Run) 방식으로 직관적인 디버깅과 유연한 모델 설계가 가능합니다. 연구 커뮤니티에서 가장 널리 사용되며, TorchScript/ONNX를 통한 프로덕션 배포도 지원합니다.

설치: pip install torch torchvision torchaudio  |  CUDA: pytorch.org에서 플랫폼별 명령어 확인  |  확인: python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())"

텐서(Tensor) 기본 연산

import torch import numpy as np # 텐서 생성 a = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32) # 리스트에서 b = torch.zeros(3, 4) # 영행렬 c = torch.ones(2, 3) # 1행렬 d = torch.randn(3, 3) # 표준정규분포 난수 e = torch.eye(3) # 단위행렬 f = torch.arange(0, 10, 2) # [0, 2, 4, 6, 8] g = torch.linspace(0, 1, 5) # [0, 0.25, 0.5, 0.75, 1] h = torch.empty(2, 3).uniform_(0, 1) # 균일분포 난수 (in-place) # 기본 수학 연산 (요소별) x = a + a # torch.add(a, a) x = a * a # torch.mul(a, a) — 요소별 곱 x = a @ a # torch.matmul(a, a) — 행렬 곱 x = a.sum() # 전체 합 → 스칼라 tensor(10.) x = a.mean(dim=0) # 열 평균 → tensor([2., 3.]) x = a.max(dim=1) # 행 최대 → values, indices 반환 x = torch.clamp(a, 0, 3) # 값 범위 제한 [0, 3] # Shape 조작 t = torch.randn(2, 3, 4) t.shape # torch.Size([2, 3, 4]) r = t.view(6, 4) # reshape (연속 메모리 필요) r = t.reshape(6, 4) # reshape (자동 복사) r = t.view(-1) # 평탄화 → (24,) r = t.unsqueeze(0) # (2,3,4) → (1,2,3,4) — 차원 추가 r = r.squeeze(0) # (1,2,3,4) → (2,3,4) — 차원 제거 r = t.permute(2, 0, 1) # 축 재배열 (2,3,4) → (4,2,3) r = t.transpose(0, 1) # 두 축 교환 (2,3,4) → (3,2,4) # 결합 & 분할 x = torch.cat([a, a], dim=0) # 행 방향 결합 (4,2) x = torch.stack([a, a], dim=0) # 새 차원으로 쌓기 (2,2,2) chunks = t.chunk(3, dim=1) # dim=1 기준 3등분 # 타입 변환 & NumPy 호환 f = a.to(torch.float64) # float32 → float64 f = a.int() # → int32 (단축형) n = a.numpy() # 텐서 → NumPy (CPU, 메모리 공유) t = torch.from_numpy(n) # NumPy → 텐서 (메모리 공유) t = a.clone().detach() # 독립 복사본 (그래프 분리)

Autograd (자동 미분)

# requires_grad=True → 연산 추적, 자동 미분 가능 x = torch.tensor([2.0, 3.0], requires_grad=True) y = x ** 2 + 3 * x + 1 # y = x² + 3x + 1 loss = y.sum() loss.backward() # 역전파 — 그래디언트 계산 print(x.grad) # dy/dx = 2x + 3 → tensor([7., 9.]) # 그래디언트 제어 x.grad.zero_() # 그래디언트 초기화 (축적 방지) # 그래디언트 추적 비활성화 (추론/전처리 시) with torch.no_grad(): y = x * 2 # 연산 추적 안 함 → 메모리 절약 y = x.detach() # 그래프에서 분리된 새 텐서 # 고차 미분 x = torch.tensor([1.0], requires_grad=True) y = x ** 3 grad1 = torch.autograd.grad(y, x, create_graph=True)[0] # 3x² = 3 grad2 = torch.autograd.grad(grad1, x)[0] # 6x = 6

nn.Module 모델 구축

import torch.nn as nn # 기본 모델 정의 class Net(nn.Module): def __init__(self, num_classes=10): super().__init__() self.fc1 = nn.Linear(784, 256) self.bn1 = nn.BatchNorm1d(256) self.fc2 = nn.Linear(256, 128) self.dropout = nn.Dropout(0.3) self.fc3 = nn.Linear(128, num_classes) def forward(self, x): x = torch.relu(self.bn1(self.fc1(x))) x = torch.relu(self.fc2(x)) x = self.dropout(x) # training 모드에서만 활성화 return self.fc3(x) # 분류: CrossEntropyLoss가 softmax 포함 model = Net(num_classes=10) print(model) # 모델 구조 출력 # 파라미터 확인 total = sum(p.numel() for p in model.parameters()) trainable = sum(p.numel() for p in model.parameters() if p.requires_grad) print(f"전체: {total:,} / 학습 가능: {trainable:,}") # 파라미터 순회 for name, param in model.named_parameters(): print(f"{name}: {param.shape}")

nn.Sequential & ModuleList

# nn.Sequential — 레이어를 순서대로 쌓기 model = nn.Sequential( nn.Linear(784, 256), nn.ReLU(), nn.BatchNorm1d(256), nn.Dropout(0.3), nn.Linear(256, 128), nn.ReLU(), nn.Linear(128, 10) ) # OrderedDict로 이름 부여 from collections import OrderedDict model = nn.Sequential(OrderedDict([ ("fc1", nn.Linear(784, 256)), ("relu1", nn.ReLU()), ("fc2", nn.Linear(256, 10)), ])) model.fc1 # 이름으로 레이어 접근 # nn.ModuleList — 동적 레이어 구성 class DynamicNet(nn.Module): def __init__(self, layers_sizes): super().__init__() self.layers = nn.ModuleList([ nn.Linear(in_f, out_f) for in_f, out_f in zip(layers_sizes[:-1], layers_sizes[1:]) ]) def forward(self, x): for layer in self.layers[:-1]: x = torch.relu(layer(x)) return self.layers[-1](x) # nn.ModuleDict — 조건부 레이어 선택 class FlexibleNet(nn.Module): def __init__(self, activation="relu"): super().__init__() self.fc = nn.Linear(784, 10) self.activations = nn.ModuleDict({ "relu": nn.ReLU(), "gelu": nn.GELU(), "silu": nn.SiLU(), }) self.act = self.activations[activation]

잔차 연결 & 고급 패턴

# 잔차 블록 (ResNet 스타일) class ResidualBlock(nn.Module): def __init__(self, dim): super().__init__() self.block = nn.Sequential( nn.Linear(dim, dim), nn.BatchNorm1d(dim), nn.ReLU(), nn.Linear(dim, dim), nn.BatchNorm1d(dim), ) def forward(self, x): return torch.relu(self.block(x) + x) # 잔차 연결 # 다중 입력 모델 class MultiInputModel(nn.Module): def __init__(self): super().__init__() self.text_branch = nn.Linear(100, 64) self.meta_branch = nn.Linear(5, 16) self.classifier = nn.Linear(80, 10) def forward(self, text, meta): t = torch.relu(self.text_branch(text)) m = torch.relu(self.meta_branch(meta)) merged = torch.cat([t, m], dim=1) # 특성 결합 return self.classifier(merged)

주요 레이어 비교

레이어용도주요 파라미터입력 shape
nn.Linear완전연결 (MLP)in_features, out_features(batch, features)
nn.Conv2d이미지 특징 추출in_channels, out_channels, kernel_size(batch, C, H, W)
nn.MaxPool2d공간 다운샘플링kernel_size, stride(batch, C, H, W)
nn.LSTM시계열/순차 데이터input_size, hidden_size, num_layers(seq, batch, features)
nn.GRU경량 순환 레이어input_size, hidden_size(seq, batch, features)
nn.Embedding정수→밀집 벡터num_embeddings, embedding_dim(batch, seq_len) 정수
nn.BatchNorm1d/2d학습 안정화, 가속num_features(batch, features) / (B,C,H,W)
nn.Dropout과적합 방지p (0~1)임의
nn.LayerNormTransformer 정규화normalized_shape임의
nn.MultiheadAttention어텐션 메커니즘embed_dim, num_heads(seq, batch, embed_dim)
TensorFlow vs PyTorch 채널 순서: TensorFlow/Keras는 (batch, H, W, C) — channels-last, PyTorch는 (batch, C, H, W) — channels-first가 기본입니다. PyTorch에서 .permute(0, 3, 1, 2)로 변환하세요.

손실 함수(Loss Function)

작업손실 함수출력 형태레이블 형태
다중 분류nn.CrossEntropyLoss()raw logits (N, C)정수 (N,)
이진 분류nn.BCEWithLogitsLoss()raw logits (N, 1)float 0/1 (N, 1)
이진 분류 (sigmoid 후)nn.BCELoss()확률 (N, 1)float 0/1 (N, 1)
회귀nn.MSELoss()(N, *)(N, *)
회귀 (이상치 강건)nn.SmoothL1Loss()(N, *)(N, *)
회귀 (L1)nn.L1Loss()(N, *)(N, *)
다중 레이블nn.BCEWithLogitsLoss()raw logits (N, C)multi-hot (N, C)
# CrossEntropyLoss는 내부에 softmax를 포함 — 모델 출력에 softmax 적용하지 말 것! criterion = nn.CrossEntropyLoss() logits = model(x) # raw logits, softmax 적용 안 함 loss = criterion(logits, labels) # labels: 정수 텐서 (LongTensor) # 클래스 불균형 처리 weights = torch.tensor([1.0, 5.0, 1.0]) # 클래스별 가중치 criterion = nn.CrossEntropyLoss(weight=weights.to(device)) # 커스텀 손실 함수 class FocalLoss(nn.Module): def __init__(self, alpha=1.0, gamma=2.0): super().__init__() self.alpha = alpha self.gamma = gamma def forward(self, inputs, targets): ce_loss = nn.functional.cross_entropy(inputs, targets, reduction="none") pt = torch.exp(-ce_loss) return (self.alpha * (1 - pt) ** self.gamma * ce_loss).mean()

옵티마이저(Optimizer) & 스케줄러

import torch.optim as optim # 주요 옵티마이저 optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=1e-4) optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2) # 분리된 가중치 감쇠 optim.RMSprop(model.parameters(), lr=1e-3) # 파라미터 그룹별 학습률 설정 optimizer = optim.Adam([ {"params": model.fc1.parameters(), "lr": 1e-4}, # 사전학습 레이어 (낮은 lr) {"params": model.fc3.parameters(), "lr": 1e-3}, # 새 레이어 (높은 lr) ], weight_decay=1e-4) # 학습률 스케줄러 scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1) # 10 에포크마다 ×0.1 scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50) # 코사인 감쇠 scheduler = optim.lr_scheduler.ReduceLROnPlateau( # 정체 시 감소 optimizer, mode="min", factor=0.5, patience=5 ) scheduler = optim.lr_scheduler.OneCycleLR( # 1-Cycle 정책 optimizer, max_lr=0.01, total_steps=1000 ) # 스케줄러 사용 (학습 루프 내에서) # scheduler.step() — 에포크 기반 (StepLR, CosineAnnealing) # scheduler.step(val_loss) — 메트릭 기반 (ReduceLROnPlateau) # scheduler.step() — 배치 기반 (OneCycleLR) — 매 배치 후 호출

표준 학습 루프

import torch import torch.nn as nn import torch.optim as optim device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = Net().to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=1e-3) scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20) # 학습 함수 def train_one_epoch(model, loader, criterion, optimizer, device): model.train() # Dropout, BatchNorm 학습 모드 running_loss = 0.0 correct = 0 total = 0 for inputs, labels in loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() # ① 그래디언트 초기화 outputs = model(inputs) # ② 순전파 loss = criterion(outputs, labels) loss.backward() # ③ 역전파 optimizer.step() # ④ 가중치 갱신 running_loss += loss.item() * inputs.size(0) _, predicted = outputs.max(1) total += labels.size(0) correct += predicted.eq(labels).sum().item() return running_loss / total, correct / total # 검증 함수 @torch.no_grad() # 그래디언트 비활성화 def evaluate(model, loader, criterion, device): model.eval() # Dropout 비활성화, BatchNorm 고정 running_loss = 0.0 correct = 0 total = 0 for inputs, labels in loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) loss = criterion(outputs, labels) running_loss += loss.item() * inputs.size(0) _, predicted = outputs.max(1) total += labels.size(0) correct += predicted.eq(labels).sum().item() return running_loss / total, correct / total # 학습 루프 (EarlyStopping 포함) best_val_loss = float("inf") patience_counter = 0 for epoch in range(100): train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer, device) val_loss, val_acc = evaluate(model, val_loader, criterion, device) scheduler.step() print(f"[{epoch+1:3d}] train_loss={train_loss:.4f} acc={train_acc:.4f} | val_loss={val_loss:.4f} acc={val_acc:.4f}") if val_loss < best_val_loss: best_val_loss = val_loss patience_counter = 0 torch.save(model.state_dict(), "best_model.pt") # 최적 모델 저장 else: patience_counter += 1 if patience_counter >= 10: print("Early stopping!") break model.load_state_dict(torch.load("best_model.pt")) # 최적 가중치 복원

DataLoader & Dataset

from torch.utils.data import Dataset, DataLoader, TensorDataset, random_split # ① TensorDataset — NumPy/텐서에서 바로 생성 dataset = TensorDataset( torch.tensor(X_train, dtype=torch.float32), torch.tensor(y_train, dtype=torch.long) ) # ② 커스텀 Dataset class MyDataset(Dataset): def __init__(self, data, labels, transform=None): self.data = data self.labels = labels self.transform = transform def __len__(self): return len(self.data) def __getitem__(self, idx): sample = self.data[idx] label = self.labels[idx] if self.transform: sample = self.transform(sample) return sample, label # DataLoader — 배치, 셔플, 병렬 로딩 train_loader = DataLoader( dataset, batch_size=64, shuffle=True, # 학습 데이터 셔플 num_workers=4, # 병렬 데이터 로딩 프로세스 pin_memory=True, # GPU 전송 가속 (CUDA) drop_last=True, # 마지막 불완전 배치 제거 ) val_loader = DataLoader(dataset, batch_size=128, shuffle=False) # 데이터 분할 train_set, val_set = random_split(dataset, [0.8, 0.2]) # 이미지 데이터: torchvision transforms from torchvision import transforms, datasets transform_train = transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) transform_val = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # 내장 데이터셋 (CIFAR-10, MNIST, ImageNet 등) train_ds = datasets.CIFAR10("./data", train=True, download=True, transform=transform_train) train_loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=4) # ImageFolder — 디렉토리 구조에서 자동 로딩 (data/train/cat/*.jpg, data/train/dog/*.jpg) train_ds = datasets.ImageFolder("data/train", transform=transform_train)

모델 저장 및 로드

# ① state_dict 저장/로드 (권장 — 가중치만, 코드 유지) torch.save(model.state_dict(), "model_weights.pt") model = Net() # 모델 구조 재생성 필요 model.load_state_dict(torch.load("model_weights.pt", weights_only=True)) model.eval() # ② 전체 모델 저장 (구조 + 가중치 — pickle 의존) torch.save(model, "full_model.pt") model = torch.load("full_model.pt", weights_only=False) # ③ 체크포인트 저장 (학습 재개용) torch.save({ "epoch": epoch, "model_state_dict": model.state_dict(), "optimizer_state_dict": optimizer.state_dict(), "scheduler_state_dict": scheduler.state_dict(), "best_val_loss": best_val_loss, }, "checkpoint.pt") # 체크포인트 복원 ckpt = torch.load("checkpoint.pt", weights_only=False) model.load_state_dict(ckpt["model_state_dict"]) optimizer.load_state_dict(ckpt["optimizer_state_dict"]) scheduler.load_state_dict(ckpt["scheduler_state_dict"]) start_epoch = ckpt["epoch"] + 1 # ④ TorchScript 변환 (C++/프로덕션 배포) scripted = torch.jit.script(model) # Python 제어흐름 포함 가능 scripted.save("model_scripted.pt") traced = torch.jit.trace(model, torch.randn(1, 784)) # 입력 기반 추적 traced.save("model_traced.pt") loaded = torch.jit.load("model_scripted.pt") # ⑤ ONNX 변환 dummy = torch.randn(1, 784) torch.onnx.export(model, dummy, "model.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}})

GPU & 디바이스 관리

# 디바이스 설정 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") device = torch.device("cuda:0") # 특정 GPU 지정 # 텐서/모델을 디바이스로 이동 x = x.to(device) # 텐서 이동 model = model.to(device) # 모델 이동 (파라미터 + 버퍼) # GPU 정보 확인 torch.cuda.is_available() # GPU 사용 가능 여부 torch.cuda.device_count() # GPU 개수 torch.cuda.get_device_name(0) # GPU 이름 torch.cuda.memory_allocated() # 현재 메모리 사용량 torch.cuda.memory_reserved() # 예약된 메모리 torch.cuda.empty_cache() # 미사용 캐시 해제 # 혼합 정밀도 학습 (AMP) — 속도 2배↑, 메모리 절감 scaler = torch.amp.GradScaler() for inputs, labels in loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() with torch.amp.autocast(device_type="cuda"): # float16 자동 변환 outputs = model(inputs) loss = criterion(outputs, labels) scaler.scale(loss).backward() # 스케일링된 역전파 scaler.step(optimizer) # 가중치 갱신 scaler.update() # 스케일 조정 # 멀티 GPU (DataParallel — 간단하지만 비효율) if torch.cuda.device_count() > 1: model = nn.DataParallel(model) # 배치를 GPU에 분배 model = model.to(device) # 멀티 GPU (DistributedDataParallel — 권장, 고성능) # torchrun --nproc_per_node=4 train.py import torch.distributed as dist from torch.nn.parallel import DistributedDataParallel as DDP dist.init_process_group(backend="nccl") local_rank = int(os.environ["LOCAL_RANK"]) model = DDP(model.to(local_rank), device_ids=[local_rank])
주의사항: model.train()model.eval()을 학습/추론 전에 반드시 호출하세요 — Dropout과 BatchNorm의 동작이 달라집니다. CrossEntropyLoss는 softmax를 내장하므로 모델 출력에 softmax를 중복 적용하면 안 됩니다. loss.backward() 전에 optimizer.zero_grad()를 호출하세요 — PyTorch는 그래디언트를 기본으로 누적합니다.

추론 & 배포

# 추론 모드 — 메모리 최적화, autograd 완전 비활성화 model.eval() with torch.inference_mode(): # torch.no_grad()보다 빠름 predictions = model(x.to(device)) probs = torch.softmax(predictions, dim=1) classes = probs.argmax(dim=1) # 배치 추론 (대량 데이터) all_preds = [] model.eval() with torch.inference_mode(): for batch in test_loader: inputs = batch[0].to(device) preds = model(inputs).argmax(dim=1) all_preds.append(preds.cpu()) all_preds = torch.cat(all_preds) # torch.compile — PyTorch 2.0+ 자동 최적화 (JIT 컴파일) model = torch.compile(model) # 첫 실행 시 컴파일, 이후 가속 model = torch.compile(model, mode="reduce-overhead") # 오버헤드 최소화

그래디언트 클리핑 & 정규화

# 그래디언트 클리핑 — 그래디언트 폭발 방지 (RNN, Transformer 필수) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # L2 norm 기준 torch.nn.utils.clip_grad_value_(model.parameters(), clip_value=0.5) # 절대값 기준 optimizer.step() # 가중치 초기화 def init_weights(m): if isinstance(m, nn.Linear): nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu") if m.bias is not None: nn.init.zeros_(m.bias) elif isinstance(m, nn.Conv2d): nn.init.kaiming_normal_(m.weight, mode="fan_out") model.apply(init_weights) # 모든 하위 모듈에 재귀 적용 # 레이어 동결 (전이 학습) for param in model.fc1.parameters(): param.requires_grad = False # fc1 레이어 가중치 고정 # 동결된 파라미터 제외한 옵티마이저 optimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3)

PyTorch 핵심 정리

작업API / 메서드비고
모델 정의nn.Module 상속__init__ + forward
간단한 모델nn.Sequential이름 부여 시 OrderedDict
학습 모드model.train()Dropout/BN 활성화
추론 모드model.eval() + inference_mode()메모리 최적화
역전파loss.backward()zero_grad → forward → backward → step
데이터 로딩DataLoaderpin_memory=True (GPU)
모델 저장torch.save(state_dict)가중치만 저장 권장
체크포인트dict로 epoch/optimizer 포함 저장학습 재개용
프로덕션 배포torch.jit.script/traceTorchScript C++ 런타임
성능 향상torch.compile()PyTorch 2.0+ JIT 컴파일
혼합 정밀도torch.amp.autocastGradScaler 필수
멀티 GPUDistributedDataParalleltorchrun으로 실행
그래디언트 클리핑clip_grad_norm_RNN/Transformer 필수
전이 학습requires_grad = False레이어 동결

CNN (합성곱 신경망)

합성곱 신경망(Convolutional Neural Network)은 이미지 인식, 객체 탐지, 세그멘테이션 등 시각적 패턴 인식에 최적화된 아키텍처입니다. 합성곱 레이어가 공간적 특징을 계층적으로 추출하고, 풀링으로 차원을 줄인 뒤 완전연결층에서 분류합니다.

합성곱 연산 이해

# Conv2D 핵심 파라미터 이해 # # 입력: (batch, H, W, C) — Keras / (batch, C, H, W) — PyTorch # # filters/out_channels : 출력 특성맵 수 (학습할 필터 개수) # kernel_size : 필터 크기 (3×3이 가장 보편적) # strides : 필터 이동 간격 (2이면 출력 크기 절반) # padding : "same" → 입출력 크기 동일, "valid" → 패딩 없음 # # 출력 크기 계산: O = (I - K + 2P) / S + 1 # I=입력크기, K=커널크기, P=패딩, S=스트라이드 # 예: 입력 28×28, kernel=3, padding=0, stride=1 → 26×26 # 예: 입력 28×28, kernel=3, padding=1, stride=1 → 28×28 (same) # 예: 입력 28×28, kernel=3, padding=0, stride=2 → 13×13

CNN — Keras 구현

from tensorflow import keras from tensorflow.keras import layers # 기본 CNN (MNIST 분류) model = keras.Sequential([ # 블록 1: 합성곱 → 정규화 → 활성화 → 풀링 layers.Conv2D(32, (3, 3), padding="same", input_shape=(28, 28, 1)), layers.BatchNormalization(), layers.ReLU(), layers.MaxPooling2D((2, 2)), # 28×28 → 14×14 # 블록 2 layers.Conv2D(64, (3, 3), padding="same"), layers.BatchNormalization(), layers.ReLU(), layers.MaxPooling2D((2, 2)), # 14×14 → 7×7 # 블록 3 layers.Conv2D(128, (3, 3), padding="same"), layers.BatchNormalization(), layers.ReLU(), layers.GlobalAveragePooling2D(), # 7×7×128 → 128 (Flatten 대신 권장) # 분류 헤드 layers.Dropout(0.5), layers.Dense(10, activation="softmax") ]) model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"]) # 1D 합성곱 (텍스트, 시계열) text_cnn = keras.Sequential([ layers.Embedding(10000, 128, input_length=200), layers.Conv1D(64, 5, activation="relu"), # 5-gram 특징 추출 layers.GlobalMaxPooling1D(), layers.Dense(1, activation="sigmoid") ])

CNN — PyTorch 구현

import torch import torch.nn as nn class CNN(nn.Module): def __init__(self, num_classes=10): super().__init__() self.features = nn.Sequential( # 블록 1 nn.Conv2d(1, 32, kernel_size=3, padding=1), # (1,28,28)→(32,28,28) nn.BatchNorm2d(32), nn.ReLU(inplace=True), nn.MaxPool2d(2), # →(32,14,14) # 블록 2 nn.Conv2d(32, 64, kernel_size=3, padding=1), # →(64,14,14) nn.BatchNorm2d(64), nn.ReLU(inplace=True), nn.MaxPool2d(2), # →(64,7,7) # 블록 3 nn.Conv2d(64, 128, kernel_size=3, padding=1), nn.BatchNorm2d(128), nn.ReLU(inplace=True), nn.AdaptiveAvgPool2d((1, 1)), # →(128,1,1) 어떤 입력 크기든 OK ) self.classifier = nn.Sequential( nn.Flatten(), nn.Dropout(0.5), nn.Linear(128, num_classes), ) def forward(self, x): x = self.features(x) return self.classifier(x) # Depthwise Separable Convolution (MobileNet 스타일 — 경량화) depthwise_sep = nn.Sequential( nn.Conv2d(64, 64, 3, padding=1, groups=64), # depthwise: 채널별 독립 합성곱 nn.Conv2d(64, 128, 1), # pointwise: 1×1 합성곱으로 채널 결합 ) # Dilated (Atrous) Convolution — 수용 영역 확장 dilated = nn.Conv2d(64, 64, 3, padding=2, dilation=2) # 3×3 필터가 5×5 영역 커버 # Transposed Convolution — 업샘플링 (세그멘테이션, GAN 생성기) upsample = nn.ConvTranspose2d(64, 32, kernel_size=4, stride=2, padding=1) # 크기 2배

주요 CNN 아키텍처 비교

아키텍처연도핵심 기법파라미터특징
LeNet-51998Conv→Pool→FC60KCNN의 시초, 손글씨 인식
AlexNet2012ReLU, Dropout, GPU60M딥러닝 붐의 시작
VGGNet20143×3 필터 반복138M단순한 구조, 깊은 네트워크
GoogLeNet2014Inception 모듈6.8M다양한 필터 크기 병렬 적용
ResNet2015잔차 연결 (Skip)25M (50)100+층 학습 가능, 가장 범용
DenseNet2017밀집 연결8M (121)모든 이전 층과 연결
MobileNet2017Depthwise Separable3.4M모바일/엣지 배포용 경량화
EfficientNet2019복합 스케일링5.3M (B0)깊이×너비×해상도 최적 균형
ConvNeXt2022Transformer 기법 적용29M현대화된 순수 CNN, ViT 경쟁

데이터 증강 (Data Augmentation)

# Keras — 레이어 기반 증강 (GPU 가속, 모델에 포함) data_augmentation = keras.Sequential([ layers.RandomFlip("horizontal"), layers.RandomRotation(0.1), # ±36도 layers.RandomZoom(0.2), # ±20% 확대/축소 layers.RandomTranslation(0.1, 0.1), # ±10% 이동 layers.RandomContrast(0.2), ]) model = keras.Sequential([ data_augmentation, # 학습 시에만 적용 (추론 시 자동 비활성화) layers.Conv2D(32, 3, activation="relu"), # ... ]) # PyTorch — torchvision.transforms from torchvision import transforms transform_train = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.8, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1), transforms.RandomErasing(p=0.3), # 랜덤 영역 마스킹 (CutOut) transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) # albumentations — 고급 증강 라이브러리 (pip install albumentations) import albumentations as A from albumentations.pytorch import ToTensorV2 aug = A.Compose([ A.RandomCrop(224, 224), A.HorizontalFlip(p=0.5), A.OneOf([ # 셋 중 하나만 적용 A.GaussNoise(var_limit=(10, 50)), A.GaussianBlur(blur_limit=3), A.MotionBlur(blur_limit=3), ], p=0.3), A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ToTensorV2(), ])
GlobalAveragePooling vs Flatten: Flatten은 특성맵을 1차원으로 펴서 파라미터가 폭증하고 과적합 위험이 큽니다. GlobalAveragePooling2D는 각 특성맵의 평균을 내어 파라미터 수를 대폭 줄이며, 현대 CNN에서 표준으로 사용합니다.

CNN 핵심 정리

개념설명권장 설정
커널 크기필터 크기, 작을수록 깊이로 보상3×3 (가장 범용)
패딩입력 테두리 0 채움"same" / padding=1 (크기 유지)
스트라이드필터 이동 간격1 (특징 추출), 2 (다운샘플링)
풀링공간 다운샘플링MaxPool 2×2 또는 stride=2 Conv
정규화학습 안정화BatchNorm (Conv 직후)
활성화비선형성 도입ReLU (기본), GELU (Transformer계열)
출력 풀링특성맵→벡터 변환GlobalAveragePooling (Flatten 대신)
필터 수 패턴깊어질수록 증가32→64→128→256 (2배씩)

RNN/LSTM (시퀀스 데이터)

순환 신경망(Recurrent Neural Network)은 시퀀스 데이터(텍스트, 시계열, 음성)를 처리하는 아키텍처입니다. LSTM과 GRU는 장기 의존성 문제(기울기 소실)를 해결한 발전된 RNN 셀입니다.

RNN/LSTM/GRU 비교

셀 타입게이트파라미터장점단점
SimpleRNN없음적음간단, 빠름기울기 소실 심함, 장기 의존성 불가
LSTMforget, input, output (3개)많음장기 의존성 학습, 가장 범용느림, 메모리 사용 높음
GRUreset, update (2개)중간LSTM과 유사 성능, 더 빠름매우 긴 시퀀스에서 LSTM보다 약간 열세

LSTM/GRU — Keras 구현

from tensorflow import keras from tensorflow.keras import layers # 텍스트 분류 (감정 분석) model = keras.Sequential([ layers.Embedding(10000, 128), # 어휘 10000개 → 128차원 벡터 layers.LSTM(128, dropout=0.2), # 마지막 타임스텝 출력만 반환 layers.Dense(1, activation="sigmoid") # 이진 분류 ]) # 양방향 (Bidirectional) — 과거+미래 문맥 모두 활용 model = keras.Sequential([ layers.Embedding(10000, 128), layers.Bidirectional(layers.LSTM(64)), # 출력 차원: 64×2 = 128 layers.Dense(1, activation="sigmoid") ]) # 다층 (Stacked) LSTM — return_sequences=True로 모든 타임스텝 전달 model = keras.Sequential([ layers.Embedding(10000, 128), layers.LSTM(128, return_sequences=True, dropout=0.2), # (batch, seq, 128) layers.LSTM(64, dropout=0.2), # (batch, 64) layers.Dense(1, activation="sigmoid") ]) # GRU — LSTM보다 경량, 비슷한 성능 model = keras.Sequential([ layers.Embedding(10000, 128), layers.GRU(64, return_sequences=True), layers.GRU(32), layers.Dense(1, activation="sigmoid") ]) # 시계열 예측 (Sequence-to-One) model = keras.Sequential([ layers.LSTM(64, input_shape=(30, 5)), # 30 타임스텝, 5 특성 layers.Dense(1) # 다음 값 예측 (회귀) ]) # 시계열 다중 스텝 예측 (Sequence-to-Sequence) model = keras.Sequential([ layers.LSTM(64, return_sequences=True, input_shape=(30, 5)), layers.LSTM(32, return_sequences=True), layers.TimeDistributed(layers.Dense(1)) # 각 타임스텝에 Dense 적용 ])

LSTM/GRU — PyTorch 구현

import torch import torch.nn as nn class TextClassifier(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) self.lstm = nn.LSTM( embed_dim, hidden_dim, num_layers=2, # 2층 스택 batch_first=True, # 입력: (batch, seq, features) bidirectional=True, # 양방향 dropout=0.3, # 층 간 드롭아웃 ) self.fc = nn.Linear(hidden_dim * 2, num_classes) # ×2: 양방향 self.dropout = nn.Dropout(0.5) def forward(self, x): embedded = self.embedding(x) # (batch, seq, embed_dim) output, (hidden, cell) = self.lstm(embedded) # hidden: (num_layers*directions, batch, hidden_dim) # 마지막 층의 정방향 + 역방향 결합 hidden = torch.cat([hidden[-2], hidden[-1]], dim=1) return self.fc(self.dropout(hidden)) # GRU 버전 — cell state 없음 class GRUClassifier(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim) self.gru = nn.GRU(embed_dim, hidden_dim, batch_first=True, bidirectional=True) self.fc = nn.Linear(hidden_dim * 2, num_classes) def forward(self, x): embedded = self.embedding(x) output, hidden = self.gru(embedded) # GRU는 hidden만 반환 (cell 없음) hidden = torch.cat([hidden[-2], hidden[-1]], dim=1) return self.fc(hidden) # 시계열 예측 class TimeSeriesLSTM(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim): super().__init__() self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers=2, batch_first=True, dropout=0.2) self.fc = nn.Linear(hidden_dim, output_dim) def forward(self, x): out, _ = self.lstm(x) # (batch, seq_len, hidden_dim) return self.fc(out[:, -1, :]) # 마지막 타임스텝만 사용

패딩 & 패킹 (가변 길이 시퀀스 처리)

# Keras — 자동 패딩/마스킹 padded = keras.utils.pad_sequences(sequences, maxlen=200, padding="post") # Embedding의 mask_zero=True로 패딩 위치 자동 무시 layers.Embedding(10000, 128, mask_zero=True) # PyTorch — pack_padded_sequence (효율적 연산) from torch.nn.utils.rnn import pad_sequence, pack_padded_sequence, pad_packed_sequence # 가변 길이 시퀀스 패딩 sequences = [torch.tensor([1,2,3]), torch.tensor([4,5]), torch.tensor([6])] padded = pad_sequence(sequences, batch_first=True, padding_value=0) # tensor([[1, 2, 3], # [4, 5, 0], # [6, 0, 0]]) lengths = torch.tensor([3, 2, 1]) packed = pack_padded_sequence(padded, lengths, batch_first=True, enforce_sorted=False) output, hidden = lstm(packed) # 패딩 위치 연산 건너뜀 output, out_lengths = pad_packed_sequence(output, batch_first=True) # 다시 패딩
주의: return_sequences=True(Keras) / output(PyTorch)는 모든 타임스텝의 출력을 반환합니다. 다층 RNN에서 중간 레이어는 반드시 모든 타임스텝을 다음 레이어에 전달해야 합니다. PyTorch LSTM의 기본 입력은 (seq, batch, features)이며, batch_first=True(batch, seq, features)로 변경하는 것을 권장합니다.

RNN/LSTM 핵심 정리

작업권장 구조비고
텍스트 분류Embedding → BiLSTM → Dense양방향이 단방향보다 우수
감성 분석Embedding → BiGRU → DenseGRU가 더 빠르고 비슷한 성능
시계열 예측 (단일)LSTM → Dense(1)마지막 타임스텝 사용
시계열 예측 (다중)LSTM(return_seq) → TimeDistributed모든 타임스텝 예측
기계 번역Encoder-Decoder + AttentionTransformer로 대체 추세
음성 인식BiLSTM + CTC Loss가변 길이 정렬 불필요
일반 권장Transformer긴 시퀀스, 병렬화 우수

Transformer (자연어 처리)

Transformer는 Self-Attention 메커니즘 기반으로 시퀀스를 병렬 처리하는 아키텍처입니다. RNN의 순차 처리 한계를 극복하여 NLP(BERT, GPT), 비전(ViT), 음성 등 거의 모든 분야에서 최고 성능을 달성하고 있습니다. "Attention Is All You Need" (2017) 논문에서 제안되었습니다.

Self-Attention 메커니즘

# Scaled Dot-Product Attention # # Attention(Q, K, V) = softmax(Q·K^T / √d_k) · V # # Q (Query) : "내가 찾는 것" — 각 토큰이 다른 토큰에게 묻는 질의 # K (Key) : "내가 가진 것" — 각 토큰이 제공하는 키 # V (Value) : "내 내용" — 실제 전달할 정보 # √d_k : 스케일링 — 내적 값이 커지면 softmax 기울기 소실 방지 import torch import torch.nn as nn import torch.nn.functional as F import math def scaled_dot_product_attention(Q, K, V, mask=None): d_k = Q.size(-1) scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k) # (batch, heads, seq, seq) if mask is not None: scores = scores.masked_fill(mask == 0, float("-inf")) # 마스킹 위치 -∞ attn_weights = F.softmax(scores, dim=-1) # 어텐션 가중치 output = torch.matmul(attn_weights, V) # 가중 합 return output, attn_weights

Multi-Head Attention (PyTorch 구현)

class MultiHeadAttention(nn.Module): def __init__(self, d_model, num_heads): super().__init__() assert d_model % num_heads == 0 self.d_model = d_model self.num_heads = num_heads self.d_k = d_model // num_heads self.W_q = nn.Linear(d_model, d_model) self.W_k = nn.Linear(d_model, d_model) self.W_v = nn.Linear(d_model, d_model) self.W_o = nn.Linear(d_model, d_model) def forward(self, query, key, value, mask=None): batch_size = query.size(0) # 선형 변환 후 헤드 분할: (batch, seq, d_model) → (batch, heads, seq, d_k) Q = self.W_q(query).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) K = self.W_k(key).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) V = self.W_v(value).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) # Scaled Dot-Product Attention attn_out, attn_weights = scaled_dot_product_attention(Q, K, V, mask) # 헤드 결합: (batch, heads, seq, d_k) → (batch, seq, d_model) attn_out = attn_out.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model) return self.W_o(attn_out) # PyTorch 내장 (권장 — Flash Attention 자동 적용) mha = nn.MultiheadAttention(embed_dim=512, num_heads=8, batch_first=True) attn_output, attn_weights = mha(query, key, value, attn_mask=mask)

Positional Encoding

# Transformer는 위치 정보가 없으므로 Positional Encoding을 추가 # PE(pos, 2i) = sin(pos / 10000^(2i/d_model)) # PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model)) class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=5000, dropout=0.1): super().__init__() self.dropout = nn.Dropout(dropout) pe = torch.zeros(max_len, d_model) position = torch.arange(0, max_len).unsqueeze(1).float() div_term = torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model)) pe[:, 0::2] = torch.sin(position * div_term) # 짝수 인덱스: sin pe[:, 1::2] = torch.cos(position * div_term) # 홀수 인덱스: cos pe = pe.unsqueeze(0) # (1, max_len, d_model) self.register_buffer("pe", pe) def forward(self, x): x = x + self.pe[:, :x.size(1)] return self.dropout(x)

Transformer Encoder Block

class TransformerEncoderBlock(nn.Module): def __init__(self, d_model, num_heads, d_ff, dropout=0.1): super().__init__() self.self_attn = nn.MultiheadAttention(d_model, num_heads, batch_first=True) self.ffn = nn.Sequential( nn.Linear(d_model, d_ff), # d_ff = d_model × 4 가 일반적 nn.GELU(), nn.Dropout(dropout), nn.Linear(d_ff, d_model), ) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) self.dropout = nn.Dropout(dropout) def forward(self, x, mask=None): # Pre-LayerNorm 구조 (Post-LN보다 학습 안정적) normed = self.norm1(x) attn_out, _ = self.self_attn(normed, normed, normed, attn_mask=mask) x = x + self.dropout(attn_out) # 잔차 연결 normed = self.norm2(x) ffn_out = self.ffn(normed) x = x + self.dropout(ffn_out) # 잔차 연결 return x # PyTorch 내장 Encoder (권장) encoder_layer = nn.TransformerEncoderLayer( d_model=512, nhead=8, dim_feedforward=2048, dropout=0.1, activation="gelu", batch_first=True, norm_first=True ) encoder = nn.TransformerEncoder(encoder_layer, num_layers=6)

Transformer Decoder Block

class TransformerDecoderBlock(nn.Module): def __init__(self, d_model, num_heads, d_ff, dropout=0.1): super().__init__() self.self_attn = nn.MultiheadAttention(d_model, num_heads, batch_first=True) self.cross_attn = nn.MultiheadAttention(d_model, num_heads, batch_first=True) self.ffn = nn.Sequential( nn.Linear(d_model, d_ff), nn.GELU(), nn.Dropout(dropout), nn.Linear(d_ff, d_model), ) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) self.norm3 = nn.LayerNorm(d_model) self.dropout = nn.Dropout(dropout) def forward(self, x, encoder_out, tgt_mask=None, memory_mask=None): # ① Masked Self-Attention (미래 토큰 참조 방지) normed = self.norm1(x) attn_out, _ = self.self_attn(normed, normed, normed, attn_mask=tgt_mask) x = x + self.dropout(attn_out) # ② Cross-Attention (인코더 출력 참조) normed = self.norm2(x) attn_out, _ = self.cross_attn(normed, encoder_out, encoder_out, attn_mask=memory_mask) x = x + self.dropout(attn_out) # ③ Feed-Forward normed = self.norm3(x) x = x + self.dropout(self.ffn(normed)) return x # Causal Mask 생성 (디코더용 — 미래 토큰 가리기) def generate_causal_mask(seq_len): return torch.triu(torch.ones(seq_len, seq_len), diagonal=1).bool() # [[False, True, True ], # [False, False, True ], # [False, False, False]]

완전한 Transformer 모델 (분류)

class TransformerClassifier(nn.Module): def __init__(self, vocab_size, d_model=256, num_heads=8, num_layers=4, d_ff=512, num_classes=2, max_len=512, dropout=0.1): super().__init__() self.embedding = nn.Embedding(vocab_size, d_model) self.pos_encoding = PositionalEncoding(d_model, max_len, dropout) encoder_layer = nn.TransformerEncoderLayer( d_model, num_heads, d_ff, dropout, activation="gelu", batch_first=True, norm_first=True ) self.encoder = nn.TransformerEncoder(encoder_layer, num_layers) self.classifier = nn.Sequential( nn.LayerNorm(d_model), nn.Linear(d_model, num_classes) ) def forward(self, x, src_mask=None): x = self.embedding(x) * math.sqrt(self.d_model) # 스케일링 x = self.pos_encoding(x) x = self.encoder(x, src_key_padding_mask=src_mask) x = x.mean(dim=1) # 평균 풀링 (또는 [CLS] 토큰) return self.classifier(x)

Hugging Face Transformers 활용

# pip install transformers datasets # ① Pipeline — 가장 간단한 사용법 (추론 전용) from transformers import pipeline # 감성 분석 classifier = pipeline("sentiment-analysis") result = classifier("This movie is amazing!") # [{'label': 'POSITIVE', 'score': 0.9998}] # 텍스트 생성 generator = pipeline("text-generation", model="gpt2") output = generator("The future of AI", max_length=50) # 질의응답 qa = pipeline("question-answering") result = qa(question="What is PyTorch?", context="PyTorch is a deep learning framework.") # 요약 summarizer = pipeline("summarization", model="facebook/bart-large-cnn") summary = summarizer(long_text, max_length=130, min_length=30) # ② 직접 모델 로드 — 토크나이저 + 모델 from transformers import AutoTokenizer, AutoModel, AutoModelForSequenceClassification tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2) # 토크나이징 inputs = tokenizer( "Hello, how are you?", padding=True, truncation=True, max_length=128, return_tensors="pt" # "pt"=PyTorch, "tf"=TensorFlow, "np"=NumPy ) # {'input_ids': tensor([[101, 7592, ...]]), 'attention_mask': tensor([[1, 1, ...]])} outputs = model(**inputs) logits = outputs.logits # (batch, num_labels) # ③ 파인튜닝 (Hugging Face Trainer) from transformers import TrainingArguments, Trainer from datasets import load_dataset dataset = load_dataset("imdb") def tokenize_fn(examples): return tokenizer(examples["text"], truncation=True, max_length=256) tokenized = dataset.map(tokenize_fn, batched=True) training_args = TrainingArguments( output_dir="./results", num_train_epochs=3, per_device_train_batch_size=16, per_device_eval_batch_size=64, learning_rate=2e-5, weight_decay=0.01, eval_strategy="epoch", save_strategy="epoch", load_best_model_at_end=True, fp16=True, # 혼합 정밀도 ) trainer = Trainer( model=model, args=training_args, train_dataset=tokenized["train"], eval_dataset=tokenized["test"], ) trainer.train()

주요 Transformer 모델 비교

모델구조특징주요 용도
BERTEncoder양방향, MLM+NSP 사전학습분류, QA, NER
GPT-2/3/4Decoder단방향, 자기회귀 생성텍스트 생성, 대화
T5Encoder-Decoder모든 NLP를 text-to-text로 통합번역, 요약, QA
BARTEncoder-Decoder노이즈 제거 자기부호화기요약, 번역
RoBERTaEncoderBERT 학습 전략 최적화분류, NER (BERT 대체)
DeBERTaEncoder분리된 어텐션 + 향상된 디코딩SuperGLUE SOTA
ViTEncoder이미지를 패치로 분할하여 처리이미지 분류
WhisperEncoder-Decoder음성→텍스트 범용 모델음성 인식, 번역
LLaMADecoder효율적 오픈소스 LLM텍스트 생성, 추론
Transformer 하이퍼파라미터 가이드: d_model = 임베딩 차원 (256~1024), num_heads = 어텐션 헤드 수 (d_model의 약수, 보통 8~16), d_ff = FFN 히든 차원 (보통 d_model × 4), num_layers = 블록 수 (6~12). 학습률은 워밍업(warmup) 후 감소하는 스케줄이 필수적입니다.

Transformer 핵심 정리

구성 요소역할핵심 포인트
Self-Attention토큰 간 관계 모델링O(n²) 복잡도, Flash Attention으로 개선
Multi-Head다양한 관계 패턴 포착헤드별 독립 어텐션 후 결합
Positional Encoding순서 정보 부여sin/cos (고정) 또는 학습 가능
Feed-Forward비선형 변환각 위치에 독립 적용 (MLP)
LayerNorm학습 안정화Pre-LN (블록 앞) 이 더 안정적
잔차 연결그래디언트 흐름 보존모든 서브레이어에 적용
Causal Mask미래 토큰 가리기디코더/자기회귀 모델 필수
Cross-Attention인코더-디코더 연결Q=디코더, K/V=인코더

전이 학습 (Transfer Learning)

전이 학습은 대규모 데이터로 사전학습된 모델의 지식을 새로운 작업에 재활용하는 기법입니다. 적은 데이터와 짧은 학습 시간으로 높은 성능을 달성할 수 있어, 실무에서 모델을 처음부터 학습하는 경우보다 전이 학습을 사용하는 경우가 훨씬 더 많습니다.

전이 학습 전략

전략방법데이터 양적합한 경우
Feature Extraction사전학습 레이어 동결, 헤드만 학습적음 (<1000)새 데이터가 원본과 유사
Fine-tuning (전체)모든 레이어를 낮은 lr로 학습많음 (>10000)데이터 충분, 도메인 다름
Gradual Unfreezing뒤쪽 레이어부터 순차적 해동중간안정적인 파인튜닝
Discriminative LR레이어별 다른 학습률중간~많음앞쪽=낮은lr, 뒤쪽=높은lr

이미지 전이 학습 — Keras

from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.applications import EfficientNetV2B0, ResNet50V2 # ① Feature Extraction (특성 추출) — 사전학습 레이어 동결 base_model = EfficientNetV2B0(weights="imagenet", include_top=False, input_shape=(224, 224, 3)) base_model.trainable = False # 모든 레이어 동결 model = keras.Sequential([ base_model, layers.GlobalAveragePooling2D(), layers.Dropout(0.3), layers.Dense(128, activation="relu"), layers.Dense(10, activation="softmax") ]) model.compile(optimizer=keras.optimizers.Adam(1e-3), # 높은 lr OK (헤드만 학습) loss="sparse_categorical_crossentropy", metrics=["accuracy"]) model.fit(train_ds, epochs=10, validation_data=val_ds) # ② Fine-tuning — 헤드 학습 후 일부 레이어 해동 base_model.trainable = True for layer in base_model.layers[:-30]: # 앞쪽 레이어는 동결 유지 layer.trainable = False model.compile(optimizer=keras.optimizers.Adam(1e-5), # 매우 낮은 lr (기존 가중치 보호) loss="sparse_categorical_crossentropy", metrics=["accuracy"]) model.fit(train_ds, epochs=10, validation_data=val_ds) # 사용 가능한 사전학습 모델 # keras.applications.EfficientNetV2B0~L — 효율적, 최신 권장 # keras.applications.ResNet50V2 — 범용, 검증된 성능 # keras.applications.MobileNetV3Large — 모바일/엣지용 # keras.applications.ConvNeXtTiny — 최신 순수 CNN

이미지 전이 학습 — PyTorch

import torch import torch.nn as nn from torchvision import models # ① Feature Extraction model = models.efficientnet_v2_s(weights="IMAGENET1K_V1") # 모든 레이어 동결 for param in model.parameters(): param.requires_grad = False # 분류 헤드 교체 model.classifier = nn.Sequential( nn.Dropout(0.3), nn.Linear(model.classifier[1].in_features, 10) ) # 교체된 헤드만 학습됨 (requires_grad=True 기본) optimizer = torch.optim.Adam(model.classifier.parameters(), lr=1e-3) # ② Fine-tuning — Discriminative Learning Rate for param in model.parameters(): param.requires_grad = True # 전체 해동 optimizer = torch.optim.AdamW([ {"params": model.features[:5].parameters(), "lr": 1e-6}, # 앞쪽: 아주 낮은 lr {"params": model.features[5:].parameters(), "lr": 1e-5}, # 중간: 낮은 lr {"params": model.classifier.parameters(), "lr": 1e-3}, # 헤드: 높은 lr ], weight_decay=0.01) # ResNet 헤드 교체 resnet = models.resnet50(weights="IMAGENET1K_V2") resnet.fc = nn.Linear(resnet.fc.in_features, 10) # 마지막 FC 교체 # ViT (Vision Transformer) 전이 학습 vit = models.vit_b_16(weights="IMAGENET1K_V1") vit.heads = nn.Linear(vit.heads[0].in_features, 10)

NLP 전이 학습 — Hugging Face

from transformers import AutoTokenizer, AutoModelForSequenceClassification # 사전학습 모델 + 분류 헤드 model_name = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=3) # Layer-wise Learning Rate Decay (LLRD) from transformers import AdamW no_decay = ["bias", "LayerNorm.weight"] optimizer_grouped = [] lr = 2e-5 decay_rate = 0.9 for i in range(12, -1, -1): # BERT 12층 → 0층 layer_lr = lr * (decay_rate ** (12 - i)) layer_params = [(n, p) for n, p in model.named_parameters() if f"layer.{i}." in n] optimizer_grouped.append({ "params": [p for n, p in layer_params if not any(nd in n for nd in no_decay)], "lr": layer_lr, "weight_decay": 0.01 }) # 한국어 모델 예시 # tokenizer = AutoTokenizer.from_pretrained("klue/bert-base") # model = AutoModelForSequenceClassification.from_pretrained("klue/bert-base", num_labels=5)

사전학습 모델 비교 (이미지)

모델파라미터ImageNet Top-1추론 속도적합한 용도
MobileNetV3-Small2.5M67.5%매우 빠름모바일, 엣지
EfficientNet-B05.3M77.1%빠름경량 서버
ResNet-5025M80.9%보통범용 베이스라인
EfficientNetV2-S21M84.2%보통서버 권장
ConvNeXt-Tiny29M82.1%보통현대 CNN
ViT-B/1686M84.5%느림대규모 데이터
EfficientNetV2-L118M85.7%느림최고 정확도
전이 학습 주의사항: Feature extraction 단계에서는 높은 학습률(1e-3)을 사용해도 되지만, fine-tuning 단계에서는 반드시 낮은 학습률(1e-5~1e-4)을 사용하세요 — 사전학습 가중치가 파괴됩니다. 입력 이미지의 전처리(정규화 값)는 사전학습 시 사용된 것과 동일해야 합니다. 데이터가 매우 적으면(<500) fine-tuning 대신 feature extraction만 사용하세요.

전이 학습 핵심 정리

단계동결 범위학습률에포크
1. Feature Extraction사전학습 전체 동결1e-35~10
2. Fine-tuning (뒤쪽)앞쪽 동결, 뒤쪽 30% 해동1e-55~15
3. Fine-tuning (전체)전체 해동 (선택)1e-5~1e-63~5
NLP Fine-tuning전체 해동 + LLRD2e-5 (상위) ~2e-6 (하위)3~5
초급자 가이드 - 딥러닝이란 무엇인가?

딥러닝을 일상적인 비유로 이해해보세요:

  • 신경망 = 뇌의 모방: 인간의 뇌처럼 여러 층(layer)의 뉴런이 연결되어 정보를 처리합니다. "깊은(deep)" 학습이라는 이름은 이 층이 여러 개라는 뜻입니다.
  • 학습 과정: (1) 데이터를 보여줌 → (2) 예측함 → (3) 정답과 비교 → (4) 틀린 만큼 조정 → 반복. 이것이 "역전파(Backpropagation)"의 핵심입니다.
  • CNN = 이미지 전문가: 사진에서 특징(모서리, 패턴, 형태)을 단계적으로 인식합니다.
  • RNN/LSTM = 시퀀스 전문가: 문장의 앞뒤 문맥을 기억하며 처리합니다.
  • Transformer = 현대 AI의 핵심: ChatGPT, DALL-E 등 최신 AI의 기반 구조입니다.

시작 팁: 수학이 부족해도 걱정하지 마세요. Keras는 model.add(Dense(64, activation="relu"))처럼 블록 쌓기 방식으로 모델을 만들 수 있습니다.

중급자 가이드 - 딥러닝 실전 개발 전략

효과적인 딥러닝 모델 개발을 위한 실전 가이드:

  • 전이 학습 우선: 처음부터 모델을 훈련하지 마세요. ResNet, BERT, GPT 등 사전 훈련된 모델을 Fine-tuning하는 것이 일반적으로 더 빠르고 좋은 성능을 냅니다.
  • 데이터 증강(Augmentation): 이미지 회전, 반전, 크롭 등으로 훈련 데이터를 인위적으로 늘립니다. torchvision.transformsalbumentations를 활용합니다.
  • 학습률(Learning Rate): 가장 중요한 하이퍼파라미터입니다. Learning Rate Finder로 최적값을 찾고, Cosine Annealing이나 OneCycleLR 스케줄러를 사용합니다.
  • 과적합 방지: Dropout, Early Stopping, Weight Decay(L2 정규화), 데이터 증강을 조합하여 과적합을 방지합니다.
  • Mixed Precision Training: torch.cuda.amp로 FP16/FP32 혼합 정밀도를 사용하면 메모리 50% 절약, 학습 속도 2-3배 향상이 가능합니다.
고급자 가이드 - 대규모 모델 학습과 최적화

프로덕션 수준의 딥러닝 시스템을 위한 고급 기법:

  • 분산 학습: DistributedDataParallel(DDP)로 멀티 GPU 학습을 수행합니다. DeepSpeed나 FSDP(Fully Sharded Data Parallel)로 수백억 파라미터 모델도 학습 가능합니다.
  • 모델 압축: 양자화(Quantization, INT8/INT4), 지식 증류(Knowledge Distillation), 가지치기(Pruning)로 모델 크기를 줄이고 추론 속도를 높입니다.
  • LLM Fine-tuning: LoRA(Low-Rank Adaptation), QLoRA로 대규모 언어 모델을 효율적으로 미세조정합니다. PEFT(Parameter-Efficient Fine-Tuning) 라이브러리가 이를 쉽게 만들어줍니다.
  • 추론 최적화: TensorRT, ONNX Runtime, vLLM으로 추론 속도를 최적화합니다. KV Cache, Flash Attention, Speculative Decoding 등의 기법이 사용됩니다.
  • RAG(Retrieval Augmented Generation): LLM에 외부 지식을 결합하여 환각(hallucination)을 줄이고 최신 정보를 반영합니다. LangChain, LlamaIndex 등의 프레임워크를 활용합니다.

15. 참고 자료 & 다음 단계

초급 중급 고급

추천 학습 리소스

파이썬 학습의 핵심은 공식 문서를 기준으로 삼고, 실습 프로젝트를 통해 지식을 체화하는 것입니다. 다음은 신뢰할 수 있는 주요 학습 리소스입니다.

주요 라이브러리

파이썬의 진정한 힘은 풍부한 서드파티 라이브러리 생태계에 있습니다. 웹 개발, 데이터 분석, AI, 자동화 등 모든 분야에서 검증된 라이브러리를 활용하면 개발 시간을 획기적으로 단축할 수 있습니다.

🌐 웹 프레임워크

  • Django - Full-stack 프레임워크
  • Flask - 경량 웹 프레임워크
  • FastAPI - 최신 비동기 API

📊 데이터 분석

  • NumPy - 수치 계산
  • Pandas - 데이터 처리
  • Matplotlib - 시각화

🤖 머신러닝/AI

  • TensorFlow - 딥러닝
  • PyTorch - 딥러닝
  • scikit-learn - ML 라이브러리

🔧 유틸리티

  • requests - HTTP 요청
  • BeautifulSoup - 웹 스크래핑
  • SQLAlchemy - DB ORM

학습 로드맵

파이썬 학습은 단계적으로 진행하는 것이 효과적입니다. 기초 문법부터 시작하여 자료구조, 함수, OOP를 익히고, 이후 관심 분야(웹, 데이터, AI 등)에 맞는 전문 라이브러리를 학습하는 것이 권장됩니다.

1단계: 기초 문법, 변수, 제어문 2단계: 함수&자료구조 함수, 리스트, 딕셔너리 3단계: 객체지향 클래스, 상속, 캡슐화 4단계 실전 실전 프로젝트 진행 전문 분야 선택 웹 개발 데이터 분석 AI/ML 자동화/스크립트

빠른 참조 Cheat Sheet

자주 사용하는 파이썬 문법과 함수를 카테고리별로 정리한 빠른 참조표입니다. 코딩 중 문법이 기억나지 않을 때 빠르게 찾아볼 수 있습니다.

카테고리주요 함수/문법
문자열.upper(), .lower(), .strip(), .split(), .join(), f-string, .replace(), .find()
리스트.append(), .extend(), .pop(), .sort(), .reverse(), list comprehension
딕셔너리.keys(), .values(), .items(), .get(), dict comprehension, {**d1, **d2}
집합.add(), .remove(), | (합집합), & (교집합), - (차집합)
파일open(), read(), readlines(), write(), with문
예외try/except/finally, raise, except (Error1, Error2)
함수def, *args, **kwargs, lambda, map(), filter()
클래스class, __init__, self, @property, @staticmethod, @classmethod
타입힌트: int, -> str, List[int], Dict[str, Any], Optional[X]

itertools 예제

itertools는 효율적인 반복 작업을 위한 표준 라이브러리 모듈입니다. 순열(permutations), 조합(combinations), 체이닝(chain), 누적(accumulate) 등 수학적 반복 패턴을 메모리 효율적인 이터레이터로 제공합니다.

from itertools import chain, combinations, permutations, accumulate # 체인 list(chain([1,2], [3,4])) # [1,2,3,4] # 순열 list(permutations("ABC", 2)) # [('A','B'), ('A','C'), ...] # 조합 list(combinations("ABC", 2)) # [('A','B'), ('A','C'), ('B','C')] # 누적 list(accumulate([1,2,3,4])) # [1, 3, 6, 10]

functools 예제

functools는 함수형 프로그래밍을 지원하는 고차 함수 모듈입니다. @lru_cache는 함수 결과를 캐싱하여 반복 호출을 최적화하고, partial은 함수의 일부 인자를 미리 고정하며, reduce는 시퀀스를 하나의 값으로 누적 계산합니다.

from functools import lru_cache, partial, reduce # 메모이제이션 @lru_cache def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) # 부분 함수 add5 = partial(lambda x, y: x + y, 5) add5(3) # 8 # 누적 계산 reduce(lambda x, y: x + y, [1,2,3,4]) # 10

collections 예제

collections는 내장 컨테이너(dict, list, set, tuple)를 확장한 특수 자료구조를 제공합니다. Counter는 요소 빈도를 세고, defaultdict는 존재하지 않는 키에 기본값을 자동 생성하며, deque는 양쪽 끝에서 O(1) 삽입/삭제가 가능한 큐입니다.

from collections import Counter, defaultdict, OrderedDict # 카운터 Counter("hello") # Counter({'l':2, 'h':1, 'e':1, 'o':1}) # 기본값 딕셔너리 d = defaultdict(int) d["count"] += 1 # 오류 없이 1

PEP 주요 규칙

PEP(Python Enhancement Proposal)는 파이썬의 새로운 기능, 코딩 스타일, 프로세스 등을 제안하는 공식 문서입니다. 특히 PEP 8(코딩 스타일 가이드)은 파이썬 커뮤니티의 사실상 표준이며, 모든 파이썬 개발자가 숙지해야 할 핵심 문서입니다.

  • PEP 8: Python 스타일 가이드
  • PEP 20: Python 철학 (import this)
  • PEP 257: Docstring 규칙
  • PEP 484: 타입 힌트
  • PEP 498: f-string
  • PEP 572: Walrus 연산자 (:=)
  • PEP 622: Structural Pattern Matching
초급자 학습 전략 - 파이썬 시작하기

프로그래밍이 처음이라면 다음 순서로 학습하세요:

  • 1주차: 변수, 자료형, print() 익히기. 간단한 계산기 프로그램 만들기.
  • 2주차: if/else 조건문, for/while 반복문. 구구단, 가위바위보 게임 만들기.
  • 3주차: 리스트, 딕셔너리 다루기. 학생 성적 관리 프로그램 만들기.
  • 4주차: 함수 정의와 호출. 기존 코드를 함수로 리팩토링하기.
  • 5-6주차: 파일 입출력, 예외 처리. 메모장 프로그램, 주소록 프로그램 만들기.

핵심 조언: "이론 30% + 실습 70%" 비율을 유지하세요. 강의를 듣고 끝나는 것이 아니라, 직접 코드를 타이핑하고 수정해보는 것이 가장 효과적입니다. 에러 메시지를 두려워하지 마세요 - 에러가 곧 학습의 기회입니다!

중급자 학습 전략 - 실무 역량 키우기

기초 문법을 넘어 실무 개발자로 성장하기 위한 전략:

  • 프로젝트 기반 학습: 웹 스크래퍼, REST API 서버, 데이터 분석 대시보드, CLI 도구 등 실제 동작하는 프로젝트를 완성하세요.
  • 오픈소스 기여: GitHub에서 관심 있는 프로젝트의 이슈를 해결하며 실전 경험을 쌓으세요. "good first issue" 태그를 찾아보세요.
  • 코드 품질 도구: black(포매터), ruff(린터), mypy(타입 체커), pytest(테스트)를 프로젝트에 도입하세요.
  • 디자인 패턴: 싱글턴, 팩토리, 옵저버, 데코레이터 패턴 등을 파이썬에 맞게 구현해보세요.
  • 알고리즘: LeetCode, 프로그래머스에서 Python으로 알고리즘 문제를 풀며 자료구조와 알고리즘 역량을 키우세요.
고급자 학습 전략 - 전문가 수준 도달하기

시니어 개발자/아키텍트 수준으로 도약하기 위한 심화 학습:

  • CPython 소스코드 읽기: cpython/Objects/ 디렉토리의 C 소스코드를 읽으며 파이썬의 내부 동작을 이해하세요. listobject.c, dictobject.c부터 시작합니다.
  • 동시성/병렬성 마스터: asyncio 이벤트 루프, Threading, Multiprocessing, concurrent.futures의 차이와 적합한 사용 상황을 완벽히 이해하세요.
  • 시스템 설계: 마이크로서비스 아키텍처, 메시지 큐(RabbitMQ, Kafka), 캐싱(Redis), 컨테이너(Docker, K8s)와 파이썬 서비스의 통합을 학습하세요.
  • 성능 프로파일링: cProfile, py-spy, memory_profiler로 CPU/메모리 병목을 분석하고, Cython이나 Rust 바인딩(PyO3)으로 핫스팟을 최적화하세요.
  • PEP 제안 추적: Python Steering Council의 결정과 새로운 PEP를 추적하며 언어의 진화 방향을 이해하세요. discuss.python.org에서 커뮤니티 논의에 참여할 수 있습니다.

🎓 마무리 퀴즈

파이썬 학습을 시작하셨습니다! 다음 중 어느 분야에 가장 관심이 있으신가요?

🌐 웹 개발
📊 데이터 분석
🤖 머신러닝/AI
⚡ 자동화/스크립트