使用 RSA 理解夸张

"My new kettle cost a million dollars."

夸张——使用夸大的言语来传达强烈观点——是语言常见的一种非字面用法。然而,在最简单的 RSA 模型下,语言的非字面用法是不可能的。Kao 等人提出,可以添加两种成分来使 RSA 能够捕捉夸张。首先,说话者传达并听话者推断的状态应包括情感维度。其次,说话者只打算传达与特定主题相关的信息,例如“这有多贵?”或“我对价格有什么感觉?”;因此,语用听话者会联合推断这个主题和状态。

[1]:
#first some imports
import torch
torch.set_default_dtype(torch.float64)  # double precision for numerical stability

import collections
import argparse
import matplotlib.pyplot as plt

import pyro
import pyro.distributions as dist
import pyro.poutine as poutine

from search_inference import HashingMarginal, memoize, Search

与简单的 RSA 示例一样,推断辅助工具 Marginal 接受一个未标准化的随机函数,通过使用 Search 构建执行轨迹的分布,并通过 HashingMarginal 构建返回值的边缘分布。

[2]:
def Marginal(fn):
    return memoize(lambda *args: HashingMarginal(Search(fn).run(*args)))

本示例的域将是包含价格(例如茶壶的价格)和说话者情绪唤醒(说话者是否认为这个价格令人恼火地贵)的状态。此处的先验是根据实验数据调整的。

[3]:
State = collections.namedtuple("State", ["price", "arousal"])

def price_prior():
    values = [50, 51, 500, 501, 1000, 1001, 5000, 5001, 10000, 10001]
    probs = torch.tensor([0.4205, 0.3865, 0.0533, 0.0538, 0.0223, 0.0211, 0.0112, 0.0111, 0.0083, 0.0120])
    ix = pyro.sample("price", dist.Categorical(probs=probs))
    return values[ix]

def arousal_prior(price):
    probs = {
        50: 0.3173,
        51: 0.3173,
        500: 0.7920,
        501: 0.7920,
        1000: 0.8933,
        1001: 0.8933,
        5000: 0.9524,
        5001: 0.9524,
        10000: 0.9864,
        10001: 0.9864
    }
    return pyro.sample("arousal", dist.Bernoulli(probs=probs[price])).item() == 1

def state_prior():
    price = price_prior()
    state = State(price=price, arousal=arousal_prior(price))
    return state

现在我们定义一个只为字面听话者产生 相关 信息的 RSA 说话者版本。我们根据讨论中的问题 (QUD) 定义相关性——这可以被视为定义说话者当前的注意点或主题。

说话者在数学上定义为

