naive_bayes (NAIVE_BAYES) clasifica texto con un modelo multinomial de Naive Bayes, el modelo de eventos estándar para texto: asigna una puntuación a cada clase según la frecuencia con la que aparecen en ella los n-gramas de la entrada. Se le proporciona una tabla de recuentos de n-gramas por clase, que compila una sola vez en un modelo al cargarse, y luego usa para clasificar cualquier texto que se le pase.
Es adecuada para la clasificación rápida y ligera de texto, como el análisis de sentimiento, el etiquetado de temas o spam y la detección de idioma o escritura.
Puedes consultar el diccionario con una de estas tres funciones:
naiveBayesClassifierdevuelve el id de la clase predicha.naiveBayesClassifierWithProbdevuelve la clase predicha con su probabilidad.naiveBayesClassifierWithAllProbsdevuelve todas las clases con su probabilidad.
dictGet simple también clasifica (consulta Notas). Hay otra función, naiveBayesNgrams, que no clasifica: divide el texto en n-gramas de la misma forma que lo hace el diccionario, para que puedas construir los datos de entrenamiento a partir de texto sin procesar (consulta Construir datos de entrenamiento a partir de texto sin procesar).
Inicio rápido
n = 1) en modo token para el análisis de sentimientos.
1. Cree una tabla fuente con los recuentos de n-gramas por clase:
1) y negativa (0):
layout NAIVE_BAYES:
PRIMARY KEY ngram hace que la columna ngram sea la clave, pero en un diccionario NAIVE_BAYES esa “clave” es el texto que se pasa para clasificar, no un valor almacenado que se busca (consulta Estructura del diccionario). LAYOUT configura el modelo: class_attribute 'class_id' marca class_id como la etiqueta de clase (por lo que el otro atributo, count, es el recuento de ocurrencias por clase), n 1 usa unigramas y mode 'token' divide el texto en palabras delimitadas por espacios en blanco (consulta Parámetros de layout).
4. Clasificar — naiveBayesClassifier devuelve el id de clase:
1 corresponde a la clase positiva, a partir de los datos de entrenamiento que insertamos en el paso 2.
0 se asigna a la clase negativa.
El mismo resultado con dictGet:
0 (negativa) con una probabilidad de 0.64.
naiveBayesClassifierWithAllProbs devuelve todas las clases ordenadas de la más probable a la menos probable, con probabilidades que suman 1.0; en este caso, 0.64 para la clase negativa y 0.36 para la positiva.
Cómo funciona
(n-gram, class, count). Cuando se carga el diccionario, las filas se compilan una sola vez en el modelo. Las filas duplicadas (n-gram, class) se suman y las filas con count = 0 se ignoran.
Clasificación (en tiempo de consulta). Para clasificar una cadena, el modelo:
- La divide en n-gramas según
modeyn(véase Modos de tokenización). - Calcula una puntuación para cada clase combinando la probabilidad previa de la clase con la frecuencia con la que los n-gramas de la entrada se han visto en esa clase.
- Ordena las clases por puntuación. La clase con la puntuación más alta es la predicción que devuelve
naiveBayesClassifier;naiveBayesClassifierWithProbynaiveBayesClassifierWithAllProbstambién devuelven probabilidades, ya sea para esa clase o para todas.
alpha, que se usa para el suavizado. El suavizado evita que el modelo asigne a una clase una puntuación de cero simplemente porque un n-grama no apareció en esa clase durante el entrenamiento. Un alpha más pequeño hace que el modelo dependa más de los datos de entrenamiento, por lo que una clase puede obtener una puntuación mucho más alta que las demás, pero también puede hacer que el modelo sea demasiado sensible cuando los datos de entrenamiento son escasos o desiguales. Un alpha más grande hace que los recuentos de n-gramas importen menos, de modo que las puntuaciones de las distintas clases se vuelven más parecidas. Si alpha es muy grande, la información de los n-gramas apenas importa y la puntuación depende sobre todo de la probabilidad previa de la clase (descrita a continuación).
El segundo es la probabilidad previa de la clase: lo que el modelo asume sobre la probabilidad de cada clase antes de examinar el texto. Actúa como una puntuación inicial que recibe cada clase antes de tener en cuenta cualquier n-grama, por lo que una probabilidad previa más alta hace que sea más probable que se prediga esa clase. Cómo se establece depende de priors_mode. De forma predeterminada (proportional), una clase con un recuento total de n-gramas mayor en los datos de entrenamiento parte de una puntuación más alta. Con uniform, todas las clases parten en igualdad de condiciones, así que solo deciden los n-gramas. Con explicit, tú mismo estableces el punto de partida de cada clase. Véase Modos de probabilidad previa.
Un n-grama que nunca se haya visto en ningún punto de los datos de entrenamiento se ignora: no forma parte del vocabulario del modelo, así que no beneficia ni perjudica a ninguna clase.
El algoritmo sigue el modelo multinomial Naive Bayes para la clasificación de texto; véase Manning, Raghavan & Schütze, Introduction to Information Retrieval, cap. 13 (Text Classification and Naive Bayes).
Estructura del diccionario
NAIVE_BAYES tiene una estructura fija:
- La
PRIMARY KEYes una única columnaString: el n-gram. Al consultar, esta “key” es el texto que pasas para clasificar, no una clave de búsqueda almacenada. - Junto a ella, declara exactamente dos atributos de enteros sin signo: la etiqueta de clase y el recuento de apariciones. Los id. de clase siempre usan
UInt32internamente, por lo que una etiqueta de clase debe caber enUInt32(como máximo4294967295) incluso si declaras su atributo comoUInt64. Un valor mayor se rechaza cuando se carga el diccionario, no cuando lo creas. Lo mismo se aplica a los tipos declarados: si un id. de clase o un recuento de origen no caben en el tipo de atributo declarado, la carga falla en lugar de truncarse silenciosamente. - El parámetro de layout
class_attributeindica qué atributo es la etiqueta de clase; el otro pasa a ser automáticamente el recuento. Los dos atributos pueden declararse en cualquiera de los dos órdenes.
(n-gram, clase) con cuántas veces apareció ese n-gram en esa clase. Generas esos recuentos tokenizando tu corpus y agrupando el resultado, ya sea en tu propia pipeline de entrenamiento o en ClickHouse a partir de texto etiquetado sin procesar (consulta Crear datos de entrenamiento a partir de texto sin procesar). El diccionario solo los consume.
Actualización del modelo. Como el modelo es un diccionario respaldado por una tabla, vuelve a entrenarlo actualizando la tabla y recargando:
Parámetros de layout
Puedes definir el diccionario con DDL
CREATE DICTIONARY (como en la guía de inicio rápido anterior) o en un archivo de configuración XML; consulta Layouts de diccionario para ver dónde debe ir ese archivo. El ejemplo siguiente establece todas las opciones de layout para que puedas verlas en conjunto; solo class_attribute, n y mode son obligatorios, y la tabla anterior muestra los valores predeterminados del resto. En un archivo de configuración, las probabilidades a priori se escriben como elementos prior repetidos (uno por clase, como se muestra a continuación), los tokens de relleno para byte y codepoint son números (la configuración no puede contener bytes sin procesar) y un literal token se escapa en XML cuando es necesario, por lo que <s> pasa a ser <s>.
- DDL
- Archivo de configuración
Modos de tokenización
mode decide qué es un “token” y, por tanto, qué forma tienen los n-gramas. Los n-gramas de origen deben haberse generado con el mismo mode y n.
byte— cada token es un único byte; no se presupone UTF-8. Conn = 2,'abc'produce los bigramas de bytes'ab','bc'. Adecuado para la detección de idioma o codificación en secuencias arbitrarias de bytes, y para cualquier dato en el que importe la señal por debajo del nivel de carácter. Suele combinarse conn >= 2.codepoint— cada token es un punto de código Unicode; la entrada se interpreta como UTF-8. Conn = 1,'café'produce los puntos de código'c','a','f','é'. Adecuado para la detección de sistema de escritura e idioma, y para texto corto o CJK, donde los límites entre palabras basados en espacios no son fiables. (Los n-gramas de origen deben ser UTF-8 válidos; la entrada de la consulta se decodifica de forma tolerante; consulta Notas.)token— cada token es una palabra delimitada por espacios en blanco ASCII (espacio, tabulación, salto de línea, retorno de carro, salto de página, tabulación vertical; las secuencias contiguas se reducen a un único separador). Los espacios en blanco Unicode no ASCII, comoU+00A0(espacio de no separación) oU+2003(espacio eme), no son separadores y permanecen dentro de un token. Los espacios en blanco son lo único que divide: no se pasa nada a minúsculas ni se elimina nada, así que'Hello, World!'se convierte en los tokens'Hello,'y'World!'(la coma, el!y las mayúsculas se conservan), y conn = 2forman el único bigrama'Hello, World!'. Adecuado para la clasificación a nivel de palabra en idiomas separados por espacios: sentimiento, tema, spam, idioma de una oración.
Modos de probabilidad previa
priors_mode determina cómo se establece.
-
proportional(predeterminado) — la probabilidad previa de cada clase es proporcional a su recuento total de n-grams en los datos de entrenamiento — la suma de la columnacountde esa clase, no su número de filas ni de documentos de entrenamiento — por lo que las clases que aparecen con más frecuencia parten como más probables. Elígelo cuando las proporciones de las clases en el entrenamiento (según el recuento total de n-grams) coincidan con las frecuencias que esperas en tiempo de consulta. No tienes que proporcionar nada — se deriva de los recuentos de origen. -
uniform— al principio, todas las clases tienen la misma probabilidad, así que ninguna parte con ventaja y la predicción depende por completo de los n-grams de la entrada. Elígelo cuando las clases estén equilibradas o cuando las frecuencias del entrenamiento no reflejen con qué frecuencia aparece cada clase en tiempo de consulta. No tienes que proporcionar nada. -
explicit— tú proporcionas las probabilidades previas conpriors [(0, 0.6), (1, 0.4)]: un par(clase, probabilidad)por clase, con cada probabilidad mayor que 0 y como máximo 1, y cuya suma total sea1.0. Elígelo cuando conozcas las tasas base reales y sean distintas de las del entrenamiento — por ejemplo, solo el 1 % del tráfico en producción es spam aunque el conjunto de entrenamiento estuviera equilibrado. Calcúlalas a partir de la proporción real esperada de cada clase.
Tokens de borde (relleno)
n > 1, donde puede mejorar la exactitud al permitir que el modelo use señales al principio y al final del texto.
Por qué ayuda. Con n > 1, los n-gramas en la parte central del texto tienen contexto completo a izquierda y derecha, pero el primero y el último no. Añadir tokens de borde crea n-gramas que marcan el “inicio del texto” y el “fin del texto”, de modo que el modelo puede aprender patrones asociados a la posición; por ejemplo, una palabra que es distintiva cuando aparece al principio de un mensaje, o un carácter típico al final de una palabra.
Qué debes hacer:
- Decide cada lado por separado.
start_tokenyend_tokenson independientes: configura uno, ambos o ninguno. Un valor vacío significa que ese lado no lleva relleno. - Elige valores poco frecuentes que no entren en conflicto con datos reales; por ejemplo,
0x01/0xFFparabyte,U+10FFFE/U+10FFFFparacodepoint, o<s>/</s>paratoken. - Genera los n-gramas de entrenamiento con el mismo relleno. El diccionario aplica relleno a la entrada de la consulta, pero nunca al origen, así que los tokens de borde ya deben estar incorporados en los n-gramas que cargues. La forma más sencilla de garantizar que coincidan es construir el origen con
naiveBayesNgrams, pasándole los mismosstart_tokenyend_token(además denymode) que le das al layout; emite exactamente los n-gramas con relleno que el diccionario produce en tiempo de consulta.
-
byte— un número para el valor del byte, en decimal o en hexadecimal con0x(así,'1'y'0x01'son lo mismo): -
codepoint— un número para el punto de código UTF-8, en decimal o en hexadecimal con0x(así,'1114110'y'0x10FFFE'son lo mismo): -
token— la cadena literal del token:
Generar datos de entrenamiento a partir de texto sin procesar
naiveBayesNgrams para dividirlo en n-gramas. Asígnele los mismos n, mode, start_token y end_token que a su layout, y producirá exactamente los n-gramas que espera el diccionario, de modo que los datos de entrenamiento coincidan con lo que ve el modelo en tiempo de consulta.
Dada una tabla con filas (class_id, text), genere el conjunto (ngram, class_id, count) con un GROUP BY:
training_data ahora es una fuente válida para un diccionario NAIVE_BAYES (aquí, unigramas de tokens; cambia los argumentos n y mode para que coincidan con tu layout). El diccionario tokeniza la entrada de la consulta exactamente como se le proporciona, por lo que, si el texto de entrenamiento está en minúsculas pero el texto de consulta no, sus n-grams no coincidirán y la exactitud del modelo se verá afectada.
Probabilidades previas y recuento de documentosLa probabilidad previa
proportional (la predeterminada) se pondera según el recuento total de n-grams de cada clase, no según su número de documentos. Si quieres la probabilidad previa clásica basada en la frecuencia de documentos (documents_in_class / total_documents), calcúlala a partir de la tabla docs sin procesar y pásala con priors_mode 'explicit':training_data, pasa la probabilidad previa explícita calculada arriba y clasifica reseñas nuevas:
1 es positiva y 0 es negativa, por lo que ambas reseñas se han clasificado correctamente.
Más ejemplos
n = 2, mode 'byte'; class 0 = cadenas de letras a–d, class 1 = letras x–z):
n = 1, mode 'codepoint'; clase 0 = latín, 1 = cirílico):
store_source:
n = 2, mode 'codepoint'; clase 0 = inglés, 1 = español). Los n-grams de entrenamiento se construyen a partir de palabras sin procesar con naiveBayesNgrams, y los tokens de límite — que se pasan tanto a la función como al layout — permiten que el modelo use la primera y la última letra de cada palabra:
Notas
- Semántica de diccionario computacional. Este es un diccionario computacional:
dictGet(dict, '<class_attribute>', text)clasificatext(la clave es una entrada que se clasifica, no una clave almacenada), el atributo de recuento no se puede consultar ydictHassiempre devuelve1. - Validación de la fuente al cargar. Cada n-grama de la fuente debe coincidir con los valores configurados de
nymode(en el modocodepoint, además, debe ser UTF-8 válido); cualquier discrepancia hace que la carga falle. Como las filas con recuento cero se ignoran (consulta Cómo funciona), una fuente vacía o que solo tenga recuentos cero no tiene datos con los que entrenar y la carga falla. - La tokenización en tiempo de consulta es flexible. A diferencia de la validación de la fuente, la entrada de la consulta nunca se rechaza. En el modo
codepoint, los bytes que no son UTF-8 válidos se decodifican en la medida de lo posible en lugar de hacer fallar la consulta; en el modotoken, solo los espacios en blanco ASCII separan palabras (los espacios en blanco Unicode, comoU+00A0, permanecen dentro de un token). La entrada malformada se sigue clasificando, normalmente a partir de las probabilidades previas, ya que sus n-gramas no coincidirán con los usados en el entrenamiento.