Pythonで翻訳文の要約

Summarizing Translated Text in Python

しばらく遠ざかっていた長文要約の自動化を、ちゃんとやりたくなりました。

字幕起し翻訳の対訳表の下に、各字幕を連結してじっくり読めるようにした文章をつけていますが、これの要約があれば記事を読む人の助けになるだろうと思いました。

各記事のハイライト部分は筆者が判断してセンテンスを選んでいましたが、ここを機械の助けを借りて、より客観的なものにしようと思います。

Ubuntu 22.04.4 LTS で Python 3.10.12が動いています。

これらの記事を参考に、自分の用途に合うようにちょっと修正しました。

# mlnSumy.py
# Pythonで翻訳文の要約

# 対象テキストファイルのパス
import myConstants as my    # 作業フォルダへのパスをまとめて記載
textDir = my.textDir
matomeTxt = textDir + "matome.txt"
kekkaTxt = textDir + "kekka.txt"

'''
コーパスの作成
要約したい文章をsumyに渡すためにコーパスを作成します。
流れとしては1文ずつ形態素解析を行なって、単語を空白区切にします。
日本語と英語の場合で若干処理が異なるため、クラスを2つ作成。
'''

import spacy
import neologdn
import re
import emoji
import mojimoji

from janome.analyzer import Analyzer
from janome.charfilter import UnicodeNormalizeCharFilter, RegexReplaceCharFilter
from janome.tokenizer import Tokenizer as JanomeTokenizer  # sumyのTokenizerと名前が被るため
from janome.tokenfilter import POSKeepFilter, ExtractAttributeFilter

class JapaneseCorpus:
    # ① spacyで用いる辞書の読み込み・形態素解析機の準備
    def __init__(self):
        self.nlp = spacy.load('ja_ginza')
        # 極端に長い入力でコケる。文字数制限を拡張する方法を模索中
        # self.nlp.max_length = 1500000 <--- 効かなかった

        self.analyzer = Analyzer(
            char_filters = [UnicodeNormalizeCharFilter(), RegexReplaceCharFilter(r'[(\)「」、。]', ' ')],
            tokenizer = JanomeTokenizer(),
            token_filters = [POSKeepFilter(['名詞', '形容詞', '副詞', '動詞']), ExtractAttributeFilter('base_form')]
        )
        
    # ② テキストを前処理にかける。
    def preprocessing(self, text):
        # まとめ文に入っている< >と《 》の部分を除去
        text = re.sub(r'<.*?>', '', text) 
        text = re.sub(r'《.*?》', '', text) 
        # 一般的な処理
        text = re.sub(r'\n', '', text)
        text = re.sub(r'\r', '', text)
        text = re.sub(r'\s', '', text)
        text = text.lower()
        text = mojimoji.zen_to_han(text, kana=True)
        text = mojimoji.han_to_zen(text, digit=False, ascii=False)
        text = ''.join(c for c in text if c not in emoji.EMOJI_DATA)  #'UNICODE_EMOJI'から変更された
        text = neologdn.normalize(text)
        return text
    # ③ 文章を1文ずつに分ける。返り値はDocオブジェクトで、1文ずつに分割した結果と形態素解析の結果を保持している。
    def make_sentence_list(self, sentences):
        doc = self.nlp(sentences)
        self.ginza_sents_object = doc.sents
        sentence_list = [s for s in doc.sents]
        return sentence_list
    # ④ 1文ずつ単語を空白で区切る。日本語の場合は形態素解析が必要。
    # janomeで形態素解析を行う。形態素解析の部分では名詞、副詞、形容詞、動詞の単語のみを残して空白区切り。
    def make_corpus(self):
        corpus = [' '.join(self.analyzer.analyze(str(s))) + '。' for s in self.ginza_sents_object]
        return corpus

class EnglishCorpus(JapaneseCorpus):
    # ①
    def __init__(self):
        self.nlp = spacy.load('en_core_web_sm')
    # ②
    def preprocessing(self, text):
        # パラグラフにまとめた文章に入っている< >と《 》の部分を除去
        text = re.sub(r'<.*?>', '', text) 
        text = re.sub(r'《.*?》', '', text) 
        # 一般的な処理
        text = re.sub(r'\n', '', text)
        text = re.sub(r'\r', '', text)
        text = mojimoji.han_to_zen(text, digit=False, ascii=False)
        text = mojimoji.zen_to_han(text, kana=True)
        text = ''.join(c for c in text if c not in emoji.UNICODE_EMOJI)
        text = neologdn.normalize(text)
        return text
    # ④
    def make_corpus(self):
        corpus = []
        for s in self.ginza_sents_object:
            tokens = [str(t) for t in s]
            corpus.append(' '.join(tokens))
        return corpus

