* 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 2: Pattern matching and ELIZA

PSYC 51.07: Models of language and communication

Jeremy R. Manning
Dartmouth College
Winter 2026

Recap from Lecture 1

  • Consciousness is complex: multiple types, hard to define
  • Language β‰ \neq thought: but they interact in interesting ways
  • Grounding matters: meaning comes from experience
  • Does the "experience" need to be first-hand? Can we "share" experience with another person? Or with a machine? What might that look like?
  • Can we have understanding without consciousness (or vice versa)?

Pattern matching can create powerful illusions of experience and understanding, even without any real comprehension or grounding. We'll explore some foundational techniques for text processing that were used to build one of the first chatbots, ELIZA. (Next time, we'll dig into how ELIZA works under the hood!)

Side note: follow along with Google Colab!

  • Go to colab.research.google.com

  • Click "New notebook"

  • Click to create new text or code cells

  • Copy and paste code from slides

  • Press Shift + Enter to run cells

  • Riff on the examples and see what happens!

Creating the illusion of experience and understanding

Humans

  • Derive meaning from experience
  • Connect words to memories, emotions, senses
  • Understand context and nuance

Computers

  • Manipulate strings (sequences of characters)
  • Have no direct experience of the world
  • Process symbols without inherent meaning

How do we bridge this gap? Can we use symbol manipulation create the appearance of understanding?

The illusion of understanding comes from the programmer's skill at designing clever pattern matching rules, along with the user having understanding and experience (and a tendancy to anthropomorphize), not from any actual understanding on the part of the machine.

Text processing and string manipulation

  • Finding: Locate patterns within text
  • Replacing: Substitute one pattern for another
  • Extracting: Pull out specific parts of text
  • Transforming: Convert text to different formats

1text = "Hello, how are you today?"
2
3# Finding (True)
4"how" in text
5
6# Replacing ("Hello, how are we today?")
7text.replace("you", "we")
8
9# Extracting ("how are you today?")
10text.split(", ")[1]
11
12# Transforming (["hello", "how", "are", "you", "today?"])
13text.lower().split()

Text processing is the foundation of computational linguistics

User input Pattern matching Response generation Output
Basic conversational AI pipeline

Every substantive conversational AI system, from ELIZA to ChatGPT, fundamentally processes text through some form of pattern matching and pattern completion, though with vastly different levels of sophistication.

Regular expressions

A sequence of characters that defines a search pattern. Regular expressions provide flexible, powerful pattern matching for text processing.

Symbol Meaning Example
. Any character h.t matches "hat", "hit", "hot"
* Zero or more ab*c matches "ac", "abc", "abbc"
() Capture group (hello) captures "hello"
| Alternation cat|dog matches "cat" or "dog"
^ Negation [^a-z] matches any non-lowercase letter

Regular expressions in Python


1import re
2
3text = "I am feeling very happy today"
4
5# Simple pattern matching
6if re.search(r"happy|sad|angry", text):
7    print("Found an emotion!")
8
9# Capture groups - extract parts of a match
10match = re.search(r"I am feeling (.*?) today", text)
11if match:
12    emotion = match.group(1)  # "very happy"
13
14# Substitution
15new_text = re.sub(r"I am", "You are", text)
16# "You are feeling very happy today"

Worked example: building a simple chatbot rule

Goal: Respond to "I am [feeling]" statements


1import re
2
3def simple_respond(user_input):
4    # Try to match "I am [something]"
5    match = re.search(
6        r"I am (.*)",
7        user_input,
8        re.IGNORECASE
9    )
10
11    if match:
12        feeling = match.group(1)
13        return f"Why are you {feeling}?"
14
15    return "Tell me more."

Try it:


1>>> simple_respond("I am tired")
2"Why are you tired?"
3
4>>> simple_respond("I am feeling anxious")
5"Why are you feeling anxious?"
6
7>>> simple_respond("Hello there")
8"Tell me more."
Key insight The computer has no idea what "tired" or "anxious" mean. It's just copying text!

Let's build our first (very simple) chatbot!


1import re
2
3# response function: process user input and
4# respond appropriately
5def respond(user_input):
6  # look for "I am ___" patterns
7  i_am = re.search(r"I am (.*)", user_input, re.IGNORECASE)
8
9  # look for "You are ___" patterns
10  you_are = re.search(r"You are (.*)", user_input, re.IGNORECASE)
11
12  if i_am:
13    feeling = i_am.group(1)
14    return f"Why are you {feeling}?"
15  elif you_are:
16    trait = you_are.group(1)
17    return f"What makes you think I am {trait}?"
18  # add some more patterns!
19  else:    
20    return "Tell me more."
continued...

Let's build our first (very simple) chatbot!


21# main loop: interact with user
22print("Bot: Hello! I'm here to listen. Type 'exit' to quit.")
23while True:
24  user_input = input("You: ")
25  if user_input.lower() in ["exit", "quit"]:
26    break
27  else: 
28    response = respond(user_input)
29  print("Bot:", response)
30print("Bot: Goodbye!")
...continued

Meet ELIZA: a computerized Rogerian therapist

ELIZA was created by Joseph Weizenbaum at MIT in 1966. It was the first-ever interactive chatbot, and one of the first programs to attempt natural language processing. ELIZA plays the role of a Rogerian therapist, using simple pattern matching to simulate a conversation with a human patient.

  • Non-directive therapy style
  • Reflects statements back to patient
  • Asks open-ended questions
  • Avoids making claims about the world
  • Rogerian style requires no real knowledge
  • Simply reflects and rephrases
  • Lets the human do the "heavy lifting" of "knowing about stuff"

Chat with ELIZA

Have a conversation with ELIZA. Try putting yourself into the "frame of mind" of someone from the 1960s who had never experienced a chatbot before, and likely who had only had limited (if any) exposure to computers. Take on the role of a "patient" seeking help from ELIZA in its role as a therapist. Then use your own (modern) knowledge and experiences to see where ELIZA breaks down.

  • What does ELIZA do surprisingly well?
  • What reveals its limitations?
  • Can you "trick" ELIZA? How?
  • What kinds of inputs break the illusion?
  • How do you think ELIZA works?

Example ELIZA conversation


1User:   I am feeling sad today
2ELIZA:  Why are you feeling sad today?
3
4User:   My mother doesn't understand me
5ELIZA:  Tell me more about your family.
6
7User:   She always criticizes everything I do
8ELIZA:  Your mother?
9
10User:   Yes, and my father too
11ELIZA:  What else comes to mind when you think of your father?
12
13User:   I don't know
14ELIZA:  Why don't you know?
Notice how ELIZA:
  • Picks up on keywords ("mother", "father")
  • Reflects statements back as questions
  • Uses generic prompts when stuck
  • Never makes specific claims about the world
Where it breaks down:

1User:   What is 2 + 2?
2ELIZA:  Why do you ask?
3
4User:   I want to know if you're intelligent
5ELIZA:  Does it please you to believe I am intelligent?

The ELIZA effect

The tendency to unconsciously assume that computer behaviors are analogous to human behaviors; to attribute human-like understanding to programs that merely simulate it.

Weizenbaum was surprised (and disturbed) by how quickly users became emotionally involved with ELIZA. His secretary reportedly asked him to leave the room so she could have a private conversation with the program!

Why do we anthropomorphize machines?

πŸ—£οΈ Language cues
πŸ‘οΈ Pattern recognition
πŸ’­ Social instincts
🎭 Theory of mind

Humans are social creatures. We evolved to detect minds and intentions, and we often over-apply this tendency, even to clearly non-conscious entities.

Discussion

Have you experienced the ELIZA effect with modern AI systems (ChatGPT, Claude, Siri, Alexa)?

  • When have you felt like an AI "understood" you?
  • Where do you think that illusion came from?
  • What broke the illusion?
  • What is the difference between seeming intelligent and being intelligent?
  • How would we know if an AI truly understood us?

Example: The ELIZA effect in modern AI

Feels like understanding:

1User: I'm really stressed about my exam
2AI:   I can hear that you're feeling overwhelmed. Exams can be really stressful. What subject is it?
3
4User: Physics. I've been studying for weeks
5AI:   Studying for weeks shows real dedication. What part of physics is giving you trouble?
Reveals the limitation:

1User: I just realized I left my exam notes in your office
2AI:   I understand that can be frustrating! Would you like tips on how to retrieve your notes?
Pattern matching, not understanding

The AI has no office. It cannot have your notes. But it responds as if this makes sense!

Reading: Weizenbaum (1966)

Weizenbaum, J. (1966). ELIZAβ€”A computer program for the study of natural language communication between man and machine. Communications of the ACM, 9(1), 36–45.

  • How does ELIZA select responses?
  • What are "scripts" in ELIZA's architecture?
  • Why did Weizenbaum choose the DOCTOR script?
  • What did Weizenbaum observe about user reactions?

Up next

How ELIZA actually works

  • Complete architecture walkthrough
  • Pattern matching and response selection
  • The role of scripts and keywords
  • We will build it ourselves!

Key takeaways

  1. String manipulation is foundational: all text-based AI builds on finding, replacing, and extracting patterns
  2. Regular expressions are powerful: flexible pattern matching enables sophisticated text processing
  3. ELIZA demonstrated the power of simplicity: a few clever rules can create convincing illusions
  4. The ELIZA effect is real: we naturally anthropomorphize systems that use language
  5. Seeming β‰ \neq being: appearing intelligent does not require actual understanding

Questions? Want to chat more?

πŸ“§ Email me
πŸ’¬ Join our Discord
πŸ’ Come to office hours

Feeling lost? Want to make sure we cover something you're excited about? Reach out if you have questions, comments, concerns, or just want to chat!