[Python] TextRank 구현 코드

Posted by 적분 ∫2tdt=t²+c
2017.04.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로 키워드를 추출합니다.




이 댓글을 비밀 댓글로
  1. 감사합니다!
  2. 좋은 글 감사합니다 !! 정말 큰 도움이 되었습니다 !! ㅎㅎ
    코드를 실행해보고싶은데

    Traceback (most recent call last):
    File "C:/Users/user1/PycharmProjects/untitled12/tr.py", line 186, in <module>
    tr.loadSents(RawSentenceReader('test2.txt'), lambda sent: filter(lambda x:x not in stopword and x[1] in ('NNG', 'NNP', 'VV', 'VA'), tagger.pos(sent)))
    File "C:/Users/user1/PycharmProjects/untitled12/tr.py", line 107, in loadSents
    for sent in filter(None, sentenceIter):
    File "C:/Users/user1/PycharmProjects/untitled12/tr.py", line 26, in __iter__
    for line in open(self.filepath, encoding='utf-8'):
    File "C:\Program Files\Anaconda3\lib\codecs.py", line 321, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
    UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbf in position 0: invalid start byte

    이러한 에러가 발생하는데 .. 해결방법을 모르겠어서 도움 얻고자 이렇게 댓글 남깁니다 .....!! ㅠㅠ
    답변 주시면 감사하겠습니다 !!
    • 인코딩 에러네요.
      입력파일이 utf-8로 인코딩이 되었는지 확인해보시길 바랍니다!
    • junsama
    • 2018.02.23 17:07 신고
    너무 잘 보고 저희 회사에서 꼭 필요한 내용입니다. 감사합니다.
    python이 초보라 이것저것 옵션 설치하고 돌렸는데 아래와 같은 오류 메시지가 나와서요~

    도와주세요 ㅠㅠ

    ===================================================================

    Load...
    Traceback (most recent call last):
    File "D:\MK\10. python\textrank.py", line 191, in <module>
    tr.load(RawTaggerReader('test2.txt'), lambda w: w not in stopword and (w[1] in ('NNG', 'NNP', 'VV', 'VA')))
    File "D:\MK\10. python\textrank.py", line 52, in __init__
    from konlpy.tag import Komoran
    File "C:\Users\junsam\AppData\Local\Programs\Python\Python36-32\lib\site-packages\konlpy\__init__.py", line 15, in <module>
    from . import tag
    File "C:\Users\junsam\AppData\Local\Programs\Python\Python36-32\lib\site-packages\konlpy\tag\__init__.py", line 4, in <module>
    from ._hannanum import Hannanum
    File "C:\Users\junsam\AppData\Local\Programs\Python\Python36-32\lib\site-packages\konlpy\tag\_hannanum.py", line 7, in <module>
    import jpype
    ModuleNotFoundError: No module named 'jpype'
    • konlpy가 제대로 설치되지 않은것 같습니다.
      http://konlpy.org/ko/v0.4.3/install/
      위 사이트를 참조하셔서 다시 한번 설치해보시길 추천드립니다.
    • junsama
    • 2018.02.23 18:25 신고
    감사합니다~
    다시 설치하니 잘 됩니다.
    한가지 더 질문이요~ 소스중에 아래와 같은 소스가 있는데..
    이게 무슨뜻인지 설명해주시면 정말 감사하겠습니다..

    tr.load(RawTaggerReader('test2.txt'), lambda w: w not in stopword and (w[1] in ('NNG', 'NNP', 'VV', 'VA')))
    • TextRank 클래스의 load 메서드는 첫번째 인자로 sentenceIter를 받고, 두번째 인자로 wordFilter를 받습니다.

      RawTaggerReader는 파일로부터 문장을 읽어들여 형태소 분석을 하여 돌려주는 iterator입니다.
      lambda w~ 부분은 stopword에는 포함되어있지 않으며, 품사는 NNG, NNP, VV, VA만 선택하여 분석에 사용하라고 단어를 필터링해주는 역할을 합니다.
  3. Traceback (most recent call last):
    File "<input>", line 1, in <module>
    File "C:\Program Files\JetBrains\PyCharm Community Edition 2018.2.2\helpers\pydev\_pydev_bundle\pydev_umd.py", line 197, in runfile
    pydev_imports.execfile(filename, global_vars, local_vars) # execute the script
    File "C:\Program Files\JetBrains\PyCharm Community Edition 2018.2.2\helpers\pydev\_pydev_imps\_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
    File "C:/Users/정수닷/PycharmProjects/untitled1/main.py", line 1, in <module>
    import networkx
    File "C:\Program Files\JetBrains\PyCharm Community Edition 2018.2.2\helpers\pydev\_pydev_bundle\pydev_import_hook.py", line 20, in do_import
    module = self._system_import(name, *args, **kwargs)
    ImportError: No module named 'networkx'

    챗봇을 프로젝트를 진행하고 있는 학생 입니다.
    이런 에러가 뜨는데 해결방법을 알고 싶습니다. ㅠㅠ
    • ImportError: No module named 'networkx'

      말 그래도 networkx 모듈이 없어서 발생하는 에러입니다. 파이썬 모듈 중에 networkx가 설치되어있는지 확인해보시길 바랍니다.
      pip를 쓰신다면 pip install networkx 명령어로 간단하게 설치하실 수 있어요
  4. 잘읽었습니다! 그럼 문장 하나를 넣으면 키워드를 추출해주는건가요? 예시를 들면요??
    • 본문에 나와있다시피 이 글은 앞글(http://bab2min.tistory.com/552)에서 정리했던 TextRank 기법에 대한 코드 요청이 많아서 포스팅한 것입니다. 자세한 내용은 앞 글을 확인하시면 되겠습니다.
  5. 안녕하세요!
    코드를 확인해보는 중에 궁금한부분이 있어서 질문드립니다~
    tr.load(RawTaggerReader('test2.txt'), lambda w: w not in stopword and (w[1] in ('NNG', 'NNP', 'VV', 'VA')))
    부분은 파일을 로드해서 처리하는거 같은데 파일 말고 따로 변수를 생성해서 해당 변수로 처리를 할려면
    어떻게 처리를 해야 되나요?
    • RawTaggerReader 대신에 RawTagger를 쓰시면됩니다.

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

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

    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'

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

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

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

    바쁜 시간에 댓글 읽어 주셔서 감사드립니다!!
    • 첫번째 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로 중간에 값을 찍어보시는게 좋을듯합니다.
      • student
      • 2018.10.11 23:32 신고
      바쁘신와중에 친절한 설명 감사합니다. 많은 도움이 되었습니다. 진심으로 감사드립니다!
  7. RawTagger 객체의 ch변수가 분명 제대로 str 타입 문장임에도 map(lambda a, b: a + b, ch[::2], ch[1::2]) <-이 함수에 의해 글자가 쪼개지지 않고 있습니다. 혹시 원인을 알 수 있을까요? 저 매핑함수가 첫글자와 두번째 글짜를 합쳐 반화하는데, 이와같이 두글자씩 끊고 있는 이유는 무엇인가요?
  8. 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))
    ------------------------------------------
    이와같이 해결하였습니다~~
    • 네 확인해 보니 해당 코드에 버그가 있었던것 같네요. 올려주신 코드처럼 수정해서 사용하시면 될것 같습니다.
      좋은 지적 감사합니다.
    • smkim
    • 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'
    • 파일명이 잘못들어가셨네요.

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

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