LLM Uygulamaları için Yapılandırılmamış Verilerin Ön İşlemesi

Cahit Barkin Ozer
10 min readApr 25, 2024

Deeplearningai’ın “Preprocessing Unstructured Data for LLM Applications” kısa kursunun Türkçe özeti.

English:

LLM’ler İçin Verilerin Ön İşlenmesi

RAG, LLM cevaplarının doğruluğundan emin olunan bir dış bilgiyle temellendirilmesi işlemidir. RAG uygulamaları bağlamı veritabanına yükler sonra gelen isteğe göre alakalı bağlam parçalarını getirir ve onları LLM’lere istek olarak yollar.

Şirketlerin veri kaynakları PDF, Word, email veya HTML gibi farklı formatlarda olabilmektedir.

Dokümanların elemanları vardır, bunlar: Başlıklar, tablolar, liste elemanları, görseller ve metinlerdir.

Dokümanların elemanların da metaverileri vardır, bunlar elemanlar hakkında daha fazla detay bilgi verirler. Hibrit arama veya kaynakla ilgili bilgilere ulaşmak için bu metaveriler faydalıdırlar. Bazı metaveriler: dosya ismi, dosya tipi, sayfa numarası ve kısım gibi bilgilerdir.

Neden verilerin ön işlenmesi zordur?

  • Farklı doküman tiplerinin eleman tipleri için görsel veya işaretsel gibi farklı ipuçları vardır.
  • Farklı doküman tiplerinin içeriğinin işlenebilmesi için standardizasyona ihtiyaç vardır.
  • Formlar ve makaleleri gibi farklı doküman formatları farklı veri çıkarma yöntemlerine ihtiyaç duyabilir.
  • Metaverileri çıkarma işlemi doküman yapısını bilmeyi gerektirir.

İçeriğin Normalleştirilmesi

Dokümanlar PDF, Word, EPUB, Markdown gibi çeşitli formatlarda bulunurlar. Verilerin ön işlenmesindeki ilk adım ham dokümanların başlık ve metin gibi ortak yapıları bulunduran ortak bir formata dönüştürülmesidir.

Normalize edilmiş formatlar herhangi bir dokümanın kaynak formatından bağımsız olarak aynı şekilde işlenmesine izin verirler. Buna örnek başlık veya alt metin gibi istenmeyen elemanların filtrelenmesi veya doküman elemanlarının parçalara ayrılması verilebilir.

Normalleştirme işlemi işlem maaliyetini düşürür. Başlangıç doküman işleme adımları, kaynak formatından bağımsız olarak işlemlerin en pahalı parçalarıdırlar. Normalize edilmiş çıktılarda parçalama(chunking) gibi işlemler maaliyetsizdir. Normalleştirme, dokümanları tekrar işlemeden birden farklı parçalamayıp deneyimlemeye imkan verir.

Veri Serileştirme

Verileri serileştirmek doküman işlemenin sonuçlarının tekrar kullanılabilir olmasına olanak sağlar.

JSON formatının avantajı popüler ve iyi biliniyor olması, standart bir HTTP cevabı olması, birçok programlama dilinde kullanılabiliyor olması ve JSONL formatına dönüştürülüp yayın akışına (streaming) çevrilebiliyor olmasıdır.

HTML sayfaları da çok önemli bir veri tipidir. HTML verileri işleyebiliyor olmak, internetteki taze verileri otomatik olarak direkt sisteminize entegre edebilmenizi sağlar.

Microsoft PowerPoint, LLM’in bilgi tabanını geliştirmek için önemli olan fikirleri, stratejileri ve sonuçları sunmak amacıyla danışmanlıkta yaygın olarak kullanılmaktadır. Çıkarma işlemi, PowerPoint slaytlarındaki madde işaretli paragraflar, slayt notları, şekiller ve tablolar gibi öğelerin ayrıştırılmasını içerir. Pptx gibi Python kitaplıkları dolaşılır ve slaytlardan metinsel veya görsel bilgiler çıkarır.

from IPython.display import JSON

import json

