In den letzten Jahren habe ich hauptsächlich mit großen Sprachmodellen, Coaching, Feinabstimmung, Aufforderung usw. gearbeitet, da dies auf dem Markt und von Benutzern hoch angefordert wurde. Aber ich glaube, dass LLMs, die hauptsächlich auf Textual content funktionieren, nur der Beginn von Genai sind. An einem bestimmten Punkt wird jeder wollen Physische AIwo Modelle auf fundiertere, menschlichere Weise sehen, hören, fühlen und begründen können.

Beginnen wir additionally mit Multimodalität. In diesem Pocket book stelle ich Llava vor, eine Architektur, die sowohl Bilder als auch Textual content interpretieren kann, um multimodale Antworten zu generieren.

In diesem Tutorial werden wir eine leichtere Komponente verwenden, die geeignet ist, das Notizbuch in einer freien Stufe wie Google Colab auszuführen.

Die Komponenten, die wir verwenden werden, sind:

1️⃣ Clip-vit B/32 als Bildcodierer

2️⃣ Tinyllama-1.1b Als Sprachmodell

3️⃣ a 2-layerer MLP-Adapter die beiden zu überbrücken

Aus dem Papier Visuelle Anweisungsstimmung (Neurips 2023)

Aufstellen

Bevor wir in den Code eintauchen können, lassen Sie uns unsere Umgebung einrichten.

Lassen Sie uns zunächst die Datensätzebibliothek installieren.

!pip set up -U datasets

Wir müssen jetzt die erforderlichen Pakete aus dem Umarmen und Pytorch importieren. Diese Importe liefern vorgebrachte Modelle und Dienstprogramme für die multimodale Verarbeitung.

import json
from pathlib import Path

import requests
import safetensors
import torch
from datasets import load_dataset
from huggingface_hub import hf_hub_download
from PIL import Picture
from transformers import (
    AutoConfig,
    AutoTokenizer,
    LlamaTokenizer,
    LlavaConfig,
    LlavaForConditionalGeneration,
    LlavaProcessor,
    Seq2SeqTrainer,
    Seq2SeqTrainingArguments,
)
from transformers.fashions.clip.modeling_clip import CLIPVisionModel
from transformers.fashions.clip.image_processing_clip import CLIPImageProcessor

Laden Sie vorgebildete Modellkomponenten herunter

Unser LLAVA -Modell besteht aus:

Bildquelle: https://arxiv.org/pdf/2103.00020

Der hf_hub_download ist ein Hub, den wir erforschen, um vorgebrägliche Gewichte abzurufen:

vision_backbone_name = "openai/clip-vit-base-patch32"
text_backbone_name = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
_ = hf_hub_download(
    vision_backbone_name, filename="pytorch_model.bin", local_dir="/content material"
)
_ = hf_hub_download(
    text_backbone_name, filename="mannequin.safetensors", local_dir="/content material"
)

Modell

Sofort ein neues LLAVA -Modell

Lassen Sie uns jetzt ein neues LLAVA -Modell instanziieren. Wie oben erläutert, besteht ein LLAVA -Modell aus zwei Teilen, einem visuellen Encoder und einem Textdecoder, den wir gerade heruntergeladen haben.

vision_config = AutoConfig.from_pretrained(vision_backbone_name).vision_config
text_config = AutoConfig.from_pretrained(text_backbone_name)

Wir geben die Spine -Modelle in der LLAVA -Konfiguration an. Wir instanziieren dann das tatsächliche Modell mit LlavaForConditionalGeneration(llava_config).

llava_config = LlavaConfig(vision_config=vision_config, text_config=text_config)
mannequin = LlavaForConditionalGeneration(llava_config).cuda()
mannequin

Einige chirurgische Operationen durchführen

Bildquelle: https://unsplash.com/images/doctor-having-operation-e285pjbc4ue

Zuvor sagten wir, wir könnten ein LLAVA-Modell konstruieren, indem wir von einem vorgebildeten Bildcodierer und einem vorgebliebenen LLM ausgestattet sind. Lass uns genau das tun!

Das ursprüngliche LLAVA -Modell wird aus a initialisiert Clip-vit l/14 und a Vicuna v1.5 7b. Um die Dinge mit den Ressourcen, die im freien Plan von Google Colab bereitgestellt werden Clip-vit b/16 und a Tinyllama 1.1b.

