글을 작성하는 이유 😃
데이콘에서 "문장 유형 분류 AI 경진대회"에서 베이스라인으로 공유한 ipynb 코드 중에서 TF-IDF가 있었다.
TF-IDF가 무엇이며, 왜하는지, 어떻게 해석하는지 궁금증을 해소하기 위해 작성한다.
TF-IDF(Term Frequency - Inverse Document Frequency)는 무엇인가? 🤔
특정 단어가 특정 문서에서 얼마나 중요한 것인지를 나타내기 위해 사용되는 수치이다. 특정 문서에서 핵심어를 추출하거나, 검색엔진에서 검색 결과의 순위를 결정 또는 문서들 사이의 비슷한 정도를 구하는 용도로 사용할 수 있다.
(1) TF(d,t) : 특정 문서 d에서의 특정 단어 t의 등장 횟수.
(2) DF(t) : 특정 단어 t가 등장한 문서의 수.
예를들어, 바나나는 문서2와 문서3에서 등장했습니다. 이 경우, 바나나의 DF는 2입니다. 문서3에서 바나나가 두 번 등장했지만, 그것은 중요한 게 아닙니다. 심지어 바나나란 단어가 문서2에서 100번 등장했고, 문서3에서 200번 등장했다고 하더라도 바나나의 DF는 2가 됩니다.
(3) IDF(d, t) : DF(t)에 반비례하는 수.
IDF라는 이름을 보고 DF의 역수가 아닐까 생각했다면, IDF는 DF의 역수를 취하고 싶은 것이 맞습니다. 그런데 log와 분모에 1을 더해주는 식에 의아하실 수 있습니다. log를 사용하지 않았을 때, IDF를 DF의 역수(ndf(t)라는 식)로 사용한다면 총 문서의 수 n이 커질 수록, IDF의 값은 기하급수적으로 커지게 됩니다. 그렇기 때문에 log를 사용합니다.
TF-IDF를 왜 사용하는가? 😋
사실 이부분이 제일 중요한 것 같다. 아무리 TF, DF, IDF를 이해해도 왜 사용해야하는지를 모르면 사용할 수가 없기 때문이다. 참고로 나는 데이콘에서 봤기에 데이콘 기준으로 왜 사용해야하는가 설명한 것이다.
한줄로 요약하면 문장(Text)을 벡터화(수치화)하기 위함이다.
입력 : "먹고 싶은 사과"
출력(벡터화) : [0. 0. 0. 0.52640543 0. 0.66767854 0.52640543 0. 0. ]
일단 문장(Text)보단 숫자(int,float)이 컴퓨터 입장에서는 계산하기 더 쉬울 것이다. 그렇기에 우리는 문장을 숫자로 변경하여 컴퓨터가 계산하기 쉽도록 해줄 것이다.
그렇다면 숫자 아무거나 입력할 것인가? No
예를 들어, "먹고 싶은 사과" 라는 문장(?)이 있으면, ["먹고" , "싶은", "사과"] 로 나눌 수 있으며, "먹고"는 0, "싶은"은 1, "사과"는 2 로 지정하여 [0 ,1 ,2]로 변경할 수 있다. 내 맘대로 [ 0 , 0 , 0 ] 이렇게 변경이 되면 ["먹고", "먹고", "먹고"] 이렇게 될 수 있다. 그렇기에 단어(?)들은 고유의 인덱스(0,1,2)를 가져야 한다.
바로 예제 보자 1 🚓
from sklearn.feature_extraction.text import TfidfVectorizer
text = [
'먹고 싶은 사과',
'먹고 싶은 바나나',
'길고 노란 바나나 바나나',
'저는 과일이 좋아요'
]
vectorizer = TfidfVectorizer()
vectorizer.fit(text) # 학습
vec = vectorizer.transform(text) # 변환
print(vec)
이렇게 나오면 무엇을 의미하는지 어려우니까 아래처럼 변경
인덱스 0,1,2 를 보면 "먹고 싶은 사과" 라는 문장에서 "싶은"은 6 , "사과"는 5, "먹고"는 3으로 지정되어 있다
즉, 결과는 "먹고 싶은 사과" -> "먹고" : 3 , "싶은" : 6 , "사과" : 5 인덱스에 값이 들어가게 된다.
print(vec.toarray()[0])
인덱스 3에 "먹고"의 값 0.5264 , 인덱스 5에 "사과"의 값 0.6676 , 인덱스 6에 "싶은"의 값 0.5264이 들어있다.
그렇다면, 나머지 0은 무엇이 들어가 있을까?
단어 | 인덱스 | "먹고 싶은 사과" 문장에 존재? | 값 |
과일이 | 0 | X | 0 |
길고 | 1 | X | 0 |
노란 | 2 | X | 0 |
먹고 | 3 | O | 0.5264 |
바나나 | 4 | X | 0 |
사과 | 5 | O | 0.6676 |
싶은 | 6 | O | 0.5264 |
저는 | 7 | X | 0 |
좋아요 | 8 | X | 0 |
의문점 🤨
"먹고" 와 "싶은" 은 단어는 다른데 왜 값(tf-idf)는 같을까?
간단히 생각하면 tf-idf는 문서 내에서 단어의 중요도를 수치화한 값이다. 즉, "먹고"와 "싶은"은 문서에서 중요도가 똑같단 뜻이 된다.
그래서 어떻게 컴퓨터(모델)는 "먹고"와 "싶은"을 다른 단어로 인식할까?
서로 인덱스라 다른점을 이용하여 다른 단어를 인식한다. "먹고"의 인덱스는 3, "싶은"의 인덱스는 6이기에 값이 같아도 위치가 달라서 다른 단어로 딥러닝 모델이 인식하는거 같다.
바로 예제 보자 2 🚕
관련없는 Text 1 , 2 를 가지고, text 1를 학습시키고, text2를 넣으면 어떻게 나올까?
from sklearn.feature_extraction.text import TfidfVectorizer
text = [
'먹고 싶은 사과',
'먹고 싶은 바나나',
'길고 노란 바나나 바나나',
'저는 과일이 좋아요'
]
vectorizer = TfidfVectorizer()
vectorizer.fit((text))
text2 = [
'사과를 먹었다',
'과일과 관련없는 이야기',
'리그오브레전드 패치했더라?',
'바나나 먹어봤냐'
]
vec = vectorizer.transform(text2)
print(vec)
결과는 하나만 나오네...
왜 그럴까?
일단, sklearn에서 제공하는 TfidfVectorizer는 analyzer가 word로 기본 설정이 되어 있다. 내 추측건데, 영어는 띄어쓰기만으로 단어를 나눌수 있기에 단순히 띄어쓰기로 단어를 나누는 것 같다. 이게 뭔소리냐??
예를 들어, text2의 4번 째 줄의 "바나나 먹어봤냐"를 띄어쓰기로 나누게 되면 ["바나나" , "먹어봤냐"] 로 나눠지게 될 것이며, 여기서 "바나나"는 text1의 2번째줄(먹고싶은바나나), 3번째줄(길고노란바나나바나나)에 나오게 된다. 그렇기에 text2의 4번째 줄(바나나 먹어봤냐)에서 "바나나"는 벡터화가 된것이다.
이에 해당하는 의미로 text2의 1번째 줄에 있는 "사과를 먹었다"는 ["사과를" , "먹었다"]로 나뉘게 될 것이며, text1에서 "사과"라는 단어는 있지만, "사과를" 이란 단어는 없다고 인식한다. 그렇기에 text2의 1번째 줄은 아무것도 출력이 되지 않는 것이다.
즉, 벡터화를 할 때는 모든 문장(문서)를 한번에 벡터화해야 누락되는 정보가 없을 것이다.(아마도)
또한, 벡터화 성능을 높이기 위해서는 한글 문서를 바로 벡터화하지말고, 토큰나이저(knolpy 등)을 이용하여 형태소 분석 후에 사용하면 더 좋을거 같단 느낌이 든다. (실험은 아직...)
출처 : https://wikidocs.net/31698
'자연어처리(NLP)' 카테고리의 다른 글
Skip-gram (0) | 2022.12.16 |
---|---|
CBOW(Continuous Bag of Words) (2) | 2022.12.16 |
워드투벡터(Word2Vec) (0) | 2022.12.16 |
워드 임베딩(Word Embedding) (0) | 2022.12.16 |
NLP에서 원-핫 인코딩(One-hot encoding)이란? (0) | 2022.12.16 |