[Python 웹 환경] Google Colab 활용 가이드 : AI 관련 코딩 실습 준비
- -
안녕하세요! 갓대희입니다.
오늘은 AI 관련하여 최근 Python을 다루는 분들이 많아지는 것 같아, 본격적인 AI 연동 개발 전 Python 개발 환경에 대해 몇가지 다뤄보려고 한다. 그 중 오늘은 Google Golab 에 대해 다뤄볼 예정이다.
Google Colab은 브라우저만 있으면 누구나 무료로 GPU/TPU를 활용한 AI 개발이 가능한 Jupyter 노트북 환경이다. 설치 없이 즉시 Python 코드를 실행하고, 머신러닝/딥러닝 모델을 학습할 수 있어 AI 입문자부터 전문가까지 널리 사용되고 있다.

인공지능과 머신러닝을 학습하거나 실무에 적용하려면 강력한 컴퓨팅 자원이 필요하다. 특히 딥러닝 모델 학습에는 GPU가 필수적이지만, 고가의 하드웨어를 개인이 구매하기는 부담스럽다. Google Colab은 이러한 진입 장벽을 낮춰주는 강력한 도구로, 클라우드 기반으로 무료 누구나 AI 개발을 시작할 수 있다.
Google Colab이란?
Google Colaboratory(줄여서 Colab)는 Google이 제공하는 클라우드 기반 Jupyter 노트북 환경다. 웹 브라우저를 통해 Python 코드를 작성하고 실행할 수 있으며, 별도의 설치나 설정 없이 즉시 사용할 수 있다.
주요 특징
- 무료 GPU/TPU 제공: 고가의 하드웨어 없이 딥러닝 모델을 학습할 수 있다.
- 설치 불필요: 브라우저만 있으면 즉시 사용 가능하며, 주요 라이브러리가 사전 설치된다.
- Google Drive 연동: 데이터와 노트북을 클라우드에 저장하고 어디서나 접근할 수 있다.
- 실시간 협업: 여러 사용자가 동시에 같은 노트북을 편집하고 공유할 수 있다.
- 풍부한 생태계: TensorFlow, PyTorch, scikit-learn 등 주요 AI 프레임워크를 지원한다.
Colab은 특히 AI 교육, 프로토타이핑, 연구 목적으로 널리 활용된다. 학생, 연구자, 데이터 과학자들이 복잡한 환경 설정 없이 바로 실험을 시작할 수 있어 생산성이 크게 향상된다.
주요 기능 및 하드웨어 스펙
제공되는 컴퓨팅 자원
무료 플랜
- GPU: NVIDIA Tesla K80 또는 T4 (12GB VRAM)
- RAM: 약 12-13GB
- 디스크: 약 100GB (임시 저장소)
- 최대 런타임: 12시간 (이후 자동 종료)
- TPU: Cloud TPU (180 테라플롭스 성능)
Colab Pro ($9.99/월)
- GPU: NVIDIA Tesla T4, P100 우선 액세스
- RAM: 최대 32GB
- 최대 런타임: 24시간
- Google Drive 저장공간: 100GB
Colab Pro+ ($49.99/월)
- GPU: NVIDIA A100, V100 우선 액세스
- RAM: 최대 52GB
- 최대 런타임: 24시간 이상
- Google Drive 저장공간: 250GB
- 컴퓨팅 유닛: 월 500개 (Pro 대비 5배)
Compute Units (컴퓨팅 유닛) 상세
2024년부터 도입된 Compute Units는 GPU/TPU 사용량을 측정하는 단위다. Pay-as-you-go 방식 또는 구독 플랜에 포함되어 제공된다.
- Pay-as-you-go: $9.99로 100 Compute Units 구매 (CU당 $0.10)
- T4 GPU: 약 11.7 CU/시간 (≈ $1.17/시간) = 약 8.5시간 / 100 CU
- V100 GPU: 약 5 CU/시간 (≈ $0.50/시간)
- A100 GPU: 약 60-62 CU/시간 (≈ $6.00-6.20/시간) - 고성능 GPU
- Pro/Pro+ 플랜: 월별 일정 CU 포함 (Pro: 100 CU/월, Pro+: 500 CU/월), 초과 시 추가 구매 가능
2025년 신기능: Gemini AI 통합
2025년 Colab은 Gemini 2.5 Flash를 통합하여 AI 협업 기능을 대폭 강화했다. 모든 사용자가 사용 가능한 새로운 AI 경험을 제공하며, Pro/Pro+ 구독자는 추가 고급 기능에 접근할 수 있다.
Gemini 2.5 Flash 주요 기능
- Agentic Collaborator: 반복적 쿼리를 통해 복잡한 문제를 자동으로 해결한다
- Data Science Agent: 차세대 데이터 과학 에이전트가 코드 작성 및 분석을 지원한다
- Code Transformation: 자연어 명령으로 코드를 자동 생성 및 변환한다
- Intelligent Suggestions: 컨텍스트 기반 코드 완성 및 최적화를 제안한다
모든 사용자 무료 제공: google.colab.ai 라이브러리
무료 사용자를 포함한 모든 Colab 사용자가 Colab UI 내에서 Gemini AI를 직접 사용할 수 있다. 자동완성, 코드 생성, 문제 해결 등이 가능하다. (2025년 11월부터)


