スキャン画像をOCRしたテキストデータをChatGPT APIで補正してみる
本が好きというよりは本を買うのが好きな私の本棚は常にぎゅうぎゅうに圧迫されており、ましてや最近は結局kindleやiPhoneからしか本を読まないのもあって、物理本についてはせっせとスキャンしてくれるサービスにお任せしながら居住環境を拡げる作業に日々勤しんでおります。
そんなこんなで既に400冊以上の蔵書がデジタル化されている訳ですが、全ての本を読み返すかと言えば読み返すわけでもなく、でもなんかせっかくデータがあるんだから知のデータベース(響きがかっこいい)的なものにできないかなとも思ったりします。
とりあえず猫も杓子もLlamaIndexにぶっ込んでしまえばそれっぽいものができそうな予感がしていますが、そもそもぶっ込むためのデータが必要です。
で、幸いBOOKSCANには取込データをOCRでテキストに変換してくれるサービスも附帯しているので、それをぶっ込んでみたらどうかと思ったわけです。
OCRの宿命:読み取りエラーはなくせない
しかしOCRはパーフェクトな技術ではありません。どうしたって読み取りエラーが発生します。以下の画像はSI出身の人ならみんな読んでる「人月の神話」のOCR結果の抜粋です。抜粋からも分かる通り、ところどころエラーが発生しています。
とりあえずChatGPTに入れてみたら何とかなるのでは?
人力で補正していくのも手ですが、かなりツライ作業になりそうです。
こういうツライ作業をAIにやってもらう手はないでしょうか。プロトタイピング的にChatGPTにお願いしてみたところ、そこそこ良さそうなアウトプットが得られました。
書籍全ての文字データをトークン数換算したところ249,640トークンだったため、API利用料金はざっくり0.5ドル程度の計算になります。一冊当たりこの程度の料金でデータクレジングできるのであれば実用上も問題なさそうです。
コード例
APIコール1回あたりの最大リクエストトークン数はプロンプトを含めて4,096トークンのため、ざっくりテキストデータを改行を単位とした3000文字以内のデータに分割します。
def split_text_by_newline(text_file, max_len):
sentences = []
sentence = ''
with open(text_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if len(sentence + line) <= max_len:
sentence += line
if len(sentence) > 0:
sentence += '\n'
else:
sentences.append(sentence)
sentence = line + '\n'
if len(sentence) > 0:
sentences.append(sentence)
return sentences
chunks = split_text_by_newline('ningetsu.txt', 3000)
実行したところ80個のチャンクになりました。
チャンク毎にChatGPTのAPIを呼び出して補正をしてもらいます。API呼び出しにはlangchainを利用しています。連続でAPIを呼び出すと、そこそこの頻度で500エラーが返ってくることがあるのですが、langchainを使えば勝手にリトライしてくれるのでありがたいです。
from langchain.llms import OpenAIChat
from langchain import PromptTemplate, LLMChain
# プリフィクスメッセージの指定
prefix_messages = [
{"role": "system", "content": "あなたは読み取りエラーの起きた文章を、可能な限りオリジナルに近い形に補正&修正するためのソフトウェアです。"}
]
llm = OpenAIChat(
temperature=0.9,
prefix_messages=prefix_messages
)
template = """次の文章の中にはスキャナによって読み取りエラーの起きた文字が含まれています。可能な限りオリジナルに近い形に補正して下さい。
{book_data}"""
prompt = PromptTemplate(
template=template,
input_variables=["book_data"]
)
llm_chain = LLMChain(
prompt=prompt,
llm=llm
)
results = []
for i, chunk in enumerate(chunks):
print(f"processing: {i + 1} / {len(chunks)}")
output = llm_chain.run(chunk)
results.append(output)
print("complete!")
プリフィクスメッセージの効果
上記のコードでは「あなたは読み取りエラーの起きた文章を、可能な限りオリジナルに近い形に補正&修正するためのソフトウェアです。」というプリフィクスメッセージを指定しています。
systemロールとしてプリフィクスメッセージを活用してChatGPTの振る舞いを定義しておくと、定義していないときに比べて期待する動作への精度が高まりました。
下図は元ファイルと、プリフィクスを指定しない場合の補正結果です。冒頭の英文に引きずられて、著者紹介まで英文になってしまっています。
次に示すのがプリフィクスを指定した場合の補正結果です。あくまで原文に忠実であるように指定しているため、英文のコンテキストに引きずられていません。
期待する動作が明確な場合は、プリフィクスメッセージを指定した方がより期待している動作に近づくと言えます。
実行結果の検証
diffをとったところ、ところどころ単語レベルでおかしくなっている文章が文脈を踏まえて訂正されていることが分かりました。
一方で内容がごっそり抜け落ちている箇所もあり、あまり動作は安定しない印象・・・。
完全に文字化けしてしまっている箇所に関しては「こちらの部分は読み取りが困難で、正確に補正することができません。」というメッセージが残されていました。
本文の内容そのものが削られてしまう動作は問題ですが、以下のようなレベルで荒れているテキストをそれなりに読める形にしてくれるという点では、使い方によってはかなり有用だと感じました。
この記事が気に入ったらサポートをしてみませんか?