8 분 소요


단어 표현

단어 벡터(word vector), 워드 임베딩(word embedding)

원-핫 인코딩(one-hot encoding)

매우 간단하고 이해하기 쉬움

실제 자연어 처리를 할 경우 수십~수백만 개의 단어를 표현해야 하는데 각 단어 벡터의 크기가 너무 커져 공간을 많이 사용 -> 매우 비효율적
단어의 의미나 특성 같은 것들이 전혀 표현되지 않음

카운트 기반

In [1]:
text = ['''
오늘 네가
보고싶다
널 다시 품에
안아보고 싶다
오늘 네가 난
생각난다
너랑 같이 산책하던
그곳에 서있다
너의 체온이
기억난다
따뜻하게 내 쉬던
숨소리 들려
오늘 네가
온 것 같아
우리 같이 잠들던
벤치에 기대니
시간이 흘러흘러
다시 만날 순 없지
그래도 보고싶다 널
그리운 내 사랑아
오늘 네가 정말
보고싶다
너를 다시 내 품에
안아보고 싶다
오늘 네가 난
생각난다
너를 쓰다듬던 손이
너를 기억한다
니가 보고싶다
''']

sklearn - CountVectorizer

단어들의 카운트(출현 빈도;frequency)로 여러 문서들을 벡터화
2글자 이상만 체크

In [2]:
from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd
import numpy as np
In [3]:
cv = CountVectorizer()
countv = cv.fit_transform(text).toarray()
countv
Out [3]:
array([[1, 2, 1, 1, 1, 1, 1, 1, 1, 3, 1, 5, 1, 3, 1, 1, 1, 1, 4, 1, 1, 2,
        1, 1, 1, 1, 1, 2, 1, 2, 1, 5, 1, 1, 1, 1, 2, 1]], dtype=int64)
In [4]:
print(cv.vocabulary_)
Out [4]:
{'오늘': 31, '네가': 11, '보고싶다': 18, '다시': 13, '품에': 36, '안아보고': 29, '싶다': 27, '생각난다': 21, '너랑': 8, '같이': 1, '산책하던': 20, '그곳에': 2, '서있다': 22, '너의': 10, '체온이': 35, '기억난다': 6, '따뜻하게': 15, '쉬던': 25, '숨소리': 24, '들려': 14, '같아': 0, '우리': 32, '잠들던': 33, '벤치에': 17, '기대니': 5, '시간이': 26, '흘러흘러': 37, '만날': 16, '없지': 30, '그래도': 3, '그리운': 4, '사랑아': 19, '정말': 34, '너를': 9, '쓰다듬던': 28, '손이': 23, '기억한다': 7, '니가': 12}

In [5]:
pd.DataFrame({'vocab':sorted(list(cv.vocabulary_.keys())), 'CountVector':countv[0]})
Out [5]:
vocab CountVector
0 같아 1
1 같이 2
2 그곳에 1
3 그래도 1
4 그리운 1
5 기대니 1
6 기억난다 1
7 기억한다 1
8 너랑 1
9 너를 3
10 너의 1
11 네가 5
12 니가 1
13 다시 3
14 들려 1
15 따뜻하게 1
16 만날 1
17 벤치에 1
18 보고싶다 4
19 사랑아 1
20 산책하던 1
21 생각난다 2
22 서있다 1
23 손이 1
24 숨소리 1
25 쉬던 1
26 시간이 1
27 싶다 2
28 쓰다듬던 1
29 안아보고 2
30 없지 1
31 오늘 5
32 우리 1
33 잠들던 1
34 정말 1
35 체온이 1
36 품에 2
37 흘러흘러 1

sklearn - TfidfVectorizer

In [6]:
from sklearn.feature_extraction.text import TfidfVectorizer
In [7]:
ti = TfidfVectorizer()
tfidfv = ti.fit_transform(text).toarray()
tfidfv
Out [7]:
array([[0.08703883, 0.17407766, 0.08703883, 0.08703883, 0.08703883,
        0.08703883, 0.08703883, 0.08703883, 0.08703883, 0.26111648,
        0.08703883, 0.43519414, 0.08703883, 0.26111648, 0.08703883,
        0.08703883, 0.08703883, 0.08703883, 0.34815531, 0.08703883,
        0.08703883, 0.17407766, 0.08703883, 0.08703883, 0.08703883,
        0.08703883, 0.08703883, 0.17407766, 0.08703883, 0.17407766,
        0.08703883, 0.43519414, 0.08703883, 0.08703883, 0.08703883,
        0.08703883, 0.17407766, 0.08703883]])
