njucckevin / SeeClick

The model, data and code for the visual GUI Agent SeeClick
Apache License 2.0
187 stars 10 forks source link

作者您好!关于aitw的数据集的验证我有个问题想请教! #29

Closed dmndxld closed 4 months ago

dmndxld commented 5 months ago

感谢您的出色工作,对我很有启发,现在我想在您的基础上做一下延展工作。关于aitw的test结果我有个疑问,我观察到test是在aitw_test.py文件的第79行load了一个json文件,名为aitw_data_test.json,我看了这个数据的构成仅包含aitw子数据集下很少的部分数据,请问是以这个结果作为文章里的榜单结果吗?没有在更大一点的测试集上测试吗?

njucckevin commented 5 months ago

感谢关注我们的工作~ 当前aitw_test.py测试的结果即对应我们正文中汇报的aitw结果,这里我们使用的训练/验证/测试划分和之前的一些工作有些不一样,我们是按照instruction-wise划分的,而之前的工作如果涉及训练的话是根据episode-wise划分的。因此正文表3中我们只和zero-shot的方法和以及Qwen-VL baseline进行了比较(这样是大致公平的),主要目的是说明经过预训练之后SeeClick相比Qwen-VL能有明显的提升。在文章附录C.3中,我们也汇报了按照AITW的原先划分上的结果,如有需要可以参考。 至于为什么要采用instruction-wise的方式,主要有两个原因:

  1. 我个人认为之前episode-wise的方式存在严重的过拟合风险。AITW包含30k个指令和715k个episode,也就是对于一条指令,平均有超过20个episode和它对应。这样,采用episode划分训练和测试集的话,实际上测试集中的所有指令都是在训练阶段见过的(并且见过很多次),模型只需要背诵一遍训练时见过的episode即可。
  2. 按照AITW的原先划分,训练数据有超过5M个step,即便按照Auto-UI的方式只使用GoogleApps子集的10%,最后也包含超过1.2M个step的数据,如果按照Auto-UI训练10个epoch需要的计算量非常大;我们在原先划分上的结果使用8*A100训练了超过4天。

个人感觉对于agent任务来说,为同一条指令收集这么多大多重复的轨迹是有些奇怪的。因此我们这里最终采用了instruction-wise的方式,保证测试时的指令是在训练过程中没有见过的。从General/Install/GoogleApps/Single/WebShopping中分别采样了545/688/306/700/700条指令,每条指令只保留一个episode,然后选择80%的指令训练其余作为测试(也划分了验证集,详情参考原论文)。我们希望这种划分能更好地体现agent的泛化性。 具体的更好的划分方式可能需要根据应用场景来决定,这里只是我们采用的一种。

dmndxld commented 5 months ago

感谢关注我们的工作~ 当前aitw_test.py测试的结果即对应我们正文中汇报的aitw结果,这里我们使用的训练/验证/测试划分和之前的一些工作有些不一样,我们是按照instruction-wise划分的,而之前的工作如果涉及训练的话是根据episode-wise划分的。因此正文表3中我们只和zero-shot的方法和以及Qwen-VL baseline进行了比较(这样是大致公平的),主要目的是说明经过预训练之后SeeClick相比Qwen-VL能有明显的提升。在文章附录C.3中,我们也汇报了按照AITW的原先划分上的结果,如有需要可以参考。 至于为什么要采用instruction-wise的方式,主要有两个原因:

  1. 我个人认为之前episode-wise的方式存在严重的过拟合风险。AITW包含30k个指令和715k个episode,也就是对于一条指令,平均有超过20个episode和它对应。这样,采用episode划分训练和测试集的话,实际上测试集中的所有指令都是在训练阶段见过的(并且见过很多次),模型只需要背诵一遍训练时见过的episode即可。
  2. 按照AITW的原先划分,训练数据有超过5M个step,即便按照Auto-UI的方式只使用GoogleApps子集的10%,最后也包含超过1.2M个step的数据,如果按照Auto-UI训练10个epoch需要的计算量非常大;我们在原先划分上的结果使用8*A100训练了超过4天。

