Compartir a través de


Tutorial de la API de WebNN

Para obtener una introducción a WebNN, incluida la información sobre la compatibilidad del sistema operativo, el soporte técnico del modelo y mucho más, visite la Información general de WebNN.

En este tutorial, encontrará información sobre cómo usar la API de WebNN para crear un sistema de clasificación de imágenes en la web que se acelera por hardware mediante GPU en el dispositivo. Aprovecharemos el modelo MobileNetv2, que es un modelo de código abierto en Hugging Face que se utiliza para clasificar imágenes.

Si desea ver y ejecutar el código final de este tutorial, puede encontrarlo en nuestra versión preliminar para desarrolladores de WebNN en GitHub.

Nota:

La API de WebNN es una recomendación de candidatos de W3C y se encuentra en las primeras fases de una versión preliminar del desarrollador. Algunas funciones son limitadas. Tenemos una lista del soporte técnico y del estado de implementación actuales.

Requisitos y configuración:

Configuración de Windows

Asegúrese de que tiene las versiones correctas de Edge, Windows y controladores de hardware, tal y como se detalla en la sección Requisitos de WebNN.

Configuración de Edge

  1. Descargue e instale Microsoft Edge Dev.

  2. Inicie Edge Beta y vaya a about:flags en la barra de direcciones.

  3. Busque "API de WebNN", haga clic en la lista desplegable y establezca en "Habilitado".

  4. Reinicie Edge, cuando se le solicite.

Imagen de WebNN habilitada en la versión beta de Edge

Configuración del entorno para desarrolladores

  1. Descargue e instale Visual Studio Code (VSCode).

  2. Inicie VSCode.

  3. Descargue e instale la extensión Live Server para VSCode en VSCode.

  4. Seleccione File --> Open Folder y cree una carpeta en blanco en la ubicación deseada.

Paso 1: Inicializar la aplicación web

  1. Para empezar, cree una nueva página de index.html. Añada el siguiente código boilerplate a su página nueva:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Website</title>
  </head>
  <body>
    <main>
        <h1>Welcome to My Website</h1>
    </main>
  </body>
</html>
  1. Compruebe que el código reutilizable y la configuración del desarrollador funcionaron seleccionando el botón Llamada en directo en la parte inferior derecha de VSCode. Esto debe iniciar un servidor local en Edge Beta que ejecute el código reutilizable.
  2. Ahora, cree un nuevo archivo llamado main.js. Esto contendrá todo el código javascript para su aplicación.
  3. A continuación, cree una subcarpeta fuera del directorio raíz denominada images. Descargue y guarde cualquier imagen dentro de la carpeta. Para esta demostración, usaremos el nombre predeterminado de image.jpg.
  4. Descargue el modelo mobilenet desde ONNX Model Zoo. En este tutorial, usará el archivo mobilenet2-10.onnx. Guarde este modelo en la carpeta raíz de la aplicación web.
  5. Por último, descargue y guarde este archivo de clases de imagen, imagenetClasses.js. Esto proporciona 1000 clasificaciones comunes de imágenes para que el modelo las use.

Paso 2: Agregar elementos de interfaz de usuario y función primaria

  1. Dentro del cuerpo de las etiquetas html de <main> que agregó en el paso anterior, reemplace el código existente por los siguientes elementos. Estos crearán un botón y aparecerá una imagen predeterminada.
<h1>Image Classification Demo!</h1> 
<div><img src="./images/image.jpg"></div> 
<button onclick="classifyImage('./images/image.jpg')"  type="button">Click Me to Classify Image!</button> 
<h1 id="outputText"> This image displayed is ... </h1>
  1. Ahora, vamos a añadir ONNX Runtime Web a su página, que es una biblioteca de JavaScript que usará para acceder a la API de WebNN. En el cuerpo de las etiquetas HTML <head>, añada los siguientes vínculos de código fuente de Javascript.
<script src="./main.js"></script> 
<script src="imagenetClasses.js"></script>
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.18.0-dev.20240311-5479124834/dist/ort.webgpu.min.js"></script> 
  1. Abra el archivo main.js y agrege el siguiente fragmento de código.
async function classifyImage(pathToImage){ 
  var imageTensor = await getImageTensorFromPath(pathToImage); // Convert image to a tensor
  var predictions = await runModel(imageTensor); // Run inference on the tensor
  console.log(predictions); // Print predictions to console
  document.getElementById("outputText").innerHTML += predictions[0].name; // Display prediction in HTML
} 

