멀티캠퍼스 데이터분석 6월 1일 수업 내용 — 토큰화 이론, KoNLPy(Okt/Kkma/Komoran), SentencePiece, 통계 기반 NLP, 영화 리뷰 감성 분류 실습
이론
1. 자연어 처리 & 토큰화 개념
2. 토큰화의 종류와 이론적 배경
3. 왜 한국어 NLP는 어려울까?
4. KoNLPy — 한국어 형태소 분석기
5. Okt (Open Korean Text)
6. Kkma (꼬꼬마) & Komoran (코모란)
7. KoNLPy의 한계 — OOV & 서브워드 토큰화
8. SentencePiece
9. 통계 기반 자연어 처리
실습
토큰화 란 컴퓨터가 자연어(인간의 언어)를 이해하고 분석할 수 있도록, 긴 텍스트를 의미가 있는 가장 작은 단위(토큰) 로 나누는 전처리 과정입니다.
1. 데이터 로드
2. 문자 토큰화
3. 토큰화된 데이터 벡터화
4. 모델 학습
5. 평가





💡 KoNLPy는 내부적으로 Java(JDK 17 권장) 를 사용합니다.
설치 전 JDK가 설치되어 있어야 합니다.
# !pip install konlpy

SNS, 댓글 등 일상적인 한글 데이터 형태소 분석에 특화된 분석기입니다.
| 태그 | 품사 | 예시 |
|---|---|---|
Noun | 명사 | 데이터, 분석 |
Verb | 동사 | 하다, 먹다 |
Adjective | 형용사 | 재미있다, 좋다 |
Josa | 조사 | 은, 는, 이, 가 |
Adverb | 부사 | 정말, 매우 |
KoreanParticle | 한국어 파티클/이모티콘 | ㅋㅋㅋ, ㅠㅠ |
Punctuation | 구두점 | . , ? ~ |
Number | 숫자 | |
Alpha | 영문자 | |
Modifier | 관형사 | 새, 헌, 첫 |
Foreign | 외국어/한자 | |
Hashtag | 해시태그 | #태그 |
| 매개변수 | 설명 |
|---|---|
norm=True | 정규화 — 비표준어, 오타, 불필요한 반복을 다듬음 |
stem=True | 어간 추출 — 동사/형용사의 어미를 잘라내고 기본형으로 통일 |


NA 처리| 태그 | 설명 | 예시 |
|---|---|---|
NNG | 일반 명사 | 데이터, 분석 |
NNP | 고유 명사 | 서울, 컴퓨터 |
NNB | 의존 명사 | 것, 수, 만큼 |
NP | 대명사 | 나, 너, 우리 |
VV | 동사 | 배우다, 먹다 |
VA | 형용사 | 좋다, 빠르다 |
MAG | 일반 부사 | 매우, 잘, 빨리 |
SL | 외국어 |
| 목적 | 권장 태그 |
|---|---|
| 감성/의도/리뷰 분석 | NNG, NNP, VV, VA, MAG, SL |
| 명사 기반 분류 | NNG, NNP, NR, NP |
| 의미 있는 단어 최대 포함 | NNG, NNP, VV, VA, MAG, MAJ, IC, SL |



Train() 으로 모델과 vocab을 생성
▁ (U+2581) — 단어의 시작을 표시하는 특수 기호 (키보드 언더바 _ 와 다름)▁ 를 공백으로 치환하여 사용| 매개변수 | 설명 |
|---|---|
input | 학습할 텍스트 파일 경로 (.txt) |
model_prefix | 저장할 모델 이름 |
vocab_size | 단어 사전 크기 (8000 / 16000 / 32000 권장) |
model_type | 토큰 생성 방식 (unigram / bpe / char / word) |
character_coverage | 학습에 포함할 문자 종류 비율 (0.9995 권장) |
shuffle_input_sentence | 문장 순서 셔플 — 특정 순서 편향 방지 |
input_sentence_size | 학습 문장 샘플링 수 |
| 값 | 설명 | 주요 사용 |
|---|---|---|
unigram | 한 단어씩 잘라서 표현 | BERT, KoGPT 등 |
bpe | 빈도 기반 병합 | GPT-2 (한글 부적합) |
char | 문자 단위 분리 | 정보가 너무 잘게 쪼개짐 |
word | 단어 단위 분리 | 한국어 부적합 |