个人感觉对于agent任务来说,为同一条指令收集这么多大多重复的轨迹是有些奇怪的。因此我们这里最终采用了instruction-wise的方式,保证测试时的指令是在训练过程中没有见过的。从General/Install/GoogleApps/Single/WebShopping中分别采样了545/688/306/700/700条指令,每条指令只保留一个episode,然后选择80%的指令训练其余作为测试(也划分了验证集,详情参考原论文)。我们希望这种划分能更好地体现agent的泛化性。 具体的更好的划分方式可能需要根据应用场景来决定,这里只是我们采用的一种。

感谢您的迅速回复,我明白了!

dmndxld commented 5 months ago

感谢关注我们的工作~ 当前aitw_test.py测试的结果即对应我们正文中汇报的aitw结果,这里我们使用的训练/验证/测试划分和之前的一些工作有些不一样,我们是按照instruction-wise划分的,而之前的工作如果涉及训练的话是根据episode-wise划分的。因此正文表3中我们只和zero-shot的方法和以及Qwen-VL baseline进行了比较(这样是大致公平的),主要目的是说明经过预训练之后SeeClick相比Qwen-VL能有明显的提升。在文章附录C.3中,我们也汇报了按照AITW的原先划分上的结果,如有需要可以参考。 至于为什么要采用instruction-wise的方式,主要有两个原因:

  1. 我个人认为之前episode-wise的方式存在严重的过拟合风险。AITW包含30k个指令和715k个episode,也就是对于一条指令,平均有超过20个episode和它对应。这样,采用episode划分训练和测试集的话,实际上测试集中的所有指令都是在训练阶段见过的(并且见过很多次),模型只需要背诵一遍训练时见过的episode即可。
  2. 按照AITW的原先划分,训练数据有超过5M个step,即便按照Auto-UI的方式只使用GoogleApps子集的10%,最后也包含超过1.2M个step的数据,如果按照Auto-UI训练10个epoch需要的计算量非常大;我们在原先划分上的结果使用8*A100训练了超过4天。

个人感觉对于agent任务来说,为同一条指令收集这么多大多重复的轨迹是有些奇怪的。因此我们这里最终采用了instruction-wise的方式,保证测试时的指令是在训练过程中没有见过的。从General/Install/GoogleApps/Single/WebShopping中分别采样了545/688/306/700/700条指令,每条指令只保留一个episode,然后选择80%的指令训练其余作为测试(也划分了验证集,详情参考原论文)。我们希望这种划分能更好地体现agent的泛化性。 具体的更好的划分方式可能需要根据应用场景来决定,这里只是我们采用的一种。

感谢关注我们的工作~ 当前aitw_test.py测试的结果即对应我们正文中汇报的aitw结果,这里我们使用的训练/验证/测试划分和之前的一些工作有些不一样,我们是按照instruction-wise划分的,而之前的工作如果涉及训练的话是根据episode-wise划分的。因此正文表3中我们只和zero-shot的方法和以及Qwen-VL baseline进行了比较(这样是大致公平的),主要目的是说明经过预训练之后SeeClick相比Qwen-VL能有明显的提升。在文章附录C.3中,我们也汇报了按照AITW的原先划分上的结果,如有需要可以参考。 至于为什么要采用instruction-wise的方式,主要有两个原因:

  1. 我个人认为之前episode-wise的方式存在严重的过拟合风险。AITW包含30k个指令和715k个episode,也就是对于一条指令,平均有超过20个episode和它对应。这样,采用episode划分训练和测试集的话,实际上测试集中的所有指令都是在训练阶段见过的(并且见过很多次),模型只需要背诵一遍训练时见过的episode即可。
  2. 按照AITW的原先划分,训练数据有超过5M个step,即便按照Auto-UI的方式只使用GoogleApps子集的10%,最后也包含超过1.2M个step的数据,如果按照Auto-UI训练10个epoch需要的计算量非常大;我们在原先划分上的结果使用8*A100训练了超过4天。

