文字反垃圾在花椒直播中的應用概述

花椒技術發表於2019-12-30

一、背景

隨著花椒使用者和主播使用者的數量不斷增加,一些非法使用者(垃圾蟲)利用花椒平臺資料流量大、資訊傳播範圍廣的優勢,通過各種多媒體手段(文字、圖片、語音、短視訊等)在使用者個人資料資訊(暱稱,簽名,頭像等)及直播間聊天等場景散播垃圾資訊,這些資訊嚴重影響了使用者的觀看體驗,甚至會導致使用者流失、活躍度下降,此外一些情節嚴重的違法違規內容會給平臺帶來運營風險和負面的社會影響。

二、問題分析

本文主要以文字為物件,簡要地介紹花椒平臺在文字反垃圾方面所採用的文字垃圾攔截技術。目前平臺上所接觸到的文字垃圾資訊基本上可以概括為以下幾個類別:

  1. 垃圾廣告:各類商品廣告、詐騙廣告等
  2. 色情內容:色情詞彙、色情服務及低俗資訊等
  3. 暴恐、政治敏感詞: 暴恐涉政、違禁品等
  4. 竟品資訊及其他資訊等

對於平臺初期資料量較少、垃圾資訊形式單一的情況,採用人工稽核的方式基本可以解決問題。但是隨著平臺業務的拓寬與發展,業務量迅速增加,僅依靠人工稽核方式無法應對,這時需要藉助一些規則策略和演算法模型輔助人工稽核,以減少人工稽核工作量,提高稽核效率。

簡單的垃圾資訊,可以通過設定規則進行關鍵詞過濾和遮蔽,正則表達則可以發揮很大作用。但是釋出者為了逃避攔截,通常都會對垃圾資訊進行改造和偽裝,比如拼音替換,同義詞替換,象形字替換,嵌入表情字元,用表情代替字元,甚至是將文字順序打亂。對於複雜的資訊,其表達形式廣泛、沒有規律,僅僅通過規則過濾達不到效果,可藉助精準的演算法模型進行檢測。

垃圾資訊攔截是一個常見的文字二分類任務,是自然語言處理領域的一個基本任務,目的是推斷出給定的文字的標籤。二分類問題常見的評價指標有準確率(accuracy),精準率(precision),召回率(recall),F1-score等。

三、文字分類演算法介紹

1、傳統文字分類方法

一般來講傳統機器學習文字分類任務過程包括文字預處理、特徵提取、文字表示、訓練分類器和分類效能評估。其中構建特徵工程和分類建模方法是文字分類任務中最重要的兩個環節。

文字的預處理包括文字分詞、去除停用詞(包括標點、數字和一些無意義的詞)、詞義消歧、統計等處理。中文與英文相比,在中文文字預處理過程中,首先要進行分詞處理,而英文文字單詞與單詞之間通過空格即可分割,無需進行分詞處理。

特徵提取和文字表示目的就是將文字轉化為計算機可以理解的向量形式。詞袋模型(Bag of Words)是用於文字表示的最簡單的方法, BoW把文字轉換為文件中單詞出現次數的矩陣,只關注文件中是否出現給定的單詞和單詞出現頻率,而捨棄文字的結構、單詞出現的順序和位置。詞頻-逆向檔案頻率(TF-IDF)是一種在文字挖掘中廣泛使用的特徵向量化方法,主要衡量一個文件中詞語在語料庫中的重要程度。Word2vec採用一系列代表文件的詞語來訓練word2vec權重矩陣,將每個詞語對映到一個固定大小的向量。

分類器用的比較多的是LR,SVM,MLP,GBDT等,當然還有其他一些分類演算法,這裡不多贅述。

文字反垃圾在花椒直播中的應用概述

2、基於CNN的文字分類方法

隨著網際網路的普及,一些使用者為求彰顯個性,開始大量使用同音字、音近字、特殊符號等異形文字(火星文)。由於這種文字與日常使用的文字相比有明顯的不同並且文法也相當奇異,目前平臺上遇到的難以識別樣本大多是數字、QQ、微信的變種、多是象形字元,不含語義、分詞模型對這些符號無法處理而且文字都很簡短。

Badcase樣本示例


文字反垃圾在花椒直播中的應用概述

傳統文字分類方法所存在的問題

  1. 這些文字如果使用常規的分詞方法會導致分詞失敗
  2. 即使能成功分詞,也很難查詢到大規模語料庫對詞語進行向量表示
  3. 過濾異種符號和文字,導致抓不住火星文特徵

因此需要一種不借助分詞的模型,以單個字詞為原子進行詞向量表示,並且可以挖掘學習詞與詞之間的語序及語義資訊。

傳統文字分類方法除了上述問題之外,還存在資料稀疏和維度爆炸等問題,對分類器非常不友好,並且模型泛化能力有限。應用深度學習解決大規模文字分類問題最重要的是解決文字表示,從文字中自動學習提取特徵,降低了人工設計特徵的難度,使用深度學習進行文字分類其效果往往要好於傳統的方法。

