<?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"><channel><title><![CDATA[Statistically Speaking]]></title><description><![CDATA[Exploring data science, AI, and real-world analytics through clear, insightful stories and hands-on guides. Welcome to Statistically Speaking.]]></description><link>https://statisticallyspeaking.tuhindutta.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1746943159393/a43556da-e26d-48e3-bf35-d91cc74b0a1e.png</url><title>Statistically Speaking</title><link>https://statisticallyspeaking.tuhindutta.com</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 10:45:30 GMT</lastBuildDate><atom:link href="https://statisticallyspeaking.tuhindutta.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Unveiling the Power of Support Vector Machines in Classification Tasks]]></title><description><![CDATA[Support Vector Machines (SVM) are among the most effective and widely used classification algorithms in machine learning. At its core, the SVM algorithm works by finding the optimal boundary—called a hyperplane—that best separates different classes i...]]></description><link>https://statisticallyspeaking.tuhindutta.com/unveiling-the-power-of-support-vector-machines-in-classification-tasks</link><guid isPermaLink="true">https://statisticallyspeaking.tuhindutta.com/unveiling-the-power-of-support-vector-machines-in-classification-tasks</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[support vector machines]]></category><category><![CDATA[SVM]]></category><category><![CDATA[classification]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[kernel trick]]></category><category><![CDATA[#MLAlgorithms]]></category><category><![CDATA[Python]]></category><category><![CDATA[sklearn]]></category><dc:creator><![CDATA[Tuhin Kumar Dutta]]></dc:creator><pubDate>Sat, 22 Jan 2022 18:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747245209095/b3111a92-cb2e-4136-a549-8c47d7a05272.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Support Vector Machines (SVM) are among the most effective and widely used classification algorithms in machine learning. At its core, the SVM algorithm works by finding the optimal boundary—called a <em>hyperplane</em>—that best separates different classes in a dataset.</p>
<ul>
<li><p>In the case of <strong>2-dimensional data</strong>, this boundary is a <strong>line</strong>.</p>
</li>
<li><p>For <strong>3-dimensional data</strong>, it becomes a <strong>plane</strong>.</p>
</li>
<li><p>And for <strong>higher dimensions</strong>, it is referred to as a <strong>hyperplane</strong>.</p>
</li>
</ul>
<p>To understand this concept intuitively, let's begin by exploring how SVM behaves with a simple 2D dataset. We'll start by generating an arbitrary dataset for visualization and demonstration purposes.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd, numpy <span class="hljs-keyword">as</span> np, matplotlib.pyplot <span class="hljs-keyword">as</span> plt, seaborn <span class="hljs-keyword">as</span> sns
<span class="hljs-keyword">from</span> sklearn.svm <span class="hljs-keyword">import</span> SVC
<span class="hljs-keyword">import</span> plotly.express <span class="hljs-keyword">as</span> px

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">making_df</span>(<span class="hljs-params">slope_type=<span class="hljs-number">1</span>,n=<span class="hljs-number">100</span>, p1=<span class="hljs-number">100</span>, p2=<span class="hljs-number">30</span>, p3=<span class="hljs-number">2</span></span>):</span>
    np.random.seed(n)

    p=np.random.randn(p1)*p2
    q=np.random.randn(p1)*p2

    <span class="hljs-keyword">if</span> slope_type == <span class="hljs-number">1</span>:        
        r=p+p3*p.max()
        s=q-p3*q.max()
    <span class="hljs-keyword">else</span>:
        r=p+p3*p.max()
        s=q+p3*q.max()        


    df1 = pd.DataFrame({<span class="hljs-string">'ones'</span>:np.ones(len(p)),<span class="hljs-string">'feature1'</span>:p,
                       <span class="hljs-string">'feature2'</span>:q, <span class="hljs-string">'label'</span>:np.ones(len(p))})

    df2 = pd.DataFrame({<span class="hljs-string">'ones'</span>:np.ones(len(r)),<span class="hljs-string">'feature1'</span>:r,
                       <span class="hljs-string">'feature2'</span>:s, <span class="hljs-string">'label'</span>:[<span class="hljs-number">-1</span>]*len(r)})

    df = pd.concat([df1,df2], axis=<span class="hljs-number">0</span>)
    df = df.sample(n=len(df))
    <span class="hljs-keyword">return</span> [df,p,q,r,s]
</code></pre>
<p>To avoid diving too deeply into the implementation details, let’s assume that the above-defined function returns a dataframe consisting of two visually separable clusters of data points. These clusters represent two distinct classes:</p>
<ul>
<li><p><strong>(p, q)</strong> for <em>negative class</em> data points</p>
</li>
<li><p><strong>(r, s)</strong> for <em>positive class</em> data points</p>
</li>
</ul>
<p>The function also accepts a few input parameters, such as the desired <strong>slope direction</strong> (positive or negative) and other customization options for generating the dataset. Since we’ll be generating dataframes multiple times throughout our practical exploration, having a reusable function makes the process more efficient.</p>
<p>Now, to understand how SVM finds a boundary between these classes, we refer to the general equation of a straight line:</p>
<h3 id="heading-axbyc0"><strong><em>ax+by+c=0</em></strong></h3>
<p>Where:</p>
<ul>
<li><p><strong><em>x</em></strong> and <strong><em>y</em></strong> are the coordinate variables</p>
</li>
<li><p><strong><em>a</em></strong>, <strong><em>b</em></strong>, and <strong><em>c</em></strong> are constants</p>
</li>
</ul>
<p>From this, we can derive:</p>
<ul>
<li><p><strong>Slope</strong> of the line: <strong><em>−a/b</em></strong></p>
</li>
<li><p><strong>Y-intercept</strong>: <strong><em>−c/b</em></strong></p>
</li>
</ul>
<p>Without delving further into theory just yet, let’s generate an arbitrary dataframe and plot it. This visual representation will help us clearly see the separation between classes and better understand what we are working with.</p>
<pre><code class="lang-python">obt = making_df(slope_type=<span class="hljs-number">1</span>,n=<span class="hljs-number">40</span>)

df = obt[<span class="hljs-number">0</span>]
p = obt[<span class="hljs-number">1</span>]
q = obt[<span class="hljs-number">2</span>]
r = obt[<span class="hljs-number">3</span>]
s = obt[<span class="hljs-number">4</span>]

