텍스트전처리와 시각화

taetae·2023년 2월 15일

단어빈도그래프와 워드클라우드

텍스트를 전처리하고 단어빈도그래프와 다양한 형태의 워드클라우드로 나타내보자.

단어빈도 그래프

그래프를 그리기전, 문서들로부터 각 단어들을 분리하는 텍스트 전처리를 거쳐야한다.

(토큰화, 어간추출, 불용어) 등 이용

실습할 문서

  • 구텐베르크 프로젝트 에 들어가면 60,000개의 무료 eBook을 다운받아 볼 수 있다.
import nltk
nltk.download("gutenberg")

from nltk.corpus import gutenberg
file_names = gutenberg.fileids()
print(file_names)

>>>
['austen-emma.txt', 'austen-persuasion.txt', 'austen-sense.txt', 'bible-kjv.txt', 'blake-poems.txt', 'bryant-stories.txt', 'burgess-busterbrown.txt', 'carroll-alice.txt', 'chesterton-ball.txt', 'chesterton-brown.txt', 'chesterton-thursday.txt', 'edgeworth-parents.txt', 'melville-moby_dick.txt', 'milton-paradise.txt', 'shakespeare-caesar.txt', 'shakespeare-hamlet.txt', 'shakespeare-macbeth.txt', 'whitman-leaves.txt']

책들을 다운받고 책 제목을 확인해보는 코드

auseten이라는 책의 텍스트를 이용해 진행!

doc_auseten = gutenberg.open("austen-persuasion.txt").read()
print("#Num of charaters used: ", len(doc_auseten))
print("#Text sample:")
print(doc_auseten[:500])

>>>
#Num of charaters used:  466292
#Text sample:
[Persuasion by Jane Austen 1818]

Chapter 1

Sir Walter Elliot, of Kellynch Hall, in Somersetshire, was a man who,
for his own amusement, never took up any book but the Baronetage;
there he found occupation for an idle hour, and consolation in a
distressed one; there his faculties were roused into admiration and
respect, by contemplating the limited remnant of the earliest patents;
there any unwelcome sensations, arising from domestic affairs
changed naturally into pity and contempt as he turn

NLTK이용해 토큰화 진행하기

from nltk.tokenize import word_tokenize
tokens_auseten = word_tokenize(doc_auseten)

print("#Num of tokens used")
print("#Tokens sample")
print(tokens_auseten[:20])

>>> #Num of tokens used 97918
#Tokens sample: 
['[', 'Persuasion', 'by', 'Jane', 'Austen', '1818', ']', 'Chapter', '1', 'Sir', 'Walter', 'Elliot', ',', 'of', 'Kellynch', 'Hall', ',', 'in', 'Somersetshire', ',']

auseten에 대한 단어를 토큰화하고 토큰 수와 앞 20개의 토큰 확인

포터 스테머로 스태밍하고 토큰 수 와 앞 20개의 토큰 확인

stemming = 어간 추출

from nltk.stem import PorterStemmer
stemmer = PorterStemmer()

# 모든 토큰에 대해 스테밍 실행
stem_tokens_auseten = [stemmer.stem(token) for token in tokens_auseten ]
# 모든 tokens_auseten에 들어있는 token을 반복문으로 가져와서 stemmer.stem에 넣어 스태밍을 실시한다.

print('#Num of tokens after stemmming: ', len(stem_tokens_auseten))
print("#Token Sample: ")
print(stem_tokens_auseten[:20])

>>> #Num of tokens after stemmming:  97918
#Token Sample: 
['[', 'persuas', 'by', 'jane', 'austen', '1818', ']', 'chapter', '1', 'sir', 'walter', 'elliot', ',', 'of', 'kellynch', 'hall', ',', 'in', 'somersetshir', ',']

그 결과 , 나온 결과 값의 예를 들자면

['[', 'Persuasion', 'by', 'Jane', 'Austen', '1818', ']', 'Chapter', '1', 'Sir', 'Walter', 'Elliot', ',', 'of', 'Kellynch', 'Hall', ',', 'in', 'Somersetshire', ',']