from unstructured_client import UnstructuredClient
from unstructured_client.models import shared
from unstructured_client.models.errors import SDKError

from unstructured.partition.html import partition_html
from unstructured.partition.pptx import partition_pptx
from unstructured.staging.base import dict_to_elements, elements_to_json

from Utils import Utils
utils = Utils()

DLAI_API_KEY = utils.get_dlai_api_key()
DLAI_API_URL = utils.get_dlai_url()

s = UnstructuredClient(
api_key_auth=DLAI_API_KEY,
server_url=DLAI_API_URL,
)

Örnek Belge: Medium Blog HTML Sayfası

from IPython.display import Image
Image(filename="images/HTML_demo.png", height=600, width=600)

filename = "example_files/medium_blog.html"
elements = partition_html(filename=filename)

element_dict = [el.to_dict() for el in elements]
example_output = json.dumps(element_dict[11:15], indent=2)
print(example_output)

JSON(example_output)

Örnek Belge: OpenAI’de MSFT PowerPoint

Image(filename="images/pptx_slide.png", height=600, width=600) 

filename = "example_files/msft_openai.pptx"
elements = partition_pptx(filename=filename)

element_dict = [el.to_dict() for el in elements]
JSON(json.dumps(element_dict[:], indent=2))

Örnek Belge: Düşünce Zinciri PDF’i

Image(filename="images/cot_paper.png", height=600, width=600) 

filename = "example_files/CoT.pdf"
with open(filename, "rb") as f:
files=shared.Files(
content=f.read(),
file_name=filename,
)

req = shared.PartitionParameters(
files=files,
strategy='hi_res',
pdf_infer_table_structure=True,
languages=["eng"],
)
try:
resp = s.general.partition(req)
print(json.dumps(resp.elements[:3], indent=2))
except SDKError as e:
print(e)

JSON(json.dumps(resp.elements, indent=2))

Metaveri Çıkarma ve Parçalama

Meta veriler, kaynak belgelerden çıkarılan içerik hakkında ek bilgi sağlar. Meta veri türlerinden biri, dosya adı, kaynak URL’si ve dosya türü gibi belgenin kendisi hakkındaki bilgilerdir. RAG sistemlerinde meta veriler, hibrit arama için filtreleme seçenekleri sağlar.

Çok sayıda belge olması durumunda anlamsal olarak çok fazla benzer eşleşme olabilir. Kullanıcılar ayrıca anlamsal olarak en benzer eşleşme yerine en güncel bilgileri isteyebilir. Hibrit arama, anlamsal aramayı filtreleme ve anahtar kelime arama gibi diğer bilgi alma teknikleriyle birleştiren bir arama stratejisidir. Bu durumlar için dokümanlardaki meta veriler filtreleme seçeneği olarak kullanılabilir.

import logging
logger = logging.getLogger()
logger.setLevel(logging.CRITICAL)

import json
from IPython.display import JSON

from unstructured_client import UnstructuredClient
from unstructured_client.models import shared
from unstructured_client.models.errors import SDKError

from unstructured.chunking.basic import chunk_elements
from unstructured.chunking.title import chunk_by_title
from unstructured.staging.base import dict_to_elements

import chromadb

from Utils import Utils
utils = Utils()

DLAI_API_KEY = utils.get_dlai_api_key()
DLAI_API_URL = utils.get_dlai_url()

s = UnstructuredClient(
api_key_auth=DLAI_API_KEY,
server_url=DLAI_API_URL,
)

Örnek Belge: İsviçre’de Kış Sporları EPUB

from IPython.display import Image
Image(filename='images/winter-sports-cover.png', height=400, width=400)
Image(filename="images/winter-sports-toc.png", height=400, width=400)

Belgeyi Yapılandırılmamış API aracılığıyla çalıştırın

filename = "example_files/winter-sports.epub"

with open(filename, "rb") as f:
files=shared.Files(
content=f.read(),
file_name=filename,
)

req = shared.PartitionParameters(files=files)

try:
resp = s.general.partition(req)
except SDKError as e:
print(e)