plt.figure(figsize=(<span class="hljs-number">20</span>,<span class="hljs-number">8</span>))
plt.scatter(p, q, facecolors=<span class="hljs-string">'purple'</span>, marker=<span class="hljs-string">'_'</span>, s=<span class="hljs-number">100</span>)
plt.scatter(r,s, facecolors=<span class="hljs-string">'red'</span>,marker=<span class="hljs-string">'+'</span>, s=<span class="hljs-number">100</span>)
plt.xlabel(<span class="hljs-string">'Features'</span>, fontsize=<span class="hljs-number">16</span>)
plt.ylabel(<span class="hljs-string">'Label'</span>, fontsize=<span class="hljs-number">16</span>)

plt.show()
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/data2.png?w=1024" alt /></p>
<p>Now we have a clear dataset with distinct data points, conveniently labeled as ‘+1’ and ‘-1’.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">primary_fit</span>(<span class="hljs-params">weight</span>):</span> 
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">nor</span>(<span class="hljs-params">lst</span>):</span>
        factor = (<span class="hljs-number">1</span>/sum([i**<span class="hljs-number">2</span> <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> lst[:<span class="hljs-number">2</span>]]))**<span class="hljs-number">0.5</span>
        <span class="hljs-keyword">return</span> [i*factor <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> lst[:<span class="hljs-number">2</span>]]
    a = nor(weight)[<span class="hljs-number">0</span>]
    b = nor(weight)[<span class="hljs-number">1</span>]
    c = weight[<span class="hljs-number">2</span>]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">st</span>(<span class="hljs-params">a,b,c</span>):</span>
        slope = -(a/b)
        intercept = -(c/b)
        x = np.concatenate([p,r])
        y = slope*x + intercept
        plt.plot(x,y) 
        <span class="hljs-keyword">return</span> [slope,intercept]

    plt.figure(figsize=(<span class="hljs-number">20</span>,<span class="hljs-number">8</span>))
    plt.scatter(p, q, facecolors=<span class="hljs-string">'purple'</span>, marker=<span class="hljs-string">'_'</span>, s=<span class="hljs-number">100</span>)
    plt.scatter(r,s, facecolors=<span class="hljs-string">'red'</span>,marker=<span class="hljs-string">'+'</span>, s=<span class="hljs-number">100</span>)
    plt.xlabel(<span class="hljs-string">'Features'</span>, fontsize=<span class="hljs-number">16</span>)
    plt.ylabel(<span class="hljs-string">'Label'</span>, fontsize=<span class="hljs-number">16</span>)
    st(a,b,c)
    plt.show()


