テキスト自動要約プログラムを作ってみた

今年の夏休みはずっと前からやってみたいと思ってたけどできていなかった...みたいなものをやっていきます。

テキスト自動要約プログラムを作りました。

今回のコードや利用したデータは私のGitHubに載せてあります。

初めに

text-summarizerという英語用の要約プログラムを参考にさせていただきました。

教師なしでの要約です。

このコードを日本語文用に拡張した形になります。感謝です。

要約の流れ

  1. 入力文章の一文一文に対して、形態素解析を行い、 各文に出現単語のsetを作成

  2. 全ての2文の組み合わせにおいて、2つの文それぞれをBoW(Bag of Words)に従って,ベクトルにする

  3. ベクトルにした後の2文の組み合わせの全てのcos類似度を求める

  4. 全ての文に対してTextRankスコアを算出

  5. スコアに基づいて各文を降順にソートし, 上位n個を列挙し、要約文生成

事前知識

これらのサイトが分かりやすいと思います。参考にさせていただきました、ありがとうございます。

  1. 形態素解析

  2. BoW(Bag of Words), cos類似度

  3. TextRankアルゴリズム

コードの説明

1. まずは必要なライブラリをインポート

import nltk
import MeCab
from nltk.cluster.util import cosine_distance
import numpy as np
import networkx as nx

nltkはNatural Language Tool Kitという自然言語処理用のライブラリです。cos類似度求めるのに使います。

MeCab形態素解析のためのライブラリです。

2. ファイルから文章読み込み

def read_article(file_name):
    file = open(file_name, "r")
    filedata = file.readlines()
    article = filedata[0].split("。")
    sentences = [sentence for sentence in article if sentence != "\n"]
    return sentences

一文の終わり("。")で文章を区切る。改行文字は除く。一文を要素とするリストを生成しました。

3. 文同士のベクトル化、cos類似度を求める

def sentence_similarity(sent1, sent2):
    wakati = MeCab.Tagger('-Owakati -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')
    node1, node2 = wakati.parseToNode(sent1), wakati.parseToNode(sent2)
    sent1, sent2 = set(), set()

    # Exclude blanks and those with specific parts of speech(Adverbs, particles, conjunctions, auxiliary verbs)                                                                                             
    while node1:
        word = node1.surface
        hinshi = node1.feature.split(",")[0]
        if word == " " or hinshi in ["副詞", "助詞", "接続詞", "助動詞"]:
            node1 = node1.next
            continue
        sent1.add(word)
        node1 = node1.next

    while node2:
        word = node2.surface
        hinshi = node2.feature.split(",")[0]
        if word == " " or hinshi in ["副詞", "助詞", "接続詞", "助動詞"]:
            node2 = node2.next
            continue
        sent2.add(word)
        node2 = node2.next

    # Bag of words                                                                                                                                                                                          
    all_words = list(sent1 | sent2)
    vector1 = [0] * len(all_words)
    vector2 = [0] * len(all_words)

    for word in sent1:
        vector1[all_words.index(word)] += 1
    for word in sent2:
        vector2[all_words.index(word)] += 1

    # cosine similarity equals 1 - cosine distance                                                                                                                                                          
    return 1 - cosine_distance(vector1, vector2)

2文を形態素解析し、BoWに基づいてベクトル化、cos類似度を求める。

この時、stopwordsは明示的に決めず、品詞が副詞、助詞、接続詞、助動詞以外のものをsetに含めました。

これらの品詞の単語は文章中において、名詞、動詞、形容詞といった品詞よりも情報量が少ないように思えたのでこの処理を行いました。

4. 文同士の類似度を要素とする行列 similarity matrixを作成

def build_similarity_matrix(sentences):
    # similarity matrix shows cosine similarity between sentences                                                                                                                                           
    num = len(sentences)
    similarity_matrix = np.zeros((num, num))
    for idx1 in range(num):
        for idx2 in range(num):
            if idx1 == idx2:
                continue
            similarity_matrix[idx1][idx2] = sentence_similarity(sentences[idx1], sentences[idx2])
    return similarity_matrix

隣接行列のノードが一文になった感じです。

5. これまでの関数を用いて要約を作成

