NVIDIA NeMo Curator ile LLM Parametre Verimli İnce Ayarı için Özel Veri Setlerini Düzenleme

7


Son yayınımızda NVIDIA NeMo Curator’ın ön eğitim veya sürekli eğitim kullanım durumları için özel veri kümelerini düzenlemek amacıyla nasıl kullanılacağını ele aldık. büyük dil modelleri (LLM) ve küçük dil modelleri (SLM).

Bu tür eğitim senaryoları LLM gelişiminin önemli bir parçası olsa da, birçok alt akış uygulaması, alan-özel veri kümelerinde mevcut temel modellerin ince ayarını içerir. Bu, LoRA ve p-ayarlama gibi denetlenen ince ayar (SFT) veya parametre açısından verimli ince ayar (PEFT) yöntemleri kullanılarak elde edilebilir.

Bu iş akışlarında, genellikle hızlı bir şekilde yineleme yapmanız ve çeşitli fikirler ve hiperparametre ayarlarıyla denemeler yapmanız ve ayrıca eğitim verilerinin nasıl işlenip modele sunulduğunu belirlemeniz gerekir. Alana özgü verilerinizin nüanslarıyla etkili öğrenmeyi garantilemek için veri kümelerinizin birden fazla varyantını işlemeli ve düzenlemelisiniz.

Bu tür iş akışlarında sınırlı miktarda veri bulunması nedeniyle, esnek bir işlem hattı kullanılarak yüksek kalitede veri toplanması hayati önem taşımaktadır.

Bu gönderi, NeMo Curator kullanarak özel bir veri küratörlüğü hattı oluşturma konusunda size yol gösterir ve özellikle SFT ve PEFT kullanım durumlarına odaklanır. NeMo Curator’ın sağladığı temel yapı taşları hakkında daha fazla bilgi için NVIDIA NeMo Curator ile LLM Eğitimi için Özel Veri Kümelerini Küratörlüğe Alma konusuna bakın.

Genel bakış

Gösterim amaçlı olarak, bu gönderi e-posta sınıflandırmasını içeren bir oyuncak örneğine odaklanmaktadır. Amaç, her kaydın bir e-postadan (konu ve gövde) ve o e-posta için önceden tanımlanmış bir sınıflandırma etiketinden oluştuğu küçük bir metin tabanlı veri kümesini düzenlemektir.

Bu amaçla, her e-postanın sekiz kategoriden birine etiketlendiği Enron e-posta veri kümesini kullandık. Bu veri kümesi HuggingFace’te herkese açık olarak mevcuttur ve ~1.400 kayıt içeriyor.

Veri toplama süreci aşağıdaki üst düzey adımları içerir:

  1. Veri setini JSONL formatına dönüştürmek için indirici, yineleyici ve çıkarıcı sınıflarını tanımlayın.
  2. Unicode gösterimini birleştirmek için mevcut araçları kullanın.
  3. Boş veya çok uzun e-postaları kaldırmak için özel veri kümesi filtreleri tanımlayın.
  4. Veri setinden kişisel olarak tanımlanabilir tüm bilgileri (PII) sansürleyin.
  5. Her kayda talimat istemleri ekleyin.
  6. Küratörlük hattını bir araya getirin.

Bu küratörlük hattının yürütülmesi tüketici sınıfı donanımda 5 dakikadan az sürmelidir. Bu eğitimin tam koduna erişmek için bkz. /NVIDIA/NeMo-Küratör GitHub deposu.

Ön koşullar

Başlamadan önce NeMo Curator çerçevesini yüklemeniz gerekir. Aşağıdaki talimatları izleyin NeMo Curator GitHub README dosyası çerçeveyi kurmak için.

Ardından, kurulumu doğrulamak ve ek bağımlılıkları yüklemek için aşağıdaki komutları çalıştırın:

$ python -c "import nemo_curator; print(nemo_curator);"
$ pip3 install requests

Özel belge oluşturucularını tanımlama

Bir veri setini düzenlemenin ilk adımı, belge oluşturucular veri setini indirip üzerinde yineleme yapabilen.

Veri kümesi indiriliyor

Uygula DocumentDownloader veri kümesinin URL’sini alan ve onu kullanarak indiren sınıf requests kütüphane.

