我的机器学习part(2020上半年)
大家一辈子都在找规律,找诀窍:找工作的规律、找赚钱的规律、找恋爱诀窍…为了生活的美好。
所谓机器学习,就是设计代码让机器从数据中帮我们找规律。
1.整体记忆
个人理解,抛砖引玉,希望通俗,会不那么严谨。
1.1 类别
大致分为有监督学习(给定标签训练,对无标签数据进行预测打上标签,分类、回归)、无监督学习(给无标签数据,找内在结构,聚类、降维)、强化学习(学习如何选择一系列行动,以最大化长期收益)这3路。
1.2 有监督/无监督
有监督和无监督:以学英语为例。有监督学习就是看中英对照文字,时间长了可以看纯英文,你会自己理解到意思。无监督学习是直接看纯英文文章,虽然不能完全理解文章的含义,积累一定量之后,也会发现一些词组、句式的固定搭配等。
以分类为例来看,目前,有监督学习的效果还是不错的,相对来讲,无监督学习比如聚类就差一些,从上述例子就可以看出来,有答案不断给你纠错和没答案的摸索最终的结果会有所差距。不过无监督的降维等信息压缩还是用处显著的。
但是有监督学习有个致命的缺陷,标注数据的成本可能会比较高,所以一般是有监督夹杂无监督一起使用。比如100w样本全部人工标注比较难,可以随机标准1w个,整体聚类,然后根据1w个在各个簇的表现进行粗标注。
1.3 有监督:回归/分类
有监督就是对一系列样本 ( x , y ) (x, y) (x,y)构建 f ( x ) → y , x ∈ A , y ∈ B f(x)\rightarrow y,x\in A,y\in B f(x)→y,x∈A,y∈B。
(分类和回归的区别上,以下知乎上找的回答,表述的很清楚,符合我实际经历中的抽象问题的感觉,直接引用了:分类与回归区别是什么? - 陶韬的回答 - 知乎
https://www.zhihu.com/question/21329754/answer/204957456)
回归与分类的根本区别在于输出空间是否为一个度量空间:
对于回归问题,其输出空间 B B B是一个度量空间,即所谓“定量”。也就是说,回归问题的输出空间定义了一个度量 去衡量输出值与真实值之间的“误差大小”。例如:预测一瓶700毫升的可乐的价格(真实价格为5元)为6元时,误差为1;预测其为7元时,误差为2。这两个预测结果是不一样的,是有度量定义来衡量这种“不一样”的(于是有了均方误差这类误差函数)。
对于分类问题,其输出空间 B B B不是度量空间,即所谓“定性”。也就是说,在分类问题中,只有分类“正确”与“错误”之分,没有这个大小的区别,至于错误时是将Class 5分到Class 6,还是Class 7,并没有区别,都是在error counter上+1。
本质上,都是feature与label的映射。 实际问题建模的时候,就根据label的是否可度量来做回归还是分类。
生活和生产中大部分问题,其实都可以看做一个分类/回归问题。冥冥之中,我们用的最多:比如成功的人有哪些特点、失败的人有哪些缺点,我们学优点改缺点;比如哪些行为讨妹纸喜欢、哪些让人讨厌,学好不学坏;比如根据这个人收入水平、按时还款比例来预测违约的可能性有多大等等。
1.4 无监督学习
主要是两个东西:一个是聚类,一个是以主成分分析、Autoencoder等为代表的信息压缩。其实聚类的簇信息,也算是信息压缩的一种表达吧?
这里引用一下另一个同学的回答,对他的观点甚是同意。什么是无监督学习? - 微调的回答 - 知乎
https://www.zhihu.com/question/23194489/answer/913424307
有监督和无监督的中间数据,都是对初始特征的加工、提炼、压缩,区别就是是否有一个label来进行适应和反馈。
用VGG、ResNet利用ImageNet数据集有监督学习训练的模型,去做图片的encode,中间层不就是信息提取、压缩么。
聚类算法及在大数据上的实践之前有过一些:
比如spark上面的K-means其实是K-means++,距离计算层面做了一些优化(重复计算的中间值存储、判断距离大小的不等式简化计算),也有他的不足:
大数据量的时候有点凉,这时候就要设计mini-batch版本的K-means;
初始点不支持自己定义(半监督学习的味道),就需要自己动手了;
仅支持欧式距离,自定义距离度量目测就得改源代码了…
另外,实践中有一些不得不考量的问题:
簇的选择、数据是否归一化、初始点的选择、距离度量方式的选择等;
K-means还是密度聚类、谱聚类/图聚类这种;
大数据背景下不同聚类方法复杂度(时间、空间)效率考量…
后续开一个博客补上这一块的感受,解决实际问题的时候,效率、操作、效果都不是书面上的那么简单。
1.5 强化学习
纸上得来终觉浅,绝知此事要躬行…等我实践几下project再来补上这一块的小结。
2.实际生产中的感觉
2.1 问题抽象
项目的实际问题是什么,需不需要转化为机器学习问题,分类、回归、信息压缩?
是不是有其他简单办法就可以解决?
适合机器学习问题解决,是分类还是回归?
这个 y y y到底是啥,有些项目中这个 y y y比较明显,有些项目中则需要你去从业务逻辑中去挖掘,比如是预测用户购买某个产品的概率大小还是购买能力的衡量,哪一个和实际问题更加接近。
正负样本的构建中,样本不足时,哪些是业务意义上的相近样本,来扩充正、负,而不是简单的上采样、下采样。比如对于续费意愿来说,续费的是正,不续费的是负,那么续费金额大、时间长的和其他续费用户的label需要做出差异么;未购买,但是反复切换到续费页面的是不是label上也做些处理;不续费的里面,投诉或者明显负反馈的label是不是作出差异更好?
评价标准是啥,这个业务是侧重准确还是召回,他们对应的实际影响是啥,最重要的目标是什么。
怎么去验证,回测、开A/B实验、抽样评估?
2.2 特征工程
现在高级的模型有强大的选择能力、非线性组合能力。
个人感觉,在深度模型大数据的背景下,特征工程的意义发生了变化:原来的特征组合、选择这部分功能,可以用模型来承担,现在更大的作用引入增量信息和人工经验
比如城市名称可能直接用是一个不重要的特征,但是转化为一线、二线等城市等级数值,变相加入城市的经济水平等信息;手机号是一个不重要的特征,但是转化为是否靓号,可能就更有效。
2.3 模型选择和优化
最近做的少了,简而言之,复杂的模型效果更好,代价就是计算力,**效率始终是实际生产时不得不考虑的东西。**此处不展开先,最近的关注点没在这上面。
3.常用框架与我的实际经历
3.1 tensorflow/torch/paddle等
没啥多说的啦,毕竟我这种菜鸡都见了不少。
我记得我最开始的写的torch还是用Lua写的,后来有了pytorch,方便不少。
基于cpu、gpu的训练也都是遍地教程进阶。
训练、预测都不是问题。比较蛋疼的是DeepLearning与大规模ETL的结合,举个例子,在实际的spark etl任务里加入图像resnet分类、bert语义分类等。
之前的做法是拆开来,resnet、bert等涉及到深度学习部分就写python,用GPU服务器/mpi集群预测;剩余的部分就是scala代码在spark上面跑。
ETL由一套python代码+一套scala代码构成,两个环境,两个子任务阶段,用起来不太舒适
虽说有tensorflow on spark、spark torch,但是pyspark目前这个性能,以及上面tf、torch的支持情况,emmm…再等等看?
有这种难受的应该不止我一个,要是能用一套scala写个pipline多香啊,intel家的bigDL能缓解这种不舒服,目前用到了这一款。
3.2 spark.ml
自带的机器学习库,常规的分类、回归、聚类等都还行。
模型大了好像训练有问题,我记得之前训练文本分类lr,如果模型太大,确实会跑得慢,也会容易挂,内存罩不住。
拆开来看:
效率方面,treeAggregate 聚合梯度时,如果模型维度达到亿级,每个梯度向量都可能达到几百兆;此时 treeAggregate 的 shuffle 的效率非常低。
资源方面,记得内存要求也很大,以L-BFGS为例,每轮迭代,driver 都需要和 executor 完成高维度向量的 aggregate 和 broadcast;two-loop recursion 算法是由 driver 单点执行,该过程是多个高维度的向量的运算;将最近 m 轮生成的 { y k } \{y_k\} {yk} 和 { s k } \{s_k\} {sk} 序列存在driver。
然后这方面,有Spark on Angel 解决这个问题(https://cloud.tencent.com/developer/article/1005581),引入 PS 的角色,把two-loop recursion 算法的运算交给 PS,而 driver 只负责任务的调度,大大减轻的对 driver 性能的依赖,腾讯家的,貌似,算是工程上的优化。
没亲身用过,有听说周围有人用。
3.3 spark+bigDL(这次主要记录这个)
bigDL,官方链接在此:1.https://software.intel.com/content/www/cn/zh/develop/articles/bigdl-distributed-deep-learning-on-apache-spark.html,
2.https://bigdl-project.github.io/,
3.https://github.com/intel-analytics/BigDL
(下面截图来自于官网)
最舒适的地方就是,将DL模型舒适的放在spark运行的数据ETL里面, 刚实践了一阵,感受一下,快乐就完事儿:
package com.intel.analytics.bigdl.models.resnet
import java.nio.ByteBuffer
import com.intel.analytics.bigdl.dataset.DataSetWj.SeqFileFolder.readLabel
import com.intel.analytics.bigdl.transform.vision.image.{BytesToMat, DistributedImageFrame, ImageFeature, ImageFrame, ImageFrameToSample, MatToTensor}
import com.intel.analytics.bigdl.utils.{Engine, T}
import org.apache.hadoop.io.Text
import org.apache.spark.sql.{DataFrame, SQLContext, SparkSession}
import com.intel.analytics.bigdl.nn.{BatchNormalization, Container, CrossEntropyCriterion, MSECriterion, Module}
import com.intel.analytics.bigdl.dataset.Sample
import com.intel.analytics.bigdl.dlframes.{DLClassifierModel, DLModel}
import com.intel.analytics.bigdl.transform.vision.image.augmentation.{CenterCrop, ChannelNormalize, Resize}
import com.intel.analytics.bigdl.transform.vision.image.opencv.OpenCVMat
import org.apache.spark.rdd.RDD
import com.intel.analytics.bigdl.tensor.Tensor
import org.apache.spark.sql.functions._
object Test_PredictCaffee {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().getOrCreate()
import spark.implicits._
//load caffee模型
val model_pretrained = Module.loadCaffeModel[Float]("xxxxxx/resnext50_deploy.prototxt",
"xxxx/resnext50.caffemodel")
Engine.init//设置并发,batchsize
val valid_seq_path = "hdfs://xxx/val"
//加载opencv库
nu.pattern.OpenCV.loadShared()
//图像的SequenceFile读取,生成RDD[ImageFeature]
def imageSeq2tensor(path: String): RDD[ImageFeature] = {
//.sequenceFile 可以传入partition参数
val rawData_0 = spark.sparkContext.sequenceFile(path, classOf[Text], classOf[Text]).
map(image => {
val rawBytes = image._2.copyBytes()
val label = 0.0f //readLabel(image._1, 0).toFloat//readLabel(image._1, 0).toFloat //0.0f //图片name分割还原后第1个字段
val uri = image._1.toString//readLabel(image._1, 1)//readLabel(image._1, 1) //image._1.toString // 图片name分割还原后第2个字段
val imgBuffer = ByteBuffer.wrap(rawBytes)
val width = imgBuffer.getInt
val height = imgBuffer.getInt
val bytes = new Array[Byte](3 * width * height)
System.arraycopy(imgBuffer.array(), 8, bytes, 0, bytes.length)
val imf = ImageFeature(bytes, label, uri)
val bytes_f = bytes.map(x => (x & 0xFF).toFloat)
val mat_0 = OpenCVMat.fromFloats(bytes_f, 256, 256, 3) //.fromImageBytes(bytes)
//var mat_1:OpenCVMat = mat_0 //var mat_2:OpenCVMat = mat_0 //Imgproc.resize(mat_1, mat_2, new Size(224, 224))//先省略CenterCrop
//ChannelNormalize.transform(mat_2, mat_2, Array(123, 117, 104), Array(1, 1, 1)) //val data = new Array[Float](224 * 224 * 3) //OpenCVMat.toFloatPixels(mat_2, data)
imf(ImageFeature.mat) = mat_0
imf(ImageFeature.originalSize) = (height, width, 3)
imf
})
rawData_0
}//val tt = rawData.first() //tt.getLabel[Float] //tt.uri //tt.bytes
//读取valid数据,RDD[ImageFeature] => ImageFrame的分布式类
val rawData_1 = imageSeq2tensor(valid_seq_path)
val rawData_ImageFrame = ImageFrame.rdd(rawData_1)//类型转为分布式的ImageFrame,可用toDistributed.rdd转回来
//预测方式1,dataframe模式,还没细看源码,还有些问题需要解决,这里的batchsize设置后会咋样,先搞定方式2吧
// val model = Module.
// loadCaffeModel[Float]("xxx/resnext50_deploy.prototxt",
// "xxxl/resnext50.caffemodel")
//val imageFrame: ImageFrame = ImageFrame.read(path, sqlContext.sparkContext)
//val images_cifar = spark.sparkContext.binaryFiles("hdfs://xxxx/data_batch_1.bin").
// map { case (p, stream) => ImageFeature(stream.toArray(), uri = p)}
//val images_cifar_mat = ImageFrame.rdd(images_cifar) -> BytesToMat()
//val imageFrame: ImageFrame = rawData_ImageFrame -> BytesToMat()//Resize(256, 256) ->
val transformer = Resize(256, 256) -> CenterCrop(224, 224) -> ChannelNormalize(123.68f, 116.779f, 103.939f, 255f, 255f, 255f) -> MatToTensor[Float]() -> ImageFrameToSample[Float]()//4.8253f, -7.25567f, -14.21365f, 73.639f, 73.2976f, 59.635f
//val transformer = Resize(256, 256) -> CenterCrop(224, 224) -> ChannelNormalize(-24.061f, -11.22f, -4.32f, 255f, 255f, 255f) -> MatToTensor[Float]() -> ImageFrameToSample[Float]()
val transformed: ImageFrame = transformer(rawData_ImageFrame)
// val imageRDD = transformed.toDistributed().rdd.map { im =>
// (im.uri, im[Sample[Float]](ImageFeature.sample).getData())//im.getLabel[Float]
// }
// val imagesDF = imageRDD.toDF("imageName", "features")
// Engine.init
// imagesDF.show(10)
// imagesDF.printSchema()
// val dlmodel: DLModel[Float] = new DLClassifierModel[Float](
// model, Array(3, 224, 224)).setBatchSize(300).setFeaturesCol("features").setPredictionCol("prediction")
// val count = imagesDF.count().toInt
// val tranDF = dlmodel.transform(imagesDF.limit(count))
// tranDF.select("imageName", "prediction").show(100, false)
//val num = partitionNum.getOrElse(Engine.nodeNumber() * Engine.coreNumber())
//预测方式2,rdd结构,现在已经ok,确实速度上还可以
val result = model_pretrained.predictImage(transformed)
val result_parse = result.toDistributed().rdd
val result_parse_df = result_parse.map(img =>{
val uri = img.uri
val label = img.getLabel[Float]
val pred_value = img.predict().toString.split("\n")(0).toFloat//.toFloat//first()[Tensor[Float]]("predict")
val diff = (label-pred_value)*(label-pred_value)
(uri, label, pred_value,diff)
}).toDF("uri", "label", "pred_value", "diff")
}
}
pom主要添加一下这俩,其他的缺啥类报错补啥…
<dependency>
<groupId>com.intel.analytics.bigdl</groupId>
<artifactId>bigdl-SPARK_2.4</artifactId>
<version>0.11.0</version>
<exclusions>
<exclusion>
<artifactId>commons-lang3</artifactId>
<groupId>org.apache.commons</groupId>
</exclusion>
<exclusion>
<artifactId>commons-collections</artifactId>
<groupId>commons-collections</groupId>
</exclusion>
<exclusion>
<artifactId>plexus-utils</artifactId>
<groupId>org.codehaus.plexus</groupId>
</exclusion>
<exclusion>
<artifactId>breeze_2.11</artifactId>
<groupId>org.scalanlp</groupId>
</exclusion>
<exclusion>
<artifactId>javax.servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.openpnp</groupId>
<artifactId>opencv</artifactId>
<version>3.2.0-1</version>
</dependency>
4.结语
文不对题,谅解。
岁月将自己的见识浅薄、自负、心态急躁的轮廓勾勒的更加明显,也给开了一扇光亮的窗,看到自己心底的善念和坚强;
也许生活有很多挫折,社会亦很残酷,让人时不时有点厌世的灰暗,但也有很多感动与美好,激情与悠扬;
既然活着,便努力去活,认真去过。
回到正题,后面会看看写聚类/强化学习相关吧,说不准。