from konlpy.tag import Okt
okt = Okt()
text = '파이썬에서 자바의 환경 변수 설정은 너무 힘이 듭니다. ㅠㅠ 오프라인 수업이 그립습니닼ㅋㅋ'
# 형태소 분리
tokens = okt.morphs(text)
print(tokens)
# 명사만 추출
nouns = okt.nouns(text)
print(nouns)
# 품사 태깅 — (단어, 품사) 튜플 리스트 반환
print(okt.pos(text))
# 조사, 구두점 제외하고 필터링
new_list = []
for t, p in okt.pos(text):
if p not in ['Josa', 'Punctuation']:
new_list.append(t)
# 정규화 + 어간 추출
okt.pos(text, norm=True, stem=True)
from konlpy.tag import Kkma
kkma = Kkma()
text_formal = '인공지능 데이터 분석가 과정에서 오신것을 환영합니다 오늘부터 자연어 처리를 배우고 다음 주에는 딥러닝 자연어 처리를 배울 예정입니다'
# 문장 분리 (구두점 없어도 문맥 파악)
sentence = kkma.sentences(text_formal)
print(sentence)
# 형태소 분리 & 명사 추출 & 품사 태깅
kkma.morphs(text)
print(kkma.nouns(text))
print(kkma.pos(text))
from konlpy.tag import Komoran
komoran = Komoran()
text3 = '요즘 혼자 여행을 다니면서 챗지피티를 이용해서 여행지를 추천받고 혼자 식사를 하기 위해서 배달의 민족을 사용합니다'
komoran.morphs(text3)
komoran.nouns(text3)
komoran.pos(text3)
# 사용자 사전 적용 (user_dic.txt — 신조어\t품사 형식)
komoran2 = Komoran(userdic='user_dic.txt')
komoran2.morphs(text3)
💡
user_dic.txt형식챗지피티\tNNP 배달의민족\tNNP신조어와 품사를 탭(
\t) 으로 구분하여 등록합니다.
# !pip install sentencepiece
import sentencepiece as spm
# 모델 학습
spm.SentencePieceTrainer.Train(
input = 'test.txt',
model_prefix = 'ko_unigram',
vocab_size = 8000,
model_type = 'unigram',
character_coverage = 0.9995,
shuffle_input_sentence = True
)
# 모델 로드
sp = spm.SentencePieceProcessor()
sp.load('ko_unigram.model')
text5 = '스토리 개연성이 부족하지만 배우들의 연기가 뛰어났다'
# 서브워드 분리
print(sp.encode_as_pieces(text5))
# ID로 인코딩 & 디코딩
ids = sp.encode_as_ids(text5)
print(ids)
print(sp.decode_ids(ids))
# 원본 복원
tokens = sp.encode_as_pieces(text5)
print('원본 복원:', sp.decode_pieces(tokens))
# ▁ 기호 처리 (U+2581 — 키보드 언더바와 다름)
char = '\u2581'
restored = ''.join(tokens).replace(char, ' ')[1:]
print(restored)
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, StratifiedKFold, GridSearchCV
from sklearn.metrics import classification_report
from sklearn.pipeline import Pipeline
from sklearn.svm import LinearSVC
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from konlpy.tag import Okt
df = pd.read_csv('../data/ratings_train.txt', sep='\t')
# 공백 문자열을 결측치로 처리
df['document'] = df['document'].str.strip()
df.loc[df['document'] == '', 'document'] = np.nan
df.dropna(inplace=True)
# 중복 리뷰 제거
df.drop_duplicates('document', inplace=True)
# id 컬럼 제거 (학습에 불필요)
df.drop('id', axis=1, inplace=True)
x = df['document']
y = df['label']
X_train, X_test, y_train, y_test = train_test_split(
x, y, test_size=0.2, stratify=y, random_state=42
)
💡 공백 문자열 처리 주의사항
' '(공백)은isna()로 감지되지 않습니다.
str.strip()으로 공백을 제거한 뒤'' == ''조건으로 결측치 처리해야 합니다.
okt = Okt()
allow_pos = ['Noun', 'Verb', 'Adjective', 'KoreanParticle', 'Alpha']
stop_word = ['하다', '되다']
len_word = 2 # 최소 글자 수
def tokenize(input_text):
result = []
for word, pos in okt.pos(input_text, norm=True, stem=True):
if (pos in allow_pos) and (word not in stop_word) and (len(word) >= len_word):
result.append(word)
return result
# CountVectorizer — 단어의 존재 여부(binary=True) 또는 빈도수
vectorizer = CountVectorizer(tokenizer=tokenize, lowercase=False, binary=True)
x_train_vec = vectorizer.fit_transform(X_train)
x_test_vec = vectorizer.transform(X_test)
model = LinearSVC(C=1.0)
model.fit(x_train_vec, y_train)
pred = model.predict(x_test_vec)
print(classification_report(y_test, pred))
pipe = Pipeline([
('vec', CountVectorizer(tokenizer=tokenize, lowercase=False)),
('model', LinearSVC())
])
param_grid = {
'vec__binary' : [True, False],
'model__C' : [0.9, 1.0]
}
cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)
grid = GridSearchCV(
estimator = pipe,
param_grid = param_grid,
cv = cv,
verbose = 1
)
# Train(20%) / Validation(72%) / Test(8%) 3분할
X_train, X_test, y_train, y_test = train_test_split(
x, y, test_size=0.8, stratify=y, random_state=42
)
X_vali, X_test, y_vali, y_test = train_test_split(
X_test, y_test, test_size=0.1, stratify=y_test, random_state=42
)
grid.fit(X_train, y_train)
print('최적의 파라미터:', grid.best_params_)
pred = grid.predict(X_test)
print(classification_report(pred, y_test))
| CountVectorizer | TfidfVectorizer | |
|---|---|---|
| 방식 | 단어 등장 빈도/유무 | 빈도 × 역문서 빈도 |
| 특징 | 단순, 빠름 | 중요 단어에 높은 가중치 |
binary=True | 등장 여부만 0/1 | - |
| 주요 사용 | 단순 분류 | 검색, 문서 유사도 |
| 개념 | 설명 |
|---|---|
| 토큰화 | 텍스트를 의미 있는 최소 단위(토큰)로 분리 |
| 형태소 | 의미를 가지는 가장 작은 언어 단위 |
| OOV | Out-Of-Vocabulary — 사전에 없는 미등록 단어 |
| 서브워드 토큰화 | 단어를 더 작은 단위로 분리 — OOV 문제 해결 |
okt.morphs() | 형태소 분리 |
okt.nouns() | 명사만 추출 |
okt.pos() | 품사 태깅 — (단어, 품사) 튜플 리스트 반환 |
norm=True | 비표준어/오타 정규화 |
stem=True | 동사/형용사 어간 추출 (기본형 통일) |
kkma.sentences() | 구두점 없어도 문장 분리 |
Komoran(userdic='파일') | 사용자 정의 사전으로 신조어 등록 |
spm.SentencePieceTrainer.Train() | SentencePiece 모델 학습 |
sp.encode_as_pieces() | 텍스트 → 서브워드 리스트 |
sp.encode_as_ids() | 텍스트 → ID 리스트 |
sp.decode_pieces() / decode_ids() | 서브워드/ID → 원본 복원 |
▁ (U+2581) | SentencePiece 단어 시작 표시 기호 |
| BoW | Bag of Words — 단어 등장 빈도 기반 벡터화 |
| TF-IDF | 단어 빈도 × 역문서 빈도 — 중요 단어 가중치 부여 |
CountVectorizer | BoW 벡터화 (binary=True → 등장 여부만) |
TfidfVectorizer | TF-IDF 벡터화 |
vectorizer.fit_transform() | Train 데이터 학습 + 변환 |
vectorizer.transform() | Test 데이터 변환 (Train 기준) |
str.strip() | 앞뒤 공백 제거 — 공백 문자열 결측치 처리 전 필수 |
LinearSVC | 선형 SVM 분류 모델 — 텍스트 분류에 자주 사용 |