<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:dc="http://purl.org/dc/elements/1.1/">
    <channel>
        <title>~agentydragon</title>
        <link>http://agentydragon.com</link>
        <description><![CDATA[*agentydragon noises*]]></description>
        <atom:link href="http://agentydragon.com/rss.xml" rel="self"
                   type="application/rss+xml" />
        <lastBuildDate>Tue, 04 Jul 2023 00:00:00 UT</lastBuildDate>
        <item>
    <title>Furry life-hack: Per-con/per-event messenger folders</title>
    <link>http://agentydragon.com/posts/2023-07-04-con-folder.html</link>
    <description><![CDATA[<p>This is just to spread a small life-hack I came up with for furry conventions.
I’ll probably post a couple more of those small life-hacks next couple days, if
things go well. (Coming back from <a href="https://www.anthrocon.org/">Anthrocon</a> with some sorta
con-crud so I’m a little dead. Probably <a href="https://knowyourmeme.com/memes/oooooo-you-like-boys-ur-a-boykisser">kissed too many boys</a>.)</p>
<p>When you’re at a con (or an event of any sort), in your messenger app (mine is
Telegram), make a folder with everyone you know at that event, or any event
chats you wanna follow.</p>
<p>This is how that looked for me at Anthrocon:</p>
<p><img src="/static/2023-07-04-telegram-folders.png" alt="Telegram screenshot of
the configation"></p>
<p>Then, when you’re in between things and are looking for people to join/stuff to
do, just go through that folder.</p>
<p>Having all the people in one folder makes it way easier to remember “oh I wanted
to meet person X”, or to just shower everyone in a “hi I’m free wanna hang out”
message.</p>
<p>(And in Telegram, you can also put that folder to the top of the chat group
list, which makes sense if you’re at a con - then you probably want to mostly
chat with other people at the con anyway.)</p>]]></description>
    <pubDate>Tue, 04 Jul 2023 00:00:00 UT</pubDate>
    <guid>http://agentydragon.com/posts/2023-07-04-con-folder.html</guid>
    <dc:creator>Rai</dc:creator>
</item>
<item>
    <title>How I got to OpenAI</title>
    <link>http://agentydragon.com/posts/2023-01-11-how-i-got-to-openai.html</link>
    <description><![CDATA[<p>In May 2022, I started working at OpenAI as a resident on RL team, and
eventually I got hired as full-time. As of today, I’m still at OpenAI, and I’m
working on some ChatGPT related stuff and try to make progress on scalable
alignment. I want to make a brief write-up of how this happened, because
sometimes people ask about it and it might be helpful to other people in a
similar situation.</p>
<p>TL;DR, this is the last couple steps:</p>
<ul>
<li>Repeat a couple of times:
<ul>
<li>Make a list of stuff that’s relevant to ML I don’t yet grok.</li>
<li>Find resources to grok it. Some examples:
<ul>
<li>I consumed all the TensorFlow tutorials that don’t go to too niche topics (though today I’d do PyTorch).
And of course made <a href="https://agentydragon.com/posts/2019-11-25-my-anki-patterns.html">tons of Anki cards</a>.
Contact me if you want some of them.</li>
<li><a href="https://www.coursera.org/specializations/deep-learning">Deep Learning specialization on Coursera</a></li>
<li><a href="https://www.coursera.org/professional-certificates/tensorflow-in-practice">DeepLearning.AI TensorFlow Developer Professional Certificate on Coursera</a></li>
<li><a href="https://www.coursera.org/specializations/reinforcement-learning">Reinforcement Learning Specialization on Coursera</a> (from Martha and Adam White of University of Alberta)</li>
<li>For RL, I did a course on Coursera</li>
</ul></li>
<li>Learn all I can from those resources, collect stuff I don’t yet
understand, find references (e.g., papers I wanna read sometime etc.).</li>
</ul></li>
<li>Read up on recent papers.</li>
<li>Read <a href="https://spinningup.openai.com/en/latest/">Spinning Up in Deep RL</a>
and follow up on the references.</li>
<li>Implement some basic RL and ML experiments - e.g., lunar lander, <a href="https://agentydragon.com/posts/2021-10-18-rai-ml-mistakes-3.html">half-cheetah</a>.</li>
<li>Take part in <a href="https://aisafety.camp/">AI Safety Camp</a>.</li>
<li>Go to <a href="https://www.zurich-ai-alignment.com/">Zurich AI alignment</a> meetups.</li>
<li>Take part in an <a href="https://www.agisafetyfundamentals.com/ai-alignment-curriculum">AGI Safety
Fundamentals</a>
course.</li>
<li>Mail the author of one paper I read about some typos/errors I found in the
paper.</li>
<li>When I feel ready-ish, keep on lookout for roles:
<ul>
<li><a href="https://openai.com/blog/openai-residency/">OpenAI residency</a></li>
<li><a href="https://humancompatible.ai/news/2020/11/12/internship-applications/">CHAI internship</a></li>
<li><a href="https://www.deepmind.com/">DeepMind</a></li>
<li><a href="https://www.anthropic.com/">Anthropic</a></li>
<li><a href="https://www.redwoodresearch.org/">Redwood Research</a></li>
<li><a href="https://intelligence.org/">Machine Intelligence Research Institute</a></li>
<li><a href="https://cohere.ai/">Cohere</a></li>
<li><a href="https://generallyintelligent.com/">Generally Intelligent</a></li>
<li><a href="https://ought.org/">Ought</a>.</li>
</ul></li>
<li>Eventually, via reference from the author, apply to OpenAI residency,
take the interviews, be super nervous, get hired :)</li>
</ul>
<p>My longer relevant history follows.</p>
<h2 id="my-history">My history</h2>
<h3 id="before-university">Before university</h3>
<figure>
<img src="/static/2023-01-11-computer.png" style="max-width: 8cm">
<figcaption>
Rai sticker, by
<a href="https://www.furaffinity.net/user/pareldraakje/">ParelDraakje</a>
</figcaption>
</figure>
<p>I’ve always been good with math and computers. (I started out as a more general
hacker, including electronics etc., but eventually specialized.)</p>
<p>I did some competitive programming, high school programming seminars, olympiads
etc. I got to district level rounds, and my top achievements were being ~#20-30
in competitive “algorithmic programming” (which is part of math olympiad in
Czech Republic), and being #1 in competitive “practical programming” (which is
more “write a program for task X” rather than “solve this theoretical problem”.</p>
<h3 id="bachelors">Bachelor’s</h3>
<p>I took a wide approach to consume all the knowledge at uni. Since high school
I’ve been freelancing, writing up small programs/websites. I leveraged that into
a series of internships - first Represent.com in Prague (via <a href="https://www.jurajmasar.com/">Juraj
Masar</a>, who
was in roughly the same uni year), then half a year for Google in Paris, then
Dropbox.</p>
<p>When in USA on the Dropbox internship, I got an offer from
<a href="https://getcruise.com/">Cruise</a> and <a href="https://www.coinbase.com/">Coinbase</a>
via <a href="https://triplebyte.com/">Triplebyte</a>. I also did full-time interviews with
Google and got an offer.</p>
<p>It turned out I was not mentally/spiritually ready for making the move to USA.
I continued on with a master’s, keeping the Google offer in my back pocket.</p>
<h3 id="masters">Master’s</h3>
<p>It was easy to do all the required courses but I ended up getting stuck on the
last master’s requirement - the thesis.
I tried to work on knowledge base completion with
<a href="https://github.com/pasky">Pasky</a> as my supervisor (who ended up (co-?)founding
<a href="https://rossum.ai/">Rossum</a> where he’s currently CTO).
This was roughly at the time when the seminal Transformers paper
<a href="https://arxiv.org/abs/1706.03762">Attention is All You Need</a>, maybe +/- half
a year or so.</p>
<p>I got involved in the rationality community in Prague and the EA community
that eventually sprouted out of it. I co-founded the
<a href="https://efektivni-altruismus.cz/">Czech EA association</a>.</p>
<p>I wanted to do an open-source re-implementation of <a href="https://research.google/pubs/pub45634/">Google’s Knowledge
Vault</a>.
It’s basically where you take Google’s Knowledge Graph, you mix in a bunch of
unstructured data like Wikipedia or such, and based on that and some neural
networks trained on the graph, you make a bigger graph, where you suggest “I think there’s relation R between entities X and Y, with probability P=0.97”.</p>
<p>Coming from Google, I assumed this would be pretty easy - you’d plug in standard
building blocks for MapReduce-type pipelines, and out would come a model.
I was trying to immediately jump to a larger scale project - completing the
<a href="https://www.dbpedia.org/">DBpedia</a> knowledge graph with Wikipedia text.</p>
<p>Things turned out way harder than I expected. I tried to use Bazel to build
Hadoop stuff to chew up Wikipedia articles, and HBase (Hadoop’s BigTable
basically) to store Wikipedia articles. I ran into tons of stupid problems -
like “oops, library X which I need and library Y which I need depend on versions
of library Z which are incompatible, so you gotta split these 2 functions into
separate binaries”. Or “oops maximum runtime on this cluster is 24 hours lol”.
The iteration time was horrible.</p>
<figure>
<img src="/static/2023-01-11-laptop-ded.png" style="max-width: 8cm">
<figcaption>
Rai sticker, by
<a href="https://www.furaffinity.net/user/ketzel99/">Ketzel99</a>.<br>It doesn’t
have a name but I think “computers were a mistake” would fit.
</figcaption>
</figure>
<p>Today I’d probably try to approach this with less hubris, and try to start from
smaller pieces, so that I’d have a fast iteration turn-out.
And also, I’d just … drop all the manual NLP and throw a Transformer at the
problem. But oh well, you live and learn.</p>
<p>I was working in the same office as a group of CUNI folks who
ended up winning an Amazon tournament for writing a chit-chat agent, but I
wasn’t working in their ecosystem - the knowledge base completion I was doing
was basically playing on my own little field.</p>
<p>On top of that, I experienced a mental health crisis, and I felt like neither me
nor anyone else will ever care about what I do in the thesis. I became depressed
and to this day feel like I haven’t quite 100% recovered. But I think that’s
more “pre-existing problems came to the surface and I can no longer ignore
them”, rather than “at this point I started having problems”.
But I always felt sadness/envy/impostor syndrome seeing that I wasn’t one of the
national-best-level people in the theoretical stuff.</p>
<p>In between the work being hard and frustrating and disconnected, and the mental
health crisis, I dropped my master’s thesis, but stayed formally enrolled as a
student.</p>
<figure>
<img src="/static/2023-01-11-ded.png" style="max-width: 8cm">
<figcaption>
“ded x.x” Rai sticker, by
<a href="https://www.furaffinity.net/user/pareldraakje/">ParelDraakje</a>
</figcaption>
</figure>
<p>Google ended up getting tired of me trying to postpone the offer for longer. I
accepted it, didn’t get a H1b, and ended up getting an offer in Google
Switzerland.</p>
<h3 id="google">Google</h3>
<p>At Google, the first team I worked on was doing some stuff which cared about
metrics and was downstream of NLP, but there wasn’t really organizational
alignment that AI mattered. There were some applications of AI in isolated
places, but the problems my team was responsible for were much more shaped like
software engineering - building pipelines, services, etc. - than research
engineering.</p>
<p>I was sorta hoping to finish my master’s thesis, but of course, didn’t have time
to do it. I kept paying my uni to extend my studies, but eventually the clock
ran out. And so I did finish all the courses for a master’s, but never actually
got it, because I didn’t get around to doing the thesis.</p>
<p>Within Google, my original hope was that I’d try to position myself into a team
that would be AI or AI-adjacent, do researchy stuff, eventually position myself
to work on AI safety. But it ended up very hard trying to switch myself from
“software engineer working in team X doing software engineer things” into
“research engineer in ML”. I didn’t have any proven experience saying “I can do
ML” - all I had was “hey I did all this stuff in uni”. But whenever I tried to
apply for an internal transfer, somehow it didn’t end up working out. I think
there must have been very hard competition. And getting rejections was always an
ow.</p>
<p>My self-confidence suffered and I started feeling like I’m “not smart enough” or
“not good enough to do this”. I envied people I knew who were working with ML.</p>
<p>Eventually I ended up saving up a bunch of money, including being lucky with
crypto. I though about it a bunch, and decided that I just felt dissatisfied
with the work at Google. It wasn’t evolving me in the direction of working in
AI. I was good at my work, but I wasn’t getting the skills I wanted to get.</p>
<h3 id="fire-mode">FIRE mode</h3>
<p>Since uni I was following the “financial independence / early retirement” (FIRE)
philosophy. See places like
<a href="https://reddit.com/r/financialindependence">/r/financialindependence</a> for info
on that. With the money I saved my simulations showed I had a maybe like 80% of
being able to stay in Switzerland, on my level of spending, basically
indefinitely.</p>
<p>So I started just chilling in Switzerland, basically trying out “what early
retirement would be like”.</p>
<p>I played a lot of Civilization 6 and Civilization 5. I didn’t really feel better
than when I was working - maybe even worse. When you’re at work, you
automatically get a bit of socializing. When you’re not, it’s actually up to
your own initiative to meet people, and that was sorta hard in Switzerland.</p>
<h3 id="chilling-and-learning-ml">Chilling and learning ML</h3>
<p>I never thought of FIRE as “I’d retire and then I just chill on the beach
sipping piña coladas forever”. I wanted to find a mix of seeing friends, having
fun, and doing stuff that I felt like it mattered.</p>
<p>When I did the EA stuff in Czech Republic (like organizing the first
EAGxPrague), it felt like it mattered. Work on AI alignment would matter - if I
could manage to do it. Failing that, I thought maybe I’d find work someplace
where I liked the mission and product. I contributed to some open-source, like
<a href="https://apps.ankiweb.net/">Anki</a> and <a href="https://www.athensresearch.org/">Athens Research</a>.</p>
<p>I’ve decided to try to put some more effort re-learning all the stuff I learned
about AI in uni, except this time I wanted to <em>actually grok it</em>, where you
could wake me up at 2 AM 5 years from now and I’d still be able to explain to
you how it works. I went over old materials and re-learned them, making Anki
cards, and started refilling the holes where my knowledge was stuck before
2017-era progress in AI - like deep RL or Transformer language models.</p>
<figure>
<img src="/static/2023-01-11-papers-ded.png" style="max-width: 8cm" alt="Sticker of Rai overwhelmed with papers">
<figcaption>
Also a Rai sticker by
<a href="https://www.furaffinity.net/user/ketzel99/">Ketzel99</a>.<br>
“Science was a mistake”? Maybe writing was? When reading paper X
that assumes you’ve taken <em>Advanced Xology</em> and did all the background
reading on Y, Z, α, β… sometimes it indeed do be like that.
</figcaption>
</figure>
<p>To repeat the bullet points from the initial TL;DR, this was the procedure:</p>
<ul>
<li>Repeat a couple of times:
<ul>
<li>Make a list of stuff that’s relevant to ML I don’t yet grok.</li>
<li>Find resources to grok it. Some examples:
<ul>
<li>I consumed all the TensorFlow tutorials that don’t go to too niche topics (though today I’d do PyTorch).
And of course made <a href="https://agentydragon.com/posts/2019-11-25-my-anki-patterns.html">tons of Anki cards</a>.
Contact me if you want some of them.</li>
<li><a href="https://www.coursera.org/specializations/deep-learning">Deep Learning specialization on Coursera</a></li>
<li><a href="https://www.coursera.org/professional-certificates/tensorflow-in-practice">DeepLearning.AI TensorFlow Developer Professional Certificate on Coursera</a></li>
<li><a href="https://www.coursera.org/specializations/reinforcement-learning">Reinforcement Learning Specialization on Coursera</a> (from Martha and Adam White of University of Alberta)</li>
<li>For RL, I did a course on Coursera</li>
</ul></li>
<li>Learn all I can from those resources, collect stuff I don’t yet
understand, find references (e.g., papers I wanna read sometime etc.).</li>
</ul></li>
<li>Read up on recent papers.</li>
<li>Read <a href="https://spinningup.openai.com/en/latest/">Spinning Up in Deep RL</a>
and follow up on the references.</li>
<li>Implement some basic RL and ML experiments - e.g., lunar lander, <a href="https://agentydragon.com/posts/2021-10-18-rai-ml-mistakes-3.html">half-cheetah</a>.</li>
<li>Take part in <a href="https://aisafety.camp/">AI Safety Camp</a>.</li>
<li>Go to <a href="https://www.zurich-ai-alignment.com/">Zurich AI alignment</a> meetups.</li>
<li>Take part in an <a href="https://www.agisafetyfundamentals.com/ai-alignment-curriculum">AGI Safety
Fundamentals</a>
course.</li>
</ul>
<p>Nowadays, I’d actually recommend <a href="https://www.jacobh.co.uk/">Jacob Hilton</a>’s
<a href="https://github.com/jacobhilton/deep_learning_curriculum">Deep Learning curriculum</a>.
It’s based on an OpenAI-internal curriculum for residents and it’s really good
at building up a good background for work at OpenAI.</p>
<p>So, I’ve been slowly taking courses, sometimes experimenting, sometimes reading
papers. Eventually I found the paper <a href="https://arxiv.org/abs/1707.06347">Proximal Policy Optimization Algorithms</a>.</p>
<figure>
<img src="/static/2023-01-11-layers.png" style="max-width: 8cm" alt="Sticker of Rai going 'Stack more layers!'">
<figcaption>
Rai “STACK MORE LAYERS” sticker, by
<a href="https://www.furaffinity.net/user/ketzel99/">Ketzel99</a>.<br>
This is my life now.
</figcaption>
</figure>
<p>I tried pretty hard to grok it, because it is sorta magic. One day I wanna write
a blog post explaining it. There’s a whole bunch of math involved in proving
that it actually works.
But I’ve been trying to build really deep foundations, really grok all the
stuff involved in doing ML. To the point that theoretically I’d be able to
relay all of this to a Russian babushka, given enough time.</p>
<p>Reading that paper, I found a few small mistakes in exposition.
The first author on that paper is <a href="http://joschu.net/">John Schulman</a>.
I sent him an email.</p>
<p>In the background I’ve been on the lookout for some roles. I hoped eventually
I’d be able to take some role where I’d build up a bit of ML experience, and
then I’d be able to take that and leverage it into a role closer to AI safety.</p>
<p>Places I’ve been looking at included:</p>
<ul>
<li><a href="https://openai.com/blog/openai-residency/">OpenAI residency</a></li>
<li><a href="https://humancompatible.ai/news/2020/11/12/internship-applications/">CHAI internship</a></li>
<li><a href="https://www.deepmind.com/">DeepMind</a></li>
<li><a href="https://www.anthropic.com/">Anthropic</a></li>
<li><a href="https://www.redwoodresearch.org/">Redwood Research</a></li>
<li><a href="https://intelligence.org/">Machine Intelligence Research Institute</a></li>
<li><a href="https://cohere.ai/">Cohere</a></li>
<li><a href="https://generallyintelligent.com/">Generally Intelligent</a></li>
<li><a href="https://ought.org/">Ought</a>.</li>
</ul>
<p>John responded to the email, and invited me to apply to the OpenAI residency.</p>
<p>OpenAI had 2 residency tracks: software engineer and research. Originally I was
scared of applying for research, because of all the self-doubt and impostor
syndrome I was (and still to a degree am) carrying from having failed to write
a master’s thesis.</p>
<p>But John encouraged me to try the research track, maybe believing in myself more
than I did - and I made it through. The research interview was the most nervous
I felt interviewing ever - it was a multi-hour small sized research project
involving tools like Jupyter, NumPy, etc.</p>
<p>And I made it. It was very useful to have all the commands for Pandas, NumPy,
etc. memorized in Anki. But if I had to take the interview again, I’d recommend
myself to spend more time playing around with various algorithms, grokking how
they behave as you change parameters, etc.</p>
<p>I ended up getting accepted both for the OpenAI residency, and an internship at
CHAI. Both of them would have been awesome to work on. Out of these two, I chose
to go with the residency - mostly because I felt that because CHAI was an
academic institution, it would have been harder to grow there post-internship
if I wasn’t a PhD candidate.</p>
<h2 id="conclusions-and-recommendations">Conclusions and recommendations</h2>
<p>Here’s stuff I would have done differently and that I’d recommend to people in a
similar position:</p>
<ul>
<li><p><strong>Be safe but take risks</strong>.</p>
<p>I think I should have been less afraid to leave Google and be without a source
of income for a while. Theoretically I think I could have executed the “take a
year off and do some studying” thing maybe without even going through Google.
Though the Google experience definitely made me a much better engineer.</p>
<p>If you want to work on AI safety, I’d recommend doing something like this (“take
a year off and learn AI”) when you have maybe like 18 months of runway. I had way
more runway than that at the point when I did that.</p></li>
<li><p><strong>Community is super important</strong>.</p>
<p>I’d recommend myself to try harder to find other people who do things like
read AI papers, do experiments, etc. - like AI meetups, AI safety camp, that
sort of thing.</p></li>
<li><p><strong>Local optimizations are also important</strong>.</p>
<p>In Switzerland, I was for a while in a loop where I felt depressed that I
wasn’t moving forward toward working in AI, and didn’t have the energy to do
that.</p>
<p>I think one thing which helped a bunch was starting to actually learn German.
Did I want to stay in Switzerland long-term? No, not necessarily. But it did
make me feel way less like an alien. I didn’t immediately broadcast to
everyone “I’m a tourist” whenever I wanted to have a coffee. And I felt like
“hey - I’m achieving a thing!”</p>
<p>I’d generalize this to:</p>
<blockquote>
<p>“Tactics mean doing what you can with what you have.”
<br>
– Civilization 6 flavor text for <a href="https://civilization.fandom.com/wiki/Military_Tactics_(Civ6)">Military Tactics</a> tech, originally <a href="https://www.brainyquote.com/quotes/saul_alinsky_378818">Saul Alinsky</a>.</p>
</blockquote>
<p>Do you really want to do thing [X] but it’s really hard and discouraging and
depressing because all actions towards [X] are not in the range of stuff you
can do now?</p>
<p>Maybe go and take a walk around the block. Wash the dishes. Will it solve [X]?
No, but you’ll make things easier for future-you. Exercise is healthy for the
mind and body. Having a nice orderly environment makes it so that when you
wake up, your first thought isn’t “ugh, all this mess”.</p>
<p>Do what you can with what you have.</p></li>
<li><p><strong>Don’t fall victim to impostor syndrome</strong>.</p>
<p>If anyone figures out how to solve this one in full generality, let me know :)</p>
<p>I think part of that can be just hanging around people who are nice and
support you. For me, actively joining the furry community has been an
important part of that. I haven’t read Vega’s <a href="https://opguides.info/">Opinionated
Guides</a> yet (they’re on the to-read list), but I
think the <a href="https://opguides.info/engineering/intro/community/">Community</a>
guide might have useful pointers. The furry community is great and so is the
hacker community. For both of those, I’ve known for a long time that they
exist and I wish I became actively involved way earlier.</p></li>
</ul>
<p>I hope maybe this might give another nudge to people who’d like to work on
alignment, or that some of this advice might be useful.</p>]]></description>
    <pubDate>Wed, 11 Jan 2023 00:00:00 UT</pubDate>
    <guid>http://agentydragon.com/posts/2023-01-11-how-i-got-to-openai.html</guid>
    <dc:creator>Rai</dc:creator>
