
大家好! 今天我们将讨论在Scala上机器学习的实现。 我将从解释我们如何过这种生活开始。 因此,我们的团队长期使用Python中机器学习的所有功能。 这很方便,有很多有用的库可用于数据准备,良好的开发基础设施,我的意思是Jupyter Notebook。 一切都很好,但是面临着在生产中并行化计算的问题,因此决定在产品中使用Scala。 我们以为为什么不存在大量的库,即使Apache Spark是用Scala编写的! 同时,今天,我们使用Python开发模型,然后重复在Scala中进行培训,以进行进一步的序列化并在生产中使用。 但是,正如他们所说,魔鬼在细节中。
亲爱的读者,我想立即澄清一下,这篇文章并不是为了削弱Python在机器学习方面的声誉。 不,主要目的是打开Scala上的机器学习世界的大门,简要概述根据我们的经验得出的替代方法,并告诉您我们遇到了什么困难。
在实践中,事实证明这并不那么快乐:实现经典机器学习算法的库并不多,而那些通常是没有大型供应商支持的OpenSource项目。 是的,当然有Spark MLib,但是它与Apache Hadoop生态系统紧密相关,我真的不想将其拖入微服务架构中。
所需要的是一种可以拯救世界并带回安宁的睡眠的解决方案,结果被发现!
你需要什么
当我们选择机器学习工具时,我们遵循以下标准:
- 它应该很简单;
- 尽管简单,但没有人取消广泛的功能;
- 我真的希望能够在Web解释器中开发模型,而不是通过控制台或常量汇编和编译来开发模型。
- 文件的可用性起着重要作用;
- 理想情况下,至少应该支持github问题。
我们看到了什么?
- Apache Spark MLib :不适合我们。 如上所述,这组库与Apache Hadoop堆栈和Spark Core本身紧密相关,后者的权重太大,无法基于该库构建微服务。
- Apache PredictionIO :一个有趣的项目,许多贡献者,都有带示例的文档。 实际上,这是一个正在旋转模型的REST服务器。 有现成的模型,例如文本分类,其启动过程在文档中进行了描述。 该文档描述了如何添加和训练模型。 我们不适合,因为Spark是在引擎盖下使用的,而这更多的是单片解决方案而不是微服务架构。
- Apache MXNet :一个有趣的使用神经网络的框架,支持Scala和Python-这很方便,您可以使用Python训练神经网络,然后在创建生产解决方案时从Scala加载保存的结果。 我们在生产解决方案中使用它,这里有单独的文章。
- Smile :非常类似于Python的scikit-learn软件包。 经典机器学习算法的实现方式很多,有示例的优质文档,github上的支持,集成的可视化工具(由Swing支持),您可以使用Jupyter Notebook开发模型。 这就是您所需要的!
环境准备
因此,我们选择了Smile。 我将以k-means聚类算法为例,告诉您如何在Jupyter Notebook中运行它。 我们需要做的第一件事是安装具有Scala支持的Jupyter Notebook。 这可以通过pip完成,也可以使用已组装和配置的Docker映像。 我是一个更简单的第二选择。
为了让Jupyter与Scala成为朋友,我想使用BeakerX,它是Docker映像的一部分,可在官方BeakerX存储库中获得。 建议在Smile文档中使用该图像,您可以像这样运行它:
但是这里遇到的第一个麻烦是在等待:在撰写本文时,Beakerx / beakerx映像内已安装了BeakerX 1.0.0,并且该项目的官方github中已经有1.4.1版本(更确切地说,最新版本1.3.0,但该向导包含1.4.1,它的工作原理:-))。
很明显,我想使用最新版本,因此我根据BeakerX 1.4.1整理了自己的图像。 我不会对Dockerfile的内容感到厌烦,这里是它的
链接 。
顺便说一下,对于那些将使用我的图像的人来说,这将是一笔不小的收获:在examples目录中,有一个示例k均值用于绘制随机序列(对于Scala笔记本而言,这并不是一件完全琐碎的任务)。
下载Jupyter Notebook中的Smile
良好的准备环境! 我们在目录的文件夹中创建一个新的Scala笔记本,然后需要从Maven下载这些库才能使Smile正常工作。
%%classpath add mvn com.github.haifengl smile-scala_2.12 1.5.2
执行代码后,已下载的jar文件列表将出现在其输出块中。
下一步:导入必要的程序包以使示例正常工作。
import java.awt.image.BufferedImage import java.awt.Color import javax.imageio.ImageIO import java.io.File import smile.clustering._
准备数据进行聚类
现在我们将解决以下问题:生成由三个原色区域组成的图像-红色,绿色和蓝色(R,G,B)。 图片中的一种颜色为准。 我们对图像的像素进行聚类,对其中像素最多的聚类进行聚类,将其颜色更改为灰色,然后根据所有像素构建一个新图像。 预期结果:主要颜色的区域将变为灰色,其余区域将不会改变其颜色。
执行此代码后,将显示以下图片:

下一步:将图片转换为一组像素。 像素表示具有以下属性的实体:
- 宽边坐标(x);
- 窄边坐标(y);
- 颜色值
- 类/集群号的可选值(在集群完成之前,它将为空)。
作为一个实体,使用
case class
很方便:
case class Pixel(x: Int, y: Int, rgbArray: Array[Double], clusterNumber: Option[Int] = None)
在此,对于颜色值,使用由红色,绿色和蓝色三个值组成的
rgbArray
数组(例如,对于红色
Array(255.0, 0, 0)
)。
这样就完成了数据准备。
像素颜色聚类
因此,我们有三种原色的像素集合,因此我们将像素分为三个类别。
文档建议将
runs
参数设置为10到20。
执行此代码后,将创建
KMeans
类型的对象。 输出块将包含有关聚类结果的信息:
K-Means distortion: 0.00000 Clusters of 230400 data points of dimension 3: 0 50813 (22.1%) 1 51667 (22.4%) 2 127920 (55.5%)
一个群集确实包含比其余群集更多的像素。 现在我们需要用0到2的类标记我们的像素集合。
重画图像
剩下的唯一事情是选择像素数最多的群集,并将此群集中包含的所有像素重新绘制为灰色(更改
rgbArray
数组的值)。
没什么复杂的,只需按簇号分组(这是我们的
Option:[Int]
),计算每个组中的元素数,然后取出具有最大元素数的簇。 接下来,仅将属于找到的群集的那些像素的颜色更改为灰色。
创建一个新图像并保存结果。
从像素集合中收集新图像:
最终,我们做到了。

我们保存两个图像。
ImageIO.write(testImage, "png", new File("testImage.png")) ImageIO.write(modifiedImage, "png", new File("modifiedImage.png"))
结论
存在于Scala上的机器学习。 要实现基本算法,无需拖动一些巨大的库。 上面的示例表明,在开发过程中您不能放弃通常的方法,同一个Jupyter Notebook可以很容易地与Scala成为朋友。
当然,要获得有关Smile的所有功能的完整概述,仅一篇文章是不够的,并且未包含在计划中。 我认为完成主要任务-打开Scala上的机器学习世界之门。 是否使用这些工具,甚至更多地将它们拖入生产环境,完全取决于您!
参考文献