File size: 4,669 Bytes
7e9cfd1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
"""Load the CSVs, clean up the text, and pair each tweet with its target."""
import re

import pandas as pd
import torch
from torch.utils.data import Dataset

LABEL2ID = {"Against": 0, "Favor": 1, "None": 2}
ID2LABEL = {v: k for k, v in LABEL2ID.items()}

_DIACRITICS = re.compile(r"[ؐ-ًؚ-ٰٟـ]")
_NON_ARABIC = re.compile(r"[^؀-ۿ0-9\s]+")
_URL = re.compile(r"https?://\S+|www\.\S+")
_MENTION = re.compile(r"@\w+")
_MULTI_SPACE = re.compile(r"\s+")
_REPEAT = re.compile(r"(.)\1{2,}")

# Short target descriptions used when the target side is expanded.
TARGET_DESCRIPTIONS = {
    "Covid Vaccine": "لقاح فيروس كورونا كوفيد 19",
    "Digital Transformation": "التحول الرقمي في الخدمات والمجتمع",
    "Women empowerment": "تمكين المرأة وحقوقها في المجتمع",
    "Women Driving": "قيادة المرأة للسيارة",
    "Ecars": "السيارات الكهربائية",
    "Trimester": "نظام الفصول الدراسية الثلاثة في العام الدراسي",
}

# More informative, neutral glosses that spell out what the target is and
# the axis of debate around it (used with --rich_desc).
TARGET_DESCRIPTIONS_RICH = {
    "Covid Vaccine": "لقاح فيروس كورونا كوفيد 19 وأخذه للوقاية من المرض",
    "Digital Transformation": (
        "التحول الرقمي: رقمنة الخدمات والمعاملات الحكومية والمجتمعية"
    ),
    "Women empowerment": "تمكين المرأة وتوسيع دورها وحقوقها في المجتمع",
    "Women Driving": "قيادة المرأة للسيارة",
    "Ecars": "السيارات الكهربائية التي تعمل بالبطارية بدلاً من الوقود",
    "Trimester": (
        "نظام الترمات: تقسيم العام الدراسي إلى ثلاثة فصول بدلاً من فصلين"
    ),
}


def preprocess(text, mode="preserve"):
    """Normalize tweet text.

    mode='strip'     removes non-Arabic characters (emoji, latin).
    mode='preserve'  keeps emoji/latin, drops only urls, mentions,
                     diacritics, repeated characters, and hashtag marks.
    """
    text = str(text)
    text = _URL.sub(" ", text)
    text = _MENTION.sub(" ", text)
    text = _DIACRITICS.sub("", text)
    text = _REPEAT.sub(r"\1\1", text)
    if mode == "strip":
        text = _NON_ARABIC.sub(" ", text)
    else:
        text = text.replace("#", " ").replace("_", " ")
    return _MULTI_SPACE.sub(" ", text).strip()


def target_side(target, use_description=False):
    target = target.strip()
    if use_description:
        return TARGET_DESCRIPTIONS.get(target, target)
    return target


def load_split(csv_path, prep_mode="preserve", has_labels=True):
    """Load a CSV into a dataframe with text, target and optional stance."""
    df = pd.read_csv(csv_path, keep_default_na=False, encoding="utf-8-sig")
    df.columns = df.columns.astype(str).str.strip()
    if "text" not in df.columns and "tweet_text" in df.columns:
        df = df.rename(columns={"tweet_text": "text"})
    df["text"] = df["text"].astype(str).str.strip()
    df["target"] = df["target"].astype(str).str.strip()
    if has_labels:
        df["stance"] = df["stance"].astype(str).str.strip()
        df = df[
            (df["text"] != "")
            & (df["target"] != "")
            & (df["stance"] != "")
        ].copy()
        bad = sorted(set(df["stance"]) - set(LABEL2ID))
        if bad:
            raise ValueError(f"Unknown stance labels in {csv_path}: {bad}")
        df["label"] = df["stance"].map(LABEL2ID).astype(int)
    df["text_clean"] = df["text"].apply(lambda t: preprocess(t, prep_mode))
    return df.reset_index(drop=True)


class StanceDataset(Dataset):
    def __init__(self, df, tokenizer, max_len=128, use_description=False,
                 has_labels=True):
        self.df = df.reset_index(drop=True)
        self.tok = tokenizer
        self.max_len = max_len
        self.use_description = use_description
        self.has_labels = has_labels

    def __len__(self):
        return len(self.df)

    def __getitem__(self, idx):
        row = self.df.iloc[idx]
        enc = self.tok(
            target_side(row["target"], self.use_description),
            row["text_clean"],
            truncation=True,
            padding="max_length",
            max_length=self.max_len,
            return_tensors="pt",
        )
        item = {k: v.squeeze(0) for k, v in enc.items()}
        if self.has_labels:
            item["labels"] = torch.tensor(int(row["label"]), dtype=torch.long)
        return item