Unisciti alla community di SIG TFX-Addons e aiutaci a rendere TFX ancora migliore! Iscriviti a SIG TFX-Addons

Migliore ingegneria ML con metadati ML

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza la fonte su GitHub Scarica taccuino

Ipotizziamo uno scenario in cui imposti una pipeline ML di produzione per classificare i pinguini. La pipeline acquisisce i dati di addestramento, addestra e valuta un modello e lo invia alla produzione.

Tuttavia, quando in seguito provi a utilizzare questo modello con un set di dati più grande che contiene diversi tipi di pinguini, osservi che il tuo modello non si comporta come previsto e inizia a classificare la specie in modo errato.

A questo punto ti interessa sapere:

  • Qual è il modo più efficiente per eseguire il debug del modello quando l'unico artefatto disponibile è il modello in produzione?
  • Quale set di dati di addestramento è stato utilizzato per addestrare il modello?
  • Quale percorso di formazione ha portato a questo modello errato?
  • Dove sono i risultati della valutazione del modello?
  • Da dove iniziare il debug?

ML metadati (MLMD) è una libreria che sfrutta i metadati associati con i modelli ML per aiutarvi a rispondere a queste domande e altro ancora. Un'analogia utile è pensare a questi metadati come l'equivalente dell'accesso allo sviluppo del software. MLMD ti consente di tenere traccia in modo affidabile degli artefatti e del lignaggio associati ai vari componenti della tua pipeline ML.

In questo tutorial, imposti una pipeline TFX per creare un modello che classifica i pinguini in tre specie in base alla massa corporea e alla lunghezza e profondità dei loro culmen e alla lunghezza delle loro pinne. Si utilizza quindi MLMD per tenere traccia della discendenza dei componenti della pipeline.

Oleodotti TFX a Colab

Colab è un ambiente di sviluppo leggero che differisce significativamente da un ambiente di produzione. In produzione, potresti avere vari componenti della pipeline come l'acquisizione di dati, la trasformazione, l'addestramento del modello, le cronologie di esecuzione e così via su più sistemi distribuiti. Per questo tutorial, dovresti essere consapevole che esistono differenze significative nell'orchestrazione e nell'archiviazione dei metadati: tutto è gestito localmente all'interno di Colab. Scopri di più su TFX in Colab qui .

Impostare

Innanzitutto, installiamo e importiamo i pacchetti necessari, impostiamo percorsi e scarichiamo i dati.

Aggiorna Pip

Per evitare di aggiornare Pip in un sistema durante l'esecuzione in locale, assicurati che sia in esecuzione in Colab. I sistemi locali possono ovviamente essere aggiornati separatamente.

try:
  import colab
  !pip install --upgrade pip
except:
  pass

Installa e importa TFX

pip install -q -U tfx

Importa pacchetti

Hai riavviato il runtime?

Se stai utilizzando Google Colab, la prima volta che esegui la cella in alto, devi riavviare il runtime facendo clic sul pulsante "RIAVVIA RUNTIME" sopra o utilizzando il menu "Runtime > Riavvia runtime ...". Ciò è dovuto al modo in cui Colab carica i pacchetti.

import os
import tempfile
import urllib
import pandas as pd

import tensorflow_model_analysis as tfma
from tfx.orchestration.experimental.interactive.interactive_context import InteractiveContext
2021-07-24 09:25:58.004364: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudart.so.11.0

Controlla le versioni TFX e MLMD.

from tfx import v1 as tfx
print('TFX version: {}'.format(tfx.__version__))
import ml_metadata as mlmd
print('MLMD version: {}'.format(mlmd.__version__))
TFX version: 1.0.0
MLMD version: 1.0.0

Scarica il set di dati

In questo CoLab, usiamo il set di dati Palmer Penguins che può essere trovato su Github . Abbiamo elaborato il set di dati, lasciando fuori tutti i record incompleti, e gocce island e sex colonne, e convertito etichette per int32 . Il set di dati contiene 334 registrazioni della massa corporea e la lunghezza e la profondità dei culmen dei pinguini e la lunghezza delle loro pinne. Utilizzi questi dati per classificare i pinguini in una delle tre specie.

