Edit page

Adjectives + QUD Model - Frankie & Shane


You’re texting your friend, who’s known to drink copious amounts of coffee. They tell you that they just bought a new kettle to make pour overs. Then they tell you it was expensive. What do you infer about their communicative goal?

Because you’re texting, you don’t have access to body language or prosody cues when deciding how to interpret their message. Are they making a neutral comment about the price? Or are they implying that they’re feeling the sting of paying too much? Perhaps they’re experiencing buyer’s remorse or feeling jipped. This is something you might reason about when interpreting their use of the word “expensive.”

In the vagueness model in chapter 5, we saw how the interpretation of “expensive” is influenced by the item being referred to and prior beliefs about the price threshold for that item.

But a pragmatic speaker might intend to communicate more than simply information about the price they paid. They might be implying that they’re unhappy about paying too much. Perhaps it’s some mixture of both.

With this in mind, we were curious what would happen when we combined the QUD and valence factor in the hyperbole model (from ch. 3) with the adjectives and threshold variable in the vagueness model (from ch. 5).

The resulting model captures a pragmatic listener’s interpretations of vague adjectives by reasoning about the likely price of an item while incorporating uncertainty about a speaker’s communicative goals.


1. The priors

To begin, we turned to the hyperbole model:

// original ch. 3 hyperbole model 

var utterances = [
  50, 51,
  500, 501,
  1000, 1001,
  5000, 5001,
  10000, 10001
]
var utterancePrior = function() {
  return  uniformDraw(utterances)
}

var meaning = function(utterance, price) {
  return utterance == price
}

…and changed the possible utterances.

// in the default hyperbole model, the utterances = prices
// in this model, utterances = adjectives or silence

var utterances = ["expensive", "notExpensive"]

var utterancePrior = function() {
  return  uniformDraw(utterances)
}

Turning to the vagueness model, we took the theta prior, the meaning function, and the cost function, slightly tweaking the latter two.

var thetaPrior = function() {
  return uniformDraw(prices)
}

var meaning = function(utterance, price, theta) {
return utterance == "expensive" ? price >= theta : 
  utterance == "notExpensive" ? price <= theta :
  true
}
var cost = function(utterance) {
  return utterance== 'notExpensive'? 1 : 
  0
};

We used the price priors in the hyperbole model as our baseline:

// original ch. 3 hyperbole model

// taken from human experiments  
var prices = [
  50, 51,
  500, 501,
  1000, 1001,
  5000, 5001,
  10000, 10001
]
var pricePrior = function() {
  return categorical({
    vs: prices,
    ps: [
      0.4205, 0.3865,
      0.0533, 0.0538,
      0.0223, 0.0211,
      0.0112, 0.0111,
      0.0083, 0.0120
    ]
  })
}

…but tweaked them to fit our model.

var prices = [
  50, 
  500,
  1000,
  5000,
  10000
]

var pricePrior = function() {
  return categorical({
    vs: prices,
    ps: [
      0.8070,
      0.1070,
      0.0434,
      0.0223,
      0.0203
    ]
  })
}

We then turned to the valence priors in the hyperbole model:

// original ch. 3 hyperbole model 

// taken from human experiments  
var valencePrior = function(state) {
  var 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
  }
  var tf = flip(probs[state])
  return tf
}

…and fit them to our model.

///fold: 
var prices = [
  50, 
  500,
  1000,
  5000,
  10000
]

var pricePrior = function() {
  return categorical({
    vs: prices,
    ps: [
      0.8070,
      0.1070,
      0.0434,
      0.0223,
      0.0203
    ]
  })
}
///

var valencePrior = function(state) {
  var probs = {
    50 : 0.3173,
    500 : 0.7920,
    1000 : 0.8933,
    5000 : 0.9524,
    10000 : 0.9864
  }
  var tf = flip(probs[state])
  return tf
}

// visualize the priors

print('The joint distribution on price and valence:')
 Infer(function(){
  var aPrice = pricePrior()
  var aValence = valencePrior(aPrice)
  return {price: aPrice, valence: aValence}
})

Our model’s QUDs:

var qudFns = {
  price : function(state) {return { price: state.price } },
  valence : function(state) {return { valence: state.valence } },
  priceValence : function(state) {
    return { price: state.price, valence: state.valence }
  }
  }

var qudPrior = function() {
  categorical({
    vs: ["price", "valence", "priceValence"],
    ps: [1, 1, 1]
  })
}

2. Levels of inference

Literal listener (L0)

Interpreting L0:

///fold: 

var utterances = ["expensive", "notExpensive"]

var utterancePrior = function() {
  return  uniformDraw(utterances)
}

var thetaPrior = function() {
  return uniformDraw(prices)
}

// theta moderates interpretation of utterances
var meaning = function(utterance, price, theta) {
return utterance == "expensive" ? price >= theta : 
  utterance == "notExpensive" ? price <= theta :
  true
}

// more words = higher cost 
var cost = function(utterance) {
  return utterance== 'notExpensive'? 1 :
  0
};

var prices = [
  50, 
  500,
  1000,
  5000,
  10000
]

var pricePrior = function() {
  return categorical({
    vs: prices,
    ps: [
      0.8070,
      0.1070,
      0.0434,
      0.0223,
      0.0203
    ]
  })
}

var valencePrior = function(state) {
  var probs = {
    50 : 0.3173,
    500 : 0.7920,
    1000 : 0.8933,
    5000 : 0.9524,
    10000 : 0.9864
  }
  var tf = flip(probs[state])
  return tf
}

var qudFns = {
  price : function(state) {return { price: state.price } },
  valence : function(state) {return { valence: state.valence } },
  priceValence : function(state) {
    return { price: state.price, valence: state.valence }
  }
  }

var qudPrior = function() {
  categorical({
    vs: ["price", "valence", "priceValence"],
    ps: [1, 1, 1]
  })
}

///

var literalListener = cache(function(utterance, qud, theta) {
  return Infer({model: function(){
    var price = uniformDraw(prices)
    var valence = valencePrior(price)
    var fullState = {price, valence}
    var qudFn = qudFns[qud]
    var qudAnswer = qudFn(fullState)
    condition( meaning(utterance, price, theta) )
    return qudAnswer
  }
               })})

print('L0 predictions:')
viz(literalListener("expensive", "priceValence", 500))

Comparing L0 between models

///fold:
// Round x to nearest multiple of b (used for approximate interpretation):
var approx = function(x,b) {
  var b = 10
  return b * Math.round(x / b)
}

// Here is the code from the Kao et al. hyperbole model
// Prior probability of kettle prices (taken from human experiments)
var prices = [
  50, 51,
  500, 501,
  1000, 1001,
  5000, 5001,
  10000, 10001
]
var pricePrior = function() {
  return categorical({
    vs: prices,
    ps: [
      0.4205, 0.3865,
      0.0533, 0.0538,
      0.0223, 0.0211,
      0.0112, 0.0111,
      0.0083, 0.0120
    ]
  })
}

// Probability that given a price state, the speaker thinks it's too
// expensive (taken from human experiments)
var valencePrior = function(state) {
  var 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
  }
  var tf = flip(probs[state])
  return tf
}

// Literal interpretation "meaning" function;
// checks if uttered number reflects price state
var meaning = function(utterance, price) {
  return utterance == price
}

var qudFns = {
  price : function(state) {return { price: state.price } },
  valence : function(state) {return { valence: state.valence } },
  priceValence : function(state) {
    return { price: state.price, valence: state.valence }
  },
  approxPrice : function(state) {return { price: approx(state.price) } },
  approxPriceValence: function(state) {
    return { price: approx(state.price), valence: state.valence  }
  }
}

// Prior over QUDs
var qudPrior = function() {
  categorical({
    vs: ["price", "valence", "priceValence", "approxPrice", "approxPriceValence"],
    ps: [1, 1, 1, 1, 1]
  })
}

// Define list of possible utterances (same as price states)
var utterances = [
  50, 51,
  500, 501,
  1000, 1001,
  5000, 5001,
  10000, 10001
]
var utterancePrior = function() {
  return  uniformDraw(utterances)
}

