<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Goyalpramod on Medium]]></title>
        <description><![CDATA[Stories by Goyalpramod on Medium]]></description>
        <link>https://medium.com/@goyalpramod?source=rss-763a19c7d326------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*nj9Du1z2TPTIdGax0Lw80Q.jpeg</url>
            <title>Stories by Goyalpramod on Medium</title>
            <link>https://medium.com/@goyalpramod?source=rss-763a19c7d326------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 10 Jun 2026 01:29:29 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@goyalpramod/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[GPT from scratch]]></title>
            <link>https://goyalpramod.medium.com/gpt-from-scratch-66b032045fce?source=rss-763a19c7d326------2</link>
            <guid isPermaLink="false">https://medium.com/p/66b032045fce</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Goyalpramod]]></dc:creator>
            <pubDate>Sun, 11 Aug 2024 04:35:32 GMT</pubDate>
            <atom:updated>2024-08-11T04:35:32.529Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/480/0*iguWpqehiDiSP4j2.gif" /><figcaption>via GIPHY</figcaption></figure><p>We are finally here. I have been waiting for this moment for a long time. This is a continuation of the series where I explain everything Andrej talks about in his series Zero to Hero. So kindly watch the video first and return here to help you understand a few things you may have missed. We will be moving forward assuming that you are already aware of the basics of ML and are here to learn the more intermediate or advanced ideas. Because that is what I will be mainly focusing on. For the more basic stuff, kindly consider going to the beginning of the series.</p><p>Now let us PROCEED!!!!</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FkCc8FmEb1nY%3Flist%3DPLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DkCc8FmEb1nY&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FkCc8FmEb1nY%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/2fec4f05487e91998e1efd8cd7f76e5c/href">https://medium.com/media/2fec4f05487e91998e1efd8cd7f76e5c/href</a></iframe><h3>Bi-gram generation</h3><p>(the <a href="https://colab.research.google.com/drive/1JMLa53HDuA-i7ZBmqV7ZnA3c_fvtXnx-?usp=sharing#scrollTo=nql_1ER53oCf">link</a> to the colab notebook)</p><pre>class BigramLanguageModel(nn.Module):<br><br>    def __init__(self, vocab_size):<br>        super().__init__()<br>        # each token directly reads off the logits for the next token from a lookup table<br>        self.token_embedding_table = nn.Embedding(vocab_size, vocab_size)<br><br>    def forward(self, idx, targets=None):<br><br>        # idx and targets are both (B,T) tensor of integers<br>        logits = self.token_embedding_table(idx) # (B,T,C)<br><br>        if targets is None:<br>            loss = None<br>        else:<br>            B, T, C = logits.shape<br>            logits = logits.view(B*T, C)<br>            targets = targets.view(B*T)<br>            loss = F.cross_entropy(logits, targets)<br><br>        return logits, loss<br><br>    def generate(self, idx, max_new_tokens):<br>        # idx is (B, T) array of indices in the current context<br>        for _ in range(max_new_tokens):<br>            # get the predictions<br>            logits, loss = self(idx)<br>            # focus only on the last time step<br>            logits = logits[:, -1, :] # becomes (B, C)<br>            # apply softmax to get probabilities<br>            probs = F.softmax(logits, dim=-1) # (B, C)<br>            # sample from the distribution<br>            idx_next = torch.multinomial(probs, num_samples=1) # (B, 1)<br>            # append sampled index to the running sequence<br>            idx = torch.cat((idx, idx_next), dim=1) # (B, T+1)<br>        return idx</pre><p>This code block made me raise a few questions, let’s go through each one by one.</p><ol><li>logits, loss = self(idx) forward is not a magic function, so why does this work?</li></ol><p>Ans. When we create a class that inherits from nn.Module (as BigramLanguageModel does), We are leveraging a lot of functionality from PyTorch&#39;s neural network module.</p><p>Here’s what’s happening:</p><ul><li>In Torch, when we use parentheses after an object (like self(idx)), it attempts to call the object&#39;s __call__ method.</li><li>The nn.Module class, which BigramLanguageModel inherits from, implements a __call__ method.</li><li>This __call__ method in nn.Module is set up to automatically call the forward method of your class.</li><li>So when we write self(idx), it&#39;s equivalent to calling self.forward(idx).</li></ul><p>2. logits = logits[:, -1, :] what is this syntax?</p><p>Ans. This line uses NumPy-style array indexing in PyTorch, which is a powerful and concise way to select specific parts of a tensor. Let’s break it down:</p><p>logits[:, -1, :] is selecting data from the logits tensor, which is a 3D tensor with shape (B, T, C), where:</p><ul><li>B is the batch size</li><li>T is the sequence length (or time steps)</li><li>C is the number of classes (or vocabulary size in this case)</li></ul><p>Here’s what each part means:</p><ol><li>: in the first position: This selects all elements along the first dimension (batch dimension). It keeps all batches.</li><li>-1 in the second position: This selects only the last element along the second dimension (time dimension). In Python, -1 as an index refers to the last element of a sequence.</li><li>: in the third position: This selects all elements along the third dimension (class dimension).</li></ol><p>So, logits[:, -1, :] is effectively saying: &quot;For all batches, take the last time step, and for that last time step, take all class probabilities.&quot;</p><p>This operation transforms the tensor from the shape (B, T, C) to (B, C), because:</p><ul><li>It keeps all B batches</li><li>It selects only one-time step (-1, the last one)</li><li>It keeps all C classes</li></ul><p>This is typically done in language models when you’re only interested in predicting the next token based on the last token in the sequence, which is the case for our bigram model. (We will change this in the future)</p><p>3. probs = F.sodtmax(logits,dim=-1) why is dim = -1?</p><p>Ans. This is a common practice in PyTorch and has a specific purpose:</p><ul><li>It applies softmax across the class dimension, which is typically the last dimension in classification tasks.</li><li>This ensures that we get a probability distribution for each item in the batch over all classes.</li><li>It’s more flexible and less error-prone than hardcoding a specific dimension number.</li><li>If the tensor shape changes (e.g., from (B, C) to (B, T, C)), dim=-1 softmax will still be applied correctly over the class dimension.</li></ul><h3>It’s all about Adam</h3><p>For some reason everyone is obsessed with Adam (sorry Eve), but why? People keep using it never bothering to explain the reason, yes it is the best. But what makes it the best? This section is going to be a deep dive into the workings and origins of our beloved Adam. Feel free to skip this part, it’s mostly for my own curiosity.</p><h4>What was done in the biblical times (a bit of history on optimizers)</h4><h4>Gradient Descent:</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/384/1*6NywY95oGV4I6m9RsQrprw.png" /></figure><p>This is the beginning of everything, We have our weights and update them with respect to the loss by multiplying it with a learning rate. It works great for small datasets, it converges and computes fast. But it is not scalable, because it can be slow to calculate the entire loss of a huge dataset also, it gets stuck on local minima or saddle points.</p><p>Now one of the biggest problems with optimizers is fixing wiggling is when we have a huge learning rate and the weight keeps jumping between the minima, to fix this we have some called exponential moving average</p><h4>Exponential Moving Average:</h4><p>The Exponential Moving Average is a technique used in optimizers to smooth out fluctuations in the optimization process. It’s particularly useful in stochastic optimization where we deal with noisy gradients.</p><pre>e_t = β * e_{t-1} + (1 - β) * x_t</pre><p>Where:</p><ul><li>e_t is the exponential average at time t</li><li>x_t is the new data point at time t</li><li>β (beta) is a parameter between 0 and 1 that determines the weight of past observations</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/835/1*BqYX9kgkW085ZWDV6V99og.png" /><figcaption>Taken from Medium article by <a href="https://medium.com/u/20c0457a86b0">Maciej Balawejder</a></figcaption></figure><p>As we can see EMA smoots out the trajectory of our loss.</p><h4>Momentum</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/507/1*ItdVn7-eVvgbwZIbcK92yg.png" /><figcaption>Shamelessly copied from the author of the original article (by Maciej Balawejder)</figcaption></figure><p>Momentum incorporates EMA with Gradient Descent, this reduces oscillations as well as reduces the chances of hitting a local minima or saddle point.</p><p>How?</p><p>Momentum works by adding a fraction of the previous update vector to the current update. This creates a sort of ‘velocity’ for the parameter updates, which allows the optimization to build up speed in directions with consistent gradients. Here’s how this addresses the two main issues:</p><ol><li>Reducing Oscillations: In areas where the gradient changes rapidly (like narrow ravines in the loss landscape), standard Gradient Descent tends to oscillate back and forth across the slopes. Momentum dampens these oscillations by averaging the gradients over time. This allows for faster progress along the ravine while reducing the back-and-forth movement.</li><li>Escaping Local Minima and Saddle Points: When the algorithm encounters a local minimum or a saddle point, the gradients become very small or zero. Standard Gradient Descent might get stuck in these areas. However, momentum allows the optimization to build up velocity. This means that even if the current gradient is small, the accumulated momentum from previous updates can help the algorithm push through these flat regions, potentially finding a better minimum.</li></ol><p>A nice visualization will be (from popular stories on momentum)</p><p>“SGD is a walking man downhill, slowly but steady. Momentum is a heavy ball running downhill, smooth and fast.”</p><p>The problem with this is though, that the learning rate stays constant. Which is not ideal, as we talked the optimizer has velocity so even when it is reaching the minima it can cause it to overshoot, so ideally we would like the learning rate to be adaptive. This is fixed by AdaGrad</p><h4>AdaGrad</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/651/1*_gqjZbnVZUZnmKSJ0-BtGQ.png" /><figcaption>Continuing my shamelessness because this is too beautifully made (if someone could teach me how to create these I would be really grateful) (P.S I found it, Use LaTeX)</figcaption></figure><p>AdaGrad has adaptive learning rate, so when the gradients are too high, it decreases the learning rate it prevents it from overshooting, when the gradients are low, it does not decrease it a lot.</p><p>But AdaGrad faces a major issue, the learning rate keeps decreasing and there may be times when we are unable to reach the minima because the learning rate came very close to zero. This is fixed by RMSProp</p><h4>RMSProp</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/627/1*hnYIiQWsiHZTbffItUAPSw.png" /></figure><p>“<strong>RMSProp</strong> is an upgraded version of AdaGrad that leverages mighty <strong>EMA</strong>(again). Instead of only <strong>accumulating</strong> the squared gradients, we <strong>control</strong> the amount of previous information. Thus the denominator won’t get large, and the learning rate won’t disappear!” [4]</p><h4>Adam</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/610/1*EYfPhZpEgT8Nr_bfajJ7FQ.png" /></figure><p>“Essentially Adam is a <strong>combination</strong> of Momentum and RMSProp. It has reduced oscillation, a more smoothed path, and adaptive learning rate capabilities. Combining those abilities makes it the most <strong>powerful</strong> and <strong>suitable</strong> for different problem optimizers.</p><blockquote><em>The good starting configuration is learning rate 0.0001, momentum 0.9, and squared gradient 0.999.” [4]</em></blockquote><h4>AdamW (The man of the hour)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/425/1*fZvIpqU6BzOGytwuFYlJOw.png" /></figure><p>The added term here alpha*lambda*Weight is the decay term. Now an obvious question will come to anyone’s mind. If Adam was so perfect why do we have Adam with Decay?</p><p>Because there was a problem with Adam</p><p>“Adaptive optimizers like Adam have become a default choice for training neural networks. However, when aiming for state-of-the-art results, researchers often prefer stochastic gradient descent (SGD) with momentum because models trained with Adam have been observed to not generalize as well.”[6]</p><p>I will suggest you read reference 6 for a better and more in depth understanding of AdamW, but what it essentially does is :</p><ul><li>AdamW applies weight decay directly to the weights, separate from the gradient update.</li><li>This decoupling ensures that weight decay behaves consistently regardless of the adaptive learning rates.</li></ul><p>Now back to our video</p><h3>SELF-ATTENTION</h3><p>This part absolutely baffled me, first question. Which looks easier to you and makes more sense?</p><p>version 3</p><pre># version 3: use Softmax<br>tril = torch.tril(torch.ones(T, T))<br>wei = torch.zeros((T,T))<br>wei = wei.masked_fill(tril == 0, float(&#39;-inf&#39;))<br>wei = F.softmax(wei, dim=-1)<br>xbow3 = wei @ x<br>torch.allclose(xbow, xbow3)</pre><p>or version 2</p><pre># version 2: using matrix multiply for a weighted aggregation<br>wei = torch.tril(torch.ones(T, T))<br>wei = wei / wei.sum(1, keepdim=True)<br>xbow2 = wei @ x # (B, T, T) @ (B, T, C) ----&gt; (B, T, C)<br>torch.allclose(xbow, xbow2)</pre><p>Unless you are a maniac anyone will say version 2, so why are we even doing version 3 (btw if they do not look the same to you, write the equation of softmax and calculate the values one by one. DO IT. YOU WANT TO BE A BETTER AI ENGINEER)</p><p>It didn’t make any sense to me initially but then it clicked. Version 2 is a straightforward normalization there is no richness of individual words being carried. This gets confusing because we see 1 for both versions.</p><p>But think of it more like this</p><pre>#VERSION 2 <br><br>1 0 0<br>0.5 0.5 0<br>0.33 0.33 0.33<br><br>#then this gets multiplied with the actuall embeddings </pre><pre>#VERSION 3 <br>{some_embedding value} 0 <br>{some_embedding value} {some_embedding value} 0<br>{some_embedding value} {some_embedding value} {some_embedding value}</pre><p>Now using softmax on them introduces non-linearity. Which we have learned so far is very important. (if it doesn&#39;t make sense now, I promise it will by the time you get to version 4)</p><p>Now off to version 4 we go, the actual self-attention block. And oh boy if it isn’t a big ol tough nut to understand. Well what is going on is pretty easy to understand, but my question is more intrinsic, why is this going on? why is this working? what was the rationale behind the creators, how did they even think of this? Let us find out together</p><pre># version 4: self-attention!<br>torch.manual_seed(1337)<br>B,T,C = 4,8,32 # batch, time, channels<br>x = torch.randn(B,T,C)<br><br># let&#39;s see a single Head perform self-attention<br>head_size = 16<br>key = nn.Linear(C, head_size, bias=False)<br>query = nn.Linear(C, head_size, bias=False)<br>value = nn.Linear(C, head_size, bias=False)<br>k = key(x)   # (B, T, 16)<br>q = query(x) # (B, T, 16)<br>wei =  q @ k.transpose(-2, -1) # (B, T, 16) @ (B, 16, T) ---&gt; (B, T, T)<br><br>tril = torch.tril(torch.ones(T, T))<br>#wei = torch.zeros((T,T))<br>wei = wei.masked_fill(tril == 0, float(&#39;-inf&#39;))<br>wei = F.softmax(wei, dim=-1)<br><br>v = value(x)<br>out = wei @ v<br>#out = wei @ x<br><br>out.shape</pre><p>I found some great articles and videos that can explain the concept way better than I can. (added some recommended watch and read section)</p><p>But the theory behind Q,K and V can be understood by the answer from the following <a href="https://stats.stackexchange.com/questions/421935/what-exactly-are-keys-queries-and-values-in-attention-mechanisms">stack overflow answer</a></p><blockquote>“The key/value/query concept is analogous to retrieval systems. For example, when you search for videos on Youtube, the search engine will map your <strong>query</strong> (text in the search bar) against a set of <strong>keys</strong> (video title, description, etc.) associated with candidate videos in their database, then present you the best matched videos (<strong>values</strong>).”</blockquote><p>To understand how it works, whatever question/text/prompt you write to the model is a query. Then the model checks for keys that are closest to your query. And then presents those values back to you. (Watch the videos by Stat Quest for a more in-depth understanding, here is a<a href="https://www.youtube.com/watch?v=zxQyTK8quyY"> recommended one</a>)</p><h3>Conclusion</h3><p>I have wanted to completely and utterly watch and understand this video from the day it came out, I am happy today that I could do it. I won&#39;t say I 100 percent understand all of it, But I believe I do understand a good chunk of it. Thank you for reading this article, Here’s to striving to be a better developer.</p><h3>Some recommended watches and reads:</h3><ul><li><a href="https://www.youtube.com/watch?v=zxQyTK8quyY">https://www.youtube.com/watch?v=zxQyTK8quyY</a></li><li><a href="https://medium.com/@geetkal67/attention-networks-a-simple-way-to-understand-self-attention-f5fb363c736d">https://medium.com/@geetkal67/attention-networks-a-simple-way-to-understand-self-attention-f5fb363c736d</a></li><li><a href="https://towardsdatascience.com/illustrated-guide-to-transformers-step-by-step-explanation-f74876522bc0">https://towardsdatascience.com/illustrated-guide-to-transformers-step-by-step-explanation-f74876522bc0</a></li><li><a href="https://sebastianraschka.com/blog/2023/self-attention-from-scratch.html">https://sebastianraschka.com/blog/2023/self-attention-from-scratch.html</a> (Honestly, read everything this guy has ever written)</li></ul><h3>References</h3><ol><li>Andrej and his amazing playlist <a href="https://www.youtube.com/playlist?list=PLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ">link</a></li></ol><p>2. Claude (hehe, thanks anthropic)</p><p><a href="https://en.wikipedia.org/wiki/Stochastic_gradient_descent">3. https://en.wikipedia.org/wiki/Stochastic_gradient_descen</a></p><p>4. <a href="https://medium.com/nerd-for-tech/optimizers-in-machine-learning-f1a9c549f8b4">https://medium.com/nerd-for-tech/optimizers-in-machine-learning-f1a9c549f8b4</a></p><p>5. <a href="https://pytorch.org/docs/stable/generated/torch.optim.AdamW.html">https://pytorch.org/docs/stable/generated/torch.optim.AdamW.html</a></p><p>6. <a href="https://towardsdatascience.com/why-adamw-matters-736223f31b5d">https://towardsdatascience.com/why-adamw-matters-736223f31b5d</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=66b032045fce" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Andrej’s Zero to Hero for Dummies Part 5]]></title>
            <link>https://goyalpramod.medium.com/andrejs-zero-to-hero-for-dummies-part-5-924ce8a482a4?source=rss-763a19c7d326------2</link>
            <guid isPermaLink="false">https://medium.com/p/924ce8a482a4</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Goyalpramod]]></dc:creator>
            <pubDate>Thu, 01 Aug 2024 08:42:42 GMT</pubDate>
            <atom:updated>2024-08-01T08:42:42.648Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QNYMC4YHA9B0_svNiTGjLw.jpeg" /></figure><p>Welp, happy to see you again. It is tough but it’s worth it. Let’s continue understanding ML in depth with the help of our lord and savior andrej DADDY. As always, watch his video first and come back here to help in your understanding.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fq8SA3rM6ckI%3Flist%3DPLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dq8SA3rM6ckI&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2Fq8SA3rM6ckI%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/399f90ed2ed0073e2871f2a0635050f6/href">https://medium.com/media/399f90ed2ed0073e2871f2a0635050f6/href</a></iframe><p>This one will be a bit different, as the lecture itself is filled with exercises. I will be diving more into how I went about thinking and trying to solve the exercises rather than explaining things. It is more about approach than the actual answer per say.</p><h3>Excercise 1</h3><p>The hardest thing for me to figure out was… how to even start? what is dlogprobs. Is it dlogprobs/dloss or the otherway around.</p><p>The fundamental thing that we need to understand is</p><p>“Direction of gradients: In backpropagation, we’re calculating dloss/dx for each variable x, not dx/dloss. This is because we want to know how changes in each variable affect the loss, to update our parameters in the direction that reduces the loss.”</p><p>I ran into the second problem when we had to calculate dhprebn, In my head we were doing the chain rule in the traditional sense and just calculating the gradients. But it is effective to think of these as nodes.</p><p>Let me tell you the question I had and the the nice answer claude-3.5-sonnet gave me which helped me with my understanding</p><p>“I do not understand how dhprebn and dbnmeani were calculated</p><p>if we try to calculate for bhprebn</p><p>dloss/dbndiff*dbndiff/dbnprebn = 1 — dbnmeani/dbhprebn</p><p>and then we calculate using the above, how has andrej done it?”</p><pre>I understand your confusion. Let&#39;s break this down step by step to clarify how Andrej derived these gradients.<br><br>First, recall the forward pass equations:<br>bnmeani = 1/n * hprebn.sum(0, keepdim=True)<br>bndiff = hprebn - bnmeani<br><br>In the backward pass, we&#39;re calculating gradients with respect to hprebn and bnmeani.<br>The key insight here is that hprebn affects the loss through two paths:<br>a) Directly through bndiff<br>b) Indirectly through bnmeani<br>For dhprebn:<br><br>The direct effect is simply dbndiff (because bndiff = hprebn - bnmeani)<br>There&#39;s no need to multiply by anything because the local derivative of hprebn with respect to bndiff is 1<br><br><br>For dbnmeani:<br><br>We need to consider how changes in bnmeani affect bndiff<br>From the equation bndiff = hprebn - bnmeani, we see that increasing bnmeani decreases bndiff by the same amount<br>This is why we have a negative sign: -dbndiff<br>We sum over axis 0 because bnmeani is computed as a mean across this axis<br><br><br>The full equations:<br>dhprebn = dbndiff.clone()<br>dbnmeani = (-dbndiff).sum(0)<br><br><br>Your equation dloss/dbndiff * dbndiff/dbnprebn = 1 - dbnmeani/dbhprebn is mixing up the concepts a bit. We&#39;re not calculating dbnmeani/dhprebn, but rather how changes in bnmeani affect the loss through bndiff.<br>The key to understanding this is to focus on how each variable directly affects the next one in the computational graph, rather than trying to compute complex relationships across multiple steps at once. This is the essence of the backpropagation algorithm - it breaks down complex derivative calculations into simpler, local computations.<br>Andrej&#39;s approach here is correctly applying the chain rule in a way that&#39;s consistent with how backpropagation works in neural networks, considering all paths through which a variable can affect the loss.</pre><p>This made me realize an Aha moment and everything just became clearer.</p><p>Well, after this everything was crystal clear to me. If you still ran into some issues with the further exercises. Feel free to drop a comment,I will try to help you out.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=924ce8a482a4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Andrej’s Zero to Hero for Dummies Part 4]]></title>
            <link>https://goyalpramod.medium.com/andrejs-zero-to-hero-for-dummies-part-4-8b6403d2d9c9?source=rss-763a19c7d326------2</link>
            <guid isPermaLink="false">https://medium.com/p/8b6403d2d9c9</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Goyalpramod]]></dc:creator>
            <pubDate>Sat, 27 Jul 2024 06:16:24 GMT</pubDate>
            <atom:updated>2024-07-27T06:16:24.453Z</atom:updated>
            <content:encoded><![CDATA[<p>Here we are again, continuing our journey of becoming better developers. This article will be a bit different. By now you must be accustomed to the basic things, so I will skip over a few sections and only talk about the one’s that I believe are hard to grasp or can seem a bit perplexing to first time “developers”</p><p>As always, kindly watch the original video first. Then come back here, to aid with your understanding.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FP6sfmUTpUmc%3Flist%3DPLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DP6sfmUTpUmc&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FP6sfmUTpUmc%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/866fb52e7ecbac5e41a392b7b6c089cd/href">https://medium.com/media/866fb52e7ecbac5e41a392b7b6c089cd/href</a></iframe><h3>The first problem</h3><p>The random initialization gives too high a loss initially, as it is being assigned extreme values, we would like it to be a uniform distribution. That is why we multiply W2 and B2 by small numbers (this is the last layer, that is why it is being called the softmax issue)</p><h3>Fixing the tanh problem</h3><p>Tanh is a squasing function, it takes big values to the extremes and the data within them is lost, so we decrease the weights. This makes sure the value that is being sent to tanh itself is small hence making sure nothing goes to the extremes</p><h3>calculating the init scale: “Kaiming init”</h3><p>I found this part rather interesting because I was never introduced to all this, I followed the modern tutorials for ML while learning and I never knew about this issue.</p><p>So essentially to bring the distribution closer to a guassian distribution we multiply the initial weights with the following formula</p><p>“gain/square_root(fan_in)”</p><p>(But as Andrej mentioned one need not worry about these problems any more due to the modern innovations in ML)</p><h3>Batch Normalization</h3><p>When I went into the lecture I was pretty sure of my understand of Batch Normalization but by the end of it I was more confused than ever.</p><p>But then I went through all the code in the colab, and it brought me back to my understanding.</p><p><a href="https://colab.research.google.com/drive/1H5CSy-OnisagUgDUXhHwo1ng2pjKHYSN?usp=sharing#scrollTo=57RCzt38r436">Google Colab</a></p><p>Let’s have a look at the following code from colab</p><pre>  ix = torch.randint(0, Xtr.shape[0], (batch_size,), generator=g)<br>  Xb, Yb = Xtr[ix], Ytr[ix] # batch X,Y<br>  <br>  # forward pass<br>  emb = C[Xb] # embed the characters into vectors<br>  embcat = emb.view(emb.shape[0], -1) # concatenate the vectors<br>  # Linear layer<br>  hpreact = embcat @ W1 #+ b1 # hidden layer pre-activation<br>  # BatchNorm layer<br>  # -------------------------------------------------------------<br>  bnmeani = hpreact.mean(0, keepdim=True)<br>  bnstdi = hpreact.std(0, keepdim=True)<br>  hpreact = bngain * (hpreact - bnmeani) / bnstdi + bnbias<br>  with torch.no_grad():<br>    bnmean_running = 0.999 * bnmean_running + 0.001 * bnmeani<br>    bnstd_running = 0.999 * bnstd_running + 0.001 * bnstdi<br>  # -------------------------------------------------------------<br>  # Non-linearity<br>  h = torch.tanh(hpreact) # hidden layer<br>  logits = h @ W2 + b2 # output layer<br>  loss = F.cross_entropy(logits, Yb) # loss function</pre><p>Batch normalization is nothing but. Taking a batch, subtracting the mean from it and dividing it by its standard deviation, this normalizes that particular batch.</p><p>Now why are we keeping a running mean and std?</p><p>Quite simply, during inference (running the model) we are not running a batch, but rather a single input. And we cannot calculate the mean and std of a single input (well you can, but it will be pretty pointless).</p><p>So we take the running mean and std that we had calculated, also we are changing it ever so slightly, because each batch will have a different mean and std.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8b6403d2d9c9" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Andrej’s Zero to Hero for Dummies Part 3]]></title>
            <link>https://goyalpramod.medium.com/andrejs-zero-to-hero-for-dummies-part-3-f2ec460d9b40?source=rss-763a19c7d326------2</link>
            <guid isPermaLink="false">https://medium.com/p/f2ec460d9b40</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Goyalpramod]]></dc:creator>
            <pubDate>Mon, 22 Jul 2024 10:54:57 GMT</pubDate>
            <atom:updated>2024-07-22T10:54:57.268Z</atom:updated>
            <content:encoded><![CDATA[<p>Continuing our epic adventure of understanding Andrej’s playlist in detail. As always I will urge you to go through the original video first. Then come back and join me on the journey to AI discovery.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FTCH_1BHY58I%3Flist%3DPLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DTCH_1BHY58I&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FTCH_1BHY58I%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/ac95e3aaf7b4bc9a34cd3098784e98a1/href">https://medium.com/media/ac95e3aaf7b4bc9a34cd3098784e98a1/href</a></iframe><h3>Intro</h3><p>The idea that I found confusing initially was how is it increasing exponentially if we change from bigrams to considering longer sequences. So let’s break that down:</p><ul><li>When we are considering bigram models. We only have to look at one character, the previous character and based on that predict the next character. Like for the name “emma” when we break it down, we are writing it as “.e”, “em”, “mm”, “ma”, “a.”. Now applying a mask that we need to predict. “._”, “e_”, “m_”, “m_”, “a_”. So we need to predict _ based only on one prev character. And we can have 27 possibilities in this case.</li><li>Now let us take an example of tri-gram for “emma”. So now that becomes “.em”, “emm”, “mma”, “ma.”. And if we add a mask to the last character, “.e_”, “em_”, “mm_”, “ma_”. Now the first position of the trigram can be filled with 27 characters and the second position can be filled with 27 characters. Giving us a permutation of 27*27 and so on.</li></ul><h3>Bengio et al. 2003 (MLP language model) paper walkthrough</h3><p><a href="https://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf">Link to the paper</a></p><ul><li>They build a word level language model</li><li>Put 17000 words in a dimensional space of size 30</li><li>Maximise log likelihood</li><li>The magic behind the model is, similar objects are clubbed together</li><li>We convert each of these 17,000 words into a vector of dimension 30. So essentially we have a matrix of 17,000 rows and 30 columns. And then the researchers are taking 3 words and trying to predict the 4th one.</li><li>So essentially we have 90 neurons to begin with.</li><li>We have a <a href="https://www.oreilly.com/library/view/tensorflow-for-deep/9781491980446/ch04.html">fully connected hidden layer.</a></li><li>The output layer has 17,000 neurons to show the predictions.</li></ul><h3>(re-)building our training dataset</h3><pre>import torch<br>import torch.nn.functional as F<br>import matplotlib.pyplot as plt # for making figures<br>%matplotlib inline</pre><pre>words = open(&#39;names.txt&#39;, &#39;r&#39;).read().splitlines()<br>words[:8]<br>len(words)</pre><pre># build the vocabulary of characters and mappings to/from integers<br>chars = sorted(list(set(&#39;&#39;.join(words))))<br>stoi = {s:i+1 for i,s in enumerate(chars)}<br>stoi[&#39;.&#39;] = 0<br>itos = {i:s for s,i in stoi.items()}<br>print(itos)</pre><pre>block_size = 3 # context length: how many characters do we take to predict the next one?<br>X, Y = [], []<br>for w in words[:5]:<br>    print(w)<br>    context = [0] * block_size<br>    for ch in w + &#39;.&#39;:<br>        ix = stoi[ch]<br>        X.append(context)<br>        Y.append(ix)<br>        print(&#39;&#39;.join(itos[i] for i in context), &#39;---&gt;&#39;, itos[ix])<br>        context = context[1:] + [ix] # crop and append<br><br>X = torch.tensor(X)<br>Y = torch.tensor(Y)</pre><p>(If at any point you believe I have not explained any point in detail, consider going through my previous blogs.)</p><p>The part I found confusing was for ch in w + ‘.’ . Lets take the example of emma to understand this. This will basically convert emma to emma. and iterate over it character by character.</p><p>(I have talked about all the other code pieces in my previous blogs)</p><h3>Implementing the embedding lookup table</h3><p>This took me an hour to finally wrap my head around. Bring your A game and focus.</p><pre># Shape and dtype information<br>X.shape, X.dtype, Y.shape, Y.dtype<br># Output: (torch.Size([32, 3]), torch.int64, torch.Size([32]), torch.int64)<br><br># Creating a random tensor<br>C = torch.randn((27, 2))<br><br># Accessing a specific element<br>C[5]<br># Output: tensor([0.1615, 1.3169])<br><br># Shape of C[X]<br>C[X].shape<br># Output: torch.Size([32, 3, 2])<br><br># Accessing a specific element<br>X[13,2]<br># Output: tensor(1)<br><br># Using X to index into C<br>C[X][13,2]<br># Output: tensor([ 1.0815, -0.3502])</pre><p>The following code block will make everything crystal clear.</p><pre># If X contains:<br>X = torch.tensor([[0, 1, 2],<br>                  [3, 4, 5],<br>                  ...  # 30 more rows<br>                 ])<br><br># And C contains:<br>C = torch.tensor([[a, b],<br>                  [c, d],<br>                  [e, f],<br>                  ...  # 24 more rows<br>                 ])<br><br># Then C[X] would result in:<br>C[X] = torch.tensor([[[a, b], [c, d], [e, f]],<br>                     [[g, h], [i, j], [k, l]],<br>                     ...  # 30 more 3x2 matrices<br>                    ])</pre><p>We are using the elements from c to embed each of the numbers in X. That is all there is to it. But this did raise a question in my mind. Why is it C[X] and not X[C] or something else? That is when I encountered something called “Fancy Indexing” or “<a href="https://numpy.org/neps/nep-0021-advanced-indexing.html">Advanced Indexing</a>”. It shows a convention of how to simplify indexing.</p><h3>Implementing the hidden layer + internals of torch.Tensor: storage, views</h3><pre># Assuming emb, W1, and b1 are already defined<br><br># Line 1<br>h = torch.tanh(emb.view(-1, 6) @ W1 + b1)<br><br># Line 2<br>result1 = torch.cat([emb[:, 0, :], emb[:, 1, :], emb[:, 2, :]], 1)<br>print(result1.shape)  # torch.Size([32, 6])<br><br># Line 3<br>result2 = torch.cat(torch.unbind(emb, 1), 1)<br>print(result2.shape)  # torch.Size([32, 6])</pre><p>We learn that .cat is an inefficient method as that creates a whole new tensor. Whereas using view manipulates the already existing tensor.</p><p>What I was more curious about was, how does .view(-1,6) works and why it works. What I found was</p><ul><li>The -1 is a special argument that tells PyTorch to automatically calculate the size of this dimension.</li><li>PyTorch will figure out what number to put in place -1 to make the reshape possible, given the total number of elements and the other dimension (6 in this case).</li></ul><p>The <a href="http://blog.ezyang.com/2019/05/pytorch-internals/">blog</a> Andrej mentions.</p><h3>Implementing the output layer</h3><pre>W2 = torch.randn((100,27))<br>b2 = torch.randn(27)</pre><pre>logits = h @ W2 + b2</pre><h3>Implementing the negative log likelihood loss</h3><pre># Calculate probabilities<br>prob = counts / counts.sum(1, keepdim=True)<br><br># Check the shape of prob<br>print(prob.shape)  # torch.Size([32, 27])<br><br># Calculate loss<br>loss = -prob[torch.arange(32), Y].log().mean()<br>print(loss)  # tensor(17.2955)</pre><ul><li>This calculates the negative log-likelihood.</li><li>torch.arange(32) creates a tensor [0, 1, 2, ..., 31].</li><li>prob[torch.arange(32), Y] selects elements from prob using two indices: the first from torch.arange(32) and the second from Y. This effectively selects the predicted probability for the correct label for each item in the batch.</li><li>.log() takes the natural logarithm of these probabilities.</li><li>.mean() calculates the mean of these log probabilities.</li><li>The negative sign at the beginning turns this into a loss (we want to maximize log probability or minimize negative log probability).</li></ul><h3>Introducing F.cross_entropy and why</h3><pre># Dataset shapes<br>X.shape, Y.shape  # dataset<br># Output: (torch.Size([32, 3]), torch.Size([32]))<br><br># Setting up reproducibility and initializing parameters<br>g = torch.Generator().manual_seed(2147483647)  # for reproducibility<br>C = torch.randn((27, 2), generator=g)<br>W1 = torch.randn((6, 100), generator=g)<br>b1 = torch.randn(100, generator=g)<br>W2 = torch.randn((100, 27), generator=g)<br>b2 = torch.randn(27, generator=g)<br>parameters = [C, W1, b1, W2, b2]<br><br># Count total number of parameters<br>sum(p.nelement() for p in parameters)  # number of parameters in total<br># Output: 3481<br><br># Forward pass<br>emb = C[X]  # (32, 3, 2)<br>h = torch.tanh(emb.view(-1, 6) @ W1 + b1)  # (32, 100)<br>logits = h @ W2 + b2  # (32, 27)<br>counts = logits.exp()<br># prob = counts / counts.sum(1, keepdim=True)<br># loss = -prob[torch.arange(32), Y].log().mean()<br>loss = F.cross_entropy(logits, Y)<br># Output: tensor(17.7697)<br><br># Example of softmax calculation<br>logits = torch.tensor([-5, -3, 0, 100]) - 100<br>counts = logits.exp()<br>probs = counts / counts.sum()<br>probs<br># Output: tensor([0.0000e+00, 1.4013e-45, 3.7835e-44, 1.0000e+00])</pre><p>More on cross_entropy <a href="https://machinelearningmastery.com/cross-entropy-for-machine-learning/">here</a></p><h3>Implementing the training loop, overfitting one batch</h3><pre>for p in parameters:<br>  p.requires_grad = True</pre><pre>for _ in range(1000):<br>    # forward pass<br>    emb = C[X]  # (32, 3, 2)<br>    h = torch.tanh(emb.view(-1, 6) @ W1 + b1)  # (32, 100)<br>    logits = h @ W2 + b2  # (32, 27)<br>    loss = F.cross_entropy(logits, Y)<br>    <br>    # backward pass<br>    for p in parameters:<br>        p.grad = None<br>    loss.backward()<br>    <br>    # update<br>    for p in parameters:<br>        p.data += -0.1 * p.grad<br><br>print(loss.item())</pre><h3>Training on the full dataset, minibatches</h3><pre>for _ in range(1000):<br>    # minibatch construct<br>    ix = torch.randint(0, X.shape[0], (32,))<br>    <br>    # forward pass<br>    emb = C[X[ix]]  # (32, 3, 2)<br>    h = torch.tanh(emb.view(-1, 6) @ W1 + b1)  # (32, 100)<br>    logits = h @ W2 + b2  # (32, 27)<br>    loss = F.cross_entropy(logits, Y[ix])<br>    <br>    # backward pass<br>    for p in parameters:<br>        p.grad = None<br>    loss.backward()<br>    <br>    # update<br>    for p in parameters:<br>        p.data += -0.1 * p.grad<br><br>print(loss.item())</pre><p>Now when I saw this, the first question that came to my mind was. “32 is such a small subset of the entire sample space, how does iterating over this make our entire pipeline better?” and out I went to find the answer to this question.</p><p>I found that the key is that while each iteration only sees a small subset, over many iterations (1000 in this case), the model will have been exposed to a large portion of the dataset, likely multiple times. This process, cycling through mini-batches of the entire dataset, is called an “epoch”. Multiple epochs allow the model to see each sample multiple times, refining its understanding with each pass.</p><h3>Finding a good initial learning rate</h3><p>Over here Andrej shows a brute force method of finding a good starting lr, But he also briefly shows lr with decay. After we have achieved a loss and keep getting the same loss, we decrease the learning rate so we can get the minimized loss.</p><pre># Created using Claude 3.5 Sonnet <br><br>import React, { useState, useEffect, useRef } from &#39;react&#39;;<br>import { Button } from &#39;@/components/ui/button&#39;;<br>import { Slider } from &#39;@/components/ui/slider&#39;;<br>import { Label } from &#39;@/components/ui/label&#39;;<br><br>const LearningRateDecayOptimizationVisualization = () =&gt; {<br>  const canvasRef = useRef(null);<br>  const [learningRate, setLearningRate] = useState(0.1);<br>  const [decayRate, setDecayRate] = useState(0.01);<br>  const [isRunning, setIsRunning] = useState(false);<br>  const [iteration, setIteration] = useState(0);<br>  const [position, setPosition] = useState({ x: 75, y: 75 });<br><br>  const drawParabola = (ctx, width, height) =&gt; {<br>    ctx.beginPath();<br>    for (let x = 0; x &lt; width; x++) {<br>      const y = 0.01 * (x - width / 2) ** 2 + 20;<br>      ctx.lineTo(x, y);<br>    }<br>    ctx.strokeStyle = &#39;blue&#39;;<br>    ctx.stroke();<br>  };<br><br>  const drawPoint = (ctx, x, y) =&gt; {<br>    ctx.beginPath();<br>    ctx.arc(x, y, 5, 0, 2 * Math.PI);<br>    ctx.fillStyle = &#39;red&#39;;<br>    ctx.fill();<br>  };<br><br>  useEffect(() =&gt; {<br>    const canvas = canvasRef.current;<br>    const ctx = canvas.getContext(&#39;2d&#39;);<br>    const width = canvas.width;<br>    const height = canvas.height;<br><br>    ctx.clearRect(0, 0, width, height);<br>    drawParabola(ctx, width, height);<br>    drawPoint(ctx, position.x, position.y);<br>  }, [position]);<br><br>  useEffect(() =&gt; {<br>    let animationId;<br><br>    const updatePosition = () =&gt; {<br>      if (isRunning) {<br>        setPosition((prevPos) =&gt; {<br>          const currentLR = learningRate / (1 + decayRate * iteration);<br>          const gradient = 0.02 * (prevPos.x - 150);<br>          const newX = prevPos.x - currentLR * gradient;<br>          const newY = 0.01 * (newX - 150) ** 2 + 20;<br>          return { x: newX, y: newY };<br>        });<br>        setIteration((prev) =&gt; prev + 1);<br>        animationId = requestAnimationFrame(updatePosition);<br>      }<br>    };<br><br>    if (isRunning) {<br>      animationId = requestAnimationFrame(updatePosition);<br>    }<br><br>    return () =&gt; cancelAnimationFrame(animationId);<br>  }, [isRunning, learningRate, decayRate, iteration]);<br><br>  const handleToggle = () =&gt; setIsRunning(!isRunning);<br><br>  const handleReset = () =&gt; {<br>    setIsRunning(false);<br>    setIteration(0);<br>    setPosition({ x: 75, y: 75 });<br>  };<br><br>  return (<br>    &lt;div className=&quot;p-4 max-w-2xl mx-auto&quot;&gt;<br>      &lt;h2 className=&quot;text-2xl font-bold mb-4&quot;&gt;Learning Rate Decay Optimization&lt;/h2&gt;<br>      &lt;canvas ref={canvasRef} width={300} height={200} className=&quot;border mb-4&quot; /&gt;<br>      &lt;div className=&quot;mb-4&quot;&gt;<br>        &lt;Label htmlFor=&quot;learning-rate&quot;&gt;Initial Learning Rate: {learningRate.toFixed(3)}&lt;/Label&gt;<br>        &lt;Slider<br>          id=&quot;learning-rate&quot;<br>          min={0.001}<br>          max={0.5}<br>          step={0.001}<br>          value={[learningRate]}<br>          onValueChange={(value) =&gt; setLearningRate(value[0])}<br>        /&gt;<br>      &lt;/div&gt;<br>      &lt;div className=&quot;mb-4&quot;&gt;<br>        &lt;Label htmlFor=&quot;decay-rate&quot;&gt;Decay Rate: {decayRate.toFixed(3)}&lt;/Label&gt;<br>        &lt;Slider<br>          id=&quot;decay-rate&quot;<br>          min={0}<br>          max={0.1}<br>          step={0.001}<br>          value={[decayRate]}<br>          onValueChange={(value) =&gt; setDecayRate(value[0])}<br>        /&gt;<br>      &lt;/div&gt;<br>      &lt;div className=&quot;flex space-x-2&quot;&gt;<br>        &lt;Button onClick={handleToggle}&gt;{isRunning ? &#39;Pause&#39; : &#39;Start&#39;}&lt;/Button&gt;<br>        &lt;Button onClick={handleReset}&gt;Reset&lt;/Button&gt;<br>      &lt;/div&gt;<br>      &lt;p className=&quot;mt-4&quot;&gt;Iteration: {iteration}&lt;/p&gt;<br>      &lt;p&gt;Current Learning Rate: {(learningRate / (1 + decayRate * iteration)).toFixed(5)}&lt;/p&gt;<br>    &lt;/div&gt;<br>  );<br>};<br><br>export default LearningRateDecayOptimizationVisualization;</pre><p>Run the above code to see how lr and decay affect loss in real-time.</p><h3>Splitting up the dataset into train/val/test splits and why</h3><p>Andrej mentions that “if train loss and dev loss are same, then the size of the hidden layer can be increased”. I had never heard about this, nor did I have any clue.</p><p>I found a rather interesting answer</p><p>“When the training and dev losses are very close, it often indicates that the model has reached its capacity to learn from the given data. The model is fitting the training data as well as it can, but it’s not overfitting (which would be indicated by a much lower training loss compared to the dev loss).”</p><h3>Experiment: larger hidden layer</h3><p>Andrej checks how the loss changes with an increase in the hidden layer. The train and dev loss still remain the same.</p><h3>Visualizing the character embeddings</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/511/1*RXVW0uLFX22Ng0OKLx70aQ.png" /><figcaption>Screenshot from Andrej’s lecture</figcaption></figure><h3>Experiment: larger embedding size</h3><p>We observe that the training and dev loss slowly diverge, another possible way to improve the performance of the models is to take more than 3 letters.</p><h3>Summary of our final code, conclusion</h3><p>Andrej summarises the entire video!!</p><h3>Sampling from the model</h3><pre># sample from the model<br>g = torch.Generator().manual_seed(2147483647 + 10)<br><br>for _ in range(20):<br>    <br>    out = []<br>    context = [0] * block_size # initialize with all ...<br>    while True:<br>      emb = C[torch.tensor([context])] # (1,block_size,d)<br>      h = torch.tanh(emb.view(1, -1) @ W1 + b1)<br>      logits = h @ W2 + b2<br>      probs = F.softmax(logits, dim=1)<br>      ix = torch.multinomial(probs, num_samples=1, generator=g).item()<br>      context = context[1:] + [ix]<br>      out.append(ix)<br>      if ix == 0:<br>        break<br>    <br>    print(&#39;&#39;.join(itos[i] for i in out))</pre><h3>Google collab (new!!) notebook advertisement</h3><p><a href="https://colab.research.google.com/drive/1YIfmkftLrz6MPTOO9Vwqrop2Q5llHIGK?usp=sharing">https://colab.research.google.com/drive/1YIfmkftLrz6MPTOO9Vwqrop2Q5llHIGK?usp=sharing</a> The google collab link!!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f2ec460d9b40" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Andrej’s Zero to Hero for Dummies Part 2]]></title>
            <link>https://goyalpramod.medium.com/andrejs-zero-to-hero-for-dummies-part-2-9ed4a6fc09a0?source=rss-763a19c7d326------2</link>
            <guid isPermaLink="false">https://medium.com/p/9ed4a6fc09a0</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Goyalpramod]]></dc:creator>
            <pubDate>Tue, 02 Jul 2024 08:16:44 GMT</pubDate>
            <atom:updated>2024-07-02T08:16:44.440Z</atom:updated>
            <content:encoded><![CDATA[<p>This is in continuation of my Zero to Hero for Dummies series, where I explain everything in Andrej’s series, so even a beginner can pick it up.</p><p>As always I urge you first to watch the original video, after which you can return to the blog to understand it better.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FPaCmpygFfXo%3Flist%3DPLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DPaCmpygFfXo&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FPaCmpygFfXo%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/f42d471a186af00b8035ecd204999c5d/href">https://medium.com/media/f42d471a186af00b8035ecd204999c5d/href</a></iframe><h3>Intro</h3><p><a href="https://github.com/karpathy/makemore">GitHub - karpathy/makemore: An autoregressive character-level language model for making more things</a></p><p>“makemore takes one text file as input, where each line is assumed to be one training thing, and generates more things like it. Under the hood, it is an <strong>autoregressive</strong> character-level language model, with a wide choice of models from bigrams all the way to a Transformer (exactly as seen in GPT). For example, we can feed it a database of names, and makemore will generate cool baby name ideas that all sound name-like, but are not already existing names. Or if we feed it a database of company names then we can generate new ideas for a name of a company. Or we can just feed it valid scrabble words and generate english-like babble.”</p><p>Makemore is a character-to-character <a href="https://towardsdatascience.com/sequence-models-and-recurrent-neural-networks-rnns-62cadeb4f1e1">sequence model</a> that generates characters by taking characters as input.</p><h4>Autoregressive</h4><ol><li>Auto” means “self” or “own.”</li><li>“Regressive” relates to regression, which involves predicting values.</li></ol><p>So, an autoregressive model is one that regresses (predicts) based on its own past values.</p><h3>Reading and exploring the dataset</h3><pre>words = open(&#39;names.txt&#39;, &#39;r&#39;).read().splitlines()</pre><p>Open is a Python File I/O method to open a file, we are reading from it hence the .read(), what split lines does, is break it down by new lines. Otherwise, we would have it as a single string which would look like emma\nolivia….</p><pre>words[:10] #prints the first 10 names, Python is 0 indexed. So we are stopping on the 10th index</pre><pre>min(len(w) for w in words) # get the length of the smallest word</pre><pre>max(len(w) for w in words) #get the length of the maximum word</pre><p>We have to understand that a lot of information is packed in a single word. If we take the example of “isabella” as Andrej mentioned.</p><p>What the model will learn is, a name is likely to start with i the letter s likely to be preceded by i the letter a is likely to be preceded by is and so on. And the whole word isabella will end after a .</p><pre>for w in words[:3]:<br>    for ch1,ch2 in zip(w,w[1:]):<br>        print(ch1,ch2)</pre><p>I found this beautiful, simple, and yet complex. Let us go step by step thinking about what is going on.</p><p>Let us just work with the first word emma so internally what happens is</p><p>w = &quot;emma&quot; and w[1:] = &quot;mma&quot;</p><p>if you remember correctly, when you apply a for loop on a string. What Python does is, iterate over each character.</p><p>Which looks like the following:</p><pre>for ch in &quot;emma&quot;:<br>    print(ch)<br><br>#e<br>#m<br>#m<br>#a</pre><p>So when we do the following</p><pre>for ch1,ch2 in zip(&#39;emma&#39;,&#39;mma&#39;):</pre><p>what happens is, the iterator goes over each of the characters ch1 for ‘emma’ and ch2 for ‘mma’. And stops when it reaches the end of the item whichever is the shortest.</p><h3>Counting bigrams in a Python dictionary</h3><pre>b = {}<br>for w in words:<br>    chs = [&#39;&lt;S&gt;&#39;] + list(w) + [&#39;&lt;E&gt;&#39;]<br>    for ch1,ch2 in zip(chs,chs[1:]):<br>        bigram = (ch1,ch2)<br>        b[bigram] = b.get(bigram, 0) + 1</pre><p>Here we are initialising b as a dictionary, b[bigram] searches for a key with that particular key, i.e bigram . On the right side, we try to retrieve the value of the key bigram , if none exist we start with zero. Otherwise, we add the number of occurrences.</p><pre>sorted(b.items(), key = lambda kv: -kv[1])</pre><p>Let&#39;s break this down into its individual components</p><ul><li>sorted() is a built-in Python function that returns a <strong>new sorted list</strong> from the given iterable.</li><li>b.items() method returns a view of the dictionary’s items as a list of (key, value) tuples. For example, if b = {(&#39;a&#39;, &#39;b&#39;): 3, (&#39;b&#39;, &#39;c&#39;): 2}, then b.items() would be [((&#39;a&#39;, &#39;b&#39;), 3), ((&#39;b&#39;, &#39;c&#39;), 2)] .</li><li>The key parameter in sorted() specifies a function to be called on each list element prior to making comparisons</li><li>lambda kv: -kv[1] is an anonymous function that takes one argument kv (each key-value pair from b.items()).</li><li>kv[1] accesses the second element of each tuple (the value, which is the count in this case). The minus sign - negates this value.</li><li>In the end, each value is negated. And we are returned a new list which is sorted with the key value pairs, with the greatest number coming first and so on.</li></ul><h3>Counting bigrams in a 2D torch tensor (“training the model”)</h3><pre>N = torch.zeros((28,28), dtype=torch.int32)</pre><p>Here we use pytorch to create a 2d matrix with 28 rows and columns, and of the <a href="https://pytorch.org/docs/stable/tensors.html">datatype</a> int32.</p><pre>chars = sorted(list(set(&#39;&#39;.join(words))))<br>stoi = {s:i for i,s in enumerate(chars)}<br>stoi[&#39;&lt;S&gt;&#39;] = 26<br>stoi[&#39;&lt;E&gt;&#39;] = 27</pre><p>&#39;&#39;.join(words) joins all the words together with no separation between them. So for example if we take words=[&#39;emma&#39;,&#39;jhon&#39;] and did &#39;_&#39;.join(words) the output will be emma_jhon .</p><p><a href="https://en.wikipedia.org/wiki/Set_(abstract_data_type)">set</a> gets rid of duplicates, list converts it into a list, and sorted returns a new final sorted list.</p><p>To create stoi we are doing <a href="https://www.freecodecamp.org/news/list-comprehension-in-python-with-code-examples/">list comprehension</a>. It is a short hand that does a for-loop iteration over a list, <a href="https://docs.python.org/3/library/functions.html#enumerate">enumerate</a> is an in-built Python that iterates over a list and keeps a count as well.</p><pre>b = {}<br>for w in words:<br>    chs = [&#39;&lt;S&gt;&#39;] + list(w) + [&#39;&lt;E&gt;&#39;]<br>    for ch1,ch2 in zip(chs,chs[1:]):<br>        ix1 = stoi[ch1]<br>        ix2 = stoi[ch2]<br>        N[ix1,ix2] += 1</pre><p>The simplest way to understand this will be start small, lets say you have 4 pairs of numbers (0,1),(1,0),(0,0),(1,1) each having occurred once. Now I want to create a matrix telling how many times each pair is coming. So if I make something like the following :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/132/1*v90fLe3uSB8R8A3KZ8SyLQ.png" /><figcaption>Created using Claude 3.5 sonnet</figcaption></figure><p>Now if I had to find out the number of occurrences of the (0,1) tuple. I will go to the 1st row (0) and to the 2nd column (1), Which will give me back 1.</p><h3>Visualizing the bigram tensor</h3><pre>itos = {i:s for s,i in stoi.items()}</pre><p>I have laid out everything for you to be able to understand this on your own. Take some time, and think what is going here step by step.</p><p>Hint: Think what will stoi.items return!!</p><pre>import matplotlib.pyplot as plt<br>%matplotlib inline<br><br>plt.figure(figsize=(16,16))<br>plt.imshow(N, cmap=&#39;Blues&#39;)<br>for i in range(28):<br>    for j in range(28):<br>        chstr = itos[i] + itos[j]<br>        plt.text(j, i, chstr, ha=&quot;center&quot;, va=&quot;bottom&quot;, color=&#39;gray&#39;)<br>        plt.text(j, i, N[i, j].item(), ha=&quot;center&quot;, va=&quot;top&quot;, color=&#39;gray&#39;)<br>plt.axis(&#39;off&#39;);</pre><p>%matplotlib inline is a Jupyter notebook magic command that ensures plots are displayed inline in the notebook.</p><p>plt.figure(figsize=(16,16)) creates a new figure with a size of 16x16 inches.</p><p>plt.imshow(N, cmap=&#39;Blues&#39;)displays the 2D array N as an image. cmap=&#39;Blues&#39; sets the color map to shades of blue, where darker blue represents higher values.</p><p>plt.text(j, i, chstr, ha=&quot;center&quot;, va=&quot;bottom&quot;, color=&#39;gray&#39;):</p><ul><li>Adds text to the plot at position (j, i).</li><li>The text is the character pair chstr.</li><li>It’s centered horizontally and aligned to the bottom vertically, in gray color.</li></ul><p>plt.text(j, i, N[i, j].item(), ha=&quot;center&quot;, va=&quot;top&quot;, color=&#39;gray&#39;) The text is the value from N[i, j], converted to a Python scalar with .item().</p><p>plt.axis(&#39;off&#39;) Turns off the axis labels and ticks.</p><h3>Deleting spurious (S) and (E) tokens in favor of a single . token</h3><pre>chars = sorted(list(set(&#39;&#39;.join(words))))<br>stoi = {s:i+1 for i,s in enumerate(chars)}<br>stoi[&#39;.&#39;] = 0<br>itos = {i:s for s,i in stoi.items()}</pre><pre>for w in words:<br>    chs = [&#39;.&#39;] + list(w) + [&#39;.&#39;]<br>    for ch1, ch2 in zip(chs, chs[1:]):<br>        ix1 = stoi[ch1]<br>        ix2 = stoi[ch2]<br>        N[ix1, ix2] += 1</pre><pre>import matplotlib.pyplot as plt<br>%matplotlib inline<br><br>plt.figure(figsize=(16,16))<br>plt.imshow(N, cmap=&#39;Blues&#39;)<br>for i in range(27):<br>    for j in range(27):<br>        chstr = itos[i] + itos[j]<br>        plt.text(j, i, chstr, ha=&quot;center&quot;, va=&quot;bottom&quot;, color=&#39;gray&#39;)<br>        plt.text(j, i, N[i, j].item(), ha=&quot;center&quot;, va=&quot;top&quot;, color=&#39;gray&#39;)<br>plt.axis(&#39;off&#39;);</pre><p>This section is pretty much self-explanatory and we have already covered most of the code, so we will be moving forward. (Feel free to drop a comment if you still do not get it though!!)</p><h3>Sampling from the model</h3><pre> N[0]</pre><p>This returns the occurrence of each bigram in our matrix</p><pre>p = N[0].float()<br>p = p / p.sum()<br>p</pre><p>We convert the int32 to float and normalize it by dividing it by the sum of occurrences. Now we have the probability of each bigram occurring.</p><pre>g = torch.Generator().manual_seed(2147483647)<br>p = torch.rand(3, generator=g)<br>p = p / p.sum()<br>p </pre><p>g is a <a href="https://pytorch.org/docs/stable/generated/torch.Generator.html">generator</a> with a manual seed given (this makes the result reproducible). Then torch.rand() generates 3 random numbers between 0 and 1. We normalize them, to get the probability of each one occurring.</p><pre>torch.multinomial(p, num_samples=100, replacement=True, generator=g)</pre><p>The final line uses torch.<a href="https://pytorch.org/docs/stable/generated/torch.multinomial.html">multinomial</a> to sample from this distribution:</p><ul><li>p: the probability distribution to sample from</li><li>num_samples=100: it will draw 100 samples</li><li>replacement=True: sampling with replacement (can pick the same index multiple times), (otherwise the number of samples keeps decreasing)</li><li>generator=g: uses the seeded random generator for reproducibility</li></ul><pre>g = torch.Generator().manual_seed(2147483647)<br><br>for i in range(20):<br>    out = []<br>    ix = 0<br>    while True:<br>        p = N[ix].float()<br>        p = p / p.sum()<br>        ix = torch.multinomial(p, num_samples=1, replacement=True, generator=g).item()<br>        out.append(itos[ix])<br>        if ix == 0:<br>            break<br>    print(&#39;&#39;.join(out))</pre><p>we are starting with ix = 0 as we replaced the start token &lt;S&gt; with . after which we keep updating the value of ix by using the generator to pick a random value from the sample with p probability.</p><h3>Efficiency! vectorized normalization of the rows, tensor broadcasting</h3><pre>P = N.float()<br>P /= P.sum(1, keepdims=True)</pre><p>These 2 lines are pretty information-packed. Even Andrej took his time explaining it and showing what happens if we are not careful. Let us break this down.</p><p>If we just did P.sum() we will get the sum of the entire 2d matrix and a single digit will be returned. For example, let us take a 2d matrix like the following arr_ = [[1,1],[1,1]] if we did arr_.sum() on this, we will get back 4 . So to avoid this, we will define the dimension about which we will be summing it up. Keepdims = True, keeps the original shape preserved. Otherwise, it converts it into a single row.</p><p>Also, we are doing P /= P... instead of P = P/P... as this is more memory efficient, otherwise a new array P will be created and that will be divided. We want to be as efficient as possible, this makes our program run faster in the long run.</p><h3>Loss function (the negative log likelihood of the data under our model)</h3><pre># GOAL: maximize likelihood of the data w.r.t. model parameters (statistical modeling)<br># equivalent to maximizing the log likelihood (because log is monotonic)<br># equivalent to minimizing the negative log likelihood<br># equivalent to minimizing the average negative log likelihood<br><br># log(a*b*c) = log(a) + log(b) + log(c)</pre><p>Let us first talk about log and some of its properties then we can proceed with talking about the Maximum Likelihood Estimation.</p><p>From the definition of log, we know if a = b^c then log(a) base b = c . (The following visualization makes it simple to understand)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/835/1*yV613KHiAijslzonhuUNog.png" /><figcaption>Created using Claude 3.5 Sonnet</figcaption></figure><p>log(a*b) = log(a) + log(b)</p><p>The reason is quite simple, let us break it down.</p><pre># Proof of Logarithm Product Rule: log(a*b) = log(a) + log(b)<br><br>Let&#39;s prove that log(a*b) = log(a) + log(b) for any positive real numbers a and b, and for any positive base c (where c ≠ 1).<br><br>## Given:<br>- Let y = log_c(a*b)<br>- Let u = log_c(a)<br>- Let v = log_c(b)<br><br>## Step 1: Express a and b in terms of u and v<br>By the definition of logarithms:<br>- c^u = a<br>- c^v = b<br><br>## Step 2: Express (a*b) in terms of u and v<br>(a*b) = c^u * c^v<br><br>## Step 3: Use the property of exponents<br>c^u * c^v = c^(u+v)<br><br>## Step 4: Express y in terms of u and v<br>Now we have:<br>y = log_c(a*b) = log_c(c^(u+v))<br><br>## Step 5: Apply the definition of logarithms<br>By the definition of logarithms, if c^x = z, then log_c(z) = x<br>Therefore:<br>y = u + v<br><br>## Step 6: Substitute back the original expressions<br>y = u + v<br>log_c(a*b) = log_c(a) + log_c(b)<br><br>Thus, we have proven that log(a*b) = log(a) + log(b).</pre><p>Now what do we mean when we say log is <a href="https://en.wikipedia.org/wiki/Monotonic_function">monotonic</a>. It simply means that the function either keeps increasing or decreasing.</p><p>Now what do we mean by <a href="https://en.wikipedia.org/wiki/Maximum_likelihood_estimation#:~:text=In%20statistics%2C%20maximum%20likelihood%20estimation,observed%20data%20is%20most%20probable.">Maximum Likelihood Estimation</a>. In simple Maximum Likelihood Estimation (MLE) is a method used to find the best values for the parameters of a statistical model. MLE tries to answer the question: “What parameter values would make our observed data most likely to occur?”</p><p>Here’s how it works:</p><ol><li>We start with some observed data.</li><li>We have a model with parameters that we want to estimate.</li><li>MLE adjusts these parameters to find the values that make our observed data the most probable.</li><li>It does this by maximizing a special function called the likelihood function, which measures how well our model explains the data.</li><li>The parameter values that give the highest likelihood are our best estimates.</li></ol><p>So, instead of minimizing error, MLE is about maximizing the probability of seeing our actual data. It’s like fine-tuning the model to match our observations as closely as possible, given the type of model we’re using.</p><pre>log_likelihood = 0.0<br>n = 0<br><br>for w in words[:3]:<br>    chs = [&#39;.&#39;] + list(w) + [&#39;.&#39;]<br>    for ch1, ch2 in zip(chs, chs[1:]):<br>        ix1 = stoi[ch1]<br>        ix2 = stoi[ch2]<br>        prob = P[ix1, ix2]<br>        logprob = torch.log(prob)<br>        log_likelihood += logprob<br>        n += 1<br>        print(f&#39;{ch1}{ch2}: {prob:.4f} {logprob:.4f}&#39;)<br><br>print(f&#39;{log_likelihood=}&#39;)<br>nll = -log_likelihood<br>print(f&#39;{nll=}&#39;)<br>print(f&#39;{nll/n=}&#39;)</pre><h3>Model smoothing with fake counts</h3><pre>P = (N+1).float()<br>P /= P.sum(1, keepdims=True)</pre><p>Smoothing a model by adding fake counts, often called “additive smoothing” or “Laplace smoothing,” is a technique used to handle the problem of zero probabilities in statistical models, especially in natural language processing and machine learning.</p><p>We take care of cases where the likelihood of an occurrence occurring is zero, like the case of jq occurring in our bigram sample space. By adding 1 to all the samples, we have effectively smoothed the whole sample space.</p><h3>PART 2: the neural network approach: intro</h3><h3>Creating the bigram dataset for the neural net</h3><pre># create the training set of bigrams (x,y)<br>xs, ys = [], []<br><br>for w in words[:1]:<br>    chs = [&#39;.&#39;] + list(w) + [&#39;.&#39;]<br>    for ch1, ch2 in zip(chs, chs[1:]):<br>        ix1 = stoi[ch1]<br>        ix2 = stoi[ch2]<br>        print(ch1, ch2)<br>        xs.append(ix1)<br>        ys.append(ix2)<br><br>xs = torch.tensor(xs)<br>ys = torch.tensor(ys)</pre><p>Now there is a difference between torch.tensor and torch.Tensor, The difference that Andrej found was that tensor with a small t returns a float whereas Tensor with a capital T returns an int. More details <a href="https://discuss.pytorch.org/t/difference-between-torch-tensor-and-torch-tensor/30786">here</a>.</p><h3>Feeding integers into neural nets? one-hot encodings</h3><pre>import torch.nn.functional as F<br>xenc = F.one_hot(xs, num_classes = 27).float()</pre><p>One hot encoding technique. That is used to convert individual classes into row vectors. For example let us say I have a basket, which has 3 kinds of fruit inside of it. Apples, oranges and bananas, now a model does not take words but rather numbers. So if we follow the same order and do the one hot encoding. If we take a banana out from the basket we will say 0,0,1 . There is another kind of encoding done for classes, that is label encoding. But we are not taking about that right now. More details <a href="https://www.kaggle.com/code/aleksandrmogilevskiy/pytorch-labelencoding-vs-onehotencoding">here</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/850/1*HSnXz5Dp6_7FZ-bD5CTgwg.png" /><figcaption>Created using Claude 3.5 Sonnet</figcaption></figure><h3>The “neural net”: one linear layer of neurons implemented with matrix multiplication</h3><pre>W = torch.randn((27,27))<br>xenc @ W</pre><p>We have created 27 neurons which take 27 inputs each and then matrix multiplied it with W. Matrix multiplication is quite different from how normal multiplication works. The following illustration may help you understand.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1S9Z98SRcSXJQ6FtCs-o9A.png" /><figcaption>Created using Claude 3.5 Sonnet</figcaption></figure><p>A simple rule to remember is nXm @ mXn = nXn the columns of the 1st matrix should match the number of rows of the 2nd matrix.</p><h3>Transforming neural net outputs into probabilities: the softmax</h3><pre>logits = xenc @ W  # log-counts<br>counts = logits.exp()  # equivalent N<br>probs = counts / counts.sum(1, keepdims=True)<br>probs</pre><p>Now to a more accurate representation</p><pre>logits = xenc @ W  # raw scores, not actually log-counts<br>counts = logits.exp()  # exponentiating the raw scores<br>probs = counts / counts.sum(1, keepdims=True)  # normalizing to get probabilities</pre><p>logits are indeed short for log-counts but here they aren’t exactly the logarithmic counts but rather the raw output from the matrix multiplication. Then we exponentiate it, so the output is always greater than 0. Then normalize it, to get the probability.</p><h3>Summary, preview to next steps, reference to micrograd</h3><pre>xs<br><br>ys<br><br><br># randomly initialize 27 neurons&#39; weights. each neuron receives 27 inputs<br>g = torch.Generator().manual_seed(2147483647)<br>W = torch.randn((27, 27), generator=g)<br><br>xenc = F.one_hot(xs, num_classes=27).float() # input to the network: one-hot encoding<br>logits = xenc @ W # predict log-counts<br>counts = logits.exp() # counts, equivalent to N<br>probs = counts / counts.sum(1, keepdims=True) # probabilities for next character<br># btw: the last 2 lines here are together called a &#39;softmax&#39;<br><br>probs.shape<br><br><br>nlls = torch.zeros(5)<br>for i in range(5):<br>  # i-th bigram:<br>  x = xs[i].item() # input character index<br>  y = ys[i].item() # label character index<br>  print(&#39;--------&#39;)<br>  print(f&#39;bigram example {i+1}: {itos[x]}{itos[y]} (indexes {x},{y})&#39;)<br>  print(&#39;input to the neural net:&#39;, x)<br>  print(&#39;output probabilities from the neural net:&#39;, probs[i])<br>  print(&#39;label (actual next character):&#39;, y)<br>  p = probs[i, y]<br>  print(&#39;probability assigned by the net to the the correct character:&#39;, p.item())<br>  logp = torch.log(p)<br>  print(&#39;log likelihood:&#39;, logp.item())<br>  nll = -logp<br>  print(&#39;negative log likelihood:&#39;, nll.item())<br>  nlls[i] = nll</pre><p>My writing cannot do justice to this part. Everything has been explained by Andrej. Even if you do not watch the whole video, watch this part again.</p><h3>Vectorized loss</h3><pre>xs #tensor([ 0,  5, 13, 13,  1])<br>ys #tensor([ 5, 13, 13,  1,  0])<br><br># randomly initialize 27 neurons&#39; weights. each neuron receives 27 inputs<br>g = torch.Generator().manual_seed(2147483647)<br>W = torch.randn((27, 27), generator=g)<br><br># forward pass<br>xenc = F.one_hot(xs, num_classes=27).float() # input to the network: one-hot encoding<br>logits = xenc @ W # predict log-counts<br>counts = logits.exp() # counts, equivalent to N<br>probs = counts / counts.sum(1, keepdims=True) # probabilities for next character<br>loss = -probs[torch.arange(5), ys].log().mean()</pre><p>I believe the question that all of us had at this point was, why use torch.arange(5) and not just xs.</p><p>Let us understand step by step. We have converted xs into probs, which is a vector or likelihood. Now for xs[0] we want to get the value present at ys[0] because when xs[0] is given as an input we want the output to be ys[0] or the prediction.</p><p>Now probs contain the likelihood of each class occurrence. We want to maximize the occurrence of the class that corresponds to ys[0] . That is why we are calculating its loss. Simple way to think is, we have replaced xs with probs and the values are it’s indices now.</p><h3>Backward and update, in PyTorch</h3><pre>W.grad = None<br>loss.backward()<br>W.data += -0.1 * W.grad</pre><p>I have covered this portion of backpropagation in my previous post, kindly consider reading that. Thank you!!</p><h3>Putting everything together</h3><pre># create the dataset<br>xs, ys = [], []<br>for w in words:<br>    chs = [&#39;.&#39;] + list(w) + [&#39;.&#39;]<br>    for ch1, ch2 in zip(chs, chs[1:]):<br>        ix1 = stoi[ch1]<br>        ix2 = stoi[ch2]<br>        xs.append(ix1)<br>        ys.append(ix2)<br>xs = torch.tensor(xs)<br>ys = torch.tensor(ys)<br>num = xs.nelement()<br>print(&#39;number of examples: &#39;, num)<br><br># initialize the &#39;network&#39;<br>g = torch.Generator().manual_seed(2147483647)<br>W = torch.randn((27, 27), generator=g, requires_grad=True)<br><br><br># gradient descent<br>for k in range(100):<br>    <br>    # forward pass<br>    xenc = F.one_hot(xs, num_classes=27).float() # input to the network: one-hot encoding<br>    logits = xenc @ W # predict log-counts<br>    counts = logits.exp() # counts, equivalent to N<br>    probs = counts / counts.sum(1, keepdims=True) # probabilities for next character<br>    loss = -probs[torch.arange(num), ys].log().mean()<br>    print(loss.item())<br>    <br>    # backward pass<br>    W.grad = None # set to zero the gradient<br>    loss.backward()<br>    <br>    # update<br>    W.data += -50 * W.grad</pre><h3>Note 1: one-hot encoding really just selects a row of the next Linear layer’s weight matrix</h3><p>Here Andrej mentions something important, What the end result W (the weights of the model) is essentially equal to what we arrived by counting. Now remember counting is not a scalable approach, that is why we are doing a gradient based approach.</p><h3>Note 2: model smoothing as regularization loss</h3><p>There are many kind of <a href="https://www.ibm.com/topics/regularization">regularization </a>in ML. Over here we are talking about L2 regularization. Regularization is like putting training wheels on a bicycle when you’re learning to ride. It helps prevent the model from going too wild or becoming overly specific to the training data.</p><p>Key points about regularization:</p><ol><li>Prevents overfitting: It stops the model from memorizing the training data and helps it generalize better to new, unseen data.</li><li>Adds a penalty: It discourages the model from using overly complex solutions by adding a cost to complexity.</li><li>Simplifies the model: It pushes the model towards simpler explanations of the data.</li><li>Balances bias and variance: It helps find a sweet spot between underfitting (too simple) and overfitting (too complex).</li></ol><h3>Sampling from the neural net</h3><pre># finally, sample from the &#39;neural net&#39; model<br>g = torch.Generator().manual_seed(2147483647)<br><br>for i in range(5):<br>    out = []<br>    ix = 0<br>    while True:<br>        # -----------<br>        # BEFORE:<br>        #p = P[ix]<br>        # -----------<br>        # NOW:<br>        xenc = F.one_hot(torch.tensor([ix]), num_classes=27).float()<br>        logits = xenc @ W # predict log-counts<br>        counts = logits.exp() # counts, equivalent to N<br>        p = counts / counts.sum(1, keepdims=True) # probabilities for next character<br>        # -----------<br>        <br>        ix = torch.multinomial(p, num_samples=1, replacement=True, generator=g).item()<br>        out.append(itos[ix])<br>        if ix == 0:<br>            break<br>    print(&#39;&#39;.join(out))</pre><h3>Conclusion</h3><p>CONGRATULATIONS you have finished this blog.</p><h3>A note</h3><p>If you enjoyed the content, consider following.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9ed4a6fc09a0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Andrej’s Zero to Hero for Dummies Part 1]]></title>
            <link>https://goyalpramod.medium.com/andrejs-zero-to-hero-for-dummies-part-1-d1f50a810077?source=rss-763a19c7d326------2</link>
            <guid isPermaLink="false">https://medium.com/p/d1f50a810077</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Goyalpramod]]></dc:creator>
            <pubDate>Wed, 26 Jun 2024 13:20:35 GMT</pubDate>
            <atom:updated>2024-07-13T05:29:04.831Z</atom:updated>
            <content:encoded><![CDATA[<p>We all love Andrej and everything he has done for the community. If it weren’t for him, we probably would have been behind a decade of progress.</p><p>He is an exceptional teacher, with a very clear and concise method of teaching. When I watched his video as a complete beginner, it took me roughly 3 weeks to make entire sense of it.</p><p>So I have created this blog series, where I will be going through his playlist. Explaining the most minute things he talks about, so even an absolute beginner can understand it.</p><p>I will recommend you have a basic understanding of Python first, that is the only prerequisite. There are plenty of amazing resources on the web, but if you are confused. Consider going through my previous <a href="https://medium.com/@goyalpramod1729/5-years-of-python-in-5-minutes-fe2af04c2fa6">post</a> on the topic.</p><p>(This blog post is an inspiration from his original video, I urge you first to complete that. And come back here to aid in your understanding, I will be following the same chapter sections he follows on Youtube)</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FVMj-3S1tku0%3Flist%3DPLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ%26start%3D5512&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DVMj-3S1tku0&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FVMj-3S1tku0%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/7984633a71e6c800d1122feb003713d0/href">https://medium.com/media/7984633a71e6c800d1122feb003713d0/href</a></iframe><h3>Intro</h3><p>Andrej talks a bit about himself</p><h3>Micrograd Overview</h3><p><a href="https://github.com/karpathy/micrograd">https://github.com/karpathy/micrograd</a> -&gt; the repository we are going to be talking about, kindly have this opened.</p><p>“A tiny Autograd engine (with a bite! :)). Implements <strong>backpropagation </strong>(<strong>reverse-mode autodiff</strong>) over a dynamically built <strong>DAG </strong>and a small <strong>neural networks</strong> library on top of it with a <strong>PyTorch</strong>-like API. Both are tiny, with about 100 and 50 lines of code respectively. The DAG only operates over <strong>scalar values</strong>, so e.g. we chop up each neuron into all of its individual tiny adds and multiplies. However, this is enough to build up entire deep neural nets doing <strong>binary classification</strong>, as the demo notebook shows. Potentially useful for educational purposes.”</p><p>I have highlighted the scariest terms, let us begin by analyzing them first.</p><h4>Backpropagation</h4><p>Throughout this tutorial I want you to be sure of two things.</p><p>We know what the input is (what we are giving to our machine learning black box) and what we want the output to be (what the machine learning black box returns.)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qrHC_I7Ybe7AYo8nwu_Eyw.png" /><figcaption>Created with Claude 3.5 Sonnet</figcaption></figure><p>Think that you are driving a car down an unknown territory. The only thing that you know is the end location.</p><p>Let us imagine you first go down path 2 (the yellow line) and end up in a dead-end. What do you do? backtrack to where you began, know that this path won&#39;t lead to the desired output (the end location), so you change your streaming direction and go down another path. You keep doing this back and forth (iteration) till you reach your goal.</p><p>Notice a few things,</p><ul><li>Not all paths will lead you to the desired location</li><li>Some paths take longer than others but you still reach the desired goal</li><li>Path 1 (red line) is the best path and if you were lucky enough to start with this, you wouldn&#39;t have to take so many detours. (the ideal case)</li></ul><p>This car navigation analogy, while simplified, gives us a good intuition about backpropagation. Now, let’s explore a related concept that’s crucial to understanding how neural networks learn: reverse-mode auto differentiation.</p><h4>Reverse-Mode Autodiff</h4><p>Reverse-mode autodiff, often simply called ‘reverse-mode autodiff’, is a powerful mathematical technique that underlies backpropagation. While backpropagation gives us the big picture of how neural networks adjust their parameters, reverse-mode autodiff provides a detailed mechanism for calculating gradients efficiently.</p><p>Let us visualize this.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w0NsWLSjzxZzLvnd78sFgw.png" /><figcaption>Created with Claude 3.5 Sonnet</figcaption></figure><p>Differentiation means the rate of change of a variable with respect to another variable. In essence how fast does one variable(this is an arbitrary value, simply called a variable) change when I tweak another variable?</p><p>Let’s say you want to make some delicious pizza (yum!!) now if I quickly add a lot of dough, how much will that affect the output. In our case our pizza.</p><p>Reverse-Mode Autodiff and Backpropagation are the same thing, but these two analogies may seem different. So let me connect them for you, in our car variable, we are changing one variable only, that is the steering. In the pizza example, we are tweaking multiple variables, like dough, sauce, cheese, etc.</p><h4>DAG (Directed Acyclic Graph)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tSJRuqxDxLYBJ1yYuj4Rrw.png" /><figcaption>Created with Claude 3.5 Sonnet</figcaption></figure><p>Directed Acyclic Graphs (DAGs) are a fundamental concept in computer science, particularly in data structures and algorithms. While a deep dive into DAGs isn’t essential for our current discussion, understanding their basic idea can enhance your grasp of how neural networks process information.</p><p>Think of a DAG as a flowchart where information moves in one direction without ever looping back. This structure is crucial in representing the flow of data and computations in neural networks. If you’re curious to learn more about DAGs, Here is a nice <a href="https://medium.com/@hamzasurti/advanced-data-structures-part-1-directed-acyclic-graph-dag-c1d1145b5e5a">guide</a> on it.</p><p>For now, let’s focus on neural networks.</p><h4>Neural Networks</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yFamSgfTft5GmQlWLknhgQ.png" /><figcaption>Created with Claude 3.5 Sonnet</figcaption></figure><p>Neural networks are a branch of machine learning which is in part inspired by how our brains work using neurons. I have provided a very basic illustration of a neural network. We will be diving deep into it, and explaining everything in detail as we move forward.</p><h4>PyTorch</h4><p><a href="https://github.com/pytorch/pytorch">GitHub - pytorch/pytorch: Tensors and Dynamic neural networks in Python with strong GPU acceleration</a></p><p>Do not reinvent the wheel!! PyTorch is a library that makes it easier for us to create and test machine-learning models. Python is riddled with multiple libraries that are our bread and butter. And if you plan on becoming a serious data scientist, you will be using them daily.</p><h4>Scalar Values</h4><p>Scalar values represent the magnitude of an entity(The absolute value, like 5, 12, 42, etc). They are from linear algebra. If you want to brush up on your linear algebra. I recommend <a href="https://www.youtube.com/watch?v=kjBOesZCoqc&amp;list=PL0-GT3co4r2y2YErbmuJw2L5tW4Ew2O5B">3Blue1Brown</a>.</p><h4>Binary Classification</h4><p>Bi-nary, two-number. Binary classification means anything that can be classified in either of two categories. You can tell if something is a potato or not by looking at it.</p><p>If this is your first time hearing about binary classification, I will recommend you do not move any further and first get done with the following two things:</p><ol><li>Learn and get used to Python</li><li>Learn basic ML, I will recommend <a href="https://www.youtube.com/@Deeplearningai">Andrew Ng’s lectures</a></li></ol><h4>On to the fun part i.e. THE CODE!!!</h4><pre>from micrograd.engine import Value<br><br>a = Value(-4.0)<br>b = Value(2.0)<br>c = a + b<br>d = a * b + b**3<br>c += c + 1<br>c += 1 + c + (-a)<br>d += d * 2 + (b + a).relu()<br>d += 3 * d + (b - a).relu()<br>e = c - d<br>f = e**2<br>g = f / 2.0<br>g += 10.0 / f<br>print(f&#39;{g.data:.4f}&#39;) # prints 24.7041, the outcome of this forward pass<br>g.backward()<br>print(f&#39;{a.grad:.4f}&#39;) # prints 138.8338, i.e. the numerical value of dg/da<br>print(f&#39;{b.grad:.4f}&#39;) # prints 645.5773, i.e. the numerical value of dg/db</pre><p>When we type a = Value(-4.0) it means we are initializing an object of the class Value with a value of -4.0. Let us have a deeper look at the class Value.</p><pre>class Value:<br>    &quot;&quot;&quot; stores a single scalar value and its gradient &quot;&quot;&quot;<br><br>    def __init__(self, data, _children=(), _op=&#39;&#39;):<br>        self.data = data<br>        self.grad = 0<br>        # internal variables used for autograd graph construction<br>        self._backward = lambda: None<br>        self._prev = set(_children)<br>        self._op = _op # the op that produced this node, for graphviz / debugging / etc<br><br>    def __add__(self, other):<br>        other = other if isinstance(other, Value) else Value(other)<br>        out = Value(self.data + other.data, (self, other), &#39;+&#39;)<br><br>        def _backward():<br>            self.grad += out.grad<br>            other.grad += out.grad<br>        out._backward = _backward<br><br>        return out<br><br>    def __mul__(self, other):<br>        other = other if isinstance(other, Value) else Value(other)<br>        out = Value(self.data * other.data, (self, other), &#39;*&#39;)<br><br>        def _backward():<br>            self.grad += other.data * out.grad<br>            other.grad += self.data * out.grad<br>        out._backward = _backward<br><br>        return out<br><br>    def __pow__(self, other):<br>        assert isinstance(other, (int, float)), &quot;only supporting int/float powers for now&quot;<br>        out = Value(self.data**other, (self,), f&#39;**{other}&#39;)<br><br>        def _backward():<br>            self.grad += (other * self.data**(other-1)) * out.grad<br>        out._backward = _backward<br><br>        return out<br><br>    def relu(self):<br>        out = Value(0 if self.data &lt; 0 else self.data, (self,), &#39;ReLU&#39;)<br><br>        def _backward():<br>            self.grad += (out.data &gt; 0) * out.grad<br>        out._backward = _backward<br><br>        return out<br><br>    def backward(self):<br><br>        # topological order all of the children in the graph<br>        topo = []<br>        visited = set()<br>        def build_topo(v):<br>            if v not in visited:<br>                visited.add(v)<br>                for child in v._prev:<br>                    build_topo(child)<br>                topo.append(v)<br>        build_topo(self)<br><br>        # go one variable at a time and apply the chain rule to get its gradient<br>        self.grad = 1<br>        for v in reversed(topo):<br>            v._backward()<br><br>    def __neg__(self): # -self<br>        return self * -1<br><br>    def __radd__(self, other): # other + self<br>        return self + other<br><br>    def __sub__(self, other): # self - other<br>        return self + (-other)<br><br>    def __rsub__(self, other): # other - self<br>        return other + (-self)<br><br>    def __rmul__(self, other): # other * self<br>        return self * other<br><br>    def __truediv__(self, other): # self / other<br>        return self * other**-1<br><br>    def __rtruediv__(self, other): # other / self<br>        return other * self**-1<br><br>    def __repr__(self):<br>        return f&quot;Value(data={self.data}, grad={self.grad})&quot;</pre><p>Do not be scared, it is quite long. But easy to break down. Let us go step by step:</p><h4>“””</h4><ul><li>The triple-double quotes show a <a href="https://peps.python.org/pep-0257/"><em>docstring</em></a>. A docstring is a way to explain what the class does, the parameters it takes, and the result it returns. It does not affect the program, it is written to increase the readability of the code.</li></ul><h4>def __init__(self, data, _children=(), _op=&#39;&#39;):</h4><ul><li>init is a <a href="https://realpython.com/python-magic-methods/">dunder method</a> that initializes (not a constructor, <a href="https://stackoverflow.com/questions/28791639/initializer-vs-constructor">read more</a>) an object of the class. Dunder methods (magic methods) are special methods in Python, characterized by a double underscore on each side.</li><li><a href="https://stackoverflow.com/questions/2709821/what-is-the-purpose-of-the-self-parameter-why-is-it-needed">self </a>-&gt; This is the specific instance of the class. It’s how Python provides you access to the object’s attributes and other methods from inside the class. In simple terms, it’s used to give identity to your class. if you write self.name = name, which is the name of the class itself now.</li><li>data -&gt; This is the information that is passed. Now we can assign this to an instance of the class.</li><li>_children=() -&gt; initialize a tuple with name _children</li><li>_op=’’ -&gt; initialize a variable with empty string.</li><li>quick note -&gt; if the term lambda seems new to you, I will recommend going through my ‘5 years of Python in 5 minutes’ where I talk about it.</li></ul><h4><strong>def __add__(self, other):</strong></h4><p>Here 5 is an object of the class int, and so is 2. Here other is not an instance of the class. It is not inside it or part of it, but 5 is. So what essentially happens</p><ul><li>When we type 5 + 2, the method __add__ gets called and the parameters are filled like the following (5).__add__(2) 5 calls the method add and sends 2 as a parameter.</li></ul><p>Let us go step by step:</p><ul><li>num_1 = Value(4.0) # Initializing an instance of the class, we now have an object named num_1, this is a class. Now if you look at __init__ num_1.data has the value of 4.0</li><li>num_2 = Value(2.0) # similar as above</li><li>Now when I do num_1 + num_2 it is possible because. Both these instances of a class have the value data. So what happens internally is (num_1.data)__add__(num_2.data)</li></ul><p>But look at what happens when we try to do the following:</p><pre>result = num_1 + 2<br>print(result)</pre><p>What is happening internally is</p><p>(num_1.data)__add__(2.data)</p><p>We will run into an attribute error. Because 2 is of class int, and the class int does not have any method data.</p><p>To fix this issue we are running the following code</p><pre>other = other if isinstance(other, Value) else Value(other)</pre><p>This checks if the incoming variable is of the instance (same class as the class that is calling the method) if not, it initializes it as such.</p><p>You can understand the rest of the dunder functions following the provided links, but they all essentially follow the same principles that have been defined.</p><p>Also quickly before I forget</p><pre>print(f&#39;{b.grad:.4f}&#39;)</pre><p>:.{num}f is a <a href="https://realpython.com/python-string-formatting/">formatting</a> technique used in Python to tell Python how many decimal points it needs to show.</p><h3>Derivative of a simple function with one input</h3><p>Let us start by going the code that Andrej writes one by one</p><pre>import math<br>import numpy as np<br>import matplotlib.pyplot as plt<br>%matplotlib inline</pre><p>Here we are importing the important libraries. When we type “as” that gives an alias to a library, so instead of typing numpy.{method} we can simply write np.{method}.</p><p>%matplotlib inline allows you to directly show the graph below the code that you write</p><pre>def f(x):<br>    return 3*x**2 - 4*x + 5<br><br>f(3.0) #prints 20.0</pre><p>Here we are defining a function, we can see that it is a <a href="https://mathworld.wolfram.com/QuadraticEquation.html">quadratic equation</a></p><pre>xs = np.arange(-5, 5, 0.25)<br>ys = f(xs)<br>plt.plot(xs, ys)</pre><p>np.arange(-5,5,0.25) generates evenly spaced values between a given interval. It will start at -5 and end at 5 with a step size of 0.25 it (-5,-4.75,-4.5…so on)</p><p>ys stores the output from the function</p><p>and plt.plot plots it. Which looks something like this</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/837/1*nEYt6sc5PRoNj5mrjv8dFw.png" /><figcaption>Curve of the equation in the interval -5 to 5</figcaption></figure><p>When we are differentiating a curve on a point. We are trying to find the slope of the equation at that particular point.</p><p>By looking at the definition given in <a href="https://en.wikipedia.org/wiki/Derivative">Wikipedia</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/353/1*465v_PvWs9A-d7rUe1dKYQ.png" /><figcaption>Taken from Wikipedia</figcaption></figure><p>If we try to break the equation down. It is calculated how much the value of f(a) changes when we introduce a very small variable h (h limits to zero means h is very small)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TXf6ShosRRPgP4lWAToVPw.png" /><figcaption>Created using Claude 3.5 Sonnet</figcaption></figure><p>The slope at any point can be simply understood as, if you were at that point and you moved in any direction for a very tiny amount, how much will you rise up or fall down relative to your original position.</p><pre>h = 0.00000001<br>x = -3.0<br>(f(x + h) - f(x)) / h #prints -22.00000039920269, the slope</pre><p>(as Andrej pointed out, we do not manually calculate the derivative every time. Keep that in your head)</p><h3>Derivative of a function with multiple inputs</h3><pre>h = 0.0001<br><br># inputs<br>a = 2.0<br>b = -3.0<br>c = 10.0<br><br>d1 = a*b + c<br>a += h<br>d2 = a*b + c<br><br>print(&#39;d1&#39;, d1) #4.0<br>print(&#39;d2&#39;, d2) #3.999699999999999<br>print(&#39;slope&#39;, (d2 - d1)/h) #-3.000000000010772</pre><p>I have been explaining things with illustrations and visuals, but I will not do so for the above equation. Why?</p><p>Because it is an equation with 3 variables, i.e. 3 dimensions, it won&#39;t have an equation of a curve but rather a plane.</p><p>If you google the slope of a plane in 3d you will get a lot of scary-looking images, but the simplest way to think about it is. If I pick one direction (1 variable) and move towards it or away from it. How much does the final value change relative to the initial value.</p><p>That is what we are trying to find out through the above code. When the value of a is nudged a bit forward. How much does this new value change relative to the old value. I.e. the slope.</p><p>I will recommend going through some of the <a href="https://en.wikipedia.org/wiki/Differentiation_rules">Differentiation rules and formulas</a>. Do not worry if they don&#39;t make sense, we will keep making it simpler as we dive into the article. These are for people who are used to calculus and just need to have a refresher look.</p><p>Andrej continues to show how changing the other variable will change d, but I believe you have the intuition so I will not repeat the same. (You are smart! don&#39;t let anyone else tell you otherwise.)</p><h3>Starting the core Value object of micrograd and its visualization</h3><pre>class Value:<br>    def __init__(self, data, _children=(), _op=&#39;&#39;, label=&#39;&#39;):<br>        self.data = data<br>        self.grad = 0 #initially we assume there is no affect on the output<br>        self._prev = set(_children)<br>        self._op = _op<br>        self.label = label<br><br>    def __repr__(self):<br>        return f&quot;Value(data={self.data})&quot;<br><br>    def __add__(self, other):<br>        out = Value(self.data + other.data, (self, other), &#39;+&#39;)<br>        return out<br><br>    def __mul__(self, other):<br>        out = Value(self.data * other.data, (self, other), &#39;*&#39;)<br>        return out<br><br>a = Value(2.0, label=&#39;a&#39;)<br>b = Value(-3.0, label=&#39;b&#39;)<br>c = Value(10.0, label=&#39;c&#39;)<br>e = a*b; e.label = &#39;e&#39;<br>d = e + c; d.label = &#39;d&#39;<br>f = Value(-2.0, label=&#39;f&#39;)<br>L = d * f; L.label = &#39;L&#39;<br>L<br>print(d) #Value(data=4.0)<br>print(d._prev) #{Value(data=-6.0), Value(data=10.0)}<br>print(d._op) #+</pre><p>We have already built an intuition about the code in the beginning, now let us understand what _children and _op do and how are we filling those parameters if we did not pass any arguments when we initialized a,b or c.</p><p>Let us start by having a deeper look at __add__</p><pre>new_var = a + b<br>print(type(new_var)) #__main__.Value</pre><p>As we know the value on the right is assigned to the variable on the left. So what happens on the left internally?</p><p>This is what goes on</p><p>Value(a.data + b.data, (a,b), &#39;+&#39;)</p><p>And now this Value instance is assigned to new_var, with instances a and b as its children. And we got this parent through the addition operation</p><p>(If we go by intuition, new_var was created by a and b, so a and b should be the parent of new_var. But it is easier to understand the other way around in a tree structure. So that is the convention we follow)</p><p>So now if we have to understand d we can break it down as so</p><p>d=a*b + c on the right hand side what happens is:</p><p>Value(((a.data)__mul__(b.data), (a,b), (&#39;*&#39;))__add__(c.data), ((a*b),c), &#39;+&#39;)</p><p>Take a bit of time why it is (a*b) and now (a,b)</p><p>Hint: what is the new instance of the class, or what is self now?</p><p>Answer&gt; ((a.data)__mul__(b.data), (a,b), (&#39;*&#39;) becomes the new instance, and the value of self.data for this instance is = a*b</p><p>d=e + c; d.label = ‘d’</p><p>This was my first time seeing ; it in Python, so I was extremely confused as to what it did. Let us find out together.</p><p>The semicolon in Python allows you to write multiple statements on a single line.</p><p>so Andrej could have written</p><pre>d = e + c <br>d.label = &#39;d&#39;</pre><p>This would have been valid as well, as for d.label. This works because label is an attribute of the Value class, initialized in the __init__ method.</p><p>In Python, you can access and modify object attributes using dot notation.</p><pre>from graphviz import Digraph<br><br>def trace(root):<br>    # builds a set of all nodes and edges in a graph<br>    nodes, edges = set(), set()<br>    def build(v):<br>        if v not in nodes:<br>            nodes.add(v)<br>            for child in v._prev:<br>                edges.add((child, v))<br>                build(child)<br>    build(root)<br>    return nodes, edges<br><br>def draw_dot(root):<br>    dot = Digraph(format=&#39;svg&#39;, graph_attr={&#39;rankdir&#39;: &#39;LR&#39;}) # LR = left to right<br><br>    nodes, edges = trace(root)<br>    for n in nodes:<br>        uid = str(id(n))<br>        # for any value in the graph, create a rectangular (&#39;record&#39;) node for it<br>        dot.node(name = uid, label = &quot;{%s | data %.4f | grad %.4f}&quot; % (n.lable,n.data,n.grad ), shape=&#39;record&#39;)<br>        if n._op:<br>            # if this value is a result of some operation, create an op node for it<br>            dot.node(name = uid + n._op, label = n._op)<br>            # and connect this node to it<br>            dot.edge(uid + n._op, uid)<br><br>    for n1, n2 in edges:<br>        # connect n1 to the op node of n2<br>        dot.edge(str(id(n1)), str(id(n2)) + n2._op)<br><br>    return dot</pre><p>(I had an issue creating the graph on my setup, so I will be attaching screenshots from the YouTube video itself. I am sorry for the inconvenience caused)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b8NvEu7hYEEwWOXAcYkiOA.png" /><figcaption>Screenshot from Youtube</figcaption></figure><p>We go with the assumption that initial derivative of L with respect to d is zero</p><h3>Manual backpropagation example #1: simple expression</h3><p>{To be continued..}</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d1f50a810077" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[5 years of Python in 5 minutes]]></title>
            <link>https://goyalpramod.medium.com/5-years-of-python-in-5-minutes-fe2af04c2fa6?source=rss-763a19c7d326------2</link>
            <guid isPermaLink="false">https://medium.com/p/fe2af04c2fa6</guid>
            <category><![CDATA[learning]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[beginner]]></category>
            <category><![CDATA[coding]]></category>
            <dc:creator><![CDATA[Goyalpramod]]></dc:creator>
            <pubDate>Mon, 24 Jun 2024 12:45:36 GMT</pubDate>
            <atom:updated>2024-06-24T12:45:36.242Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*lqExoBndGZnJJaoV" /><figcaption>Photo by <a href="https://unsplash.com/@cdr6934?utm_source=medium&amp;utm_medium=referral">Chris Ried</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Python is one of the most beginner-friendly and versatile languages. I plan to make a blog series explaining multiple ML concepts and ideas in depth with illustrations. But I believe before we can start with that, we have to get some prerequisites cleared. This is less of a guide and more of a crash course. In the end, I will also point to the direction I would go to learn some more complex ideas if I were a complete beginner.</p><h3>What’s in a name! (Variables &amp; Data Type)</h3><pre>_str = &quot;a&quot;<br>_str = &quot;Pramod&quot;<br>_int = 1<br>_float = 1.0<br>_bool = True</pre><p>Variables in programming are just like the way we had in grade 6 mathematics. They are names that you assign a value to.</p><p>There is a <a href="https://peps.python.org/pep-0008/#function-and-variable-names">convention </a>that one needs to follow while naming their variables. Like a variable name cannot begin with a number, that will throw an error.</p><p>After naming a variable, the values are assigned right to left. And different values have different <a href="https://realpython.com/python-data-types/">data types</a>. I have covered the most basic ones that are used often, along with the name of the datatype.</p><h3>Why was 6 afraid of 7? Because 7 8 9 (Arithemetic &amp; Logical Operations)</h3><pre>_num1 = 2<br>_num2 = 1<br><br># Arithmatic Operation <br>print(_num1 + _num2) # Add, Answer = 3<br>print(_num1 - _num2) # Subtract, Answer = 1<br>print(_num1 * _num2) # Multiple, Answer = 2<br>print(_num1 / _num2) # Float divide, Answer = 2.0<br>print(_num1 // _num2) # Int divide, Answer = 2<br>print(_num1 ** _num2) # Pow, Answer = 2<br><br># Logical Operations<br>print(&quot;\nLogical Operations:&quot;)<br>print(_num1 &gt; _num2)   # Greater than, Answer = True<br>print(_num1 &lt; _num2)   # Less than, Answer = False<br>print(_num1 &gt;= _num2)  # Greater than or equal to, Answer = True<br>print(_num1 &lt;= _num2)  # Less than or equal to, Answer = False<br>print(_num1 == _num2)  # Equal to, Answer = False<br>print(_num1 != _num2)  # Not equal to, Answer = True<br><br># Boolean Operations<br>print(&quot;\nBoolean Operations:&quot;)<br>print(True and False)  # Logical AND, Answer = False<br>print(True or False)   # Logical OR, Answer = True<br>print(not True)        # Logical NOT, Answer = False<br><br># = is known as the assignment operator and it assigns the value right to left<br></pre><p>When multiple operation are present in the same time, the program follows an <a href="https://runestone.academy/ns/books/published/fopp/Conditionals/PrecedenceofOperators.html">order of operation</a> execution. The important thing to remember is, cross datatype operation is not possible in Python. You cannot write “pramod” + 5 and expect to get back pramod5. However you can do “pramod” + “5” and get back “pramod5”</p><h3>Go with the flow (Control Flow)</h3><pre># If-Else Statement<br>_num = 2<br>if _num &gt;= 1: # This does a boolean expression and checks if the given condition is true<br>    print(&quot;Number is Positive&quot;)<br>elif _num &lt;= -1: # Check if this condition is true<br>    print(&quot;Number is Negative&quot;)<br>else: # If the given conditions are false, execute the else statement<br>    print(&quot;Number is Negative&quot;)<br><br># For Loop<br>fruits = [&quot;apple&quot;, &quot;banana&quot;, &quot;mango&quot;]<br>for fruit in fruits: <br>    print(f&quot;I like {fruit}&quot;)<br><br># While Loop<br>count = 0<br>while count &lt; 5:<br>    print(f&quot;Count is {count}&quot;)<br>    count += 1 # Always have an exit loop condition. Otherwise you will be stuck in an infinite loop<br><br># Break and Continue<br>for number in range(10):<br>    if number == 3:<br>        continue  # Skip 3, if the above condition is true. Move on.<br>    if number == 8:<br>        break  # Stop at 8, If the above condition is true. Get out of the loop!<br>    print(number)</pre><p>A few important things to remember, you can nest these control statements, so you can have a for loop inside a for loop and so on.</p><p>There is a less used Control flow logic, called <a href="https://www.freecodecamp.org/news/python-switch-statement-switch-case-example/">switch statements</a> but I have seldom seen them used in practice.</p><p>Another important fact to remember is, that in Python. For loops act like iterator and are different from the way for loops function in other languages like c/c++ or java. For more details read this <a href="https://www.reddit.com/r/learnpython/comments/stviu5/why_does_the_for_loop_work_so_different_from/">thread</a>.</p><h3>Fuctions are cool! (Functions)</h3><pre># Simple function<br>def greet(name):<br>    return f&quot;Hello, {name}!&quot;<br><br>print(greet(&quot;Pramod&quot;)) # This prints Pramod <br><br># Function with default parameter<br>def power(base, exponent=2):<br>    return base ** exponent<br><br>print(power(3))    # 3^2 = 9<br>print(power(3, 3)) # 3^3 = 27<br><br># Function with multiple returns<br>def divide(a, b):<br>    if b == 0:<br>        return &quot;Error: Division by zero&quot;<br>    return a / b<br><br>print(divide(10, 2)) # 5<br>print(divide(5, 0)) # Take a guess! <br><br># Lambda function (anonymous function)<br>square = lambda x: x ** 2<br>print(square(4))  # 16</pre><p>In the case where we are returning “Error: Division by Zero” What if we try to divide two numbers which results in a huge number that cannot be fit in memory? or we run into some other issue that we cannot account for and we encounter during runtime. Such cases are dealt using <a href="https://docs.python.org/3/tutorial/errors.html">exception handling</a>.</p><p>The anonymous function I introduced is one thing Python is famous for, they are often used in the famous (sometimes infamous) Python one liners (here is a list of some of my <a href="https://wiki.python.org/moin/Powerful%20Python%20One-Liners">favourites</a>)</p><h3>Hold it all together (Lists, Tuples &amp; Dictionaries)</h3><pre># Lists<br>fruits = [&quot;apple&quot;, &quot;banana&quot;, &quot;mango&quot;]<br>print(fruits[0])  # apple, python is a zero indexed language <br>fruits.append(&quot;date&quot;) # append to the list <br>print(fruits)  # [&#39;apple&#39;, &#39;banana&#39;, &#39;mango&#39;, &#39;date&#39;]<br><br># Tuples<br>coordinates = (10, 20)<br>x, y = coordinates<br>print(f&quot;X: {x}, Y: {y}&quot;)<br><br># Dictionaries<br>person = {<br>    &quot;name&quot;: &quot;John&quot;,<br>    &quot;age&quot;: 30,<br>    &quot;city&quot;: &quot;New York&quot;<br>}<br>print(person[&quot;name&quot;])  # John<br>person[&quot;job&quot;] = &quot;Engineer&quot;<br>print(person)<br><br># List comprehension<br>squares = [x**2 for x in range(5)]<br>print(squares)  # [0, 1, 4, 9, 16]</pre><p>The fundatmental difference between lists and tuples are. The former is <a href="https://realpython.com/python-mutable-vs-immutable-types/">mutable </a>while the other is not. Essential, mutable objects can be edited, while immutable objects cannot.</p><p>Dictionaries are like <a href="https://www.freecodecamp.org/news/what-is-a-hash-map/">hash maps</a> of Python, they are your best friends.</p><p><a href="https://www.freecodecamp.org/news/list-comprehension-in-python-with-code-examples/">List comprehension</a> is something Pythonic that is a simple way of iterating over a list and running a function over it (think the things you can do with lambda over here).</p><h3>It is all data in, data out (File I/O)</h3><pre># Writing to a file<br>with open(&quot;example.txt&quot;, &quot;w&quot;) as file:<br>    file.write(&quot;Hello, World!\n&quot;)<br>    file.write(&quot;This is a test file.\n&quot;)<br><br># Reading from a file<br>with open(&quot;example.txt&quot;, &quot;r&quot;) as file:<br>    content = file.read()<br>    print(content)<br><br># Appending to a file<br>with open(&quot;example.txt&quot;, &quot;a&quot;) as file:<br>    file.write(&quot;This line is appended.\n&quot;)</pre><p>I see programming as Input -&gt; Magic -&gt; Output. As long as you have clearly defined inputs and what you want as the output. Writing the “Magic” behind it, isn’t that tough.</p><h3>Back to CLASS!! (classes and Object Oriented Programming)</h3><pre># Define a simple class<br>class Dog:<br>    # Class attribute<br>    species = &quot;Canis familiaris&quot;<br><br>    # Constructor method<br>    def __init__(self, name, age):<br>        self.name = name  # Instance attribute<br>        self.age = age    # Instance attribute<br><br>    # Instance method<br>    def description(self):<br>        return f&quot;{self.name} is {self.age} years old&quot;<br><br>    # Another instance method<br>    def speak(self, sound):<br>        return f&quot;{self.name} says {sound}&quot;<br><br># Create instances of the Dog class<br>buddy = Dog(&quot;Buddy&quot;, 9)<br>miles = Dog(&quot;Miles&quot;, 4)</pre><p><a href="https://realpython.com/python3-object-oriented-programming/">OOPs </a>is the essence behind Python, different languages have difference essences and therefore different languages are used for different purposes (besides from a plethora of reasons).</p><p>There are multiple concepts in classes itself which is beside the purview of this article, like <a href="https://realpython.com/inheritance-composition-python/">inheritance</a>, <a href="https://realpython.com/instance-class-and-static-methods-demystified/">static methods</a>, <a href="https://www.datacamp.com/tutorial/decorators-python">decorators </a>etc</p><h3>The most important thing of it all</h3><blockquote>“One does not need to know everything. One only needs to know where to find it, when one needs it.”</blockquote><p>This is the dogma of most programmers (or atleast the one’s I have met). This article wasn’t meant to turn you into a Python wiz in 5 minutes. But rather show you how simple things are, and where to fidn the right resources to learn and use these. With the information I have given you. You can make most simple applications.</p><p>Now as promised, these are the few places I will recommend people to look to upskill themselves in Python.</p><ul><li><a href="https://automatetheboringstuff.com/">Automate the boring stuff</a> (A beautiful book that captures everything in detail about Python, and its FREE!!!)</li><li><a href="https://www.youtube.com/watch?v=kqtD5dpn9C8&amp;t=793s">Programming with Mosh</a> (I love his style of pedagogy and I personally started from his tutorials)</li><li><a href="https://www.youtube.com/watch?v=VchuKL44s6E">Tech with Tim</a> (One of the few youtubers who talks about complex and advanced Python topics)</li></ul><h3>End Note</h3><p>I hope I was able to teach you something, if you liked the article consider following me and sharing it with anyone who may find it useful.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fe2af04c2fa6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I Wasted 2 Years Learning AI/ML and Then Finally Landed My Dream Job]]></title>
            <link>https://goyalpramod.medium.com/how-i-wasted-2-years-learning-ai-ml-and-then-finally-landed-my-dream-job-f8a143047ec3?source=rss-763a19c7d326------2</link>
            <guid isPermaLink="false">https://medium.com/p/f8a143047ec3</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[learning]]></category>
            <category><![CDATA[beginner]]></category>
            <dc:creator><![CDATA[Goyalpramod]]></dc:creator>
            <pubDate>Sun, 23 Jun 2024 14:03:11 GMT</pubDate>
            <atom:updated>2024-06-23T14:03:11.019Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YFLljhzmeH707udG" /><figcaption>Photo by <a href="https://unsplash.com/@altumcode?utm_source=medium&amp;utm_medium=referral">AltumCode</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>I started learning ML in my second year of college. The first thing I did was, keep watching Andrew Ng’s videos for days. 3 months to be exact. After that, I stopped watching his videos and switched to another tutorial series. I was stuck in this loop for 2 years before I finally realized my mistake and fixed it.</p><p>In this article I will be sharing the things that I got right and the things I did not. So you do not make the same mistakes I made and land your dream job (hopefully sooner than me).</p><h3>Tutorial hell</h3><p>“What if I miss out on some information if I do not watch this tutorial”, All of us have that fear of missing out. We are afraid that we will fail if we do not have 100% of the information.</p><p>The first thing to realize is, that one can never have all the information. And even if one does have all the information, one cannot guarantee success. As soon as we can accept and let go of this fear of information scarcity, We take our first steps to success.</p><p>If you keep watching tutorials, it puts your mind on autopilot. We feed ourselves copium that we are learning something. Meanwhile, effective learning is about 30%.</p><p>When you stop watching videos, and start applying them. You learn about the gaps in your knowledge. Which forces you to read up on them again. Hence making your learning more effective.</p><h4>The turning point</h4><p>After 2 years of continuously watching videos of different tutorials, I was still not confident. There were 2 things that I was consistent with, watching videos and talking to people about ML. So I had gotten my name out as a person who was interested in ml.</p><p>My friends heard the news of a big national ML hackathon being conducted and asked me to join their team. As they knew how much I loved ML. I said yes on a whim. I spent a few hours building a simple LSTM model for the prediction of words (something very similar to ChatGPT but on a very small scale). Writing everything from scratch, and surprisingly, WE WON!!! I did not even apply 50 percent of everything I had learned nor did I remember half of it.</p><p>As soon as you take the leap of faith from fear of missing out, to the thrill of application that is when your hard work will start bearing fruits.</p><p>After winning the hackathon, I now had confidence. I started taking part in more competitions, we won a few, lost a lot. But this was crucial for teaching me the real-world applications of ML and what are its limitations as well. As I got good at it, I became more passionate about it. And explored more areas in ML like research and teaching ML to others.</p><p>By teaching others, I was forcing myself to get a very good grasp of the concepts behind how everything is built and made. This made my foundation strong.</p><h3>Things to Do and Not to Do</h3><ul><li>Do NOT just learn ML, <strong>apply it</strong>. Think of something real-world you face every day. Make it fun, We used to have a game where we would try to predict the temperature of the next day and whoever was the closest got a “samosa” from the one who was the furthest from the ground truth.</li><li><strong>Share </strong>what you learn. This is the quickest way to have the information imprinted in your own head for a long time.</li><li>You cannot love something you hate, if you have put genuine effort into this. And do not like it. Stop it, I wasted 2 years and I have no remorse. Because I still liked what I did.</li><li><strong>Network, </strong>build in public. Tell people what you are doing. What you want to do; This will connect you to the right people. As well as teach you things you could not have learned alone.</li></ul><h3>How did I get my job?</h3><p>I had been dabbling in open source for a while, talking to a lot of people. Meeting strangers, asking friends if they knew anyone who worked in open source. That is when a friend introduced me to Ronit. Ronit had started 2 companies and now was working in a company making more in a month than most people make in a whole year. And no one knew about him!!</p><p>He was humble, and down to earth and told me about what he does and how he does it. Taking inspiration from him, I started applying to different companies, working in open source. Solving issues.</p><p>After doing this for two months, I told Ronit about everything I had accomplished. He was astounded, and told me… “If you are so good, why not apply to the company I work at.” I took it up as a challenge, I drafted a mail that instantly captured the CEO’s attention. We had an introduction call in the evening.</p><p>Surprise Surprise, it was not an intro call but rather a technical test. This is when my countless hours of learning and applying ML helped me. I was able to ace it. And 1 week later I got the job.</p><h3>Key takeaways</h3><p>What I want you to learn from this article in a crux is, to keep trying different things till you find something that you genuinely find interesting. If you are in the top 1% in anything, you will be well off. And if you find something interesting you will do it while someone half-baked will drop it off. I urge you, to go out there in the world. And explore whatever you want to be doing with this life of yours.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f8a143047ec3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What you NEED to know as a beginner developer!!]]></title>
            <link>https://goyalpramod.medium.com/what-you-need-to-know-as-a-beginner-developer-d204b72d9120?source=rss-763a19c7d326------2</link>
            <guid isPermaLink="false">https://medium.com/p/d204b72d9120</guid>
            <category><![CDATA[beginner]]></category>
            <category><![CDATA[software]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Goyalpramod]]></dc:creator>
            <pubDate>Sat, 22 Jun 2024 12:49:12 GMT</pubDate>
            <atom:updated>2024-06-22T12:49:12.534Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*eQqx0WaMbhbf_c7Q" /><figcaption>Photo by <a href="https://unsplash.com/@ricaros?utm_source=medium&amp;utm_medium=referral">Danial Igdery</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>I recently got a new PC and had to set up my programming environment from scratch, A task that seemed daunting when I first started programming 4 years ago back in college. This seemingly hard task was over in a mere 30 minutes.</p><p>This experience made me realize that there are fundamental skills and dev-tools every aspiring developer should master to accelerate their growth.</p><h4>Git Gud</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*H3OzsqUOl8M1h_aF" /><figcaption>Photo by <a href="https://unsplash.com/@synkevych?utm_source=medium&amp;utm_medium=referral">Roman Synkevych</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>The first and foremost thing a good developer needs to know how to use is Git and GitHub (yes, both of them are different).</p><p>Git is the software you use to “version control” (a fancy word, that means. Take a snapshot of the history of everything you commit) your code. And GitHub is the place where it is all stored.</p><p>Plenty of videos and articles explain the difference in detail and how to set it up. Here is one of my favorite <a href="https://www.youtube.com/watch?v=RGOj5yH7evk">Git and GitHub for beginners by FreeCodeCamp</a>. When in doubt, go with FreeCodeCamp.</p><p>If you went through the video, you must have seen some convoluted commands. Now no one remembers them (well at least I don&#39;t), here is a <a href="https://www.atlassian.com/git/tutorials/atlassian-git-cheatsheet">cheat sheet </a>I swear by.</p><h4>There is no place like your own IDE</h4><p>Integrated Development Environment (IDE) is the place where you will be doing most of your programming, so you must learn how to navigate through it well. Every IDE has its own pros and cons. For most beginners, the one that I suggest is <a href="https://code.visualstudio.com/">Visual Studio Code</a>*</p><p>The extensions in it are easy to set up and it has relatively easy shortcuts to remember.</p><p>A few of my favorite extensions:</p><ul><li><a href="https://marketplace.visualstudio.com/items?itemName=johnpapa.vscode-peacock">Peacock</a></li><li><a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode">Prettier</a></li><li><a href="https://marketplace.visualstudio.com/items?itemName=PKief.material-icon-theme">Material theme icons</a></li><li><a href="https://marketplace.visualstudio.com/items?itemName=GitHub.copilot">Github Copilot</a></li><li><a href="https://marketplace.visualstudio.com/items?itemName=formulahendry.code-runner">code runner</a></li></ul><p>And a few of my favorite shortcuts that I cannot live without:</p><ul><li>Ctrl + left mouse click (Do this over any function and it will take you to the function definition)</li><li>Ctrl + Shift + F (find anything present inside the directory)</li><li>Middle mouse click (MULTIPLE CURSOURS!!!!!)</li><li>Alt + Up/Down arrow key (move the line of code)</li><li>Alt + Shift + Up/Down key (Copy the line of code up or down)</li><li>Del key (start deleting from the right, opposite of backspace)</li><li>Ctrl + Backspace (Delete whole words)</li><li>Ctrl + X (Cut a whole line)</li><li>Home key (start of a line)</li><li>End key (end of a line)</li></ul><p>This can become an endless list really, I urge you to go and explore it yourself.</p><p>*VS Code is a text editor and not an IDE, <a href="https://www.freecodecamp.org/news/visual-studio-vs-visual-studio-code/#:~:text=Visual%20Studio%20is%20an%20integrated,debugging%2C%20and%20running%20your%20code.">find more details about the difference here</a></p><h4>Do you even “language” bro</h4><blockquote>“Which programming language should I learn first?”</blockquote><p>If I had a penny for every time someone asked me this question, I probably wouldn&#39;t need to work anymore. The answer is….. it depends. On a lot of factors like, “What are you hoping to develop?”, “How experienced you are with programming”, “Who is going to be your end user” yada yada ya, and so on.</p><p>But if you are a complete beginner, I will recommend starting with <a href="https://www.youtube.com/watch?v=rfscVS0vtbw"><em>Python</em></a><em> </em>if you would like to start with machine learning, or some simple GUI &amp; games.</p><p>But if you are interested in making websites and apps, you should start with <a href="https://www.youtube.com/watch?v=PkZNo7MFNFg"><em>JavaScript</em></a>, JS, and Python are very versatile and they can help you make 90% of the things you can think of.</p><p>As you grow and become better, you should also learn a low-level language like <a href="https://www.youtube.com/watch?v=8jLOx1hD3_o">c/c++</a>, <a href="https://www.youtube.com/watch?v=A74TOX803D0">Java</a>, <a href="https://www.youtube.com/watch?v=MsocPEZBd-M&amp;t=76s">Rust</a>, <a href="https://www.youtube.com/watch?v=un6ZyFkqFKo">GO</a>, etc. These will help you understand the very basics of how memory stack, control flow, pointers, etc work. These are essential if you plan to stay in the developer world for long.</p><p>If you are starting out with Python, I will urge you to go through this <a href="https://github.com/goyalpramod/cars-api">repo </a>created by me. Which shows the best practices around the language.</p><h4>Know your whale friend (Docker)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gdwKFEqZ06KeSWrf" /><figcaption>Photo by <a href="https://unsplash.com/@carrier_lost?utm_source=medium&amp;utm_medium=referral">Ian Taylor</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Don’t let Docker intimidate you — it’s simpler than it looks and designed to make your development life easier. As you grow and keep shipping products, you will stumble into the age-old saying “Works on my machine bro” Docker is built to take care of this.</p><p><a href="https://www.docker.com/">Docker</a> creates an image of your program, which you can upload to <a href="https://hub.docker.com/">DockerHub</a> from which people can get it. And start running it on their system. It is also important that you know Docker is different from virtual environments. You can find the difference <a href="https://www.freecodecamp.org/news/docker-vs-vm-key-differences-you-should-know/">here</a>.</p><p>This <a href="https://www.youtube.com/watch?v=gAkwW2tuIqE">video</a> by fireship.io (one of the best developer channels out there, consider subscribing to him). Does an amazing job of teaching it.</p><h4>Linux is life (at least to a developer)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7sBGIxy8smmN_86Q" /><figcaption>Photo by <a href="https://unsplash.com/@6heinz3r?utm_source=medium&amp;utm_medium=referral">Gabriel Heinzer</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Linux is a necessary evil that every developer has to learn eventually. But why? Well because most of everything that runs on the cloud runs on Linux. So if you ever plan to have something that can be served to other people. It is easier done if you understand how and where it runs.</p><p>Now here are some good and easy resources to start with Linux and learn it. (start with an easy distro like Ubuntu)</p><ul><li><a href="https://linuxjourney.com/">https://linuxjourney.com/</a> A nice interactive website</li><li><a href="https://training.linuxfoundation.org/training/introduction-to-linux/">https://training.linuxfoundation.org/training/introduction-to-linux/</a> Linux by the people who built it</li><li><a href="https://www.youtube.com/watch?v=sWbUDq4S6Y8">Intro to Linux</a> by FCC</li></ul><h4>AI is the new black</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*cJcpyRPm_KaHMrCB" /><figcaption>Photo by <a href="https://unsplash.com/@steve_j?utm_source=medium&amp;utm_medium=referral">Steve Johnson</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>If you do not use AI in your workflow in this day and age, you will be left behind. You NEED to start using a general-purpose AI like ChatGPT or Claude to help you learn, understand, and write code. And to do this well, you have to learn something called prompt engineering.</p><p>Now there is too much “buzz” around prompt engineering, but it is just. Expressing yourself so the model can perform well.</p><p>Here are a few tricks and prompts that I use myself:</p><ul><li>Act as X (tell the model what you want it to act as, a python developer, a CTO, an IT expert, etc)</li><li>Think step by step (giving it time to think makes it better)</li><li>The audience is X (tell it who is going to read it)</li><li>Be clear and explain your task very well.</li></ul><p>Here is a prompt that I often use</p><p>“Act as Python developer and help me debug this code. Think step by step and ask me any doubts you may have. Do not make any assumptions. The audience is an experienced developer himself. I will provide you some documentation in triple backticks and the code in triple double quotes”</p><h4>The end of the beginning (and a bit about myself)</h4><p>And with this, you have come to the end of this little intro to being a good developer. I plan to keep releasing content about how to become an AI developer and be a good one while at it.</p><p>I am Pramod, a founding AI developer in <a href="https://www.dimension.dev/">Dimension</a>. I am from India and I love to build…. anything really, as long as it is creative and can keep me up at night.</p><p>Consider following me on my socials to stay up to date with my articles.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d204b72d9120" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I made my first contribution to C4GT as someone who has never worked in open-source]]></title>
            <link>https://goyalpramod.medium.com/how-i-made-my-first-contribution-to-c4gt-as-someone-who-has-never-worked-in-open-source-6b6c7b71da3b?source=rss-763a19c7d326------2</link>
            <guid isPermaLink="false">https://medium.com/p/6b6c7b71da3b</guid>
            <category><![CDATA[c4gt]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[open-source]]></category>
            <dc:creator><![CDATA[Goyalpramod]]></dc:creator>
            <pubDate>Wed, 31 May 2023 09:20:13 GMT</pubDate>
            <atom:updated>2023-06-20T05:52:07.079Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IG9oPuoDSEtCdut7" /><figcaption>Photo by <a href="https://unsplash.com/@markusspiske?utm_source=medium&amp;utm_medium=referral">Markus Spiske</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h4>Introduction</h4><p>Hi there!!! I am your average college student who was intimidated by open source. I always heard the stories of people who got into programs like GSoC, MLH fellowship, and GitHub Winter of Code and wondered if I could ever contribute to any of the projects and get accepted into such prestigious programs.</p><p>I had worked on multiple projects of my own but working with someone else’s code was just too frightening, “What If I mess something up”, “What if I am not capable enough”, “I do not even know where to start, this is just a waste of time”.</p><p>That is when I heard about C4GT</p><h4>C4GT</h4><p>If you haven’t heard about C4GT, it is an initiative by Samagra, a governance consulting firm, to upskill the next generation of programmers. It has many perks like working with professional programmers in a one-to-one mentorship, Working on a complete government project, and to top it all off, a stipend of 1 lakh for the 2 months of the program</p><p><a href="https://www.codeforgovtech.in/">Code for GovTech - Digital Public Goods</a></p><p>The heart and idea behind open source are, working together, to make small small contributions and develop an idea with like-minded people. And the mentors of C4GT stay true to this. All of them are easy to approach and guide you from the very beginning on how you can go about making your first contribution, even if you have no idea what you are doing (like me)</p><h4>Making my first contribution</h4><ol><li>Joining the<a href="https://discord.com/invite/VPrXf7Jxpr"> Discord</a> server was my first step. This introduced me to the community and showed all the other enthusiastic people working and helping each other to make contributions. It was amazing to see so many people from so many different backgrounds. I remember even seeing a kid in 10th grade enquiring about how to make a contribution. This lit a fire inside of me, and I decided. Doesn’t matter if I get into the program or not. I will make a contribution.</li><li>I was fortunate enough to be clear about my own strengths. I knew I had worked in Python and had skills in the ML, DS, and AI domains. So I went to the whole <a href="https://github.com/Code4GovTech/C4GT/wiki">list of projects</a> provided by C4GT. And opened each project one by one.</li><li>Each project had a tech stack written with it, this helped me pick point the exact projects with which I could help. (any project that mentioned js, react, angular, etc was of no use to me as I had little to no idea about them. So I specifically targetted the projects which had Python mentioned in their stack)</li><li>So I chose to contribute to the <a href="https://github.com/Code4GovTech/C4GT/wiki/Text2SQL">Text2SQL</a> project. But if that is not the case for you, You can always contact the mentors tell them about what you are good at, and ask about which project will be good for you.</li><li>I went to the project and went through the <a href="https://github.com/Samagra-Development/Text2SQL/issues">issues</a> present in it. I was looking for the tag “good first issue” or “beginner” which I knew were easy issues that anyone can fix. Luckily I found an issue that was dated February 22. I believed I could contribute to this issue</li><li>But it was so old, Hence I was uncertain whether they were still looking for contributions to that issue. I contacted the mentor through Discord about it. And he was very helpful and happy to guide me through it</li><li>He made me realize every little contribution counts. So I decided to work on it.</li><li>I made a few directory changes and added a list for the literature review. As small as this seems, it helped create a place where everyone wishing to contribute to the project in the future could write down their findings.</li><li>And finally, I got my first <a href="https://github.com/Samagra-Development/Text2SQL/pull/34">PR </a>merged</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1yUbFRqZDdxxNIDToEoN5w.png" /><figcaption>Getting my PR merged yehhh!!!!</figcaption></figure><h4>Why you should contribute</h4><p>There are multiple reasons one should contribute to open source even if one may or may not get selected into the program, a few of them being:</p><ol><li>Learning and Knowledge Sharing: Open source projects promote a culture of learning and knowledge sharing. By contributing, you have the opportunity to explore new technologies, programming languages, and frameworks. You can also learn from the code reviews and feedback provided by other contributors, improving your understanding of best practices and coding standards.</li><li>Building a Portfolio: Open source contributions serve as evidence of your skills and commitment to the software development community. They can be valuable additions to your professional portfolio, demonstrating your ability to work on collaborative projects and showcasing your code quality to potential employers.</li><li>Networking and Collaboration: Engaging in open-source projects enables you to connect and collaborate with developers from around the nation. This network can lead to valuable professional connections, mentorship opportunities, and exposure to diverse perspectives and approaches to problem-solving.</li></ol><h4>Conclusion</h4><p>If you are a programmer or someone who is just getting into programming. This is the perfect opportunity for you to learn and grow, The community is extremely helpful and the learning opportunity is tremendous. So I urge you if you have the slightest bit of interest. Come join us.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6b6c7b71da3b" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>