个人感觉对于agent任务来说,为同一条指令收集这么多大多重复的轨迹是有些奇怪的。因此我们这里最终采用了instruction-wise的方式,保证测试时的指令是在训练过程中没有见过的。从General/Install/GoogleApps/Single/WebShopping中分别采样了545/688/306/700/700条指令,每条指令只保留一个episode,然后选择80%的指令训练其余作为测试(也划分了验证集,详情参考原论文)。我们希望这种划分能更好地体现agent的泛化性。 具体的更好的划分方式可能需要根据应用场景来决定,这里只是我们采用的一种。

您好!还有个问题想请教您,因为我可能还是需要多扩大一些数据规模做一些工作,能麻烦您能提供一下当时做这个json文件的脚本吗?以及对应采样images的脚本?

njucckevin commented 5 months ago

AITW数据集的处理是有些繁琐的,主要是要先从google提供的数据中下载tfrecord文件,然后从文件中解码出图片和标注数据。这些数据挺大的(图片超过400G),我没办法把原始数据直接打包发给你。如果你需要原先AITW的规模,我感觉需要参考原始google的仓库处理一遍数据并解码保存图片,或者参考一下其他工作例如Auto-UI的处理步骤。 然后你也可以留个邮箱,我把我当时的一些的处理脚本发给你,但仅供参考,因为那个我没整理比较乱。

dmndxld commented 5 months ago

AITW数据集的处理是有些繁琐的,主要是要先从google提供的数据中下载tfrecord文件,然后从文件中解码出图片和标注数据。这些数据挺大的(图片超过400G),我没办法把原始数据直接打包发给你。如果你需要原先AITW的规模,我感觉需要参考原始google的仓库处理一遍数据并解码保存图片,或者参考一下其他工作例如Auto-UI的处理步骤。 然后你也可以留个邮箱,我把我当时的一些的处理脚本发给你,但仅供参考,因为那个我没整理比较乱。

好的感谢您!麻烦您传一下当时的处理脚本,这是我的邮箱tanhyt@163.com。

njucckevin commented 5 months ago

已发

dmndxld commented 4 months ago

师哥您好!我在复现general子数据集上准确率(54.3%)与您附录报道的差距较大(67.6%),有些问题想再次请教! 首先仍然用的是aitw_test.py文件测试,具体的模型是您在hf上发布的aitw微调版本,其数据是general子数据集通过您给定的脚本处理得到的json文件,然后我自己根据episode_wise划分了部分数据(根据auto-ui的json对episode划分)作为测试集。 因为我发现您脚本里对step_data处理后缺少"action_type_text"这个字段,然后我自己根据您aitw_test.py文件倒推了一下。这是我的生成逻辑:

def add_action_type_text(example):
    """根据generate_action函数改写,函数返回后赋给step_data的'action_type_text'字段"""
    ui_element_num = len(example["annot_position"])/4
    assert len(example["ocr_text"]) == ui_element_num
    assert len(example["ocr_icon"]) == ui_element_num

    try:
        # 几种特殊的既定动作
        if example["action_type_id"] == 5:
            action = 'PRESS BACK'.lower()
            return action
        elif example["action_type_id"] == 6:
            action = 'PRESS HOME'.lower()
            return action
        elif example["action_type_id"] == 7:
            action = 'PRESS ENTER'.lower()
            return action
        elif example["action_type_id"] == 10:
            action = 'STATUS TASK COMPLETE'.lower()
            return action
        elif example["action_type_id"] == 11:
            action = 'STATUS TASK IMPOSSIBLE'.lower()
            return action

        assert example["action_type_id"] == 3 or example["action_type_id"] == 4

        if example["action_type_id"] == 3:
            # type_text = example["action_type_text"]
            type_text = example["type_text"]
            action = 'type '+type_text
            return action

        # 对于dual point情况,判断是上下左右滚动,还是点击某个点,后者根据点击的位置确定最近的ocr内容作为action
        touch = example["touch"]
        lift = example["lift"]
        distance = np.sqrt((lift[0]-touch[0])**2+(lift[1]-touch[1])**2)
        if distance >= 0.04:    # 如果起点和终点不在一起,认为是手势操作,这里只考虑四种手势:左滑,右滑,上滑,下滑
            width = example["img_width"]
            height = example["img_height"]
            touch = [touch[0] * width, touch[1] * height]
            lift = [lift[0] * width, lift[1] * height]
            # 转换到以起点为原点的标准坐标系
            lift_1 = [lift[0]-touch[0], touch[1]-lift[1]]
            if lift_1[0] == 0:
                if lift_1[1] > 0:
                    action = "scroll down"
                else:
                    action = "scroll up"
            else:
                k = float(lift_1[1] / lift_1[0])
                if -1 <= k <= 1:
                    if lift_1[0] > 0:
                        action = "scroll left"
                    else:
                        action = "scroll right"
                else:
                    if lift_1[1] > 0:
                        action = "scroll down"
                    else:
                        action = "scroll up"
            return action
        else:   # 起点和终点接近,认为是点击操作,从ui_element中找到与之距离最近的
            action = 'click'
            return action
    except:
        return "Error in data"