// precise numbers can be assumed to be costlier than round numbers
var preciseNumberCost = 1
var cost = function(utterance){
  return utterance == approx(utterance) ? // if it's a round number utterance
    0 : // no cost
  preciseNumberCost // cost of precise numbers (>= 0)
}
///
var literalListener = cache(function(utterance, qud) {
  return Infer({model: function(){
    var price = uniformDraw(prices)
    var valence = valencePrior(price)
    var fullState = {price, valence}
    var qudFn = qudFns[qud]
    var qudAnswer = qudFn(fullState)
    condition( meaning(utterance, price) )
    return qudAnswer
  }
               })})

print('The original hyperbole model L0 predictions:')
viz(literalListener(500, "priceValence"))

Pragmatic speaker (S1)

Interpreting S1

///fold: 

var utterances = ["expensive", "notExpensive"]

var utterancePrior = function() {
  return  uniformDraw(utterances)
}

var thetaPrior = function() {
  return uniformDraw(prices)
}

// theta moderates interpretation of utterances
var meaning = function(utterance, price, theta) {
return utterance == "expensive" ? price >= theta : 
  utterance == "notExpensive" ? price <= theta :
  true
}

// more words = higher cost 
var cost = function(utterance) {
  return utterance== 'notExpensive'? 1 :
  0
};

var prices = [
  50, 
  500,
  1000,
  5000,
  10000
]

var pricePrior = function() {
  return categorical({
    vs: prices,
    ps: [
      0.8070,
      0.1070,
      0.0434,
      0.0223,
      0.0203
    ]
  })
}

var valencePrior = function(state) {
  var probs = {
    50 : 0.3173,
    500 : 0.7920,
    1000 : 0.8933,
    5000 : 0.9524,
    10000 : 0.9864
  }
  var tf = flip(probs[state])
  return tf
}

var qudFns = {
  price : function(state) {return { price: state.price } },
  valence : function(state) {return { valence: state.valence } },
  priceValence : function(state) {
    return { price: state.price, valence: state.valence }
  }
  }

var qudPrior = function() {
  categorical({
    vs: ["price", "valence", "priceValence"],
    ps: [1, 1, 1]
  })
}

var literalListener = cache(function(utterance, qud, theta) {
  return Infer({model: function(){
    var price = uniformDraw(prices)
    var valence = valencePrior(price)
    var fullState = {price, valence}
    var qudFn = qudFns[qud]
    var qudAnswer = qudFn(fullState)
    condition( meaning(utterance, price, theta) )
    return qudAnswer
  }
               })})

///
// speaker optimality
var alpha = 1

var speaker = cache(function(fullState, qud, theta) {
  return Infer({model: function(){
    var utterance = utterancePrior()
    var qudFn = qudFns[qud]
    var qudAnswer = qudFn(fullState)
    factor(alpha*(literalListener(utterance,qud,theta).score(qudAnswer) 
                  - cost(utterance)))
    return utterance
  }})
})

print('Our speaker predictions:')
viz(speaker({price:500, valence:true}, "valence", 50))

Pragmatic listener (L1)

Interpreting L1

///fold: 

var utterances = ["expensive", "notExpensive"]

var utterancePrior = function() {
  return  uniformDraw(utterances)
}

var thetaPrior = function() {
  return uniformDraw(prices)
}

// theta moderates interpretation of utterances
var meaning = function(utterance, price, theta) {
return utterance == "expensive" ? price >= theta : 
  utterance == "notExpensive" ? price <= theta :
  true
}

// more words = higher cost 
var cost = function(utterance) {
  return utterance== 'notExpensive'? 1 :
  0
};

var prices = [
  50, 
  500,
  1000,
  5000,
  10000
]

var pricePrior = function() {
  return categorical({
    vs: prices,
    ps: [
      0.8070,
      0.1070,
      0.0434,
      0.0223,
      0.0203
    ]
  })
}

var valencePrior = function(state) {
  var probs = {
    50 : 0.3173,
    500 : 0.7920,
    1000 : 0.8933,
    5000 : 0.9524,
    10000 : 0.9864
  }
  var tf = flip(probs[state])
  return tf
}

var qudFns = {
  price : function(state) {return { price: state.price } },
  valence : function(state) {return { valence: state.valence } },
  priceValence : function(state) {
    return { price: state.price, valence: state.valence }
  }
  }

