Detección de contenido inseguro para la marca a través de la visión artificial

Victoria Seoane
.
February 25, 2024
Detección de contenido inseguro para la marca a través de la visión artificial

En el ámbito de la publicidad digital, garantizar la seguridad de la marca es una preocupación primordial. Implica la tarea de identificar y proteger las ubicaciones de los anuncios dentro del contenido que se alinean con los valores y objetivos de la marca y, a veces lo que es más importante, detectar el contenido que no se alinea con el mensaje de la marca. En 2019, la Alianza Global para unos Medios Responsables (GARM) publicó el estándar de seguridad e idoneidad de las marcas, que ayuda a las marcas a comunicar sus necesidades particulares en un lenguaje común en toda la industria.

Desafío empresarial

Con este contexto en mente, queremos presentar un desafío que nos presentó uno de nuestros clientes: ¿cómo identificar el contenido inseguro para la marca en un gran corpus de vídeos de forma rentable? El análisis de vídeo tenía un coste prohibitivo, por lo que diseñamos una solución que aprovechaba el análisis de imágenes y audio. Después de todo, los vídeos son un gran conjunto de imágenes superpuestas con sonido, ¿verdad?

En este artículo, nos centraremos en el componente de visión artificial del análisis: ¿cómo encontrar, entre otras cosas, escenas de vídeo relacionadas con el crimen, las armas, el terrorismo, el alcohol, las drogas o la pornografía?

Primer paso: diseñar la solución

Lo primero que decidimos fue basar nuestra solución en una muestra de imágenes fijas de vídeo de todo el vídeo. A pesar de que esto aumentaba la probabilidad de error, el análisis de imágenes es más económico que el análisis de vídeo, por lo que podíamos muestrear un mayor número de imágenes. Con fines ilustrativos, si usáramos AWS Rekognition para la moderación de contenido mediante análisis de vídeo, el coste sería de 0,10 USD por minuto, mientras que el reconocimiento de imágenes costaría 0,0008 USD por imagen. Podemos muestrear 100 imágenes en un minuto de vídeo y, aun así, estar por debajo del coste del vídeo.

La cantidad de imágenes a muestrear será un parámetro a tener en cuenta para equilibrar la precisión del modelo y el costo total. Para muestrear estas imágenes, escribimos un CV abierto script para cargar un vídeo y muestrear un subconjunto de imágenes fijas a partir de él.

no-line-numbersimport cv2import osdef sample_images(path, file_name, directory_to_store_at=“/tmp”): “”” Capture frames from a video and save sampled images at specified intervals. Args: path (str): Path to the input video file. file_name (str): Base name for the output image files. directory_to_store_at (str, optional): Directory to save the sampled images. Defaults to “/tmp”. Returns: List[str]: List of file paths to the sampled images. “”” images = [] cam = cv2.VideoCapture(path) total_frames = int(cam.get(cv2.CAP_PROP_FRAME_COUNT)) fps = int(cam.get(cv2.CAP_PROP_FPS)) video_duration = total_frames / fps # Calculate the interval (in minutes) to sample at # according to the video duration (adjust to business logic) interval_minutes = 0.1 if video_duration < 60: interval_minutes = 0.1 else: interval_minutes = 1 frame_interval = int(fps * 60 * interval_minutes) if not os.path.exists(directory_to_store_at): os.makedirs(directory_to_store_at) # start sampling at frame 15 to avoid the initial black frames currentframe = 15 while(True): current_frame = current_frame + frame_interval cam.set(cv2.CAP_PROP_POS_FRAMES, current_frame) ret, frame = cam.read() # reading from frame if ret: # if video is still left continue creating images file_id = file_name + ‘frame’ + str(current_frame) + ‘.jpg’ name = directory_to_store_at + file_id image_string = cv2.imencode(‘.jpg’, frame)[1] with open(name, ‘wb’) as file: file.write(image_string.tobytes()) images.append(name) else: break # Release all space and windows once done cam.release() cv2.destroyAllWindows() return images