然后我就对应的往step_data里添加了这个"action_type_text"字段,另外我把脚本中

def get_episode(
  episode,
  show_annotations = False,
  show_actions = False,
):
  ep_data = []
  imgs = []
  for i, ex in enumerate(episode):
    step_data = {}
    ep_id = ex.features.feature['episode_id'].bytes_list.value[0].decode('utf-8')

    goal = ex.features.feature['goal_info'].bytes_list.value[0].decode('utf-8')
    ocr_text = [item.decode('utf-8') for item in ex.features.feature['image/ui_annotations_text'].bytes_list.value]
    annot_position = ex.features.feature['image/ui_annotations_positions'].float_list.value
    annot_position = [round(item, 3) for item in annot_position]
    annot_type = [item.decode('utf-8') for item in ex.features.feature['image/ui_annotations_ui_types'].bytes_list.value]

    image_height = ex.features.feature['image/height'].int64_list.value[0]
    image_width = ex.features.feature['image/width'].int64_list.value[0]
    image_channels = ex.features.feature['image/channels'].int64_list.value[0]
    image = _decode_image(ex, image_height, image_width, image_channels)
    imgs.append(image)
    img_filename = str(ep_id)+'_'+str(i)

    touch_y, touch_x = ex.features.feature['results/yx_touch'].float_list.value
    touch_y, touch_x = round(touch_y, 3), round(touch_x, 3)
    lift_y, lift_x = ex.features.feature['results/yx_lift'].float_list.value
    lift_y, lift_x = round(lift_y, 3), round(lift_x, 3)
    ex_action_type = ex.features.feature['results/action_type'].int64_list.value[0]
    type_text = (ex.features.feature['results/type_action'].bytes_list.value[0].decode('utf-8'))

    step_data = {"ep_id": ep_id, "goal": goal, "step": i, "annot_position": annot_position, "ocr_text": ocr_text,
                 "ocr_icon": annot_type, "touch": [touch_x, touch_y], "lift": [lift_x, lift_y], "action_type": ex_action_type,
                 "type_text": type_text, "img_filename": img_filename, "img_width":image_width, "img_height": image_height}

    ep_data.append(step_data)

  return ep_data, imgs

"action_type"字段更改成了"action_type_id",并且额外保留了原tf文件里的device_type和android_api_level两个字段信息,最后用

