發布日期: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,可簡化執行機器學習模型的程序。處理模型載入、符記化和後處理等工作。
我們的示範程式碼除了準備模型之外,還會將耗用大量運算資源的模型準備步驟卸載至網路工作站。這可讓主執行緒保持回應。進一步瞭解如何將耗用資源的任務卸載至網路工作者。
我們的 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;
}
當主執行緒要求 worker 執行時,我們會呼叫分類函式。在示範中,我們會在使用者停止輸入時觸發分類器 (請參閱 TYPING_DELAY
)。發生這種情況時,主執行緒會傳送訊息給 worker,其中包含要分類的使用者輸入內容。
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。
- Nuance:雖然 toxic-bert 能有效偵測明顯的有害內容,但在處理較為微妙或取決於情境的情況 (例如反諷) 時,可能會遇到困難。有害性可能非常主觀且難以察覺。舉例來說,您可能會將特定字詞或表情符號歸類為有害內容。微調有助於改善這些區域的準確度。
我們即將推出一篇關於微調惡意內容模型的文章。
替代方案
- MediaPipe 文字分類。請務必使用與分類工作相容的模型。
TensorFlow.js toxicity classifier。這個模型的大小較小,擷取速度也較快,但已一段時間未經過最佳化,因此您可能會發現推論速度比使用 Transformers.js 時稍慢。
結論
用戶端惡意行為偵測功能是一項強大的工具,可用於改善線上社群。
透過利用 toxic-bert 等 AI 模型,並透過 Transformer.js 在瀏覽器中執行,您就能導入即時意見回饋機制,以便禁止有害行為,並減少伺服器上的有害內容分類負載。
這個用戶端方法已可跨瀏覽器運作。不過,請注意相關限制,尤其是模型放送成本和下載大小。為用戶端 AI 採用效能最佳做法,並快取模型。
如要全面偵測有害內容,請同時採用用戶端和伺服器端方法。