長文テキストを自動要約しよう（その1）～sumy～

#はじめに
テキストを自動で要約してくれるPythonライブラリがあります。
長文、短文に関わらずテキストを与えると、ぎゅっとまとめた要約文を返してくれるというものです。

どの程度の要約ができるかはやってみないとわかりません。
テキスト要約には、大きく「抽出型」と「生成型」があるようですので、いずれもやってみることにしました。
今回の記事は、sumyという「抽出型」のPythonライブラリの適用例の紹介です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/678ead80-59d4-25f1-bb84-67f4ea8285ad.png)
##sumyについて

>- Python Package IndexにAutomatic text summarizerとあります。抽出型テキスト要約ができるPythonのライブラリです。
- HTMLページやプレーンテキストから要約を抽出するためのシンプルなライブラリとコマンドラインユーティリティで、複数の要約アルゴリズム（LexRank, Lsa, Reduction, Luhn, SumBasic, KL等）に対応しています。

##実行条件&やってみたこと
>- **Google colab**で実行
- [**Wikipediaのヘビーメタル**](https://ja.wikipedia.org/wiki/%E3%83%98%E3%83%B4%E3%82%A3%E3%83%A1%E3%82%BF%E3%83%AB) の内容を要約した。
- パソコン内の任意のファイルを選択するダイアログが表示されるGoogle colabの機能を使用（対象ファイルはtxtファイルのみ）
- [**sumy で要約(spaCy、GiNZA) を試してみた: Pythonで自然言語処理にフリーライド**](https://itdepends.hateblo.jp/entry/2020/06/07/210222) の内容を参考に、自然言語処理ライブラリにspaCy、spaCy をフロントエンドとする日本語NLPライブラリの GiNZAを使用
- GiNZA解析モデルパッケージは、最新の解析精度重視モデル **ja-ginza-electra** を適用
- sumyの要約アルゴリズム、LexRank, Lsa, Reduction, Luhn, SumBasic, KL の要約結果を表示
- sumyの要約センテンス数設定をGoogle colabの機能（フォーム：スライドバー）で対応　※設定幅：3～30（Default=3, 1Step）

#ライブラリインストール＆インポート
```Python:GiNZAアンインストール（不安定になる場合は、アンインストールしたほうがいい）
pip uninstall ginza ja-ginza
```

```Python:sudachipy,GiNZAインストール
!pip install sudachipy sudachidict_core
!pip install -U ginza https://github.com/megagonlabs/ginza/releases/download/latest/ja_ginza_electra-latest-with-model.tar.gz
```

```Python:spacy,ja_ginza_electra適用
import spacy
nlp = spacy.load('ja_ginza_electra')
```

```Python:sumyインストール
pip install sumy
```

```Python:tinysegmenterインストール
pip install tinysegmenter
```
```Python:reインストール
import re
```

#テキスト読込み

```Python:Google‗colabファイル読込みダイアログ
from google.colab import files
print('txtファイル(UTF-8）を指定してください')
uploaded = files.upload()

for fn in uploaded.keys():
  print('User uploaded file "{name}" with length {length} bytes'.format(name=fn, length=len(uploaded[fn])))
```
※以下は「heavy.text」というファイル読込み後の表示をキャプチャしたものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/7ee9b86e-b95c-fdc0-bad6-c0b6df9f8ee4.png)


```Python:要約センテンス数設定
#@title 要約センテンス数設定（Defalt = 3） { run: "auto" }
Sentences = 3 #@param {type:"slider", min:3, max:30, step:1}
```
※上記記述で以下のフォームが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/302cadee-ae94-f89f-0d8f-c63704b5338d.png)

#テキスト要約処理
```Python:原文書の処理
if len(uploaded.keys()) != 1:
    print("アップロードは１ファイルにのみ限ります")
else:
    target = list(uploaded.keys())[0]
    
with open(target) as f:
    contents = f.readlines()

document = ''.join(contents).replace(' ', '').replace('　', '')

#document = re.findall("[^。]+。?", document.replace('\n', ''))
```

```Python:
corpus = []
originals = []
doc = nlp(document)
for s in doc.sents:
    originals.append(s)
    tokens = []
    for t in s:
        tokens.append(t.lemma_)
    corpus.append(' '.join(tokens))
```