def generate_action(example):
    """接受一个example,根据内容确定其中action的一个自然语言表示"""
    ui_element_num = len(example["annot_position"])/4
    assert len(example["ocr_text"]) == ui_element_num
    assert len(example["ocr_icon"]) == ui_element_num

    try:
        # 几种特殊的既定动作
        if example["action_type_id"] == 5:
            action = 'PRESS BACK'.lower()
            return action
        elif example["action_type_id"] == 6:
            action = 'PRESS HOME'.lower()
            return action
        elif example["action_type_id"] == 7:
            action = 'PRESS ENTER'.lower()
            return action
        elif example["action_type_id"] == 10:
            action = 'STATUS TASK COMPLETE'.lower()
            return action
        elif example["action_type_id"] == 11:
            action = 'STATUS TASK IMPOSSIBLE'.lower()
            return action

        assert example["action_type_id"] == 3 or example["action_type_id"] == 4

        if example["action_type_id"] == 3:
            # type_text = example["action_type_text"]
            type_text = example["type_text"]
            action = 'type '+type_text
            return action

        # 对于dual point情况,判断是上下左右滚动,还是点击某个点,后者根据点击的位置确定最近的ocr内容作为action
        touch = example["touch"]
        lift = example["lift"]
        distance = np.sqrt((lift[0]-touch[0])**2+(lift[1]-touch[1])**2)
        if distance >= 0.04:    # 如果起点和终点不在一起,认为是手势操作,这里只考虑四种手势:左滑,右滑,上滑,下滑
            width = example["img_width"]
            height = example["img_height"]
            touch = [touch[0] * width, touch[1] * height]
            lift = [lift[0] * width, lift[1] * height]
            # 转换到以起点为原点的标准坐标系
            lift_1 = [lift[0]-touch[0], touch[1]-lift[1]]
            if lift_1[0] == 0:
                if lift_1[1] > 0:
                    action = "scroll down"
                else:
                    action = "scroll up"
            else:
                k = float(lift_1[1] / lift_1[0])
                if -1 <= k <= 1:
                    if lift_1[0] > 0:
                        action = "scroll left"
                    else:
                        action = "scroll right"
                else:
                    if lift_1[1] > 0:
                        action = "scroll down"
                    else:
                        action = "scroll up"
            return action
        else:   # 起点和终点接近,认为是点击操作,从ui_element中找到与之距离最近的
            ui_element_position = np.array([example["annot_position"][i:i + 4] for i in range(0, len(example["annot_position"]), 4)])
            # 如果touch点在某个ui_element内部,直接认为是该element
            for i, bbox in enumerate(ui_element_position):
                y, x, h, w = bbox
                if x <= touch[0] <= x+w and y <= touch[1] <= y+h:
                    if example["ocr_icon"][i] == "TEXT":
                        text = example["ocr_text"][i]
                        action = "click " + text
                    else:
                        icon = example["ocr_icon"][i].split('_')[1:]
                        action = "click " + ' '.join(icon)
                    return action
            # 否则,寻找距离touch点最近的ui_element
            width = example["img_width"]
            height = example["img_height"]
            ui_center = []
            for bbox in ui_element_position:
                y, x, h, w = bbox
                y, x, h, w = y*height, x*width, h*height, w*width
                center = [x+0.5*w, y+0.5*h]
                ui_center.append(center)
            # 计算touch点到这些ui元素中心点的距离
            ui_distance = []
            for i in range(len(ui_center)):
                distance = np.sqrt((touch[0]*width - ui_center[i][0]) ** 2 + (touch[1]*height - ui_center[i][1]) ** 2)
                ui_distance.append(distance)
            min_value = min(ui_distance)
            # 使用index()方法找到最小元素的索引
            min_index = ui_distance.index(min_value)
            if example["ocr_icon"][min_index] == "TEXT":
                text = example["ocr_text"][min_index]
                action = "click "+ text
            else:
                icon = example["ocr_icon"][min_index].split('_')[1:]
                action = "click "+' '.join(icon)
            return action
    except:
        return "Error in data"

这个函数赋值给'action_addition'字段(似乎在测试时也没用到?),请问为什么会出现这么大的一个gap?是不是我"action_type_text"字段添加的与您原来实现有误?

njucckevin commented 4 months ago

因为hf上的那个checkpoint是对应正文Table3中的结果的,只用了大概2000多条指令的数据训练,然后在我们自己划分的测试集上测试,可以参考提供的资源试着复现一下,应该结果是没问题的。

dmndxld commented 4 months ago

因为hf上的那个checkpoint是对应正文Table3中的结果的,只用了大概2000多条指令的数据训练,然后在我们自己划分的测试集上测试,可以参考提供的资源试着复现一下,应该结果是没问题的。

好的,感谢师哥及时回复,谢谢!

likaixin2000 commented 1 month ago

作者您好,请问可以分享一下AiTW的处理脚本吗,非常感谢!我的邮箱是likaixin@u.nus.edu

likaixin2000 commented 1 month ago

作者您好,请问可以分享一下AiTW的处理脚本吗,非常感谢!我的邮箱是likaixin@u.nus.edu

已收到,非常感谢作者的迅速回复!