Was ist Mean Pooling über die Tokens?

In meinem vorigen Blog Post bin ich über die Dimension des Vektors gestolpert.  Statt 768 wie aus Ollama generiert waren es plötzlich 3072. Da vierfache, wie mir nach einigen Überlegen aufgefallen ist. Also nocheinmal ein Blick in das ONNX Modell per Netron

Screenshot 2025-08-01 133324

Da stehts, 768. Der Fehler sitzt vor dem Computer.

Wenn man mit KI-Modellen wie BERT oder anderen Transformer-Modellen in .NET arbeitet – zum Beispiel über ein ONNX-Modell mit Microsoft.ML.OnnxRuntime – bekommt man beim Einlesen eines Satzes oft nicht einfach einen Vektor zurück, sondern eine sogenannte "Token-Ausgabe".

Ein typisches Beispiel:
Du gibst den Satz „Netzwerk / Internet Wien“ ein und bekommst vom Modell einen Tensor mit der Form:

last_hidden_state, Shape: 1 4 768

Was bedeutet das?

1 steht für die Batchgröße (wir geben nur einen Satz ein)

4 ist die Anzahl der Tokens im Satz (z. B. ["Netzwerk", "/", "Internet", "Wien"])

768 ist die Vektorgröße pro Token (dies ist fest im Modell so definiert)

Das heißt: Für jeden Token im Satz bekommst du einen Vektor mit 768 Werten. Insgesamt erhältst du also 4 solcher Vektoren.

Mein Fehler im Code

var output = results.First().AsTensor<float>().ToArray();

Ergebnis nicht 768 Werte, sondern etwa 3072 oder mehr.

Die Erklärung ist einfach: .ToArray() nimmt den gesamten Inhalt des Tensors – also alle Token-Vektoren zusammen. Bei 4 Tokens mit je 768 Werten ergibt das 4 * 768 = 3072 Werte.

Aber eigentlich willst du nicht alle Tokens einzeln analysieren, sondern einen einzigen Vektor für den ganzen Satz. Genau dafür gibt es "Mean Pooling".

Allerdings gibt es auch noch andere Varianten. Die Lösung steht in einer weiteren json.

"word_embedding_dimension": 768,

"pooling_mode_cls_token": false,

"pooling_mode_mean_tokens": true,

"pooling_mode_max_tokens": false,

"pooling_mode_mean_sqrt_len_tokens": false,

"pooling_mode_weightedmean_tokens": false,

"pooling_mode_lasttoken": false

Man berechnet den Durchschnitt über alle Token-Vektoren. Für jede der 768 Positionen wird der Mittelwert über die Tokens gebildet. So entsteht ein einziger Vektor mit 768 Werten, der den gesamten Satz beschreibt.

   1:  var tensor = results.First(r => r.Name == "last_hidden_state").AsTensor<float>();
   2:  var dims = tensor.Dimensions.ToArray();
   3:  int tokenCount = dims[1]; // z. B. 4
   4:  int embeddingSize = dims[2]; // 768
   5:   
   6:  float[] meanVector = new float[embeddingSize];
   7:   
   8:  for (int i = 0; i < tokenCount; i++)
   9:  {
  10:  for (int j = 0; j < embeddingSize; j++)
  11:  {
  12:  meanVector[j] += tensor[0, i, j];
  13:  }
  14:  }
  15:   
  16:  for (int j = 0; j < embeddingSize; j++)
  17:  {
  18:  meanVector[j] /= tokenCount;
  19:  }

Nun kann ich auf meiner bisherigen Vektordatenbank aufsetzen und semantisch ähnliche Titel suchen.

Kommentare sind geschlossen