은공지능 공작소 :: '딥러닝 공부/자연어처리' 카테고리의 글 목록

안녕하세요. 은공지능 공작소의 파이찬입니다!
정말 오랜만에 포스팅을 올리네요. 9월달은 정말 바쁘게 지낸 것 같습니다.
오늘은 tf-idf vectorizer를 생성할 때 필요한 여러가지 파라미터들에 대해 알아보겠습니다.

min_idf, analyzer, sublinear_tf, ngram_range, max_features
이렇게 5가지 파라미터들에 대해서 알아볼 것입니다.

 

 

 

 

 

 

 

 

 

 

 

 

1. 사전준비

 

 

 

from sklearn.feature_extraction.text import TfidfVectorizer

text = ['I go to my home my home is very large',
        'I went out my home I go to the market',
        'I bought a yellow lemon I go back to home']

tfidf_vectorizer = TfidfVectorizer()
우선 위와같이 데이터를 생성해 줍니다.
저번 시간에 썼던 데이터와 동일한 데이터 입니다.
오늘 사용할 파라미터는 TfidfVectorizer()의 괄호 안에 들어가는 것들입니다.

 

 

 

 

 

tfidf_vectorizer.fit(text) # 벡터라이저가 단어들을 학습합니다.
tfidf_vectorizer.vocabulary_ # 벡터라이저가 학습한 단어사전을 출력합니다.
sorted(tfidf_vectorizer.vocabulary_.items()) # 단어사전을 정렬합니다.
output:
[('back', 0),
 ('bought', 1),
 ('go', 2),
 ('home', 3),
 ('is', 4),
 ('large', 5),
 ('lemon', 6),
 ('market', 7),
 ('my', 8),
 ('out', 9),
 ('the', 10),
 ('to', 11),
 ('very', 12),
 ('went', 13),
 ('yellow', 14)]
먼저 fit 명령어를 통해서 text 변수에 저장된 데이터를 학습시켜줘야 합니다.
이렇게 학습이 끝나면, 단어사전과 idf 벡터 등을 출력해볼 수 있습니다.
단어사전은 .vocabulary_ 명령어를 통해 출력이 가능합니다.

저희는 출력을 이쁘게 하기 위해서 sorted 명령어도 같이 사용해 보았습니다.
sorted 명령어를 통해 살펴보니, 단어들은 알파벳 오름차순으로
인덱스가 부여된다는 사실도 알 수 있었습니다.

 

 

 

 

 

 

 

2. min_df 파라미터 알아보기

 

 

 

min-df는 DF(document-frequency)의 최소 빈도값을 설정해주는 파라미터입니다.
DF는 특정 단어가 나타나는 '문서의 수'를 의미합니다.
단어의 수가 아니라 문서의 수라는 것에 주의해 주세요.

DF 개념에 대해 위의 예시로 다시 설명을 드리자면
단어 'home'의 경우 전체 문서에서 빈도는 4번 이지만,
'home'이라는 단어가 포함된 문서의 수는 3개이기 때문에 DF = 3 입니다.

이러한 DF의 최소값을 설정하여 해당 값보다 작은 DF를 가진 단어들은
단어사전 (vocabulary_)에서 제외하고, 인덱스를 부여하지 않습니다.
이것이 바로 min_df 파라미터 입니다. 코드로 바로 실습해 보겠습니다.

 

 

 

 

 

min_df_vectorizer = TfidfVectorizer(min_df = 2)
min_df_vectorizer.fit(text)
sorted(min_df_vectorizer.vocabulary_.items())
output:
[('go', 0), ('home', 1), ('my', 2), ('to', 3)]
TfidfVectorizer 객체를 생성할 때, 위와 같이 min-df 파라미터를 설정하였습니다.
최소 DF가 2로 설정되었으니, 1인 것들은 모두 탈락하게 됩니다.
(PPT 설명화면에서 회색박스 글자들은 모두 DF가 1인 것들입니다.)

단어사전을 출력해보니 4개의 단어만 남았습니다.
최소 DF를 2 이상 갖는 단어들이 단어사전에 남아있게 되는 것입니다.
이 역시 알파벳 오름차순으로 인덱스를 부여받아 0 ~ 3 의 값이 할당이 되었습니다.

 

 

 

 

 

 

 

3. analyzer 파라미터 알아보기

 

 

analyzer 파라미터는 학습단위를 결정하는 파라미터입니다.
word, char 2가지 옵션 정도를 고려해볼 수 있습니다.

analyzer = 'word'라고 설정시, 학습의 단위를 단어로 설정합니다. (ex - home, go, my ...)
analyzer = 'char'라고 설정시, 학습의 단위를 글자로 설정합니다.(ex - a, b, c, d ...)

 

 

 

 

 

vectorizer = TfidfVectorizer(analyzer = 'word')
vectorizer.fit(text)
sorted(vectorizer.vocabulary_.items())
output:
[('back', 0),
 ('bought', 1),
 ('go', 2),
 ('home', 3),
 ('is', 4),
 ('large', 5),
 ('lemon', 6),
 ('market', 7),
 ('my', 8),
 ('out', 9),
 ('the', 10),
 ('to', 11),
 ('very', 12),
 ('went', 13),
 ('yellow', 14)]
analyzer = 'word'로 파라미터를 주었을 때, 단어 사전의 모습입니다.
기존의 결과와 크게 다르지 않은 모습을 보여줍니다.

 

 

 

 

 

vectorizer = TfidfVectorizer(analyzer = 'char')
vectorizer.fit(text)
sorted(vectorizer.vocabulary_.items())
output:
[(' ', 0),
 ('a', 1),
 ('b', 2),
 ('c', 3),
 ('e', 4),
 ('g', 5),
 ('h', 6),
 ('i', 7),
 ('k', 8),
 ('l', 9),
 ('m', 10),
 ('n', 11),
 ('o', 12),
 ('r', 13),
 ('s', 14),
 ('t', 15),
 ('u', 16),
 ('v', 17),
 ('w', 18),
 ('y', 19)]
analyzer = 'char'로 설정했을 때의 모습입니다.
위의 결과물처럼 각 글자마다 인덱스가 부여된 것을 확인하실 수 있습니다.

그렇다면 위와 같이 char 기반의 analyzer가 실제 모델에서
좋은 성능을 보일지 의문이 남을 수도 있을 것 같습니다.

결론부터 말씀드리면, 상당히 좋은 결과를 내놓을 수 있습니다.
제가 CNN 모델에서 char analyzer로 학습을 시킨 적이 있었는데,
생각보다 괜찮은 결과가 나왔던 것으로 기억합니다.

그러니 모델의 성능을 올리기 위해서,
word 기반 방법만 고집할 것이 아니라 char 기반의 방법도 시도해 볼 만하다고 생각합니다.

 

 

 

 

 

 

 

4. sublinear_tf 파라미터 알아보기

 

 

 

sublinear_tf 파라미터는 TF (Term-Frequency, 단어빈도) 값의
스무딩(smoothing)
여부를 결정하는 파라미터 입니다. (True/False)

TF -> 1 + ln(TF)
위와 같은 수식을 통해 스무딩 처리가 이루어집니다.
이를 이해하기 쉽도록 간단한 예제를 하나 준비해 보았습니다.

 

 

 

 

 

import numpy as np
def sublinear_func(input):
    rst = 1 + np.log(input)
    return rst
    
sublinear_func(100)
sublinear_func(10000)
output:
5.605170185988092
10.210340371976184
sublinear_tf를 구현해보는 간단한 함수를 만들어 보았습니다.
결과를 보시면 100은 5.6 정도로, 10000은 10.2 정도로 값이 확 줄어든 것을 확인하실 수 있습니다.
이처럼 sublinear_tf는 높은 TF값을 완만하게 처리하는 효과를 가지고 있습니다.

TF의 아웃라이어가 너무 심한 데이터의 경우,
이런 서브리니어 파라미터를 True로 바꿔주면 어느정도 효과를 기대할 수 있습니다.
간단히 코드 실습 후 넘어가겠습니다.

 

 

 

 

 

vectorizer = TfidfVectorizer(sublinear_tf = True) # defalut는 False입니다.
vectorizer.fit(text)
a = vectorizer.transform(text).toarray()
a
output:
array([[0.        , 0.        , 0.23427226, 0.39665741, 0.39665741,
        0.39665741, 0.        , 0.        , 0.51076864, 0.        ,
        0.        , 0.23427226, 0.39665741, 0.        , 0.        ],
       [0.        , 0.        , 0.24902824, 0.24902824, 0.        ,
        0.        , 0.        , 0.42164146, 0.3206692 , 0.42164146,
        0.42164146, 0.24902824, 0.        , 0.42164146, 0.        ],
       [0.44514923, 0.44514923, 0.26291231, 0.26291231, 0.        ,
        0.        , 0.44514923, 0.        , 0.        , 0.        ,
        0.        , 0.26291231, 0.        , 0.        , 0.44514923]])
위와 같이 sublinear_tf를 True로 설정하면, 높은 TF값들에 대해서 스무딩 처리를 해줍니다.
하지만 저희 예제 데이터는 너무 작기 때문에, 해당 파라리터의 효과가 별로 없는 것 같네요.
'TF 값에 대해 아웃라이어 처리를 해준다'라는 효과 정도만 기억하고 넘어가시면 되겠습니다.

 

 

 

 

 

 

 

5. ngram_range 파라미터 이해하기

 

 

 

n-gram이라는 것은 단어의 묶음을 말합니다.
이 단어의 묶음을 범위를 설정하는 것이 ngram_range 파라미터입니다.

예를들어, ngram_range = (1, 1)이라면 단어의 묶음을 1개부터 1개까지 설정하라는 의미입니다.
단어사전에는 1개 단어묶음에 인덱스가 할당되겠죠.
이는 기존의 벡터라이저와 별 차이가 없는 결과를 내놓습니다.

만약 ngram_range = (1, 2)라고 한다면, 단어의 묶음을 1개부터 2개까지 설정하라는 뜻입니다.
단어사전에는 1개 단어묶음도 있고, 2개 단어묶음도 존재하게 되겠죠.
가령, 'go home', 'very good'과 같은 2개 짜리 묶음도 인덱스를 받게 되는 것입니다.

 

 

 

 

 

