Traffine I/O

Bahasa Indonesia

2022-07-02

Regularisasi dalam Pembelajaran Mesin

Apa itu Regularisasi

Regularisasi adalah teknik yang digunakan dalam pembelajaran mesin dan pemodelan statistik untuk mengurangi kompleksitas model dengan menambahkan istilah hukuman ke fungsi kerugian. Istilah hukuman ini mencegah overfitting dan memastikan model generalisasi dengan baik pada data yang belum pernah dilihat sebelumnya. Dengan kata lain, regularisasi membantu mencapai keseimbangan antara underfitting dan overfitting dengan membatasi kapasitas model untuk mempelajari pola-pola kompleks dalam data.

Pentingnya Regularisasi dalam Pembelajaran Mesin

Regularisasi memainkan peran penting dalam pembelajaran mesin karena beberapa alasan:

  • Mencegah Overfitting
    Overfitting terjadi saat model mempelajari noise dalam data pelatihan, sehingga menghasilkan kinerja yang buruk pada data yang belum dilihat sebelumnya. Regularisasi membantu mencegah overfitting dengan memberikan hukuman pada model yang kompleks dan mendorong model yang lebih sederhana.

  • Seleksi Fitur
    Beberapa teknik regularisasi, seperti regularisasi L1, dapat mempromosikan sparsitas pada model dengan mereduksi beberapa koefisien menjadi nol. Hal ini efektif dalam melakukan seleksi fitur, membuat model lebih dapat diinterpretasi dan robust.

  • Stabilitas
    Teknik regularisasi, seperti regularisasi L2, dapat meningkatkan stabilitas model dengan mengurangi sensitivitas koefisien model terhadap perubahan kecil pada data masukan.

  • Mengurangi Kompleksitas Model
    Regularisasi membatasi kapasitas model, sehingga menghasilkan model yang lebih sederhana dan lebih mudah diinterpretasi serta dipelihara.

Overfitting dan Underfitting

Dalam pembelajaran mesin, tujuan akhir adalah membangun model yang generalisasi dengan baik pada data yang belum pernah dilihat sebelumnya. Namun, ada dua tantangan umum yang muncul selama proses pembuatan model: overfitting dan underfitting. Kedua hal ini dapat berdampak negatif pada kinerja model pada data baru.

  • Overfitting
    Overfitting terjadi saat model mempelajari noise atau fluktuasi acak dalam data pelatihan alih-alih pola-pola mendasar. Akibatnya, model dapat bekerja sangat baik pada data pelatihan tetapi buruk pada data yang belum pernah dilihat sebelumnya. Overfitting umumnya terjadi ketika model terlalu kompleks dan memiliki variasi yang tinggi.

  • Underfitting
    Underfitting terjadi saat model terlalu sederhana untuk menangkap pola-pola mendasar dalam data. Akibatnya, model bekerja buruk pada data pelatihan dan data yang belum pernah dilihat sebelumnya. Underfitting adalah hasil dari bias yang tinggi pada model.

Regularisasi L1 (Lasso)

Regularisasi L1, juga dikenal sebagai Lasso (Least Absolute Shrinkage and Selection Operator), adalah teknik regularisasi yang menambahkan nilai absolut dari koefisien model ke dalam fungsi kerugian. Fungsi kerugian yang dimodifikasi untuk regularisasi L1 dapat direpresentasikan sebagai berikut:

L1\_loss = Original\_loss + \ \sum_{i} |w_i|

di mana w_i adalah koefisien model dan \lambda adalah parameter regularisasi yang mengontrol kekuatan istilah hukuman.

Regularisasi L1 mendorong sparsitas pada model dengan mereduksi beberapa koefisien menjadi nol, sehingga efektif dalam melakukan seleksi fitur. Hal ini menghasilkan model yang lebih mudah diinterpretasi dan lebih sedikit kompleks.

Keuntungan

  • Seleksi Fitur
    Regularisasi L1 dapat melakukan seleksi fitur, membuat model lebih mudah diinterpretasi dan lebih robust.

  • Simplicity Model
    Dengan mendorong sparsitas pada koefisien model, regularisasi L1 menghasilkan model yang lebih sederhana, lebih mudah diinterpretasi, dan lebih mudah dipelihara.

