728x90

BoW(Bag of Words)란?

 

문서에 나오는 모든 단어에서 중복을 포함하여 가방(bag)에 담은 뒤, 각 단어가 얼마나 등장했는지 세는 방법이다.

 

BoW 모델은 단어의 등장 빈도나 순서를 고려하지 않고, 단어들의 출현 여부를 중점으로 본다. 문서를 단어들의 가방(Bag)으로 생각하고, 각 단어의 등장 여부를 표시하기 위해 이진 벡터(0 또는 1)로 표현한다.

BoW 모델을 만들기 위해 다음과 같은 단계를 수행한다.

1. 토큰화: 문서를 단어나 토큰으로 나눈다.
2. 단어 집합 구축: 토큰화된 단어들의 고유한 집합을 만든다. 이를 단어 집합(Vocabulary)이라고 한다.
3. 벡터화: 각 문서를 단어 집합의 크기와 동일한 길이의 벡터로 변환한다. 각 원소는 해당 단어의 등장 여부를 나타낸다. 즉, 해당 단어가 있으면 1, 없으면 0으로 표현된다.

BoW 모델은 문서의 단어 순서나 문법적 구조를 고려하지 않기 때문에, 단어의 출현 여부에만 초점을 둔다. 이러한 특성으로 인해 BoW 모델은 문서 분류, 정보 검색, 문서 유사도 측정 등 다양한 자연어 처리 작업에 사용될 수 있다. 그러나 문맥 정보의 손실과 단어의 중요성 파악의 어려움 등 일부 한계가 있을 수 있다.

 

다음과 같은 문장에서,

"저는 인공지능 분야에 취업할 계획입니다. 인공지능은 재미있습니다. 인공지능 분야에서 특히 자연어 처리에 관심이 있습니다."

# Open-source Korean Text Processor
from konlpy.tag import Okt # Open Korean Text

okt = Okt()

def build_bow(doc):

    # 입력 문장에서 마침표 제거
    doc = doc.replace('.', '')

    # 형태소 분석으로 문장 토큰화
    tokenized_doc = okt.morphs(doc)

    # 단어와 인덱스를 저장할 딕셔너리 생성
    word_to_index = {}

    # Bag of Words 벡터 생성을 위한 리스트 생성
    bow = []

    # 마침표 제거되고 토큰화된 단어에 대한 반복 작업
    for word in tokenized_doc:
        if word not in word_to_index.keys():
            # 새로운 단어를 딕셔너리에 추가하고 인덱스를 할당
            word_to_index[word] = len(word_to_index)

            # 새로운 단어의 출현 횟수인 기본값 1을 bow 리스트 마지막 위치에 추가
            bow.insert(len(word_to_index) -1, 1)
        else:
            # 이미 등장한 단어의 인덱스를 가져온다.
            index = word_to_index.get(word)
            # 해당 단어의 bow 리스트의 값을 1 증가시킨다.
            bow[index] = bow[index] + 1

    return word_to_index, bow

doc1 = "저는 인공지능 분야에 취업할 계획입니다. 인공지능은 재미있습니다. 인공지능 분야에서도 특히 자연어 처리에 관심이 있습니다."

vocab, bow = build_bow(doc1) # word_to_index를 vocab 변수에, bow를 bow 변수에 저장
print('단어: ', vocab)
print('Bag of Words Vector: ', bow)

BoW로 표현하면 다음과 같다.

 

단어:  {'저': 0, '는': 1, '인공': 2, '지능': 3, '분야': 4, '에': 5, '취업': 6, '할': 7, '계획': 8, '입니다': 9, '은': 10, '재미있습니다': 11, '에서': 12, '특히': 13, '자연어': 14, '처리': 15, '관심': 16, '이': 17, '있습니다': 18}

