Edit page

Increase in efficiency

///fold:

var getLexiconElement = function(utt, target, params) {
  // use conjunction to evaluate truth conditions
  var componentValues = map(function(word) {
    return params.lexicon[word][target]
  }, utt.split('_'));
  return product(componentValues);
};

var getUttCost = function(utt) {
  return utt.split('_').length;
};

var L0 = cache(function(utt, params) {
  return Mixture({
    dists: [
      Categorical({vs: params.context}),
      Infer({method: "enumerate"}, function() {
        var obj = uniformDraw(params.context);
        factor(Math.log(getLexiconElement(utt, obj, params)));
        return obj;
      })],
    ps: [params.guessingEpsilon, 1 - params.guessingEpsilon]
  });
}, 1000);

var S1 = cache(function(obj, params) {
  return Mixture({
    dists: [
      Categorical({vs: params.utterances}),
      Infer({method: "enumerate"}, function() {
        var utt = uniformDraw(params.utterances);
        var utility = ((1-params.costWeight) * L0(utt, params).score(obj)
                       - params.costWeight * getUttCost(utt));
        factor(params.speakerAlpha * utility);
        return utt;
      })],
    ps: [params.guessingEpsilon, 1 - params.guessingEpsilon]
  })
}, 1000)

var L1 = cache(function(utt, params) {
  return Mixture({
    dists: [
      Categorical({vs: params.context}),
      Infer({method: "enumerate"}, function() {
        var obj = uniformDraw(params.context);
        factor(params.listenerAlpha * S1(obj, params).score(utt));
        return obj;
      })],
    ps: [params.guessingEpsilon, 1 - params.guessingEpsilon]
  })
}, 1000)

var S_uncertain = function(object, posterior) {
  return Infer({method: "enumerate"}, function() {
    var utt = uniformDraw(params.utterances);
    var inf = expectation(posterior, function(lexicon) {
      var config = extend(params, {lexicon});
      return L1(utt, config).score(object);
    });
    var utility = ((1-params.costWeight) * inf
                   - params.costWeight * getUttCost(utt));

    factor(params.speakerAlpha * utility);
    return utt;
  });
};

var L_uncertain = function(utt, posterior) {
  return Infer({method: "enumerate"}, function() {
    var object = uniformDraw(params.context);
    var utility = expectation(posterior, function(lexicon) {
      var config = extend(params, {lexicon})
      return S1(object, config).score(utt);
    });
    factor(params.listenerAlpha * utility);
    return object;
  });
};

///

var unconstrainedUtterances = ['word1', 'word2', 'word3', 'word4'];
var derivedUtterances = ['word1_word2', 'word3_word4']; 
var utterances = unconstrainedUtterances.concat(derivedUtterances);
var objects = ['bluecircle', 'redsquare'];
var meanings = ['bluecircle', 'redsquare'];
var numMeanings = meanings.length;


var params = {
  speakerAlpha : 16,
  listenerAlpha: 16,
  discountFactor: 0.8,
  costWeight: 0.2,
  guessingEpsilon: 0.01,
  numTrials: 10,
  context: objects,
  utterances: utterances,
  objects: objects,
  inferOptions: {method: 'enumerate'}
};

var sampleLexicon = function(obs) {
  return _.zipObject(unconstrainedUtterances, map(function(utt) {
    var blueProb = beta(obs[utt]['bluecircle'], obs[utt]['redsquare']);
    return {'bluecircle' : blueProb, 'redsquare' : 1 - blueProb};
  }, unconstrainedUtterances));
};

