V tomto návodu vám ukážu, jak pomocí Langchain, RAG a GPT Vision získat obrázky i text z pdf dokumentu a následně tato data analyzovat.
Tento kód budeme spouštět pomocí platformy Google Colab.
Návod
Nejprve si nainstalujeme potřebné knihovny.
! pip install pdf2image
! pip install pytesseract
! apt install poppler-utils
! apt install tesseract-ocr
! pip install kaleido cohere tiktoken python-multipart
! pip install -U langchain openai chromadb langchain-experimental # (newest versions required for multi-modal)
! pip install "unstructured[all-docs]==0.10.19" pillow pydantic lxml pillow matplotlib tiktoken open_clip_torch torch
Nastavíme cestu, kde bude uložen pdf dokument a následně i všechny soubory.
import os
path = "/content/data/"
file_name = os.listdir(path)
V následujícím kódu uděláme:
- Nainstalujeme knihovny.
- Importujeme data.
- Extrahujeme obrázky/tabulky a rozdělíme text do chunks.
- Importujeme knihovny, vytvoříme chroma databázi, přidáme obrázky a dokumenty.
- Vytvoříme RAG.
- Importujeme knihovny a následně dekódujeme, změníme vel. obrázku a enkódujeme.
- Přidáme kontext a obrázky, pokud jsou přítomny, následně i systémovou výzvu pro analýzu.
- Nastavíme API klíč a položíme dotaz.
Nahrajeme pdf dokument do prostředí Google Colab. Jako příklad jsem použil jednu studii ohledně řeckých bronzových uměleckých soch.
Kód
# Extract images, tables, and chunk text
from unstructured.partition.pdf import partition_pdf
raw_pdf_elements = partition_pdf(
filename=path + file_name[0],
extract_images_in_pdf=True,
infer_table_structure=True,
chunking_strategy="by_title",
max_characters=4000,
new_after_n_chars=3800,
combine_text_under_n_chars=2000,
image_output_dir_path=path
) # Closing parenthesis was missing here
tables = []
texts = []
for element in raw_pdf_elements:
if "unstructured.documents.elements.Table" in str(type(element)):
tables.append(str(element))
elif "unstructured.documents.elements.CompositeElement" in str(type(element)):
texts.append(str(element))
print(len(tables))
print(len(texts))
import os
import uuid
import chromadb
import numpy as np
from langchain.vectorstores import Chroma
from langchain_experimental.open_clip import OpenCLIPEmbeddings
from PIL import Image as _PILImage
# Create chroma
vectorstore = Chroma(
collection_name="mm_rag_clip_photos", embedding_function=OpenCLIPEmbeddings()
)
# Get image URIs with .jpg extension only
image_uris = sorted(
[
os.path.join(path, image_name)
for image_name in os.listdir(path)
if image_name.endswith(".jpg")
]
)
# Add images
vectorstore.add_images(uris=image_uris)
# Add documents
vectorstore.add_texts(texts=texts)
# Make retriever
retriever = vectorstore.as_retriever()
import base64
import io
from io import BytesIO
import numpy as np
from PIL import Image
def resize_base64_image(base64_string, size=(128, 128)):
"""
Resize an image encoded as a Base64 string.
Args:
base64_string (str): Base64 string of the original image.
size (tuple): Desired size of the image as (width, height).
Returns:
str: Base64 string of the resized image.
"""
# Decode the Base64 string
img_data = base64.b64decode(base64_string)
img = Image.open(io.BytesIO(img_data))
# Resize the image
resized_img = img.resize(size, Image.LANCZOS)
# Save the resized image to a bytes buffer
buffered = io.BytesIO()
resized_img.save(buffered, format=img.format)
# Encode the resized image to Base64
return base64.b64encode(buffered.getvalue()).decode("utf-8")
def is_base64(s):
"""Check if a string is Base64 encoded"""
try:
return base64.b64encode(base64.b64decode(s)) == s.encode()
except Exception:
return False
def split_image_text_types(docs):
"""Split numpy array images and texts"""
images = []
text = []
for doc in docs:
doc = doc.page_content # Extract Document contents
if is_base64(doc):
# Resize image to avoid OAI server error
images.append(
resize_base64_image(doc, size=(250, 250))
) # base64 encoded str
else:
text.append(doc)
return {"images": images, "texts": text}
from operator import itemgetter
from langchain.chat_models import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda, RunnablePassthrough, RunnableParallel
def prompt_func(data_dict):
# Joining the context texts into a single string
formatted_texts = "\n".join(data_dict["context"]["texts"])
messages = []
# Adding image(s) to the messages if present
if data_dict["context"]["images"]:
image_message = {
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{data_dict['context']['images'][0]}"
},
}
messages.append(image_message)
# Adding the text message for analysis
text_message = {
"type": "text",
"text": (
"Jsi odborný kritik umění a historik. Máš za úkol analyzovat a interpretovat obrazy, "
"vzhledem k jejich historickému a kulturnímu významu. Vedle obrázků bude "
"poskytnuto se souvisejícím textem i kontext nabídky. Oba budou načteny z vektorové databáze "
"na základě klíčových slov zadaných uživatelem. Využij prosím své rozsáhlé znalosti a analytické schopnosti k poskytnutí "
"komplexního shrnutí, které obsahuje:\n"
"- Podrobný popis vizuálních prvků na obrázku.\n"
"- Historický a kulturní kontext obrázku.\n"
"- Interpretace symboliky a významu obrázku.\n"
"- Spojení mezi obrázkem a souvisejícím textem.\n\n"
f"Klíčová slova poskytnutá uživatelem: {data_dict['question']}\n\n"
"Text a/nebo tabulky:\n"
f"{formatted_texts}"
),
}
messages.append(text_message)
return [HumanMessage(content=messages)]
from google.colab import userdata
openai_api_key = userdata.get('OPENAI_API_KEY')
model = ChatOpenAI(temperature=0,
openai_api_key=openai_api_key,
model="gpt-4-vision-preview",
max_tokens=1024)
# RAG pipeline
chain = (
{
"context": retriever | RunnableLambda(split_image_text_types),
"question": RunnablePassthrough(),
}
| RunnableParallel({"response":prompt_func| model| StrOutputParser(),
"context": itemgetter("context"),})
)
response = chain.invoke("bronzová hlava")
#
print(response['response'])
print(response['context'])
Odpověď GPT:
Na obrázku je zobrazena bronzová hlava, která má všechny znaky klasického uměleckého díla. Hlava je vyobrazena v profilu a zdá se, že je součástí kruhového medailonu. Je zde patrný detailní modelace vlasů a obličejových rysů, stejně jako zdobení v podobě věnce nebo diadému na hlavě. Tato hlava také nese atributy, které by mohly naznačovat, že se jedná o zobrazení bohyně, možná Athény, vzhledem k přítomnosti co se zdá být sova a olivových větví, které jsou tradičně spojeny s touto bohyní moudrosti a válečné strategie v řecké mytologii.
Historický a kulturní kontext obrázku je zřejmě helénistické období, což je doba po Alexandru Velikém, kdy řecká kultura a vliv pronikaly do široké oblasti a mísily se s místními kulturami. Helénistické umění je známé svou dramatickou expresivitou a detailním zpracováním, což je vidět i na tomto medailonu.
Interpretace symboliky a významu obrázku může odkazovat na ideály moudrosti, ochrany a intelektuální síly, které Athéna představuje. Bronz jako materiál může symbolizovat trvalost a sílu, zatímco detailní zpracování povrchu může odkazovat na helénistické umělecké dovednosti a estetické hodnoty.
Spojení mezi obrázkem a souvisejícím textem je zřejmé v odkazu na helénistické umění a jeho schopnost vyjádřit hluboké a komplexní myšlenky prostřednictvím „výmluvných povrchů“. Text naznačuje, že i když jsou helénistické bronzové sochy vzdálené a neosobní, stále nám umožňují nahlédnout do jiného způsobu bytí a pojetí etického života. Tento medailon, který byl pravděpodobně nalezen v paláci v Thessaloniki a byl součástí vozu, je příkladem toho, jak helénistické umění dokázalo ztělesnit a předat tyto koncepty.
Můžeme i zobrazit referovaný obrázek z dokumentu.
from IPython.display import HTML, display
def plt_img_base64(img_base64):
# Create an HTML img tag with the base64 string as the source
image_html = f'<img src="data:image/jpeg;base64,{img_base64}" />'
# Display the image by rendering the HTML
display(HTML(image_html))
plt_img_base64(response['context']['images'][0])
Konverzace
Následně můžeme pokládat další otázky, které nás zajímají.
response = chain.invoke("Portrait of a Boy, analýza sochy a kontextu")
print(response['response'])
print(response['context'])
Odpověď:
Vizuální prvky na obrázku: Obrázek zobrazuje sochu chlapce, který je zobrazen v životní velikosti. Socha je vyrobena z mramoru a je velmi detailně zpracovaná. Chlapec má na sobě římský oděv a jeho vlasy jsou pečlivě upravené. Jeho výraz je zaměřený a zamyšlený, což naznačuje, že je to portrét někoho důležitého nebo významného.
Historický a kulturní kontext obrázku: Socha pochází z hellénistického období, které trvalo od konce 4. století př. n. l. do 1. století n. l. Toto období bylo charakterizováno rozšířením řecké kultury po celém Středomoří a Blízkém východě. Hellénistické sochařství bylo známé svou realistickou a detailní práci, což je vidět i na této soše. Socha může být portrétem nějakého významného chlapce, možná syna nějakého významného politika nebo vojevůdce.
Interpretace symboliky a významu obrázku: Socha může symbolizovat mládí, nevinnost a potenciál. Detailní zpracování a realistický výraz naznačují, že chlapec byl důležitou osobou v řecké společnosti. Jeho oděv a účes mohou naznačovat jeho společenský status nebo rodinné pozadí.
Spojení mezi obrázkem a souvisejícím textem: Text „HELLENISTIC SCULPTURE ART IN AMERICA“ naznačuje, že socha je součástí sbírky hellénistického sochařství v Americe. Text „ART IN AMERICA XX3“ a „ART IN AMERICA XX5“ může být odkazem na konkrétní výstavu nebo katalog, ve kterém je socha zahrnuta. Celkově text poskytuje kontext pro sochu a její význam v rámci amerického uměleckého světa. {‚images‘: [], ‚texts‘: [‚HELLENISTIC SCULPTURE ART IN AMERICA XX9‘, ‚ART IN AMERICA XX3‘, ‚ART IN AMERICA\n\nXX3‘, ‚HELLENISTIC SCULPTURE\n\nART IN AMERICA\n\nXX5‘]}
Kód funguje dobře na interpretaci umění, nebo jiných technických dokumentů s vizuálními prvky. Samozřejmě je tomu potřeba přizpůsobit i systémovou výzvu. Bohužel funkcionalita nedokáže zobrazit všechny typy obrázků. Když jsem např. zkoušel nějaké analytické vizualizace a grafy, tak je nebyla schopna rozeznat jako obrázky. Vždy si však můžete vyzkoušet, které obrázky se zobrazí a které ne pomocí funkce Python. U delších dokumentů trvá extrahování obrázků a tvorba vektor databáze poměrně dlouho.