DATA_PATH = 'https://raw.githubusercontent.com/tensorflow/tfx/master/tfx/examples/penguin/data/labelled/penguins_processed.csv'
_data_root = tempfile.mkdtemp(prefix='tfx-data')
_data_filepath = os.path.join(_data_root, "penguins_processed.csv")
urllib.request.urlretrieve(DATA_PATH, _data_filepath)
('/tmp/tfx-dataduqrz_en/penguins_processed.csv',
 <http.client.HTTPMessage at 0x7fba1ebc0e10>)

Crea un contesto interattivo

Per eseguire i componenti TFX in modo interattivo in questo notebook, creare un InteractiveContext . InteractiveContext utilizza una directory temporanea con un'istanza di database MLMD effimero. Si noti che le chiamate verso InteractiveContext sono gabbie di fuori dell'ambiente Colab.

In generale, si tratta di una buona pratica di gruppo simile corre conduttura sotto un Context .

interactive_context = InteractiveContext()
WARNING:absl:InteractiveContext pipeline_root argument not provided: using temporary directory /tmp/tfx-interactive-2021-07-24T09_26_00.721922-9h873pwa as root for pipeline outputs.
WARNING:absl:InteractiveContext metadata_connection_config not provided: using SQLite ML Metadata database at /tmp/tfx-interactive-2021-07-24T09_26_00.721922-9h873pwa/metadata.sqlite.

Costruisci la pipeline TFX

Una pipeline TFX è costituita da diversi componenti che eseguono diversi aspetti del flusso di lavoro ML. In questo notebook, è possibile creare ed eseguire le ExampleGen , StatisticsGen , SchemaGen e Trainer componenti e utilizzare Evaluator e Pusher componente per valutare e spingere il modello addestrato.

Fare riferimento alla componenti esercitazione per ulteriori informazioni sui componenti della pipeline TFX.

Crea un'istanza ed esegui il componente ExampleGen

example_gen = tfx.components.CsvExampleGen(input_base=_data_root)
interactive_context.run(example_gen)
WARNING:apache_beam.runners.interactive.interactive_environment:Dependencies required for Interactive Beam PCollection visualization are not available, please use: `pip install apache-beam[interactive]` to install necessary dependencies to enable all data visualization features.
WARNING:root:Make sure that locally built Python SDK docker image has Python 3.7 interpreter.
WARNING:apache_beam.io.tfrecordio:Couldn't find python-snappy so the implementation of _TFRecordUtil._masked_crc32c is not as fast as it could be.

Crea un'istanza ed esegui il componente StatisticsGen

statistics_gen = tfx.components.StatisticsGen(
    examples=example_gen.outputs['examples'])
interactive_context.run(statistics_gen)
WARNING:root:Make sure that locally built Python SDK docker image has Python 3.7 interpreter.

Istanzia ed esegui il componente SchemaGen

infer_schema = tfx.components.SchemaGen(
    statistics=statistics_gen.outputs['statistics'], infer_feature_shape=True)
interactive_context.run(infer_schema)
2021-07-24 09:26:04.200547: W ml_metadata/metadata_store/rdbms_metadata_access_object.cc:623] No property is defined for the Type

Crea un'istanza ed esegui il componente Trainer

# Define the module file for the Trainer component
trainer_module_file = 'penguin_trainer.py'
%%writefile {trainer_module_file}

# Define the training algorithm for the Trainer module file
import os
from typing import List, Text

import tensorflow as tf
from tensorflow import keras

from tfx import v1 as tfx
from tfx_bsl.public import tfxio

from tensorflow_metadata.proto.v0 import schema_pb2

# Features used for classification - culmen length and depth, flipper length,
# body mass, and species.

_LABEL_KEY = 'species'

_FEATURE_KEYS = [
    'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'body_mass_g'
]


