1. 연구 목표 수립
번역 앱(파파고, 구글번역, 카카오번역 등)에서 사용되는 입력 언어 감지 모델을 직접 구축해보는 것이 목표임. 단, 한국어·중국어·일본어처럼 UTF-8 코드 범위로 단순히 구분 가능한 언어가 아니라 알파벳을 공유하는 언어들을 다룸.
1.1 문제 정의
- 대상 언어 — 영어(en), 프랑스어(fr), 인도네시아어(id), 타갈로그어(tl)
- 제한 조건 — a~z 알파벳을 공통으로 사용하는 언어로 한정
- 핵심 가설 — "알파벳을 공유하더라도 문자 사용 빈도는 언어별로 상이하다"
- 사람의 인지로는 미세한 차이를 구분하기 어려움 → ML로 해결
1.2 학습 설계
- 태스크 유형 — 머신러닝 / 지도학습 / 다중 분류(정답 4개)
- 데이터 형태 — (n, 26 + 1) 구조
- 피처(독립변수) — a 빈도, b 빈도, …, z 빈도 (26개)
- 타겟(종속변수) — en / fr / id / tl 중 하나
- 데이터 소스 — 공통 내용을 각국 언어로 번역한 문서(성경, 소설, 매뉴얼 등)
- 가정 — 절차 학습이 목적이므로 소량 데이터 사용. 과적합은 감수하고 신뢰성보다 절차에 집중
모델 자체의 정확도가 아니라 ML 파이프라인 전 과정을 손으로 한 번 끝까지 돌려보는 것이 본 실습의 핵심임.
2. 데이터 수집
데이터 수집 난이도는 통상 4단계로 구분됨.
| 레벨 | 설명 | 대표 사례 |
| Level 1 | 데이터가 이미 준비되어 제공됨 | 본 실습 |
| Level 2 | 공개 데이터셋 다운로드 | Kaggle, UCI |
| Level 3 | API/크롤링으로 직접 수집 | Twitter API, Selenium |
| Level 4 | 센서·로그 등 raw 데이터부터 ETL 구축 | 산업 현장 데이터 |
본 실습은 텍스트 파일이 사전에 제공된 Level 1에 해당함.
import glob
def get_train_data(dir='train'):
return glob.glob(f'/content/drive/MyDrive/6. ML_DL/{dir}/*.txt')
train_files = get_train_data() # 훈련용 raw data
test_files = get_train_data('test') # 테스트용 raw data
- glob.glob — 디렉토리 내 패턴에 일치하는 파일 경로 리스트를 반환
- 파일명 규칙 — {언어코드}-{번호}.txt 형태 (예: en-01.txt, fr-03.txt)
3. 데이터 준비 / 전처리
원시 텍스트(말뭉치)를 (n, 27) 수치 데이터로 변환하는 단계임. ETL 중 Extract·Transform에 해당함.
3.1 전처리 절차
말뭉치 1개 → 노이즈 제거(알파벳만 남김) → 소문자 변환 → 빈도 계산 → 정규화(softmax 개념 활용) → 26차원 벡터로 변환.
왜 정규화가 필요한가? 문서마다 글자 수가 다르므로 절대 빈도를 그대로 사용하면 문서 길이에 비례한 편향이 생김. 전체 빈도로 나눠 **상대 빈도(비율)**로 만들어야 길이 무관 비교가 가능함.
3.2 정답 데이터 추출
파일명에서 언어 코드만 추출하는 함수.
- 입력 — /.../en-01.txt
- 출력 — 'en'
def get_label(file_path):
return file_path.split('/')[-1].split('.')[0].split('-')[0]
체이닝 방식의 문자열 분리로 경로 → 파일명 → 확장자 제외 → 언어코드 순으로 좁혀 들어감.
3.3 피처 데이터 추출
import re
from string import ascii_lowercase
def get_feature(file_path):
# 1) 파일 읽고 소문자 변환
with open(file_path) as f:
raw_data = f.read().lower()
# 2) 노이즈 제거 — a~z 외 모든 문자 제거
pattern = re.compile('[^a-z]*')
raw_data = pattern.sub('', raw_data)
# 3) 빈도 계산
freq = [0] * len(ascii_lowercase)
A_ASCII_IDX = ord('a')
for ch in raw_data:
freq[ord(ch) - A_ASCII_IDX] += 1
# 4) 정규화 — 전체 빈도 대비 비율
total = len(raw_data)
return [f / total for f in freq]
핵심 포인트는 다음과 같음.
- 정규식 [^a-z]* — ^는 부정, []는 문자 집합, *는 0회 이상 반복. 즉 "a~z가 아닌 모든 문자를 0회 이상 반복하여" 매칭
- ord(ch) - ord('a') — 문자를 0~25 인덱스로 변환하는 정수 매핑 방식. 별도 dict 없이 O(1)로 인덱싱 가능
- 상대 빈도 변환 — 분산이 큰 절대 빈도(1~1000단위)를 0~1 구간으로 압축하는 효과
3.4 전체 데이터셋 구성
def corpus_preprocessing(dir='train'):
features, labels = [], []
files = get_train_data(dir)
for file in files:
features.append(get_feature(file))
labels.append(get_label(file))
return {"features": features, "labels": labels}
train_features = corpus_preprocessing()
test_features = corpus_preprocessing('test')
피처와 레이블을 dict로 묶어 반환하는 구조. ET(Extract-Transform) 완료 시점이며, 이후 Load(구글드라이브 또는 S3 저장)는 EDA 단계에서 수행됨.
4. 데이터 분석 / EDA
4.1 EDA의 위치
- 통상의 ML 워크플로 — EDA는 피처 엔지니어링 전략 수립을 위해서만 수행되며 생략되는 경우도 많음
- 본 실습의 EDA 목적 — "알파벳 사용 빈도가 언어별로 상이하다"는 가설 검증
4.2 DataFrame 변환
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
df = pd.DataFrame(data=train_features['features'], columns=list(ascii_lowercase))
df['label'] = train_features['labels']
- columns=list(ascii_lowercase) — ['a', 'b', ..., 'z'] 26개 컬럼명 자동 생성
- label 컬럼 추가 — 마지막에 정답 컬럼을 붙여 (n, 27) 완성
4.3 Load — 영속화
df.to_csv('lang_freq.csv', index=False)
df.to_parquet('lang_freq.parquet')
CSV와 Parquet 두 포맷으로 저장. 실서비스에서는 Parquet이 압축률·읽기 속도 면에서 유리함.
4.4 언어별 평균 빈도 비교
df_pv = df.pivot_table(index='label', aggfunc='mean')
언어 코드를 인덱스로 두고 각 알파벳 빈도의 평균을 집계함. 결과 테이블에서 모음(a/e/i/o/u)에서 서구권과 동남아권의 차이가 두드러짐이 확인됨.
4.5 시각화로 가설 검증
plt.style.use('ggplot')
df_pv.T.plot(kind='bar', subplots=True, figsize=(20, 15), ylim=(0, 0.23))
plt.show()
- subplots=True — 언어별로 개별 차트 생성
- ylim=(0, 0.23) — y축 범위 고정. 여러 차트를 비교할 때 스케일 통일은 필수
df_pv.T.plot(kind='line', figsize=(20, 8))
선형 차트로 추세를 비교한 결과 — 추세는 유사하나 높낮이가 상이함. 이는 ML 모델이 학습할 만한 변별력이 있음을 의미함.
추세가 완전히 무작위였다면 학습 가치가 없지만, 공통 추세 위에 언어별 편차가 존재하므로 분류기가 결정 경계를 그릴 여지가 충분함.
4.6 문자별 히스토그램
for ch in ascii_lowercase:
for code in df_pv.index:
df_code = df.loc[df.label == code][ch]
df_code.plot(kind='hist', alpha=0.5, label=code)
plt.legend()
plt.suptitle(f'{ch} freq histogram')
plt.show()
26개 문자 각각에 대해 4개 언어의 빈도 분포를 겹쳐 그림. alpha=0.5로 반투명 처리하여 겹치는 영역도 식별 가능하게 함.
4.7 EDA 결론
| 항목 | 결과 |
| 가설 검증 | 언어별 빈도 차이 존재 확인 → 분류 가능성 있음 |
| 데이터 한계 | 훈련 20개 / 테스트 8개로 과적합 위험 높음 |
| 신뢰성 | 데이터 부족으로 통계적 신뢰성 낮음 |
본 실습은 모델 성능이 목적이 아니라 ML Flow 전 구간을 손으로 돌려보는 것이 목적이므로 데이터 부족은 의도된 trade-off임.
5. 모델 구축
5.1 전체 절차
1. 베이스라인 구축 — MVP로 알고리즘부터 모델 덤프까지 빠르게 관통
2. 알고리즘 선정 — SageMaker Canvas에서는 AutoML로 자동화
3. 데이터 준비/피처엔지니어링
4. 학습
5. 예측
6. 평가
7. 최적화 (반복)
8. 모델 덤프
9. 모델 서비스
SageMaker Notebook은 모든 단계를 수동 커스텀 구성, Canvas는 2~7번을 AutoML로 자동화한다는 차이가 있음.
5.2 알고리즘 선정 — RandomForestClassifier
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier() # 기본값: 결정트리 100개
- 선정 이유 — 소량 데이터에서도 안정적이고 비선형 결정 경계 학습 가능
- 앙상블 방식 — 100개 결정트리의 다수결 → 단일 트리보다 분산이 낮음
5.3 학습 — Fit
x = train_features['features'] # 2차원 (대문자 X 관습)
y = train_features['labels'] # 1차원 (소문자 y 관습)
model.fit(x, y)
변수 명명 컨벤션 — 피처는 2차원이므로 대문자 X, 레이블은 1차원이므로 소문자 y로 표기하는 관습이 있음. 본 코드는 소문자 x로 작성됨.
5.4 예측 — Predict
x_ = test_features['features']
y_ = test_features['labels']
pred_y = model.predict(x_)
학습 데이터가 아닌 테스트 데이터로 예측해야 일반화 성능을 측정할 수 있음.
5.5 평가 — Metrics
from sklearn.metrics import accuracy_score, classification_report
print(accuracy_score(y_, pred_y)) # 1.0 (100%)
print(classification_report(y_, pred_y))
| 메트릭 | 의미 |
| Accuracy | 전체 중 맞춘 비율 |
| Precision | 예측 양성 중 실제 양성 비율 (오탐 관점) |
| Recall | 실제 양성 중 예측한 비율 (누락 관점) |
| F1-score | Precision과 Recall의 조화평균 |
| Macro avg | 클래스별 메트릭의 단순 평균 |
정확도 100%의 함정 — 데이터가 20개뿐이라 모델이 모든 패턴을 외워버린 과적합 상태일 가능성이 매우 높음. 실서비스에서는 데이터 증강이 필수.
5.6 모델 덤프 — joblib
import joblib
joblib.dump(model, 'model_clf_lang_v1_20260521T095600')
- joblib — scikit-learn 모델 직렬화 표준 도구. pickle보다 numpy 배열 직렬화에 최적화됨
- 파일명 규칙 — model_{태스크}_{도메인}_{버전}_{타임스탬프} 형태로 버전 관리
5.7 모델 로드 및 재예측
model_loaded = joblib.load('/content/model_clf_lang_v1_20260521T095600')
model_loaded.score(x_, y_)
- .score() — 예측과 평가를 한 번에 수행 (분류기 기본값은 accuracy)
6. 시스템 구축 — Gradio 배포
6.1 Gradio 설치
!pip install gradio -q
import gradio
Gradio는 ML 모델을 웹 UI로 즉시 노출할 수 있는 도구임. Flask/FastAPI 없이 함수 1개만 정의하면 됨.
6.2 기본 인터페이스
def 판별모델예측함수(input_text):
return '영어' # 임시 mock
gradio.Interface(판별모델예측함수, inputs='text', outputs='text').launch()
6.3 챗봇 인터페이스 + 실제 예측 연결
def get_feature(input_text):
raw_data = input_text.lower()
pattern = re.compile('[^a-z]*')
raw_data = pattern.sub('', raw_data)
freqs = [0] * len(ascii_lowercase)
A_ASCII_IDX = ord('a')
for ch in raw_data:
freqs[ord(ch) - A_ASCII_IDX] += 1
total = len(raw_data)
return [f / total for f in freqs]
def 판별모델예측함수2(input_text, history):
feature = get_feature(input_text)
# 실제로는 model.predict([feature])[0] 호출
return '영어'
gradio.ChatInterface(판별모델예측함수2, type='messages').launch()
6.4 배포 옵션 비교
| 방식 | 특징 | 비용 |
| EC2 직접 구축 | Gradio 서버를 인스턴스에 상주 | 인스턴스 상시 비용 |
| Lambda 엔드포인트 | 요청 단위 과금, 콜드스타트 존재 | 호출당 과금 |
| SageMaker Endpoint | ML 전용 엔드포인트, 오토스케일링 | 인스턴스 + 추론 비용 |
6.5 SageMaker 응답 예시
{
"predictions": [{
"predicted_label": "fr",
"probability": 0.8606454730033875,
"probabilities": "[0.0477, 0.8606, 0.0458, 0.0458]",
"labels": "['en', 'fr', 'id', 'tl']"
}]
}
- predicted_label — 최종 분류 결과
- probability — 해당 클래스의 확률값
- probabilities — 전체 클래스별 확률 분포 → 추론 신뢰도 판단에 활용
7. 요약
| 단계 | 핵심 산출물 | 사용 도구 |
| 1. 연구목표 | 다중분류 문제 정의 | — |
| 2. 데이터수집 | .txt 파일 집합 | glob |
| 3. 전처리 | (n, 27) 수치 데이터 | re, ord() |
| 4. EDA | 가설 검증 + 시각화 | pandas, matplotlib |
| 5. 모델구축 | 학습된 분류기 | sklearn, joblib |
| 6. 시스템 | 웹 UI / 엔드포인트 | gradio, SageMaker |
ML 프로젝트의 본질은 모델 정확도가 아니라 전 구간을 통합적으로 운영할 수 있는가임. 본 실습처럼 데이터가 적어도 Flow 자체를 끝까지 돌려본 경험은 이후 AutoML, SageMaker Canvas로 확장될 때 그대로 자산이 됨.
'SK플래닛 ai활용 데이터엔지니어 과정 2기 > ML & DL' 카테고리의 다른 글
| DL 3 - CNN (0) | 2026.05.26 |
|---|---|
| DL 2 - 전이학습 (0) | 2026.05.26 |
| DL 1 - 딥러닝 개요 (0) | 2026.05.26 |
| ML 3 - 머신러닝 지도학습 (회귀) (0) | 2026.05.25 |
| ML 1 - 개념 정리 (0) | 2026.05.20 |