JSON(json.dumps(resp.elements[0:3], indent=2))

Bölümlerle ilişkili öğeleri bulma

[x for x in resp.elements if x['type'] == 'Title' and 'hockey' in x['text'].lower()]

chapters = [
"THE SUN-SEEKER",
"RINKS AND SKATERS",
"TEES AND CRAMPITS",
"ICE-HOCKEY",
"SKI-ING",
"NOTES ON WINTER RESORTS",
"FOR PARENTS AND GUARDIANS",
]

chapter_ids = {}
for element in resp.elements:
for chapter in chapters:
if element["text"] == chapter and element["type"] == "Title":
chapter_ids[element["element_id"]] = chapter
break

chapter_to_id = {v: k for k, v in chapter_ids.items()}
[x for x in resp.elements if x["metadata"].get("parent_id") == chapter_to_id["ICE-HOCKEY"]][0]

Dokümanları bir vektör veri tabanına yükleme

client = chromadb.PersistentClient(path="chroma_tmp", settings=chromadb.Settings(allow_reset=True))
client.reset()

collection = client.create_collection(
name="winter_sports",
metadata={"hnsw:space": "cosine"}
)

# Below code takes a little
for element in resp.elements:
parent_id = element["metadata"].get("parent_id")
chapter = chapter_ids.get(parent_id, "")
collection.add(
documents=[element["text"]],
ids=[element["element_id"]],
metadatas=[{"chapter": chapter}]
)

Vector DB’deki öğeleri görün

results = collection.peek()
print(results["documents"])

Meta verilerle karma arama gerçekleştirin

result = collection.query(
query_texts=["How many players are on a team?"],
n_results=2,
where={"chapter": "ICE-HOCKEY"},
)
print(json.dumps(result, indent=2))

İçeriği Parçalara Ayırmak

Vektör veritabanları RAG işlemlerini gerçekleştirebilmek için dokümanların parçalara ayrılmasına ihtiyaç duyarlar çünkü LLM’lerin tek seferde işlenebilen token sayısı sınırı bulunmaktadır. Dokümanın parçalanma şekline göre aynı sorgu farklı cevaplar getirebilir. Dokümanı parçalara ayırmanın en temel yolu onu aynı boyuttaki parçalara ayırmaktır. Dokümanın yapısı biliniyorsa o doğal yapı korunacak şekilde parçalara da ayrılabilir.

Optimal parçalara ayırma yaklaşımı şu şekildedir: Önce doküman alt elementlerine göre başlıklar, alt başlıklar gibi ayrılır. Sonrasında parça limiti dolana kadar bu alt parçalar parçalara eklenir. Yeni bir parçaya geçilirken yeni bir parçaya geçildiğini belli edilen semboller eklenir. Mesela yeni bir başlık, anlamsal olarak yeni bir kısım gibi yerlere gelindiğinde eklenebilir. Ayrıca art arda gelen parçalar parça sınırını geçmiyorsa birleştirilebilir.

Aşağıda aynı sayfanın üstte karakter sayısına göre altta ise doküman elemanlarına göre bölündüğü bir örnek verilmiştir:

elements = dict_to_elements(resp.elements)

chunks = chunk_by_title(
elements,
combine_text_under_n_chars=100,
max_characters=3000,
)

JSON(json.dumps(chunks[0].to_dict(), indent=2))
len(elements)
len(chunks)

PDF’leri ve Görselleri Ön İşleme

HTML, Word belgeleri ve işaretleme gibi birçok belge türü biçimlendirme bilgilerini içerir. Bu belgeler kural tabanlı ayrıştırıcılarla önceden işlenebilir. PDF’ler ve resimler gibi diğer belgeler için biçimlendirme bilgileri görseldir. Belge Görüntü Analizi (DIA), bir belgenin ham görüntüsünden biçimlendirme bilgilerini ve metni çıkarmamızı sağlar.

En popüler 2 belge görüntü analiz yöntemi belge düzeni tespiti ve görüntü transformatörleridir. Belge düzeni algılama, belge görüntüsündeki düzen öğelerinin etrafına sınırlayıcı kutular çizmek ve etiketlemek için bir nesne algılama modeli kullanır.

