In [1]데이터 불러오기
import pandas as pd
# 수집한 CSV에는 열 이름이 없습니다.
data = pd.read_csv("data/hand_data.csv", header=None)
data.shape
Out[1] 출력 보기
(347, 64)
rock_scissors_paper.pkl 0.35MB · 로컬 보관models/rock_scissors_paper.pkl (노트북 폴더 기준)| 항목 | 값 |
|---|---|
| 베이스 모델 | 직접 구현 (처음부터 학습). 특징 추출은 MediaPipe hand_landmarker.task (float16, .../hand_landmarker/float16/1/, 04_3_mediapipe_download.ipynb로 다운로드) |
| 크기 · 변형 | RandomForestClassifier(n_estimators=100, random_state=42). 나머지 하이퍼파라미터는 scikit-learn 기본값. 파일 크기 0.35MB |
| 리비전 | scikit-learn 1.9.0, joblib 1.5.3, mediapipe 1.1.0 (uv.lock 기준) |
| 아키텍처 | 입력 63차원(손 랜드마크 21개 × x, y, z) → 결정 트리 100개의 투표 → 3개 클래스 (0: Rock, 1: Scissors, 2: Paper) |
| 학습 방식 | 처음부터 학습 (지도학습, 사전학습 가중치 없음) |
| 프롬프트 · 에이전트 구성 | 해당 없음 |
| 항목 | 값 |
|---|---|
| 확보 방식 | 직접 수집 |
| 출처 | 웹캠으로 직접 만든 data/hand_data.csv (열 이름 없음, 64열 = 좌표 63 + 라벨 1) |
| 직접 수집 내용 | 04_5_mediapipe_collect_data.py: 웹캠(좌우 반전) 프레임에서 MediaPipe HandLandmarker(num_hands=1, 감지·존재·추적 confidence 0.5)가 찾은 손 좌표 21개(x, y, z)를 모음. 키보드 1/2/3을 누르는 순간의 좌표 한 줄을 Rock / Scissors / Paper 라벨과 함께 CSV에 추가(append). 라벨은 누른 키로 직접 붙임. 수집 기간·수집한 사람 수·촬영 환경 [미확인] (CSV에 시각 정보가 없음) |
| 이용 조건 확인 | 해당 없음 (외부 사이트가 아니라 본인 웹캠으로 수집). 영상 프레임은 저장하지 않고 좌표만 저장 |
| 규모 | 347개 (노트북 data.shape = (347, 64)) |
| 분할 | train 277 / test 70, train_test_split 비율 8:2, 시드 42, shuffle. validation 분할 없음 |
| 레이블·클래스 분포 | Rock 167 (48.1%) / Scissors 120 (34.6%) / Paper 60 (17.3%). test 지원 수는 Rock 35 / Scissors 21 / Paper 14 (노트북 출력은 분포를 직접 보여 주지 않아 CSV를 세어 확인) |
| 전처리 | 열 이름 지정(x1, y1, z1 … x21, y21, z21, label), 라벨 → 숫자 (Rock=0, Scissors=1, Paper=2, 예전 오타 표기 Sissors도 1로 매핑). 좌표 정규화·스케일링 없음 (MediaPipe가 주는 정규화 좌표 그대로) |
| 개인정보·민감정보 처리 | 손 좌표 숫자만 저장하고 영상·이미지는 저장하지 않음. 이 레포에도 원본 CSV는 넣지 않음 |
| 항목 | 값 |
|---|---|
| 하이퍼파라미터 | n_estimators=100, random_state=42, 그 외 기본값 |
| 에폭 / 스텝 | 해당 없음 (트리 100개를 한 번에 fit) |
| 학습 시간 | [미확인] (노트북에 시간 측정이 없음) |
| 하드웨어 | CPU (scikit-learn은 GPU를 쓰지 않음). 장비는 이 컴퓨터의 Intel Core Ultra 9 185H로 보이나 이 노트북에서 직접 확인하지는 않음 [미확인] |
| 프레임워크 | scikit-learn 1.9.0, pandas 3.0.5, joblib 1.5.3 (uv.lock 기준) |
| 시드 · 재현 방법 | train_test_split(test_size=0.2, random_state=42, shuffle=True), 모델 random_state=42. 노트북을 위에서부터 실행 (data/hand_data.csv 필요) |
테스트셋(70개, 전체의 20%)에서 classification_report 결과입니다.
| 클래스 | precision | recall | f1-score | support |
|---|---|---|---|---|
| Rock | 1.00 | 0.97 | 0.99 | 35 |
| Scissors | 0.88 | 1.00 | 0.93 | 21 |
| Paper | 1.00 | 0.86 | 0.92 | 14 |
| accuracy | 0.96 | 70 | ||
| macro avg | 0.96 | 0.94 | 0.95 | 70 |
| weighted avg | 0.96 | 0.96 | 0.96 | 70 |
측정 안 함. 지연 시간·처리량·메모리를 재는 코드가 노트북에 없습니다. infer_ms는 넣지 않았습니다. 노트북은 저장한 모델로 테스트 샘플 1개를 예측해 Rock이 나오는 것만 확인했습니다.
| 장비 | 장비 이름 | 배치 크기 | 지연 시간 (샘플당, p50 / p95) | 처리량 (샘플/초) | 메모리 사용 |
|---|---|---|---|---|---|
| CPU | [미확인] | 1 | 측정 안 함 | 측정 안 함 | 측정 안 함 |
| GPU | 해당 없음 (scikit-learn은 CPU 전용) | - | - | - | - |
class_weight를 쓰지 않았고, test가 70개라 샘플 1~2개로 지표가 크게 움직임num_hands=1)header=None으로 읽고 열 이름을 직접 붙여야 한다to_numpy()로 맞춤joblib.dump / joblib.load로 저장한 모델을 실시간 웹캠 예측 스크립트(04_7_mediapipe_pred.py)에서 불러 쓴다import pandas as pd
# 수집한 CSV에는 열 이름이 없습니다.
data = pd.read_csv("data/hand_data.csv", header=None)
data.shape
(347, 64)
# 바위=0, 가위=1, 보=2 (기존 Sissors 표기도 포함)
label_map = {"Rock": 0, "Scissors": 1, "Sissors": 1, "Paper": 2}
data["label_enc"] = data["label"].map(label_map)
data[["label", "label_enc"]].head()
from sklearn.model_selection import train_test_split
# X는 손 좌표, y는 정답 라벨입니다.
X = data[columns[:-1]].to_numpy()
y = data["label_enc"].to_numpy()
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, shuffle=True
)
print("학습 데이터:", X_train.shape, y_train.shape)
print("평가 데이터:", X_test.shape, y_test.shape)
학습 데이터: (277, 63) (277,) 평가 데이터: (70, 63) (70,)
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
from sklearn.metrics import classification_report
y_pred = model.predict(X_test)
print(classification_report(
y_test, y_pred,
labels=[0, 1, 2],
target_names=["Rock", "Scissors", "Paper"],
zero_division=0
))
precision recall f1-score support
Rock 1.00 0.97 0.99 35
Scissors 0.88 1.00 0.93 21
Paper 1.00 0.86 0.92 14
accuracy 0.96 70
macro avg 0.96 0.94 0.95 70
weighted avg 0.96 0.96 0.96 70import joblib
from pathlib import Path
model_path = "models/rock_scissors_paper.pkl"
Path("models").mkdir(parents=True, exist_ok=True)
joblib.dump(model, model_path)
# 저장한 모델로 평가 데이터 한 개를 예측합니다.
loaded_model = joblib.load(model_path)
prediction = loaded_model.predict(X_test[:1])[0]
label_names = {0: "Rock", 1: "Scissors", 2: "Paper"}
print("예측 결과:", label_names[prediction])
예측 결과: Rock
같은 과제·데이터셋으로 학습한 모델이 생기면 여기에서 비교됩니다.