# Colab에서 Gemini AI 활용 방법 (2025년 11월부터 무료 사용 가능)
# 방법 1: 왼쪽 패널의 "Gemini" 탭에서 직접 사용 (추천)
# 이것이 가장 간단하고 추천되는 방법이다. 별도 설정이 필요 없다.
# - 코드 설명 요청: 선택한 코드 → 우클릭 → "Gemini 물어보기"
# - 에러 해결: 에러 메시지 선택 → Gemini에 설명 요청
# - 코드 생성: 빈 셀에 자연어 입력 → Gemini가 코드 자동 생성
# 방법 2: Python SDK를 사용한 프로그래매틱 접근 (선택사항)
# 다음 코드는 Google AI Studio에서 API 키를 얻었을 때만 동작한다
# https://makersuite.google.com 에서 API 키 생성 후 아래 코드 실행
# Google AI Python SDK 설치
!pip install -q google-generativeai
# API 키 설정 (https://makersuite.google.com에서 발급)
import os
os.environ['GOOGLE_API_KEY'] = 'YOUR_API_KEY_HERE' # 실제 API 키로 교체
# Gemini API를 import하고 초기화한다
import google.generativeai as genai
# API 키로 초기화 (Colab 환경에서 자동 인증 가능)
genai.configure() # 환경 변수의 API 키 사용
# Gemini 모델 사용
model = genai.GenerativeModel("gemini-2.5-flash")
response = model.generate_content("Python으로 간단한 데이터 시각화 코드를 작성해줘")
print(response.text)
Gemini AI 기능은 디버깅, 코드 리팩토링, 데이터 분석 워크플로우 자동화에 특히 유용하다. 복잡한 에러 메시지를 AI에게 보내면 해결책을 제안받을 수 있다.
시작하기: Colab 사용법
Colab 접속 및 노트북 생성
1. Google 계정으로 로그인: colab.research.google.com에 접속하자.
2. 새 노트북 생성: 'File > New notebook' 또는 '+ 새 노트' 버튼을 클릭하자.

3. 런타임 설정: 상단 메뉴에서 'Runtime > Change runtime type'을 선택하자.

4. 하드웨어 가속기 선택: Hardware accelerator에서 GPU 또는 TPU를 선택하자.
5. Python 버전 선택: Runtime type에서 Python 3를 선택하자 (기본값).

6. 저장: 노트북은 자동으로 Google Drive의 'Colab Notebooks' 폴더에 저장된다.
GPU를 사용하지 않는 간단한 작업에는 하드웨어 가속기를 'None'으로 설정하자. 이렇게 하면 GPU 자원을 절약하고, 실제로 필요한 딥러닝 작업에 더 많은 GPU 시간을 확보할 수 있다.
키보드 단축키로 생산성 높이기
Colab에서 효율적으로 작업하려면 키보드 단축키를 활용하는 것이 중요하다. 마우스 사용을 최소화하여 개발 속도를 크게 향상시킬 수 있다.
코드 실행 단축키
Ctrl + Enter(Mac:Cmd + Enter): 현재 셀 실행Shift + Enter: 현재 셀 실행 후 다음 셀로 이동 (없으면 새 셀 생성)Alt + Enter(Mac:Option + Enter): 현재 셀 실행 후 아래에 새 셀 삽입
셀 관리 단축키
먼저 Ctrl + M (Mac: Cmd + M)을 누른 후 아래 키를 입력하자:
Ctrl + M → B: 아래에 새 코드 셀 삽입Ctrl + M → A: 위에 새 코드 셀 삽입Ctrl + M → D: 현재 셀 삭제 (2번 누름)Ctrl + M → Y: 코드 셀로 변환Ctrl + M → M: 마크다운 셀로 변환Ctrl + M → Z: 셀 삭제 실행 취소
기타 유용한 단축키
Ctrl + S(Mac:Cmd + S): 노트북 저장Ctrl + /(Mac:Cmd + /): 주석 토글Ctrl + M → H: 단축키 도움말 표시Tab: 자동 완성Shift + Tab: 함수 도움말 표시
예제 1 : 환경 설정 및 기본 확인
Colab 환경을 확인하고, GPU가 제대로 할당되었는지 검증해보자. 이는 모든 AI 프로젝트의 시작점이다.
import sys # 시스템 관련 모듈 import
import platform # 플랫폼 정보 확인 모듈
# Python 버전 출력 - Colab은 기본적으로 Python 3.x 제공
print(f"Python 버전: {sys.version}")
# 운영체제 정보 출력 - Colab은 Linux 환경
print(f"운영체제: {platform.system()} {platform.release()}")
# CPU 정보 확인 - 일반적으로 Intel Xeon 프로세서
print(f"프로세서: {platform.processor()}")
ex) 현재 사용중인 내 환경 정보 확인1

+ 코드를 클릭하여 코드 셀을 하나 더 추가해보자.

import torch # PyTorch 라이브러리 - 딥러닝 프레임워크
import tensorflow as tf # TensorFlow 라이브러리 - Google의 딥러닝 프레임워크
# PyTorch에서 GPU(CUDA) 사용 가능 여부 확인
# CUDA는 NVIDIA GPU를 사용하기 위한 병렬 컴퓨팅 플랫폼
print(f"PyTorch CUDA 사용 가능: {torch.cuda.is_available()}")
# 사용 가능한 GPU가 있다면 GPU 이름 출력
if torch.cuda.is_available():
# GPU 개수 확인 - Colab은 일반적으로 1개의 GPU 제공
print(f"GPU 개수: {torch.cuda.device_count()}")
# 현재 사용 중인 GPU의 이름 출력 (예: Tesla T4, K80 등)
print(f"GPU 이름: {torch.cuda.get_device_name(0)}")
# GPU 메모리 정보 출력 (바이트 단위를 GB로 변환)
print(f"GPU 메모리: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.2f} GB")
# TensorFlow에서 사용 가능한 GPU 목록 출력
# TensorFlow는 GPU를 PhysicalDevice 객체로 관리
print(f"\nTensorFlow GPU 목록: {tf.config.list_physical_devices('GPU')}")
ex) 현재 사용중인 내 환경 정보 확인2

import psutil # 시스템 유틸리티 모듈 - 메모리, CPU 정보 확인
import shutil # 파일 작업 모듈 - 디스크 용량 확인
# RAM 정보 확인
ram = psutil.virtual_memory()
# 전체 RAM 용량을 GB 단위로 출력
print(f"전체 RAM: {ram.total / 1024**3:.2f} GB")
# 사용 가능한 RAM 용량
print(f"사용 가능한 RAM: {ram.available / 1024**3:.2f} GB")
# RAM 사용률 (퍼센트)
print(f"RAM 사용률: {ram.percent}%")
# 디스크 정보 확인
disk = shutil.disk_usage("/")
# 전체 디스크 용량 (Colab은 약 100GB 제공)
print(f"\n전체 디스크: {disk.total / 1024**3:.2f} GB")
# 사용 가능한 디스크 공간
print(f"사용 가능한 디스크: {disk.free / 1024**3:.2f} GB")
ex) 현재 사용중인 내 환경 정보 확인3

