Siri Belajar AI:Buat RAG dari kosong (Bahagian 1)

Abu Huzaifah Bin Haji Bidin
9 min readApr 13, 2024

--

Pebenda RAG ni?

RAG membawa maksud Retrieval Augmented Generation. Ia membolehkan LLM (Large Language Model) macam ChatGPT ambil maklumat yang kita beri, dan menghasilkan jawapan yang lebih tepat dan cekap. Senang bahasanya macam:

  • Retrieval — Kita bagi dia maklumat, contoh macam dokumen-dokumen yang kita ada, PDF ke, Word ke yang terdiri dari maklumat-maklumat yang penting
  • Augmented — Kita perkuatkan, perhebatkan (augmented) dan perkemaskan maklumat yang diberi tu supaya ia senang diproses oleh LLM
  • Generation — Dari maklumat yang diberi dan diperkuatkan, kita harap LLM macam ChatGPT tak adalah bagi jawapan mengarut. Kita nak dia bagi jawapan yang berfakta berdasarkan maklumat atau teks yang sudah kita suap pada dia

Kau orang semua boleh baca lebih detail pasal RAG ni kat sini… https://arxiv.org/abs/2005.11401

Imej diambil dari artikel https://www.linkedin.com/pulse/what-retrieval-augmented-generation-grow-right/

Kenapa nak kena pakai RAG?

Sebab Generative AI ni walaupun gempak, dia suka bagi jawapan yang memandai-mandai. Saintis AI panggil benda ni sebagai ‘halusinasi’. Selain dari tu kita nak jugak Generative AI ni bekerja dengan data kita. Sebab secara dasarnya, Generative AI ni dilatih dengan data umum, data internet. Jadi kalau kita nak dia buat kerja yang spesifik, kita kenalah gunakan RAG untuk dia memproses data yang spesifik untuk kerja kita tu.

Apa benda yang kita boleh buat dengan RAG ni?

Banyak, antaranya:

  1. Kita boleh buat AI yang menggantikan pusat khidmat pelanggan. Kita bagi dia data-data tentang bisnes kita. Lepas tu, kita harapkan AI tu bolehlah jadi pengganti kepada pusat khidmat pelanggan. Jimat sikit duit.
  2. Boleh jugak bantu kita dalam tugas seharian. Contoh kalau peguam ke, jurutera ke, yang terpaksa rujuk banyak dokumen. Kita boleh bagi je dokumen tu pada mana-mana AI, biar dia faham, lepas tu boleh tanya dia je.
  3. Mungkin juga untuk pelajar. Bagi AI ni buku teks, suruh AI fahamkan, lepastu AI ni boleh bantu pelajar untuk ulangkaji.

Atas ni antara contoh lah. Banyak lagi yang kita boleh buat dengan RAG ni, kalau kita dah faham betul-betul macam mana nak buat, dan macam mana dia berfungsi.

Kenapa nak kena buat sendiri?

Jadi kita nak kena buat apa ni?

Ok, untuk kali ni, kita akan cuba buat RAG dari kosong menggunakan data kejuruteraan. Data Engineering. Dokumen yang aku pilih adalah API standard AP 521 . Ia menceritakan tentang sizing and selection of pressure relieving devices.

Macam mana nak buat? Ini langkah-langkah (resepi macam Khairul Aming punya) yang kita perlu lakukan.

  1. Buka PDF document tu, API standard tu. Keluarkan semua teks yang ada dalam dokument tu.
  2. Formatkan supaya dia sedia untuk masuk dalam embedding model
  3. Embedkan teks tu (tukarkan dia jadi nombor) supaya kita boleh simpan dan proses.
  4. Buat sistem ‘retrieval’ yang menggunakan vector search untuk mencari teks yang kita dah embed tadi
  5. Buat prompt.
  6. Generate jawapan yang memuaskan dari teks yang sudah kita berikan

