2. Вибірка Даних

Reading time: 6 minutes

Вибірка Даних

Вибірка Даних є важливим процесом підготовки даних для навчання великих мовних моделей (LLMs), таких як GPT. Це включає організацію текстових даних у вхідні та цільові послідовності, які модель використовує для навчання передбачення наступного слова (або токена) на основі попередніх слів. Правильна вибірка даних забезпечує ефективне захоплення мовних патернів і залежностей моделлю.

tip

Мета цього другого етапу дуже проста: Вибрати вхідні дані та підготувати їх до етапу навчання, зазвичай розділяючи набір даних на речення певної довжини та також генеруючи очікувану відповідь.

Чому Вибірка Даних Важлива

LLMs, такі як GPT, навчаються генерувати або передбачати текст, розуміючи контекст, наданий попередніми словами. Щоб досягти цього, навчальні дані повинні бути структуровані таким чином, щоб модель могла вивчити зв'язок між послідовностями слів і їх наступними словами. Цей структурований підхід дозволяє моделі узагальнювати та генерувати зв'язний і контекстуально релевантний текст.

Ключові Концепції у Вибірці Даних

  1. Токенізація: Розбиття тексту на менші одиниці, звані токенами (наприклад, слова, підслова або символи).
  2. Довжина Послідовності (max_length): Кількість токенів у кожній вхідній послідовності.
  3. Зсувне Вікно: Метод створення перекриваючих вхідних послідовностей шляхом переміщення вікна по токенізованому тексту.
  4. Крок: Кількість токенів, на яку зсувне вікно рухається вперед для створення наступної послідовності.

Покроковий Приклад

Давайте пройдемося через приклад, щоб проілюструвати вибірку даних.

Приклад Тексту

arduino
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."

Токенізація

Припустимо, ми використовуємо базовий токенізатор, який розділяє текст на слова та розділові знаки:

vbnet
Tokens: ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit."]

Параметри

  • Максимальна довжина послідовності (max_length): 4 токени
  • Крок ковзного вікна: 1 токен

Створення вхідних та цільових послідовностей

  1. Метод ковзного вікна:
  • Вхідні послідовності: Кожна вхідна послідовність складається з max_length токенів.
  • Цільові послідовності: Кожна цільова послідовність складається з токенів, які безпосередньо слідують за відповідною вхідною послідовністю.
  1. Генерація послідовностей:
Позиція вікнаВхідна послідовністьЦільова послідовність
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."]
  1. Результуючі масиви вхідних та цільових даних:
  • Вхідні:
python
[
["Lorem", "ipsum", "dolor", "sit"],
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
]
  • Цільові:
python
[
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
["amet,", "consectetur", "adipiscing", "elit."],
]

Візуальне представлення

Позиція токенаТокен
1Lorem
2ipsum
3dolor
4sit
5amet,
6consectetur
7adipiscing
8elit.

Ковзне вікно з кроком 1:

  • Перше вікно (позиції 1-4): ["Lorem", "ipsum", "dolor", "sit"] → Ціль: ["ipsum", "dolor", "sit", "amet,"]
  • Друге вікно (позиції 2-5): ["ipsum", "dolor", "sit", "amet,"] → Ціль: ["dolor", "sit", "amet,", "consectetur"]
  • Третє вікно (позиції 3-6): ["dolor", "sit", "amet,", "consectetur"] → Ціль: ["sit", "amet,", "consectetur", "adipiscing"]
  • Четверте вікно (позиції 4-7): ["sit", "amet,", "consectetur", "adipiscing"] → Ціль: ["amet,", "consectetur", "adipiscing", "elit."]

Розуміння кроку

  • Крок 1: Вікно рухається вперед на один токен щоразу, що призводить до високої перекритості послідовностей. Це може призвести до кращого навчання контекстуальних зв'язків, але може збільшити ризик перенавчання, оскільки подібні дані повторюються.
  • Крок 2: Вікно рухається вперед на два токени щоразу, зменшуючи перекриття. Це зменшує надмірність і обчислювальне навантаження, але може пропустити деякі контекстуальні нюанси.
  • Крок, рівний max_length: Вікно рухається вперед на весь розмір вікна, що призводить до неперекриваючих послідовностей. Це мінімізує надмірність даних, але може обмежити здатність моделі вчитися залежностям між послідовностями.

Приклад з кроком 2:

Використовуючи той же токенізований текст і max_length 4:

  • Перше вікно (позиції 1-4): ["Lorem", "ipsum", "dolor", "sit"] → Ціль: ["ipsum", "dolor", "sit", "amet,"]
  • Друге вікно (позиції 3-6): ["dolor", "sit", "amet,", "consectetur"] → Ціль: ["sit", "amet,", "consectetur", "adipiscing"]
  • Третє вікно (позиції 5-8): ["amet,", "consectetur", "adipiscing", "elit."] → Ціль: ["consectetur", "adipiscing", "elit.", "sed"] (Припускаючи продовження)

Приклад коду

Давайте зрозуміємо це краще з прикладу коду з 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]])
]

Посилання