Open7
LMDX: Cloud Vision API x LLMで画像から構造化データ作成
ここら辺のアプローチを参考に、画像から構造化データ作成を試してみるのが良さそう。
LMDXの手法から試す。
下記ステップで実現するみたいだが、今回は試し打ちなので、"Chunking"と"Decoding"はなしで実装してみる。時間があれば、"Decoding"は部分的に実装するかも...
- OCR
- Chunking
- Prompt generation
- LLM inference
- Decoding
OCR
Cloud Vision APIを使う、以上。
import io
from google.cloud import vision
client = vision.ImageAnnotatorClient()
# TODO: fileを定義
with io.open(file, 'rb') as image_file:
content = image_file.read()
image = vision.Image(content=content)
res = client.document_text_detection(
image=image,
image_context={'language_hints': ['ja']})
ocr_result = res
Prompt generation
OCR結果をどのような形でLLMに渡すかを決めないといけない。
- ワード単位のセグメントを利用するか、行単位のセグメントを利用するか?
- 4つ角の座標を利用するか、(x, y)のシンプルな座標を利用するか?
- 量子化(ビニング)の粒度
今回は、「paragraph
単位のセグメント、シンプルな座標(4つ角の中央座標)、B=100での量子化」でやってみる。
Cloud Vision APIのレスポンスからparagraph
ごとにデータを抽出し、量子化された中央座標を算出、それを元にLLMに渡すドキュメントデータを作成する。
最後に、LLMに渡すプロンプトを作成する。
Vertex = namedtuple('Vertex', ['x', 'y'])
Sentence = namedtuple('Sentence', ['text', 'bounding_box', 'quantized_center'])
def generate_document(full_text_annotation):
def main(full_text_annotation):
sentences = extract_sentences_from_full_text_annotation(full_text_annotation)
# ここが大事
document = "".join(f"{sentence.text} {sentence.quantized_center.x}|{sentence.quantized_center.y}\n" for sentence in sentences)
return document
def quantize(value, min_value, max_value, bins):
bin_size = (max_value - min_value) / bins
return int((value - min_value) // bin_size)
def extract_sentences_from_full_text_annotation(full_text_annotation, bins = 100):
sentences = []
for page in full_text_annotation.pages:
max_x = page.width
max_y = page.height
for block in page.blocks:
for paragraph in block.paragraphs:
paragraph_text = ""
for word in paragraph.words:
word_text = "".join(symbol.text for symbol in word.symbols)
paragraph_text += word_text + " "
min_x = 0
min_y = 0
vertices = paragraph.bounding_box.vertices
center_x = sum(vertex.x for vertex in vertices) / 4
center_y = sum(vertex.y for vertex in vertices) / 4
quantized_center_x = quantize(center_x, min_x, max_x, bins)
quantized_center_y = quantize(center_y, min_y, max_y, bins)
quantized_center = Vertex(quantized_center_x, quantized_center_y)
sentences.append(Sentence(paragraph_text, paragraph.bounding_box, quantized_center))
return sentences
return main(full_text_annotation)
def generate_task():
task = "From the document, extract the text values and tags of the following entities:\n"
# 適宜、フォーマット変える。
task += '{ "name": "", "address": "" }\n'
return task
def generate_prompt(full_text_annotation):
document = generate_document(full_text_annotation)
task = generate_task()
prompt = f"<Document>\n{document}</Document>\n"
prompt += f"<Task>\n{task}</Task>\n"
prompt += "<Extraction>\n"
return prompt
LLM inference
Geminiで生成したプロンプトを用いて構造化データを生成する。
※gpt-4o
だと座標(x|y
のやつ)が消されて構造化データにしてしまう。また、精度もgemini-pro
の方が良かった。
import google.generativeai as genai
model = "models/gemini-pro"
client = genai.GenerativeModel(model)
def generate_text(prompt):
response = client.generate_content(prompt)
return response.text
備考
10個程度教師データ準備して、ファインチューニングをすれば、66 -> 89に精度が上がるみたいなので、これ試した方が良さそう。
LayoutML触る。