In [8]:
print(ti.vocabulary_)
Out [8]:
{'오늘': 31, '네가': 11, '보고싶다': 18, '다시': 13, '품에': 36, '안아보고': 29, '싶다': 27, '생각난다': 21, '너랑': 8, '같이': 1, '산책하던': 20, '그곳에': 2, '서있다': 22, '너의': 10, '체온이': 35, '기억난다': 6, '따뜻하게': 15, '쉬던': 25, '숨소리': 24, '들려': 14, '같아': 0, '우리': 32, '잠들던': 33, '벤치에': 17, '기대니': 5, '시간이': 26, '흘러흘러': 37, '만날': 16, '없지': 30, '그래도': 3, '그리운': 4, '사랑아': 19, '정말': 34, '너를': 9, '쓰다듬던': 28, '손이': 23, '기억한다': 7, '니가': 12}

In [9]:
pd.DataFrame({'vocab':sorted(list(ti.vocabulary_.keys())), 'TfidfVector':tfidfv[0]})
Out [9]:
vocab TfidfVector
0 같아 0.087039
1 같이 0.174078
2 그곳에 0.087039
3 그래도 0.087039
4 그리운 0.087039
5 기대니 0.087039
6 기억난다 0.087039
7 기억한다 0.087039
8 너랑 0.087039
9 너를 0.261116
10 너의 0.087039
11 네가 0.435194
12 니가 0.087039
13 다시 0.261116
14 들려 0.087039
15 따뜻하게 0.087039
16 만날 0.087039
17 벤치에 0.087039
18 보고싶다 0.348155
19 사랑아 0.087039
20 산책하던 0.087039
21 생각난다 0.174078
22 서있다 0.087039
23 손이 0.087039
24 숨소리 0.087039
25 쉬던 0.087039
26 시간이 0.087039
27 싶다 0.174078
28 쓰다듬던 0.087039
29 안아보고 0.174078
30 없지 0.087039
31 오늘 0.435194
32 우리 0.087039
33 잠들던 0.087039
34 정말 0.087039
35 체온이 0.087039
36 품에 0.174078
37 흘러흘러 0.087039
  • Tfidf: Term Frequency-Inverse Document Frequency
    (TF: 단어의 반복)/(IDF: 문서에서 반복)
    모든 문서에 상투적으로 사용되는 단어(중요도가 낮은 단어)를 나눠서 제어해준다. - 무의미성
In [10]:
documents = [
    '먹고 싶은 치킨',
    '먹고 싶은 피자',
    '맛있지만 살이 찌는 피자 피자',
    '나는 야식이 좋아요'
]
In [11]:
vocab = list(set(word for document in documents for word in document.split()))
print(vocab)

vocab2 = []
for document in documents:
    for word in document.split():
        vocab2.append(word)
print(list(set(vocab2)))
Out [11]:
['나는', '살이', '치킨', '야식이', '피자', '찌는', '좋아요', '싶은', '먹고', '맛있지만']
['나는', '살이', '치킨', '야식이', '피자', '찌는', '좋아요', '싶은', '먹고', '맛있지만']

In [12]:
vocab.sort()
len(vocab), vocab
Out [12]:
(10, ['나는', '맛있지만', '먹고', '살이', '싶은', '야식이', '좋아요', '찌는', '치킨', '피자'])
In [13]:
# 문서의 길이
N = len(documents)
N
Out [13]:
4

TF 코드 구현

CounterVectorizer

In [14]:
# TF 구하는 함수
def tf(text, d):
    return d.count(text)
In [15]:
tf('피자', '맛있지만 살이 찌는 피자 피자')
Out [15]:
2
In [16]:
result = []
for i in range(N):
    result.append([])
    d = documents[i]
    for j in range(len(vocab)):
        t = vocab[j]
        result[-1].append(tf(t, d))
        
result # Bag of words: 분류 하기 위함, 특히 감성 평가
Out [16]:
[[0, 0, 1, 0, 1, 0, 0, 0, 1, 0],
 [0, 0, 1, 0, 1, 0, 0, 0, 0, 1],
 [0, 1, 0, 1, 0, 0, 0, 1, 0, 2],
 [1, 0, 0, 0, 0, 1, 1, 0, 0, 0]]
In [17]:
# CountVectorizer와 비교
cv = CountVectorizer()
countv = cv.fit_transform(documents).toarray()
countv
Out [17]:
array([[0, 0, 1, 0, 1, 0, 0, 0, 1, 0],
       [0, 0, 1, 0, 1, 0, 0, 0, 0, 1],
       [0, 1, 0, 1, 0, 0, 0, 1, 0, 2],
       [1, 0, 0, 0, 0, 1, 1, 0, 0, 0]], dtype=int64)