def _input_fn(file_pattern: List[Text],
              data_accessor: tfx.components.DataAccessor,
              schema: schema_pb2.Schema, batch_size: int) -> tf.data.Dataset:
  return data_accessor.tf_dataset_factory(
      file_pattern,
      tfxio.TensorFlowDatasetOptions(
          batch_size=batch_size, label_key=_LABEL_KEY), schema).repeat()


def _build_keras_model():
  inputs = [keras.layers.Input(shape=(1,), name=f) for f in _FEATURE_KEYS]
  d = keras.layers.concatenate(inputs)
  d = keras.layers.Dense(8, activation='relu')(d)
  d = keras.layers.Dense(8, activation='relu')(d)
  outputs = keras.layers.Dense(3)(d)
  model = keras.Model(inputs=inputs, outputs=outputs)
  model.compile(
      optimizer=keras.optimizers.Adam(1e-2),
      loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
      metrics=[keras.metrics.SparseCategoricalAccuracy()])
  return model


def run_fn(fn_args: tfx.components.FnArgs):
  schema = schema_pb2.Schema()
  tfx.utils.parse_pbtxt_file(fn_args.schema_path, schema)
  train_dataset = _input_fn(
      fn_args.train_files, fn_args.data_accessor, schema, batch_size=10)
  eval_dataset = _input_fn(
      fn_args.eval_files, fn_args.data_accessor, schema, batch_size=10)
  model = _build_keras_model()
  model.fit(
      train_dataset,
      epochs=int(fn_args.train_steps / 20),
      steps_per_epoch=20,
      validation_data=eval_dataset,
      validation_steps=fn_args.eval_steps)
  model.save(fn_args.serving_model_dir, save_format='tf')
Writing penguin_trainer.py

Eseguire il Trainer componente.

trainer = tfx.components.Trainer(
    module_file=os.path.abspath(trainer_module_file),
    examples=example_gen.outputs['examples'],
    schema=infer_schema.outputs['schema'],
    train_args=tfx.proto.TrainArgs(num_steps=100),
    eval_args=tfx.proto.EvalArgs(num_steps=50))
