File size: 3,421 Bytes
396164f
 
 
 
745bcdf
 
 
396164f
 
 
745bcdf
 
 
 
 
396164f
745bcdf
396164f
745bcdf
396164f
 
 
 
745bcdf
396164f
745bcdf
396164f
745bcdf
396164f
 
745bcdf
 
 
396164f
 
 
745bcdf
396164f
 
745bcdf
 
396164f
745bcdf
396164f
 
 
745bcdf
 
 
 
 
 
1da84f0
745bcdf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1da84f0
745bcdf
 
 
 
 
1da84f0
745bcdf
 
 
 
 
396164f
745bcdf
396164f
 
 
 
 
 
 
 
745bcdf
396164f
 
 
 
 
 
 
745bcdf
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
---
base_model: roberta-base
language: en
tags:
  - narrative
  - text-classification
  - pytorch
---

# narrative-event-relation-roberta

RoBERTa-base fine-tuned for event relation identification: binary temporal (sequential vs non-sequential) and causal classification. Uses entity markers [E1]/[E2] inserted at character offsets. Trained on LLM pseudo-labels with held-out human gold evaluation.

> **Note:** Full model card with training details coming soon.

## Loading

Download `model.pt` and `tokenizer/` from this repo, then:

```python
import torch
from transformers import AutoModel, AutoTokenizer
from torch import nn

ENTITY_MARKERS = ["[E1]", "[/E1]", "[E2]", "[/E2]"]

class EventRelationRoBERTa(nn.Module):
    def __init__(self, model_name, n_new_tokens):
        super().__init__()
        self.backbone = AutoModel.from_pretrained(model_name)
        # Required: training resized the vocab for the 4 entity markers.
        # Without this, load_state_dict fails on an embedding size mismatch.
        self.backbone.resize_token_embeddings(self.backbone.config.vocab_size + n_new_tokens)
        hidden = self.backbone.config.hidden_size
        self.temporal_head = nn.Linear(hidden, 1)
        self.causal_head   = nn.Linear(hidden, 1)

    def forward(self, input_ids, attention_mask):
        cls = self.backbone(input_ids=input_ids, attention_mask=attention_mask).last_hidden_state[:, 0, :]
        return self.temporal_head(cls).squeeze(-1), self.causal_head(cls).squeeze(-1)

tokenizer = AutoTokenizer.from_pretrained("tokenizer/")
model = EventRelationRoBERTa("roberta-base", len(ENTITY_MARKERS))
model.load_state_dict(torch.load("model.pt", map_location="cpu", weights_only=True))
model.eval()
```

## Input format

The model takes a single string with the two candidate event triggers wrapped in
entity markers at their character offsets. It never sees spans as structured input.

```python
def insert_markers(text, span1, span2):
    """span1/span2 are [char_start, char_end, ...]; span1 must precede span2."""
    insertions = sorted([
        (span1[0], "[E1]"), (span1[1], "[/E1]"),
        (span2[0], "[E2]"), (span2[1], "[/E2]"),
    ], key=lambda x: -x[0])
    for pos, marker in insertions:
        text = text[:pos] + marker + text[pos:]
    return text

text = "She opened the door and the cat escaped."
marked = insert_markers(text, [4, 10], [32, 39])
# 'She [E1]opened[/E1] the door and the cat [E2]escaped[/E2].'

enc = tokenizer(marked, max_length=256, padding="max_length",
                truncation=True, return_tensors="pt")
with torch.no_grad():
    t_logit, c_logit = model(enc["input_ids"], enc["attention_mask"])

# Raw logits; threshold at 0 (equivalently, sigmoid > 0.5).
is_sequential = bool(t_logit > 0)   # temporal: sequential vs not
is_causal     = bool(c_logit > 0)   # causal: causally related vs not
```

Note `max_length=256`: markers pushed past that limit are truncated away and the
prediction becomes meaningless. Check that both markers survive tokenization for
long inputs.

## Config

```json
{
  "model_name": "roberta-base",
  "max_len": 256,
  "dims": [
    "temporal_sequential",
    "causal"
  ],
  "data_source": "/projects/tejo9855/Projects/llm-narrative-annotations/event_relation/outputs/google_gemma-4-31B-it/20260518_143249",
  "n_train": 6219,
  "n_val": 690,
  "val_frac": 0.1,
  "best_epoch": 4,
  "seed": 42,
  "test_f1_gold": 0.805
}
```