Die einzige Komponente, die wir trainieren, ist ein 2-Schicht-MLP-Adapter dazwischen.

Um die Clip- und Tinyllama-Modelle zu verwenden, müssen wir ihre vorgebrachten Gewichte laden. Diese Gewichte können jedoch in verschiedenen Formaten wie Safetensoren oder .bin vorhanden sein. Die Funktion load_Weights übernimmt dies für uns. Es überprüft den Dateityp und ruft die richtige Ladefunktion auf.

def load_weights(path_to_weights: str):
    if path_to_weights.endswith(".safetensors"):
        return load_safetensors_weights(path_to_weights)
    elif path_to_weights.endswith(".bin"):
        return load_bin_weights(path_to_weights)
    else:
        elevate ValueError(f"Unsupported weights file: {path_to_weights}")

def load_bin_weights(path_to_weights: str):
    return torch.load(path_to_weights, weights_only=True)

def load_safetensors_weights(path_to_weights: str):
    return safetensors.torch.load_file(path_to_weights)

vision_backbone_state_dict = load_weights("/content material/pytorch_model.bin")
text_backbone_state_dict = load_weights("/content material/mannequin.safetensors")

Injizieren Sie das Gewicht des Imaginative and prescient Spine in das Modell 💉 💉

Die nächsten Linien lädt die Gewichte in den Sichtteil des Modells. Wir setzen strikt = falsch flexibel zu sein, da wir es ermöglicht, alle Gewichte zu überspringen, die nicht perfekt der erwarteten Struktur des Modells entsprechen.

incompatible_keys = mannequin.vision_tower.load_state_dict(
    vision_backbone_state_dict, strict=False
)

assert len(incompatible_keys.missing_keys) == 0, (
    f"Lacking keys in state dict: {incompatible_keys.missing_keys}"
)

incompatible_keys.unexpected_keys

In die Gewichte des Textes Rückgrat in das Modell einfügen 💉

Gleiche Logik wie zuvor, aber auch für das Textmodell.

incompatible_keys = mannequin.language_model.load_state_dict(
    text_backbone_state_dict, strict=True
)

Die vorgeborenen Komponenten einfrieren ❄️

Wir möchten jetzt die Spine -Visible- und Textmodelle einfrieren, da wir ihre Gewichte während des Trainings nicht aktualisieren möchten.

Wir trainieren nur den kleinen Adapter (den MLP, der Sehvermögen und Sprache verbindet), das viel leichter und schneller zu trainieren ist.

_ = mannequin.vision_tower.requires_grad_(False)
_ = mannequin.language_model.requires_grad_(False)
# Then we outline a helper operate to rely mannequin parameters

def count_parameters(mannequin, trainable_only=False):
    return sum(
        p.numel()
        for p in mannequin.parameters()
        if not trainable_only or p.requires_grad
    )

print(f"Whole parameters: {count_parameters(mannequin)}")
print(f"Trainable parameters: {count_parameters(mannequin, trainable_only=True)}")

Prozessor

Bevor wir einen Textual content in unser Modell einfügen, müssen wir Wörter in Zahlen umwandeln. Dafür wird der Tokenizer benötigt.

tokenizer = LlamaTokenizer.from_pretrained(
    text_backbone_name, additional_special_tokens=("<picture>", "<pad>")
)
tokenizer.pad_token_id = 32001

Im Folgenden finden Sie das Format, mit dem wir mit unserem LLAVA -Modell chatten werden.

Der erste Teil ist der sogenannte Teil Systemaufforderungdie allgemeine Richtlinien für die Reaktion des Modells auf den Benutzer enthält.

Der zweite Teil ist eine Jinja -Vorlage (im Grunde genommen Code), die feststellt, wie die Konversation auf der Grundlage einer strukturierten Eingabe (siehe Beispiel unten) erstellt wird.

