728x90
반응형

키워드 추출 방법

Bag-of-Words

  가장 단순하게 단어의 빈도수를 측정하는 방법이다. 뉴스 헤드라인의 불용어 전처리만 잘 수행한다면 특별한 공식도 필요 없이 사용된 횟수를 계산하면 된다. 불용어 전처리에 대해선 아래에서 따로 설명할 예정이다.

from collections import Counter

headlines = [
    "경제가 성장하고 주식 시장이 사상 최고치를 기록하다",
    "정부가 경제를 개선하기 위한 새로운 정책 발표",
    "경제 호황 속에 주식 시장 사상 초유의 성장",
    "정부, 성장을 촉진하기 위한 새로운 경제 정책 발표",
    "경제 성장 지속되며 주식 시장 사상 최고치 경신"
]

# headline을 token으로 분리
headlines = [token for headline in headlines for token in set(headline.split(' ')) if len(token) > 0]
# 단어 빈도 수 분석, 상위 5개 단어 출력
word_counts = Counter(headlines)
top_word_in_section = word_counts.most_common(5)

  collections 라이브러리의 Counter 클래스를 통해 간단히 구현이 가능하다. 뉴스 헤드라인이 있는 list (headlines)를 가정하고 코드를 구현하였다.

  • List comprehesion을 사용하여 string인 각 헤드라인을 split() 을 통해 여러 token으로 분리한다.
  • 개별 token으로 분리된 list에 set을 씌움으로써 헤드라인 내에서 중복을 제거할 수 있다. 이는 한 헤드라인에서 중복된 단어는 유의미한 정보를 왜곡할 것이라 판단하여 제거하였다.
  • 위 예시에서는 문제되지 않으나 띄어쓰기가 2번 연속 사용되어 공란인 token이 발생할 수 있다. 이후에 해당 공란이 오류의 원인이 될 수 있으므로 len()를 통해 현재 단계에서 제거하였다.
  • 전처리된 headlines에 Counter를 씌우고, most_common() 메서드를 통해 결과를 확인할 수 있다.

  'top_word_in_section'은 ('주식', 3), ('사상', 3), ('경제', 3), ('위한', 2), ('정책', 2) 가 된다. 불용어가 처리되지 않아 정확한 분석은 아니지만 정상적으로 빈도 수를 계산한 것을 알 수 있다.

TF-IDF

  TF-IDF는 문서 내의 단어의 중요성을 평가하기 위한 통계적 방법이다. 크게 두 가지 요소로 구성되어 있다.

  Term Frequency (TF) 특정 단어가 문서 내에서 얼마나 자주 등장하는지를 나타내는 값이다. 단어의 빈도수가 높을수록 해당 단어의 중요도가 높다고 간주된다.

TF-IDF

  Inverse Document Frequency (IDF)는 단어가 전체 문서 집합에서 얼마나 희귀한지를 나타내는 값이다. 특정 단어가 많은 문서에 등장하면 그 단어의 구별 능력이 낮다고 판단해 중요도를 낮춘다.

TF-IDF

  최종적으로 TF-IDF 값은 다음과 같이 계산된다.

TF-IDF

from sklearn.feature_extraction.text import TfidfVectorizer

vectorizer = TfidfVectorizer()

# TF-IDF 계산
tfidf_matrix = np.array(vectorizer.fit_transform(headlines)).flatten()
tfidf_feature_names = vectorizer.get_feature_names_out()

# 결과
tfidf_matrix, tfidf_feature_names

  위 수식을 따로 구하진 않고, sklearn에 있는 TfidfVectorizer를 사용하여 계산할 수 있다. 다른 sklearn 모듈 처럼 fit_transform 메서드를 사용하여 matrix를 계산한다. 키워드는 get_feature_names_out 메서드로 얻을 수 있다. 위 함수의 결과는 아래와 같다. 

키워드 추출 후 동일 카테고리로 묶기

# 키워드와 TF-IDF 점수 매핑
keyword_scores = {tfidf_feature_names[i]: tfidf_matrix[i] for i in range(len(tfidf_feature_names))}
keyword_scores = sorted(keyword_scores.items(), key=lambda item: item[1], reverse=True)

# 상위 키워드 출력
top_n = 10
print(f"Top {top_n} keywords by TF-IDF:")
for keyword, score in keyword_scores[:top_n]:
    print(f"{keyword}: {score:.4f}")

  상위 10개의 키워드를 추출한 결과이다. 이전 Bag-of-Words 방식과 결과가 크게 다르지 않으나, 개수가 아닌 점수로 표현된 것을 알 수 있다.

키워드 추출 후 동일 카테고리로 묶기

728x90

전처리

  키워드 추출에 대해 먼저 알아봤지만, "성장", "성장을", "성장하고" 가 분리되는 건 적합하지 않다. 이에 적절한 전처리를 통해 불용어를 처리해주어야 한다. 불용어란, '이', '가', '은', '는', '을', '들', '까지', '때', '거의' 와 같은 단어 자체에 의미가 있지 않아 키워드 분석시 제외해야하는 단어들을 의미한다.

