텍스트 요약이란 말 그대로, 글의 핵심적인 내용을 추출하여 보다 적은 문장 수의 글로 간추리는 것을 말한다. 이전 글들을 보면 알겠지만 여러 기계번역 모델로 챗봇을 구현해보았는데, 기계번역 모델로 텍스트 요약도 가능하다고 하여, 앞으로 텍스트 요약 알고리즘에 대해 공부해보려고 한다.🐱🏍🐱🏍
텍스트 요약에는 크게 추출적 요약(Extractive Summarization)과 추상적 요약(Abstractive Summarization)으로 나눠진다고 한다.
추출적 요약은 말 그대로 기존의 글에서 중요도가 높거나 핵심이 되는 문장 그대로 추출해서 요약글을 만드는 것이다. 즉, 새로운 단어가 생겨나거나 새로운 문장이 생성되지 않는 요약 방법이다. 반면, 추상적 요약은 새로운 단어와 새로운 문장을 생성해서 요약을 하는 방법이다.
추출적 요약과 추상적 요약에 대해 생각해보자면, 예를들어 학생한테 독후감 과제를 냈는데 한명은 중요한 문장 그대로만 뽑아서 그대로 써왔고, 다른 학생은 자기 나름대로 새로운 문장을 써서 요약을 해왔다면 뭔가 후자 학생에게 좀더 좋은 점수를 주었을 것이다.
이렇듯 추상적 요약이 뭔가 더 사람이 요약하는 방식과 비슷하고, 퀄리티가 높은 텍스트요약 방법이지 않을까 생각한다. 다만 알고리즘이 복잡할 것이라고 예상된다. 아무튼 이번 글에서는 먼저 추출적 요약 방법에 대해 공부하고, 간단하게 구현해보는 코드를 적어보고자 한다.🐱👓🐱👓
추출적 요약법에도 다양한 기계번역 모델이나 알고리즘이 있지만, 가장 대표적인 것이 바로 TextRank이다. 먼저 TextRank에 대해 알아보자!
TextRank는 2004년 발표된 알고리즘으로, 구글의 PageRank 논문(1998) - The PageRank Citation Ranking: Bringing Order to the Web을 기반으로 한 알고리즘이다. PageRank에 대해 간단하게 설명하자면, 하이퍼링크를 가지는 웹 페이지에 대해서 얼마나 참조가 됐는지, 또는 유입이 되었는지 등으로 페이지의 순위를 매기는 알고리즘을 말한다.
예를 들어 나는 인터넷 페이지에서 우연히 맘에 드는 옷이 광고되고 있어서 해당 쇼핑몰 웹페이지를 클릭하고 들어갔을 때, 그 옷에 대해 살펴본 후 맘에 든다면 해당 쇼핑몰사이트에서 또 다른 옷을 보기 위해 상단에 쇼핑몰 Home으로 돌아가는 배너를 누른다. 그리고 상의 카테고리 등 세부 카테고리를 눌러서 해당 페이지로 들어가고, 또 상단에 있는 베스트 상품 등을 클릭해서 상세 페이지로 들어간다. 그리고 또 다른 옷을 보기 위해서 다시 Home으로 돌아가곤 한다. 이러한 경우 가장 Rank가 높은 페이지는 어디일까? 바로 쇼핑몰 Homepage일 것이다. 그 다음으로는 세부카테고리 페이지, 베스트 상품 페이지, 기타 항목 페이지가 뒤를 이을 것이다. 이처럼 PageRank는 각 웹페이지마다 하이퍼링크가 있을 때 얼마나 링크를 받느냐에 따라 순위를 매기는 알고리즘을 말한다. 추가로 덧붙이자면 PageRank에서는 링크를 클릭할 확률로 그 순위를 매긴다고 한다.
아무튼 다시 돌아와서, TextRank는 이런 PageRank의 알고리즘을 활용한 것으로, 페이지의 개념을 단어의 개념으로 바꾼 알고리즘이다. 즉, 텍스트로 이루어진 글에서 특정 단어가 다른 문장과 얼마만큼의 관계를 맺고 있는지를 계산하는 것이다. TextRank : Bringing Order into Texts 논문에 따르면, TextRank는 그래프 기반의 랭킹 모델(graph-based ranking model)로, 이러한 순위를 매기는 방법이 문단의 추출적 요약에 매우 효과적일 것이라 생각되어 개발했다고 한다.
그렇다면, 어떻게 순위를 매기는 것이 관건인데, 논문에 따르면 'voting' 또는 'recommendation'의 아이디어를 생각했다고 한다. 예를 들어, 한 vertex(=node)가 다른 vertex와 연결된다면, 이를 연결한 vertex에 투표(casting a vote)했다고 본다. 이렇게 다른 vertex에 투표하는 수가 많아질 수록, 특정 vertex의 중요도는 점점 커지게 되는 것이다. 그리고 이 투표 수는 그 vertex의 순위를 매기게 되는 값이 되는 것이다.
위 이미지는 상단에 주어진 글에 대해 텍스트 간 관계를 나타낸 그래프로 그린 샘플 이미지이다. 내용과 그래프를 비교해보면 각 문장에서 단어 간의 관계를 선으로 연결하는 모습을 살펴볼 수 있는데, 순위는 다음과 같은 수식에 의해 매긴다고 한다.
위 식에서 S(Vi)가 최종적으로 구하려는 TextRank 값을 의미하고, Wji는 해당 문장이나 단어 i와 j 사이의 가중치를 의미한다. Wji를 정의하는 수식에 대해서는 TextRank 논문에 나와있다. 한편, d는 damping facter로, 한 vertex에서 다른 vertex로 연결될 확률로 0과 1 사이의 숫자를 갖는다(논문에서는 0.85로 지정). 이렇게 vertex마다 위 식을 통해 TextRank값을 구하고 나면, 그 크기대로 정렬을 해서 순위를 매기게 된다고 한다.
추가로, 자연어 텍스트에 대한 graph-based ranking 알고리즘은 다음과 같이 수행된다고 한다.
- Identify text units that best define the task at hand, and add them as vertices in the graph.
- Identify relations that connect such text units, and use these relations to draw edges between vertices in the graph. Edges can be directed or undirected, weighted or unweighted.
- Iterate the graph-based ranking algorithm until convergence.
- Sort vertices based on their final score. Use the values attached to each vertex for ranking/selection decisions.
TextRank를 아주아주 초 간단하게(2-3줄) 구현할 수 있는 모듈이 파이썬에서 제공되고 있다. 하나의 모듈 안에 위에서 배운 알고리즘대로 순위가 매겨진 단어집이 들어있기 때문에 코드를 보면서 알고리즘을 살펴볼 수 없는게 아쉽긴 하다.(실현에 의의를..) 아무튼 나는 아래 코드를 코랩에서 실행해보았다.
먼저, 아래와 같이 summarize를 import 해준다. 사실 이게 전부다.
from gensim.summarization.summarizer import summarize
그리고 요약을 하고자 하는 텍스트를 불러온다. 무슨 텍스트로 할지 고민했는데, 항순원의 소설 '소나기'를 가져왔다.
text = '''소년은 개울가에서 소녀를 보자 곧 윤 초시네 증손녀(曾孫女)딸이라는 걸 알 수 있었다. 소녀는 개울에다 손을 잠그고 물장난을 하고 있는 것이다. 서울서는 이런 개울물을 보지 못하기나 한 듯이.
벌써 며칠째 소녀는, 학교에서 돌아오는 길에 물장난이었다. 그런데, 어제까지 개울 기슭에서 하더니, 오늘은 징검다리 한가운데 앉아서 하고 있다.'''
위에는 3문장만 입력해놨지만 사실 전문을 그냥 복붙으로 때려박았다.. txt파일을 구글드라이브에 올린 뒤 연동하기도 귀찮기도 해서..ㅎ 아무튼 각자의 방법으로 파일을 불러와서 text 변수에 입력시키면 된다.
그리고 이제 summarze() 모듈을 사용하면 되는데, 여기에도 몇 가지의 기능이 있다.
이제 하나씩 사용해보겠다. 먼저 기본값에 따라 요약된 것을 출력해보겠다.
print(summarize(text))
그 결과, 다음과 같이 출력되었다. 약 60문장으로 요약된 것 같다.
소년은 개울가에서 소녀를 보자 곧 윤 초시네 증손녀(曾孫女)딸이라는 걸 알 수 있었다.
벌써 며칠째 소녀는, 학교에서 돌아오는 길에 물장난이었다.
소년은 개울둑에 앉아 버렸다.
소녀가 비키기를 기다리자는 것이다.
요행 지나가는 사람이 있어, 소녀가 길을 비켜 주었다.
이 날은 소녀가 징검다리 한가운데 앉아 세수를 하고 있었다.
소녀는 소년이 개울둑에 앉아 있는 걸 아는지 모르는지 그냥 날쌔게 물만 움켜 낸다.
그러다가 소녀가 물 속에서 무엇을 하나 집어 낸다.
소년은 저도 모르게 벌떡 일어섰다.
단발 머리를 나풀거리며 소녀가 막 달린다.
그러고도 상당한 시간이 지났다고 생각됐다.
소녀가 갈꽃을 안고 있었다.
소년은 이 갈꽃이 아주 뵈지 않게 되기까지 그대로 서 있었다.
문득, 소녀가 던지 조약돌을 내려다보았다.
소녀의 그림자가 뵈지 않았다.
소녀의 그림자가 뵈지 않는 날이 계속될수록 소년의 가슴 한 구석에는 어딘가 허전함이 자리 잡는 것이었다.
그러한 어떤 날, 소년은 전에 소녀가 앉아 물장난을 하던 징검다리 한가운데에 앉아 보았다.
소년은 두 손으로 물 속의 얼굴을 움키었다.
소녀가 이리로 건너오고 있지 않느냐.
‘숨어서 내가 하는 일을 엿보고 있었구나.’ 소년은 달리기를 시작했다.
소년은 한 손으로 코피를 훔쳐내면서 그냥 달렸다.
개울가에 이르니, 며칠째 보이지 않던 소녀가 건너편 가에 앉아 물장난을 하고 있었다.
여기서 소녀는 아래편으로 한 삼 마장쯤, 소년은 우대로 한 십 리 가까운 길을 가야 한다.
소녀의 눈이 금새 ‘바보,바보,’할 것만 같았다.
허수아비가 서 있었다.
‘참, 오늘은 일찍 집으로 돌아가 텃논의 참새를 봐야 할걸.’ 하는 생각이 든다.
소녀가 그리로 달려간다.
소녀가 먼저 뛰어 건넜다.
그리고는 이렇게 먹어야 한다는 듯이, 먼저 대강이를 한 입 베물어 낸 다음, 손톱으로 한 돌이 껍질을 벗겨 우쩍 깨문다.
소녀가 산을 향해 달려갔다.
이번은 소년이 뒤따라 달리지 않았다.
그러나 소녀는
소녀가 조용히 일어나 비탈진 곳으로 간다.
소녀가 손을 내밀었다.
소년은 저도 모르게 생채기에 입술을 가져다 대고 빨기 시작했다.
좀 만에 숨이 차 돌아온 소년은
이것만은 소녀가 흉내 내지 못할, 자기 혼자만이 할 수 있는 일인 것이다.
그러나, 원두막은 기둥이 기울고 지붕도 갈래갈래 찢어져 있었다.
무명 겹저고리를 벗어 소녀의 어깨를 싸 주었다.
소녀는 비에 젖은 눈을 들어 한 번 쳐다보았을 뿐, 소년이 하는 대로 잠자코 있었다.
소녀가 들어선 곳도 비가 새기 시작했다.
세워 놓은 수숫단 속을 비집어 보더니, 옆의 수숫단을 날라다 덧세운다.
소녀가 속삭이듯이, 이리 들어와 앉으라고 했다.
소녀가 다시, 들어와 앉으라고 했다.
그러나, 소녀는 상관없다고 생각했다.
그러나, 고개를 돌리지 않았다.
소녀는 ‘어머나’소리를 지르며 소년의 목을 끌어안았다.
그 뒤로 소녀의 모습은 뵈지 않았다.
그러나, 뵈지 않았다.
그날도 소년은 주머니 속 흰 조약돌만 만지작거리며 개울가로 나왔다.
그랬더니, 이 쪽 개울둑에 소녀가 앉아 있는 게 아닌가.
어쩐지 소녀의 얼굴이 해쓱해져 있었다.
소녀가 가만히 고개를 끄덕이었다.
소녀가 분홍 스웨터 앞자락을 내려다본다.
소녀가 가만히 보조개를 떠올리며,
소년은 스웨터 앞자락만 바라보고 있었다.
소년은 얼굴이 확 달아오름을 느꼈다.
소년은 주춤한다.
소년은 소녀네가 이사해 오기 전에 벌써 어른들의 이야기를 들어서, 윤 초시 손자(孫子)가 서울서 사업에 실패해 가지고 고향에 돌아오지 않을 수 없게 되었다는 걸 알고 있었다.
소녀와 헤어져 돌아오는 길에, 소년은 혼잣속으로, 소녀가 이사를 간다는 말을 수없이 되뇌어 보았다.
그렇건만, 소년은 지금 자기가 씹고 있는 대추알의 단맛을 모르고 있었다.
이 날 밤, 소년은 몰래 덕쇠 할아버지네 호두밭으로 갔다.
이튿날, 소년이 학교에서 돌아오니, 아버지가 나들이옷으로 갈아입고 닭 한 마리를 안고 있었다.
소년이 이번에는 어머니한테 아버지가 어디 가시느냐고 물어 보았다.
소년은 갈림길에서 아래쪽으로 가 보았다.
갈밭머리에서 바라보는 서당골 마을은 쪽빛 하늘 아래 한결 가까워 보였다.
소년은 저도 모르게 주머니 속 호두알을 만지작거리며, 한 손으로는 수없이 갈꽃을 휘어 꺾고 있었다.
이번엔 비율을 지정하여 요약해보겠다. 0.1로 지정해주었다.
print(summarize(text, ratio=0.1))
소년은 개울가에서 소녀를 보자 곧 윤 초시네 증손녀(曾孫女)딸이라는 걸 알 수 있었다.
요행 지나가는 사람이 있어, 소녀가 길을 비켜 주었다.
이 날은 소녀가 징검다리 한가운데 앉아 세수를 하고 있었다.
소녀는 소년이 개울둑에 앉아 있는 걸 아는지 모르는지 그냥 날쌔게 물만 움켜 낸다.
그러다가 소녀가 물 속에서 무엇을 하나 집어 낸다.
그러고도 상당한 시간이 지났다고 생각됐다.
소녀가 갈꽃을 안고 있었다.
소년은 이 갈꽃이 아주 뵈지 않게 되기까지 그대로 서 있었다.
소녀의 그림자가 뵈지 않았다.
소녀의 그림자가 뵈지 않는 날이 계속될수록 소년의 가슴 한 구석에는 어딘가 허전함이 자리 잡는 것이었다.
그러한 어떤 날, 소년은 전에 소녀가 앉아 물장난을 하던 징검다리 한가운데에 앉아 보았다.
소년은 한 손으로 코피를 훔쳐내면서 그냥 달렸다.
개울가에 이르니, 며칠째 보이지 않던 소녀가 건너편 가에 앉아 물장난을 하고 있었다.
여기서 소녀는 아래편으로 한 삼 마장쯤, 소년은 우대로 한 십 리 가까운 길을 가야 한다.
소녀의 눈이 금새 ‘바보,바보,’할 것만 같았다.
소녀가 그리로 달려간다.
그러나 소녀는
소녀는 비에 젖은 눈을 들어 한 번 쳐다보았을 뿐, 소년이 하는 대로 잠자코 있었다.
소녀가 들어선 곳도 비가 새기 시작했다.
그 뒤로 소녀의 모습은 뵈지 않았다.
그러나, 뵈지 않았다.
그날도 소년은 주머니 속 흰 조약돌만 만지작거리며 개울가로 나왔다.
어쩐지 소녀의 얼굴이 해쓱해져 있었다.
소년은 스웨터 앞자락만 바라보고 있었다.
소년은 얼굴이 확 달아오름을 느꼈다.
소년은 소녀네가 이사해 오기 전에 벌써 어른들의 이야기를 들어서, 윤 초시 손자(孫子)가 서울서 사업에 실패해 가지고 고향에 돌아오지 않을 수 없게 되었다는 걸 알고 있었다.
소녀와 헤어져 돌아오는 길에, 소년은 혼잣속으로, 소녀가 이사를 간다는 말을 수없이 되뇌어 보았다.
그렇건만, 소년은 지금 자기가 씹고 있는 대추알의 단맛을 모르고 있었다.
이튿날, 소년이 학교에서 돌아오니, 아버지가 나들이옷으로 갈아입고 닭 한 마리를 안고 있었다.
소년이 이번에는 어머니한테 아버지가 어디 가시느냐고 물어 보았다.
소년은 갈림길에서 아래쪽으로 가 보았다.
갈밭머리에서 바라보는 서당골 마을은 쪽빛 하늘 아래 한결 가까워 보였다.
소년은 저도 모르게 주머니 속 호두알을 만지작거리며, 한 손으로는 수없이 갈꽃을 휘어 꺾고 있었다.
그 결과 33문장으로 요약이 되었는데, 0.1 비율로 요약했으니 총 330문장으로 소설이 이루어져 있나보다. 아무튼 요약된 내용을 읽으니 꽤 핵심적인 내용이 들어간 것 같다. 한번 0.05로 다시 요약해보겠다.
print(summarize(text, ratio=0.05))
소년은 개울가에서 소녀를 보자 곧 윤 초시네 증손녀(曾孫女)딸이라는 걸 알 수 있었다.
이 날은 소녀가 징검다리 한가운데 앉아 세수를 하고 있었다.
그러다가 소녀가 물 속에서 무엇을 하나 집어 낸다.
소녀가 갈꽃을 안고 있었다.
소년은 이 갈꽃이 아주 뵈지 않게 되기까지 그대로 서 있었다.
소녀의 그림자가 뵈지 않는 날이 계속될수록 소년의 가슴 한 구석에는 어딘가 허전함이 자리 잡는 것이었다.
그러한 어떤 날, 소년은 전에 소녀가 앉아 물장난을 하던 징검다리 한가운데에 앉아 보았다.
개울가에 이르니, 며칠째 보이지 않던 소녀가 건너편 가에 앉아 물장난을 하고 있었다.
소녀는 비에 젖은 눈을 들어 한 번 쳐다보았을 뿐, 소년이 하는 대로 잠자코 있었다.
그날도 소년은 주머니 속 흰 조약돌만 만지작거리며 개울가로 나왔다.
어쩐지 소녀의 얼굴이 해쓱해져 있었다.
소년은 스웨터 앞자락만 바라보고 있었다.
소년은 소녀네가 이사해 오기 전에 벌써 어른들의 이야기를 들어서, 윤 초시 손자(孫子)가 서울서 사업에 실패해 가지고 고향에 돌아오지 않을 수 없게 되었다는 걸 알고 있었다.
소녀와 헤어져 돌아오는 길에, 소년은 혼잣속으로, 소녀가 이사를 간다는 말을 수없이 되뇌어 보았다.
그렇건만, 소년은 지금 자기가 씹고 있는 대추알의 단맛을 모르고 있었다.
소년은 저도 모르게 주머니 속 호두알을 만지작거리며, 한 손으로는 수없이 갈꽃을 휘어 꺾고 있었다.
0.1로 요약하나 0.05로 요약하나 출력된 결과를 보고 드는 생각은 뭔가 중요한 내용이 빠진 듯한 느낌이었다..! 소설은 '발단 → 전개 → 위기 → 절정 → 결말'로 이루어져 있다고 고등학교에서 배운 기억이 있는데, 뭔가 소나기가 내리고 나서부터 소녀와 소년이 비를 피하고, 상처를 닦고, 업고 이런 위기 -> 절정 부분 내용이 쏙 빠진 느낌이다.. 흠..
이번에는 출력할 단어 수를 300개로 지정해서 출력해보겠다.
print(summarize(text, word_count=300))
소년은 개울가에서 소녀를 보자 곧 윤 초시네 증손녀(曾孫女)딸이라는 걸 알 수 있었다.
요행 지나가는 사람이 있어, 소녀가 길을 비켜 주었다.
이 날은 소녀가 징검다리 한가운데 앉아 세수를 하고 있었다.
소녀는 소년이 개울둑에 앉아 있는 걸 아는지 모르는지 그냥 날쌔게 물만 움켜 낸다.
그러다가 소녀가 물 속에서 무엇을 하나 집어 낸다.
그러고도 상당한 시간이 지났다고 생각됐다.
소녀가 갈꽃을 안고 있었다.
소년은 이 갈꽃이 아주 뵈지 않게 되기까지 그대로 서 있었다.
소녀의 그림자가 뵈지 않았다.
소녀의 그림자가 뵈지 않는 날이 계속될수록 소년의 가슴 한 구석에는 어딘가 허전함이 자리 잡는 것이었다.
그러한 어떤 날, 소년은 전에 소녀가 앉아 물장난을 하던 징검다리 한가운데에 앉아 보았다.
소년은 한 손으로 코피를 훔쳐내면서 그냥 달렸다.
개울가에 이르니, 며칠째 보이지 않던 소녀가 건너편 가에 앉아 물장난을 하고 있었다.
여기서 소녀는 아래편으로 한 삼 마장쯤, 소년은 우대로 한 십 리 가까운 길을 가야 한다.
소녀의 눈이 금새 ‘바보,바보,’할 것만 같았다.
소녀가 그리로 달려간다.
그러나 소녀는
소녀는 비에 젖은 눈을 들어 한 번 쳐다보았을 뿐, 소년이 하는 대로 잠자코 있었다.
소녀가 들어선 곳도 비가 새기 시작했다.
그 뒤로 소녀의 모습은 뵈지 않았다.
그러나, 뵈지 않았다.
그날도 소년은 주머니 속 흰 조약돌만 만지작거리며 개울가로 나왔다.
어쩐지 소녀의 얼굴이 해쓱해져 있었다.
소녀가 가만히 보조개를 떠올리며,
소년은 스웨터 앞자락만 바라보고 있었다.
소년은 얼굴이 확 달아오름을 느꼈다.
소년은 소녀네가 이사해 오기 전에 벌써 어른들의 이야기를 들어서, 윤 초시 손자(孫子)가 서울서 사업에 실패해 가지고 고향에 돌아오지 않을 수 없게 되었다는 걸 알고 있었다.
소녀와 헤어져 돌아오는 길에, 소년은 혼잣속으로, 소녀가 이사를 간다는 말을 수없이 되뇌어 보았다.
그렇건만, 소년은 지금 자기가 씹고 있는 대추알의 단맛을 모르고 있었다.
이튿날, 소년이 학교에서 돌아오니, 아버지가 나들이옷으로 갈아입고 닭 한 마리를 안고 있었다.
소년이 이번에는 어머니한테 아버지가 어디 가시느냐고 물어 보았다.
소년은 갈림길에서 아래쪽으로 가 보았다.
갈밭머리에서 바라보는 서당골 마을은 쪽빛 하늘 아래 한결 가까워 보였다.
소년은 저도 모르게 주머니 속 호두알을 만지작거리며, 한 손으로는 수없이 갈꽃을 휘어 꺾고 있었다.
마찬가지로 중요한 부분이 빠진 느낌이다.. 사실 뉴스 기사로 테스트를 해보았는데 꽤 잘 요약을 한다 생각했는데, 소설은 요약하기 어렵나..?😂😂 생각해보면 뉴스 기사의 경우 중요한 키워드가 반복적으로 나오면 순위가 높은 단어가 많은 문장이 쉽게 출력될텐데 소설의 경우 난해했을 것 같기도 하다..
마지막으로 소나기 소설을 3문장으로 요약한다면?이 궁금해서 마지막 코드를 실행해보았다.
print(summarize(text, ratio=0.01))
이 날은 소녀가 징검다리 한가운데 앉아 세수를 하고 있었다.
소년은 이 갈꽃이 아주 뵈지 않게 되기까지 그대로 서 있었다.
그러한 어떤 날, 소년은 전에 소녀가 앉아 물장난을 하던 징검다리 한가운데에 앉아 보았다.
추출된 문장은 위와 같다. 소설에 자주 나오는 '소녀', '징검다리', '소년', '있었다' ,'앉아', '물장난'과 같은 단어가 포함된 문장이 출력된 것 같다.
아무튼 이번 글에서는 이렇게 TextRank에 대해 살펴보고, 파이썬으로 TextRank가 구현된 summarize 모듈을 이용하여 추출적요약을 해보았다. 다음 글에서는 TextRank를 아예 처음부터 만들어 모델을 만든 뒤, 다시 텍스트를 넣어보도록 하겠다. 다음엔 성공하길😎✨
gensim 패키지를 사용할 경우, 단순히 어절(띄어쓰기) 단위로 분리된 토큰을 기준으로 계산하기 때문에 결과가 좋지 않습니다.
한국어 형태소 기반 토크나이저 기반으로 적용하고자 할 경우 아래 레포지토리 추천드립니다.
https://github.com/lovit/textrank/