상세 컨텐츠

본문 제목

[Python] TextRank 구현 코드

프로그래밍

by 적분 ∫2tdt=t²+c 2017. 4. 20. 17:13

본문

앞선글(TextRank 기법을 이용한 핵심 어구 추출 및 텍스트 요약) 에서 텍스트 랭크 알고리즘을 소개하면서 직접 실험해보고자 파이썬 코드를 짰었는데요, 정리해서 공유해드립니다. 페이지랭크 계산에 networkx 패키지를 사용하므로, networkx가 설치되어있는지 확인부탁드립니다.


* 의존 패키지: networkx





사용법은 어렵지 않습니다. 다음과 같이 쓰면 됩니다.

TextRank 생성자는 인수로 window, coef, threshold를 받습니다.

* window : 문맥으로 사용할 단어의 개수. 기본값 5로 주면 특정 단어의 좌우 5개씩, 총 10개 단어를 문맥으로 사용합니다.

* coef : 동시출현 빈도를 weight에 반영하는 비율입니다. 기본값은 1.0로, 동시출현 빈도를 weight에 전부 반영합니다. 0.0일 경우 빈도를 반영하지 않고 모든 간선의 weight을 1로 동일하게 간주합니다.

* threshold: 문서 요약시 관련있는 문장으로 여길 최소 유사도값. 기본값은 0.005이고, 이 값보다 작은 유사도를 가지는 문장쌍은 관련없는문장으로 처리합니다.


문서요약시에는 loadSents로 문장을 읽어들이고, summarize로 결과를 얻습니다.




키워드 추출시에는 load로 문장을 읽어들이고, extract로 키워드를 추출합니다.




관련글 더보기