Paso 3: Datos previos al proceso

  1. La función que acaba de agregar llama a getImageTensorFromPath, otra función que tiene que implementar. Lo agregará a continuación, así como otra función asincrónica a la que llama para recuperar la propia imagen.
  async function getImageTensorFromPath(path, width = 224, height = 224) {
    var image = await loadImagefromPath(path, width, height); // 1. load the image
    var imageTensor = imageDataToTensor(image); // 2. convert to tensor
    return imageTensor; // 3. return the tensor
  } 

  async function loadImagefromPath(path, resizedWidth, resizedHeight) {
    var imageData = await Jimp.read(path).then(imageBuffer => { // Use Jimp to load the image and resize it.
      return imageBuffer.resize(resizedWidth, resizedHeight);
    });

    return imageData.bitmap;
  }
  1. También debe agregar la función a la imageDataToTensor que se hace referencia anteriormente, que representará la imagen cargada en un formato tensor que funcionará con nuestro modelo ONNX. Esta es una función más implicada, aunque puede parecer familiar si ha trabajado con aplicaciones de clasificación de imágenes similares anteriormente. Para obtener una explicación extendida, puede ver este tutorial de ONNX.
  function imageDataToTensor(image) {
    var imageBufferData = image.data;
    let pixelCount = image.width * image.height;
    const float32Data = new Float32Array(3 * pixelCount); // Allocate enough space for red/green/blue channels.

    // Loop through the image buffer, extracting the (R, G, B) channels, rearranging from
    // packed channels to planar channels, and converting to floating point.
    for (let i = 0; i < pixelCount; i++) {
      float32Data[pixelCount * 0 + i] = imageBufferData[i * 4 + 0] / 255.0; // Red
      float32Data[pixelCount * 1 + i] = imageBufferData[i * 4 + 1] / 255.0; // Green
      float32Data[pixelCount * 2 + i] = imageBufferData[i * 4 + 2] / 255.0; // Blue
      // Skip the unused alpha channel: imageBufferData[i * 4 + 3].
    }
    let dimensions = [1, 3, image.height, image.width];
    const inputTensor = new ort.Tensor("float32", float32Data, dimensions);
    return inputTensor;
  }

Paso 4: Llamada a WebNN

  1. Ahora ha agregado todas las funciones necesarias para recuperar la imagen y representarla como tensor. Ahora, con la biblioteca web en tiempo de ejecución de ONNX que cargó anteriormente, ejecutará el modelo. Tenga en cuenta que, para usar WebNN aquí, solo tiene que especificar executionProvider = "webnn": el soporte de ONNX Runtime facilita la habilitación de WebNN.
  async function runModel(preprocessedData) { 
    // Set up environment.
    ort.env.wasm.numThreads = 1; 
    ort.env.wasm.simd = true; 
    ort.env.wasm.proxy = true; 
    ort.env.logLevel = "verbose";  
    ort.env.debug = true; 

    // Configure WebNN.
    const executionProvider = "webnn"; // Other options: webgpu 
    const modelPath = "./mobilenetv2-7.onnx" 
    const options = {
	    executionProviders: [{ name: executionProvider, deviceType: "gpu", powerPreference: "default" }],
      freeDimensionOverrides: {"batch": 1, "channels": 3, "height": 224, "width": 224}
    };
    modelSession = await ort.InferenceSession.create(modelPath, options); 

    // Create feeds with the input name from model export and the preprocessed data. 
    const feeds = {}; 
    feeds[modelSession.inputNames[0]] = preprocessedData; 
    // Run the session inference.
    const outputData = await modelSession.run(feeds); 
    // Get output results with the output name from the model export. 
    const output = outputData[modelSession.outputNames[0]]; 
    // Get the softmax of the output data. The softmax transforms values to be between 0 and 1.
    var outputSoftmax = softmax(Array.prototype.slice.call(output.data)); 
    // Get the top 5 results.
    var results = imagenetClassesTopK(outputSoftmax, 5);

    return results; 
  } 

Paso 5: Datos posteriores al proceso

  1. Por último, agregará una función softmax y, a continuación, agregará la función final para devolver la clasificación de imágenes más probable. El softmax transforma los valores entre 0 y 1, que es el formato de probabilidad necesario para esta clasificación final.

En primer lugar, agregue los siguientes archivos de código fuente para bibliotecas auxiliares Jimp y Lodash en la etiqueta principal de main.js.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jimp/0.22.12/jimp.min.js" integrity="sha512-8xrUum7qKj8xbiUrOzDEJL5uLjpSIMxVevAM5pvBroaxJnxJGFsKaohQPmlzQP8rEoAxrAujWttTnx3AMgGIww==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

Ahora, agregue estas funciones siguientes a main.js.

// The softmax transforms values to be between 0 and 1.
function softmax(resultArray) {
  // Get the largest value in the array.
  const largestNumber = Math.max(...resultArray);
  // Apply the exponential function to each result item subtracted by the largest number, using reduction to get the
  // previous result number and the current number to sum all the exponentials results.
  const sumOfExp = resultArray 
    .map(resultItem => Math.exp(resultItem - largestNumber)) 
    .reduce((prevNumber, currentNumber) => prevNumber + currentNumber);

  // Normalize the resultArray by dividing by the sum of all exponentials.
  // This normalization ensures that the sum of the components of the output vector is 1.
  return resultArray.map((resultValue, index) => {
    return Math.exp(resultValue - largestNumber) / sumOfExp
  });
}

function imagenetClassesTopK(classProbabilities, k = 5) { 
  const probs = _.isTypedArray(classProbabilities)
    ? Array.prototype.slice.call(classProbabilities)
    : classProbabilities;

  const sorted = _.reverse(
    _.sortBy(
      probs.map((prob, index) => [prob, index]),
      probIndex => probIndex[0]
    )
  );

  const topK = _.take(sorted, k).map(probIndex => {
    const iClass = imagenetClasses[probIndex[1]]
    return {
      id: iClass[0],
      index: parseInt(probIndex[1].toString(), 10),
      name: iClass[1].replace(/_/g, " "),
      probability: probIndex[0]
    }
  });
  return topK;
}
  1. Ahora ha añadido todo el scripting necesario para ejecutar la clasificación de imágenes con WebNN en la aplicación web básica. Con la extensión Live Server para VS Code, ahora puede iniciar la página web básica en la aplicación y ver los resultados de la clasificación automáticamente.