No 1 sampai 3 tu kita buat sebagai bahagian pertama, bahagian memproses teks. Bahagian kedua adalah buat cari dan jawab.

Aku pilih dokumen API sebab aku ni jurutera proses, memang API ni makanan aku. Tapi kalau kau orang ada dokumen-dokumen lain nak pakai boleh je. Aku akan buat tutorial ni langkah demi langkah. Harap kau orang semua boleh ikut.

Pra-syarat, Benda yang Perlu ada sebelum mula

  1. Kena ada komputer yang kalau boleh ada GPU. Tak ada GPU pun tak apa, tapi nanti proses kod kau berjalan nanti perlahan
  2. Kalau kau nak juga rasa pakai GPU mungkin boleh pakai Google Colab. Hanya daftar dan kau boleh gunakan secara percuma. https://colab.research.google.com/
  3. Adalah sedikit kemahiran coding, terutamanya python. Aku punya kemahiran pun masih lagi tahap-tahap beginner, tapi aku dah boleh buat dah RAG pipeline ni sendiri.

Ok, dan kau orang semua kenalah install dulu, package-package ni dulu macam kat bawah.

#preinstall all packages
!pip install PyMuPDF
!pip install voyageai
!pip install -U sentence-transformers

Bahagian 1: Pemprosesan Teks

Sebelum tu, kita kena pastikan dokumen kita ada. Boleh ikut kod seperti di bawah. Kod in cuma memeriksa sama ada dokumen tu ada ke tidak dalam folder yang kita simpan. Kalau tak ada dia akan muat turun balik

# Download PDF file
import os
import requests

# Get PDF document
pdf_path = "/content/API STD 521 2022.pdf"

# Download PDF if it doesn't already exist
if not os.path.exists(pdf_path):
print("File doesn't exist, downloading...")

# The URL of the PDF you want to download
url = "https://github.com/maercaestro/ragtrial/blob/2f0809b3b0ecb503af385f63d3f39749f2d89497/API%20RP%20520%20PART%202.pdf"

# The local filename to save the downloaded file
filename = pdf_path

# Send a GET request to the URL
response = requests.get(url)

# Check if the request was successful
if response.status_code == 200:
# Open a file in binary write mode and save the content to it
with open(filename, "wb") as file:
file.write(response.content)
print(f"The file has been downloaded and saved as {filename}")
else:
print(f"Failed to download the file. Status code: {response.status_code}")
else:
print(f"File {pdf_path} exists.")

Ok, bahagian seterusnya kita akan bawak keluar teks-teks ni dari PDF. Kita akan gunakan PyMuPDF.

import fitz # (pymupdf, found this is better than pypdf for our use case, note: licence is AGPL-3.0, keep that in mind if you want to use any code commercially)
from tqdm.auto import tqdm # for progress bars, requires !pip install tqdm

def text_formatter(text: str) -> str:
"""Performs minor formatting on text."""
cleaned_text = text.replace("\n", " ").strip() # note: this might be different for each doc (best to experiment)

# Other potential text formatting functions can go here
return cleaned_text

# Open PDF and get lines/pages
# Note: this only focuses on text, rather than images/figures etc
def open_and_read_pdf(pdf_path: str) -> list[dict]:
"""
Opens a PDF file, reads its text content page by page, and collects statistics.

Parameters:
pdf_path (str): The file path to the PDF document to be opened and read.

Returns:
list[dict]: A list of dictionaries, each containing the page number
(adjusted), character count, word count, sentence count, token count, and the extracted text
for each page.
"""
doc = fitz.open(pdf_path) # open a document
pages_and_texts = []
for page_number, page in tqdm(enumerate(doc)): # iterate the document pages
text = page.get_text() # get plain text encoded as UTF-8
text = text_formatter(text)
pages_and_texts.append({"page_number": page_number +8, # adjust page numbers since our PDF starts on page 42
"page_char_count": len(text),
"page_word_count": len(text.split(" ")),
"page_sentence_count_raw": len(text.split(". ")),
"page_token_count": len(text) / 4, # 1 token = ~4 chars, see: https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them
"text": text})
return pages_and_texts