```Python:GiNZA固有表現抽出
#GiNZA固有表現抽出
doc = nlp(document)
spacy.displacy.render(doc, style="ent", jupyter=True)
```
※これは要約処理に必要ではありませんが、GiNZAは以下のように固有表現が可視化できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/95776c3c-3227-0eb7-7089-5d11a26e2568.png)


#テキスト要約
```Python:要約アルゴリズムインポートとトークナイズ
from sumy.parsers.plaintext import PlaintextParser
from sumy.nlp.tokenizers import Tokenizer
from sumy.summarizers.lex_rank import LexRankSummarizer
from sumy.summarizers.lsa import LsaSummarizer
from sumy.summarizers.reduction import ReductionSummarizer
from sumy.summarizers.luhn import LuhnSummarizer
from sumy.summarizers.sum_basic import SumBasicSummarizer
from sumy.summarizers.kl import KLSummarizer

# 連結したcorpusを再度tinysegmenterでトークナイズさせる
parser = PlaintextParser.from_string(''.join(corpus), Tokenizer('japanese'))
```

```Python:LexRankによる要約
summarizer = LexRankSummarizer()
summarizer.stop_words = [' ']  # スペースも1単語として認識されるため、ストップワードにすることで除外する
# sentencres_count指定
summary = summarizer(document=parser.document, sentences_count=Sentences)

# 原文書を表示
print(u'[原文書]')
print(document)

# 要約文を表示
print(u'[要約文書]')
for sentence in summary:
    print(originals[corpus.index(sentence.__str__())])
```
```Python:Lsaによる要約
summarizer = LsaSummarizer()
summarizer.stop_words = [' ']  # スペースも1単語として認識されるため、ストップワードにすることで除外する
# sentencres_count指定
summary = summarizer(document=parser.document, sentences_count=Sentences)

print(u'[原文書]')
print(document)
print(u'[要約文書:Lsa]')
for sentence in summary:
    print(originals[corpus.index(sentence.__str__())])
```
```Python:Reductionによる要約
summarizer = ReductionSummarizer()
summarizer.stop_words = [' ']  # スペースも1単語として認識されるため、ストップワードにすることで除外する
# sentencres_count指定
summary = summarizer(document=parser.document, sentences_count=Sentences)

print(u'[原文書]')
print(document)
print(u'[要約文書:Reduction]')
for sentence in summary:
    print(originals[corpus.index(sentence.__str__())])
```
```Python:Luhnによる要約
summarizer = LuhnSummarizer()
summarizer.stop_words = [' ']  # スペースも1単語として認識されるため、ストップワードにすることで除外する
# sentencres_count指定
summary = summarizer(document=parser.document, sentences_count=Sentences)

print(u'[原文書]')
print(document)
print(u'[要約文書:Luhn]')
for sentence in summary:
    print(originals[corpus.index(sentence.__str__())])
```
```Python:SumBasicによる要約
summarizer = SumBasicSummarizer()
summarizer.stop_words = [' ']  # スペースも1単語として認識されるため、ストップワードにすることで除外する
# sentencres_count指定
summary = summarizer(document=parser.document, sentences_count=Sentences)

print(u'[原文書]')
print(document)
print(u'[要約文書:SumBasic]')
for sentence in summary:
    print(originals[corpus.index(sentence.__str__())])
```
```Python:KLによる要約
summarizer = KLSummarizer()
summarizer.stop_words = [' ']  # スペースも1単語として認識されるため、ストップワードにすることで除外する
# sentencres_count指定
summary = summarizer(document=parser.document, sentences_count=Sentences)

print(u'[原文書]')
print(document)
print(u'[要約文書:KL]')
for sentence in summary:
    print(originals[corpus.index(sentence.__str__())])
```