def generate_summary(file_name, top_n = 3):
    summarize_text = [ ]
    # Step 1 - Read text and split it                                                                                                                                                                       
    sentences =  read_article(file_name)

    # Step 2 - Generate Similary Martix across sentences                                                                                                                                                    
    sentence_similarity_martix = build_similarity_matrix(sentences)

    # Step 3 - Rank sentences in similarity martix                                                                                                                                                          
    sentence_similarity_graph = nx.from_numpy_array(sentence_similarity_martix)
    scores = nx.pagerank(sentence_similarity_graph)

    # Step 4 - Sort the rank and pick top sentences                                                                                                                                                         
    ranked_sentence = sorted([(scores[i], s) for i, s in enumerate(sentences)], reverse=True)

    # Print top_n important sentences
    print("top_n important sentences:")
    for i in range(top_n):
        print(f"score:{ranked_sentence[i][0]}, sentence:{ranked_sentence[i][1]}")
        summarize_text.append("".join(ranked_sentence[i][1]))

    # Step 5 - Output a summary                                                                                                                                                                             
    print("Summary: \n", "。".join(summarize_text)+"。")

Step3にてnx.pagerankを用いて1文ごとのTextRankスコアをつけています。それらをStep4にて降順ソートして、その上位top_n個を選んで要約完成です。

要約出力の前に、ランク付けした上位top_n個の文を列挙して出力します。

コード全体

import nltk
import MeCab
from nltk.cluster.util import cosine_distance
import numpy as np
import networkx as nx

def read_article(file_name):
    file = open(file_name, "r")
    filedata = file.readlines()
    article = filedata[0].split("。")
    sentences = [sentence for sentence in article if sentence != "\n"]
    return sentences

def sentence_similarity(sent1, sent2):
    wakati = MeCab.Tagger('-Owakati -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')
    node1, node2 = wakati.parseToNode(sent1), wakati.parseToNode(sent2)
    sent1, sent2 = set(), set()

    # Exclude blanks and those with specific parts of speech(Adverbs, particles, conjunctions, auxiliary verbs)                                                                                             
    while node1:
        word = node1.surface
        hinshi = node1.feature.split(",")[0]
        if word == " " or hinshi in ["副詞", "助詞", "接続詞", "助動詞"]:
            node1 = node1.next
            continue
        sent1.add(word)
        node1 = node1.next

    while node2:
        word = node2.surface
        hinshi = node2.feature.split(",")[0]
        if word == " " or hinshi in ["副詞", "助詞", "接続詞", "助動詞"]:
            node2 = node2.next
            continue
        sent2.add(word)
        node2 = node2.next

    # Bag of words                                                                                                                                                                                          
    all_words = list(sent1 | sent2)
    vector1 = [0] * len(all_words)
    vector2 = [0] * len(all_words)

    for word in sent1:
        vector1[all_words.index(word)] += 1
    for word in sent2:
        vector2[all_words.index(word)] += 1

    # cosine similarity equals 1 - cosine distance                                                                                                                                                          
    return 1 - cosine_distance(vector1, vector2)

def build_similarity_matrix(sentences):
    # similarity matrix shows cosine similarity between sentences                                                                                                                                           
    num = len(sentences)
    similarity_matrix = np.zeros((num, num))
    for idx1 in range(num):
        for idx2 in range(num):
            if idx1 == idx2:
                continue
            similarity_matrix[idx1][idx2] = sentence_similarity(sentences[idx1], sentences[idx2])
    return similarity_matrix

def generate_summary(file_name, top_n = 3):
    summarize_text = []
    # Step 1 - Read text and split it                                                                                                                                                                       
    sentences =  read_article(file_name)

    # Step 2 - Generate Similary Martix across sentences                                                                                                                                                    
    sentence_similarity_martix = build_similarity_matrix(sentences)

    # Step 3 - Rank sentences in similarity martix                                                                                                                                                          
    sentence_similarity_graph = nx.from_numpy_array(sentence_similarity_martix)
    scores = nx.pagerank(sentence_similarity_graph)

    # Step 4 - Sort the rank and pick top sentences                                                                                                                                                         
    ranked_sentence = sorted([(scores[i], s) for i, s in enumerate(sentences)], reverse=True)

    print("top_n important sentences:")
    for i in range(top_n):
        print(f"score:{ranked_sentence[i][0]}, sentence:{ranked_sentence[i][1]}")
        summarize_text.append("".join(ranked_sentence[i][1]))

    # Step 5 - Output a summary                                                                                                                                                                             
    print("Summary: \n", "。".join(summarize_text)+"。")

# make a summary                                                                                                                                                                                            
generate_summary("softbank.txt", 5)

