Embeddingi

Jedno zadanie obejmuje każdy wektor tworzony przez LibreYOLO. embed zwraca wiersze float32 o długości jednostkowej, których iloczyn skalarny jest miarą podobieństwa, niezależnie od tego, czy wiersz opisuje cały obraz, pojedynczą wykrytą twarz czy wiersz tekstu. Ta sama klasa Gallery dopasowuje je wszystkie.

Klucz zadania
embed
Aliasy
face-recognition, reid, face
Struktury wyników
Embeddings, Identities
Typ wiersza
float32, długość jednostkowa

Definicja

embed przekształca obraz, region obrazu lub ciąg znaków w wiersz float32 o stałej szerokości i długości równej jeden. Ponieważ każdy wiersz jest wektorem jednostkowym, dwa wektory porównuje się iloczynem skalarnym, a dwa ich zestawy jednym mnożeniem macierzy. Żaden inny element zadania nie zależy od modelu. Wyszukiwanie, wykrywanie duplikatów, ponowna identyfikacja i rozpoznawanie twarzy to te same działania arytmetyczne wykonywane na innych wierszach.

Wektor jest wynikiem. Nie ma listy klas, dlatego nazwa jest przypisywana później przez porównanie z dostarczonymi referencjami, a nie na podstawie elementu, którego przewidywanie wytrenowano w sieci.

Trzy kształty

KształtResults.embeddingsResults.boxesŹródło
Cały obraz(1, D)NonePrzekazanie obrazu do rodziny obsługującej cały obraz
Region(N, D)(N, 4), wiersze wyrównaneRodziny wykonujące najpierw lokalizację, na przykład rozpoznawanie twarzy
Tekstnie jest obiektem Resultsmodel.embed_text(texts), zwraca (M, D)

Wynik całego obrazu pozostaje dwuwymiarowy nawet w przypadku jednego obrazu. Kształt (D,) nie jest dozwolony, więc kod korzystający z wyniku nie musi osobno obsługiwać przypadku pojedynczego wiersza. Tekst zwraca zwykły tensor zamiast Results, ponieważ ciąg znaków nie jest źródłem obrazu. Przekazanie go do model(...) nadal oznacza ścieżkę lub adres URL, a biblioteka nigdy nie zgaduje, że ciąg znaków jest prozą.

Kanoniczny klucz zadania to embed. Wartości embedding, embeddings, face-recognition, facial-recognition, recognition, face, faceid i reid są do niego normalizowane, więc task="reid" i task="embed" wybierają dokładnie to samo zadanie.

Modele

Zadanie obsługują cztery rodziny. Wyraźnie dzielą się według tego, czy najpierw lokalizują jakiś element.

RodzinaKształtWymiarObsługuje również
LibreFaceRecRegion, jeden wiersz na wykrytą twarz512Nic; embed jest jedynym zadaniem
CLIPCały obraz, ze sparowaną częścią tekstową512 dla b32 i b16, 768 dla l14classify, które pozostaje zadaniem domyślnym
SigLIP 2Cały obraz, ze sparowaną częścią tekstową768 dla b16, 1152 dla so400mclassify, które pozostaje zadaniem domyślnym
DINOv2Cały obraz, tylko obraz384semantic, classify

CLIP i SigLIP 2 zachowują classify jako zadanie domyślne, dlatego trzeba jawnie wybrać task="embed". Ich istniejący checkpoint -cls jest wspólnym artefaktem z dwiema częściami. Nie publikuje się zduplikowanego checkpointu -embed dla identycznych wag.

Funkcja embed_text istnieje wyłącznie w CLIP i SigLIP 2, czyli dwóch rodzinach z częścią tekstową. DINOv2 jej nie ma. Embedding DINOv2 pomija głowice semantyczne i klasyfikacyjne oraz odczytuje końcowy znormalizowany token CLS przy 224 pikselach. Warianty n, s, m i l korzystają z tego samego enkodera DINOv2-S, dlatego wszystkie zwracają D = 384.

Backbone wyłącznie klasyfikacyjne dodane w tym wydaniu, ViT, Swin i DeiT, deklarują tylko classify i nie obsługują tego zadania.

Cały obraz
from libreyolo import LibreYOLO, SAMPLE_IMAGE # Domyślnym zadaniem CLIP jest classify, dlatego jawnie wybierz wektor.model = LibreYOLO("LibreCLIPb32-cls.pt", task="embed")result = model(SAMPLE_IMAGE) print(result.embeddings.data.shape)  # (1, 512), jeden wiersz na obrazprint(result.boxes)                  # None: niczego nie zlokalizowano
Każdy region
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("librefacerec-l.onnx")result = model(SAMPLE_IMAGE) # Wiersz i opisuje region w ramce i.print(result.boxes.xyxy.shape)       # (N, 4)print(result.embeddings.data.shape)  # (N, 512)
Wiele obrazów jednocześnie
from libreyolo.models.dinov2.model import LibreDINOv2 model = LibreDINOv2(size="s", task="embed") # Każdy wiersz z każdego wyniku połączony w jeden tensor.vectors = model.embed(["a.jpg", "b.jpg", "c.jpg"])print(vectors.shape)  # (3, 384)
Tekst
from libreyolo import LibreYOLO model = LibreYOLO("LibreCLIPb32-cls.pt", task="embed") # Tekst jest metodą, nigdy źródłem predykcji. Ciąg przekazany do# model(...) nadal oznacza ścieżkę lub adres URL.text = model.embed_text(["a photo of a cat", "a photo of a dog"])print(text.shape)  # (2, 512)

model.embed(source, **kwargs) jest skrótem dla batcha. Uruchamia predict i łączy każdy wiersz z każdego wyniku w jeden tensor float32 CPU o kształcie (N_total, D), zgłaszając wyjątek, jeśli wiersze mają różne wymiary. Rodzina, której obsługiwane zadania nie obejmują embed, zgłasza NotImplementedError.

Struktury wyników

result.embeddings jest strukturą Embeddings. Jej data ma zawsze kształt (N, D), typ float32 i jest już znormalizowane metodą L2 przez ścieżkę inferencji. Dane wejściowe, które nie są dwuwymiarowe, powodują zgłoszenie wyjątku zamiast cichej zmiany kształtu.

ElementZnaczenie
.dataMacierz (N, D)
.dimD
.normalizedTe same wiersze ponownie normalizowane zapobiegawczo
.similarity(other)(N, M) względem innego zestawu lub (N,) względem pojedynczego wektora (D,)
.verify(i, j, threshold=0.4)Czy wiersze i i j przedstawiają ten sam obiekt

result.identities jest strukturą Identities, obecną tylko po przekazaniu galerii. Jest zwykłym kontenerem, a nie tensorem, więc przenoszenie obiektu Results między urządzeniami pozostawia ją bez zmian.

ElementZnaczenie
.nameLista nazw, z None tam, gdzie żaden wynik nie przekroczył progu
.scoreNajlepszy wskaźnik cosinusowy float32 o kształcie (N,), zachowywany nawet przy nazwie None
.dataLista krotek (name, score)

Porównanie dwóch zestawów wierszy
from libreyolo import LibreYOLO model = LibreYOLO("LibreCLIPb32-cls.pt", task="embed") query = model.embed("query.jpg")          # (1, 512)pool = model.embed(["a.jpg", "b.jpg"])    # (2, 512) # Wiersze mają długość jednostkową, więc podobieństwo cosinusowe jest iloczynem skalarnym.scores = model("query.jpg").embeddings.similarity(pool)print(scores.shape)  # (1, 2)
Obraz względem tekstu
import torch from libreyolo import LibreYOLO model = LibreYOLO("LibreCLIPb32-cls.pt", task="embed") image = model.embed("photo.jpg")                       # (1, 512)text = model.embed_text(["a cat", "a dog", "a car"])   # (3, 512) print(torch.matmul(image, text.T))

Wektory są domyślnie pomijane w summary() i to_json(), ponieważ wiersz z 512 wartościami float zajmuje około dwóch kilobajtów na obiekt. Każdy wiersz zamiast tego zgłasza embedding_dim, a po użyciu galerii także identity i identity_score. Aby uwzględnić liczby, przekaż summary(embeddings=True).

Galerie

Gallery jest nazwanym zestawem wierszy referencyjnych. Przechowuje każdą referencję osobno zamiast je uśredniać, dlatego nazwa jest oceniana na podstawie jednej najlepiej dopasowanej referencji, a dodanie złego zdjęcia nie przesuwa centroidu tożsamości.

Rejestracja i identyfikacja
from libreyolo import Gallery, LibreYOLO model = LibreYOLO("librefacerec-l.onnx") gallery = Gallery(model)gallery.enroll("ada", ["people/ada/1.jpg", "people/ada/2.jpg"])gallery.enroll("grace", "people/grace/1.jpg")gallery.save("refs.npz") result = model("group.jpg", gallery=gallery, threshold=0.4)for name, score in result.identities.data:    print(name, score)   # poniżej progu name ma wartość None
Wyszukiwanie top-k
from libreyolo import Galleryfrom libreyolo.models.dinov2.model import LibreDINOv2 model = LibreDINOv2(size="s", task="embed")gallery = Gallery.load("refs.npz", model=model) result = model("query.jpg")matches = gallery.match(result.embeddings, top_k=5, threshold=0.4)print(matches[0])   # [(name, score), ...] dla pierwszego wiersza
Rejestracja istniejącego wektora
from libreyolo import Gallery gallery = Gallery()gallery.enroll_embedding("ada", vector)  # normalizowany przy dodawaniuprint(gallery.identities, gallery.dim, len(gallery))

Gallery(model) wiąże galerię z wagami, które będą tworzyć jej wektory. enroll(name, sources, select="best") uruchamia predykcję na każdym źródle i zachowuje wiersz o największej pewności z każdego wyniku. select="all" zachowuje zamiast tego każdy wiersz, co jest właściwym wyborem, gdy obraz referencyjny faktycznie zawiera kilka obiektów. enroll_embedding(name, vector) pomija inferencję i bezpośrednio przyjmuje wektor, normalizując go i odrzucając wiersz złożony wyłącznie z zer.

FaceGallery jest trwałym aliasem tej samej klasy, a archiwa utworzone przez starsze wydania obsługujące wyłącznie twarze nadal można wczytać.

Dopasowanie i progi

Dopasowanie jest gęstym mnożeniem macierzy względem każdej zapisanej referencji, redukowanym do jednego wskaźnika na nazwę przez wybranie maksimum. Nie ma indeksu przybliżonego, co zachowuje dokładne wyniki i nakłada praktyczne ograniczenie na rozmiar galerii.

Dwa punkty wejścia różnią się działaniem poniżej progu. Funkcja match() zwraca [(name, score), ...] dla każdego wiersza i odrzuca wszystko poniżej progu, dlatego wiersz bez dopasowania otrzymuje pustą listę. Funkcja identify() zwraca strukturę Identities, która zawsze zachowuje najlepszy wskaźnik i ustawia nazwę na None, gdy znajduje się on poniżej progu. Żadna z nich nigdy nie podstawia najbliższej nazwy niespełniającej progu.

Domyślny próg wynosi wszędzie 0.4. Jest to wartość cosinusowa, a nie prawdopodobieństwo. Właściwy punkt pracy jest cechą danych i tolerancji na fałszywe dopasowania, dlatego zamiast przyjmować wartość domyślną należy przetestować zakres progów na oznaczonych parach. Polecenie libreyolo enroll i argument predykcji gallery= używają tej samej wartości.

Trwały zapis

Funkcja save(path) zapisuje skompresowany plik .npz zawierający wektory, nazwy i blok metadanych z wersją formatu, wymiarem embeddingu oraz odciskiem wag, które utworzyły wiersze. Gallery.load(path, model=...) sprawdza oba elementy przed jakimkolwiek porównaniem, dlatego wskazanie galerii innemu modelowi zgłasza wyjątek zamiast po cichu porównywać wektory z dwóch niezwiązanych przestrzeni. Nie można zapisać pustej galerii.

Wiersz poleceń

PolecenieCel
libreyolo enrollPrzejście przez drzewo z jednym folderem na tożsamość oraz zapisanie lub rozszerzenie galerii .npz
libreyolo compareUtworzenie embeddingu głównego obiektu na dwóch obrazach i zgłoszenie podobieństwa cosinusowego
libreyolo verifyTo samo polecenie dostępne pod drugą nazwą
libreyolo predict gallery=...Dołączenie tożsamości do zwykłego uruchomienia predykcji

Rejestracja drzewa folderów
# source/<identity>/*.jpg. Istniejąca galeria jest rozszerzana w miejscu.libreyolo enroll model=librefacerec-l.onnx source=people/ gallery=refs.npz
Identyfikacja podczas predykcji
libreyolo predict model=librefacerec-l.onnx source=group.jpg \  gallery=refs.npz gallery_threshold=0.45
Porównanie dwóch obrazów
libreyolo compare model=librefacerec-l.onnx \  source=a.jpg source2=b.jpg threshold=0.4 # verify jest tym samym poleceniem dostępnym pod drugą nazwą.libreyolo verify model=librefacerec-l.onnx source=a.jpg source2=b.jpg --json

Każde polecenie LibreYOLO przyjmuje zarówno postać key=value, jak i --key value, dlatego gallery=refs.npz oraz --gallery refs.npz są tym samym argumentem.

Polecenie enroll przyjmuje model, source i gallery, a opcjonalnie także face-detector, device, --json i --quiet. Odczytuje po jednym folderze na tożsamość. Nazwa folderu jest tożsamością, a każdy obraz w jego wnętrzu wnosi referencje:

people/
  ada/
    1.jpg
    2.jpg
  grace/
    1.jpg

Obraz, który niczego nie zwraca, jest pomijany z komunikatem na stderr zamiast przerwania uruchomienia, a podsumowanie podaje liczbę referencji zapisanych dla każdej nazwy. Istniejący plik galerii jest rozszerzany w miejscu, dzięki czemu tożsamości można dodawać z czasem.

compare i verify są jedną funkcją zarejestrowaną dwukrotnie. Przyjmują model, source, source2 oraz opcjonalny threshold, po czym wyświetlają podobieństwo cosinusowe, wynik zgodności lub różnicy oraz próg, który dał ten wynik. Opcja --json wyświetla te same trzy pola jako obiekt.

W poleceniu predict argument gallery wskazuje zapisany plik .npz, a gallery_threshold nadpisuje domyślną wartość 0.4. Przekazanie galerii do modelu, którego zadaniem nie jest embed, powoduje błąd zamiast cichego braku działania. Brakujący plik galerii powoduje wyświetlenie sugestii użycia polecenia libreyolo enroll, które może go utworzyć.

Twarze

Rozpoznawanie twarzy jest regionalnym kształtem tego zadania i jedyną dostarczaną implementacją tego kształtu. Dodaje przed głowicą embeddingu etap detekcji i wyrównania, a także metodę verify(), argument własnych ramek, opublikowane wyniki dokładności i wskazówki kalibracji progu. Wszystko to znajduje się na stronie rozpoznawania twarzy, która jest odpowiednim przewodnikiem dla twarzy. Cała zawartość tej strony ma do niej zastosowanie bez zmian.

Trenowanie, walidacja i eksport

Żaden element tego zadania nie jest trenowany w LibreYOLO. Głowica embeddingu twarzy jest artefaktem ONNX, którego funkcje train(), val() i export() zgłaszają wyjątek. Głowicę należy wytrenować w projekcie źródłowym i wczytać plik ze ścieżki. CLIP, SigLIP 2 i DINOv2 obsługują trenowanie i eksport przez zadania klasyfikacji i segmentacji, a nie przez embed.

Nie ma walidatora wyszukiwania. Dokładność weryfikacji należy mierzyć na oznaczonych parach przez przeszukiwanie wartości threshold, a dokładność identyfikacji przez dodanie referencji do galerii i odczytywanie identities.name oraz identities.score na odłożonych obrazach. Nazwę None należy liczyć jako odrzucenie.

Klucz zadania i aliasy odczytano z libreyolo/tasks.py. Struktury wyników pochodzą z klas Embeddings i Identities w libreyolo/utils/results.py. API Gallery pochodzi z libreyolo/utils/gallery.py. Funkcje embed i _postprocess_embeddings pochodzą z libreyolo/models/base/model.py. Obsługiwane rodziny znaleziono przez wyszukanie embed w SUPPORTED_TASKS w plikach libreyolo/models/**/model.py. Powierzchnia CLI pochodzi z libreyolo/cli/__init__.py, libreyolo/cli/commands/special.py i libreyolo/cli/commands/predict.py. Założenia projektowe pochodzą z docs/adr/0015-embed-generalization.md.