CoolFace
Modelpublic

lucymakeit/dom-node-classifier

sourceHugging Faceapache-2.0updated 4mo agoView on Hugging Face
0likes10downloads
Model Card

dom-node-classifier

Model description

dom-node-classifier is a GATv2 (Graph Attention Network v2) that classifies every node of an HTML DOM into one of 14 semantic classes. It is designed to serve as a perception layer for browser agents and web annotation pipelines.

The model takes a structured DOM representation (nodes with features + a tree edge index) and outputs a class label and confidence score per node. It does not process raw HTML or screenshots — the DOM must be pre-extracted into the JSON format described below.

Architecture: GATv2 with 3 message-passing layers, 4 attention heads, hidden dimension 128, and a learned input projection that mixes heterogeneous node features before graph propagation.

Why GATv2 over GAT v1? GATv1's attention is static (monotonic across queries). GATv2 (Brody, Alon & Yahav, 2022) introduces a non-linearity inside the attention mechanism, enabling truly dynamic, query-dependent attention weights. This matters for DOM nodes whose relevance depends heavily on context.


Intended uses

  • Browser agent perception: replacing raw HTML with a typed, confidence-ranked element list to reduce LLM context usage.
  • DOM annotation: automatically labeling nodes in a page corpus for downstream ML tasks.
  • Web research: studying element-type distributions across sites, languages, and page categories.

Out-of-scope uses

  • Accessibility compliance: the model classifies semantic roles as observed in the wild, not as defined by WCAG or ARIA specifications. Do not use it for accessibility audits.
  • Production-critical UX automation without human oversight: F1 on thin classes (particularly action_input, action_select, structure_dismissible) is insufficient for fully unattended operation.
  • Adversarial robustness: the model was not trained against adversarially obfuscated DOM structures.

How to use

python
from model.inference import DOMClassifier
from pathlib import Path
import json

# Load from HuggingFace weights (model.safetensors + config.json must be in the same directory)
clf = DOMClassifier.from_checkpoint("checkpoints_final/model.safetensors")
# Or from a local .pt checkpoint:  DOMClassifier.from_checkpoint("checkpoints_final/best.pt")

raw_page = json.loads(Path("examples/sample_page.json").read_text())
predictions = clf.classify_page(raw_page, action_only=False, min_confidence=0.5)

for p in predictions:
    print(f"[{p['class']:25s}] {p['confidence']:.2f}  {p['selector']}")

Input format

raw_page is a dict with the following top-level keys:

KeyTypeDescription
urlstringPage URL (used for link feature computation)
viewportdict {width, height}Viewport dimensions in pixels
nodeslist of node dictsOne entry per DOM node
edgeslist of [src_idx, dst_idx] pairsParent→child edges using node list indices

Each node dict:

KeyRequiredTypeDescription
idyesstringUnique node identifier
tagyesstringHTML tag name (e.g. "button", "div")
textnostringVisible text content (truncated to 200 chars)
selectornostringCSS selector (returned in predictions, not used as feature)
classesnolist[str]CSS class tokens
attrsnodictHTML attributes (href, id, type, role, …)
cssnodictComputed CSS (display, position, visibility, opacity, cursor, font_size, font_weight, z_index)
bboxnodict {x, y, width, height}Bounding box in pixels
depthnointDOM depth from root
n_childrennointNumber of direct children
is_visiblenoboolWhether the node is visible
in_viewportnoboolWhether the node is in the initial viewport
has_listeners_heuristicnoboolWhether the node likely has JS event listeners

Missing optional fields default to sensible zeros/empty values.

A complete example is in `examples/sample_page.json`.


Training data

The model was trained on a curated set of ~135 diverse web pages spanning e-commerce, SaaS, documentation, news, government, and forms, in English and French. Labels were generated by a deterministic heuristic pipeline based on HTML semantics, ARIA roles, CSS properties, and link structure — not by human annotators.

The training dataset is not publicly distributed.


Training procedure

Hardware: NVIDIA L40S (48 GB VRAM)

Hyperparameters:

ParameterValue
Epochs80 (early stopping, patience=15)
Batch size8 pages
OptimizerAdamW
Learning rate1e-3
LR scheduleCosine annealing
Weight decay1e-4
Dropout0.3
Hidden dim128
Attention heads4
GATv2 layers3
Class weightingsqrt-inverse frequency
Edge augmentationReverse edges + sibling edges

Feature vector (618 dims/node):

Feature blockDimsNotes
Tag one-hot5150 tags + OOV bucket
Class hash128Hashing trick over CSS class tokens (Tailwind-robust)
Attribute presence17id, href, role, aria-*, type, placeholder, …
Computed CSS28display (11) + position (5) + 6 numeric CSS values
Bounding box5x, y, w, h, area (normalized by viewport)
Topology5depth, nchildren, isvisible, inviewport, haslisteners
Link semantics9absolute/relative/fragment/mailto, same-host/domain, path depth
Text embedding384MiniLM-L6-v2 sentence embedding (frozen)

Validation criterion: best checkpoint selected by macro-F1 on the validation split.

Data split: 70 / 15 / 15 train/val/test, stratified by page.


Evaluation results

Evaluated on a held-out test set (15% of pages, stratified split). Numbers reported as mean ± std across 5 independent training runs with different random seeds.

MetricMean ± stdMinMax
Macro F10.825 ± 0.0260.7970.865
Weighted F10.917 ± 0.0320.8820.965
Action F1 (5 classes)0.895 ± 0.0360.8180.917

Per-class F1, mean ± std across 5 seeds:

ClassMean F1StdTest support (best seed)
action_input0.6860.10425
action_select0.7680.0868
action_button0.9090.0711 577
action_link_internal0.9960.0043 119
action_link_external0.9960.003327
structure_navigation0.8840.06252
structure_region0.7700.14052
structure_dismissible0.3630.073158
structure_card0.6250.1991 045
structure_list_item0.9740.0153 885
content_heading0.9860.007525
content_text0.7360.067322
content_media0.9150.0351 319
noise0.9380.02218 345

Limitations

  • Low-support classes. action_input (n=25) and action_select (n=8) have very small test sets — F1 estimates for these classes have high variance and should not be over-interpreted.
  • `structure_dismissible` is hard. Cookie banners and modal overlays vary enormously across sites. Mean F1 of 0.363 reflects genuine label ambiguity, not a model bug.
  • Heuristic labels. Training labels come from deterministic rules, not human annotation. Near-boundary elements (e.g. a decorative <button> vs. a functional one) may be mislabeled.
  • No price class. Numerical price strings are classified as noise. This is a known gap.
  • Static DOM only. The model operates on a single DOM snapshot. Dynamically loaded content, shadow DOM, and canvas elements are not modeled.
  • Dataset size and diversity. ~135 pages, English and French only. Sites in other languages or with highly unusual layouts are out-of-distribution.

Bias and ethical considerations

  • The model encodes statistical regularities of how web developers structure pages in the training data. Sites that deviate from common patterns (niche CMS, custom frameworks) may see lower accuracy.
  • The noise class is a catch-all for elements that don't fit other categories. Misclassified functional elements (e.g. a decorative-looking but important button) will be silently dropped in action_only=True mode. Always set a confidence threshold and review low-confidence predictions.
  • The model should not be used as the sole decision-maker for automated actions on behalf of users without oversight.

License

Apache 2.0 — see LICENSE.

Citation

If you use this model in your work, a link back to this repository is appreciated.

Contact

Lucy Paureau · lmi.rest · lucy.paureau@gmail.com