标签归档:LSTM

详解如何用 LSTM 自动识别验证码

雷锋网按:本文作者 Slyne_D,原载于作者个人博客,雷锋网已获授权。

这是去年博主心血来潮实现的一个小模型,现在把它总结一下。由于楼主比较懒,网上许多方法都需要切割图片,但是楼主思索了一下感觉让模型有多个输出就可以了呀,没必要一定要切割的吧?切不好还需要损失信息啊!本文比较简单,只基于传统的验证码。

Part 0 模型概览

从图片到序列实际上就是Image2text也就是seq2seq的一种。encoder是Image, decoder是验证码序列。由于keras不支持传统的在decoder部分每个cell输出需要作为下一个rnn的cell的输入(见下图),所以我们这里把decoder部分的输入用encoder(image)的最后一层复制N份作为decoder部分的每个cell的输入。

典型的seq2seq

keras可以直接实现的image2text

当然利用 recurrentshopseq2seq,我们也可以实现标准的seq2seq的网络结构(后文会写)。

Part I 收集数据

网上还是有一些数据集可以用的,包括dataCastle也举办过验证码识别的比赛,都有现成的标注好了的数据集。(然而难点是各种花式验证码啊,填字的,滑动的,还有那个基于语义的reCaptcha~)。

因为我想弄出各种长度的验证码,所以我还是在github上下载了一个生成验证码的python包。

下载后,按照例子生成验证码(包含26个小写英文字母):

#!/usr/bin/env python

# -*- coding: utf-8

from captcha.image import ImageCaptcha

from random import sample


image = ImageCaptcha() #fonts=[ "font/Xenotron.ttf"]

characters =  list("abcdefghijklmnopqrstuvwxyz")


def generate_data(digits_num, output, total):

    num = 0

    while(num<total):

        cur_cap = sample(characters, digits_num)

        cur_cap =''.join(cur_cap)

        _ = image.generate(cur_cap)

        image.write(cur_cap, output+cur_cap+".png")

        num += 1


generate_data(4, "images/four_digit/", 10000)  #产生四个字符长度的验证码

generate_data(5, "images/five_digit/", 10000) #产生五个字符长度的验证码

generate_data(6, "images/six_digit/", 10000) #产生六个字符长度的验证码

generate_data(7, "images/seven_digit/",10000) # 产生七个字符长度的验证码

产生的验证码

(目测了一下生成验证码的包的代码,发现主要是在x,y轴上做一些变换,加入一些噪音)

Part II 预处理

由于生成的图片不是相同尺寸的,为了方便训练我们需要转换成相同尺寸的。另外由于验证码长度不同,我们需要在label上多加一个符号来表示这个序列的结束。

处理之后的结果就是图像size全部为Height=60, Width=250, Channel=3。label全部用字符id表示,并且末尾加上表示<EOF>的id。比如假设a-z的id为0-25,<EOF>的id为26,那么对于验证码"abdf"的label也就是[0,1,3,5,26,26,26,26],"abcdefg"的label为[0,1,2,3,4,5,6,26]。

由于我们用的是categorical_crossentropy来判断每个输出的结果,所以对label我们还需要把其变成one-hot的形式,那么用Keras现成的工具to_categorical函数对上面的label做一下处理就可以了。比如abdf的label进一步转换成:

[[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],

[0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],

[0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],

[0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],

[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],

[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],

[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],

[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]]

Part III 构建模型

不借助外部包可以实现的模型

def create_simpleCnnRnn(image_shape, max_caption_len,vocab_size):

    image_model = Sequential()

    # image_shape : C,W,H

    # input: 100×100 images with 3 channels -> (3, 100, 100) tensors.

    # this applies 32 convolution filters of size 3×3 each.

    image_model.add(Convolution2D(32, 3, 3, border_mode='valid', input_shape=image_shape))

    image_model.add(BatchNormalization())

    image_model.add(Activation('relu'))

    image_model.add(Convolution2D(32, 3, 3))

    image_model.add(BatchNormalization())

    image_model.add(Activation('relu'))

    image_model.add(MaxPooling2D(pool_size=(2, 2)))

    image_model.add(Dropout(0.25))

    image_model.add(Convolution2D(64, 3, 3, border_mode='valid'))

    image_model.add(BatchNormalization())

    image_model.add(Activation('relu'))

    image_model.add(Convolution2D(64, 3, 3))

    image_model.add(BatchNormalization())

    image_model.add(Activation('relu'))

    image_model.add(MaxPooling2D(pool_size=(2, 2)))

    image_model.add(Dropout(0.25))

    image_model.add(Flatten())

    # Note: Keras does automatic shape inference.

    image_model.add(Dense(128))

    image_model.add(RepeatVector(max_caption_len)) # 复制8份

    image_model.add(Bidirectional(GRU(output_dim=128, return_sequences=True)))

    image_model.add(TimeDistributed(Dense(vocab_size)))

    image_model.add(Activation('softmax'))

    sgd = SGD(lr=0.002, decay=1e-6, momentum=0.9, nesterov=True)

    image_model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])

    return image_model

