#Day61

D0-$ANG ₩0N·2026년 1월 25일
post-thumbnail

TextRank를 이용한 추출 요약 실습


주요코드

from sumy.parsers.plaintext import PlaintextParser
from sumy.nlp.tokenizers import Tokenizer
from sumy.summarizers.text_rank import TextRankSummarizer

text = """
And she won't eat her dinner - rice pudding again -
I've promised her dolls and a daisy-chain,
I've promised her sweets and a ride in the train,
And it's lovely rice pudding for dinner again!
"""

parser = PlaintextParser.from_string(text, Tokenizer("english"))
summarizer = TextRankSummarizer()

summary_sentences = summarizer(parser.document, 2)

for sentence in summary_sentences:
    print(sentence)
from sumy.parsers.plaintext import PlaintextParser
from sumy.nlp.tokenizers import Tokenizer
from sumy.summarizers.text_rank import TextRankSummarizer
  • PlaintextParser: 일반 텍스트 문자열을 분석할 수 있는 형태로 변환합니다.

  • Tokenizer: 문장을 단어 단위로 쪼개는 역할을 합니다 (언어별 처리를 위함).

  • TextRankSummarizer: 요약 알고리즘인 TextRank를 불러옵니다.

text = """... (시 내용) ..."""
  • 분석할 원문 텍스트를 text 변수에 저장했습니다. 내용은 'Mary Jane'이 쌀 푸딩(Rice Pudding)을 먹기 싫어하는 내용의 시입니다.
parser = PlaintextParser.from_string(text, Tokenizer("english"))
  • from_string: 문자열(text)을 입력받습니다.

  • Tokenizer("english"): 영어 텍스트임을 명시하여, 영어 문법에 맞게 토큰화(단어 분리)를 수행하도록 설정합니다.

이번 실습에서는 문장 간의 관계를 그래프로 이해하는 방식을 통해 TextRank 알고리즘의 핵심 원리를 학습했다.
TextRank는 NLP에서 비교적 고전적인 알고리즘이지만, 구조 자체는 여전히 강력하다는 점이 인상 깊었다.

특히 추출 요약(Extractive Summarization) 개념을 명확히 이해할 수 있었다.
요약이라고 해서 문장을 새로 생성하는 것이 아니라, 원문에 존재하는 문장 중에서 가장 중요한 문장을 그대로 선택해 가져오는 방식이라는 점이 명확해졌다.

또한 TextRank가 그래프 기반 알고리즘이라는 점도 흥미로웠다.
각 문장을 노드(Node)로 보고, 문장 간 유사도를 엣지(Edge)로 연결한 뒤,
구글의 PageRank와 유사한 방식으로 중요도를 계산해 핵심 문장을 찾는 구조라는 점을 이해했다.

전체적인 NLP 처리 흐름도 다시 정리할 수 있었다.
텍스트 입력부터 시작해서 토큰화 → 파싱 → 요약기로 이어지는 파이프라인을 직접 따라가 보면서,
이론으로만 알던 NLP 처리 과정이 실제로 어떻게 연결되는지 감이 잡혔다.

2.TextRank 실습2

주요코드

# -*- coding: utf-8 -*-
import re
from sumy.parsers.plaintext import PlaintextParser



text = """Rice Pudding - Poem by Alan Alexander Milne
... What is the matter with Mary Jane?
... She's crying with all her might and main,
... And she won't eat her dinner - rice pudding again -
... What is the matter with Mary Jane?
... What is the matter with Mary Jane?
... I've promised her dolls and a daisy-chain,
... And a book about animals - all in vain -
... What is the matter with Mary Jane?
... What is the matter with Mary Jane?
... She's perfectly well, and she hasn't a pain;
... But, look at her, now she's beginning again! -
... What is the matter with Mary Jane?
... What is the matter with Mary Jane?
... I've promised her sweets and a ride in the train,
... And I've begged her to stop for a bit and explain -
... What is the matter with Mary Jane?
... What is the matter with Mary Jane?
... She's perfectly well and she hasn't a pain,
... And it's lovely rice pudding for dinner again!
... What is the matter with Mary Jane?"""


