12.3 Gym-Spam架构

Gym-Spam基于OpenAI Gym和Keras-rl开发,主要由DQNAgent、SpamEnv_v0、Spam_Check、Spam_Manipulator和Features组成,如图12-4所示。Features将垃圾邮件样本转换成向量,Spam_Check基于规则用于垃圾邮件检测,垃圾邮件的特征向量作为状态传递。DQNAgent基于当前状态和一定的策略,选择免杀动作。SpamEnv_v0根据免杀动作,通过Spam_Manipulator针对垃圾邮件样本执行免杀操作,然后使用Features重新计算特征,再使用Spam_Check判断,如果不是垃圾邮件,反馈10并结束本轮学习;如果是垃圾邮件,反馈0,并将新状态给DQNAgent,DQNAgent继续选择下一步免杀操作,如此循环。下面我们将介绍每个组件的具体原理和实现。

图12-4 Gym-Spam架构

12.3.1 Features类

Features类加载之前训练好的CountVectorizer对象的词袋字典,然后使用该字典对传入的邮件内容进行特征提取。这里需要强调的是,CountVectorizer返回的是稀疏矩阵,所以需要显示转换成NumPy数组,代码如下:


def extract(self,str):
    featurevectors=None
    if os.path.exists(self.vocabulary_file):
        vocabulary = joblib.load(self.vocabulary_file)
        vectorizer = CountVectorizer(   decode_error='ignore',
                                        vocabulary=vocabulary,
                                        strip_accents='ascii',
                                        stop_words='english',
                                        max_df=1.0,
                                        min_df=1)
        #CountVectorizer产生的是稀疏矩阵 所以需要使用toarray()转换成了numpy结构
        featurevectors=vectorizer.transform([str]).toarray()

12.3.2 Spam_Manipulator类

Spam_Manipulator类实现了针对垃圾邮件样本的免杀操作,定义了对应的转换表ACTION_TABLE,代码如下:


ACTION_TABLE = {
'addTab': 'addTab',
'addEnter': 'addEnter',
'confusionCase':'confusionCase',
'lineBreak': 'lineBreak',
'addHyphen': 'addHyphen',
'doubleChar': 'doubleChar',
}

Spam_Manipulator类实现了随机增加TAB、回车、换行符以及连字符,实现了大小写混淆和使用错别字功能。关于这些技术的详细原理请参考12.2节“垃圾邮件检测绕过技术”。

Spam_Manipulator类的modify实现了使用函数名称的字符串访问函数的功能,如图12-5所示,modify需要和ACTION_TABLE和ACTION_LOOKUP配合使用,代码如下:


def modify(self,str, _action, seed=6):
    print "Do action :%s" % _action
    action_func=Spam_Manipulator().__getattribute__(_action)
    return action_func(str,seed)

图12-5 通过动作序号执行函数的流程图

12.3.3 DQNAgent类

DQNAgent具体实现了强化学习算法,关于DQN的详细介绍请参考本书第6章“Kera-rl简介”中的内容,本章主要介绍在Gym-Spam中如何使用DQNAgent。

首先定义创建深度学习网络的函数:input_shape表示输入的特征向量的维度,layers表示深度学习网络的各层层数,nb_actions表示动作空间的大小,由于在本例中动作空间是有限的离散值,所以nb_actions事实上也就是动作的个数,同时也是深度学习网络输出层节点数。这里深度学习网络使用的是多层感知机(MLP),所以直接指定层数即可,本例中建议使用两层隐藏层,节点数分别为16和8,代码如下:


def generate_dense_model(input_shape, layers, nb_actions):
    model = Sequential()
    model.add(Flatten(input_shape=input_shape))
    model.add(Dropout(0.1))  
    for layer in layers:
        model.add(Dense(layer))
        model.add(BatchNormalization())
        model.add(ELU(alpha=1.0))
    model.add(Dense(nb_actions))
    model.add(Activation('linear'))
    return model