Una vez que hayamos obtenido nuestras imágenes de imágenes fijas, ¿cómo queremos clasificarlas? ¿Qué modelo debemos usar? Lo primero que pensamos fue aprovechar una idea similar a la que expusimos en nuestro anterior entrada de blog, clasificarlos en todas las diferentes categorías y tener una categoría «general» para contenido seguro. Sin embargo, esto tenía varios problemas. Para empezar, una imagen podía pertenecer a varias categorías diferentes, lo que hacía que la clasificación multiclase no fuera una solución suficiente. Además, ¿qué pasaría con las imágenes que no pertenecieran a ninguna de las categorías? La categoría «segura» podría ser enorme. La cantidad de datos que necesitaríamos para entrenar a los modelos en todas las cosas que son realmente seguras habría sido enorme. No solo eso, sino que estaríamos incurriendo en un desequilibrio de clases y en los problemas que esto puede conllevar. Por ejemplo, el modelo puede inclinarse hacia la clase mayoritaria, lo que llevaría a un desempeño deficiente en la clase minoritaria.

Para resolver el problema de las etiquetas múltiples, consideramos los modelos de etiquetas múltiples, pero el problema con la cantidad de ejemplos necesarios para un contenido «seguro» aún persistía.

Por ello, decidimos abordar el problema desde una nueva perspectiva. En lugar de crear un modelo para clasificarlos todos (lo siento, es una broma fácil), creamos un conjunto de modelos de clasificación de una clase, uno para cada una de las clases que queremos detectar. La clasificación de una clase, también conocida como detección de anomalías, es una técnica en la que el modelo aprende a identificar una clase específica de datos a partir de un conjunto de datos predominantemente normal. Los codificadores automáticos, un tipo de arquitectura de redes neuronales, son particularmente adecuados para esta tarea.

¡Echemos un vistazo a los codificadores automáticos!

Los codificadores automáticos son un tipo de red neuronal artificial que se utiliza en el aprendizaje automático no supervisado. Están diseñados principalmente para la reducción de la dimensionalidad, el aprendizaje de funciones y la compresión de datos. Los codificadores automáticos tienen aplicaciones en una variedad de dominios, incluido el procesamiento de imágenes y señales, la detección de anomalías e incluso el procesamiento del lenguaje natural.

Un autocodificador consta de dos partes principales: un codificador y un decodificador. Así es como funcionan:

Codificador: El codificador toma una entrada (que puede ser una imagen, una secuencia de datos o cualquier otra forma de datos estructurados) y la asigna a una representación de dimensiones inferiores, a menudo denominada «espacio latente» o «codificación». Este proceso implica una serie de transformaciones y capas que capturan las características o patrones esenciales de los datos de entrada. El objetivo del codificador es reducir la dimensionalidad y, al mismo tiempo, preservar la información más importante.

Espacio latente: El espacio latente es una representación comprimida de los datos de entrada y, por lo general, tiene una dimensión inferior a la de los datos originales. Constituye un obstáculo en la arquitectura del codificador automático, ya que obliga al modelo a capturar las características más importantes de forma compacta.

Decodificador: El decodificador toma la representación del espacio latente y reconstruye los datos de entrada a partir de ella. Consiste en capas que realizan la operación inversa del codificador, intentando generar una salida que se parezca mucho a la entrada original. La calidad de la reconstrucción es una medida de lo bien que el codificador automático ha aprendido a capturar las características esenciales de los datos.

Los codificadores automáticos se entrenan mediante una pérdida de reconstrucción, que mide la diferencia entre la entrada y la salida. El objetivo durante el entrenamiento es minimizar esta pérdida, enseñando de manera efectiva al codificador automático a reproducir los datos de entrada con la mayor precisión posible. En el caso de la clasificación de una clase, si la pérdida de reconstrucción es alta para una entrada en particular, esto sugiere que la entrada no se ajusta bien a los patrones aprendidos, lo que indica que la imagen no pertenece a la clase en la que se entrenó el modelo. Pongamos un ejemplo: imaginemos que entrenamos dos codificadores automáticos, uno para detectar armas y otro para detectar drogas. Luego podemos enviar una imagen arbitraria a cada codificador automático y considerar el error de reconstrucción de ambos. Si descubrimos que el codificador automático de fármacos tiene un error de reconstrucción bajo, es probable que la imagen esté relacionada con fármacos.

Obtención de los datos de entrenamiento necesarios

Es difícil encontrar conjuntos de datos de código abierto sobre muchos de estos temas, especialmente debido a su sensibilidad. Por eso era crucial crear nuestros propios conjuntos de datos. Creamos ocho conjuntos de datos diferentes: crimen, armas, escenas militares, terrorismo, contenido explícito, contenido sexy, imágenes sobre fumadores e imágenes sobre bebidas alcohólicas; cada uno de ellos tenía entre 200 y 500 imágenes. Para ello, utilizamos imágenes de uso comercial permitidas de diversas fuentes, como Flickr, Google y Wikimedia. Nuestro equipo las revisó manualmente para asegurarnos de que las imágenes originales representaban bien lo que podía tener un vídeo poco seguro para una marca. Por ejemplo, la imagen de un soldado posando en uniforme está relacionada con temas militares, pero no es lo que la norma GARM denomina contenido inseguro. Un comentario personal: etiquetar los datos es una tarea que es mejor hacer en equipo y en pequeñas dosis y, si es posible, viendo vídeos de adorables cachorros mientras están juntos.