['[', 'persuas', 'by', 'jane', 'austen', '1818', ']', 'chapter', '1', 'sir', 'walter', 'elliot', ',', 'of', 'kellynch', 'hall', ',', 'in', 'somersetshir', ',']

  1. Persuasion → persuas
  2. Austen → austen
  3. Somersetshire → somersetshir

이런식으로 변환이 됨을 볼 수 있다.

WordNetLemmatizer을 이용해 표제어 추출

WordNetLemmatizer 는 입력으로 단어가 동사, 품사라는 사실을 알려줄 수 있음

ex) dies , watched, has 가 문장에서 동사로 쓰였다는 것을 알려준다면 표제어 추출기는 품사의 정보를 보존하면서 정확한 Lemma를 출력하게 됩니다.

from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
lem_tokens_auseten = [ lemmatizer.lemmatize(token) for token in tokens_auseten ]

print('#Num of tokens after stemmming: ', len(stem_tokens_auseten))
print("#Token Sample: ")
print(stem_tokens_auseten[:20])

>>> #Num of tokens after stemmming:  97918
#Token Sample: 
['[', 'persuas', 'by', 'jane', 'austen', '1818', ']', 'chapter', '1', 'sir', 'walter', 'elliot', ',', 'of', 'kellynch', 'hall', ',', 'in', 'somersetshir', ',']
💡 결과를 종합적으로 확인했을때, 토큰 수는 변화 없다!

why? 토큰화된 결과에 대해 개별적으로 어간 추출과 표제어 추출을 수행하기 때문이다.

정규표현식을 이용해 토큰화를 하고 결과를 비교해보자

정규표현식 후, 토큰화하기

from nltk.tokenize import RegexpTokenizer
tokenizer  = RegexpTokenizer("[\w']{3,}")
reg_tokens_auseten = tokenizer.tokenize(doc_auseten.lower())

# print(reg_tokens_auseten)
print('#Num of tokens with RegexpTokenizer: ', len(reg_tokens_auseten))
print("#Token Sample: ")
print(reg_tokens_auseten[:20])

>>> #Num of tokens with RegexpTokenizer:  65707
#Token Sample: 
['persuasion', 'jane', 'austen', '1818', 'chapter', 'sir', 'walter', 'elliot', 'kellynch', 'hall', 'somersetshire', 'was', 'man', 'who', 'for', 'his', 'own', 'amusement', 'never', 'took']

wordTokenizer을 사용했을때보다 토큰 수가 현저히 줄어들었음을 확인 할 수 잇다.

‘[’]의 부호와 2자 이하의 글자들이 모두 제외되었기 때문이다.

여기서 생각해봐야할것 : 우리가 진행할 목적이 그래프를 이용한 시각화이기 때문에 단어를 알아보기 쉽게 시각화시켜야 한다. 따라서 stemming은 사용하지 않도록 !!!

영어에서 불용어 제거하기

from nltk.corpus import stopwords
english_stops = set(stopwords.words('english')) # 반복되지 않도록 set로 변환 -> 자동 중복 제거

# stopwords를 제외한 단어들만으로 리스트를 생성하기

result_auseten = [ word for word in reg_tokens_auseten if word not in english_stops ]

print('#Num of tokens after stopword elimination : ', len(result_auseten))
print("#Token Sample: ")
print(result_auseten[:20])

>>> #Num of tokens after stopword elimination :  37883
#Token Sample: 
['persuasion', 'jane', 'austen', '1818', 'chapter', 'sir', 'walter', 'elliot', 'kellynch', 'hall', 'somersetshire', 'man', 'amusement', 'never', 'took', 'book', 'baronetage', 'found', 'occupation', 'idle']

불용어 제거 후, 단어가 더 감소함을 확인할 수 있다.

빈도수 순서대로 딕셔너리에 저장

auseten_word_count = dict()
for word in result_auseten:
  auseten_word_count[word] = auseten_word_count.get(word,0)+1
print('#Num of use words : ', len(auseten_word_count))

sorted_word_count = sorted(auseten_word_count, key=auseten_word_count.get, reverse = True)
print("#Top 20 high frequency: ")

for key in sorted_word_count[:20]:
  print(f'{repr(key)}: {auseten_word_count[key]}', end=",")

