Open5

OpenAIのAPIでは扱えない大きなテキストを要約する

kurehajimekurehajime

Open AIのAPIには一度に利用できるトークン数に縛りがある。
とはいえ頑張ってOpenAIのAPIでは扱えない大きなテキストを要約してみる。

方法は以下の通り色々考えられるが、今回は一番シンプルな1のアプローチを試す。

  1. 単純に分割して、細切れになったものを最後に統合して要約する
  2. 前の方から少しずつ要約して、次のブロックを要約するときには前の要約を添える
  3. 単純に分割するのではなく、範囲が一部重複するように分割する
kurehajimekurehajime

ソース

言語はPython。環境はGoogle Colabを利用する。

トークンを数えるためにtiktoken、OpenAIのAPIを叩くためにopenai をインストールする。

!pip install tiktoken
!pip install openai 

APIキーをプログラムに読み込ませる。APIキーを手に入れるにはOpenAIのサイトで会員登録する必要がある。

from getpass import getpass
secret = getpass('Enter the secret value: ')

要約を試すために走れメロスをダウンロードしてローカルに保存する。

!mkdir ./data
!curl -L -o ./data/走れメロス.txt https://gist.githubusercontent.com/kurehajime/def4e8a65a68f4c95520944b77317213/raw/57a91dc0f845269ef01a1935637bb93c6588e4b0/%25E8%25B5%25B0%25E3%2582%258C%25E3%2583%25A1%25E3%2583%25AD%25E3%2582%25B9.txt

走れメロスのデータを読み込んで、トークン数を数えてみる。

import tiktoken
encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")
data = None
with open('./data/走れメロス.txt', 'r') as f:
  data = f.read()
count = len(encoding.encode(data))
print(count)

トークン数は11200。ChatGPTのAPIでは一度に4000トークンまでしか扱えない。

11200

トークン数に応じてテキストを分割する関数を定義する。
この関数に最大トークン数を決めてテキストを渡すと、それを超えそうに、かつ変なところで分断されないように分割してくれる。

def split_by_token(text,block_size,sep):
  lines = text.split(sep)
  blocks = []
  token = 0
  block = ''
  for line in lines:
    t = len(encoding.encode(line))
    if token > 0 and block_size < (token + t):
      blocks.append(block)
      token = 0
      block = '' 
    token += t
    block += line
  blocks.append(block)
  return blocks

最後に要約を表示する。
まずは各ブロックごとに要約を表示し、最後に全体の要約を表示する。

blocks = split_by_token(data,2000,'\n')
sumally = ''
for block in blocks:
  suma = summarize(block,140) + '\n'
  print(suma)
  sumally += suma
final_sumally = summarize(sumally,140)
print('【要約】')
print(final_sumally)
kurehajimekurehajime

結果

各ブロックごとの要約

メロスは暴君の王を倒すため、都市へ行く決心をする。しかし、都市は暗く、人々は恐れている。老人から聞いた話によると、王は多くの人々を殺害し、臣下の心まで疑っているという。メロスは王宮に入るが、短剣を持っていたため、警備員に捕まってしまう。暴君は彼を問い返し、民の忠誠を疑っていることを告白する。二人は意見が対立し、物語は終わる。

メロスは、処刑までに3日間の日限を求め、妹に結婚式を挙げさせたいと頼み込むが、暴君は身代わりの男を殺してやることを計画する。メロスは友人に事情を話し、身代わりとして取り残された友人を殺さないように頼む。メロスは村に到着し、妹に結婚式を挙げるが、疲労困憊で倒れてしまう。

メロスは急いで花婿の家に行き、明日に結婚式をしてもらえるよう頼んだ。最初は婿が断ったが、夜明けまでの議論の末、承諾した。しかし、結婚式当日に大雨が降り始めた。祝宴は楽しく続いていたが、メロスは約束の時間までに帰らなければならないと思い、花嫁や花婿に別れを告げ、出発することに。

主人公が友人を救う為に駆け足で走る途中、洪水により橋が壊れ、激流に飲み込まれてしまう。しかし、泳ぎ切って対岸に着き、山賊に襲われたが、一人を倒して武器を手に入れる。

メロスは山賊を倒し、疲れて膝を折り、友を裏切ったことに苦しみながら、定められた不名誉な運命に従うことを決める。彼は自分が一人の裏切り者であることを嘆きながら、眠ってしまう。

メロスは遅れていない、信頼されている。希望を持ち、肉体の疲れを癒やし、正義のため走る。悪魔の囁きを忘れ、愛と誠の力を信じる。フィロストラトスから、友人のセリヌンティウスが死刑になったことを聞くが、メロスは諦めない。最後の力を振り絞り、沈む夕陽の中、刑場に突入し、間に合った。

群衆に埋もれながらも、磔の柱に釣り上げられた友人を救うために、メロスは縄がほどかれる前に叫ぶ。そして、頬を殴り合い、「信実とは、決して空虚な妄想ではなかった」と暴君ディオニスに言われる。最後に、メロスは緋のマントを着て少女に捧げられ、勇者自身は赤面するのであった。

最後から二番目のブロックのフィロストラトスから、友人のセリヌンティウスが死刑になったことを聞くが、メロスは諦めない。という記述。なんか勝手にセリヌンティウスが殺されている。

おそらく原文の「フィロストラトスでございます。貴方のお友達セリヌンティウス様の弟子でございます。」その若い石工も、メロスの後について走りながら叫んだ。「もう、駄目でございます。むだでございます。走るのは、やめて下さい。もう、あの方をお助けになることは出来ません。」という記述に引っ張られたのだろう。

全体の要約

メロスは暴君を倒すため都市へ向かうが、警備員に捕まってしまう。友人を救うため身代わりの男を止めるために駆け、洪水と山賊に遭いながら戦い、最後に友人を救出する。正義と愛を信じ、友情を守るために闘い続けたメロスは最後に勝利し、少女の愛を受ける。

警備員という表現が気になるが、原文の巡邏の警吏を意訳したのだろう。おおむね要約できている。