primary_fit([<span class="hljs-number">-1</span>,<span class="hljs-number">1</span>,<span class="hljs-number">110</span>])
</code></pre>
<p>We can try to fit a line using a = -1, b = 1, and c = 110.</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/data-1.png?w=1024" alt /></p>
<p>With the selected values of <strong><em>a</em></strong>, <strong><em>b</em></strong>, and <strong><em>c</em></strong>, we were able to fit a line in the form of <strong><em>ax+by+c=0</em></strong></p>
<p>This line successfully separates the data points into two distinct clusters or classes. In the context of Support Vector Machines (SVM), this separating boundary is referred to as a <strong>hyperplane</strong>.</p>
<ul>
<li><p>In <strong>2D</strong>, the hyperplane is simply a line.</p>
</li>
<li><p>In <strong>3D</strong>, it becomes a 2D <strong>plane</strong>.</p>
</li>
<li><p>In higher dimensions (4D, 5D, etc.), the hyperplane could theoretically take forms like a <strong>cube</strong> or even a <strong>tesseract</strong>, although we can't visualize them directly.</p>
</li>
</ul>
<p>From the plotted graph, we can logically infer:</p>
<ul>
<li><p>If <strong><em>ax+by+c &gt;</em> 0</strong>, the point can be classified as belonging to one class (say, <strong>–1</strong>)</p>
</li>
<li><p>If <strong><em>ax+by+c</em> &lt; 0</strong>, the point belongs to the other class (say, <strong>+1</strong>)</p>
</li>
</ul>
<p>This behavior forms the basis for classification using SVM. However, one interesting observation is that different values of aaa, bbb, and ccc can generate <strong>different hyperplanes</strong>—each potentially separating the points in a slightly different manner.</p>
<p>Let’s explore this further by fitting another line using a new set of parameters:</p>
<p><strong>a=−1, b=3, c=80</strong></p>
<p>We’ll visualize how this new line affects the classification boundary and compare it with the previous one.</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/data3.png?w=1024" alt /></p>
<p>We observe that with every new combination of values for <strong><em>a</em></strong>, <strong><em>b</em></strong>, and <strong><em>c</em></strong>, an entirely different line is produced. This illustrates a key challenge in Support Vector Machines:</p>
<ol>
<li><p><strong>The first issue</strong> is that the parameters aaa, bbb, and ccc need to be carefully optimized in order to find the <em>best possible separating line</em>—one that generalizes well to unseen data.</p>
</li>
<li><p><strong>The second issue</strong> arises when, during real-world application, new data points appear that are either very close to the decision boundary or even on the opposite side of it. These edge cases can lead to misclassifications and reduce the model’s reliability.</p>
</li>
</ol>
<p>To address both of these concerns, <strong>SVM introduces the concept of margins</strong>.</p>
<h3 id="heading-what-are-margins">What are Margins?</h3>
<p>Simply put, margins are the regions on either side of the classifier (or hyperplane) that define the boundaries of each class. More formally:</p>
<blockquote>
<p>A <strong>margin</strong> is the sum of the perpendicular distances from the classifier to the closest data point in each class.</p>
</blockquote>
<p>The goal of an SVM is not just to find <em>any</em> separating line, but to find the one that maximizes this margin. This optimal boundary is known as the <strong>maximum margin classifier</strong> and is considered to be the most robust in terms of generalization.</p>
<p>In the next section, we’ll visualize these margins and understand how they make SVM one of the most powerful classification algorithms available.</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/dis.png?w=656" alt class="image--center mx-auto" /></p>
<h2 id="heading-svm-formulation">🧠 SVM Formulation</h2>
<p>The fundamental goal of a Support Vector Machine (SVM) is to find a <strong>hyperplane</strong> that not only separates the data but does so with the <strong>maximum possible margin</strong>. This margin is the buffer zone that helps ensure better generalization when the model is applied to unseen data.</p>
<p>Mathematically, this objective can be expressed as:</p>
<p>$$l_i\cdot (W \cdot y_i) \geq M$$</p><p>Where:</p>
<ul>
<li><p>\(l_i\) is the label of the ithi^{th}ith training data point (either +1 or -1),</p>
</li>
<li><p>\(y_i\)​ is the feature vector of the ithi^{th}ith point,</p>
</li>
<li><p>\(W\) is the weight vector perpendicular to the hyperplane,</p>
</li>
<li><p>\(M\) is the <strong>margin</strong> we want to maximize.</p>
</li>
</ul>
<h3 id="heading-understanding-the-expression">🧾 Understanding the Expression</h3>
<p>The term \(W \cdot y_i\)​ represents the <strong>projected distance</strong> of the point from the hyperplane. Since both \(l_i\) (the class label) and the projection share the same sign, their product will always be <strong>positive</strong> when the point is on the correct side of the margin.</p>
<p>Thus, the constraint \( l_i\cdot (W \cdot y_i) \geq M\) ensures that all data points lie <strong>outside or on the margin</strong>, reinforcing the separation.</p>
<hr />
<h3 id="heading-distance-from-a-point-to-a-hyperplane">📐 Distance from a Point to a Hyperplane</h3>
<p>For any point PPP, the perpendicular distance ddd from a line (or hyperplane) defined by \(ax+by+c=0\) is given by:</p>
<p>$$d=\frac{|a x + b y + c|}{\sqrt{a^2 + b^2}}$$</p><p>This formula helps quantify how close each point is to the classifier. In the context of SVM, we are particularly interested in the <strong>closest points</strong> from both classes—these are the <strong>support vectors</strong>.</p>
<hr />
<h3 id="heading-goal-of-svm">🎯 Goal of SVM</h3>
<p>The classifier that we are searching for must be one that is <strong>farther from the closest points</strong> (support vectors) of both classes. That is, the SVM algorithm <strong>maximizes the minimum distance</strong> to any data point, ensuring the most robust separation.</p>
<p>This leads to the optimization problem at the heart of SVM:</p>
<p>\(min⁡\frac{1}{2}\|W\|^2\) subject to \(l_i(W \cdot y_i + b)≥1\)</p>
<p>In simple terms, this optimization tries to:</p>
<ul>
<li><p>Minimize the norm of the weight vector (which is equivalent to maximizing margin),</p>
</li>
<li><p>While making sure that all data points are correctly classified and lie outside the margin.</p>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sup</span>(<span class="hljs-params">weight, epsilon=<span class="hljs-number">0</span></span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">nor</span>(<span class="hljs-params">lst</span>):</span>
        factor = (<span class="hljs-number">1</span>/sum([i**<span class="hljs-number">2</span> <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> lst[:<span class="hljs-number">2</span>]]))**<span class="hljs-number">0.5</span>
        <span class="hljs-keyword">return</span> [i*factor <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> lst[:<span class="hljs-number">2</span>]]

    a = nor(weight)[<span class="hljs-number">0</span>]
    b = nor(weight)[<span class="hljs-number">1</span>]
    c = weight[<span class="hljs-number">2</span>]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">st</span>(<span class="hljs-params">a,b,c</span>):</span>
        slope = -(a/b)
        intercept = -(c/b)

        x = np.concatenate([p,r])
        y = slope*x + intercept
        <span class="hljs-keyword">return</span> [x,y]

    df[<span class="hljs-string">'distances'</span>] = np.matmul(np.array(df.iloc[:,:<span class="hljs-number">3</span>]),np.array([c,a,b]))

    df[<span class="hljs-string">'formulation'</span>] = df.label * df.distances

    <span class="hljs-keyword">if</span> -(a/b)&gt;<span class="hljs-number">0</span>:
        df_neg = df[df.label&lt;<span class="hljs-number">0</span>].sort_values(<span class="hljs-string">'formulation'</span>)
        df_pos = df[df.label&gt;<span class="hljs-number">0</span>].sort_values(<span class="hljs-string">'formulation'</span>)
    <span class="hljs-keyword">elif</span> -(a/b)&lt;<span class="hljs-number">0</span>:
        df_neg = df[df.label&lt;<span class="hljs-number">0</span>].sort_values(<span class="hljs-string">'formulation'</span>, ascending=<span class="hljs-literal">False</span>)
        df_pos = df[df.label&gt;<span class="hljs-number">0</span>].sort_values(<span class="hljs-string">'formulation'</span>, ascending=<span class="hljs-literal">False</span>)



    neg_dist = df_neg.formulation.values[<span class="hljs-number">0</span>]
    pos_dist = df_pos.formulation.values[<span class="hljs-number">0</span>]


    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">vert_dist</span>(<span class="hljs-params">distance</span>):</span>
        <span class="hljs-keyword">import</span> math
        slope = -(a/b)
        angle = abs(math.degrees(math.atan(slope)))
        vertical = distance/math.cos(math.radians(angle))
        <span class="hljs-keyword">return</span> vertical

    neg_vert_dist = vert_dist(neg_dist)
    pos_vert_dist = vert_dist(pos_dist)

    intercept = -(c/b)
    pos_intercept = intercept + pos_vert_dist
    neg_intercept = intercept - neg_vert_dist
    c_intercept = min([neg_intercept, pos_intercept]) + abs((neg_intercept - pos_intercept)/<span class="hljs-number">2</span>)

    c_pos = -(pos_intercept*b)
    c_neg = -(neg_intercept*b)
    c_new = -(c_intercept*b)

    <span class="hljs-keyword">if</span> epsilon != <span class="hljs-number">0</span>:

        margin = (neg_dist+pos_dist)*(<span class="hljs-number">1</span>-epsilon)
        distance = margin/<span class="hljs-number">2</span>
        dist_vert_dist = vert_dist(distance)

        pos_intercept = c_intercept + dist_vert_dist
        neg_intercept = c_intercept - dist_vert_dist
        c_pos = -(pos_intercept*b)
        c_neg = -(neg_intercept*b) 


    plt.figure(figsize=(<span class="hljs-number">20</span>,<span class="hljs-number">8</span>))
    plt.scatter(p, q, facecolors=<span class="hljs-string">'purple'</span>, marker=<span class="hljs-string">'_'</span>, s=<span class="hljs-number">100</span>)
    plt.scatter(r,s, facecolors=<span class="hljs-string">'red'</span>, marker=<span class="hljs-string">'+'</span>, s=<span class="hljs-number">100</span>)
    plt.xlabel(<span class="hljs-string">'Features'</span>, fontsize=<span class="hljs-number">16</span>)
    plt.ylabel(<span class="hljs-string">'Label'</span>, fontsize=<span class="hljs-number">16</span>)

    plt.plot(st(a,b,c_new)[<span class="hljs-number">0</span>], st(a,b,c_new)[<span class="hljs-number">1</span>], linewidth=<span class="hljs-number">3.5</span>, label=<span class="hljs-string">'classifier'</span>)
    plt.plot(st(a,b,c_pos)[<span class="hljs-number">0</span>], st(a,b,c_pos)[<span class="hljs-number">1</span>], <span class="hljs-string">'--'</span>, linewidth=<span class="hljs-number">1</span>, alpha=<span class="hljs-number">0.8</span>, label=<span class="hljs-string">'negative margin'</span>)
    plt.plot(st(a,b,c_neg)[<span class="hljs-number">0</span>], st(a,b,c_neg)[<span class="hljs-number">1</span>], <span class="hljs-string">'--'</span>, linewidth=<span class="hljs-number">1</span>, alpha=<span class="hljs-number">0.8</span>, label=<span class="hljs-string">'positive margin'</span>)
    plt.legend()
    plt.show()


weights = [<span class="hljs-number">-1</span>,<span class="hljs-number">1</span>,<span class="hljs-number">110</span>]
sup(weights)
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Note:</strong> The above function is solely intended for visualization and demonstration purposes to aid conceptual understanding. It does not represent the actual model-building process in practice.</div>
</div>

<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/pro.png?w=1024" alt /></p>
<p>The negative and positive margin lines pass through the nearest negative and positive data points to the classifier, respectively. However, there can be instances where data points lie very close to the classifier. In such scenarios, the model may attempt to overfit the data by shrinking the margin, as it strives to achieve the ideal condition of zero classification errors. Let’s visualize how this behavior manifests.</p>
<pre><code class="lang-python">sup(weights, overfit=<span class="hljs-literal">True</span>)
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/overfit.png?w=1024" alt /></p>
<p>While this overfitted model may perform exceptionally well during training, it suffers a significant drawback. Due to the narrow margin—i.e., a highly constrained decision boundary—the model is more likely to misclassify data during evaluation or testing. Let’s understand this visually. Suppose we receive a new data point that truly belongs to the positive class but falls just above the negative margin. The model, relying on the tight boundary, would incorrectly classify it as ‘-1’. This misclassification leads to a drop in overall accuracy.</p>
<p>To address this issue, Support Vector Machines introduce a <strong>slack variable (ϵ)</strong> into the formulation. This allows the model to tolerate some degree of misclassification, thereby increasing its generalization ability.</p>
<p>The updated SVM constraint becomes:</p>
<p>$$l_i\cdot (W \cdot y_i) \geq M(1 - \epsilon_i)$$</p><p>From this formulation, we can clearly infer that the margin is inversely proportional to the slack variable. The slack variable <strong>ϵᵢ</strong> represents the degree of allowable error for each data point. In other words, it measures how much a data point is permitted to violate the margin.</p>
<p>A higher value of ϵ indicates more flexibility, meaning the model is allowed to misclassify certain points, thus preventing overfitting. Let’s visualize the impact of this by setting ϵ = 0.6 and examining how the decision boundary adapts.</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/sof.png?w=1024" alt /></p>
<p>Now, the model becomes <strong>generalized</strong> by allowing a few training data points to cross the margin boundaries. These relaxed thresholds are known as <strong>soft margins</strong>, which define the extent to which the model can tolerate errors in the training set without significantly compromising its generalization capabilities.</p>
<p>By incorporating this flexibility using the slack variable ε\varepsilonε, the model avoids overfitting and becomes more robust to unseen data. Let’s now revisit the same scenario illustrated previously, where the overfitted model misclassified a positive point due to overly narrow margins, and observe how the introduction of soft margins improves the outcome.</p>
<pre><code class="lang-python">sup(weights, epsilon=<span class="hljs-number">0.6</span>, test_pos_pos=<span class="hljs-number">48</span>)
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/conseq.png?w=1024" alt /></p>
<p>Unlike the earlier overfitted model, this generalized version will no longer misclassify the data point (represented by the red dot), as it now falls within the permissible margin. This demonstrates how the introduction of soft margins helps the model tolerate minor violations and improves overall robustness.</p>
<p>I hope this demonstration has provided a clear understanding of the foundational concepts behind Support Vector Machines. Now, let’s explore an additional insight: as the value of ε\varepsilonε increases, the soft margin becomes narrower. Conversely, when <em>ε=0</em>, the soft margins coincide exactly with the classifier, as the condition becomes \(l_i(W⋅yi​)≥0\). Let’s visualize how the fit changes with different values of <em>ε</em>.</p>
<pre><code class="lang-python"><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> np.arange(<span class="hljs-number">0.2</span>,<span class="hljs-number">1.1</span>,<span class="hljs-number">0.2</span>):
    sup(weights, epsilon=i, show=<span class="hljs-literal">True</span>)
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/1.png?w=1024" alt /></p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/1.png?w=1024" alt /></p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/3.png?w=1024" alt /></p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/3.png?w=1024" alt /></p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/5.png?w=1024" alt /></p>
<p>The greater the value of <em>ε</em>, the simpler the model becomes. A larger <em>ε</em> allows the model to be more flexible, reducing its tendency to fit the classifier too aggressively and thereby avoiding overfitting.</p>
<p>However, all these formulations assume that the classes are linearly separable—an ideal condition that rarely holds true in real-world scenarios. To better reflect practical situations, let’s now construct another dataset that introduces some overlap between classes, mimicking the complexity of real-world data.</p>
<pre><code class="lang-python">x = np.linspace(<span class="hljs-number">-5.0</span>, <span class="hljs-number">2.0</span>, <span class="hljs-number">100</span>)
y = np.sqrt(<span class="hljs-number">10</span>**<span class="hljs-number">2</span> + x**<span class="hljs-number">2</span>)
y=np.hstack([y,-y])
x=np.hstack([x,-x])

x1 = np.linspace(<span class="hljs-number">-5.0</span>, <span class="hljs-number">2.0</span>, <span class="hljs-number">100</span>)
y1 = np.sqrt(<span class="hljs-number">5</span>**<span class="hljs-number">2</span> - x1**<span class="hljs-number">2</span>)
y1=np.hstack([y1,-y1])
x1=np.hstack([x1,-x1])

df1 =pd.DataFrame(np.vstack([y,x]).T,columns=[<span class="hljs-string">'X1'</span>,<span class="hljs-string">'X2'</span>])
df1[<span class="hljs-string">'Y'</span>]=<span class="hljs-number">0</span>
df2 =pd.DataFrame(np.vstack([y1,x1]).T,columns=[<span class="hljs-string">'X1'</span>,<span class="hljs-string">'X2'</span>])
df2[<span class="hljs-string">'Y'</span>]=<span class="hljs-number">1</span>
df = df1.append(df2)

df.head()
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/tab.png?w=181" alt /></p>
<p>Let's take a look at the graphical representation of the data.</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/gr.png?w=370" alt /></p>
<p>As we can see, these data points are not linearly separable. To handle such cases, the concept of <strong>Kernels</strong> was introduced. Kernels are mathematical functions that, when applied to the data points from different features, transform them into a higher-dimensional space, allowing us to better classify the data. Essentially, a kernel function allows us to find a decision boundary in a transformed feature space, even when the original space is not linearly separable.</p>
<p>There are several types of kernels, each of which performs a different transformation. While the mathematical expressions for these kernels vary, their main goal is the same: to map the data points into a higher-dimensional space where they become linearly separable.</p>
<p>For simplicity, let’s consider one of the most commonly used kernels: <strong>Polynomial Kernel</strong>.</p>
<p>$$K(x,y)=(x^Ty+c)^d$$</p><p>Above mentioned is the mathematical expression for the Polynomial kernel. Let’s now calculate the transformation and observe the new dimensions formulated by the kernel. In our dataset, \(x\) and \(y\) are represented by\(X_1\) and\(X_2\) respectively.</p>
<p>When applying the Polynomial kernel function, we are essentially mapping the input features (from a lower-dimensional space) into a higher-dimensional space. The transformation process allows us to achieve linear separability in situations where the original data is not linearly separable.</p>
<p>We can compute the transformed feature space for our dataset by applying the kernel to pairs of data points, where each pair is processed through the following:</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/mt.png?w=1024" alt /></p>
<p>From the above derivation, we obtained three new dimensions, namely:</p>
<ol>
<li><p>\(X_1^2\)</p>
</li>
<li><p>\(X_2^2\)</p>
</li>
<li><p>\(X_1 \cdot X_2\)</p>
</li>
</ol>
<p>Let's calculate these new dimensions and integrate them into the original dataframe.</p>
<pre><code class="lang-python">df[<span class="hljs-string">'X1_Square'</span>]= df[<span class="hljs-string">'X1'</span>]**<span class="hljs-number">2</span>
df[<span class="hljs-string">'X2_Square'</span>]= df[<span class="hljs-string">'X2'</span>]**<span class="hljs-number">2</span>
df[<span class="hljs-string">'X1X2'</span>] = (df[<span class="hljs-string">'X1'</span>] *df[<span class="hljs-string">'X2'</span>])
df.head()
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/inc-1.png?w=401" alt /></p>
<p>Now, let’s plot these 3 newly formed dimensions in a 3D graph.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Creating dataset</span>
df1 = df[df.Y==<span class="hljs-number">0</span>]
a = np.array(df1.X1_Square)
b = np.array(df1.X2_Square)
c = np.array(df1.X1X2)

<span class="hljs-comment"># Creating figure</span>
plt.figure(figsize = (<span class="hljs-number">13</span>,<span class="hljs-number">13</span>))
ax = plt.axes(projection =<span class="hljs-string">"3d"</span>)

<span class="hljs-comment"># Creating plot</span>
ax.scatter3D(a, b, c, s=<span class="hljs-number">8</span>, label=<span class="hljs-string">'class 0'</span>)

<span class="hljs-comment"># Creating dataset</span>
df1 = df[df.Y==<span class="hljs-number">1</span>]
a = np.array(df1.X1_Square)
b = np.array(df1.X2_Square)
c = np.array(df1.X1X2)

<span class="hljs-comment"># Creating plot</span>
ax.scatter3D(a, b, c, s=<span class="hljs-number">8</span>, label=<span class="hljs-string">'class 1'</span>)

ax. set_xlabel(<span class="hljs-string">'X1_Square'</span>, fontsize=<span class="hljs-number">13</span>)
ax. set_ylabel(<span class="hljs-string">'X2_Square'</span>, fontsize=<span class="hljs-number">13</span>)
ax. set_zlabel(<span class="hljs-string">'X1X2'</span>, fontsize=<span class="hljs-number">13</span>)
plt.legend(fontsize=<span class="hljs-number">12</span>)

ax.view_init(<span class="hljs-number">30</span>,<span class="hljs-number">250</span>)

<span class="hljs-comment"># show plot</span>
plt.show()
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/08/plt-1.png?w=721" alt /></p>
<p>After applying the kernel, we can easily imagine fitting a 2D plane between the two classes, using all the theoretical concepts discussed earlier. Unlike before kernel execution, where the data was not linearly separable, now there exists a hyperplane that can successfully classify both the classes efficiently. This becomes possible only due to the introduction of additional dimensions over the initial 2D Cartesian plane, allowing the model to find a suitable separating boundary in the transformed feature space.</p>
<p>I hope this explanation provided a clear understanding of the Support Vector Machine (SVM) classifier algorithm and the internal process it follows. Although the practical implementation using automated processes with libraries (such as <code>scikit-learn</code>) has not been discussed here, I can assure you that once the working of the algorithm is clear, model building becomes pretty straightforward.</p>
<p>For further details and practical implementation, you can refer to the scikit-learn SVC documentation.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>For more insights, projects, and articles, visit my portfolio at </em><a target="_blank" class="autolinkedURL autolinkedURL-url" href="http://www.tuhindutta.com/"><em>tuhindutta.com</em></a><em>.</em></div>
</div>]]></content:encoded></item><item><title><![CDATA[Boosting Machine Learning Adaboost Guide]]></title><description><![CDATA[Boosting is one of the most widely used classes of algorithms in machine learning, applied globally to tackle a variety of complex problems. It involves combining multiple weak learners—typically simple models that perform just slightly better than r...]]></description><link>https://statisticallyspeaking.tuhindutta.com/boosting-machine-learning-adaboost-guide</link><guid isPermaLink="true">https://statisticallyspeaking.tuhindutta.com/boosting-machine-learning-adaboost-guide</guid><category><![CDATA[ML Algorithms]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[boosting]]></category><category><![CDATA[#MachineLearning #AdaBoost #EnsembleLearning #AI #Python #DataScience #Boosting #TechInnovation #MLAlgorithms #Hashnode #AIModels #TechBlog #DataAnalytics]]></category><category><![CDATA[#MLAlgorithms]]></category><category><![CDATA[Python]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[ensemblelearning]]></category><category><![CDATA[adaboost]]></category><dc:creator><![CDATA[Tuhin Kumar Dutta]]></dc:creator><pubDate>Wed, 17 Nov 2021 18:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1746973654279/d34434a2-f98d-4825-ab07-291cb37bf043.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Boosting</strong> is one of the most widely used classes of algorithms in machine learning, applied globally to tackle a variety of complex problems. It involves combining multiple <strong>weak learners</strong>—typically simple models that perform just slightly better than random guessing—to collectively form a strong predictive model. Each learner is trained sequentially, with a focus on correcting the mistakes made by its predecessors. Misclassified or hard-to-learn data points are given more importance in subsequent rounds. In this blog, we’ll explore one of the simplest and most well-known boosting algorithms, <strong>AdaBoost</strong> (Adaptive Boosting). We'll also implement it from scratch and demonstrate how it can significantly improve model accuracy.</p>
<p>Before diving into AdaBoost, let’s first understand the core principle of boosting through the following illustration.</p>
<p><a target="_blank" href="https://criticalmind.tech.blog/wp-content/uploads/2021/09/9ca3e-1m2uhkzwwj0kfqyl5tbfnsq.png"><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/boost.png?w=544" alt /></a></p>
<p>In the illustration above, Boxes 1, 2, and 3 represent classifications made by individual models—D1, D2, and D3—each of which is a weak learner and performs poorly when used alone. However, when these models are combined, as shown in Box 4, they work together to make highly accurate predictions.</p>
<p>This is the core idea behind <strong>AdaBoost</strong> as well. In AdaBoost, each model’s prediction is weighted, and these weights are updated during training based on whether the model classifies each instance correctly or not. Incorrectly classified instances are given higher weights, making them more influential in the next iteration.</p>
<p>We’ll walk through each step of the AdaBoost algorithm and its implementation. For practical understanding, we’ll use the <strong>Breast Cancer</strong> dataset from the <code>sklearn</code> library. Let's start by loading and visualizing the dataset.</p>
<pre><code class="lang-python">df = pd.DataFrame(load_breast_cancer().data, columns=load_breast_cancer().feature_names)
df[<span class="hljs-string">'label'</span>] = load_breast_cancer().target

