Upload folder using huggingface_hub
Browse files- src/__pycache__/__init__.cpython-311.pyc +0 -0
- src/__pycache__/__init__.cpython-314.pyc +0 -0
- src/__pycache__/config.cpython-311.pyc +0 -0
- src/__pycache__/config.cpython-314.pyc +0 -0
- src/__pycache__/evaluate.cpython-311.pyc +0 -0
- src/__pycache__/evaluate.cpython-314.pyc +0 -0
- src/__pycache__/infer.cpython-311.pyc +0 -0
- src/__pycache__/infer.cpython-314.pyc +0 -0
- src/__pycache__/preprocess.cpython-311.pyc +0 -0
- src/__pycache__/preprocess.cpython-314.pyc +0 -0
- src/__pycache__/train.cpython-311.pyc +0 -0
- src/__pycache__/train.cpython-314.pyc +0 -0
- src/multilingual_sentiment_analysis.egg-info/PKG-INFO +8 -0
- src/multilingual_sentiment_analysis.egg-info/SOURCES.txt +15 -0
- src/multilingual_sentiment_analysis.egg-info/dependency_links.txt +1 -0
- src/multilingual_sentiment_analysis.egg-info/requires.txt +4 -0
- src/multilingual_sentiment_analysis.egg-info/top_level.txt +2 -0
- src/multilingual_sentiment_analysis/__init__.py +0 -0
- src/multilingual_sentiment_analysis/__pycache__/__init__.cpython-311.pyc +0 -0
- src/multilingual_sentiment_analysis/__pycache__/__init__.cpython-314.pyc +0 -0
- src/multilingual_sentiment_analysis/__pycache__/config.cpython-311.pyc +0 -0
- src/multilingual_sentiment_analysis/__pycache__/config.cpython-314.pyc +0 -0
- src/multilingual_sentiment_analysis/__pycache__/evaluate.cpython-314.pyc +0 -0
- src/multilingual_sentiment_analysis/__pycache__/infer.cpython-311.pyc +0 -0
- src/multilingual_sentiment_analysis/__pycache__/infer.cpython-314.pyc +0 -0
- src/multilingual_sentiment_analysis/__pycache__/preprocess.cpython-311.pyc +0 -0
- src/multilingual_sentiment_analysis/__pycache__/preprocess.cpython-314.pyc +0 -0
- src/multilingual_sentiment_analysis/__pycache__/train.cpython-314.pyc +0 -0
- src/multilingual_sentiment_analysis/config.py +17 -0
- src/multilingual_sentiment_analysis/evaluate.py +89 -0
- src/multilingual_sentiment_analysis/infer.py +62 -0
- src/multilingual_sentiment_analysis/preprocess.py +60 -0
- src/multilingual_sentiment_analysis/train.py +66 -0
src/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (190 Bytes). View file
|
|
|
src/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (180 Bytes). View file
|
|
|
src/__pycache__/config.cpython-311.pyc
ADDED
|
Binary file (1.39 kB). View file
|
|
|
src/__pycache__/config.cpython-314.pyc
ADDED
|
Binary file (1.29 kB). View file
|
|
|
src/__pycache__/evaluate.cpython-311.pyc
ADDED
|
Binary file (7.44 kB). View file
|
|
|
src/__pycache__/evaluate.cpython-314.pyc
ADDED
|
Binary file (6.83 kB). View file
|
|
|
src/__pycache__/infer.cpython-311.pyc
ADDED
|
Binary file (4.66 kB). View file
|
|
|
src/__pycache__/infer.cpython-314.pyc
ADDED
|
Binary file (4.58 kB). View file
|
|
|
src/__pycache__/preprocess.cpython-311.pyc
ADDED
|
Binary file (5.26 kB). View file
|
|
|
src/__pycache__/preprocess.cpython-314.pyc
ADDED
|
Binary file (5.03 kB). View file
|
|
|
src/__pycache__/train.cpython-311.pyc
ADDED
|
Binary file (4.54 kB). View file
|
|
|
src/__pycache__/train.cpython-314.pyc
ADDED
|
Binary file (4.24 kB). View file
|
|
|
src/multilingual_sentiment_analysis.egg-info/PKG-INFO
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: multilingual_sentiment_analysis
|
| 3 |
+
Version: 0.1.0
|
| 4 |
+
Requires-Dist: transformers
|
| 5 |
+
Requires-Dist: datasets
|
| 6 |
+
Requires-Dist: torch
|
| 7 |
+
Requires-Dist: gradio
|
| 8 |
+
Dynamic: requires-dist
|
src/multilingual_sentiment_analysis.egg-info/SOURCES.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
README.md
|
| 2 |
+
setup.py
|
| 3 |
+
src/multilingual_sentiment_analysis/__init__.py
|
| 4 |
+
src/multilingual_sentiment_analysis/config.py
|
| 5 |
+
src/multilingual_sentiment_analysis/evaluate.py
|
| 6 |
+
src/multilingual_sentiment_analysis/infer.py
|
| 7 |
+
src/multilingual_sentiment_analysis/preprocess.py
|
| 8 |
+
src/multilingual_sentiment_analysis/train.py
|
| 9 |
+
src/multilingual_sentiment_analysis.egg-info/PKG-INFO
|
| 10 |
+
src/multilingual_sentiment_analysis.egg-info/SOURCES.txt
|
| 11 |
+
src/multilingual_sentiment_analysis.egg-info/dependency_links.txt
|
| 12 |
+
src/multilingual_sentiment_analysis.egg-info/requires.txt
|
| 13 |
+
src/multilingual_sentiment_analysis.egg-info/top_level.txt
|
| 14 |
+
tests/test_app.py
|
| 15 |
+
tests/test_preprocess.py
|
src/multilingual_sentiment_analysis.egg-info/dependency_links.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
src/multilingual_sentiment_analysis.egg-info/requires.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
transformers
|
| 2 |
+
datasets
|
| 3 |
+
torch
|
| 4 |
+
gradio
|
src/multilingual_sentiment_analysis.egg-info/top_level.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
app
|
| 2 |
+
multilingual_sentiment_analysis
|
src/multilingual_sentiment_analysis/__init__.py
ADDED
|
File without changes
|
src/multilingual_sentiment_analysis/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (222 Bytes). View file
|
|
|
src/multilingual_sentiment_analysis/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (146 Bytes). View file
|
|
|
src/multilingual_sentiment_analysis/__pycache__/config.cpython-311.pyc
ADDED
|
Binary file (1.43 kB). View file
|
|
|
src/multilingual_sentiment_analysis/__pycache__/config.cpython-314.pyc
ADDED
|
Binary file (1.32 kB). View file
|
|
|
src/multilingual_sentiment_analysis/__pycache__/evaluate.cpython-314.pyc
ADDED
|
Binary file (6.86 kB). View file
|
|
|
src/multilingual_sentiment_analysis/__pycache__/infer.cpython-311.pyc
ADDED
|
Binary file (5.05 kB). View file
|
|
|
src/multilingual_sentiment_analysis/__pycache__/infer.cpython-314.pyc
ADDED
|
Binary file (4.94 kB). View file
|
|
|
src/multilingual_sentiment_analysis/__pycache__/preprocess.cpython-311.pyc
ADDED
|
Binary file (5.29 kB). View file
|
|
|
src/multilingual_sentiment_analysis/__pycache__/preprocess.cpython-314.pyc
ADDED
|
Binary file (5.06 kB). View file
|
|
|
src/multilingual_sentiment_analysis/__pycache__/train.cpython-314.pyc
ADDED
|
Binary file (4.27 kB). View file
|
|
|
src/multilingual_sentiment_analysis/config.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
| 4 |
+
DATA_DIR = PROJECT_ROOT / "data"
|
| 5 |
+
AIRLINE_DATA_DIR = DATA_DIR / "airline_tweets"
|
| 6 |
+
MULTILINGUAL_DATA_DIR = DATA_DIR / "multilingual"
|
| 7 |
+
AIRLINE_RESULTS_DIR = PROJECT_ROOT / "results" / "airline_tweets"
|
| 8 |
+
RESULTS_DIR = PROJECT_ROOT / "results" / "multilingual"
|
| 9 |
+
AIRLINE_MODEL_DIR = PROJECT_ROOT / "sentiment_model" / "airline-tweets-sentiment-model"
|
| 10 |
+
MODEL_DIR = PROJECT_ROOT / "sentiment_model" / "general-multilingual-sentiment-model"
|
| 11 |
+
|
| 12 |
+
MODEL_CHECKPOINT = "xlm-roberta-base"
|
| 13 |
+
LABEL2ID = {"negative": 0, "neutral": 1, "positive": 2}
|
| 14 |
+
ID2LABEL = {value: key for key, value in LABEL2ID.items()}
|
| 15 |
+
MAX_LENGTH = 128
|
| 16 |
+
DATASET_NAME = "cardiffnlp/tweet_sentiment_multilingual"
|
| 17 |
+
LANGUAGES = ("arabic", "english", "french", "german", "hindi", "italian", "portuguese", "spanish")
|
src/multilingual_sentiment_analysis/evaluate.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Evaluate a saved sentiment model and produce plots."""
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import matplotlib.pyplot as plt
|
| 8 |
+
import numpy as np
|
| 9 |
+
import seaborn as sns
|
| 10 |
+
from datasets import load_from_disk
|
| 11 |
+
from sklearn.metrics import classification_report, confusion_matrix
|
| 12 |
+
from transformers import AutoModelForSequenceClassification, AutoTokenizer, Trainer, TrainingArguments
|
| 13 |
+
|
| 14 |
+
from .config import ID2LABEL, MODEL_DIR, MULTILINGUAL_DATA_DIR, RESULTS_DIR
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def evaluate(model_dir: Path, test_dataset: Path, output_dir: Path, show: bool = False) -> None:
|
| 18 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 19 |
+
model = AutoModelForSequenceClassification.from_pretrained(model_dir)
|
| 20 |
+
tokenizer = AutoTokenizer.from_pretrained(model_dir)
|
| 21 |
+
trainer = Trainer(
|
| 22 |
+
model=model,
|
| 23 |
+
args=TrainingArguments(output_dir=str(output_dir), use_cpu=True, report_to="none"),
|
| 24 |
+
processing_class=tokenizer,
|
| 25 |
+
)
|
| 26 |
+
dataset = load_from_disk(str(test_dataset))
|
| 27 |
+
if "language" not in dataset.column_names:
|
| 28 |
+
raise ValueError("The test dataset must include a language column for per-language evaluation.")
|
| 29 |
+
predictions = trainer.predict(dataset)
|
| 30 |
+
y_true = predictions.label_ids
|
| 31 |
+
y_pred = np.argmax(predictions.predictions, axis=-1)
|
| 32 |
+
labels = list(sorted(ID2LABEL))
|
| 33 |
+
names = [ID2LABEL[label] for label in labels]
|
| 34 |
+
report = classification_report(y_true, y_pred, labels=labels, target_names=names, zero_division=0, output_dict=True)
|
| 35 |
+
print(classification_report(y_true, y_pred, labels=labels, target_names=names, zero_division=0))
|
| 36 |
+
per_language = {}
|
| 37 |
+
languages = np.array(dataset["language"])
|
| 38 |
+
for language in sorted(set(languages)):
|
| 39 |
+
mask = languages == language
|
| 40 |
+
per_language[language] = classification_report(
|
| 41 |
+
y_true[mask], y_pred[mask], labels=labels, target_names=names, zero_division=0, output_dict=True
|
| 42 |
+
)
|
| 43 |
+
with (output_dir / "metrics.json").open("w", encoding="utf-8") as file:
|
| 44 |
+
json.dump({"overall": report, "per_language": per_language}, file, ensure_ascii=False, indent=2)
|
| 45 |
+
cm = confusion_matrix(y_true, y_pred, labels=labels)
|
| 46 |
+
|
| 47 |
+
plt.figure(figsize=(6, 5))
|
| 48 |
+
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", xticklabels=names, yticklabels=names)
|
| 49 |
+
plt.xlabel("Predicted label")
|
| 50 |
+
plt.ylabel("True label")
|
| 51 |
+
plt.title("Confusion Matrix — Test Set")
|
| 52 |
+
plt.tight_layout()
|
| 53 |
+
plt.savefig(output_dir / "confusion_matrix.png", dpi=150)
|
| 54 |
+
|
| 55 |
+
state_path = model_dir / "trainer_state.json"
|
| 56 |
+
if state_path.exists():
|
| 57 |
+
with state_path.open(encoding="utf-8") as file:
|
| 58 |
+
history = json.load(file).get("log_history", [])
|
| 59 |
+
train = [(entry["step"], entry["loss"]) for entry in history if "loss" in entry]
|
| 60 |
+
validation = [(entry["step"], entry["eval_loss"]) for entry in history if "eval_loss" in entry]
|
| 61 |
+
if train or validation:
|
| 62 |
+
plt.figure(figsize=(7, 4))
|
| 63 |
+
if train:
|
| 64 |
+
plt.plot(*zip(*train), label="Training loss")
|
| 65 |
+
if validation:
|
| 66 |
+
plt.plot(*zip(*validation), label="Validation loss")
|
| 67 |
+
plt.xlabel("Step")
|
| 68 |
+
plt.ylabel("Loss")
|
| 69 |
+
plt.title("Training vs. Validation Loss")
|
| 70 |
+
plt.legend()
|
| 71 |
+
plt.tight_layout()
|
| 72 |
+
plt.savefig(output_dir / "loss_curve.png", dpi=150)
|
| 73 |
+
if show:
|
| 74 |
+
plt.show()
|
| 75 |
+
plt.close("all")
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def main() -> None:
|
| 79 |
+
parser = argparse.ArgumentParser(description="Evaluate a saved sentiment model.")
|
| 80 |
+
parser.add_argument("--model-dir", type=Path, default=MODEL_DIR)
|
| 81 |
+
parser.add_argument("--test-dataset", type=Path, default=MULTILINGUAL_DATA_DIR / "test_dataset")
|
| 82 |
+
parser.add_argument("--output-dir", type=Path, default=RESULTS_DIR / "eval")
|
| 83 |
+
parser.add_argument("--show", action="store_true")
|
| 84 |
+
args = parser.parse_args()
|
| 85 |
+
evaluate(args.model_dir, args.test_dataset, args.output_dir, args.show)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
if __name__ == "__main__":
|
| 89 |
+
main()
|
src/multilingual_sentiment_analysis/infer.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Inference helpers for the saved sentiment model."""
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
from functools import lru_cache
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
from transformers import pipeline
|
| 9 |
+
|
| 10 |
+
from .config import MODEL_DIR
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def resolve_model_path() -> str:
|
| 14 |
+
"""Return an explicitly configured Hub/local model or the local default."""
|
| 15 |
+
configured = os.environ.get("SENTIMENT_MODEL_PATH")
|
| 16 |
+
if configured:
|
| 17 |
+
model_path = Path(configured).expanduser()
|
| 18 |
+
if model_path.is_dir():
|
| 19 |
+
return str(model_path)
|
| 20 |
+
if not model_path.is_absolute() and "/" in configured:
|
| 21 |
+
return configured # Hugging Face model ID, e.g. username/model-name
|
| 22 |
+
raise FileNotFoundError(f"Configured SENTIMENT_MODEL_PATH does not exist: {model_path}")
|
| 23 |
+
|
| 24 |
+
model_path = MODEL_DIR
|
| 25 |
+
if not model_path.is_dir():
|
| 26 |
+
raise FileNotFoundError(
|
| 27 |
+
f"No fine-tuned model found at {model_path}. Train one with `python -m multilingual_sentiment_analysis.train` "
|
| 28 |
+
"or set SENTIMENT_MODEL_PATH to a compatible model directory."
|
| 29 |
+
)
|
| 30 |
+
return str(model_path)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@lru_cache(maxsize=1)
|
| 34 |
+
def get_classifier():
|
| 35 |
+
use_gpu = os.environ.get("USE_GPU", "false").lower() == "true"
|
| 36 |
+
if use_gpu and not torch.cuda.is_available():
|
| 37 |
+
raise RuntimeError("USE_GPU=true but CUDA is not available.")
|
| 38 |
+
return pipeline(
|
| 39 |
+
"sentiment-analysis", model=resolve_model_path(), tokenizer=resolve_model_path(),
|
| 40 |
+
device=0 if use_gpu else -1,
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def predict(text: str) -> dict[str, float | str]:
|
| 45 |
+
if not isinstance(text, str) or not text.strip():
|
| 46 |
+
raise ValueError("Input must be a non-empty string.")
|
| 47 |
+
result = get_classifier()(text.strip())[0]
|
| 48 |
+
return {"label": result["label"], "confidence": float(result["score"])}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def predict_batch(texts: list[str]) -> list[dict[str, float | str]]:
|
| 52 |
+
if not isinstance(texts, list) or not texts or any(not isinstance(text, str) or not text.strip() for text in texts):
|
| 53 |
+
raise ValueError("Input must be a non-empty list of non-empty strings.")
|
| 54 |
+
return [
|
| 55 |
+
{"label": result["label"], "confidence": float(result["score"])}
|
| 56 |
+
for result in get_classifier()([text.strip() for text in texts])
|
| 57 |
+
]
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
if __name__ == "__main__":
|
| 61 |
+
for sample in ("The flight was delayed three hours.", "Merci, le vol était très agréable !", "الخدمة كانت ممتازة"):
|
| 62 |
+
print(f"{sample}\n -> {predict(sample)}\n")
|
src/multilingual_sentiment_analysis/preprocess.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Prepare Cardiff NLP's multilingual tweet-sentiment dataset for fine-tuning."""
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import re
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from datasets import DatasetDict, concatenate_datasets, load_dataset
|
| 8 |
+
from transformers import AutoTokenizer
|
| 9 |
+
|
| 10 |
+
from .config import DATASET_NAME, LANGUAGES, MAX_LENGTH, MODEL_CHECKPOINT, MULTILINGUAL_DATA_DIR
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def clean_text(text: str) -> str:
|
| 14 |
+
"""Remove tweet metadata while preserving Unicode text, emojis, and accents."""
|
| 15 |
+
text = str(text).lower()
|
| 16 |
+
text = re.sub(r"https?://\S+|www\.\S+", "", text)
|
| 17 |
+
text = re.sub(r"@\w+", "", text)
|
| 18 |
+
text = text.replace("#", "")
|
| 19 |
+
return re.sub(r"\s+", " ", text).strip()
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def load_multilingual_splits(dataset_name: str = DATASET_NAME) -> DatasetDict:
|
| 23 |
+
"""Load official splits and retain each row's language for later evaluation.
|
| 24 |
+
|
| 25 |
+
The dataset's ``all`` configuration merges languages but does not expose their
|
| 26 |
+
provenance. Loading each official configuration retains that information.
|
| 27 |
+
"""
|
| 28 |
+
grouped = {"train": [], "validation": [], "test": []}
|
| 29 |
+
for language in LANGUAGES:
|
| 30 |
+
dataset = load_dataset(dataset_name, language)
|
| 31 |
+
for split in grouped:
|
| 32 |
+
grouped[split].append(dataset[split].add_column("language", [language] * len(dataset[split])))
|
| 33 |
+
return DatasetDict({split: concatenate_datasets(parts).shuffle(seed=42) for split, parts in grouped.items()})
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def build_datasets(output_dir: Path, checkpoint: str = MODEL_CHECKPOINT) -> None:
|
| 37 |
+
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
|
| 38 |
+
|
| 39 |
+
def tokenize(batch):
|
| 40 |
+
return tokenizer(batch["text"], truncation=True, max_length=MAX_LENGTH)
|
| 41 |
+
|
| 42 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 43 |
+
datasets = load_multilingual_splits()
|
| 44 |
+
for split, dataset in datasets.items():
|
| 45 |
+
cleaned = dataset.map(lambda row: {"text": clean_text(row["text"])})
|
| 46 |
+
cleaned.filter(lambda row: bool(row["text"])).map(tokenize, batched=True).save_to_disk(
|
| 47 |
+
str(output_dir / f"{split.replace('validation', 'val')}_dataset")
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def main() -> None:
|
| 52 |
+
parser = argparse.ArgumentParser(description="Download and prepare multilingual tweet-sentiment data.")
|
| 53 |
+
parser.add_argument("--output-dir", type=Path, default=MULTILINGUAL_DATA_DIR)
|
| 54 |
+
parser.add_argument("--checkpoint", default=MODEL_CHECKPOINT)
|
| 55 |
+
args = parser.parse_args()
|
| 56 |
+
build_datasets(args.output_dir, args.checkpoint)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
main()
|
src/multilingual_sentiment_analysis/train.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Fine-tune the sentiment model."""
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
from datasets import load_from_disk
|
| 8 |
+
from sklearn.metrics import accuracy_score, f1_score
|
| 9 |
+
from transformers import (
|
| 10 |
+
AutoModelForSequenceClassification,
|
| 11 |
+
AutoTokenizer,
|
| 12 |
+
DataCollatorWithPadding,
|
| 13 |
+
Trainer,
|
| 14 |
+
TrainingArguments,
|
| 15 |
+
set_seed,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
from .config import ID2LABEL, LABEL2ID, MODEL_CHECKPOINT, MODEL_DIR, MULTILINGUAL_DATA_DIR, RESULTS_DIR
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def compute_metrics(eval_pred):
|
| 22 |
+
logits, labels = eval_pred
|
| 23 |
+
predictions = np.argmax(logits, axis=-1)
|
| 24 |
+
return {
|
| 25 |
+
"accuracy": accuracy_score(labels, predictions),
|
| 26 |
+
"f1": f1_score(labels, predictions, average="weighted"),
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def train(data_dir: Path, output_dir: Path, model_dir: Path, checkpoint: str, epochs: float, use_cpu: bool) -> None:
|
| 31 |
+
set_seed(42)
|
| 32 |
+
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
|
| 33 |
+
model = AutoModelForSequenceClassification.from_pretrained(
|
| 34 |
+
checkpoint, num_labels=len(LABEL2ID), id2label=ID2LABEL, label2id=LABEL2ID
|
| 35 |
+
)
|
| 36 |
+
args = TrainingArguments(
|
| 37 |
+
output_dir=str(output_dir), eval_strategy="epoch", save_strategy="epoch",
|
| 38 |
+
learning_rate=2e-5, per_device_train_batch_size=16, per_device_eval_batch_size=32,
|
| 39 |
+
num_train_epochs=epochs, weight_decay=0.01, load_best_model_at_end=True,
|
| 40 |
+
metric_for_best_model="f1", logging_steps=50, report_to="none", use_cpu=use_cpu,
|
| 41 |
+
)
|
| 42 |
+
trainer = Trainer(
|
| 43 |
+
model=model, args=args, train_dataset=load_from_disk(str(data_dir / "train_dataset")),
|
| 44 |
+
eval_dataset=load_from_disk(str(data_dir / "val_dataset")),
|
| 45 |
+
data_collator=DataCollatorWithPadding(tokenizer=tokenizer), processing_class=tokenizer,
|
| 46 |
+
compute_metrics=compute_metrics,
|
| 47 |
+
)
|
| 48 |
+
trainer.train()
|
| 49 |
+
trainer.save_model(str(model_dir))
|
| 50 |
+
tokenizer.save_pretrained(str(model_dir))
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def main() -> None:
|
| 54 |
+
parser = argparse.ArgumentParser(description="Fine-tune XLM-R sentiment classification.")
|
| 55 |
+
parser.add_argument("--data-dir", type=Path, default=MULTILINGUAL_DATA_DIR)
|
| 56 |
+
parser.add_argument("--output-dir", type=Path, default=RESULTS_DIR)
|
| 57 |
+
parser.add_argument("--model-dir", type=Path, default=MODEL_DIR)
|
| 58 |
+
parser.add_argument("--checkpoint", default=MODEL_CHECKPOINT)
|
| 59 |
+
parser.add_argument("--epochs", type=float, default=3)
|
| 60 |
+
parser.add_argument("--cpu", action="store_true", help="Force CPU training.")
|
| 61 |
+
args = parser.parse_args()
|
| 62 |
+
train(args.data_dir, args.output_dir, args.model_dir, args.checkpoint, args.epochs, args.cpu)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
if __name__ == "__main__":
|
| 66 |
+
main()
|