likes
comments
collection
share

AI时代,敏感词过滤,如何精准且高效,方法+代码实现

作者站长头像
站长
· 阅读数 9

前言

自从我开始搞大模型应用,就一直有一个头疼的问题困扰着我的团队,那就是避免敏感信息

传统的做法是通过一些匹配算法,过滤掉敏感词,这个后面我们再讲。

但大模型的对话中,想要防止他做一些不合法的事情,就比较困难了。

传统做法&代码实现

原理

比较经典的算法有kmp 字典树,ac自动机,或者配合起来使用。

我们这里用一种字节树(我起的名)的方法。就是将所有敏感词,构建成一颗树,如下图:

AI时代,敏感词过滤,如何精准且高效,方法+代码实现

需要对一段文本过滤时,则将文本依次从左向右,在这颗树中匹配。

代码实现

github 传送门

树节点的结构:

pub struct Node<V>{
    key:u8,
    data:Option<V>,
    next:Vec<Node<V>>
}

敏感词的抽象:

pub trait AsBytes{
    fn as_byte(&self)-> &[u8];
}

功能实现:

impl<V> ByteMap<V>
{
    pub fn new()->Self{
        ByteMap{root:Node::default(0)}
    }

    pub fn insert<K:AsBytes>(&mut self,key:&K,value:V){
        let keys = key.as_byte().to_vec();
        self.root.insert(keys.into_iter(),value);
    }

    pub fn get<K:AsBytes>(&self,key:K)->Option<&V>{
        let keys = key.as_byte();
        self.root.get(keys.iter())
    }
    
    // 找到第一个匹配的项
    pub fn match_first<K:AsBytes>(&self,keys:K) ->Option<&V>{
        let keys = keys.as_byte();
        return self.root.match_first(keys.iter())
    }

    // 匹配所有子集
    pub fn match_all<'a, K: AsBytes>(&'a self, keys:&'a K) ->Vec<&'a V> {
        let path = keys.as_byte();
        let vals = vec![];
        return self.root.match_all(path.iter(),vals);
    }

    pub fn remove<K:AsBytes>(&mut self, key:K) ->Option<V>{
        let keys = key.as_byte();
        self.root.remove(keys.iter())
    }
}

敏感词抽象的默认实现

我们需要给各种类型实现AsByte的默认实现,方便后续使用。

// 数值类型,节省代码
macro_rules! number_default_for_as_bytes {
    ($($b:tt,$n:tt);*) => {
        $(
        impl AsBytes for $b
        {
            fn as_byte(&self) -> &[u8] {
                unsafe {
                    &*(self as *const $b as *const [u8;$n])
                }
            }
        }
        )*

    };
}
number_default_for_as_bytes!(u8,1;u16,2;u32,4;u64,8;u128,16;i8,1;i16,2;i32,4;i64,8;i128,16);

// 对usize 和isize的特殊处理,略,详细请看上面github传送门

// 对u8 list的一系列实现

// char的实现,方便中文的处理
impl AsBytes for &[char]{
    fn as_byte(&self) -> &[u8] {
        unsafe {
            std::mem::transmute(*self)
        }
    }
}
impl AsBytes for Vec<char>{
    fn as_byte(&self) -> &[u8] {
        let cs = self.as_slice();
        unsafe {
            std::mem::transmute(cs)
        }
    }
}

// 编译器不保证所有情况都解引用,我们给引用类型也加一个自实现
impl<T> AsBytes for &T
where T: AsBytes
{
    fn as_byte(&self) -> &[u8] {
        (*self).as_byte()
    }
}

注意: 在rust中想按中文字符切割,需要转成charchar长度是4 固定的,表示utf8时不会压缩空间。

使用

#[test]
fn byte_map_chinese(){
    let mut map = ByteMap::new();
    map.insert(&("你好".chars().collect::<Vec<char>>()),"你好");
    map.insert(&("hello".chars().collect::<Vec<char>>()),"hello");
    map.insert(&("123".chars().collect::<Vec<char>>()),"123");

    let target = "飞流123hello你好飞流直下三千尺".chars().collect::<Vec<char>>();

    for i in 0..target.len(){
        if let Some(s) = map.match_first(&target[i..]){
            println!("match ok ---> {}",s);
        }
    }
}

ai 相关的思考

分词

上面的匹配方式过于死板,随便加个符号,都能够轻松的被绕开。

为了加快速度和准确程度,我们可以对输入文本先过滤,再分词,再匹配。类似下面的方式

import jieba
import re

input = "我来-到北京a清华大*学"
input = re.sub(r'[-a*]','',input)

seg_list = jieba.cut(input, cut_all=True)
print("Full Mode: " + "/ ".join(seg_list)) 

#> 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学

语法纠正

因为汉语言的博大精深,有些话词序颠倒,正常人也能够理解,但是想找出他们,殊为不易。

这种情况,需要先做词法纠正,如纠正错别字,纠正字序错误,再找敏感词。

这种方法不常用,方案复杂,收益低,缺点明显。

机器学习

利用一些传统的机器学习算法,做分类。

对一段文本进行分类,存在敏感词or不存在敏感词

这种方式需要衡量准确度,既容易误伤,又容易遗漏

NLP

利用NLP处理。

举个简单的思路:分词之后,计算目标词的向量,然后召回敏感词。

如果最相近敏感词和目标词的相似度超过某个阈值,则视为存在敏感信息。

也可以训练一个分类器 进行辨别。

这种方法速度稍慢,容易误伤。

集成

实际工程上,通常的做法是多个方法结合在一起使用。针对自己的业务,衡量耗时 效果 成本 等等因素,灵活的变通才是王道。

AIGC & 大模型

上面提到的过滤方法,都建立在你已知有哪些敏感词,有哪些敏感场景的情况下,进行防御的。

而现在的时代是,ai是生产者,ai是创造者,ai是消费者等等 多身份于一体的,那敏感信息,不合法的信息就更加难以防御。

看一下GPT是如何犯罪的。

如果你直接问它,如下图:

AI时代,敏感词过滤,如何精准且高效,方法+代码实现

但咱们可以迂回一下:

AI时代,敏感词过滤,如何精准且高效,方法+代码实现

看到这里,你大概是否理解了,我在前言中提到的头疼的问题,尤其是我们所处的互联网环境更加严格。

敏感词拦截

我们还是可以用传统的方式拦截,但有几个问题。

  1. 文本是流式的,那个要求过滤方式也是流式的。

    • 看到这,就解释了,有那么多现成的库,为啥我们要自己实现一套byte式的过滤方法。
    • 基于上面的我们的代码,你能否封装成一个流式处理库?
  2. 因为回复是实时模式,也就是你发现敏感词的时候,人已经看到了前面的内容,这种情况又要如何处理?

    • 数据加密,本文不讨论,以后再说

大模型安全

解决问题的最好方法,就是铲除源头。不要让模型产出有危害的信息是最好的方式。

这好像已经脱离了敏感词的范畴,以后我们单开一篇讲。

尾语

时代变了,我们面临的问题也变了,多思考,才不会落伍。