Kekurangan

  • Instabilitas
    Regularisasi L1 dapat menyebabkan solusi yang tidak stabil ketika ada multikolinearitas antara fitur, karena cenderung memilih hanya satu fitur dari grup fitur yang berkorelasi.

  • Tidak Sesuai untuk Dataset Kecil
    Regularisasi L1 mungkin tidak berkinerja baik pada dataset kecil, karena sifatnya yang sparsitas dapat memperkenalkan bias tambahan.

Regularisasi L2 (Ridge)

Regularisasi L2, juga dikenal sebagai Ridge, adalah teknik regularisasi populer lainnya yang menambahkan kuadrat dari koefisien model ke dalam fungsi kerugian. Fungsi kerugian yang dimodifikasi untuk regularisasi L2 dapat direpresentasikan sebagai berikut:

L2\_loss = Original\_loss + \lambda \sum_{i} w_i^2

di mana w_i adalah koefisien model dan \lambda adalah parameter regularisasi yang mengontrol kekuatan istilah hukuman.

Regularisasi L2 mendorong model untuk menggunakan semua fitur, namun dengan koefisien yang lebih kecil, mengurangi overfitting dan mempromosikan stabilitas.

Keuntungan

  • Stabilitas
    Regularisasi L2 lebih stabil daripada regularisasi L1 dan bekerja dengan baik ketika ada multikolinearitas antara fitur, karena menyebar efek fitur yang berkorelasi di antara mereka.

  • Bias yang Lebih Sedikit
    Regularisasi L2 cenderung memperkenalkan bias yang lebih sedikit pada model dibandingkan regularisasi L1, sehingga lebih cocok untuk dataset yang lebih kecil.

Kekurangan

  • Tidak Ada Seleksi Fitur
    Berbeda dengan regularisasi L1, regularisasi L2 tidak mendorong sparsitas pada koefisien model, dan oleh karena itu, tidak melakukan seleksi fitur.

  • Model yang Kurang Dapat Diinterpretasi
    Karena regularisasi L2 tidak mendorong sparsitas, model yang dihasilkan mungkin kurang dapat diinterpretasi dibandingkan dengan model yang dihasilkan menggunakan regularisasi L1.

Regularisasi Elastic Net

Regularisasi Elastic Net adalah teknik hibrida yang menggabungkan keuntungan dari regularisasi L1 dan L2. Ini menggabungkan nilai absolut dan kuadrat dari koefisien model dalam fungsi kerugian. Fungsi kerugian yang dimodifikasi untuk regularisasi Elastic Net dapat direpresentasikan sebagai berikut:

ElasticNet\_loss = Original\_loss + \lambda (l1\_ratio \sum_{i} |w_i| + (1 - l1\_ratio) \sum_{i} w_i^2)

di mana w_i adalah koefisien model, \lambda adalah parameter regularisasi keseluruhan, dan l1_ratio adalah parameter campuran yang menentukan bobot istilah regularisasi L1 dan L2 dalam fungsi kerugian gabungan.

Regularisasi Elastic Net menyeimbangkan sifat induksi sparse dari regularisasi L1 dengan sifat promosi stabilitas dari regularisasi L2.

Keuntungan

  • Menyeimbangkan Regularisasi L1 dan L2
    Regularisasi Elastic Net menyeimbangkan sifat induksi sparse dari regularisasi L1 dengan sifat promosi stabilitas dari regularisasi L2, sehingga menjadi pilihan yang cocok untuk berbagai masalah.

  • Seleksi Fitur
    Regularisasi Elastic Net dapat melakukan seleksi fitur sambil menjaga stabilitas model, tidak seperti regularisasi L1, yang dapat tidak stabil dalam kehadiran multikolinearitas.

Kekurangan

  • Kompleksitas Komputasi
    Regularisasi Elastic Net memerlukan lebih banyak resource komputasi dibandingkan regularisasi L1 atau L2, karena melibatkan optimasi dua parameter regularisasi.

  • Pemilihan Hyperparameter
    Parameter tambahan, l1_ratio, perlu diatur, yang dapat meningkatkan kompleksitas proses pemilihan model.

Memilih Teknik Regularisasi yang Tepat