```Python:全表示
print(u'[原文書]')
print(document)

print(u'[要約文書:LexRank]')
for sentence in summary:
    print(originals[corpus.index(sentence.__str__())])

summarizer = LsaSummarizer()
summarizer.stop_words = [' ']  # スペースも1単語として認識されるため、ストップワードにすることで除外する
# sentencres_count指定
summary = summarizer(document=parser.document, sentences_count=Sentences)

print(u'[要約文書:Lsa]')
for sentence in summary:
    print(originals[corpus.index(sentence.__str__())])

summarizer = ReductionSummarizer()
summarizer.stop_words = [' ']  # スペースも1単語として認識されるため、ストップワードにすることで除外する
# sentencres_count指定
summary = summarizer(document=parser.document, sentences_count=Sentences)

print(u'[要約文書:Reduction]')
for sentence in summary:
    print(originals[corpus.index(sentence.__str__())])

summarizer = LuhnSummarizer()
summarizer.stop_words = [' ']  # スペースも1単語として認識されるため、ストップワードにすることで除外する
# sentencres_count指定
summary = summarizer(document=parser.document, sentences_count=Sentences)

print(u'[要約文書:Luhn]')
for sentence in summary:
    print(originals[corpus.index(sentence.__str__())])

summarizer = SumBasicSummarizer()
summarizer.stop_words = [' ']  # スペースも1単語として認識されるため、ストップワードにすることで除外する
# sentencres_count指定
summary = summarizer(document=parser.document, sentences_count=Sentences)

print(u'[要約文書:SumBasic]')
for sentence in summary:
    print(originals[corpus.index(sentence.__str__())])

summarizer = KLSummarizer()
summarizer.stop_words = [' ']  # スペースも1単語として認識されるため、ストップワードにすることで除外する
# sentencres_count指定
summary = summarizer(document=parser.document, sentences_count=Sentences)

print(u'[要約文書:KL]')
for sentence in summary:
    print(originals[corpus.index(sentence.__str__())])
```

#各アルゴリズムの要約結果
><details><summary>**LexRank**</summary><div>
ヘヴィメタルバンドにはギタリストが2人いることが多い。
1980年にはアイアン・メイデン、デフ・レパードがメジャーデビューし、シーンは一気に活性化していく。
LAメタル、またはグラム・メタルと呼ばれるジャンルが誕生する。
</div></details>

><details><summary>**Lsa**</summary><div>
ハードロック/ヘヴィメタルは70年代半ばごろから、アリーナ・ロックや産業ロック的なバンドと、アルバム志向のヘヴィメタル・バンドに分かれる傾向も見られ、また、時代が新しくなるにしたがって、シーンも細分化が進んできた。
代表的なギタリストには、ジミー・ペイジ[6]、トニー・アイオミ、エドワード・ヴァン・ヘイレン、マイケル・シェンカー、アンガス・ヤング、イングヴェイ・マルムスティーン、スティーヴ・ヴァイらがいる。
その他にも1960年代後半からクリーム、ヴァニラ・ファッジ、レッド・ツェッペリン、ディープ・パープルを始めとするラウドなロックが多数現れた。
</div></details>

><details><summary>**Reduction**</summary><div>
しかし、例えば皮製のファッションは、ロブ・ハルフォードのSMファッションが由来であり[12]、他の例として黒人音楽を取り入れたコーンのようなニュー・メタルバンドでは、Bボーイファッションやストリート系ファッションを取り入れたり、スリップノットのようにマスクとユニフォームに身を包むなど、バンドやプレイヤー個人ごとのアイディアや音楽性、信条などから多様化しているのが実際である。
名詞であるヘヴィメタルが使用されたのは、ビートニク作家であるウィリアム・S・バロウズの著作『ソフト・マシーン』(1961年)であり、彼はのちの作品『ノヴァ急報』でこのテーマを追求し、ヘヴィメタルという単語を依存性の強い薬物のメタファーとして用いている[19]。
80年代後半のメタルシーンを席巻したスラッシュメタルも、似通ったスタイルのバンドの乱立などで衰退していくが、フロリダではスラッシュメタルの凶暴性を突き詰めたデスメタルが[36][37]、北欧ノルウェーでは、デスメタルを否定し80年代スラッシュメタルへの回帰を唱えて、反キリスト教のコンセプトを強調したブラックメタルが誕生するなど[38]、その後のエクストリーム・メタルシーンの成立に大きな影響を与えるとともに、シーンの細分化が進んだ。
</div></details>