Bag of Words Vector:  [1, 1, 3, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

 

TF-IDF란?

TF-IDF는 문서 내 단어마다 중요도를 고려하여 가중치를 주는 통계적인 단어 표현 방법이다. 자연어 처리에서 정보수집, 텍스트 마이닝 및 유저 모델링의 가중치 계산에 자주 사용된다.

 

TF(Term Frequency) 값은 문서 내에서 단어가 등장하는 빈도가 높을수록 커지고, 반대로 IDF(Inverse Document Frequency) 값은 (검색 대상이 되는) 전체 문서의 집합에서 해당 단어가 적게 나타날수록 커진다.

*해당 단어가 전체 문서 집합에서 적게 나타날수록 IDF 값이 커지는 이유는, 그 단어가 특정 문서에서 나타나면 해당 문서를 다른 문서와 구별하는 데 더 많은 정보를 제공하기 때문이다.

 

다시 말해,  IDF 값이 낮은 단어는 문서를 구별하는 데 중요하지 않은 단어이고

IDF 값이 높은 단어는 문서를 구별하는 데 중요한 단어라고 말할 수 있다.

 

TF-IDF 는 TF와 IDF곱으로 나타낸다.

 

TF(단어 출현빈도​) x IDF(역 문서 빈도)

 

구분 내용
문서1 과일에는 비타민C가 다량 함유되어 있다.
문서2 비타민C를 채우기 위해서는 다양한 방법이 있다. 비타오백을 마시는 방법, 비타민C가 함유된 건강보조식품을 먹는 방법 등.
문서3 동남아에 가면 대체로 한국보다 과일을 저렴하게 살 수 있다.
문서4 비타민은 여러 종류가 있다.  비타민A, 비타민B, 비타민C, 비타민D...
문서5 한라봉은 제주 특산품으로, 많은 사람들이 즐겨 찾는 과일이다.

 

from sklearn.feature_extraction.text import CountVectorizer

documents = [
    "과일에는 비타민C가 다량 함유되어 있다.", 
    "비타민C를 채우기 위해서는 다양한 방법이 있다. 비타오백을 마시는 방법, 비타민C가 함유된 건강보조식품을 먹는 방법 등.",
    "동남아에 가면 대체로 한국보다 과일을 저렴하게 살 수 있다.",
    "비타민은 여러 종류가 있다.  비타민A, 비타민B, 비타민C, 비타민D...",
    "한라봉은 제주 특산품으로, 많은 사람들이 즐겨 찾는 과일이다."
]

vectorizer = CountVectorizer()

# documents에서 각 단어의 빈도수 기록
print(vectorizer.fit_transform(documents).toarray())
[[0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0] [0 1 0 0 0 0 1 0 0 1 0 1 2 1 0 0 0 1 1 0 0 1 0 0 1 1 0 0 0 0 0 1 0 0 0 0 1] [1 0 0 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0 0 0] [0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 1 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0] [0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 1 1 0 1 0 1 0 0]] 
# 각 단어와 매핑된 인덱스 출력
print(vectorizer.vocabulary_)
{'과일에는': 2, '비타민c가': 17, '다량': 5, '함유되어': 35, '있다': 25, '비타민c를': 18, '채우기': 31, '위해서는': 24, '다양한': 6, '방법이': 13, '비타오백을': 21, '마시는': 9, '방법': 12, '함유된': 36, '건강보조식품을': 1, '먹는': 11, '동남아에': 8, '가면': 0, '대체로': 7, '한국보다': 33, '과일을': 3, '저렴하게': 26, '비타민은': 20, '여러': 23, '종류가': 28, '비타민a': 14, '비타민b': 15, '비타민c': 16, '비타민d': 19, '한라봉은': 34, '제주': 27, '특산품으로': 32, '많은': 10, '사람들이': 22, '즐겨': 29, '찾는': 30, '과일이다': 4}
# 사이킷런은 TF-IDF를 자동 계산해주는 TfidfVectorizer를 제공한다.
from sklearn.feature_extraction.text import TfidfVectorizer

documents = [
    "과일에는 비타민C가 다량 함유되어 있다.", 
    "비타민C를 채우기 위해서는 다양한 방법이 있다. 비타오백을 마시는 방법, 비타민C가 함유된 건강보조식품을 먹는 방법 등.",
    "동남아에 가면 대체로 한국보다 과일을 저렴하게 살 수 있다.",
    "비타민은 여러 종류가 있다.  비타민A, 비타민B, 비타민C, 비타민D...",
    "한라봉은 제주 특산품으로, 많은 사람들이 즐겨 찾는 과일이다."
]

tfidfv = TfidfVectorizer().fit(documents)
print(tfidfv.transform(documents).toarray())
print(tfidfv.vocabulary_)
>>
[[0. 0. 0.50199209 0. 0. 0.50199209 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.40500406 0. 0. 0. 0. 0. 0. 0. 0.28281359 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.50199209 0. ] [0. 0.25847202 0. 0. 0. 0. 0.25847202 0. 0. 0.25847202 0. 0.25847202 0.51694403 0.25847202 0. 0. 0. 0.20853359 0.25847202 0. 0. 0.25847202 0. 0. 0.25847202 0.14561862 0. 0. 0. 0. 0. 0.25847202 0. 0. 0. 0. 0.25847202] [0.39786049 0. 0. 0.39786049 0. 0. 0. 0.39786049 0.39786049 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.22414766 0.39786049 0. 0. 0. 0. 0. 0. 0.39786049 0. 0. 0. ] [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.3696763 0.3696763 0.3696763 0. 0. 0.3696763 0.3696763 0. 0. 0.3696763 0. 0.20826918 0. 0. 0.3696763 0. 0. 0. 0. 0. 0. 0. 0. ] [0. 0. 0. 0. 0.35355339 0. 0. 0. 0. 0. 0.35355339 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.35355339 0. 0. 0. 0. 0.35355339 0. 0.35355339 0.35355339 0. 0.35355339 0. 0.35355339 0. 0. ]]

>>
{'과일에는': 2, '비타민c가': 17, '다량': 5, '함유되어': 35, '있다': 25, '비타민c를': 18, '채우기': 31, '위해서는': 24, '다양한': 6, '방법이': 13, '비타오백을': 21, '마시는': 9, '방법': 12, '함유된': 36, '건강보조식품을': 1, '먹는': 11, '동남아에': 8, '가면': 0, '대체로': 7, '한국보다': 33, '과일을': 3, '저렴하게': 26, '비타민은': 20, '여러': 23, '종류가': 28, '비타민a': 14, '비타민b': 15, '비타민c': 16, '비타민d': 19, '한라봉은': 34, '제주': 27, '특산품으로': 32, '많은': 10, '사람들이': 22, '즐겨': 29, '찾는': 30, '과일이다': 4}
import pandas as pd
tfidfv.vocabulary_

result = tfidfv.transform(documents).toarray()
tfidf_ = pd.DataFrame(result, columns = tfidfv.vocabulary_)
print(tfidf_)


 

 

728x90

+ Recent posts