vectorizer = TfidfVectorizer(ngram_range = (1, 1))
vectorizer.fit(text)
sorted(vectorizer.vocabulary_.items())
output:
[('back', 0),
 ('bought', 1),
 ('go', 2),
 ('home', 3),
 ('is', 4),
 ('large', 5),
 ('lemon', 6),
 ('market', 7),
 ('my', 8),
 ('out', 9),
 ('the', 10),
 ('to', 11),
 ('very', 12),
 ('went', 13),
 ('yellow', 14)]
ngram_range를 (1,1)로 설정했을 때의 모습입니다.
단어사전을 살펴보니, 기존의 벡터라이저와 별 차이가 없는 것을 볼 수 있습니다.

 

 

 

 

 

vectorizer = TfidfVectorizer(ngram_range = (1, 2))
vectorizer.fit(text)
sorted(vectorizer.vocabulary_.items())
output:
[('back', 0),
 ('back to', 1),
 ('bought', 2),
 ('bought yellow', 3),
 ('go', 4),
 ('go back', 5),
 ('go to', 6),
 ('home', 7),
 ('home go', 8),
 ...
이번에는 ngram_range를 (1, 2)로 설정해보았습니다.
이는 단어묶음을 1개부터 2개까지로 세팅하겠다는 뜻입니다.
단어사전을 출력해보니, 단어 2개 묶음도 인덱스를 받은 것을 볼 수 있습니다.

사실 이번 포스팅은 이 ngram을 설명할고 하다가,
아예 기본적인 파라미터들을 다 설명해기로 하고 만든 것입니다 ^^

그럼 이 ngram_range를 사용하는 이유는 무엇일까요?
단어가 묶여야 비로소 의미를 가지는 것들이 있기 때문입니다.
예를들어 'very'라는 단어 하나만으로는 의미를 잘 알 수 없지만
'very good', 'very bad' 등과 같이 뒤에 오는 단어가 같이 오면 정확한 의미를 알 수 있습니다.
이렇게 묶여야 의미가 살아나는 녀석들 때문에, ngram_range를 사용한다고 볼 수 있습니다.

 

 

 

 

 

6. max_features 파라미터 알아보기

 

 

 

max_feature는 tf-idf vector의 최대 feature를 설정해주는 파라미터입니다.
해당 파라미터를 이해하려면, feature의 개념에 대해 아셔야 합니다.

머신러닝에서 feature란, 테이블의 컬럼에 해당하는 개념입니다.
또한 행렬의 열에 해당하는 것이기도 합니다.

TF-IDF 벡터는 단어사전의 인덱스만큼 feature를 부여받습니다.
그럼 구체적인 예시를 코드로 한 번 살펴보겠습니다.

 

 

 

 

 

text
output:
['I go to my home my home is very large',
 'I went out my home I go to the market',
 'I bought a yellow lemon I go back to home']
저희의 데이터는 위와 같이 생겼습니다.
모두 15 종류의 단어가 사용되었는데요. 그러므로 feature값은 15가 할당이 됩니다.

 

 

 

 

 

vectorizer = TfidfVectorizer()
a = vectorizer.fit_transform(text).toarray()
a.shape
output:
(3, 15)
벡터의 shape를 출력해보면 (3, 15)가 나옵니다.
이 15라는 값이 feature에 해당하는 것입니다. 이해가 되셨죠?

이제 max_features 파라미터를 통해,
이 feature값을 10으로 제한해보겠습니다.

 

 

 

 

 

vectorizer = TfidfVectorizer(max_features = 10)
a = vectorizer.fit_transform(text).toarray()
a.shape
output:
(3, 10)
feature의 값을 10으로 제한하니, 벡터의 모양이 변했습니다.
(3, 10)으로 shape가 바뀐 것을 확인하실 수 있을 겁니다.

해당 파라미터는 어느 상황에 사용하게 될까요?
자연어처리 데이터를 다루다보면, 엄청 긴 데이터를 만날 수 있습니다.

예를 들어서 다른 데이터의 단어들은 10정도를 가지는데,
어떤 데이터만 단어 종류가 100이 넘어간다고 하면,
이 100에 맞추어 feature의 수가 엄청 늘어나게 됩니다.
그럴 경우, 모델 성능이 저하될 수도 있는 것이죠.

그렇기 때문에 사전에 EDA를 통해서 데이터의 length를 확인하시는 것이 중요합니다.
그렇게 되면 통계적으로 엄청 정확한 기준은 아닐지라도,
대략 해당 파라미터의 스타트 지점을 유추해 볼 수 있을 것입니다.

 

 

 

 

 

 

 

7. 마무리

 

 

 

네 이렇게 오늘은 tf-idf의 중요한 파라미터 5가지에 대해서 알아보았습니다.
도움이 되셨다면 포스팅 하단에 하트 버튼 한 번씩 눌러주시는 것 잊지 마세요~!

지금까지 은공지능 공작소의 파이찬이었습니다.
감사합니다.

 

반응형
블로그 이미지

pychan

딥러닝에 관련된 시행착오, 사소하지만 중요한 것들, 가능한 모든 여정을 담았습니다.

,

안녕하세요! 은공지능 공작소 운영자 파이찬입니다.
오늘은 TF-IDF 벡터화에 대한 내용을 다루겠습니다.
자연어처리를 하다 보면 많이 등장하는 Feature extraction 기법입니다.

 

 

 

 

 

 

 

 

 

 

 

 

1. TF-IDF의 개념

 

 

 

TF-IDF의 풀네임은 Term Frequency - Inverse Document Frequency입니다.
TF, IDF의 의미를 각각 이해하시면 정확한 의미 파악이 가능합니다.
자세한 내용은 위의 그림을 참조해 주세요.

그럼 이 TF-IDF가 어떻게 사용되고,
어떤 맥락에서 등장하게 되었는지 이해해보도록 하겠습니다.

 

 

 

 

 

TF-IDF는 일종의 특징 추출(Feature extraction) 기법입니다.
특징 추출이란, 말 그대로 raw data에서 특징을 뽑아내는 것을 의미합니다.

좀 추상적으로 들릴 수도 있는데요, 한마디로 정의하자면 '수치화'입니다.
텍스트 데이터를 컴퓨터가 그대로 인식할 수는 없습니다.
컴퓨터가 인식하려면 이를 숫자의 형태로 바꾸어 주어야 합니다.

이렇게 숫자의 형태로 바꾸어 주는 과정에서,
어떻게 하면 사람이 인지하듯이, 데이터의 특징을 잘 담을 수 있을지 고민하게 되고
여러 가지 특징 추출 기법이 등장하는 것입니다.

 

 

 

 

 

여러가지 특징 추출 기법 중 가장 기본적이 count 기반 특징 추출기법입니다.
python scikit에서 CountVectorizer가 바로 이것입니다.

하지만 위의 그림에서 나오듯이, 이 CountVectorizer는 한계점이 존재합니다.
단어의 빈도수를 기반으로 하기 때문에,
조사, 관사 등의 의미 없는 단어에 높은 수치를 부여할 수 있다는 것입니다.

 

 

 

 

 

이러한 단점을 해결할 수 있는 것이 TF-IDF입니다.
일반적으로 TF-IDF가 CountVectorizer 보다 좋은 결과를 내놓는 것으로 알려져 있습니다.

 

 

 

 

 

이제 다시 한번 정확하게, TF, DF, IDF의 의미를 짚고 넘어가겠습니다.
위의 그림을 통해 의미를 이해하시면 됩니다.

+
유튜브 동영상에는 설명이 빠진 것이 있어 보충합니다.
TF-IDF에서 한 글자의 단어(Term)는 단어 사전에서 제외됩니다.
위의 문서들에 등장하는 단어 중 'I' 나 'a' 같은 단어에 이에 해당됩니다.

 

 

 

 

 

DF의 개념은 혼동될 수 있으니 조심하셔야 합니다.
TF가 '단어'의 빈도를 나타내었다면, 'DF'는 문서의 빈도를 나타냅니다.

위의 그림에서 home은 전체 문서에서 총 4번 등장합니다.
그렇다고 해서 DF = 4로 잡으시면 안 된다는 말씀입니다.

 

 

 

 

 

비록 단어의 등장 횟수는 4번이지만, 해당 단어를 가진 문서는 총 3개이기 때문에
여기서 home의 DF는 3으로 잡는 것이 정확합니다.
이러한 점을 숙지하시고 넘어가시면 되겠습니다.

 

 

 

 

 

마지막으로 IDF의 개념을 잡고 넘어가겠습니다.
IDF는 DF에 Inverse를 붙여준 것입니다.
수학에서 Inverse는 '역수'라는 의미를 가지고 있습니다.

역수는 분모와 분자를 뒤집는 것을 의미합니다.
예를 들어 3을 3/1이라고 표현하고, 분모와 분자를 뒤집는다면,
1/3이라고 할 수 있습니다. 이것이 역수의 개념입니다.

역수를 취해주면, 원래는 큰 값이 작은 값으로 확 줄어드는 효과를 낼 수 있습니다.
이러한 방식으로 많이 등장하는 단어에 패널티를 주기 때문에
DF에 Inverse를 붙여서 IDF라고 이름이 붙여졌습니다.
IDF를 구하는 수식은 위의 그림을 참조해주시면 됩니다.

 

 

 

 

 

from sklearn.feature_extraction.text import TfidfVectorizer

text = ['I go to my home my home is very large', # Doc[0]
        'I went out my home I go to the market', # Doc[1]
        'I bought a yellow lemon I go back to home'] # Doc[2]

tfidf_vectorizer = TfidfVectorizer() # TF-IDF 객체선언
이제 본격적으로 코드 실습을 시작하겠습니다.
설명자료에서 쓰인 문장들을 그대로 가져왔습니다. 총 3개의 문서가 담긴 데이터입니다.

모듈은 사이킷런의 Feature extraction을 사용합니다.
그중에서도 TfidfVectorizer라는 모듈을 사용하여 객체를 선언해주면 됩니다.

 

 

 

 

 

tfidf_vectorizer.fit(text) # 단어를 학습시킴
tfidf_vectorizer.vocabulary_ # 단어사전을 출력
sorted(tfidf_vectorizer.vocabulary_.items()) # 단어사전 정렬
output:
[('back', 0),
 ('bought', 1),
 ('go', 2),
 ('home', 3),
 ('is', 4),
 ('large', 5),
 ('lemon', 6),
 ('market', 7),
 ('my', 8),
 ('out', 9),
 ('the', 10),
 ('to', 11),
 ('very', 12),
 ('went', 13),
 ('yellow', 14)]
단어 사전을 정렬해서 출력한 모습입니다.
한 글자 짜리 단어들은 제외하고 단어 사전이 만들어졌습니다.

a, b, c, d 오름차순으로 순차적으로 인덱스가 할당된 모습입니다.
이제 Vectorizer는 이 사전을 참고하여, TF/DF/IDF 벡터화를 수행하게 됩니다.

 

 

 

 

 

 

 

2. TF, DF, IDF 벡터화 과정의 이해

 

 

 

tfidf_vectorizer.idf_
output:
array([1.69314718, 1.69314718, 1.        , 1.        , 1.69314718,
       1.69314718, 1.69314718, 1.69314718, 1.28768207, 1.69314718,
       1.69314718, 1.        , 1.69314718, 1.69314718, 1.69314718])
이제 TF, DF, IDF 벡터화 과정에 대해서 설명드리겠습니다.
그전에 IDF와 TF-IDF과정을 거친 결과가 어떻게 되는지 확인하고 넘어가겠습니다.
위의 결과는 IDF 벡터화의 결과물입니다.

 

 

 

 

 

tfidf_vectorizer.idf_.shape
output:
(15,)
코드의 뒤에 .shape를 붙여서 결과를 확인해 보십시오.
그러면 (15, ) 라는 결과가 출력이 됩니다.
이는 .vocabulary_ 명령어를 통해 확인한 단어 사전의 단어수와 일치합니다.

 

 

 

 

 

tfidf_vectorizer.transform(text).toarray()
output:
array([[0.        , 0.        , 0.2170186 , 0.4340372 , 0.36744443,
        0.36744443, 0.        , 0.        , 0.55890191, 0.        ,
        0.        , 0.2170186 , 0.36744443, 0.        , 0.        ],
       [0.        , 0.        , 0.24902824, 0.24902824, 0.        ,
        0.        , 0.        , 0.42164146, 0.3206692 , 0.42164146,
        0.42164146, 0.24902824, 0.        , 0.42164146, 0.        ],
       [0.44514923, 0.44514923, 0.26291231, 0.26291231, 0.        ,
        0.        , 0.44514923, 0.        , 0.        , 0.        ,
        0.        , 0.26291231, 0.        , 0.        , 0.44514923]])
위의 결과는 TF-IDF 벡터화의 최종 결과물입니다.
각각의 Document 마다 array가 하나씩 할당되어, 총 3개의 결과물이 나왔습니다.
대괄호로 묶인 덩어리를 세어보면 3개가 있는 것을 확인할 수 있습니다.

이렇게 하여 IDF의 모습, Tf-IDF의 최종 결과물까지 확인해보았습니다.
저희의 목표는 어떻게 이런 결과가 나오게 되었는지를 자세히 살펴보는 것입니다.
과정을 해부하는 것이지요!

지금부터 다시 PPT 자료를 통해,
TF 벡터화, DF 벡터화, IDF 벡터화에 대해 하나하나씩 자세히 파헤쳐보겠습니다.

 

 

 

 

 

왼쪽의 단어 사전은 fit 함수를 통해 만들어준 것입니다.
이를 쉽게 그림으로 표현해보면 오른쪽과 같이 됩니다.

 

 

 

 

 

이렇게 만들어준 단어 사전을 통해, TF가 벡터화되는 것입니다.
문서(Document)마다 하나씩 TF가 만들어집니다.
그러니 총 3개의 TF가 생성되는 것이죠.

각각의 TF 벡터에는 해당 단어들의 빈도가 숫자로 들어가게 됩니다.
예를 들어, Doc[0]의 home이라는 항을 살펴보겠습니다.
home이라는 단어는 Doc[0]에 총 2번 등장했기 때문에 2가 할당이 되는 방식입니다.
이런 방식으로 전체 TF 벡터가 만들어집니다.

 

 

 

 

 

이번에는 DF(Document Frequency)의 벡터화 과정에 대해 알아보겠습니다.
DF의 벡터화도 TF 벡터화와 비슷합니다.
다만, DF는 단어의 수가 아닌 '해당 단어가 포함된 문서의 수'라는 점을 기억해주시면 됩니다.

 

 

 

 

 

마지막으로 IDF(Inverse-Document Frequency) 벡터화 과정입니다.
IDF 벡터화는 위의 그림처럼 일종의 자연로그 변환을 거칩니다.
이런 방식으로 값이 높은 것들은 낮추고, 값이 낮은 것들은 높일 수 있습니다.

 

 

 

 

 

 

 

3. IDF 벡터화 해부

 

 

 

tfidf_vectorizer.idf_
output:
array([1.69314718, 1.69314718, 1.        , 1.        , 1.69314718,
       1.69314718, 1.69314718, 1.69314718, 1.28768207, 1.69314718,
       1.69314718, 1.        , 1.69314718, 1.69314718, 1.69314718])
이제 위와 같은 수치들을 직접 구해볼 것입니다.
코딩해서 계산한 과정이 맞다면 바로 위에 있는 결과와 같은 결과가 나오겠고,
제대로 IDF를 이해한 것이겠죠? ^^

IDF 벡터를 만들기 위해선 DF부터 만들어야 합니다.
DF 벡터는 직접 일일이 손코딩을 해줘야 합니다.
하지만 여러분은 그냥 아래 코드를 복붙해주세요~ 수고는 제가 합니다

 

 

 

 

 

import numpy as np
DF_vec = np.array([1, 1, 3, 3, 1,
                   1, 1, 1, 2, 1,
                   1, 3, 1, 1, 1])
DF 벡터를 위와 같이 만들어 주면 됩니다.

 

 

 

 

 

def idf_func(n, df):
    import numpy as np
    rst = np.log((1+n)/(1+df)) + 1
    return rst
다음으로 DF를 IDF로 로그 변환해주는 함수를 만들어 줍니다.
함수의 인자는 n(총 문서의 개수)과 df 벡터를 넣어주었습니다.

 

 

 

 

 

idf_func(3, DF_vec)
output:
array([1.69314718, 1.69314718, 1.        , 1.        , 1.69314718,
       1.69314718, 1.69314718, 1.69314718, 1.28768207, 1.69314718,
       1.69314718, 1.        , 1.69314718, 1.69314718, 1.69314718])
tfidf_vectorizer.idf_
output:
array([1.69314718, 1.69314718, 1.        , 1.        , 1.69314718,
       1.69314718, 1.69314718, 1.69314718, 1.28768207, 1.69314718,
       1.69314718, 1.        , 1.69314718, 1.69314718, 1.69314718])
함수를 통해 구한 것과 idfVectorizer로 구한 값이 정확히 같게 나왔습니다.
포인트는 로그 변환 방식을 이해하는 것입니다.
이렇게 하여 IDF 벡터화 과정 해부가 끝났습니다.

+
해당 로그 변환은 IDF_smoothing을 거친 것입니다. (Default option)
이 IDF_smoothing에 대해서는 다음 포스팅에서 다루어 보도록 하겠습니다.

 

 

 

 

 

 

 

4. TF-IDF 벡터화 해부

 

 

 

tfidf_vectorizer.transform(text).toarray()
output:
array([[0.        , 0.        , 0.2170186 , 0.4340372 , 0.36744443,
        0.36744443, 0.        , 0.        , 0.55890191, 0.        ,
        0.        , 0.2170186 , 0.36744443, 0.        , 0.        ],
       [0.        , 0.        , 0.24902824, 0.24902824, 0.        ,
        0.        , 0.        , 0.42164146, 0.3206692 , 0.42164146,
        0.42164146, 0.24902824, 0.        , 0.42164146, 0.        ],
       [0.44514923, 0.44514923, 0.26291231, 0.26291231, 0.        ,
        0.        , 0.44514923, 0.        , 0.        , 0.        ,
        0.        , 0.26291231, 0.        , 0.        , 0.44514923]])
이제 TF-IDF 벡터화를 해부해보겠습니다.
위의 벡터 수치들을 직접 만들어 보는 것이 저희의 목표입니다.

TF-IDF 벡터화는 쉽게 말해서 TF x IDF로 이루어져 있다고 설명을 드렸습니다.
그런데 여기까지 해도 결과는 실제 코드에서 나온 결과와 같지 않을 것입니다.

왜냐면 이 곱한 결과에 L2정규화까지 해야
위에 보이는 결과가 나오기 때문이지요. 그림으로 설명드리면 다음과 같습니다.

 

 

 

 

 

최종 TF-IDF는 위와 같이 크게 2단계를 거쳐서 산출됩니다.

1. TF벡터와 IDF벡터를 원소곱 해줍니다.
2. 위의 결과에 L2 정규화를 해줍니다.

L2 정규화에 대해서 간단히 설명드리겠습니다.
위의 하얀색 박스로 되어 있는 수식을 참고하시면 됩니다.
각각의 원소들을 제곱합에 루트를 씌워준 값으로 나눠준 것입니다.
이러한 방식으로 벡터화의 결과를 좀 완만하게 만들 수 있습니다.

이렇게 2단계를 거쳐서 최종 TF-IDF 벡터가 나오게 됩니다.
이제 이를 코딩 실습을 통해 이해해 보겠습니다.

 

 

 

 

 

tfidf_vectorizer.transform(text).toarray()[0]
array([0.        , 0.        , 0.2170186 , 0.4340372 , 0.36744443,
       0.36744443, 0.        , 0.        , 0.55890191, 0.        ,
       0.        , 0.2170186 , 0.36744443, 0.        , 0.        ])
저희의 목표는 위의 결과를 만드는 것입니다.
이는 첫 번째 Document에 해당하는 TF-IDF 벡터입니다.

 

 

 

 

 

count_vec = np.array([0, 0, 1, 2, 1,
                      1, 0, 0, 2, 0,
                      0, 1, 1, 0, 0])
먼저 TF 벡터부터 만들어 주겠습니다.
위의 Doc[0]에 해당하는 TF 벡터를 그대로 복붙 해가시길 바랍니다.
이는 첫 번째 문서에 나오는 단어 빈도수를 나타내는 벡터입니다.

 

 

 

 

 

tfidf_vectorizer.idf_
output:
array([1.69314718, 1.69314718, 1.        , 1.        , 1.69314718,
       1.69314718, 1.69314718, 1.69314718, 1.28768207, 1.69314718,
       1.69314718, 1.        , 1.69314718, 1.69314718, 1.69314718])
그다음으로 해야 할 것은 IDF 벡터를 만들어주는 것입니다.
이것은 이미 이전에 저희가 다 해본 것이기 때문에,
tfidf_vectorizer가 만들어주는 결과물을 그냥 쓰도록 하겠습니다.

이제 위에서 만들어준 TF 벡터와 여기 있는 IDF 벡터를 곱해줘야 합니다.

 

 

 

 

 

np.multiply(count_vec, tfidf_vectorizer.idf_)
output:
array([0.        , 0.        , 1.        , 2.        , 1.69314718,
       1.69314718, 0.        , 0.        , 2.57536414, 0.        ,
       0.        , 1.        , 1.69314718, 0.        , 0.        ])
위와 같은 방식으로 TF벡터와 IDF벡터를 곱해줍니다.

여기서 중요한 것은 np.multiply 함수를 쓰는 것입니다.
이는 벡터 각각의 원소들의 곱셈을 해주는 함수입니다.
(np.matmul과는 반드시 구분해서 써야 합니다)

이제 위에 나온 결과에 L2 정규화를 해주면
최종 TF-IDF 벡터를 구하게 됩니다. 거의 다 왔습니다!

 

 

 

 

 

from sklearn import preprocessing
tf_idf_before_l2 = np.multiply(count_vec, tfidf_vectorizer.idf_)
tf_idf_before_l2 = tf_idf_before_l2.reshape(1, -1)
tf_idf_after_l2 = preprocessing.normalize(tf_idf_before_l2, norm='l2')
위와 같이 사이킷런의 preprocessing 모듈을 이용하면
쉽게 L2 정규화를 할 수 있습니다.

먼저 np.multiply한 결과를 tf_idf_before_l2에 담아줍니다.
다음으로는 한 번 reshape를 해주는데, 이러한 과정을 거치는 이유는
L2 정규화의 범위를 전체로 잡아주기 위한 것입니다.
(그렇지 않으면 각 행 or 열을 기준으로 잡아버립니다)

마지막으로 preprocessing.normalize 함수를 통해 L2 정규화를 진행합니다.
제일 마지막에 norm = 'l2'라고 된 코드로 L2 정규화를 명시해주면 됩니다.

 

 

 

 

 

tf_idf_after_l2
output:
array([[0.        , 0.        , 0.2170186 , 0.4340372 , 0.36744443,
        0.36744443, 0.        , 0.        , 0.55890191, 0.        ,
        0.        , 0.2170186 , 0.36744443, 0.        , 0.        ]])
tfidf_vectorizer.transform(text).toarray()[0]
array([0.        , 0.        , 0.2170186 , 0.4340372 , 0.36744443,
       0.36744443, 0.        , 0.        , 0.55890191, 0.        ,
       0.        , 0.2170186 , 0.36744443, 0.        , 0.        ])
이제 tf_idf_after_l2의 수치가 제대로 나왔는지 비교를 해봅시다.
모든 값이 정확히 벡터라이저의 값과 일치하는 것을 확인하실 수 있습니다.

이런 식으로 TF벡터와 IDF벡터를 곱한 것에
L2정규화까지 거치는 것이 바로 TF-IDF 벡터를 산출하는 과정인 것입니다.

 

 

 

 

 

 

(사진: 은공지능 공작소 작업실)

수고하셨습니다. 오늘은 TF-IDF의 개념을 이해하고
각각의 과정들을 해부해보는 시간을 가졌습니다.

도움이 되셨다면 하단의 ♥ 버튼 클릭을 해주시길 바랍니다.
긴 포스팅 읽어주셔서 감사합니다.
반응형
블로그 이미지

pychan

딥러닝에 관련된 시행착오, 사소하지만 중요한 것들, 가능한 모든 여정을 담았습니다.

,

 

안녕하세요. 은공지능 공작소를 운영하는 파이찬입니다~!
오늘은 IMDb 리뷰 데이터로 워드 클라우드를 그리는 3번째 시간입니다.

1편에서는 전처리 작업을 진행했고,
2편에서는 기본적인 워드 클라우드를 그렸습니다.
(아래 포스팅 링크 참조해주세요)
 

[Python/Jupyter] 자연어처리를 위한 IMDB 데이터 전처리 과정 (데이터 다운로드부터 라벨링까지!)

안녕하세요! 은공지능 공작소의 파이찬입니다. 오늘은 파이썬으로 IMDB 영화데이터를 다운받고 전처리해보겠습니다. 전처리 작업은 자연어 처리를 위한 라벨링까지 진행합니다. https://www.imdb.com/ Ratings an..

chan-lab.tistory.com

 

[Python/Jupyter] IMDB 영화데이터로 워드클라우드 그리기 (기본편)

안녕하세요. 은공지능 공작소의 파이찬입니다. 오늘은 IMDb 영화리뷰 데이터로 워드클라우드를 그려보겠습니다. 데이터 전처리 과정은 아래 포스팅을 참조해주시길 바랍니다. [Python/Jupyter] 자연어처리를 위한..

chan-lab.tistory.com

 

 

 

 

 

오늘 진행할 부분은 위의 워드 클라우드에서 <br> 태그를 제거하는 것과,
불용어 사전 업데이트를 통해서 위에 보이는 일부 단어를 제거하는 것입니다.
또한 더 나아가서, 각각 긍정 단어와 부정 단어로만 이루어진 워드 클라우드도 그려보겠습니다.

 

 

 

 

 

 

 

 

 

 

 

 

1. html 태그 제거하기 (Beautiful Soup)

 

 

먼저 데이터를 살펴보겠습니다.
저희가 쓸 데이터는 지난 시간부터 쭉 사용해온 train_data입니다.
이 train_data는 txt, label이라는 2가지 컬럼이 있습니다.

txt 컬럼은 영화 리뷰가 텍스트로 담긴 컬럼이고요,
label 컬럼은 라벨(정답)이 달려있습니다.
각각 1은 긍정적인 리뷰, 0은 부정적인 리뷰를 나타냅니다.

 

 

 

 

 

from bs4 import BeautifulSoup

a = '<br>abcde</br>' # 이런 식으로 br 태그를 포함한 문자열입니다.
print(a)

a = BeautifulSoup(a, 'html5lib').get_text()
print(a)
output:
<br>abcde</br>
abcde
Beautiful Soup를 이용하여, html 태그를 제거하는 예시 코드입니다.
<br>이라고 된 부분이 html 코드입니다. 이는 웹디자인에서 줄을 바꿔주는 역할을 합니다.
하지만 워드클라우드 입장에서는 필요 없으니 제거를 해줘야 합니다.

위와 같이 Beautiful soup를 이용하여 한 번 필터링을 해주면
html 태그가 깔끔하게 제거가 됩니다.
output 부분을 보시면, <br>이라고 된 부분이 없어진 것을 확인할 수 있습니다.

지금 당장은 영화 데이터를 건들지 않겠습니다.
이렇게 Beautiful soup 함수에 대해 이해하시고 넘어가시면 됩니다.

 

 

 

 

 

 

 

2. Stopwords(불용어) 업데이트하기

 

 

 

import nltk

stopwords = nltk.corpus.stopwords.words('english') # 불용어 객체 선언
newStopWords = ['stopWord1','stopWord2'] # 추가하고 싶은 불용어 리스트
stopwords.extend(newStopWords) # 불용어 사전 업데이트
stopwords
output:
['i',
 'me',
 'my',
 ...
  "wouldn't",
 'stopWord1',
 'stopWord2']
위와 같은 방식으로 불용어 사전을 업데이트할 수 있습니다.
output을 보시면 마지막에 stopwords1, stopwords2가 업데이트된 것을
확인하실 수 있을 겁니다.

이렇게 stopwords를 업데이트해주는 것은
바로 wordcloud를 그려줄 때 옵션으로 적용할 수 있습니다.

이제 html 제거와 stopwords 업데이트를 모두 이해했으니,
이를 영화 데이터에 적용시켜 봅시다.

 

 

 

 

 

 

 

3. IMDb 데이터에서 html 태그 제거해주기

 

 

def data_engineering(input):
    from bs4 import BeautifulSoup
    
    rst = [] # rst는 result의 줄임말
    for i in input:
        # html 태그 제거
        data = BeautifulSoup(i, 'html5lib').get_text() # 필터 적용
        rst.append(data) # 결과를 rst리스트에 계속 추가
    
    return rst
    
train_data_html_out = data_engineering(train_data['txt'])
html 태그를 제거하는 함수를 하나 만들어 주고, 이름은 data_engineering으로 붙였습니다.
그리고 이 함수를 통해 train_data의 txt 컬럼을 정제하였습니다.
정제된 결과는 train_data_html_out에 담았고, 결과는 아래와 같습니다.

 

 

 

 

 

영화 리뷰 데이터에서 <br>과 같은 html 태그가 깔끔하게 사라진 모습입니다.

 

 

 

 

 

 

 

4. Stopwords(불용어) 업데이트해주기

 

 

 

위의 워드클라우드는 저번 시간에 그린 워드 클라우드입니다.
html 태그도 제거 안되어있고, 불용어 처리도 기본적인 것들로만 되어 있습니다.

여기서 제거해주고 싶은 단어를 3가지 정도 찾아보겠습니다.
큰 글자일수록, 해당 단어가 데이터에 자주 등장함을 의미합니다.

여기서는 'movie', 'film', 'one' 세 가지 단어를 제거해보겠습니다.
그러기 위해서는 위 3 단어로 불용어 사전을 업데이트해줘야 합니다.

 

 

 

 

 

stopwords_pychan = nltk.corpus.stopwords.words('english')
newStopWords = ['one', 'movie', 'film']
stopwords_pychan.extend(newStopWords)
위에서 보시는 것처럼 'one', 'movie', 'film'이라는 3 단어를 불용어에 추가한 후
이를 extend 함수를 통해 업데이트해주었습니다.
업데이트된 결과는 아래와 같습니다.

 

 

 

 

 

빨간 박스를 보면 3가지 불용어가 잘 추가된 것을 확인하실 수 있습니다.

 

 

 

 

 

 

 

5. 워드클라우드 그려보기

 

 

 

from wordcloud import WordCloud, STOPWORDS
import matplotlib.pyplot as plt
%matplotlib inline

wordcloud = WordCloud(stopwords = stopwords_pychan,
                      background_color = 'black', #배경색
                      width = 800,
                      height = 600).generate(' '.join(train_data_html_out))

plt.figure(figsize = (15, 10)) # 각각 가로 세로 인치 입니다.
plt.axis("off") # 축에 표시되는 눈금을 제거하는 옵션
plt.imshow(wordcloud) # 이미지가 표시되도록 하는 옵션. 
plt.show() # 최종으로 보여주는 옵션. 
html이 제거된 데이터를 가지고 워드 클라우드를 그려보았습니다.
여기서 stopwords 옵션에 주목해주시길 바랍니다.

stopwords_pychan이라고 해준 것을 사용했습니다.
이는 위에서 저희가 'one', 'movie', 'film' 3가지 불용어를 추가해준 것입니다.
불용어 처리를 했기에 앞으로 그릴 워드클라우드에서는 해당 단어들이 보이면 안 되겠지요.

나머지 코드들은 지난 시간에 다 설명드린 내용이므로 넘어가겠습니다.
바로 결과를 확인해보시죠.

 

 

 

 

 

br 태그를 포함해서 film, movie, one이 사라진
워드클라우드가 나온 것을 확인하실 수 있습니다.

그런데 이런 것들을 제거해줬더니,
이번엔 show나 character 같은 엉뚱한 단어들이 수면위로 올라왔습니다.

이런 결과는 워드클라우드를 그린다 해도 별로 의미가 없을 것 같습니다.
제가 생각했을 때, 좀 의미가 있으려면 지역 명칭이 나온다던지, 영화배우 이름이 나온다던지
이런 고유명사가 나와준다면 워드클라우드가 의미가 있을 것 같은데,
이렇게 나오는 것은 좀 더 손을 많이 봐야 할 것 같습니다. 

그렇다면 긍정, 부정 단어들로만 워드 클라우드를 딱딱 그릴 수는 없을까 고민을 해보았습니다.
위에서 했던 것처럼 stopwords를 일일이 손으로 추가하다가는 엄청 시간이 많이 걸릴 것 같습니다.

이런 방법이 다른 모델에 적용이 될지도 의심스럽구요.
(여기서 제거된 단어가 다른 자연어 워드클라우드에서는 의미를 가질 수도 있으니까요.)

다행히도 방법이 있었습니다. 바로 긍정 단어, 부정 단어만 모아놓은 일종의 리스트를 적용시키는 것인데요.
구글링 결과 깃허브에 누가 이러한 단어들을 정리를 해두었습니다.
이제부터 그것을 적용시켜서 각각 긍정 워드클라우드, 부정 워드클라우드를 그려볼까 합니다.

 

 

 

 

 

 

 

6. 긍정, 부정 단어 리스트 적용하여 워드 클라우드 그리기

 

 

 

 

shekhargulati/sentiment-analysis-python

sentiment analysis step by step. Contribute to shekhargulati/sentiment-analysis-python development by creating an account on GitHub.

github.com

위의 깃허브에 긍정단어, 부정단어가 정리된 텍스트 파일이 있습니다.
프로필을 보니, 인도 프로그래머 Shekhar Gulati라는 분이네요 ㅎㅎ
(Thank you for your Git :)

 

 

 

 

 

사이트에 들어가시면 위와 같은 화면이 뜹니다.
초록 단추를 누른 후, 위와 같이 웹주소를 복사해 줍니다.

 

 

 

 

 

conda install git
이제 아나콘다 프롬프트를 실행시켜 줍니다.
그리고 git 라이브러리를 설치하지 않으신 분들은 위와 같이,
git 라이브러리를 설치하는 명령어를 날려줍니다.

 

 

 

 

 

git clone https://github.com/shekhargulati/sentiment-analysis-python.git
git clone이라고 치신 후, 아까 복사해 두었던 링크 주소를 붙여넣기 해줍니다.
그러면 해당 프로젝트가 다운로드가 될 것입니다.

 

 

 

 

 

빨간 박스로 된 경로를 확인해주시길 바랍니다.
각각의 txt 파일들을 열어보면 위와 같이 긍정단어, 부정단어들로 구성이 되어 있습니다.

하지만 바로 긍정단어, 부정단어가 나오지는 않습니다.
위에 35줄 정도의 데이터 설명이 함께 들어있기 때문에, 전처리가 조금 필요한 상황입니다.

 

 

 

 

 

import os
word_path = 'C:/Users/white/sentiment-analysis-python/opinion-lexicon-English/'
pos_word = open(os.path.join(word_path, 'positive-words.txt'), 'r').read()
위와 같이 txt 폴더 경로를 word_path에 담아주고,
os.path.join 함수를 이용해서 경로를 조인해 주었습니다.

이 os.path.join 함수는 파일 경로를 조인할 때 쓰는 함수입니다.
저런 식으로 경로를 쓰면, 해당 폴더의 하위 파일까지 경로가 합쳐져 출력됩니다.

 

 

 

 

 

pos_word를 출력한 결과입니다.
중간중간에 \n이라고 써진 것이 유난히 많이 보이는군요.
줄바꿈(엔터)로 구분된 txt 파일이라는 것을 알 수 있습니다.

 

 

 

 

 

negative 파일도 같은 방식으로 불러와 주려고 했는데... 에러가 나버렸습니다.
이는 코덱 에러로, 인코딩 형식이 달라서 생기는 에러입니다.

이럴 경우, 파일의 인코딩을 바꿔주고
open 함수에서 encoding 옵션을 명시하면 에러를 해결할 수 있습니다.

 

 

 

 

 

위의 그림처럼 negative-words.txt 파일을 열고,
다른 이름으로 저장을 클릭합니다.

 

 

 

 

 

다음으로 인코딩을 UTF-8로 바꿔주고, 저장을 클릭해줍니다.

 

 

 

 

 

neg_word = open(os.path.join(word_path, 'negative-words.txt'), 'r', encoding='UTF-8').read()
이제  negative-words.txt를 불러올 때, 위와 같이 인코딩을 명시해줍니다.
encoding='UTF-8'이라고 된 부분이 인코딩을 명시하는 부분입니다.

이런 방법으로 다양한 인코딩 문제들을 쉽게 해결할 수 있습니다.

 

 

 

 

 

negative words도 정상적으로 불러와졌습니다.
positive words 파일과 마찬가지로 \n으로 구분되어 있는 것을 알 수 있습니다.

 

 

 

 

 

pos_word = pos_word.split('\n')
pos_word = pos_word[35:]

neg_word = neg_word.split('\n')
neg_word = neg_word[35:]
위와 같은 방식으로 전처리를 조금 해줍니다.
각각 \n으로 구분자를 명시해서 쪼개 주고(split)
35번째 인덱스부터 다시 변수에 담아주는 방식입니다.

\n을 구분자로 쓴 이유는, 해당 txt 파일이 줄바꿈(엔터)로 구분되어 있기 때문입니다.
또한 35번째 index부터 긍정/부정 단어가 나오기 때문에,
인덱스를 [35:]로 설정해, [1:34]의 설명 부분을 제외시켰습니다.

 

 

 

 

 

def data_cleaning(input, pos):
    
    clean_word = [] # 리턴값을 담아줄 리스트 선언
    
    for i in input: # i에는 여러 문장들이 담김.
        for j in i.split(): # i를 단어별로 쪼개 j에 담음
            
            if pos: # pos가 True라면 실행
                if j in pos_word: # 단어를 하나씩 positive-word와 비교
                    clean_word.append(j)
                    
            else: # pos가 False라면 실행
                if j in neg_word: # 단어를 하나씩 negative-words와 비교
                    clean_word.append(j)
    return clean_word
이제 위와 같이 데이터에서 긍정, 부정단어들을 필터링 해주는 함수를 만들어줍니다.
input 변수로 리뷰 데이터를 받아서 return값으로 긍정/부정 단어가 담긴
리스트를 출력해주는 방식입니다.

자세한 설명은 코드 주석으로 대신하겠습니다 ^^;
참고로 제 유튜브 영상을 보시면 어떻게 생각하면서 함수를 짜는지
세세하게 설명이 되어있습니다.

 

 

 

 

 

clean_pos_data = data_cleaning(train_data_html_out, pos = True)
clean_neg_data = data_cleaning(train_data_html_out, pos = False)
이제 위에서 만든 함수를 이용하여, 데이터를 클렌징해줍니다.
각각 clean_pos_data와 clean_neg_data에는
긍정, 부정 단어의 알맹이들만 남아 있을 것입니다.

 

 

 

 

 

wordcloud = WordCloud(stopwords = stopwords, # 불용어
                      background_color='black', #배경색
                      width = 800, #가로
                      height = 600).generate(' '.join(clean_pos_data))

plt.figure(figsize = (15, 10)) # 가로, 세로
plt.axis('off') # 눈금을 제거해주는 옵션
plt.imshow(wordcloud) # 안 써주면 워드클라우드가 빈 화면으로 나옴
plt.show()
wordcloud = WordCloud(stopwords = stopwords, # 불용어
                      background_color='black', #배경색
                      width = 800, #가로
                      height = 600).generate(' '.join(clean_neg_data))

plt.figure(figsize = (15, 10)) # 가로, 세로
plt.axis('off') # 눈금을 제거해주는 옵션
plt.imshow(wordcloud) # 안 써주면 워드클라우드가 빈 화면으로 나옴
plt.show()
각각 긍정 워드클라우드, 부정 워드클라우드를 출력하는 코드입니다.

 

 

 

 

 

짜잔!

아름다운 워드클라우드가 완성이 됐습니다.
긍정워드 클라우드는 like, good, great 등의 단어가 많이 보입니다.
반면에 부정 워드클라우드는 bad, hard, worst 등의 단어들이 눈에 띕니다.

 

 

 

 

 

네 이렇게 해서 html 태그 제거, 불용어 업데이트,
마지막으로 긍정/부정 워드클라우드 그리는 법까지 모두 다루어보았습니다.

현재 포스팅 날짜 8월 14일... 무더운 날씨가 이어지고 있습니다.
모두 무더위에 건강 챙기시길 바랍니다. 긴 글 읽어주셔서 감사합니다.

 

반응형
블로그 이미지

pychan

딥러닝에 관련된 시행착오, 사소하지만 중요한 것들, 가능한 모든 여정을 담았습니다.

,

안녕하세요. 은공지능 공작소의 파이찬입니다.
오늘은 자연어처리 강력한 코딩 팁을 들고 왔습니다.
apply, lambda, python comprehension을 이용한 데이터 EDA 방법입니다.

이 3가지 조합으로 정말 많은 정보들을 뽑아낼 수 있습니다.
저도 이 3가지 코딩법은 각각 알고 있었지만, 이것을 조합해서 쓸 때 더욱 강력하다는 것을 알았고,
또 몸에 체득하는 것이 중요하다는 것을 알았습니다.

여러분들도 실전 코딩에서 유용하게 사용하실 수 있길 바랍니다.
그럼 시작해보겠습니다.

 

 

 

 

 

 

 

 

 

 

 

 

1. 데이터 생성해주기

 

 

 

데이터는 성경 데이터를 가져왔습니다. 마태복음 11장입니다.

"무거운 짐 진 자들아 다 내게로 오라 내가 너희를 쉬게 하리라"
라는 구절로 유명한 챕터입니다. 마음이 평온해지는 구절이죠. ^^

데이터 생성하는 부분은 크게 중요하지 않으니,

그냥 아래 코드를 복붙하셔서 진행하시면 됩니다.

 

 

 

 

 

data = '''After Jesus had finished instructing his twelve disciples, he went on from there to teach and preach in the towns of Galilee.
When John heard in prison what Christ was doing, he sent his disciples
to ask him, "Are you the one who was to come, or should we expect someone else?"
Jesus replied, "Go back and report to John what you hear and see:
The blind receive sight, the lame walk, those who have leprosyare cured, the deaf hear, the dead are raised, and the good news is preached to the poor.
Blessed is the man who does not fall away on account of me."
As John's disciples were leaving, Jesus began to speak to the crowd about John: "What did you go out into the desert to see? A reed swayed by the wind?
If not, what did you go out to see? A man dressed in fine clothes? No, those who wear fine clothes are in kings' palaces.
Then what did you go out to see? A prophet? Yes, I tell you, and more than a prophet.
This is the one about whom it is written: " 'I will send my messenger ahead of you, who will prepare your way before you.'
I tell you the truth: Among those born of women there has not risen anyone greater than John the Baptist; yet he who is least in the kingdom of heaven is greater than he.
From the days of John the Baptist until now, the kingdom of heaven has been forcefully advancing, and forceful men lay hold of it.
For all the Prophets and the Law prophesied until John.
And if you are willing to accept it, he is the Elijah who was to come.
He who has ears, let him hear.
"To what can I compare this generation? They are like children sitting in the marketplaces and calling out to others:
" 'We played the flute for you, and you did not dance; we sang a dirge and you did not mourn.'
For John came neither eating nor drinking, and they say, 'He has a demon.'
The Son of Man came eating and drinking, and they say, 'Here is a glutton and a drunkard, a friend of tax collectors and "sinners." ' But wisdom is proved right by her actions."
Then Jesus began to denounce the cities in which most of his miracles had been performed, because they did not repent.
"Woe to you, Korazin! Woe to you, Bethsaida! If the miracles that were performed in you had been performed in Tyre and Sidon, they would have repented long ago in sackcloth and ashes.
But I tell you, it will be more bearable for Tyre and Sidon on the day of judgment than for you.
And you, Capernaum, will you be lifted up to the skies? No, you will go down to the depths. If the miracles that were performed in you had been performed in Sodom, it would have remained to this day.
But I tell you that it will be more bearable for Sodom on the day of judgment than for you."
At that time Jesus said, "I praise you, Father, Lord of heaven and earth, because you have hidden these things from the wise and learned, and revealed them to little children.
Yes, Father, for this was your good pleasure.
"All things have been committed to me by my Father. No one knows the Son except the Father, and no one knows the Father except the Son and those to whom the Son chooses to reveal him.
"Come to me, all you who are weary and burdened, and I will give you rest.
Take my yoke upon you and learn from me, for I am gentle and humble in hear   t, and you will find rest for your souls.
For my yoke is easy and my burden is light."'''
data_dict = {}
data_dict['verse'] = []

for row in [x for x in data.split('\n')]: 
    data_dict['verse'].append(row)
    
bible_data = pd.DataFrame.from_dict(data_dict)
# bible_data
이렇게 하여 마태복음 11장을 가지고 데이터 프레임을 만들어 보았습니다.
다음으로 Apply 함수에 대해 이해해보는 시간을 가지겠습니다.

 

 

 

 

 

 

 

2. Apply 함수 이해하기

 

 

 

Apply 함수는 컬럼 단위로 적용되는 함수입니다. 
즉, 행열 중 열을 기준으로 다양한 함수를 적용합니다.

적용되는 함수는 기본함수부터 사용자가 정의한 함수까지 다양합니다.
물론 람다(lambda)도 쓰일 수가 있고요.

그럼 길이를 구하는 len 함수부터 적용해서 이해를 해보겠습니다.

 

 

 

 

 

bible_data['verse'].apply(len)
output:
0     125
1      70
2      80
...
27     74
28    116
29     44
Name: verse, dtype: int64
이렇게 하면 verse 컬럼에 있는 모든 데이터에 len 함수가 적용되어 그 값이 출력이 됩니다.
한 번 type같은 함수들도 직접 적용해보시길 바랍니다.

이렇게 len(input), type(input) 형식으로 사용되는 함수들은 바로 적용을 시킬 수가 있습니다.
이런 함수들은 괄호 안에 인자를 넣어서 작동하는 기본 함수들입니다.

하지만 이러한 기본함수들 이외에도 점( . )을 이용해 쓰는 함수들도 있습니다.
apply 함수만 해도 점( . )을 이용해 쓰는 함수이고,
list.append 등도 이러한 방식으로 사용이 됩니다.

이런 함수들은 lambda를 통해 적용을 할 수 있습니다.
바로 아래에서 자세히 다루어 보겠습니다.

 

 

 

 

 

 

 

3. Lambda 함수 이해하기

 

 

 

bible_data['verse'][0] 
output:
'After Jesus had finished instructing his twelve disciples, he went on from there to teach and preach in the towns of Galilee.'
위와 같이 마태복음 11장 1절을 살펴보겠습니다.
첫 글자가 After~ 와 같이 대문자로 시작하는 것을 확인할 수 있습니다.

하지만 모든 절(verse)이 다 대문자로 시작하는 것은 아닙니다.
그래서 저희는 lambda를 이용해서,
첫 글자가 대문자인지 검사하는 코드를 작성해 보겠습니다.

여기에는 2가지 방법이 있습니다.
① 직접 사용자 정의 함수를 만든다.
② 람다 구문을 이용한다.

물론 제가 강조드리고 싶은 것은 ②번이지만, ①번부터 차근차근 다 살펴보도록 하겠습니다.

 

 

 

 

 

def IsUpper(input):
    return input[0].isupper()

bible_data['verse'].apply(IsUpper)
output:
0      True
1      True
2     False
...
27    False
28     True
29     True
Name: verse, dtype: bool
위와 같이 사용자 정의 함수 + apply 함수를 이용하여,
첫글자가 대문자인지를 검사할 수 있습니다.

하지만 그것보다 더 간단한 방법이 있는데,
그것이 바로 lambda 구문의 활용입니다.

 

 

 

 

 

bible_data['verse'].apply(lambda x : x[0].isupper())
output:
0      True
1      True
2     False
...
27    False
28     True
29     True
Name: verse, dtype: bool
이런 식으로 즉석에서 사용자 정의 함수를 만드는 것이 바로 lambda 구문입니다.
아까와 동일한 결과를 내놓지만 코드는 더 깔끔하죠!

이런 것들이 몸에 체득이 되면, 코딩 실무에서 매우 강력한 무기가 될 수 있고
고수의 느낌도 슬슬 풍길 수 있습니다.
여기에다가 list comprehension을 더해주면 더욱 완벽해집니다.

 

 

 

 

 

 

 

4. List comprehension 이해하기

 

 

 

List comprehension... 이름만 들으면 참 아리송합니다.
 리스트는 파이썬의 리스트 데이터 타입을 말하는 것인데,
컴프리헨션의 의미와 구문 사용법이 잘 매칭이 되질 않습니다.

굳이 짜맞추자면 "고도의 이해력을 가진 사람만이 쓸 수 있는 코딩구문"(???)
네 개소리인 것 같습니다...ㅋㅋㅋ
그냥 컴프리헨션이라는 코딩 문법이 있고, 사용법을 숙지하고 가시면 됩니다.

이번에는 각 row 마다 대문자의 개수를 세주는 상황을 가정하겠습니다.
마태복음 11장은 총 30절이 있으니, 30개의 숫자를 가진 컬럼이 출력되는 방식입니다.
가장 기본적은 Comprehension부터 살펴보겠습니다.

 

 

 

 

 

[i for i in bible_data['verse']]
output:
['After Jesus had finished instructing his twelve disciples, he went on from there to teach and preach in the towns of Galilee.',
 'When John heard in prison what Christ was doing, he sent his disciples',
 'to ask him, "Are you the one who was to come, or should we expect someone else?"',
 
 ...
 
 '"Come to me, all you who are weary and burdened, and I will give you rest.',
 'Take my yoke upon you and learn from me, for I am gentle and humble in heart, and you will find rest for your souls.',
 'For my yoke is easy and my burden is light."']
가장 기본적인 List comprehension의 구문입니다.
이런 식으로 Comprehension은 리스트 대괄호로 감싸주는 것이 기본입니다.

 

 

 

 

 

일반적인 for 반복문은 많이 보셨을 것입니다.
list comprehension은 간단한 코딩을 위해 실행문을 for문 앞으로 가져오는 것입니다.

위의 그림을 보면 이해가 쉬우실 겁니다.
달라지는 점은 실행문이 앞으로 온다는 것, 그리고 대괄호로 감싸주는 것뿐입니다.

 

 

 

 

 

[len(i) for i in bible_data['verse']]
output:
[125,
 70,
 80,
 ...
 74,
 116,
 44]
좀 더 이해를 돕기 위해, i 에 len() 함수를 씌워보았습니다.
 이런 식으로 함수를 씌워주면, 모든 데이터가 len 함수를 거쳐서 나오게 됩니다.
이것이 바로 for문을 간단하게 코딩할 수 있는 list comprehension입니다.

 

 

 

 

 

 

 

5. 종합 연습 (Apply, Lambda, List comprehension 모두 사용)

 

 

 

지금까지 Apply, Lambda, List comprehension까지 모두 다루어보았습니다.
이제는 이 3가지 무기들을 조합해, 강력한 무기를 사용해볼 때입니다.

이번에는 각 절(verse) 마다 몇 개의 대문자가 있는지
출력해보는 상황을 가정하겠습니다.

코딩 논리 흐름은 다음과 같습니다.
apply로 문장에 접근 -> 문자 별로 쪼개고 대문자인 것을 True/False로 출력 -> np.sum

 

 

 

 

 

bible_data['verse'].apply(lambda x: [i for i in x]) # lambda x는 in 뒤에 들어갑니다.
bible_data['verse'].apply(lambda x: [i.isupper() for i in x]) # for앞이 실행문인것 기억
bible_data['verse'].apply(lambda x: np.sum([i.isupper() for i in x])) # np.sum 위치 주목
output:
0     3
1     3
2     1

...

27    2
28    2
29    1
Name: verse, dtype: int64
한 줄, 한 줄 실행해보시면 이해가 빠르실 것입니다.
output은 마지막 줄 결과만 담았습니다.

위와 같은 방식으로 얼마든지 여러 줄의 코딩을 간단하게 만들 수 있습니다.
모두 몸과 마음에 잘 익혀두어 고수의 길을 가시길 바랍니다!
오늘 포스팅은 여기까지 하겠습니다. 감사합니다.
반응형
블로그 이미지

pychan

딥러닝에 관련된 시행착오, 사소하지만 중요한 것들, 가능한 모든 여정을 담았습니다.

,

안녕하세요. 은공지능 공작소의 파이찬입니다.
오늘은 IMDb 영화리뷰 데이터로 워드클라우드를 그려보겠습니다.
데이터 전처리 과정은 아래 포스팅을 참조해주시길 바랍니다.
 

[Python/Jupyter] 자연어처리를 위한 IMDB 데이터 전처리 과정 (데이터 다운로드부터 라벨링까지!)

안녕하세요! 은공지능 공작소의 파이찬입니다. 오늘은 파이썬으로 IMDB 영화데이터를 다운받고 전처리해보겠습니다. 전처리 작업은 자연어 처리를 위한 라벨링까지 진행합니다. https://www.imdb.com/ Ratings an..

chan-lab.tistory.com

 

 

 

 

 

 

 

 

 

 

 

 

1. 필요한 라이브러리 불러오기

 

 

 

from wordcloud import WordCloud, STOPWORDS
import matplotlib.pyplot as plt
%matplotlib inline # matplotlib가 주피터노트북에 표시되도록 설정
필요한 라이브러리는 총 3가지 입니다.
wordcloud, stopwords, matplotlib를 사용하겠습니다.

wordcloud는 워드클라우드 객체를 생성해주는 라이브러리이고,
stopwords는 일종의 불용어 집합 사전입니다.
불용어란 인터넷 검색시 잘 사용하지 않는 단어라고 합니다. 즉, 별 의미없는 단어라는 것입니다.
조사, 관사, 전치사, 접속사 등이 불용어에 해당됩니다. 이를 stopword 라고 부릅니다.

matplotlib는 파이썬언어로 그래프를 그려줄 수 있는 라이브러리 입니다.
워드클라우드 말고도 기본적인 선그래프, 박스플롯 등도
모두 이 matplotlib 라이브러리를 사용해서 그릴 수 있습니다.

 

 

 

 

 

 

 

2. 워드클라우드 데이터 살펴보기

 

 

 

워드 클라우드를 그릴 Train_data 입니다. (지난 포스팅에서 만들어 둔 것입니다.)
긍정리뷰와 부정리뷰를 모두 포함하고 있고, 총 25,000개의 행을 가지고 있습니다.

 

 

 

 

 

 

 

3. join 함수 이해하기

 

 

 

O Captain! my Captain! our fearful trip is done.
The ship has weather'd every rack, the prize we sought is won.

오 선장님! 나의 선장님! 우리의 두려운 여정은 끝났고,
배는 그 어떠한 비바람도 견뎠으며, 우리가 얻고자 한 상품을 쟁취했습니다.

- 월트 휘트먼의 소설 『풀잎』 中 -
join 함수를 설명하기 위해서, 월트 휘트먼의 소설 『풀잎』에서 3 문장을 가져왔습니다.
죽은시인의 사회라는 영화에서도 등장하는 꽤 유명한 문장이죠.

이제 이 3문장을 가지고 판다스 데이터 프레임을 만들어보도록 하겠습니다.

 

 

 

 

 

a = 'O Captain! my Captain!'
b = 'our fearful trip is done.'
c = "The ship has weather'd every rack, the prize we sought is won"

test_data = {}
test_data['sentence'] = []

test_data['sentence'].append(a)
test_data['sentence'].append(b)
test_data['sentence'].append(c)

test_data = pd.DataFrame.from_dict(test_data)
test_data
output:
                                            sentence
0                             O Captain! my Captain!
1                          our fearful trip is done.
2  The ship has weather'd every rack, the prize w...
위와 같이 코드를 짜주면, 3문장이 담긴 데이터프레임이 하나 만들어 집니다.
이제 이것들을 join 함수를 이용해서 하나로 통합해주겠습니다.

 

 

 

 

 

' '.join(test_data['sentence'])
output:
"O Captain! my Captain! our fearful trip is done. The ship has weather'd every rack, 
the prize we sought is won"
공백(' ')에 조인함수를 붙이면, 위와 같은 결과물을 얻을 수 있습니다.
이 공백의 의미는 구분자를 공백으로 해주겠다는 뜻 입니다.
이해를 돕기 위해 아래와 같이 % 문자열에 join을 적용해 보겠습니다.

 

 

 

 

 

'%'.join(test_data['sentence'])
output:
"O Captain! my Captain!%our fearful trip is done.%The ship has weather'd every rack,
the prize we sought is won"
문장 중간중간에 %가 들어간 것이 보이시나요?
이런 식으로 구분자와 join 함수를 통해, 문장들을 하나로 통합할 수 있습니다.

이해를 돕기 위해 %를 사용한 것 이니,
다음 코드부터는 정상적으로 공백 구분자로 진행하겠습니다.

 

 

 

 

 

 

 

4. 워드클라우드 그리기

 

 

 

wordcloud = WordCloud(stopwords = STOPWORDS,
                      background_color = 'black', #배경색
                      width = 800,
                      height = 600).generate(' '.join(train_data['txt']))
                      
plt.figure(figsize = (15, 10)) # (가로인치, 세로인치)
plt.axis("off") # 축눈금 제거
plt.imshow(wordcloud) # 이미지가 표시되도록
plt.show() # 최종 출력문
위와 같이 입력해하여,
워드 클라우드 객체 생성 후 워드 클라우드를 그려줍니다.

stopwords불용어처리를 해주는 옵션이고, 아래에서 더 자세히 다루겠습니다.
background_color배경색을 지정해주는 옵션입니다.
width, height는 각각 워드클라우드의 가로길이, 세로길이를 나타내고 단위는 픽셀입니다.

.generate()를 통해 워드클라우드 객체에 데이터를 넣을 수 있습니다.
괄호 안에는 공백과 join 함수로 만든 통합문이 들어가게 됩니다.
(위에서 자세히 설명 드렸습니다.)

plt라고 되어 있는 부분들은 모두 워드클라우드를 그려주기 위한 부분입니다.
설명은 코드주석으로 대신하겠습니다.

 

 

 

 

 

생성된 워드클라우드의 모습입니다.
아직 다듬어야 할 부분이 많지만, 그래도 아름답죠? ㅎㅎ

 

 

 

 

 

 

 

+ Stopwords 자세히 알아보기

 

 

 

stopwords = set(STOPWORDS)
stopwords
output:
{'a',
 'about',
 'above',
 ...
 'yours',
 'yourself',
 'yourselves'}
stopwords는 앞에서도 말씀드렸다시피, 불용어를 의미합니다.
불용어는 검색어로 잘 쓰이지 않는 단어를 뜻합니다. 즉, 쓸데없어서 삭제해야 하는 단어이지요.

위의 결과에서도 확인하실 수 있듯이, 별로 큰 의미를 갖지 못하는 단어들이 나열되어 있습니다.
여기에는 전치사, 관사, 관계대명사, 지시대명사 등이 포함이 됩니다.

WordCloud 객체에서는 Stopwords 옵션이 있는데,
stopwords = STOPWORDS 라고 해주면 자동으로 불용어를 제거한 후 출력을 해줍니다.

 

 

 

 

 

네 이렇게 해서 오늘은 기본적인 워드클라우드 그리는 법에 대해 알아보았습니다.
다음 시간에는 html 태그제거와 stopwords 업데이트 방법 등에 대해 알아보겠습니다.

위의 워드클라우드를 보면 br이라고 되어 있는 것들이 유독 눈에 많이 들어옵니다.
이는 html의 <br> 태그인데요, 키보드의 엔터키처럼 줄을 바꿔주는 기능을 합니다.
이런 단어들은 제거를 해주는 것이 당연하겠지요?
또한 불용어 사전을 업데이트하는 방법도 다음 포스팅에서 다루어볼 생각입니다.

오늘 포스팅은 여기까지 하겠습니다.
감사합니다.

 

반응형
블로그 이미지

pychan

딥러닝에 관련된 시행착오, 사소하지만 중요한 것들, 가능한 모든 여정을 담았습니다.

,

안녕하세요! 은공지능 공작소의 파이찬입니다.
오늘은 파이썬으로 IMDB 영화데이터를 다운받고 전처리해보겠습니다.
전처리 작업은 자연어 처리를 위한 라벨링까지 진행합니다.

 

 

 

 

 

https://www.imdb.com/

 

Ratings and Reviews for New Movies and TV Shows - IMDb

IMDb is the world's most popular and authoritative source for movie, TV and celebrity content. Find ratings and reviews for the newest movie and TV shows.

www.imdb.com

위의 사이트가 바로 IMDb 영화 리뷰 사이트입니다.
시간 되시는 분들은 접속하셔서 쓱~ 한번 둘러보시는 것도 좋습니다.
저희는 웹크롤링을 하면 좋겠지만... 스탠퍼드 대학교에서 데이터를 모아 둔 것이 있으므로,
이 데이터를 다운받아서 바로 사용하도록 하겠습니다 ^^

 

 

 

 

 

 

 

 

 

 

1. 데이터 다운받기

 

 

import os, re
import pandas as pd
import tensorflow as tf
from tensorflow.keras import utils

dataset = tf.keras.utils.get_file(
    fname='imdb.tar.gz', # 다운받은 압축파일의 이름
    origin = "http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz", # 링크주소
    extract = True) # 압축 해제 여부
우선 import 명령어를 사용해서 필요한 모듈을 다운받아 줍니다.
데이터셋을 다운받는 방법은 위와 같습니다.
자세한 설명은 코드 주석으로 대체하겠습니다.

Keras의 utils 함수는 웹 상의 데이터를 쉽게 다운로드 할 수 있도록 지원해줍니다.
링크 주소는 오타가 나기 쉬우니, 제 코드 복붙하시길 바랍니다.
(오른쪽 클릭하시고 복붙해가세요~! 오른쪽마우스 클릭방지 안 걸어 놨습니다 ㅎㅎ)

 

 

 

 

 

basic_dir = os.path.dirname(dataset)
print(basic_dir)
output:
C:\Users\white\.keras\datasets
이제 데이터를 다운받았으니, 데이터가 어느 경로에 있는지 확인이 필요합니다.
os.path.dirname 함수를 사용하면, 데이터의 경로를 확인할 수 있습니다.
한 번 직접 윈도우 탐색기를 통해서 폴더 구조를 살펴봅시다.

 

 

 

 

 

경로를 따라 직접 폴더에 들어가보시면, 위의 화면같은 창이 뜰 것입니다.
크게는 Train data와 Test data로 나뉘어져 있고,
그 안에는 Positive 리뷰와 negative 리뷰가 있습니다.
각각의 앞글자를 따서 폴더 이름은 pos, neg라고 되어 있을 것입니다.

 

 

 

 

 

test 폴더의 neg 폴더까지 들어가보았습니다. 여기서 txt 파일을 하나 열어봅시다.
저런 식으로 한 텍스트 파일 안에는 리뷰가 여러 문장으로 존재합니다.
그러니 코딩을 할 때, 크게 4 종류의 파일에 접근을 해야 하는 것입니다.
(train_pos, train_neg, test_pos, test_neg)

이러한 구조를 기억하면서 다음으로 넘어가겠습니다.

 

 

 

 

 

 

 

2. txt 파일을 가져와 데이터 담기

 

 

 

변수 리스트:
basic_dir = 'C:\Users\white\.keras\datasets'
os.path.join(basic_dir, 'aclImdb')
output:
'C:\\Users\\white\\.keras\\datasets\\aclImdb'
basic_dir 변수에는 dataset 폴더까지의 경로가 담겨 있습니다.
만일 여기서 한단계 하위 폴더에 접근하고 싶다면, os.path.join 함수를 쓰시면 됩니다.

os.path.join(basic_dir, 'aclImdb') 이라고 입력하시면,
그 데이터셋보다 한 단계 아래인 폴더에까지 접근이 가능합니다.
이렇게 os.path.join 함수는 디렉터리 경로를 조인해주는 역할을 합니다.

 

 

 

 

 

path_train_pos = os.path.join(basic_dir, 'aclImdb', 'train', 'pos')
path_train_pos
output:
'C:\\Users\\white\\.keras\\datasets\\aclImdb\\train\\pos'
이제 위와 같이 써주면, 텍스트파일이 있는 경로까지 접근이 가능합니다.
이 경로를 path_train_pos에 저장하도록 하겠습니다.

그렇다면 이제 각각의 텍스트 파일에 접근해야 하는 과제가 남았습니다.
이럴 때 사용하기 딱 좋은 함수가 하나 있습니다.

 

 

 

 

 

os.listdir(path_train_pos)
output:
['0_9.txt',
 '10000_8.txt',
 ...]
바로 os.listdir 함수입니다.
이 함수는 인자 경로에 있는 파일들을 리스트에 담아서 출력을 해줍니다.
그렇지 않으면 일일이 12,500개의 txt 파일을 손코딩 해야 할 수밖에요..

 

 

 

 

 

for i in os.listdir(path_train_pos):
    path = os.path.join(path_train_pos, i)
    print(path)
output:
C:\Users\white\.keras\datasets\aclImdb\train\pos\0_9.txt
C:\Users\white\.keras\datasets\aclImdb\train\pos\10000_8.txt
C:\Users\white\.keras\datasets\aclImdb\train\pos\10001_10.txt
...
이제 for 반복문으로 각각의 텍스트 파일의 경로들을 만들어 줍니다.
위와 같이 os.path.join을 쓰면, path_train_pos 경로의 모든 txt 파일경로를 출력할 수 있습니다.

 

 

 

 

 

for i in os.listdir(path_train_pos):
    path = os.path.join(path_train_pos, i)
#     print(path)
    rst = open(path, "r", encoding="UTF-8").read()
    print(rst+ '\n\n')
이제 각각의 txt 파일에 접근했으니, 해당 파일들을 읽어보는 작업을 해보겠습니다.
파일 읽는 법은 open().read()를 써주면 됩니다.

open 함수 안에는 ① 경로명, ② 모드, ③ 인코딩이 인자로 들어갑니다.
저는 open(path, "r", encoding = 'UTF-8') 이라고 했습니다.

코드의 의미는, ①경로명은 path 변수에 담긴 것으로 하겠다.
② 모드는 "r" 즉, reading 모드를 적용하겠다. (그밖에 w, a 모드가 있습니다.)
③ 인코딩은 UTF-8을 적용하겠다. 정도로 봐주시면 됩니다.

특히 인코딩 부분을 명시하지 않을 경우, 오류가 발생할 수 있으니
이 점 유의해주시길 바랍니다.

 

 

 

 

 

출력 결과는 위와 같습니다. print 문에 '\n\n'을 넣어서, 각각의 txt 파일을 구분했습니다.
출력 결과를 보시면 중간에 2칸의 공백줄이 추가된 것을 보실 수 있습니다.
이제 이러한 rst 결과물들을 딕셔너리에 담아보는 작업을 해보겠습니다.

 

 

 

 

 

 

# 먼저 데이터라는 딕셔너리를 하나 생성해줍니다.
data = {}

# 그리고 그 안에, txt라는 키를 가진 리스트를 하나 만들어 줄게요.
data['txt'] = []

for i in os.listdir(path_train_pos):
    path = os.path.join(path_train_pos, i)
    rst = open(path, "r", encoding="UTF-8").read()
    data['txt'].append(rst)
    # list.append()는 리스트에 데이터를 계속 추가해줍니다.
먼저 data라는 딕셔너리를 하나 만들어주고,
그 안에 'txt'라는 키를 가진 리스트를 생성해 줍니다.

그리고 for 반복문에서 계속 해당 리스트에
rst 결과물을 추가(append)해주는 구조입니다.

이런 식으로 train/pos 경로의 모든 텍스트 파일들을
data['txt']라는 리스트에 추가할 수 있습니다.

하지만 지금 작업은 train의 pos 폴더 내용물에만 해당되는 것입니다.
아직 train_neg, test_pos, test_neg 폴더의 작업이 남아 있습니다 ㅜㅜ
3번의 작업을 반복해야 하니, 이 쯤에서 함수를 하나 만들어 주는 것이 좋을 듯 합니다.

 

 

 

 

 

def make_dict(dir):
    data = {}
    data['txt'] = []

    for i in os.listdir(dir):
        path = os.path.join(dir, i)
        rst = open(path, "r", encoding="UTF-8").read()
        data['txt'].append(rst)
        
    return pd.DataFrame.from_dict(data)
위와 같이 dir를 인자로 받는 함수를 하나 만들어 줬습니다.
리턴값은 판다스 데이터 프레임으로 지정해줬습니다.
이제 이 함수를 이용해 4개 경로의 데이터를 담아보도록 하겠습니다.

 

 

 

 

 

train_pos = make_dict(os.path.join(basic_dir, 'aclImdb', 'train', 'pos'))
train_neg = make_dict(os.path.join(basic_dir, 'aclImdb', 'train', 'neg'))
test_pos = make_dict(os.path.join(basic_dir, 'aclImdb', 'test', 'pos'))
test_neg = make_dict(os.path.join(basic_dir, 'aclImdb', 'test', 'neg'))
이렇게 4개 변수에 만들어 준 함수를 적용하면, 각각 경로의 데이터를 모두 담을 수 있습니다.
이제 이 4개 데이터에 라벨링을 해주고, 
데이터를 통합해주는 일만 남았습니다.

 

 

 

 

 

 

 

3. 데이터 라벨링하고 통합해주기

 

 

 

train_pos['label'] = 1
train_neg['label'] = 0
test_pos['label'] = 1
test_neg['label'] = 0
라벨링을 해주는 코드입니다.
긍정적인 리뷰 데이터는 1 이라는 라벨을 달아주고,
부정적인 리뷰 데이터는 0 이라는 라벨을 달았습니다.

라벨링은 위와 같이 컴퓨터가 이해하기 쉽도록, 꼬리표(label)를 달아주는 작업입니다.
주로 머신러닝의 지도학습에 사용할 용도로 라벨링을 해줍니다.

 

 

 

 

 

train_data = pd.concat([train_pos, train_neg])
test_data = pd.concat([test_pos, test_neg])
마지막으로 데이터를 통합해줍니다.
pd.concat 함수를 사용했습니다.

pd.concat은 데이터를 세로로 통합해주는 역할을 합니다.
행과 열이 있는 데이터에서, 열은 고정해두고 행을 통합한다는 의미입니다.

 

 

 

 

 

test_data를 출력해본 모습입니다.
1 라벨과 0 라벨이 모두 잘 달린 25,000개의 데이터를 확인하실 수 있습니다.

네 이렇게 오늘은 IMDb 영화데이터를 전처리하고 라벨링하는 작업까지 해보았습니다.
쓰고나니 너무 장황하게 설명해놓은 감이 있네요 ㅜㅜ
원래 제가 좀 TMI 기질이 다분히 있어서.. ㅋㅋ 모쪼록 긴 글 읽어주셔서 감사합니다.

 

 

반응형
블로그 이미지

pychan

딥러닝에 관련된 시행착오, 사소하지만 중요한 것들, 가능한 모든 여정을 담았습니다.

,