Vision transformatör modelleri, girdi olarak bir belgenin görüntüsünü alır ve JSON gibi yapılandırılmış bir çıktının metin temsilini üretir. VIT’ler isteğe bağlı olarak bir metin istemi de alabilirler.

Belge düzeni tespitinde, sınırlayıcı kutuları tanımlamak ve sınıflandırmak için YOLOX veya Detectron2 gibi bir bilgisayarlı görme modelleri kullanılır. Daha sonra bu kutuların içindeki metin, gerektiğinde nesne karakter tanıma (OCR) kullanılarak çıkarılır. PDF’ler gibi bazı belgeler için metin, OCR olmadan doğrudan belgelerden çıkarılabilir.

Document Layout Detection example

Belge Düzeni Modelleri sabit bir öğe türleri kümesine sahiptir ve avantaj olarak sınırlayıcı kutu bilgilerini alır. Dezavantajı ise 2 model çağrısı gerektirmesi ve daha az esnek olmasıdır.

Vision Transformers’ta giriş görüntüleri kodlayıcıya iletilir ve kod çözücü metin çıktısı üretir. Belge Anlama Mimarisi (DONUT), VIT’ler için ortak bir mimaridir. Bu yaklaşımda, giriş görüntüsü doğrudan metne dönüştürüldüğü için OCR’lara gerek yoktur. VIT’ler, modeli yapılandırılmış belge çıktısıyla geçerli bir JSON dizesi çıkaracak şekilde eğitebilir.

Vision Transformers standart dışı belge türleri konusunda daha esnektir. Yeni ontolojilere daha kolay uyum sağlarlar. Dezavantajı ise modelin üretken olması nedeniyle halüsinasyonlara yatkın olmasıdır. VIT’ler hesaplama açısından pahalıdır.

from unstructured_client import UnstructuredClient
from unstructured_client.models import shared
from unstructured_client.models.errors import SDKError

from unstructured.partition.html import partition_html
from unstructured.partition.pdf import partition_pdf

from unstructured.staging.base import dict_to_elements

from Utils import Utils
utils = Utils()

DLAI_API_KEY = utils.get_dlai_api_key()
DLAI_API_URL = utils.get_dlai_url()

s = UnstructuredClient(
api_key_auth=DLAI_API_KEY,
server_url=DLAI_API_URL,
)

Örnek Belge: PDF ve HTML’deki Haberler

from IPython.display import Image
Image(filename="images/el_nino.png", height=600, width=600)

Dokümanı HTML olarak işleyin

filename = "example_files/el_nino.html"
html_elements = partition_html(filename=filename)

for element in html_elements[:10]:
print(f"{element.category.upper()}: {element.text}")

Belgeyi Belge Düzeni Algılamayla İşleyin

filename = "example_files/el_nino.pdf"
pdf_elements = partition_pdf(filename=filename, strategy="fast")

for element in pdf_elements[:10]:
print(f"{element.category.upper()}: {element.text}")

with open(filename, "rb") as f:
files=shared.Files(
content=f.read(),
file_name=filename,
)

req = shared.PartitionParameters(
files=files,
strategy="hi_res",
hi_res_model_name="yolox",
)

try:
resp = s.general.partition(req)
dld_elements = dict_to_elements(resp.elements)
except SDKError as e:
print(e)

for element in dld_elements[:10]:
print(f"{element.category.upper()}: {element.text}")

import collections

len(html_elements)

html_categories = [el.category for el in html_elements]
collections.Counter(html_categories).most_common()

len(dld_elements)

dld_categories = [el.category for el in dld_elements]
collections.Counter(dld_categories).most_common()

Tabloları Çıkarma

