File size: 2,819 Bytes
795f737
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
"""Small vocabulary and deterministic candidate retrieval, without site rules."""
from collections import Counter
import re
import torch

KINDS = ('C', 'T', 'O')
ROLES = {'button', 'link', 'textbox', 'searchbox', 'combobox', 'checkbox', 'radio', 'listbox', 'spinbutton'}


def words(text):
    return re.findall(r'\w+', text.casefold(), flags=re.UNICODE)


def fit_vocab(rows, limit=4096):
    counts = Counter()
    for row in rows:
        counts.update(words(row['goal']))
        for element in row['elements']:
            counts.update(words(element['role'] + ' ' + element['name']))
    return {'<pad>':0, '<unk>':1, **{token:i+2 for i, (token, _) in enumerate(counts.most_common(limit-2))}}


def lexical(goal, element):
    goal_words, name_words = set(words(goal)), set(words(element['name']))
    overlap = len(goal_words & name_words)
    return [overlap / max(1,len(name_words)), overlap / max(1,len(goal_words)),
            float(element['name'].casefold() in goal.casefold()),
            float(element.get('visible', True)), float(element.get('enabled', True))]


def candidates(goal, elements, limit=40):
    eligible = [i for i, e in enumerate(elements) if e.get('visible', True) and e.get('enabled', True)
                and not e.get('sensitive', False) and e['role'] in ROLES]
    return sorted(eligible, key=lambda i: (-lexical(goal,elements[i])[0], i))[:limit]


def encode(rows, vocab, max_tokens=24, max_candidates=40):
    # Training padding uses batch size; inference uses only the surviving candidates.
    maps = [candidates(row['goal'],row['elements'],max_candidates) for row in rows]
    count = max(1, max(map(len,maps),default=0))
    goal = torch.zeros((len(rows),max_tokens),dtype=torch.long)
    element = torch.zeros((len(rows),count,max_tokens),dtype=torch.long)
    features = torch.zeros((len(rows),count,5))
    mask = torch.zeros((len(rows),count),dtype=torch.bool)
    targets = torch.full((len(rows),),-100,dtype=torch.long)
    actions = torch.full((len(rows),),-100,dtype=torch.long)
    def tokens(text):
        return [vocab.get(token,1) for token in words(text)[:max_tokens]]
    for batch,row in enumerate(rows):
        ids = tokens(row['goal'])
        goal[batch,:len(ids)] = torch.tensor(ids,dtype=torch.long)
        if row.get('action') in KINDS:
            actions[batch] = KINDS.index(row['action'])
        for j,index in enumerate(maps[batch]):
            e = row['elements'][index]
            ids = tokens(e['role'] + ' ' + e['name'])
            element[batch,j,:len(ids)] = torch.tensor(ids,dtype=torch.long)
            features[batch,j] = torch.tensor(lexical(row['goal'],e))
            mask[batch,j] = True
            if row.get('target') == index:
                targets[batch] = j
    return (goal,element,features,mask),actions,targets,maps