# COMBO-NLP

A library for Morphosyntactic Tagging and Dependency Parsing based on [Universal Dependencies](https://universaldependencies.org/).

## Features

- **Multi-task learning**: morphosyntactic tagging (UPOS, XPOS, UFeats), lemmatization, dependency parsing
- **Biaffine attention** for dependency parsing, **character-level seq2seq** lemmatization
- **Two training modes**: full fine-tuning and LoRA (Low-Rank Adaptation)
- **CoNLL 2018 evaluation**: UPOS, XPOS, UFeats, AllTags, Lemmas, UAS, LAS, CLAS, MLAS, BLEX
- **Multi-treebank training**: combine multiple treebanks for the same language
- **Device support**: NVIDIA CUDA, Apple MPS, CPU
- **Full pipeline**: train → export → upload to HuggingFace Hub in one command

## Installation

### uv package manager (if not installed)
macOS / Linux
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
Windows
```powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

### Automatic installation
Create a new virtual environment.
```
uv venv
source .venv/bin/activate
```
Install COMBO-NLP.
```
uv pip install combo-nlp
```

### LAMBO segmenter (optional)

A segmenter is only needed when passing raw text strings to COMBO. If you provide pre-tokenized input (`list[str]` or `list[list[str]]`), no segmenter is required.

When you initialize COMBO with a language name (e.g. `COMBO("Polish")`), it automatically loads a [LAMBO](https://gitlab.clarin-pl.eu/syntactic-tools/lambo) segmenter. If LAMBO is not installed, an `ImportError` is raised. LAMBO is hosted on a custom PyPI index and must be installed separately:

```bash
uv pip install --index-url https://pypi.clarin-pl.eu/ lambo
```

Alternatively, add the custom index to your project's `pyproject.toml` so that `lambo` resolves automatically:

```toml
[[tool.uv.index]]
url = "https://pypi.clarin-pl.eu/"
```

### Source installation

Clone the repository and install in a virtual environment.

```bash
git clone https://gitlab.clarin-pl.eu/syntactic-tools/combo-nlp.git
cd combo-nlp
uv venv
source .venv/bin/activate
uv sync
```

## Basic usage

After installing via `pip install combo-nlp`, use COMBO directly in Python:

```python
from combo import COMBO

# Load by HuggingFace model ID:
nlp = COMBO.from_pretrained("clarin-pl/combo-nlp-xlm-roberta-base-polish-pdb-ud2.17")
result = nlp("Ala ma kota.")

# Or load by language name (with Lambo segmenter):
nlp = COMBO("Polish")
result = nlp("Ala ma kota.")

# Or use the Language enum:
from combo import Language
nlp = COMBO(Language.POLISH)
result = nlp("Ala ma kota.")

# Multiple sentences:
result = nlp(["Ala ma kota.", "Pies je."])

# Access results:
for sentence in result:
    for token in sentence:
        print(token.form, token.upos, token.head, token.deprel, token.lemma)
```

### Pre-tokenized input

```python
from combo import COMBO

nlp = COMBO.from_pretrained("clarin-pl/combo-nlp-xlm-roberta-base-polish-pdb-ud2.17")

# Single sentence:
result = nlp(["Ala", "ma", "kota", "."], tokenized=True)

# Multiple sentences:
result = nlp([["Ala", "ma", "kota", "."], ["Pies", "je", "."]], tokenized=True)
```

## Training & Evaluation

### Environment Setup

Copy `.env.example` to `.env` and fill in your API keys:

```bash
cp .env.example .env
```

- **`WANDB_API_KEY`** — from https://wandb.ai/authorize (experiment tracking)
- **`HF_TOKEN`** — from https://huggingface.co/settings/tokens (uploading models)

### Training

```bash
combo-nlp-train --task-config config/tasks/combo-nlp-xlm-roberta-base-polish-pdb-ud2.17.yaml

# Resume from checkpoint:
combo-nlp-train --task-config config/tasks/combo-nlp-xlm-roberta-base-polish-pdb-ud2.17.yaml \
    --resume /path/to/checkpoint_latest.pt
```

### Evaluation

Evaluation uses the official CoNLL 2018 script. The model can be loaded from HuggingFace Hub (`--model`), a local directory (`--model-dir`), or a training checkpoint (`--task-config` + `--checkpoint`).

```bash
# Evaluate a HuggingFace model (aligned, gold tokenization):
combo-nlp-evaluate --model clarin-pl/combo-nlp-xlm-roberta-base-polish-pdb-ud2.17 \
    --test-file /path/to/test.conllu

# Full-text evaluation (end-to-end with LAMBO segmentation):
combo-nlp-evaluate --model clarin-pl/combo-nlp-xlm-roberta-base-polish-pdb-ud2.17 \
    --test-file /path/to/test.conllu --full-text --language Polish

# Evaluate a training checkpoint:
combo-nlp-evaluate --task-config config/tasks/combo-nlp-xlm-roberta-base-polish-pdb-ud2.17.yaml

# Compare two CoNLL-U files directly:
combo-nlp-evaluate \
    --gold-file /path/to/gold.conllu \
    --predictions-file /path/to/predictions.conllu
```

### Prediction

```bash
combo-nlp-predict --model-dir /path/to/model --text "Ala ma kota ."

# Parse a CoNLL-U file:
combo-nlp-predict --model-dir /path/to/model \
    --input input.conllu --output output.conllu
```

## Configuration

Two-level config system:

- **`config/base.yaml`** — shared defaults (architecture, hyperparameters, training settings)
- **`config/tasks/<task>.yaml`** — task-specific overrides (language, treebanks, base model)

Task configs are deep-merged on top of base config — only specify fields that differ. To add a new language, create a task config and train.

## Full Pipeline

Train, export, and upload to HuggingFace Hub in one command:

```bash
combo-nlp-pipeline --task-config config/tasks/combo-nlp-xlm-roberta-base-polish-pdb-ud2.17.yaml

# Dry run:
combo-nlp-pipeline --task-config config/tasks/combo-nlp-xlm-roberta-base-polish-pdb-ud2.17.yaml --dry-run
```

Steps: **Train** (fine-tune with per-epoch evaluation) → **Export** (package model, generate model card) → **Upload** (push to HuggingFace Hub).

## CLI Commands

| Command | Description |
|---|---|
| `combo-nlp-train` | Train a model |
| `combo-nlp-evaluate` | Evaluate a model |
| `combo-nlp-predict` | Run predictions |
| `combo-nlp-export` | Export a trained model |
| `combo-nlp-pipeline` | Full train → export → upload pipeline |

## License

## Citations