><details><summary>**Luhn**</summary><div>
しかし、例えば皮製のファッションは、ロブ・ハルフォードのSMファッションが由来であり[12]、他の例として黒人音楽を取り入れたコーンのようなニュー・メタルバンドでは、Bボーイファッションやストリート系ファッションを取り入れたり、スリップノットのようにマスクとユニフォームに身を包むなど、バンドやプレイヤー個人ごとのアイディアや音楽性、信条などから多様化しているのが実際である。
80年代後半のメタルシーンを席巻したスラッシュメタルも、似通ったスタイルのバンドの乱立などで衰退していくが、フロリダではスラッシュメタルの凶暴性を突き詰めたデスメタルが[36][37]、北欧ノルウェーでは、デスメタルを否定し80年代スラッシュメタルへの回帰を唱えて、反キリスト教のコンセプトを強調したブラックメタルが誕生するなど[38]、その後のエクストリーム・メタルシーンの成立に大きな影響を与えるとともに、シーンの細分化が進んだ。
このような流れの中、シャロン・オズボーンは、夫オジー・オズボーンが時代の半歩先を行く音楽性で常にヘヴィメタルの象徴であり続けたことを活かし、若手ニューメタル・バンドとオジー・オズボーン擁するブラック・サバスという組み合わせで全米をツアーするオズフェストというツアーに打って出る。
</div></details>

><details><summary>**SumBasic**</summary><div>
また、ドラムソロやベースソロも行われることも多く、歌よりも演奏で魅せるような曲も多く、インストゥルメンタルの曲も多い。
ヘヴィメタルバンドにはギタリストが2人いることが多い。
ブルースの影響を捨て去ることで、真っ白なヘヴィメタルの隆盛に寄与したのである[26][27]。
</div></details>

><details><summary>**KL**</summary><div>
ヘヴィメタルバンドにはギタリストが2人いることが多い。
1980年にはアイアン・メイデン、デフ・レパードがメジャーデビューし、シーンは一気に活性化していく。
LAメタル、またはグラム・メタルと呼ばれるジャンルが誕生する。
</div></details>

#最後に
実施前は、機械的テキスト要約なんて・・・という感があったが、結果を見た限り「機械も侮れんな」と思いました。要約文は部分的に切り取った印象もなくはないですが、各アルゴリズムの要約結果はそれなりの内容だったからです。
読みたいと思えない、読まざるを得ない長文が目の前にある場合は、まずこれで要約するとよいかもしれません。
文章説明が多い内容やデータベースの要約にも有効かもしれません。
ご興味ある方は、sumyを試してはいかがでしょうか？

sumy使用にあたり、うまくいかなかったり戸惑った点を、以下FAQに記載しました。こちらも参考にししていただければと思います。

次回は、「生成型」の自動要約にチャレンジしようと思います。
最後まで読んでいただき、ありがとうございました。

##FAQ
- ライブラリのインストールとインポートは数分かかります。気長に待ちましょう。
- 「ランタイム→すべてのセルを実行」し、ライブラリが読み込まれた後、[ファイル選択] ボタンをクリックし、要約したいテキストファイル（.txt）指定するだけです。
- 要約結果は、アルゴリズム毎に「原文書」「要約文書」が表示されます。全表示では、原文書とすべてのアルゴリズム（LexRank, Lsa, Reduction, Luhn, SumBasic, KL）の要約結果が確認できます。
- 要約センテンス数：要約したいセンテンス数をスライドバーにて設定できます。※設定幅：3～50（Default=3, 1Step）
- [**注意**] テキストファイルは文字コードを「UTF-8」としてください。
- [**注意**] 「ライブラリのインストール＆インポート」でエラーとなる場合がありました。 GiNZAをアンインストールし、再インストール（ランタイム再起動）すると復帰しました。最初のセルの#を外すとこれが実行できます。
- [**注意**] テキストの内容によって特定のアルゴリズムのみ要約エラーとなる場合がありました。 エラー箇所が表示されますので、必要に応じ該当箇所を修正したり、変更したり、カットしたりしてください。
- sumyの要約結果をデータフレームに納めたかったが、特殊なtypeなのか・・・うまくできなかった。

##参考サイト
https://software-data-mining.com/python%E3%81%AB%E3%82%88%E3%82%8B%E8%87%AA%E7%84%B6%E8%A8%80%E8%AA%9E%E5%87%A6%E7%90%86%E6%8A%80%E6%B3%95%E3%82%92%E3%81%B5%E3%82%93%E3%81%A0%E3%82%93%E3%81%AB%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F/

https://itdepends.hateblo.jp/entry/2020/06/07/210222

https://dendenblog.xyz/ginza-v5/

https://megagonlabs.github.io/ginza/

https://ai-tanteki.com/app/search