前面几节叙述了卷积神经网络在图像分类中的应用,本节将描述深度学习网络在诸如回归预测、自然语言处理等领域的应用。主要内容如下:
Drop Out
策略,以及Fast.AI
附加层架构分析。Drop Out
策略,以及Fast.AI附加层架构分析Drop Out
策略是一种避免过拟合的有效手段。在Fast.AI框架下,通过设置分类器构造函数的ps
参数,来启用Drop Out
功能:
learn = ConvLearner.pretrained(arch, data, ps=0.5, precompute=True)
learn
第二条语句会输出Fast.AI在已有的网络结构的基础上添加的网络层:
Sequential(
(0): BatchNorm1d(1024, eps=1e-05, momentum=0.1, affine=True)
(1): Dropout(p=0.5)
(2): Linear(in_features=1024, out_features=512)
(3): ReLU()
(4): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True)
(5): Dropout(p=0.5)
(6): Linear(in_features=512, out_features=120)
(7): LogSoftmax()
)
其中(0)和(4)BatchNorm
层的功能将在第七课中叙述。(1)和(5)为Drop Out
层,参数为p=0.5
,(2)(3)层构成一个全连接层后接ReLU
非线性输出的结构,(6)(7)层构成一个全连接层后接LogSoftmax
的输出层结构(之所以对Softmax
取对数,是出于精度的考虑)。
Drop Out
层的参数p
的作用是:对上一层产生的数值,以概率p
随机丢弃。如果丢弃的这些值和用于分类的特征非常相关,这样操作就会使得网络继续深究用于分类的其他特征,从而获取更为全面的信息,提高泛化能力。在进行丢弃的同时,保留下来的值要做相应倍数的放大,以保证不会因为丢弃一定比例的值而出现加权结果的系统性偏差。
Fast.AI框架中默认的设置为:第一个Drop Out
层的p
值为0.25,第二个Drop Out
层的p
值为0.5。这也是为什么验证数据集上的损失函数要小于训练数据集上的损失函数的原因,因为Fast.AI在训练时启用了Drop Out
,而在验证时则未做Drop Out
操作,这反应了Drop Out
在提高网络泛化能力方面的效果。
若要改变默认设置,可在学习器生成函数中传入二元数组ps
。若要禁用Drop Out
,可设置ps=0.
,此时附加层的结构为:
Sequential(
(0): BatchNorm1d(4096, eps=1e-05, momentum=0.1, affine=True)
(1): Linear(in_features=4096, out_features=512)
(2): ReLU()
(3): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True)
(4): Linear(in_features=512, out_features=120)
(5): LogSoftmax()
)
事实上,附加层的结构还可简化,设置xtra_fc=[]
,可得到如下结构:
Sequential(
(0): BatchNorm1d(1024, eps=1e-05, momentum=0.1, affine=True)
(1): Linear(in_features=1024, out_features=120)
(2): LogSoftmax()
)
通过xtra_fc
还能添加其他结构的网络层,如xtra_fc=[700, 300]
,可以得到如下附加层:
Sequential( (0): BatchNorm1d(1024, eps=1e-05, momentum=0.1, affine=True) (1): Linear(in_features=1024, out_features=700) (2): ReLU() (3): BatchNorm1d(700, eps=1e-05, momentum=0.1, affine=True) (4): Linear(in_features=700, out_features=300) (5): ReLU() (6): BatchNorm1d(300, eps=1e-05, momentum=0.1, affine=True) (7): Linear(in_features=300, out_features=120) (8): LogSoftmax() )
结构化数据是指那些以数据表形式存在的数据,如公司的财务报表、Facebook的用户信息等。其处理方式区别于诸如音频、图像、语言文本等非结构化的数据。
本节课程将基于Kaggle竞赛中的Rossmann数据集构建能够进行回归预测的深度学习网络。数据可在Fast.AI数据集中下载。
Rossmann是德国的一家日用品连锁超市。现需要预测超市两周内的销售情况,可利用的信息有产品推广情况、竞争对手信息、学校及地方假期、地理位置等。
从Fast.AI下载的数据比Kaggle官网下载的数据多了一些,如googletrend.csv等。这是由于课程所用数据已经按照当年该比赛中第三名的预处理流程清洗了一遍,并补充了一些诸如谷歌搜索趋势等的辅助数据。此外,还需做的工作是:修改各表中不是理想数据类型的字段,如布尔值要做0-1转换;通过Fast.AI的add_datepart()
函数处理日期,即标定每天是一周、一个月、一年的第几天等;把关联表联接在一起;填充未能获取的字段;等等。这部分内容可参看Fast.AI的Machine Learning课程。最终数据存储为feather
格式,这种格式可以使数据存储在RAM中,从而提高读写效率。
最终形成的数据表中包含多种字段,如商店编号、日期、一周中第几天、是否是假期、距离上次假期多少天、距离下次假期多少天、竞争店铺距离、谷歌趋势值、天气参数等。其中像竞争店铺距离、谷歌趋势值等属于连续变量;而像商店编号、一周中第几天则属于类型变量(Categorical Parameters
),其只能取若干阶值(levels
)。另外像年份这样的变量,虽然是一年一年连续变化,但若看成类型变量,则可以针对不同年份区别对待。而像气温这样的连续变化的变量,也可以通过划分区段转换成类型变量(尽管课程中并没有这样做)。划分字段后,调用pandas
相关函数,将其转化为对应的数据类型:joined[
,joined[
个人看法:类型变量和连续变量的划分更多体现的是对所建模型与实际应用场景是否契合的考量,也没有一定之规。
调用如下语句:
df, y, nas, mapper = proc_df(joined_samp, 'Sales', do_scale=True)
其中proc_df
表示process data frame
。“Sales
”指定了作为输出的字段,用于生成y
,并会被从表中删除;do_scale
表示将对数据进行归一化,即使之变成均值为0、方差为1的数据,所做的变换被记录在mapper
中,用于对测试数据做同样变换。proc_df()
还将处理缺失值,对于类型变量缺失值将会替换为0,对于连续变量,缺失值将会被替换为中值。
经过上述步骤之后,就可调用Fast.AI的接口,获取满足网络输入要求的数据:
md = ColumnarModelData.from_data_frame(PATH, val_idx, df,
yl.astype(np.float32), cat_flds=cat_vars, bs=128,
test_df=df_test)
其中yl
为销售量(归一化处理后)的对数;cat_flds
指定了哪些字段是类型变量。
然后获取预测器:
m = md.get_learner(emb_szs, len(df.columns)-len(cat_vars),
0.04, 1, [1000,500], [0.001,0.01],
y_range=y_range)
其中emb_szs
是针对类型变量的一个关键参数,标明了内置矩阵的维度;内置矩阵的原理后面会详述;0.04
指定了和内置矩阵相关的丢弃概率;1
为输出的尺寸;[1000, 500]
和[0.001, 0.01]
分别为两个附加层的尺寸和丢弃率(参见上节课的博文);y_range
限定了最终输出结果的数值范围。
神经网络的训练会采用反向传播算法,其涉及求导,因此,对于类型变量这样取值离散的输入,得做进一步的处理。一种策略就是使用向量替换类型变量,而向量的每个元素取值连续。例如,可采用7个长度相同的向量表示周一到周日。这里引入了超参数——向量长度,一个设置参考是,取类型变量取值个数的一半,但当类型变量取值太多时,可设置一个上限。比如表征一周七天的向量的维度就可设置为4。这一超参数对应于get_learner()
中的emb_szs
参数,而由类型参数的值所对应的向量组成的矩阵称为内置矩阵(Embedding Matrix
)。
之所以用向量而不是用单一数值代替类型变量,是因为使用向量可以提供更多的语义信息。以预测商品销量为例,周末和工作日可能是影响销量的一个因素,这一因素关联所有商品;另一方面,一周的某一天可能和特定商品关联紧密,比如,周五可能啤酒的销量会高。考量这两类因素的作用,使用单一值替换周几,表述力就会不足。
事实上,内置矩阵方法和一位有效编码(one-hot
)方法相互等效,但后者涉及了矩阵相乘,远没有查表取出对应向量的做法高效。
按照第二课中的通用步骤训练网络即可。
本节将介绍如何利用深度学习网络对IMDB的影评进行分析,判断好评差评。类似的应用场景还有:对冲基金舆情分析,即分析文章消息、Twiiter内容是否会导致市场波动;用户服务咨询,用以判断用户是否会接受相关服务协定;等。
我们首先需要一个能够读懂英文的语言模型。所谓语言模型,就是在输入若干字词后,能够输出一个接下来的字词。课程中将使用IMDB影评数据,训练一个网络,该网络能够在输入一小段前置语句后,生成段落。
所用数据为Large Movie Review Dataset,(注意,Fast.AI提供的数据和IMDB提供的数据略有不同,Fast.AI提供的数据将所有影评数据置入all
子目录下),下载解压后,其目录结构如下
imdbEr.txt imdb.vocab models/ README test/ tmp/ train/
在train
文件夹下,以pos
、neg
和unsup
目录区分了影评类别,每个影评文档是以独立的txt
格式存储的;all
中包含了所有文件,总共约5万篇,1700万词。影评得分为10分制,不低于于7分的判定为好评,不高于4分的判定为差评,其余为判定为中立。本例中不考虑中立的数据。
首先将影评文档进行分词,这会用到自然语言处理的一个第三方库spacy
,并需要其内置的模型en
。
pip install spacy
python -m spacy download en
以下是生成模型所需类型的数据的语句:
bs=64; bptt=70
TEXT = data.Field(lower=True, tokenize="spacy")
FILES = dict(train=TRN_PATH, validation=VAL_PATH, test=VAL_PATH)
md = LanguageModelData.from_text_files(PATH, TEXT, **FILES, bs=bs, bptt=bptt, min_freq=10)
参数bs
为数据块大小,bptt
是BackProp Through Time
的缩写,限定了一次处理的词数,也决定了后向传播的所作用的层数(本节所用的网络是循环神经网络),具体原理后续课程会介绍。
第二条语句生成一个torchtext
的field
对象,指定对其中的数据进行小写转换,并用spacy
进行分词。第三条语句生成训练集、验证集、测试集的路径字典,本例中并未划分验证集,将使用测试集作为验证集。第四条语句则是生成模型所需的数据,这一步之后,TEXT
对象将会携带一个词库TEXT.vocab
,该词库包含了分词结果中所有词频不低于min_freq
的不同的词,每个词会有一个对应的整数索引。而md
中的数据的组织形式如图3
所示,具体描述如下:将所有影评文档连接为词的长向量,然后划分为bs
段,将这bs
段堆积转置得到一个词矩阵,这个词矩阵中每一行的词都是上一行的词的下一个。而每次取出用于做训练的块的大小则由bptt
约定。每次迭代,torchtext
会在bptt
附近选择一个随机值,这种机制是为了实现像图片网络中那种数据随机化(shuffle
)的效果;但由于词与词之间的顺序关系是本节的应用场景所要处理事项,因此采用随机化块大小的策略。
本例中获取网络模型的代码如下:
earner = md.get_model(opt_fn, em_sz, nh, nl,
dropouti=0.05, dropout=0.05, wdrop=0.1, dropoute=0.02, dropouth=0.05)
learner.reg_fn = partial(seq2seq_reg, alpha=2, beta=1)
learner.clip=0.3
其中opt_fn = partial(optim.Adam, betas=(0.7, 0.99))
,即采用的优化方法为Adam
,后续课程会介绍相关原理以及后面参数的意义。em_sz=200
是内置矩阵的尺寸,即每个词将会被一个200维的向量表示;nh=500
是隐含层的大小;nl=3
是网络层数。在此,Fast.AI采用的模型是Stephen Merity的LSTM语言模型,get_model()
中的后面几个drop out
值都是该模型中与避免过拟合有关的参数,具体原理将在本系列课程的第七课中交代。learner.reg_fn
同样起到抑制过拟合现象的作用,其原理也会在第七课中有所论述。learn.clip
限定了梯度下降算法的步长。
训练模型后,调用learner.save_encoder()
存储模型,以用于后面的影评分类。
Fast.AI尚未提供较为便捷的相关接口,课程中给出的测试代码及说明如下:
# Set batch size to 1
m[0].bs=1
# Turn off dropout
m.eval()
# Reset hidden state
m.reset()
# Get predictions from model
res,*_ = m(t)
# Put the batch size back to what it was
m[0].bs=bs
其中m=learner.model
,t
为分词后、并用字词索引值表示的向量。最终达到的效果是输入"So, it wasn't quite was I was expecting, but I really liked it anyway! The best"
后,输出"film ever !
。
数据整理:
IMDB_LABEL = data.Field(sequential=False)
splits = torchtext.datasets.IMDB.splits(TEXT, IMDB_LABEL, 'data/')
第一条语句表明IMDB_LABEL
是一个没有序列关系的向量,用于存储每篇影评的标签。第二条语句使用torchtext
的splits()
函数来分割数据集,产生训练数据、测试数据、验证数据。torchtext
内置了针对IMDB
影评数据的一些接口。
获取满足输入要求的数据对象:
md2 = TextData.from_splits(PATH, splits, bs)
其中Fast.AI的TextData
模块可以从torchtext.splits()
的输出结果中构建满足输入要求的数据对象。
获取网络模型,进行训练:
获取模型的代码如下:
m3 = md2.get_model(opt_fn, 1500, bptt, emb_sz=em_sz, n_hid=nh, n_layers=nl,
dropout=0.1, dropouti=0.4, wdrop=0.5, dropoute=0.05, dropouth=0.3)
m3.reg_fn = partial(seq2seq_reg, alpha=2, beta=1)
m3.load_encoder(f'adam3_20_enc')
其中adam3_20_enc
即是前面所得到的语言模型。训练步骤与前面课程中的图像网络的训练过程大体相似。
add_datepart()
对日期进行处理,提取其在一周中、一月内、一年里的索引,否则不能发现数据中的周期性、季节性。rmspe()
实现了该值的计算,则在训练网络时,传入该函数的引用:learn.fit(lr, 3, metrics=[rmspe])
。lesson4-imdb.ipynb
中,在执行learner.load_cycle('adam3_10',2)
可能会提示模型找不到,这是由于这个notebook里的某些cells可能是课上加上的,并不是顺序执行的。可以先把这一句跳过。lang_model-arxiv.ipynb
展示了如何组织符合Fast.AI/torchtext规范的数据。is_numeric_dtype
错误的方法:使用版本高于0.23的pandas会出现这类错误。