\[P_S(u|s,q) \propto \left[ \sum_{w'} \delta_{q(w')=q(w)} P_\text{Lit}(w'|u) p(u) \right]^\alpha\]

为了将其实现为概率程序,我们从辅助函数 project 开始,它接受某个(离散)域上的分布和此域上的函数 qud。它使用 Marginal(作为 Python 装饰器)创建前推分布。说话者的相关信息随后只是关于此投影中状态的信息。

[4]:
@Marginal
def project(dist,qud):
    v = pyro.sample("proj",dist)
    return qud_fns[qud](v)

@Marginal
def literal_listener(utterance):
    state=state_prior()
    pyro.factor("literal_meaning", 0. if meaning(utterance, state.price) else -999999.)
    return state

@Marginal
def speaker(state, qud):
    alpha = 1.
    qudValue = qud_fns[qud](state)
    with poutine.scale(scale=torch.tensor(alpha)):
        utterance = utterance_prior()
        literal_marginal = literal_listener(utterance)
        projected_literal = project(literal_marginal, qud)
        pyro.sample("listener", projected_literal, obs=qudValue)
    return utterance

可能的 QUD 捕捉了说话者可能关注价格、她的情感或这些的某种组合。我们假设 QUD 的先验是均匀的。

[5]:
#The QUD functions we consider:
qud_fns = {
    "price": lambda state: State(price=state.price, arousal=None),
    "arousal": lambda state: State(price=None, arousal=state.arousal),
    "priceArousal": lambda state: State(price=state.price, arousal=state.arousal),
}

def qud_prior():
    values = list(qud_fns.keys())
    ix = pyro.sample("qud", dist.Categorical(probs=torch.ones(len(values)) / len(values)))
    return values[ix]

现在我们指定话语的含义(标准数字词指称:“N”表示恰好 \(N\))和均匀的话语先验。

[6]:
def utterance_prior():
    utterances = [50, 51, 500, 501, 1000, 1001, 5000, 5001, 10000, 10001]
    ix = pyro.sample("utterance", dist.Categorical(probs=torch.ones(len(utterances)) / len(utterances)))
    return utterances[ix]

def meaning(utterance, price):
    return utterance == price

好的,让我们看看这位说话者会说什么数字词来表达不同的状态和 QUD。

[7]:
#silly plotting helper:
def plot_dist(d):
    support = d.enumerate_support()
    data = [d.log_prob(s).exp().item() for s in d.enumerate_support()]
    names = support

    ax = plt.subplot(111)
    width=0.3
    bins = list(map(lambda x: x-width/2,range(1,len(data)+1)))
    ax.bar(bins,data,width=width)
    ax.set_xticks(list(map(lambda x: x, range(1,len(data)+1))))
    ax.set_xticklabels(names,rotation=45, rotation_mode="anchor", ha="right")


# plot_dist( speaker(State(price=50, arousal=False), "arousal") )
# plot_dist( speaker(State(price=50, arousal=True), "price") )
plot_dist( speaker(State(price=50, arousal=True), "arousal") )
_images/RSA-hyperbole_13_0.png

尝试上面的不同值!说话者何时会倾向于非字面话语?

最后,语用听话者不知道 QUD 是什么,因此会联合推断 QUD 和状态。

[8]:
@Marginal
def pragmatic_listener(utterance):
    state = state_prior()
    qud = qud_prior()
    speaker_marginal = speaker(state, qud)
    pyro.sample("speaker", speaker_marginal, obs=utterance)
    return state

这位听话者如何解释说出的价格“10,000”?一方面,这是一个先验上极不可能的价格,另一方面,如果它是真的,它会伴随强烈的唤醒。总而言之,这成为一个貌似合理的 夸张 话语

[9]:
plot_dist( pragmatic_listener(10000) )
_images/RSA-hyperbole_18_0.png

语用光环

“它花费了五十美元”通常被解释为花费 大约 50 美元——可能是 51 美元;然而“它花费了五十一美元”则被解释为 51 美元,肯定不是 50 美元。这种非对称的不精确性常被称为语用光环或语用松弛。

我们可以扩展夸张模型来捕捉数字的这种额外非字面用法,方法是包含将附近数字折叠的 QUD 函数,并假设整数稍微更可能(因为它们更容易说出口)。

[10]:
#A helper to round a number to the nearest ten:
def approx(x, b=None):
    if b is None:
        b = 10.
    div = float(x)/b
    rounded = int(div) + 1 if div - float(int(div)) >= 0.5 else int(div)
    return int(b) * rounded

#The QUD functions we consider:
qud_fns = {
    "price": lambda state: State(price=state.price, arousal=None),
    "arousal": lambda state: State(price=None, arousal=state.arousal),
    "priceArousal": lambda state: State(price=state.price, arousal=state.arousal),
    "approxPrice": lambda state: State(price=approx(state.price), arousal=None),
    "approxPriceArousal": lambda state: State(price=approx(state.price), arousal=state.arousal),
}

def qud_prior():
    values = list(qud_fns.keys())
    ix = pyro.sample("qud", dist.Categorical(probs=torch.ones(len(values)) / len(values)))
    return values[ix]

def utterance_cost(numberUtt):
    preciseNumberCost = 10.
    return 0. if approx(numberUtt) == numberUtt else preciseNumberCost

def utterance_prior():
    utterances = [50, 51, 500, 501, 1000, 1001, 5000, 5001, 10000, 10001]
    utteranceLogits = -torch.tensor(list(map(utterance_cost, utterances)),
                                    dtype=torch.float64)
    ix = pyro.sample("utterance", dist.Categorical(logits=utteranceLogits))
    return utterances[ix]

RSA 说话者和听话者的定义保持不变

[11]:
@Marginal
def literal_listener(utterance):
    state=state_prior()
    pyro.factor("literal_meaning", 0. if meaning(utterance, state.price) else -999999.)
    return state

@Marginal
def speaker(state, qud):
    alpha = 1.
    qudValue = qud_fns[qud](state)
    with poutine.scale(scale=torch.tensor(alpha)):
        utterance = utterance_prior()
        literal_marginal = literal_listener(utterance)
        projected_literal = project(literal_marginal, qud)
        pyro.sample("listener", projected_literal, obs=qudValue)
    return utterance

@Marginal
def pragmatic_listener(utterance):
    state = state_prior()
    qud = qud_prior()
    speaker_marginal = speaker(state, qud)
    pyro.sample("speaker", speaker_marginal, obs=utterance)
    return state

好的,让我们看看是否获得了所需的非对称松弛(我们在这里只关心解释的价格,因此我们将唤醒边缘化掉)。

[12]:
@Marginal
def pragmatic_listener_price_marginal(utterance):
    return pyro.sample("pm", pragmatic_listener(utterance)).price

plot_dist(pragmatic_listener_price_marginal(50))
_images/RSA-hyperbole_24_0.png
[13]:
plot_dist(pragmatic_listener_price_marginal(51))
_images/RSA-hyperbole_25_0.png

反讽和更复杂的情感

在上述夸张模型中,我们假设了一种非常简单的情感模型:一个具有两个值(高唤醒和低唤醒)的单维度。实际的情感最好表示为一个二维空间,对应于效价和唤醒。Kao 和 Goodman (2015) 表明,将情感空间扩展到这两个维度会立即引入数字的一种新用法:言语反讽,其中对应于高唤醒、正效价状态的话语被用来传达高唤醒但负效价(或反之)。