Preparación de los datos para la formación

Colocaremos todas nuestras imágenes en una carpeta, definida aquí como carpeta de imágenes. También definiremos la altura y el ancho de la imagen, que son necesarios para los modelos con codificadores automáticos. Es importante usar un tamaño de imagen que mantenga la calidad de la imagen sin que sea demasiado grande, ya que las imágenes grandes crearán modelos más grandes, ¡y requerirán más memoria para funcionar! En nuestro caso, 256 × 256 píxeles es una forma razonable.

no-line-numbersimport osimport numpy as np# Set the path to the folder containing your imagesimage_folder = ‘./training_images_military’# Define image dimensionstarget_height = 256target_width = 256# Initialize an empty list to store the preprocessed imagespreprocessed_images = []from keras.layers import Input, Densefrom keras.models import Modelfrom keras.preprocessing.image import load_img, img_to_array# Loop through the images in the folderfor image_filename in os.listdir(image_folder): # Construct the full path to the image file image_path = os.path.join(image_folder, image_filename) # Load the image using Keras’ load_img function image = load_img(image_path, target_size=(target_height, target_width)) # Convert the image to a NumPy array and normalize pixel values image_array = img_to_array(image) / 255.0 # Append the preprocessed image to the list preprocessed_images.append(image_array)# Convert the list of preprocessed images to a NumPy arrayX_train = np.array(preprocessed_images)

Entrenamiento modelo

Ahora crearemos una función para entrenar nuestro codificador automático.

En este caso, optamos por utilizar capas densas, pero hay diferentes capas disponibles: entre otras, Conv2D, MaxPooling2D y UpSampling2D. La arquitectura elegida fue una combinación de estudio y experimentación: determinar qué arquitectura producía los mejores resultados y, al mismo tiempo, mantener la complejidad del modelo razonablemente baja. El tamaño óptimo de los lotes y las épocas eran una combinación de alta precisión y tamaño total del modelo. Queríamos buenos modelos, pero que pudieran funcionar en ordenadores con 256 GB de memoria.

no-line-numbersdef train_and_evaluate(epochs, batch_size, loss, training_data): “”” Train and evaluate an autoencoder model on the provided training data. Args: epochs (int): The number of training epochs. batch_size (int): The batch size for training. loss (str): The loss function to be used for training. training_data (numpy.ndarray): The training data to be used for autoencoder training. target_height (int): The height of the target input images. target_width (int): The width of the target input images. Returns: tensorflow.keras.models.Model: Trained autoencoder model. “”” input_dim = (target_height, target_width, 3) dim = target_height * target_width * 3 input_dim = (dim,) input_img = Input(shape=input_dim) encoded = Dense(units=256, activation=‘relu’)(input_img) encoded = Dense(units=128, activation=‘relu’)(encoded) encoded = Dense(units=64, activation=‘relu’)(encoded) decoded = Dense(units=128, activation=‘relu’)(encoded) decoded = Dense(units=256, activation=‘relu’)(decoded) decoded = Dense(units=dim, activation=‘sigmoid’)(decoded) autoencoder = Model(input_img, decoded) autoencoder.compile(optimizer=‘adam’, loss=loss) # Train the autoencoder epochs = epochs batch_size = batch_size validation_split = 0.2 history = autoencoder.fit(training_data, training_data, epochs=epochs, batch_size=batch_size, validation_split=validation_split, verbose=0) return autoencoder

Con esa función definida, entrenamos el modelo:

no-line-numbersautoencoder = train_and_evaluate(50, 16, ‘mse’, X_train)

Probando el modelo

Ahora viene la parte divertida. ¡Usaremos la siguiente función para clasificar nuestras imágenes!