In [18]:
tf_res = pd.DataFrame(result, columns=vocab)
tf_res
Out [18]:
나는 맛있지만 먹고 살이 싶은 야식이 좋아요 찌는 치킨 피자
0 0 0 1 0 1 0 0 0 1 0
1 0 0 1 0 1 0 0 0 0 1
2 0 1 0 1 0 0 0 1 0 2
3 1 0 0 0 0 1 1 0 0 0

‘먹고 싶은 치킨’,
‘먹고 싶은 피자’,
‘맛있지만 살이 찌는 피자 피자’,
‘나는 야식이 좋아요’

IDF 코드 구현

T 문서마다 많이 나온 단어는 값이 작음(inverse)

In [19]:
# IDF 구하는 함수
from math import log

def idf(text):
    df = 0
    for doc in documents:
        df += t in doc # 문서마다 반복 수 카운트
    return log(N/(df+1)) # N:문서 수, df+1:zero division 방지, log:큰 값을 제한
In [20]:
result = []
for j in range(len(vocab)):
    t = vocab[j]
    result.append(idf(t))
np.array(result)
Out [20]:
array([0.69314718, 0.69314718, 0.28768207, 0.69314718, 0.28768207,
       0.69314718, 0.69314718, 0.69314718, 0.69314718, 0.28768207])
In [21]:
idf_result = pd.DataFrame(result, columns=['idf'], index=vocab)
idf_result
Out [21]:
idf
나는 0.693147
맛있지만 0.693147
먹고 0.287682
살이 0.693147
싶은 0.287682
야식이 0.693147
좋아요 0.693147
찌는 0.693147
치킨 0.693147
피자 0.287682

TF-IDF 코드 구현

TfidfVectorizer

In [22]:
# TF-IDF 구하는 함수
def tfidf(t, d):
    return tf(t, d) * idf(t)
In [23]:
result = []
for i in range(N):
    result.append([])
    d = documents[i]
    for j in range(len(vocab)):
        t = vocab[j]
        result[-1].append(tfidf(t, d))
np.array(result)
Out [23]:
array([[0.        , 0.        , 0.28768207, 0.        , 0.28768207,
        0.        , 0.        , 0.        , 0.69314718, 0.        ],
       [0.        , 0.        , 0.28768207, 0.        , 0.28768207,
        0.        , 0.        , 0.        , 0.        , 0.28768207],
       [0.        , 0.69314718, 0.        , 0.69314718, 0.        ,
        0.        , 0.        , 0.69314718, 0.        , 0.57536414],
       [0.69314718, 0.        , 0.        , 0.        , 0.        ,
        0.69314718, 0.69314718, 0.        , 0.        , 0.        ]])

sklearn의 TfidfVectorizer 구현

In [24]:
# TF 구하는 함수
def tf(text, d):
    return d.count(text)
In [25]:
count_vec = []
for i in range(N):
    count_vec.append([])
    d = documents[i]
    for j in range(len(vocab)):
        t = vocab[j]
        count_vec[-1].append(tf(t, d))
count_vec
Out [25]:
[[0, 0, 1, 0, 1, 0, 0, 0, 1, 0],
 [0, 0, 1, 0, 1, 0, 0, 0, 0, 1],
 [0, 1, 0, 1, 0, 0, 0, 1, 0, 2],
 [1, 0, 0, 0, 0, 1, 1, 0, 0, 0]]

보편적인 idf를 구하는 식에서 로그항의 분자에 1을 더해주고 로그항에 1을 더함

In [26]:
from math import log

def s_idf(text):
    df = 0
    for doc in documents:
        df += t in doc
    return log((N+1)/(df+1))+1
In [27]:
s_idf_rst = []
for j in range(len(vocab)):
    t = vocab[j]
    s_idf_rst.append(s_idf(t))
np.array(s_idf_rst)
Out [27]:
array([1.91629073, 1.91629073, 1.51082562, 1.91629073, 1.51082562,
       1.91629073, 1.91629073, 1.91629073, 1.91629073, 1.51082562])

Tfidfvectorizer의 idf와 비교해 본다.

In [28]:
ti = TfidfVectorizer()
ti.fit(documents)
ti.idf_
Out [28]:
array([1.91629073, 1.91629073, 1.51082562, 1.91629073, 1.51082562,
       1.91629073, 1.91629073, 1.91629073, 1.91629073, 1.51082562])