댓글 영역

  • 이전 댓글 더보기
  • 프로필 사진
    2018.09.11 15:42
    안녕하세요!
    코드를 확인해보는 중에 궁금한부분이 있어서 질문드립니다~
    tr.load(RawTaggerReader('test2.txt'), lambda w: w not in stopword and (w[1] in ('NNG', 'NNP', 'VV', 'VA')))
    부분은 파일을 로드해서 처리하는거 같은데 파일 말고 따로 변수를 생성해서 해당 변수로 처리를 할려면
    어떻게 처리를 해야 되나요?
    • 프로필 사진
      2018.09.11 18:54 신고
      RawTaggerReader 대신에 RawTagger를 쓰시면됩니다.

      RawTagger("특정 텍스트....")

      이 경우 입력된 텍스트는 \n를 기준으로 split되어 사용됩니다.
  • 프로필 사진
    2018.09.12 23:43
    좋은 글 감사하게 잘 봤습니다.
    코드를 따라 해볼려고 하니

    Traceback (most recent call last):
    File "textrank.py", line 192, in <module>
    tagger = Komoran()
    File "C:\Python36\lib\site-packages\konlpy\tag\_komoran.py", line 91, in __init__
    jvm.init_jvm(jvmpath, max_heap_size)
    File "C:\Python36\lib\site-packages\konlpy\jvm.py", line 66, in init_jvm
    '-ea', '-Xmx{}m'.format(max_heap_size))
    File "C:\Python36\lib\site-packages\jpype\_core.py", line 71, in startJVM
    _initialize()
    File "C:\Python36\lib\site-packages\jpype\_core.py", line 48, in _initialize
    _jclass._initialize()
    File "C:\Python36\lib\site-packages\jpype\_jclass.py", line 46, in _initialize
    _jpype.setResource('JavaClass', _JavaClass)
    AttributeError: module '_jpype' has no attribute 'setResource'

    라고 오류가 나는데 모듈설치가 잘못된걸까요? 자바도 새로 설치하고
    전부 새로설치해봐도 안되네요 ㅠ
    • 프로필 사진
      2018.09.12 23:57 신고
      konlpy가 제대로 설치가 안된것 같습니다.
      https://konlpy-ko.readthedocs.io/ko/v0.4.3/install/ 를 확인하셔서 jpype를 설치해보심을 추천해드립니다.
  • 프로필 사진
    2018.10.03 17:40
    안녕하세요. 자연어처리를 공부하고 있는 학생입니다.
    올려주신 Textrank, Pmi 코드를 제가 좀 변형시키고 문장 추출하는 알고리즘을 다시 짜서 적용해보고 있는데요.
    작성자님의 지금 이 코드 출처를 명시하고 제 깃허브에 변형한 코드를 올려도 될까요?
  • 프로필 사진
    2018.10.11 20:40
    안녕하세요 자연어 처리에 대해서 공부하고 있는 학생입니다! 공부하는데 있어서 본 블로그의 게시글이 많은 도움이 되고있어 먼저 감사드린다는 말씀 꼭 전해드리고 싶네요!
    다름이 아니라 본 코드를 보면서 궁금한 점이 있어서 글을 남깁니다.

    extract함수 부분에서 전체적으로 세 번의 for문이 등장하는데요 여기서 첫 번째와 세 번째의 for문이 이해되지 않습니다.
    그 중에서도 첫 번째와 세 번째 for문에 등장하는 tuples dictionary가 어떤 목적으로 사용되는지 이해되지 않네요..ㅠ

    제가 이해하기로는 첫 번째 for문에서는 tuples의 키가 (('단어1', '단어2'), )가 되고 세 번째 for문에서의 tuples는 키가 ('단어1' , '단어2')가 되는데 어떤 목적으로 이렇게 분리해서 구성했는지 알 수 있을까요 ㅠㅠ 더 불어 키가 (('단어1', '단어2'), )일 때와 ('단어1', '단어2')일 때의 value는 각각 어떤 의미를 가지는 지도 궁금합니다.

    바쁜 시간에 댓글 읽어 주셔서 감사드립니다!!
    • 프로필 사진
      2018.10.11 22:42 신고
      첫번째 for문은 cand 리스트에 있는 단어 목록들을 순회합니다. cand에는 텍스트 랭크를 기반으로 추출한 상위 N개의 키워드가 들어가 있겠죠.
      첫번째 for문 내부의 이중for문에서는 이 상위 N개의 키워드 간의 PMI를 계산하여 pairness에 저장해둡니다.

      세번째 for문에서는 이 pairness를 활용하는데요, pairness의 key는 (키워드1, 키워드2)이고, value는 PMI값이었죠? 세번째 for문 내부의 while문에서는 pairness에 저장된 값들을 활용해 키워드를 연장합니다. 예를 들어 pairness에 (키워드1, 키워드2)도 들어있고, (키워드2, 키워드3)도 들어있다면, 이들을 연결해 (키워드1, 키워드2, 키워드3) 으로 확장하는 식입니다.

      더 디테일한 것은 디버깅을 걸어보시거나, print로 중간에 값을 찍어보시는게 좋을듯합니다.
    • 프로필 사진
      2018.10.11 23:32
      바쁘신와중에 친절한 설명 감사합니다. 많은 도움이 되었습니다. 진심으로 감사드립니다!
  • 프로필 사진
    2018.10.23 18:16
    RawTagger 객체의 ch변수가 분명 제대로 str 타입 문장임에도 map(lambda a, b: a + b, ch[::2], ch[1::2]) <-이 함수에 의해 글자가 쪼개지지 않고 있습니다. 혹시 원인을 알 수 있을까요? 저 매핑함수가 첫글자와 두번째 글짜를 합쳐 반화하는데, 이와같이 두글자씩 끊고 있는 이유는 무엇인가요?
  • 프로필 사진
    2018.10.23 20:37
    line이 한줄이어서 Iterator가 생성되지 않을떄 RawTagger가 아무것도 반환하지 않는 문제가 발생하여
    -----------------------------------------
    for line in self.textIter:
    ch = self.rgxSplitter.split(line)
    if len(ch) == 1:
    ch.append(".")
    for s in map(lambda a,b:a+b, ch[0::2], ch[1::2]):
    #print(s)
    if not s:
    continue
    yield (self.tagger.pos(s))
    ------------------------------------------
    이와같이 해결하였습니다~~
    • 프로필 사진
      2018.10.23 22:44 신고
      네 확인해 보니 해당 코드에 버그가 있었던것 같네요. 올려주신 코드처럼 수정해서 사용하시면 될것 같습니다.
      좋은 지적 감사합니다.
  • 프로필 사진
    2018.10.24 22:46
    안녕하세요!! 중간에 아래와 같은 오류가 낫는데.... 어떤 부분이 문제인걸까요? 도움 부탁드립니다 ㅠㅠ

    Traceback (most recent call last):

    File "<ipython-input-12-80d9c268d8a6>", line 1, in <module>
    runfile('C:/Users/chosun/.spyder-py3/temp.py', wdir='C:/Users/chosun/.spyder-py3')

    File "C:\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 668, in runfile
    execfile(filename, namespace)

    File "C:\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 108, in execfile
    exec(compile(f.read(), filename, 'exec'), namespace)

    File "C:/Users/chosun/.spyder-py3/temp.py", line 199, in <module>
    tr.load(RawTaggerReader('C:\\Users\chosun\Desktop\test.txt'), lambda w: w not in stopword and (w[1] in ('NNG', 'NNP', 'VV', 'VA')))

    File "C:/Users/chosun/.spyder-py3/temp.py", line 93, in load
    for sent in sentenceIter:

    File "C:/Users/chosun/.spyder-py3/temp.py", line 66, in __iter__
    for line in open(self.filepath, encoding='utf-8'):

    OSError: [Errno 22] Invalid argument: 'C:\\Users\\chosun\\Desktop\test.txt'
    • 프로필 사진
      2018.10.26 12:23 신고
      파일명이 잘못들어가셨네요.

      'C:\\\\Users\\\\chosun\\\\Desktop\\test.txt'

      라고 입력하신듯한데, 'C:\\\\Users\\\\chosun\\\\Desktop\\\\test.txt'
      가 되어야합니다. \는 문자열 내에서 특별한 의미로 쓰이기 때문에, 실제 \를 표현하기 위해서는 \\라고 쓰셔야합니다.

  • 프로필 사진
    2019.02.14 16:46
    비밀댓글입니다
    • 프로필 사진
      2019.02.14 23:08 신고
      tr.loadSents 부분에서 파라미터를 RawSentenceReader(filepath) 대신 RawSentence(sentences)를 쓰시면 됩니다. sentences 위치에 문장들이 들어간 리스트를 넣어주시면 돼요.
  • 프로필 사진
    2019.02.18 03:03
    C:\Users\COM\PycharmProjects\untitled14\venv\Scripts\python.exe C:/Users/COM/PycharmProjects/untitled14/aa.py
    Load...
    Traceback (most recent call last):
    File "C:/Users/COM/PycharmProjects/untitled14/aa.py", line 217, in <module>
    tr.loadSents(RawSentence(z), lambda sent: filter(lambda x:x not in stopword and x[1] in ('NNG', 'NNP', 'VV', 'VA'), tagger.pos(sent)))
    File "C:/Users/COM/PycharmProjects/untitled14/aa.py", line 118, in loadSents
    s = set(filter(None, tokenizer(sent)))
    File "C:/Users/COM/PycharmProjects/untitled14/aa.py", line 217, in <lambda>
    tr.loadSents(RawSentence(z), lambda sent: filter(lambda x:x not in stopword and x[1] in ('NNG', 'NNP', 'VV', 'VA'), tagger.pos(sent)))
    File "C:\Users\COM\PycharmProjects\untitled14\venv\lib\site-packages\konlpy\tag\_komoran.py", line 67, in pos
    result = [(token.getMorph(), token.getPos()) for token in result]
    TypeError: iter() returned non-iterator of type 'java.util.ArrayList$Itr'
    이렇게 오류가 났는데 혹시 해결방법좀 여쭤봐도 괜찮을까요 아무리 찾아봐도 나오지가 않아서요 ㅜ.ㅜ
  • 프로필 사진
    2019.03.17 15:45
    for i in range(len(self.dictCount)):
    for j in range(i + 1, len(self.dictCount)):
    s = similarity(sentSet[i], sentSet[j])
    if s < self.threshold: continue
    self.dictBiCount[i, j] = s
    혹시 이구문에 대해 풀어서 설명해주실수 있나요? 아무리 해석하려 해도 무슨뜻인지 이해가 가지 않아서요!
    항상 좋은정보 감사합니다 !
    • 프로필 사진
      2019.03.17 23:45 신고
      각 문장을 정점으로하는 그래프를 생성하는 부분이라고 보시면 됩니다.
      sentSet 내의 모든 두 쌍의 문장(sentSet[i]와 sentSet[j])에 대해 유사도를 계산하고, 이 값이 특정한 threshold보다 높을 경우 i-j의 연결강도를 s로 설정하는 겁니다.
  • 프로필 사진
    2019.05.20 15:20
    안녕하세요! 코드 공부중에 궁금한 점이 있어 댓글 남깁니다!
    1. for sent in filter(None, sentenceIter):
    if type(sent) == str:
    if tokenizer: s = set(filter(None, tokenizer(sent)))
    여기에서 tokenizer는 형태소로 나눈것이고 sent는 문장인걸로 이해했는데 tokenizer(sent)를 하면 형태소로 나오더라구요! 혹시 tokenizer(sent)가 어떤기능인지 알수있을까요??
    2. stopword = set([('있', 'VV'), ('하', 'VV'), ('되', 'VV') ])
    제가 여기에 대명사를 추가하고 싶은데 혹시 stopword를 저렇게 3개만 정하신 이유가 있을까요??

    정말정말 좋은 코드 감사합니다! 많이 배우고 있습니다!
    • 프로필 사진
      2019.05.20 15:25 신고
      1. tokenizer는 문장을 토큰 단위로 잘라주는 역할을 수행하는 함수입니다. tokenizer를 None으로 넣었을 경우 regexp를 이용하여 구두점 및 공백을 기준으로 문장을 분리하구요(102번 라인), 그게 아니라면 외부에서 입력한 tokenizer 함수를 사용합니다.

      2. 별 이유는 없습니다 ㅎㅎ. 사실 좀 더 다듬으려면 외부에서 stopword 목록을 읽어들여서 더 많은 stopword를 제거해야할텐데 여기에서는 그냥 기본적인 틀만 보여주고자하였기 때문에 그 부분은 신경쓰지 않았었습니다.
  • 프로필 사진
    2019.05.20 17:24
    아 그럼 loadSents()에 RawSentence()(=sentencelter)와 Komoran()으로 나눈 형태소가 filter를 거쳐서 들어가지만(=tokenizer) loadSents()함수의 tokenizer(sent)는 그냥 sentd을 공백 단위로 나누는 건가요?!

    아 그리고
    tr.loadSents(RawSentence(a), lambda sent: filter(lambda x:x not in stopword and x[1] in ('NNG', 'NNP', 'VV', 'VA'), tagger.pos(sent)))
    에서 x[1]을 이유를 알수있을까요?!

    번거롭게 해드려서 죄송합니다!
    • 프로필 사진
      2019.05.20 22:53 신고
      1. loadSents의 두번째 인자로 Komoran 형태소 분석기로 형태소 분석을 하는 람다함수 ( lambda sent: filter(lambda x:x not in stopword and x[1] in ('NNG', 'NNP', 'VV', 'VA'), tagger.pos(sent)) ) 가 들어가는 것입니다. 그러면 loadSents 내부에서 저 lambda 함수(=>tokenizer)를 이용해 문장을 형태소 분석하는 것이구요.

      2. Komoran의 pos() 함수는 [(형태소, 품사), (형태소, 품사) ...] 형태의 반환값을 돌려줍니다. 품사 정보를 확인하기 위해 x[1]을 사용한 것입니다. 길지만 좀더 이해하기 쉽게 풀어쓰면

      lambda sent: ((morpheme, tag) for morpheme, tag in tagger.pos(sent) if morpheme not in stopword and tag in ('NNG', 'NNP', 'VV', 'VA'))

      처럼 쓸 수 있겠습니다.
  • 프로필 사진
    2019.06.06 22:10
    안녕하세요, 텍스트랭크를 통한 키워드 추출 검색하다 좋은 글 발견하게 되었습니다.
    이미 konlpy을 이용해 토크나이징을 마쳤는데, 텍스트 자체가 아니라 토큰들을 인풋으로 넣어 주요 키워드를 추출할 수는 없을까요?
    • 프로필 사진
      2019.06.07 12:18 신고
      인풋에 토큰들의 리스트의 리스트를 넣어주면 됩니다. 인풋은 결국 문장의 iterator로 처리되기 때문에, 문장의 리스트를 넣어도 작동하거든요.
      토큰들의 리스트가 문장이라고 볼수 있기 때문에 토큰들의 리스트의 리스트를 넣어주시면 되겠습니다.
  • 프로필 사진
    2020.02.19 03:34
    Load...
    Traceback (most recent call last):
    File "/home/pi/Desktop/test.py", line 190, in <module>
    tr.load(RawTaggerReader(os.path.dirname('test2.txt')), lambda w: w not in stopword and (w[1] in ('NNG', 'NNP', 'VV', 'VA')))
    File "/home/pi/Desktop/test.py", line 86, in load
    for sent in sentenceIter:
    File "/home/pi/Desktop/test.py", line 59, in __iter__
    for line in open(self.filepath, encoding='utf-8'):
    FileNotFoundError: [Errno 2] 그런 파일이나 디렉터리가 없습니다: ''

    라즈베리파이(OS 라즈비안)을 사용하고있습니다.
    test2.txt파일의 경로와 test.py(소스코드)의 경로가 같은데 찾질 못하는것같습니다...
    절대경로 (/home/pi/Desktop/test2.txt)를 써도 마찬가지로 찾지 못합니다... 어떡하죠?
  • 프로필 사진
    2020.02.21 16:03
    input에 들어가는 txt파일의 안에 문단들이 리스트 형태로 들어가있는 건가요?
  • 프로필 사진
    2020.03.19 13:39
    안녕하세요! 많은 도움 받고 있습니다.
    한 가지 여쭤볼게 있는데, 텍스트 전체에서 특정 단어쌍(keyword1,keyword2)의 PMI값만 출력하려 할때는 어떻게 하면 될까요? 답변 기다리겠습니다. 감사합니다.^^
  • 프로필 사진
    2020.03.27 22:46
    혹시 이거 프로젝트 진횅하는데 사용해도 될까요?
  • 프로필 사진
    2020.04.21 22:26 신고
    안녕하세요 혹시 이러한 오류가 뜨는데 제가 어떤걸 놓치고 있는지 알 수 있을까요?ㅠㅠ

    line 232, in <module>
    lambda sent: filter(lambda x: x not in stopword and x[1] in ('NNG', 'NNP', 'VV', 'VA'),
    line 134, in loadSents
    for sent in filter(None, sentenceIter):
    TypeError: 'RawSentenceReader' object is not iterable
  • 프로필 사진
    2020.05.14 17:15
    뒤늦게 공부중인데 많은 도움이 되고 있습니다.
    감사합니다~
  • 프로필 사진
    2020.08.09 19:08
    print("%s\t%g" % (k, kw[k]))

    이게 어떤식으로 활용되는건지 설명해주실 수 있나요 ? 처음보는 개념이라 ...

    %s부분만 출력하고 싶어서...