# Dietro la pipeline

{#if fw === 'pt'}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/it/chapter2/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/it/chapter2/section2_pt.ipynb"},
]} />

{:else}

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/it/chapter2/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/it/chapter2/section2_tf.ipynb"},
]} />

{/if}

<Tip>
   Questa è la prima sezione in cui il contenuto è leggermente diverso a seconda che si utilizzi PyTorch o TensorFlow. Attivate lo switch sopra il titolo per selezionare la tua piattaforma preferita!
</Tip>

{#if fw === 'pt'}
<Youtube id="1pedAIvTWXk"/>
{:else}
<Youtube id="wVN12smEvqg"/>
{/if}

Cominciamo con un esempio completo, dando un'occhiata a ciò che è successo dietro le quinte quando abbiamo eseguito il seguente codice nel [Capitolo 1](/course/chapter1):

```python
from transformers import pipeline

classifier = pipeline("sentiment-analysis")
classifier(
    [
        "I've been waiting for a HuggingFace course my whole life.",
        "I hate this so much!",
    ]
)
```

e ottenuto:

```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
```

Come abbiamo visto nel [Capitolo 1](/course/chapter1), questa pipeline raggruppa tre fasi: la pre-elaborazione, il passaggio degli input attraverso il modello e la post-elaborazione:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline.svg" alt="La pipeline NLP completa: tokenizzazione del testo, conversione in ID e inferenza attraverso il modello Transformer ed il modello head."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline-dark.svg" alt="La pipeline NLP completa: tokenizzazione del testo, conversione in ID e inferenza attraverso il modello Transformer ed il modello head.."/>
</div>

Esaminiamo rapidamente ciascuno di essi.

## Preelaborazione con un tokenizer

Come altre reti neurali, i modelli Transformer non possono elaborare direttamente il testo non elaborato, quindi la prima fase della nostra pipeline consiste nel convertire gli input testuali in numeri che il modello possa interpretare. Per fare ciò, utilizziamo un *tokenizer*, che sarà responsabile di:

- Suddivisione dell'input in parole, sottoparole o simboli (come la punteggiatura) che vengono chiamati *token*.
- Mappare ogni token in un numero intero
- Aggiunta di ulteriori input che possono essere utili per il modello

Tutta questa preelaborazione deve essere fatta esattamente nello stesso modo in cui è stato preaddestrato il modello, quindi dobbiamo prima scaricare queste informazioni dal [Model Hub](https://huggingface.co/models). Per farlo, si usa la classe `AutoTokenizer` e il suo metodo `from_pretrained()`. Utilizzando il nome del checkpoint del nostro modello, recupererà automaticamente i dati associati al tokenizer del modello e li metterà in cache (in modo che vengano scaricati solo la prima volta che si esegue il codice sottostante).

Poiché il checkpoint predefinito della pipeline `sentiment-analysis` è `distilbert-base-uncased-finetuned-sst-2-english` (si può vedere la sua scheda modello [qui](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), eseguiamo quanto segue:

```python
from transformers import AutoTokenizer

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
```

Una volta che abbiamo il tokenizer, possiamo passargli direttamente le nostre frasi e otterremo un dizionario pronto per il nostro modello! L'unica cosa che resta da fare è convertire l'elenco degli ID in ingresso in tensori.

È possibile utilizzare i 🤗 Transformer senza doversi preoccupare di quale framework ML venga utilizzato come backend;potrebbe essere PyTorch o TensorFlow, o Flax per alcuni modelli. Tuttavia, i modelli Transformer accettano solo *tensors* come input. Se è la prima volta che sentite parlare di tensori, potete pensare a loro come array NumPy. Un array NumPy può essere uno scalare (0D), un vettore (1D), una matrice (2D) o avere più dimensioni. Si tratta effettivamente di un tensore; i tensori di altri framework ML si comportano in modo simile e di solito sono semplici da istanziare come gli array NumPy.

Per specificare il tipo di tensori che vogliamo ottenere (PyTorch, TensorFlow o NumPy), usiamo l'argomento `return_tensors`:

{#if fw === 'pt'}
```python
raw_inputs = [
    "I've been waiting for a HuggingFace course my whole life.",
    "I hate this so much!",
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt")
print(inputs)
```
{:else}
```python
raw_inputs = [
    "I've been waiting for a HuggingFace course my whole life.",
    "I hate this so much!",
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf")
print(inputs)
```
{/if}

Non preoccupatevi ancora di padding e truncation; li spiegheremo più avanti.Le cose principali da ricordare sono che si può passare una frase o un elenco di frasi, oltre a specificare il tipo di tensori che si desidera ottenere (se non viene passato alcun tipo, si otterrà una lista di liste come risultato).

{#if fw === 'pt'}

Ecco come appaiono i risultati come tensori PyTorch:

```python out
{
    'input_ids': tensor([
        [  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172, 2607,  2026,  2878,  2166,  1012,   102],
        [  101,  1045,  5223,  2023,  2061,  2172,   999,   102,     0,     0,     0,     0,     0,     0,     0,     0]
    ]), 
    'attention_mask': tensor([
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    ])
}
```
{:else}

Ecco come appaiono i risultati come tensori TensorFlow:

```python out
{
    'input_ids': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,  2607,  2026,  2878,  2166,  1012,   102],
            [  101,  1045,  5223,  2023,  2061,  2172,   999,   102,     0,     0,     0,     0,     0,     0,     0,     0]
        ], dtype=int32)>, 
    'attention_mask': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        ], dtype=int32)>
}
```
{/if}

L'output stesso è un dizionario contenente due chiavi, `input_ids` e `attention_mask`. `input_ids` contiene due righe di interi (uno per ogni frase) che sono gli identificatori unici dei token in ogni frase. Spiegheremo cosa sia la `attention_mask` più avanti in questo capitolo.

## Passare attraverso il modello

{#if fw === 'pt'}
Possiamo scaricare il nostro modello preaddestrato nello stesso modo in cui abbiamo fatto con il nostro tokenizer. 🤗 Transformers fornisce una classe `AutoModel` che ha anche un metodo `from_pretrained()`:

```python
from transformers import AutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
```
{:else}
Possiamo scaricare il nostro modello preaddestrato nello stesso modo in cui abbiamo fatto con il nostro tokenizer.  🤗 Transformers fornisce una classe `TFAutoModel` che ha anche un metodo `from_pretrained`:

```python
from transformers import TFAutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModel.from_pretrained(checkpoint)
```
{/if}

In questo frammento di codice, abbiamo scaricato lo stesso checkpoint usato in precedenza nella nostra pipeline (in realtà dovrebbe essere già nella cache) e abbiamo istanziato un modello con esso.

Questa architettura contiene solo il modulo Transformer di base: dati alcuni input, produce quelli che chiameremo *hidden states*, noti anche come *features*. Per ogni input del modello, recupereremo un vettore ad alta dimensionalità che rappresenta la **comprensione contestuale di quell'input da parte del modello Transformer**.

Se per te tutto questo non ha senso, non preoccuparti. Ti spiegheremo tutto più avanti.

Anche se questi stati nascosti possono essere utili da soli, di solito sono input di un'altra parte del modello, nota come *head*. Nel [Capitolo 1](/course/chapter1), i diversi compiti potrebbero essere eseguiti con la stessa architettura, ma a ciascuno di essi sarà associata una head diversa.

### Un vettore ad alta dimensionalità?

Il vettore emesso dal modulo Transformer è solitamente di grandi dimensioni. In genere ha tre dimensioni:

- **Dimensione del batch**: Il numero di sequenze elaborate alla volta (2 nel nostro esempio).
- **Lunghezza della sequenza**: La lunghezza della rappresentazione numerica della sequenza (16 nel nostro esempio).
- **Dimensione nascosta**: La dimensione del vettore di ciascun ingresso del modello.

Si dice che è "ad alta dimensionalità" a causa dell'ultimo valore. La dimensione nascosta può essere molto grande (768 è comune per i modelli più piccoli, mentre nei modelli più grandi può arrivare a 3072 o più).

Lo possiamo vedere se alimentiamo il nostro modello con gli input che abbiamo preelaborato:

{#if fw === 'pt'}
```python
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
```

```python out
torch.Size([2, 16, 768])
```
{:else}
```py
outputs = model(inputs)
print(outputs.last_hidden_state.shape)
```

```python out
(2, 16, 768)
```
{/if}

Si noti che gli output dei modelli 🤗 Transformers si comportano come `namedtuple` o dizionari. Si può accedere agli elementi per attributi (come abbiamo fatto noi) sia per chiave (`outputs["last_hidden_state"]`), sia per indice se si sa esattamente dove si trova ciò che si sta cercando (`outputs[0]`).

### Model heads: Dare un senso ai numeri

Le model head prendono in input il vettore ad alta dimensione degli stati nascosti e lo proiettano su una dimensione diversa. Di solito sono composte da uno o pochi strati lineari:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head.svg" alt="Una rete di Transformer accanto alla sua head."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head-dark.svg" alt="Una rete di Transformer accanto alla sua head."/>
</div>

Gli output del modello Transformer vengono inviati direttamente alla model head per essere elaborati.

In questo diagramma, il modello è rappresentato dallo strato embeddings e dagli strati successivi. Il livello embeddings converte ogni ID dell'input tokenizzato in un vettore che rappresenta il token associato. I livelli successivi manipolano questi vettori utilizzando il meccanismo di attenzione per produrre la rappresentazione finale delle frasi.

Esistono diverse architetture disponibili nei 🤗 Transformers, ognuna delle quali è stata progettata per affrontare un compito specifico. Ecco un elenco non esaustivo:

- `*Model` (retrieve the hidden states)
- `*ForCausalLM`
- `*ForMaskedLM`
- `*ForMultipleChoice`
- `*ForQuestionAnswering`
- `*ForSequenceClassification`
- `*ForTokenClassification`
- e altre 🤗

{#if fw === 'pt'}
Per il nostro esempio, avremo bisogno di un modello con una classificazion head della sequenza (per poter classificare le frasi come positive o negative). Quindi, non useremo la classe `AutoModel`, ma `AutoModelForSequenceClassification`:

```python
from transformers import AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(**inputs)
```
{:else}
Per il nostro esempio, avremo bisogno di un modello con una classificazion head della sequenza (per poter classificare le frasi come positive o negative). Quindi, non useremo la classe `TFAutoModel`, ma `TFAutoModelForSequenceClassification`:

```python
from transformers import TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(inputs)
```
{/if}

Ora, se osserviamo la forma dei nostri output, la dimensionalità sarà molto più bassa: la model head prende in input i vettori ad alta dimensionalità che abbiamo visto prima e produce vettori contenenti due valori (uno per etichetta):

```python
print(outputs.logits.shape)
```

{#if fw === 'pt'}
```python out
torch.Size([2, 2])
```
{:else}
```python out
(2, 2)
```
{/if}

Dato che abbiamo solo due frasi e due etichette, il risultato che otteniamo dal nostro modello è di forma 2 x 2.

## Postprocessing the output

I valori che otteniamo come output dal nostro modello non hanno necessariamente senso da soli. Diamo un'occhiata:

```python
print(outputs.logits)
```

{#if fw === 'pt'}
```python out
tensor([[-1.5607,  1.6123],
        [ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)
```
{:else}
```python out
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
    array([[-1.5606991,  1.6122842],
           [ 4.169231 , -3.3464472]], dtype=float32)>
```
{/if}

Il nostro modello ha previsto `[-1.5607, 1.6123]` per la prima frase e `[ 4.1692, -3.3464]` per la seconda. Non si tratta di probabilità ma di *logit*, i punteggi non normalizzati emessi dall'ultimo livello del modello. Per poterli convertire in probabilità, devono passare attraverso un layer [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (tutti i modelli 🤗 Transformers producono i logits, poiché la funzione di perdita per l'addestramento generalmente fonde l'ultima funzione di attivazione, come SoftMax, con la funzione di perdita effettiva, come la cross entropy):

{#if fw === 'pt'}
```py
import torch

predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(predictions)
```
{:else}
```py
import tensorflow as tf

predictions = tf.math.softmax(outputs.logits, axis=-1)
print(predictions)
```
{/if}

{#if fw === 'pt'}
```python out
tensor([[4.0195e-02, 9.5980e-01],
        [9.9946e-01, 5.4418e-04]], grad_fn=<SoftmaxBackward>)
```
{:else}
```python out
tf.Tensor(
[[4.01951671e-02 9.59804833e-01]
 [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32)
```
{/if}

Ora possiamo vedere che il modello ha previsto `[0,0402, 0,9598]` per la prima frase e `[0,9995, 0,0005]` per la seconda. Si tratta di punteggi di probabilità riconoscibili.

Per ottenere le etichette corrispondenti a ogni posizione, si può ispezionare l'attributo `id2label` della configurazione del modello (si veda la prossima sezione):

```python
model.config.id2label
```

```python out
{0: 'NEGATIVE', 1: 'POSITIVE'}
```

Ora possiamo concludere che il modello ha previsto quanto segue:
 
- Prima frase: NEGATIVE: 0.0402, POSITIVE: 0.9598
- Seconda frase: NEGATIVE: 0.9995, POSITIVE: 0.0005

Abbiamo riprodotto con successo le tre fasi della pipeline: preelaborazione con i tokenizer, passaggio degli input attraverso il modello e postelaborazione! Ora prendiamoci un po' di tempo per approfondire ognuna di queste fasi.

<Tip>
✏️ **Provaci anche tu!** Scegli due (o più) testi di tua proprietà e lanciali all'interno della pipeline `sentiment-analysis`. Successivamente, replica i passi che hai visto qui e verifica di aver ottenuto gli stessi risultati!
</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/it/chapter2/2.mdx" />