xablex commited on
Commit
c81f977
·
verified ·
1 Parent(s): cfae936

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Prosody Page Classifiers
2
+
3
+ Three finetuned models for binary page classification in the
4
+ [Princeton Prosody Archive](https://prosody.princeton.edu/) corpus. Labels are
5
+ `TU` (0) and `non-TU` (1). Each subfolder is independently loadable
6
+ — use whichever modality you have inputs for.
7
+
8
+ | Folder | Model |
9
+ |--------|-------|
10
+ | `distilbert-text/` | DistilBERT text classifier (text-only) |
11
+ | `vit-image/` | ViT-base image classifier (image-only) |
12
+ | `gated-fusion/` | Gated-fusion multimodal classifier (text + image) |
13
+
14
+ - **`distilbert-text/`** and **`vit-image/`** are standard Hugging Face repos
15
+ (`AutoModelForSequenceClassification` / `AutoModelForImageClassification`).
16
+ - **`gated-fusion/`** is a custom multimodal model; load it via the bundled
17
+ `modeling_gatedfusion.py` (see that folder's README).
18
+
19
+ See each subfolder's `README.md` for a copy-paste usage snippet.
distilbert-text/README.md ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Prosody DistilBERT Text Classifier
2
+
3
+ Fine-tuned `distilbert-base-uncased` for binary page classification in the
4
+ [Princeton Prosody Archive](https://prosody.princeton.edu/) corpus, using page
5
+ **text** (OCR transcription) only.
6
+
7
+ - **Classes:** `TU` (0), `non-TU` (1)
8
+ - **Architecture:** `DistilBertForSequenceClassification` (HF-native)
9
+
10
+ ```python
11
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
12
+ import torch
13
+
14
+ model = AutoModelForSequenceClassification.from_pretrained("./distilbert-text")
15
+ tok = AutoTokenizer.from_pretrained("./distilbert-text")
16
+
17
+ enc = tok("a line of verse ...", truncation=True, max_length=512, return_tensors="pt")
18
+ with torch.no_grad():
19
+ probs = model(**enc).logits.softmax(-1)[0]
20
+ print({model.config.id2label[i]: float(p) for i, p in enumerate(probs)})
21
+ ```
22
+
23
+ `label_encoder.pkl` is the original sklearn `LabelEncoder` (`class1`->0, `class2`->1)
24
+ kept for provenance.
distilbert-text/config.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "activation": "gelu",
3
+ "architectures": [
4
+ "DistilBertForSequenceClassification"
5
+ ],
6
+ "attention_dropout": 0.1,
7
+ "bos_token_id": null,
8
+ "dim": 768,
9
+ "dropout": 0.1,
10
+ "dtype": "float32",
11
+ "eos_token_id": null,
12
+ "hidden_dim": 3072,
13
+ "id2label": {
14
+ "0": "TU",
15
+ "1": "non-TU"
16
+ },
17
+ "initializer_range": 0.02,
18
+ "label2id": {
19
+ "TU": 0,
20
+ "non-TU": 1
21
+ },
22
+ "max_position_embeddings": 512,
23
+ "model_type": "distilbert",
24
+ "n_heads": 12,
25
+ "n_layers": 6,
26
+ "pad_token_id": 0,
27
+ "problem_type": "single_label_classification",
28
+ "qa_dropout": 0.1,
29
+ "seq_classif_dropout": 0.2,
30
+ "sinusoidal_pos_embds": false,
31
+ "tie_weights_": true,
32
+ "tie_word_embeddings": true,
33
+ "transformers_version": "5.11.0",
34
+ "vocab_size": 30522
35
+ }
distilbert-text/label_encoder.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b117610370253f52309d0663253878cd20c9674e2d87456e467e3ca7ba5ecf18
3
+ size 283
distilbert-text/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a299309f21dc059ae68073b24c121e006abb3c69e2aa1a39537be15c27061b02
3
+ size 267832560
distilbert-text/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
distilbert-text/tokenizer_config.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "clean_up_tokenization_spaces": true,
4
+ "cls_token": "[CLS]",
5
+ "do_basic_tokenize": true,
6
+ "do_lower_case": true,
7
+ "is_local": true,
8
+ "local_files_only": false,
9
+ "mask_token": "[MASK]",
10
+ "model_max_length": 512,
11
+ "never_split": null,
12
+ "pad_token": "[PAD]",
13
+ "sep_token": "[SEP]",
14
+ "strip_accents": null,
15
+ "tokenize_chinese_chars": true,
16
+ "tokenizer_class": "DistilBertTokenizer",
17
+ "unk_token": "[UNK]"
18
+ }
gated-fusion/README.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Prosody Gated-Fusion Multimodal Classifier
2
+
3
+ Multimodal (text + page-image) binary classifier for the
4
+ [Princeton Prosody Archive](https://prosody.princeton.edu/) corpus. Fuses a
5
+ **DistilBERT** text encoder and a **ViT-base** image encoder with a learnable
6
+ **gate** that weights the two modalities per example.
7
+
8
+ - **Classes:** `TU` (0), `non-TU` (1)
9
+ - **Text encoder:** `distilbert-base-uncased` · **Vision encoder:** `google/vit-base-patch16-224`
10
+ - **Fusion:** per-modality projection to 512-d, a sigmoid gate over the
11
+ concatenated features, a residual combination, and a linear head.
12
+
13
+ ```python
14
+ import sys; sys.path.insert(0, ".") # so modeling_gatedfusion.py is importable
15
+ from modeling_gatedfusion import GatedFusionClassifier, GatedFusionProcessor
16
+ import torch
17
+
18
+ model = GatedFusionClassifier.from_pretrained(".")
19
+ proc = GatedFusionProcessor.from_pretrained(".")
20
+
21
+ batch = proc(text="a line of verse ...", image="page.png")
22
+ with torch.no_grad():
23
+ probs = model(**batch).softmax(-1)[0]
24
+ print({model_labels[i]: float(p) for i, p in enumerate(probs)}) # see config.json id2label
25
+ ```
26
+
27
+ `modeling_gatedfusion.py` rebuilds the DistilBERT/ViT encoders from their base
28
+ configs and loads every weight from `model.safetensors`, so no separate
29
+ base-weight files are needed.
gated-fusion/config.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "gated_fusion_multimodal",
3
+ "architectures": [
4
+ "GatedFusionClassifier"
5
+ ],
6
+ "text_model": "distilbert-base-uncased",
7
+ "vision_model": "google/vit-base-patch16-224",
8
+ "text_dim": 768,
9
+ "vision_dim": 768,
10
+ "fusion_dim": 512,
11
+ "num_classes": 2,
12
+ "max_length": 512,
13
+ "id2label": {
14
+ "0": "TU",
15
+ "1": "non-TU"
16
+ },
17
+ "label2id": {
18
+ "TU": 0,
19
+ "non-TU": 1
20
+ }
21
+ }
gated-fusion/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cf3873da40b9e678fa3da6dba6fe19f143011b187bdb4003cc1021cdc0024b3a
3
+ size 616302144
gated-fusion/modeling_gatedfusion.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Standalone definition of the Prosody gated-fusion multimodal classifier
3
+ (text = DistilBERT, vision = ViT-base) plus a `from_pretrained`-style loader.
4
+
5
+ This file is meant to travel *inside* the Hugging Face repo alongside
6
+ `config.json` and `model.safetensors`. It is fully self-contained:
7
+
8
+ * The DistilBERT and ViT encoders are rebuilt from their standard base
9
+ configurations (distilbert-base-uncased / google/vit-base-patch16-224),
10
+ which have identical layer shapes to the trained sub-encoders.
11
+ * All weights — including the encoders — are then loaded from
12
+ `model.safetensors`, so NO separate base-weight files are required.
13
+
14
+ Usage:
15
+ from modeling_gatedfusion import GatedFusionClassifier, GatedFusionProcessor
16
+ model = GatedFusionClassifier.from_pretrained("path/to/repo")
17
+ processor = GatedFusionProcessor.from_pretrained("path/to/repo")
18
+
19
+ batch = processor(text="a line of verse", image="page.png")
20
+ logits = model(**batch)
21
+ """
22
+
23
+ import json
24
+ import os
25
+
26
+ import torch
27
+ import torch.nn as nn
28
+ from PIL import Image
29
+ from safetensors.torch import load_file
30
+ from transformers import (
31
+ DistilBertConfig, DistilBertModel, DistilBertTokenizer,
32
+ ViTConfig, ViTModel, ViTImageProcessor,
33
+ )
34
+
35
+
36
+ class GatedFusionClassifier(nn.Module):
37
+ """Learnable gating mechanism to balance text and vision modalities."""
38
+
39
+ def __init__(self, distilbert_model, vit_model, num_classes=2, fusion_dim=512):
40
+ super().__init__()
41
+ self.distilbert = distilbert_model
42
+ self.vit = vit_model
43
+
44
+ self.text_dim = self.distilbert.config.hidden_size
45
+ self.vision_dim = self.vit.config.hidden_size
46
+
47
+ # Project each modality to a common dimension
48
+ self.text_projection = nn.Linear(self.text_dim, fusion_dim)
49
+ self.vision_projection = nn.Linear(self.vision_dim, fusion_dim)
50
+
51
+ # Gating mechanism
52
+ self.gate = nn.Sequential(
53
+ nn.Linear(fusion_dim * 2, fusion_dim),
54
+ nn.ReLU(),
55
+ nn.Linear(fusion_dim, 2),
56
+ nn.Sigmoid(),
57
+ )
58
+
59
+ self.classifier = nn.Linear(fusion_dim, num_classes)
60
+ self.dropout = nn.Dropout(0.1)
61
+
62
+ def forward(self, input_ids, attention_mask, pixel_values):
63
+ text_features = self.distilbert(
64
+ input_ids=input_ids, attention_mask=attention_mask
65
+ ).last_hidden_state[:, 0]
66
+ vision_features = self.vit(pixel_values=pixel_values).last_hidden_state[:, 0]
67
+
68
+ text_proj = self.text_projection(text_features)
69
+ vision_proj = self.vision_projection(vision_features)
70
+
71
+ gates = self.gate(torch.cat([text_proj, vision_proj], dim=1))
72
+ gated_text = text_proj * gates[:, 0:1]
73
+ gated_vision = vision_proj * gates[:, 1:2]
74
+
75
+ # Combine gated modalities with a residual connection
76
+ fused = gated_text + gated_vision + text_proj + vision_proj
77
+ fused = self.dropout(fused)
78
+ return self.classifier(fused)
79
+
80
+ @classmethod
81
+ def from_pretrained(cls, repo_dir, map_location="cpu"):
82
+ """Rebuild the architecture from config.json and load model.safetensors."""
83
+ with open(os.path.join(repo_dir, "config.json")) as f:
84
+ cfg = json.load(f)
85
+
86
+ # Rebuild encoders from base configs (architecture only — weights come
87
+ # from the safetensors file below). Defaults match the base checkpoints.
88
+ distilbert = DistilBertModel(DistilBertConfig())
89
+ vit = ViTModel(ViTConfig())
90
+
91
+ model = cls(
92
+ distilbert,
93
+ vit,
94
+ num_classes=cfg.get("num_classes", 2),
95
+ fusion_dim=cfg.get("fusion_dim", 512),
96
+ )
97
+
98
+ state = load_file(os.path.join(repo_dir, "model.safetensors"), device=map_location)
99
+ model.load_state_dict(state, strict=True) # raises if any key mismatches
100
+ model.eval()
101
+ return model
102
+
103
+
104
+ class GatedFusionProcessor:
105
+ """Bundles the DistilBERT tokenizer and ViT image processor."""
106
+
107
+ def __init__(self, tokenizer, image_processor, max_length=512):
108
+ self.tokenizer = tokenizer
109
+ self.image_processor = image_processor
110
+ self.max_length = max_length
111
+
112
+ @classmethod
113
+ def from_pretrained(cls, repo_dir):
114
+ with open(os.path.join(repo_dir, "config.json")) as f:
115
+ cfg = json.load(f)
116
+ tokenizer = DistilBertTokenizer.from_pretrained(repo_dir)
117
+ image_processor = ViTImageProcessor.from_pretrained(repo_dir)
118
+ return cls(tokenizer, image_processor, max_length=cfg.get("max_length", 512))
119
+
120
+ def __call__(self, text, image):
121
+ """`image` may be a path or a PIL.Image."""
122
+ if isinstance(image, str):
123
+ image = Image.open(image).convert("RGB")
124
+ enc = self.tokenizer(
125
+ text,
126
+ truncation=True,
127
+ padding="max_length",
128
+ max_length=self.max_length,
129
+ return_tensors="pt",
130
+ )
131
+ pixel_values = self.image_processor(image, return_tensors="pt")["pixel_values"]
132
+ return {
133
+ "input_ids": enc["input_ids"],
134
+ "attention_mask": enc["attention_mask"],
135
+ "pixel_values": pixel_values,
136
+ }
gated-fusion/preprocessor_config.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "do_normalize": true,
3
+ "do_rescale": true,
4
+ "do_resize": true,
5
+ "image_mean": [
6
+ 0.5,
7
+ 0.5,
8
+ 0.5
9
+ ],
10
+ "image_processor_type": "ViTImageProcessor",
11
+ "image_std": [
12
+ 0.5,
13
+ 0.5,
14
+ 0.5
15
+ ],
16
+ "resample": 2,
17
+ "rescale_factor": 0.00392156862745098,
18
+ "size": {
19
+ "height": 224,
20
+ "width": 224
21
+ }
22
+ }
gated-fusion/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
gated-fusion/tokenizer_config.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "cls_token": "[CLS]",
4
+ "do_lower_case": true,
5
+ "is_local": false,
6
+ "local_files_only": false,
7
+ "mask_token": "[MASK]",
8
+ "model_max_length": 512,
9
+ "pad_token": "[PAD]",
10
+ "sep_token": "[SEP]",
11
+ "strip_accents": null,
12
+ "tokenize_chinese_chars": true,
13
+ "tokenizer_class": "DistilBertTokenizer",
14
+ "unk_token": "[UNK]"
15
+ }
vit-image/README.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Prosody ViT Image Classifier
2
+
3
+ Fine-tuned `google/vit-base-patch16-224` for binary page classification in the
4
+ [Princeton Prosody Archive](https://prosody.princeton.edu/) corpus, using the
5
+ scanned page **image** only.
6
+
7
+ - **Classes:** `TU` (0), `non-TU` (1)
8
+ - **Architecture:** `ViTForImageClassification` (HF-native)
9
+
10
+ ```python
11
+ from transformers import AutoModelForImageClassification, AutoImageProcessor
12
+ from PIL import Image
13
+ import torch
14
+
15
+ model = AutoModelForImageClassification.from_pretrained("./vit-image")
16
+ proc = AutoImageProcessor.from_pretrained("./vit-image")
17
+
18
+ img = Image.open("page.png").convert("RGB")
19
+ inp = proc(img, return_tensors="pt")
20
+ with torch.no_grad():
21
+ probs = model(**inp).logits.softmax(-1)[0]
22
+ print({model.config.id2label[i]: float(p) for i, p in enumerate(probs)})
23
+ ```
vit-image/config.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "ViTForImageClassification"
4
+ ],
5
+ "attention_probs_dropout_prob": 0.0,
6
+ "dtype": "float32",
7
+ "encoder_stride": 16,
8
+ "hidden_act": "gelu",
9
+ "hidden_dropout_prob": 0.0,
10
+ "hidden_size": 768,
11
+ "id2label": {
12
+ "0": "TU",
13
+ "1": "non-TU"
14
+ },
15
+ "image_size": 224,
16
+ "initializer_range": 0.02,
17
+ "intermediate_size": 3072,
18
+ "label2id": {
19
+ "TU": 0,
20
+ "non-TU": 1
21
+ },
22
+ "layer_norm_eps": 1e-12,
23
+ "model_type": "vit",
24
+ "num_attention_heads": 12,
25
+ "num_channels": 3,
26
+ "num_hidden_layers": 12,
27
+ "patch_size": 16,
28
+ "pooler_act": "tanh",
29
+ "pooler_output_size": 768,
30
+ "qkv_bias": true,
31
+ "transformers_version": "5.11.0"
32
+ }
vit-image/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:08d5b964ecef0e4178107b9896d5e5cb83c629829465cc9a0ecb69600b4909f8
3
+ size 343223968
vit-image/preprocessor_config.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "do_normalize": true,
3
+ "do_rescale": true,
4
+ "do_resize": true,
5
+ "image_mean": [
6
+ 0.5,
7
+ 0.5,
8
+ 0.5
9
+ ],
10
+ "image_processor_type": "ViTImageProcessor",
11
+ "image_std": [
12
+ 0.5,
13
+ 0.5,
14
+ 0.5
15
+ ],
16
+ "resample": 2,
17
+ "rescale_factor": 0.00392156862745098,
18
+ "size": {
19
+ "height": 224,
20
+ "width": 224
21
+ }
22
+ }