import requests
from nemo_curator.download.doc_builder import DocumentDownloader

class EmailsDownloader(DocumentDownloader):
    def __init__(self, download_dir: str):
        super().__init__()

        if not os.path.isdir(download_dir):
            os.makedirs(download_dir)

        self._download_dir = download_dir
        print("Download directory: ", self._download_dir)

    def download(self, url: str) -> str:
        filename = os.path.basename(url)
        output_file = os.path.join(self._download_dir, filename)

        if os.path.exists(output_file):
            print(f"File '{output_file}' already exists, skipping download.")
            return output_file

        print(f"Downloading Enron emails dataset from '{url}'...")
        response = requests.get(url)

        with open(output_file, "wb") as file:
            file.write(response.content)

        return output_file

İndirilen veri seti bir metin dosyasıdır ve her giriş yaklaşık olarak aşağıdaki biçimi takip eder:

“<s>[system instruction prompts]

Subject:: [email subject]
Body:: [email body]

[category label] <s>”

Bu format, düzenli ifadeler kullanılarak kolayca bileşenlerine ayrılabilir. Unutulmaması gereken en önemli şey, girdilerin dizilerle ayrılmasıdır. “<s> … <s>” ve her zaman talimat istemleriyle başlayın. Ayrıca, örnek ayırıcı belirteçleri ve sistem istemi belirteçleri, Llama 2 belirteçleyici ailesiyle uyumludur.

Bu verileri özel belirteçleri desteklemeyen diğer belirteçleyiciler veya modellerle kullanabileceğiniz için, ayrıştırma sırasında bu talimatları ve belirteçleri atmak en iyisidir. Bu gönderinin ilerleyen kısımlarında, talimat istemlerinin veya özel belirteçlerin NeMo Curator kullanılarak her bir girdiye nasıl eklenebileceğini gösteriyoruz DocumentModifier araçlar.

Veri setini ayrıştırma ve yineleme

Uygula DocumentIterator Ve DocumentExtractor e-posta konusunu, gövdesini ve kategori (sınıf) etiketlerini çıkarmak için sınıflar:

from nemo_curator.download.doc_builder import (
    DocumentExtractor,
    DocumentIterator,
)

class EmailsIterator(DocumentIterator):

    def __init__(self):
        super().__init__()
        self._counter = -1
        self._extractor = EmailsExtractor()
        # The regular expression pattern to extract each email.
        self._pattern = re.compile(r"\"<s>.*?<s>\"", re.DOTALL)

    def iterate(self, file_path):
        self._counter = -1
        file_name = os.path.basename(file_path)

        with open(file_path, "r", encoding="utf-8") as file:
            lines = file.readlines()

        # Ignore the first line which contains the header.
        file_content = "".join(lines[1:])
        # Find all the emails in the file.
        it = self._pattern.finditer(file_content)

        for email in it:
            self._counter += 1
            content = email.group().strip('"').strip()
            meta = {
                "filename": file_name,
                "id": f"email-{self._counter}",
            }
            extracted_content = self._extractor.extract(content)

            # Skip if no content extracted
            if not extracted_content:
                continue

            record = {**meta, **extracted_content}
            yield record


class EmailsExtractor(DocumentExtractor):
    def __init__(self):
        super().__init__()
        # The regular expression pattern to extract subject/body/label into groups.
        self._pattern = re.compile(
            r"Subject:: (.*?)\nBody:: (.*?)\n.*\[/INST\] (.*?) <s>", re.DOTALL
        )

    def extract(self, content: str) -> Dict[str, str]:
        matches = self._pattern.findall(content)

        if not matches:
            return None

        matches = matches[0]

        return {
            "subject": matches[0].strip(),
            "body": matches[1].strip(),
            "category": matches[2].strip(),
        }

Yineleyici, düzenli ifadeyi kullanır \"<s>.*?<s>\" her örneği bulmak için. Daha sonra dizeyi, düzenli ifadeyi kullanan çıkarıcıya geçirir "Subject:: (.*?)\nBody:: (.*?)\n.*\[/INST\] (.*?) <s>"Bu ifade gruplama operatörünü kullanır (.*?) özneyi, gövdeyi ve kategoriyi çıkarmak için.