借助recurrentshop和seq2seq可以实现的结构

def create_imgText(image_shape, max_caption_len,vocab_size):

    image_model = Sequential()

    # image_shape : C,W,H

    # input: 100×100 images with 3 channels -> (3, 100, 100) tensors.

    # this applies 32 convolution filters of size 3×3 each.

    image_model.add(Convolution2D(32, 3, 3, border_mode='valid', input_shape=image_shape))

    image_model.add(BatchNormalization())

    image_model.add(Activation('relu'))

    image_model.add(Convolution2D(32, 3, 3))

    image_model.add(BatchNormalization())

    image_model.add(Activation('relu'))

    image_model.add(MaxPooling2D(pool_size=(2, 2)))

    image_model.add(Dropout(0.25))

    image_model.add(Convolution2D(64, 3, 3, border_mode='valid'))

    image_model.add(BatchNormalization())

    image_model.add(Activation('relu'))

    image_model.add(Convolution2D(64, 3, 3))

    image_model.add(BatchNormalization())

    image_model.add(Activation('relu'))

    image_model.add(MaxPooling2D(pool_size=(2, 2)))

    image_model.add(Dropout(0.25))

    image_model.add(Flatten())

    # Note: Keras does automatic shape inference.

    image_model.add(Dense(128))

    image_model.add(RepeatVector(1)) # 为了兼容seq2seq,要多包一个[]

    #model = AttentionSeq2Seq(input_dim=128, input_length=1, hidden_dim=128, output_length=max_caption_len, output_dim=128, depth=2) 

    model = Seq2Seq(input_dim=128, input_length=1, hidden_dim=128, output_length=max_caption_len,

                             output_dim=128, peek=True)

    image_model.add(model)

    image_model.add(TimeDistributed(Dense(vocab_size)))

    image_model.add(Activation('softmax'))

    image_model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])


    return image_model

Part IV 模型训练

之前写过固定长度的验证码的序列准确率可以达到99%,项目可以参考这里

另外,我们在用Keras训练的时候会有一个acc,这个acc是指的一个字符的准确率,并不是这一串序列的准确率。也就是说在可以预期的情况下,如果你的一个字符的准确率达到了99%,那么如果你的序列长度是5的时候,理论上你的序列准确率是0.99^5 = 0.95, 如果像我们一样序列长度是7,则为0.99^8=0.923。

所以当你要看到实际的验证集上的准确率的时候,应该自己写一个callback的类来评测,只有当序列中所有的字符都和label一样才可以算正确。

class ValidateAcc(Callback):

    def __init__(self, image_model, val_data, val_label, model_output):

        self.image_model = image_model

        self.val = val_data

        self.val_label = val_label

        self.model_output = model_output


    def on_epoch_end(self, epoch, logs={}):  # 每个epoch结束后会调用该方法

        print '\n—————————————–'

        self.image_model.load_weights(self.model_output+'weights.%02d.hdf5' % epoch)

        r = self.image_model.predict(val, verbose=0)

        y_predict = np.asarray([np.argmax(i, axis=1) for i in r])

        val_true = np.asarray([np.argmax(i, axis = 1) for i in self.val_label])

        length = len(y_predict) * 1.0

        correct = 0

        for (true,predict) in zip(val_true,y_predict):

            print true,predict

            if list(true) == list(predict):

                correct += 1

        print "Validation set acc is: ", correct/length

        print '\n—————————————–'



val_acc_check_pointer = ValidateAcc(image_model,val,val_label,model_output)

记录每个epoch的模型结果