이 코드를 실행하면 현재 Colab 환경의 하드웨어 스펙을 확인할 수 있다. GPU가 할당되지 않았다면 Runtime 설정을 다시 확인하자.
예제 2: 데이터 분석 기초 (NumPy & Pandas)
AI 프로젝트의 첫 단계는 데이터 처리다. NumPy와 Pandas는 데이터 과학과 머신러닝의 필수 라이브러리로, 효율적인 배열 연산과 데이터프레임 조작을 지원한다. 파이썬을 조금 이래저래 써봤다고 하면 무조건 사용해봤을 필수 라이브러리이다.
import numpy as np # NumPy는 수치 연산을 위한 핵심 라이브러리
# 1차원 배열 생성 - Python 리스트를 NumPy 배열로 변환
arr = np.array([1, 2, 3, 4, 5])
print(f"배열: {arr}")
print(f"배열 타입: {type(arr)}") # numpy.ndarray 타입
print(f"배열 형태: {arr.shape}") # (5,) - 5개 요소를 가진 1차원 배열
# 2차원 배열 (행렬) 생성 - 이미지 데이터나 신경망 가중치 표현에 사용
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(f"\n행렬:\n{matrix}")
print(f"행렬 형태: {matrix.shape}") # (3, 3) - 3x3 행렬
# 배열 생성 유틸리티 함수들
# zeros: 0으로 채워진 배열 - 초기화에 자주 사용
zeros = np.zeros((2, 3)) # 2x3 크기의 0 행렬
print(f"\n0 행렬:\n{zeros}")
# ones: 1로 채워진 배열
ones = np.ones((2, 3))
print(f"\n1 행렬:\n{ones}")
# arange: 순차적인 숫자 배열 - Python의 range()와 유사
seq = np.arange(0, 10, 2) # 0부터 10 미만까지 2씩 증가
print(f"\n순차 배열: {seq}")
# linspace: 균등 간격 배열 - 시각화나 수치 해석에 유용
lin = np.linspace(0, 1, 5) # 0과 1 사이를 5개로 균등 분할
print(f"균등 간격 배열: {lin}")
# 배열 연산 - 브로드캐스팅을 통한 벡터화 연산
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
# 요소별 덧셈 - GPU 가속이 가능한 벡터 연산
print(f"\n배열 덧셈: {arr1 + arr2}")
# 요소별 곱셈 - 신경망의 요소별 곱(element-wise product)
print(f"배열 곱셈: {arr1 * arr2}")
# 행렬 곱셈(내적) - 선형대수 연산, 신경망의 핵심
print(f"내적: {np.dot(arr1, arr2)}")
# 통계 함수 - 데이터 분석의 기본
data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(f"\n평균: {np.mean(data)}") # 산술 평균
print(f"중앙값: {np.median(data)}") # 중앙값
print(f"표준편차: {np.std(data)}") # 표준편차 - 데이터의 분산 정도
print(f"최댓값: {np.max(data)}")
print(f"최솟값: {np.min(data)}")