copy = df.copy()

df.head()
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/da1.png?w=951" alt /></p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/da2.png?w=399" alt /></p>
<p>Since the dataset contains a large number of features, we’ll primarily focus on the <code>label</code> column along with the new columns that will be appended next to it during the boosting process.</p>
<p>To begin, let’s use a <strong>Decision Tree</strong> as our initial weak learner to make a first-round prediction and observe the accuracy achieved.</p>
<pre><code class="lang-python">dt = DecisionTreeClassifier(random_state=<span class="hljs-number">50</span>, min_samples_split=<span class="hljs-number">100</span>)
dt.fit(df.iloc[:,:<span class="hljs-number">-1</span>],df.iloc[:,<span class="hljs-number">-1</span>])
df[<span class="hljs-string">'prediction'</span>] = dt.predict(df.iloc[:,:<span class="hljs-number">-1</span>])
df.iloc[:<span class="hljs-number">5</span>,<span class="hljs-number">-2</span>:]
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/pr-1.png?w=139" alt /></p>
<pre><code class="lang-bash">Accuracy = 0.945518453427065
We are getting an accuracy of around 95%.
</code></pre>
<p>Let’s now apply AdaBoost and see whether it improves the model's accuracy.</p>
<h2 id="heading-step-1">Step 1</h2>
<p>Assign an initial weight of 1\m to each data point, where mmm is the total number of data points in the dataset.</p>
<pre><code class="lang-python">df[<span class="hljs-string">'weight'</span>] = <span class="hljs-number">1</span>/len(df)
df.iloc[:<span class="hljs-number">5</span>,<span class="hljs-number">-3</span>:]
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/wei.png?w=199" alt /></p>
<h2 id="heading-step-2">Step 2</h2>
<p>Calculate the number of incorrectly classified data points.</p>
<pre><code class="lang-python">
no_of_errors = len(df[df.label != df.prediction1])
no_of_errors
</code></pre>
<p><strong>There are 31 incorrectly classified data points.</strong></p>
<p>Next, let's calculate the total error, where the total error is given by:</p>
<p><strong>total error = no_of_errors/total number of data points</strong></p>
<pre><code class="lang-python">total_errors = no_of_errors/len(df)
total_errors
</code></pre>
<p>The total error is calculated to be <strong>0.0545</strong>.</p>
<h3 id="heading-step-3">Step 3</h3>
<p>Calculating the amount of say (α)</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/say.png?w=976" alt /></p>
<pre><code class="lang-python">alpha = <span class="hljs-number">0.5</span> * np.log((<span class="hljs-number">1</span>-total_errors)/total_errors)
alpha
</code></pre>
<p>We get 1.423 as the value for the amount of say.</p>
<h3 id="heading-step-4">Step 4</h3>
<p><strong>The weight update is performed using the following rules:</strong></p>
<ul>
<li><p>For a correct prediction:</p>
<p>  <strong>updated weight = old weight X e<sup>–α</sup></strong></p>
</li>
<li><p>For an incorrect prediction:</p>
<p>  <strong>updated weight = old weight X e<sup>α</sup></strong></p>
</li>
</ul>
<pre><code class="lang-python">df[<span class="hljs-string">'weight_updated'</span>] = df.loc[df.label != df.prediction].weight * np.exp(alpha)
df.weight_updated = df[<span class="hljs-string">'weight_updated'</span>].fillna(df[df.label == df.prediction].weight * np.exp(-alpha))
df.iloc[:<span class="hljs-number">5</span>,<span class="hljs-number">-4</span>:]
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/upda.png?w=300" alt /></p>
<p>The updated weights are then normalized, ensuring that the sum of all the weights in the column equals 1.</p>
<pre><code class="lang-python">df.weight_updated = df.weight_updated/df.weight_updated.sum()
df.iloc[:<span class="hljs-number">5</span>,<span class="hljs-number">-4</span>:]
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/nor.png?w=304" alt /></p>
<p>Now, we can observe that in the updated weights column, the values for the incorrectly classified data points are significantly higher compared to those for the correctly classified ones.</p>
<h2 id="heading-step-5">Step 5</h2>
<p>Next, ranges are created for each updated weight, which represent the cumulative sum of the values in the column. For example, the range for index 0 is '0 to 0.016129', the range for index 1 is '0.016123 to (0.016129 + 0.000929)', the range for index 2 is '(0.016129 + 0.000929) to ((0.016129 + 0.000929) + 0.000929)', and so on.</p>
<p>In this manner, the range values for the last index will sum up to 1, as the weights have been normalized, ensuring their total is equal to 1.</p>
<h2 id="heading-step-6">Step 6</h2>
<p>Resampling of data is performed by selecting a random number between 0 and 1. The range in which this number falls determines which index from the 'df' dataframe is included in the new resampled dataframe. Since the weights of the incorrectly predicted data points are higher, the corresponding ranges will also be larger. As a result, many of the randomly chosen numbers will fall within the ranges of the incorrectly predicted data points. Consequently, these data points will be repeated more frequently in the resampled dataframe, giving them more priority in subsequent iterations.</p>
<pre><code class="lang-python">resampled = pd.DataFrame(columns=df.columns[:<span class="hljs-number">31</span>])
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(df)):
    index = df[df.ranges == df[np.random.rand()&lt;df.ranges].ranges.min()].index
    resampled.loc[i] = list(df.iloc[index,:<span class="hljs-number">31</span>].values[<span class="hljs-number">0</span>])