check_pointer = ModelCheckpoint(filepath=model_output + "weights.{epoch:02d}.hdf5")

训练

image_model.fit(train, train_label,

                shuffle=True, batch_size=16, nb_epoch=20, validation_split=0.2, callbacks=[check_pointer, val_acc_check_pointer])

Part V 训练结果

在39866张生成的验证码上,27906张作为训练,11960张作为验证集。

第一种模型:

序列训练了大约80轮,在验证集上最高的准确率为0.9264, 但是很容易变化比如多跑一轮就可能变成0.7,主要原因还是因为预测的时候考虑的是整个序列而不是单个字符,只要有一个字符没有预测准确整个序列就是错误的。

第二种模型:

第二个模型也就是上面的create_imgText,验证集上的最高准确率差不多是0.9655(当然我没有很仔细的去调参,感觉调的好的话两个模型应该是差不多的,验证集达到0.96之后相对稳定)。

Part VI 其它

看起来还是觉得keras实现简单的模型会比较容易,稍微变形一点的模型就很纠结了,比较好的是基础的模型用上其他包都可以实现。keras 2.0.x开始的版本跟1.0.x还是有些差异的,而且recurrentshop现在也是支持2.0版本的。如果在建模型的时候想更flexible一点的话,还是用tensorflow会比较好,可以调整的东西也比较多,那下一篇可以写一下img2txt的tensorflow版本。

Part VII 代码

完整源代码:https://github.com/Slyne/CaptchaVariLength

Part VIII 后续

现在的这两个模型还是需要指定最大的长度,后面有时间会在训练集最多只有8个字符的情况下,利用rnn的最后一层进一步对于有9个以及以上字符的验证码效果,看看是不是可以再进一步的扩展到任意长度。(又立了一个flag~)

雷锋网相关阅读:

LSTM Networks 应用于股票市场探究

一文详解如何用 TensorFlow 实现基于 LSTM 的文本分类(附源码)

雷锋网

LSTM Networks 应用于股票市场探究

雷锋网按:本文原作者BigQuant,本文原载于知乎专栏。雷锋网已获得授权转载。

摘要:BigQuant平台上的 StockRanker 算法在选股方面有不俗的表现,模型在 15、16 年的回测收益率也很高 (使用默认因子收益率就达到 170% 左右)。然而,StockRanker 在股灾时期回撤很大 (使用默认因子回撤 55%),因此需要择时模型,控制 StockRanker 在大盘走势不好时的仓位。 LSTM(长短期记忆神经网络) 是一种善于处理和预测时间序列相关数据的 RNN。本文初步探究了 LSTM 在股票市场的应用,进而将 LSTM 对沪深 300 未来五日收益率的预测作为择时器并与 StockRanker 结合使用,在对回测收益率有较好保证的前提下,较为显著地降低了 StockRanker 的回撤。

LSTM Networks(长短期记忆神经网络)简介

LSTM Networks 是递归神经网络(RNNs)的一种,该算法由 Sepp Hochreiter 和 Jurgen Schmidhuber 在 Neural Computation 上首次公布。后经过人们的不断改进,LSTM 的内部结构逐渐变得完善起来(图 1)。在处理和预测时间序列相关的数据时会比一般的 RNNs 表现的更好。目前,LSTM Networks 已经被广泛应用在机器人控制、文本识别及预测、语音识别、蛋白质同源检测等领域。基于 LSTM Networks 在这些方面的优异表现,本文旨在探究 LSTM 是否可以应用于股票时间序列的预测。

LSTM Networks 处理股票时间序列的流程

本文使用的 LSTM 处理股票序列的流程如图 2。本文的整体流程均在 BigQuant 量化平台上进行,构建 LSTM 模型使用库主要为 Keras。

数据获取与处理:对于时间序列,我们通常会以 [X(t-n),X(t-n+1),…,X(t-1),X(t)] 这 n 个时刻的数据作为输入来预测 (t+1) 时刻的输出。对于股票来说,在 t 时刻会有若干个 features,因此,为了丰富 features 以使模型更加精确,本文将 n(time series)×s(features per time series) 的二维向量作为输入。LSTM 对于数据标准化的要求很高,因此本文所有 input 数据均经过 z-score 标准化处理。

LSTM 模型构建:作为循环层的一种神经网络结构,只使用 LSTM 并不能构建出一个完整的模型,LSTM 还需要与其他神经网络层(如 Dense 层、卷积层等)配合使用。此外,还可以构建多层 LSTM 层来增加模型的复杂性。

