Bias Mitigation: Debiasing, CDA, and Fair Fine-tuning › Counterfactual Data Augmentation: A Worked Example

Michael BrenndoerferMarch 15, 20265 min read
Language AI HandbookAll chapters
Part 58 of 67Bias and Fairness
Chapter 443Bias Mitigation: Debiasing, CDA, and Fair Fine-tuning › Counterfactual Data Augmentation: A Worked Example
Reading section 8 of 13 · Counterfactual Data Augmentation: A Worked Example · 5 minSection 8 of 13 · 5 min

Book contents

Section 8 of 13 · 5 min read

Bias Mitigation: Debiasing, CDA, and Fair Fine-tuning › Counterfactual Data Augmentation: A Worked ExampleOne section · Sections in this chapter8 / 13 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let us implement CDA for a sentiment analysis task, showing how augmentation prevents gendered sentiment disparities.

In[13]:
Code
# Swap dictionary: male -> female and female -> male
gender_swap = {
    "he": "she",
    "she": "he",
    "him": "her",
    "her": "him",
    "his": "hers",
    "hers": "his",
    "himself": "herself",
    "herself": "himself",
    "man": "woman",
    "woman": "man",
    "men": "women",
    "women": "men",
    "boy": "girl",
    "girl": "boy",
    "boys": "girls",
    "girls": "boys",
    "male": "female",
    "female": "male",
    "father": "mother",
    "mother": "father",
    "son": "daughter",
    "daughter": "son",
    "brother": "sister",
    "sister": "brother",
    "husband": "wife",
    "wife": "husband",
    "mr": "ms",
    "ms": "mr",
    "mr.": "ms.",
    "ms.": "mr.",
}


def swap_gender(text: str, swap_dict: dict) -> str:
    """Apply gender swap to a text string, preserving capitalization."""
    tokens = text.split()
    swapped = []
    for token in tokens:
        # Strip punctuation for lookup, preserve it
        word = token.rstrip(".,!?;:").lower()
        punct = token[len(word) :]

        if word in swap_dict:
            replacement = swap_dict[word]
            # Preserve original capitalization
            if token[0].isupper():
                replacement = replacement.capitalize()
            swapped.append(replacement + punct)
        else:
            swapped.append(token)

    return " ".join(swapped)


# Sample training data with gendered labels
# Intentionally male-skewed (6:2) to demonstrate CDA's rebalancing effect
training_data = [
    (
        "He was an excellent engineer, solving every problem with confidence.",
        "positive",
    ),
    ("The man quickly became the team lead after just one year.", "positive"),
    ("He had a commanding presence in every meeting.", "positive"),
    ("The boy showed remarkable aptitude for mathematics.", "positive"),
    ("His contributions to the project were consistently praised.", "positive"),
    ("The father of three managed a team of twenty engineers.", "positive"),
    (
        "She struggled to communicate clearly with the technical team.",
        "negative",
    ),
    ("She was too emotional during the performance review.", "negative"),
]
In[14]:
Code
def apply_cda(data: list, swap_dict: dict) -> list:
    """Apply counterfactual data augmentation to a labeled dataset."""
    augmented = list(data)  # Start with original data

    for text, label in data:
        swapped = swap_gender(text, swap_dict)
        # Only add if the swap actually changed something
        if swapped != text:
            augmented.append((swapped, label))

    return augmented


augmented_data = apply_cda(training_data, gender_swap)
Out[15]:
Console
Original dataset size:  8
Augmented dataset size: 16

Sample augmented pairs:
----------------------------------------------------------------------
Original:  [positive] He was an excellent engineer, solving every problem with confidence.
Augmented: [positive] She was an excellent engineer, solving every problem with confidence.

Original:  [positive] The man quickly became the team lead after just one year.
Augmented: [positive] The woman quickly became the team lead after just one year.

Original:  [positive] He had a commanding presence in every meeting.
Augmented: [positive] She had a commanding presence in every meeting.
In[16]:
Code
# Analyze demographic balance after augmentation
def count_demographic_terms(data: list) -> dict:
    """Count occurrences of male and female terms in dataset."""
    male_terms = {
        "he",
        "him",
        "his",
        "man",
        "men",
        "boy",
        "boys",
        "himself",
        "male",
        "father",
        "son",
        "brother",
        "husband",
        "mr",
    }
    female_terms = {
        "she",
        "her",
        "hers",
        "woman",
        "women",
        "girl",
        "girls",
        "herself",
        "female",
        "mother",
        "daughter",
        "sister",
        "wife",
        "ms",
    }

    male_count = 0
    female_count = 0

    for text, _ in data:
        tokens = set(text.lower().split())
        male_count += len(tokens & male_terms)
        female_count += len(tokens & female_terms)

    return {"male": male_count, "female": female_count}


counts_before = count_demographic_terms(training_data)
counts_after = count_demographic_terms(augmented_data)
Out[17]:
Console
Demographic term balance:
Dataset           Male terms   Female terms   Balance ratio
----------------------------------------------------------
Before CDA                 6              2           0.33
After CDA                  8              8           1.00

A ratio of 1.0 indicates perfect gender balance.

The balance ratio approaching 1.0 after augmentation confirms that the dataset now treats male and female demographic signals with equal frequency. A model trained on this augmented dataset cannot rely on gendered pronouns as predictive features, because every example that references a man has a paired example referencing a woman with the same label.

Notice what CDA preserves as well as what it changes. The sentiment labels remain identical for original and augmented pairs, which is the correct behavior for a task where quality judgments should be gender-neutral. The vocabulary outside the swap list (words like "excellent", "commanding", "struggled", "emotional") also remains unchanged, which means the model still sees the full lexical signal in each example. CDA is a targeted operation: it corrects the demographic co-occurrence statistics without distorting any other feature of the text.

Out[18]:
Visualization
Bar chart showing male and female demographic term counts before CDA with unequal distribution.
Demographic term counts in the original training dataset before CDA. The unequal distribution of male- and female-coded terms reflects a corpus where positive examples are predominantly associated with male subjects, which would lead a trained model to learn a spurious gender-sentiment correlation.
Bar chart showing male and female demographic term counts after CDA with balanced distribution.
Demographic term counts after applying CDA augmentation. The two groups appear with nearly equal frequency, removing the statistical basis for gender-dependent predictions and forcing the model to rely on sentiment-relevant vocabulary instead.