interactive_context.run(trainer)
running bdist_wheel
running build
running build_py
creating build
creating build/lib
copying penguin_trainer.py -> build/lib
installing to /tmp/tmpox9a4duz
running install
running install_lib
copying build/lib/penguin_trainer.py -> /tmp/tmpox9a4duz
running install_egg_info
running egg_info
creating tfx_user_code_Trainer.egg-info
writing tfx_user_code_Trainer.egg-info/PKG-INFO
writing dependency_links to tfx_user_code_Trainer.egg-info/dependency_links.txt
writing top-level names to tfx_user_code_Trainer.egg-info/top_level.txt
writing manifest file 'tfx_user_code_Trainer.egg-info/SOURCES.txt'
reading manifest file 'tfx_user_code_Trainer.egg-info/SOURCES.txt'
writing manifest file 'tfx_user_code_Trainer.egg-info/SOURCES.txt'
Copying tfx_user_code_Trainer.egg-info to /tmp/tmpox9a4duz/tfx_user_code_Trainer-0.0+fef7c4ed90dc336ca26daee59d65660cf8da5fa988b2ca0c89df2f558fda10f4-py3.7.egg-info
running install_scripts
creating /tmp/tmpox9a4duz/tfx_user_code_Trainer-0.0+fef7c4ed90dc336ca26daee59d65660cf8da5fa988b2ca0c89df2f558fda10f4.dist-info/WHEEL
creating '/tmp/tmpg1jzunkf/tfx_user_code_Trainer-0.0+fef7c4ed90dc336ca26daee59d65660cf8da5fa988b2ca0c89df2f558fda10f4-py3-none-any.whl' and adding '/tmp/tmpox9a4duz' to it
adding 'penguin_trainer.py'
adding 'tfx_user_code_Trainer-0.0+fef7c4ed90dc336ca26daee59d65660cf8da5fa988b2ca0c89df2f558fda10f4.dist-info/METADATA'
adding 'tfx_user_code_Trainer-0.0+fef7c4ed90dc336ca26daee59d65660cf8da5fa988b2ca0c89df2f558fda10f4.dist-info/WHEEL'
adding 'tfx_user_code_Trainer-0.0+fef7c4ed90dc336ca26daee59d65660cf8da5fa988b2ca0c89df2f558fda10f4.dist-info/top_level.txt'
adding 'tfx_user_code_Trainer-0.0+fef7c4ed90dc336ca26daee59d65660cf8da5fa988b2ca0c89df2f558fda10f4.dist-info/RECORD'
removing /tmp/tmpox9a4duz
2021-07-24 09:26:04.716013: W ml_metadata/metadata_store/rdbms_metadata_access_object.cc:623] No property is defined for the Type
2021-07-24 09:26:04.719884: W ml_metadata/metadata_store/rdbms_metadata_access_object.cc:623] No property is defined for the Type
Processing /tmp/tfx-interactive-2021-07-24T09_26_00.721922-9h873pwa/_wheels/tfx_user_code_Trainer-0.0+fef7c4ed90dc336ca26daee59d65660cf8da5fa988b2ca0c89df2f558fda10f4-py3-none-any.whl
Installing collected packages: tfx-user-code-Trainer
Successfully installed tfx-user-code-Trainer-0.0+fef7c4ed90dc336ca26daee59d65660cf8da5fa988b2ca0c89df2f558fda10f4
2021-07-24 09:26:06.256595: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcuda.so.1
2021-07-24 09:26:06.260327: E tensorflow/stream_executor/cuda/cuda_driver.cc:328] failed call to cuInit: CUDA_ERROR_SYSTEM_DRIVER_MISMATCH: system has unsupported display driver / cuda driver combination
2021-07-24 09:26:06.260361: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:169] retrieving CUDA diagnostic information for host: kokoro-gcp-ubuntu-prod-559609198
2021-07-24 09:26:06.260368: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:176] hostname: kokoro-gcp-ubuntu-prod-559609198
2021-07-24 09:26:06.260480: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:200] libcuda reported version is: 470.57.2
2021-07-24 09:26:06.260502: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:204] kernel reported version is: 465.27.0
2021-07-24 09:26:06.260508: E tensorflow/stream_executor/cuda/cuda_diagnostics.cc:313] kernel version 465.27.0 does not match DSO version 470.57.2 -- cannot find working devices in this configuration
2021-07-24 09:26:06.260728: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
Epoch 1/5
 1/20 [>.............................] - ETA: 6s - loss: 1.1316 - sparse_categorical_accuracy: 0.0000e+00
2021-07-24 09:26:06.711432: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:176] None of the MLIR Optimization Passes are enabled (registered 2)
2021-07-24 09:26:06.713005: I tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 2000179999 Hz
20/20 [==============================] - 1s 10ms/step - loss: 1.0774 - sparse_categorical_accuracy: 0.4050 - val_loss: 1.0396 - val_sparse_categorical_accuracy: 0.4700
Epoch 2/5
20/20 [==============================] - 0s 5ms/step - loss: 0.9500 - sparse_categorical_accuracy: 0.5350 - val_loss: 0.8672 - val_sparse_categorical_accuracy: 0.7800
Epoch 3/5
20/20 [==============================] - 0s 4ms/step - loss: 0.7380 - sparse_categorical_accuracy: 0.7700 - val_loss: 0.5768 - val_sparse_categorical_accuracy: 0.7800
Epoch 4/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4604 - sparse_categorical_accuracy: 0.8100 - val_loss: 0.4236 - val_sparse_categorical_accuracy: 0.7800
Epoch 5/5
20/20 [==============================] - 0s 4ms/step - loss: 0.3520 - sparse_categorical_accuracy: 0.8150 - val_loss: 0.3400 - val_sparse_categorical_accuracy: 0.7800
2021-07-24 09:26:07.824456: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
INFO:tensorflow:Assets written to: /tmp/tfx-interactive-2021-07-24T09_26_00.721922-9h873pwa/Trainer/model/4/Format-Serving/assets
INFO:tensorflow:Assets written to: /tmp/tfx-interactive-2021-07-24T09_26_00.721922-9h873pwa/Trainer/model/4/Format-Serving/assets