pages_and_texts = open_and_read_pdf(pdf_path=pdf_path)
pages_and_texts[:2]

Boleh lihat fungsi open_and_read_pdf() tu. Fungsi ini akan keluarkan semua teks yang ada dalam pdf tersebut dan bahagikannya kepada satu senarai data yang kita boleh gunakan nanti. Dari fungsi ini kita keluarkan page_number, page_char_count, page_sentence_count_raw, page_token_count (nanti aku cerita apa tu token) dan juga teks tu sendiri. Hasilnya boleh dilihat seperti di bawah:

Hasil dari fungsi open_and_read_pdf()

Seeloknya kita simpanlah senarai data ni dalam bentuk table/jadual. Kita boleh gunakan pandas dataframe

import pandas as pd

df = pd.DataFrame(pages_and_texts)
df.head()

Dan hasilnya seperti di bawah, nampak kemas sedikitlah kan?

Dan kalau kita kenakan (apply) fungsi describe() pada dataframe kita ni, dia akan jadi seperti di bawah. Perhatikan pada jumlah token yang kita ada, kerana inilah yang akan kita bincang selepas ini. Apa itu token.

Serba sedikit tentang token

Token ni penting. Sebab token ni lah pengukuran yang digunakan oleh LLM macam ChatGPT untuk mengukur berapa banyak perkataan/teks yang mereka boleh proses pada satu-satu masa.

Tapi dalam kes ni kita nak pakai token ni untuk embedding. Embedding model ni digunakan untuk tukar teks yang kita proses ke dalam bentuk nombor. Nombor yang boleh diproses oleh AI.

Setiap embedding model ada had pada jumlah token yang mereka boleh proses

Disebabkan ada had token yang boleh diproses, sebaiknya kita kecilkan lah pembahagian teks kita ni lagi. Sekarang ni kita bahagikan mengikut muka surat, eloknya kita bahagikan mengikut ayat

Bahagikan muka surat ke ayat

Disebabkan saiz yang besar, jadi boleh jugak kita bahagikan text kita tu, pisahkan kepada ayat instead of muka surat. Kita akan gunakan spacy untuk bahagian ni

from spacy.lang.en import English

nlp = English()

# add a sentencizer pipeline
nlp.add_pipe("sentencizer")

#create document instance as example
doc = nlp("This is a sentence. This is another sentence. I like elephants.")
assert len(list(doc.sents)) == 3

#print out sentences split
list(doc.sents)
for item in tqdm(pages_and_texts):
item["sentences"] = list(nlp(item["text"]).sents)

#make sure all sentences are strings
item["sentences"] = [str(sentence) for sentence in item["sentences"]]

#count the sentences
item["page_sentence_count_spacy"] = len(item["sentences"])

Dan bila kita susun balik data kita dia akan nampak lebih kemas

df= pd.DataFrame(pages_and_texts)
df.describe().round(2)

Kita pun sebenarnya boleh pisahkan ayat ni kepada bahagian yang lebih kecil, kita namakannya sebagai chunking. Boleh ikut kod seperti di bawah.

import re

# split each chunk into its own item

pages_and_chunks = []
for item in tqdm(pages_and_texts):
for sentence_chunk in item["sentence_chunks"]:
chunk_dict = {}
chunk_dict["page_number"] = item["page_number"]

#joint the sentences together into a paragraph like structure
joined_sentence_chunk = " ".join(sentence_chunk).replace(" "," ").strip()
joined_sentence_chunk = re.sub(r'\s+', ' ', joined_sentence_chunk)

chunk_dict["sentence_chunk"] = joined_sentence_chunk

#get some stats on our chunks
chunk_dict["chunk_char_count"] = len(joined_sentence_chunk)
chunk_dict["chunk_word_count"] = len([word for word in joined_sentence_chunk.split(" ")] )
chunk_dict["chunk_token_count"] = len(joined_sentence_chunk) / 4