auseten_word_count 의 dict객체 생성

result_auseten 안의 단어들을 받고 딕셔너리에 저장

  • dictionary.get(keyname, value) key가 존재 않는 경우 반환 값. (기본값: None)

딕셔너리로 단어별 개수를 세고, 빈도가 큰 순으로 정리하기

빈도수 상위 단어를 봤을때, could와 같은 딱히 필요없는 조동사와 같은 단어가 있음… → 품사 태깅을 진행하는 것이 필요할 것으로 보임!

my_tag_set = ["NN","VB", "VBD","JJ"]
my_words = [word for word, tag in nltk.pos_tag(result_auseten) if tag in my_tag_set]

auseten_word_count = dict()
for word in my_words:
  auseten_word_count[word] = auseten_word_count.get(word,0)+1

# 정렬하기

sorted_word_count = sorted(auseten_word_count, key=auseten_word_count.get, reverse = True)

# 빈도수 상위 20개의 단어를 출력

for key in sorted_word_count[:20]:
  print(f'{repr(key)}: {auseten_word_count[key]}', end=",")

>>>'anne': 327,'captain': 232,'mrs': 230,'elliot': 205,'lady': 202,'good': 187,'wentworth': 176,'said': 173,'little': 168,'time': 152,'nothing': 139,'sir': 135,'great': 130,'man': 127,'walter': 117,'much': 117,'mary': 111,'miss': 109,'house': 94,'last': 88,

각각 NN, VB, VBD, JJ 의 태크 셋을 형성하고 단어 태깅하기

정렬후 단어를 출력하고 matplotlib.pyplot을 이용해서 그래프를 그린다 !

import matplotlib.pyplot as plt
%matplotlib inline

# 정렬된 단어 리스트에 대해 빈도수를 가져와서 리스트 생성
w = [auseten_word_count[key] for key in sorted_word_count]
plt.plot(w)
plt.show()

출력 결과

지프의 법칙이 보임

  • 지프의 법칙 빈도수에 따라 정렬된 단어의 순위와 빈도수가 극단적으로 반비례함을 보여줌.

빈도수 기반 막대그래프

n = sorted_word_count[:20][::-1]

# print(type(sorted_word_count))
# 20개의 단어에 대한 빈도수 체크

w = [auseten_word_count[key] for key in n]
plt.barh(range(len(n)),w,tick_label = n)
plt.show()

워드 클라우드 시각화 결과

기본 install

!pip install wordcloud

from wordcloud import WordCloud

wordcloud = WordCloud().generate(doc_auseten)

plt.axis("off")
plt.imshow(wordcloud, interpolation='bilinear')
plt.show()

텍스트 데이터를 전처리한 워드클라우드 완성

generate_from_frequencies() 메서드 사용한 워드클라우드

wordcloud = WordCloud(max_font_size = 60).generate_from_frequencies(auseten_word_count)
plt.figure()
plt.axis("off")
plt.imshow(wordcloud, interpolation = "bilinear")
plt.show()

generate_from_frequencies() 메서드를 이용하면 계산된 빈도를 이용해 워드클라우드를 그릴 수 있다.

이미지 위에 워드클라우드 그리기

import numpy as np
from PIL import Image

alice_mask = np.array(Image.open("alice_mask.png")) 
wc = WordCloud(background_color="white",
               max_words=30, 
               mask=alice_mask, 
               contour_width=3, 
               contour_color='steelblue') 

wc.generate_from_frequencies(auseten_word_count)

wc.to_file("alice.png") 

# 결과 그리기
plt.figure()
plt.axis("off")
plt.imshow(wc, interpolation='bilinear')
plt.show()

< konlpy install >