然后我们初始化Gym环境,获取环境env的动作空间大小:


ENV_NAME = 'Spam-v0' 
env = gym.make(ENV_NAME)
nb_actions = env.action_space.n
window_length = 1 

创建DQNAgent的深度学习网络:


model = generate_dense_model((window_length,) + env.observation_space.shape, layers, nb_actions)

创建策略对象policy,这里使用的是玻尔兹曼算法:


policy = BoltzmannQPolicy()

创建记忆体,大小为100万:


memory = SequentialMemory(limit=1000000, ignore_episode_boundaries=False, window_length=window_length)

创建DQNAgent对象agent,指定使用的深度学习网络、动作空间大小、记忆体、使用的策略和批处理大小等参数,代码如下:


agent = DQNAgent(model=model, nb_actions=nb_actions, memory=memory, nb_steps_warmup=16,enable_double_dqn=True,enable_dueling_network=True, dueling_type='avg',arget_model_update=1e-2, policy=policy, batch_size=16)

编译agent中的深度学习网络并开始学习,学习的总步数为rounds,其中非常重要的一个参数是nb_max_episode_steps,设置它的目的是在一轮学习中如果超过阈值可以自动退出,避免在一轮学习中因为异常情况一直学习,影响其他轮学习,代码如下:


agent.compile(RMSprop(lr=1e-3), metrics=['mae'])
agent.fit(env, nb_steps=rounds, visualize=False, verbose=2,nb_max_episode_steps=nb_max_episode_steps_train)

12.3.4 SpamEnv_v0类

SpamEnv_v0类基于OpenAI Gym框架,实现了强化学习中环境的主要功能。SpamEnv_v0在初始化阶段加载了垃圾邮件样本文件,并且随机划分成了训练样本和测试样本,其测试样本占40%,开发阶段也可以设置较小的训练集个数,比如100,代码如下:


def load_all_spam():
    spam=[]
    for i in range(1,7):
        path="../../data/mail/enron%d/spam/" % i
        print "Load %s" % path
        spam+=load_files_from_dir(path)
    # 划分训练和测试集合
    samples_train, samples_test = train_test_split(spam, test_size=100)
    return samples_train, samples_test
samples_train, samples_test=load_all_spam()

定义动作转换表ACTION_LOOKUP:


ACTION_LOOKUP = {i: act for i, act in enumerate(Spam_Manipulator.ACTION_TABLE.keys())}

初始化动作空间,创建获取特征的对象features_extra,用于检测垃圾邮件的spam_checker以及用于修改样本的spam_manipulatorer,代码如下:


self.action_space = spaces.Discrete(len(ACTION_LOOKUP))
#当前处理的样本
self.current_sample=""
self.features_extra=Features(vocabulary_file)
self.spam_checker=Spam_Check()
#根据动作修改当前样本免杀
self.spam_manipulatorer= Spam_Manipulator()

涉及的主要函数介绍如下。

1.Step

Step函数根据输入的动作序号,针对当前的样本进行修改,然后再检测是否是垃圾邮件,如果不是垃圾邮件,说明免杀成功,回馈10,标记此轮学习完成,反之反馈0,继续学习,代码如下:


def _step(self, action):
    r=0
    is_gameover=False
    _action=ACTION_LOOKUP[action]
self.current_sample=self.spam_manipulatorer.modify(self.current_sample,_action)
    self.observation_space = self.features_extra.extract(self.current_sample)
    if self.spam_checker.check_spam(self.observation_space) < 1.0:
        #给奖励
        r=10
        is_gameover=True
        print "Good!!!!!!!avoid spam detect:%s" % self.current_sample
    return self.observation_space, r,is_gameover,{}

2.Reset

Reset函数负责重置环境,从训练样本列表中随机选择一个作为当前样本,并转换成对应的特征向量,作为初始状态。代码如下:


def _reset(self):
    self.current_sample=random.choice(samples_train)
    self.observation_space=self.features_extra.extract(self.current_sample)
    return self.observation_space