구한 TF 벡터와 IDF 벡터를 곱해준다.

In [29]:
import numpy as np
np.multiply(np.array(count_vec), np.array(s_idf_rst))
Out [29]:
array([[0.        , 0.        , 1.51082562, 0.        , 1.51082562,
        0.        , 0.        , 0.        , 1.91629073, 0.        ],
       [0.        , 0.        , 1.51082562, 0.        , 1.51082562,
        0.        , 0.        , 0.        , 0.        , 1.51082562],
       [0.        , 1.91629073, 0.        , 1.91629073, 0.        ,
        0.        , 0.        , 1.91629073, 0.        , 3.02165125],
       [1.91629073, 0.        , 0.        , 0.        , 0.        ,
        1.91629073, 1.91629073, 0.        , 0.        , 0.        ]])

L2 정규화를 한다.

In [30]:
from sklearn.preprocessing import normalize
In [31]:
tfidf_bfor_l2 = np.multiply(np.array(count_vec), np.array(s_idf_rst))
tfidf_afer_l2 = normalize(tfidf_bfor_l2, norm='l2')
In [32]:
tfidf_afer_l2
Out [32]:
array([[0.        , 0.        , 0.52640543, 0.        , 0.52640543,
        0.        , 0.        , 0.        , 0.66767854, 0.        ],
       [0.        , 0.        , 0.57735027, 0.        , 0.57735027,
        0.        , 0.        , 0.        , 0.        , 0.57735027],
       [0.        , 0.42693074, 0.        , 0.42693074, 0.        ,
        0.        , 0.        , 0.42693074, 0.        , 0.6731942 ],
       [0.57735027, 0.        , 0.        , 0.        , 0.        ,
        0.57735027, 0.57735027, 0.        , 0.        , 0.        ]])

TfidfVectorizer 최종 결과와 비교해본다.

In [33]:
ti = TfidfVectorizer()
tfidfv = ti.fit_transform(documents).toarray()
tfidfv
Out [33]:
array([[0.        , 0.        , 0.52640543, 0.        , 0.52640543,
        0.        , 0.        , 0.        , 0.66767854, 0.        ],
       [0.        , 0.        , 0.57735027, 0.        , 0.57735027,
        0.        , 0.        , 0.        , 0.        , 0.57735027],
       [0.        , 0.42693074, 0.        , 0.42693074, 0.        ,
        0.        , 0.        , 0.42693074, 0.        , 0.6731942 ],
       [0.57735027, 0.        , 0.        , 0.        , 0.        ,
        0.57735027, 0.57735027, 0.        , 0.        , 0.        ]])
In [34]:
tfidf_res = pd.DataFrame(tfidf_afer_l2, columns=vocab)
tfidf_res
Out [34]:
나는 맛있지만 먹고 살이 싶은 야식이 좋아요 찌는 치킨 피자
0 0.00000 0.000000 0.526405 0.000000 0.526405 0.00000 0.00000 0.000000 0.667679 0.000000
1 0.00000 0.000000 0.577350 0.000000 0.577350 0.00000 0.00000 0.000000 0.000000 0.577350
2 0.00000 0.426931 0.000000 0.426931 0.000000 0.00000 0.00000 0.426931 0.000000 0.673194
3 0.57735 0.000000 0.000000 0.000000 0.000000 0.57735 0.57735 0.000000 0.000000 0.000000

네이버 영화 리뷰 분석

In [35]:
from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd
In [36]:
df_train = pd.read_csv('datasets/ratings_train.txt', delimiter='\t')
df_train.head()
Out [36]:
id document label
0 9976970 아 더빙.. 진짜 짜증나네요 목소리 0
1 3819312 흠...포스터보고 초딩영화줄....오버연기조차 가볍지 않구나 1
2 10265843 너무재밓었다그래서보는것을추천한다 0
3 9045019 교도소 이야기구먼 ..솔직히 재미는 없다..평점 조정 0
4 6483659 사이몬페그의 익살스런 연기가 돋보였던 영화!스파이더맨에서 늙어보이기만 했던 커스틴 ... 1
In [37]:
# df_train = df_train[:10000]
In [38]:
df_train.dropna(inplace=True)
df_train.info()
Out [38]:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 149995 entries, 0 to 149999
Data columns (total 3 columns):
 #   Column    Non-Null Count   Dtype 
---  ------    --------------   ----- 
 0   id        149995 non-null  int64 
 1   document  149995 non-null  object
 2   label     149995 non-null  int64 
