前端也能AI,基于tfjs衡量组件研发效率(基础篇)

1,032 阅读11分钟

导读

在即将2022年的今天,前端还依然在跟“降本增效”,“疯狂提效”贴身肉搏,但在这个过程中前端界实践了一系列的提效措施:基础设施库,模版化,工程化,模块化,组件化等,以及抽象化程度更高的:企业级框架,低代码/无代码,智能生成代码等。但这些措施过后到底有没有真正起到效果呢,很难得之,因为做提效的人很少有人会去提这套衡量体系,去真正的完全“数字化”的方式来衡量我们的日常工作。
本文以“组件化研发模式”为基础,基于前端要交付的产物如:HTML / JS / CSS(或者是一种DSL),探寻一种能够衡量我们开发效率的思路,并提供一个初步实现

页面里一切都是组件

组件搭建.png


这是支付宝·花呗内的一个常规的H5 hybrid页面构成。React组件在这里是作为一等“公民”,页面的核心逻辑均会由组件承担,所以整体的页面架构就像这样:

H5 hybrid页面架构

  • H5营销产品
    • 核心页面,其它页面
      • 核心组件,各种功能组件
        • 可复用的基础组件,纯UI组件,逻辑组件
          • 其它npm依赖


所以整体的开发流程就变成了:

产品需求 -> 前端 -> 系分设计 -> 开发组件1,组件2,组件3 -> 交付组件1,组件2,组件3

组件的开发情况如何

当前端日常的开发都是围绕组件进行的时候,会有一些技术措施去“降本增效”,提升开发效率,但是在这个过程中最重要的是如何去总结一些客观的数字指标来衡量开发效率,因为只有具备了可量化的指标之后才客观的得到结果。

一、衡量指标设计


**效率公式:**组件开发效率 = 组件复杂度 / 组件开发时间

其中,组件的开发时间统计方式相对比较,只要把开发的时间累加一下就行,但是组件复杂度该如何衡量呢,开发人员是基于哪种思维去对比组件的复杂性的?

复杂度

当开发人员去对比2个React组件之间的复杂性时,通过查阅组件源码就可以得到一个模糊的体感
image.png
再把这些模糊的体感稍微整理之后,可以提取出这几个以下具体的React组件的源码特性:

  • React Component 特性
    • Props结构深度
    • State数量和结构深度
    • JSX结构深度
    • hooks圈复杂度
  • UI 特性
    • CSS 结构深度
    • 是否包含2D/3D互动,这里不考虑互动的可量化复杂性,只考虑是否包含互动
  • 服务依赖
    • 服务端API
    • 客户端API
  • npm依赖
    • 内部的可复用业务组件
    • 通用工具库
  • 其它的业务特性
    • 是否包含“投放”数据源

通过采集和提取这些指标,最后也许就能得到一个相对客观,且可量化的复杂度指标

开发时间

相比对复杂度,开发时间的计算比较简单,常见的:

  • 代码时间:git代码提交记录累加
  • 开发人日:雨燕迭代发布平台里所记录的迭代持续时间,


不管是哪种方式都可以得到一个客观的时间,重要的是要统一标准

二、智能

根据前面的复杂度的体感分析,我们得知因子与最终的结果也许存在一个线性关系,即:



为了计算出最准确佳的组件复杂度的分数,我们通过拆解组件的因子,然后专家们凭借经验给与权重,最后得出分数。但是,

在"AI"如此流行的今天,我们完全可以从这种工程编码思维力跳脱出来,因为像这种有“输入”,求“输出”,只缺“过程”的场景,就是一个典型的,可以通过AI方式来解决的问题

image.png

训练数据

