There are multiple methods for sampling utterances from a trained language model (LM). What you're doing is certainly a valid approach, and fairly modern. Having, I'll just outline a few more approaches here that people have found empirically useful. These are commonly used in large LM such as those found in GPT-2 or RoBERTa.
As a formalism, we assume a max-probability decoding objective. There isn't usually a single best approach; good approaches are heavily task-dependent as well. However, each of these techniques targets failure modes in neural text generation, which can serve as a good heuristic for your own experimentation.
The Old.
Greedy decoding. At each time step, select the token with the highest probability. Fast, but trivially leads to non-diverse (and often suboptimal!) responses.
Beam search. At each time step, take the top $k$ generated utterances so far; use those as the starting point for search in the next iteration. Addresses the limitations of greedy decoding without blowing up the search space. Can lead to pathologically repetitive/non-diverse responses.
Pure sampling. This is what you're doing -- take a random choice weighted by the probability density generated at each time step. This actually results (empirically) in generated text with a similar token distribution as human-generated text (+ slightly higher perplexity) -- however, there is no guarantee of syntactic/grammatical coherence.
Newer approaches.
Softmax with temperature. Not a decoding algorithm, but a common trick. This is an extension to the above approaches that redistribute the probability mass used to sample tokens; @Tim has covered it extensively already on this thread.
Top-$k$ sampling. Builds off of weighted-choice sampling by only retaining $k$ words with the highest probability mass at each timestep and then sampling within that distribution. Lower $k$ leads to more generic output; higher $k$ leads to more diverse output. Pure sampling can be thought of as top-$V$ sampling ($V$ = size of vocabulary); greedy decoding is top-$1$ sampling.
Nucleus sampling. Based on a parameter $0 <= p <= 1$, aggregates the smallest set of words that have summed probability mass $p$. Can be thought of as a variation of top-k sampling with dynamic $k$.
Combinations of these are also valid -- top-k sampling is sometimes used with nucleus sampling, for example. You might also notice that softmax with temperature and nucleus sampling are both methods of redistributing the probability mass over the distribution of tokens; as a toy example, temperature decreases and lower p both have the effect of "sharpening" the distribution by dampening (or removing!) the likelihood of sampling rarer tokens.
Other variables to tune:
Length penalty. You can weight the probability scores of a sentence by a function of the length; without this weighting, max-probability decoding methods will favor shorter sentences (joint log-likelihood monotonically decreases as you add more tokens). This is a common scoring function:
$$\text{length_penalty}(Y) = \frac{(5 + |Y|)^\alpha}{(5 + 1)^\alpha}$$
where $0 < \alpha < 1$, with $\alpha = 0$ reverting to vanilla beam search. "Famously" used in Google Translate.
Repetition penalty. Lower the chance of repetition by discounting the scores of previously-generated tokens. Proposed here; a little finnicky empirically.
Min/max length. This is a quick-and-dirty way to ensure that your model generates text of an appropriate length; I used this personally to tune a summarization model.
Further reading.
- Beam search Wikipedia page.
- Holtzman, A. et. al. The curious case of neural text degeneration. (HIGHLY recommended)
- Stewart, Russell. Maximum Likelihood Decoding with RNNs - the
good, the bad, and the ugly
- See, Abigail. Natural Language Generation (CS224N Lecture 15,
Stanford)
- Von Platen, Patrick. How to generate text: using
different decoding methods for language generation with
Transformers