var qudPrior = function() {
  categorical({
    vs: ["price", "valence", "priceValence"],
    ps: [1, 1, 1]
  })
}

var literalListener = cache(function(utterance, qud, theta) {
  return Infer({model: function(){
    var price = uniformDraw(prices)
    var valence = valencePrior(price)
    var fullState = {price, valence}
    var qudFn = qudFns[qud]
    var qudAnswer = qudFn(fullState)
    condition( meaning(utterance, price, theta) )
    return qudAnswer
  }
               })})


// speaker optimality
var alpha = 1

var speaker = cache(function(fullState, qud, theta) {
  return Infer({model: function(){
    var utterance = utterancePrior()
    var qudFn = qudFns[qud]
    var qudAnswer = qudFn(fullState)
    factor(alpha*(literalListener(utterance,qud,theta).score(qudAnswer) 
                  - cost(utterance)))
    return utterance
  }})
})

///
var pragmaticListener = cache(function(utterance) {
  return Infer({model: function(){
    //////// priors ////////
    var price = pricePrior()
    var valence = valencePrior(price)
    var qud = qudPrior()
    var theta = thetaPrior()
    ////////////////////////
    var fullState = {price, valence}
    observe(speaker(fullState, qud, theta), utterance)
    return {price, valence}
  }})
})

var listenerPosterior1 = pragmaticListener("expensive")
var listenerPosterior2 = pragmaticListener('notExpensive')

print('Pragmatic listener hears "expensive":')
viz(listenerPosterior1)
print('Pragmatic listener hears "not expensive":')
viz(listenerPosterior2)

Original hyperbole model predictions (for comparison):

///fold:

// CHAPTER 3 ORIGINAL MODEL 
// Round x to nearest multiple of b (used for approximate interpretation):
var approx = function(x,b) {
  var b = 10
  return b * Math.round(x / b)
}

// Here is the code from the Kao et al. hyperbole model
// Prior probability of kettle prices (taken from human experiments)
var prices = [
  50, 51,
  500, 501,
  1000, 1001,
  5000, 5001,
  10000, 10001
]
var pricePrior = function() {
  return categorical({
    vs: prices,
    ps: [
      0.4205, 0.3865,
      0.0533, 0.0538,
      0.0223, 0.0211,
      0.0112, 0.0111,
      0.0083, 0.0120
    ]
  })
}

// Probability that given a price state, the speaker thinks it's too
// expensive (taken from human experiments)
var valencePrior = function(state) {
  var 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
  }
  var tf = flip(probs[state])
  return tf
}

// Literal interpretation "meaning" function;
// checks if uttered number reflects price state
var meaning = function(utterance, price) {
  return utterance == price
}

var qudFns = {
  price : function(state) {return { price: state.price } },
  valence : function(state) {return { valence: state.valence } },
  priceValence : function(state) {
    return { price: state.price, valence: state.valence }
  },
  approxPrice : function(state) {return { price: approx(state.price) } },
  approxPriceValence: function(state) {
    return { price: approx(state.price), valence: state.valence  }
  }
}

// Prior over QUDs
var qudPrior = function() {
  categorical({
    vs: ["price", "valence", "priceValence", "approxPrice", "approxPriceValence"],
    ps: [1, 1, 1, 1, 1]
  })
}

// Define list of possible utterances (same as price states)
var utterances = [
  50, 51,
  500, 501,
  1000, 1001,
  5000, 5001,
  10000, 10001
]
var utterancePrior = function() {
  return  uniformDraw(utterances)
}

// precise numbers can be assumed to be costlier than round numbers
var preciseNumberCost = 1
var cost = function(utterance){
  return utterance == approx(utterance) ? // if it's a round number utterance
    0 : // no cost
  preciseNumberCost // cost of precise numbers (>= 0)
}

// Literal listener, infers the qud answer assuming the utterance is
// true of the state
var literalListener = cache(function(utterance, qud) {
  return Infer({model: function(){
    var price = uniformDraw(prices)
    var valence = valencePrior(price)
    var fullState = {price, valence}
    var qudFn = qudFns[qud]
    var qudAnswer = qudFn(fullState)
    condition( meaning(utterance, price) )
    return qudAnswer
  }
               })})

