Ch4. 머신러닝 기초 — 자연어처리(NLP) 기초
자연어처리(NLP)란 무엇인가
자연어처리(Natural Language Processing, NLP)는 컴퓨터가 인간의 언어(자연어)를 이해하고 생성하도록 하는 AI 분야입니다. 텍스트와 음성을 다루며, 머신러닝과 언어학이 결합된 영역입니다.
NLP가 다루는 주요 과제:
텍스트 분류 → 스팸 필터, 감성분석, 뉴스 카테고리 분류
정보 추출 → 개체명 인식(NER), 관계 추출
기계 번역 → 영→한, 한→영 번역
질문 응답(QA) → 검색 엔진, 챗봇
텍스트 요약 → 뉴스 요약, 문서 압축
언어 생성 → GPT 계열의 텍스트 생성
NLP가 어려운 이유는 언어 자체의 모호성, 문맥 의존성, 문화적 뉘앙스 등 수많은 복잡성을 내포하기 때문입니다. “배가 아프다”의 ‘배’가 선박인지 복부인지 문맥 없이는 판단하기 어렵습니다.
텍스트 전처리 파이프라인
원시 텍스트 데이터는 바로 모델에 넣을 수 없습니다. 컴퓨터가 처리할 수 있는 형태로 변환하는 전처리 단계가 필수입니다.
1단계: 토크나이징 (Tokenizing)
텍스트를 **토큰(Token)**이라는 단위로 분리하는 과정입니다.
문장: "머신러닝은 정말 재미있습니다!"
단어 토크나이징:
["머신러닝은", "정말", "재미있습니다!"]
문자 토크나이징:
["머", "신", "러", "닝", "은", " ", "정", ...]
서브워드 토크나이징 (BPE 등):
["머신러닝", "##은", "정말", "재미", "##있습니다", "!"]
← 현대 LLM이 주로 사용하는 방식
2단계: 정규화 (Normalization)
import re
text = "Hello!!! This is a TEST... visit http://example.com"
# 소문자 변환
text = text.lower()
# "hello!!! this is a test... visit http://example.com"
# URL 제거
text = re.sub(r'http\S+', '', text)
# "hello!!! this is a test... visit "
# 특수문자 제거
text = re.sub(r'[^a-z\s]', '', text)
# "hello this is a test visit "
# 다중 공백 정리
text = re.sub(r'\s+', ' ', text).strip()
# "hello this is a test visit"
3단계: 불용어 제거 (Stop Words)
불용어(Stop Words): "이", "그", "저", "을", "를", "은", "는", "the", "a", "an", "is" 등
→ 문장에서 자주 등장하지만 의미 정보가 거의 없는 단어
원문: "나는 오늘 학교에 가서 공부를 했습니다"
불용어 제거 후: ["오늘", "학교", "공부"] ← 핵심 의미어만 남김
주의: 무조건 제거가 좋은 것은 아님
→ 감성분석에서 "not good" → "good"으로 의미 반전될 수 있음
4단계: 어간 추출 · 표제어 추출
어간 추출 (Stemming): 규칙 기반, 단어의 원형 근사치
running → run
studies → studi (오류 가능)
표제어 추출 (Lemmatization): 사전 기반, 정확한 기본형
running → run
studies → study
better → good (비교급→원형)
→ Lemmatization이 더 정확하지만 느림
→ 속도가 중요하면 Stemming
Bag of Words (BoW)
텍스트를 단어의 출현 빈도로 표현하는 가장 단순한 방법입니다. 단어의 순서는 무시하고 오직 “어떤 단어가 몇 번 나왔는가”만 봅니다.
문서 1: "고양이가 매트 위에 앉았다"
문서 2: "강아지가 매트 위에 누웠다"
문서 3: "고양이가 강아지를 좋아한다"
어휘집(Vocabulary): {고양이, 매트, 위에, 앉았다, 강아지, 누웠다, 좋아한다}
BoW 벡터:
고양이 매트 위에 앉았다 강아지 누웠다 좋아한다
문서1: 1 1 1 1 0 0 0
문서2: 0 1 1 0 1 1 0
문서3: 1 0 0 0 1 0 1
BoW의 한계:
- 단어 순서 정보 손실 (“나는 너를 좋아한다” vs “너는 나를 좋아한다” → 동일)
- 의미적 유사성 반영 불가 (“자동차”와 “차량”을 완전히 다른 단어로 처리)
- 희소 벡터(Sparse Vector): 어휘집이 크면 대부분이 0
TF-IDF
BoW의 단순 빈도 계산을 개선한 방법으로, 문서 내 빈도는 높고 다른 문서에선 희귀한 단어에 높은 가중치를 부여합니다.
TF (Term Frequency, 단어 빈도):
TF(t, d) = 문서 d에서 단어 t의 출현 횟수 / 문서 d의 전체 단어 수
IDF (Inverse Document Frequency, 역문서빈도):
IDF(t) = log(전체 문서 수 / 단어 t가 등장한 문서 수)
TF-IDF(t, d) = TF(t, d) × IDF(t)
직관적 예시:
"오늘", "그리고", "입니다" → 모든 문서에 자주 등장
→ IDF 낮음 → TF-IDF 낮음 (공통 단어, 중요도 낮음)
"퀀텀컴퓨팅", "역전파", "트랜스포머" → 특정 문서에만 등장
→ IDF 높음 → TF-IDF 높음 (핵심 키워드, 중요도 높음)
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
"머신러닝 알고리즘 학습 방법",
"딥러닝 신경망 학습 방법",
"자연어처리 텍스트 분석 방법"
]
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(corpus)
# 어휘집 확인
print(vectorizer.get_feature_names_out())
# tfidf_matrix.shape = (3, n_features)
워드 임베딩 (Word Embedding)
BoW·TF-IDF의 희소 벡터 문제를 해결하기 위해 단어를 밀집된 저차원 실수 벡터로 표현하는 방법입니다. 의미적으로 유사한 단어는 벡터 공간에서 가깝게 위치합니다.
Word2Vec 직관
2013년 Google이 발표한 Word2Vec은 **“비슷한 문맥에서 등장하는 단어는 유사한 의미를 가진다”**는 분포 가설에 기반합니다.
학습 방식 1 — CBOW (Continuous Bag of Words):
주변 단어(Context)로 중심 단어 예측
[나는, 학교에, 간다] → [오늘]
학습 방식 2 — Skip-gram:
중심 단어로 주변 단어 예측
[오늘] → [나는, 학교에, 간다]
Word2Vec이 학습한 흥미로운 특성:
벡터 연산:
vec("왕") - vec("남자") + vec("여자") ≈ vec("여왕")
vec("파리") - vec("프랑스") + vec("한국") ≈ vec("서울")
단어 간 의미 거리:
cos_similarity(vec("고양이"), vec("강아지")) > cos_similarity(vec("고양이"), vec("자동차"))
사전 훈련 임베딩 활용
import gensim.downloader as api
# 사전 훈련된 Word2Vec 로드 (수십억 단어로 훈련)
wv = api.load("word2vec-google-news-300")
# 유사 단어 조회
wv.most_similar("king")
# [('queen', 0.71), ('monarch', 0.64), ('prince', 0.62), ...]
# 단어 벡터 (300차원)
vec = wv["machine"] # shape: (300,)
감성분석 실전 (Sentiment Analysis)
감성분석은 텍스트에서 긍정/부정/중립 감성을 자동으로 분류하는 NLP의 대표 응용입니다.
규칙 기반 vs 머신러닝 접근
규칙 기반:
→ 감성 사전(lexicon) 사용: "좋다"(+1), "나쁘다"(-1), "매우"(×2)
→ 합산 점수로 감성 결정
→ 구현 간단, 새 도메인에 적용 어려움
머신러닝 기반:
→ 레이블된 리뷰 데이터로 분류기 훈련
→ TF-IDF + 로지스틱회귀, 랜덤포레스트
→ 딥러닝: LSTM, BERT 기반 파인튜닝
TF-IDF + 로지스틱회귀 감성분석 파이프라인
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
# 예시 데이터
texts = [
"정말 맛있고 서비스도 훌륭합니다",
"음식이 너무 짜고 가격도 비쌉니다",
"분위기가 좋고 직원이 친절해요",
"기다리는 시간이 너무 길었습니다"
]
labels = [1, 0, 1, 0] # 1=긍정, 0=부정
X_train, X_test, y_train, y_test = train_test_split(
texts, labels, test_size=0.2, random_state=42
)
# 파이프라인 구성
pipeline = Pipeline([
('tfidf', TfidfVectorizer(ngram_range=(1, 2))), # 1~2 연속 단어
('clf', LogisticRegression(max_iter=1000))
])
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
print(classification_report(y_test, y_pred))
텍스트 분류 파이프라인
1. 데이터 수집 · 레이블링
→ 크롤링, 크라우드소싱, 자체 구축
2. 전처리
→ 정규화 → 토크나이징 → 불용어 제거 → 스테밍/표제어
3. 특성 추출
→ BoW / TF-IDF / Word2Vec / BERT 임베딩
4. 모델 학습
→ 나이브 베이즈, 로지스틱회귀, SVM, LSTM, BERT
5. 평가
→ Accuracy, F1, Precision, Recall (불균형 시 F1 중심)
6. 오류 분석
→ 잘못 분류된 샘플 확인 → 특성 추가·전처리 개선
N-그램 (N-gram)
N-그램: N개의 연속된 단어 단위
→ 단어 순서 일부를 반영하여 BoW 한계 보완
문장: "나는 오늘 학교에 갔다"
1-gram (unigram): ["나는", "오늘", "학교에", "갔다"]
2-gram (bigram): ["나는 오늘", "오늘 학교에", "학교에 갔다"]
3-gram (trigram): ["나는 오늘 학교에", "오늘 학교에 갔다"]
→ N이 클수록 문맥 포착 ↑, 희소성 ↑, 메모리 ↑
→ 실전에서는 1~2그램 혼합이 가장 일반적
한국어 NLP 특이사항
한국어는 **교착어(Agglutinative Language)**로, 조사·어미가 어근에 붙어 의미가 달라지는 특성이 있습니다.
형태소 분석 (Morphological Analysis)
한국어 특성:
"사랑하다" = "사랑" + "하" + "다"
"학교에서" = "학교" + "에서"
"먹었습니다" = "먹" + "었" + "습니다"
→ 단순 공백 기준 분리는 한국어에 부적합
→ 형태소 분석기 필수
# KoNLPy 형태소 분석 예시
from konlpy.tag import Okt
okt = Okt()
text = "나는 오늘 학교에 가서 머신러닝을 공부했습니다"
# 형태소 분석
morphs = okt.morphs(text)
# ['나', '는', '오늘', '학교', '에', '가서', '머신러닝', '을', '공부', '했습니다']
# 품사 태깅
pos = okt.pos(text)
# [('나', 'Noun'), ('는', 'Josa'), ('오늘', 'Noun'), ...]
# 명사만 추출
nouns = okt.nouns(text)
# ['학교', '머신러닝', '공부']
주요 한국어 NLP 도구
| 도구 | 특징 |
|---|---|
| KoNLPy (Okt, Kkma, Komoran) | Python용 형태소 분석 패키지 |
| Kiwi | 빠르고 정확한 오픈소스 분석기 |
| KLUE-BERT | 한국어 특화 사전훈련 모델 |
| KoGPT2 | 한국어 텍스트 생성 모델 |
핵심 개념 카드
토크나이징의 중요성 ★★★★★ : NLP의 첫 단계. 텍스트를 토큰 단위로 분리. 한국어는 형태소 분석기 필수. 현대 LLM은 서브워드 토크나이징(BPE) 사용. 암기 포인트: 한국어 단순 공백 분리 → 틀린 결과 위험
TF-IDF 직관 ★★★★★ : 문서 내 많이 나오면서(TF↑) 다른 문서엔 드문 단어(IDF↑)일수록 중요. TF-IDF = TF × IDF. 암기 포인트: 모든 문서에 나오는 단어(IDF≈0) = 중요도 낮음
Word2Vec 분포 가설 ★★★★★ : “비슷한 문맥에서 등장하는 단어는 유사한 의미.” vec(“왕”) - vec(“남자”) + vec(“여자”) ≈ vec(“여왕”). 암기 포인트: 의미 유사 = 벡터 공간에서 가까움
BoW 한계 ★★★☆☆ : 단어 순서 무시, 의미 유사성 반영 불가, 희소 벡터. N-gram으로 순서 일부 보완, 임베딩으로 의미 보완. 암기 포인트: “나는 너를 좋아한다”와 “너는 나를 좋아한다”가 BoW에선 동일
한국어 교착어 특성 ★★★☆☆ : 조사·어미가 어근에 결합. 형태소 분석 없이는 올바른 토크나이징 불가. KoNLPy/Kiwi 같은 형태소 분석기 필수. 암기 포인트: “학교에서” → [“학교”, “에서”] 분리 후 처리
실전 퀴즈
Q1. TF-IDF에서 “그리고”, “입니다” 같은 단어가 낮은 점수를 받는 이유는?
이 단어들은 거의 모든 문서에 등장하여 IDF(역문서빈도)가 매우 낮습니다. IDF = log(전체 문서 수 / 등장 문서 수)이므로, 분모(등장 문서 수)가 전체 문서 수에 가까워지면 log(1) ≈ 0이 됩니다. TF-IDF = TF × IDF이므로 결과값이 0에 가까워져 중요도가 낮게 평가됩니다.
Q2. Word2Vec이 단어를 벡터로 변환하는 핵심 아이디어는?
분포 가설(Distributional Hypothesis): “비슷한 문맥(주변 단어)에서 등장하는 단어는 유사한 의미를 가진다”는 원칙 아래, CBOW(주변 단어로 중심 단어 예측) 또는 Skip-gram(중심 단어로 주변 단어 예측) 방식으로 학습합니다. 이를 통해 의미적으로 유사한 단어들이 벡터 공간에서 가까운 위치를 갖게 됩니다.
Q3. 감성분석에서 불용어를 무조건 제거하면 안 되는 경우의 예를 들어라.
“not good”(좋지 않다)에서 “not”을 불용어로 제거하면 “good”(좋다)만 남아 긍정으로 오분류될 수 있습니다. 부정어(“not”, “never”, “안”, “못”)는 감성을 뒤집는 결정적 단어이므로 제거하면 의미가 반전됩니다. 감성분석에서는 불용어 목록을 도메인에 맞게 커스터마이징해야 합니다.
Q4. 한국어 NLP에서 형태소 분석이 반드시 필요한 이유는?
한국어는 교착어로 “먹었습니다”처럼 어근(“먹”)에 시제·존칭 어미가 붙어 하나의 어절이 됩니다. 공백 기준으로 분리하면 “먹었습니다”, “먹었어요”, “먹었다” 등이 모두 다른 단어로 취급됩니다. 형태소 분석으로 어근을 분리해야 “먹다”라는 공통 개념으로 통합하여 모델이 의미를 올바르게 학습할 수 있습니다.
Q5. 텍스트 분류 파이프라인에서 오류 분석이 중요한 이유는?
수치 지표(Accuracy, F1)만으로는 모델이 어떤 유형의 샘플에서 왜 실패하는지 알 수 없습니다. 오류 분석을 통해 혼동 행렬(Confusion Matrix)을 확인하고, 잘못 분류된 샘플의 패턴을 발견하면 새로운 특성 추가, 전처리 개선, 클래스 불균형 처리 등 구체적인 개선 방향을 도출할 수 있습니다. “어디서 틀리는가”를 아는 것이 개선의 출발점입니다.
OIYO 편집부
Content Editor지식 인큐베이터이자 전문 콘텐츠 크리에이터. 경영, 경제, 법률 및 실생활에 유용한 실무/자격증 중심의 깊이 있는 정보를 연구하고 공유합니다.