</item>
<item>
    <title>Furry Rationalists &amp; Effective Anthropomorphism both exist</title>
    <link>http://agentydragon.com/posts/2022-10-24-furry-rationalists.html</link>
    <description><![CDATA[<p>(cross-posted on <a href="https://www.lesswrong.com/posts/kEchp4aP9pdvdRLRf/furry-rationalists-and-effective-anthropomorphism-both-exist">LessWrong</a> and <a href="https://forum.effectivealtruism.org/posts/Sj9RMx3gn8oxi3ycX/furry-rationalists-and-effective-anthropomorphism-both-exist">EA Forum</a>)</p>
<p>Hi!</p>
<figure>
<img src="/static/2022-sticker-hello.png" style="max-width: 8cm" alt="Sticker of Rai waving hello">
</figure>
<p>I’m Rai and I’m a furry (specifically, dragon). The last couple years, I’ve been
running a Furry Rationalists Telegram group. It looks like we exist, and not
everyone who should know we exist does yet, so I wanted to just write this to
advertise that this furry+EA/rationality corner exists, and if you’re
furry-adjacent &amp; rationality-adjacent and nice, you’re invited to join us :)</p>
<p>Here’s the invite link for the Furry Rationalists group:
<a href="https://adgn.link/furry-rationalists-telegram" class="uri">https://adgn.link/furry-rationalists-telegram</a></p>
<p>There’s ~50 of us and we’re chill - we have self-improvement, science, and cute
animal GIFs. If you’d like a preview, here’s the <a href="https://docs.google.com/document/d/1xqNgRYzdVFO736oOIHK2oTIN3F8gty-lyYMYFyz1dLQ/view">guidelines + meta doc</a>.
We’re 18+, but we’re not adult-oriented - we’re 18+ just so that we can talk
about adult stuff if it does come up. If you happen to be &lt;18 and wanna join,
let me know, we might update this.</p>
<p>If you’re reading this a while later, and the link expired, contact me (via
some method on this website), or look us up on
<a href="https://www.furry-telegram-groups.net/" class="uri">https://www.furry-telegram-groups.net/</a>, a search for “rationality” should find
us.</p>
<p>There’s also a smaller Effective Anthropomorphism Discord server, run by
<a href="https://twitter.com/birdpathy">bird</a>. For an invite link, go to
<a href="https://adgn.link/effective-anthropomorphism-discord" class="uri">https://adgn.link/effective-anthropomorphism-discord</a></p>
<p>Come say hi, and feel free to share if you know anyone who’d be interested!</p>]]></description>
    <pubDate>Mon, 24 Oct 2022 00:00:00 UT</pubDate>
    <guid>http://agentydragon.com/posts/2022-10-24-furry-rationalists.html</guid>
    <dc:creator>Rai</dc:creator>
