latent state // журнал
27 октября 2026 // NLP · ML // 6 мин

LLM [2] Подготовка данных

Чистим текст: шапки, язык, пустые строки, склейка через <|endoftext|>.

Открываем архив, который мы скачали в прошлой статье. Книги на которых мы будем учить модель находятся в папке: gutenberg/data/raw/PG*_raw.txt

  • 51 667 файлов
  • 20 ГБ
  • Метаданные: metadata/metadata.csv

Код статьи: https://github.com/format37/gpt

В корень я кладу config.yaml. Пропишите в нём ваши пути. У меня такие:

prepare_dataset:
  metadata_folder: /home/alex/projects/gutenberg/metadata/metadata.csv
  data_dir: /home/alex/projects/gutenberg/data/raw
  target_dir: /home/alex/projects/gutenberg/data/target

Откройте любой файл PG*_raw.txt.

The Project Gutenberg EBook of Journal des Goncourt (Deuxième série,
deuxième volume), by Edmond de Goncourt

This eBook is for the use of anyone anywhere at no cost and with
almost no restrictions whatsoever.  You may copy it, give it away or
re-use it under the terms of the Project Gutenberg License included
with this eBook or online at www.gutenberg.org


Title: Journal des Goncourt (Deuxième série, deuxième volume)
       Mémoires de la vie littéraire

Author: Edmond de Goncourt

Release Date: December 30, 2005 [EBook #17420]

Language: French

Character set encoding: UTF-8

*** START OF THIS PROJECT GUTENBERG EBOOK JOURNAL DES GONCOURT ***




Produced by Carlo Traverso, Mireille Harmelin and the
Online Distributed Proofreading Team of Europe. This file
was produced from images generously made available by the
Bibliothèque nationale de France (BnF/Gallica) at
http://gallica.bnf.fr.





JOURNAL DES GONCOURT
_Mémoires de la Vie Littéraire_


DEUXIÈME SÉRIE--DEUXIÈME VOLUME--TOME CINQUIÈME
1872-1877


PARIS, BIBLIOTHÈQUE-CHARPENTIER, 11, RUE DE GRENELLE.
1891




PRÉFACE

(_Réponse à Monsieur Renan_)


Monsieur Renan me faisait l'honneur de me dire, il y a des années, qu'une
lettre fausse avait été publiée par LE FIGARO, comme émanant de lui, et
que son dédain de l'imprimé était tel, qu'il n'avait pas réclamé.

Le monsieur Renan de l'année dernière, est vraiment bien changé.

A propos de vieilles conversations de 1870, rapportées dans mon Journal:
voici la lettre, que le _Petit Lannionnais_ publiait de l'auteur de la VIE
DE JÉSUS-CHRIST.

      Paris, 26 novembre, 1890.

      Ah! mon cher cousin, que je vous sais gré de vous indigner pour moi,
      en ce temps de mensonge...

Один файл это одна книга. Шапка содержит лицензию. Потом идет заголовок и абзацы разделённые пустой строкой и тремя звездочками. Нам нужно чтобы текст выглядел так как он будет выглядеть в чате. Абзацы разделенные пустой строкой.

Отбираем подходящие файлы:

  • Английские тексты
  • Имеющиеся файлы
from tqdm import tqdm
import os
import yaml
import pandas as pd
from cleanup import strip_headers

def file_list(config):
    target_dir = config["target_dir"]
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)

    # Select only english texts
    df = pd.read_csv(config["metadata_folder"])
    print('Initial size:', len(df))
    df = df[(df.language=="['en']") & (df.type=='Text')]
    print('English texts:', len(df))

    # Only existing files
    files = os.listdir(config["data_dir"])
    downloaded = [file.split("_")[0] for file in files]
    df = df[df.id.isin(downloaded)]
    print('Existing:', len(df))

    # Compose file path
    pathes = []
    for name in df.id:
        path = os.path.join(config['data_dir'], f"{name}_raw.txt")
        pathes.append(path)
    return pathes

Для удаления лицензии нам понадобится cleanup.py - Убедитесь что он у вас в data/ Изначально он взят из c-w/gutenberg - я убрал там лимит в 100 строк на поиск маркера *** END OF, чтобы концовка срезалась и у коротких книг.

  • Сжимаем серию пустых строк до одной пустой строки
