Views
No views yet
roberta-base CLS embedding with 25 normalized
linguistic features through a feature-attention module and a learned gated
fusion, followed by a 2-class classifier (0 = human, 1 = machine).FacebookAI/roberta-base (bottom 6 layers + embeddings frozen)model.py).link to github repofeatures.py and normalized with the shipped
ling_scaler.pkl; a raw/zero feature vector produces meaningless scores.| File | Purpose |
|---|---|
hybrid_model_best.pt | PyTorch state_dict for HybridClassifier (~500 MB). |
model.py | Self-contained architecture definition + load_model() helper. |
features.py | Self-contained linguistic feature extraction (normalize → chunk → 25 features). |
ling_scaler.pkl | Fitted training StandardScaler used to normalize the 25 features. Required for valid predictions. |
example_usage.py | Runnable end-to-end scoring example (raw text → prediction). |
1pip install torch transformers spacy scikit-learn
2python -m spacy download en_core_web_lg1python example_usage.py --text "Your transcript goes here."
2python example_usage.py --file document.txt1import torch
2from transformers import RobertaTokenizer, GPT2LMHeadModel, GPT2TokenizerFast
3import spacy, pickle
4from model import load_model, CONFIG
5from features import extract_raw_features, prepare_document
6from example_usage import score_text
7
8device = "cuda" if torch.cuda.is_available() else "cpu"
9model = load_model("hybrid_model_best.pt", device=device)
10tokenizer = RobertaTokenizer.from_pretrained(CONFIG["roberta_model"])
11nlp = spacy.load("en_core_web_lg", disable=["ner"])
12gpt2_tokenizer = GPT2TokenizerFast.from_pretrained("gpt2")
13gpt2_model = GPT2LMHeadModel.from_pretrained("gpt2").to(device).eval()
14scaler = pickle.load(open("ling_scaler.pkl", "rb"))
15
16p_llm, chunk_probs = score_text(
17 "Your transcript goes here.",
18 model, tokenizer, nlp, gpt2_model, gpt2_tokenizer, scaler, device=device,
19)
20print(p_llm, "->", "machine" if p_llm >= 0.5 else "human")features.py and normalized with the fitted training scaler (ling_scaler.pkl):msttr, avg_word_len, hapax_ratio, function_ratio, punct_density, char_entropy,
burstiness, repetition_ratio, avg_sent_len, sent_len_std, noun_ratio,
verb_ratio, adj_ratio, adv_ratio, pron_ratio, pos_diversity, avg_tree_depth,
max_tree_depth, sub_clause_ratio, dm_density, sent_len_cv, fp_ratio,
num_sentences, words_per_sent, perplexityfeatures.py reproduces the training/testing pipeline exactly: normalize_text
→ sliding_window_chunk (450-word windows, 350-word stride) → 24 spaCy featuresStandardScaler.transform. Document-level scores are the
mean of per-chunk P(machine).no_attack (clean), synonym,
polish, paraphrase, perplexity_attack, and paraphrase_by_llm. Labels are
0 = human, 1 = machine. Documents are lowercased/cleaned, split by document
id (no document leakage across train/val/test), then chunked with a 450-word
sliding window (350-word stride). See 01_data_preprocessing_v2.py.roberta-base with embeddings and the bottom 6 encoder layers frozen.StandardScaler-normalized) passed through a
feature-attention module and projected to 768-d, then fused with the RoBERTa
CLS embedding via a learned sigmoid gate.| Model | Accuracy | Macro-F1 | AUC |
|---|---|---|---|
| Hybrid (this model) | 0.9507 | 0.9505 | 0.9772 |
| RoBERTa-only baseline | 0.9170 | 0.9158 | 0.9824 |
| Feature-only MLP baseline | 0.8462 | 0.8459 | 0.9213 |
state_dict; load it with the HybridClassifier
in model.py (see load_model()).return_gate=True in forward() also returns the fusion gate values and the
feature-attention weights for interpretability.