Normalmente quando parliamo di integrare in una Web App un modello di Intelligenza Artificiale pensiamo immediatamente a server in Python o, ancora più spesso, a API di piattaforme in cloud. In questa piccola guida vogliamo però esplorare un terreno poco battuto: la possibilità di includere modelli AI direttamente nelle Web App per usarle già oggi client-side grazie a Javascript e gli attuali Standard Web!
Ma prima di lanciarci con la fantasia e il codice, è necessario che vi introduca i protagonisti della storia.
Vi Presento i Transformers…
I “Transformers” hanno rivoluzionato il campo dell’elaborazione del linguaggio naturale (NLP) e dell’intelligenza artificiale: questa architettura, basata sul concetto di “auto-attenzione”, consente ai modelli di prestare attenzione a diverse parti di un testo contemporaneamente, catturando così le relazioni tra le parole anche se sono distanti nella sequenza.
Ma cosa rende i “Transformers” così speciali?
A differenza dei tradizionali modelli di rete neurale sequenziale, come le reti neurali ricorrenti (RNN) o le reti neurali convoluzionali (CNN), che elaborano i dati in modo sequenziale, i “Transformers” sono in grado di elaborare le sequenze in parallelo, rendendo l’elaborazione molto più efficiente e rapida.
Transformers.js – La Libreria per eseguire [quasi] tutti i Modelli disponibili su Huggingface nel Browser!
Tra i tanti nuovi tool e software legati alle IA potrebbe esserti sfuggita una soluzione che unisce i “Transformers” ed il mondo web: è la libreria Transformers.js!
Questa libreria ti permette di eseguire modelli di intelligenza artificiale disponibili nel mondo Python direttamente nel tuo browser, aprendo così nuove possibilità nel campo dello sviluppo web e non solo.
Con Transformers.js, puoi incorporare facilmente potenti modelli di NLP, come GPT e BERT ma anche task di Computer Vision come image-to-text, depth-estimation e molto altro, nelle tue applicazioni web, Node.js o Electron!
Tutto senza la necessità di dipendere da server esterni o servizi cloud poiché tutto viene eseguito all’interno del browser.
Questa caratteristica, oltre all’evidente basso costo di infrastruttura introduce un livello di privacy completo: i dati (informazioni sensibili come lo stato di salute o le immagini della webcam) possono non lasciare il dispositivo dell’utente: addirittura, dopo aver scaricato il modello, la tua web app funzionerà anche offline!
Ma come possiamo iniziare a utilizzare Transformers.js nei nostri progetti JavaScript? Continua a leggere per scoprirlo!
Ora Proviamo l’Ebbrezza di Creare una Vera Web App Basata su AI…
Ecco la parte più emozionante: mettere in pratica ciò che abbiamo discusso e creare una vera web app basata su intelligenza artificiale utilizzando Transformers.js!
In questo tutorial, ti guideremo passo dopo passo attraverso la creazione di una semplice applicazione web che utilizza un modello di generazione di testo basato su “Transformers” per tradurre dall’italiano all’inglese le frasi inserite dall’utente. Vedrai come è facile integrare Transformers.js nel tuo progetto ed iniziare ad esplorare il mondo dell’Intelligenza Artificiale in modo creativo e familiare.
Prepara il tuo editor di codice preferito ed affrontiamo questa sfida passo passo… come in una ricetta di cucina!
Ricetta pratica per Traduttore basato su Intelligenza Artificiale integrato in pagina web
A titolo di esempio svilupperemo un semplice traduttore che userà un modello IA per tradurre dall’Italiano all’Inglese con un click!
Requisiti
Non serve saperne di IA, Python o data-science, basta cavarsela con vanilla JavaScript, HTML5 e CSS: la ricetta è pensata per essere chiara ed accessibile!
Ad esempio, non useremo nessun bundler ma una semplice cartella /public
da servire con il vostro web server preferito.
Ingredienti
Per la nostra webapp avremo bisogno di tre ingredienti principali:
Una UI minimalista:
textarea
dove l’utente scriverà il testo da tradurrebutton
per avviare la traduzionetextarea
per visualizzare l’output tradotto
Un web worker (caricato come modulo): conterrà il nostro modello di IA e ci permetterà di interrogarlo senza bloccare l’interfaccia utente del main thread.
Una semplice logica applicativa: sarà responsabile di legare insieme la UI, il web worker e il processo di traduzione.
Procedura
Preparazione dell’UI
Creiamo una semplice pagina HTML con le textarea per l’input e l’output, insieme a un bottone per avviare la traduzione.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web based AI Translator</title>
<link rel="stylesheet" href="./style.css">
<script type="module" src="./app.js"></script>
</head>
<body>
<div id="container">
<form id="translation-form">
<!-- input da tradurre -->
<textarea id="italian-text" rows="6" spellcheck="false" required disabled placeholder="Italian text"></textarea>
<!-- bottone a cui attaccheremo l'evento per tradurre -->
<button id="translate-button" type="button" disabled>Loading model...</button>
<!-- output tradotto (sola lettura) -->
<textarea id="english-text" rows="6" spellcheck="false" placeholder="English text" readonly></textarea>
</form>
</div>
<!-- includiamo una progress bar per indicare quando scarichiamo il modello IA (spesso > 50Mb) -->
<div id="progress-bar"></div>
</body>
</html>
Code language: HTML, XML (xml)
Preparazione dell’esperienza Utente
Ora che abbiamo pronto il nostro index.html
è il momento di abbozzare la nostra app.js
!
Ci limiteremo a definire due funzioni per abilitare e disabilitare l’interazione con l’interfaccia utente (facendo attenzione ad aspettare che la pagina html sia caricata).
🔮 Ci servirà in seguito quando saremo in attesa di risultati di processi lunghi fatti dal Web Worker.
document.addEventListener("DOMContentLoaded", () => {
const translateButton = document.getElementById('translate-button');
const italianTextarea = document.getElementById('italian-text');
const englishTextarea = document.getElementById('english-text');
const progressBar = document.getElementById('progress-bar');
const disableUI = () => {
italianTextarea.setAttribute('disabled', true);
englishTextarea.setAttribute('disabled', true);
translateButton.setAttribute('disabled', true);
translateButton.innerText = 'Translating...'
}
const enableUI = () => {
italianTextarea.removeAttribute('disabled');
englishTextarea.removeAttribute('disabled');
translateButton.removeAttribute('disabled');
translateButton.innerText = 'Translate'
}
// ...
}
Code language: JavaScript (javascript)
Creazione di un Web Worker per scaricare ed eseguire il modello IA
Poiché il download dei file del modello IA da Huggingface può essere lungo e soprattutto sarà la CPU dell’utente ad eseguirlo, non possiamo non includere un Web Worker al nostro progetto!
🌶️ Questa è la parte più piccante della ricetta ma il concetto è molto semplice e basato su messaggi tra la App ed il Web Worker:
Quando si carica la pagina…
App: "Scarica i file del modello"
Code language: JavaScript (javascript)
…qualche secondo o minuto dopo…
Worker: "Fatto"
Code language: JavaScript (javascript)
Mentre quando premo il bottone…
App: "Traduci questo testo"
Code language: JavaScript (javascript)
…qualche secondo o minuto dopo…
Worker: "Ecco il risultato"
Code language: JavaScript (javascript)
Questo semplice meccanismo separa in due thread l’esecuzione liberando l’UI di spiecevoli blocchi!
Creiamo quindi un nuovo file JavaScript dedicato esclusivamente al web worker, e che all’interno carica il modello di IA che desideriamo utilizzare per la traduzione.
import {
pipeline,
env
} from "https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.1";
env.allowLocalModels = false; //se si usano i modelli da Huggingface questo deve essere impostato
var translator; //questa è effettivamente la nostra funzione basata su AI
var task; //ogni modello appartiene ad un task, in questo caso è "translation"
var model; //il nome del modello che stiamo usando
const progressCallback = (data) => {
self.postMessage({
status: 'downloading',
result: data
});
}
const updateCallback = (beams) => {
const decodedText = translator.tokenizer.decode(beams[0].output_token_ids, {
skip_special_tokens: true,
})
self.postMessage({
status: 'update',
result: decodedText
});
}
const resultCallback = (output) => {
self.postMessage({
status: 'result',
result: output
})
}
self.addEventListener('message', async (event) => {
const message = event.data;
if (message.action == 'download') {
task = message.task;
model = message.model;
translator = await pipeline(task, model, {
progress_callback: progressCallback
});
self.postMessage({
status: 'ready',
task: task,
model: model
});
} else if (message.action == 'translate') {
const output = await translator(message.input,
{ // ingrediente segreto
...message.generation,
callback_function: updateCallback
}
);
resultCallback(output[0].translation_text);
}
});
Code language: JavaScript (javascript)
Aggiungiamo il Web Worker alla App
Torniamo nel nostro app.js
dove:
- Caricheremo il file
worker.js
facendo attenzione a passare come opzionetype: "module"
permettendoci di caricare Translator.js come modulo ES6 👩🎤 - Definiremo due funzioni per mandare al web worker due messaggi o azioni:
download
etranslate
- Definiremo cosa fare quando le nostre azioni producono i rispettivi risultati:
ready
quando il modello e pronto eresult
quando è pronto il risultato dell’esecuzione del modello IA (testo tradotto in questo caso) - Includiamo le funzioni
enableUI()
edisableUI()
per informare l’utente quando la APP può interagire e quando no - Extra: il web worker ci informa anche del progresso nei download e per far capire che la APP sta facendo qualcosa aggiornando una sua proprietà in funzione della percentuale di download
--progress-bar-percentage
var aiWorker = new Worker('worker.js', {
type: "module" // essenziale per poter caricare moduli Javascript all'interno del Web Worker
});
// un wrapper che manda come messaggio l'input dell'utente al Web Worker
const translate = (text) => {
disableUI(); //ci metteremo un po'...
aiWorker.postMessage({
action: 'translate',
input: text
})
}
// un wrapper che manda come messaggio il nome del modello da far scaricare al Web Worker
const download = (model) => {
disableUI(); //ci metteremo un po'...
aiWorker.postMessage({
action: 'download',
task: 'translation',
model: model
});
}
// per ricevere le risposte del Web Worker useremo l'evento 'message'
aiWorker.addEventListener('message', (event) => {
// il contenuto di event.data lo decidi in worker.js
const { status, result } = event.data;
// Il Web Worker ci informa che...
if (status == 'downloading') {
// I file del modello si stanno scaricando da Huggingface
if (result.status == 'progress') {
// usiamo una variabile CSS per animare la progress bar quando veniamo informati dello stato del download
progressBar.style.setProperty("--progress-bar-percentage", result.progress + "%");
}
} else if (status == 'ready') {
// Tutto è pronto per tradurre quindi agganciamo al bottone l'azione della traduzione
translateButton.addEventListener('click', () => {
translate(italianTextarea.value);
})
// Ora possiamo attivare la UI
enableUI();
} else if (status == 'update') {
// ingrediente segreto
englishTextarea.value = result;
} else if (status == 'result') {
englishTextarea.value = result;
enableUI();
}
})
download('Xenova/opus-mt-it-en');
Code language: JavaScript (javascript)
L’ingrediente segreto
Noi commenti del Web Worker e della App ci sono due punti indicati come ingrediente segreto
… di cosa si tratta?
Quando creiamo la nostra “pipeline” possiamo aggiungere una callback che ci informa dei progressi nell’esecuzione del modello dandoci l’output parziale della risposta. Questo è un ingrediente essenziale perché altrimenti l’utente non vedrebbe l’output se non alla fine dell’esecuzione che può metterci anche minuti.
Dal punto di vista dell’esperienza utente sarà essenziale trovare modi interessanti e creativi per dare l’illusione alle persone che la nostra Intelligenza Artificiale sia viva e pensante! 🎩✨🐇
Seguendo questi passaggi, sarai in grado di creare una webapp per tradurre testo basata su intelligenza artificiale!
Modello AI Client-Side: Conclusioni
Ci sono tantissimi limiti all’utilizzo di questa tecnologia in questo momento: i file dei modelli molto pesanti e la lentezza di una semplice CPU sono sicuramente i più evidenti! Inoltre tantissimi modelli non posso proprio essere eseguiti nel contesto del browser.
Tuttavia credo ci siano degli importanti aspetti che rendono Transformers.js una grande opportunità:
- Si basa sulla tecnologia forse più ubiqua del momento, i Transformers, ed è anch’essa sviluppata all’interno di Huggingface… è proprio la cugina Javascript della libreria Python 🤗
- Puoi rendere compatibile qualsiasi modello normalmente per Python con uno script (che lo trasforma in un formato ottimizzato ONNX): questo significa avere potenzialmente tutti i modelli appena escono!
- I parametri dei modelli Python sono gli stessi in Transformers.js quindi la documentazione, appena imparati i concetti chiave, è valida anche per i programmatori Javascript
- Allarga tantissimo l’ecosistema dei modelli IA portandoli nelle PWA, nei server Node.js e addirittura nelle Applicazioni AI based per Desktop con ElectronJS… il tutto ben documentato persino con casi d’uso di React e altri framework
- Zero costi extra per piattaforme esterne ed API
- Il suo autore, Xenova, sembra avere le idee molto chiare e da questa intervista sembra assolutamente sul pezzo… e questo mi sembra un aspetto molto promettente 🌟
Ma soprattutto stiamo tutti aspettando il vero supereroe della storia: WebGPU
Ma di questo parleremo in futuri articoli già a partire dal prossimo di questa serie: Eseguiamo un vero Large Language Model (LLM) client-side nel browser!