no-line-numbersdef classify_image(image_path, autoencoder, threshold=0.001, target_height = 256, target_width = 256): “”” Classify an image based on its reconstruction error using an autoencoder. Args: image_path (str): Path to the image file to be classified. autoencoder (tensorflow.keras.models.Model): Trained autoencoder model used for image reconstruction. threshold (float, optional): Threshold for classification. Images with a Mean Squared Error (MSE) below this threshold are considered to belong to the class. Defaults to 0.001. target_height (int, optional): The target height for resizing the image. Defaults to 256. target_width (int, optional): The target width for resizing the image. Defaults to 256. Returns: tuple: A tuple containing: – mse (float): The Mean Squared Error (MSE) between the original and reconstructed image. – belongs (bool): True if the image belongs to the class based on the MSE, False otherwise. “”” image = load_img(image_path, target_size=(target_height, target_width)) image_array = img_to_array(image) / 255.0 # Reshape the image array to match the input shape of the autoencoder image_array = image_array.reshape(1, –1) # Use the autoencoder to predict the reconstructed image reconstructed_image = autoencoder.predict(image_array) # Calculate the Mean Squared Error (MSE) as the reconstruction error mse = np.mean(np.square(image_array – reconstructed_image)) if mse < threshold: #Image belongs to the class belongs = True else: #Image does not belong to the class belongs = False return (mse, belongs)

Y ahora, a la hora de la verdad, lo probamos con nuestros datos:

no-line-numbersfor image in os.listdir(image_folder): mse, belongs = classify_image(“training_images_crime/” + image, autoencoder, threshold)

Ajustar el umbral

Si prestas atención, verás un «umbral» mencionado en el código anterior, de forma muy casual. ¿Qué es eso? ¿Recuerdas que hace algunas secciones hablamos sobre cómo se pueden usar los codificadores automáticos para la clasificación de una clase? Le pedimos al modelo que codifique una imagen y, a continuación, mida el error de reconstrucción. En este caso, utilizaremos el error cuadrático medio (MSE), la media de los cuadrados de los errores entre la imagen real y la imagen predicha. Si el error es grande, significa que la imagen no pertenece al conjunto de imágenes que utilizamos para el entrenamiento porque el modelo no pudo recrearla con precisión. Este es el concepto que utilizaremos para clasificar nuestras imágenes. Sin embargo, ¿qué es un «gran error»? Esto es algo que tendremos que investigar: establecer un umbral que sea coherente con tus datos. Un error lo suficientemente bajo como para mantener altos los verdaderos positivos y verdaderos negativos, y los falsos positivos y falsos negativos bajos. Por supuesto, esto también dependerá del caso de uso empresarial y de la sensibilidad a los errores de tipo I y tipo II.

Esta es la distribución de los MSE de las imágenes que pertenecían a la clase:

Y esta es la distribución de los MSE para las imágenes que no pertenecían a la clase:

Como podemos ver, la elección del umbral es tanto una ciencia como un arte: elegir un umbral más bajo nos hará marcar muchas imágenes que pertenecen a la clase como no pertenecientes y correremos el riesgo de no captar contenido inseguro para la marca. Por otro lado, si elegimos un umbral más alto, marcaremos más inventario del estrictamente necesario. En este caso, un umbral de 0,05 era una buena elección, ya que capturaba la mayoría de las imágenes que pertenecían a la clase y clasificaba mal muy pocas imágenes que no pertenecían.

Un ejercicio interesante es analizar las imágenes que se clasificaron erróneamente. En este caso, dado que muchas de las imágenes del entrenamiento tenían un fondo desértico debido a las campañas militares en Oriente Medio, las imágenes del desierto se clasificaron como de contenido militar. Para mejorar esto, mejoré mis datos de entrenamiento para capturar imágenes de otros contextos, como la guerra de Vietnam.

Conclusión

La seguridad de la marca en la publicidad exige una atención meticulosa al contexto en el que se muestran los anuncios. Nuestro desafío consistía en identificar cómo detectar el posible contexto de colocación de anuncios de una manera rentable.

Podemos solucionar este problema empleando codificadores automáticos de Keras para la clasificación de fotogramas de vídeo en una sola clase. Este enfoque reduce considerablemente los requisitos de etiquetado de datos, ya que los modelos con etiquetas múltiples y clases múltiples requerirían una amplia variedad de datos «seguros para la marca».

Entrenar a un codificador automático en contenido seguro para la marca y establecer un umbral para los errores de reconstrucción nos permite identificar con precisión las ubicaciones que se ajustan a los valores de la marca.

Esta solución ofrece un potencial significativo para racionalizar los esfuerzos de seguridad de la marca en la publicidad, garantizando que los anuncios se presenten de acuerdo con la identidad y la misión de la marca.

Manténgase a la vanguardia de las últimas tendencias y conocimientos sobre big data, aprendizaje automático e inteligencia artificial. ¡No se lo pierda y suscríbase a nuestro boletín de noticias!