Çıkarılan bu parçalar, yararlı meta verilerle (örneğin her e-posta için benzersiz bir kimlik) birlikte bir sözlükte saklanır ve arayana geri gönderilir.

Artık bu veri setini, NeMo Curator’ın desteklediği birçok formattan biri olan JSONL formatına dönüştürmeye hazırsınız

Veri kümesini JSONL biçimine yazma

Veri kümesi düz metin dosyası olarak indirilir. Uygula DocumentIterator Ve DocumentExtractor Kayıtlar arasında yineleme yapmak, bunları JSONL biçimine dönüştürmek ve her kaydı bir dosyadaki satır olarak depolamak için sınıflar.

import json

def download_and_convert_to_jsonl() -> str:
    """
    Downloads the emails dataset and converts it to JSONL format.

    Returns:
        str: The path to the JSONL file.
    """

    # Download the dataset in raw format and convert it to JSONL.
    downloader = EmailsDownloader(DATA_DIR)
    output_path = os.path.join(DATA_DIR, "emails.jsonl")
    raw_fp = downloader.download(DATASET_URL)

    iterator = EmailsIterator()

    # Parse the raw data and write it to a JSONL file.
    with open(output_path, "w") as f:
        for record in iterator.iterate(raw_fp):
            json_record = json.dumps(record, ensure_ascii=False)
            f.write(json_record + "\n")

    return output_path

Veri kümesindeki her kayıttan gelen bilgiler birden fazla JSON alanına yazılır:

  • subject
  • body
  • category
  • Meta Veri:

Bu gereklidir çünkü NeMo Curator’daki birçok veri düzenleme işlemi her kayıttaki hangi alanda işlem yapılacağını bilmelidir. Bu yapı, NeMo Curator işlemleri için farklı veri kümesi bilgilerinin uygun şekilde hedeflenmesini sağlar.

Veri setini belge oluşturucuları kullanarak yükleme

NeMo Curator’da veri kümeleri, şu türdeki nesneler olarak temsil edilir: DocumentDataset. Bu, veri kümelerini diskten çeşitli biçimlerde yüklemek için yardımcılar sağlar. Veri kümesini yüklemek ve onunla çalışmaya başlamak için aşağıdaki kodu kullanın:

from nemo_curator.datasets import DocumentDataset
# define `filepath` to be the path to the JSONL file created above.
dataset = DocumentDataset.read_json(filepath, add_filename=True)

Artık özel bir veri kümesi düzenleme hattı tanımlamak ve verileri hazırlamak için gereken her şeye sahipsiniz.

Çevrimiçi kaynaklardan alınan metinlerde tutarsızlıklar veya Unicode hataları olabileceğinden, veri kümelerindeki tüm Unicode sorunlarını düzeltmek genellikle iyi bir uygulamadır.

Belgeleri değiştirmek için NeMo Curator bir DocumentModifier arayüz ile birlikte Modify helper, her belgeden verilen metnin nasıl değiştirileceğini tanımlar. Kendi özel belge değiştiricilerinizi uygulama hakkında daha fazla bilgi için, önceki yazıda yer alan Metin temizleme ve birleştirme bölümüne bakın.

Bu örnekte, şunu uygulamak yeterlidir: UnicodeReformatter veri kümesine. Her kayıtta birden fazla alan olduğundan, işlemi veri kümesindeki her ilgili alana bir kez uygulayın. Bu işlemler, Sequential sınıf:

Sequential([
    Modify(UnicodeReformatter(), text_field="subject"),
    Modify(UnicodeReformatter(), text_field="body"),
    Modify(UnicodeReformatter(), text_field="category"),
])

Özel veri kümesi filtreleri tasarlama

Birçok PEFT kullanım durumunda, veri setini iyileştirmek, alakasız veya düşük kaliteli olabilecek kayıtları veya belirli uygunsuz niteliklere sahip kayıtları filtrelemeyi içerir. E-posta veri setinde, bazı e-postalar çok uzun veya boştur. Gösterim amaçlı olarak, özel uygulayarak bu tür tüm kayıtları veri setinden kaldırın DocumentFilter sınıflar:

