# 2. Próbkowanie Danych {{#include ../../banners/hacktricks-training.md}} ## **Próbkowanie Danych** **Próbkowanie Danych** jest kluczowym procesem w przygotowywaniu danych do trenowania dużych modeli językowych (LLM) takich jak GPT. Polega na organizowaniu danych tekstowych w sekwencje wejściowe i docelowe, które model wykorzystuje do nauki przewidywania następnego słowa (lub tokena) na podstawie poprzednich słów. Odpowiednie próbkowanie danych zapewnia, że model skutecznie uchwyci wzorce językowe i zależności. > [!TIP] > Celem tej drugiej fazy jest bardzo proste: **Próbkowanie danych wejściowych i przygotowanie ich do fazy treningowej, zazwyczaj poprzez podział zbioru danych na zdania o określonej długości oraz generowanie oczekiwanej odpowiedzi.** ### **Dlaczego Próbkowanie Danych Ma Znaczenie** LLM, takie jak GPT, są trenowane do generowania lub przewidywania tekstu poprzez zrozumienie kontekstu dostarczonego przez poprzednie słowa. Aby to osiągnąć, dane treningowe muszą być ustrukturyzowane w sposób, który pozwala modelowi nauczyć się relacji między sekwencjami słów a ich następnymi słowami. To ustrukturyzowane podejście pozwala modelowi na generalizację i generowanie spójnego oraz kontekstowo odpowiedniego tekstu. ### **Kluczowe Pojęcia w Próbkowaniu Danych** 1. **Tokenizacja:** Rozkładanie tekstu na mniejsze jednostki zwane tokenami (np. słowa, podsłowa lub znaki). 2. **Długość Sekwencji (max_length):** Liczba tokenów w każdej sekwencji wejściowej. 3. **Przesuwane Okno:** Metoda tworzenia nakładających się sekwencji wejściowych poprzez przesuwanie okna po tokenizowanym tekście. 4. **Krok:** Liczba tokenów, o które przesuwa się okno, aby utworzyć następną sekwencję. ### **Przykład Krok po Kroku** Przejdźmy przez przykład, aby zilustrować próbkowanie danych. **Przykładowy Tekst** ```arduino "Lorem ipsum dolor sit amet, consectetur adipiscing elit." ``` **Tokenizacja** Załóżmy, że używamy **podstawowego tokenizera**, który dzieli tekst na słowa i znaki interpunkcyjne: ```vbnet Tokens: ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit."] ``` **Parametry** - **Maksymalna długość sekwencji (max_length):** 4 tokeny - **Krok okna przesuwającego:** 1 token **Tworzenie sekwencji wejściowych i docelowych** 1. **Podejście okna przesuwającego:** - **Sekwencje wejściowe:** Każda sekwencja wejściowa składa się z `max_length` tokenów. - **Sekwencje docelowe:** Każda sekwencja docelowa składa się z tokenów, które bezpośrednio następują po odpowiadającej sekwencji wejściowej. 2. **Generowanie sekwencji:**
Pozycja oknaSekwencja wejściowaSekwencja docelowa
1["Lorem", "ipsum", "dolor", "sit"]["ipsum", "dolor", "sit", "amet,"]
2["ipsum", "dolor", "sit", "amet,"]["dolor", "sit", "amet,", "consectetur"]
3["dolor", "sit", "amet,", "consectetur"]["sit", "amet,", "consectetur", "adipiscing"]
4["sit", "amet,", "consectetur", "adipiscing"]["amet,", "consectetur", "adipiscing", "elit."]
3. **Wynikowe tablice wejściowe i docelowe:** - **Wejście:** ```python [ ["Lorem", "ipsum", "dolor", "sit"], ["ipsum", "dolor", "sit", "amet,"], ["dolor", "sit", "amet,", "consectetur"], ["sit", "amet,", "consectetur", "adipiscing"], ] ``` - **Cel:** ```python [ ["ipsum", "dolor", "sit", "amet,"], ["dolor", "sit", "amet,", "consectetur"], ["sit", "amet,", "consectetur", "adipiscing"], ["amet,", "consectetur", "adipiscing", "elit."], ] ``` **Wizualna reprezentacja**
Pozycja tokenaToken
1Lorem
2ipsum
3dolor
4sit
5amet,
6consectetur
7adipiscing
8elit.
**Okno przesuwające z krokiem 1:** - **Pierwsze okno (pozycje 1-4):** \["Lorem", "ipsum", "dolor", "sit"] → **Cel:** \["ipsum", "dolor", "sit", "amet,"] - **Drugie okno (pozycje 2-5):** \["ipsum", "dolor", "sit", "amet,"] → **Cel:** \["dolor", "sit", "amet,", "consectetur"] - **Trzecie okno (pozycje 3-6):** \["dolor", "sit", "amet,", "consectetur"] → **Cel:** \["sit", "amet,", "consectetur", "adipiscing"] - **Czwarte okno (pozycje 4-7):** \["sit", "amet,", "consectetur", "adipiscing"] → **Cel:** \["amet,", "consectetur", "adipiscing", "elit."] **Zrozumienie kroku** - **Krok 1:** Okno przesuwa się do przodu o jeden token za każdym razem, co skutkuje silnie nakładającymi się sekwencjami. Może to prowadzić do lepszego uczenia się relacji kontekstowych, ale może zwiększyć ryzyko przeuczenia, ponieważ podobne punkty danych są powtarzane. - **Krok 2:** Okno przesuwa się do przodu o dwa tokeny za każdym razem, co zmniejsza nakładanie. To zmniejsza redundancję i obciążenie obliczeniowe, ale może pominąć niektóre kontekstowe niuanse. - **Krok równy max_length:** Okno przesuwa się do przodu o całą wielkość okna, co skutkuje sekwencjami bez nakładania. Minimalizuje to redundancję danych, ale może ograniczyć zdolność modelu do uczenia się zależności między sekwencjami. **Przykład z krokiem 2:** Używając tego samego tokenizowanego tekstu i `max_length` równym 4: - **Pierwsze okno (pozycje 1-4):** \["Lorem", "ipsum", "dolor", "sit"] → **Cel:** \["ipsum", "dolor", "sit", "amet,"] - **Drugie okno (pozycje 3-6):** \["dolor", "sit", "amet,", "consectetur"] → **Cel:** \["sit", "amet,", "consectetur", "adipiscing"] - **Trzecie okno (pozycje 5-8):** \["amet,", "consectetur", "adipiscing", "elit."] → **Cel:** \["consectetur", "adipiscing", "elit.", "sed"] _(Zakładając kontynuację)_ ## Przykład kodu Zrozummy to lepiej na przykładzie kodu z [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb): ```python # Download the text to pre-train the LLM import urllib.request url = ("https://raw.githubusercontent.com/rasbt/LLMs-from-scratch/main/ch02/01_main-chapter-code/the-verdict.txt") file_path = "the-verdict.txt" urllib.request.urlretrieve(url, file_path) with open("the-verdict.txt", "r", encoding="utf-8") as f: raw_text = f.read() """ Create a class that will receive some params lie tokenizer and text and will prepare the input chunks and the target chunks to prepare the LLM to learn which next token to generate """ import torch from torch.utils.data import Dataset, DataLoader class GPTDatasetV1(Dataset): def __init__(self, txt, tokenizer, max_length, stride): self.input_ids = [] self.target_ids = [] # Tokenize the entire text token_ids = tokenizer.encode(txt, allowed_special={"<|endoftext|>"}) # Use a sliding window to chunk the book into overlapping sequences of max_length for i in range(0, len(token_ids) - max_length, stride): input_chunk = token_ids[i:i + max_length] target_chunk = token_ids[i + 1: i + max_length + 1] self.input_ids.append(torch.tensor(input_chunk)) self.target_ids.append(torch.tensor(target_chunk)) def __len__(self): return len(self.input_ids) def __getitem__(self, idx): return self.input_ids[idx], self.target_ids[idx] """ Create a data loader which given the text and some params will prepare the inputs and targets with the previous class and then create a torch DataLoader with the info """ import tiktoken def create_dataloader_v1(txt, batch_size=4, max_length=256, stride=128, shuffle=True, drop_last=True, num_workers=0): # Initialize the tokenizer tokenizer = tiktoken.get_encoding("gpt2") # Create dataset dataset = GPTDatasetV1(txt, tokenizer, max_length, stride) # Create dataloader dataloader = DataLoader( dataset, batch_size=batch_size, shuffle=shuffle, drop_last=drop_last, num_workers=num_workers ) return dataloader """ Finally, create the data loader with the params we want: - The used text for training - batch_size: The size of each batch - max_length: The size of each entry on each batch - stride: The sliding window (how many tokens should the next entry advance compared to the previous one). The smaller the more overfitting, usually this is equals to the max_length so the same tokens aren't repeated. - shuffle: Re-order randomly """ dataloader = create_dataloader_v1( raw_text, batch_size=8, max_length=4, stride=1, shuffle=False ) data_iter = iter(dataloader) first_batch = next(data_iter) print(first_batch) # Note the batch_size of 8, the max_length of 4 and the stride of 1 [ # Input tensor([[ 40, 367, 2885, 1464], [ 367, 2885, 1464, 1807], [ 2885, 1464, 1807, 3619], [ 1464, 1807, 3619, 402], [ 1807, 3619, 402, 271], [ 3619, 402, 271, 10899], [ 402, 271, 10899, 2138], [ 271, 10899, 2138, 257]]), # Target tensor([[ 367, 2885, 1464, 1807], [ 2885, 1464, 1807, 3619], [ 1464, 1807, 3619, 402], [ 1807, 3619, 402, 271], [ 3619, 402, 271, 10899], [ 402, 271, 10899, 2138], [ 271, 10899, 2138, 257], [10899, 2138, 257, 7026]]) ] # With stride=4 this will be the result: [ # Input tensor([[ 40, 367, 2885, 1464], [ 1807, 3619, 402, 271], [10899, 2138, 257, 7026], [15632, 438, 2016, 257], [ 922, 5891, 1576, 438], [ 568, 340, 373, 645], [ 1049, 5975, 284, 502], [ 284, 3285, 326, 11]]), # Target tensor([[ 367, 2885, 1464, 1807], [ 3619, 402, 271, 10899], [ 2138, 257, 7026, 15632], [ 438, 2016, 257, 922], [ 5891, 1576, 438, 568], [ 340, 373, 645, 1049], [ 5975, 284, 502, 284], [ 3285, 326, 11, 287]]) ] ``` ## Odniesienia - [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch) {{#include ../../banners/hacktricks-training.md}}