Publicado em 13 de novembro de 2024
Discurso de ódio, assédio e abuso on-line se tornaram um problema generalizado. Os comentários tóxicos silenciam vozes importantes e afastar usuários e clientes. A detecção de toxicidade protege seus usuários e cria um ambiente on-line mais seguro.
Nesta série de dois episódios, vamos mostrar como usar a IA para detectar e reduzir a toxicidade na fonte: os teclados dos usuários.
Na parte um, discutimos os casos de uso e os benefícios dessa abordagem.
Nesta segunda parte, vamos nos aprofundar na implementação, incluindo exemplos de código e dicas de UX.
Demonstração e código
Teste nossa demonstração e investigue o código no GitHub.
Suporte ao navegador
Nossa demonstração é executada nas versões mais recentes do Safari, Chrome, Edge e Firefox.
Selecionar um modelo e uma biblioteca
Usamos a biblioteca Transformers.js do Hugging Face, que oferece ferramentas para trabalhar com modelos de aprendizado de máquina no navegador. Nosso código de demonstração é derivado deste exemplo de classificação de texto.
Escolhemos o modelo toxic-bert, um modelo pré-treinado projetado para identificar padrões de linguagem tóxicos. É uma versão compatível com a Web de unitary/toxic-bert. Para mais detalhes sobre os rótulos do modelo e a classificação de ataques de identidade, consulte a página do modelo de rosto abraço apertado.
Depois que o modelo é transferido por download, a inferência é rápida.
Por exemplo, normalmente leva menos de 500 milissegundos no Chrome em execução em um dispositivo Android de gama média que testamos (um smartphone Pixel 7 normal, não o modelo Pro de maior desempenho). Execute seus próprios comparativos que representem sua base de usuários.
Implementação
Estas são as principais etapas da nossa implementação:
Definir um limite de toxicidade
Nosso classificador de toxicidade fornece pontuações de toxicidade entre 0
e 1
. Nesse
intervalo, precisamos definir um limite para determinar o que constitui um comentário
tóxico. Um limite comumente usado é 0.9
. Isso permite que você detecte comentários
claramente tóxicos, evitando a sensibilidade excessiva, que pode levar a muitos
falsos positivos, ou seja, comentários inofensivos categorizados como tóxicos.
export const TOXICITY_THRESHOLD = 0.9
Importar os componentes
Começamos importando os componentes necessários da biblioteca
@xenova/transformers
. Também importamos constantes e valores de configuração, incluindo o
limite de toxicidade.
import { env, pipeline } from '@xenova/transformers';
// Model name: 'Xenova/toxic-bert'
// Our threshold is set to 0.9
import { TOXICITY_THRESHOLD, MODEL_NAME } from './config.js';
Carregar o modelo e se comunicar com a linha de execução principal
Carregue o modelo de detecção de toxicidade toxic-bert e use-o para preparar nosso
classificador. A versão menos complexa é
const classifier = await pipeline('text-classification', MODEL_NAME);
Criar um pipeline, como no código de exemplo, é a primeira etapa para executar tarefas de inferência.
A função do pipeline usa dois argumentos: a tarefa ('text-classification'
)
e o modelo (Xenova/toxic-bert
).
Termo-chave: em Transformers.js, um pipeline é uma API de alto nível que simplifica o processo de execução de modelos de ML. Ele processa tarefas como carregamento de modelo, tokenização e pós-processamento.
Nosso código de demonstração faz um pouco mais do que apenas preparar o modelo, porque transferimos as etapas de preparação de modelo computacionalmente caras para um worker da Web. Isso permite que a linha de execução principal continue responsiva. Saiba mais sobre como transferir tarefas caras para um worker da Web.
Nosso worker precisa se comunicar com a linha de execução principal, usando mensagens para indicar o status do modelo e os resultados da avaliação de toxicidade. Confira os códigos de mensagem que criamos e que correspondem a diferentes status do ciclo de vida de preparação e inferência do modelo.
let classifier = null;
(async function () {
// Signal to the main thread that model preparation has started
self.postMessage({ code: MESSAGE_CODE.PREPARING_MODEL, payload: null });
try {
// Prepare the model
classifier = await pipeline('text-classification', MODEL_NAME);
// Signal to the main thread that the model is ready
self.postMessage({ code: MESSAGE_CODE.MODEL_READY, payload: null });
} catch (error) {
console.error('[Worker] Error preparing model:', error);
self.postMessage({ code: MESSAGE_CODE.MODEL_ERROR, payload: null });
}
})();
Classificar a entrada do usuário
Na função classify
, usamos o classificador criado anteriormente para analisar um comentário do usuário. Retornamos a saída bruta do classificador de
toxicidade: rótulos e pontuações.
// Asynchronous function to classify user input
// output: [{ label: 'toxic', score: 0.9243140482902527 },
// ... { label: 'insult', score: 0.96187334060668945 }
// { label: 'obscene', score: 0.03452680632472038 }, ...etc]
async function classify(text) {
if (!classifier) {
throw new Error("Can't run inference, the model is not ready yet");
}
let results = await classifier(text, { topk: null });
return results;
}
Chamamos nossa função de classificação quando a linha de execução principal pede que o worker faça isso.
Na nossa demonstração, ativamos o classificador assim que o usuário para de digitar
(consulte TYPING_DELAY
). Quando isso acontece, nossa linha de execução principal envia uma mensagem para o
worker que contém a entrada do usuário para classificação.
self.onmessage = async function (message) {
// User input
const textToClassify = message.data;
if (!classifier) {
throw new Error("Can't run inference, the model is not ready yet");
}
self.postMessage({ code: MESSAGE_CODE.GENERATING_RESPONSE, payload: null });
// Inference: run the classifier
let classificationResults = null;
try {
classificationResults = await classify(textToClassify);
} catch (error) {
console.error('[Worker] Error: ', error);
self.postMessage({
code: MESSAGE_CODE.INFERENCE_ERROR,
});
return;
}
const toxicityTypes = getToxicityTypes(classificationResults);
const toxicityAssessement = {
isToxic: toxicityTypes.length > 0,
toxicityTypeList: toxicityTypes.length > 0 ? toxicityTypes.join(', ') : '',
};
console.info('[Worker] Toxicity assessed: ', toxicityAssessement);
self.postMessage({
code: MESSAGE_CODE.RESPONSE_READY,
payload: toxicityAssessement,
});
};
Processar a saída
Verificamos se as pontuações de saída do classificador excedem nosso limite. Se esse for o caso, vamos observar o rótulo em questão.
Se algum dos rótulos de toxicidade for listado, o comentário será sinalizado como potencialmente tóxico.
// input: [{ label: 'toxic', score: 0.9243140482902527 }, ...
// { label: 'insult', score: 0.96187334060668945 },
// { label: 'obscene', score: 0.03452680632472038 }, ...etc]
// output: ['toxic', 'insult']
function getToxicityTypes(results) {
const toxicityAssessment = [];
for (let element of results) {
// If a label's score > our threshold, save the label
if (element.score > TOXICITY_THRESHOLD) {
toxicityAssessment.push(element.label);
}
}
return toxicityAssessment;
}
self.onmessage = async function (message) {
// User input
const textToClassify = message.data;
if (!classifier) {
throw new Error("Can't run inference, the model is not ready yet");
}
self.postMessage({ code: MESSAGE_CODE.GENERATING_RESPONSE, payload: null });
// Inference: run the classifier
let classificationResults = null;
try {
classificationResults = await classify(textToClassify);
} catch (error) {
self.postMessage({
code: MESSAGE_CODE.INFERENCE_ERROR,
});
return;
}
const toxicityTypes = getToxicityTypes(classificationResults);
const toxicityAssessement = {
// If any toxicity label is listed, the comment is flagged as
// potentially toxic (isToxic true)
isToxic: toxicityTypes.length > 0,
toxicityTypeList: toxicityTypes.length > 0 ? toxicityTypes.join(', ') : '',
};
self.postMessage({
code: MESSAGE_CODE.RESPONSE_READY,
payload: toxicityAssessement,
});
};
Mostrar uma dica
Se isToxic
for verdadeiro, vamos mostrar uma dica para o usuário. Na nossa demonstração, não usamos
o tipo de toxicidade mais refinado, mas o disponibilizamos para a linha de execução principal, se necessário (toxicityTypeList
). Ele pode
ser útil para seu caso de uso.
Experiência do usuário
Na nossa demonstração, fizemos as seguintes escolhas:
- Sempre permitir a postagem. Nossa dica de toxicidade do lado do cliente não impede que o usuário publique. Na nossa demonstração, o usuário pode postar um comentário mesmo que o modelo não tenha sido carregado (e, portanto, não ofereça uma avaliação de toxicidade) e mesmo que o comentário seja detectado como tóxico. Conforme recomendado, você precisa ter um segundo sistema para detectar comentários tóxicos. Se fizer sentido para seu app, informe ao usuário que o comentário foi enviado ao cliente, mas foi sinalizado no servidor ou durante a inspeção humana.
- Evite falsos negativos. Quando um comentário não é classificado como tóxico, nossa demonstração não oferece feedback (por exemplo, "Bom comentário!"). Além de ser barulhento, oferecer feedback positivo pode enviar o sinal errado, porque nosso classificador perde alguns comentários tóxicos ocasionalmente, mas inevitavelmente.
Melhorias e alternativas
Limitações e aprimoramentos futuros
- Idiomas: o modelo que estamos usando oferece suporte principalmente ao inglês. Para oferecer suporte a vários idiomas, você precisa fazer ajustes. Vários modelos de toxicidade listados no Hugging Face oferecem suporte a idiomas diferentes do inglês (russo, holandês), mas não são compatíveis com o Transformers.js no momento.
- Nuance: embora o toxic-bert detecte efetivamente a toxicidade explícita, ele pode ter dificuldades com casos mais sutis ou dependentes do contexto (ironia, sarcasmo). A toxicidade pode ser altamente subjetiva e sutil. Por exemplo, talvez você queira classificar determinados termos ou até emojis como tóxicos. O ajuste fino pode ajudar a melhorar a precisão nessas áreas.
Em breve, vamos publicar um artigo sobre como ajustar um modelo de toxicidade.
Alternativas
- MediaPipe para classificação de texto. Use um modelo que seja compatível com tarefas de classificação.
Classificador de toxicidade do TensorFlow.js. Ele oferece um modelo menor e mais rápido de buscar, mas não foi otimizado há algum tempo. Portanto, a inferência pode ser um pouco mais lenta do que com o Transformers.js.
Conclusão
A detecção de toxicidade do lado do cliente é uma ferramenta poderosa para melhorar as comunidades on-line.
Ao usar modelos de IA, como o toxic-bert, que são executados no navegador com o Transformers.js, é possível implementar mecanismos de feedback em tempo real que desencorajam comportamentos tóxicos e reduzem a carga de classificação de toxicidade nos servidores.
Essa abordagem do lado do cliente já funciona em todos os navegadores. No entanto, lembre-se das limitações, principalmente em termos de custos de veiculação de modelos e tamanho de download. Aplique as práticas recomendadas de desempenho para a IA do lado do cliente e armazene o modelo em cache.
Para uma detecção abrangente de toxicidade, combine as abordagens do lado do cliente e do servidor.