在确定了AI方式之后,接下来就是非常重要的数据准备的环节,在这得先规范我们的打分规范以便于我们去人工打标,组件打分规范和衡量依据如下:

  • 5分)变态组件:互动,重型业务,多接口,投放
  • ​4分)复杂组件:状态多变,逻辑复杂,多个接口,依赖层次深
  • 3分)标准组件:常规的业务组件,涉及1~2个依赖​
  • 2分)轻松组件:有一定状态变化UI,逻辑较少,常规的基础组件
  • 1分)简单组件:单逻辑,纯UI

实际的组件分数并不会像这样粗粒度,相同分数的组件肯定不是完全一致的复杂度。

但为了能够快速完成打标和MVP版本,我们可以先简化一下问题,在前期先使用这样的打分标准让程序先跑起来,等到模型更精细后再来输出更细致的线性得分

设计模型

现在我们将原本的线性问题变成了分类问题,所以在设计模型的将重点考虑如何“分类”,幸运的是在AI界的经典hello world项目:tfjs实现 CNN 识别手写数字,就是一个分类问题。

在这个识别手写数字中使用的是卷积神经网络,通过卷积和池化,它是特别擅长处理图像等带有大量数据输入的场景,但遗憾这对于我们的组件打分的场景来说,就是一把杀牛刀了,所以在这里会做适当的简化

// 简化后的神经网络模型,非常简单
function designModel() {
  const model = tf.sequential();

  model.add(tf.layers.dense({
    inputShape: [COMPONENT_FEATURES_LENGTH], // 组件特征张量的长度
    units: 250,
    activation: 'relu',
  }));
  model.add(tf.layers.dense({
    units: 150,
    activation: 'relu',
  }));
  model.add(tf.layers.dense({
    units: 50,
    activation: 'relu',
  }));
  model.add(tf.layers.dense({
    units: 5,
    activation: 'softmax',
  }));

  const optimizer = tf.train.adam();

  model.compile({
    optimizer,
    loss: 'sparseCategoricalCrossentropy',
  });
  return model;
}


神经网络

推荐电子书《神经网络和深度学习第一章》,对神经网络有很直观的说明解释

简化层数

去掉卷积层,使用了普通的神经网络隐藏层,这里使用了3层隐藏层并附加一定数量的神经元数量,逐层收敛。神经网络的隐藏层的层数和units数量如何决定?遗憾的是并没有特别科学的方法,主要靠经验。

这里有个知乎文章可以参考下,如何确定神经网络的层数和隐藏层神经元数量,参考表格:
image.png

激活函数

relu 常用的非线性激活函数,简单应用,常规选择
sofmax 由于我们最后的结果是一个分类,经过sofamax计算后才能最终的结果收敛在[0, 1]区间方便做最后的衡量

损失函数

跟上面的手写识别差别在于 categoricalCrossentropy 应对的输入值是二值矩阵,而在我们这个场景里,我们的输入是一维的,所以要改用 sparseCategoricalCrossentropy,参考资料

三、工程实现

在经历了前期的技术调研和本地实验之后,就可以准备把这套设计搬到线上了。

这时候可以发现了除了最基本的模型之外,要想实现整个组件智能打分功能,还需要有完整配套的上下游工程,诸如组件的打标,模型监测,以及最后的开发体验demo等繁多的事情,所以一开始我想可以借助PAI平台来节省一下重复建设的成本,但是在经历了短暂了试用之后,我发现这个平台应该不是为我这个AI小白设计的,甚至还得重新学习下python才能使用,最后学习使用这个平台比学习AI本身还费劲。

好在,这块技术链路并不复杂,样本数也很下,依靠工作台本身的机器资源足以cover住,所以最后整体实现的工程架构如下所示:
image.png

原始数据采集

通过内源CDN提供的强大在线静态资源能力

g.alipay.com/meta/@alipa…

image.png

通过g.alipay.com,让我只需要通过组件名称 + 版本号,就可以极其快速的获取组件的结构树和在线资源,通过字符串解析的方式收集组件的feature。

组件feature只是“x”,除此之外还需要与“x”对应的“y”,这个没法自动,所以弄了个简易的CRUD后台作为打标用,顺便集成了组件的数据采集解析功能,比手动写sql方便了很多
image.png

