CoolFace
Modelpublic

bendavidsteel/Qwen3.5-4B-stance-detection

sourceHugging Faceapache-2.0updated 16d agoView on Hugging Face
0likes44downloads
Model Card

Qwen3.5-4B-stance-detection

Qwen/Qwen3.5-4B fine-tuned for three-way stance detection: given a document and a target, predict whether the author is favor, against, or neutral toward that target.

Trained with LoRA on a sequence-classification head (the adapter is merged into these weights), as part of the StanceMining pipeline. It is the Qwen3.5 successor to bendavidsteel/Qwen3-4B-stance-detection.

Model details

Base modelQwen/Qwen3.5-4B
ArchitectureQwen3_5ForSequenceClassification (hybrid linear/full attention, 32 layers)
Parameters4.21 B
Precisionbfloat16
Classification headscore.weight, shape [3, 2560]
Max position embeddings262144 (trained and evaluated at 2048)
Labels0: neutral, 1: favor, 2: against

Usage

The classification head pools the final non-padding token, so inputs must be formatted with the chat template and a generation prompt — the same way the model was trained.

python
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer

repo = "bendavidsteel/Qwen3.5-4B-stance-detection"
tokenizer = AutoTokenizer.from_pretrained(repo)
model = AutoModelForSequenceClassification.from_pretrained(repo, dtype=torch.bfloat16, device_map="auto")
model.eval()

PROMPT = (
    "For the following text, determine whether the author's stance is in favor of, "
    "against, or neutral toward the target: '{target}'. Consider the language used, "
    "any explicit statements of position, and contextual clues that suggest the "
    "author's stance. Answer only with 'in favor', 'against', or 'neutral'. \n\n"
    "Text: '{text}'"
)

def classify(text, target):
    messages = [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": PROMPT.format(target=target, text=text)},
    ]
    inputs = tokenizer.apply_chat_template(
        messages,
        add_generation_prompt=True,
        enable_thinking=False,
        truncation=True,
        max_length=2048,
        return_tensors="pt",
        return_dict=True,
    ).to(model.device)
    with torch.no_grad():
        logits = model(**inputs).logits[0]
    probs = logits.softmax(-1)
    return model.config.id2label[int(probs.argmax())], probs.tolist()

print(classify("Cycling infrastructure has made my commute so much safer.", "bike lanes"))

Prompt templates

The prompt is part of the model's interface — the fine-tune only ever saw inputs in these two forms. Both are also stored in metadata.json in this repo.

Standalone document:

For the following text, determine whether the author's stance is in favor of, against, or neutral toward the target: '{target}'. Consider the language used, any explicit statements of position, and contextual clues that suggest the author's stance. Answer only with 'in favor', 'against', or 'neutral'. 

Text: '{text}'

Document in a reply chain (used for conversational datasets such as MT-CSD and CTSDT):

For the following text, determine whether the author's stance is in favor of, against, or neutral toward the target: '{target}'. Consider the language used, any explicit statements of position, the chain of parent texts that the author is replying to, and contextual clues that suggest the author's attitude. Answer only with 'in favor', 'against', or 'neutral'.

Parent Document Chain (from oldest to most recent):
{parent_chain}

Text: '{text}'

Training data

Fine-tuned on the combined training splits of eight stance datasets:

DatasetDomainLanguage
VASTVaried, zero-shot topicsen
EZ-STANCEVaried, zero/few-shot targetsen
P-StanceUS political figuresen
SemEval-2016 Task 6Mixed political/social targetsen
MT-CSDMulti-turn conversational threadsen
CTSDTCOVID-19 vaccination, conversation threadsen
CataloniaCatalonia independencees, ca
French-ElectionLe Pen, Macron, Italian referendumfr, it

Training procedure

LoRA adapters on a randomly initialised sequence-classification head, merged into the base weights after training. Hyperparameters from the wandb run autumn-moon-345:

LoRA rank / alpha / dropout4 / 8 / 0.2
Learning rate1e-4
Per-device batch size8
Gradient accumulation steps16 (effective batch 128)
Max sequence length2048 (padded)
Precisionbfloat16, no quantization
Attentionflash_attention_2
System messageYou are a helpful assistant.

Training ran as three consecutive jobs — an initial run plus two resumptions after crashes — totalling roughly 69 GPU-hours. Final held-out validation loss was 0.575.

Evaluation

Held-out test split of the combined corpus (16,972 examples).

Overall

MetricValue
Accuracy0.7403
Macro F10.7399
Macro precision0.7386
Macro recall0.7436

Per class (derived from the confusion matrix in metadata.json)

ClassPrecisionRecallF1Support
neutral0.70490.77910.74014762
favor0.71960.73290.72625612
against0.79130.71870.75326598

Per dataset

DatasetAccuracyMacro F1PrecisionRecall
VAST0.78280.77870.77820.7797
EZ-STANCE0.66050.66540.67700.6602
P-Stance0.85070.85020.85100.8498
SemEval0.70860.69320.70170.7230
MT-CSD0.67060.62890.66200.6131
CTSDT0.80290.75200.79360.7243
Catalonia0.75060.75330.75400.7533
French-Election0.77160.70430.68370.8159

Confusion matrix (rows are true labels, columns predicted)

neutralfavoragainst
neutral3710577475
favor7234113776
against83010264742

Comparison with the Qwen3 generation

Same training and test splits, same prompts.

ModelAccuracyMacro F1
Qwen3-1.7B-stance-detection0.71820.7183
Qwen3-4B-stance-detection0.73530.7350
Qwen3.5-4B-stance-detection (this model)0.74030.7399

Limitations

  • —The label set is closed and three-way; targets outside the training domains, and stance expressed through heavy irony or in-group reference, remain hard.
  • —Coverage outside English is limited to Catalan/Spanish independence tweets and French/Italian election tweets, so non-English performance should not be assumed to generalise to other topics.
  • —neutral absorbs both "no stance" and "discusses the target without taking a side", which differ across the source datasets.
  • —Inputs longer than 2048 tokens were truncated during training and evaluation, despite the larger context window of the base model.

License

Apache 2.0, following the base model.