En el ámbito de la publicidad digital, garantizar la seguridad de la marca es una preocupación primordial. Implica la tarea de identificar y proteger las ubicaciones de los anuncios dentro del contenido que se alinean con los valores y objetivos de la marca y, a veces lo que es más importante, detectar el contenido que no se alinea con el mensaje de la marca. En 2019, la Alianza Global para unos Medios Responsables (GARM) publicó el estándar de seguridad e idoneidad de las marcas, que ayuda a las marcas a comunicar sus necesidades particulares en un lenguaje común en toda la industria.

Desafío empresarial

Con este contexto en mente, queremos presentar un desafío que nos presentó uno de nuestros clientes: ¿cómo identificar el contenido inseguro para la marca en un gran corpus de vídeos de forma rentable? El análisis de vídeo tenía un coste prohibitivo, por lo que diseñamos una solución que aprovechaba el análisis de imágenes y audio. Después de todo, los vídeos son un gran conjunto de imágenes superpuestas con sonido, ¿verdad?

En este artículo, nos centraremos en el componente de visión artificial del análisis: ¿cómo encontrar, entre otras cosas, escenas de vídeo relacionadas con el crimen, las armas, el terrorismo, el alcohol, las drogas o la pornografía?

Primer paso: diseñar la solución

Lo primero que decidimos fue basar nuestra solución en una muestra de imágenes fijas de vídeo de todo el vídeo. A pesar de que esto aumentaba la probabilidad de error, el análisis de imágenes es más económico que el análisis de vídeo, por lo que podíamos muestrear un mayor número de imágenes. Con fines ilustrativos, si usáramos AWS Rekognition para la moderación de contenido mediante análisis de vídeo, el coste sería de 0,10 USD por minuto, mientras que el reconocimiento de imágenes costaría 0,0008 USD por imagen. Podemos muestrear 100 imágenes en un minuto de vídeo y, aun así, estar por debajo del coste del vídeo.

La cantidad de imágenes a muestrear será un parámetro a tener en cuenta para equilibrar la precisión del modelo y el costo total. Para muestrear estas imágenes, escribimos un CV abierto script para cargar un vídeo y muestrear un subconjunto de imágenes fijas a partir de él.

no-line-numbersimport cv2import osdef sample_images(path, file_name, directory_to_store_at=“/tmp”): “”” Capture frames from a video and save sampled images at specified intervals. Args: path (str): Path to the input video file. file_name (str): Base name for the output image files. directory_to_store_at (str, optional): Directory to save the sampled images. Defaults to “/tmp”. Returns: List[str]: List of file paths to the sampled images. “”” images = [] cam = cv2.VideoCapture(path) total_frames = int(cam.get(cv2.CAP_PROP_FRAME_COUNT)) fps = int(cam.get(cv2.CAP_PROP_FPS)) video_duration = total_frames / fps # Calculate the interval (in minutes) to sample at # according to the video duration (adjust to business logic) interval_minutes = 0.1 if video_duration < 60: interval_minutes = 0.1 else: interval_minutes = 1 frame_interval = int(fps * 60 * interval_minutes) if not os.path.exists(directory_to_store_at): os.makedirs(directory_to_store_at) # start sampling at frame 15 to avoid the initial black frames currentframe = 15 while(True): current_frame = current_frame + frame_interval cam.set(cv2.CAP_PROP_POS_FRAMES, current_frame) ret, frame = cam.read() # reading from frame if ret: # if video is still left continue creating images file_id = file_name + ‘frame’ + str(current_frame) + ‘.jpg’ name = directory_to_store_at + file_id image_string = cv2.imencode(‘.jpg’, frame)[1] with open(name, ‘wb’) as file: file.write(image_string.tobytes()) images.append(name) else: break # Release all space and windows once done cam.release() cv2.destroyAllWindows() return images

Una vez que hayamos obtenido nuestras imágenes de imágenes fijas, ¿cómo queremos clasificarlas? ¿Qué modelo debemos usar? Lo primero que pensamos fue aprovechar una idea similar a la que expusimos en nuestro anterior entrada de blog, clasificarlos en todas las diferentes categorías y tener una categoría «general» para contenido seguro. Sin embargo, esto tenía varios problemas. Para empezar, una imagen podía pertenecer a varias categorías diferentes, lo que hacía que la clasificación multiclase no fuera una solución suficiente. Además, ¿qué pasaría con las imágenes que no pertenecieran a ninguna de las categorías? La categoría «segura» podría ser enorme. La cantidad de datos que necesitaríamos para entrenar a los modelos en todas las cosas que son realmente seguras habría sido enorme. No solo eso, sino que estaríamos incurriendo en un desequilibrio de clases y en los problemas que esto puede conllevar. Por ejemplo, el modelo puede inclinarse hacia la clase mayoritaria, lo que llevaría a un desempeño deficiente en la clase minoritaria.

