* 2. Include this script: * 3. Create charts with minimal configuration - colors are auto-applied! */ (function() { 'use strict'; // ========================================================================== // READ COLORS FROM CSS CUSTOM PROPERTIES // This ensures chart colors stay in sync with the theme // ========================================================================== /** * Get a CSS custom property value from :root */ function getCSSVar(name, fallback = '') { if (typeof getComputedStyle === 'undefined') return fallback; const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim(); return value || fallback; } /** * Build palette from CSS custom properties (with fallbacks) */ function buildPaletteFromCSS() { return { // Primary brand colors dartmouthGreen: getCSSVar('--dartmouth-green', '#00693e'), textPrimary: getCSSVar('--text-primary', '#0a2518'), textSecondary: getCSSVar('--text-secondary', '#0a3d23'), // Chart colors (from CSS --chart-color-N variables) chartColors: [ getCSSVar('--chart-color-1', '#00693e'), getCSSVar('--chart-color-2', '#267aba'), getCSSVar('--chart-color-3', '#ffa00f'), getCSSVar('--chart-color-4', '#9d162e'), getCSSVar('--chart-color-5', '#8a6996'), getCSSVar('--chart-color-6', '#a5d75f'), getCSSVar('--chart-color-7', '#003c73'), getCSSVar('--chart-color-8', '#d94415'), getCSSVar('--chart-color-9', '#643c20'), getCSSVar('--chart-color-10', '#c4dd88'), getCSSVar('--chart-color-11', '#f5dc69'), getCSSVar('--chart-color-12', '#424141'), ], // Background colors (semi-transparent versions) chartBgColors: [ getCSSVar('--chart-bg-1', 'rgba(0, 105, 62, 0.5)'), getCSSVar('--chart-bg-2', 'rgba(38, 122, 186, 0.5)'), getCSSVar('--chart-bg-3', 'rgba(255, 160, 15, 0.5)'), getCSSVar('--chart-bg-4', 'rgba(157, 22, 46, 0.5)'), getCSSVar('--chart-bg-5', 'rgba(138, 105, 150, 0.5)'), getCSSVar('--chart-bg-6', 'rgba(165, 215, 95, 0.5)'), ], // Semantic colors positive: getCSSVar('--chart-positive', '#00693e'), negative: getCSSVar('--chart-negative', '#9d162e'), neutral: getCSSVar('--chart-neutral', '#424141'), highlight: getCSSVar('--chart-highlight', '#ffa00f'), // Grid and axis colors gridLight: getCSSVar('--chart-grid-light', 'rgba(0, 105, 62, 0.1)'), gridMedium: getCSSVar('--chart-grid-medium', 'rgba(0, 105, 62, 0.15)'), gridDark: getCSSVar('--chart-grid-dark', 'rgba(0, 105, 62, 0.2)'), axisColor: getCSSVar('--chart-axis-color', '#0a2518'), // Font fontFamily: getCSSVar('--chart-font-family', "'Avenir LT Std', 'Avenir', 'Avenir Next', -apple-system, BlinkMacSystemFont, sans-serif"), }; } // Initialize palette (will be populated when DOM is ready) let CDL_PALETTE = null; // For convenience, expose primary chart colors array let CHART_COLORS = null; // ========================================================================== // FONT CONFIGURATION // Responsive font sizes based on typical Marp slide dimensions (1280x720) // ========================================================================== const FONT_CONFIG = { sizes: { title: 22, // Chart title subtitle: 18, // Subtitle legend: 16, // Legend labels axisTitle: 18, // Axis titles axisTicks: 16, // Axis tick labels tooltip: 14, // Tooltip text dataLabels: 14, // Data labels on charts }, weight: { normal: 400, medium: 500, bold: 600, }, }; // ========================================================================== // HELPER FUNCTIONS // ========================================================================== /** * Ensure palette is initialized */ function ensurePalette() { if (!CDL_PALETTE) { CDL_PALETTE = buildPaletteFromCSS(); CHART_COLORS = CDL_PALETTE.chartColors; } return CDL_PALETTE; } /** * Get color for a dataset at given index * Cycles through palette if more datasets than colors */ function getColor(index) { ensurePalette(); return CHART_COLORS[index % CHART_COLORS.length]; } /** * Get color with alpha transparency */ function getColorWithAlpha(color, alpha) { // Handle hex colors if (color.startsWith('#')) { const r = parseInt(color.slice(1, 3), 16); const g = parseInt(color.slice(3, 5), 16); const b = parseInt(color.slice(5, 7), 16); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } // Handle rgba colors if (color.startsWith('rgba')) { return color.replace(/[\d.]+\)$/, `${alpha})`); } return color; } /** * Generate colors for all datasets in chart data * Automatically assigns colors if not specified */ function autoAssignColors(data, chartType) { if (!data || !data.datasets) return data; data.datasets.forEach((dataset, index) => { const baseColor = getColor(index); // Only assign colors if not already specified switch (chartType) { case 'bar': case 'horizontalBar': if (!dataset.backgroundColor) { dataset.backgroundColor = baseColor; } if (!dataset.borderColor) { dataset.borderColor = baseColor; } if (dataset.borderWidth === undefined) { dataset.borderWidth = 2; } break; case 'line': if (!dataset.borderColor) { dataset.borderColor = baseColor; } if (!dataset.backgroundColor) { dataset.backgroundColor = getColorWithAlpha(baseColor, 0.1); } if (dataset.borderWidth === undefined) { dataset.borderWidth = 3; } if (dataset.pointRadius === undefined) { dataset.pointRadius = 6; } if (!dataset.pointBackgroundColor) { dataset.pointBackgroundColor = baseColor; } if (dataset.tension === undefined) { dataset.tension = 0.3; } break; case 'scatter': case 'bubble': if (!dataset.backgroundColor) { dataset.backgroundColor = baseColor; } if (!dataset.borderColor) { dataset.borderColor = baseColor; } if (dataset.pointRadius === undefined) { dataset.pointRadius = 15; } if (dataset.pointHoverRadius === undefined) { dataset.pointHoverRadius = 18; } break; case 'pie': case 'doughnut': case 'polarArea': // For pie charts, we need multiple colors for one dataset if (!dataset.backgroundColor) { const numItems = dataset.data ? dataset.data.length : 6; dataset.backgroundColor = []; for (let i = 0; i < numItems; i++) { dataset.backgroundColor.push(getColor(i)); } } if (!dataset.borderColor) { dataset.borderColor = '#d8d8d8'; // Slide background } if (dataset.borderWidth === undefined) { dataset.borderWidth = 2; } break; case 'radar': if (!dataset.borderColor) { dataset.borderColor = baseColor; } if (!dataset.backgroundColor) { dataset.backgroundColor = getColorWithAlpha(baseColor, 0.2); } if (dataset.borderWidth === undefined) { dataset.borderWidth = 2; } if (dataset.pointRadius === undefined) { dataset.pointRadius = 4; } if (!dataset.pointBackgroundColor) { dataset.pointBackgroundColor = baseColor; } break; default: // Generic color assignment if (!dataset.backgroundColor) { dataset.backgroundColor = baseColor; } if (!dataset.borderColor) { dataset.borderColor = baseColor; } } }); return data; } // ========================================================================== // CHART.JS GLOBAL DEFAULTS // ========================================================================== function applyGlobalDefaults() { if (typeof Chart === 'undefined') { console.warn('Chart.js not loaded. chart-defaults.js requires Chart.js to be loaded first.'); return false; } // Ensure palette is loaded from CSS const palette = ensurePalette(); // Font defaults Chart.defaults.font.family = palette.fontFamily; Chart.defaults.font.size = FONT_CONFIG.sizes.axisTicks; Chart.defaults.color = palette.textPrimary; // Responsive defaults Chart.defaults.responsive = true; Chart.defaults.maintainAspectRatio = false; // Animation (subtle) Chart.defaults.animation.duration = 400; // Plugin defaults // Legend Chart.defaults.plugins.legend.labels.font = { family: palette.fontFamily, size: FONT_CONFIG.sizes.legend, weight: FONT_CONFIG.weight.normal, }; Chart.defaults.plugins.legend.labels.color = palette.textPrimary; Chart.defaults.plugins.legend.labels.usePointStyle = true; Chart.defaults.plugins.legend.labels.padding = 20; // Title Chart.defaults.plugins.title.font = { family: palette.fontFamily, size: FONT_CONFIG.sizes.title, weight: FONT_CONFIG.weight.medium, }; Chart.defaults.plugins.title.color = palette.textPrimary; // Tooltip Chart.defaults.plugins.tooltip.backgroundColor = palette.textPrimary; Chart.defaults.plugins.tooltip.titleFont = { family: palette.fontFamily, size: FONT_CONFIG.sizes.tooltip, weight: FONT_CONFIG.weight.medium, }; Chart.defaults.plugins.tooltip.bodyFont = { family: palette.fontFamily, size: FONT_CONFIG.sizes.tooltip, }; Chart.defaults.plugins.tooltip.cornerRadius = 4; Chart.defaults.plugins.tooltip.padding = 10; // Scale defaults (for cartesian charts) // These need to be applied per-scale type const scaleDefaults = { grid: { color: palette.gridLight, lineWidth: 1, }, border: { color: palette.gridDark, width: 1, }, ticks: { font: { family: palette.fontFamily, size: FONT_CONFIG.sizes.axisTicks, }, color: palette.textPrimary, }, title: { font: { family: palette.fontFamily, size: FONT_CONFIG.sizes.axisTitle, weight: FONT_CONFIG.weight.normal, }, color: palette.textPrimary, }, }; // Apply scale defaults to linear scale if (Chart.defaults.scales && Chart.defaults.scales.linear) { if (Chart.defaults.scales.linear.grid) Object.assign(Chart.defaults.scales.linear.grid, scaleDefaults.grid); if (Chart.defaults.scales.linear.border) Object.assign(Chart.defaults.scales.linear.border, scaleDefaults.border); if (Chart.defaults.scales.linear.ticks) Object.assign(Chart.defaults.scales.linear.ticks, scaleDefaults.ticks); if (Chart.defaults.scales.linear.title) Object.assign(Chart.defaults.scales.linear.title, scaleDefaults.title); } // Apply scale defaults to category scale if (Chart.defaults.scales && Chart.defaults.scales.category) { if (Chart.defaults.scales.category.grid) Object.assign(Chart.defaults.scales.category.grid, scaleDefaults.grid); if (Chart.defaults.scales.category.border) Object.assign(Chart.defaults.scales.category.border, scaleDefaults.border); if (Chart.defaults.scales.category.ticks) Object.assign(Chart.defaults.scales.category.ticks, scaleDefaults.ticks); if (Chart.defaults.scales.category.title) Object.assign(Chart.defaults.scales.category.title, scaleDefaults.title); } // Apply scale defaults to logarithmic scale if (Chart.defaults.scales && Chart.defaults.scales.logarithmic) { if (Chart.defaults.scales.logarithmic.grid) Object.assign(Chart.defaults.scales.logarithmic.grid, scaleDefaults.grid); if (Chart.defaults.scales.logarithmic.border) Object.assign(Chart.defaults.scales.logarithmic.border, scaleDefaults.border); if (Chart.defaults.scales.logarithmic.ticks) Object.assign(Chart.defaults.scales.logarithmic.ticks, scaleDefaults.ticks); if (Chart.defaults.scales.logarithmic.title) Object.assign(Chart.defaults.scales.logarithmic.title, scaleDefaults.title); } // Apply scale defaults to radial scale (for radar charts) if (Chart.defaults.scales && Chart.defaults.scales.radialLinear) { if (Chart.defaults.scales.radialLinear.grid) Chart.defaults.scales.radialLinear.grid.color = palette.gridLight; if (Chart.defaults.scales.radialLinear.angleLines) Chart.defaults.scales.radialLinear.angleLines.color = palette.gridMedium; if (Chart.defaults.scales.radialLinear.pointLabels) { Chart.defaults.scales.radialLinear.pointLabels.font = { family: palette.fontFamily, size: FONT_CONFIG.sizes.axisTicks, }; Chart.defaults.scales.radialLinear.pointLabels.color = palette.textPrimary; } } return true; } // ========================================================================== // CHART WRAPPER FOR AUTO-STYLING // ========================================================================== /** * Wrap the Chart constructor to automatically apply CDL styling */ function wrapChartConstructor() { if (typeof Chart === 'undefined') return; const OriginalChart = Chart; // Create a wrapper that auto-applies colors window.Chart = function(ctx, config) { // Auto-assign colors if not specified if (config && config.data) { config.data = autoAssignColors(config.data, config.type); } // Merge default options for specific chart types if (config && config.options) { config.options = applyChartTypeDefaults(config.type, config.options); } // Call original constructor return new OriginalChart(ctx, config); }; // Copy static properties and methods Object.setPrototypeOf(window.Chart, OriginalChart); Object.assign(window.Chart, OriginalChart); // Preserve the prototype chain window.Chart.prototype = OriginalChart.prototype; } /** * Apply chart-type specific defaults */ function applyChartTypeDefaults(chartType, userOptions) { const options = { ...userOptions }; switch (chartType) { case 'bar': case 'horizontalBar': // Bar chart defaults if (!options.scales) options.scales = {}; if (!options.scales.x) options.scales.x = {}; if (!options.scales.y) options.scales.y = {}; // Hide x-axis grid for cleaner look if (options.scales.x.grid === undefined) { options.scales.x.grid = { display: false }; } break; case 'line': // Line chart defaults if (!options.interaction) { options.interaction = { intersect: false, mode: 'index' }; } break; case 'pie': case 'doughnut': // Pie/doughnut defaults if (!options.plugins) options.plugins = {}; if (options.plugins.legend === undefined) { const palette = ensurePalette(); options.plugins.legend = { position: 'right', labels: { font: { family: palette.fontFamily, size: FONT_CONFIG.sizes.legend, }, color: palette.textPrimary, padding: 15, }, }; } break; case 'radar': // Radar chart defaults - keep as-is, scale defaults applied globally break; case 'scatter': case 'bubble': // Scatter/bubble defaults if (!options.scales) options.scales = {}; if (!options.scales.x) options.scales.x = {}; if (!options.scales.y) options.scales.y = {}; break; } return options; } // ========================================================================== // CONVENIENCE FUNCTIONS FOR USERS // Exposed on window.CDLChart for easy access // ========================================================================== window.CDLChart = { // Color palette access (getters to ensure lazy initialization) get colors() { return ensurePalette().chartColors; }, get palette() { return ensurePalette(); }, // Get specific color by index getColor: getColor, // Get color with transparency getColorWithAlpha: getColorWithAlpha, // Get array of colors for a specific count getColors: function(count) { ensurePalette(); const result = []; for (let i = 0; i < count; i++) { result.push(getColor(i)); } return result; }, // Font configuration fonts: FONT_CONFIG, // Quick chart creation helpers // These create minimal config that auto-applies all styling /** * Create a simple bar chart * @param {string} canvasId - Canvas element ID * @param {string[]} labels - X-axis labels * @param {number[]} data - Data values * @param {object} options - Optional overrides */ bar: function(canvasId, labels, data, options = {}) { return new Chart(document.getElementById(canvasId), { type: 'bar', data: { labels: labels, datasets: [{ data: data }], }, options: { plugins: { legend: { display: false } }, ...options, }, }); }, /** * Create a simple line chart * @param {string} canvasId - Canvas element ID * @param {string[]} labels - X-axis labels * @param {Array} datasets - Array of {label, data} objects * @param {object} options - Optional overrides */ line: function(canvasId, labels, datasets, options = {}) { return new Chart(document.getElementById(canvasId), { type: 'line', data: { labels: labels, datasets: datasets.map(ds => ({ label: ds.label, data: ds.data, fill: ds.fill !== undefined ? ds.fill : true, })), }, options: options, }); }, /** * Create a simple pie chart * @param {string} canvasId - Canvas element ID * @param {string[]} labels - Slice labels * @param {number[]} data - Data values * @param {object} options - Optional overrides */ pie: function(canvasId, labels, data, options = {}) { return new Chart(document.getElementById(canvasId), { type: 'pie', data: { labels: labels, datasets: [{ data: data }], }, options: options, }); }, /** * Create a simple scatter chart * @param {string} canvasId - Canvas element ID * @param {Array} datasets - Array of {label, data: [{x, y}]} objects * @param {object} options - Optional overrides */ scatter: function(canvasId, datasets, options = {}) { return new Chart(document.getElementById(canvasId), { type: 'scatter', data: { datasets: datasets.map(ds => ({ label: ds.label, data: ds.data, })), }, options: options, }); }, /** * Create a doughnut chart * @param {string} canvasId - Canvas element ID * @param {string[]} labels - Slice labels * @param {number[]} data - Data values * @param {object} options - Optional overrides */ doughnut: function(canvasId, labels, data, options = {}) { return new Chart(document.getElementById(canvasId), { type: 'doughnut', data: { labels: labels, datasets: [{ data: data }], }, options: options, }); }, /** * Create a radar chart * @param {string} canvasId - Canvas element ID * @param {string[]} labels - Axis labels * @param {Array} datasets - Array of {label, data} objects * @param {object} options - Optional overrides */ radar: function(canvasId, labels, datasets, options = {}) { return new Chart(document.getElementById(canvasId), { type: 'radar', data: { labels: labels, datasets: datasets.map(ds => ({ label: ds.label, data: ds.data, })), }, options: options, }); }, }; // ========================================================================== // INITIALIZATION // ========================================================================== function initialize() { // Wait for Chart.js to be available if (typeof Chart !== 'undefined') { applyGlobalDefaults(); wrapChartConstructor(); console.log('CDL Chart defaults applied successfully.'); return true; } else { // Chart.js not yet loaded - wait and retry let retries = 0; const maxRetries = 50; // 5 seconds max wait const checkInterval = setInterval(function() { retries++; if (typeof Chart !== 'undefined') { clearInterval(checkInterval); applyGlobalDefaults(); wrapChartConstructor(); console.log('CDL Chart defaults applied successfully (after waiting for Chart.js).'); } else if (retries >= maxRetries) { clearInterval(checkInterval); console.warn('Chart.js not found after waiting. CDL Chart defaults not applied.'); } }, 100); return false; } } // Initialize IMMEDIATELY - this must run BEFORE any chart creation scripts // Chart.js CDN should be loaded before this script initialize(); })();

Lecture 11: Word embeddings

PSYC 51.17: Models of language and communication

Jeremy R. Manning
Dartmouth College
Winter 2026

Learning objectives

  1. Understand Word2Vec (CBOW and Skip-gram)
  2. Explain how negative sampling makes training efficient
  3. Compare Word2Vec, GloVe, and FastText
  4. Use word embeddings to solve analogies and compute similarities
  5. Recognize biases in word embeddings

Word2Vec showed that neural networks can learn powerful semantic representations from raw text alone.

From count-based to prediction-based embeddings

  • Input: term-document matrices
  • Uses term co-occurrence statistics across documents
  • Captures global patterns and linear relationships
  • Input: raw text sequences
  • Learn to predict context from word (and vice versa)
  • Use local context to learn embeddings
  • Captures non-linear relationships

Beyond the implementation details, there is an even more fundamental difference between the two classes of approaches. Whereas count-based methods learn by counting (where embeddings are the end goal), prediction-based methods like Word2Vec learn by predicting; the embeddings come "for free" as a consequence of learning to predict.

Word2Vec: words that appear in similar contexts should have similar embeddings

In NLP, context refers to the words surrounding a target word within a specified window size (i.e., number of preceding and proceeding words/tokens).

"The quick brown [TARGET] jumped over the lazy dog"

Context: [The, quick, brown, jumped, over, the, lazy, dog]
Target: fox

  • Train a model to predict target from context (or vice versa)
  • The learned weights become our embedding vectors!
  • We don't actually care about the prediction task; we just want the embeddings!

CBOW: Continuous Bag of Words

Given context words, predict the center word

Input: "the, cat, on, the" → Predict: "sat"

  • Quick training (averages context vectors)
  • Better performance for frequent words (word appears in many contexts)
  • Smooths over distributional information

Skip-gram

Given center word, predict the context words

Input: "sat" → Predict: "the, cat, on, the"

  • Slower than CBOW but often results in better quality embeddings
  • Better performance for rare words (more focus on specific contexts where the word appears)
  • Most commonly used in practice

Context windows: worked example

Sentence: "The cat sat on the mat" | Window size = 2


1Position 0: "The" → Context: [cat, sat]
2Position 1: "cat" → Context: [The, sat, on]
3Position 2: "sat" → Context: [The, cat, on, the]
4Position 3: "on"  → Context: [cat, sat, the, mat]
5Position 4: "the" → Context: [sat, on, mat]
6Position 5: "mat" → Context: [on, the]

Skip-gram training pairs (target → context):


1(The, cat), (The, sat), (cat, The), (cat, sat), (cat, on), ...

The efficiency problem

Softmax over entire vocabulary is expensive!

P(wcontext∣wcenter)=exp⁡(vcontextTvcenter)∑w=1Vexp⁡(vwTvcenter)P(w_{context}|w_{center}) = \frac{\exp(v_{context}^T v_{center})}{\sum_{w=1}^V \exp(v_w^T v_{center})}

For 100k vocabulary: 100k exponentials per training example!

Solution: Negative sampling

Instead of predicting across all words, create binary classification:

  • Positive: Actual context word (label = 1)
  • Negative: kk random words (label = 0)

Training becomes O(k)O(k) instead of O(V)O(V)!

Typical kk: 5–20 negative samples per positive example.

Negative sampling example

Training pair: ("cat", "sat") — cat is center, sat is context


1# Positive sample: Does "sat" appear near "cat"? YES (label=1)
2positive_pair = ("cat", "sat", label=1)
3
4# Negative samples: Random words that DON'T appear near "cat"
5negative_pairs = [
6    ("cat", "algorithm", label=0),
7    ("cat", "president", label=0),
8    ("cat", "quantum", label=0),
9]
10
11# Train binary classifier: Is this a real context pair?
12# Instead of 100k-way softmax → just 4 binary predictions!

The famous result: vector arithmetic

King→−Man→+Woman→≈Queen→\overrightarrow{\text{King}} - \overrightarrow{\text{Man}} + \overrightarrow{\text{Woman}} \approx \overrightarrow{\text{Queen}}

  • Paris→−France→+Italy→≈Rome→\overrightarrow{\text{Paris}} - \overrightarrow{\text{France}} + \overrightarrow{\text{Italy}} \approx \overrightarrow{\text{Rome}}
  • Walking→−Walk→+Swim→≈Swimming→\overrightarrow{\text{Walking}} - \overrightarrow{\text{Walk}} + \overrightarrow{\text{Swim}} \approx \overrightarrow{\text{Swimming}}
  • Bigger→−Big→+Small→≈Smaller→\overrightarrow{\text{Bigger}} - \overrightarrow{\text{Big}} + \overrightarrow{\text{Small}} \approx \overrightarrow{\text{Smaller}}

Subtracting "man" removes the "male" component; adding "woman" adds the "female" component. Result: "female royal" = queen!

By learning embeddings that predict context, Word2Vec ends up capturing how each word relates to the contexts in which it appears. It's like each word has some "pull" on the surrounding words (and vice versa). When we do vector arithmetic, we're manipulating these "pulls" to find words that fit the same relational pattern.

Word2Vec in Python


1from gensim.models import Word2Vec
2import gensim.downloader as api
3
4# Load pre-trained model (3B words, 300 dimensions)
5model = api.load('word2vec-google-news-300')
6
7# Find similar words
8model.most_similar('computer', topn=5)
9# [('computers', 0.72), ('laptop', 0.69), ('PC', 0.68), ...]
10
11# Word analogies: king - man + woman = ?
12model.most_similar(positive=['king', 'woman'], negative=['man'], topn=1)
13# [('queen', 0.71)]
14
15# Train your own
16sentences = [['the', 'cat', 'sat'], ['the', 'dog', 'ran']]
17custom_model = Word2Vec(sentences, vector_size=100, window=5, min_count=1, sg=1)

GloVe: Global Vectors

Pennington, Socher, and Manning (2014, EMNLP) GloVe: Global Vectors for Word Representation.

Ratios of co-occurrence probabilities encode meaning better than raw probabilities!

How does GloVe work?

Probe word P(w|ice) P(w|steam) Ratio
solid high low >> 1
gas low high << 1
water high high ~ 1

GloVe learns word vectors whose dot product relates to co-occurrence probability:

w⃗iTw⃗j≈log⁡P(i,j)\vec{w}_i^T \vec{w}_j \approx \log P(i,j)

FastText: leveraging subword information

Bojanowski, Grave, Joulin, and Mikolov (2017, TACL) Enriching word vectors with subword information.

Subword information refers to the smaller components that make up words, such as character n-grams, prefixes, suffixes, and morphemes. These components can provide valuable insights into the meaning and structure of words, especially for rare or out-of-vocabulary terms.

Word2Vec and GloVe give ONE vector per word:

  • New word? Unknown!
  • Misspelling? Unknown!
  • Rare morphological form? Unknown!

But "running", "runner", "runnable" share morphology, and we can exploit that!

Remember back to when we introduced tokens made up of subword units (like Byte Pair Encoding)? FastText takes advantage of this idea by breaking words down into smaller parts, allowing it to create embeddings for words it hasn't seen before.

FastText: character n-grams

Character n-grams are contiguous sequences of 'n' characters extracted from a word.

Character trigrams: <wh, whe, her, ere, re>

Word vector = average of n-gram vectors:

w⃗where=15(z⃗<wh+z⃗whe+z⃗her+z⃗ere+z⃗re>)\vec{w}_{where} = \frac{1}{5}(\vec{z}_{<wh} + \vec{z}_{whe} + \vec{z}_{her} + \vec{z}_{ere} + \vec{z}_{re>})

  • Handles OOV words! Can generate vectors for words never seen in training
  • Captures morphology: "unhappiness" shares n-grams with "unhappy", "happiness"
  • Great for morphologically rich languages: German, Turkish, Finnish
  • Robust to typos: misspellings share many n-grams with correct spellings

Bias in word embeddings

Embeddings learn biases from training data!

  • Man→:Computer programmer→::Woman→:Homemaker→\overrightarrow{\text{Man}} : \overrightarrow{\text{Computer programmer}} :: \overrightarrow{\text{Woman}} : \overrightarrow{\text{Homemaker}}
  • Man→:Doctor→::Woman→:Nurse→\overrightarrow{\text{Man}} : \overrightarrow{\text{Doctor}} :: \overrightarrow{\text{Woman}} : \overrightarrow{\text{Nurse}}

Estimating bias


1def gender_bias_score(word):
2    return model.similarity(word, 'man') - model.similarity(word, 'woman')
3
4# Results from Google News Word2Vec:
5# programmer: +0.091 (toward man)
6# nurse: -0.109 (toward woman)

Embeddings used in hiring tools, search, and recommendations can perpetuate bias!

Summary

  1. Word2Vec (2013): neural revolution — predict context from words
    • Skip-gram most common, negative sampling for efficiency
  2. GloVe (2014): global co-occurrence statistics
  3. FastText (2017): character n-grams handle OOV
  4. Bias: embeddings reflect training data biases

Word embeddings are fundamentally static: each word has a single vector representation, regardless of context. This means they cannot capture the different meanings a word might have in different sentences (e.g., "bank" as a financial institution vs. "bank" as the side of a river). Contextual embeddings (like ELMo, BERT) address this limitation by generating word representations that depend on the surrounding words.

Assignment 3: Wikipedia Embeddings

  • Implement 10+ embedding methods (LSA, Word2Vec, GloVe, FastText, SBERT, BGE, E5, ...)
  • Visualize with UMAP, clustering, and DataMapPlot
  • Evaluate via document matching (can embeddings match first/second halves?)
  • Write some reflection essays on trade-offs and meaning

Questions?

📧 Email me
💬 Join our Discord
💁 Come to office hours

Contextual embeddings (ELMo, BERT)! These models construct embeddings based on the entire sentence, allowing for context-sensitive representations.