Finans ve sigorta gibi bazı endüstriler, belgelerin içindeki tablolar olan yapılandırılmamış verilerin içine yerleştirilmiş yapılandırılmış verilerle yoğun olarak ilgilenmektedir. Tablo sorusu cevap benzeri durumları uygulamak için belgelerden tabloların çıkarılması yararlı olacaktır. Tablo transformatörleri, görüntü transformatörleri ve OCR son işleme kullanılabilir. Tablolar ayrıca yapıyı korumak için HTML formatında da çıkarılabilir. OCR son işlemesi, iyi çalışan tablolar için hızlı ve doğrudur ancak istatistiksel veya kural tabanlı ayrıştırma gerektirir, daha az esnektir ve görüntünün sınırlayıcı kutularına geri bağlantısı yoktur.

Tablo transformatörleri, tablo hücreleri için sınırlayıcı kutuları tanımlayan ve çıktıyı HTML’ye dönüştüren modellerdir. Tablo transformatörlerini uygulamak için öncelikle belge yerleşim modelini kullanarak tabloları tanımlayın. Daha sonra tabloyu tablo transformatöründen geçirin. Tablo transformatörleri hücreleri orijinal sınırlayıcı kutulara kadar izleyebilir. Dezavantajı birden fazla pahalı model çağrısına duyulan ihtiyaçtır.

Extracting Tables with Vision Transformers
from unstructured_client import UnstructuredClient
from unstructured_client.models import shared
from unstructured_client.models.errors import SDKError

from unstructured.staging.base import dict_to_elements

from Utils import Utils
utils = Utils()

DLAI_API_KEY = utils.get_dlai_api_key()
DLAI_API_URL = utils.get_dlai_url()

s = UnstructuredClient(
api_key_auth=DLAI_API_KEY,
server_url=DLAI_API_URL,
)

Örnek Doküman: Yerleştirilmiş Resimler ve Tablolar

from IPython.display import Image
Image(filename="images/embedded-images-tables.jpg", height=600, width=600)

Belgeyi İşleyin ve Tabloları Çıkarın

filename = "example_files/embedded-images-tables.pdf"

with open(filename, "rb") as f:
files=shared.Files(
content=f.read(),
file_name=filename,
)

req = shared.PartitionParameters(
files=files,
strategy="hi_res",
hi_res_model_name="yolox",
skip_infer_table_types=[],
pdf_infer_table_structure=True,
)

try:
resp = s.general.partition(req)
elements = dict_to_elements(resp.elements)
except SDKError as e:
print(e)

tables = [el for el in elements if el.category == "Table"]

tables[0].text

table_html = tables[0].metadata.text_as_html

from io import StringIO
from lxml import etree

parser = etree.XMLParser(remove_blank_text=True)
file_obj = StringIO(table_html)
tree = etree.parse(file_obj, parser)
print(etree.tostring(tree, pretty_print=True).decode())

from IPython.core.display import HTML
HTML(table_html)

from langchain_openai import ChatOpenAI
from langchain_core.documents import Document
from langchain.chains.summarize import load_summarize_chain

llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo-1106")
chain = load_summarize_chain(llm, chain_type="stuff")
chain.invoke([Document(page_content=table_html)])

RAG Bot’u Oluşturma

from unstructured_client import UnstructuredClient
from unstructured_client.models import shared
from unstructured_client.models.errors import SDKError

from unstructured.chunking.title import chunk_by_title
from unstructured.partition.md import partition_md
from unstructured.partition.pptx import partition_pptx
from unstructured.staging.base import dict_to_elements

import chromadb

from Utils import Utils
utils = Utils()

DLAI_API_KEY = utils.get_dlai_api_key()
DLAI_API_URL = utils.get_dlai_url()

s = UnstructuredClient(
api_key_auth=DLAI_API_KEY,
server_url=DLAI_API_URL,
)

Örnek Uygulama: Donut Modeli Hakkında Soru-Cevap

from IPython.display import Image
Image(filename='images/donut_paper.png', height=400, width=400)
Image(filename='images/donut_slide.png', height=400, width=400)
Image(filename='images/donut_readme.png', height=600, width=600)

PDF’leri ön-işleyin

filename = "example_files/donut_paper.pdf"

with open(filename, "rb") as f:
files=shared.Files(
content=f.read(),
file_name=filename,
)