# --------------------------------------------
# ✅ 전처리: gensim/sumy/transformers 모두 문장 경계가 중요
# - "..." 같은 패턴이 많으면 문장 분리가 흐트러져 요약 품질이 급락함
# --------------------------------------------
def clean_text(t: str) -> str:
    t = t.replace("\r\n", "\n")
    t = re.sub(r"\n{2,}", "\n\n", t)
    t = re.sub(r"^\s*\.\.\.\s*", "", t, flags=re.MULTILINE)  # 줄 앞 "... " 제거
    return t.strip()


text = clean_text(text)


# --------------------------------------------
# ✅ 1순위: Transformers 추상 요약(품질 좋음)
# - 영어: facebook/bart-large-cnn
# - 한국어: gogamza/kobart-summarization (원하면 아래 model_name만 바꾸면 됨)
# --------------------------------------------
def summarize_with_transformers(t: str, model_name: str, max_len=120, min_len=30):
    from transformers import pipeline

    # device=-1 : CPU
    summarizer = pipeline("summarization", model="facebook/bart-large-cnn")

    out = summarizer(t, max_length=max_len, min_length=min_len, do_sample=False)
    return out[0]["summary_text"]


# --------------------------------------------
# ✅ 2순위: sumy 추출 요약(가벼움/설치 쉬움)
# - 영어는 LexRank가 무난
# - 한국어는 sumy 토크나이저가 제한적이라(정확도↓) 영어에 더 적합
# --------------------------------------------
def summarize_with_sumy_lexrank(t: str, sentences=3, language="english"):
    from sumy.parsers.plaintext import PlaintextParser
    from sumy.nlp.tokenizers import Tokenizer
    from sumy.summarizers.lex_rank import LexRankSummarizer

    parser = PlaintextParser.from_string(t, Tokenizer(language))
    summarizer = LexRankSummarizer()
    summary = summarizer(parser.document, sentences)
    return "\n".join(str(s) for s in summary)


# --------------------------------------------
# ✅ 실행: transformers -> 실패하면 sumy로 fallback
# --------------------------------------------
try:
    # 영어 요약 (시)
    print("[Transformers: BART 요약]")
    print(summarize_with_transformers(text, model_name="facebook/bart-large-cnn"))
except Exception as e:
    print("[Transformers 요약 실패] -> sumy로 대체합니다.")
    print("원인:", repr(e))
    print("\n[Sumy: LexRank 요약]")
    print(summarize_with_sumy_lexrank(text, sentences=4, language="english"))

결과

(venv_nlp) apple@apples-MacBook-Air pythoncode % /Users/apple/Desktop/pythoncode/venv_nlp/bin/python 
"/Users/apple/Desktop/pythoncode/1512_실행_TextRank를 이용한 추출 요약.py"

[Transformers: BART 요약]

[Transformers 요약 실패] -> sumy로 대체합니다.
원인: KeyError("Unknown task summarization, available tasks are ['any-to-any', 'audio-classification', 'automatic-speech-recognition', 'depth-estimation', 'document-question-answering', 'feature-extraction', 'fill-mask', 'image-classification', 'image-feature-extraction', 'image-segmentation', 'image-text-to-text', 'image-to-image', 'keypoint-matching', 'mask-generation', 'ner', 'object-detection', 'question-answering', 'sentiment-analysis', 'table-question-answering', 'text-classification', 'text-generation', 'text-to-audio', 'text-to-speech', 'token-classification', 'video-classification', 'visual-question-answering', 'vqa', 'zero-shot-audio-classification', 'zero-shot-classification', 'zero-shot-image-classification', 'zero-shot-object-detection', 'translation_XX_to_YY']")