Memilih teknik regularisasi yang sesuai tergantung pada berbagai faktor, seperti ukuran dataset, keberadaan multikolinearitas, dan properti model yang diinginkan. Berikut beberapa panduan untuk membantu Anda memilih metode regularisasi yang tepat:

  • Ukuran Dataset
    Untuk dataset kecil, regularisasi L2 umumnya lebih sesuai, karena memperkenalkan bias yang lebih sedikit dibandingkan regularisasi L1. Namun, untuk dataset yang lebih besar, regularisasi L1 dapat bermanfaat karena sifat induksi sparse yang menghasilkan model yang lebih mudah diinterpretasi.

  • Multikolinearitas
    Jika dataset Anda memiliki multikolinearitas antara fitur, regularisasi L2 atau Elastic Net mungkin lebih tepat, karena mereka menyebar efek fitur yang berkorelasi di antara mereka dan mempromosikan stabilitas. Regularisasi L1 mungkin tidak berfungsi dengan baik dalam hal ini, karena cenderung memilih hanya satu fitur dari grup fitur yang berkorelasi.

  • Seleksi Fitur
    Jika Anda menginginkan model yang melakukan seleksi fitur, regularisasi L1 atau Elastic Net mungkin merupakan pilihan yang cocok, karena mereka mendorong sparsitas pada koefisien model. Regularisasi L2 tidak melakukan seleksi fitur, karena tidak mendorong sparsitas.

  • Interpretabilitas Model
    Jika Anda memprioritaskan interpretabilitas model, regularisasi L1 dapat menjadi pilihan yang baik karena sifat induksi sparse-nya. Namun, regularisasi Elastic Net juga dapat menghasilkan model yang mudah diinterpretasi sambil menjaga stabilitas dalam kehadiran multikolinearitas.

Visualisasi Regularisasi L1 dan L2

Berikut adalah skrip untuk memplotting regularisasi L1 dan L2 dengan Python.

Plotting 2D

python
import numpy as np
import matplotlib.pyplot as plt
import glob
import os
import warnings

lmbda = 2
w,h = 10,10

beta0 = np.linspace(-w, w, 100)
beta1 = np.linspace(-h, h, 100)
B0, B1 = np.meshgrid(beta0, beta1)

