A fine-tuned version of TextMachineProject/NewsBERT 1800-1920, conditioned on each item's publication year as a continuous input. A single model was trained across the full 1800-1920 span of the original NewsBERT 1800-1920's dataset, with each training example's year injected as a sinusoidal (Fourier) feature added to the token embeddings. Nearby years produce nearby embeddings by construction,...
Model source
Source description
Sources
1 sourceSource excerpts
3 excerptsbasemodel: TextMachineProject/NewsBERT1800-1920 libraryname: transformers tags: bert masked-language-modeling historical-nlp temporal-language-model
TextMachineProject/NewsBERT_1800-1920ContinuousTimeEmbeddinghidden_sizeLayerNormcontinuous_time_embedding.pyAutoModelForMaskedLM.from_pretrained(...)textyear2e-51e-4torchtorchtransformerspython import torch import torch.nn.functional as F import math from huggingface_hub import hf_hub_download import shutil, os REPO_ID = "npedrazzini/NewsBERT_1800-1920-Temporal" # Download the companion module from the model repo and make it importable py_path = hf_hub_download(repo_id=REPO_ID, filename="continuous_time_embedding.py") shutil.copy(py_path, os.path.basename(py_path)) from continuous_time_embedding import load_continuous_time_model tokenizer, model = load_continuous_time_model(REPO_ID) device = next(model.parameters()).device python def predict_masked(sentence, year, top_k=5): enc = tokenizer(sentence, return_tensors="pt").to(device) mask_pos = (enc["input_ids"][0] == tokenizer.mask_token_id).nonzero(as_tuple=True)[0] if len(mask_pos) == 0: raise ValueError(f"No [MASK] found in: {sentence!r}") mask_pos = mask_pos[0].item() embeddings_module = model.get_input_embeddings_module() tok_embeds = embeddings_module(enc["input_ids"]) time_vec = model.time_embed(torch.tensor([float(year)]).to(device)).unsqueeze(1) tok_embeds = model.post_inject_norm(tok_embeds + time_vec) out = model.model(inputs_embeds=tok_embeds, attention_mask=enc["attention_mask"]) probs = F.softmax(out.logits[0, mask_pos].float(), dim=-1) top = torch.topk(probs, top_k) return [(tokenizer.convert_ids_to_tokens([tid.item()])[0], p.item()) for tid, p in zip(top.indices, top.values)] def get_target_word_probability(sentence, target_word, year): target_ids = tokenizer.encode(target_word, add_special_tokens=False) mask_str = " ".join([tokenizer.mask_token] * len(target_ids)) masked_sentence = sentence.replace(target_word, mask_str, 1) enc = tokenizer(masked_sentence, return_tensors="pt").to(device) mask_positions = (enc["input_ids"][0] == tokenizer.mask_token_id).nonzero(as_tuple=True)[0].tolist() embeddings_module = model.get_input_embeddings_module() tok_embeds = embeddings_module(enc["input_ids"]) time_vec = model.time_embed(torch.tensor([float(year)]).to(device)).unsqueeze(1) tok_embeds = model.post_inject_norm(tok_embeds + time_vec) out = model.model(inputs_embeds=tok_embeds, attention_mask=enc["attention_mask"]) logprob_sum = 0.0 for pos, tgt_id in zip(mask_positions, target_ids): log_probs = F.log_softmax(out.logits[0, pos].float(), dim=-1) logprob_sum += log_probs[tgt_id].item() prob = math.exp(logprob_sum) if len(target_ids) == 1 else None return prob, logprob_sum python ### Example 1: fill-mask, top predictions at different years sentence = "The [MASK] arrived on time." for year in [1800, 1810, 1820, 1830, 1840, 1850, 1860, 1870, 1880, 1890, 1900, 1910, 1920]: preds = predict_masked(sentence, year, top_k=5) pred_str = ", ".join(f"{tok}({p:.3f})" for tok, p in preds) print(f"year={year}: {pred_str}") # Example 2: probability of a specific word, across years sentence = "The train arrived at the station on time" for year in [1800, 1810, 1820, 1830, 1840, 1850, 1860, 1870, 1880, 1890, 1900, 1910, 1920]: prob, logprob_sum = get_target_word_probability(sentence, "train", year) if prob is not None: print(f"year={year}: P(train)={prob:.10f} (logprob_sum={logprob_sum:.4f})") else: print(f"year={year}: logprob_sum={logprob_sum:.4f} (multi-subtoken word, no single probability)") year=1800: express(0.048), steamer(0.043), mail(0.031), train(0.018), news(0.014) year=1810: express(0.064), steamer(0.044), mail(0.035), train(0.026), news(0.015) year=1820: express(0.068), steamer(0.044), mail(0.035), train(0.032), news(0.017) year=1830: express(0.078), steamer(0.043), mail(0.038), train(0.033), news(0.019) year=1840: express(0.087), steamer(0.040), train(0.039), mail(0.038), news(0.023) year=1850: express(0.081), train(0.042), steamer(0.039), mail(0.038), news(0.024) year=1860: express(0.079), train(0.042), mail(0.038), steamer(0.035), news(0.029) year=1870: express(0.080), train(0.050), mail(0.037), news(0.034), telegram(0.032) year=1880: express(0.069), train(0.051), news(0.035), mail(0.035), telegram(0.032) year=1890: express(0.063), train(0.052), news(0.039), telegram(0.034), mail(0.034) year=1900: express(0.059), train(0.048), news(0.046), telegram(0.038), mail(0.033) year=1910: express(0.054), news(0.052), train(0.047), telegram(0.039), mail(0.031) year=1920: news(0.057), express(0.049), train(0.044), telegram(0.040), mail(0.030)year=1800: P(train)=0.2596983756 (logprob_sum=-1.3482) year=1810: P(train)=0.3239273912 (logprob_sum=-1.1272) year=1820: P(train)=0.3619919056 (logprob_sum=-1.0161) year=1830: P(train)=0.3642843769 (logprob_sum=-1.0098) year=1840: P(train)=0.3824294963 (logprob_sum=-0.9612) year=1850: P(train)=0.3897419142 (logprob_sum=-0.9423) year=1860: P(train)=0.3831270530 (logprob_sum=-0.9594) year=1870: P(train)=0.3918517235 (logprob_sum=-0.9369) year=1880: P(train)=0.3810631289 (logprob_sum=-0.9648) year=1890: P(train)=0.3701365410 (logprob_sum=-0.9939) year=1900: P(train)=0.3599881482 (logprob_sum=-1.0217) year=1910: P(train)=0.3460587446 (logprob_sum=-1.0611) year=1920: P(train)=0.3430851355 (logprob_sum=-1.0698)python event_tests = [ ("The garrison at lucknow was relieved after a long siege.", "lucknow", 1857), ("President lincoln was assassinated.", "lincoln", 1865), ("After a long siege, the fortress of sebastopol finally fell.", "sebastopol", 1855), ("President mckinley was assassinated.", "mckinley", 1901), ("The queen has died.", "died", 1901)] test_years = list(range(1800, 1921, 5)) fig, axes = plt.subplots(len(event_tests), 1, figsize=(11, 4.5 * len(EVENT_TESTS))) for ax, (sentence, target_word, expected_year) in zip(axes, event_tests): masked_sentence = sentence.replace(target_word, tokenizer.mask_token, 1) n_subtokens = len(tokenizer.encode(target_word, add_special_tokens=False)) is_single_token = n_subtokens == 1 results = [get_target_word_probability(sentence, target_word, year) for year in test_years] plot_vals = [prob if is_single_token else logprob_sum for prob, logprob_sum in results] for year, (prob, logprob_sum) in zip(test_years, results): print(f"{sentence} | year={year}: " + (f"P({target_word})={prob:.6f}" if is_single_token else f"logprob_sum={logprob_sum:.4f}")) event_prob, event_logprob = get_target_word_probability(sentence, target_word, expected_year) event_val = event_prob if is_single_token else event_logprob ax.plot(test_years, plot_vals, marker="o") ax.scatter([expected_year], [event_val], marker="*", s=120, color="red", zorder=5, label=f"at {expected_year}") ax.axvline(expected_year, color="red", linestyle="--", alpha=0.4) ax.set_title(f"{'P' if is_single_token else 'log P'}({target_word!r}) across years\n\"{sentence}\"") ax.set_xlabel("queried year") ax.set_ylabel("probability" if is_single_token else "log probability") xticks = sorted(set(test_years) | {expected_year}) ax.set_xticks(xticks) ax.set_xticklabels(xticks, rotation=45, ha="right") ax.legend() ax.grid(True, alpha=0.3) plt.tight_layout() plt.show() A fine-tuned version of TextMachineProject/NewsBERT 1800-1920, conditioned on each item's publication year as a continuous input. A single model was trained across the full 1800-1920 span of the original NewsBERT 1800-1920's dataset, with each training…
npedrazzini/NewsBERT_1800-1920-Temporal