LLAVA_CHAT_TEMPLATE = (
    "A chat between a curious person and a man-made intelligence assistant. The assistant provides useful, detailed, and well mannered solutions to the person's questions. "
    "{% for message in messages %}{% if message('function') == 'person' %}USER: {% else %}ASSISTANT: {% endif %}{% for merchandise in message('content material') %}{% if merchandise('sort') == 'textual content' %}{{ merchandise('textual content') }}{% elif merchandise('sort') == 'picture' %}<picture>{% endif %}{% endfor %}{% if message('function') == 'person' %} {% else %}{{eos_token}}{% endif %}{% endfor %}"
)
tokenizer.chat_template = LLAVA_CHAT_TEMPLATE
sample_messages = (
    {
        "content material": (
            {
                "index": 0,
                "textual content": None,
                "sort": "picture"
            },
            {
                "index": None,
                "textual content": "nWhat potential actions is likely to be fashionable at this location?",
                "sort": "textual content"
            }
        ),
        "function": "person"
    },
    {
        "content material": (
            {
                "index": None,
                "textual content": (
                    "At this location, with a sandy path resulting in the ocean the place a number of boats, together with "
                    "sailboats, are moored, fashionable actions would possibly embody boating, crusing, swimming, and "
                    "beachcombing. Moreover, the sandy path and shoreline present an excellent setting for leisurely "
                    "strolls and picnics, whereas the ocean view affords a serene atmosphere for leisure and "
                    "pictures. Relying on the particular space and accessible amenities, different water sports activities akin to "
                    "kayaking, paddleboarding, and snorkeling may be prevalent."
                ),
                "sort": "textual content"
            }
        ),
        "function": "assistant"
    }
)

Wenden wir die Chat -Vorlage auf unsere Samples an.

tokenizer.apply_chat_template(
    sample_messages, tokenize=False, add_generation_prompt=False
)

Zu diesem Zeitpunkt haben wir unseren Tokenizer eingerichtet und das Imaginative and prescient -Modell heruntergeladen. Wir bringen sie in einen einheitlichen Einheit zusammen Prozessor.

processor = LlavaProcessor(
    image_processor=CLIPImageProcessor.from_pretrained(vision_backbone_name),
    tokenizer=tokenizer,
    patch_size=mannequin.config.vision_config.patch_size,
)
processor.chat_template = LLAVA_CHAT_TEMPLATE

Seit wir spezielle Token wie hinzugefügt haben <picture> Und <pad> Zu unserem Tokenizer früher muss das Modell vorhanden Passen Sie seinen Wortschatz an sie auch zu verstehen

mannequin.resize_token_embeddings(len(tokenizer), pad_to_multiple_of=8)

Datensatz

Lassen Sie uns den Datensatz herunterladen, den wir vom Umarmungsgesicht verwenden werden.

Der Datensatz, der Muster von Bild-Textual content-Paaren enthält, ist öffentlich verfügbar und kann gefunden werden Hier.

train_dataset = load_dataset(
    "HuggingFaceH4/llava-instruct-mix-vsft", break up="prepare", streaming=True
)

Wie sehen unsere Trainingsbeispiele aus?

subsequent(iter(train_dataset))

Wie bauen wir eine Reihe von Beispielen auf?

Die folgende Funktion nimmt RAW-Bild-Textual content-Beispiele auf und verwandelt sie in modellbereite Eingänge. Es formatiert die Nachrichten mithilfe der Chat -Vorlage, verarbeitet sowohl den Textual content als auch das Bild mit dem LlavaProcessor Wir haben zuvor definiert und erstellen ordnungsgemäße Trainingsetiketten, während wir die Polsterung ignoriert haben.

def get_data_collator(processor, ignore_index):
    def collate_examples(examples):
        # Extract texts and pictures from the uncooked examples
        texts = ()
        photographs = ()
        for instance in examples:
            messages = instance("messages")
            textual content = processor.tokenizer.apply_chat_template(
                messages, tokenize=False, add_generation_prompt=False
            )
            texts.append(textual content)
            photographs.append(instance("photographs")(0))

        # Course of the inputs (tokenize textual content and remodel photographs)
        batch = processor(texts, photographs, return_tensors="pt", padding=True)

        # Create labels
        labels = batch("input_ids").clone()
        if processor.tokenizer.pad_token_id just isn't None:
            labels(labels == processor.tokenizer.pad_token_id) = ignore_index
        batch("labels") = labels

        return batch

    return collate_examples

# NOTE: this does a bit greater than a collate operate ought to...

Ausbildung

Definieren wir schließlich die Trainingsargumente, einschließlich Chargengröße, Lernrate, Gesamtschritte und verwenden Sie gemischte Präzision (FP16) für die Geschwindigkeit. Wir vermeiden auch das Speichern von Kontrollpunkten, um die Dinge Licht zu halten. Dann wickeln wir alles in a ein Seq2SeqTrainerÜbergeben des Modells, des Datensatzes und unserem benutzerdefinierten Collator für Picture-Textual content-Eingaben.