from nemo_curator.filters import DocumentFilter

class FilterEmailsWithLongBody(DocumentFilter):
    """
    If the email is too long, discard.
    """

    def __init__(self, max_length: int = 5000):
        super().__init__()
        self.max_length = max_length

    def score_document(self, text: str) -> bool:
        return len(text) <= self.max_length

    def keep_document(self, score) -> bool:
        return score

class FilterEmptyEmails(DocumentFilter):
    """
    Detects empty emails (either empty body, or labeled as empty).
    """

    def score_document(self, text: str) -> bool:
        return (
            not isinstance(text, str)  # The text is not a string
            or len(text.strip()) == 0  # The text is empty
            or "Empty message" in text  # The email is labeled as empty
        )

    def keep_document(self, score) -> bool:
        return score

The FilterEmailsWithLongBody sınıf, sağlanan metindeki karakter sayısını sayar ve döndürür True eğer uzunluk kabul edilebilirse, veya False aksi takdirde. Bu filtreyi açıkça uygulamanız gerekir body her kayıt için alan.

The FilterEmptyEmails sınıf, boş bir e-postayı ifade edip etmediğini belirlemek için verilen metnin türünü ve içeriğini kontrol eder ve döndürür True e-postanın boş olduğu kabul edilirse veya False Aksi takdirde. Bu filtreyi tüm ilgili alanlara açıkça uygulamalısınız: subject, bodyVe category her kayıt için alanlar.

Döndürülen değer, kod okunabilirliğini artıran sınıfın adlandırmasıyla tutarlıdır. Ancak, amaç boş e-postaları atmak olduğundan, bu filtreden gelen sonuçlar tersine çevrilmelidir. Başka bir deyişle, filtre şunu döndürürse kaydı atın True ve filtre geri dönerse kaydı tutun FalseBu, ilgili bayrağın sağlanmasıyla yapılabilir. ScoreFilter yardımcı:

Sequential([
    # Apply only to the `body` field.
    ScoreFilter(FilterEmailsWithLongBody(), text_field="body", score_type=bool),
    # Apply to all fields, also invert the action.
    ScoreFilter(FilterEmptyEmails(), text_field="subject", score_type=bool, invert=True),
    ScoreFilter(FilterEmptyEmails(), text_field="body", score_type=bool, invert=True),
    ScoreFilter(FilterEmptyEmails(), text_field="category", score_type=bool, invert=True),
])

Bayrağı belirtin invert=True talimat vermek ScoreFilter filtrenin döndürdüğü belgeleri atmak için Trues’yi belirterekcore_type=boolher filtre için dönüş türünü açıkça belirtirsiniz; bu da yürütme sırasında tür çıkarımından kaçınılmasını sağlar.

Kişisel olarak tanımlanabilir tüm bilgilerin sansürlenmesi

Sonra, her kaydın konusundan ve gövdesinden tüm kişisel olarak tanımlanabilir bilgileri (PII) sansürlemek için bir işlem adımı tanımlayın. Bu veri kümesi, e-postalar, telefon veya faks numaraları, adlar ve adresler gibi birçok PII örneği içerir.

NeMo Curator, tespit edilecek PII türünü ve her tespit için hangi eylemin gerçekleştirileceğini belirtmeyi kolaylaştırır. Her tespiti özel belirteçlerle değiştirin:

def redact_pii(dataset: DocumentDataset, text_field) -> DocumentDataset:
    redactor = Modify(
        PiiModifier(
            supported_entities=[
                "ADDRESS",
                "EMAIL_ADDRESS",
                "LOCATION",
                "PERSON",
                "URL",
                "PHONE_NUMBER",
            ],
            anonymize_action="replace",
            device="cpu",
        ),
        text_field=text_field,
    )
    return redactor(dataset)

Bu işlemleri şuraya uygulayabilirsiniz: subject Ve body Python’u kullanarak alanları ayrı ayrı functools.partial yardımcı:

from functools import partial

redact_pii_subject = partial(redact_pii, text_field="subject")
redact_pii_body = partial(redact_pii, text_field="body")

Sequential([
    redact_pii_subject,
    redact_pii_body,
    ]
)