模型读写

原本模型的训练是一件非常简单,结果在部署到预发后碰到TensorFlow在机器上的兼容性问题:

Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX

经查后得知tensorflow在node版的版本tfjs-node是一个经过优化,可以在CPU上加速的版本,会依赖特定CPU的某些指令集,但不幸的是这个预发机器的CPU似乎无法支持这个优化版,总之tfjs-node就是跑不起来😢

幸好浏览器版的 “@tensorflow/tfjs” 可以作为降级版本在node端也能跑起来,但跑起来之后又遇到了一个新问题,浏览器端的tfjs不直接支持nodejs文件系统。

因为我们是一个在线服务,在完成模型的训练后必须能够将模型保存下来用于服务用户,由于导出的模型是一个json文件和二进制文件,所以模型最好保存OSS上,在组件进行预测打分需要的时候再下载回来。但我不希望每次模型打分的时候都从OSS下载,希望在机器本地可以有一个本地文件版本,只在有更新的时候才去下载模型并更新本地文件。

虽然tfjs无法直接支持nodejs文件系统,它的API设计十分合理(不愧是Goolge),支持自定义读写接口pathOrIOHandler load接口文档,再完成这个文件读写之后,这个模型终于在预发布环境里执行训练,并预测打分了!✿✿ヽ(°▽°)ノ✿

附一个心路历程

半夜,在我把tfjs的读写接口写得差不多之后才想起来,tfjs-node能支持node文件系统,而tfjs不支持,是不是在其内部其实也是做了同样的读写实现,细思极恐。扒拉了tfjs-node的源码之后,果然发现了Node FIle System的实现,tfjs-node/src/io/file_system.ts 果然优秀

不愧是Google,再一次

四、最后MVP效果

通过测试剩下的未加入的组件,经过我手动统计之后,总体准确率:60~70% (人肉统计,仅供参考)

个别组件的偏差值会很大,跟理想的90%差距很远,还是一个实验状态,改进架构和模型。

demo截图效果:
image.png

总结一下前面的过程:

image.png

热心的朋友看到这里可能会想:“开发时间”去哪里了,不是说好的衡量开发效率吗?

现在距离组件开发效率这事儿,还差一点点。到这里我们完成了阶段性的,关于组件复杂度的粒度衡量,更精细的粒度还需要进一步打磨,毕竟现在的模型不够成熟,后面还需要很多时间来“调”参。

后续的想法计划:

  • 基于源码或其他基础范式而不是人工随意拍的因子,组件的特征也能智能化采集
  • 精细的组件研发时间投入统计
  • 数据报表统计,从多个维度可量化的衡量所有的产出的组件,最终得出组件的真正价值

招聘:快手欢迎你

如果你对以上感兴趣,欢迎加入我们一起搞事情,我们希望你:

  • 扎实的编程功底或者极强的理工科思维。
  • 理智思考。
  • 有自我追求。
  • 自信但不自负。

我们没有任何年限或者经历之类的硬性限制,你甚至可以不熟悉前端。只要你对我们想做的事情感兴趣,满足以上条件就欢迎你来应聘。

关于职位和如何应聘

当前职位所属部门为:快手商业化技术部-前端研发-通用技术组。

Base 地:杭州/北京。

部门前端研发人数目前 170+,今年仍会有大幅扩充。

我的简历邮箱:zhouyunge@kuaishou.com (来源注明:juejin)

招聘职级和薪酬对标业内 p5-p8。

你需要准备的

  • 公开的专栏、文章、代码仓库等技术积累(如果有)。
  • 技能树(如果认为以上的文章、代码等已经足够说明自身的能力,并且包括了知识边界,可以省略技能树。)
  • 地址:技能树飞书版 或者 技能树语雀版

参考文章 《体系化的 Web 应用研发与极致信任的招聘》