# :*:・。,☆゚"・:*:・。,。・:*:・゚"☆,。・:*:*:・。,☆゚"・:*:・。,。・:*:・゚"☆。:*:・。,☆゚"・:*:・。,。・:*:・゚"☆,。・:*:*:・。,☆゚"・:*:・。,。・:*:

'''
要約
sumyを使った要約を行う。コーパスを渡すだけで要約結果を出力してくれる。
あらかじめ機能として備わっているLexRank、TextRank、LSA、KL、 Luhn、 Reduction、SumBasicのアルゴリズムを使えるような実装。
sentences_count 要約後の文章の数を指定
'''

from sumy.parsers.plaintext import PlaintextParser
from sumy.nlp.tokenizers import Tokenizer
from sumy.utils import get_stop_words
# algorithms
from sumy.summarizers.lex_rank import LexRankSummarizer
from sumy.summarizers.text_rank import TextRankSummarizer
from sumy.summarizers.lsa import LsaSummarizer
from sumy.summarizers.kl import KLSummarizer
from sumy.summarizers.luhn import LuhnSummarizer
from sumy.summarizers.reduction import ReductionSummarizer
from sumy.summarizers.sum_basic import SumBasicSummarizer
algorithm_dic = {"lex": LexRankSummarizer(), "tex": TextRankSummarizer(), "lsa": LsaSummarizer(),\
                 "kl": KLSummarizer(), "luhn": LuhnSummarizer(), "redu": ReductionSummarizer(),\
                 "sum": SumBasicSummarizer()}
def summarize_sentences(sentences, sentences_count=3, algorithm="lex", language="japanese"):
    # ①
    if language == "japanese":
        corpus_maker = JapaneseCorpus()
    else:
        corpus_maker = EnglishCorpus()
    preprocessed_sentences = corpus_maker.preprocessing(sentences)
    preprocessed_sentence_list = corpus_maker.make_sentence_list(preprocessed_sentences)
    corpus = corpus_maker.make_corpus()
    parser = PlaintextParser.from_string(" ".join(corpus), Tokenizer(language))
    # ②
    try:
        summarizer = algorithm_dic[algorithm]
    except KeyError:
        print("algorithm name:'{}'is not found.".format(algorithm))
    summarizer.stop_words = get_stop_words(language)
    summary = summarizer(document=parser.document, sentences_count=sentences_count)
    # ③
    if language == "japanese":
        return "".join([str(preprocessed_sentence_list[corpus.index(sentence.__str__())]) for sentence in summary])
    else:
        return " ".join([sentence.__str__() for sentence in summary])

# :*:・。,☆゚"・:*:・。,。・:*:・゚"☆,。・:*:*:・。,☆゚"・:*:・。,。・:*:・゚"☆。:*:・。,☆゚"・:*:・。,。・:*:・゚"☆,。・:*:*:・。,☆゚"・:*:・。,。・:*:
if __name__ == "__main__":
    # まとめ文から読込
    with open(matomeTxt, "r") as f:
        text = f.read()

    sentences_count = 3
    # "lex": LexRankSummarizer()
    # "tex": TextRankSummarizer()
    # "lsa": LsaSummarizer()
    # "kl": KLSummarizer()
    # "luhn": LuhnSummarizer()
    # "redu": ReductionSummarizer()
    # "sum": SumBasicSummarizer()
    algorithmList = ["lex", "tex", "lsa", "kl","luhn", "redu", "sum"]
    language="japanese"

    # 結果ファイルの初期化
    with open(kekkaTxt, "w") as ff:
        ff.write("")

    # リストにあるアルゴリズムの結果を全部書き出す
    for i in algorithmList:
        sum_sentences = summarize_sentences(
            text,
            sentences_count = sentences_count,
            algorithm = i,
            language=language
            )
        resultList = sum_sentences.split("。")
        resultList = resultList[:-1]
        outTxt = "\n" + i + " ----------------------------------\n"
        for j in resultList:
            outTxt = outTxt + j + "。\n"
        with open(kekkaTxt, "a") as ff:
            ff.write(outTxt)
        print(outTxt)

動作結果

最近翻訳した「グレート・リセット」を阻止せよ。それは人類に劇的な結果をもたらす。和訳全文で試した結果です。

