File size: 6,111 Bytes
341869a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
"""
Data Preprocessing Module
Handles text cleaning, tokenization, lemmatization, and class balancing.
"""

import re
import string
import pandas as pd
import numpy as np
from collections import Counter
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from textblob import TextBlob

# Download required NLTK data
def download_nltk_data():
    resources = ['stopwords', 'wordnet', 'omw-1.4', 'punkt', 'averaged_perceptron_tagger']
    for r in resources:
        try:
            nltk.download(r, quiet=True)
        except Exception:
            pass

download_nltk_data()

# ─────────────────────────────────────────────
# Label Mapping
# ─────────────────────────────────────────────

# Map dataset labels β†’ unified 7-class schema
LABEL_MAP = {
    "Normal":               "Normal",
    "Anxiety":              "Anxiety",
    "Depression":           "Depression",
    "Suicidal":             "Suicidal",
    "Bipolar":              "Bipolar",
    "Stress":               "Stress",
    "Personality disorder": "Personality Disorder",
    # Aliases (in case of variations)
    "PTSD":                 "Stress",
    "BPD":                  "Personality Disorder",
    "Borderline":           "Personality Disorder",
}

LABEL2ID = {
    "Normal":               0,
    "Anxiety":              1,
    "Depression":           2,
    "Suicidal":             3,
    "Bipolar":              4,
    "Stress":               5,
    "Personality Disorder": 6,
}

ID2LABEL = {v: k for k, v in LABEL2ID.items()}

NUM_LABELS = len(LABEL2ID)

# ─────────────────────────────────────────────
# Text Cleaning
# ─────────────────────────────────────────────

def clean_text(text: str) -> str:
    """
    Thorough text cleaning pipeline:
    - Lowercase
    - Remove URLs, mentions, hashtags
    - Remove special characters / digits
    - Normalize whitespace
    """
    if not isinstance(text, str):
        return ""

    # Lowercase
    text = text.lower()

    # Remove URLs
    text = re.sub(r"http\S+|www\S+|https\S+", "", text, flags=re.MULTILINE)

    # Remove Reddit-style mentions and subreddits
    text = re.sub(r"@\w+|r/\w+|u/\w+", "", text)

    # Remove HTML entities
    text = re.sub(r"&[a-z]+;", " ", text)

    # Remove digits
    text = re.sub(r"\d+", "", text)

    # Remove punctuation (keep apostrophes for contractions)
    text = text.translate(str.maketrans("", "", string.punctuation.replace("'", "")))

    # Remove extra whitespace
    text = re.sub(r"\s+", " ", text).strip()

    return text


def lemmatize_text(text: str) -> str:
    """Lemmatize tokens and remove stopwords."""
    lemmatizer = WordNetLemmatizer()
    stop_words = set(stopwords.words("english"))

    tokens = text.split()
    tokens = [lemmatizer.lemmatize(t) for t in tokens if t not in stop_words and len(t) > 2]
    return " ".join(tokens)


def get_sentiment_features(text: str) -> dict:
    """
    Extract TextBlob sentiment features:
    - polarity (-1 to 1)
    - subjectivity (0 to 1)
    - sentiment_label: Positive / Neutral / Negative
    """
    blob = TextBlob(text)
    polarity = blob.sentiment.polarity
    subjectivity = blob.sentiment.subjectivity

    if polarity > 0.05:
        label = "Positive"
    elif polarity < -0.05:
        label = "Negative"
    else:
        label = "Neutral"

    return {
        "polarity": polarity,
        "subjectivity": subjectivity,
        "sentiment_label": label,
    }


def full_preprocess(text: str, lemmatize: bool = True) -> str:
    """Full preprocessing pipeline for inference."""
    text = clean_text(text)
    if lemmatize:
        text = lemmatize_text(text)
    return text


# ─────────────────────────────────────────────
# Dataset Loading
# ─────────────────────────────────────────────

def load_dataset(csv_path: str, max_len: int = 512) -> pd.DataFrame:
    """
    Load and preprocess the CSV dataset.

    Expected columns: (index), statement, status
    Returns a clean DataFrame with columns: text, label, label_id, sentiment_*
    """
    print(f"[DATA] Loading dataset from: {csv_path}")
    df = pd.read_csv(csv_path, index_col=0)

    # Rename columns
    df.columns = [c.strip().lower() for c in df.columns]
    if "statement" in df.columns:
        df.rename(columns={"statement": "text", "status": "label"}, inplace=True)

    # Drop nulls
    df.dropna(subset=["text", "label"], inplace=True)

    # Normalize labels
    df["label"] = df["label"].str.strip().map(LABEL_MAP)
    df.dropna(subset=["label"], inplace=True)

    # Map to numeric IDs
    df["label_id"] = df["label"].map(LABEL2ID)

    # Clean text
    print("[DATA] Cleaning text...")
    df["text_clean"] = df["text"].apply(clean_text)

    # Filter too-short texts
    df = df[df["text_clean"].str.len() > 5].reset_index(drop=True)

    # Sentiment features
    print("[DATA] Extracting sentiment features...")
    sentiment = df["text"].apply(get_sentiment_features).apply(pd.Series)
    df = pd.concat([df, sentiment], axis=1)

    print(f"[DATA] Dataset loaded: {len(df)} samples")
    print(f"[DATA] Label distribution:\n{df['label'].value_counts()}")

    return df


def compute_class_weights(label_ids: np.ndarray) -> np.ndarray:
    """
    Compute inverse-frequency class weights for weighted loss.
    Returns array of shape [num_classes].
    """
    counts = Counter(label_ids)
    total = sum(counts.values())
    weights = np.zeros(NUM_LABELS, dtype=np.float32)
    for cls_id, count in counts.items():
        weights[cls_id] = total / (NUM_LABELS * count)
    return weights