⛄ 단어 빈도 그래프
텍스트 분석에서 가장 단순하고 기본적인 아이디어는 하나 혹은 여러 개의 문서에서 가장 많이 사용된 단어를 파악하는 것으로, 이것만으로도 상당히 많은 정보를 얻을 수 있다.
단어 빈도 그래프를 그리려면 먼저 단어의 빈도를 구해야 한다. 이 작업을 하기 전 전처리 단계에서 앞서 배운 다양한 전처리 기법들을 활용한다.
데이터셋은 저작권이 만료된 영어 소설들을 제공하는 구텐베르크 프로젝트가 있는데, 여기서 루이스 캐럴의 이상한 나라의 앨리스 문서를 활용한다.
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']
[nltk_data] Downloading package gutenberg to /root/nltk_data...
[nltk_data] Unzipping corpora/gutenberg.zip.
gutenberg.open()으로 파일 제목을 넣어 해당 파일을 열 수 있으며, read()로 그 내용을 읽어온다.
doc_alice = gutenberg.open('carroll-alice.txt').read()
#사용된 문자의 수
print('#Num of characters used: ', len(doc_alice))
print('#Text sample:')
#앞의 500자만 출력
print(doc_alice[:500])
✨ 실행결과
#Num of characters used: 144395
#Text sample:
[Alice's Adventures in Wonderland by Lewis Carroll 1865] CHAPTER I. Down the Rabbit-Hole Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice 'without pictures or conversation?' So she was considering in her own mind (as well as she could, for the hot day made her feel very sleepy an
이제 NLTK를 이용해 토큰화를 진행한다.
#토큰화
nltk.download('punkt')
from nltk.tokenize import word_tokenize
#토큰화 실행
tokens_alice = word_tokenize(doc_alice)
print('#Num of tokens used:', len(tokens_alice))
print('#Token sample:')
print(tokens_alice[:20])
✨ 실행결과
[nltk_data] Downloading package punkt to /root/nltk_data...
[nltk_data] Package punkt is already up-to-date!
#Num of tokens used: 33494
#Token sample:
['[', 'Alice', "'s", 'Adventures', 'in', 'Wonderland', 'by', 'Lewis', 'Carroll', '1865', ']', 'CHAPTER', 'I', '.', 'Down', 'the', 'Rabbit-Hole', 'Alice', 'was', 'beginning']
포터 스테머로 스태밍하기
#포터 스테머로 stemming
from nltk.stem import PorterStemmer
stemmer = PorterStemmer()
#모든 토큰에 대해 스테밍 실행
stem_tokens_alice = [stemmer.stem(token) for token in tokens_alice]
print('#Num of tokens after stemming:', len(stem_tokens_alice))
print('#Token sample:')
print(stem_tokens_alice[:20])
✨ 실행결과
#Num of tokens after stemming: 33494
#Token sample:
['[', 'alic', "'s", 'adventur', 'in', 'wonderland', 'by', 'lewi', 'carrol', '1865', ']', 'chapter', 'i', '.', 'down', 'the', 'rabbit-hol', 'alic', 'wa', 'begin']
WordNetLemmatizer를 이용해 표제어 추출
#WordNetLemmatizer 로 표제어 추출
nltk.download('wordnet')
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
#모든 토큰에 대해 스테밍 시행
lem_tokens_alice = [lemmatizer.lemmatize(token) for token in tokens_alice]
print('#Num of tokens after lemmatization:', len(lem_tokens_alice))
print('#Token sample:')
print(lem_tokens_alice[:20])
✨ 실행결과
#Num of tokens after lemmatization: 33494
#Token sample:
['[', 'Alice', "'s", 'Adventures', 'in', 'Wonderland', 'by', 'Lewis', 'Carroll', '1865', ']', 'CHAPTER', 'I', '.', 'Down', 'the', 'Rabbit-Hole', 'Alice', 'wa', 'beginning']
[nltk_data] Downloading package wordnet to /root/nltk_data...
[nltk_data] Package wordnet is already up-to-date!
이번에는 정규표현식을 이용해 토큰화를 진행해보자.
#정규표현식으로 토큰화 진행
from nltk.tokenize import RegexpTokenizer
tokenizer = RegexpTokenizer("[\w']{3,}")
reg_tokens_alice = tokenizer.tokenize(doc_alice.lower())
print('#Num of tokens with RegexpTokenizer:', len(reg_tokens_alice))
print('#Token sample:')
print(reg_tokens_alice[:20])
✨ 실행결과
#Num of tokens with RegexpTokenizer: 21616
#Token sample:
["alice's", 'adventures', 'wonderland', 'lewis', 'carroll', '1865', 'chapter', 'down', 'the', 'rabbit', 'hole', 'alice', 'was', 'beginning', 'get', 'very', 'tired', 'sitting', 'her', 'sister']
어떤 토크나이저를 사용할지는 사용자의 판단에 따라 다르다.
여기서는 정규표현식을 활용한 토크나이저의 결과를 사용하기로 하고, 불용어를 제거한다.
또한 분석 목적이 그래프를 이용한 시각화이므로 단어를 알아보기 쉽도록 스테밍은 하지 않는다.
#불용어 제거
nltk.download('stopwords')
#일반적으로 분석대상이 아닌 단어들
from nltk.corpus import stopwords
#반복이 되지 않도록 set으로 변환
english_stops = set(stopwords.words('english'))
#stopwords를 제외한 단어들만으로 list를 생성
result_alice = [word for word in reg_tokens_alice if word not in english_stops]
print('#Num of tokens after stopword elimination:', len(result_alice))
print('#Token sample:')
print(result_alice[:20])
✨ 실행결과
#Num of tokens after stopword elimination: 12999
#Token sample:
["alice's", 'adventures', 'wonderland', 'lewis', 'carroll', '1865', 'chapter', 'rabbit', 'hole', 'alice', 'beginning', 'get', 'tired', 'sitting', 'sister', 'bank', 'nothing', 'twice', 'peeped', 'book']
[nltk_data] Downloading package stopwords to /root/nltk_data... [nltk_data] Package stopwords is already up-to-date!
텍스트 전처리 완료!
각 단어별 빈도를 계산한다.
#각 단어별 빈도를 계산
alice_word_count = dict()
for word in result_alice:
alice_word_count[word] = alice_word_count.get(word,0) + 1
print('#Num of used words:', len(alice_word_count))
sorted_word_count = sorted(alice_word_count, key=alice_word_count.get, reverse = True)
print("#Top 20 high frequency words:")
#빈도수 상위 20개의 단어를 출력
for key in sorted_word_count[:20]:
print(f'{repr(key)}: {alice_word_count[key]}', end = ',')
✨ 실행결과
#Num of used words: 2687
#Top 20 high frequency words:
'said': 462,'alice': 385,'little': 128,'one': 98,'know': 88,'like': 85,'went': 83,'would': 78,'could': 77,'thought': 74,'time': 71,'queen': 68,'see': 67,'king': 61,'began': 58,'turtle': 57,"'and": 56,'way': 56,'mock': 56,'quite': 55,
빈도가 높은 상위 20개의 단어를 보면 would, could, and와 같은 단어는 딱히 필요하지 않아 보인다.
따라서 의미가 있을 것으로 생각되는 명사, 동사, 형용사만 추출해보자.
#명사, 동사, 형용사만 추출
nltk.download('averaged_perceptron_tagger')
my_tag_set = ['NN', 'VB', 'VBD', 'JJ']
my_words = [word for word, tag in nltk.pos_tag(result_alice) if tag in my_tag_set]
print(my_words[:20])
alice_word_count = dict()
for word in my_words:
alice_word_count[word] = alice_word_count.get(word, 0) + 1
print('#Num of used words:', len(alice_word_count))
sorted_word_count = sorted(alice_word_count, key = alice_word_count.get, reverse = True)
print('#Top 20 high frequency words:')
#빈도수 상위 20개의 단어를 출력
for key in sorted_word_count[:20]:
print(f'{repr(key)}: {alice_word_count[key]}', end=',')
✨ 실행결과
[nltk_data] Downloading package averaged_perceptron_tagger to [nltk_data] /root/nltk_data...
[nltk_data] Package averaged_perceptron_tagger is already up-to-
[nltk_data] date!
["alice's", 'carroll', 'chapter', 'rabbit', 'hole', 'alice', 'get', 'tired', 'sister', 'bank', 'nothing', 'peeped', 'book', 'sister', 'reading', 'book', 'thought', 'alice', 'mind', 'well']
#Num of used words: 1726
#Top 20 high frequency words:
'said': 462,'alice': 293,'little': 124,'went': 83,'time': 71,'queen': 66,'began': 58,'way': 56,'turtle': 56,'mock': 55,'thought': 54,'thing': 49,'voice': 48,'head': 46,'gryphon': 45,'got': 44,'rabbit': 42,'looked': 42,'see': 42,'came': 40,
이제 그래프를 이용해 시각화를 진행하자.
시각화를 위한 도구로는 파이썬의 matplotlib 라이브러리를 사용한다.
#시각화
import matplotlib.pyplot as plt
#정렬된 단어 리스트에 대해 빈도수를 가져와서 리스트 생성
w = [alice_word_count[key] for key in sorted_word_count]
plt.plot(w)
plt.show
✨ 실행결과
위 그래프만으로 정보를 파악하긴 쉽지 않다.
더 정확한 정보들을 파악하기 위해서는 그래프도 상위 빈도수 단어에 대해서만 그려야 한다.
#빈도수 상위 20개의 단어를 추출하여 역순으로 정렬
n = sorted_word_count[:20][::-1]
#추출된 단어에 대해 빈도를 추출
w = [alice_word_count[key] for key in n]
#수평 막대 그래프
plt.barh(range(len(n)), w, tick_label = n)
plt.show()
✨ 실행결과
⛄ 워드 클라우드로 한눈에 보기
워드 클라우드는 빈도가 높은 단어는 크게, 낮은 단어는 작게 보여줌으로써 한눈에 전체적인 현황을 파악할 수 있게 해주는 시각화 도구이다.
우선 라이브러리를 임포트하고 객체를 만든 후 generate()를 호출하면 된다.
이때 generate()의 인수로 분석하고자 하는 텍스트를 넘겨주면 된다.
from wordcloud import WordCloud
#Generate a word cloud image
wordcloud = WordCloud().generate(doc_alice)
plt.axis("off")
plt.imshow(wordcloud, interpolation='bilinear') #이미지 출력
plt.show()
✨ 실행결과
이전에 전처리를 통해 빈도를 계산했던 딕셔너리를 활용하여 워드클라우드를 만들 수 있다.
빈도 수가 높은 단어와 낮은 단어의 폰트 크기가 너무 차이가 난다면 wordCloud의 max_font_size로 최대 폰트 크기를 조절할 수 있다.
wordcloud = WordCloud(max_font_size=60).generate_from_frequencies(alice_word_count)
plt.figure()
plt.axis("off")
plt.imshow(wordcloud, interpolation="bilinear")
plt.show()
✨ 실행결과
또한, 이미지 위에 워드클라우드를 출력할 수 있다. 이미지를 불러와서 워드 클라우드 객체를 만들 때 mask 매개변수에 할당해주면 된다.
#이미지 적용
import numpy as np
from PIL import Image
#파일 업로드
from google.colab import files
f = files.upload()
#배경이미지를 불러와서 numpy array로 변환
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(alice_word_count)
#결과를 이미지 파일로 저장
wc.to_file("alice.png")
#화면에 결과를 출력
plt.figure()
plt.axis("off")
plt.imshow(wc, interpolation='bilinear')
plt.show()
✨ 실행결과
⛄ 한국어 문서에 대한 그래프와 워드 클라우드
아래 코드는 코랩에서 한국어 폰트 적용을 위한 설치 코드이다.
해당 코드를 실행한 후 런타임 → 런타임 다시 시작을 한 후 KoNLPy 설치 코드로 넘어가면 된다.
!sudo apt-get install -y fonts-nanum
!sudo fc-cache -fv
!rm ~/.cache/matplotlib -rf
KoNLPy에서 헌법 텍스트를 불러오고 타입과 문자 수 앞 일부분 등을 확인해본다.
먼저 KoNLPy를 설치한다.
!pip install konlpy
from konlpy.corpus import kolaw
const_doc = kolaw.open('constitution.txt').read()
#가져온 데이터의 type을 확인
print(type(const_doc))
print(len(const_doc))
print(const_doc[:600])
✨ 실행결과
<class 'str'>
18884
대한민국헌법
유구한 역사와 전통에 빛나는 우리 대한국민은 3·1운동으로 건립된 대한민국임시정부의 법통과 불의에 항거한 4·19민주이념을 계승하고, 조국의 민주개혁과 평화적 통일의 사명에 입각하여 정의·인도와 동포애로써 민족의 단결을 공고히 하고, 모든 사회적 폐습과 불의를 타파하며, 자율과 조화를 바탕으로 자유민주적 기본질서를 더욱 확고히 하여 정치·경제·사회·문화의 모든 영역에 있어서 각인의 기회를 균등히 하고, 능력을 최고도로 발휘하게 하며, 자유와 권리에 따르는 책임과 의무를 완수하게 하여, 안으로는 국민생활의 균등한 향상을 기하고 밖으로는 항구적인 세계평화와 인류공영에 이바지함으로써 우리들과 우리들의 자손의 안전과 자유와 행복을 영원히 확보할 것을 다짐하면서 1948년 7월 12일에 제정되고 8차에 걸쳐 개정된 헌법을 이제 국회의 의결을 거쳐 국민투표에 의하여 개정한다. 제1장 총강 제1조 ① 대한민국은 민주공화국이다. ②대한민국의 주권은 국민에게 있고, 모든 권력은 국민으로부터 나온다. 제2조 ① 대한민국의 국민이 되는 요건은 법률로 정한다. ②국가는 법률이 정하는 바에 의하여 재외국민을 보호할 의무를 진다. 제3조 대한민
앞서 배운 내용을 토대로 KoNLPy를 사용해 헌법 텍스트에 대해 형태소 분석을 진행하고 결과를 살펴본다.
# 형태소 분석 진행
from konlpy.tag import Okt
t = Okt()
# 형태소 단위로 tokenize
tokens_const = t.morphs(const_doc)
print('#토큰의 수:', len(tokens_const))
print('#앞 100개의 토큰')
print(tokens_const[:100])
✨ 실행결과
#토큰의 수: 8796
#앞 100개의 토큰
['대한민국', '헌법', '\n\n', '유구', '한', '역사', '와', '전통', '에', '빛나는', '우리', '대', '한', '국민', '은', '3', '·', '1', '운동', '으로', '건립', '된', '대한민국', '임시정부', '의', '법', '통과', '불의', '에', '항거', '한', '4', '·', '19', '민주', '이념', '을', '계승', '하고', ',', '조국', '의', '민주', '개혁', '과', '평화', '적', '통일', '의', '사명', '에', '입', '각하', '여', '정의', '·', '인도', '와', '동포', '애', '로써', '민족', '의', '단결', '을', '공고', '히', '하고', ',', '모든', '사회', '적', '폐습', '과', '불의', '를', '타파', '하며', ',', '자율', '과', '조화', '를', '바탕', '으로', '자유민주', '적', '기', '본', '질서', '를', '더욱', '확고히', '하여', '정치', '·', '경제', '·', '사회', '·']
\n\n을 비롯한 기호와 3, 1과 같은 숫자 그리고 와, 에, 은 등은 딱히 의미가 없는 문자들이다. 일반적으로 전처리를 진행할 때 어떤 품사를 선택할지에 대해서는 좀 더 면밀한 분석이 필요하지만, 보통 워드 클라우드를 그릴 때에는 명사만 사용하는 것이 일반적이다.
# 형태소 단위로 tokenize 후 명사만 추출
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개의 토큰
['대한민국', '헌법', '유구', '역사', '전통', '우리', '국민', '운동', '건립', '대한민국', '임시정부', '통과', '불의', '항거', '민주', '이념', '계승', '조국', '민주', '개혁', '평화', '통일', '사명', '각하', '정의', '인도', '동포', '로써', '민족', '단결', '공고', '모든', '사회', '폐습', '불의', '타파', '자율', '조화', '바탕', '자유민주', '질서', '더욱', '정치', '경제', '사회', '문화', '모든', '영역', '각인', '기회', '능력', '최고', '도로', '발휘', '자유', '권리', '책임', '의무', '완수', '국민', '생활', '향상', '기하', '항구', '세계', '평화', '인류', '공영', '이바지', '우리', '우리', '자손', '안전', '자유', '행복', '확보', '다짐', '제정', '개정', '헌법', '이제', '국회', '의결', '국민투표', '개정', '대한민국', '민주공화국', '대한민국', '주권', '국민', '모든', '권력', '국민', '대한민국', '국민', '요건', '법률', '국가', '법률', '재외국민']
이제 수평 막대그래프를 그려보자.
주의할 점은 코랩에서는 반드시 한글 폰트를 지정해줘야 한다.
#수평 막대그래프 그리기
from matplotlib import font_manager, rc
plt.rc('font', family = 'NanumBarunGothic')
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.savefig('bar.png') # 필요한 경우, 그래프를 이미지 파일로 저장한다.
plt.show()
word_graph(const_cnt, max_words=20)
✨ 실행결과
[('조직', 18), ('국회의원', 18), ('임기', 18), ('직무', 19), ('국무총리', 19), ('자유', 20), ('정부', 20), ('선거', 20), ('임명', 20), ('권리', 21), ('의원', 21), ('사항', 23), ('기타', 26), ('모든', 37), ('헌법', 53), ('국민', 61), ('국회', 68), ('국가', 73), ('대통령', 83), ('법률', 127)]
이제 워드클라우드를 그려봅니다. 반드시 한국어 폰트를 지정해줘야 한다.
font_path = '/usr/share/fonts/truetype/nanum/NanumGothic.ttf'
wordcloud = WordCloud(font_path = font_path).generate(const_doc)
plt.axis("off")
plt.imshow(wordcloud, interpolation='bilinear')
plt.show()
✨ 실행결과
WordCloud 객체를 생성할 때 인수를 적절히 활용하면 생성할 워드 클라우드 이미지를 세밀하게 조정할 수 있다.
이미지의 너비와 높이를 지정하는 width, height와 배경색을 지정하는 background_color가 있다.
여기서 plt.imshow()로 코랩에 출력하는 이미지는 WordCloud가 생성하는 이미지를 다시 재처리해 보여주므로 지정한 크기가 반영되지 않는다. 크기가 반영된 이미지를 얻고 싶다면 to_file() 메서드를 사용하면 된다. 이 메서드로 이미지를 따로 저장할 수 있다.
wordcloud = WordCloud(
font_path = font_path,
max_font_size = 100,
width = 800, # 이미지 너비 지정
height = 400, # 이미지 높이 지정
background_color='white', # 이미지 배경색 지정
max_words=50)
# 원문이 아닌 형태소 분석 결과로부터 워드클라우드를 생성
wordcloud.generate_from_frequencies(const_cnt)
# 생성한 이미지를 파일로 저장
wordcloud.to_file("const.png")
plt.axis("off")
plt.imshow(wordcloud, interpolation='bilinear')
plt.show()
✨ 실행결과
출처: 고려대학교 컴퓨터 동아리 KUCC 2023 자연어처리 세션
'인공지능 > 자연어처리' 카테고리의 다른 글
텍스트 전처리 (0) | 2023.04.01 |
---|---|
텍스트 마이닝 기초 (0) | 2023.04.01 |