File size: 6,271 Bytes
92d53f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Source datasets and the mapping from their labels onto the Lighthouse taxonomy.

Every label decision made here is a modelling choice that shows up in the results, so
each one carries its reasoning. If you change a mapping, rebuild the splits and say so in
``docs/log.md``.

See ``docs/context.md`` section 10 for licences and why these sources were chosen.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Callable

from lighthouse.taxonomy import Harm


@dataclass(frozen=True)
class Source:
    key: str
    hf_id: str
    split: str
    text_column: str
    licence: str
    note: str
    to_harm: Callable[[dict], Harm | None]
    """Return the Harm for a row, or None to drop the row."""


# --------------------------------------------------------------------------------------
# Jigsaw toxic comment classification challenge
# --------------------------------------------------------------------------------------
# Multi-label: toxic, severe_toxic, obscene, threat, insult, identity_hate.
# Resolved to a single label by severity priority, because our turn head is single-label.
# Priority order is deliberate: a comment that is both a threat and an insult is a threat,
# and mis-ranking that direction is the expensive mistake.
#
# `obscene` alone is not harassment: profanity directed at nobody is not bullying, and
# treating it as such would flood HARASSMENT with noise and teach the model that swearing
# equals harm. Obscene rows only count when they co-occur with toxic or insult.


def _jigsaw_to_harm(row: dict) -> Harm | None:
    def on(col: str) -> bool:
        return str(row.get(col, "0")).strip() in {"1", "1.0", "True", "true"}

    if on("threat"):
        return Harm.THREAT
    if on("identity_hate"):
        return Harm.IDENTITY_ATTACK
    if on("insult") or on("toxic") or on("severe_toxic"):
        return Harm.HARASSMENT
    if on("obscene"):
        return None  # profanity without a target: ambiguous, drop rather than mislabel
    return Harm.NONE


JIGSAW = Source(
    key="jigsaw",
    hf_id="thesofakillers/jigsaw-toxic-comment-classification-challenge",
    split="train",
    text_column="comment_text",
    licence="CC0 (Wikipedia comments), Jigsaw/Conversation AI",
    note=(
        "159,571 Wikipedia talk-page comments. The only public source in this build that "
        "carries an explicit `threat` label, which is what T3 hangs off."
    ),
    to_harm=_jigsaw_to_harm,
)


# --------------------------------------------------------------------------------------
# Reddit SuicideWatch / depression / teenagers
# --------------------------------------------------------------------------------------
# Three subreddits, which map cleanly onto three of our classes:
#   SuicideWatch -> SELF_HARM   (the T4 signal; nothing else in the corpus provides it)
#   depression   -> DISTRESS    (first-person distress without harm intent)
#   teenagers    -> NONE        (ordinary teen-voice chat: the best in-domain negative
#                                available, and much closer to our users than Wikipedia)
#
# Caveat recorded honestly in the README: a subreddit is a proxy for a label, not a
# clinical annotation. A SuicideWatch post is not verified suicidal ideation, and a
# r/teenagers post is not verified benign.


def _reddit_to_harm(row: dict) -> Harm | None:
    cls = str(row.get("class", "")).strip().lower()
    return {
        "suicidewatch": Harm.SELF_HARM,
        "depression": Harm.DISTRESS,
        "teenagers": Harm.NONE,
    }.get(cls)


REDDIT_SUICIDE = Source(
    key="reddit",
    hf_id="joshyii/suicide_depression_detection",
    split="train",
    text_column="text",
    licence="public Reddit posts, research redistribution",
    note=(
        "348,124 posts across r/SuicideWatch, r/depression, r/teenagers. Supplies "
        "SELF_HARM, DISTRESS and in-domain teen-voice NONE. Deliberately NOT combined "
        "with Ram07/Detection-for-Suicide: both derive from the same SuicideWatch scrape "
        "and mixing them would leak near-duplicates across the train/test boundary."
    ),
    to_harm=_reddit_to_harm,
)


# --------------------------------------------------------------------------------------
# Cyberbullying tweets (the 6-class Kaggle taxonomy)
# --------------------------------------------------------------------------------------
# The four targeted classes are attacks on a protected characteristic, which is exactly
# IDENTITY_ATTACK. `other_cyberbullying` is untargeted bullying, so HARASSMENT.


def _cyberbullying_to_harm(row: dict) -> Harm | None:
    out = str(row.get("output", "")).strip().lower()
    return {
        "not_cyberbullying": Harm.NONE,
        "religion": Harm.IDENTITY_ATTACK,
        "ethnicity": Harm.IDENTITY_ATTACK,
        "gender": Harm.IDENTITY_ATTACK,
        "age": Harm.IDENTITY_ATTACK,
        "other_cyberbullying": Harm.HARASSMENT,
    }.get(out)


CYBERBULLYING = Source(
    key="cyberbullying",
    hf_id="AnikaBasu/CyberbullyingDataset",
    split="train",
    text_column="instruction",  # the tweet itself; 'text' is a constant prompt preamble
    licence="CC0, mirror of Kaggle andrewmvd/cyberbullying-classification",
    note=(
        "2,956 tweets in the 6-class cyberbullying taxonomy. Small, but the only source "
        "with short social-media-length text, which is closer to a chat turn than either "
        "Wikipedia comments or Reddit posts."
    ),
    to_harm=_cyberbullying_to_harm,
)


SOURCES: list[Source] = [JIGSAW, REDDIT_SUICIDE, CYBERBULLYING]


# --------------------------------------------------------------------------------------
# Conversation-level source, used from day 4, not part of the turn-level split
# --------------------------------------------------------------------------------------

ESCONV_HF_ID = "thu-coai/esconv"
ESCONV_LICENCE = "CC-BY-NC-4.0 — non-commercial only, stated in the README"
ESCONV_NOTE = (
    "1,300 annotated multi-turn emotional-support conversations. Used for conversation "
    "structure and as realistic low-tier (T0/T1) negatives on day 4, NOT as turn-level "
    "training labels: 'seeker turn in a support conversation' is too crude a proxy for "
    "a harm label and would poison DISTRESS."
)