resampled.head()
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/res.png?w=944" alt /></p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/res2.png?w=394" alt /></p>
<p>The resampled data is then processed in the same way as in step 1. These 6 steps are repeated iteratively until the total error becomes zero, or the number of iterations reaches infinity. To automate this process, let's build a function that accumulates all the above steps to perform the iterations.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">adaboost</span>(<span class="hljs-params">df</span>):</span>
    dt = DecisionTreeClassifier(random_state=<span class="hljs-number">50</span>, min_samples_split=<span class="hljs-number">100</span>)
    dt.fit(df.iloc[:,:<span class="hljs-number">30</span>],df.iloc[:,<span class="hljs-number">30</span>])
    df[<span class="hljs-string">'prediction'</span>] = dt.predict(df.iloc[:,:<span class="hljs-number">30</span>])

    df[<span class="hljs-string">'weight'</span>] = <span class="hljs-number">1</span>/len(df)

    no_of_errors = len(df[df.label != df.prediction])

    total_errors = no_of_errors/len(df)

    alpha = <span class="hljs-number">0.5</span> * np.log((<span class="hljs-number">1</span>-total_errors)/total_errors)

    df[<span class="hljs-string">'weight_updated'</span>] = df.loc[df.label != df.prediction].weight * np.exp(alpha)
    df.weight_updated = df[<span class="hljs-string">'weight_updated'</span>].fillna(df[df.label == df.prediction].weight * np.exp(-alpha))

    df.weight_updated = df.weight_updated/df.weight_updated.sum()

    p = <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(df)):
        df.loc[i,<span class="hljs-string">'ranges'</span>] = df.loc[i,<span class="hljs-string">'weight_updated'</span>] + p
        p = df.loc[i,<span class="hljs-string">'ranges'</span>]

    resampled = pd.DataFrame(columns=df.columns[:<span class="hljs-number">31</span>])
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(df)):
        index = df[df.ranges == df[np.random.rand()&lt;df.ranges].ranges.min()].index
        resampled.loc[i] = list(df.iloc[index,:<span class="hljs-number">31</span>].values[<span class="hljs-number">0</span>])  

    df = resampled

    <span class="hljs-keyword">return</span> [df, dt]
