====== HanDicをPythonで利用する:TF-IDFの計算 ======
===== はじめに =====
[[https://ja.osdn.net/projects/handic/|HanDic]]をPythonで利用する例として,文書中に現れる単語の重要度を示す,TF-IDFの計算を行ってみます.TF-IDFについては以下などを参照:
* [[https://www.sejuku.net/blog/26420|自然言語処理の基礎技術!tf-idfを簡単に解説!]]
* [[https://dev.classmethod.jp/articles/yoshim_2017ad_tfidf_1-2/|tf-idfについてざっくりまとめ_理論編]]
実際にスクリプトを書くに当たっては,以下を参考にしました.必要な部分をだいたい丸写ししています:
* [[https://qiita.com/y_itoh/items/1e91ec59dac403d8e8e6|3. Pythonによる自然言語処理 3-2. TF-IDF分析[小説にみる特徴語の検出]]]
* [[https://qiita.com/ground0state/items/155b77f4c07e1a509a14|scikit-learnでTF-IDFを計算する]]
* [[https://note.com/shimakaze_soft/n/n6e92d1a4851b|自然言語処理の基礎であるTF-IDFの計算方法とPythonによる実装方法を解説]]
Pythonのバージョンは3.7.11,HanDicはhandic-mecab-20210116を用いました.MeCabを扱うためのモジュールmecab-python3ほか,必要なモジュールがインストールされていることが前提です.HanDicはPythonスクリプトのあるディレクトリ下,''handic''という名前のディレクトリに保存されているとします.
なお,HanDicに含まれるファイルのうち''dicrc''に記述の不備があり,「bos-feature」行の末尾に「'',*,*''」を追加しました.
また,[[korean:mecab:python|HanDicをPythonで利用する]]で説明した''k2jamo.py''も作っておきます.
===== やること =====
韓国の新聞社サイトで公開されているRSSフィードを使って,リストにある記事を取得し,名詞類(普通名詞,固有名詞,語根)のTF-IDFを計算します.その上で,リスト中の記事から10本をランダムに選び,TF-IDF上位10語を列挙します.
ここでは例として,경향신문(京郷新聞)の[[https://www.khan.co.kr/help/help_rss.html|RSSサービス]]から,「정치(政治)」カテゴリのフィードを利用します.大手の新聞社でRSSサービスを行っているのは以下の通りです:
* [[https://rssplus.chosun.com/|조선일보(朝鮮日報)]]:記事の構造が分からない
* [[https://rss.joins.com/|중앙일보(中央日報)]]:カテゴリによっては更新されていない・リンクが切れている
* [[http://rss.donga.com/|동아일보(東亜日報)]]:記事の構造が分からない
* [[https://www.hani.co.kr/arti/RSS/|한겨레(ハンギョレ)]]
* [[http://www.nocutnews.co.kr/rss/|노컷뉴스(ノーカットニュース/CBS)]]
* [[http://rss.kmib.co.kr/|국민일보(国民日報)]]
* [[https://www.segye.com/rssInfo|세계일보(世界日報)]]
* [[http://rss.hankooki.com/|한국아이닷컴(hankooki.com)]]
* [[https://www.mk.co.kr/rss/|매일경제(毎日経済)]]
付記した理由から,ここでは경향신문を取り上げることにします.他のRSSを利用する場合,適宜読み替えてください.
===== 準備 =====
必要なモジュールなどを呼び出します.
import random, requests, MeCab
from bs4 import BeautifulSoup
from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd
import k2jamo
何をどういう順番で呼び出すのか,お作法がちょっと分からないので適当にやっています.
===== MeCabの呼び出し =====
MeCabのインスタンスを作成します.
tokenizer = MeCab.Tagger('-d handic')
tokenizer.parse('')
===== 記事のスクレイピング =====
경향신문の記事URLから該当する記事を取得し,本文(''
''の内容)を取得します.''requests.get()''でうまく記事が取得できなかったので,User Agentを設定してあります.
# 記事と本文の取得
def news_scraping(url):
paras = []
ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
headers = {'User-Agent': ua}
html = requests.get(url, headers=headers)
soup = BeautifulSoup(html.content, 'html.parser')
# 경향신문向け
articles = soup.select('p.content_text')
# 중앙일보向け
# articles = soup.select('#article_body > p')
for p in articles:
paras.append(p.text)
return '\n'.join(paras)
なお,記事本文が格納されている要素はサイトによって異なります.ここまで分かっている中では,중앙일보(中央日報)の場合,「경향신문向け」としてある次の行をコメントアウトして,''articles = soup.select('#article_body > p')''の方を使用すると,記事本文が取り出せます.他はまだ調査中です.
> [2021/11/06 追加] いくつか調べた結果,書き換えの内容は以下の通りです.
>
> 한겨레(ハンギョレ) ''articles = soup.select('div.article-text')''
> 노컷뉴스(ノーカットニュース・CBS) ''articles = soup.select('div', id = 'pnlContent')''
===== MeCabによる解析 =====
MeCabで解析し,普通名詞(世宗タグ「NNG」),固有名詞(同「NNP」),語根(同「XR」)を取り出してその基本形を配列に格納します.最後に,配列の内容をスペースで連結します.
# mecabで解析,スペース区切りの配列にする
def extract(text):
words = []
node = tokenizer.parseToNode(text)
while node:
if node.feature.split(',')[10] in ['NNG', 'NNP', 'XR']:
words.append(node.feature.split(',')[5])
node = node.next
text_result = ' '.join(words)
return text_result
名詞だけでもよいのですが,語根には「간단02(簡単)」「깨끗」「소중01(所重)」などといった語があり,特に政治の記事では人や政策のイメージなどとも関連すると思われるので,とりあえず含めてあります.世宗タグについては[[korean:mecab:pos|HanDicの品詞体系]](すいません,書きかけです)を参照のこと.
===== RSSフィードからのリンク取得および記事の処理 =====
RSSフィードから各記事のリンクを取得します.
# RSSフィードの処理
# 경향신문 정치
feed = 'https://www.khan.co.kr/rss/rssdata/politic_news.xml'
# 중앙일보 주요기사
# feed = 'https://rss.joins.com/joins_homenews_list.xml'
req = requests.get(feed)
req.encoding = req.apparent_encoding
txt = BeautifulSoup(req.text, 'lxml-xml')
rss_list = txt.select('channel > item')
中央日報の主要記事も例として挙げてあります.中央日報で試す場合はコメントアウトを外し,上の「경향신문 정치」の次の行をコメントアウトしてください.また上述の通り,''news_scraping()''も変更しておく必要があります.
次に,リンクのリストからそれぞれの記事を取得して処理し,MeCabで解析する部分です.
# 記事本文の処理,docsに格納
docs = []
titles = []
for i in range(len(rss_list)):
print("processing:", i+1, "/", len(rss_list))
text = k2jamo.substitute(news_scraping(rss_list[i].link.text))
text = extract(text)
titles.append(rss_list[i].title.text)
docs.append(text)
各記事のタイトルは,別途配列に入れました.
===== TF-IDFの計算と上位10語の表示 =====
scikit-learnでTF-IDFを計算します.アップデートしたら「''get_feature_names()''はバージョン1.0では非推奨で,1.2で削除する」と出てきたので,''get_feature_names_out()''を使っています.
# モデルを作成
vectorizer = TfidfVectorizer(smooth_idf=False)
values = vectorizer.fit_transform(docs).toarray()
feature_names = vectorizer.get_feature_names_out()
df = pd.DataFrame(values, columns = feature_names, index=titles)
上記データフレームから,ランダムに10件の記事を選んで,それぞれTF-IDFの値で上位10語を列挙します.
# 文書をランダムに10個選択,それぞれ上位10語の出力
for num in random.sample(range(len(df)), 10):
temp_df = []
temp_df = df[num:num+1].T
temp_df = temp_df.sort_values(by=titles[num], ascending=False)
print(temp_df.head(10))
実行結果は以下の通り.フィードの項目(item)数が10以下だとエラーになります.''random.sample()''で取り出す数を変更してください.
> python handic_mecab.py
processing: 1 / 54
Some characters could not be decoded, and were replaced with REPLACEMENT CHARACTER.
...中略...
processing: 54 / 54
Some characters could not be decoded, and were replaced with REPLACEMENT CHARACTER.
안철수 출마와 김동연, 제3지대 가능성은
지대07 0.363606
동연 0.293522
철수 0.269679
안03 0.248124
후보04 0.186889
양보03 0.149720
철수02 0.146761
사람 0.141769
출마 0.135213
김06 0.127492
한미연합군사령관 “가장 강력하고 준비된 연합전력”
연합03 0.383949
연합사 0.358117
군사령부 0.268588
주한02 0.268588
미14 0.239796
창설 0.179059
억제 0.179059
기념식 0.179059
미구03 0.179059
유지09 0.098292
윤석열, 공정·정의·소통 강조하며 ‘반문 기치’…“내 승리, 뼈아플 것”
윤01 0.297543
총장01 0.247608
정권04 0.226711
연설02 0.201548
나라01 0.180365
다짐 0.179952
공정02 0.172756
상식06 0.157493
후보04 0.152771
약탈 0.140487
당심에 막힌 홍준표의 두 번째 도전…“경선서 국민 관심 끌어온 게 내 역할”
홍01 0.288711
의원05 0.250445
경선08 0.197312
도전04 0.187441
역할 0.182526
세대02 0.180847
윤01 0.149584
마지막 0.145998
총장01 0.136150
중순 0.132053
민주당, 윤석열 후보 확정에 의혹·실언 거론하며 “정책·비전 선거” 제안
후보04 0.315293
의혹 0.280119
정책02 0.203595
수사18 0.203595
민주당 0.187165
윤01 0.185772
축하 0.175173
비전06 0.175173
중심01 0.155191
대변인 0.144918
문 대통령, 체코 등 4개국과 정상회담서 ‘원전 협력’ 강조…탈원전 정책 역행
원전04 0.623669
체코 0.205854
문01 0.178851
폴란드 0.176447
회담 0.176447
정상11 0.157953
총리01 0.155917
대통령 0.154182
기술01 0.147039
헝가리 0.147039
유승민 “깨끗이 승복하겠다”···백의종군 선언
경선08 0.338128
승리 0.330845
패배 0.317707
의원05 0.268237
지지자 0.211805
승복02 0.166795
문05 0.158157
결과02 0.157122
일01 0.154956
대선80 0.150343
이재명 “대선이 정책과 비전으로 미래 열어가는 장이 되길” 정의당 “고발 사주와 대장동…누가 덜 나쁜지 경쟁은 안 돼”
후보04 0.364681
축하 0.334874
윤01 0.248031
대변인 0.211075
미래02 0.183283
대선80 0.178533
수석02 0.158307
대출03 0.158307
의혹 0.152999
수사18 0.148270
청와대 ‘요소수 대응 TF’ 가동··· 매일 비상점검
요소04 0.403954
운영03 0.315363
대응02 0.297735
외교01 0.242372
수급02 0.188388
점검 0.188388
수석02 0.178641
수석 0.161582
청와대 0.155120
체계03 0.145901
송영길 “윤석열, 국민과 언론 앞에 성실하게 답변하라”
송01 0.438483
성실02 0.255614
의혹 0.234262
수사18 0.227021
기대03 0.219745
답변 0.219242
대표 0.191708
후보04 0.186125
윤01 0.172622
경쟁 0.170266
文字コード関連のエラーが出ますが,だいたいうまくいっているようです.경향신문以外の記事を使う場合,このエラーは出ませんでした.
大統領選挙(대선)が近づいて,各党の候補者選び(경선)が熱を帯びていることもあり,それぞれの記事で取り扱われている候補者(후보)の名前(윤, 홍, 유, 안, 김, ...)などが上位にきていることが分かります.
===== 終わりに =====
[[korean:mecab:r_howto|RとRMeCabで扱う]]場合,別途Perlなどで字母に分解したりする必要がありましたが,データの取得から文字列の処理,形態素解析,計算まで一つの言語で完結するのは便利です.
{{indexmenu_n>205}}