FastText & L2 정규화 & Vectorizer 클래스 확장 & gensim_test 모듈화 정리

syeom·2026년 6월 14일

멀티캠퍼스 데이터분석 6월 9일 수업 내용 — FastText 이론 및 실습, L2 정규화, 상품 추천 시스템, Vectorizer 클래스 확장(Word2Vec+FastText), gensim_test 모듈화, GridSearchCV 연동


📌 목차

FastText
1. FastText 이론 — OOV 문제 해결
2. FastText vs Word2Vec 실습 비교
3. FastText 상품 추천 시스템

Vectorizer 클래스 확장

  1. Vectorizer 클래스 — Word2Vec & FastText 통합
  2. gensim_test 모듈화 & GridSearchCV 연동 실습

FastText

1. FastText 이론 — OOV 문제 해결

Word2Vec의 한계 — OOV (Out Of Vocabulary)

  • '강아지''강아지들'완전히 다른 단어로 인식
  • 학습 데이터에 없는 신조어나 변형 단어는 벡터화 불가능

FastText의 해결책 — Subword 단위 벡터화

  • Word2Vec과 학습 방식은 동일하지만 기준이 단어 → 글자
  • '강아지''강', '아', '지', '강아', '아지', '강아지' (n-gram 방식)
  • 서브워드(subword) 단위로 벡터화하므로 미등록 단어도 처리 가능

FastText 추가 매개변수

매개변수기본값설명
min_n3최소 subword 길이
max_n6최대 subword 길이
bucket2e+6단어 사전 최대 크기 제한

💡 min_n=0, max_n=0 으로 설정하면 subword 사용 금지 → Word2Vec 방식과 동일
min_n 은 반드시 max_n 보다 작거나 같아야 합니다.


2. FastText vs Word2Vec 실습 비교

기본 비교

from gensim.models import Word2Vec, FastText

sentences = [
    ['이커머스', '데이터', '분석', '진행'],
    ['상품', '리뷰', '감성', '분석', '합니다'],
    ['형태소', '단위', '임베딩', '가능']
]

model_wv = Word2Vec(
    sentences=sentences, window=3, vector_size=50,
    min_count=1, sg=1, epochs=10
)

model_ft = FastText(
    sentences=sentences, window=3, vector_size=50,
    min_count=1, sg=1, epochs=50,
    min_n=2, max_n=4
)

# 단어 벡터 확인
model_wv.wv['이커머스']
model_ft.wv['이커머스']

# 유사 단어 검색
model_wv.wv.most_similar('데이터', topn=3)
model_ft.wv.most_similar('데이터', topn=3)

OOV 처리 비교

# Word2Vec — 사전에 없는 단어 → 에러 발생
# model_wv.wv['감정']  ← KeyError 발생

# FastText — 사전에 없는 단어도 subword로 벡터 생성 가능
model_ft.wv['감정']
model_ft.wv.most_similar('감정', topn=3)

형태소 변형 유사도 비교

sentences2 = [
    ['고양이', '고양이들', '귀엽다', '동물', '반려동물'],
    ['강아지', '강아지들', '귀엽다', '동물', '반려동물'],
    ['달리다', '달리는', '달림', '걷다', '걷는'],
    ['예쁘다', '예쁨', '예쁜', '매력적이다'],
    ['컴퓨터', '컴퓨팅', '컴퓨터들', '기계']
]

w2v = Word2Vec(sentences=sentences2, window=3, min_count=1, sg=1, seed=42)
ft  = FastText(sentences=sentences2, window=3, min_count=1, sg=1, seed=42, min_n=2)

# 형태소 변형 단어 간 유사도 비교
test_text = [
    ['강아지', '강아지들'],
    ['고양이', '고양이들'],
    ['달리다', '달리는'],
    ['예쁘다', '예쁜'],
    ['컴퓨터', '컴퓨팅'],
    ['달리다', '걷다']
]

for word1, word2 in test_text:
    w2v_sim = w2v.wv.similarity(word1, word2)
    ft_sim  = ft.wv.similarity(word1, word2)
    print(f'{word1}{word2} 유사도 → Word2Vec({round(w2v_sim,4)}) / FastText({round(ft_sim,4)})')

💡 FastText는 '강아지''강아지들'을 더 높은 유사도로 계산합니다.
두 단어가 공통 subword('강아지')를 공유하기 때문입니다.


3. FastText 상품 추천 시스템

상품 데이터 & 벡터화

from konlpy.tag import Komoran
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

products = {
    'P001': '무선 이어폰 블루투스 노이즈캔슬링 충전케이스',
    'P002': '유선 이어폰 하이파이 금도금 플러그',
    'P003': '게이밍 마우스 RGB 경량 디자인',
    'P004': '무선 마우스 초경량 블루투스 듀얼모드',
    'P005': '헤드폰 노이즈캔슬링 유선'
}

