Publicado em: 13 de novembro de 2024
O discurso de ódio, o assédio e o abuso on-line se tornaram um problema generalizado na Internet. Comentários tóxicos silenciam vozes importantes e afastam 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 duas partes, vamos mostrar como usar a IA para detectar e reduzir a toxicidade na origem: os teclados dos usuários.
Na parte um, discutimos os casos de uso e os benefícios dessa abordagem.
Nesta segunda parte, vamos analisar a 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 machine learning 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óxica. É 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 do Hugging Face.
Depois que o modelo é baixado, a inferência é rápida.
Por exemplo, geralmente leva menos de 500 milissegundos no Chrome em um dispositivo Android de gama média que testamos (um smartphone Pixel 7 comum, não o modelo Pro mais eficiente). Execute seus próprios comparativos de mercado que sejam representativos da 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 entre 0 e 1. Dentro desse intervalo, precisamos definir um limite para determinar o que constitui um comentário tóxico. Um limite usado com frequência é 0.9. Assim, você detecta comentários claramente tóxicos e evita 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 nosso 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
Carregamos o modelo de detecção de toxicidade toxic-bert e o usamos para preparar nosso
classificador. A versão menos complexa é
const classifier = await pipeline('text-classification', MODEL_NAME);
Criar um pipeline, como no exemplo de código, é a primeira etapa para executar tarefas de inferência.
A função de pipeline usa dois argumentos: a tarefa ('text-classification') e o modelo (Xenova/toxic-bert).
Termo principal: no 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 descarregamos as etapas de preparação do modelo computacionalmente caras para um worker da Web. Isso permite que a linha de execução principal permaneça responsiva. Saiba mais sobre como transferir tarefas caras para um service worker.
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 mapeiam 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, acionamos 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 ao 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 for o caso, vamos anotar o rótulo em questão.
Se um 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 thre>shold, 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
// potential>ly toxic (isToxic true)
isToxic: toxicityTy>pes.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 ao usuário. Na 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). Talvez ele seja útil para seu caso de uso.
Experiência do usuário
Na nossa demonstração, fizemos as seguintes escolhas:
- Sempre permitir postagens. Nossa dica de toxicidade do lado do cliente não impede que o usuário faça postagens. 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 esteja oferecendo uma avaliação de toxicidade) e mesmo que o comentário seja detectado como tóxico. Conforme recomendado, você deve ter um segundo sistema para detectar comentários tóxicos. Se fizer sentido para seu aplicativo, informe ao usuário que o comentário foi enviado no cliente, mas depois 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 ruidoso, oferecer feedback positivo pode enviar o sinal errado, porque nosso classificador ocasionalmente, mas inevitavelmente, perde alguns comentários tóxicos.
Melhorias e alternativas
Limitações e aprimoramentos futuros
- Idiomas: o modelo que estamos usando é compatível principalmente com inglês. Para suporte multilíngue, é necessário fazer o ajuste refinado. Vários modelos de toxicidade listados no Hugging Face são compatíveis com 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 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, você pode querer que determinados termos ou até mesmo emojis sejam classificados como tóxicos. O ajuste fino pode ajudar a melhorar a precisão nessas áreas.
Vamos publicar um artigo sobre o ajuste de um modelo de toxicidade.
Alternativas
- MediaPipe para classificação de texto. Use um modelo 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 é otimizado há algum tempo. Por isso, você pode notar que a inferência é um pouco mais lenta do que com 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 é executado no navegador com 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, não se esqueça das limitações, principalmente em termos de custos de exibição do modelo e tamanho do download. Aplique as práticas recomendadas de desempenho para IA do lado do cliente e armazene o modelo em cache.
Para uma detecção abrangente de toxicidade, combine abordagens do lado do cliente e do servidor.