dtypes: int64(2), object(1)
memory usage: 4.6+ MB

In [39]:
df_test = pd.read_csv('datasets/ratings_test.txt', delimiter='\t')
df_test.head()
Out [39]:
id document label
0 6270596 굳 ㅋ 1
1 9274899 GDNTOPCLASSINTHECLUB 0
2 8544678 뭐야 이 평점들은.... 나쁘진 않지만 10점 짜리는 더더욱 아니잖아 0
3 6825595 지루하지는 않은데 완전 막장임... 돈주고 보기에는.... 0
4 6723715 3D만 아니었어도 별 다섯 개 줬을텐데.. 왜 3D로 나와서 제 심기를 불편하게 하죠?? 0
In [40]:
df_test.dropna(inplace=True)
df_test.info()
Out [40]:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 49997 entries, 0 to 49999
Data columns (total 3 columns):
 #   Column    Non-Null Count  Dtype 
---  ------    --------------  ----- 
 0   id        49997 non-null  int64 
 1   document  49997 non-null  object
 2   label     49997 non-null  int64 
dtypes: int64(2), object(1)
memory usage: 1.5+ MB

In [41]:
X_train = df_train['document']
y_train = df_train['label']

X_test = df_test['document']
y_test = df_test['label']

출력 초과 에러

IOPub data rate exceeded.
The Jupyter server will temporarily stop sending output
to the client in order to avoid crashing it.
To change this limit, set the config variable
`--ServerApp.iopub_data_rate_limit`.

Current values:
ServerApp.iopub_data_rate_limit=1000000.0 (bytes/sec)
ServerApp.rate_limit_window=3.0 (secs)

CMD

jupyter lab --ServerApp.iopub_data_rate_limit=1.0e10
In [42]:
naver_re_tv = TfidfVectorizer()
naver_re_tv.fit(X_train)
print(naver_re_tv.vocabulary_)
Out [42]:
{'더빙': 71119, '진짜': 246232, '짜증나네요': 248358, '목소리': 99567, '포스터보고': 273335, '초딩영화줄': 255126, '오버연기조차': 190112, '가볍지': 16352, '않구나': 167602, '너무재밓었다그래서보는것을추천한다': 57394, '교도소': 33783, '이야기구먼': 208071, '솔직히': 145795, '재미는': 222295, '없다': 177352, '평점': 271982, '조정': 234711, '사이몬페그의': 133947,

...

'고마움': 28958, '1점도아깝다진짜': 2615, '척편이여서': 253296, '불가50가지': 126113, '찎었냐': 250252, '220215679580': 3645, '크리쳐개그물임ㅋㅋ': 263752, '흥겹다ㅋ': 291905, '높아서ㅋㅋ': 60705, 'carl': 8822, '세이건으로': 143023, '디케이드': 80079, '오즈인데': 190555, '더블은': 71115, '뭔죄인가': 105744, '거들먹거리고': 24486, '혼혈은': 287413, '수간하는': 146244}

In [43]:
X_train_tfidf = naver_re_tv.transform(X_train)
X_train_tfidf # 희소행렬 생성 (293366개의 단어에 대해 Bag of word)
Out [43]:
<149995x293366 sparse matrix of type '<class 'numpy.float64'>'
	with 1074805 stored elements in Compressed Sparse Row format>
In [44]:
X_test_tfidf = naver_re_tv.transform(X_test)
X_test_tfidf
Out [44]:
<49997x293366 sparse matrix of type '<class 'numpy.float64'>'
	with 284188 stored elements in Compressed Sparse Row format>

LogisticRegression 머신러닝

In [45]:
from sklearn.linear_model import LogisticRegression
In [46]:
lr_model = LogisticRegression(max_iter=400)
lr_model.fit(X_train_tfidf, y_train)
lr_model.score(X_test_tfidf, y_test)
Out [46]:
0.8125087505250315

예측 기반

Word2vec

  • CBOW: 입력값으로 여러 개의 단어를 사용, 학습을 위해 하나의 단어와 비교
  • Skip-Gram: 입력값이 하나의 단어를 사용, 학습을 위해 주변의 여러 단어와 비교

단어 간의 유사도를 잘 측정한다.

word2vec

한국-서울+도쿄 => 일본
단어 한국과 단어 서울의 거리는 단어 ‘일본’과 단어 도쿄의 거리와 같다

한국-서울+베이징 => 중국
맥주-치킨+피자 => 음료수
남자-아빠+엄마 => 여자


Reference

태그: ,

카테고리:

업데이트:

댓글남기기