pages_and_chunks.append(chunk_dict)


len(pages_and_chunks)

Dalam kes ini, kita menggunakan regex (regular expression) untuk memisahkan ayat-ayat kita kedalam pisahan yang lebih kecil, dan daripada itu kita buat juga satu senarai data yang baru yang kita namakan sebagai pages_and_chunks.

Senarai data inilah yang kita akan embedkan, tanamkan ke dalam embedding model.

Kita perkemaskan dulu

df2 = pd.DataFrame(pages_and_chunks)
df2.describe().round(2)

Dan ini hasilnya

Mari kita Embedding / Tanamkan

Komputer ni dia tak faham teks,tulisan. Yang dia faham hanyalah nombor. Jadi ada keperluan untuk tukarkan semua teks kita kepada nombor. Supaya kita boleh proses teks ni dalam LLM.

Yang penting embedding ni akan tukarkan teks kepada nombor yang bermakna. Nombor yang telah dimasukkan dengan konteks. Konteks yang telah dipelajari. Jadi bila kita embed sesuatu perkataan, kita secara tidak lansung telah memberi konteks pada nombor yang kemudiannya membolehkan LLM memahami teks yang kita hantar.

Kita akan menggunakan package/library sentence transformer. Embedding model yang akan kita gunakan “all-mpnet-base-v2”. Kau orang boleh baca lebih lanjut pasal model tu kat sini

https://huggingface.co/sentence-transformers/all-mpnet-base-v2

from sentence_transformers import SentenceTransformer
embedding_model = SentenceTransformer(model_name_or_path="all-mpnet-base-v2", device="cuda")
%%time

for item in tqdm(pages_and_chunks):
item["embedding"] = embedding_model.encode(item["sentence_chunk"])

# turn chhunks into a single list

text_chunks = [item["sentence_chunk"] for item in pages_and_chunks]

Dan kalau kita tukarkan senarai ni ke dalam bentuk dataframe, dia akan jadi macam ni

Nampak tak, column embedding kat hujung tu? Yesza, itulah kita punya value embedding. Kita dahpun tukar ayat-ayat kita ke dalam nombor yang boleh diproses oleh llm model kita.

Sekarang nak simpan mana embedding ni?

Ok, kita dah ada value embedding. Tapi kita kena simpan value embedding ni supaya kita boleh gunakan untuk proses dalam LLM kita nanti. Tapi nak simpan kat mana? Dalam kes tutorial ni, aku hanya menggunakan satu dokumen sahaja, jadi saiz embedding tu kecil je. Seeloknnya aku simpan dalam excel je, macam kat bawah

df_embed.to_csv("txtch_embeddings.csv", index=False)

Tapi tu kalau kau proses satu dokumen je. Macam mana kalau kau proses banyak dokumen? 10,20,100. Memang tak logiklah kalau nak simpan dalam excel file je. Jadi kau perlu ada vector database. Ha, untuk yang tu kau boleh layari https://faiss.ai/.

Yang ni dari Facebook, dia memang ada menyediakan vector database percuma, cuma kena ingatlah, bila percuma ni, data kau terdedah kepada awam. Jadi seeloknya janganlah letak data-data yang rahasia dalam ni.

Ok, selesai bahagian pertama. Dalam bahagian seterusnya, kita akan tengok pulak macam mana nak gunakan data embedding ni untuk kita masukkan dalam LLM. Kita juga akan belajar sedikit matematik tentang fungsi persamaan.

Bahagian 2:

https://medium.com/@maercaestro/siri-belajar-ai-buat-rag-dari-kosong-bahagian-2-a41554905d37

--

--

Abu Huzaifah Bin Haji Bidin

Process Engineer with passion in anything data, analytics and AI. And I also write! Visit my website for more info (https://maercaestro.github.io)