Greedy decoding picks the best token at each step and can never go back. Beam search keeps the `beam_width` best partial sequences at each step and picks the best finished one at the end. Translation and summarization models decode with beam search. ## Task Write `beam_search(step_fn, bos_id, eos_id, beam_width, max_len, length_penalty=0.0)`. A hypothesis is a tuple of token ids and a raw score. The search starts with one alive hypothesis: the tuple `(bos_id,)` with score 0. The raw score of a hypothesis is the sum of $\log p(t_i \mid t_{= 1` | | `max_len` | int | scalar | the largest number of generated tokens, `>= 1` | | `length_penalty` | float | scalar | the exponent $\alpha$ of the length in the final score | - Returns a list of `(tokens, score)` pairs. `tokens` is a tuple of ints that starts with `bos_id`. `score` is a float. ## Constraints - `2 <= V <= 50` and `max_len <= 20`. - Never call `step_fn` on a prefix that ends with `eos_id`. - `step_fn` can return a new array on each call. Do not change the arrays that it returns. - Use Python and numpy only. ## Examples The toy model has $V = 4$, BOS 4 and EOS 0. It gives these probabilities for the next token: ```python TABLE = { (4,): [0.05, 0.5, 0.4, 0.05], (4, 1): [0.25, 0.25, 0.25, 0.25], (4, 2): [0.9, 0.04, 0.03, 0.03], } def toy(prefix): return np.log(np.array(TABLE.get(tuple(prefix), [0.6, 0.2, 0.1, 0.1]))) ``` ```python beam_search(toy, 4, 0, 1, 3) ``` ```text [((4, 1, 0), -2.0794415416798357)] ``` ```python beam_search(toy, 4, 0, 2, 3) ``` ```text [((4, 2, 0), -1.0216512475319812), ((4, 1, 0), -2.0794415416798357)] ``` ```python beam_search(toy, 4, 0, 2, 3, length_penalty=1.0) ``` ```text [((4, 2, 0), -0.5108256237659906), ((4, 1, 0), -1.0397207708399179)] ```