發布日期:2024 年 11 月 13 日
仇恨言論、騷擾和網路濫用行為已成為普遍的線上問題。 惡意留言會扼殺重要意見,並趕走使用者和顧客。毒性偵測功能可保護使用者,並打造更安全的線上環境。
在這個分為兩部分的系列文章中,我們將探討如何運用 AI 技術,從源頭 (也就是使用者的鍵盤) 偵測並減少有害內容。
在第一部分中,我們討論了這種做法的用途和優點。
在第二部分中,我們將深入探討實作方式,包括程式碼範例和使用者體驗提示。
示範和程式碼
試用試用版,並研究 GitHub 上的程式碼。
瀏覽器支援
我們的試用版可在最新版的 Safari、Chrome、Edge 和 Firefox 中執行。
選取模型和程式庫
我們使用 Hugging Face 的 Transformers.js 程式庫,該程式庫提供在瀏覽器中使用機器學習模型的工具。我們的示範程式碼衍生自這個文字分類範例。
我們選擇 toxic-bert 模型,這是一種預先訓練模型,可識別惡意語言模式。這是 unitary/toxic-bert 的網頁相容版本。如要進一步瞭解模型的標籤和身分攻擊分類,請參閱 Hugging Face 模型頁面。
模型下載完成後,推論速度會很快。
舉例來說,在我們測試過的中階 Android 裝置 (一般 Pixel 7 手機,而非效能較高的 Pro 機型) 上執行的 Chrome,通常會在 500 毫秒內完成。執行能代表使用者族群的基準。
導入作業
以下是我們導入時採取的關鍵步驟:
設定毒性門檻
我們的毒性分類器會提供 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 中,管道是高階 API,可簡化執行機器學習模型的程序。可處理模型載入、權杖化和後續處理等工作。
我們的示範程式碼不只準備模型,還會將運算成本高昂的模型準備步驟卸載至 Web 工作站。這樣主執行緒就能保持回應。進一步瞭解如何將耗用大量資源的工作卸載至 Web Worker。
我們的 Worker 需要與主執行緒通訊,並使用訊息指出模型狀態和毒性評估結果。請參閱我們建立的訊息代碼,這些代碼會對應到模型準備和推論生命週期的不同狀態。
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,
});
};
處理輸出內容
我們會檢查分類器的輸出分數是否超過門檻。如果是,我們會記下有問題的標籤。
如果列出任何惡意標籤,系統就會將留言標示為可能含有惡意內容。
// 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),以備不時之需。您可能會發現這對您的用途很有幫助。
使用者體驗
在我們的示範中,我們選擇了下列項目:
- 一律允許發布貼文。我們的用戶端有害內容提示不會阻止使用者發布內容,在我們的示範中,即使模型尚未載入 (因此無法提供有害程度評估),使用者仍可發布留言,即使留言遭系統判定為有害也一樣。如建議所述,您應使用第二個系統偵測惡意留言。如果您的應用程式適合這麼做,請考慮在用戶端通知使用者留言已通過審查,但隨後在伺服器或人工檢查期間遭到檢舉。
- 請注意誤判。如果留言未歸類為有害,我們的試用版不會提供意見回饋 (例如「好留言!」)。除了會造成干擾,提供正面意見回饋也可能傳送錯誤信號,因為分類器偶爾但不可避免地會漏掉一些有害留言。
強化和替代方案
功能上現有的不足與未來補強
- 語言:我們使用的模型主要支援英文。如要支援多種語言,請進行微調。Hugging Face 上列出的多個毒性模型支援非英文語言 (俄文、荷蘭文),但目前與 Transformers.js 不相容。
- 細微差異:雖然 toxic-bert 能有效偵測明顯的有害內容,但可能難以處理較細微或與情境相關的案例 (反諷、挖苦)。有害內容可能非常主觀且難以察覺。舉例來說,您可能希望將特定字詞或表情符號歸類為有害內容。微調有助於提升這些領域的準確度。
我們即將發布文章,說明如何微調毒性模型。
替代方案
- 用於文字分類的 MediaPipe。請務必使用與分類工作相容的模型。
TensorFlow.js toxicity 分類器。 這個模型較小,擷取速度也較快,但已有一段時間未經過最佳化,因此您可能會發現推論速度比 Transformers.js 慢一些。
結論
用戶端惡意指數偵測是一項強大工具,可提升線上社群的品質。
您可以運用 Transformers.js 在瀏覽器中執行的惡意指數 BERT 等 AI 模型,實作即時意見回饋機制,遏止惡意行為,並減輕伺服器的惡意指數分類負擔。
這個用戶端方法已適用於各個瀏覽器。但請注意限制,尤其是模型服務成本和下載大小。採用用戶端 AI 的效能最佳做法,並快取模型。
如要全面偵測有害內容,請結合用戶端和伺服器端方法。