Team Ai
Apppublic

jampekka/slms_tutorial

sourceHugging Faceagpl-3.0updated 2y agoView on Hugging Face
0likes
basics.py447 linesDownload Raw Back to root
1import marimo2 3__generated_with = "0.6.8"4app = marimo.App(app_title="SLMs basics")5 6 7@app.cell8def __():9    import marimo as mo10    from pprint import pformat11    from collections import defaultdict12    import utils as U13 14    U.init_output15    return U, defaultdict, mo, pformat16 17 18@app.cell19def __(mo):20    mo.md(21        r"""22        # Small language models23 24        ## Happy birthday25        ---26        To get started, we analyze lyrics of perhaps the most popular song in the world.27        You may be familiar with the lyrics:28        """29    )30    return31 32 33@app.cell34def __():35    corpus_text = """36     Happy birthday to you 37     Happy birthday to you 38     Happy birthday dear Dave 39     Happy birthday to you 40    """41    corpus_text42    return corpus_text,43 44 45@app.cell46def __(mo):47    mo.md(48        r"""49        To work with text, we usually want to split it to some shorter pieces, such50        as words. In general, such pieces are called **tokens**, but we'll start with just51        words. Our lyrics split into words become:52        """53    )54    return55 56 57@app.cell58def __(U, corpus_text):59    corpus_words = corpus_text.split(' ')60    U.python_out(corpus_words)61    return corpus_words,62 63 64@app.cell65def __(mo):66    mo.md(67        rf"""68        (The here `'\n'` means that we start a new line. While not really a word, we treat it as such for now.)69 70        We can also build our **vocabulary**, which is just all individual words that is in our lyrics:71        """72    )73    return74 75 76@app.cell77def __(U, corpus_words):78    # Using dict instead of set to keep the order79    _vocabulary = {w: None for w in corpus_words}.keys()80    U.python_out(list(_vocabulary))81    return82 83 84@app.cell85def __(mo):86    mo.md(87        r"""88        The currently popular large language models (LLMs) -- such as GPT, Llama and Mistral -- are based on predicting what token becomes after89        some number of tokens.90 91        In our case, for example, the word `'Happy'` is followerd by the word `'birthday'` and the92        word `'birthday'` is followed by the word `'to'`.93 94        In fact, to make an extremely simple language model, we can just list what words are followed by each95        word. For our lyrics this becomes:96        """97    )98    return99 100 101@app.cell102def __(U, corpus_words):103    next_words = {}104    for i in range(len(corpus_words)-1):105        word = corpus_words[i]106        next_word = corpus_words[i+1]107        if word not in next_words:108            next_words[word] = []109        next_words[word].append(next_word)110    U.python_out(next_words)111    return i, next_word, next_words, word112 113 114@app.cell115def __(mo):116    mo.md(r"Or as a visual graph format:")117    return118 119 120@app.cell121def __(U, next_words):122    U.plot_follower_graph(next_words)123    return124 125 126@app.cell127def __(mo):128    mo.md(129        r"""130        We can see that after a new line `'\n'` we always get the word `'Happy'`, and `'Happy'` is always followed by131        `'birthday'`. Somewhat more interestingly, the word `'birthday'` was followed three times by `'to'` but also132        once by `'dear'`.133 134        With this model, we are ready to generate new lyrics! Select the next word from the dropdown135        to add it into the lyrics.136        """137    )138    return139 140 141@app.cell142def __(corpus_words, mo):143    initial_lyrics_birthday = tuple(corpus_words[:2])144    get_lyrics_birthday, set_lyrics_birthday = mo.state(initial_lyrics_birthday, allow_self_loops=True)145    return get_lyrics_birthday, initial_lyrics_birthday, set_lyrics_birthday146 147 148@app.cell149def __(mo):150    def dropdown_generate(next_words, lyrics_state, initial_lyrics):151        get_lyrics, set_lyrics = lyrics_state152        lyrics = get_lyrics()153        options = set(next_words[lyrics[-1]])154        def update(value):155            new_lyrics = (*get_lyrics(), value)156            set_lyrics((*get_lyrics(), value))157 158        lyrics_text = ' ' + ' '.join(get_lyrics())159        optvals = {repr(o): o for o in options}160        dropdown = mo.ui.dropdown(options=optvals, on_change=update)161        reset = mo.ui.button(162            label="Reset lyrics",163            on_change=lambda *args: set_lyrics(initial_lyrics)164        )165 166        #lyrics_el = mo.Html(f"<pre>{lyrics_text} {dropdown}</pre>")167        return dropdown, reset168    return dropdown_generate,169 170 171@app.cell172def __(173    dropdown_generate,174    get_lyrics_birthday,175    initial_lyrics_birthday,176    mo,177    next_words,178    set_lyrics_birthday,179):180    # These have to be globals for the events to be triggered.181    # Marimo has some ways to go to enable modular code182    dropdown_birthday, reset_birthday = dropdown_generate(next_words, (get_lyrics_birthday, set_lyrics_birthday), initial_lyrics_birthday)183    _text = ' '.join(get_lyrics_birthday())184    _lyrics_el = mo.Html(f"<pre>{_text} {dropdown_birthday}</pre>")185 186    mo.hstack([_lyrics_el, reset_birthday])187    return dropdown_birthday, reset_birthday188 189 190@app.cell191def __(mo):192    mo.md(193        rf"""194        ## Blowin' in the wind195        ---196 197        The previous looked only one word at the time. However, we can easily use more than one word to predict the next one. How many words (or tokens) we use to predict the next one, is known as the **context length**. The context length of the previous example was 1.198 199        With the very simple lyrics context length more than 1 does not make much sense, so let's pick something a bit more complicated:200        """201    )202    return203 204 205@app.cell206def __():207    blowin_text = """208     Yes, and how many roads must a man walk down, before you call him a man? 209     And how many seas must a white dove sail, before she sleeps in the sand? 210     Yes, and how many times must the cannonballs fly, before they're forever banned? 211 212     Yes, and how many years must a mountain exist, before it is washed to the sea? 213     And how many years can some people exist, before they're allowed to be free? 214     Yes, and how many times can a man turn his head, and pretend that he just doesn't see? 215 216     Yes, and how many times must a man look up, before he can see the sky? 217     And how many ears must one man have, before he can hear people cry? 218     Yes, and how many deaths will it take 'til he knows, that too many people have died? 219    """220    blowin_text221    return blowin_text,222 223 224@app.cell225def __(mo):226    mo.md(227        rf"""228        You may recognize the lyrics. They're the verses of the Bob Dylan's song [Blowin' in the Wind](https://www.youtube.com/watch?v=MMFj8uDubsE).229 230        We proceed like before, first splitting the lyrics into words:231        """232    )233    return234 235 236@app.cell237def __(U, blowin_text):238    blowin_words = blowin_text.split(' ')239    U.python_out(blowin_words)240    return blowin_words,241 242 243@app.cell244def __(mo):245    mo.md(246        rf"""247        Note that we now have punctuation included in the ''words'', like the comma in `'Yes,'` the question mark in `'man?'`. We also treat two newlines `'\n\n'` as one ''word''. This comes handy, as it separates the verses.248 249        We now have quite a bit larger vocabulary:250        """251    )252    return253 254 255@app.cell256def __(U, blowin_words):257    U.python_out(list(U.corpus_to_vocabulary(blowin_words)))258    return259 260 261@app.cell262def __(mo):263    mo.md(264        rf"""265        ### More context266        ---267        We build a simple language model again with these lyrics. These simple models are usually called ''Markov Chain text generators''. This is a bit misleading, as even the next-token-predicting LLMs are Markov chains. We won't discuss what Markov chains really are and what makes a model such, but Wikipedia has a [rather good article](https://en.wikipedia.org/wiki/Markov_chain) of these if you're interested. 268 269        Previously in the ''Happy Birthday'' example the model looked only one word at the time. However, we can easily use more than one word to predict the next one. How many words (or tokens) we use to predict the next one, is known as the **context length**. The context length of the previous example was 1.270 271        For lyrics as simple as in ''Happy Birthday'' using a context length more than 1 didn't make much sense. However, with the more complicated lyrics we can see how the model behavior changes with different context lengths.272 273        You can select the context length with the slider and see how the model changes.274        """275    )276    return277 278 279@app.cell280def __(context_length_slider, mo):281    mo.md(f"The context length is {context_length_slider.value}")282    return283 284 285@app.cell286def __(mo):287    # TODO: Display context length value288    context_length_slider = mo.ui.slider(start=1, stop=8, full_width=True)289    context_length_slider290    return context_length_slider,291 292 293@app.cell294def __(blowin_words, context_length_slider, defaultdict):295    #blowin_context_length = 2296    blowin_context_length = context_length_slider.value297    # Doing this more succintly now298    def get_ngrams(tokens, n):299        for i in range(len(tokens) - n + 1):300            yield tokens[i:i+n]301 302    blowin_next_words1 = defaultdict(list) 303    for *_context, _next_word in get_ngrams(blowin_words, blowin_context_length + 1):304        blowin_next_words1[tuple(_context)].append(_next_word)305 306    #python_out(dict(blowin_next_words1))307    return blowin_context_length, blowin_next_words1, get_ngrams308 309 310@app.cell311def __():312    #plot_follower_graph(blowin_next_words1)313    return314 315 316@app.cell317def __(mo):318    mo.md(rf"We can now generate some lyrics with the model. Here's some machine generated ones, you can do your own below.")319    return320 321 322@app.cell323def __():324    import random325    random.seed(3)326    return random,327 328 329@app.cell330def __(mo):331    regen_blowin1_btn = mo.ui.button(label="Generate new verse")332    regen_blowin1_btn333    return regen_blowin1_btn,334 335 336@app.cell337def genblow1_1(U, blowin_next_words1, random, regen_blowin1_btn):338    # TODO: Keep the seed constant across generations339 340    regen_blowin1_btn341 342    def _generate(next_words):343        context = next(iter(next_words.keys()))344        yield from context345 346        while True:347            choices = next_words[context]348            if not choices: return349            next_word = random.choice(choices)350            if next_word == '\n\n': return351            yield next_word352            context = (*context[1:], next_word)353 354    _generated = list(_generate(blowin_next_words1))355    U.pre_box(' '.join(_generated))356    return357 358 359@app.cell360def __(U, blowin_next_words1, mo):361    mo.accordion({362        "Next word table": U.python_out(dict(blowin_next_words1)),363        "Next word graph": U.plot_follower_graph(blowin_next_words1)364    })365    return366 367 368@app.cell369def __(mo):370    mo.md(371        rf"""372        With a short context length the lyrics dont make much sense. With a longer context length it starts to just copy the originals. Try to find a context length that seems to make a nice tradeoff between these. As a hint, you can get something quite silly with some context lengths.373 374        Try to be such a language model yourself! This time the generated lyrics are hidden. Don't peek at them before you're done, and pretend you don't remember what you picked before!375        """376    )377    return378 379 380@app.cell381def __(blowin_context_length, blowin_words, mo):382    initial_lyrics_blowin = blowin_words[:blowin_context_length + 1]383    get_lyrics_blowin1, set_lyrics_blowin1 = mo.state(initial_lyrics_blowin, allow_self_loops=True)384    return get_lyrics_blowin1, initial_lyrics_blowin, set_lyrics_blowin1385 386 387@app.cell388def __(389    blowin_context_length,390    blowin_next_words1,391    get_lyrics_blowin1,392    initial_lyrics_blowin,393    mo,394    set_lyrics_blowin1,395):396    def dropdown_generate_blowin(next_words, lyrics_state, initial_lyrics):397        get_lyrics, set_lyrics = lyrics_state398        lyrics = get_lyrics()399        context = tuple(lyrics[-blowin_context_length:])400        options = set(next_words[context])401        def update(value):402            new_lyrics = (*get_lyrics(), value)403            set_lyrics((*get_lyrics(), value))404 405        lyrics_text = ' ' + ' '.join(get_lyrics())406        optvals = {repr(o): o for o in options}407        dropdown = mo.ui.dropdown(options=optvals, on_change=update)408        reset = mo.ui.button(409            label="Reset lyrics",410            on_change=lambda *args: set_lyrics(initial_lyrics)411        )412 413        #lyrics_el = mo.Html(f"<pre>{lyrics_text} {dropdown}</pre>")414        return dropdown, reset415 416    dropdown_blowin1, reset_blowin1 = dropdown_generate_blowin(blowin_next_words1, (get_lyrics_blowin1, set_lyrics_blowin1), initial_lyrics_blowin)417    _ctx = ', '.join(map(repr, get_lyrics_blowin1()[-blowin_context_length:]))418    _lyrics_el = mo.Html(f"<pre>{_ctx} {dropdown_blowin1}</pre>")419 420    _lyrics_el421    return dropdown_blowin1, dropdown_generate_blowin, reset_blowin1422 423 424@app.cell425def __(get_lyrics_blowin1, mo, reset_blowin1):426    _lyrics = ' '.join(get_lyrics_blowin1())427    _spoiler = mo.accordion({'Your generated lyrics. SPOILER!': mo.Html(f"<pre>{_lyrics}</pre>")})428    mo.vstack([_spoiler, reset_blowin1])429    return430 431 432@app.cell433def __(mo):434    mo.md(435        rf"""436        ---437        In the next notebook, we'll take a closer look at **tokenization**, i.e. how we split the text for processing.438 439        [Continue to Tokenization >](?file=tokenization.py)440        """441    )442    return443 444 445if __name__ == "__main__":446    app.run()447