Spaces:
Sleeping
Sleeping
| import argparse | |
| import json | |
| from pathlib import Path | |
| import numpy as np | |
| import pandas as pd | |
| from datasets import Dataset | |
| from sklearn.metrics import accuracy_score, f1_score | |
| from sklearn.model_selection import train_test_split | |
| from transformers import ( | |
| AutoModelForSequenceClassification, | |
| AutoTokenizer, | |
| DataCollatorWithPadding, | |
| Trainer, | |
| TrainingArguments, | |
| set_seed, | |
| ) | |
| def read_table(path: Path) -> pd.DataFrame: | |
| if path.suffix.lower() == ".jsonl": | |
| return pd.read_json(path, lines=True) | |
| if path.suffix.lower() == ".json": | |
| return pd.read_json(path) | |
| return pd.read_csv(path) | |
| def first_existing(columns, candidates): | |
| for candidate in candidates: | |
| if candidate in columns: | |
| return candidate | |
| raise ValueError(f"Не нашёл ни одну из колонок: {candidates}") | |
| def prepare_dataframe(path: Path, max_samples: int | None) -> tuple[pd.DataFrame, list[str]]: | |
| df = read_table(path) | |
| title_col = first_existing(df.columns, ["title", "Title"]) | |
| abstract_col = first_existing(df.columns, ["abstract", "summary", "Abstract"]) | |
| label_col = first_existing(df.columns, ["tag", "categories", "category", "label"]) | |
| df = df[[title_col, abstract_col, label_col]].dropna() | |
| df = df.rename(columns={title_col: "title", abstract_col: "abstract", label_col: "label_name"}) | |
| df["label_name"] = df["label_name"].astype(str).str.split().str[0] | |
| counts = df["label_name"].value_counts() | |
| valid_labels = counts[counts >= 20].index | |
| df = df[df["label_name"].isin(valid_labels)].copy() | |
| if max_samples is not None: | |
| df = df.sample(min(max_samples, len(df)), random_state=42) | |
| labels = sorted(df["label_name"].unique()) | |
| label2id = {label: i for i, label in enumerate(labels)} | |
| df["label"] = df["label_name"].map(label2id) | |
| df["text"] = "Title: " + df["title"].astype(str) + "\nAbstract: " + df["abstract"].astype(str) | |
| return df[["text", "label"]], labels | |
| def compute_metrics(eval_pred): | |
| logits, labels = eval_pred | |
| predictions = np.argmax(logits, axis=-1) | |
| return { | |
| "accuracy": accuracy_score(labels, predictions), | |
| "macro_f1": f1_score(labels, predictions, average="macro"), | |
| } | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--data", type=Path, required=True, help="CSV/JSON/JSONL с title, abstract и tag/categories.") | |
| parser.add_argument("--model-name", default="prajjwal1/bert-tiny") | |
| parser.add_argument("--output-dir", type=Path, default=Path("model")) | |
| parser.add_argument("--max-samples", type=int, default=5000) | |
| parser.add_argument("--epochs", type=float, default=3.0) | |
| parser.add_argument("--batch-size", type=int, default=16) | |
| parser.add_argument("--lr", type=float, default=2e-5) | |
| args = parser.parse_args() | |
| set_seed(42) | |
| df, labels = prepare_dataframe(args.data, args.max_samples) | |
| train_df, valid_df = train_test_split( | |
| df, | |
| test_size=0.15, | |
| random_state=42, | |
| stratify=df["label"], | |
| ) | |
| tokenizer = AutoTokenizer.from_pretrained(args.model_name, use_fast=False) | |
| def tokenize(batch): | |
| return tokenizer(batch["text"], truncation=True, max_length=384) | |
| train_ds = Dataset.from_pandas(train_df, preserve_index=False).map(tokenize, batched=True) | |
| valid_ds = Dataset.from_pandas(valid_df, preserve_index=False).map(tokenize, batched=True) | |
| id2label = {i: label for i, label in enumerate(labels)} | |
| label2id = {label: i for i, label in id2label.items()} | |
| model = AutoModelForSequenceClassification.from_pretrained( | |
| args.model_name, | |
| num_labels=len(labels), | |
| id2label=id2label, | |
| label2id=label2id, | |
| ) | |
| training_kwargs = { | |
| "output_dir": str(args.output_dir), | |
| "learning_rate": args.lr, | |
| "per_device_train_batch_size": args.batch_size, | |
| "per_device_eval_batch_size": args.batch_size, | |
| "num_train_epochs": args.epochs, | |
| "weight_decay": 0.01, | |
| "load_best_model_at_end": True, | |
| "metric_for_best_model": "macro_f1", | |
| "report_to": "none", | |
| "save_safetensors": False, | |
| } | |
| try: | |
| training_args = TrainingArguments( | |
| evaluation_strategy="epoch", | |
| save_strategy="epoch", | |
| **training_kwargs, | |
| ) | |
| except TypeError: | |
| training_args = TrainingArguments( | |
| eval_strategy="epoch", | |
| save_strategy="epoch", | |
| **training_kwargs, | |
| ) | |
| trainer = Trainer( | |
| model=model, | |
| args=training_args, | |
| train_dataset=train_ds, | |
| eval_dataset=valid_ds, | |
| tokenizer=tokenizer, | |
| data_collator=DataCollatorWithPadding(tokenizer), | |
| compute_metrics=compute_metrics, | |
| ) | |
| trainer.train() | |
| metrics = trainer.evaluate() | |
| args.output_dir.mkdir(parents=True, exist_ok=True) | |
| trainer.save_model(args.output_dir) | |
| tokenizer.save_pretrained(args.output_dir) | |
| with (args.output_dir / "metrics.json").open("w", encoding="utf-8") as f: | |
| json.dump(metrics, f, ensure_ascii=False, indent=2) | |
| print(json.dumps(metrics, ensure_ascii=False, indent=2)) | |
| if __name__ == "__main__": | |
| main() | |