Para resolver el problema de las etiquetas múltiples, consideramos los modelos de etiquetas múltiples, pero el problema con la cantidad de ejemplos necesarios para un contenido «seguro» aún persistía.

Por ello, decidimos abordar el problema desde una nueva perspectiva. En lugar de crear un modelo para clasificarlos todos (lo siento, es una broma fácil), creamos un conjunto de modelos de clasificación de una clase, uno para cada una de las clases que queremos detectar. La clasificación de una clase, también conocida como detección de anomalías, es una técnica en la que el modelo aprende a identificar una clase específica de datos a partir de un conjunto de datos predominantemente normal. Los codificadores automáticos, un tipo de arquitectura de redes neuronales, son particularmente adecuados para esta tarea.

¡Echemos un vistazo a los codificadores automáticos!

Los codificadores automáticos son un tipo de red neuronal artificial que se utiliza en el aprendizaje automático no supervisado. Están diseñados principalmente para la reducción de la dimensionalidad, el aprendizaje de funciones y la compresión de datos. Los codificadores automáticos tienen aplicaciones en una variedad de dominios, incluido el procesamiento de imágenes y señales, la detección de anomalías e incluso el procesamiento del lenguaje natural.

Un autocodificador consta de dos partes principales: un codificador y un decodificador. Así es como funcionan:

Codificador: El codificador toma una entrada (que puede ser una imagen, una secuencia de datos o cualquier otra forma de datos estructurados) y la asigna a una representación de dimensiones inferiores, a menudo denominada «espacio latente» o «codificación». Este proceso implica una serie de transformaciones y capas que capturan las características o patrones esenciales de los datos de entrada. El objetivo del codificador es reducir la dimensionalidad y, al mismo tiempo, preservar la información más importante.

Espacio latente: El espacio latente es una representación comprimida de los datos de entrada y, por lo general, tiene una dimensión inferior a la de los datos originales. Constituye un obstáculo en la arquitectura del codificador automático, ya que obliga al modelo a capturar las características más importantes de forma compacta.

Decodificador: El decodificador toma la representación del espacio latente y reconstruye los datos de entrada a partir de ella. Consiste en capas que realizan la operación inversa del codificador, intentando generar una salida que se parezca mucho a la entrada original. La calidad de la reconstrucción es una medida de lo bien que el codificador automático ha aprendido a capturar las características esenciales de los datos.

Los codificadores automáticos se entrenan mediante una pérdida de reconstrucción, que mide la diferencia entre la entrada y la salida. El objetivo durante el entrenamiento es minimizar esta pérdida, enseñando de manera efectiva al codificador automático a reproducir los datos de entrada con la mayor precisión posible. En el caso de la clasificación de una clase, si la pérdida de reconstrucción es alta para una entrada en particular, esto sugiere que la entrada no se ajusta bien a los patrones aprendidos, lo que indica que la imagen no pertenece a la clase en la que se entrenó el modelo. Pongamos un ejemplo: imaginemos que entrenamos dos codificadores automáticos, uno para detectar armas y otro para detectar drogas. Luego podemos enviar una imagen arbitraria a cada codificador automático y considerar el error de reconstrucción de ambos. Si descubrimos que el codificador automático de fármacos tiene un error de reconstrucción bajo, es probable que la imagen esté relacionada con fármacos.

Obtención de los datos de entrenamiento necesarios

Es difícil encontrar conjuntos de datos de código abierto sobre muchos de estos temas, especialmente debido a su sensibilidad. Por eso era crucial crear nuestros propios conjuntos de datos. Creamos ocho conjuntos de datos diferentes: crimen, armas, escenas militares, terrorismo, contenido explícito, contenido sexy, imágenes sobre fumadores e imágenes sobre bebidas alcohólicas; cada uno de ellos tenía entre 200 y 500 imágenes. Para ello, utilizamos imágenes de uso comercial permitidas de diversas fuentes, como Flickr, Google y Wikimedia. Nuestro equipo las revisó manualmente para asegurarnos de que las imágenes originales representaban bien lo que podía tener un vídeo poco seguro para una marca. Por ejemplo, la imagen de un soldado posando en uniforme está relacionada con temas militares, pero no es lo que la norma GARM denomina contenido inseguro. Un comentario personal: etiquetar los datos es una tarea que es mejor hacer en equipo y en pequeñas dosis y, si es posible, viendo vídeos de adorables cachorros mientras están juntos.

