Download code/scripts/band.py from nonsodev/essay-coach: direct link, hf CLI and curl.
- Browser
- Download file 3.7 kB
-
https://huggingface.co/nonsodev/essay-coach/resolve/main/code/scripts/band.py
- Command line
-
hf download hf://nonsodev/essay-coach/code/scripts/band.py
-
curl -L -o band.py https://huggingface.co/nonsodev/essay-coach/resolve/main/code/scripts/band.py
3.7 kB
| """Fit the IELTS band estimate on top of the two trained models, on the ~1.2k human-banded IELTS essays. | |
| MODAL_PROFILE=labs3 modal run scripts/band.py --scores scores-v1 --parts parts-v1 | |
| Features per essay: the six raw scores, word count, number of parts of each type, share of effective parts. Model: | |
| ridge regression, 5-fold cross-validation, reported as exact-band and within-half-band accuracy and RMSE in bands. | |
| The fitted weights go to /data/runs/band-v1 (band.json) so inference is a dot product. | |
| """ | |
| import modal | |
| app = modal.App("essay-coach-band") | |
| vol = modal.Volume.from_name("essay-data") | |
| cache = modal.Volume.from_name("essay-hf-cache", create_if_missing=True) | |
| image = (modal.Image.debian_slim(python_version="3.11") | |
| .pip_install("torch==2.4.1", index_url="https://download.pytorch.org/whl/cu124") | |
| .pip_install("transformers==4.46.3", "sentencepiece", "protobuf", "tiktoken", "safetensors", "pandas", "numpy<2.3", | |
| "scikit-learn", "huggingface_hub") | |
| .add_local_python_source("coach")) | |
| def fit(scores: str, parts: str, tag: str): | |
| import json | |
| import os | |
| import numpy as np | |
| import pandas as pd | |
| from sklearn.linear_model import Ridge | |
| from sklearn.model_selection import KFold | |
| from coach.data import TRAITS, TYPES | |
| from coach.infer import Coach | |
| df = pd.read_csv("/data/ielts-writing-scored-essays-dataset/ielts_writing_dataset.csv") | |
| df = df[df.Overall.notna() & df.Essay.notna()].copy() | |
| df["Task_Type"] = df.Task_Type.astype(int) | |
| print(f"IELTS essays with a band: {len(df)} (task 2: {(df.Task_Type == 2).sum()})", flush=True) | |
| coach = Coach(f"/data/runs/{scores}", f"/data/runs/{parts}") | |
| feats = [] | |
| for i, r in enumerate(df.itertuples(index=False)): | |
| read = coach.read(str(r.Essay)) | |
| raw = read["scores_raw"] | |
| ps = read["parts"] | |
| f = [raw[t] for t in TRAITS] + [np.log1p(len(str(r.Essay).split()))] + [sum(p["type"] == t for p in ps) for t in TYPES] | |
| f += [np.mean([p["effectiveness"] == "Effective" for p in ps]) if ps else 0.0, float(r.Task_Type == 2)] | |
| feats.append(f) | |
| if i % 200 == 0: | |
| print(i, flush=True) | |
| X, y = np.array(feats, dtype=np.float32), df.Overall.values.astype(np.float32) | |
| names = TRAITS + ["log_words"] + [f"n_{t}" for t in TYPES] + ["share_effective", "task2"] | |
| mu, sd = X.mean(0), X.std(0) + 1e-6 | |
| Xn = (X - mu) / sd | |
| pred = np.zeros_like(y) | |
| for trn, val in KFold(5, shuffle=True, random_state=0).split(Xn): | |
| m = Ridge(alpha=3.0).fit(Xn[trn], y[trn]) | |
| pred[val] = m.predict(Xn[val]) | |
| snapped = np.clip(np.round(pred * 2) / 2, 1, 9) | |
| res = {"n": int(len(y)), "rmse_bands": float(np.sqrt(((pred - y) ** 2).mean())), | |
| "exact": float((snapped == y).mean()), "within_half": float((np.abs(snapped - y) <= 0.5).mean()), | |
| "within_one": float((np.abs(snapped - y) <= 1.0).mean()), "baseline_rmse_mean": float(y.std()), | |
| "corr": float(np.corrcoef(pred, y)[0, 1])} | |
| print(res, flush=True) | |
| m = Ridge(alpha=3.0).fit(Xn, y) | |
| run = f"/data/runs/{tag}" | |
| os.makedirs(run, exist_ok=True) | |
| json.dump({"features": names, "mean": mu.tolist(), "std": sd.tolist(), "coef": m.coef_.tolist(), | |
| "intercept": float(m.intercept_), "cv": res, "scores_run": scores, "parts_run": parts}, open(f"{run}/band.json", "w"), indent=1) | |
| vol.commit() | |
| return res | |
| def main(scores: str = "scores-v1", parts: str = "parts-v1", tag: str = "band-v1"): | |
| print(fit.remote(scores, parts, tag)) | |