!apt-get update !apt-get install g++ openjdk-8-jdk !pip install konlpy JPype1-py3 !bash <(curl -s https://raw.githubusercontent.com/konlpy/konlpy/master/scripts/mecab.sh)

한국어 문서에 대한 그래프와 워드클라우드

from konlpy.corpus import kolaw
const_doc = kolaw.open('constitution.txt').read()

print(type(const_doc))
print(len(const_doc))
print(const_doc[:600])

>>>
<class 'str'>
18884
대한민국헌법

유구한 역사와 전통에 빛나는 우리 대한국민은 3·1운동으로 건립된 대한민국임시정부의 법통과 불의에 항거한 4·19민주이념을 계승하고, 조국의 민주개혁과 평화적 통일의 사명에 입각하여 정의·인도와 동포애로써 민족의 단결을 공고히 하고, 모든 사회적 폐습과 불의를 타파하며, 자율과 조화를 바탕으로 자유민주적 기본질서를 더욱 확고히 하여 정치·경제·사회·문화의 모든 영역에 있어서 각인의 기회를 균등히 하고, 능력을 최고도로 발휘하게 하며, 자유와 권리에 따르는 책임과 의무를 완수하게 하여, 안으로는 국민생활의 균등한 향상을 기하고 밖으로는 항구적인 세계평화와 인류공영에 이바지함으로써 우리들과 우리들의 자손의 안전과 자유와 행복을 영원히 확보할 것을 다짐하면서 1948712일에 제정되고 8차에 걸쳐 개정된 헌법을 이제 국회의 의결을 거쳐 국민투표에 의하여 개정한다.1장 총강
  제1조 ① 대한민국은 민주공화국이다.
②대한민국의 주권은 국민에게 있고, 모든 권력은 국민으로부터 나온다.2조 ① 대한민국의 국민이 되는 요건은 법률로 정한다.
②국가는 법률이 정하는 바에 의하여 재외국민을 보호할 의무를 진다.3조 대한민

형태소 단위로 토크나이즈

from konlpy.tag import Okt

t = Okt()
tokens_const = t.morphs(const_doc) 
print('#토큰의 수:', len(tokens_const))
print('#앞 100개의 토큰')
print(tokens_const[:100])

>>>
#토큰의 수: 3882
#앞 100개의 토큰
['대한민국', '헌법', '유구', '역사', '전통', '우리', '국민', '운동

형태소 단위로 토크나이즈 후 명사만 추출


tokens_const = t.nouns(const_doc) 
print('#토큰의 수:', len(tokens_const))
print('#앞 100개의 토큰')
print(tokens_const[:100])

>>> #토큰의 수: 3882
#앞 100개의 토큰
['대한민국', '헌법', '유구', '역사', '전통', '우리', '국민', '운동'

토큰화할때 길이가 1 이상인 단어들만 저장하기


tokens_const = [token for token in tokens_const if len(token) > 1]
print('#토큰의 수:', len(tokens_const))
print('#앞 100개의 토큰')
print(tokens_const[:100])

>
#토큰의 수: 3013
#앞 100개의 토큰
['대한민국', '헌법', '유구', '역사', '전통', '우리', '국민', '운동

!apt-get update -qq !apt-get install fonts-nanum* -qq FONT_PATH = "/usr/share/fonts/truetype/nanum/NanumGothic.ttf"

나눔 고딕 install 및 폰트 경로 지정하기

from matplotlib import font_manager, rc
import platform
!pip install koreanize-matplotlib
import koreanize_matplotlib

라이브러리 불러오기

font_name = font_manager.FontProperties(fname=FONT_PATH).get_name()
rc('font', family=font_name)

const_cnt = {}
for word in tokens_const:
    const_cnt[word] = const_cnt.get(word, 0) + 1

def word_graph(cnt, max_words=10):
    
    sorted_w = sorted(cnt.items(), key=lambda kv: kv[1])
    print(sorted_w[-max_words:])
    n, w = zip(*sorted_w[-max_words:])

    plt.barh(range(len(n)),w,tick_label=n)
    plt.show()

word_graph(const_cnt, max_words=20)

결과 빈도수 순서대로 막대그래프 만들기 barh

<한글 그래프에 출력 안돼…. ㅠㅠㅠ>

font_path = FONT_PATH
wordcloud = WordCloud(font_path = font_path).generate(const_doc)

plt.axis("off")
plt.imshow(wordcloud, interpolation='bilinear')
plt.show()

기본 워드 클라우드 생성

내가 원하는 색깔 크기의 워드클라우드 생성

profile
interested in Data Science & AI

0개의 댓글