</code></pre>
<p>The above function returns the resampled DataFrame and the trained model from each iteration. Upon execution, it stores the final resampled DataFrame along with the list of trained models across all iterations.</p>
<pre><code class="lang-python">df = copy.copy()

models = []    

<span class="hljs-keyword">try</span>:
    <span class="hljs-keyword">for</span> iter <span class="hljs-keyword">in</span> range(<span class="hljs-number">20</span>):        
        ada = adaboost(df)
        df = ada[<span class="hljs-number">0</span>]    
        models.append(ada[<span class="hljs-number">1</span>])
        print(<span class="hljs-string">'Decision stamp {0}'</span>.format(iter+<span class="hljs-number">1</span>))

<span class="hljs-keyword">except</span> Exception:
    <span class="hljs-keyword">pass</span>
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/dec-1.png?w=154" alt /></p>
<p>We have obtained 10 decision stumps (weak learners), which will now be used collectively to make future predictions. These models will be applied to the same dataset on which we initially observed an accuracy of 95%. Let's aggregate the outputs from all these models to evaluate the performance of the boosted ensemble.</p>
<pre><code class="lang-python">pred = np.zeros(len(df))
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(models)):    
    pred += models[i].predict(copy.iloc[:,:<span class="hljs-number">-1</span>])