TextCNN 原理

CNN(Convolutional Neural Network)卷積神經網路在影像處理方面應用很多,TextCNN模型創新性的將CNN結構應用在了NLP領域,為文字分類提供了新的思路,TextCNN解決了傳統方法分詞處理和詞向量表示兩個關鍵問題,其貢獻主要有以下幾點:

  1. 避免分詞,以字元為單位的文字向量表示
  2. CNN能捕捉區域性區域的詞序及語義資訊,所表達的特徵更加豐富
  3. 採用不同尺寸的卷積核,可以提取到 n-gram 的特徵
  4. 卷積結構運算速度快,模型響應時長控制在 50ms 以下


文字反垃圾在花椒直播中的應用概述

模型結構


文字反垃圾在花椒直播中的應用概述

文字反垃圾在花椒直播中的應用概述

TextCNN 模型採用交叉熵損失函式,即將文字處理建模為一個二分類問題。該模型先將文字進行詞嵌入(Embedding)獲得詞向量,然後採用不同尺寸卷積核進行卷積運算提取特徵,接著進行最大池化(Max pooling)得到顯著特徵,最後接一個概率輸出層(Softmax)進行文字分類。

卷積部分

對於一維的文字資料,經過詞向量化操作後可以得到類似於影像的二維向量。假設輸入的每個詞的向量維度為k,即詞向量空間維度為k,則包含n個單詞的句子組成一個 n×k 的二維矩陣,假設卷積核為h×k,h則是卷積滑動視窗的大小,卷積特徵


文字反垃圾在花椒直播中的應用概述


。注意到卷積核的寬度k與詞向量的維度一致,是因為輸入的每一行向量代表一個詞,即在抽取特徵的過程中,將詞作為文字的最小粒度。

例如有一個樣本 T={"我","愛","花","椒","直","播"},樣本輸入長度為N= 6,詞向量空間維度為 k=5,假設滑動視窗尺寸h=4,則卷積核尺寸為4×5。

文字反垃圾在花椒直播中的應用概述


輸入資料



文字反垃圾在花椒直播中的應用概述


卷積核


假設滑動視窗移動步長為1,上述輸入資料經過卷積運算後將得到如下長度為n-h+1=3的向量輸出結果


文字反垃圾在花椒直播中的應用概述


同理,假設採用2,3,4三種尺寸卷積核,每種尺寸對應有m個卷積核,這樣經過卷積運算後,每個尺寸的卷積核對應有


文字反垃圾在花椒直播中的應用概述


的卷積特徵。

池化層

最大池化即對領域內特徵點取最大值,通常情況下max-pooling能減小卷積層引數誤差造成估計均值的偏移,更多的保留顯著特徵資訊,最大池化的定義及示例如下:


文字反垃圾在花椒直播中的應用概述
文字反垃圾在花椒直播中的應用概述


對於上述卷積操作得到的3m個(n−h+1)×1的卷積特徵採用尺寸為(n−h+1)×1的最大池化(max_pooling)操作得到3個m×1維的特徵,最後這些特徵進行拼接,得到3m×1維的向量。當用CNN提取出特徵向量後,就可以將其輸入到概率輸出層(softmax)進行分類,其中softmax函式定義如下:


文字反垃圾在花椒直播中的應用概述


以下是TextCNN模型程式碼實現:

#coding:utf-8
import tensorflow as tf
import numpy as np