lex
  • 何年もの間、世界経済フォーラム(wef)をはじめとする金融寡頭勢力によって作られた組織が、「グレート・リセット」の必要性を宣言してきました。
  • 様々な歴史的リセットがありましたが、そのいずれもが人類にとって劇的な損失を伴っていたことを指摘しているのです。
  • その技術的、芸術的な完成度と絶大な安定性からすると、これらの建物は、動力といえば馬車しかなく、技術的な補助設備も発達していなかった時代には、全く当てはまりません。
tex
  • 様々な歴史的リセットがありましたが、そのいずれもが人類にとって劇的な損失を伴っていたことを指摘しているのです。
  • そして神経科学者たちは、今日の人類が本来持っている脳力の10%しか使えていないことが証明されたと見ています。
  • 今回の少々厳しい内容の番組は、皆さんが検討し、考え、議論し、行動するよう促すことを目的としています。
lsa
  • それというのもこの番組は、優生学的な動機に基づく金融寡頭政治が現在意図している「グレート・リセット」の計画が、いかに重大なものであるかということを示すからです。
  • 今日でもその痕跡を見ることができる大災害が、この洗練された文明を殆ど消し去ってしまったのでしょうか?
  • 泥の洪水-埋もれた過去ベルリン、シカゴ、モスクワなど、世界中の主要都市の殆どで、地面に沈んだような古い家屋が見つかっています。
kl
  • 何年もの間、世界経済フォーラム(wef)をはじめとする金融寡頭勢力によって作られた組織が、「グレート・リセット」の必要性を宣言してきました。
  • もし私たちがすでにリセットの犠牲者であり、遺伝的に縮小された人間であり、「本来の人間」の代替物に過ぎないとしたら?
  • 写真や地図、目撃者の証言などを比較すると、19世紀初頭、泥の洪水と呼ばれる不可解な世界的現象が文明全体を埋没させたことがわかります。
luhn
  • 考古学者や歴史学者は、私たちの公式な歴史学が破綻や矛盾に満ちていると指摘しています。
  • それというのもこの番組は、優生学的な動機に基づく金融寡頭政治が現在意図している「グレート・リセット」の計画が、いかに重大なものであるかということを示すからです。
  • 高度に発達した文明の痕跡全ての大陸で、驚くほどよく似た建築様式の建物(教会や大聖堂、モスク、豪華な図書館、政府の建物)が見つかっています。
redu
  • 様々な歴史的リセットがありましたが、そのいずれもが人類にとって劇的な損失を伴っていたことを指摘しているのです。
  • そして神経科学者たちは、今日の人類が本来持っている脳力の10%しか使えていないことが証明されたと見ています。
  • 今回の少々厳しい内容の番組は、皆さんが検討し、考え、議論し、行動するよう促すことを目的としています
sum
  • これは、世界経済や金融経済だけでなく、全ての人々の社会構造を「新しい正常性」にリセットするものです。
  • 今回の少々厳しい内容の番組は、皆さんが検討し、考え、議論し、行動するよう促すことを目的としています。
  • 公式の歴史書には、このテーマについては何も書かれていません。

まとめ

上の表で2回以上出たセンテンスをまとめると以下のようになりました。

  • 何年もの間、世界経済フォーラム(wef)をはじめとする金融寡頭勢力によって作られた組織が、「グレート・リセット」の必要性を宣言してきました。

  • 様々な歴史的リセットがありましたが、そのいずれもが人類にとって劇的な損失を伴っていたことを指摘しているのです。

  • そして神経科学者たちは、今日の人類が本来持っている脳力の10%しか使えていないことが証明されたと見ています。

  • 今回の少々厳しい内容の番組は、皆さんが検討し、考え、議論し、行動するよう促すことを目的としています。

  • それというのもこの番組は、優生学的な動機に基づく金融寡頭政治が現在意図している「グレート・リセット」の計画が、いかに重大なものであるかということを示すからです。

ちなみにこれは、筆者が重要だと思ったことの箇条書きです。

  • 金融寡頭勢力による「グレート・リセット」は人類にとって劇的な損失をもたらす。

  • 私たちは既にリセットの犠牲者であり、遺伝的に縮小された人間なのだろうか? 歴史的証拠は?

  • 優生学的な動機に基づくグレート・リセットを、毅然として立ち向かい逆転する動機にしよう。

ふむふむ。プログラムの結果も筆者の要約も似てますね。これは今後の翻訳文要約に使えそうです。 嬉しい。


関連記事