[Sumy: LexRank 요약]
She's crying with all her might and main, And she won't eat her dinner - rice pudding again - What is the matter with Mary Jane?
What is the matter with Mary Jane?
I've promised her sweets and a ride in the train, And I've begged her to stop for a bit and explain - What is the matter with Mary Jane?
She's perfectly well and she hasn't a pain, And it's lovely rice pudding for dinner again!

Text Summarization 실습 코드 전체 설명 (Transformers + Sumy)

이 글은 텍스트 요약(Text Summarization) 실습 코드가
어떤 흐름으로 동작하고, 각 코드가 어떤 역할을 하는지를
프로그래밍 관점과 NLP 관점에서 정리한 설명이다.


1. 이 코드의 전체 목적

이 코드는 하나의 긴 텍스트를 입력으로 받아 다음 순서로 처리한다.

  1. 텍스트 전처리
  2. 딥러닝 기반 추상 요약(Transformers, BART) 시도
  3. 딥러닝 요약이 실패할 경우
  4. 전통적인 추출 요약(Sumy, LexRank)으로 자동 대체

즉, 항상 요약 결과를 보장하는 요약 파이프라인이다.


2. import 구문의 역할

  • re
    정규표현식을 이용한 문자열 전처리를 위해 사용된다.

  • sumy.parsers.plaintext.PlaintextParser
    텍스트를 문장 단위로 분리하여 sumy 내부에서 처리 가능한 문서 객체로 변환하는 역할을 한다.

이 단계에서는 아직 요약을 수행하지 않고,
“무엇을 사용할지 선언”하는 준비 단계에 해당한다.


3. 입력 텍스트(text 변수)

입력 데이터는 Alan Alexander Milne의 시(Rice Pudding)이다.

특징은 다음과 같다.

  • 각 줄 앞에 “...” 패턴이 존재
  • 시 형태라 문장 경계가 불안정
  • 그대로 요약하면 문장 분리 실패 가능성 큼

따라서 전처리가 필수적이다.


4. clean_text 함수 (전처리의 핵심)

이 함수의 목적은 단 하나다.

요약 알고리즘이 문장을 정확히 인식하도록 텍스트를 정리하는 것

주요 작업은 다음과 같다.

  • 운영체제별 줄바꿈 통일
  • 불필요하게 많은 빈 줄 제거
  • 각 줄 앞에 붙은 “...” 패턴 제거
  • 앞뒤 공백 제거

이 단계가 없으면 요약 품질이 급격히 떨어진다.

전처리는 요약 성능의 절반 이상을 차지한다고 봐도 된다.


5. Transformers 기반 요약 함수

summarize_with_transformers 함수는
딥러닝 기반 추상 요약(abstractive summarization) 을 담당한다.

핵심 특징은 다음과 같다.

  • BART 대규모 사전학습 모델 사용
  • 원문 문장을 그대로 고르지 않고
  • 새로운 요약 문장을 생성

동작 흐름은 다음과 같다.

  1. AutoTokenizer로 텍스트를 토큰화
  2. AutoModelForSeq2SeqLM으로 요약 전용 모델 로드
  3. 입력 텍스트를 최대 길이에 맞게 자름
  4. beam search를 이용해 요약 문장 생성
  5. 토큰을 다시 사람이 읽을 수 있는 문장으로 복원

이 방식은 품질은 매우 높지만,

  • 모델 용량이 크고
  • 최초 실행 시 다운로드 시간이 오래 걸리며
  • 라이브러리 버전에 민감하다

6. Sumy + LexRank 요약 함수

summarize_with_sumy_lexrank 함수는
전통적인 추출 요약(extractive summarization) 을 담당한다.

이 방식의 특징은 다음과 같다.

  • 원문에 존재하는 문장만 선택
  • 새로운 문장을 생성하지 않음
  • 계산이 빠르고 안정적
  • 환경 의존성이 적음

동작 원리는 다음과 같다.

  1. 텍스트를 문장 단위로 분리
  2. 각 문장을 그래프의 노드로 취급
  3. 문장 간 유사도를 계산
  4. PageRank 방식으로 문장 중요도 산정
  5. 가장 중요한 문장 N개 선택