var iterate = function(dataSoFar) {
  globalStore.trialNum += 1;
  var repNum =  Math.floor(globalStore.trialNum / 2);
  var currTrial = {
    trialNum: globalStore.trialNum,
    intendedName: (globalStore.trialNum % 2) == 0 ? 'bluecircle' : 'redsquare',
    context : objects,
    speakerID: (repNum % 2) == 0 ? 1 : 2,
    listenerID: (repNum % 2) == 0 ? 2 : 1
  };

  // sample from beta using conjugate prior
  var speakerPosterior = Infer({method: 'forward', samples: 1000}, function() {
    return sampleLexicon(dataSoFar[currTrial.speakerID]);
  });
  var listenerPosterior = Infer({method: 'forward', samples: 1000}, function() {
    return sampleLexicon(dataSoFar[currTrial.listenerID]);
  });
  
  // sample from speaker and listener agents with this posterior
  var speakerOutput = S_uncertain(currTrial.intendedName, speakerPosterior);
  var speakerChoice =  sample(speakerOutput);
  var listenerOutput = L_uncertain(speakerChoice, listenerPosterior);
  var listenerChoice = sample(listenerOutput);
  console.log('on trial', globalStore.trialNum, 'speaker says', speakerChoice, 'and listener responds', listenerChoice)
  //console.log(speakerOutput)
  
  // record outcomes & move to next trial
  if(currTrial.trialNum < params.numTrials) {
    var newData = _.zipObject([currTrial.speakerID, currTrial.listenerID], map(function(id) {
      return _.zipObject(['word1', 'word2', 'word3', 'word4'], map(function(word) {
        return _.zipObject(['bluecircle', 'redsquare'], map(function(obj) {
          var objMatch = id == currTrial.speakerID ? listenerChoice : currTrial.intendedName;
          var uttMatch = _.includes(speakerChoice.split('_'), word)
          return (obj == objMatch && uttMatch) ? dataSoFar[id][word][obj] + 1 : dataSoFar[id][word][obj];
        }, ['bluecircle', 'redsquare']));
      }, ['word1', 'word2', 'word3', 'word4']));
    }, [currTrial.speakerID, currTrial.listenerID]));
    var longUtteranceProb = Math.exp(speakerOutput.score('word1_word2')) + Math.exp(speakerOutput.score('word3_word4'))
    globalStore.longUtteranceProbs.push(longUtteranceProb)
    console.log("P('long utterance') =", longUtteranceProb)
    //console.log('new counts are', JSON.stringify(newData, null, 1))
    iterate(newData);
  }
};

globalStore.trialNum = 0;
globalStore.longUtteranceProbs = [];
// initialize with pseudocounts
iterate({
  1: {word1 : {'bluecircle' : 1, 'redsquare' : 0.5},
      word2: {'bluecircle' : 1, 'redsquare' : 0.5},
      word3: {'bluecircle' : 0.5, 'redsquare' : 1},
      word4: {'bluecircle' : 0.5, 'redsquare' : 1}},
  2: {word1 : {'bluecircle' : 1, 'redsquare' : 0.5},
      word2 : {'bluecircle' : 1, 'redsquare' : 0.5},
      word3 : {'bluecircle' : 0.5, 'redsquare' : 1},
      word4 : {'bluecircle' : 0.5, 'redsquare' : 1}}
});
viz.line(_.range(params.numTrials), globalStore.longUtteranceProbs)

Old versions

Why do Americans drive on the right side of the road while the British drive on the left? Why do English speakers use ‘cat’ to refer to a pet that goes ‘meow’ while the French use the word ‘chat’? These conventions or norms govern much of our everyday behavior. While there is a substantial literature using simple agent-based models showing how these arbitrary but stable patterns can emerge in large populations, there has been comparatively less work on the cognitive underpinnings of conventions.

Here, we focus on a classic case of conventionalization of language in a reference game (Clark & Wilkes-Gibbs, 1986). In the simplest version of this task, two players are presented with an array of objects constructed from tangrams which are not easily describable. One of them is designated as the ‘target’ for the speaker, and their goal is to produce an utterance that will allow the listener to distinguish the correct object from their array.

Our model of conventionalization combines two innovations in modeling language understanding: lexical uncertainty and a noisy communication channel. The former introduces uncertainty over the exact meanings of words in the lexicon; the latter introduces a perceptual noise model by which utterances can be corrupted during transmission. We gradually build up to this combined model by tackling sub-problems.

Part 1: arbitrary mapping

We begin by implementing the simplest lexical uncertainty model, used in Bergen, Levy, & Goodman (2016) to account for M-implicatures. In the simplified case we consider, there are just two labels and two tangrams. How does the pair converge on a mapping?

This is the simplest demonstration of conventions; even though neither party knows the meaning of a label at the outset, a random choice is taken to be evidence for a particular lexicon and it becomes the base for successful communication.

///fold:
var getTrajectories = function(data) {
  var keys = _.keys(data[0]);
  return reduce(function(key, memo) {
    var timeBasedKeys = map(function(i) {return key + "." + i}, _.range(data.length));
    var vals = _.map(data, key);
    return extend(_.zipObject(timeBasedKeys, vals), memo)
  }, [], keys)
};
///

// set up speaker optimality & number of iterations
var params = {
  alpha : 5,
  beta : 1,
  numSteps : 6
};

// possible states of the world
var states = ['t1', 't2'];
var statePrior =  Categorical({vs: states, ps: [1/2, 1/2]});

// possible utterances
var utterances = ['label1', 'label2'];
var utterancePrior = Categorical({vs: utterances, ps: [1/2, 1/2]});

// takes a sample from a (discretized) dirichlet distribution for each word,
// representing the extent to which that word describes each object
var lexiconPrior = Infer({method: 'enumerate'}, function(){
  var meanings = map(function(utt) {
    var t1Prob = uniformDraw([0.01, .25, .5, .75, 0.99]);
    return {'t1' : t1Prob, 't2' : 1-t1Prob};
  }, utterances);
  return _.zipObject(utterances, meanings);
});