req = shared.PartitionParameters(
files=files,
strategy="hi_res",
hi_res_model_name="yolox",
pdf_infer_table_structure=True,
skip_infer_table_types=[],
)

try:
resp = s.general.partition(req)
pdf_elements = dict_to_elements(resp.elements)
except SDKError as e:
print(e)

pdf_elements[0].to_dict()

tables = [el for el in pdf_elements if el.category == "Table"]

table_html = tables[0].metadata.text_as_html

from io import StringIO
from lxml import etree

parser = etree.XMLParser(remove_blank_text=True)
file_obj = StringIO(table_html)
tree = etree.parse(file_obj, parser)
print(etree.tostring(tree, pretty_print=True).decode())

Image(filename='images/donut_references.png', height=400, width=400)

reference_title = [
el for el in pdf_elements
if el.text == "References"
and el.category == "Title"
][0]

reference_title.to_dict()

references_id = reference_title.id

for element in pdf_elements:
if element.metadata.parent_id == references_id:
print(element)
break

pdf_elements = [el for el in pdf_elements if el.metadata.parent_id != references_id]

Başlıkları filtreleme

Image(filename='images/donut_headers.png', height=400, width=400) 
headers = [el for el in pdf_elements if el.category == "Header"]
headers[1].to_dict()
pdf_elements = [el for el in pdf_elements if el.category != "Header"]

PowerPoint Slaytını Ön İşlemden Geçirme

filename = "example_files/donut_slide.pptx"
pptx_elements = partition_pptx(filename=filename)

README’yi ön işleme tabi tutmak

filename = "example_files/donut_readme.md"
md_elements = partition_md(filename=filename)

Belgeleri Vector DB’ye yüklemek

elements = chunk_by_title(pdf_elements + pptx_elements + md_elements)

from langchain_community.vectorstores import Chroma
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings

documents = []
for element in elements:
metadata = element.metadata.to_dict()
del metadata["languages"]
metadata["source"] = metadata["filename"]
documents.append(Document(page_content=element.text, metadata=metadata))

embeddings = OpenAIEmbeddings()

vectorstore = Chroma.from_documents(documents, embeddings)

retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 6}
)

from langchain.prompts.prompt import PromptTemplate
from langchain_openai import OpenAI
from langchain.chains import ConversationalRetrievalChain, LLMChain
from langchain.chains.qa_with_sources import load_qa_with_sources_chain

template = """You are an AI assistant for answering questions about the Donut document understanding model.
You are given the following extracted parts of a long document and a question. Provide a conversational answer.
If you don't know the answer, just say "Hmm, I'm not sure." Don't try to make up an answer.
If the question is not about Donut, politely inform them that you are tuned to only answer questions about Donut.
Question: {question}
=========
{context}
=========
Answer in Markdown:"""
prompt = PromptTemplate(template=template, input_variables=["question", "context"])

llm = OpenAI(temperature=0)

doc_chain = load_qa_with_sources_chain(llm, chain_type="map_reduce")
question_generator_chain = LLMChain(llm=llm, prompt=prompt)
qa_chain = ConversationalRetrievalChain(
retriever=retriever,
question_generator=question_generator_chain,
combine_docs_chain=doc_chain,
)

qa_chain.invoke({
"question": "How does Donut compare to other document understanding models?",
"chat_history": []
})["answer"]

filter_retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 1, "filter": {"source": "donut_readme.md"}}
)

filter_chain = ConversationalRetrievalChain(
retriever=filter_retriever,
question_generator=question_generator_chain,
combine_docs_chain=doc_chain,
)

filter_chain.invoke({
"question": "How do I classify documents with DONUT?",
"chat_history": [],
"filter": filter,
})["answer"]

Resource

[1] Deeplearning.ai, (Nisan 2024), Preprocessing Unstructured Data For LLM Applications:

https://learn.deeplearning.ai/courses/preprocessing-unstructured-data-for-llm-applications/

--

--

Cahit Barkin Ozer

Daha fazla şey öğrenmek ve daha iyi olmak isteyen bir yazılım mühendisi.