DFA 算法选型思路

484 阅读3分钟

1. 写在最前面

真的是心力交瘁的一个月,最近在做「敏感词过滤」的功能:

  • 需要识别敏感的英语单词
  • 需要识别敏感的英语词组

虽然笔者在需求评审会议上极力说明,这个需求的复杂程度比较高,自己实现有种种风险,但是无奈人家人多,被产品和负责人怼回来了。

行吧,做就做呗。不过这让我想起来一句话,「人有多大胆,地有多大产」。

2. 算法调研

2.1 google 大法好

本着不懂就查的思路,笔者从 google 处得到了一丝丝思路:

DFA.png

2.2 github 一下

接着这个思路,笔者从一众开源算法中,挑选到了

sensitive.png

dirty.png

注:其实还尝试了 godlp,但这个更倾向于一个根据一系列规则对敏感数据的识别和处置方案,不大适用本场景。

3. 上手

3.1 不如试试

本着 「talk is cheap show me the code」的原则,笔者就这两个库,是否能够满足,产品的需求做了一个简单的 POC 尝试,代码如下:

package main
​
import (
  "bytes"
  "fmt"
  "io/ioutil"
​
  filter "github.com/antlinker/go-dirtyfilter"
  "github.com/importcjj/sensitive"
)
​
func FilterDirtyFilter(path, content string, isReplace bool) ([]string, error) {
  fd, err := ioutil.ReadFile(path)
  if err != nil {
    return nil, err
  }
  df := filter.NewNodeReaderFilter(bytes.NewReader(fd), '\n')
  var (
    result = make([]string, 0)
    rs     string
  )
  if isReplace {
    rs, err = df.Replace(content, '*')
    result = append(result, rs)
​
  } else {
    result, err = df.Filter(content)
  }
​
  if err != nil {
    return nil, err
  }
  return result, nil
​
}
​
func FilterSensitive(path, content string, isReplace bool) ([]string, error) {
  filter := sensitive.New()
  err := filter.LoadWordDict(path)
  if err != nil {
    return nil, err
  }
  if isReplace {
    s := filter.Replace(content, '*')
    return []string{s}, nil
​
  }
​
  s := filter.FindAll(content)
  return s, nil
}
​
func TestCNFilter() {
  c := "小明抓到了一只王八"
  fmt.Printf("原始词 %s - 脏词 %s\n", c, "王八")
  sr, err := FilterSensitive("./filter-cn.txt", c, true)
  if err != nil {
    panic(err)
  }
  dr, err := FilterDirtyFilter("./filter-cn.txt", c, true)
  if err != nil {
    panic(err)
  }
  fmt.Println("sensitive", sr)
  fmt.Println("dirty", dr)
​
}
​
func TestENFilter() {
  c := "fucked"
  fmt.Printf("原始词 %s - 脏词 %s\n", c, "fuck")
  sr, err := FilterSensitive("./filter-en.txt", c, true)
  if err != nil {
    panic(err)
  }
  dr, err := FilterDirtyFilter("./filter-en.txt", c, true)
  if err != nil {
    panic(err)
  }
  fmt.Println("sensitive", sr)
  fmt.Println("dirty", dr)
​
}
​
func main() {
  TestCNFilter()
  fmt.Println()
  TestENFilter()
}
​

注:./filter-en.txt 为与 mian.go 同级的一个文件,包含「fuck」 字符

./filter-cn.txt 为与 main.go 同级的一个文件,包含 「王八」 字符

3.2 求甚解

上述两个库都是 DFA 算法的一种实现,实现的时候使用 Trie Tree 这种数据结构。这篇文章主要介绍选项的思路,后续有空会整理一篇 DFA 跟 Trie Tree 相关的文章。

注:先记在这里,求不打自己的脸。

4. 遇到的问题

「出来混,总是会遇到各种各样的问题」

4.1 英文词组可拆为字母

英文单词由单词组成,假设敏感词的词库里有 fuck 这个单词,所有带有 fuck 的词组,下图两个 golang 库测试例子:

fuck.png

4.2 误识别的问题

开源版本的算法,都是识别部分,不支持对上下文及情绪进行判断,会产生较高的误识别率,下图两个 golang 库测试例子:

误识别.png

注:做人啊,不能要求太高,连情绪啥的都开源了,人家做这个的公司不得黄了

5. 碎碎念

以上就是这个月糟心的开始,毕竟猜测没准产品他们后面就想着要不自己实现一个好了……,但是做人还是要乐观、坚持、努力吖:

  • 相逢的意义在于照亮彼此,不然的话 一个人喝茶也很浪漫,一个人吹风也能清醒。
  • 为什么要蜷缩在黑暗里,仅有一次的人生当然要活的炽热。
  • 在你的成长过程中,你会渐渐发现世界是以这样一种方式在运转着,人们会告诉你世事险恶,你要遵规守纪,不要做一些太出格的事。这是非常局限的生活。当你发现这样一个简单的事实,生命会无限扩展——你身边一切被你称之为生活的事物,都是一些不如你聪慧的人创造的。而你可以影响它们,你可以改变这一切,你可以建造出给别人带来福祉的事物,上面有着只属于你的印记。一旦你学会这一点,你将卓越不凡。

6. 参考资料