// length-based cost (although they're all the same length here)
var uttCost = function(utt) {
  return utt.split(' ').length;
};

// literal listener (using real-valued lexicon)
var L0 = cache(function(utt, lexicon) {
  return Infer({method:"enumerate"}, function(){
    var state = sample(statePrior);
    factor(Math.log(lexicon[utt][state]));
    return state;
  });
});

// pragmatic speaker 
var S1 = cache(function(state, lexicon) {
  return Infer({method:"enumerate"}, function(){
    var utt = sample(utterancePrior);
    factor(params.alpha * (L0(utt, lexicon).score(state))
           - params.beta * uttCost(utt));
    return utt;
  });
});

// conventional listener
var L1 = cache(function(utt, lexicon) {
  return Infer({method:"enumerate"}, function(){
    var state = sample(statePrior);
    observe(S1(state, lexicon), utt);
    return state;
  });
});

// compute lexicon posterior, taking into account some previous observations
// speakers do this by assuming data came from knowledgable listener, and vice versa
var lexiconPosterior = cache(function(originAgent, data) {
  return Infer({method: 'enumerate'}, function() {
    var lexicon = sample(lexiconPrior);
    mapData({data: data}, function(datum){
      if(originAgent === 'L') 
        observe(S1(datum.response, lexicon), datum.utt);
      else if(originAgent === 'S') 
        observe(L1(datum.utt, lexicon), datum.response);
    });
    return lexicon;
  });
});

// conventional listener (L1, marginalizing over lexicons)
var L = cache(function(utt, data) {
  return Infer({method:"enumerate"}, function(){
    var lexicon = sample(lexiconPosterior('L', data));
    var state = sample(L1(utt, lexicon));
    return state;
  });
});

// conventional speaker (S1, reasoning about expected L1 behavior across lexicons)
var S = cache(function(state, data) {
  return Infer({method:"enumerate"}, function(){
    var utt = sample(utterancePrior);
    var listener = Infer({method: 'enumerate'}, function() {
      var lexicon = sample(lexiconPosterior('S', data));
      return sample(L1(utt, lexicon))
    });
    factor(params.alpha * listener.score(state)
           - params.beta * uttCost(utt));
    return utt;
  });
});

var model = function() {
  var step = function(data) {
    if(data.length > params.numSteps) return getTrajectories(data);
    var state = sample(statePrior);
    var utt = sample(S(state, data));
    var response = sample(L(utt, data));
    var newDatum = {utt, response, intended: state, acc: state == response};
    return step(data.concat(newDatum));
  };
  step([]);
};

model();

The listener is initially uncertain about which tangram ‘label1’ is referring to, but after observing evidence in which a speaker produced ‘label1’ for ‘1’, they update their beliefs about the likely lexicon and subsequently become more likely to interpret ‘label1’ as referring to ‘t1.’ Note that it is also more likely to interpret ‘label2’ as referring to ‘t2’, even though it has not observed any explicit usage of this label. This latter effect is a standard consequence of pragmatic reasoning. Uncomment for additional explorations of this model, and examine what the lexicon posterior looks like after seeing different data.

Part 2: Dropping redundant information (conjunctions and modifiers)

In our data, several of the most frequently dropped utterances between the first round and the last round are modifying clauses. We account for this in an RSA model by enriching the lexicon with compositional semantics. In addition to bare labels, the speaker can form conjunctions of labels which have a meaning derived from the individual label meanings in the lexicon. We also introduce some initial bias for some labels toward one tangram and other labels toward the other tangram. The intended behavior is that speakers are initially more likely to form conjunctions of the two labels with a bias toward the target tangram, in effect hedging against the possibility that they’re in a world where one or the other of those labels doesn’t turn out to mean the target tangram. As uncertainty over the lexicon decreases over multiple rounds, however, this information will become redundant and the benefit of hedging will not be worth the additional utterance cost.

///fold:
var initList = function(n, val) {
  return repeat(n, function() {return val})
}

var uniformPs = function(vs) {
  return initList(vs.length, 1/vs.length)
}

var getTrajectories = function(data) {
  var keys = _.keys(data[0]);
  return reduce(function(key, memo) {
    var timeBasedKeys = map(function(i) {return key + "." + i}, _.range(data.length));
    var vals = _.map(data, key);
    return extend(_.zipObject(timeBasedKeys, vals), memo)
  }, [], keys)
};
///

// speaker optimality
var params = {
  alpha : 5,
  beta : 1,
  numSteps : 6
};

// possible states of the world
var states = [{type: 0, color: 0}, 
              {type: 1, color: 1}];