</item>
<item>
    <title>Making Neovim&#39;s color scheme follow system light/dark preference on GNOME</title>
    <link>http://agentydragon.com/posts/2022-09-03-neovim-theme-switch.html</link>
    <description><![CDATA[<p>GNOME has a feature where there’s a system-level setting for whether user
prefers light or dark theme. This got added relatively recently (in <a href="https://release.gnome.org/42/">GNOME
42</a>). Its intended use is so that apps can automatically adjust to match
the user’s preferences without hacks. Hacks like “read the current GTK theme
and try to figure out whether it’s light or dark based on some heuristic”.</p>
<p>Today I’ve managed to put together one annoying missing piece of personal infra.
I’ve had <code>gnome-terminal</code> sorta-following the setting for a while, and tonight
I’ve made <a href="https://neovim.io/">Neovim</a> also do that. To celebrate, I wanted to share how
this works.</p>
<figure>
<video controls loop autoplay style="max-width: 100%">
<source src="/static/2022-09-02-themes.mp4" type="video/mp4">
</video>
<figcaption>
GNOME Terminal and Neovim both following system theme
</figcaption>
</figure>
<p>All scripts copied here are in my <a href="https://gitlab.com/agentydragon/ducktape">ducktape</a> repo in
<a href="https://gitlab.com/agentydragon/ducktape/-/tree/main/dotfiles/local/bin"><code>dotfiles/local/bin</code></a>, where you can copy fork and improve
to your heart’s content. The Neovim part was added in <a href="https://gitlab.com/agentydragon/ducktape/-/merge_requests/81">MR #81</a>.</p>
<p>Those are the dependencies:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">pip</span> install pynvim absl-py dbus-python</span></code></pre></div>
<h2 id="shared-setup">Shared setup</h2>
<h3 id="night-theme-switcher">Night Theme Switcher</h3>
<p>Install the <a href="https://extensions.gnome.org/extension/2236/nightthemeswitcher/">Night Theme Switcher</a> GNOME extension.
This extension lets you attach scripts to when the theme is changed.</p>
<h3 id="lightdark-scripts">Light/dark scripts</h3>
<p>Create a pair of scripts, <code>set_light_theme</code> and <code>set_dark_theme</code>, put them
wherever. Mine are currently in <code>~/.local/bin</code>.</p>
<p>Point Night Theme Switcher to run those when the theme is changed.</p>
<h2 id="neovim">Neovim</h2>
<p>In my particular case, I like <a href="https://ethanschoonover.com/solarized/">Solarized colors</a>, which I have
everywhere I can (VSCode, Neovim, <code>gnome-terminal</code>, even this site - as of now).
I use the <a href="https://github.com/altercation/vim-colors-solarized"><code>vim-colors-solarized</code></a> plugin which adds
both light and dark variants, toggled by <code>set background=light</code> or <code>dark</code>.</p>
<h3 id="init.vim"><code>init.vim</code></h3>
<p>Open <code>~/.config/nvim/init.vim</code> and add this hunk somewhere near the top.
It’ll read the current setting from <code>gsettings</code> and update Vim’s background to
match.</p>
<pre class="vim"><code>&quot; Set color theme to light/dark based on current system preferences.
&quot; Done early to prefer flashing of the wrong theme before this runs.
&quot; Will later be picked up when setting up Solarized colors.
&quot; Called on theme switches by set_light_theme, set_dark_theme scripts.
function! UpdateThemeFromGnome()
  if !executable(&#39;gsettings&#39;)
    return
  endif

  let color_scheme = system(&#39;gsettings get org.gnome.desktop.interface color-scheme&#39;)
  &quot; remove newline character from color_scheme
  let color_scheme = substitute(color_scheme, &quot;\n&quot;, &quot;&quot;, &quot;&quot;)
  &quot; Remove quote marks
  let color_scheme = substitute(color_scheme, &quot;&#39;&quot;, &quot;&quot;, &quot;g&quot;)

  if color_scheme == &#39;prefer-dark&#39;
    set background=dark
  else
    &quot; With disabled night mode, value seems to be to &#39;default&#39; on my system.
    set background=light
  endif
endfunction

call UpdateThemeFromGnome()</code></pre>
<h3 id="update_nvim_theme_from_gnome"><code>update_nvim_theme_from_gnome</code></h3>
<p>Create this script somewhere and <code>chmod +x</code> it.
I named it <code>update_nvim_theme_from_gnome</code>.
It’ll use <code>pynvim</code> to connect to running Neovim instances and run the function
we made above to update the background.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/python</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="co"># Updates the theme on all running Neovim instances.</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> glob</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> os</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> pynvim <span class="im">import</span> attach</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="co"># </span><span class="al">TODO</span><span class="co">: should probably only try to do this to *my* neovim instances</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> <span class="bu">dir</span> <span class="kw">in</span> glob.glob(<span class="st">&#39;/tmp/nvim*&#39;</span>):</span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a>    socket <span class="op">=</span> os.path.join(<span class="bu">dir</span>, <span class="st">&#39;0&#39;</span>)</span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a>    nvim <span class="op">=</span> attach(<span class="st">&quot;socket&quot;</span>, path<span class="op">=</span>socket)</span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a>    nvim.command(<span class="st">&quot;call UpdateThemeFromGnome()&quot;</span>)</span></code></pre></div>
<p>Update <code>set_light_theme</code> and <code>set_dark_theme</code> to call it. This will make it so
that when you switch theme, it’ll not just affect new Neovim instances, but also
all currently running ones.</p>
<p>There’s a TODO in there. Exercise for the reader I guess - I don’t particularly
care because I rarely run Neovim as <code>root</code>, but I expect this would crash
and burn if there were Neovim running as any user other than you. Cause it would
probably not let you write into that socket.</p>
<h2 id="gnome-terminal">GNOME Terminal</h2>
<p>I have another script for GNOME Terminal doing something similar.</p>
<p>It assumes that you have a light and dark profile set up. Open GNOME Terminal
preferences and note down the names of the profiles you wanna use in
light/dark configurations</p>
<h3 id="switch_gnome_terminal_profile"><code>switch_gnome_terminal_profile</code></h3>
<p>Let’s call our script <code>switch_gnome_terminal_profile</code>:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/python</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="co"># Works on gnome-terminal 3.44.0 as of 2022-09-03.</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Requirements: absl-py, dbus-python</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> typing <span class="im">import</span> List</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> json</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> re</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> subprocess</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> ast</span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> dbus</span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> xml.etree <span class="im">import</span> ElementTree</span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> absl <span class="im">import</span> app, flags, logging</span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a>_PROFILE <span class="op">=</span> flags.DEFINE_string(<span class="st">&#39;profile&#39;</span>, <span class="va">None</span>,</span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a>                               <span class="st">&#39;Name or UUID of profile to set everywhere&#39;</span>)</span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> gsettings_get_profile_uuid_list() <span class="op">-&gt;</span> List[<span class="bu">str</span>]:</span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a>    out <span class="op">=</span> subprocess.check_output(</span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a>        [<span class="st">&quot;gsettings&quot;</span>, <span class="st">&quot;get&quot;</span>, <span class="st">&quot;org.gnome.Terminal.ProfilesList&quot;</span>,</span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a>         <span class="st">&quot;list&quot;</span>]).decode(<span class="st">&#39;utf-8&#39;</span>)</span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> ast.literal_eval(out)</span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> gsettings_get_default_profile_uuid() <span class="op">-&gt;</span> <span class="bu">str</span>:</span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a>    out <span class="op">=</span> subprocess.check_output(</span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a>        [<span class="st">&quot;gsettings&quot;</span>, <span class="st">&quot;get&quot;</span>, <span class="st">&quot;org.gnome.Terminal.ProfilesList&quot;</span>,</span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a>         <span class="st">&quot;default&quot;</span>]).decode(<span class="st">&#39;utf-8&#39;</span>)</span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> ast.literal_eval(out)</span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> gsettings_set_default_profile_uuid(uuid: <span class="bu">str</span>) <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb4-33"><a href="#cb4-33" aria-hidden="true" tabindex="-1"></a>    out <span class="op">=</span> subprocess.check_output([</span>
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true" tabindex="-1"></a>        <span class="st">&quot;gsettings&quot;</span>, <span class="st">&quot;set&quot;</span>, <span class="st">&quot;org.gnome.Terminal.ProfilesList&quot;</span>, <span class="st">&quot;default&quot;</span>,</span>
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true" tabindex="-1"></a>        <span class="ss">f&quot;&#39;</span><span class="sc">{</span>uuid<span class="sc">}</span><span class="ss">&#39;&quot;</span></span>
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true" tabindex="-1"></a>    ]).decode(<span class="st">&#39;utf-8&#39;</span>)</span>
<span id="cb4-37"><a href="#cb4-37" aria-hidden="true" tabindex="-1"></a>    <span class="cf">assert</span> out <span class="op">==</span> <span class="st">&#39;&#39;</span></span>
<span id="cb4-38"><a href="#cb4-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-39"><a href="#cb4-39" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-40"><a href="#cb4-40" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> dconf_get_profile_visible_name(uuid: <span class="bu">str</span>) <span class="op">-&gt;</span> <span class="bu">str</span>:</span>
<span id="cb4-41"><a href="#cb4-41" aria-hidden="true" tabindex="-1"></a>    <span class="co"># As of 2022-09-03 (gnome-terminal 3.44.0), somehow the visible-name only</span></span>
<span id="cb4-42"><a href="#cb4-42" aria-hidden="true" tabindex="-1"></a>    <span class="co"># seems to propagate correctly into dconf, not into gsettings...</span></span>
<span id="cb4-43"><a href="#cb4-43" aria-hidden="true" tabindex="-1"></a>    <span class="co"># but the list of profiles (ProfileList) is up in gsettings.</span></span>
<span id="cb4-44"><a href="#cb4-44" aria-hidden="true" tabindex="-1"></a>    <span class="co"># dconf list /org/gnome/terminal/legacy/profiles:/ returns a lot of profiles</span></span>
<span id="cb4-45"><a href="#cb4-45" aria-hidden="true" tabindex="-1"></a>    <span class="co"># which I&#39;ve deleted a long time back.</span></span>
<span id="cb4-46"><a href="#cb4-46" aria-hidden="true" tabindex="-1"></a>    name <span class="op">=</span> subprocess.check_output([</span>
<span id="cb4-47"><a href="#cb4-47" aria-hidden="true" tabindex="-1"></a>        <span class="st">&quot;dconf&quot;</span>, <span class="st">&quot;read&quot;</span>,</span>
<span id="cb4-48"><a href="#cb4-48" aria-hidden="true" tabindex="-1"></a>        <span class="ss">f&quot;/org/gnome/terminal/legacy/profiles:/:</span><span class="sc">{</span>uuid<span class="sc">}</span><span class="ss">/visible-name&quot;</span></span>
<span id="cb4-49"><a href="#cb4-49" aria-hidden="true" tabindex="-1"></a>    ]).decode(<span class="st">&#39;utf-8&#39;</span>).strip()</span>
<span id="cb4-50"><a href="#cb4-50" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> ast.literal_eval(name)</span>
<span id="cb4-51"><a href="#cb4-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-52"><a href="#cb4-52" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-53"><a href="#cb4-53" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> dbus_update_profile_on_all_windows(uuid: <span class="bu">str</span>) <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb4-54"><a href="#cb4-54" aria-hidden="true" tabindex="-1"></a>    bus <span class="op">=</span> dbus.SessionBus()</span>
<span id="cb4-55"><a href="#cb4-55" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-56"><a href="#cb4-56" aria-hidden="true" tabindex="-1"></a>    obj <span class="op">=</span> bus.get_object(<span class="st">&#39;org.gnome.Terminal&#39;</span>, <span class="st">&#39;/org/gnome/Terminal/window&#39;</span>)</span>
<span id="cb4-57"><a href="#cb4-57" aria-hidden="true" tabindex="-1"></a>    iface <span class="op">=</span> dbus.Interface(obj, <span class="st">&#39;org.freedesktop.DBus.Introspectable&#39;</span>)</span>
<span id="cb4-58"><a href="#cb4-58" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-59"><a href="#cb4-59" aria-hidden="true" tabindex="-1"></a>    tree <span class="op">=</span> ElementTree.fromstring(iface.Introspect())</span>
<span id="cb4-60"><a href="#cb4-60" aria-hidden="true" tabindex="-1"></a>    windows <span class="op">=</span> [child.attrib[<span class="st">&#39;name&#39;</span>] <span class="cf">for</span> child <span class="kw">in</span> tree <span class="cf">if</span> child.tag <span class="op">==</span> <span class="st">&#39;node&#39;</span>]</span>
<span id="cb4-61"><a href="#cb4-61" aria-hidden="true" tabindex="-1"></a>    logging.info(<span class="st">&quot;requesting new uuid: </span><span class="sc">%s</span><span class="st">&quot;</span>, uuid)</span>
<span id="cb4-62"><a href="#cb4-62" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-63"><a href="#cb4-63" aria-hidden="true" tabindex="-1"></a>    <span class="kw">def</span> _get_window_profile_uuid(window_actions_iface):</span>
<span id="cb4-64"><a href="#cb4-64" aria-hidden="true" tabindex="-1"></a>        <span class="co"># gnome-terminal source code pointer:</span></span>
<span id="cb4-65"><a href="#cb4-65" aria-hidden="true" tabindex="-1"></a>        <span class="co"># https://gitlab.gnome.org/GNOME/gnome-terminal/-/blob/f85f2a381e5ba9904d00236e46fc72ae31253ff0/src/terminal-window.cc#L402</span></span>
<span id="cb4-66"><a href="#cb4-66" aria-hidden="true" tabindex="-1"></a>        <span class="co"># D-Feet (https://wiki.gnome.org/action/show/Apps/DFeet) is useful for</span></span>
<span id="cb4-67"><a href="#cb4-67" aria-hidden="true" tabindex="-1"></a>        <span class="co"># manual poking.</span></span>
<span id="cb4-68"><a href="#cb4-68" aria-hidden="true" tabindex="-1"></a>        description <span class="op">=</span> window_actions_iface.Describe(<span class="st">&#39;profile&#39;</span>)</span>
<span id="cb4-69"><a href="#cb4-69" aria-hidden="true" tabindex="-1"></a>        profile_uuid <span class="op">=</span> description[<span class="dv">2</span>][<span class="dv">0</span>]</span>
<span id="cb4-70"><a href="#cb4-70" aria-hidden="true" tabindex="-1"></a>        <span class="cf">return</span> profile_uuid</span>
<span id="cb4-71"><a href="#cb4-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-72"><a href="#cb4-72" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> window <span class="kw">in</span> windows:</span>
<span id="cb4-73"><a href="#cb4-73" aria-hidden="true" tabindex="-1"></a>        window_path <span class="op">=</span> <span class="ss">f&#39;/org/gnome/Terminal/window/</span><span class="sc">{</span>window<span class="sc">}</span><span class="ss">&#39;</span></span>
<span id="cb4-74"><a href="#cb4-74" aria-hidden="true" tabindex="-1"></a>        <span class="co"># </span><span class="al">TODO</span><span class="co">: if there&#39;s other windows open - like Gnome Terminal preferences,</span></span>
<span id="cb4-75"><a href="#cb4-75" aria-hidden="true" tabindex="-1"></a>        <span class="co"># About dialog etc. - this will also catch those windows and fail</span></span>
<span id="cb4-76"><a href="#cb4-76" aria-hidden="true" tabindex="-1"></a>        <span class="co"># because they do not have the &#39;profile&#39; action.</span></span>
<span id="cb4-77"><a href="#cb4-77" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-78"><a href="#cb4-78" aria-hidden="true" tabindex="-1"></a>        obj <span class="op">=</span> bus.get_object(<span class="st">&#39;org.gnome.Terminal&#39;</span>, window_path)</span>
<span id="cb4-79"><a href="#cb4-79" aria-hidden="true" tabindex="-1"></a>        logging.info(<span class="st">&quot;talking to: </span><span class="sc">%s</span><span class="st">&quot;</span>, obj)</span>
<span id="cb4-80"><a href="#cb4-80" aria-hidden="true" tabindex="-1"></a>        window_actions_iface <span class="op">=</span> dbus.Interface(obj, <span class="st">&#39;org.gtk.Actions&#39;</span>)</span>
<span id="cb4-81"><a href="#cb4-81" aria-hidden="true" tabindex="-1"></a>        logging.info(<span class="st">&quot;current uuid: </span><span class="sc">%s</span><span class="st">&quot;</span>,</span>
<span id="cb4-82"><a href="#cb4-82" aria-hidden="true" tabindex="-1"></a>                     _get_window_profile_uuid(window_actions_iface))</span>
<span id="cb4-83"><a href="#cb4-83" aria-hidden="true" tabindex="-1"></a>        <span class="co">#res = window_actions_iface.Activate(&#39;about&#39;, [], [])</span></span>
<span id="cb4-84"><a href="#cb4-84" aria-hidden="true" tabindex="-1"></a>        res <span class="op">=</span> window_actions_iface.SetState(</span>
<span id="cb4-85"><a href="#cb4-85" aria-hidden="true" tabindex="-1"></a>            <span class="co"># https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI#Overview-2</span></span>
<span id="cb4-86"><a href="#cb4-86" aria-hidden="true" tabindex="-1"></a>            <span class="co"># https://gitlab.gnome.org/GNOME/gnome-terminal/-/blob/f85f2a381e5ba9904d00236e46fc72ae31253ff0/src/terminal-window.cc#L2132</span></span>
<span id="cb4-87"><a href="#cb4-87" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;profile&#39;</span>,</span>
<span id="cb4-88"><a href="#cb4-88" aria-hidden="true" tabindex="-1"></a>            <span class="co"># Requested new state</span></span>
<span id="cb4-89"><a href="#cb4-89" aria-hidden="true" tabindex="-1"></a>            <span class="co"># https://gitlab.gnome.org/GNOME/gnome-terminal/-/blob/f85f2a381e5ba9904d00236e46fc72ae31253ff0/src/terminal-window.cc#L1319</span></span>
<span id="cb4-90"><a href="#cb4-90" aria-hidden="true" tabindex="-1"></a>            uuid,</span>
<span id="cb4-91"><a href="#cb4-91" aria-hidden="true" tabindex="-1"></a>            <span class="co"># &quot;Platform data&quot; - `a{sv}`</span></span>
<span id="cb4-92"><a href="#cb4-92" aria-hidden="true" tabindex="-1"></a>            [])</span>
<span id="cb4-93"><a href="#cb4-93" aria-hidden="true" tabindex="-1"></a>        logging.info(<span class="st">&quot;window_actions_iface.SetState result: </span><span class="sc">%s</span><span class="st">&quot;</span>, res)</span>
<span id="cb4-94"><a href="#cb4-94" aria-hidden="true" tabindex="-1"></a>        uuid_after <span class="op">=</span> _get_window_profile_uuid(window_actions_iface)</span>
<span id="cb4-95"><a href="#cb4-95" aria-hidden="true" tabindex="-1"></a>        logging.info(<span class="st">&quot;new uuid: </span><span class="sc">%s</span><span class="st">&quot;</span>, uuid_after)</span>
<span id="cb4-96"><a href="#cb4-96" aria-hidden="true" tabindex="-1"></a>        <span class="cf">assert</span> new_uuid <span class="op">==</span> uuid</span>
<span id="cb4-97"><a href="#cb4-97" aria-hidden="true" tabindex="-1"></a>        <span class="co"># </span><span class="al">TODO</span><span class="co">: this only includes currently active tabs, not background tabs :/</span></span>
<span id="cb4-98"><a href="#cb4-98" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-99"><a href="#cb4-99" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-100"><a href="#cb4-100" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> main(_):</span>
<span id="cb4-101"><a href="#cb4-101" aria-hidden="true" tabindex="-1"></a>    profile_uuids <span class="op">=</span> <span class="bu">set</span>(gsettings_get_profile_uuid_list())</span>
<span id="cb4-102"><a href="#cb4-102" aria-hidden="true" tabindex="-1"></a>    uuid_by_name <span class="op">=</span> {}</span>
<span id="cb4-103"><a href="#cb4-103" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> uuid <span class="kw">in</span> profile_uuids:</span>
<span id="cb4-104"><a href="#cb4-104" aria-hidden="true" tabindex="-1"></a>        name <span class="op">=</span> dconf_get_profile_visible_name(uuid)</span>
<span id="cb4-105"><a href="#cb4-105" aria-hidden="true" tabindex="-1"></a>        uuid_by_name[name] <span class="op">=</span> uuid</span>
<span id="cb4-106"><a href="#cb4-106" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-107"><a href="#cb4-107" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> _PROFILE.value <span class="kw">in</span> profile_uuids:</span>
<span id="cb4-108"><a href="#cb4-108" aria-hidden="true" tabindex="-1"></a>        uuid <span class="op">=</span> _PROFILE.value</span>
<span id="cb4-109"><a href="#cb4-109" aria-hidden="true" tabindex="-1"></a>    <span class="cf">elif</span> _PROFILE.value <span class="kw">in</span> uuid_by_name:</span>
<span id="cb4-110"><a href="#cb4-110" aria-hidden="true" tabindex="-1"></a>        uuid <span class="op">=</span> uuid_by_name[_PROFILE.value]</span>
<span id="cb4-111"><a href="#cb4-111" aria-hidden="true" tabindex="-1"></a>    <span class="cf">else</span>:</span>
<span id="cb4-112"><a href="#cb4-112" aria-hidden="true" tabindex="-1"></a>        <span class="cf">raise</span> <span class="pp">Exception</span>(<span class="st">&quot;No such profile (by UUID or name)&quot;</span>)</span>
<span id="cb4-113"><a href="#cb4-113" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-114"><a href="#cb4-114" aria-hidden="true" tabindex="-1"></a>    gsettings_set_default_profile_uuid(uuid)</span>
<span id="cb4-115"><a href="#cb4-115" aria-hidden="true" tabindex="-1"></a>    dbus_update_profile_on_all_windows(uuid)</span>
<span id="cb4-116"><a href="#cb4-116" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-117"><a href="#cb4-117" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-118"><a href="#cb4-118" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&#39;__main__&#39;</span>:</span>
<span id="cb4-119"><a href="#cb4-119" aria-hidden="true" tabindex="-1"></a>    flags.mark_flag_as_required(_PROFILE.name)</span>
<span id="cb4-120"><a href="#cb4-120" aria-hidden="true" tabindex="-1"></a>    app.run(main)</span></code></pre></div>
<p>This script expects a profile name or UUID in <code>--profile</code>, and when called,
it’ll update GNOME Terminal’s settings to have that profile be the default.
That will make any new terminal windows/tabs use that profile.</p>
<p>Then it’ll talk to GNOME Terminal over <a href="https://dbus.freedesktop.org/doc/dbus-python/tutorial.html">dbus</a> and update the profile
of each window. Unfortunately, this only updates the theme on windows that
are currently active - i.e., not on background tabs. I’ve not yet figured out
how to fix this - I’ve looked into <a href="https://gitlab.gnome.org/GNOME/gnome-terminal/-/tree/master"><code>gnome-terminal</code>’s source
code</a> when I originally wrote the script, and I even
faintly remember reporting this as an issue. Basically that the dbus interface
should be a bit extended. If you know how to fix this, let me know.</p>
<p>Figuring this out took a while. <a href="https://wiki.gnome.org/Apps/DFeet">D-Feet</a> has been useful for it.</p>
<figure>
<img src="/static/2022-09-02-d-feet.png" alt="Screenshot of using D-Feet to poke GNOME Terminal" style="max-width: 100%">
</figure>
<p>Generally, it’s very questionable and broke for me at least once (because of
something having to do with which particular knobs are in <code>gconf</code> vs <code>dconf</code>
vs <code>gsettings</code>). Works for me on gnome-terminal 3.44.0. Caveat emptor.</p>
<p>As with the other scripts in here, it’s currently <a href="https://gitlab.com/agentydragon/ducktape/-/blob/main/dotfiles/local/bin/switch_gnome_terminal_profile">in my ducktape
repo</a> and if I update it later, it’ll be reflected there.</p>
<h2 id="putting-it-together">Putting it together</h2>
<p>Just make your <code>set_light_theme</code> and <code>set_dark_theme</code> scripts call the
appropriate scripts for <code>gnome-terminal</code> and Neovim. Here’s how they look for
me:</p>
<h3 id="set_dark_theme"><code>set_dark_theme</code></h3>
<div class="sourceCode" id="cb5"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/bin/bash</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="ex">switch_gnome_terminal_profile</span> <span class="at">--profile</span><span class="op">=</span><span class="st">&#39;Solarized Dark&#39;</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="ex">update_nvim_theme_from_gnome</span></span></code></pre></div>
<h3 id="set_light_theme"><code>set_light_theme</code></h3>
<div class="sourceCode" id="cb6"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/bin/bash</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="ex">switch_gnome_terminal_profile</span> <span class="at">--profile</span><span class="op">=</span><span class="st">&#39;Solarized Light&#39;</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="ex">update_nvim_theme_from_gnome</span></span></code></pre></div>
<p>Why is one on <code>PATH</code> and not the other? Tech debt in my personal infra.
Deployment step of built artifacts isn’t separated and my old scripts repo isn’t
yet merged into my <a href="https://gitlab.com/agentydragon/ducktape">maximally glorious Ducktape monorepo</a>. Sue me :P</p>
<p>Still, over time, I’ve made it a project to make the duct tape holding together
my computer have a better CI setup than many commercial software projects :P</p>
<figure>
<!-- TODO: technically a <video>. oh well. -->
<img src="/static/im-not-proud-of-it.gif" alt="I'm not proud of it. I am a bit." />
</figure>
<h2 id="short-update">Short update</h2>
<p>Oh also I’m now in San Francisco and at <a href="https://openai.com/">OpenAI</a>, working on
reinforcement learning. Long time, much news. Also <a href="https://github.com/features/copilot/">Copilot</a> is
a thing and has surprised me very strongly by how good and useful it is.
Sometime I’ll be writing some sorta summary of last year or two, but today is
not the day and this is not that blogpost.</p>
<p>EDIT (2023-01-16): <a href="/posts/2023-01-11-how-i-got-to-openai.html">How I got to OpenAI</a> is that blogpost.</p>
<p>Cheers, have a nice long weekend if you’re in the US.</p>]]></description>
    <pubDate>Sat, 03 Sep 2022 00:00:00 UT</pubDate>
    <guid>http://agentydragon.com/posts/2022-09-03-neovim-theme-switch.html</guid>
    <dc:creator>Rai</dc:creator>
</item>
<item>
    <title>Rai&#39;s ML mistakes, part 3 of ∞</title>
    <link>http://agentydragon.com/posts/2021-10-18-rai-ml-mistakes-3.html</link>
    <description><![CDATA[<p>Previous parts:</p>
<ul>
<li><a href="/posts/2020-12-31-cartpole-q-learning.html">Part 1 on Cartpole</a></li>
<li><a href="/posts/2021-01-04-rai-ml-mistakes-2.html">Part 2 on Lunar Landing</a></li>
</ul>
<h2 id="next-step-half-cheetah-td3-continuous-actions">Next step: half-cheetah, TD3, continuous actions</h2>
<p>I’m not doing all that great emotionally, but I’m trying to keep learning RL,
even if slowly. I made Cartpole and Lunar Landing environments work. Both of
them have discrete actions. The next environment I went to try to learn was the
<a href="https://www.endtoend.ai/envs/gym/mujoco/half-cheetah/">Half Cheetah</a>.</p>
<figure>
<figure>
<img src="/static/2021-10-18-standing.png" alt="Standing half-cheetah" />
<figcaption aria-hidden="true">Standing half-cheetah</figcaption>
</figure>
</figure>
<p>In this environment, you control a simple robot and are trying to teach it to
run. You control it with continuous signals. I’m not sure what exactly they
mean, probably something like force applied to joints. Continuous actions mean
you need to use slightly different algorithms. I went to learn TD3 (twin delayed
deep deterministic actor-critic), based on <a href="https://spinningup.openai.com/en/latest/algorithms/td3.html">OpenAI’s treatment in Spinning Up in
Deep RL</a>. It was
published in a 2018 paper called <a href="https://arxiv.org/abs/1802.09477">Addressing Function Approximation Error in
Actor-Critic Methods</a>.</p>
<h3 id="sidenote-depending-on-mujoco-sucks">Sidenote: depending on MuJoCo sucks</h3>
<p>The vanilla half-cheetah environment is written with MuJoCo. MuJoCo is a
<em>commercial</em> physics simulator used for a lot of robotics environments like
this. You need a license to run it. As of now (October 18, 2021), there is a
free license available for everyone to run MuJoCo until the end of this month.
But in general, closed-source dependencies for open research <em>suck</em>.</p>
<p>There’s this open-source physics engine called <a href="https://pybullet.org/">Bullet</a>.
I’ve played with it a bit in middle-high school when I was trying to write some
3D stuff. Turns out they have since made Python bindings, and implemented a
bunch of OpenAI Gym environments. So you can now run lots of environments
without MuJoCo :)</p>
<p>To use the PyBullet environments, install the <code>pybullet</code> Python package and
<code>import pybullet_envs</code>. The PyBullet repo has <a href="https://github.com/bulletphysics/bullet3/blob/master/examples/pybullet/gym/pybullet_envs/__init__.py">the list of implemented
environments</a>.</p>
<h3 id="sidenote-2-actually-mujoco-is-now-open-source">Sidenote 2: Actually MuJoCo is now open-source</h3>
<p>So, later on the same day I wrote this post, turns out DeepMind bought MuJoCo
and made it open-source and free (at <a href="https://mujoco.org/" class="uri">https://mujoco.org/</a>). Good stuff :)</p>
<h2 id="td3-description">TD3 description</h2>
<h3 id="q-learning">Q learning</h3>
<p>To describe TD3 briefly, it’s similar to Q learning.</p>
<p>In Q learning, you’re learning a function \(\hat{\mathrm{Q}}_\theta(s,a)\), and a
policy \(\pi\). You update \(\theta\) to make \(\hat{\mathrm{Q}}_\theta(s,a)\)
match closer to the actual Q function for the policy \(\pi\), and you also
update the policy \(\pi\) to gradually improve. You can do this exactly if
you have a small enough environment to hold all this in memory. The procedure
you use to make \(\hat{\mathrm{Q}}_\theta\) approximate
\(\mathrm{Q}_\pi\) is basically SARSA: you minimize the squared error
between \(\hat{\mathrm{Q}}_\theta(s,a)\) and an estimator that converges to
center on the actual \(\mathrm{Q}_\pi(s,a)\). In the finite case, that
Q learning estimator for a transition \(s \xrightarrow{a} (r, s’)\) is
\(r + \gamma \max_{a’} \hat{\mathrm{Q}}_\theta(s’,a’)\). In vanilla Q
learning, the followed policy is \(\mathrm{greedy}(\hat{\mathrm{Q}})\),
which is what that maximum does.</p>
<p>But when you’re in a continuous action space, you can’t just \(\arg\max\)
over all possible actions.</p>
<h3 id="ddpg">DDPG</h3>
<p>Enter DDPG (Deep Deterministic Policy Gradient), in which you maintain 2
networks: the <em>critic</em> \(\hat{\mathrm{Q}}_\theta(s,a)\) which approximates
the Q value of the current policy, and the <em>actor</em> - a deterministic policy
\(\pi_\varphi: \mathcal{S} \rightarrow \mathcal{A}\), which you improve
based on the critic’s estimations.</p>
<p>Run the agent with your current policy in a replay buffer, plus with some
exploration (like a bit of Gaussian noise added to actions). Draw a batch from
the replay buffer, and do an optimization step on the critic to minimize its
Bellman error:</p>
<p><span class="math display">$$
\arg\min_\theta \sum_{(s,a,r,s') \in \mathrm{batch}}
\left[\hat{\mathrm{Q}}_\theta(s,a) - (r + \gamma \hat{\mathrm{Q}}_\theta(s', \pi_\varphi(s')))\right]^2
$$</span></p>
<p>Then update the actor to choose actions that get better Q values on the same
batch:
<span class="math display">$$\arg\max_\varphi \sum_{(s,a) \in \mathrm{batch}} \hat{\mathrm{Q}}_\theta(s,\pi_\varphi(s))$$</span>
The batch has to be drawn randomly. This is important, because if you draw a
bunch of states that immediately follow each other, their predictions will end
up pulling each other to explode towards infinity.</p>
<p>To prevent similar feedback cycles between the actor and critic, you keep 2
copies of each: the <em>optimized</em> one and the <em>target</em> one. They start out as
exact copies. When computing the Bellman targets for the critic, instead of
using the <em>optimized</em> actor and critic, use the <em>target</em> ones:</p>
<p><span class="math display">$$
\arg\min_\theta \sum_{(s,a,r,s') \in \mathrm{batch}}
\left[\hat{\mathrm{Q}}_{\theta_\text{opt}}(s,a) - (r + \gamma \hat{\mathrm{Q}}_{\theta_\text{targ}}(s', \pi_{\varphi_\text{targ}}(s')))\right]^2
$$</span></p>
<p>And slowly <a href="https://paperswithcode.com/method/polyak-averaging">Polyak-average</a>
the target networks towards the optimized one (with (\approx 0.05)):</p>
<p><span class="math display">$$
\begin{align*}
\theta_\text{targ} &amp; \gets \varrho \cdot \theta_\text{opt} + (1-\varrho) \cdot \theta_\text{targ} \\
\varphi_\text{targ} &amp; \gets \varrho \cdot \varphi_\text{opt} + (1-\varrho) \cdot \varphi_\text{targ}
\end{align*}
$$</span></p>
<p>By the way, I made up this a shorthand notation for this operation “update x towards
y with update size (\alpha)”:</p>
<p><span class="math display">$$
\require{extpfeil}
\theta_\text{targ} \xmapsto{\varrho} \theta_\text{opt}, \varphi_\text{targ}
\xmapsto{\varrho} \varphi_\text{opt}
$$</span></p>
<h3 id="td3">TD3</h3>
<p>Twin Delayed Deep Deterministic Policy Gradient was introduced in a paper called
<a href="https://arxiv.org/abs/1802.09477">Addressing Function Approximation Error in Actor-Critic Methods</a>.
Note the “function approximation error” part. This talks about the error
inherent in how \(\hat{\mathrm{Q}}_\theta\) approximates the real
\(\mathrm{Q}_\pi\). In particular, if in a state \(s\), the critic
overestimates the Q value for some action \(a\), the actor’s optimization
step will be incentivized to exploit that overestimation. But that doesn’t mean
it’ll actually get a better result.</p>
<p>TD3 adds 3 steps to address this:</p>
<ol type="1">
<li><em>Target policy smoothing</em>: in the Bellman update, instead of expecting to
follow \(\pi_{\varphi_\text{targ}}\) exactly, add a bit of Gaussian
noise to the chosen action. That way the policy can’t try to hit a small peak
of overestimation by the critic.</li>
<li><em>Twin critics</em>: train 2 critics, both to minimize the Bellman error. Optimize
the policy to maximize one of them. Instead of setting critics’ targets based
on one critic, choose the target based on the lesser of their two
predictions. If you train 2 networks, they’re unlikely to overestimate the
real Q function in the same place.
<span class="math display">$$
\arg\min_\theta \sum_{i \in {1, 2}} \sum_{(s,a,r,s') \in \mathrm{batch}}
\left[\hat{\mathrm{Q}}_{\theta_{i, \text{opt}}}(s,a) - (r + \gamma \min_{j\in {1, 2}}\hat{\mathrm{Q}}_{\theta_{j, \text{targ}}}(s', \pi_{\varphi_\text{targ}}(s')))\right]^2
$$</span></li>
<li><em>Delayed policy updates</em>: update the policy just once per 2 batches (i.e.,
2x slower than the critics).</li>
</ol>
<h2 id="rais-ml-mistake-5-multiplication-what-multiplication">Rai’s ML mistake #5: Multiplication? What multiplication?</h2>
<p>The following happened over the course of ~6 weeks, as I gathered a few hours at
a time of energy, will, etc. to work on this.</p>
<p>So, I go and implement my algorithm and run it. On my first try, I implement it
wrong because I misremember how to implement it. I go back to Spinning Up in
Deep RL, smack myself on the forehead, and go fix it. Run it again.</p>
<p>It’s learning <em>something</em>. The average reward is going up. But slowly.</p>
<figure>
<figure>
<img src="/static/2021-10-18-mean-reward.png" alt="Slowly increasing mean reward graph" />
<figcaption aria-hidden="true">Slowly increasing mean reward graph</figcaption>
</figure>
</figure>
<p>Then, over the next ~4 weeks, whenever I have some time, I try to bang my head
against the keyboard some more. Tweak all the hyperparameters. Look up the
hyperparameters they use in <a href="https://github.com/DLR-RM/rl-baselines3-zoo/blob/master/hyperparams/td3.yml">rl-baselines3-zoo</a>.
No dice. Repeat for a while, for all hyperparameters - critic learning rate,
actor learning rate, actor delay, replay buffer size, batch size, Polyak rate,
discount rate, initial random action steps. Rewrite the code twice-thrice. Still
the same issue. Keep it training for several days. Does not help. Repeat a few
times.</p>
<h3 id="thanks-god-theres-a-reference-implementation">Thanks God there’s a reference implementation</h3>
<p>I wanted to implement this algorithm on my own, because I want to grok it.
But I got to the end of my wits here, and started thinking: “hell, can this
algorithm even <em>solve this environment</em>”? <a href="https://arxiv.org/abs/1802.09477">The paper</a> had graphs and
results with the half-cheetah. But that was the MuJoCo half-cheetah. Maybe the
PyBullet half-cheetah had a different reward scale and this was actually as good
as it went?</p>
<p>Unlikely. All my half-cheetah did in evaluation was stand upright without moving.
Maybe sometimes kinda jump once.</p>
<figure>
<figure>
<img src="/static/2021-10-18-standing.png" alt="Standing half-cheetah" />
<figcaption aria-hidden="true">Standing half-cheetah</figcaption>
</figure>
</figure>
<p>But yeah. Let’s run the reference implementation and see what it does. I start
it for a few minutes, and…</p>
<pre><code>...
Total T: 109000 Episode Num: 109 Episode T: 1000 Reward: 938.893
Total T: 110000 Episode Num: 110 Episode T: 1000 Reward: 987.304
---------------------------------------
Evaluation over 10 episodes: 963.088
---------------------------------------</code></pre>
<p>God dammit. I was getting <em>maybe</em>, on a <em>lucky episode</em>, like 300 at most, and
that was <em>after millions of training steps</em>…</p>
<h3 id="does-it-just-not-work-because-of-tensorflow">Does it just not work because of Tensorflow?</h3>
<p>Okay, so I have code A which does not work (my code), and code B which does
(reference implementation). I know what to do here. Align code A and code B
together so that they’re similar, and then scour the diff line by line.
Somewhere in there there’s my bug.</p>
<p>I refactor my code, rename variables, etc., until the diff is small.</p>
<p>Now the only diff I see is basically me using Tensorflow and the reference
implementation using PyTorch. Stuff like this:</p>
<pre><code>2,3c3,7
&lt; import tensorflow as tf
&lt; from tensorflow.keras import layers as tfkl
---
&gt; import torch
&gt; import torch.nn as nn
&gt; import torch.nn.functional as F
136,137c123,124
&lt;         state = tf.expand_dims(state, axis=0)
&lt;         return tf.squeeze(self.actor(state), axis=0).numpy()
---
&gt;         state = torch.FloatTensor(state.reshape(1, -1)).to(device)
&gt;         return self.actor(state).cpu().data.numpy().flatten()</code></pre>
<p>And yet, my code doesn’t work, and their code does.</p>
<p>I bang my head against this for maybe 2 more days. So, where can the differences
be?</p>
<p>Maybe different action scaling. I align action scaling like they do. Instead of
“sigmoid, then rescale from 0 to 1 into <code>action_space.low</code> to
<code>action_space.high</code>”, I do their “tanh, then multiply by
<code>action_space.high</code>”. Those should be basically the same thing, but I do it
anyway just to be safe. Still doesn’t work.</p>
<p>Maybe different initialization. Unlikely, but possible.
Their code uses Torch’s <a href="https://pytorch.org/docs/stable/generated/torch.nn.Linear.html">Linear</a>.
It initializes weights and biases both randomly from
\(\text{Uniform}([\pm \sqrt{\frac{1}{\text{input size}} }])\).
I use TensorFlow/Keras’s <a href="https://www.tensorflow.org/api_docs/python/tf/keras/layers/Dense">Dense</a>.
It uses <a href="https://www.tensorflow.org/api_docs/python/tf/keras/initializers/GlorotUniform">Xavier uniform initialization</a>
(aka Glorot initialization, named … after someone named Xavier Glorot)
by default, which draws from
\(\text{Uniform}([\pm \sqrt{\frac{6}{\text{input size} + \text{output size} } }])\).
And TensorFlow initializes biases to zero.
Okay. I rewrite the TensorFlow initialization to do the same thing as Torch.
Still the same. God dammit.</p>
<p>Does the ADAM optimizer in TensorFlow and PyTorch work differently? … Maybe.
I’m gonna shelve the idea of stepping through it for later.</p>
<h3 id="copy-the-whole-goddamn-setup">Copy the whole goddamn setup</h3>
<p>I decide that I’ll copy even more of their code. Okay, this is unlikely, but
what if there’s something wrong with how I initialize the environment or
something? I copy their <code>main.py</code>, switch it to TensorFlow, and use their
<code>utils.py</code>.</p>
<p>And now it works, and I scream.</p>
<p>It’s the <code>utils.py</code> that did it. <a href="https://github.com/sfujim/TD3/blob/master/utils.py">That file in their repo</a>
implements the replay buffer. I didn’t pore over my implementation in detail,
because … hey, it’s the simplest part. How would I mess up a replay buffer?</p>
<p>Their replay buffer exists in RAM, and has NumPy arrays. It uses NumPy’s
randomness. I use TensorFlow variables and TensorFlow’s <a href="https://www.tensorflow.org/api_docs/python/tf/random/Generator"><code>tf.random.Generator</code></a>.</p>
<p>After some work, I find the culprit lines.</p>
<p>Here’s how my code stores the replay buffer’s rewards and “is this the final
state” flag:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> ReplayBuffer:</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>, state_dim, action_dim, max_size<span class="op">=</span><span class="bu">int</span>(<span class="fl">1e6</span>)):</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> <span class="co"># ... snip ...</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>        <span class="va">self</span>.reward <span class="op">=</span> tf.Variable(tf.zeros((max_size, )), dtype<span class="op">=</span>tf.float32)</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>        <span class="va">self</span>.not_done <span class="op">=</span> tf.Variable(tf.zeros((max_size, ), dtype<span class="op">=</span>tf.float32),</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>                                    dtype<span class="op">=</span>tf.float32)</span></code></pre></div>
<p>And this is how they do it:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> ReplayBuffer(<span class="bu">object</span>):</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>, state_dim, action_dim, max_size<span class="op">=</span><span class="bu">int</span>(<span class="fl">1e6</span>)):</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> <span class="co"># ... snip ...</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>        <span class="va">self</span>.reward <span class="op">=</span> np.zeros((max_size, <span class="dv">1</span>))</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>        <span class="va">self</span>.not_done <span class="op">=</span> np.zeros((max_size, <span class="dv">1</span>))</span></code></pre></div>
<p>What’s the difference? I have a vector, a 1-dimensional tensor. They have a
2-dimensional tensor, with second dimension 1.</p>
<h3 id="and-of-course-its-goddamn-tensor-shapes">And of course it’s goddamn tensor shapes</h3>
<p>And of course it’s goddamn tensor shapes.</p>
<p>On its own, it doesn’t matter whether I have the replay buffer shaped like I had
it, or like they did.</p>
<p>What matters is what happens when I combine it with this code which computes
the target Q values:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Select action according to policy and add clipped noise</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>noise <span class="op">=</span> tf.random.normal(shape<span class="op">=</span>action.shape) <span class="op">*</span> <span class="va">self</span>.policy_noise</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>noise <span class="op">=</span> tf.clip_by_value(noise, <span class="op">-</span><span class="va">self</span>.noise_clip, <span class="va">self</span>.noise_clip)</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>next_action <span class="op">=</span> (<span class="va">self</span>.actor_target(next_state) <span class="op">+</span> noise)</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>next_action <span class="op">=</span> tf.clip_by_value(next_action, <span class="op">-</span><span class="va">self</span>.max_action,</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>          <span class="va">self</span>.max_action)</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="co"># Compute the target Q value</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>target_Q1, target_Q2 <span class="op">=</span> <span class="va">self</span>.critic_target((next_state, next_action))</span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a>target_Q <span class="op">=</span> tf.math.minimum(target_Q1, target_Q2)</span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>target_Q <span class="op">=</span> reward <span class="op">+</span> not_done <span class="op">*</span> <span class="va">self</span>.discount <span class="op">*</span> target_Q</span></code></pre></div>
<p>TD3 maintains 2 critics. <code>critic_target</code> is a Keras model, which contains two
stacks of feed-forward layers. At the end, they have a
<code>q_value = tf.keras.layers.Dense(1, ...)</code>, and then the whole model returns the
tuple <code>(q1_value, q2_value)</code>.</p>
<p>With that in mind, what’s the shape of <code>target_Q1</code>?</p>
<p>No, it’s not <span class="math inline">(batch size)</span>. It’s <span class="math inline">(batch size, 1)</span>. Because
of course there’s the last dimension of size 1 - if your model has more than 1
output node, you need to stack them.</p>
<p>What’s the shape of <code>not_done</code>?</p>
<p>With my replay buffer, it was <span class="math inline">(batch size)</span>. One scalar per experience
in batch, right? With the reference implementation’s replay buffer, it was
<span class="math inline">(batch size, 1)</span>.</p>
<p>Consider the line <code>target_Q = reward + not_done * self.discount * target_Q</code>,
where <code>target_Q</code> has shape <span class="math inline">(batch size, 1)</span> and, as established for my
code, <code>not_done</code> has shape <span class="math inline">(batch size)</span>. What’s the shape of the
computed expression?</p>
<p>If you answered <span class="math inline">(batch size, batch size)</span>, I guess you win.
And that was not intended. I wanted it to be <span class="math inline">(batch size)</span> - one
target Q value for one replay experience in batch.</p>
<p>What happens next with this <code>target_Q</code>?</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="at">@tf.function</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> _mse(x, y):</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> tf.reduce_mean(tf.math.squared_difference(x, y))</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="cf">with</span> tf.GradientTape() <span class="im">as</span> tape:</span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a>    <span class="co"># Get current Q estimates</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a>    current_Q1, current_Q2 <span class="op">=</span> <span class="va">self</span>.critic((state, action))</span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a>    <span class="co"># Compute critic loss</span></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a>    critic_loss <span class="op">=</span> (_mse(current_Q1, target_Q) <span class="op">+</span></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a>     _mse(current_Q2, target_Q))</span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a><span class="co"># Optimize the critic</span></span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a><span class="va">self</span>.critic_optimizer.minimize(</span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a>    critic_loss, tape<span class="op">=</span>tape, var_list<span class="op">=</span><span class="va">self</span>.critic.trainable_variables)</span></code></pre></div>
<p>… Great. <code>current_Q1</code> / <code>current_Q2</code> have shapes <span class="math inline">(batch size, 1)</span>,
which is compatible with <span class="math inline">(batch size, batch size)</span>. So they get
auto-broadcast… and <code>reduce_mean</code> gladly reduces the matrix of squared errors
to a scalar mean. Awesome. Thank you. I’m so glad Autobroadcast Man and Default
Options Girl arrived and saved the day.</p>
<h3 id="learnings">Learnings</h3>
<p>So what did I learn today?</p>
<p>It’s always, <em>always</em>, <strong>ALWAYS</strong> those goddamn tensor shapes.</p>
<p>Put <a href="https://www.tensorflow.org/api_docs/python/tf/debugging/assert_shapes"><code>tf.debugging.assert_shapes</code></a>
f***ing <strong>everywhere</strong>.</p>
<p>And probably write unit tests, too. I probably won’t be bothered to do that
anyway because writing unit tests for code that has complex optimizers and
neural nets is annoying.</p>
<p>And maybe I should be using operators that don’t auto-broadcast, or something
that doesn’t allow me to mistakenly vector-vector-to-matrix multiply when I
mean to vector-vector-elementwise multiply.</p>
<p>I’m not done being annoyed and sour about this but I’m done ranting about it
here. Ciao, see you next time I kill a few weeks on another mistake this stupid.
This is the point of all those “Rai’s ML mistakes” posts. I write these
algorithms so I can grok them, and I headsmash the keyboard this way because
I hope now I’ll remember with a bit more clarity and immediacy: “It’s always the
goddamn tensor shapes”.</p>
<p>My fixed code is <a href="https://gitlab.com/agentydragon/rl-experiments/-/merge_requests/8">on my GitLab</a>, and here’s the obligatory scoot-scoot:</p>
<figure>
<video controls loop autoplay>
<source src="/static/2021-10-18-half-cheetah-216.mp4" type="video/mp4">
</video>
<div>
<p>A half-cheetah doing the scoot-scoot after 216 episodes of training.</p>
</div>
</figure>
<h3 id="update-mujoco-half-cheetah">Update: MuJoCo half-cheetah</h3>
<p>So, since MuJoCo is now open-source, I tried it and got the MuJoCo environment
<code>HalfCheetah-v2</code> also working. Nice :)</p>
<figure>
<video controls loop autoplay>
<source src="/static/2021-10-19-half-cheetah-mujoco.mp4" type="video/mp4">
</video>
<div>
<p>Trained MuJoCo half-cheetah. Episode reward is 5330.</p>
</div>
</figure>]]></description>
    <pubDate>Mon, 18 Oct 2021 00:00:00 UT</pubDate>
    <guid>http://agentydragon.com/posts/2021-10-18-rai-ml-mistakes-3.html</guid>
    <dc:creator>Rai</dc:creator>
</item>
<item>
    <title>Rai&#39;s ML mistakes, part 2 of ∞</title>
    <link>http://agentydragon.com/posts/2021-01-04-rai-ml-mistakes-2.html</link>
    <description><![CDATA[<p>Continuing my list of ML mistakes from <a href="/posts/2020-12-31-cartpole-q-learning.html">last
time</a>, here’s:</p>
<h1 id="rais-ml-mistake-4-too-much-autograd">Rai’s ML mistake #4: Too much autograd</h1>
<p>So here I am, writing an agent for
<a href="https://gym.openai.com/envs/LunarLander-v2/">LunarLander-v2</a>.
I’m using Q-learning. I approximate <span class="math inline"><em>q</em><sub>*</sub>(<em>s</em>, <em>a</em>)</span> as
<span class="math inline">q̂<sub><em>w</em></sub>(<em>s</em>, <em>a</em>)</span> with a neural network, taking a vector representing
the state, and outputting one approximate action value per output.
The neural network is trained to minimize squared TD error on the policy the
agent’s running, which is <span class="math inline"><em>ε</em></span>-greedy with respect to
<span class="math inline">q̂</span>:</p>
<p><span class="math display">$$
\begin{align*}
\require{extpfeil}
\ell(w) &amp;= \mathop{\mathbb{E}}\limits_{(S \xrightarrow{A} R,S') \sim \mathrm{greedy}(\mathrm{\hat{q}}_w)}
\left[ \left(\mathrm{\hat{q}}_w(S, A) - (R + \gamma \max_{A'} \mathrm{\hat{q}}_w(S',A')) \right)^2 \right] \\
\text{output } &amp;\arg\min_w \ell(w)
\end{align*}
$$</span></p>
<h2 id="not-quite-off-policy">Not quite off-policy</h2>
<p>One note about the “policity” of this method.</p>
<p>Tabular Q-learning without function approximation is off-policy - you learn
about <span class="math inline"><em>π</em><sub>*</sub></span> from experience <span class="math inline">(<em>S</em>→<sub><em>A</em></sub><em>R</em>, <em>S</em><sup>′</sup>)</span> sampled from any
(sane™) policy. You just keep updating <span class="math inline">q̂(<em>S</em>, <em>A</em>)</span> towards
<span class="math inline"><em>R</em> + <em>γ</em> ⋅ max<sub><em>A</em><sup>′</sup></sub>q̂(<em>S</em><sup>′</sup>, <em>A</em><sup>′</sup>)</span>, and to <span class="math inline">max </span> is there because you want
to learn about the optimal policy.</p>
<p>But note that in <span class="math inline"><em>ℓ</em>(<em>w</em>)</span>, the experience <span class="math inline">(<em>S</em>→<sub><em>A</em></sub><em>R</em>, <em>S</em><sup>′</sup>)</span> is
sampled from the policy <span class="math inline">greedy(q̂<sub><em>w</em></sub>)</span>.
We need to expect <em>over a policy</em>, because we’re using function approximation,
so presumably we cannot learn a <span class="math inline"><em>w</em></span> which would make <span class="math inline">q̂<sub><em>w</em></sub></span>
exactly fit <span class="math inline"><em>q</em><sub>*</sub></span>. So we have to pick out battles for how well do we
approximate <span class="math inline"><em>q</em><sub>*</sub></span> - we care about approximating it closely for
states and actions actually visited by the estimation policy.</p>
<p>Instead of assuming that we can sample <span class="math inline">(<em>S</em>→<sub><em>A</em></sub><em>R</em>, <em>S</em><sup>′</sup>)</span> from
<span class="math inline">greedy(q̂<sub><em>w</em></sub>)</span> (so that we can approximate the expected
squared TD error over it), I guess you could use the general importance sampling
recipe to get rid of that:</p>
<p><span class="math display">$$
\mathop{\mathbb{E}}_\limits{X\sim \pi}[\mathrm{f}(X)] =
\mathop{\mathbb{E}}_\limits{X\sim b}\left[\mathrm{f}(X) \cdot \frac{\pi(X)}{b(X)}\right]
$$</span></p>
<h2 id="semi-gradient">Semi-gradient</h2>
<p>So, we want to minimize <span class="math inline"><em>ℓ</em></span>.</p>
<p>Note that <span class="math inline"><em>ℓ</em></span> depends on <span class="math inline"><em>w</em></span> (via <span class="math inline">q̂<sub><em>w</em></sub></span>) in 3 places:</p>
<ol type="1">
<li>In <span class="math inline">q̂<sub><em>w</em></sub>(<em>S</em>, <em>A</em>)</span>, which we are trying to nudge to move to the
right place,</li>
<li>in <span class="math inline"><em>R</em> + <em>γ</em>max<sub><em>A</em><sup>′</sup></sub>q̂<sub><em>w</em></sub>(<em>S</em><sup>′</sup>, <em>A</em><sup>′</sup>)</span>, which is a sample
from a distribution centered on
<span class="math inline"><em>q</em><sub>greedy(q̂<sub><em>w</em></sub>)</sub>(<em>S</em>, <em>A</em>)</span>,</li>
<li>and in the distribution we’re taking the expectation on.</li>
</ol>
<p>In practice, we hold (2) and (3) constant, and in one optimization step, we
wiggle <span class="math inline"><em>w</em></span> only to move <span class="math inline">q̂<sub><em>w</em></sub>(<em>S</em>, <em>A</em>)</span> closer to targets.
That means that in our gradient, we are ignoring the dependency of (2) and (3)
on the <span class="math inline"><em>w</em></span> that we are optimizing, which makes this not a full gradient method,
but instead a <em>semi-gradient</em> method.</p>
<h2 id="experience-replay">Experience replay</h2>
<p>My first shot at this agent just learned from 1 step (sampled from
<span class="math inline"><em>ε</em></span>-greedy policy for <span class="math inline">q̂<sub><em>w</em></sub></span>) at a time. It worked
in the sense that it ended up learning a policy close enough to “solving the
environment”. (The environment says the “solved reward” is 200. I got maybe like
150-180 over 100 episodes, so not quite there, but it’s close enough for me to
say “meh, I’ll wiggle a few hyperparameters and get there”.)</p>
<p>But to learn a fair policy, it took the agent about 10 000 episodes, and the
per-episode total reward over time made a spiky ugly graph:</p>
<figure>
<img src="/static/2020-12-31-total_reward.svg" style="height: 400px;"
     title="Total reward per episode graph">
</figure>
<p>I don’t like that it takes all of 10 000 episodes, and I don’t like how spiky
and ugly the chart is.</p>
<p>Experience replay means we store a bunch of experience
<span class="math inline">(<em>S</em>→<sub><em>A</em></sub><em>R</em>, <em>S</em><sup>′</sup>)<sub>1, 2, …</sub></span> in a buffer, and instead of updating
<span class="math inline"><em>w</em></span> by some gradient-based optimization method (I used ADAM) to minimize squared
TD error one step at a time, we update it to minimize squared TD error over the
whole buffer, a bunch of steps at a time.</p>
<p>Experience replay should make learning more sample-efficient (so it should need
less than 10 000 episodes). Also, it should reduce one source of “spikiness
and ugliness” in the chart, because the chart will be doing step updates on
a larger batch. Making the batch larger should reduce the variance of the
updates.</p>
<h2 id="broken-code">Broken code</h2>
<p>So, here’s how I initially implemented one step of the update.
<code>self.experience_{prestates, actions, rewards, poststates, done}</code> holds
the experience buffer (<span class="math inline"><em>S</em>, <em>A</em>, <em>R</em>, <em>S</em><sup>′</sup></span> respectively for observed transition
<span class="math inline"><em>S</em>→<sub><em>A</em></sub><em>R</em>, <em>S</em><sup>′</sup></span>, plus flag to signal end of episode).</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="at">@tf.function</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> q_update(<span class="va">self</span>):</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>  <span class="cf">with</span> tf.GradientTape() <span class="im">as</span> tape:</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="co"># \max_{A&#39;} \hat{q}(S&#39;, A&#39;)</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    best_next_action_value <span class="op">=</span> tf.reduce_max(</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>        <span class="va">self</span>.q_net(<span class="va">self</span>.experience_poststates), axis<span class="op">=</span><span class="dv">1</span>)</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>    <span class="co"># If episode ends after this step, the environment will only give us</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>    <span class="co"># one step of reward and nothing more. Otherwise, the value of the next</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>    <span class="co"># state S&#39; is best_next_action_value.</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>    next_state_value <span class="op">=</span> tf.where(</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>        <span class="va">self</span>.experience_done,</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>        tf.zeros_like(best_next_action_value),</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>        best_next_action_value)</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a>    targets <span class="op">=</span> <span class="va">self</span>.experience_rewards <span class="op">+</span> <span class="va">self</span>.discount_rate <span class="op">*</span> next_state_value</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>    <span class="co"># For all states S_i in the experience buffer, compute Q(S_i, *) for all</span></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>    <span class="co"># actions.</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a>    next_action_values <span class="op">=</span> <span class="va">self</span>.q_net(<span class="va">self</span>.experience_prestates)</span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a>    <span class="co"># Select Q(S_i, A_i) where A_i corresponds to the recorded experience</span></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a>    <span class="co"># S_i --(A_i)--&gt; R_i, S&#39;_i, done_i.</span></span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a>    indices <span class="op">=</span> tf.stack(</span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a>      (tf.<span class="bu">range</span>(<span class="va">self</span>.experience_buffer_size), <span class="va">self</span>.experience_actions),</span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a>      axis<span class="op">=</span><span class="dv">1</span>)</span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a>    values_of_selected_actions <span class="op">=</span> tf.gather_nd(next_action_values, indices)</span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a>    loss <span class="op">=</span> tf.keras.losses.MeanSquaredError()(</span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a>        values_of_selected_actions, targets)</span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-29"><a href="#cb1-29" aria-hidden="true" tabindex="-1"></a>  grad <span class="op">=</span> tape.gradient(loss, <span class="va">self</span>.q_net.trainable_variables)</span>
<span id="cb1-30"><a href="#cb1-30" aria-hidden="true" tabindex="-1"></a>  <span class="va">self</span>.optimizer.apply_gradients(<span class="bu">zip</span>(grad, <span class="va">self</span>.q_net.trainable_variables))</span></code></pre></div>
<p>What’s wrong here?</p>
<p>The symptom is that the policy is not improving. The total reward per episode
is just oscillating.</p>
<figure>
<img src="/static/2020-sticker-hmm.png" title="Hmm">
</figure>
<h2 id="the-problem">The problem</h2>
<p>Remember how I said it’s a <em>semi-gradient</em> method?</p>
<p>Here’s the fix:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="at">@tf.function</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> q_update(<span class="va">self</span>):</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  <span class="co"># \max_{A&#39;} \hat{q}(S&#39;, A&#39;)</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  best_next_action_value <span class="op">=</span> tf.reduce_max(</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>      <span class="va">self</span>.q_net(<span class="va">self</span>.experience_poststates), axis<span class="op">=</span><span class="dv">1</span>)</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>  <span class="co"># If episode ends after this step, the environment will only give us</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>  <span class="co"># one step of reward and nothing more. Otherwise, the value of the next</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>  <span class="co"># state S&#39; is best_next_action_value.</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>  next_state_value <span class="op">=</span> tf.where(</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>      <span class="va">self</span>.experience_done,</span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>      tf.zeros_like(best_next_action_value),</span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>      best_next_action_value)</span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>  targets <span class="op">=</span> <span class="va">self</span>.experience_rewards <span class="op">+</span> <span class="va">self</span>.discount_rate <span class="op">*</span> next_state_value</span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a>  <span class="cf">with</span> tf.GradientTape() <span class="im">as</span> tape:</span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a>    <span class="co"># For all states S_i in the experience buffer, compute Q(S_i, *) for all</span></span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a>    <span class="co"># actions.</span></span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a>    next_action_values <span class="op">=</span> <span class="va">self</span>.q_net(<span class="va">self</span>.experience_prestates)</span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a>    <span class="co"># Select Q(S_i, A_i) where A_i corresponds to the recorded experience</span></span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a>    <span class="co"># S_i --(A_i)--&gt; R_i, S&#39;_i, done_i.</span></span>
<span id="cb2-21"><a href="#cb2-21" aria-hidden="true" tabindex="-1"></a>    indices <span class="op">=</span> tf.stack(</span>
<span id="cb2-22"><a href="#cb2-22" aria-hidden="true" tabindex="-1"></a>      (tf.<span class="bu">range</span>(<span class="va">self</span>.experience_buffer_size), <span class="va">self</span>.experience_actions),</span>
<span id="cb2-23"><a href="#cb2-23" aria-hidden="true" tabindex="-1"></a>      axis<span class="op">=</span><span class="dv">1</span>)</span>
<span id="cb2-24"><a href="#cb2-24" aria-hidden="true" tabindex="-1"></a>    values_of_selected_actions <span class="op">=</span> tf.gather_nd(next_action_values, indices)</span>
<span id="cb2-25"><a href="#cb2-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-26"><a href="#cb2-26" aria-hidden="true" tabindex="-1"></a>    loss <span class="op">=</span> tf.keras.losses.MeanSquaredError()(</span>
<span id="cb2-27"><a href="#cb2-27" aria-hidden="true" tabindex="-1"></a>        values_of_selected_actions, targets)</span>
<span id="cb2-28"><a href="#cb2-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-29"><a href="#cb2-29" aria-hidden="true" tabindex="-1"></a>  grad <span class="op">=</span> tape.gradient(loss, <span class="va">self</span>.q_net.trainable_variables)</span>
<span id="cb2-30"><a href="#cb2-30" aria-hidden="true" tabindex="-1"></a>  <span class="va">self</span>.optimizer.apply_gradients(<span class="bu">zip</span>(grad, <span class="va">self</span>.q_net.trainable_variables))</span></code></pre></div>
<p>So, what was the problem?</p>
<p>The code calls the Q network twice: once to compute the targets (<span class="math inline"><em>R</em> + <em>γ</em> ⋅ max<sub><em>A</em><sup>′</sup></sub>q̂<sub><em>w</em></sub>(<em>S</em><sup>′</sup>, <em>A</em><sup>′</sup>)</span>), once to compute
<span class="math inline">q̂<sub><em>w</em></sub>(<em>S</em>, <em>A</em>)</span>. Then, we will compute a loss, and we will take its
partial <em>“semi-derivative”</em> with respect to <span class="math inline"><em>w</em></span>, and apply the gradient to
bring <span class="math inline">q̂<sub><em>w</em></sub>(<em>S</em>, <em>A</em>)</span> closer to the target.</p>
<p>The problem was: I also put the target computation into <code>GradientTape</code> scope,
so the optimization was given the freedom to change not just
<span class="math inline">q̂<sub><em>w</em></sub>(<em>S</em>, <em>A</em>)</span>, but <em>also</em> <span class="math inline">q̂<sub><em>w</em></sub>(<em>S</em><sup>′</sup>, <em>A</em><sup>′</sup>)</span>.
So the fix was just to move computing the targets out of the <code>GradientTape</code>
scope.</p>
<p>I looked at this code basically non-stop for 2 hours, and I realized the error
when I took a break and talked with a friend.</p>
<figure>
<img src="/static/2020-sticker-ded.png" title="_(x.x)_   <-- ded">
</figure>
<h2 id="pet-peeve-47-math-typesetting">Pet peeve #47: math typesetting</h2>
<p><em>The full list of previous 46 pet peeves will be provided on request, subject
to a reasonable processing fee.</em></p>
<h3 id="mathjax-hat-and-mathrm">MathJax, <code>\hat</code> and <code>\mathrm</code></h3>
<p><span class="math inline">q̂</span> is a function (of <span class="math inline"><em>w</em>, <em>S</em>, <em>A</em></span>), not a variable, so it
shouldn’t be typeset in italic. I tried using <code>\hat{\mathrm{q}}_w</code>. I believe
that works in LaTeX but turns out that MathJax is not willing to render it
(<span class="math inline">$\hat{\mathrm{q}}$</span>). But <code>\mathrm{\hat{q}}</code> is perfectly fine:
<span class="math inline">q̂</span>. But <code>\mathrm{\hat{q}}_w</code> is perfectly fine:
<span class="math inline">q̂<sub><em>w</em></sub></span>.</p>
<h3 id="mathjax-and-inline-xrightarrow">MathJax and inline <code>\xrightarrow</code></h3>
<p>Also, my MathJax doesn’t seem to understand <code>\xrightarrow</code> in inline equations.
That’s a shame, because <code>S \xrightarrow{A} R, S'</code> is more readable than
<code>S \rightarrow_A R, S'</code> (<span class="math inline"><em>S</em>→<sub><em>A</em></sub><em>R</em>, <em>S</em><sup>′</sup></span>), which I used here instead
(in inline equations). It looks like this:
<span class="math display">$$S \xrightarrow{A} R, S'$$</span>
Let me know if you know what’s up with those MathJax things.
I wonder if it’s MathJax being wrong, or me sucking at LaTeX.</p>
<h3 id="why-im-a-math-typesetting-snob">Why I’m a math typesetting snob</h3>
<p>Typesetting things that aren’t variables as if they were variables really bugs
me, because it makes the formulas really ugly. And the font you use to typeset
a math thing is a very useful hint for the reader about what sort of object it
is. I learned a bit about it when volunteering as a
<a href="https://ksp.mff.cuni.cz/">KSP</a> organizer - KSP is full of math snobs. Compare:</p>
<p><span class="math display">$$
\begin{align*}
\mathrm{Loss}(w) = \sum_i (\mathrm{f}(x_i) - y_i)^2 \\
Loss(w) = \sum_i (f(x_i) - y_i)^2
\end{align*}
$$</span></p>
<p>In the second one, it takes a bit of processing to understand that <span class="math inline"><em>L</em><em>o</em><em>s</em><em>s</em></span> is
not a multiplication (<span class="math inline"><em>L</em> ⋅ <em>o</em> ⋅ <em>s</em> ⋅ <em>s</em></span>), and that <span class="math inline"><em>f</em>(<em>x</em><sub><em>i</em></sub>)</span> is
function application.</p>
<p>If you want to read more, you can take a look at <a href="https://en.wikipedia.org/wiki/Typographical_conventions_in_mathematical_formulae">Typographical conventions in mathematical
formulae on Wikipedia</a>.
Or maybe some LaTeX / TeX books or reference material might have a lot of
explanations, like “use this in these situations”. And also good math books
often have a large table at the front which explains used conventions, like
“<span class="math inline"><strong>w</strong></span> is a vector, <span class="math inline">X</span> is a matrix, <span class="math inline">f</span> is a function,
…”</p>
<figure>
<img src="https://imgs.xkcd.com/comics/kerning.png" style="height: 400px;"
     title="XKCD 1015 (Kerning)" alt="XKCD comic about kerning">
<div>
<p><a href="https://xkcd.com/1015/">https://xkcd.com/1015/</a>
<br>
Now you know about ugly errors in math typesetting, and if you Google it,
also about bad kerning. You’re welcome, pass it along.</p>
</div>
</figure>
<figure>
<img src="/static/2020-sticker-mlem.png" title="Mlem!" alt="Rai sticker with tongue out">
</figure>]]></description>
    <pubDate>Mon, 04 Jan 2021 00:00:00 UT</pubDate>
    <guid>http://agentydragon.com/posts/2021-01-04-rai-ml-mistakes-2.html</guid>
    <dc:creator>Rai</dc:creator>
</item>
<item>
    <title>Cartpole Q-learning</title>
    <link>http://agentydragon.com/posts/2020-12-31-cartpole-q-learning.html</link>
    <description><![CDATA[<p>Recently I’ve been working on skilling up on reinforcement learning,
particularly practice. I’m currently on the last course of the
<a href="https://www.coursera.org/specializations/reinforcement-learning">Reinforcement Learning specialization</a>
from University of Alberta on Coursera. The last piece of the course is about
solving the <a href="https://gym.openai.com/envs/LunarLander-v2/">Lunar Lander</a>
environment. I’ve been trying to solve it on my own first before going through
the labs, so that I can learn things deeper and experiment.</p>
<p>I’ve tried implementing an actor-critic agent. The actor is a feed-forward
neural network specifying a parameterized policy <span class="math inline"><em>π</em><sub><em>θ</em></sub></span>. The network’s
input is a representation of the state, and it has one output per action.
The policy is a softmax over these outputs. I tried a critic for predicting
both <span class="math inline"><em>v̂</em><sub><em>w</em></sub></span>, and <span class="math inline"><em>q̂</em><sub><em>w</em></sub></span>.</p>
<p>I’ve not had good luck getting this to work so far. At one point I got the agent
to fly above the surface (without landing), but then later I edited the code
somewhat, aaaaand it was gone.</p>
<p>I stared a bunch into my update equations, but have not been able to find any
obvious errors. I used TensorFlow’s
<a href="https://www.tensorflow.org/tensorboard/hyperparameter_tuning_with_hparams">HParams</a>
to try to tune all the hyperparameters, like actor learning rate, critic
learning rate, and learning rate for the average reward. (I wrote it attempting
to use the continuing average reward formulation.)</p>
<p>I decided to first try a simpler environment, <a href="https://gym.openai.com/envs/CartPole-v0/">CartPole</a>.</p>
<p>In the end, I managed to solve it a couple hours back.</p>
<p>In the implementation, I’ve made a couple mistakes and observations, which I
want to note down.</p>
<h2 id="colab-notebook-with-my-cartpole-agent">Colab notebook with my CartPole agent</h2>
<p>Here’s my notebook if you want to play around:</p>
<p><a href="https://colab.research.google.com/github/agentydragon/agentydragon.github.io/blob/devel/notebooks/2020-12-31-cartpole-q-learning.ipynb" target="_parent">
<img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab">
</a></p>
<h2 id="rais-ml-mistake-1-short-episodic-environments-can-use-high-γ">Rai’s ML mistake #1: Short episodic environments can use high γ</h2>
<p>I initially wrote my code to use a discount rate of 0.9. On
<a href="https://gym.openai.com/evaluations/eval_EIcM1ZBnQW2LBaFN6FY65g/">n1try’s solution</a>
that I found on <a href="https://gym.openai.com/envs/CartPole-v0/">the leaderboard</a>
(unfortunately not sure which one it was), the discount rate was actually set
to 1.</p>
<p>I suspect I might have set the discount rate too low. The CartPole environment
has episodes which have length of only up to ~500, with 1 unit of reward per
step.</p>
<p>If you have a discount rate γ and an average per-step reward of <span class="math inline"><em>r</em></span>,
then in an infinite environment, the value of a state will be something like:
<span class="math display">$$ \frac{r}{1-\gamma} = r + r \cdot \gamma + r \cdot \gamma^2 $$</span>
Knowing this, I was a worried that if I set <span class="math inline"><em>γ</em></span> too high, the targets
for the Q network to learn would have a high variance. But I forgot that the
environment had only like ~500 steps, so setting <span class="math inline"><em>γ</em> = 1</span> would be alright
in this case.</p>
<p><em>Lesson learned</em>: I need to keep in mind the environment’s characteristics, in
particular how long are the episodes and how high total rewards can I expect.</p>
<h2 id="rais-ml-mistake-2-too-little-exploration">Rai’s ML mistake #2: Too little exploration</h2>
<p>The algorithm that ended up working for me was Q-learning (with function
approximation by a small neural network). I selected actions
<span class="math inline"><em>ε</em></span>-greedily, with <span class="math inline"><em>ε</em></span> set to 0.02, so ~2% chance of
random moves.</p>
<p>Looking at some solutions of the environment that I found, they had much higher
exploration rates. Some that I saw had 100% random actions initially, and had
it then decay. And the particular solution I was looking at set the minimal
exploration rate, after all the decay, to <em>10%</em> - 5x more than I had.</p>
<p>I think my code found a policy that “solves” the environment faster when I put
in 10% exploration.</p>
<h2 id="rais-ml-mistake-3-evaluation-interfering-with-training">Rai’s ML mistake #3: Evaluation interfering with training</h2>
<p>I ran my algorithm on 10k episodes, and every 1k episodes, I ran the code in
“greedy mode” (i.e., no random actions) and recorded average performance on 100
episodes. I did that because my Q-learning implementation was executing an
<span class="math inline"><em>ε</em></span>-soft policy, which might be worse than the greedy policy that
it’s learning. I don’t know how “fragile” the CartPole environment is (i.e.,
how much worse the total reward per episode gets if I force an agent to take
some amount of random actions), but I wanted to rule it out as a source of
errors.</p>
<p>I implemented the evaluation by just adding a flag <code>train: bool = True</code> to
the agent’s functions. If <code>train</code> was <code>False</code>, I’d skip all the update steps
and select actions greedily.</p>
<p>Unfortunately, I ended up making a mistake and I forgot to add the condition
around one branch of code - updating the Q network after a final step (i.e.,
when the agent receives a last reward and the episode ends).</p>
<p>As a result, the agent ended up executing ~100 incorrect updates (based on
an incorrect last action and state, towards the final reward), one incorrect
update per evaluation episode.</p>
<p><strong>Lesson learned</strong>: Double check my code? Maybe even test my code? Not sure
how to learn from this :/</p>
<h2 id="forgetting-when-learned-too-much"><em>Forgetting when learned too much‽‽</em></h2>
<figure>
<img src="/static/2020-12-31-total_reward.svg" style="height: 400px;"
     title="Total reward per episode graph" alt="Graph of total reward per episode">
<div>
<p><a href="https://www.youtube.com/watch?v=sIlNIVXpIns"><em>𝄞 ♫ Look at this graaaph♫ 𝄻</em></a></p>
</div>
</figure>
<p>So, until episode maybe 400 or so, nothing much is happening.
Then until about step 1800, it’s sorta doing something but could be better.
Then at around step 1800, it finds a good policy, and returns are nice.
Basically, problem solved.</p>
<p>Then I train it for a bit longer, and at episode ~2.2k… for some reason
performance goes WAY down for about 300 or so episodes. It’s as bad as if the
network forgot everything it learned before.</p>
<p>Then, after a while (at about 2700), it quickly climbs back up to good
performance. On the full graph with 10k episodes, this cycle would repeat maybe
every 2000-3000 episodes. Wat.</p>
<p>I have no idea what’s going on here. Maybe one sort-of ideas, but I have not
tested it, and I have a relatively low degree of confidence in it.</p>
<p>Maybe for some reason the critic might overfit in some way that makes it behave
badly on some early action. Maybe it’s trying to learn some relatively “late
game” stuff, and the update that happens ends up screwing some early behavior,
so the agent then has to spend a bunch of episodes learning the right early
behavior again. The policy changes in a non-continuous way, so if some early
decision switches to do the wrong thing, the agent will then have to follow a
bunch of wrong trajectories until the one-step Q updates end up bubbling up to
the initial wrong decision. I guess this might be somewhat mitigated by using
eligibility traces so that updates bubble up faster, or by using actor-critic
with soft policies.</p>
<p>Another potential reason for having a really bad episode might be if the agent
happens to pick a random action (with probability <span class="math inline"><em>ε</em></span>) at an early
point where the pole is unstable and very easy to mess up. And then it can’t
recover from that error. But that explanation isn’t supported by how wide these
areas of low episode returns are. It might explain maybe one sporadic bad
episode, but not a whole bunch of them after each other.</p>
<h2 id="next-steps">Next steps</h2>
<p>Now that I got a CartPole agent running, I’ll come back to the Lunar Lander
environment. I’ll first try solving it again with a Q network. I could probably
similarly get away with not discounting rewards at all (<span class="math inline"><em>γ</em> = 1</span>).</p>
<p>Also I’d like to implement experience replay to make this more sample-efficient.</p>
<p>If that ends up working, I still want to get actor-critic working.</p>
<p>Obligatory scoot-scoot:</p>
<video controls loop autoplay>
<source src="/static/2020-12-31-cartpole.mp4" type="video/mp4">
</video>]]></description>
    <pubDate>Thu, 31 Dec 2020 00:00:00 UT</pubDate>
    <guid>http://agentydragon.com/posts/2020-12-31-cartpole-q-learning.html</guid>
    <dc:creator>Rai</dc:creator>
</item>
<item>
    <title>Playing with AI</title>
    <link>http://agentydragon.com/posts/2020-11-23-playing-with-ai.html</link>
    <description><![CDATA[<style>
.highlight-on .generated {
  background-color: yellow;
}
</style>
<div id="playing-with-ai-toggle-highlight">
<p>The last about 2 weeks I have taken some time to finally get practical AI experience, so I’m running TensorFlow and all, and making lots of Anki cards.</p>
<h2 id="sidenote-anki-for-programming-is-awesome">Sidenote: Anki for programming is awesome</h2>
<p>By the way, Anki cards are so useful for learning how to use libraries fluently without looking things up it’s ridiculous.</p>
<p>For example, thanks to a bunch of Clozes, if you give me a CSV dataset, I can define, train and evaluate a network using the Keras functional API, without looking up anything. It means I get quickly to the interesting stuff, and don’t waste 80% of my time looking up things I already looked up before. If I see that I’m looking something up for, say, the 3rd time, I just make the cards that would have helped me, and that might be the last time I look that up.</p>
<h2 id="can-a-computer-now-write-similarly-well-to-me">Can a computer now write similarly well to me?</h2>
<p>I saw very interesting examples of how well modern language model can generate text, and I’m wondering how close can I be emulated at this point. Depending on how good a language model is, it could replace the copywriting industry and make a 1000% profit margin on top. And I sort of wonder how close it is to me. Though I’m not sure what would I do if I learned my writing can be replaced. I guess I would continue enjoying it, because it feels like I’m “expressing my soul”, and also it’s useful to sharpen my thoughts. If my understanding of something is foggy, when I write things down, I can much more easily spot where exactly I’m confused, or what’s a question I can’t answer, or to realize “hey actually I made a logical error, I no longer believe the conclusion I wanted to justify”.</p>
<p>But I guess I could then just as well automate this whole website thing. I put too little stuff on it anyway - if I just got myself to do the work and put my ideas into polished-ish writing, I would write so much more.</p>
<p>I guess I’d still write also because there’s some pleasure in someone telling me “oh by the way I read your article the other day, thanks for that tip”. And I’d not get that from text I had a Transformer write. Even if it did manage to write a thing as good as me or better, so that people would compliment me for “hey nice thing on your website”, it would still make me go a bit “nice!”, but it would ring a little hollow, I guess. Praise for work that isn’t mine. But then, did the Transformer really “work” for it? Also the work coming up with the architecture and implementing it and making <a href="https://transformer.huggingface.co/doc/distil-gpt2">Write With Transformer</a> belongs to many many other people.</p>
<h2 id="experiment">Experiment</h2>
<p>So I’m going to try it in this article. I will start slowly replacing words by top suggestions (<a href="https://transformer.huggingface.co/doc/distil-gpt2">Write With Transformer distil-gpt2</a>). I’ll start with maybe 90% me, 10% Transformer, and by the time I finish writing this, it’ll be 100% Transformer. And I won’t, at least for now, tell you which parts are me and which are the generator. That way I won’t have just a test of “I can / cannot be replaced by a Transformer”, but by asking people which sentences were from me and which were from the Transformer, I’ll get more gradual information about the point at which today’s Transformers can replace me. From what I read, models like GPT-3 are able to convincingly copy “surface style”, and they are able to make simple inferences, but they might make mistakes.</p>
<p>By the way, the footer of <a href="https://transformer.huggingface.co/">Write With Transformer</a> says: “It is to writing what calculators are to calculus.” And that’s a nice sentence in that, on a shallow reading, it sounds like a positive comparison. “It is to writing what [things for doing mathematics] are to [field of mathematics].” But I never had a calculator that was any good for calculus. I never saw a calculator with a “derive by x” or “indefinite integral dx”. Though now I also wonder why no calculator has it. It would be so useful, and not that hard to implement. Mathematica can integrate most of what you throw at it! And algorithms for integrating broad classes of functions are also totally a thing in literature!</p>
<p>“It is to writing what Mathematica is to calculus”? Sure. That sounds useful. A
tool that can solve 90% of practical problems. Neat. “It is to writing what
calculators are to calculus”? AAA, <span class="generated">you know what? I’ve been</span>
in many situations where you can have all the calculators you want, and they won’t save you from an ugly enough integral.</p>
<p>It sounds like one of those “proverb insults”, like “you must be at the top of the Gauss curve”.</p>
<p>Also the test of making the Transformer generate parts of the article can show
if it could be useful as a computer-assisted authoring tool.</p>
<p><span class="generated">I wonder what a problem is</span> there with making the
jobs of some people much easier with AI like this. For example, I have a virtual assistant, and I think it should be possible to augment them with a Transformer. You train the Transformer on chats of the highest rated virtual assistants with customers, and annotate the conversations with times when the virtual assistant had to do something. Then you integrate that Transformer into, say, Google Chat, and add some quick shortcuts, like “Tab” for “autocomplete”. I fully expect we should be able to mostly automate conversation like “hello, I hope you’re having a nice day”.</p>
<h2 id="motivation-to-do-my-own-thing">Motivation to do my own thing</h2>
<p>By the way, the other day I stumbled on <a href="https://betterexplained.com/">Better Explained</a>, and the author has a great article: Surviving (and thriving) on your own: Know Thyself.</p>
<p>And this <span class="generated">is the best of</span> sources of motivation to do my own thing that I’ve
seen in a while. This article made me realize that yes, there are actually things I want to do. I can just look at my TODO list in my Roam Research database. If I only had the most productive ~8 hours of my time available for all the things I want to learn and make.</p>
<p>So I’ve been considering going part-time at Google. <span class="generated">For some time I’ve found
that I</span> just can’t muster the energy to be consistently productive after work. And switching contexts is expensive, and gets much more expensive when you switch to a context you haven’t seen for a couple days. Like what might happen if one day I do 4 hours of Colab experimentation, then the next week I don’t have spare energy after work, and then a week later I open the notebook again, read and go 🤨. It helps to keep notes, and not half-ass the code style too badly, but there’s a trade-off between “help future-me quickly get back to productivity on this task” and “spend energy making progress”.</p>
<p>Also, with stocks having recovered from COVID and with Bitcoin currently going through Another One Of Those, I’ve been closely watching how much longer until financial independence.</p>
<p>I am not at the point of “I’ll be able to live on this on my current standard
forever”. But at a lower standard? Like moving back to Czech Republic? For some
values of “lower standard”, yes. And at some some point, the marginal expected
gains from a higher monthly budget will become dominated by the gains of having
more free time. And that <span class="generated">makes</span> it less and less rational to trade financial security for freedom.</p>
<p>And it’s not like I can’t make money doing things I want to do, either. There’s
such a huge spectrum. I can career-steer at Google more boldly, or go part-time
to do my own thing. Or even <span class="generated">if it’s a little less expensive</span> It might not be just the same freedom as reducing my hours, or working with a company that’s more closely aligned with my interests, or even quitting and doing my thing.</p>
<h2 id="still-experimenting">Still experimenting</h2>
<p>By the way, I’m still doing the GPT-3 <span class="generated">of this</span> article thing, with slowly
increasing density. And as I increase the density, I expect <span class="generated">it will be more
“efficient” to</span> just output tons of filler text. Filler is used in more
articles than crisp sentences that have only narrow meaning. If GPT-3 outputs
“What gives?” after any of my sentences, it will probably get a higher reward
than if it outputs “an endomorphism that is an isomorphism is an automorphism”,
just because it’s <span class="generated">all too hard to get some extra filler</span> into a place where it would not plausibly fit. What gives?</p>
<p>So expect this piece of writing to slowly degrade from saying something to
saying nothing in so many words. <span class="generated">And I’m doing it in my mind</span> expecting
some successive Tab press to generate either nonsense, or bullshit. I <span class="generated">m
going to continue to keep it in</span> line with making sense, as far as I’m able to express myself through filler text.</p>
<p><span class="generated">As a result, I think this will</span> sometimes end up stating things I don’t
actually endorse or believe. If that happens, I think I’ll also release the
“spoiler” (annotating the sections of this text that are generated) immediately,
so that I don’t accidentally say something like “I for one welcome our AI
overlords”. Well, <span class="generated">I am sure I will</span> As long as we as a species don’t fail the exam.</p>
<p><span class="generated">As far as I’m concerned, I will continue to put together articles on</span>
whatever interests me, to write code for problems I want solved, and to try to
improve my habits.
If the worldview and “values-view” and “lifestyle” that I want to implement
sticks, <span class="generated">then the same can be said for every</span> positive change it’s brought
the past couple weeks. <span class="generated">So, what’s really causing this</span> to slip away slowly?
Why have previous times when I held similar viewpoints slipped back into
routine? Maybe it’s just because it’s been the default for so long for me to
work <span class="generated">through the things I like</span> Slowly, <span class="generated">slowly</span> because of all the panic from ideas like “maybe I might have to leave work or start burning money instead of making it”.</p>
<p><span class="generated">And that will change with time</span> Or so would I hope. <span class="generated">Advertisements</span>
Popular media. <span class="generated">I will be keeping track</span> of all those attention-eaters. I don’t want to have a week where all my energy goes into a black hole.</p>
<p><span class="generated">I just want to keep going.</span> <span class="generated">I don’t want to get bored.</span>
Curiosity and learning things and solving problems for people is life.
<span class="generated">I just want to have something that will change and</span> <span class="generated">change and improve my
life.</span> In the direction of more aliveness and generating meaning.
<span class="generated">And to keep doing that I can only have one way or another</span></p>
<p><span class="generated">The bottom line is I don’t want to do things I want to do in the way that I
want to do.</span> Not with the current default setting where “want” tends to fall back.
<span class="generated">I want to do everything that I can to get my attention. But not with the
current default setting where “want” tends to fall back. I don’t want to get
bored. It’s just a matter of how much I like it.</span> In the moment.</p>
<p><span class="generated">
And for those of you out there who are interested in reading, please, like me, subscribe to my Facebook page and share your thoughts about the situation with me . (By email or to your friends , subscribe to my blog <a href="/">here</a>.) I will be taking the time and effort I have put into writing to make it easier to make things better for you.</p>
<p>And for those of you who aren’t interested in reading, please , like me, subscribe to my Facebook page and share your thoughts about the situation with me. (By email or to your friends, subscribe to my blog <a href="/">here</a>.) I will be taking the time and effort I have put into writing to make it easier to make things better for you.
</span></p>
<p>So <span class="generated"> I’m going to be publishing the article in the second week and I’ll be
posting the article in the second week and I ’ll be posting the article in the
second week and I’ll be posting the article in the second week and I</span> am
<span class="generated">posting the article in the second week and I will be posting the</span> rest <span class="generated">of
the post and I will be posting</span> to RSS and Atom and Hacker News maybe and
<span class="generated">maybe on the same page. If you like the content you see in</span> this website,
thanks, I really <span class="generated">appreciate you! You know the best way to make your next book
available is to check out my Blog</span></p>
<p>I might be repeating the experiment with different language models, to see which ones can keep going to a higher density without devolving into meaninglessness.
<span class="generated">But if you do it this way, and have more questions, I’ll post it again, and I
’ll be posting it</span> guess in which week. From what I gather from this experiment, looks like I might not be 100% obsolete just yet. Walk on warm sands.</p>
</div>
<button onclick="document.getElementById(&#39;playing-with-ai-toggle-highlight&#39;).classList.toggle(&#39;highlight-on&#39;); return true;">
Highlight generated text by AI
</button>]]></description>
    <pubDate>Mon, 23 Nov 2020 00:00:00 UT</pubDate>
    <guid>http://agentydragon.com/posts/2020-11-23-playing-with-ai.html</guid>
    <dc:creator>Rai</dc:creator>
</item>
<item>
    <title>The principle of no non-Apologies</title>
    <link>http://agentydragon.com/posts/2020-05-28-principle-of-no-non-apologies.html</link>
    <description><![CDATA[<p><strong>TL;DR:</strong> Principle of no non-Apologies: “Distinguish between saying I’m sorry
and apologizing. Don’t give non-Apologies.” Do not Apologize when you don’t
agree that you fucked up. When you fucked up, own the fuck-up and, if it’s
systematic, commit to reducing future fuck-ups.</p>
<h1 id="everyday-im-sorry-is-usually-not-an-apology">Everyday “I’m sorry” is usually not an Apology</h1>
<p>“I’m sorry” can be used in several ways.</p>
<p>One way is using it as a conciliatory gesture, basically saying “you’re stronger
than me, I submit, please don’t hurt me”. It’s one possible way I might react
when under threat by someone stronger making demands I don’t agree with.</p>
<p>Another way is to say “this was accidental, I didn’t intend to hurt you”, like
when you bump into someone when boarding your tram.</p>
<p>But when you use the words that way, you are not making an <em>Apology</em>. And it’s
useful to distinguish between these uses of “I’m sorry” andactual Apologies.</p>
<h1 id="apologies-and-non-apologies">Apologies and non-Apologies</h1>
<p>Courtesy of an unknown source that I can’t immediately recall, you are
<em>Apologizing</em> when you:</p>
<ol type="1">
<li>Communicate understanding that you behaved badly (and own responsibility for
it),</li>
<li>try to fix the negative consequences of that behavior, and</li>
<li>commit to work on not acting similarly in the future.</li>
</ol>
<p>An Apology which holds to this definition makes you vulnerable (because you are
open about the weakness that caused the behavior), and it’s not to be made
lightly, because of the commitment. It is also virtuous to own your mistakes or
systematic problems, and to work on them.</p>
<p>On the other hand, if you use the ritual apologetic words but do not meet these criteria, let’s call that a
<em>non-Apology</em>.</p>
<p>A prototypical example is “I’m sorry you feel that way”, which happens when a
sociopath in charge is forced by overwhelming force to “Apologize”.</p>
<p>“I’m sorry” that you tell your boss just to make them stop grilling you is also,
under my use of the word, a <em>non-Apology</em>.</p>
<p>So is, in many (but not all) cases, a “sorry I’m late” I might say when coming
to a meeting. Also the “bump into someone on the tram” example, and the “I yield
I’ll do what you demand” example.</p>
<p>(So, notice that I’m not saying non-Apologizes are morally bad. Some of them
are, but many are also just those tiny social rituals you need to do so you
make it clear to people you aren’t a dick.)</p>
<h1 id="principle-of-no-non-apologies">Principle of no non-Apologies</h1>
<p>My <em>principle of no non-Apologies</em> is two-part:</p>
<h2 id="distinguish-between-saying-im-sorry-and-apologizing">Distinguish between saying “I’m sorry” and Apologizing</h2>
<p>This first part I recommend adopting universally. Know the difference between
the social ritual that <em>evolved from small routinized Apologies</em> and actual
Apologies, and know which one you are doing at which time.</p>
<h2 id="dont-give-non-apologies">Don’t give non-Apologies</h2>
<p>This second part I apply to relationships into which I want to bring my whole
self, mostly my personal relationships, but also some work relationships.</p>
<p>Unfortunately, many of us are stuck in power differential relationships with
people who demand apologetic-sounding words, and there might be no better
solution than to yield. But still, it’s good to know that you are saying “I’m
sorry”, and not Apologizing. That way, you can appease without cognitive
dissonance.</p>
<p>But in relationships with mutual care and respect and compassion, it should make
sense that you shouldn’t be obliged to Apologize if you don’t agree that you did
anything wrong. When you feel pressed to apologize, your first instinct should
be to ask what you did wrong, and if there are different viewpoints, have a
conversation.</p>
<p>If your behavior is worthy of an apology, don’t stop at “I’m sorry”. Understand
why the behavior happened, and work to prevent it from causing more bad
consequences in the future.</p>
<h1 id="p.s.-generalizations">P.S.: Generalizations</h1>
<p>This is just one instance of a more general move of looking at some social
ritual (like apologizing) and looking at it a little “sideways”: getting back
in touch with the original meanings of the expressions used in it. Rituals and
words can lose meaning over time, and you can lose concepts when that happens.
If you want to see what it’s like to look at things that way, I’ve had a pretty
vivid experience of it after finishing Wittgenstein’s Tractatus.</p>]]></description>
    <pubDate>Thu, 28 May 2020 00:00:00 UT</pubDate>
    <guid>http://agentydragon.com/posts/2020-05-28-principle-of-no-non-apologies.html</guid>
    <dc:creator>Rai</dc:creator>
</item>
<item>
    <title>Growth mindset for better sex</title>
    <link>http://agentydragon.com/posts/2020-04-28-growth-mindset-for-better-sex.html</link>
    <description><![CDATA[<p><strong>TLDR</strong>: Fixed mindset and fear of inadequacy hinder learning. Competence gives
you confidence - where you’re competent and confident, you don’t have fear of
inadequacy. And if you don’t learn fast because you’re afraid of feedback
(because you’re afraid of inadequacy), you’ll not get better, leaving you
relatively incompetent and afraid of inadequacy. 🗘</p>
<p>A thing about sex recently clicked from several sources:</p>
<ul>
<li>Being high and talking with a certain sergal,</li>
<li>plus reading a Facebook thing by Duncan:
<iframe src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Fduncan.sabien%2Fposts%2F3280464855321542&width=500" width="500" height="242" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true" allow="encrypted-media"></iframe></li>
<li>plus listening to John Vervaecke’s series <a href="https://www.youtube.com/playlist?list=PLND1JCRq8Vuh3f0P5qjrSdb5eC1ZfZwWJ">Awakening from the Meaning Crisis</a>.
(One of the episodes talks about fixed/growth mindset, but I can’t find it
right now. You should go watch the whole series anyway, it’s awesome.)</li>
</ul>
<p>During sex, I have a background fear of “what if I can’t bring them to orgasm”.
I want my partner to enjoy it as well, and I want to reciprocate, and I would
feel bad if they bring me to orgasm but I can’t bring them to orgasm. So, I
hurry and try hard to bring them to orgasm, because I am not confident of my
sexual skill. Orgasm is cool, but the sex before orgasm is also cool. Sex that
doesn’t feel hurried and where you can take your time and stretch out pleasure.
But, so far, in like 90% of the sex I’ve had so far I had the thought “aaaa sex
aaaa gotta perform and be good enough and hurry and give them an orgasm aaaa”,
somewhere in the background. In that kind of environment, you don’t get a lot
of learning done.</p>
<p>Say you’re learning how to play the violin. We know lots about learning. CFAR’s
handbook (which I think is still not publicly readable) has a lot of useful
stuff on it. Things that make learning work well include:</p>
<ul>
<li>You need to <strong>learn the basics</strong> first. You don’t start by playing Vivaldi’s
Four Seasons. You start by playing one note until you get that one note at
least 90% right: holding the violin correctly, not having the bow screech, not
touching the strings that shouldn’t ring, holding the tone for as long as
required without it fluctuating (unless you want it to).</li>
<li>You need <strong>feedback</strong>, and the faster the feedback loop is, the better. If you
play the <a href="https://www.youtube.com/watch?v=GRxofEmo3HA">Four Seasons</a> and by
Summer you start holding the bow wrong, your violin tutor will immediately
stop you and tell you. The tutor won’t wait the remaining 20 minutes to tell
you that starting with Summer they stopped enjoying your performance and just
hoped you’d get it over with soon.</li>
</ul>
<p>So. When having sex, I hurry because I worry I might be unable bring my partner
to orgasm, making the experience less than optimal. And I never really get
learning done because I always hurry because I worry I can’t bring my partner to
orgasm, which I worry about because I never learned enough to be confident in my
ability to do that. 🗘</p>
<p>With the next couple of partners I have, I think I’ll ask if we could get some
learning done. As in, play the Four Seasons, and tell each other if we’re off
tune or in the wrong rhytm or need to tune the strings. And ask proactively,
too. If you’re scared that holding the violin wrong makes you a not-good-enough
violin player forever, you’ll be holding it wrong until you chance upon the
right way by stochastic gradient descent, with the loss function being “the
partner’s body language looks happy”. That also converges (if you’re good
at reading people), but slower.</p>
<p>Go learn about growth/fixed mindset if you haven’t yet.
I’ve known about the concept for a while, but somehow I never thought of
applying it to this area until now. And I suspect a lot of the places where I’m
not competent right now are also places where I have fixed mindset but haven’t
yet realized it or addressed it. Peace.</p>]]></description>
    <pubDate>Tue, 28 Apr 2020 00:00:00 UT</pubDate>
    <guid>http://agentydragon.com/posts/2020-04-28-growth-mindset-for-better-sex.html</guid>
    <dc:creator>Rai</dc:creator>
</item>

    </channel>
</rss>