def collapse_blank_lines(text):
    result = []
    for line in text.split("\n"):
        if line.strip():
            # Has text in a line
            result.append(line)
        elif result and result[-1] != "":
            # First empty string after the text
            result.append("")
        # Skip other empty strings
    return "\n".join(result)
  • Склеиваем книги через токен <|endoftext|> чтобы модель понимала что документ кончился и следующий текст с ним не связан
  • Пишем в файлы по 500 МБ, чтобы иметь возможность обучать по частям
def combine_files(files, config):
    max_size_mb = 500
    separator = "<|endoftext|>"
    target_dir = config['target_dir']

    if not os.path.exists(target_dir):
        os.makedirs(target_dir)

    current_content = []
    current_size = 0
    file_counter = 1

    for file_path in tqdm(files):
        try:
            with open(file_path, "r", encoding="utf-8") as file:
                content = file.read()
        except UnicodeDecodeError:
            # Not UTF-8 (UTF-16, cp1252): skip the book
            tqdm.write(f"Skipping {file_path}: not UTF-8")
            continue

        # Remove extra \ufeff garbage
        content = content.replace("\ufeff", "")
        # Remove license
        content = strip_headers(content)
        # Collapse blank lines
        content = collapse_blank_lines(content)

        # Write each 500 mb
        estimated_size = len(content.encode("utf-8"))
        if current_size + estimated_size > max_size_mb * 1024 * 1024:
            target_file_path = os.path.join(
                target_dir, 
                f"combined_{file_counter}.txt"
                )
            with open(target_file_path, "w", encoding="utf-8") as target_file:
                target_file.write(separator.join(current_content))
            file_counter += 1
            current_content = [content]
            current_size = estimated_size
        else:
            current_content.append(content)
            current_size += estimated_size

    # Write tail file
    if current_content:
        target_file_path = os.path.join(target_dir, f"combined_{file_counter}.txt")
        with open(target_file_path, "w", encoding="utf-8") as target_file:
            target_file.write(separator.join(current_content))

    print(f"{file_counter} file(s) saved in {target_dir}")

def main():
    with open("config.yaml") as f:
        config = yaml.safe_load(f)["prepare_dataset"]
    files = file_list(config)
    combine_files(files, config)

if __name__=='__main__':
    main()

Запускаем:

python data/prepare_dataset.py

На моей машине выполнение заняло около 23 минут. Я намеренно не делал параллелизацию и дедупликацию чтобы не усложнять там где это не сыграет большой роли для финального качества GPT 2 small.

Дедупликация исключает похожие книги. Например одна и та же книга может быть выпущена разными изданиями и оба издания могут быть представлены в датасете. Строго говоря, посимвольно они отличаются но текст почти идентичен. Если считать по совпадению название + автор то дублей у нас в датасете около 0.5% - несколько пьес Шекспира модель увидит 4 раза вместо 2.

В процессе вы увидите около 66 сообщений вроде

Skipping /home/alex/projects/gutenberg/data/raw/PG10511_raw.txt: not UTF-8

Это нормально. Несколько книг будет отброшено, чтобы не иметь проблем с кодировкой.

Взглянем на результат:

The Magna Carta

Contents

 The Text of Magna Carta
 Magna Carta 1215
 The text of THE MAGNA CARTA

A note from Michael Hart, preparer of the 0.1 version.

This file contains a number of versions of the Magna Carta, some of
which were a little mangled in transit. I am sure our volunteers will
find and correct errors I didn’t catch, and that version 0.2 - 1.0 will
have significant improvements, as well as at least one more version in
Latin.

Version 1.0 may contain a dozen different versions.

The Text of Magna Carta

JOHN, by the grace of God King of England, Lord of Ireland, Duke of
Normandy and Aquitaine, and Count of Anjou, to his archbishops,
bishops, abbots, earls, barons, justices, foresters, sheriffs,
stewards, servants, and to all his officials and loyal subjects,
Greeting.

KNOW THAT BEFORE GOD, for the health of our soul and those of our
ancestors and heirs, to the honour of God, the exaltation of the holy
Church, and the better ordering...
...on the fifteenth day of June,
in the seventeenth year of our reign.
<|endoftext|>                       SENECA

                  APOCOLOCYNTOSIS

          WITH AN ENGLISH TRANSLATION BY

            W.H.D. ROUSE, M.A. LITT. D.

                       MCMXX

INTRODUCTION

This piece is ascribed to Seneca by ancient

Обратите внимание что специальное слово <|endoftext|> разделяет книги.

Итог:

  • 39250 книг
  • 30 файлов до 500 МБ
  • Общий объем 14.3 ГБ

Переходим к следующей части - Токенизатору.