import re
from konlpy.tag import Kkma

headlines = [
    "경제가 성장하고 주식 시장이 사상 최고치를 기록하다",
    "정부가 경제를 개선하기 위한 새로운 정책 발표",
    "경제 호황 속에 주식 시장 사상 초유의 성장",
    "정부, 성장을 촉진하기 위한 새로운 경제 정책 발표",
    "경제 성장 지속되며 주식 시장 사상 최고치 경신"
]

# 1. 텍스트 전처리 함수 정의
def preprocess_text(text):
    text = re.sub(r'\b\w{1}\b', ' ', text)  # 1글자 이하 단어 제거
    text = re.sub(r'[^\w\s]', ' ', text)  # 문장부호 제거
    return text

# 2. KoNLPy의 Okt 형태소 분석기를 이용하여 불용어 처리
def tokenize_and_remove_stopwords(text):
    tokens = Kkma().nouns(text)
    return ' '.join([token for token in tokens if len(token) > 1])

# 모든 텍스트 전처리
headlines = [preprocess_text(headline) for headline in headlines]
headlines = [tokenize_and_remove_stopwords(headline) for headline in headlines]

  우선 preprocess_text 함수는 예시처럼 깔끔한 헤드라인이 아닌 경우를 대비하여 적용한다. 1글자 단어는 사람의 성을 한자로 한 글자로 표현한 경우를 고려하여 적용하였다. 문장부호는 반점, 온점, [ ] 와 같은 기호를 제거한다.

  이후 tokenize_and_remove_stopwords 함수를 통해 불용어를 처리한다. 다만 다른 언어들과 같이 한국어는 불용어(stopwords)가 라이브러리 형태로 존재하지 않는다. 목적이 키워드 추출인 만큼 핵심이 되는 명사를 추출하는 것이 적절할 것이라는 가정하에, konlpy의 Kkma 클래스를 활용하여 명사만 추출하였다. konlpy에는 다른 여러 클래스가 존재하며 자세한 내용은 공식 document를 참소하면 된다. 아래는 이를 통해 얻은 결과이다. 초기 headline에서 완벽하진 않지만 나쁘지 않게 전처리를 수행한 것을 확인할 수 있다.

키워드 추출 후 동일 카테고리로 묶기

  이렇게 전처리된 헤드라인으로 위에서 살펴봤던 Bag-of-Words와 tf-idf를 둘 다 적용해 본 결과는 아래와 같다. 두 방식에서 순위는 약간 차이가 있으나 크게 다른 결과를 보이진 않는다. 또한 둘다 빠른 알고리즘이나 Bag-of-Words가 미세하게 빠른 속도를 보인다. 상황에 맞춰 필요한 알고리즘을 활용하면 좋을 듯 하다.

키워드 추출 후 동일 카테고리로 묶기

키워드 묶기

  이렇게 가장 많은 키워드를 추출한 경우, 동일한 헤드라인에서 겹치는 여러 명사가 존재하면 다른 키워드를 관찰할 수 있는 기회가 사라지게 된다. 이를 보완하고자 computer vision의 object detection 영역에서 활용되는 IoU 개념을 활용하고자 한다. 이는 두 범주 간 전체 합집합에 대한 교집합의 비율로 아래 이미지처럼 표현된다. 즉, 두 키워드가 있을 때 둘 중 하나라도 키워드를 포함하고 있는 전체 헤드라인 중에, 두 키워드를 모두 포함하고 있는 헤드라인이 일정 비율 이상일 경우 같은 범주의 키워드로 처리하는 것이다. 이미지 출처

IoU

 

keyword_category = {}
for keyword in top_word_in_section:
    # 카테고리가 비어있으면 신규 추가
    if len(keyword_category) == 0: keyword_category[len(keyword_category)] = [keyword[0]]
    # 카테고리가 있으면 비교
    else:
        # 전체 기존 카테고리를 확인
        for key,value in keyword_category.items():
            ori = set([headline for headline in headlines if value[0] in headline])
            new = set([headline for headline in headlines if keyword[0] in headline])
            # 기존 카테고리가 IoU가 0.1 이상
            if len(ori.intersection(new)) / len(ori.union(new)) > 0.9:
                keyword_category[key].append(keyword[0])
                break
        else:
            keyword_category[len(keyword_category)] = [keyword[0]]

  이를 구현한 방식이다. 가장 많이 나온 키워드부터 순차적으로 확인하며, 현재 확인 중인 키워드가 더 많이 나왔던 다른 키워드와의 IoU가 임계값(threshold) 이상이면 동일한 그룹로 묶고 그렇지 않으면 새로운 그룹을 생성한다. 이를 통해 지금까지 활용한 예시의 결과는 아래처럼 나온다. 임의의 헤드라인으로 구성했기 때문에 threshold가 과하게 높은 상황으로 상황에 맞춰 IoU는 조절할 필요가 있다.

키워드 추출 후 동일 카테고리로 묶기

728x90
반응형

+ Recent posts