import pandas as pd # Pandas는 테이블 형태 데이터 처리 라이브러리
# 딕셔너리로 데이터프레임 생성 - 엑셀 스프레드시트와 유사한 구조
data = {
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'], # 이름 열
'Age': [25, 30, 35, 28, 32], # 나이 열
'Score': [85, 90, 78, 92, 88] # 점수 열
}
# DataFrame 객체 생성 - 2차원 테이블 구조
df = pd.DataFrame(data)
print("데이터프레임:")
print(df)
# 데이터프레임 정보 확인
print(f"\n데이터 형태: {df.shape}") # (행 수, 열 수)
print(f"\n데이터 타입:\n{df.dtypes}") # 각 열의 데이터 타입
print(f"\n기본 통계:\n{df.describe()}") # 수치형 열의 통계 요약
# 데이터 선택 및 필터링
# 열 선택 - 대괄호 표기법
print(f"\nName 열:\n{df['Name']}")
# 조건 필터링 - Boolean indexing
# 나이가 30 이상인 행만 선택
filtered = df[df['Age'] >= 30]
print(f"\n나이 30 이상:\n{filtered}")
# 복수 조건 - & (AND), | (OR) 연산자 사용
# 나이 30 이상이고 점수 85 이상
multi_filter = df[(df['Age'] >= 30) & (df['Score'] >= 85)]
print(f"\n나이 30 이상, 점수 85 이상:\n{multi_filter}")
# 새 열 추가 - 기존 열을 활용한 계산
df['Grade'] = df['Score'].apply(lambda x: 'A' if x >= 90 else ('B' if x >= 80 else 'C'))
print(f"\n등급 추가:\n{df}")
# 정렬 - 점수 내림차순
sorted_df = df.sort_values('Score', ascending=False)
print(f"\n점수 내림차순 정렬:\n{sorted_df}")
# 그룹화 및 집계 - SQL의 GROUP BY와 유사
# 등급별 평균 나이 계산
grouped = df.groupby('Grade')['Age'].mean()
print(f"\n등급별 평균 나이:\n{grouped}")
데이터프레임:
Name Age Score
0 Alice 25 85
1 Bob 30 90
2 Charlie 35 78
3 David 28 92
4 Eve 32 88
데이터 형태: (5, 3)
데이터 타입:
Name object
Age int64
Score int64
dtype: object
기본 통계:
Age Score
count 5.000000 5.000000
mean 30.000000 86.600000
std 3.807887 5.458938
min 25.000000 78.000000
25% 28.000000 85.000000
50% 30.000000 88.000000
75% 32.000000 90.000000
max 35.000000 92.000000
Name 열:
0 Alice
1 Bob
2 Charlie
3 David
4 Eve
Name: Name, dtype: object
나이 30 이상:
Name Age Score
1 Bob 30 90
2 Charlie 35 78
4 Eve 32 88
나이 30 이상, 점수 85 이상:
Name Age Score
1 Bob 30 90
4 Eve 32 88
등급 추가:
Name Age Score Grade
0 Alice 25 85 B
1 Bob 30 90 A
2 Charlie 35 78 C
3 David 28 92 A
4 Eve 32 88 B
점수 내림차순 정렬:
Name Age Score Grade
3 David 28 92 A
1 Bob 30 90 A
4 Eve 32 88 B
0 Alice 25 85 B
2 Charlie 35 78 C
등급별 평균 나이:
Grade
A 29.0
B 28.5
C 35.0
Name: Age, dtype: float64
NumPy는 다차원 배열 연산에 특화되어 있으며, Pandas는 테이블 형태의 데이터를 다루는데 최적화되어 있다.
이 두 라이브러리는 모든 데이터 과학 프로젝트의 기반이 된다.
예제 3: 머신러닝 기초 (Scikit-learn)
Scikit-learn은 전통적인 머신러닝 알고리즘을 제공하는 라이브러리다. 분류, 회귀, 군집화 등 다양한 알고리즘을 일관된 API로 사용할 수 있다.
당장은 그냥 이런게 있다는 정도만 알고 넘어가면 좋을 것 같다. 유명한 붓꽃(Iris) 데이터셋을 사용하여 분류 모델을 살펴볼 수 있는 소스만 남겨 두도록 한다.
from sklearn.datasets import load_iris # 붓꽃 데이터셋 로드
from sklearn.model_selection import train_test_split # 데이터 분할 함수
from sklearn.ensemble import RandomForestClassifier # 랜덤 포레스트 분류기
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix # 평가 지표
import matplotlib.pyplot as plt # 시각화 라이브러리
import seaborn as sns # 통계 시각화 라이브러리
# 1. 데이터 로드
# load_iris()는 150개 샘플의 붓꽃 데이터 반환 (4개 특성, 3개 클래스)
iris = load_iris()
X = iris.data # 특성 데이터 (꽃잎/꽃받침의 길이와 너비)
y = iris.target # 타겟 레이블 (0: Setosa, 1: Versicolor, 2: Virginica)
print(f"특성 데이터 형태: {X.shape}") # (150, 4) - 150개 샘플, 4개 특성
print(f"타겟 데이터 형태: {y.shape}") # (150,) - 150개 레이블
print(f"특성 이름: {iris.feature_names}") # 각 특성의 이름
print(f"클래스 이름: {iris.target_names}") # 3가지 붓꽃 종류
# 2. 데이터 분할
# 전체 데이터를 학습용(80%)과 테스트용(20%)으로 분할
# random_state는 재현성을 위한 시드값
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, # 20%를 테스트 데이터로
random_state=42, # 난수 시드 고정
stratify=y # 클래스 비율을 유지하며 분할
)
print(f"\n학습 데이터 크기: {X_train.shape}") # (120, 4)
print(f"테스트 데이터 크기: {X_test.shape}") # (30, 4)
# 3. 모델 생성 및 학습
# 랜덤 포레스트: 여러 개의 결정 트리를 앙상블하는 알고리즘
model = RandomForestClassifier(
n_estimators=100, # 100개의 결정 트리 사용
random_state=42, # 재현성을 위한 시드
max_depth=5, # 트리의 최대 깊이 제한 (과적합 방지)
n_jobs=-1 # 모든 CPU 코어 사용 (병렬 처리)
)
# fit() 메서드로 모델 학습 - 데이터 패턴 학습
model.fit(X_train, y_train)
print("\n모델 학습 완료!")
# 4. 예측 및 평가
# 학습된 모델로 테스트 데이터 예측
y_pred = model.predict(X_test)
# 정확도 계산 - 전체 예측 중 맞춘 비율
accuracy = accuracy_score(y_test, y_pred)
print(f"\n정확도: {accuracy:.4f} ({accuracy*100:.2f}%)")
# 상세 분류 리포트 - precision, recall, f1-score
print("\n분류 리포트:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))
# 혼동 행렬(Confusion Matrix) 생성
# 각 클래스별로 실제 vs 예측을 비교
cm = confusion_matrix(y_test, y_pred)
# 혼동 행렬 시각화
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=iris.target_names,
yticklabels=iris.target_names)
plt.title('혼동 행렬 (Confusion Matrix)')
plt.ylabel('실제 클래스')
plt.xlabel('예측 클래스')
plt.show()
# 5. 특성 중요도 분석
# 랜덤 포레스트는 각 특성의 중요도를 제공
feature_importance = pd.DataFrame({
'특성': iris.feature_names,
'중요도': model.feature_importances_
}).sort_values('중요도', ascending=False)
print("\n특성 중요도:")
print(feature_importance)
# 특성 중요도 시각화
plt.figure(figsize=(10, 6))
plt.barh(feature_importance['특성'], feature_importance['중요도'])
plt.xlabel('중요도')
plt.title('특성 중요도 분석')
plt.show()
혼동 행렬(Confusion Matrix)은 모델이 어떤 클래스를 잘못 예측하는지 파악하는데 유용하다. 대각선은 올바른 예측을, 나머지는 오분류를 나타낸다. 특성 중요도를 확인하면 어떤 입력 변수가 예측에 가장 큰 영향을 미치는지 알 수 있다.
예제 4: 딥러닝 기초 (TensorFlow/Keras)
딥러닝은 다층 신경망을 사용하여 복잡한 패턴을 학습하는 기술이다.
TensorFlow와 Keras를 사용하여 손글씨 숫자(MNIST) 데이터셋을 분류하는 신경망을 구축한다.
import tensorflow as tf # TensorFlow 2.x
from tensorflow import keras # Keras는 TensorFlow의 고수준 API
from tensorflow.keras import layers # 신경망 레이어 모듈
import matplotlib.pyplot as plt # 시각화
import numpy as np
# 1. 데이터 로드 및 전처리
# MNIST: 0-9 숫자 손글씨 이미지 (28x28 픽셀, 70,000장)
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
print(f"학습 데이터 형태: {x_train.shape}") # (60000, 28, 28)
print(f"테스트 데이터 형태: {x_test.shape}") # (10000, 28, 28)
print(f"레이블 범위: {np.min(y_train)} ~ {np.max(y_train)}") # 0 ~ 9
# 데이터 정규화: 픽셀 값을 0-255에서 0-1 범위로 변환
# 신경망 학습 시 입력 정규화는 필수 (수렴 속도 향상, 안정성 증가)
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
# 이미지 평탄화: (28, 28) → (784,) 1차원 벡터로 변환
# Fully Connected Network에 입력하기 위해 필요
x_train_flat = x_train.reshape(-1, 28*28) # -1은 자동 계산 (60000)
x_test_flat = x_test.reshape(-1, 28*28)
print(f"\n평탄화 후 형태: {x_train_flat.shape}") # (60000, 784)
# 2. 신경망 모델 구축
# Sequential API: 레이어를 순차적으로 쌓는 간단한 방법
model = keras.Sequential([
# 입력층: 784개 픽셀 입력
layers.Input(shape=(784,)),
# 첫 번째 은닉층: 128개 뉴런, ReLU 활성화 함수
# ReLU(x) = max(0, x): 음수는 0, 양수는 그대로 (비선형성 도입)
layers.Dense(128, activation='relu', name='hidden1'),
# 드롭아웃: 학습 중 20%의 뉴런을 무작위로 비활성화
# 과적합(overfitting) 방지 기법
layers.Dropout(0.2, name='dropout1'),
# 두 번째 은닉층: 64개 뉴런
layers.Dense(64, activation='relu', name='hidden2'),
# 출력층: 10개 클래스 (0-9 숫자), softmax 활성화
# softmax: 출력을 확률 분포로 변환 (합이 1)
layers.Dense(10, activation='softmax', name='output')
])
# 모델 구조 요약
model.summary()
# 3. 모델 컴파일
# 학습에 필요한 최적화 알고리즘, 손실 함수, 평가 지표 설정
model.compile(
# Adam 옵티마이저: 학습률을 자동으로 조정하는 고급 경사하강법
optimizer='adam',
# 다중 클래스 분류 손실 함수
# sparse_categorical_crossentropy: 레이블이 정수형일 때 사용
loss='sparse_categorical_crossentropy',
# 평가 지표: 정확도
metrics=['accuracy']
)
# 4. 모델 학습
# fit() 메서드로 학습 실행
history = model.fit(
x_train_flat, y_train, # 학습 데이터
epochs=10, # 전체 데이터를 10번 반복 학습
batch_size=32, # 한 번에 32개 샘플씩 처리 (미니배치)
validation_split=0.2, # 학습 데이터의 20%를 검증용으로 사용
verbose=1 # 학습 과정 출력
)
# 5. 모델 평가
# 테스트 데이터로 최종 성능 평가
test_loss, test_accuracy = model.evaluate(x_test_flat, y_test, verbose=0)
print(f"\n테스트 정확도: {test_accuracy:.4f} ({test_accuracy*100:.2f}%)")
print(f"테스트 손실: {test_loss:.4f}")
# 6. 학습 과정 시각화
# 학습/검증 정확도 그래프
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='학습 정확도')
plt.plot(history.history['val_accuracy'], label='검증 정확도')
plt.xlabel('Epoch')
plt.ylabel('정확도')
plt.legend()
plt.title('모델 정확도')
plt.grid(True)
# 학습/검증 손실 그래프
plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='학습 손실')
plt.plot(history.history['val_loss'], label='검증 손실')
plt.xlabel('Epoch')
plt.ylabel('손실')
plt.legend()
plt.title('모델 손실')
plt.grid(True)
plt.tight_layout()
plt.show()
# 7. 예측 테스트
# 테스트 데이터에서 무작위로 10개 샘플 선택하여 예측
num_samples = 10
random_indices = np.random.randint(0, len(x_test), num_samples)
plt.figure(figsize=(15, 3))
for i, idx in enumerate(random_indices):
# 예측 수행: 각 클래스에 대한 확률 반환
prediction = model.predict(x_test_flat[idx:idx+1], verbose=0)
predicted_label = np.argmax(prediction) # 가장 높은 확률의 클래스
true_label = y_test[idx]
# 이미지와 예측 결과 시각화
plt.subplot(1, num_samples, i+1)
plt.imshow(x_test[idx], cmap='gray')
# 정답이면 파란색, 오답이면 빨간색
color = 'blue' if predicted_label == true_label else 'red'
plt.title(f'예측: {predicted_label}\n실제: {true_label}', color=color)
plt.axis('off')
plt.tight_layout()
plt.show()
# 8. 모델 저장 (선택사항)
# 학습된 모델을 파일로 저장하여 재사용 가능
model.save('mnist_model.h5')
print("\n모델이 'mnist_model.h5'로 저장되었다.")
이 예제는 완전 연결 신경망(Fully Connected Network)을 구축하는 예시 라고 한다. 딥러닝의 핵심 개념(층, 활성화 함수, 손실 함수, 최적화)을 모두 포함하고 있어 학습용으로 매우 적합하다.
예제 5: 합성곱 신경망 (CNN)으로 이미지 분류
합성곱 신경망(CNN)은 이미지 처리에 특화된 신경망 구조다. 이미지의 공간적 특징을 효과적으로 학습할 수 있어 컴퓨터 비전 분야에서 필수적이다. Fashion MNIST 데이터셋으로 의류 이미지를 분류한다.
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models
import numpy as np
import matplotlib.pyplot as plt
# 1. 데이터 로드
# Fashion MNIST: 10가지 의류 카테고리의 28x28 그레이스케일 이미지
(x_train, y_train), (x_test, y_test) = keras.datasets.fashion_mnist.load_data()
# 클래스 이름 정의
class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat',
'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
print(f"학습 데이터: {x_train.shape}") # (60000, 28, 28)
print(f"클래스: {class_names}")
# 2. 데이터 전처리
# 정규화: 0-255 → 0-1 범위로 변환
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
# CNN에 입력하기 위해 채널 차원 추가
# (60000, 28, 28) → (60000, 28, 28, 1)
# 마지막 1은 그레이스케일 채널 (RGB는 3)
x_train = x_train.reshape(-1, 28, 28, 1)
x_test = x_test.reshape(-1, 28, 28, 1)
print(f"전처리 후: {x_train.shape}") # (60000, 28, 28, 1)
# 3. CNN 모델 구축
model = models.Sequential([
# 첫 번째 합성곱 블록
# Conv2D: 2D 합성곱 레이어 - 이미지의 국소적 패턴 감지
layers.Conv2D(
32, # 32개의 필터(커널) 사용
(3, 3), # 3x3 크기의 필터
activation='relu', # ReLU 활성화
input_shape=(28, 28, 1) # 입력 이미지 크기
),
# MaxPooling: 공간 해상도 축소, 주요 특징 보존
# 2x2 영역에서 최댓값만 선택하여 크기를 절반으로 줄임
layers.MaxPooling2D((2, 2)),
# 두 번째 합성곱 블록
layers.Conv2D(64, (3, 3), activation='relu'), # 64개 필터로 증가
layers.MaxPooling2D((2, 2)),
# 세 번째 합성곱 블록
layers.Conv2D(64, (3, 3), activation='relu'),
# Flatten: 다차원 특성 맵을 1차원으로 평탄화
# (batch, height, width, channels) → (batch, features)
layers.Flatten(),
# 완전 연결층 (Fully Connected Layer)
layers.Dense(64, activation='relu'),
# 출력층: 10개 클래스에 대한 확률 분포
layers.Dense(10, activation='softmax')
])
# 모델 구조 확인
model.summary()
# 4. 모델 컴파일
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# 5. 콜백 설정
# EarlyStopping: 검증 손실이 개선되지 않으면 조기 종료
early_stop = keras.callbacks.EarlyStopping(
monitor='val_loss', # 검증 손실 모니터링
patience=3, # 3 에포크 동안 개선 없으면 중단
restore_best_weights=True # 최상의 가중치로 복원
)
# 6. 모델 학습
history = model.fit(
x_train, y_train,
epochs=15, # 최대 15 에포크
batch_size=64, # 배치 크기 64
validation_split=0.2, # 20% 검증 데이터
callbacks=[early_stop], # 콜백 적용
verbose=1
)
# 7. 모델 평가
test_loss, test_accuracy = model.evaluate(x_test, y_test, verbose=0)
print(f"\n테스트 정확도: {test_accuracy:.4f} ({test_accuracy*100:.2f}%)")
# 8. 학습 곡선 시각화
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='학습 정확도')
plt.plot(history.history['val_accuracy'], label='검증 정확도')
plt.xlabel('Epoch')
plt.ylabel('정확도')
plt.legend()
plt.title('CNN 학습 정확도')
plt.grid(True)
plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='학습 손실')
plt.plot(history.history['val_loss'], label='검증 손실')
plt.xlabel('Epoch')
plt.ylabel('손실')
plt.legend()
plt.title('CNN 학습 손실')
plt.grid(True)
plt.tight_layout()
plt.show()
# 9. 예측 및 시각화
# 테스트 데이터에서 무작위 샘플 선택
num_rows, num_cols = 3, 5
num_images = num_rows * num_cols
random_indices = np.random.randint(0, len(x_test), num_images)
# 예측 수행
predictions = model.predict(x_test[random_indices])
plt.figure(figsize=(15, 9))
for i, idx in enumerate(random_indices):
predicted_label = np.argmax(predictions[i])
true_label = y_test[idx]
# 예측 확률
confidence = np.max(predictions[i]) * 100
plt.subplot(num_rows, num_cols, i+1)
plt.imshow(x_test[idx].reshape(28, 28), cmap='gray')
# 정답 여부에 따라 색상 변경
color = 'blue' if predicted_label == true_label else 'red'
plt.title(f'{class_names[predicted_label]}\n({confidence:.1f}%)\n실제: {class_names[true_label]}',
fontsize=9, color=color)
plt.axis('off')
plt.tight_layout()
plt.show()
# 10. 특성 맵 시각화 (선택사항)
# 첫 번째 합성곱 레이어의 출력 확인
layer_outputs = [layer.output for layer in model.layers[:3]] # 처음 3개 레이어
activation_model = models.Model(inputs=model.input, outputs=layer_outputs)
# 샘플 이미지로 활성화 맵 생성
sample_image = x_test[0:1] # 첫 번째 테스트 이미지
activations = activation_model.predict(sample_image)
# 첫 번째 Conv2D 레이어의 특성 맵 시각화
first_layer_activation = activations[0]
print(f"\n첫 번째 Conv2D 출력 형태: {first_layer_activation.shape}")
# 처음 8개 필터의 특성 맵 출력
plt.figure(figsize=(12, 6))
for i in range(8):
plt.subplot(2, 4, i+1)
plt.imshow(first_layer_activation[0, :, :, i], cmap='viridis')
plt.title(f'필터 {i+1}')
plt.axis('off')
plt.suptitle('첫 번째 합성곱 레이어의 특성 맵')
plt.tight_layout()
plt.show()

