批量泛化出大量样本,为模型训练提供数据保障!
优采云 发布时间: 2021-07-29 23:03批量泛化出大量样本,为模型训练提供数据保障!
在 UI2CODE 项目中,我们广泛使用深度学习方法来进行一些对象检测。深度学习模型的训练不可避免地需要大量的样本,所以如何让大量的样本满足模型训练的需要是我们必须解决的问题。在这个文章中,我们将介绍我们如何使用工具对大量样本进行批量泛化,为模型训练提供数据保障。
1.样本现状
我们的模型要解决的问题是在设计草稿图片上识别基本控件和其他信息,包括位置和类别。它需要的样本有两个主要问题:
数据量小:一个APP的页面是有限的,特别是针对单个APP优化适配时,页面数量比较少,可能几十到几百。但是模型对样本数量的需求是巨大的,尤其是像比较复杂的模型,对数据量的需求至少是10000级,单靠真实样本是远远不能满足要求的。标注成本高:物体检测的样本标注不仅需要标注物体的类别,还需要标注物体的具体位置,一个样本上会有多个物体标签。因此,标记此类样本的成本非常高。 2.样本获取方式
有几种主要的样品获取方式。
对于真实样品,这种类型的质量是最高的。如果要训练一个好的模型,这类样本基本是必不可少的。但是,由于此类样本数量少且成本高,因此需要其他方法。补充样本量。
对于数据增强,这种方法简单快捷,但效果有限。尤其是我们UI2CODE中识别控件的任务,旋转等操作基本无效。
因此,我们需要使用sample mocks来扩大我们的数据量,并尝试模拟质量更高、体积更大的样本。这里我们选择使用 Weex 页面对样本进行模拟泛化。 (当然还有一些其他的方法,比如利用Android的特性,在运行时APP页面中,抓取页面数据,过滤清理后,得到带有注解的样本,这里不展开)
3.WEEX 页面示例概括
这里介绍如何使用Weex页面批量泛化样本,获取样本标注方法。
前端页面功能
我们之所以选择使用前端页面来生成样本,是因为前端页面更多是为了数据展示,并且拥有完整的DOM树。只要我们持有DOM树,就可以解析出里面的各种元素。
对于节点内容,只要我们改变元素内容即可。这样,我们就可以轻松地从一个前端页面中归纳出多个不同文本和不同图片的样本。
当然,我们闲鱼APP上有大量的Weex活动页面,这也是我们选择泛化Weex页面的原因之一。
泛化思路
我们需要的基本控件分为“文本”、“图片”和“形状”三类。对于一个页面,我们的文字和图片内容基本上是可以替换的,所以我们解析出所有节点之后,替换里面的文字和图片,然后进行渲染,得到一个新的样本。
使用 Puppeteer 实现泛化
要得到一个Weex页面,我们需要一个渲染容器,我们可以很方便的修改它的内容。在这里,我们选择了谷歌的Puppeteer,它是谷歌推出的一个js接口包,可以运行Chrome Headless环境并对其进行控制。通过它,我们可以模拟一个Chrome运行环境并对其进行操作。官方简介在这里。
先启动一个没有界面的浏览器:
const browser = await puppeteer.launch({
headless: true
});
启动一个页面,然后打开一个网站:
const page = await browser.newPage();
await page.goto(nowUrls, {waitUntil: ['load','domcontentloaded','networkidle0']});
模拟iPhone6环境:
await page.emulate({
'name': 'iPhone 6',
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
'viewport': {
'width': 750,
'height': 1334,
'deviceScaleFactor': 1,
'isMobile': true,
'hasTouch': true,
'isLandscape': false
}
});
搜索所需控件:
let d_root = document.querySelectorAll('.weex-root');
let nodes_root = [];
collectChildren(d_root, nodes_root);
/**
* 遍历节点,搜集所有需要的控件
*/
function collectChildren(d, _nodes) {
for(var i = 0,l = d.length;i < l;i++){
let hasPushed = false;
//nodeType === 1 时 push
if (d[i].nodeType !== 1 && d[i].nodeType !== 3) {
continue;
}
if(d[i].style){
let backgrounColorValue = d[i].style['background-color'];
if(backgrounColorValue && backgrounColorValue !== 'rgb(255, 255, 255)' && backgrounColorValue !== 'rgb(0, 0, 0)' && backgrounColorValue !== 'transparent'){
_nodes.push(d[i]);
hasPushed = true;
}
}
if(d[i].hasChildNodes()){
collectChildren(d[i].childNodes, _nodes);
}else{
let _node = d[i];
let _className = _node.className;
if(!_className && _node.nodeName === '#text'){
_className = _node.parentNode.className;
}
if(_className && !hasPushed){
if(_className.indexOf('weex-text') > -1 || _className.indexOf('weex-image') > -1){
_nodes.push(d[i]);
}
}
}
}
return _nodes;
}
获取控制信息:
/**
* 获取 基础视图元素的属性
*/
function getRealyStyle(node,attrKey){
let wvStyle = window.getComputedStyle(node);
if(node[attrKey] && node[attrKey] !== ''){
return node[attrKey];
}else{
return wvStyle[attrKey]
}
}
/**
* 获取 基础视图元素的位置
*/
function getViewPosition(node){
const {top, left, bottom, right} = node.getBoundingClientRect();
return {
"y": top,
"x": left,
"height": bottom-top,
"width": right-left
}
}
获取页面图片:
await page.screenshot({
path: pngName,
fullPage : true
});
清理数据:
有些页面会有弹窗(遮罩层),而我们的标注规则是只标注上层,所以需要根据遮罩层的位置和大小来过滤掉下层控件。
通过上面的方法,我们可以得到每个文本、图片、形状以及它们的位置和属性。根据位置和控制类别信息,我们可以得到带有位置和类别注释的样本。
通用文本和图像
通过上面的方法,只要你提供一个Weex页面的url,就可以得到一个带有注解的真实样本。后面我们只需要修改其中的text和image节点的内容,就可以批量泛化多个样本了。这些样本都是根据实际的页面布局,质量比较高,泛化率可以随意控制。比如设置1:10,可以从100个点生成10000个样本,大大增加了样本量。
5.总结
通过Weex泛化样本的方式,我们从100多个Weex活跃页面中泛化了10000+个样本,无需人工标注,节省了大量标注成本。并且因为样本质量比较高,模型的准确率有了很大的提高。当然,我们也探索了很多其他的方法,包括抓取Android运行时页面数据生成自动标记数据,使用训练好的模型自动预标记以节省人工标记的人工成本。未来,我们也会继续探索更多的样本生成和自动标记方法,为模型训练提供更多有用的数据。