Oppsummeringer
I denne delen av kurset, skal vi forsøke å bruke språkmodellen på noen artikler. Oppsummeringer av dokumenter kalles gjerne summarizing eller summarization, i koden. Det fins dedikert programvare for å lage oppsummeringer. Imidlertid har store språkmodeller også begynt å beherske oppgaven ganske bra.
Enda en gang bruker vi LangChain, et bibliotek med åpen kildekode, som brukes til å lage programvare med store språkmodeller.
Oppgave 5.1: Lage en ny notebook
Lag en ny Jupyter Notebook som du kaller summarizing ved å klikke i JupyterLabs filmeny, deretter New og Notebook. Hvis du blir spurt om å velge en kjerne, velg “Python 3”. Gi den nye notebooken et navn ved å klikke JupyterLabs filmeny og så Rename Notebook. Bruk navnet summarizing.
Oppgave 5.2: Stoppe gamle kjerner
JupyterLab bruker en Python kjerne til å kjøre kode i hver notebook. For å frigjøre GPU minne som ble brukt i forrige kapittel, bør du stoppe kjernen fra den notebooken. I menyen på venstre side i JupyterLab, klikk den mørke sirkelen som har en hvit firkant inni. Klikk så KERNELS og Shut Down All.
Dokumentenes plassering
Vi har samlet noen forskningsartikler som har Creative Commons lisens. Vi skal nå forsøke å laste opp alle dokumentene fra mappen som defineres under. Hvis du foretrekker, kan du endre stien til en annen mappe:
document_folder = '/fp/projects01/ec443/documents/terrorism'
Språkmodellen
Vi skal bruke modeller fra HuggingFace, en nettside som har verktøy og modeller til maskinlæring. Vi vil bruke språkmodellen google/gemma-3-4b-it, som har åpne vekter og parametere. Modellen har et stort kontekstvindu, som betyr at vi kan bruke den til å behandle ganske store dokumenter. Likevel er den liten nok til at vi kan bruke den med den minste GPUen på Fox.
Tokens kontra ord
Korte ord kan være ett enkelt token, men lengre ord består vanligvis av flere tokens. Maksimum dokumentstørrelse med denne modellen er derfor mindre enn 128k ord. Akkurat hvor mange ord man skal beregne per token kommer an på tokenizeren. Store språkmodeller har vanligvis egne tokenizere. Vi kommer til å bruke standard tokenizeren som hører til den store språkmodellen vi til enhver tid bruker.
import os
os.environ['HF_HOME'] = '/fp/projects01/ec443/huggingface/cache/'
For å bruke modellen, lager vi en pipeline. En pipeline kan bestå av flere steg, men i dette tilfellet trenger vi bare ett steg. Vi kan bruke metoden HuggingFacePipeline.from_model_id(), som automatisk laster ned den spesifiserte modellen fra HuggingFace
Som vi har gjort før, skal vi sjekke om vi har GPU:
import torch
device = 0 if torch.cuda.is_available() else -1
from langchain_community.llms import HuggingFacePipeline
llm = HuggingFacePipeline.from_model_id(
model_id='google/gemma-3-4b-it',
task='text-generation',
device=device,
pipeline_kwargs={
'max_new_tokens': 1500,
#'do_sample': True,
#'temperature': 0.3,
#'num_beams': 4,
}
)
Vi kan gi noen argumenter til “pipelinen”:
model_id: modellens navn fra HuggingFacetask: oppgaven du planlegger å bruke modellen tildevice: GPU maskinvaren som enheten bruker. Hvis vi ikke spesifiserer en enhet, vil GPU ikke brukes.pipeline_kwargs: (keyword arguments) tilleggsparametere som gis til modellenmax_new_tokens: max lengde på teksten som genereresdo_sample: HvisFalsevil det mest sannsynlige ordet bli valgt. Dette gjør outputten deterministisk. Vi kan sørge for en mer tilfeldig utvelging. Standardverdien later til å væreTrue.temperature: temperaturkontrollen er den statistiske distribusjonen til neste ord. Vanligvis et tall mellom 0 and 1. Lav temperatur øker sannsynligheten for vanlige ord. Høy temperatur øker muligheten for sjeldnere ord i output. Utviklerne har ofte en anbefaling hva angår temperatur. Vi bruker anbefalingen som et startpunkt.num_beams: som standard gir modellen en enkel sekvens av tokens/ord. Med beam search, vil programmet bygge flere samtidige sekvenser, og deretter velge den beste til slutt.
Å lage instruks/ prompt
Vi kan bruke en instruks til å fortelle språkmodellen hvordan vi ønsker at den skal svare. Instruksen bør være kort og konstruktiv. Vi lager også plassholdere til konteksten. Kontekst og input er her det samme. LangChain bytter disse ut med de aktuelle dokumentene når vi kjører en instruks.
from langchain_classic.chains.combine_documents import create_stuff_documents_chain
from langchain_classic.chains.llm import LLMChain
from langchain_core.prompts import PromptTemplate
separator = '\nYour Summary:\n'
prompt_template = '''Write a summary of the following:
{context}
''' + separator
prompt = PromptTemplate(template=prompt_template,
input_variables=['context'])
Skille oppsummeringen fra inputten
LangChain returnerer både input instruksen og svaret som genereres i en lang tekst. For å få bare oppsummeringen, må vi splitte oppsummeringen fra dokumentet som vi sendte som input. Til dette kan vi bruke LangChain output parseren som lyder navnet RegexParser:
from langchain_classic.output_parsers import RegexParser
import re
output_parser = RegexParser(
regex=rf'{separator}(.*)',
output_keys=['summary'],
flags=re.DOTALL)
Å lage kjede (chain)
Dokument innlasteren laster hver PDF side som et separat ‘document’. Dette er delvis av tekniske årsaker og på grunn av måten PDFer er organisert. Av denne grunn bruker vi en kjede som kalles create_stuff_documents_chain som (gjen)forener flere dokumenter til ett enkelt stort dokument:
chain = create_stuff_documents_chain(
llm, prompt, output_parser=output_parser)
Laste inn dokumentene
Vi bruker LangChain sin DirectoryLoader til å laste inn alle filer fra document_folder. document_folder defineres i starten av denne Notebooken:
from langchain_community.document_loaders import DirectoryLoader
loader = DirectoryLoader(document_folder)
documents = loader.load()
print('number of documents:', len(documents))
Lage oppsummeringene
Nå kan vi iterere over disse dokumentene med en for-loop:
summaries = {}
for document in documents:
filename = document.metadata['source']
print('Summarizing document:', filename)
result = chain.invoke({"context": [document]})
summary = result['summary']
summaries[filename] = summary
print('Summary of file', filename)
print(summary)
Lagre oppsummeringene til tekstfiler
Til slutt lagrer vi oppsummeringene for at vi senere skal kunne se dem. Vi lagrer oppsummeringene i filen summaries.txt. Hvis du vil, kan du lagre hver oppsummering i en egen fil:
with open('summaries.txt', 'w') as outfile:
for filename in summaries:
print('Summary of ', filename, file = outfile)
print(summaries[filename], file=outfile)
print(file=outfile)
Oppgaver
Oppgave 5.3: Oppsummere dine egne dokumenter
Lag en oppsummering av et dokument som du laster opp i din egen dokumentmappe. Les oppsummeringen nøye, og vurdere resultatet i lys av følgende momenter:
Er oppsummeringen nyttig?
Er det noe som mangler i oppsummeringen?
Er lengden adekvat?
Oppgave 5.4: Tilpass oppsummeringen
Prøv å lage noen tilpasninger i instruksen for å justere oppsummeringen som du fikk i forrige oppgave. Kan du for eksempel spørre etter en lengre eller mer nøyaktig oppsummering? Eller kan du be modellen om å legge vekt på visse aspekter i teksten?
Oppgave 5.5: Lage en oppsummering på et annet språk
Vi kan bruke modellen til å få en oppsummering på et annet språk enn originaldokumentet. Hvis for eksempel instruksen er på Norsk, vil svaret vanligvis også gis på Norsk. Du kan også spesifisere i instruksen hvilket språk du ønsker å ha oppsummeringen på. Bruk modellen til å lage en oppsummering av ditt dokument fra den andre oppgaven, på et annet språk enn det opprinnelig ble gitt.
Oppgave 5.6: Slurmjobber
Når du har laget et program som virker, er det mer effektivt å kjøre det som en batch jobb enn i JupyterLab. Dette er fordi JupyterLab reserverer en GPU hele tiden, også når den ikke kjører. Dette er grunnen til at det ferdige programmet bør lages til et Python program som legges inn i den ordinære køen på tungregningsklyngen. Les mer i om Scheduling jobs.
Du kan lagre koden ved å klikke Filmenyen i JupyterLab. Velg “Save and Export Notebook As…” og deretter “Executable Script”. Resultatet er Python filen summarizing.py som lastes ned lokalt på din maskin. Du trenger også å laste ned slurmskriptet LLM.slurm
Last opp både python filen summarizing.py og slurm skriptet LLM.slurm til Fox. Du starter jobben med denne kommandoen:
sbatch LLM.slurm summarizing.py
Slurm lager en loggfil for hver jobb som deretter lagres med et navn som for eksempel slurm-1358473.out. Som standard blir disse loggfilene lagret i den samme arbeidskatalogen (working directory) som du kjører sbatch kommandoen fra. Dersom du ønsker å lagre loggen et annet sted, kan du legge til en linje som spesifiserer ønsket sted i slurm. Husk å endre brukernavnet:
#SBATCH --output=/fp/projects01/ec443/<username>/logs/slurm-%j.out
Anbefaling : Dersom du skal feilsøke pythonfilene dine, kan det være hensiktsmessig å laste ned Sublime text.
Bonusmateriale
Oppgave 5.7: Lage en metaoppsumemring
Vi kan også forsøke å generere en oppsummering for alle dokumentene. Dette vil ikke gi så mye mening dersom dokumentene har helt forskjellige temaer. Hvis dokumentene henger sammen, eller har samme tema, kan det gi mening å lage en metaoppsummering.
Først må vi importere noen funksjoner
from langchain_classic.schema.document import Document
from langchain_classic.prompts import ChatPromptTemplate
Vi lager en ny instruks/ prompt, med mer spesifikke instruksjoner enn vi gjorde med de vanlige oppsummeringene.
total_prompt = ChatPromptTemplate.from_messages(
[("system", "Below is a list of summaries of some papers. Make a total summary all the information in all the papers:\n\n{context}\n\nTotal Summary:")]
)
Så kan vi lage en ny lenke/ chain basert på Språkmodellen og instruksen.
total_chain = create_stuff_documents_chain(llm, total_prompt)
Lenken trenger en liste av Document objekter til innputt:
list_of_summaries = [Document(summary) for summary in summaries.values()]
Nå kan vi påkalle (invoke) lenken (chain) med dene listen som input, og deretter skrive resultatet med print.
total_summary = total_chain.invoke({"context": list_of_summaries})
print('Summary of all the summaries:')
print(total_summary)
Til slutt lagrer vi metaoppsummeringen i en tekstfil.
with open('total_summary.txt', 'w') as outfile:
print(total_summary, file=outfile)