Ejercicios resueltos — Capítulo 28¶
Volver al índice · Capítulo original
Ejercicio 1: Análisis de sentimiento¶
Añade análisis de sentimiento a los comentarios.
Solución¶
npm install @xenova/transformers
import { pipeline } from '@xenova/transformers';
const clasificador = await pipeline(
'sentiment-analysis',
'Xenova/distilbert-base-uncased-finetuned-sst-2-english'
);
async function analizar(texto) {
const [resultado] = await clasificador(texto);
return resultado;
}
const r = await analizar('Me encanta este libro');
console.log(r);
// { label: 'POSITIVE', score: 0.9998 }
Mostrar en UI:
<textarea id="texto" placeholder="Escribe algo..."></textarea>
<p id="resultado"></p>
<script type="module">
import { pipeline } from '@xenova/transformers';
const clasificador = await pipeline('sentiment-analysis');
document.getElementById('texto').addEventListener('input', async (e) => {
const texto = e.target.value.trim();
if (texto.length < 3) return;
const [r] = await clasificador(texto);
document.getElementById('resultado').textContent =
r.label === 'POSITIVE' ? '😊 Positivo' : '😞 Negativo';
});
</script>
Ejercicio 2: Buscador semántico¶
Crea un buscador semántico con embeddings.
Solución¶
import { pipeline } from '@xenova/transformers';
const extractor = await pipeline(
'feature-extraction',
'Xenova/all-MiniLM-L6-v2'
);
async function embedding(texto) {
const salida = await extractor(texto, {
pooling: 'mean',
normalize: true,
});
return salida.data;
}
function similitud(a, b) {
return a.reduce((sum, v, i) => sum + v * b[i], 0);
}
async function buscar(consulta, documentos) {
const vecConsulta = await embedding(consulta);
const vecDocs = await Promise.all(documentos.map(embedding));
const puntuados = documentos.map((doc, i) => ({
doc,
score: similitud(vecConsulta, vecDocs[i]),
}));
return puntuados.sort((a, b) => b.score - a.score);
}
const docs = [
'Cómo usar HTML',
'Introducción a CSS',
'JavaScript moderno',
'Guía de TypeScript',
];
const resultados = await buscar('estilos web', docs);
console.log(resultados);
// [ 'Introducción a CSS', 'Cómo usar HTML', ... ]
Ejercicio 3: Dictado por voz¶
Implementa dictado por voz en un formulario.
Solución¶
<label for="texto">Dictado</label>
<textarea id="texto" rows="5"></textarea>
<button id="btn">🎤 Hablar</button>
<script>
const btn = document.getElementById('btn');
const area = document.getElementById('texto');
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SR) {
btn.disabled = true;
btn.textContent = 'No soportado';
} else {
const recognition = new SR();
recognition.lang = 'es-ES';
recognition.continuous = true;
recognition.onresult = (event) => {
let texto = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
texto += event.results[i][0].transcript;
}
area.value += texto;
};
btn.addEventListener('click', () => {
if (btn.textContent.includes('Hablar')) {
recognition.start();
btn.textContent = '🔴 Parar';
} else {
recognition.stop();
btn.textContent = '🎤 Hablar';
}
});
}
</script>
Ejercicio 4: WebLLM¶
Prueba WebLLM con un modelo pequeño.
Solución¶
npm install @mlc-ai/web-llm
<div id="progreso"></div>
<p id="respuesta"></p>
<script type="module">
import * as webllm from '@mlc-ai/web-llm';
const motor = await webllm.CreateMLCEngine(
'Llama-3.2-1B-Instruct-q4f32_1-MLC',
{
initProgressCallback: (info) => {
document.getElementById('progreso').textContent =
`Cargando: ${(info.progress * 100).toFixed(1)}%`;
},
}
);
const respuesta = await motor.chat.completions.create({
messages: [
{ role: 'system', content: 'Resume en 20 palabras.' },
{ role: 'user', content: 'HTML es un lenguaje de marcado...' },
],
});
document.getElementById('respuesta').textContent =
respuesta.choices[0].message.content;
</script>
Requiere WebGPU
Chrome/Edge 113+ o Safari 17+. Firefox va llegando.
Ejercicio 5: MediaPipe para gestos¶
Usa MediaPipe para detectar gestos.
Solución¶
npm install @mediapipe/tasks-vision
<video autoplay playsinline></video>
<script type="module">
import { HandLandmarker, FilesetResolver } from '@mediapipe/tasks-vision';
const vision = await FilesetResolver.forVisionTasks(
'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm'
);
const detector = await HandLandmarker.createFromOptions(vision, {
baseOptions: {
modelAssetPath: 'https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task',
},
numHands: 1,
});
const video = document.querySelector('video');
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
video.srcObject = stream;
function detectar() {
const resultados = detector.detectForVideo(video, performance.now());
if (resultados.landmarks.length > 0) {
const mano = resultados.landmarks[0];
// Verificar si el índice está "levantado" (posición y < posición y del nudillo)
const dedoLevantado = mano[8].y < mano[6].y;
if (dedoLevantado) console.log('👍 Dedo levantado');
}
requestAnimationFrame(detectar);
}
detectar();
</script>
Ejercicio 6: TensorFlow.js¶
Entrena un modelo simple en el navegador.
Solución¶
npm install @tensorflow/tfjs
import * as tf from '@tensorflow/tfjs';
// Datos: y = 2x + 1
const xs = tf.tensor1d([1, 2, 3, 4, 5]);
const ys = tf.tensor1d([3, 5, 7, 9, 11]);
const modelo = tf.sequential();
modelo.add(tf.layers.dense({ units: 1, inputShape: [1] }));
modelo.compile({ loss: 'meanSquaredError', optimizer: 'sgd' });
await modelo.fit(xs, ys, {
epochs: 200,
callbacks: {
onEpochEnd: (epoch, logs) => console.log(`Epoch ${epoch}: loss=${logs.loss.toFixed(4)}`),
},
});
const prediccion = modelo.predict(tf.tensor1d([6]));
prediccion.print(); // ~13
Ejercicio 7: Cachear el modelo¶
Cachea el modelo para que no se descargue cada vez.
Solución¶
import { env, pipeline } from '@xenova/transformers';
env.useBrowserCache = true;
env.allowLocalModels = false;
// El modelo se descarga la primera vez y se guarda en Cache Storage
const clasificador = await pipeline('sentiment-analysis');
Verificar: DevTools → Application → Cache Storage → transformers-cache.
Servir desde tu dominio:
env.remoteHost = 'https://libro.urukais.com/models/';