class TextCNN(object):
    def __init__(self, sequence_length, num_classes, vocab_size, embedding_size, 
                    filter_sizes, num_filters, l2_reg_lambda=0.0):
        self.input_x = tf.placeholder(tf.int32, [None, sequence_length], name="input_x")
        self.input_y = tf.placeholder(tf.float32, [None, num_classes], name="input_y")
        self.dropout_keep_prob = tf.placeholder(tf.float32, name="dropout_keep_prob")
        l2_loss = tf.constant(0.0)

        #Embedding
        with tf.device('/cpu:0'), tf.name_scope("embedding"):
            self.W = tf.get_variable('lookup_table',
                     dtype=tf.float32,
                     shape=[vocab_size, embedding_size],
                     initializer=tf.random_uniform_initializer())
             self.W = tf.concat((tf.zeros(shape=[1, embedding_size]), self.W[1:, :]), 0)
             self.embedded_chars = tf.nn.embedding_lookup(self.W, self.input_x)
             self.embedded_chars_expanded = tf.expand_dims(self.embedded_chars, -1)

        #Convolution
        pooled_outputs = []

        for i, filter_size in enumerate(filter_sizes):
           with tf.name_scope("conv-maxpool-%s" % filter_size):
              filter_shape = [filter_size, embedding_size, 1, num_filters]
              W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name="W")
              b = tf.Variable(tf.constant(0.1,shape=[num_filters]), name="b")
              conv = tf.nn.conv2d(self.embedded_chars_expanded,W,strides=[1, 1, 1, 1],
                                        padding="VALID",name="conv")
              h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu")
              pooled = tf.nn.max_pool(h,ksize=[1, sequence_length - filter_size + 1, 1, 1],
                                            strides=[1, 1, 1, 1],padding='VALID',name="pool")
              pooled_outputs.append(pooled)

       num_filters_total = num_filters * len(filter_sizes)
       self.h_pool = tf.concat(pooled_outputs, 3)
       self.h_pool_flat = tf.reshape(self.h_pool, [-1, num_filters_total])

       with tf.name_scope("dropout"):
           self.h_drop = tf.nn.dropout(self.h_pool_flat, self.dropout_keep_prob)

       #Output
       with tf.name_scope("output"):
          W = tf.get_variable("W",shape=[num_filters_total, num_classes],
                                    initializer=tf.contrib.layers.xavier_initializer())
          b = tf.Variable(tf.constant(0.1, shape=[num_classes]), name="b")
          l2_loss += tf.nn.l2_loss(W)
          l2_loss += tf.nn.l2_loss(b)
          self.scores = tf.nn.xw_plus_b(self.h_drop, W, b, name="scores")
          self.predictions = tf.argmax(self.scores, 1, name="predictions")

       #Loss
       with tf.name_scope("loss"):
          losses = tf.nn.softmax_cross_entropy_with_logits(logits=self.scores, labels=self.input_y)
          self.loss = tf.reduce_mean(losses) + l2_reg_lambda * l2_loss

      #Accuracy
        with tf.name_scope("accuracy"):
           correct_predictions = tf.equal(self.predictions, tf.argmax(self.input_y, 1))
           self.accuracy = tf.reduce_mean(tf.cast(correct_predictions, "float"), name="accuracy")複製程式碼

訓練結果


文字反垃圾在花椒直播中的應用概述


小結

本節簡要地介紹了傳統方法在文字分類方法任務中的基本流程以及存在的問題,並且闡述了深度學習方法在文字分類任務中優勢,以及TextCNN以單個字元為單位,採用卷積提取區域性特徵,對於處理類似火星文的文字更加魯棒。此外之所以選用CNN而沒有選用像word2vec以及沒有提到的RNN等深度學習方法,是因為CNN相對於word2vec能獲得更好的區域性的語序資訊及語義資訊;相比於RNN而言,CNN 是分層架構,CNN更適合提取關鍵特徵,對於分類問題效果更好,而RNN是連續結構,更適合順序建模,此外CNN適合平行計算,還可以採用GPU加速計算,響應時間短,inference只有3ms,非常適合垃圾文字檢測速度的要求。

四、文字反垃圾模型線上部署流程

1、服務架構


文字反垃圾在花椒直播中的應用概述

反垃圾服務分為線上與線下兩層。線上實時服務要求毫秒級判斷文字是否屬於垃圾文字,線下離線計算需要根據新進的樣本不斷更新模型,並及時推送到線上。垃圾文字識別是一個長期攻防的過程,平臺上的垃圾文字會不斷演變,模型的效果也會隨之變化。

2、 Tensorflow serving模型部署

TensorFlow Serving是一個靈活、高效能的機器學習模型服務系統,專為生產環境而設計。使用TensorFlow Serving可以將訓練好的機器學習模型輕鬆部署到線上,並且支援熱更新。它使用gRPC作為介面接受外部呼叫,服務穩定,介面簡單。能檢測模型最新版本並自動載入。這意味著一旦部署 TensorFlow Serving 後,不需要為線上服務操心,只需要關心線下模型訓練。

3、客戶端呼叫

TensorFlow Serving通過gRPC服務接受外部呼叫。gRPC是一個高效能、通用的開源RPC框架, gRPC提供了一種簡單的方法來精確地定義服務和自動為客戶端生成可靠性很強的功能庫。

在使用gRPC進行通訊之前,需要完成兩步操作:

  1. 定義服務
  2. 生成服務端和客戶端程式碼

定義服務這塊工作TensorFlow Serving已經幫我們完成了。TensorFlow Serving專案中model.proto、predict.proto和prediction_service.proto這個三個.proto檔案定義了一次預測請求的輸入和輸出。

接下來用寫好的客戶端程式來呼叫部署好的模型,啟動服務後,訪問下面地址可以檢視識別結果,說明模型部署成功且可以正常使用。


文字反垃圾在花椒直播中的應用概述


參考資料

  1. Kim Y. Convolutional neural networks for sentence classification[J]. arXiv preprint arXiv:1408.5882, 2014.
  2. web.stanford.edu/class/cs224…
  3. www.cnblogs.com/ljhdo/p/105…
  4. tensorflow.google.cn/tfx/serving…
  5. baike.baidu.com/item/火星文/608814


相關文章