CNN은 합성곱 레이어를 통해 이미지의 에지, 텍스처 같은 저수준 특징부터 복잡한 패턴까지 계층적으로 학습한다. MaxPooling은 위치 불변성을 제공하여 이미지가 약간 이동하거나 변형되어도 같은 객체로 인식할 수 있게 한다. 특성 맵을 시각화하면 각 레이어가 무엇을 학습하는지 직관적으로 이해할 수 있다.
Colab 활용 팁 및 모범 사례
활용 팁
1. GPU 메모리 관리
- 큰 모델을 학습할 때는 배치 크기를 줄여 메모리 사용량을 조절하자.
- 사용하지 않는 변수는
del로 삭제하고torch.cuda.empty_cache()로 캐시를 비운다. - 혼합 정밀도 학습(Mixed Precision)을 사용하면 메모리를 절약하고 속도를 향상시킬 수 있다.
2. Google Drive 연동
- 대용량 데이터셋은 Google Drive에 저장하고 마운트하여 사용한다.
- 학습된 모델을 Drive에 저장하여 세션 종료 후에도 보존할 수 있다.
3. 런타임 관리
- 무료 사용자는 12시간 제한이 있으므로, 긴 학습은 체크포인트를 저장하며 진행하자.
- 브라우저를 닫아도 학습이 계속되지만, 일정 시간 비활성 상태면 연결이 끊어질 수 있다.
- 실행 완료 알림을 받으려면 Colab 설정에서 알림을 활성화하자.
4. 패키지 설치
- 필요한 라이브러리는
!pip install명령으로 설치한다. - 설치는 런타임마다 다시 해야 하므로, 첫 셀에 설치 명령을 모아두는 것이 좋다.
- 특정 버전이 필요하면
!pip install tensorflow==2.10.0형식으로 지정한다.
5. 데이터 업로드
- 작은 파일은 Colab UI로 직접 업로드할 수 있다.
- 큰 파일은 Google Drive, Kaggle API, 또는 wget으로 다운로드하자.
- GitHub 저장소의 데이터는
!git clone으로 복제할 수 있다.
매직 커맨드로 효율 극대화
매직 커맨드(Magic Commands)는 Jupyter/Colab에서 제공하는 특수 명령어로, % (라인 매직) 또는 %% (셀 매직)로 시작한다. 코드 실행 시간 측정, 셸 명령 실행 등 다양한 유틸리티 기능을 제공한다.
실행 시간 측정
%%time
# 셀 전체 실행 시간 측정 (1회)
import numpy as np
large_array = np.random.rand(1000, 1000)
result = np.dot(large_array, large_array)
# CPU times: user 150 ms, sys: 20 ms, total: 170 ms
# Wall time: 85 ms

