게시일: 2024년 11월 13일
증오심 표현, 괴롭힘, 온라인 악용은 온라인에서 만연한 문제입니다. 악성 댓글은 중요한 목소리를 묵살하고 사용자와 고객을 떠나게 만듭니다. 악성 콘텐츠 감지는 사용자를 보호하고 더 안전한 온라인 환경을 조성합니다.
이 2부 시리즈에서는 AI를 사용하여 사용자의 키보드에서 발생하는 악성성을 감지하고 완화하는 방법을 살펴봅니다.
1단계에서는 이 접근 방식의 사용 사례와 이점에 대해 논의했습니다.
이 두 번째 부분에서는 코드 예시와 UX 도움말을 비롯한 구현을 자세히 살펴봅니다.
데모 및 코드
데모를 사용해 보고 GitHub의 코드를 살펴보세요.
브라우저 지원
데모는 최신 버전의 Safari, Chrome, Edge, Firefox에서 실행됩니다.
모델 및 라이브러리 선택
브라우저에서 머신러닝 모델을 사용하는 도구를 제공하는 Hugging Face의 Transformers.js 라이브러리를 사용합니다. 데모 코드는 이 텍스트 분류 예에서 파생되었습니다.
악의적인 언어 패턴을 식별하도록 설계된 사전 학습된 모델인 toxic-bert 모델을 선택합니다. unitary/toxic-bert의 웹 호환 버전입니다. 모델 라벨 및 ID 공격 분류에 관한 자세한 내용은 Hugging Face 모델 페이지를 참고하세요.
모델이 다운로드되면 추론이 빠릅니다.
예를 들어 테스트한 중급 Android 기기 (성능이 더 우수한 Pro 모델이 아닌 일반 Pixel 7 휴대전화)에서 실행되는 Chrome에서는 일반적으로 500밀리초 미만이 소요됩니다. 사용자층을 대표하는 자체 벤치마크를 실행합니다.
구현
다음은 구현의 주요 단계입니다.
독성 기준점 설정
YouTube의 악의성 분류기는 0
과 1
사이의 악의성 점수를 제공합니다. 이 범위 내에서 유해한 댓글을 구성하는 요소를 결정하기 위한 기준을 설정해야 합니다. 일반적으로 사용되는 임곗값은 0.9
입니다. 이렇게 하면 노골적으로 악의적인 댓글을 포착하면서도 과도한 민감성으로 인해 거짓양성이 너무 많이 발생하지 않도록 할 수 있습니다 (즉, 악의적인 것으로 분류된 무해한 댓글).
export const TOXICITY_THRESHOLD = 0.9
구성요소 가져오기
먼저 @xenova/transformers
라이브러리에서 필요한 구성요소를 가져옵니다. 또한 독성 기준점을 비롯한 상수와 구성 값을 가져옵니다.
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';
모델 로드 및 기본 스레드와 통신
독성 감지 모델 toxic-bert를 로드하고 이를 사용하여 분류기를 준비합니다. 가장 간단한 버전은 다음과 같습니다.
const classifier = await pipeline('text-classification', MODEL_NAME);
예시 코드와 같이 파이프라인을 만드는 것이 추론 작업을 실행하는 첫 번째 단계입니다.
파이프라인 함수는 작업 ('text-classification'
)과 모델 (Xenova/toxic-bert
)이라는 두 가지 인수를 사용합니다.
핵심 용어: Transformers.js에서 파이프라인은 ML 모델을 실행하는 프로세스를 간소화하는 상위 수준 API입니다. 모델 로드, 토큰화, 후처리와 같은 작업을 처리합니다.
컴퓨팅 비용이 많이 드는 모델 준비 단계를 웹 작업자에게 오프로드하므로 이 데모 코드는 모델을 준비하는 것 이상을 수행합니다. 이렇게 하면 기본 스레드가 계속 응답할 수 있습니다. 비용이 많이 드는 작업을 웹 워커로 오프로드하는 방법을 자세히 알아보세요.
작업자는 메시지를 사용하여 모델 상태와 독성 평가 결과를 나타내면서 기본 스레드와 통신해야 합니다. 모델 준비 및 추론 수명 주기의 다양한 상태에 매핑되는 메시지 코드를 살펴보세요.
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 });
}
})();
사용자 입력 분류
classify
함수에서는 이전에 만든 분류기를 사용하여 사용자 댓글을 분석합니다. 독성 분류기의 원시 출력인 라벨과 점수를 반환합니다.
// 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;
}
기본 스레드에서 작업자에게 분류를 요청하면 분류 함수를 호출합니다. 이 데모에서는 사용자가 입력을 중지하는 즉시 분류기를 트리거합니다(TYPING_DELAY
참고). 이 경우 기본 스레드는 분류할 사용자 입력이 포함된 메시지를 작업자에게 전송합니다.
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,
});
};
출력 처리
분류 기준의 출력 점수가 기준점을 초과하는지 확인합니다. 이 경우 Google에서는 문제의 라벨을 기록합니다.
악의성 라벨이 표시되면 댓글이 악의적인 것으로 신고될 수 있습니다.
// 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,
});
};
힌트 표시
isToxic
이 true인 경우 사용자에게 힌트를 표시합니다. 이 데모에서는 더 세분화된 독성 유형을 사용하지 않지만 필요한 경우 기본 스레드에서 사용할 수 있도록 했습니다 (toxicityTypeList
). 사용 사례에 유용할 수 있습니다.
사용자 환경
이 데모에서는 다음과 같이 선택했습니다.
- 항상 게시 허용 Google의 클라이언트 측 유해성 힌트는 사용자가 게시하는 것을 막지 않습니다. 이 데모에서는 모델이 로드되지 않았거나 (따라서 악성 댓글 평가를 제공하지 않음) 댓글이 악성으로 감지되더라도 사용자가 댓글을 게시할 수 있습니다. 권장사항에 따라 악성 댓글을 감지하는 두 번째 시스템을 마련해야 합니다. 애플리케이션에 적합한 경우 사용자에게 댓글이 클라이언트에서 처리되었지만 서버에서 또는 사람의 검토 중에 신고되었다고 알리는 것이 좋습니다.
- 거짓음성에 주의하세요. 댓글이 악의적인 것으로 분류되지 않으면 데모에서는 의견을 제공하지 않습니다 (예: '좋은 댓글입니다'). 노이즈가 많을 뿐만 아니라 긍정적인 의견을 제공하면 잘못된 신호를 보낼 수 있습니다. 분류기가 가끔씩 악의적인 댓글을 놓치는 경우가 있기 때문입니다.
개선사항 및 대안
제한사항 및 업데이트 예정
- 언어: 사용 중인 모델은 주로 영어를 지원합니다. 다국어 지원을 위해서는 미세 조정이 필요합니다. Hugging Face에 나열된 여러 독성 모델은 현재 Transformers.js와 호환되지 않지만 러시아어, 네덜란드어와 같은 비영어 언어를 지원합니다.
- Nuance: toxic-bert는 노골적인 악의성을 효과적으로 감지하지만, 더 미묘하거나 맥락에 따라 달라지는 케이스 (비꼬음, 냉소)에는 어려움을 겪을 수 있습니다. 악의성은 매우 주관적이고 미묘할 수 있습니다. 예를 들어 특정 용어 또는 이모티콘을 유해한 것으로 분류할 수 있습니다. 미세 조정을 통해 이러한 영역의 정확성을 개선할 수 있습니다.
악의적인 표현 모델 미세 조정에 관한 도움말이 곧 제공될 예정입니다.
대안
- 텍스트 분류를 위한 MediaPipe 분류 태스크와 호환되는 모델을 사용해야 합니다.
TensorFlow.js 독성 분류기 가져오기가 더 빠르고 더 작은 모델을 제공하지만, 한동안 최적화되지 않았으므로 Transformers.js보다 추론 속도가 다소 느릴 수 있습니다.
결론
클라이언트 측 악성 콘텐츠 감지는 온라인 커뮤니티를 개선하는 데 유용한 도구입니다.
Transformers.js와 함께 브라우저에서 실행되는 toxic-bert와 같은 AI 모델을 활용하면 유해한 행동을 억제하고 서버의 유해성 분류 부하를 줄이는 실시간 피드백 메커니즘을 구현할 수 있습니다.
이 클라이언트 측 접근 방식은 이미 여러 브라우저에서 작동합니다. 그러나 특히 모델 제공 비용 및 다운로드 크기와 관련된 제한사항에 유의하세요. 클라이언트 측 AI에 성능 권장사항을 적용하고 모델을 캐시합니다.
포괄적인 악성 콘텐츠 감지를 위해 클라이언트 측 접근 방식과 서버 측 접근 방식을 결합하세요.