回测:本文进行的回测分为两种,一是直接将 LSTM 输出结果作为做单信号在个股上进行回测,二是将 LSTM 的预测结果作为一种择时信号,再配合其他选股模型(如 BigQuant 平台的 StockRanker)进行回测。

LSTM 应用股票市场初探

之前我们做过 LSTM 应用于股票市场的初步探究(链接地址),使用方法为利用沪深 300 前 100 天的收盘价预测下一天的收盘价。从结果来看,LSTM 对未来 20 天的预测基本上是对过去 100 天收盘价变化的趋势的总括,因此最终的预测结果以及回测结果都不是很理想。 之后尝试增加了 features(每日 Open,High,Low,Close,Amount,Volume),效果依然不是很好。

通过对结果进行分析以及阅读研究一些研报,得到的初步结论为:一是 input 时间跨度太长(100 天的价格走势对未来一天的价格变化影响很小),而待预测数据时间跨度太短;二是收盘价(Close)是非平稳数据,LSTM 对于非平稳数据的预测效果没有平稳数据好。

LSTM 对沪深 300 未来五日收益率预测

综合以上两点,本文所使用的输入和输出为利用过去 30 天的数据预测将来五天的收益。

测试对象:沪深 300

数据选择和处理:

  • input 的时间跨度为 30 天,每天的 features 为 ['close','open','high','low','amount','volume'] 共 6 个,因此每个 input 为 30×6 的二维向量。

  • output 为未来 5 日收益 future_return_5(future_return_5>0.2, 取 0.2;future_return_5<-0.2, 取 – 0.2),为使训练效果更加明显,output=future_return_5×10; features 均经过标准化处理 (在每个样本内每个 feature 标准化处理一次)。

  • 训练数据:沪深 300 2005-01-01 至 2014-12-31 时间段的数据;测试数据:沪深 300 2015-01-01 至 2017-05-01 时间段数据。

  • 模型构建:鉴于数据较少(训练数据约 2500 个,预测数据约 500 个),因此模型构建的相对简单。模型共四层,为一层 LSTM 层 + 三层 Dense 层(图 3)。

  • 回测:得到 LSTM 预测结果后,若 LSTM 预测值小于 0,则记为 – 1,若大于 0,记为 1。

每个模型做两次回测,第一次回测(后文简称回测 1)为直接以 LSTM 预测值在沪深 300 上做单:若 LSTM 预测值为 1,买入并持有 5day(若之前已持仓,则更新持有天数),若 LSTM 预测值为 – 1,若为空仓期,则继续空仓,若已持有股票,则不更新持有天数;

第二次回测(后文简称回测 2)为以 LSTM 为择时指标,与 StockRanker 结合在 3000 只股票做单:若 LSTM 预测值为 1,则允许 StockRanker 根据其排序分数买入股票,若 LSTM 预测值为 – 1,若为空仓期,则继续空仓,若已持有股票,则禁止 StockRanker 买入股票,根据现有股票的买入时间,5 天内清仓;

1)future_return_5 是否二极化处理比较

对于 future_return_5 的处理分为两种情况,一种为直接将 future_return_5 作为 output 进行模型训练,二是将 future_return_5 二极化(future_return_5>0, 取 1;future_return_5<=0, 取 – 1),然后将二极化后的数据作为 output 进行模型训练。

两种处理方法的回测情况如图 4,图 5。由于模型每次初始化权重不一样,每次预测和回测结果会有一些差别,但经过多次回测统计,直接将 future_return_5 作为 output 进行模型训练是一个更好的选择。在本文接下来的讨论中,将会直接将 future_return_5 作为 output 进行模型训练。

2) 在权重上施加正则项探究

神经网络的过拟合:在训练神经网络过程中,“过拟合” 是一项尽量要避免的事。神经网络 “死记” 训练数据。过拟合意味着模型在训练数据的表现会很好,但对于训练以外的预测则效果很差。原因通常为模型 “死记” 训练数据及其噪声,从而导致模型过于复杂。本文使用的沪深 300 的数据量不是太多,因此防止模型过拟合就尤为重要。