Preparación de los datos para la formación

Colocaremos todas nuestras imágenes en una carpeta, definida aquí como carpeta de imágenes. También definiremos la altura y el ancho de la imagen, que son necesarios para los modelos con codificadores automáticos. Es importante usar un tamaño de imagen que mantenga la calidad de la imagen sin que sea demasiado grande, ya que las imágenes grandes crearán modelos más grandes, ¡y requerirán más memoria para funcionar! En nuestro caso, 256 × 256 píxeles es una forma razonable.

no-line-numbersimport osimport numpy as np# Set the path to the folder containing your imagesimage_folder = ‘./training_images_military’# Define image dimensionstarget_height = 256target_width = 256# Initialize an empty list to store the preprocessed imagespreprocessed_images = []from keras.layers import Input, Densefrom keras.models import Modelfrom keras.preprocessing.image import load_img, img_to_array# Loop through the images in the folderfor image_filename in os.listdir(image_folder): # Construct the full path to the image file image_path = os.path.join(image_folder, image_filename) # Load the image using Keras’ load_img function image = load_img(image_path, target_size=(target_height, target_width)) # Convert the image to a NumPy array and normalize pixel values image_array = img_to_array(image) / 255.0 # Append the preprocessed image to the list preprocessed_images.append(image_array)# Convert the list of preprocessed images to a NumPy arrayX_train = np.array(preprocessed_images)

Entrenamiento modelo

Ahora crearemos una función para entrenar nuestro codificador automático.

En este caso, optamos por utilizar capas densas, pero hay diferentes capas disponibles: entre otras, Conv2D, MaxPooling2D y UpSampling2D. La arquitectura elegida fue una combinación de estudio y experimentación: determinar qué arquitectura producía los mejores resultados y, al mismo tiempo, mantener la complejidad del modelo razonablemente baja. El tamaño óptimo de los lotes y las épocas eran una combinación de alta precisión y tamaño total del modelo. Queríamos buenos modelos, pero que pudieran funcionar en ordenadores con 256 GB de memoria.

no-line-numbersdef train_and_evaluate(epochs, batch_size, loss, training_data): “”” Train and evaluate an autoencoder model on the provided training data. Args: epochs (int): The number of training epochs. batch_size (int): The batch size for training. loss (str): The loss function to be used for training. training_data (numpy.ndarray): The training data to be used for autoencoder training. target_height (int): The height of the target input images. target_width (int): The width of the target input images. Returns: tensorflow.keras.models.Model: Trained autoencoder model. “”” input_dim = (target_height, target_width, 3) dim = target_height * target_width * 3 input_dim = (dim,) input_img = Input(shape=input_dim) encoded = Dense(units=256, activation=‘relu’)(input_img) encoded = Dense(units=128, activation=‘relu’)(encoded) encoded = Dense(units=64, activation=‘relu’)(encoded) decoded = Dense(units=128, activation=‘relu’)(encoded) decoded = Dense(units=256, activation=‘relu’)(decoded) decoded = Dense(units=dim, activation=‘sigmoid’)(decoded) autoencoder = Model(input_img, decoded) autoencoder.compile(optimizer=‘adam’, loss=loss) # Train the autoencoder epochs = epochs batch_size = batch_size validation_split = 0.2 history = autoencoder.fit(training_data, training_data, epochs=epochs, batch_size=batch_size, validation_split=validation_split, verbose=0) return autoencoder

Con esa función definida, entrenamos el modelo:

no-line-numbersautoencoder = train_and_evaluate(50, 16, ‘mse’, X_train)

Probando el modelo

Ahora viene la parte divertida. ¡Usaremos la siguiente función para clasificar nuestras imágenes!