実行結果

入力は日本経済新聞の記事を参考にしました。ここでは、ランク上位5個の文で要約を作ることにしています。

結果1

入力文(短い文章):

ソフトバンクが6月末時点で、米アマゾン株約10億ドルを保有していたことがわかった。保有資産の現金化で得た資金を上場株で運用していることを明らかにしており、他にも米マイクロソフトや米テスラなどの株式も保有していた。米規制当局に提出した文書によると、6月末時点でIT企業を中心に26の米上場株を保有。傘下の英半導体設計アームの売却・再編に向けた交渉を進めているとみられる米半導体エヌビディアの株式も約1億8000万ドル保有していた。ソフトバンクは4.5兆円の保有資産の現金化を進めている。自社株買いや負債削減に充てる方針だが、資金が一時的に積み上がっている。11日の決算発表時に孫正義会長兼社長は、資金の一部を4~6月に30銘柄の上場株で試験的に運用したことを明らかにしていた。4~6月ではソフトバンク本体で1兆円超を投資し、一部を売却して約650億円の売却益を計上した。投資先を多様化するため、ソフトバンクが67%、孫氏が33%を出資して投資運用会社を立ち上げることも決めている。

<結果>

top_n important sentences:

score:0.11111111111111112, sentence:自社株買いや負債削減に充てる方針だが、資金が一時的に積み上がっている

score:0.11111111111111112, sentence:米規制当局に提出した文書によると、6月末時点でIT企業を中心に26の米上場株を保有

score:0.11111111111111112, sentence:傘下の英半導体設計アームの売却・再編に向けた交渉を進めているとみられる米半導体エヌビディアの株式も約1億8000万ドル保有していた

score:0.11111111111111112, sentence:4~6月ではソフトバンク本体で1兆円超を投資し、一部を売却して約650億円の売却益を計上した

score:0.1111111111111111, sentence:投資先を多様化するため、ソフトバンクが67%、孫氏が33%を出資して投資運用会社を立ち上げることも決めている

Summary:

自社株買いや負債削減に充てる方針だが、資金が一時的に積み上がっている。米規制当局に提出した文書によると、6月末時点でIT企業を中心に26の米上場株を保有。傘下の英半導体設計アームの売却・再編に向けた交渉を進めているとみられる米半導体エヌビディアの株式も約1億8000万ドル保有していた。4~6月ではソフトバンク本体で1兆円超を投資し、一部を売却して約650億円の売却益を計上した。投資先を多様化するため、ソフトバンクが67%、孫氏が33%を出資して投資運用会社を立ち上げることも決めている。

結果2

入力文(長い文章):

