<jupyter_start><jupyter_text>Субдифференциальное исчислениеОдним из распространенных расширений градиентного спуска для случая негладких выпуклых функций является субградиентный спуск.Определение. Вектор $g\in \mathbb{R}^n$ функции $f:\mathcal{D}\subset\mathbb{R}^n\rightarrow \mathbb{R}$ называется субградиентом в точке $x$ если $\forall y\in \mathbb{D}$ выполняется$$f(y)\geq f(x)+g^T(y-x).$$Выпуклые функции не обязаны быть дифференцируемы, поэтому градиентный спуск нельзя корректно применить для произвольной выпуклой функции, тем не менее любая выпуклая функция в любой точке имеет хотя бы один субградиент, который можно использовать в качестве направления в градиентном спуске сохраняя сходимость.Определение. Субдифференциалом $f$ в точке $x$ называется множество всех субградиентов $f$ в точке $x$ и обозначается $\partial f(x)$.<jupyter_code>import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = [7, 7]
plt.rcParams["text.usetex"] = True
import numpy as np
def smooth_function(x):
    return x ** 2

def nonsmooth_function(x):
    return (x + 0.5) ** 2 if x > 0 else (x - 0.5) ** 2

def smooth_d(x):
    return 2 * x

def nonsmooth_left_d(x):
    return 2 * (x - 0.5)

def nonsmooth_right_d(x):
    return 2 * (x + 0.5)

def show_subgradient():
    fig, axs = plt.subplots(1, 2, figsize=(15, 7))
    
    axs[0].set_xlim((-2, 2))
    axs[0].set_ylim((-1, 3))

    axs[1].set_xlim((-2, 2))
    axs[1].set_ylim((-2, 2))

    
    for ax in axs:     
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)

        for name, spine in ax.spines.items():
            spine.set_visible(False)
            spine.set_visible(False)
       
    q = np.arange(-2, 2, 0.01)
    r = np.arange(-1, 1, 0.01)
    x_star = -0.2
    
    axs[0].plot(q, [smooth_function(x) for x in q], color='black')
    axs[0].plot(q, [smooth_function(x_star) + smooth_d(x_star) * (y - x_star) for y in q], color='grey', linestyle='--')
    axs[0].scatter([x_star], [smooth_function(x_star)], color='black')
    axs[0].text(1-0.4, smooth_function(1)+0.5, r"$f(x)$", fontsize=25)
    #print(smooth_d(x_star), 180 * np.arctan(smooth_d(x_star)) / np.pi)
    axs[0].text(x_star-0.5, smooth_function(x_star)-0.8, r"$\nabla f(x^*)^T(x-x^*)$",
                rotation=180 * np.arctan(smooth_d(x_star)) / np.pi, fontsize=20)

    x_star = 0
    
    axs[1].plot(q, [nonsmooth_function(x) for x in q], color='black')
    axs[1].plot(q, [nonsmooth_function(x_star) + nonsmooth_left_d(x_star) * (y - x_star) for y in q], color='grey', linestyle='--')
    axs[1].plot(q, [nonsmooth_function(x_star) + nonsmooth_right_d(x_star) * (y - x_star) for y in q], color='grey', linestyle='--')
    axs[1].scatter([x_star], [nonsmooth_function(x_star)], color='black')
    axs[1].text(0.5-0.4, nonsmooth_function(0.5)+0.7, r"$f(x)$", fontsize=25)
    #print(nonsmooth_left_d(x_star), 180 * np.arctan(nonsmooth_left_d(x_star)) / np.pi)
    axs[1].text(x_star+0.2, nonsmooth_function(x_star)-0.8, r"$\nabla g_2^T(x-x^*)$",
                rotation=180 * np.arctan(nonsmooth_left_d(x_star)) / np.pi, fontsize=20)
    axs[1].text(x_star-1.2, nonsmooth_function(x_star)-0.8, r"$\nabla g_1^T(x-x^*)$",
                rotation=180 * np.arctan(nonsmooth_right_d(x_star)) / np.pi, fontsize=20)
    plt.close(fig)
    return fig

show_subgradient()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.16656769596199525, 'fraction_numerical': 0.0332541567695962, 'mean_word_length': 3.8755426917510856, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 6, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '359449', 'license': 'MIT', 'n_tokens_mistral': 1522, 'n_tokens_neox': 1542, 'n_words': 274, 'path': 'optimization_course/08_subgradients.ipynb', 'repo_name': 'ivanoleinik/interactive-visualization', 'size': '3368'}