我的机器学习-part(2020-1)【有监督/无监督、Spark+BigDL】


大家一辈子都在找规律,找诀窍:找工作的规律、找赚钱的规律、找恋爱诀窍…为了生活的美好。

所谓机器学习,就是设计代码让机器从数据中帮我们找规律。

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,xA,yB

(分类和回归的区别上,以下知乎上找的回答,表述的很清楚,符合我实际经历中的抽象问题的感觉,直接引用了:分类与回归区别是什么? - 陶韬的回答 - 知乎
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.结语

文不对题,谅解。

岁月将自己的见识浅薄、自负、心态急躁的轮廓勾勒的更加明显,也给开了一扇光亮的窗,看到自己心底的善念和坚强;
也许生活有很多挫折,社会亦很残酷,让人时不时有点厌世的灰暗,但也有很多感动与美好,激情与悠扬;
既然活着,便努力去活,认真去过。

回到正题,后面会看看写聚类/强化学习相关吧,说不准。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值