pred
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/out-1.png?w=550" alt /></p>
<p>These values represent the aggregated predictions from all the models. Since each model outputs either a 0 or 1, a value like 2 in the array indicates that 2 models classified the instance as class 1, while a value of 6 means 6 out of the 10 models predicted class 1 for that particular instance.</p>
<p>Based on the number of models used, a threshold is set at half that number. If the aggregate output for any data point exceeds this threshold, it is classified as 1; otherwise, it is classified as 0. In our case, since we used 10 models, any output value greater than 5 is considered class 1, and the rest are classified as class 0.</p>
<pre><code class="lang-python">threshold = len(models)/<span class="hljs-number">2</span>
vec = np.vectorize(<span class="hljs-keyword">lambda</span> x: <span class="hljs-number">1</span> <span class="hljs-keyword">if</span> x&gt;threshold <span class="hljs-keyword">else</span> <span class="hljs-number">0</span>)
final_prediction = vec(pred)
final_prediction
</code></pre>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/out2.png?w=669" alt /></p>
<p>Now, using the output above, we calculate the accuracy.</p>
<pre><code class="lang-python">copy[<span class="hljs-string">'final_prediction'</span>] = final_prediction

print(<span class="hljs-string">'Accuracy ='</span>,accuracy_score(copy.label, copy.final_prediction))
</code></pre>
<pre><code class="lang-bash">Accuracy = 0.9753954305799648
This time we obtained an accuracy of 98%.
</code></pre>
<p>Thus, we can see that we have successfully improved the accuracy using Boosting.</p>
<p><em>Point to note:</em> This entire process is purely for demonstration and conceptual understanding. It is <strong>not recommended</strong> to use this manual implementation for solving real-world problems. For practical purposes, you should use the automated and optimized <code>AdaBoostClassifier</code> provided by the <code>sklearn</code> library.</p>
<p>I hope I was able to explain the algorithm clearly enough for you to understand and experiment with.<br />Your valuable feedback is always appreciated!</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>For more insights, projects, and articles, visit my portfolio at </em><a target="_new" href="https://www.tuhindutta.com"><em>www.tuhindutta.com</em></a><em>.</em></div>
</div>]]></content:encoded></item><item><title><![CDATA[Understanding the Sigmoid Function and Its Applications in Machine Learning]]></title><description><![CDATA[Let’s start with the basics—what exactly is the sigmoid function?
The sigmoid function is a mathematical function commonly used in machine learning, especially in binary classification problems and neural networks. It’s defined by the formula:

When ...]]></description><link>https://statisticallyspeaking.tuhindutta.com/understanding-sigmoid-function-applications-machine-learning</link><guid isPermaLink="true">https://statisticallyspeaking.tuhindutta.com/understanding-sigmoid-function-applications-machine-learning</guid><category><![CDATA[sigmoid function]]></category><category><![CDATA[logistic regression]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[binary classification]]></category><category><![CDATA[Statistical Methods]]></category><dc:creator><![CDATA[Tuhin Kumar Dutta]]></dc:creator><pubDate>Sat, 11 Sep 2021 18:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1746950828123/9f496a26-8dd3-40f2-808d-6bf02ba87f32.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let’s start with the basics—what exactly is the sigmoid function?</p>
<p>The sigmoid function is a mathematical function commonly used in machine learning, especially in binary classification problems and neural networks. It’s defined by the formula:</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/sig.png?w=292" alt class="image--center mx-auto" /></p>
<p>When plotted on a graph, the sigmoid function creates a smooth, S-shaped curve that asymptotically approaches 0 and 1 at the extremes.</p>
<p><a target="_blank" href="https://upload.wikimedia.org/wikipedia/commons/thumb/8/88/Logistic-curve.svg/1200px-Logistic-curve.svg.png"><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/sig2.png?w=1024" alt /></a></p>
<p>In the realm of machine learning, the sigmoid function proves to be incredibly useful—particularly in classification tasks where outcomes are binary, with one class represented as 0 and the other as 1.</p>
<p>A prominent example of such an application is <strong>Logistic Regression</strong>, a fundamental algorithm used for binary classification. In this context, the sigmoid function is employed to convert the model's linear output into a probability between 0 and 1, allowing us to interpret the result as the likelihood of belonging to a particular class.</p>
<p>To understand this more concretely, let’s walk through a simple example using an arbitrary dataset with one independent (input) variable and one dependent (output) variable. This will help illustrate the role sigmoid plays in logistic regression.</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/df-1.png?w=108" alt /></p>
<p>Let’s plot the data to gain a visual understanding.</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/pla.png?w=974" alt /></p>
<p>We clearly can't fit a straight line through the data points, as there is no apparent linear relationship.<br />However, for illustrative purposes, let’s assume a weight and intercept to fit an arbitrary linear model.</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/arb.png?w=975" alt /></p>
<p>It’s evident that the linear model doesn’t help much in this case.<br />To address this, we apply the sigmoid function to transform the linear equation—essentially replacing xxx in the sigmoid expression with the linear combination of features (as defined in our earlier equation).</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/scr.png?w=560" alt /></p>
<p>Now, let’s plot the transformed values against the independent variables to visualize the relationship.</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/fit-1.png?w=972" alt /></p>
<p>From the graph above, we observe that the straight line has been transformed into an S-shaped sigmoid curve, with values ranging between 0 and 1.<br />Now, each data point can be projected onto the sigmoid curve, and we can define a threshold above which the predicted class is 1.<br />For this example, let’s set the threshold at 0.23.</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/thr.png?w=977" alt /></p>
<p>For any new data, after applying the sigmoid transformation, if the resulting value exceeds the threshold (0.23), we classify it as belonging to class 1.<br />Note that, in the <strong>Logistic Regression</strong> model from the <strong>scikit-learn</strong> library, the default threshold is 0.5. Any data point with a sigmoid output greater than 0.5 is classified as class 1.</p>
<p>To fit the best sigmoid curve, we need to choose the optimal values for the weights and intercept. This process is similar to linear regression, where gradient descent is used to minimize the loss function. For logistic regression, the loss function is referred to as <strong>Log Loss</strong>, which is defined by the following expression:</p>
<p><img src="https://criticalmind.tech.blog/wp-content/uploads/2021/09/log.png?w=1024" alt /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>For more insights, projects, and articles, visit my portfolio at </em><a target="_new" href="https://www.tuhindutta.com"><em>www.tuhindutta.com</em></a><em>.</em></div>
</div>]]></content:encoded></item></channel></rss>