前天下班浏览朋友圈,雪晴数据网转发了一篇译文,大数据工具比较:R 语言和 Spark 谁更胜一筹?,原作者测试了在限定为单机环境下,使用Kaggle提供的手写识别的数据在R和Spark平台运行不同算法的对比速度,结论有一下几个:

  • 主成分分析R要4小时,Spark要10秒
  • 逻辑回归:R运行7小时,Spark约5分钟
  • 朴素贝叶斯,因为算法太简单,差异不那么巨大(45s和9s)
  • 决策树:R完全跑不起来,Spark花20秒

在整篇文章作者的测试非常武断,有很多误导观众的信息。R再怎么样也不会如此不济,下面会逐条针对原文做补充。当然,这里面我还想强调:在使用算法解决问题的过程中,以下几大行为必须纠正(或者叫做流氓法则):

  1. 不贴源代码,是耍流氓
  2. 张冠李戴,完全复制,不做任何优化就对比,更是耍流氓
  3. 不考虑业务场景乱用算法,就是耍流氓
  4. 不考虑数据形态以及精度要求,拿算法的帽子乱盖,还是耍流氓

我们也来玩玩测试

首先提供一下我的环境,11 年购入的 Thinkpad T420,i7-2640M,4核心,8G 内存;R 版本Windows 64位原生编译版本,未做 Blas 库的任何优化。在这台玩魔兽现在都有点卡的老爷机上,以上几个算法在单机版的R平台上测试速度为:

  • 主成分分析为 1.64s
  • 二分类逻辑回归 5s,多分类逻辑回归为58s
  • 决策树使用更高级的GBDT,3s

同时为了表明不是流氓,给出代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
library(data.table)
library(irlba)
library(LiblineaR)
library(SparseM)
library(xgboost)

setwd("E:/Dropbox/data/handwriting")
train <- fread('train.csv')

train <- as(train, 'Matrix')
Y <- train[,1]
A <- train[,-1]

## Principal Components Analysis
system.time(P <- irlba(A, nv=9, center=colMeans(A)))

## Logistic Regression
id <- Y == 1
Y1 <- as.numeric(id)
## single label
system.time(m1 <- LiblineaR(data = as(A, 'matrix.csr'), target = Y1, type = 5))
## multi label
system.time(m2 <- LiblineaR(data = as(A, 'matrix.csr'), target = Y, type = 5))

## Confusion Matrix
dd <- data.frame(predict(m2, as(A, 'matrix.csr')), Y)
table(dd)

## eXtreme Gradient Boosting (Tree)
system.time(bst <- xgboost(data = A, label = Y1, nrounds=10, objective = "binary:logistic"))


深层次的原因

写到这里,有看官肯定会有疑问了:为什么同样的平台怎么就差异这么大了呢?在解释之前,首先需要明确的一点:

Spark 作为利用多机内存处理大数据的利器,必然是大规模数据计算的未来发展方向,这个势是无法阻挡的。

但 R 从最初设计就不是干这个的!R 语言基本继承了 S 语言的设计理念,我们先了解一下当时 S 诞生的理由(Yihui Xie,2008)。

  1. 1975-1976年,贝尔实验室统计研究部使用一套庞大且文档齐全的 Fortran 库做统计研究,简称为 SCS(Statistical Computing Subroutines)。
  2. SCS 库为模拟实验、大问题研究、蒙特卡罗分析和非标准分析提供了完美支持。但统计研究部主要负责非标准的数据案例,
  3. 这种方式更接近于现代数据分析需求-探索性数据分析技术(Exploratory Data Analysis),在这个需求下花在大量的编程的精力和问题的价值是极不相符的。
  4. 以John Chambers核心的统计研究部最终结论是需要一套完整语言系统,于是 S 语言诞生了。

简要说:需要高性能计算的部分使用 C 和 Fortran,需要处理逻辑以及快速分析部分使用 R。这一直都是R存在的理念。直至今日,R 的源码 50% 是C,30% 是Fortran,仅有 20% 的 R 代码。大量通用化计算模块均以 R 包的方式存在。R 单机版性能不济,十之八九是用户采用了不恰当的方式。

比如 Regression 问题,当数据量小的时候一般用 QR 分解,亦可以直接用标准最小二乘方式,这时候就需要计算X的内积以及内积的求逆。当维度很大时,这种做法完全就是个灾难。如果我们改用梯度下降或坐标下降方法之后,虽然牺牲了精度,但计算效率可以得到极大改观。

同样上个世纪 Friedman 提出了Gradient Boosting Machine,该方法每一代辛勤的科研工作者均站在前人的肩膀上做了更多的优化,拿早期版本和最新的成果进行对比,(比如用 Python 的 sklearn 库中的 GBM 和 R 中的 xgboost 比,然后说 R 快),这本身是赤裸裸地耍流氓

对于像 SVD 分解这类问题,如果进行全矩阵分解,计算量暴大不说,单单存储就不太容易搞定。Lancozs算法可以只求解我们关注的最大的几个奇异值,忽略尾部的奇异值,有这么方便的方法还去做全矩阵分解,请参见流氓法则 4。

写在最后

如果过于迷信工具,而忽视了要解决问题的本质,就需要参见流氓做法 3 和 4了。当我们碰到一个问题,首先判断是否能够转化为数学问题,然后判断用什么方法解决它,再然后判断适用的工具以及工具对应的特定方法。

突然记起 Python 社区 Zoom.Q 大妈T恤上有句话,叫做

人生苦短,Python 是岸

天天争论哪个工具好用,舍本逐末,那真是人生苦短了~