TensorFlow Template Applications
作者: 陈迪豪,小米深度学习工程师,目前负责小米云深度学习平台架构与开发。
模板应用介绍
TensorFlow template applications表示通用的TensorFlow应用代码,用户通过复用这些模板大大降低了编写TensorFlow模型的难度和成本。
由于模型训练数据一般都是稠密的CSV格式,或者稀疏的LIBSVM格式,这些数据都可以转成TFRecords格式,通过一个通用的模型应用加上不同的超参数组合就可以实现不同的TensorFlow应用模型,用户从数据转化、模型训练到在线预测甚至不需要编写一行代码。
我们在Github上开源了 deep_recommend_system 模板项目,已经支持任意稠密数据和稀疏数据的训练,集成Python和Spark生成TFRecords训练样本,支持模型导出、模型服务和跨语言的gRPC客户端,通过超参数指定模型为LR、MLP、Wide and deep或CNN结构和是否开启Dropout、Batch normalization、Learning rate decay等特性。
稠密数据模板
目前我们模板已经支持稠密的样本特征,只需要将原始数据保存为CSV格式,通过提供的Python脚本或Spark应用就可以转化和训练模型了,计算机视觉用到的图片也可以转化为稠密数据处理。
威斯康星癌症数据
在 data/cancer 我们提供了维斯康星大学提供的已标记癌症数据,原始数据以CSV格式保存,其中前9列为features,最后一列为label。
10,10,10,8,6,1,8,9,1,1
6,2,1,1,1,1,7,1,1,0
2,5,3,3,6,7,7,5,1,1
10,4,3,1,3,3,6,5,2,1
6,10,10,2,8,10,7,3,3,1
对于通用的模板应用,我们可以直接读取特征和标签而忽略数据代表的物理含义,通过TensorFlow提供的Python API可以在本地将CSV格式数据转化成TFRecords格式,然后利用QueueRunner和Coordinator实现多线程批量读取数据的随机梯度下降训练。
python generate_csv_tfrecords.py
如果训练样本过大,使用Python效率显然是比较低的,因为我们基于TFRecords的Protobuf接口实现了Scala应用,可以提交任务到Spark进行分布式处理。对于标准的CSV数据格式或LIBSVM格式数据可以直接使用,非标准的CSV数据或用户定制的格式需要修改Scala的解析代码再提交。
mvn clean package
$SPARK_HOME/bin/spark-submit --master yarn-client --num-executors 1 --class com.tobe.data.GenerateSparseTfrecords --conf spark.speculation=false predict-1.0-SNAPSHOT.jar hdfs://deep_recommend_system/data/cancer/cancer_train.csv hdfs://deep_recommend_system/data/cancer_tfrecords
有了标准的TFRecords训练数据,我们可以直接启动dense_classifier.py来训练和导出模型,根据不同业务输入的特征维度和标签维度就可以直接训练。
./dense_classifier.py --feature_size 9 --label_size 2
Iris数据
针对特定数据集的处理可以通过修改TensorFlow应用代码来实现,而模板应用的优势在于不需要修改任何一行代码就可以直接训练新的模型,并且提供相同的使用接口和完整的LR、MLP、Wide and deep、Dropout、Batch normalization、Learning rate decay等功能。
在机器学习上有个经典例子就是处理Iris数据集,我们从Wikipedia下载了完整的训练数据和验证数据到 data/iris 中,数据格式也是通用的CSV格式,其中第1维是label,后面4为是features。
0,4.8,3.4,1.9,0.2
1,5.7,2.8,4.1,1.3
0,5.3,3.7,1.5,0.2
2,6.7,2.5,5.8,1.8
0,4.8,3.1,1.6,0.2
和维斯康星大学癌症数据一样,我们可以使用开源的通用Python脚本和Spark应用来读取CSV格式并且生成标准的TFRecords格式数据。
python generate_csv_tfrecords.py
$SPARK_HOME/bin/spark-submit --master yarn-client --num-executors 1 --class com.tobe.data.GenerateSparseTfrecords --conf spark.speculation=false predict-1.0-SNAPSHOT.jar hdfs://deep_recommend_system/data/iris/iris_train.csv hdfs://deep_recommend_system/data/iris_tfrecords
TFRecords统一了TensorFlow应用读取训练数据的接口,支持glob匹配文件名队列,并且多线程批量读取样本数据,支持顺序读取和shuffle随机读取,通过epoch参数配置读取样本数据的次数,对于大规模样本的训练尤其是深度学习非常有帮助。通过TFRecords对数据的抽象,我们已经实现的dense_classifier.py不仅可以处理前面的癌症数据,对于Iris数据集,不需要修改任何一行代码就可以直接运行,还可以在运行时指定不同的深度模型。
./dense_classifier.py --feature_size 4 --label_size 3 --train_tfrecords_file ./data/iris/iris_train.csv.tfrecords --validate_tfrecords_file ./data/iris/iris_test.csv.tfrecords --model wide_and_deep
肺癌数据
对于传统的推荐或分类问题,使用标准的CSV格式也许就够了,而对于图像,是否也可以使用同一个TensorFlow template application来训练呢?答案是肯定的,我们从Kagggle的 2017 Data Science Bowl 竞赛中,下载了约70G的DCM样本数据,DCM是包含了用户肺部CT图片的医疗专用格式,通过脚本可以转成稠密的CSV格式并生成TFRecords。由于样本数据量脚本,我们直接使用Spark处理,而预处理的Scala代码竟然不需要任何修改,只需要传入原始数据目录和生成数据目录即可,足以看出标准数据格式和通用模板应用的强大之处。
$SPARK_HOME/bin/spark-submit --master yarn-client --num-executors 1 --class com.tobe.data.GenerateSparseTfrecords --conf spark.speculation=false predict-1.0-SNAPSHOT.jar hdfs://deep_recommend_system/data/lung/*.csv hdfs://deep_recommend_system/data/lung_tfrecords
有了TFRecords数据,我们可以继续使用dense_classifier.py进行训练,考虑到这是对图片数据进行分类的场景,我们直接直接传入超参数选择CNN网络模型。如果大家没有Spark环境,我们也提供了Python脚本生成基本的TFRecords文件进行训练和预测。
./dense_classifier.py --train_tfrecords_file ./data/lung/train.dcm.csv.tfrecords --validate_tfrecords_file ./data/lung/validate.dcm.csv.tfrecords --feature_size 262144 --label_size 2 --batch_size 1 --validate_batch_size 1 --model cnn
在整个Data Science Bowl的模型训练和预测过程中,我们修改TensorFlow应用程序的任何一行代码,将模型需要的特征维度和标签维度都作为超参数传入,如果用户使用MLP模型,甚至可以传入“128 64 32 16 8 4”这样的参数表示使用6层全连接网络和每一层网络的隐节点个数,由模板文件动态生成用户需要的模型结构。
由于TensorFlow的Graph也就是模型是通过DAG有向无环图描述的,Graph的结构可以用Python的分支和循环实现,因为也必然也可以通过超参数的方式来控制和实现,这也是TensorFlow template applications有可能实现的理论基础。
稀疏数据模板
前面介绍了稠密数据的模板应用dense_classifier.py,而对于稀疏数据,我们不能通过补零的方式作为稠密数据来训练,需要避免存储空间的浪费和低效的高维矩阵乘法。在TensorFlow中稀疏数据一般使用SparseTensor来表示,在第一层中使用embedding_lookup_sparse实现高效的“矩阵乘法“,而在后面的网络结构就和稠密数据的网络结构是一样的,因此为稀疏数据提供通用模板应用也是可能的。
对于稀疏数据,一般会使用LIBSVM格式来表示,通过TFRecords提供的Protobuf接口,我们也可以使用Python或Spark应用来将LIBSVM格式数据转成TFRecords格式,生成后的数据也可以作为TensorFlow应用的标准数据来训练和预测。
a8a数据
为了实现通用的稀疏数据模板应用,我们下载了 data/a8a 数据集进行训练和验证,在标准的数据格式中第一列是label,而后面空格分隔了多组feature id和feature value的值。
0 5:1 6:1 17:1 21:1 35:1 40:1 53:1 63:1 71:1 73:1 74:1 76:1 80:1 83:1
1 5:1 7:1 17:1 22:1 36:1 40:1 51:1 63:1 67:1 73:1 74:1 76:1 81:1 83:1
1 2:1 6:1 14:1 29:1 39:1 42:1 52:1 64:1 67:1 72:1 75:1 76:1 82:1 83:1
1 4:1 6:1 16:1 19:1 39:1 40:1 51:1 63:1 67:1 73:1 75:1 76:1 80:1 83:1
1 2:1 11:1 15:1 19:1 39:1 40:1 52:1 63:1 68:1 73:1 74:1 76:1 80:1 90:1
只要数据符合LIBSVM标准格式,就可以直接使用开源的Python脚本来转化成TFRecords的格式,在TensorFlow应用代码中直接读取SparseTensor进行训练了。
python generate_libsvm_tfrecord.py
对于大规模数据集,使用Spark可以极大加速数据的预处理和生成,和稠密数据类似,我们已经开源了对应的Scala代码,把数据保存到HDFS分布式存储中,直接编译生成jar包提交到Spark即可,整个过程都不需要因为数据集维度不同而修改任何的代码。
$SPARK_HOME/bin/spark-submit --master yarn-client --num-executors 1 --class com.tobe.data.GenerateSparseTfrecords --conf spark.speculation=false predict-1.0-SNAPSHOT.jar hdfs://deep_recommend_system/data/a8a/a8a_train.libsvm hdfs:://deep_recommend_system/data/a8a_tfrecords
有了标准的TFRecords格式数据,我们就可以使用模板应用sparse_classifier.py来训练和导出模型了。对于不同的数据集或模型,我们一样可以通过超参数的方式传入特征维度和标签维度来动态定义第一层神经网络的输入格式。
./sparse_classifier.py --feature_size 124 --label_size 2
对于通用的稀疏数据的模型训练,我们还可以参考sparse_classifier.py的源码,通过超参数来控制模型的结构、开启Dropout、Batch normalization和Learning rate decay等特性。并且使用通用的模板应用,可以直接导出标准的TensorFlow模型文件,使用TensorFlow Serving加载,直接使用 deep_recommend_system 已经开源的Python/Java/Scala/Golang等gRPC客户端进行预测服务。
总结
最后总结下,TensorFlow template applications概念并不表示全新的框架或者类库,而且我们在生产实践中总结得到的通用的应用模板,通过统一的代码模板不仅简化了用户的使用和入门成本,而且规范了数据处理、参数训练、模型导出和在线预测完整的解决方案。
TensorFlow提供了代码生成DAG图的方式,并且支持抽象的TFRecords训练数据格式,让标准的数据格式如CSV和LIBSVM可以直接使用已有的工具转化和训练,不写一行代码就可以进行深度学习模型训练和预测成为可能,尤其在Cloud Machine Learning场景下可以让更多数据科学家参与到深度学习的浪潮中。而通过超参数来定义神经网络模型对于learning to learn场景也很有意义,使用增强学习的Exploration/Exploitation策略让开发者更快捷地得到最优的超参数组合和模型架构。
如果对更多TensorFlow技术感兴趣欢迎关注我的微博: tobe-陈迪豪 。
Report Story