args = Seq2SeqTrainingArguments(
    output_dir="/content material/training_output",
    per_device_train_batch_size=2,
    gradient_accumulation_steps=4,
    learning_rate=2e-4,
    max_steps=350,
    lr_scheduler_type="cosine_with_min_lr",
    lr_scheduler_kwargs={"min_lr": 2e-5},
    warmup_ratio=0.05,
    logging_strategy="steps",
    logging_steps=5,
    fp16=True,
    remove_unused_columns=False,  # Necessary!
    optim="adamw_torch",
    report_to="none",
    save_strategy="no",  # let's not save the checkpoint to disk, in any other case it will take 5 minutes
)

coach = Seq2SeqTrainer(
    mannequin=mannequin,
    args=args,
    data_collator=get_data_collator(
        processor, ignore_index=mannequin.config.ignore_index,
    ),
    train_dataset=train_dataset,
)
coach.prepare()

Schlussfolgerung

Um zu beachten, dass Inferenz wie erwartet funktioniert, sollten Sie schwerere Modelle verwenden und länger trainieren.

Wir werden dieses Bild für Inferenz verwenden:

Bildquelle: https://it.wikipedia.org/wiki/gioconda#/media/file:mona_lisa,_by_leonardo_da_vinci,_from_c2rmf_retouched.jpg
dialog = (
    {
        "content material": (
            {
                "sort": "picture"
            },
            {
                "textual content": "nWhat is represented within the picture?",
                "sort": "textual content"
            }
        ),
        "function": "person"
    }
)

In diesem Zellblock laden wir als Beispiel ein Bild aus einer URL und formatieren eine Konversation mit der Chat -Vorlage. Der Prozessor verwandelt sich beide in Tensoren. Anschließend verschieben wir die Eingabe in das Gerät des Modells und generieren eine Antwort, sodass das Modell das Bild basierend auf der Eingabeaufforderung des Benutzers beschreiben lassen.

image_url = "https://llava-vl.github.io/static/photographs/monalisa.jpg"

inputs_for_generation = processor(
    photographs=Picture.open(requests.get(image_url, stream=True).uncooked),
    textual content=processor.apply_chat_template(dialog, add_generation_prompt=True),
    return_tensors="pt",
)

inputs_for_generation = inputs_for_generation.to(machine=mannequin.machine)
output = coach.mannequin.generate(
    **inputs_for_generation, max_new_tokens=200, do_sample=False
)
print(processor.decode(output(0), skip_special_tokens=True))

Erweiterungen und Verbesserungen

  • Verwenden Sie einen größeren Bildcodierer (z. B. Clip-vit groß) und LLM (z. B. Lama 3.1 8b)
  • Länger trainieren. Das Modell dauert einige Zeit, um herauszufinden, wie man Anweisungen in Gegenwart von Bildfunktionen befolgt
  • Folgen Sie dem von der ursprünglichen LLAVA angenommenen mehrstufigen Schulungsverfahren
    • Stufe 1: Vorausbildung zur Function-Ausrichtung -> Trainieren Sie das Modell unter Anweisungsdaten mit Einzelverkehrsanweisungen, wo es aufgefordert wird, das Bild kurz zu beschreiben. Bildcodierer und LLM sind eingefroren
    • Stufe 2: Feinabstimmung Finish-to-Finish -> Trainieren Sie das Modell auf Multi-Flip-Befehlsdaten. Nur der Bildcodierer ist eingefroren

Arbeitsdemo: huggingface.co/areas/badayvedat/llava

Abschluss

Ich denke, dieses kleine Projekt ist interessant, um besser zu verstehen, wie multimodale Modelle wie Llava funktionieren. Selbst wenn wir kleinere Modelle verwendet haben, ist die Hauptidee dieselbe: Kombinieren Sie Imaginative and prescient und Sprache zu einem System, das Bilder verstehen und darüber sprechen kann.

Natürlich sind die in diesem Spielzeugbeispiel erzielten Ergebnisse nicht wirklich intestine; Es gibt viel Raum für Verbesserungen. Es ist jedoch eine große Herausforderung, Llava in einer Umgebung mit begrenzten Ressourcen zu machen

Folgen Sie mir weiter Tds Wenn Sie diesen Artikel mögen! 😁

💼 LinkedIn ️ | 🐦 x (Twitter) | 💻 Web site

Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert