Rational Speech Act Theory¶

This notebook implements a simple model from the Rational Speech Act (RSA) framework for scalar implicature (Goodman & Stuhlmüller, 2013).

Prerequisites¶

This tutorial assumes you have completed the Introduction to FlipPy tutorial.

Learning Objectives¶

By the end of this tutorial, you will be able to:

  1. Understand how pragmatic language interpretation emerges from recursive reasoning
  2. Implement literal and pragmatic listener/speaker models in FlipPy
  3. Explore how rationality parameters affect communication

What is Scalar Implicature?¶

Scalar implicature is a phenomenon in pragmatics where listeners infer more than the literal meaning of words. For example:

"Some students passed the exam."

Literally, this is true even if all students passed. But listeners typically infer that not all students passed—otherwise, the speaker would have said "all."

This inference arises because speakers are assumed to be cooperative and informative. If the speaker knew all students passed, saying "some" would be less informative than saying "all." The listener reasons about what the speaker would say, and infers the stronger meaning.

The RSA framework formalizes this intuition using probabilistic models of speakers and listeners who reason about each other recursively.

In [1]:
from flippy import infer, condition, flip, draw_from
from math import exp, log as log_
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

def log(x):
    return log_(x) if x > 0 else float('-inf')

Setting Up the Model¶

In our model, the world represents how many objects (out of 3) have a particular property. So world = 0 means "none," world = 1 or world = 2 means "some but not all," and world = 3 means "all."

The utterances are the words a speaker can say: "none," "some," or "all."

The meaning function defines the literal (logical) truth of each utterance in each world:

  • "none" is true when world == 0
  • "some" is true when world > 0 (i.e., at least one)
  • "all" is true when world == 3

Note that "some" is literally true even when all objects have the property—the pragmatic exclusion of "all" is what the model will derive!

In [2]:
def world_prior():
    return draw_from(4)

def utterance_prior():
    return draw_from(['some', "all", 'none'])

def meaning(utt, world):
    return (
        world > 0 if utt == 'some' else
        world == 3 if utt == 'all' else
        world == 0 if utt == 'none' else
        True
    )

The RSA Model: Three Levels of Reasoning¶

The RSA framework defines a hierarchy of agents:

  1. Literal Listener (L0): Interprets utterances according to their literal meaning only
  2. Literal Speaker (S1): Chooses utterances to inform the literal listener
  3. Pragmatic Listener (L1): Interprets utterances by reasoning about what a speaker would say

The key insight is that the pragmatic listener reasons: "If the speaker meant world 3 (all), they would have said 'all.' Since they said 'some,' they probably meant world 1 or 2."

The rationality parameter controls how optimal the speaker is—higher values mean the speaker chooses more informative utterances more deterministically.

In [3]:
@infer
def literal_listener(utterance):
    world = world_prior()
    m = meaning(utterance, world)
    condition(m)
    return world

@infer
def literal_speaker(world, rationality=1) -> str:
    utterance = utterance_prior()
    listener_beliefs = literal_listener(utterance)
    true_belief = listener_beliefs.prob(world)
    condition(exp(rationality*log(true_belief)))
    return utterance

@infer
def pragmatic_listener(utterance, speaker_rationality=1):
    world = world_prior()
    speaker_policy = literal_speaker(world, rationality=speaker_rationality)
    speaker_policy.observe(utterance)
    return world
In [4]:
speaker_policy_df = []
fig, axes = plt.subplots(1, 4, figsize=(10, 2))
for ax, w in zip(axes, range(4)):
    speaker_policy = literal_speaker(w, rationality=1)
    ax.bar(['none', 'some', 'all'], 
           [speaker_policy.prob('none'), speaker_policy.prob('some'), speaker_policy.prob('all')])
    ax.set_title(f'World {w}')
fig.tight_layout()
No description has been provided for this image

Interpreting the speaker policies: Each plot shows what a rational speaker would say given the true world state:

  • World 0: The speaker says "none" (the only true and informative option)
  • World 1-2: The speaker says "some" (true and distinguishes from world 0 and 3)
  • World 3: The speaker says "all" (true and more informative than "some")

Notice that for worlds 1 and 2, "some" is preferred over other utterances because it's the most informative true statement.

In [5]:
pragmatic_listener("some", speaker_rationality=2)
Out[5]:
ElementProbability
10.476
20.476
30.048

Interpreting the pragmatic listener: When the listener hears "some" with speaker rationality 2, they infer that worlds 1 and 2 are roughly equally likely (~48% each), while world 3 is unlikely (~5%). This demonstrates scalar implicature: even though "some" is literally true in world 3, the pragmatic listener infers it's probably not the case—otherwise the speaker would have said "all."

Recursive Reasoning: Going Deeper¶

The model above has just one level of pragmatic reasoning. But we can extend this to arbitrary levels:

  • Level 0 Listener: Interprets literally
  • Level 1 Speaker: Reasons about level 0 listener
  • Level 2 Listener: Reasons about level 1 speaker
  • Level 3 Speaker: Reasons about level 2 listener
  • ... and so on

In the code below, we implement mutually recursive listener and speaker functions that can reason at any level. Even levels (0, 2, 4, ...) are listeners; odd levels (1, 3, 5, ...) are speakers.

In [6]:
@infer
def listener(utterance, speaker_rationality=1, level=0):
    assert level % 2 == 0, "Level must be even for listener"
    world = world_prior()
    if level <= 0:
        m = meaning(utterance, world)
        condition(m)
        return world
    speaker_policy = speaker(world, rationality=speaker_rationality, level=level-1)
    speaker_policy.observe(utterance)
    return world

@infer
def speaker(world, rationality=1, level=1) -> str:
    assert level % 2 == 1, "Level must be odd for speaker"
    utterance = utterance_prior()
    listener_beliefs = listener(
        utterance,
        speaker_rationality=rationality,
        level=level - 1
    )
    true_belief = listener_beliefs.prob(world)
    condition(exp(rationality*log(true_belief)))
    return utterance
In [7]:
speaker_policy_df = []
for rationality in [.1, .5, 1, 2, 5, 10]:
    for level in range(1, 21, 2):
        policy = speaker(3, rationality=rationality, level=level)
        speaker_policy_df.append({
            'rationality': rationality,
            'level': level,
            **dict(policy),
        })
speaker_policy_df = pd.DataFrame(speaker_policy_df)
In [8]:
sns.lineplot(
    data=speaker_policy_df,
    x='level',
    y='all',
    hue='rationality',
)
Out[8]:
<Axes: xlabel='level', ylabel='all'>
No description has been provided for this image
In [9]:
listener_inference_df = []
for rationality in [.1, .5, 1, 2, 5, 10]:
    for level in range(0, 21, 2):
        world_dist = listener("some", speaker_rationality=rationality, level=level)
        listener_inference_df.append({
            'rationality': rationality,
            'level': level,
            **dict(world_dist)
        })
listener_inference_df = pd.DataFrame(listener_inference_df)
In [10]:
sns.lineplot(
    data=listener_inference_df,
    x='level',
    y=2,
    hue='rationality',
)
Out[10]:
<Axes: xlabel='level', ylabel='2'>
No description has been provided for this image

Interpreting the recursive reasoning plots:

The plots above show how behavior changes with reasoning level and rationality:

  1. Speaker plot (first): Shows probability of saying "all" in world 3 across reasoning levels. Higher rationality leads to faster convergence to optimal behavior (always saying "all").

  2. Listener plot (second): Shows probability of inferring world 2 when hearing "some" across reasoning levels. With higher rationality, the listener more quickly learns to exclude world 3 (where the speaker would say "all").

Key insight: After just a few levels of reasoning, the behavior stabilizes. This suggests that human pragmatic inference may only require a few levels of recursive reasoning—consistent with empirical findings in cognitive science.

Summary¶

In this tutorial, you learned:

  • Scalar implicature arises from pragmatic reasoning about speaker intentions
  • RSA models formalize this as recursive Bayesian inference between speakers and listeners
  • Rationality controls how optimally agents communicate
  • Recursive depth affects inference quality, but behavior stabilizes quickly

The RSA framework has been applied to many linguistic phenomena beyond scalar implicature, including metaphor, hyperbole, and politeness. See probmods.org for more examples.