LexRank는 TextRank 계열 알고리즘 중 하나다.


7. try / except 구조의 의미

실행부는 다음과 같은 철학을 가진 구조다.

  • 1순위: 딥러닝 요약 시도
  • 실패하면 즉시 fallback
  • 프로그램은 절대 멈추지 않음

이는 실무 코드에서 매우 중요한 패턴이다.

딥러닝 모델은 환경 문제, 메모리 문제, 다운로드 문제로 실패할 수 있기 때문에
항상 대체 수단을 준비해야 한다.


8. 실제 실행 결과의 의미

  • Transformers(BART) 요약
    모델 다운로드 및 환경 문제로 실패하거나 오래 걸릴 수 있음

  • Sumy(LexRank) 요약
    즉시 실행되며 안정적으로 결과 출력

이 실습의 핵심은
“어떤 방식이 더 좋다”가 아니라
두 방식의 차이와 장단점을 직접 비교하는 것이다.


9. 이 코드로 얻어야 할 핵심 포인트

  1. 전처리는 요약 품질에 결정적이다
  2. 추출 요약은 빠르고 안정적이다
  3. 추상 요약은 품질이 높지만 무겁다
  4. 실무 코드는 항상 fallback 구조를 가진다
  5. 교재 코드와 최신 라이브러리는 다를 수 있다

10. 한 줄 요약

이 코드는
전통 NLP 요약과 딥러닝 요약을 하나의 흐름에서 비교하고,
실패 시 자동으로 대체하는 텍스트 요약 실습 코드
이다.

BART실습

# -*- coding: utf-8 -*-
import re
from sumy.parsers.plaintext import PlaintextParser



text = """Rice Pudding - Poem by Alan Alexander Milne
... What is the matter with Mary Jane?
... She's crying with all her might and main,
... And she won't eat her dinner - rice pudding again -
... What is the matter with Mary Jane?
... What is the matter with Mary Jane?
... I've promised her dolls and a daisy-chain,
... And a book about animals - all in vain -
... What is the matter with Mary Jane?
... What is the matter with Mary Jane?
... She's perfectly well, and she hasn't a pain;
... But, look at her, now she's beginning again! -
... What is the matter with Mary Jane?
... What is the matter with Mary Jane?
... I've promised her sweets and a ride in the train,
... And I've begged her to stop for a bit and explain -
... What is the matter with Mary Jane?
... What is the matter with Mary Jane?
... She's perfectly well and she hasn't a pain,
... And it's lovely rice pudding for dinner again!
... What is the matter with Mary Jane?"""


# --------------------------------------------
# ✅ 전처리: gensim/sumy/transformers 모두 문장 경계가 중요
# - "..." 같은 패턴이 많으면 문장 분리가 흐트러져 요약 품질이 급락함
# --------------------------------------------
def clean_text(t: str) -> str:
    t = t.replace("\r\n", "\n")
    t = re.sub(r"\n{2,}", "\n\n", t)
    t = re.sub(r"^\s*\.\.\.\s*", "", t, flags=re.MULTILINE)  # 줄 앞 "... " 제거
    return t.strip()


text = clean_text(text)


# --------------------------------------------
# ✅ 1순위: Transformers 추상 요약(품질 좋음)
# - 영어: facebook/bart-large-cnn
# - 한국어: gogamza/kobart-summarization (원하면 아래 model_name만 바꾸면 됨)
# --------------------------------------------
def summarize_with_transformers(t: str, max_len=120, min_len=30):
    from transformers import AutoTokenizer, AutoModelForSeq2SeqLM

    model_name = "facebook/bart-large-cnn"

    tokenizer = AutoTokenizer.from_pretrained(model_name)
    model = AutoModelForSeq2SeqLM.from_pretrained(model_name)

    inputs = tokenizer(
        t,
        max_length=1024,
        truncation=True,
        return_tensors="pt"
    )

    summary_ids = model.generate(
        inputs["input_ids"],
        max_length=max_len,
        min_length=min_len,
        num_beams=4,
        early_stopping=True
    )

    return tokenizer.decode(summary_ids[0], skip_special_tokens=True)