data = products.values()

komoran = Komoran()
tokens_split    = [name.split() for name in data]
tokens_komoran  = [komoran.morphs(name) for name in data]

# FastText 학습 (공백 기준 토큰화 사용)
ft2 = FastText(
    sentences=tokens_split, window=3, min_count=1,
    sg=1, min_n=2, max_n=4, seed=42
)

L2 정규화 문장 벡터 생성

def sent_vector(token, type=None):
    """
    단어 벡터 평균 계산 (FastText는 미등록 단어도 처리 가능)
    type='l2' 시 L2 정규화 적용
    """
    vectors = [ft2.wv[word] for word in token]   # FastText는 모든 단어 처리 가능

    if not vectors:
        return np.zeros(ft2.vector_size)

    v = np.mean(vectors, axis=0)

    if type == 'l2':
        # L2 정규화 — 벡터의 크기(norm)로 나눠 방향만 남김
        v = v / (np.linalg.norm(v) + 1e-12)

    return v

# 일반 벡터
items_vector    = [sent_vector(token)       for token in tokens_split]
# L2 정규화 벡터
items_vector_l2 = [sent_vector(token, 'l2') for token in tokens_split]

💡 L2 정규화(L2 Normalization)란?
벡터를 자신의 크기(norm) 로 나눠 길이가 1인 단위 벡터로 변환합니다.
코사인 유사도 계산 시 벡터 크기의 영향을 제거하여 방향만으로 유사도를 비교할 수 있습니다.
np.linalg.norm(v) 로 L2 norm 계산, 1e-12 는 분모 0 방지용입니다.

상품 ID 기반 추천 함수

def recommand_by_text(product_id):
    """특정 상품 ID와 유사한 상품 목록 반환"""
    # list.index() — 특정 값의 위치(인덱스) 반환
    idx  = list(products.keys()).index(product_id)
    sims = cosine_similarity([items_vector[idx]], items_vector).ravel()
    order = sims.argsort()[::-1]

    result = []
    for i in order:
        if sims[i] != 1.0:   # 자기 자신 제외 (유사도=1.0)
            result.append([list(products.values())[i], round(sims[i], 4)])
    return result

recommand_by_text('P003')

검색어 기반 추천 함수 (L2 정규화)

def recommand_by_search(text):
    """검색어와 유사한 상품 상위 3개 반환 (L2 정규화 사용)"""
    vector = ft2.wv[text]
    v      = vector / (np.linalg.norm(vector) + 1e-12)   # 검색어도 L2 정규화

    sims  = cosine_similarity([v], items_vector_l2).ravel()
    order = sims.argsort()[::-1]

    result = []
    for i in order[:3]:
        result.append([list(products.values())[i], round(sims[i], 4)])
    return result

print(recommand_by_search('노이즈캔슬링'))

Vectorizer 클래스 확장

4. Vectorizer 클래스 — Word2Vec & FastText 통합

기존 Word2VecVectorizer 에 FastText, L2 정규화, type 선택 기능을 추가합니다.

import numpy as np
from gensim.models import Word2Vec, FastText
from sklearn.base import BaseEstimator, TransformerMixin

class Vectorizer(BaseEstimator, TransformerMixin):

    def __init__(self, tokenizer=None, vector_size=100, min_count=5,
                 window=5, sg=1, epochs=100, workers=1, seed=42,
                 min_n=2, max_n=4, bucket=2e+6,
                 type='w2v',   # 'w2v': Word2Vec / 'ft': FastText
                 l2=False):    # L2 정규화 적용 여부
        self.tokenizer   = tokenizer
        self.vector_size = vector_size
        self.min_count   = min_count
        self.window      = window
        self.sg          = sg
        self.epochs      = epochs
        self.workers     = workers
        self.seed        = seed
        self.min_n       = min_n
        self.max_n       = max_n
        self.bucket      = bucket
        self.type        = type
        self.l2          = l2
        self.model       = None
        self.voca        = None

    def to_token(self, sentences):
        if self.tokenizer is None:
            return [sentence.split() for sentence in sentences]
        return [self.tokenizer(sentence) for sentence in sentences]

    def fit(self, X, y=None):
        sentences = self.to_token(X)

        if self.type == 'w2v':
            self.model = Word2Vec(
                sentences=sentences, vector_size=self.vector_size,
                window=self.window, min_count=self.min_count, sg=self.sg,
                epochs=self.epochs, workers=self.workers, seed=self.seed
            )
        elif self.type == 'ft':
            self.model = FastText(
                sentences=sentences, vector_size=self.vector_size,
                window=self.window, min_count=self.min_count, sg=self.sg,
                epochs=self.epochs, workers=self.workers, seed=self.seed,
                min_n=self.min_n, max_n=self.max_n, bucket=self.bucket
            )

        self.voca = self.model.wv.key_to_index.keys()
        return self

    def doc_vec(self, token):
        vectors = []
        for word in token:
            if self.type == 'w2v':
                # Word2Vec은 사전에 없는 단어 제외
                if word in self.model.wv.index_to_key:
                    vectors.append(self.model.wv[word])
            elif self.type == 'ft':
                # FastText는 미등록 단어도 subword로 처리
                vectors.append(self.model.wv[word])

        if vectors:
            result = np.mean(vectors, axis=0)
            if self.l2:
                result = result / (np.linalg.norm(result) + 1e-12)
        else:
            result = np.zeros(self.model.vector_size)
        return result

    def transform(self, X):
        sentences = self.to_token(X)
        return np.array([self.doc_vec(token) for token in sentences])