var statePrior =  Categorical({vs: states, ps: [1/2, 1/2]});

// possible base utterances, and possible conjunctions
var unconstrainedUtterances = ['type_a', 'type_b', 'color_a','color_b'];
var derivedUtterances = ['type_a type_b', 'type_a color_a','type_a color_b',
                         'type_b color_a','type_b color_b','color_a color_b'];
var utterances = unconstrainedUtterances.concat(derivedUtterances);
var utterancePrior = Categorical({vs: utterances, ps: uniformPs(utterances)});

// takes a sample from a (biased & discretized) dirichlet distribution for each word,
// representing the extent to which that word describes each object
var lexiconPrior = Infer({method: 'enumerate'}, function(){
  var meanings = map(function(utt) {
    var aBias = utt.split('_')[1] === 'a'
    var ps = aBias ? [.3,.25,.2,.15,.1] : [.1,.15,.2,.25,.3];
    return categorical({vs: [0.01, 0.25, .5, .75, .99], ps})
  }, unconstrainedUtterances);
  return _.zipObject(unconstrainedUtterances, meanings);
});

// length-based cost
var uttCost = function(utt) {
  return utt.split(' ').length;
};

// Looks up the meaning of an utterance in a lexicon object
var uttFitness = cache(function(utt, state, lexicon) {
  return Math.log(reduce(function(subUtt, memo) {
    var relevantProperty = (subUtt.split('_')[0] === 'type' ?
                            state.type : state.color);
    var lexiconProb = relevantProperty ? lexicon[subUtt] : 1- lexicon[subUtt]
    return lexiconProb * memo;
  }, 1, utt.split(' ')));
});


// literal listener
var L0 = cache(function(utt, lexicon) {
  return Infer({method:"enumerate"}, function(){
    var state = sample(statePrior);
    factor(uttFitness(utt, state, lexicon));
    return state;
  });
});

// pragmatic speaker
var S1 = cache(function(state, lexicon) {
  return Infer({method:"enumerate"}, function(){
    var utt = sample(utterancePrior);
    factor(params.alpha * L0(utt, lexicon).score(state)
           - params.beta * uttCost(utt));
    return utt;
  });
});

// conventional listener
var L1 = cache(function(utt, lexicon) {
  return Infer({method:"enumerate"}, function(){
    var state = sample(statePrior);
    observe(S1(state, lexicon), utt);
    return state;
  });
});

var lexiconPosterior = cache(function(originAgent, data) {
  return Infer({method: 'enumerate'}, function() {
    var lexicon = sample(lexiconPrior);
    mapData({data: data}, function(datum){
      if(originAgent === 'L') {
        observe(S1(datum.response, lexicon), datum.utt);
      } else if(originAgent === 'S') {
        observe(L1(datum.utt, lexicon), datum.response);
      }
    });
    return lexicon;
  });
});

// conventional listener (L1, marginalizing over lexicons)
var L = cache(function(utt, data) {
  return Infer({method:"enumerate"}, function(){
    var lexicon = sample(lexiconPosterior('L', data));
    var state = sample(L1(utt, lexicon));
    return state;
  });
});

// conventional speaker (S1, reasoning about expected listener across lexicons)
var S = cache(function(state, data) {
  return Infer({method:"enumerate"}, function(){
    var utt = sample(utterancePrior);
    var listener = Infer({method: 'enumerate'}, function() {
      var lexicon = sample(lexiconPosterior('S', data));
      return sample(L1(utt, lexicon));
    });
    factor(params.alpha * listener.score(state)
           - params.beta * uttCost(utt));
    return utt;
  });
});

var model = function() {
  var step = function(data) {
    if(data.length > params.numSteps) return getTrajectories(data);
    var state = states[0]
    var utt = sample(S(state, data));
    var response = sample(L(utt, data));
    var newDatum = {utt, response, intended: state, acc: state == response};
    return step(data.concat(newDatum));
  };
  step([]);
};

model();

We see that there’s an initial preference for the longer conjunction of “type_a” and “color_a” despite the utterance cost because

(1) there’s an initial bias in the lexicon prior for ‘a’ utterances to correspond to properties with the value 0 (thus why neither of the ‘b’ utterances are assigned any probability)

(2) the conjunction hedges against unlikely but possible lexicons where one or the other utterance actually corresponds to a property with value 1.

After observing an example of this conjunction referring to the first state, the conjunction actually becomes more preferred by the speaker, as it increases the probability of utterances where both type_a and color_a mean the first state. The evidence is indeterminate about the separate meanings of type_a and color_a. Because of cost considerations, however, the shorter utterances are still assigned some probability. Once the speaker produces one or the other short utterance by chance, then, it breaks the symmetry and this shorter utterance becomes the most probable in future rounds.