// set speaker optimality
var alpha = 1

// Speaker, chooses an utterance to convey a particular answer of the qud
var speaker = cache(function(fullState, qud) {
  return Infer({model: function(){
    var utterance = utterancePrior()
    var qudFn = qudFns[qud]
    var qudAnswer = qudFn(fullState)
    factor(alpha*(literalListener(utterance,qud).score(qudAnswer) 
                  - cost(utterance)))
    return utterance
  }})
})
///
var pragmaticListener = cache(function(utterance) {
  return Infer({model: function(){
    //////// priors ////////
    var price = pricePrior()
    var valence = valencePrior(price)
    var qud = qudPrior()
    ////////////////////////
    var fullState = {price, valence}
    observe(speaker(fullState, qud), utterance)
    return fullState
  }})
})
var listenerPosterior = pragmaticListener(10000)

print("Original hyperbole model predictions")
print("Pragmatic listener's joint interpretation of 'The kettle cost $10,000':")
viz(listenerPosterior)

3. Putting it all together

Full adjectives + QUD model:

// ADJECTIVES + QUD MODEL
// frankie + shane RSA project 

// code adapted from the Kao et al. hyperbole model + 
// gradable adjectives & vagueness resolution model 


var utterances = ["expensive", "notExpensive"]

var utterancePrior = function() {
  return  uniformDraw(utterances)
}

var thetaPrior = function() {
  return uniformDraw(prices)
}

// theta moderates interpretation of utterances
var meaning = function(utterance, price, theta) {
return utterance == "expensive" ? price >= theta : 
  utterance == "notExpensive" ? price <= theta :
  true
}

// more words = higher cost 
var cost = function(utterance) {
  return utterance== 'notExpensive'? 1 :
  0
};

var prices = [
  50, 
  500,
  1000,
  5000,
  10000
]

var pricePrior = function() {
  return categorical({
    vs: prices,
    ps: [
      0.8070,
      0.1070,
      0.0434,
      0.0223,
      0.0203
    ]
  })
}

var valencePrior = function(state) {
  var probs = {
    50 : 0.3173,
    500 : 0.7920,
    1000 : 0.8933,
    5000 : 0.9524,
    10000 : 0.9864
  }
  var tf = flip(probs[state])
  return tf
}

var qudFns = {
  price : function(state) {return { price: state.price } },
  valence : function(state) {return { valence: state.valence } },
  priceValence : function(state) {
    return { price: state.price, valence: state.valence }
  }
  }

var qudPrior = function() {
  categorical({
    vs: ["price", "valence", "priceValence"],
    ps: [1, 1, 1]
  })
}

var literalListener = cache(function(utterance, qud, theta) {
  return Infer({model: function(){
    var price = uniformDraw(prices)
    var valence = valencePrior(price)
    var fullState = {price, valence}
    var qudFn = qudFns[qud]
    var qudAnswer = qudFn(fullState)
    condition( meaning(utterance, price, theta) )
    return qudAnswer
  }
               })})

// speaker optimality
var alpha = 1

var speaker = cache(function(fullState, qud, theta) {
  return Infer({model: function(){
    var utterance = utterancePrior()
    var qudFn = qudFns[qud]
    var qudAnswer = qudFn(fullState)
    factor(alpha*(literalListener(utterance,qud,theta).score(qudAnswer) 
                  - cost(utterance)))
    return utterance
  }})
})

var pragmaticListener = cache(function(utterance) {
  return Infer({model: function(){
    //////// priors ////////
    var price = pricePrior()
    var valence = valencePrior(price)
    var qud = qudPrior()
    var theta = thetaPrior()
    ////////////////////////
    var fullState = {price, valence}
    observe(speaker(fullState, qud, theta), utterance)
    return {price, valence}

  }})
})

var listenerPosterior1 = pragmaticListener("expensive")
var listenerPosterior2 = pragmaticListener('notExpensive')

print('Pragmatic listener hears "expensive":')
viz(listenerPosterior1)
print('Pragmatic listener hears "not expensive":')
viz(listenerPosterior2)