%%timeit
# 셀 전체를 여러 번 실행하여 평균 측정 (벤치마크)
import numpy as np
arr = np.random.rand(100, 100)
np.dot(arr, arr)
# 100 loops, best of 5: 2.5 ms per loop

셸 명령 실행
%%bash
# 여러 줄의 bash 명령을 순차적으로 실행
echo "현재 디렉토리: $(pwd)"
echo "Python 버전:"
python --version
nvidia-smi # GPU 정보 확인

작업 디렉토리 관리
%cd sample_data # 디렉토리 이동
%pwd # 현재 디렉토리 출력
%ls -la # 파일 목록 상세 표시
ex) 동일 라인에 주석은 오류가 발생한다.


변수 저장 및 로드
# 변수를 파일로 저장 (pickle 사용)
large_data = [1, 2, 3, 4, 5]
%store large_data
# 저장된 변수 불러오기
%store -r large_data
디버깅 및 트러블슈팅
효과적인 디버깅은 AI 개발의 핵심이다. Colab에서 사용할 수 있는 디버깅 도구와 일반적인 오류 해결 방법을 알아보자.
Python 디버거 활용
import ipdb
def complex_function(data):
# 디버깅을 시작할 위치에 set_trace() 삽입
ipdb.set_trace()
# 여기서 실행이 멈추고 대화형 디버깅 시작
processed = [x * 2 for x in data]
result = sum(processed) / len(processed)
return result
# 함수 호출 시 디버거가 활성화됨
# 대화형 프롬프트에서 변수 검사, 단계별 실행 가능
complex_function([1, 2, 3, 4, 5])
# ipdb 명령어:
# n (next): 다음 줄로 이동
# c (continue): 계속 실행
# p 변수명: 변수 값 출력
# l (list): 현재 코드 위치 표시
# q (quit): 디버거 종료
ex) 다음과 같은 오류가 발생할떈 해당 라이브러리 설치하면 된다.