トヨタ自動車が米アマゾン・ドット・コムクラウドコンピューティングの領域で提携を拡大する。従来は米マイクロソフトとの協力が中心だったが、クラウド基盤サービスで世界首位のアマゾンとも関係を深めて「コネクテッドカー(つながる車)」を強化する狙いだ。「競わせる調達」で技術力とコスト競争力を高める。トヨタが17日に発表した。同社は2020年中に日米中の主要市場で販売する乗用車にDCMと呼ぶ通信用コンピューターを標準搭載する計画を示している。街中を走る車から集めたビッグデータの解析や活用に、アマゾン子会社の米アマゾン・ウェブ・サービス(AWS)が提供する機械学習などの技術を使うことを検討する。米調査会社のカナリスによると、20年4~6月期の企業向けクラウド基盤サービスの世界シェアはAWSが31%で首位だった。トヨタが11年に業務提携したマイクロソフトも営業力を生かして事業を拡大しているが、同四半期のシェアは20%と、AWSの背中を追う展開が続いている。それでもトヨタマイクロソフトとの関係を重視してきた。同社もトヨタがつながる車の普及を見据えて16年に設立した現在のトヨタコネクティッド・ノースアメリカに5%出資したほか、トヨタ首脳が力を入れるレース活動にも「テクノロジーパートナー」などとして協力。異例の対応でトヨタへの貢献を示してきた。一方、AWSも世界有数の製造業であるトヨタへの食い込みを重点課題に据えてきたが、従来は部分的な取り組みにとどまり非公表だった。今回はグループとしての連携を明確にして、さらに「適切なクラウド(技術)を適切な場所、適切な時に使う」(北米トヨタのブライアン・クルサール最高技術責任者=CTO)との言質も得た。背景にあるのはトヨタとIT(情報技術)業界の環境変化だ。「多くのパートナーがAWSを使っており、提携先を広げることによりデータ移行の効率性を高められるといった効果を見込める」。17日に取材に応じたクルサール氏はAWSとの提携強化に踏み切った理由をこう説明した。トヨタはつながる車を増やすことに加え、収集したビッグデータをライドシェア(相乗り)やタクシー、自動車保険といった分野の企業と共有するためのプラットフォーム(基盤)を構築する考えを示している。米ウーバーテクノロジーズなど連携を見込む企業の多くがAWSを使っており、提携強化を促した。コストや開発スピードといった競争力を高めやすいクラウドの普及が進むとともに、「特に大手企業を中心に1社のサービスに縛られたくないと考える顧客が増えている」(米グーグルクラウド部門のトーマス・クリアン最高経営責任者=CEO)ことも追い風になっている。IT業界では特定の企業や技術への依存度が高くなりすぎると、技術力やコスト競争力が優れた製品・サービスへの乗り換えが難しくなる「ベンダーロックイン」の問題が指摘されてきた。特にAWSを追うマイクロソフトやグーグルが複数企業のクラウド使ってシステムを一元開発・運用する基盤の開発に注力している。トヨタの調達部門が守ってきた素材や部品を複数のメーカーから仕入れる原則と照らし合わせてみても、こうした複数企業のサービスを併用する「マルチクラウド」の流れは好都合だ。実際、トヨタ幹部はマルチクラウドが今回の判断を後押ししたことを認め、「コストという観点でも明らかに合理的だ」と述べた。新型コロナウイルスの感染拡大という逆風が吹き付けるなか、トヨタは20年4~6月期に連結最終黒字を確保した。お家芸ともいえる原価低減を徹底した結果だが、自動車の部品の7割は外部調達といわれており、素材・部品メーカーの努力の産物ともいえる。普及とともにクラウドトヨタの競わせる調達の洗礼を浴びることになる。

<結果>

top_n important sentences:

score:0.03846153846153848, sentence:特にAWSを追うマイクロソフトやグーグルが複数企業のクラウド使ってシステムを一元開発・運用する基盤の開発に注力している

score:0.03846153846153848, sentence:新型コロナウイルスの感染拡大という逆風が吹き付けるなか、トヨタは20年4~6月期に連結最終黒字を確保した

score:0.03846153846153848, sentence:従来は米マイクロソフトとの協力が中心だったが、クラウド基盤サービスで世界首位のアマゾンとも関係を深めて「コネクテッドカー(つながる車)」を強化する狙いだ

score:0.03846153846153848, sentence:実際、トヨタ幹部はマルチクラウドが今回の判断を後押ししたことを認め、「コストという観点でも明らかに合理的だ」と述べた

score:0.03846153846153848, sentence:トヨタ自動車が米アマゾン・ドット・コムクラウドコンピューティングの領域で提携を拡大する

Summary:

特にAWSを追うマイクロソフトやグーグルが複数企業のクラウド使ってシステムを一元開発・運用する基盤の開発に注力している。新型コロナウイルスの感染拡大という逆風が吹き付けるなか、トヨタは20年4~6月期に連結最終黒字を確保した。従来は米マイクロソフトとの協力が中心だったが、クラウド基盤サービスで世界首位のアマゾンとも関係を深めて「コネクテッドカー(つながる車)」を強化する狙いだ。実際、トヨタ幹部はマルチクラウドが今回の判断を後押ししたことを認め、「コストという観点でも明らかに合理的だ」と述べた。トヨタ自動車が米アマゾン・ドット・コムクラウドコンピューティングの領域で提携を拡大する。

考察

定性的な評価にはなってしまいますが、割と良い要約になっている気がします。 ランク付けした際、スコアが等しい文が複数あるのが気になりました。ここでの順番は決められないので、要約中の文の順番は合理的な理由付けができないようです。

可視化

実験結果2で入力した文章において、各文をノード、各文間のcos類似度をエッジとした時のグラフを可視化してみた。

f:id:spond:20200820141320j:plain

お、おう、、

参考文献

  1. text-summarizer
  2. Understand Text Summarization and create your own summarizer in python
  3. MeCabを使ってみよう
  4. MeCabをpythonで使うまで
  5. 日本語ストップワードの考察
  6. 大自然言語時代のための、文章要約
  7. 日本経済新聞 ソフトバンク記事
  8. 日本経済新聞 トヨタ記事