Valuta e spingi il modello

Utilizzare il Evaluator componente per valutare e 'benedire' il modello prima di utilizzare il Pusher componente di spingere il modello in una directory di servire.

_serving_model_dir = os.path.join(tempfile.mkdtemp(),
                                  'serving_model/penguins_classification')
eval_config = tfma.EvalConfig(
    model_specs=[
        tfma.ModelSpec(label_key='species', signature_name='serving_default')
    ],
    metrics_specs=[
        tfma.MetricsSpec(metrics=[
            tfma.MetricConfig(
                class_name='SparseCategoricalAccuracy',
                threshold=tfma.MetricThreshold(
                    value_threshold=tfma.GenericValueThreshold(
                        lower_bound={'value': 0.6})))
        ])
    ],
    slicing_specs=[tfma.SlicingSpec()])
evaluator = tfx.components.Evaluator(
    examples=example_gen.outputs['examples'],
    model=trainer.outputs['model'],
    schema=infer_schema.outputs['schema'],
    eval_config=eval_config)
interactive_context.run(evaluator)
2021-07-24 09:26:08.397533: W ml_metadata/metadata_store/rdbms_metadata_access_object.cc:623] No property is defined for the Type
2021-07-24 09:26:08.400911: W ml_metadata/metadata_store/rdbms_metadata_access_object.cc:623] No property is defined for the Type
WARNING:root:Make sure that locally built Python SDK docker image has Python 3.7 interpreter.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_model_analysis/writers/metrics_plots_and_validations_writer.py:113: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_model_analysis/writers/metrics_plots_and_validations_writer.py:113: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`
pusher = tfx.components.Pusher(
    model=trainer.outputs['model'],
    model_blessing=evaluator.outputs['blessing'],
    push_destination=tfx.proto.PushDestination(
        filesystem=tfx.proto.PushDestination.Filesystem(
            base_directory=_serving_model_dir)))
interactive_context.run(pusher)
2021-07-24 09:26:12.698547: W ml_metadata/metadata_store/rdbms_metadata_access_object.cc:623] No property is defined for the Type

L'esecuzione della pipeline TFX popola il database MLMD. Nella sezione successiva, utilizzerai l'API MLMD per interrogare questo database per informazioni sui metadati.

Interroga il database MLMD

Il database MLMD memorizza tre tipi di metadati:

  • Metadati sulla pipeline e informazioni di derivazione associate ai componenti della pipeline
  • Metadati sugli artefatti generati durante l'esecuzione della pipeline
  • Metadati sulle esecuzioni della pipeline

Una tipica pipeline dell'ambiente di produzione serve più modelli man mano che arrivano nuovi dati. Quando si riscontrano risultati errati nei modelli serviti, è possibile interrogare il database MLMD per isolare i modelli errati. Puoi quindi tracciare la discendenza dei componenti della pipeline che corrispondono a questi modelli per eseguire il debug dei tuoi modelli

Impostare dei metadati (MD) con InteractiveContext definito in precedenza per interrogare il database MLMD.

connection_config = interactive_context.metadata_connection_config
store = mlmd.MetadataStore(connection_config)

# All TFX artifacts are stored in the base directory
base_dir = connection_config.sqlite.filename_uri.split('metadata.sqlite')[0]

Creare alcune funzioni di supporto per visualizzare i dati dall'archivio MD.

def display_types(types):
  # Helper function to render dataframes for the artifact and execution types
  table = {'id': [], 'name': []}
  for a_type in types:
    table['id'].append(a_type.id)
    table['name'].append(a_type.name)
  return pd.DataFrame(data=table)
def display_artifacts(store, artifacts):
  # Helper function to render dataframes for the input artifacts
  table = {'artifact id': [], 'type': [], 'uri': []}
  for a in artifacts:
    table['artifact id'].append(a.id)
    artifact_type = store.get_artifact_types_by_id([a.type_id])[0]
    table['type'].append(artifact_type.name)
    table['uri'].append(a.uri.replace(base_dir, './'))
  return pd.DataFrame(data=table)
def display_properties(store, node):
  # Helper function to render dataframes for artifact and execution properties
  table = {'property': [], 'value': []}
  for k, v in node.properties.items():
    table['property'].append(k)
    table['value'].append(
        v.string_value if v.HasField('string_value') else v.int_value)
  for k, v in node.custom_properties.items():
    table['property'].append(k)
    table['value'].append(
        v.string_value if v.HasField('string_value') else v.int_value)
  return pd.DataFrame(data=table)

In primo luogo, interrogare il negozio MD per un elenco di tutti i suoi memorizzati ArtifactTypes .

display_types(store.get_artifact_types())

Avanti, interrogare tutti PushedModel artefatti.

pushed_models = store.get_artifacts_by_type("PushedModel")
display_artifacts(store, pushed_models)

Interroga l'archivio MD per l'ultimo modello inviato. Questo tutorial ha un solo modello spinto.

pushed_model = pushed_models[-1]
display_properties(store, pushed_model)

Uno dei primi passaggi nel debug di un modello inviato consiste nell'esaminare quale modello sottoposto a training viene inviato e quali dati di training vengono utilizzati per eseguire il training di tale modello.

MLMD fornisce API di attraversamento per esaminare il grafico della provenienza, che è possibile utilizzare per analizzare la provenienza del modello.

def get_one_hop_parent_artifacts(store, artifacts):
  # Get a list of artifacts within a 1-hop of the artifacts of interest
  artifact_ids = [artifact.id for artifact in artifacts]
  executions_ids = set(
      event.execution_id
      for event in store.get_events_by_artifact_ids(artifact_ids)
      if event.type == mlmd.proto.Event.OUTPUT)
  artifacts_ids = set(
      event.artifact_id
      for event in store.get_events_by_execution_ids(executions_ids)
      if event.type == mlmd.proto.Event.INPUT)
  return [artifact for artifact in store.get_artifacts_by_id(artifacts_ids)]

Interroga gli artefatti padre per il modello spinto.

parent_artifacts = get_one_hop_parent_artifacts(store, [pushed_model])
display_artifacts(store, parent_artifacts)

Interrogare le proprietà per il modello.

exported_model = parent_artifacts[0]
display_properties(store, exported_model)

Interroga gli artefatti upstream per il modello.

model_parents = get_one_hop_parent_artifacts(store, [exported_model])
display_artifacts(store, model_parents)

Ottieni i dati di addestramento con cui il modello è stato addestrato.

used_data = model_parents[0]
display_properties(store, used_data)

Ora che hai i dati di training con cui il modello è stato addestrato, interroga nuovamente il database per trovare il passaggio di training (esecuzione). Interrogare l'archivio MD per un elenco dei tipi di esecuzione registrati.

display_types(store.get_execution_types())

Il passo è la formazione ExecutionType nome tfx.components.trainer.component.Trainer . Attraversa il negozio MD per ottenere la corsa del trainer che corrisponde al modello spinto.

def find_producer_execution(store, artifact):
  executions_ids = set(
      event.execution_id
      for event in store.get_events_by_artifact_ids([artifact.id])
      if event.type == mlmd.proto.Event.OUTPUT)
  return store.get_executions_by_id(executions_ids)[0]

trainer = find_producer_execution(store, exported_model)
display_properties(store, trainer)

Riepilogo

In questo tutorial, hai appreso come sfruttare MLMD per tracciare la discendenza dei componenti della tua pipeline TFX e risolvere i problemi.

Per ulteriori informazioni su come utilizzare MLMD, consulta queste risorse aggiuntive: