255 lines
12 KiB
Markdown
255 lines
12 KiB
Markdown
<!--Copyright 2020 The HuggingFace Team. All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
|
the License. You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
|
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
|
|
⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
|
|
rendered properly in your Markdown viewer.
|
|
|
|
-->
|
|
|
|
# Wie erstellt man eine benutzerdefinierte Pipeline?
|
|
|
|
In dieser Anleitung sehen wir uns an, wie Sie eine benutzerdefinierte Pipeline erstellen und sie auf dem [Hub](https://hf.co/models) freigeben oder sie der
|
|
🤗 Transformers-Bibliothek hinzufügen.
|
|
|
|
Zuallererst müssen Sie entscheiden, welche Roheingaben die Pipeline verarbeiten kann. Es kann sich um Strings, rohe Bytes,
|
|
Dictionaries oder was auch immer die wahrscheinlichste gewünschte Eingabe ist. Versuchen Sie, diese Eingaben so rein wie möglich in Python zu halten
|
|
denn das macht die Kompatibilität einfacher (auch mit anderen Sprachen über JSON). Dies werden die Eingaben der
|
|
Pipeline (`Vorverarbeitung`).
|
|
|
|
Definieren Sie dann die `Outputs`. Dieselbe Richtlinie wie für die Eingänge. Je einfacher, desto besser. Dies werden die Ausgaben der
|
|
Methode `Postprocess`.
|
|
|
|
Beginnen Sie damit, die Basisklasse `Pipeline` mit den 4 Methoden zu erben, die für die Implementierung von `preprocess` benötigt werden,
|
|
Weiterleiten", "Nachbearbeitung" und "Parameter säubern".
|
|
|
|
|
|
```python
|
|
from transformers import Pipeline
|
|
|
|
|
|
class MyPipeline(Pipeline):
|
|
def _sanitize_parameters(self, **kwargs):
|
|
preprocess_kwargs = {}
|
|
if "maybe_arg" in kwargs:
|
|
preprocess_kwargs["maybe_arg"] = kwargs["maybe_arg"]
|
|
return preprocess_kwargs, {}, {}
|
|
|
|
def preprocess(self, inputs, maybe_arg=2):
|
|
model_input = Tensor(inputs["input_ids"])
|
|
return {"model_input": model_input}
|
|
|
|
def _forward(self, model_inputs):
|
|
# model_inputs == {"model_input": model_input}
|
|
outputs = self.model(**model_inputs)
|
|
# Maybe {"logits": Tensor(...)}
|
|
return outputs
|
|
|
|
def postprocess(self, model_outputs):
|
|
best_class = model_outputs["logits"].softmax(-1)
|
|
return best_class
|
|
```
|
|
|
|
Die Struktur dieser Aufteilung soll eine relativ nahtlose Unterstützung für CPU/GPU ermöglichen und gleichzeitig die Durchführung von
|
|
Vor-/Nachbearbeitung auf der CPU in verschiedenen Threads
|
|
|
|
Preprocess" nimmt die ursprünglich definierten Eingaben und wandelt sie in etwas um, das in das Modell eingespeist werden kann. Es kann
|
|
mehr Informationen enthalten und ist normalerweise ein `Dict`.
|
|
|
|
`_forward` ist das Implementierungsdetail und ist nicht dafür gedacht, direkt aufgerufen zu werden. Weiterleiten" ist die bevorzugte
|
|
aufgerufene Methode, da sie Sicherheitsvorkehrungen enthält, die sicherstellen, dass alles auf dem erwarteten Gerät funktioniert. Wenn etwas
|
|
mit einem realen Modell verknüpft ist, gehört es in die Methode `_forward`, alles andere gehört in die Methoden preprocess/postprocess.
|
|
|
|
Die Methode `Postprocess` nimmt die Ausgabe von `_forward` und verwandelt sie in die endgültige Ausgabe, die zuvor festgelegt wurde.
|
|
zuvor entschieden wurde.
|
|
|
|
Die Methode `_sanitize_parameters` ermöglicht es dem Benutzer, beliebige Parameter zu übergeben, wann immer er möchte, sei es bei der Initialisierung
|
|
Zeit `pipeline(...., maybe_arg=4)` oder zur Aufrufzeit `pipe = pipeline(...); output = pipe(...., maybe_arg=4)`.
|
|
|
|
Die Rückgabe von `_sanitize_parameters` sind die 3 Dicts von kwargs, die direkt an `preprocess` übergeben werden,
|
|
`_forward` und `postprocess` übergeben werden. Füllen Sie nichts aus, wenn der Aufrufer keinen zusätzlichen Parameter angegeben hat. Das
|
|
erlaubt es, die Standardargumente in der Funktionsdefinition beizubehalten, was immer "natürlicher" ist.
|
|
|
|
Ein klassisches Beispiel wäre das Argument `top_k` in der Nachbearbeitung bei Klassifizierungsaufgaben.
|
|
|
|
```python
|
|
>>> pipe = pipeline("my-new-task")
|
|
>>> pipe("This is a test")
|
|
[{"label": "1-star", "score": 0.8}, {"label": "2-star", "score": 0.1}, {"label": "3-star", "score": 0.05}
|
|
{"label": "4-star", "score": 0.025}, {"label": "5-star", "score": 0.025}]
|
|
|
|
>>> pipe("This is a test", top_k=2)
|
|
[{"label": "1-star", "score": 0.8}, {"label": "2-star", "score": 0.1}]
|
|
```
|
|
|
|
In order to achieve that, we'll update our `postprocess` method with a default parameter to `5`. and edit
|
|
`_sanitize_parameters` to allow this new parameter.
|
|
|
|
|
|
```python
|
|
def postprocess(self, model_outputs, top_k=5):
|
|
best_class = model_outputs["logits"].softmax(-1)
|
|
# Add logic to handle top_k
|
|
return best_class
|
|
|
|
|
|
def _sanitize_parameters(self, **kwargs):
|
|
preprocess_kwargs = {}
|
|
if "maybe_arg" in kwargs:
|
|
preprocess_kwargs["maybe_arg"] = kwargs["maybe_arg"]
|
|
|
|
postprocess_kwargs = {}
|
|
if "top_k" in kwargs:
|
|
postprocess_kwargs["top_k"] = kwargs["top_k"]
|
|
return preprocess_kwargs, {}, postprocess_kwargs
|
|
```
|
|
|
|
Versuchen Sie, die Eingaben/Ausgaben sehr einfach und idealerweise JSON-serialisierbar zu halten, da dies die Verwendung der Pipeline sehr einfach macht
|
|
ohne dass die Benutzer neue Arten von Objekten verstehen müssen. Es ist auch relativ üblich, viele verschiedene Arten von Argumenten zu unterstützen
|
|
von Argumenten zu unterstützen (Audiodateien, die Dateinamen, URLs oder reine Bytes sein können).
|
|
|
|
|
|
|
|
## Hinzufügen zur Liste der unterstützten Aufgaben
|
|
|
|
Um Ihre `neue Aufgabe` in die Liste der unterstützten Aufgaben aufzunehmen, müssen Sie sie zur `PIPELINE_REGISTRY` hinzufügen:
|
|
|
|
```python
|
|
from transformers.pipelines import PIPELINE_REGISTRY
|
|
|
|
PIPELINE_REGISTRY.register_pipeline(
|
|
"new-task",
|
|
pipeline_class=MyPipeline,
|
|
pt_model=AutoModelForSequenceClassification,
|
|
)
|
|
```
|
|
|
|
Wenn Sie möchten, können Sie ein Standardmodell angeben. In diesem Fall sollte es mit einer bestimmten Revision (die der Name einer Verzweigung oder ein Commit-Hash sein kann, hier haben wir `"abcdef"` genommen) sowie mit dem Typ versehen sein:
|
|
|
|
```python
|
|
PIPELINE_REGISTRY.register_pipeline(
|
|
"new-task",
|
|
pipeline_class=MyPipeline,
|
|
pt_model=AutoModelForSequenceClassification,
|
|
default={"pt": ("user/awesome_model", "abcdef")},
|
|
type="text", # current support type: text, audio, image, multimodal
|
|
)
|
|
```
|
|
|
|
## Teilen Sie Ihre Pipeline auf dem Hub
|
|
|
|
Um Ihre benutzerdefinierte Pipeline auf dem Hub freizugeben, müssen Sie lediglich den benutzerdefinierten Code Ihrer `Pipeline`-Unterklasse in einer
|
|
Python-Datei speichern. Nehmen wir zum Beispiel an, Sie möchten eine benutzerdefinierte Pipeline für die Klassifizierung von Satzpaaren wie folgt verwenden:
|
|
|
|
```py
|
|
import numpy as np
|
|
|
|
from transformers import Pipeline
|
|
|
|
|
|
def softmax(outputs):
|
|
maxes = np.max(outputs, axis=-1, keepdims=True)
|
|
shifted_exp = np.exp(outputs - maxes)
|
|
return shifted_exp / shifted_exp.sum(axis=-1, keepdims=True)
|
|
|
|
|
|
class PairClassificationPipeline(Pipeline):
|
|
def _sanitize_parameters(self, **kwargs):
|
|
preprocess_kwargs = {}
|
|
if "second_text" in kwargs:
|
|
preprocess_kwargs["second_text"] = kwargs["second_text"]
|
|
return preprocess_kwargs, {}, {}
|
|
|
|
def preprocess(self, text, second_text=None):
|
|
return self.tokenizer(text, text_pair=second_text, return_tensors=self.framework)
|
|
|
|
def _forward(self, model_inputs):
|
|
return self.model(**model_inputs)
|
|
|
|
def postprocess(self, model_outputs):
|
|
logits = model_outputs.logits[0].numpy()
|
|
probabilities = softmax(logits)
|
|
|
|
best_class = np.argmax(probabilities)
|
|
label = self.model.config.id2label[best_class]
|
|
score = probabilities[best_class].item()
|
|
logits = logits.tolist()
|
|
return {"label": label, "score": score, "logits": logits}
|
|
```
|
|
|
|
Die Implementierung ist Framework-unabhängig und funktioniert für PyTorch- und TensorFlow-Modelle. Wenn wir dies in einer Datei
|
|
einer Datei namens `pair_classification.py` gespeichert haben, können wir sie importieren und wie folgt registrieren:
|
|
|
|
```py
|
|
from pair_classification import PairClassificationPipeline
|
|
from transformers.pipelines import PIPELINE_REGISTRY
|
|
from transformers import AutoModelForSequenceClassification, TFAutoModelForSequenceClassification
|
|
|
|
PIPELINE_REGISTRY.register_pipeline(
|
|
"pair-classification",
|
|
pipeline_class=PairClassificationPipeline,
|
|
pt_model=AutoModelForSequenceClassification,
|
|
tf_model=TFAutoModelForSequenceClassification,
|
|
)
|
|
```
|
|
|
|
Sobald dies geschehen ist, können wir es mit einem vortrainierten Modell verwenden. Zum Beispiel wurde `sgugger/finetuned-bert-mrpc` auf den
|
|
auf den MRPC-Datensatz abgestimmt, der Satzpaare als Paraphrasen oder nicht klassifiziert.
|
|
|
|
```py
|
|
from transformers import pipeline
|
|
|
|
classifier = pipeline("pair-classification", model="sgugger/finetuned-bert-mrpc")
|
|
```
|
|
|
|
Dann können wir sie auf dem Hub mit der Methode `push_to_hub` freigeben:
|
|
|
|
```py
|
|
classifier.push_to_hub("test-dynamic-pipeline")
|
|
```
|
|
|
|
Dadurch wird die Datei, in der Sie `PairClassificationPipeline` definiert haben, in den Ordner `"test-dynamic-pipeline"` kopiert,
|
|
und speichert das Modell und den Tokenizer der Pipeline, bevor Sie alles in das Repository verschieben
|
|
`{Ihr_Benutzername}/test-dynamic-pipeline`. Danach kann jeder die Pipeline verwenden, solange er die Option
|
|
`trust_remote_code=True` angeben:
|
|
|
|
```py
|
|
from transformers import pipeline
|
|
|
|
classifier = pipeline(model="{your_username}/test-dynamic-pipeline", trust_remote_code=True)
|
|
```
|
|
|
|
## Hinzufügen der Pipeline zu 🤗 Transformers
|
|
|
|
Wenn Sie Ihre Pipeline zu 🤗 Transformers beitragen möchten, müssen Sie ein neues Modul im Untermodul `pipelines` hinzufügen
|
|
mit dem Code Ihrer Pipeline hinzufügen. Fügen Sie es dann der Liste der in `pipelines/__init__.py` definierten Aufgaben hinzu.
|
|
|
|
Dann müssen Sie noch Tests hinzufügen. Erstellen Sie eine neue Datei `tests/test_pipelines_MY_PIPELINE.py` mit Beispielen für die anderen Tests.
|
|
|
|
Die Funktion `run_pipeline_test` ist sehr allgemein gehalten und läuft auf kleinen Zufallsmodellen auf jeder möglichen
|
|
Architektur, wie durch `model_mapping` und `tf_model_mapping` definiert.
|
|
|
|
Dies ist sehr wichtig, um die zukünftige Kompatibilität zu testen, d.h. wenn jemand ein neues Modell für
|
|
`XXXForQuestionAnswering` hinzufügt, wird der Pipeline-Test versuchen, mit diesem Modell zu arbeiten. Da die Modelle zufällig sind, ist es
|
|
ist es unmöglich, die tatsächlichen Werte zu überprüfen. Deshalb gibt es eine Hilfsfunktion `ANY`, die einfach versucht, die
|
|
Ausgabe der Pipeline TYPE.
|
|
|
|
Außerdem *müssen* Sie 2 (idealerweise 4) Tests implementieren.
|
|
|
|
- `test_small_model_pt` : Definieren Sie 1 kleines Modell für diese Pipeline (es spielt keine Rolle, ob die Ergebnisse keinen Sinn ergeben)
|
|
und testen Sie die Ausgaben der Pipeline. Die Ergebnisse sollten die gleichen sein wie bei `test_small_model_tf`.
|
|
- `test_small_model_tf` : Definieren Sie 1 kleines Modell für diese Pipeline (es spielt keine Rolle, ob die Ergebnisse keinen Sinn ergeben)
|
|
und testen Sie die Ausgaben der Pipeline. Die Ergebnisse sollten die gleichen sein wie bei `test_small_model_pt`.
|
|
- `test_large_model_pt` (`optional`): Testet die Pipeline an einer echten Pipeline, bei der die Ergebnisse
|
|
Sinn machen. Diese Tests sind langsam und sollten als solche gekennzeichnet werden. Hier geht es darum, die Pipeline zu präsentieren und sicherzustellen
|
|
sicherzustellen, dass es in zukünftigen Versionen keine Abweichungen gibt.
|
|
- `test_large_model_tf` (`optional`): Testet die Pipeline an einer echten Pipeline, bei der die Ergebnisse
|
|
Sinn machen. Diese Tests sind langsam und sollten als solche gekennzeichnet werden. Hier geht es darum, die Pipeline zu präsentieren und sicherzustellen
|
|
sicherzustellen, dass es in zukünftigen Versionen keine Abweichungen gibt.
|