训练 LSTM 模型时,在参数层面上有两个十分重要的参数可以控制模型的过拟合:Dropout 参数和在权重上施加正则项。Dropout 是指在每次输入时随机丢弃一些 features,从而提高模型的鲁棒性。它的出发点是通过不停去改变网络的结构,使神经网络记住的不是训练数据本身,而是能学出一些规律性的东西。正则项则是通过在计算损失函数时增加一项 L2 范数,使一些权重的值趋近于 0,避免模型对每个 feature 强行适应与拟合,从而提高鲁棒性,也有因子选择的效果;(若希望在数学层面了解正则项更多知识,参考《机器学习中防止过拟合的处理方法》) 。在 1) 的模型训练中,我们加入了 Dropout 参数来避免过拟合。接下来我们尝试额外在权重上施加正则项来测试模型的表现。

回测结果如图 6,加入正则项之后回测 1 和回测 2 的最大回撤均有下降,说明加入正则项后确实减轻了模型的过拟合。比较加入正则项前后回测 1 的持仓情况,可以看到加入正则化后空仓期更长, 做单次数减少 (19/17),可以理解为:加入正则项之后,模型会变得更加保守。

正则项的问题:经过试验, 对于一个 LSTM 模型来说,正则项的参数十分重要,调参也需要长时间尝试,不合适的参数选择会造成模型的预测值偏正分布 (大部分预测值大于 0) 或偏负分布,从而导致预测结果不准确,而较好的正则参数会使模型泛化性非常好 (图 6 所用参数训练出来的模型的预测值属于轻度偏正分布)。本文之后的讨论仍会基于未加权重正则项的 LSTM 模型。

3) 双输入模型探究

除了传统的 Sequential Model(一输入,一输出) 外,本文还尝试构建了 Functional Model(支持多输入,多输出)。前面提到的 features 处理方法丢失了一项重要的信息:价格的高低。相同的 input 处在 3000 点和 6000 点时的 future_return_5 可能有很大不同。因此,本文尝试构建了 "二输入一输出" 的 Functional Model: 标准化后的 features 作为 input 输入 LSTM 层, LSTM 层的输出结果和一个指标 – label(label=np.round(close/500)) 作为 input 输入后面的 Dense 层,最终输出仍为 future_return_5(图 7)。

回测结果如图 8。由回测结果可以看出,加入指示标后的 LSTM 模型收益率相对下降,但是回撤更小。LSTM 预测值小于 0 的时间段覆盖了沪深 300 上大多数大幅下跌的时间段, 虽然也错误地将一些震荡或上涨趋势划归为下跌趋势。或许这是不可避免的,俗话说高风险高回报,风险低那么回报也不会非常高,高回报和低风险往往不可兼得。

结论与展望

本文通过探究性地应用 LSTM 对沪深 300 未来五日收益率进行预测,初步说明了 LSTM Networks 是可以用在股票市场上的。
由于 LSTM 更适用于处理个股 / 指数,因此,将 LSTM 作为择时模型与其他选股模型配合使用效果较好。利用 LSTM 模型对沪深 300 数据进行预测并将结果作为择时信号,可以显著改善 stockranker 选股模型在回测阶段的回撤。

展望:由于个股数据量较少,LSTM 模型的可扩展程度和复杂度受到很大制约,features 的选择也受到限制(若 input 的 features 太多,而 data 较少的话,会使一部分 features 不能发挥出应有的作用,也极易造成过拟合)。将来我们希望能在个股 / 指数的小时或分钟数据上测试 LSTM 的性能。另外,将探究 LSTM 模型能否将属于一个行业的所有股票 data 一起处理也是一个可选的方向。

说明:由于每次训练 LSTM 模型权重更新情况不同以及 Dropout 的随机性,LSTM 模型的每次训练训练结果都会有差异。

附:

提示:由于 LSTM 涉及参数众多,目前我们还不能保证 LSTM 模型的稳定性, 本文所附回测结果均为多次训练模型后选取的较为理想的情况,目的是说明 LSTM 是可以应用于股票市场的以及将其作为择时模型是可能的。本文所述以及提供的代码仅供探究及讨论,若要形成一个在股票市场比较实用的 LSTM 模型,还需要在 features 选择、模型构建、模型参数选择以及调优等方面花费大量精力。

源代码:

LSTM Networks 应用于股票市场探究之 Sequential Model
LSTM Networks 应用于股票市场探究之 Functional Model

雷锋网