# --------------------------------------------
# ✅ 2순위: sumy 추출 요약(가벼움/설치 쉬움)
# - 영어는 LexRank가 무난
# - 한국어는 sumy 토크나이저가 제한적이라(정확도↓) 영어에 더 적합
# --------------------------------------------
def summarize_with_sumy_lexrank(t: str, sentences=3, language="english"):
    from sumy.parsers.plaintext import PlaintextParser
    from sumy.nlp.tokenizers import Tokenizer
    from sumy.summarizers.lex_rank import LexRankSummarizer

    parser = PlaintextParser.from_string(t, Tokenizer(language))
    summarizer = LexRankSummarizer()
    summary = summarizer(parser.document, sentences)

    return "\n".join(str(s) for s in summary)




# --------------------------------------------
# ✅ 실행: transformers -> 실패하면 sumy로 fallback
# --------------------------------------------
try:
    print("[Transformers: BART 요약]")
    print(summarize_with_transformers(text))
except Exception as e:
    print("[Transformers 요약 실패] -> sumy로 대체합니다.")
    print("원인:", repr(e))
    print("\n[Sumy: LexRank 요약]")
    print(summarize_with_sumy_lexrank(text, sentences=4, language="english"))

결과

Transformers: BART 요약]
Please make sure the generation config includes forced_bos_token_id=0.
Loading weights: 100%|█| 511/511
[00:00<00:00, 4153.12it/s, Materializing param=model.encoder.layers
Rice Pudding - Poem by Alan Alexander Milne. Poem was written by Milne for his daughter Mary Jane. The poem was written in memory of Mary Jane, who died in a car accident.

코드 흐름 설명

1. 전처리 단계

clean_text 함수는 요약 품질을 좌우하는 핵심 단계다.

  • 운영체제별 줄바꿈 차이를 통일한다
  • 불필요한 빈 줄을 제거한다
  • 각 줄 앞에 반복적으로 붙은 "..." 패턴을 제거한다

이 전처리가 없으면:

  • LexRank는 문장 그래프를 잘못 구성하고
  • BART는 문맥을 이상하게 해석한다

2. BART 기반 추상 요약

summarize_with_transformers 함수는 딥러닝 기반 추상 요약을 담당한다.

동작 흐름은 다음과 같다.

  • 텍스트를 토크나이저로 숫자 토큰으로 변환
  • Encoder–Decoder 구조의 BART 모델에 입력
  • generate()를 통해 새로운 요약 문장을 생성
  • 기존 문장을 그대로 뽑지 않고 의미를 재구성

이 방식의 특징:

  • 읽기 좋은 문장 생성
  • 표현력이 뛰어남
  • 원문에 없는 내용을 생성할 수도 있음 (hallucination 가능성)

3. LexRank 기반 추출 요약

summarize_with_sumy_lexrank 함수는 전통 NLP 기반 추출 요약이다.

  • 문장을 노드로 하는 그래프 구성
  • 문장 간 유사도를 엣지 가중치로 사용
  • PageRank 방식으로 중요한 문장 선택
  • 원문 문장을 그대로 가져옴

특징:

  • 사실 왜곡 없음
  • 빠르고 안정적
  • 문장 표현은 다소 거칠 수 있음

4. 실행부 (실무형 구조)

실행부에서는 다음 전략을 사용한다.

  • 1순위: BART 요약 시도
  • 실패 시: LexRank 요약으로 자동 대체

이 구조는 실제 서비스 코드에서도 자주 사용되는 패턴이다.


실습 핵심

  • TextRank / LexRank는 문장 선택 기반 요약
  • BART는 문장 생성 기반 요약
  • 추출 요약은 안전하고 빠르다
  • 추상 요약은 자연스럽지만 사실 왜곡 위험이 있다
  • 2개의 차이를 비교하는게 핵심
profile
Change Up

0개의 댓글