no-line-numbersdef classify_image(image_path, autoencoder, threshold=0.001, target_height = 256, target_width = 256): “”” Classify an image based on its reconstruction error using an autoencoder. Args: image_path (str): Path to the image file to be classified. autoencoder (tensorflow.keras.models.Model): Trained autoencoder model used for image reconstruction. threshold (float, optional): Threshold for classification. Images with a Mean Squared Error (MSE) below this threshold are considered to belong to the class. Defaults to 0.001. target_height (int, optional): The target height for resizing the image. Defaults to 256. target_width (int, optional): The target width for resizing the image. Defaults to 256. Returns: tuple: A tuple containing: – mse (float): The Mean Squared Error (MSE) between the original and reconstructed image. – belongs (bool): True if the image belongs to the class based on the MSE, False otherwise. “”” image = load_img(image_path, target_size=(target_height, target_width)) image_array = img_to_array(image) / 255.0 # Reshape the image array to match the input shape of the autoencoder image_array = image_array.reshape(1, –1) # Use the autoencoder to predict the reconstructed image reconstructed_image = autoencoder.predict(image_array) # Calculate the Mean Squared Error (MSE) as the reconstruction error mse = np.mean(np.square(image_array – reconstructed_image)) if mse < threshold: #Image belongs to the class belongs = True else: #Image does not belong to the class belongs = False return (mse, belongs)

Y ahora, a la hora de la verdad, lo probamos con nuestros datos:

no-line-numbersfor image in os.listdir(image_folder): mse, belongs = classify_image(“training_images_crime/” + image, autoencoder, threshold)

Ajustar el umbral

Si prestas atención, verás un «umbral» mencionado en el código anterior, de forma muy casual. ¿Qué es eso? ¿Recuerdas que hace algunas secciones hablamos sobre cómo se pueden usar los codificadores automáticos para la clasificación de una clase? Le pedimos al modelo que codifique una imagen y, a continuación, mida el error de reconstrucción. En este caso, utilizaremos el error cuadrático medio (MSE), la media de los cuadrados de los errores entre la imagen real y la imagen predicha. Si el error es grande, significa que la imagen no pertenece al conjunto de imágenes que utilizamos para el entrenamiento porque el modelo no pudo recrearla con precisión. Este es el concepto que utilizaremos para clasificar nuestras imágenes. Sin embargo, ¿qué es un «gran error»? Esto es algo que tendremos que investigar: establecer un umbral que sea coherente con tus datos. Un error lo suficientemente bajo como para mantener altos los verdaderos positivos y verdaderos negativos, y los falsos positivos y falsos negativos bajos. Por supuesto, esto también dependerá del caso de uso empresarial y de la sensibilidad a los errores de tipo I y tipo II.

Esta es la distribución de los MSE de las imágenes que pertenecían a la clase:

Y esta es la distribución de los MSE para las imágenes que no pertenecían a la clase:

Como podemos ver, la elección del umbral es tanto una ciencia como un arte: elegir un umbral más bajo nos hará marcar muchas imágenes que pertenecen a la clase como no pertenecientes y correremos el riesgo de no captar contenido inseguro para la marca. Por otro lado, si elegimos un umbral más alto, marcaremos más inventario del estrictamente necesario. En este caso, un umbral de 0,05 era una buena elección, ya que capturaba la mayoría de las imágenes que pertenecían a la clase y clasificaba mal muy pocas imágenes que no pertenecían.

Un ejercicio interesante es analizar las imágenes que se clasificaron erróneamente. En este caso, dado que muchas de las imágenes del entrenamiento tenían un fondo desértico debido a las campañas militares en Oriente Medio, las imágenes del desierto se clasificaron como de contenido militar. Para mejorar esto, mejoré mis datos de entrenamiento para capturar imágenes de otros contextos, como la guerra de Vietnam.

Conclusión

La seguridad de la marca en la publicidad exige una atención meticulosa al contexto en el que se muestran los anuncios. Nuestro desafío consistía en identificar cómo detectar el posible contexto de colocación de anuncios de una manera rentable.

Podemos solucionar este problema empleando codificadores automáticos de Keras para la clasificación de fotogramas de vídeo en una sola clase. Este enfoque reduce considerablemente los requisitos de etiquetado de datos, ya que los modelos con etiquetas múltiples y clases múltiples requerirían una amplia variedad de datos «seguros para la marca».

Entrenar a un codificador automático en contenido seguro para la marca y establecer un umbral para los errores de reconstrucción nos permite identificar con precisión las ubicaciones que se ajustan a los valores de la marca.

Esta solución ofrece un potencial significativo para racionalizar los esfuerzos de seguridad de la marca en la publicidad, garantizando que los anuncios se presenten de acuerdo con la identidad y la misión de la marca.

Manténgase a la vanguardia de las últimas tendencias y conocimientos sobre big data, aprendizaje automático e inteligencia artificial. ¡No se lo pierda y suscríbase a nuestro boletín de noticias!