- 라이브러리 설치 (!pip install ipdb)

- 이후 다시 실행히 정상적으로 디버거 동작 확인

# Python 3.7+ 내장 디버거 사용
def example_function(a, b):
print(f"Initial values: a={a}, b={b}")
breakpoint() # 여기서 실행이 멈춥니다
c = a + b
print(f"Sum: {c}")
return c
# 함수 호출
result = example_function(10, 20)
print(f"Function returned: {result}")
# 디버거 명령어:
# n (next): 다음 줄로 이동
# s (step): 함수 내부로 진입
# c (continue): 디버거 종료 후 계속 실행
# p 변수명: 변수 값 출력 (예: p a)
# l (list): 현재 코드 위치 표시
# q (quit): 디버거 강제 종료

일반적인 오류 및 해결책
RuntimeError: CUDA out of memory원인: GPU 메모리보다 큰 모델 또는 배치 크기 사용
해결책:
# 배치 크기 줄이기
batch_size = 32 # 64 → 32로 감소
# GPU 캐시 비우기
import torch
torch.cuda.empty_cache()
# 사용하지 않는 변수 삭제
del large_tensor, unused_model
import gc
gc.collect()
# 혼합 정밀도 학습 (FP16) 사용
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
with autocast():
output = model(input)
loss = criterion(output, target)
원인: 12시간 제한 또는 장시간 비활성
해결책:
# 주기적으로 체크포인트 저장
import os
checkpoint_dir = '/content/drive/MyDrive/checkpoints'
os.makedirs(checkpoint_dir, exist_ok=True)
for epoch in range(num_epochs):
# 학습 코드...
# 매 5 에포크마다 저장
if (epoch + 1) % 5 == 0:
checkpoint_path = f'{checkpoint_dir}/model_epoch_{epoch+1}.pth'
torch.save(model.state_dict(), checkpoint_path)
print(f"체크포인트 저장: {checkpoint_path}")
# 재개 시 마지막 체크포인트 로드
latest_checkpoint = f'{checkpoint_dir}/model_epoch_10.pth'
if os.path.exists(latest_checkpoint):
model.load_state_dict(torch.load(latest_checkpoint))
print("체크포인트에서 복원 완료")
ImportError 또는 ModuleNotFoundError원인: 라이브러리 버전 불일치
해결책:
# 특정 버전 설치
!pip install tensorflow==2.12.0
# 현재 설치된 패키지 버전 확인
!pip show tensorflow
# 패키지 업그레이드
!pip install --upgrade tensorflow
# requirements.txt로 일괄 설치
!pip install -r requirements.txt
GitHub 연동으로 버전 관리하기
Colab 노트북과 코드를 GitHub 저장소와 연동하여 버전 관리를 할 수 있다. 팀 협업과 코드 이력 추적에 유용하다.
# 1. GitHub 저장소 클론
!git clone https://github.com/username/repo-name.git
%cd repo-name # 저장소 디렉토리로 이동
# 2. Git 사용자 정보 설정 (최초 1회)
!git config --global user.email "your-email@example.com"
!git config --global user.name "Your Name"
# 3. 변경 사항 확인
!git status
# 4. 변경된 파일 스테이징
!git add notebook.ipynb # 특정 파일
!git add . # 모든 변경 사항
# 5. 커밋
!git commit -m "Add training experiments"
# 6. GitHub에 푸시
# 첫 푸시 시 인증 토큰 필요 (GitHub Personal Access Token)
!git push origin main
# GitHub Personal Access Token 생성:
# GitHub → Settings → Developer settings → Personal access tokens → Generate new token
# Colab UI 사용:
# File > Save a copy in GitHub
# → GitHub 저장소 선택
# → 커밋 메시지 입력
# → OK
# Python 코드로 GitHub 파일 다운로드
!wget https://raw.githubusercontent.com/username/repo/main/data.csv
# Google Drive에 GitHub 저장소 동기화
from google.colab import drive
drive.mount('/content/drive')
%cd /content/drive/MyDrive/
!git clone https://github.com/username/repo-name.git
# 이후 Drive에서 직접 Git 작업 가능
개발자에게 유용한 팁
Kaggle 데이터셋 직접 다운로드
Kaggle API를 사용하여 데이터셋을 Colab으로 직접 다운로드할 수 있다.
# Kaggle API 설치
!pip install -q kaggle
# Kaggle API 키 업로드 (kaggle.json 파일 필요)
from google.colab import files
uploaded = files.upload() # kaggle.json 선택
# API 키 설정
!mkdir -p ~/.kaggle
!mv kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json
# 데이터셋 다운로드 예시
!kaggle datasets download -d username/dataset-name
!unzip dataset-name.zip
isort로 import 자동 정리
isort 라이브러리를 사용하면 import문을 자동으로 정렬하고 그룹화할 수 있다.
# isort 설치
!pip install -q isort
# Python 파일의 import 자동 정렬
!isort your_script.py
# Colab 노트북 전체 셀에 적용 (매직 커맨드)
%load_ext isort
%isort # 현재 셀의 import문 정리
Session 관리 및 리소스 모니터링
Runtime 메뉴를 통해 현재 세션의 리소스 사용량을 실시간으로 모니터링할 수 있다.
- Runtime → Manage sessions: 모든 활성 세션 확인 및 종료
- RAM/Disk 게이지: 화면 우측 상단에서 실시간 사용량 확인
- 세션 연결 유지: 브라우저 콘솔에서 자동 클릭 스크립트 실행 가능 (주의: 악용 금지)
# Python으로 리소스 사용량 확인
import psutil
# RAM 사용량
ram = psutil.virtual_memory()
print(f"RAM 사용률: {ram.percent}%")
print(f"사용 가능: {ram.available / 1024**3:.2f} GB")
# Disk 사용량
disk = psutil.disk_usage('/')
print(f"\nDisk 사용률: {disk.percent}%")
print(f"남은 공간: {disk.free / 1024**3:.2f} GB")
VM 파일시스템 활용 (Drive 대비 속도가 훨씬 빠름)
Google Drive 파일은 네트워크를 통해 접근하므로 느리다. 대용량 데이터는 VM 로컬 파일시스템으로 복사하면 속도가 크게 향상된다.
# Google Drive 데이터를 VM 로컬로 복사 (빠른 접근)
from google.colab import drive
drive.mount('/content/drive')
# Drive에서 VM으로 복사 (한 번만 실행)
!cp -r /content/drive/MyDrive/large_dataset /content/local_dataset
# 이후 /content/local_dataset 사용 (10배 이상 빠름)
# 주의: 런타임 종료 시 삭제되므로 결과는 다시 Drive로 저장 필요
from google.colab import drive
# Google Drive를 /content/drive에 마운트
# 실행 시 권한 승인 팝업이 나타남
drive.mount('/content/drive')
# Drive에 저장된 파일 접근 예시
# /content/drive/MyDrive/path/to/file.csv
# 모델 저장 예시
import torch
model_path = '/content/drive/MyDrive/models/my_model.pth'
torch.save(model.state_dict(), model_path)
print(f"모델 저장 완료: {model_path}")
import torch
import gc # Python 가비지 컬렉터
# 현재 GPU 메모리 사용량 확인
def print_gpu_memory():
if torch.cuda.is_available():
allocated = torch.cuda.memory_allocated() / 1024**3
reserved = torch.cuda.memory_reserved() / 1024**3
print(f"할당된 메모리: {allocated:.2f} GB")
print(f"예약된 메모리: {reserved:.2f} GB")
# GPU 메모리 정리
def clear_gpu_memory():
# Python 가비지 컬렉션 실행
gc.collect()
# PyTorch GPU 캐시 비우기
if torch.cuda.is_available():
torch.cuda.empty_cache()
print("GPU 메모리 정리 완료")
# 사용 예시
print("정리 전:")
print_gpu_memory()
# 불필요한 텐서 삭제
# del large_tensor
clear_gpu_memory()
print("\n정리 후:")
print_gpu_memory()
자주 묻는 질문
model.save(), PyTorch는 torch.save()를 사용한다. 로컬로 다운로드하려면 files.download() 함수를 사용할 수 있다.!를 붙이면 셸 명령을 실행할 수 있다. 예: !ls, !pwd, !nvidia-smi 등. 여러 줄 명령은 %%bash 매직 명령을 사용한다.참고 자료
- Google Colab 공식 사이트 - Colab 시작하기 및 공식 튜토리얼
- Colab 가격 정책 - Pro 및 Pro+ 플랜 상세 정보
- TensorFlow 공식 튜토리얼 - TensorFlow/Keras 학습 자료
- PyTorch 공식 튜토리얼 - PyTorch 학습 자료
- Scikit-learn 공식 문서 - 머신러닝 알고리즘 레퍼런스
- NumPy 공식 문서 - 수치 연산 라이브러리 가이드
- Pandas 공식 문서 - 데이터 분석 라이브러리 문서
'AI > DevTools for AI' 카테고리의 다른 글
| [Python] 파이썬 로컬 개발 환경 설정 : AI 관련 코딩 실습 준비 (1) | 2025.11.30 |
|---|
소중한 공감 감사합니다