File size: 3,041 Bytes
439dcd5
 
 
 
 
 
 
 
 
 
4123863
 
 
 
439dcd5
 
4123863
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49a8dc7
 
 
 
 
4123863
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49a8dc7
4123863
 
 
 
 
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
---
title: Physh Classification
emoji: 🏆
colorFrom: red
colorTo: purple
sdk: gradio
sdk_version: 6.28.0
python_version: '3.12'
app_file: app.py
pinned: false
license: apache-2.0
models:
  - LukeFP/physh_topic_supervised_classifier
  - google/embeddinggemma-300m
---

# PhySH Topic Classifier

Paste a physics title and abstract; get back its [PhySH](https://physh.org)
**disciplines** and **top-level research-area concepts**.

## How it works

```
text ──EmbeddingGemma-300m──> 768-d vector
          │
          ├──> discipline head   768 → 1024 → 512 → 18    sigmoid
          │                                    │
          └──> concept head  [768 + 18] → 1024 → 512 → 186 sigmoid
                                   ▲
                       discipline probabilities
```

Both heads are multi-label MLPs with ReLU and dropout 0.3, trained on
EmbeddingGemma vectors. The concept head is *conditioned* on the discipline
head's output: its 786-dimensional input is the text embedding concatenated with
the 18 discipline probabilities (the checkpoint records `use_logits: False`, so
probabilities rather than logits are what it expects).

Weights live in
[`LukeFP/physh_topic_supervised_classifier`](https://huggingface.co/LukeFP/physh_topic_supervised_classifier)
and are downloaded at startup, so retraining only requires a push to that repo —
no change here.

| Head | micro-F1 | macro-F1 | avg labels/sample |
|---|---|---|---|
| Discipline (18) | 0.799 | 0.683 | 1.41 |
| Concept (186) | 0.641 | 0.423 | 2.12 |

## Setup

`google/embeddinggemma-300m` is a gated repo. Accept the Gemma license on the
model page, then add a read token as a Space secret named `HF_TOKEN`
(Settings → Variables and secrets). Without it the Space boots but the first
classification fails.

This Space runs on **ZeroGPU**: `infer()` carries the `@spaces.GPU` decorator,
the models are loaded on CPU in the main process, and device placement happens
inside the decorated function. The same code runs unchanged on CPU hardware —
`spaces` is optional at import and `torch.cuda.is_available()` picks the device.

### Prompt format

EmbeddingGemma prepends a task-specific prefix, and the prefix used here must
match the one used to build the training embeddings — a mismatch degrades
accuracy quietly instead of erroring. The default is the document prompt
(`title: none | text: …`); the Advanced panel lets you switch and compare.

## Running locally

```bash
pip install -r requirements.txt
export HF_TOKEN=hf_...
python app.py
```

Set `PHYSH_WEIGHTS_DIR=/path/to/physh_topic_supervised_classifier` to load the
`.pt` files from a local clone instead of the Hub.

## API

Gradio exposes the Space as an API, which is the practical route for batch
labelling:

```python
from gradio_client import Client

client = Client("LukeFP/Physh_Classification")
disciplines, concepts, summary = client.predict(
    "Title and abstract…", 0.5, "document — title: none | text: {}", 8,
    api_name="/classify",
)
```