기존 Word2VecVectorizer와의 차이점

항목Word2VecVectorizerVectorizer
지원 모델Word2Vec만Word2Vec + FastText
type 매개변수없음'w2v' / 'ft' 선택 가능
L2 정규화없음l2=True/False
FastText 전용 매개변수없음min_n, max_n, bucket
OOV 처리제외FastText 사용 시 처리 가능

5. gensim_test 모듈화 & GridSearchCV 연동 실습

Vectorizer 클래스를 gensim_test.py 로 저장하고 모듈로 불러옵니다.

데이터 로드 & 전처리

from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV, StratifiedKFold
from sklearn.metrics import classification_report
from sklearn.svm import SVC
from konlpy.tag import Okt
import pandas as pd
from gensim_test import Vectorizer

df = pd.read_csv('../data/ratings_test.txt', sep='\t')

# 결측치 제거
df.dropna(inplace=True)

# 좌우 공백 제거
df['document'] = df['document'].str.strip()

# 빈 문자열 제거
df = df.loc[~(df['document'] == '')]

# 중복 document 제거
df.drop_duplicates('document', inplace=True)

💡 공백 처리 순서
1. str.strip() — 좌우 공백 제거
2. == '' 조건으로 빈 문자열 확인 후 제거
공백만 있는 문자열은 isna() 로 감지되지 않으므로 반드시 위 두 단계를 거쳐야 합니다.

Pipeline + GridSearchCV

okt       = Okt()
tokenizer = lambda x: [word for word in okt.morphs(x)]

pipe = Pipeline([
    ('vector', Vectorizer(tokenizer=tokenizer, min_count=1)),
    ('svc',    SVC(random_state=42))
])

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

params = {
    'vector__type'        : ['w2v', 'ft'],
    'vector__vector_size' : [80, 100],
    'vector__l2'          : [False, True],
    'svc__C'              : [0.8, 1.0]
}

grid = GridSearchCV(estimator=pipe, param_grid=params, cv=cv, verbose=1)

# 상위 100개로 학습
X_train = df.head(100)['document'].values
y_train = df.head(100)['label'].values

# 하위 100개로 평가
X_test  = df.tail(100)['document'].values
y_test  = df.tail(100)['label'].values

grid.fit(X_train, y_train)

print('최적 파라미터:', grid.best_params_)
print('최적 Score  :', grid.best_score_)

pred = grid.predict(X_test)
print(classification_report(pred, y_test))

파라미터 조합 요약

단계매개변수조합
vectortype'w2v' / 'ft'
vectorvector_size80 / 100
vectorl2False / True
svcC0.8 / 1.0

총 2 × 2 × 2 × 2 = 16가지 조합, StratifiedKFold 5회 → 80번 학습


📎 핵심 개념 요약

개념설명
FastTextsubword 단위 벡터화 — OOV 문제 해결
OOVOut Of Vocabulary — 사전에 없는 미등록 단어
subwordn-gram 방식으로 단어를 쪼갠 글자 단위
min_n / max_nFastText subword 최소/최대 길이
min_n=0, max_n=0subword 비사용 → Word2Vec 방식과 동일
model_ft.wv['미등록단어']FastText는 미등록 단어도 벡터 생성 가능
L2 정규화벡터를 길이 1로 변환 → 방향만으로 유사도 비교
np.linalg.norm(v)벡터의 L2 norm(크기) 계산
v / (norm + 1e-12)L2 정규화 수식 (분모 0 방지)
list.index(value)리스트에서 특정 값의 위치 반환
sims.argsort()[::-1]유사도 내림차순 정렬 인덱스
type='w2v' / 'ft'Vectorizer에서 모델 선택 매개변수
l2=TrueVectorizer에서 L2 정규화 적용
vector__typePipeline 내 Vectorizer 매개변수 접근
str.strip()좌우 공백 제거
~(df['col'] == '')빈 문자열 제외 필터
StratifiedKFold클래스 비율 유지하며 K겹 교차 검증
profile
공부 기록

0개의 댓글