def diamond(lmbda=1, n=100):
    "get points along diamond at distance lmbda from origin"
    points = []
    x = np.linspace(0, lmbda, num=n // 4)
    points.extend(list(zip(x, -x + lmbda)))
    x = np.linspace(0, lmbda, num=n // 4)
    points.extend(list(zip(x,  x - lmbda)))
    x = np.linspace(-lmbda, 0, num=n // 4)
    points.extend(list(zip(x, -x - lmbda)))
    x = np.linspace(-lmbda, 0, num=n // 4)
    points.extend(list(zip(x,  x + lmbda)))
    return np.array(points)

def circle(lmbda=1, n=100):
    points = []
    for angle in np.linspace(0,np.pi/2, num=n//4):
        x = np.cos(angle) * lmbda
        y = np.sin(angle) * lmbda
        points.append((x,y))
    for angle in np.linspace(np.pi/2,np.pi, num=n//4):
        x = np.cos(angle) * lmbda
        y = np.sin(angle) * lmbda
        points.append((x,y))
    for angle in np.linspace(np.pi, np.pi*3/2, num=n//4):
        x = np.cos(angle) * lmbda
        y = np.sin(angle) * lmbda
        points.append((x,y))
    for angle in np.linspace(np.pi*3/2, 2*np.pi,num=n//4):
        x = np.cos(angle) * lmbda
        y = np.sin(angle) * lmbda
        points.append((x,y))
    return np.array(points)

def loss(b0, b1,
         a = 1,
         b = 1,
         c = 0,
         cx = -10,
         cy = 5):
    return a * (b0 - cx) ** 2 + b * (b1 - cy) ** 2 + c * (b0 - cx) * (b1 - cy)

def select_parameters(lmbda, reg, force_symmetric_loss, force_one_nonpredictive):
    while True:
        a = np.random.random() * 10
        b = np.random.random() * 10
        c = np.random.random() * 4 - 1.5
        if force_symmetric_loss:
            b = a
            c = 0
        elif force_one_nonpredictive:
            if np.random.random() > 0.5:
                a = np.random.random() * 15 - 5
                b = .1
            else:
                b = np.random.random() * 15 - 5
                a = .1
            c = 0
        x, y = 0, 0

        if reg=='L1':
            while np.abs(x) + np.abs(y) <= lmbda:
                x = np.random.random() * 2 * w - w
                y = np.random.random() * 2 * h - h
        else:
            while np.sqrt(x**2 + y**2) <= lmbda:
                x = np.random.random() * 2 * w - w
                y = np.random.random() * 2 * h - h

        Z = loss(B0, B1, a=a, b=b, c=c, cx=x, cy=y)
        loss_at_min = loss(x, y, a=a, b=b, c=c, cx=x, cy=y)
        if (Z >= loss_at_min).all():
            break
    return Z, a, b, c, x, y

def plot_loss(boundary, reg,
              boundary_color='#2D435D',
              boundary_dot_color='#E32CA6',
              force_symmetric_loss=False, force_one_nonpredictive=False,
              show_contours=True, contour_levels=50, show_loss_eqn=False,
              show_min_loss=True,idx=None,fig=None,ax=None,num_trials=None):
    Z, a, b, c, x, y = select_parameters(lmbda, reg,
                          force_symmetric_loss=force_symmetric_loss,
                          force_one_nonpredictive=force_one_nonpredictive)
    eqn = f"{a:.2f}(b0 - {x:.2f})^2 + {b:.2f}(b1 - {y:.2f})^2 + {c:.2f} b0 b1"
    n_col = 5
    if show_loss_eqn:
        ax[idx//n_col, idx%n_col].set_title(eqn, fontsize=10)
    ax[idx//n_col, idx%n_col].set_xlabel("x", fontsize=8, labelpad=0)
    ax[idx//n_col, idx%n_col].set_ylabel("y", fontsize=8, labelpad=-10)
    ax[idx//n_col, idx%n_col].set_xticks([-10,-5,0,5,10])
    ax[idx//n_col, idx%n_col].set_yticks([-10,-5,0,5,10])
    ax[idx//n_col, idx%n_col].set_xlabel(r"$w_1$", fontsize=8)
    ax[idx//n_col, idx%n_col].set_ylabel(r"$w_2$", fontsize=8)
    shape = ""
    if force_symmetric_loss:
        shape = "symmetric "
    elif force_one_nonpredictive:
        shape = "orthogonal "
    ax[idx//n_col, idx%n_col].set_title(f"{reg} constraint w/{shape}loss function", fontsize=8)
    if show_contours:
        ax[idx//n_col, idx%n_col].contour(B0, B1, Z, levels=contour_levels, linewidths=1.0, cmap='coolwarm')
    else:
        ax[idx//n_col, idx%n_col].contourf(B0, B1, Z, levels=contour_levels, cmap='coolwarm')

    ax[idx//n_col, idx%n_col].plot([-w,+w],[0,0], '-', c='k', lw=.5)
    ax[idx//n_col, idx%n_col].plot([0, 0],[-h,h], '-', c='k', lw=.5)

    if boundary is not None:
        ax[idx//n_col, idx%n_col].plot(boundary[:,0], boundary[:,1], '-', lw=1.5, c=boundary_color)

    if show_min_loss:
        ax[idx//n_col, idx%n_col].scatter([x],[y], s=90, c='k')

    eqn = f"{a:.2f}(b0 - {x:.2f})^2 + {b:.2f}(b1 - {y:.2f})^2 + {c:.2f} (b0-{x:.2f}) (b1-{y:.2f})"
    if boundary is not None:
        losses = [loss(*edgeloc, a=a, b=b, c=c, cx=x, cy=y) for edgeloc in boundary]
        minloss_idx = np.argmin(losses)
        coeff = boundary[minloss_idx]
        ax[idx//n_col, idx%n_col].scatter([coeff[0]], [coeff[1]], s=90, c=boundary_dot_color)
        if force_symmetric_loss:
            if reg=='L2':
                ax[idx//n_col, idx%n_col].plot([x,0],[y,0], ':', c='k')
            else:
                ax[idx//n_col, idx%n_col].plot([x,coeff[0]],[y,coeff[1]], ':', c='k')

def plot_2d(reg, force_symmetric_loss=False, force_one_nonpredictive=False,num_trials=None):
    if num_trials <=5:
        fig,ax = plt.subplots(1,5,figsize=(10,2),squeeze=False)
    elif num_trials > 5 and num_trials<=10:
        fig,ax = plt.subplots(2,5,figsize=(10,4))
    elif num_trials > 10 and num_trials<=15:
        fig,ax = plt.subplots(3,5,figsize=(10,6))
        fig.subplots_adjust(hspace=0.6, wspace=0.4)
    if reg == 'L1':
        boundary = diamond(lmbda=lmbda, n=100)
    else:
        boundary = circle(lmbda=lmbda, n=100)
    for i in range(num_trials):
        plot_loss(boundary=boundary, reg=reg,
                  force_symmetric_loss=force_symmetric_loss,
                  force_one_nonpredictive=force_one_nonpredictive,
                  contour_levels=contour_levels,idx=i,fig=fig,ax=ax,num_trials=num_trials)
        shape_fname = ""
        if force_symmetric_loss:
            shape_fname = "symmetric-"
        elif force_one_nonpredictive:
            shape_fname = "orthogonal-"
        plt.tight_layout()

    if num_trials%5!=0:
        k = 5*(1 + num_trials//5)-num_trials
        for i in range(k):
            fig.delaxes(ax[num_trials//5][-i-1])
    plt.show()

n_trials = 5
contour_levels=50
s = 0
np.random.seed(s)
for mode in ["L1", "L2"]:
    plot_2d(reg=mode,num_trials=n_trials)

2D plot of L1 and L2 regularization

Plotting 3D

python
# https://github.com/parrt/website-explained.ai/blob/master/regularization/code/l2loss_with_penalty.py

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.patches import Circle
import mpl_toolkits.mplot3d.art3d as art3d
import glob
import os
from PIL import Image as PIL_Image

def diamond(lmbda=1, n=100):
    "get points along diamond at distance lmbda from origin"
    points = []
    x = np.linspace(0, lmbda, num=n // 4)
    points.extend(list(zip(x, -x + lmbda)))
    x = np.linspace(0, lmbda, num=n // 4)
    points.extend(list(zip(x,  x - lmbda)))
    x = np.linspace(-lmbda, 0, num=n // 4)
    points.extend(list(zip(x, -x - lmbda)))
    x = np.linspace(-lmbda, 0, num=n // 4)
    points.extend(list(zip(x,  x + lmbda)))
    return np.array(points)

def circle(lmbda=1, n=100):
    points = []
    for angle in np.linspace(0,np.pi/2, num=n//4):
        x = np.cos(angle) * lmbda
        y = np.sin(angle) * lmbda
        points.append((x,y))
    for angle in np.linspace(np.pi/2,np.pi, num=n//4):
        x = np.cos(angle) * lmbda
        y = np.sin(angle) * lmbda
        points.append((x,y))
    for angle in np.linspace(np.pi, np.pi*3/2, num=n//4):
        x = np.cos(angle) * lmbda
        y = np.sin(angle) * lmbda
        points.append((x,y))
    for angle in np.linspace(np.pi*3/2, 2*np.pi,num=n//4):
        x = np.cos(angle) * lmbda
        y = np.sin(angle) * lmbda
        points.append((x,y))
    return np.array(points)

def loss(b0, b1,
         a = 1,
         b = 1,
         c = 0,     # axis stretch
         cx = -10,  # shift center x location
         cy = 5,    # shift center y
         lmbda=1.0,
         yintercept=100):
    eqn = f"{a:.2f}(b0 - {cx:.2f})^2 + {b:.2f}(b1 - {cy:.2f})^2 + {c:.2f} (b0-{cx:.2f}) (b1-{cy:.2f}) + {yintercept}"
    return lmbda * (a * (b0 - cx) ** 2 + b * (b1 - cy) ** 2) + c * (b0 - cx) * (b1 - cy) + yintercept

def loss_l1(b0,b1,a=1,b=1,c=0,cx=-10,cy=5,lmbda=1.0,yintercept=100):
    return lmbda*(a*np.abs(b0-cx) + b*np.abs(b1-cy))

def plot_3d(mode, last_lmbda, stepsize, lmbdas):
    fig = plt.figure(figsize=(10,10))
    plt.subplots_adjust(wspace=0.4, hspace=2.0)
    for i,lmbda in enumerate(lmbdas):
        ax = fig.add_subplot(3,3,i+1, projection='3d')
        ax.set_xlabel("$w_1$", labelpad=0)
        ax.set_ylabel("$w_2$", labelpad=0)
        ax.set_title(mode + "Regularization",fontsize=10)
        ax.tick_params(axis='x', pad=0)
        ax.tick_params(axis='y', pad=0)
        ax.set_zlim(0, 1400)

        cx = 15
        cy = -15

        ax.plot([cx], [cy], marker='x', markersize=10, color='black')
        ax.text(-20,20,800, f"$lambda={lmbda:.1f}$", fontsize=10)

        beta0 = np.linspace(-30, 30, 300)
        beta1 = np.linspace(-30, 30, 300)

        B0, B1 = np.meshgrid(beta0, beta1)

        if mode=='L2':
            Z1 = loss(B0, B1, a=1, b=1, c=0, cx=0, cy=0, lmbda=lmbda, yintercept=0)
            Z2 = loss(B0, B1, a=5, b=5, c=0, cx=cx, cy=cy, yintercept=0)
            Z = Z1 + Z2

        elif mode=='L1':
            Z1 = loss_l1(B0,B1,a=5,b=5,cx=0,cy=0,lmbda=lmbda)
            Z2 = loss(B0, B1, a=5, b=5, c=0, cx=cx, cy=cy, yintercept=0)
            Z = Z1 + Z2

        origin = Circle(xy=(0, 0), radius=1, color='k')
        ax.add_patch(origin)
        art3d.pathpatch_2d_to_3d(origin, z=0, zdir="z")

        scale = 1.5
        vmax = 8000
        contr = ax.contour(B0, B1, Z, levels=50, linewidths=.5,
                        cmap='coolwarm',
                        zdir='z', offset=0, vmax=vmax)

        #surface plot
        j = lmbda*scale
        b0 = (j, 20-j)
        beta0 = np.linspace(-j, 25-j, 300)
        beta1 = np.linspace(-25+j, j, 300)
        B0, B1 = np.meshgrid(beta0, beta1)

        if mode=='L1':
            Z1 = loss_l1(B0,B1,a=5,b=5,cx=0,cy=0,lmbda=lmbda)
            Z2 = loss(B0, B1, a=5, b=5, c=0, cx=cx, cy=cy, yintercept=0)
            Z = Z1 + Z2

        elif mode=='L2':
            Z1 = loss(B0, B1, a=1, b=1, c=0, cx=0, cy=0, lmbda=lmbda, yintercept=0)
            Z2 = loss(B0, B1, a=5, b=5, c=0, cx=cx, cy=cy, yintercept=0)
            Z = Z1 + Z2

        vmax = 2700
        ax.plot_surface(B0, B1, Z, alpha=1.0, cmap='coolwarm', vmax=vmax)

        if mode=="L1":
            boundary = diamond(lmbda=lmbda)
            ax.plot(boundary[:, 0], boundary[:, 1], '-', lw=.5, c="red")

        elif mode=="L2":
            boundary = circle(lmbda=lmbda)
            ax.plot(boundary[:, 0], boundary[:, 1], '-', lw=.5, c="red")

        ax.view_init(elev=38, azim=-134)

        plt.tight_layout()

last_lmbda = 10
stepsize = 3.0
lmbdas = list(np.arange(1, last_lmbda, step=stepsize))

for mode in ["L1", "L2"]:
    plot_3d(mode, last_lmbda, stepsize, lmbdas)

3D plot of L1 and L2 regularization

References

https://towardsdatascience.com/regularization-in-machine-learning-76441ddcf99a
https://www.linkedin.com/pulse/intuitive-visual-explanation-differences-between-l1-l2-xiaoli-chen
https://www.analyticsvidhya.com/blog/2022/08/regularization-in-machine-learning/
https://www.dataquest.io/blog/regularization-in-machine-learning/
https://www.simplilearn.com/tutorials/machine-learning-tutorial/regularization-in-machine-learning
https://developers.google.com/machine-learning/crash-course/regularization-for-simplicity/l2-regularization
https://www.analyticsvidhya.com/blog/2021/05/complete-guide-to-regularization-techniques-in-machine-learning/
https://www.youtube.com/watch?v=QNxNCgtWSaY&ab_channel=MachineLearning%26MyMusic
https://www.youtube.com/watch?v=3vfiMRjgzZ8&t=3s&ab_channel=予備校のノリで学ぶ「大学の数学・物理」

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!