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

前言

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

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

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

传统做法&代码实现

原理

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

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

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

代码实现

github 传送门

树节点的结构:

rust 复制代码
pub struct Node<V>{
    key:u8,
    data:Option<V>,
    next:Vec<Node<V>>
}

敏感词的抽象:

rust 复制代码
pub trait AsBytes{
    fn as_byte(&self)-> &[u8];
}

功能实现:

rust 复制代码
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的默认实现,方便后续使用。

rust 复制代码
// 数值类型,节省代码
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时不会压缩空间。

使用

rust 复制代码
#[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 相关的思考

分词

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

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

python 复制代码
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是如何犯罪的。

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

但咱们可以迂回一下:

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

敏感词拦截

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

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

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

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

大模型安全

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

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

尾语

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

相关推荐
努力的布布20 分钟前
SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
java·后端·spring
PacosonSWJTU25 分钟前
spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理)
java·后端·springmvc
记得开心一点嘛34 分钟前
在Java项目中如何使用Scala实现尾递归优化来解决爆栈问题
开发语言·后端·scala
学习前端的小z1 小时前
【AIGC】ChatGPT是如何思考的:探索CoT思维链技术的奥秘
人工智能·chatgpt·aigc
黄俊懿1 小时前
【深入理解SpringCloud微服务】手写实现各种限流算法——固定时间窗、滑动时间窗、令牌桶算法、漏桶算法
java·后端·算法·spring cloud·微服务·架构
2401_857439692 小时前
“衣依”服装销售平台:Spring Boot技术应用与优化
spring boot·后端·mfc
Jerry.ZZZ2 小时前
系统设计,如何设计一个秒杀功能
后端
企业码道刘某3 小时前
EasyExcel 大数据量导入导出解决方案进阶
java·后端·mybatis
九圣残炎4 小时前
【springboot】简易模块化开发项目整合Redis
spring boot·redis·后端