<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Marcus Alder on Medium]]></title>
        <description><![CDATA[Stories by Marcus Alder on Medium]]></description>
        <link>https://medium.com/@marcusa314?source=rss-caa961dea52------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*oAcxJ2Rbdtdg9_dS4P5-LA.jpeg</url>
            <title>Stories by Marcus Alder on Medium</title>
            <link>https://medium.com/@marcusa314?source=rss-caa961dea52------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 07 Apr 2026 23:30:06 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@marcusa314/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Visualizing Words]]></title>
            <link>https://medium.com/@marcusa314/visualizing-words-377624cb20c7?source=rss-caa961dea52------2</link>
            <guid isPermaLink="false">https://medium.com/p/377624cb20c7</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[text-mining]]></category>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[visualization]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Marcus Alder]]></dc:creator>
            <pubDate>Sat, 22 Feb 2020 20:00:24 GMT</pubDate>
            <atom:updated>2020-02-22T20:00:24.818Z</atom:updated>
            <content:encoded><![CDATA[<h4>PCA and clustering in Python</h4><p>In this post, I’ll show how to use a few NLP techniques to transform words into mathematical representations and plot them as points, as well as provide some examples. The graph below was created from the <em>Star Wars </em>wiki <a href="https://starwars.fandom.com/wiki/Main_Page">Wookieepedia</a> and colored with a clustering algorithm.</p><figure><img alt="3D Plot of Star Wars characters" src="https://cdn-images-1.medium.com/max/800/1*Y7K_-6HZqbii1V8Ms6SALQ.gif" /><figcaption>Plot of characters, locations, and organizations from Star Wars</figcaption></figure><p>The words’ coordinates are created from <strong>word embeddings </strong>(<strong>word vectors</strong>) which are created based on the contexts each word appears in. The vectors have properties related to the words’ meanings, approximately satisfying equations like (vector for “Paris”) - (vector for “France”) + (vector for “Italy”) ≈ (vector for “Rome”)— i.e. you can take Paris, substitute France out for Italy, and you’ll get Rome. Clustering and plotting also reveals interesting patterns; if you’ve watched <em>Star Wars </em>you might notice the clustering algorithm has unknowingly separated people, places, and organizations.</p><p>All the code is available at <a href="https://github.com/LogicalShark/wordvec">github.com/LogicalShark/wordvec</a></p><h3>Collecting Data</h3><p>Find an appropriate corpus for your analysis, or for more general uses like finding analyzing countries or movies you could use a <a href="https://en.wikipedia.org/wiki/List_of_text_corpora">generic text corpus</a>. Get enough data for get meaningful word embeddings, but just 50KB or less can be sufficient if it’s all relevant.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*SoZ1fQnKehlgxCu7" /><figcaption>The dump will be XML, which you may want to preprocess</figcaption></figure><p>I analyzed proper names from franchises I personally like, and if you want to do the same I recommend checking the franchise’s <a href="https://www.fandom.com/">Fandom</a> wiki for a database dump at “whatever.fandom.com/wiki/Special:Statistics” (although sadly some don’t provide database dumps).</p><h3>Generating Word Vectors</h3><p><a href="https://github.com/LogicalShark/wordvec/blob/master/wvgen.py">wvgen.py on github</a></p><p>To create the vectors we need the words they correspond to, which requires splitting the text into words. We can then use Word2Vec (a word vector creation model) to create the vectors. To get a list of words I used NLTK’s word-tokenizer, and for an Word2Vec implementation I used gensim. Here’s some more details on processing the text:</p><p><strong>Memory Limitations</strong>: The input was too large to manipulate all at once, but since Word2Vec can take an iterator as input, I made a custom iterator which takes files one line at a time for tokenization.</p><p><strong>Synonyms</strong>: There are some names written in multiple ways that we want represented in one vector, like Donkey Kong = DK or Obi-Wan Kenobi = Ben Kenobi. Instead of combining the output vectors, we can prevent the problem with string replacement on the input. For example, replacing all instances of “Donkey Kong” with “DK” means this character is represented by a single vector for the word “DK,” instead of two.</p><p><strong>Multi-word Expressions</strong>: Sometimes we want one vector to represent multiple words (e.g. Han Solo, Peach’s Castle), but the words get split by tokenization and become separate vectors. I used NLTK’s multi-word expression tokenizer (MWEtokenizer), which lets you add these names as custom phrases to be re-concatenated after the output of the word tokenization. An alternative would string replacement again (replace “Han Solo” with “HanSolo”) but I found MWEtokenizer to be simpler.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/982/1*x3g7uFBVdP70zjE9FXpp9A.png" /><figcaption>Summary of the custom iterator</figcaption></figure><p><strong>Summary:</strong> The iterator takes each line in the file, makes replacements for synonym consistency, performs word tokenization, and finally condenses multi-word expressions. Word2Vec takes the iterator as an argument in lieu of a word list and generates a model with the word vectors.</p><h3>Graphing Word Vectors</h3><p><a href="https://github.com/LogicalShark/wordvec/blob/master/wvplot.py">wvplot.py on github</a></p><p>Words have greatly varied contexts, meaning the vectors have a lot of features. Instead of graphing on three features, we can use <strong>Principal Component Analysis (PCA) </strong>to calculate a linear combination of features providing the orthogonal axes with the greatest variance. To further visualize patterns, the point’s text colors are set with <strong>k-means++ clustering </strong>(using sklearn) which automatically creates k “clusters.” I used matplotlib for graphing, giving an interactive graph like this:</p><figure><img alt="Plot of names in Super Mario" src="https://cdn-images-1.medium.com/max/800/1*ekOy3Jgj5_EC-H_cSXIWuA.gif" /><figcaption>Plot of characters, locations, and games in the Super Mario franchise. The overlapping red points includes all the locations (e.g. Peach’s Castle, New Donk City) and some characters (e.g. Dry Bones, Hammer Bro)</figcaption></figure><p>The <em>Super Mario</em> Fandom wiki was used to generate the vectors. A k=3 clustering seems to create a “main character” cluster, a “location/secondary character” cluster, and a “game” cluster. Daisy, Waluigi and Toad are appropriately positioned between main and secondary characters. If Donkey Kong seems a bit closer to the game cluster than the other main characters, it’s because “Donkey Kong” is both a character and the original arcade game!</p><figure><img alt="2D plot of characters in The Office" src="https://cdn-images-1.medium.com/max/1024/1*7vcu14G3lZcdEN67lNpkOQ.png" /><figcaption>2D Plot example using characters from The Office (TV), k=4</figcaption></figure><p>Sometimes 2D plots are enough to show patterns. The x-axis positions and clusters in the above plot approximately correlate with the screen presence of each character, matching this chart:</p><figure><img alt="Graph of characters from The Office by their number of lines of dialogue" src="https://cdn-images-1.medium.com/max/1024/0*cV5hSHYRm1O-KIzg" /><figcaption>Source and more data on <a href="https://www.reddit.com/r/dataisbeautiful/comments/6qbn4x/total_line_count_of_main_characters_in_the_office/dkw0fsl/">this reddit post</a></figcaption></figure><p><strong>Minor spoilers for <em>Hollow Knight</em> in second plot below!</strong></p><figure><img alt="3D plot of League of Legends champions" src="https://cdn-images-1.medium.com/max/1024/1*nHWQ7ooR57xKvFBk_Og2kQ.png" /><figcaption>All League of Legends champions with k=5. Clusters are determined not only by features visible in the plot but also many unseen features, which is why the red cluster is not a clear, separate group</figcaption></figure><h3>More Plots and Word Vector Arithmetic</h3><p><a href="https://github.com/LogicalShark/wordvec/blob/master/wvarith.py">wvarith.py on github</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-ypEJQiSkkWgNz3ftS275g.png" /><figcaption>Locations and characters from the indie game Hollow Knight, k=3</figcaption></figure><p>For arithmetic, the function most_similar_cosmul will find approximate solutions to vector addition of each word in positive and subtraction of those in negative. I found it useful to put one more positive word than negative, for a net of one word vector. This includes a single word in positive and empty negative.</p><p><a href="https://github.com/LogicalShark/wordvec/blob/master/wvlinear.py">wvlinear.py</a> uses this function to search for equations. However, unrelated words often combine by pure coincidence, so I recommend looking for relationships yourself with <a href="https://github.com/LogicalShark/wordvec/blob/master/wvarith.py">wvarith.py</a>.</p><p>Thanks for reading! All the code and some sample models are available at <a href="https://github.com/LogicalShark/wordvec">github.com/LogicalShark/wordvec</a>. Let me know if you have any questions!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=377624cb20c7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Gopher with Artificial Intelligence]]></title>
            <link>https://medium.com/google-cloud/the-gopher-with-artificial-intelligence-67715aeb13de?source=rss-caa961dea52------2</link>
            <guid isPermaLink="false">https://medium.com/p/67715aeb13de</guid>
            <category><![CDATA[go]]></category>
            <category><![CDATA[unity]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[game-development]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <dc:creator><![CDATA[Marcus Alder]]></dc:creator>
            <pubDate>Thu, 22 Aug 2019 17:55:56 GMT</pubDate>
            <atom:updated>2019-08-22T20:09:30.020Z</atom:updated>
            <content:encoded><![CDATA[<h4>Using Supervised ML for Games</h4><p>Machine learning is used everywhere these days. For the Go language conference <a href="https://www.gophercon.com/">GopherCon</a> 2019, we put Go’s Gopher mascot into an endless running game. By collecting data from human players and training a model on GCP, we were able to create an AI player that scored better than many of the humans. The backend code is available in <a href="https://github.com/GoogleCloudPlatform/golang-samples/tree/master/getting-started/gopher-run">this repo</a>.</p><h3>The Game</h3><p>Gopher Run was playable on Chromebooks at Google Cloud’s GopherCon booth. The main controls are simple: jump with up arrow and roll with down arrow. The levels are procedurally generated, so they’re infinite and change every time (but contain similar patterns). You take damage if you run into spikes or bugs, and after losing your three lives, the number of coins you collected is your score. The player speeds up over time at predetermined intervals, making the game harder as you progress. The game has a few other mechanics, but they’re not relevant to this post.</p><figure><img alt="Sample gameplay" src="https://cdn-images-1.medium.com/max/600/0*RbJv-YjQHxua2PNK" /><figcaption><em>Gameplay example</em></figcaption></figure><figure><img alt="Faster gameplay" src="https://cdn-images-1.medium.com/max/600/0*G8Zw8lfS0RjQj8oL" /><figcaption><em>It speeds up as you progress, requiring faster reaction times</em></figcaption></figure><h3>The AI</h3><p>The AI created for this game uses <strong>supervised learning</strong>, meaning it trains on the top players’ behaviors, unlike <strong>reinforcement learning </strong>which trains by playing and improving itself. While reinforcement learning has constant improvement and doesn’t require storing player data, it also takes much longer to train (the supervised algorithm only needs to run once) and it’s harder to set up and implement.</p><p>The training data consists of snapshots of the game recorded every half-second and whenever the player performs an action. This way the algorithm sees how the player reacts to different situations (by jumping, rolling, or doing nothing). After training and deploying the model, actions of the AI are determined by giving the current game state to the deployed model for a prediction of what a player is most likely to do given the situation.</p><p>I used the Unity engine for the game and the Google Cloud AI platform for ML, but this works with any engine that can send HTTP requests and ML service (or custom ML code).</p><h3>Creating the Input</h3><p>If you’re making this kind of AI, one of the more challenging tasks is determining what to put in the input. To keep everything fast and memory efficient it’s important to condense a game state into only the most helpful information. To record the player’s situation, I stored their current y position and vertical velocity. Since I wanted the AI to avoid obstacles, I looked at the nearest three bugs and one spike ahead of the player, and stored their y positions and x distances from the player (normalized by player speed, see note below). A data point was collected every time the player performed an action as well as every half-second to capture times when the player was doing nothing. The data was stored in a structure in Unity until score submission. Then it was sent to a csv file in Cloud Storage, and preparation for training collects the top 10 players’ files.</p><figure><img alt="Example of a game state with labeled distances" src="https://cdn-images-1.medium.com/max/961/0*QrYFuTM7XFqrOJII" /><figcaption>Diagram of input variables</figcaption></figure><p>The diagram shows the variables collected from the nearest spike and nearest three bugs. The information given to the algorithm includes the y variables, each dx variable divided by the player’s speed, as well as the player’s vertical velocity.</p><p><em>Note on normalization</em>: Horizontal spacing of objects scales with speed — compare the two gameplay gifs in the first section. Consider that a jump at high speed covers more horizontal distance despite having the same peak height and airtime. Dividing by speed means the input variable is the time it will take to reach the hazard (e.g. a spike 6 units ahead ÷ a speed of 12 units/second = 0.5 seconds to reach the spike), and this is what’s actually important for timing inputs like jumps.</p><h3>Training</h3><p>I used GCloud’s built-in <a href="https://xgboost.readthedocs.io/en/latest/">XGBOOST</a> framework with a classification objective (multi:softmax), which took around 7 minutes to train each time. More complicated behavior might be better suited to neural networks or regression objectives. Find something appropriate for your input which gives relevant information for the AI’s behavior. For example, I could have used a regression objective with -1 = roll, 0 = do nothing, 1 = jump. I elected not to since the player’s input was not analog (the buttons were either held or not, it wasn’t a 360° control stick), and it wouldn’t handle jumping while rolling at the same time.</p><p>Most games will probably only require a one-time training job. However, for the purposes of the demo, we wanted to see the AI improve as players improved and contributed better data, so we re-trained at regular intervals. You can automate this with a shell script (<a href="https://github.com/GoogleCloudPlatform/golang-samples/blob/master/getting-started/gopher-run/cmd/training.sh">here’s</a> the one I made). It uses the gcloud setup commands from the AI Platform <a href="https://cloud.google.com/ml-engine/docs/algorithms/linear-start#setup">quickstart</a>, then loops the training and version creation commands (also in the quickstart). This creates a trained model in a Cloud Storage directory, which overrides the model from the previous training job, and creates a new version of the deployed model (whose job-dir parameter points to the training output directory). You do have to deploy the model manually to create the first version (easy with the cloud console), but afterwards the new versions use the latest training output and set themselves as the default version, so prediction requests will always use the latest model.</p><h3>Using ML Predictions</h3><p>The AI player is identical to normal player but keyboard input is disabled, and instead it repeatedly makes requests to the ML model. Requests to a deployed model in the GCloud AI platform are formatted as the input minus the target column (i.e. the action, which is what’s being predicted). It then returns one of the possible target classes, indicating that a player in this game state would be likely to roll, jump, or do nothing (which calls the roll or jump method).</p><p><em>Note on GCloud outputs</em>: Even with though multi-class classification algorithm uses strings for the class column, a quirk of Google Cloud AI is that the prediction returns a float instead of a string, either 0.0 (first class seen in the input), 1.0 (second class seen in the input), 2.0, etc. Since the correspondence between the floats and the classes changes depending on the order the classes are seen in the input, prefix the csv training file with one dummy data line for each class, so the class represented by 0.0 is always jump, 1.0 is always roll, etc.</p><h3>Running the AI</h3><p>The earliest versions didn’t get very far. One early version with not enough data jumped every time it reached the ground. The AI quickly improved as it got more data and human players did better, but somewhat plateaued at a high score of 268 on the first day, which was enough to put it in the top 10 leaderboard until it got pushed out around the end of the day. On the second day it kept improving and at the end it reached 370. In comparison, human players typically scored 10–30 on their first run, and the leaderboard scores were around the 200–800 range on day 1 and 600–2000 on day 2 (much higher due to the returning players).</p><figure><img alt="Early AI gameplay" src="https://cdn-images-1.medium.com/max/600/0*VAK4GRDb3A1Xjfv6" /><figcaption><em>Early AI struggled with roll timing</em></figcaption></figure><figure><img alt="Late AI gameplay" src="https://cdn-images-1.medium.com/max/600/0*yK-ckANaap6aqA1o" /><figcaption><em>Later AI was much better</em></figcaption></figure><h3>Takeaways</h3><p><strong>Optimize performance and memory usage.</strong></p><p>There was a bug in which I accidentally collected data every frame, which destroyed the framerate and crashed the WebGL build by using up all the memory (even with the relatively simple data points I was collecting). It’s always good to follow standard best practices, like keeping Unity’s Update function as small as possible.</p><p><strong>Time to query the model is non-negligible in a fast-paced game.</strong></p><p>The AI’s gameplay had short but noticeable pauses while waiting for responses from the model. Under other circumstances it might be possible to send multiple data points at once, since the API takes in a list. I requested one data point at a time because I didn’t know where the AI player was going to be in the future or the future vertical velocity. I also set up the HTTP requests to run asynchronously, but the game moved too fast and the responses wouldn’t come after the AI had already colliding with upcoming hazards.</p><p>Because of the aforementioned constraint, I only queried the AI every half-second (more often at higher speeds). This meant that the AI was limited in how often it could perform a new action. I had “stop rolling” counted as its own distinct input, so a pattern which required rolling followed by a pattern which required jumping would sometimes see the AI successfully roll under the first part, then stop rolling, then before another half-second had passed it would run into the second pattern because it hadn’t yet made a new request (which would have told it to jump).</p><p><strong>This kind of ML works well and can be used for more complex tasks.</strong></p><p>After getting enough data, the AI was consistently good (outside of the issue in the above paragraph, which was a limitation of the implementation rather than the model). I used the ML output in a very direct way by having the AI copy the action predicted for a player, but there are more creative possibilities when you know what the player is likely to do in any situation. If the player always jumps over certain bug patterns instead of rolling, the level generation algorithm could adapt and increase the frequency of a similar pattern with added bugs above, forcing the player to roll. In a game like PAC-MAN or Bomberman, the AI could cut the player off by predicting where they will go next. There are endless applications in every genre, so I encourage you to use ML for something creative in your own projects.</p><p>Have you implemented any games? Do you think AI could be trained to play your game? Let me know in the comments!</p><p>Special thanks to Tyler Bui-Palsulich and Franzi Hinkelmann as well as Dane Liergaard, Jon Foust, and everyone on the Go and Cloud DPE teams in Google NYC.</p><p>The Go/Bash code is available <a href="https://github.com/GoogleCloudPlatform/golang-samples/tree/master/getting-started/gopher-run">here</a>, and the Unity project is currently not available but will be linked here if it is in the future. More information on GCP AI and pricing model can be found <a href="https://cloud.google.com/ai-platform/">here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=67715aeb13de" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/the-gopher-with-artificial-intelligence-67715aeb13de">The Gopher with Artificial Intelligence</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>