作者:@Galaxyzeta + @DDDFaker
由于一开始对于 Python 处理结构化数据不是很熟,再加上拿到规模较大的数据集不知该从何处入手,算法研究小组决定先用Excel进行数据探索。
拿到数据的时候,发现有四张表格,均为csv格式,其中两张表格为各企业的年度数据,另两张表格是各企业的基本概况,由此发现此题目需要参赛者对多表数据进行合并。
(问题定性)
首先观察训练集数据,发现 flag = 1 或者 NA,而验证集中 flag 存在0、1、NA、blank几种情况,由此想到NA、blank是数据缺失,而0、1是我们要预测的值,由此该问题可以定性为二分类。而训练集中的flag = 1或NA,没有0,因此不能直接拿来训练,而且验证集的数据条目数远远大于训练集,若按照原集训练显然是不合理的,因此考虑将验证集数据集合并,然后按照一定的比例进行划分。
(特征工程)
查阅相关资料,结合题目要求,我们对什么是僵尸企业,如何划分僵尸企业有了初步的认知。文献对僵尸企业的描述多半为 “连续亏损,扭亏无望,靠政府资助”,描述十分模糊,没有具体的定量描述。根据这些描述,我们首先重点考虑企业的收支、盈利、负债、所有者权益以及各类总额状况,认为这些特征与预测结果具有强相关性。
经过多表数据的 Excel 融合,继续通过 flag 列筛选,观察僵尸企业/非僵尸企业的共性特征,结果发现知识产权、控制人持股比例的分布过于随机,因此暂时认为它们与预测结果具有弱相关性。
由于给出的原始数据分布差异十分明显,不在同一数量级,再加上不同企业的行业分类、地域、服务种类、企业性质等存在差异,所以不同企业的收支、负债情况显然是存在明显差异的,因此我们认为不能直接把部分原始数据套到模型中训练,而是应该计算相应的比例,再行训练。
(缺失值处理)
经筛选,表格中存在大量缺失值,因此对缺失值的处理显得尤其重要,对于缺失值,初步提出若干处理思路:
(小结)
经过初步分析,我们大致确定了计划:先进行数据预处理/特征工程,然后建立简单的机器学习模型进行训练。
一开始用的是 Excel 表格处理,但由于其面对大量数据的处理效率实在太低,再加上不是自动化的,后来全面转向 python pandas 数据处理,为了进一步提升效率和处理准确性,期间代码重写过多次。
为了方便地检验特征值相关性以及添加新地特征值,目前的数据处理是针对全数据的处理,为了特征值提取的方便,设计了填充/抛弃 NA 数据,仅提取 NA flag,任意获得输出的特征值列等功能。
函数定义:
def data_process(f1,f2,f3,f4,out,fillna:bool=False,flagna:bool=False)->None
其中,f1、f2、f3、f4是给出的 训练集/验证集csv,out 是输出csv,fillna 为 False 时,采取严格的数据筛选模式,即一遇到NA就舍弃,给定数据少于三年同样舍弃;fillna 为 True 时,采取尽可能准确的 NA 值填充策略。当flagNa 为 True 时,输出结果仅保留 Na 值 flag,供半监督学习使用;flagNa 为 False 时,输出结果舍弃 Flag = Na。
数据处理的步骤大致分为以下几点:
具体阐述:
表头手动替换成英文(后面我会把这个过程改成自动进行)、表中中文数据(交通运输业、有限责任公司等等)自动替换英文,这样可以避免中文处理导致的编码问题。
若存在ID不确定,直接去除(事实上没有这种情况的发生)。
若 flag = NA,不论是在训练集还是验证集,由于其不可用于全监督学习,故直接去除(函数设计了只保留NA值的方法,用于半监督提取数据用)。
根据企业属性的不同组合计算出平均值参考表,根据此表填充年报表格的 NA 值,知识产权表格默认填充0,融资额度/数值 表格的数据暂不填充,在计算后,若存在NA则填充平均值。
分组计算比例数据,主要有:
为表中的枚举属性值建立One hot编码列。
人工选择需要抛弃的特征值,且根据传入的参数,选择是否仅保留 NA Flag。
经过处理的数据与给定的原企业ID信息比对,找出在处理过程中丢失的ID,并将其写入文件,以便后续处理。(目前处理方式是将其flag直接定为0,在预测后将缺失的数据与已经预测的数据合并,然后再计算预测精度。)
关于数据预处理的效率:
对于10000条记录~50000条记录的单次处理可以在大约2秒内完成,效率还算不错。
研究模型时提出了多种方向:
多层感知机模型及其超参数设置:
重点研究和调整的是多层感知机模型,该模型可以表述如下:
Input -- BN -- Dense(X, relu) -- BN -- Dense(Y, relu) -- Dense(1, Sigmoid)
输入数据阶段,用 normalize() 进行 L1 正则化,目的有二:
此过程直接对数据进行,不包括在模型内。
(以下不保证准确,我不太清楚其中原理)
其中,BN 代表 Batch Normalization,批规范化。引入批规范化的优势在于:
批规范化不宜过多,尤其是这种层数不多的模型,批正则化过度会导致训练精度上不去。
Dense是中间隐层,可以根据需要增加或减少。增加 Dense 层深度和特征维度能加快模型收敛速度,同时增加模型复杂度和过拟合风险,实验结果表明两层中间隐层的效果是相对最好的。X设置为4,Y设置为8是在有BN层的前提下,否则收敛速度过慢。
使用 Relu 函数有对抗梯度消失、加速收敛、计算效率高等诸多好处。
最终层采用 Sigmoid 激活函数输出分类预测概率,理论上,二分类问题采用 sigmoid 和 softmax 是等价的,但根据网友实践,二分类问题采用 sigmoid 实际效果更好。而且 sigmoid 作为输出层激活函数,只需要1个输出层神经元,比起 softmax 的两个神经元,减少了少量不必要的计算。
损失函数选择了均方损失误差(mean squared error),选择二分类交叉熵(binary cross entrophy)也可,但经过测试,感觉MSE更可靠。
模型优化器采用SGD,这种优化器是所有优化器中收敛速度相对最慢的,但好处是结果准确可靠,以下是一些超参数调节:
训练
暂时采取四特征值作为训练依据,即:
参与训练的数据为原训练集全体Flag = 1的数据,加上等量从验证集获得的Flag = 0的数据(取出之后对应的行会被删除,保证训练与验证的数据是完全不同的),训练之前数据经过了随机排列。这样安排主要是考虑到验证集中完整的数据多余经过NA值填充的数据,用完全 “干净” 的数据训练比拿 “经过少量污染” 的数据训练效果更好。
训练epoch预设1000,实际上大约50epoch,模型就已经收敛,在训练过程中自动记录 Accuracy 最高的模型,作为最终训练的模型。训练的数据集为经过严格筛选的数据(3年完整不含NA数据),训练过程中取经过NA值填充的数据记录准确度。
最终四特征值下得到的模型在划分的测试集下,得到98.7%的准确度,随机抽样测试多次,准确度几乎不变,从而初步检验了模型的稳定性和准确性。
关于半监督学习
由于训练集中存在大量NA值数据。验证集中也存在部分NA值数据,由此想到可以将这些数据提取出来,用已有模型对其打 “伪标签”,然后再将这些数据添加到训练集中重新训练,这属于 “数据增强” 的一种手段。但经过尝试,增强效果几乎没有体现。
模型的打标签效率
万级别数据打标签大约2s即可完成,其中不包括 tensorflow 预加载的过程。
(这部分真不知道该怎么写,调参靠感觉,我不记得有一个明确的调参过程)
一开始已经确定要用多层感知机模型,调参主要集中在特征工程,模型修改,超参数调节三个方面:
特征工程调节
一开始尝试把给出的所有标签数据全部传入模型进行训练,但效果非常不好。后来根据数据之间的关系,分别衍生出更多看似合理的标签,逐渐减少特征,最终发现四特征值下模型的表现是最好的。
有时候训练 loss 不降反升,就是数据预处理出现问题,其中可能包含 inf / NA 等数据;有时训练 loss 波动特别的大,是 NA 值填充错误导致的。
个人认为在本场景下,数据的处理比模型的调节更为重要。
模型调节
主要体现在是否使用 Batch Normalization,需要多少 dense Layer,每个dense Layer 怎么设置神经元的个数这几个问题。
把Batch Normalization 去掉,将会严重影响训练收敛速度,通过增加每层神经元个数,虽然速度有一定程度提高,但训练到准确度较高时,损失函数几乎不下降,精度也难以提升。
想到增加 Batch Normalization 主要是之前在 CNN 图片分类时使用过,效果很明显。增加 BN 后,每层神经元个数可以大量下调,但训练速度依旧非常快,且不会影响最终训练的精度。
关于dense Layer的选择:尝试过1、2、3、4层中间隐层,4层中间隐层收敛速度极快,但一段时间后训练过拟合,验证集 loss 快速上升。1层中间隐层因不能很好表达特征,最后结果没有2层中间隐层好。
关于每层应该设置几个神经元:和dense Layer选择同理,主要考虑收敛速度和模型复杂度之间的关系。
超参数调节
采取吴恩达机器学习视频中的3倍率二分法调参,每次调参结束后绘制 loss - acc 曲线,对比后确定了合适的学习率。由于恒定学习率对于模型后期的收敛是不利的,因此考虑设定动态变化的学习率,即经过一个 epoch 学习率衰减指定值。测试发现 2e-5 到 3e-5 的衰减是比较好的。