Talimat istemleri ekleme

Veri küratörlüğü boru hattının son adımı, her kayda talimat istemleri eklemeyi ve her kategori değerinin bir nokta ile sonlanmasını sağlamayı içerir. Bunlar, ilgili DocumentModifier sınıflar:

from nemo_curator.modifiers import DocumentModifier

class AddSystemPrompt(DocumentModifier):
    def modify_document(self, text: str) -> str:
        return SYS_PROMPT_TEMPLATE % text


class AddPeriod(DocumentModifier):
    def modify_document(self, text: str) -> str:
        return text + "."

Kod örneğinde, SYS_PROMPT_TEMPLATE değişken, metnin etrafına talimat istemleri eklemek için kullanılabilen bir biçimlendirme dizesi içerir. Bu değiştiriciler birbirine zincirlenebilir:

Sequential([
    Modify(AddSystemPrompt(), text_field="body"),
    Modify(AddPeriod(), text_field="category"),
])

Küratörlük hattını bir araya getirmek

Küratörlük işlem hattının her adımını uyguladıktan sonra, her şeyi bir araya getirme ve her işlemi veri kümesine sırayla uygulama zamanı geldi. Şunu kullanabilirsiniz: Sequential küratörlük işlemlerini bir arada zincirlemek için sınıf:

curation_steps = Sequential(
    [
        #
        # Unify the text encoding to Unicode.
        #
        Modify(UnicodeReformatter(), text_field="subject"),
        Modify(UnicodeReformatter(), text_field="body"),
        Modify(UnicodeReformatter(), text_field="category"),

        #
        # Filtering
        #
        ScoreFilter(
            FilterEmptyEmails(), text_field="subject", score_type=bool, invert=True
        ),
        ScoreFilter(
            FilterEmptyEmails(), text_field="body", score_type=bool, invert=True
        ),
        ScoreFilter(
            FilterEmptyEmails(), text_field="category", score_type=bool, invert=True
        ),
        ScoreFilter(FilterEmailsWithLongBody(), text_field="body", score_type=bool),

        #
        # Redact personally identifiable information (PII).
        #

        redact_pii_subject,
        redact_pii_body,

        #
        # Final modifications.
        #
        Modify(AddSystemPrompt(), text_field="body"),
        Modify(AddPeriod(), text_field="category"),
    ]
)

dataset = curation_steps(dataset)
dataset = dataset.persist()
dataset.to_json("/output/path", write_to_filename=True)

NeMo Curator, veri kümesiyle dağıtılmış bir şekilde çalışmak için Dask’ı kullanır. Dask işlemleri tembel olarak değerlendirildiğinden, .persist Dask’a işlemleri uygulamasını talimat veren işlev. İşlem tamamlandıktan sonra, veri setini JSONL biçiminde diske yazarak çağırabilirsiniz. .to_json fonksiyonu ve bir çıktı yolu sağlanması.

Sonraki adımlar

Bu eğitimde, NeMo Curator kullanılarak özel bir veri toplama hattının nasıl oluşturulacağı gösterilmiş olup, özellikle SFT ve PEFT kullanım örneklerine odaklanılmıştır.

Kolay erişim için öğreticiyi şuraya yükledik: /NVIDIA/NeMo-Küratör GitHub deposu. En son gelişmelerden haberdar olmak ve yeni özellikler, hata düzeltmeleri ve güncellemeler hakkında bildirimler almak için depoyu yıldızlayın.

Artık verileri düzenlediğinize göre, LoRA ile e-posta sınıflandırması için Llama 2 modeli gibi bir LLM’yi ince ayarlayabilirsiniz. Daha fazla bilgi için bkz. NeMo framework PEFT ile Llama 2 oyun kitabı.

Ayrıca, kuruluşların veri küratörlüğüne her yerden başlamaları için en kolay yolu sağlayan NVIDIA NeMo Curator mikro servisine erişim talebinde bulunabilirsiniz. Başvurmak için NeMo Curator Mikro Servis Erken Erişimi’ne bakın.

Kaynak: Nvidia

Doğrudan cihazınızda gerçek zamanlı güncellemeleri alın, şimdi abone olun.

Yorumlar