<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>alchemmist — C</title><link>https://alchemmist.xyz/ru/tags/c/</link><description>Последние записи в блоге alchemmist</description><generator>Hugo 0.163.3</generator><language>ru</language><atom:link href="https://alchemmist.xyz/ru/tags/c/index.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Wed, 10 Sep 2025 17:31:00 +0300</lastBuildDate><item><title>Алгоритм Уэлфорда</title><link>https://alchemmist.xyz/ru/articles/welford-algorithm/</link><pubDate>Wed, 10 Sep 2025 17:31:00 +0300</pubDate><dc:creator>alchemmist</dc:creator><guid>https://alchemmist.xyz/ru/articles/welford-algorithm/</guid><description>Представьте, что к вам потоком поступают данные: миллионы транзакций, цены акций, показания датчиков. Вам нужно вычислить среднее значение, дисперсию и корреляции. Классический способ — сохранить все данные и затем вычислить. Но это занимает много памяти, вычисления становятся тяжелыми, на больших числах возникают ошибки округления. Недавно я столкнулся с алгоритмом Уэлфорда, который позволяет проводить онлайн-вычисления за одну итерацию без сохранения больших данных. Это очень элегантный, стабильный и точный алгоритм.
Постановка задачи довольно проста: мы хотим вычислять статистики (среднее, дисперсию, ковариацию, корреляцию) шаг за шагом, когда мы получаем $n$ данных в потоке, без сохранения всех данных и дополнительных итераций. Напомним формулы базовым способом. Среднее:</description><content:encoded><![CDATA[<p>Представьте, что к вам потоком поступают данные: миллионы транзакций, цены акций, показания датчиков. Вам нужно вычислить среднее значение, дисперсию и корреляции. Классический способ — сохранить все данные и затем вычислить. Но это занимает много памяти, вычисления становятся тяжелыми, на больших числах возникают ошибки округления. Недавно я столкнулся с алгоритмом Уэлфорда, который позволяет проводить онлайн-вычисления за одну итерацию без сохранения больших данных. Это очень элегантный, стабильный и точный алгоритм.</p>
<p>Постановка задачи довольно проста: мы хотим вычислять статистики (среднее, дисперсию, ковариацию, корреляцию) шаг за шагом, когда мы получаем $n$ данных в потоке, без сохранения всех данных и дополнительных итераций. Напомним формулы базовым способом. Среднее:
</p>
$$
\overline{x} = \frac{1}{n}\sum\limits_{i = 1}^{n}x_i
$$<p>
Дисперсия:
</p>
$$
s^2 = \frac{1}{n - 1}\sum\limits_{i = 1}^{n}(x_i - \overline{x})^2
$$<p>
Как видите, для среднего нужна сумма: первая проблема — как получить сумму без всех чисел? А для дисперсии нужно среднее: вторая проблема — как узнать среднее без всех данных?</p>

<h2 id="интуиция-метода">
  <a class="link" href="#%d0%b8%d0%bd%d1%82%d1%83%d0%b8%d1%86%d0%b8%d1%8f-%d0%bc%d0%b5%d1%82%d0%be%d0%b4%d0%b0">
    #
  </a>
  Интуиция метода
</h2>

<p>Попробуем прочувствовать интуицию метода Уэлфорда. Представьте, у нас есть набор данных: $A = \{1, 2, 3\}$, среднее этого набора $\overline{x}_A = \frac{1 + 2 +3}{3} = 2$, и затем мы получили еще одно число: $6$. Чувствуете, что оно сдвинет наше среднее в сторону больших значений? Но насколько? Представьте похожий набор, но с большим количеством данных: $B = \{1, 1, 2, 2, 3, 3\}$, среднее также равно $2$, и мы также добавляем число $6$. Снова чувствуем, что сдвиг будет в сторону больших значений, но не такой сильный. Проверим нашу интуицию:
</p>
$$
\begin{align*}
&A' = \{1, 2, 3, 6\}, \quad \overline{x}_{A'} = \frac{1 + 2 + 3 + 6}{4} = 3, \quad \Delta_A = |\overline{x}_A - \overline{x}_{A'}| = 1 \\
&B' = \{1, 1, 2, 2, 3, 3, 6\},  \quad \overline{x}_{B'} = \frac{2(1 + 2 + 3) + 6}{7} \approx 2.57, \quad \Delta_{B} = \overline{x}_B - \overline{x}_{B'} \approx 0.57
\end{align*}
$$<p>
Наша интуиция была права! Осталось понять, как мы можем вычислить эту дельту, если у нас есть только предыдущее среднее $\overline{x}_{i - 1}$, новое число $x_i$ и количество наших чисел. Метод Уэлфорда гласит: каждое новое значение сдвигает среднее в свою сторону с весом $1/i$:
</p>
$$
\overline{x}_i = \overline{x}_{i - 1} + \frac{1}{i}(x_i - \overline{x}_{i - 1})
$$<p>
Проверим это для набора $B$. У нас есть среднее без нового числа $\overline{x}_{i - 1} = 2$ и новое число $x_i = 6$, и это седьмое число, $i = 7$. Цель — найти среднее $\overline{x}_i$ по формуле Уэлфорда:
</p>
$$
\overline{x}_i = 2 + \frac{1}{7}(6 - 2) = 2 + 0.57\ldotp\!\ldotp\!\ldotp \ \approx 2.57
$$<p>
Как видите, это абсолютно точное значение. Отлично!</p>

<h2 id="дисперсия-корреляция-ковариация">
  <a class="link" href="#%d0%b4%d0%b8%d1%81%d0%bf%d0%b5%d1%80%d1%81%d0%b8%d1%8f-%d0%ba%d0%be%d1%80%d1%80%d0%b5%d0%bb%d1%8f%d1%86%d0%b8%d1%8f-%d0%ba%d0%be%d0%b2%d0%b0%d1%80%d0%b8%d0%b0%d1%86%d0%b8%d1%8f">
    #
  </a>
  Дисперсия, корреляция, ковариация
</h2>

<p>Истинная сила алгоритма Уэлфорда раскрывается при вычислении дисперсии. Наивный подход требует хранения всех точек данных и пересчета среднего перед вычислением дисперсии, что вычислительно дорого. Метод Уэлфорда изящно решает это, поддерживая текущую оценку дисперсии с использованием следующего рекуррентного соотношения:</p>
<p>Для дисперсии мы поддерживаем текущее среднее $\overline{x}$ и сумму квадратов разностей $S$. Для каждой новой точки данных $x_n$ на шаге $n$:
</p>
$$
\begin{gather*}
\overline{x}_n = \overline{x}_{n-1} + \frac{1}{n}(x_n - \overline{x}_{n-1}) \\
S_n = S_{n-1} + (x_n - \overline{x}_{n-1})(x_n - \overline{x}_n)
\end{gather*}
$$<p>
Дисперсию генеральной совокупности можно затем вычислить как $\sigma^2 = S_n/n$, а выборочную дисперсию как $s^2 = S_n/(n-1)$.</p>
<p>Этот подход красиво расширяется на ковариацию и корреляцию. Для двух переменных $X$ и $Y$ мы поддерживаем счетчики $n$, средние $\overline{x}$ и $\overline{y}$, и накопленные произведения $C_{xy}$:
</p>
$$
\begin{gather*}
\delta_x = x_n - \overline{x}_{n-1}, \quad
\delta_y = y_n - \overline{y}_{n-1} \\
\overline{x}_n = \overline{x}_{n-1} + \frac{\delta_x}{n}, \quad
\overline{y}_n = \overline{y}_{n-1} + \frac{\delta_y}{n} \\
C_{xy_n} = C_{xy_{n-1}} + \delta_x (y_n - \overline{y}_n) \\
\end{gather*}
$$<p>
Ковариация тогда равна $\text{cov}(X,Y) = \frac{C_{xy_n}}{(n-1)}$, а корреляция $\text{corr}(X,Y) = \frac{C_{xy_n}}{\sqrt{S_{x_n} S_{y_n}}}$.</p>

<h2 id="реализация-на-c">
  <a class="link" href="#%d1%80%d0%b5%d0%b0%d0%bb%d0%b8%d0%b7%d0%b0%d1%86%d0%b8%d1%8f-%d0%bd%d0%b0-c">
    #
  </a>
  Реализация на C
</h2>

<p>Вот полная реализация алгоритма Уэлфорда на C для вычисления среднего и дисперсии:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;math.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">long</span> <span class="kt">long</span> <span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">double</span> <span class="n">mean</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">double</span> <span class="n">M2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">welford_state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">welford_update</span><span class="p">(</span><span class="n">welford_state</span> <span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="kt">double</span> <span class="n">x</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">state</span><span class="o">-&gt;</span><span class="n">n</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">double</span> <span class="n">delta</span> <span class="o">=</span> <span class="n">x</span> <span class="o">-</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">mean</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">state</span><span class="o">-&gt;</span><span class="n">mean</span> <span class="o">+=</span> <span class="n">delta</span> <span class="o">/</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">state</span><span class="o">-&gt;</span><span class="n">M2</span> <span class="o">+=</span> <span class="n">delta</span> <span class="o">*</span> <span class="p">(</span><span class="n">x</span> <span class="o">-</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">mean</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">double</span> <span class="nf">welford_mean</span><span class="p">(</span><span class="k">const</span> <span class="n">welford_state</span> <span class="o">*</span><span class="n">state</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">mean</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">double</span> <span class="nf">welford_variance</span><span class="p">(</span><span class="k">const</span> <span class="n">welford_state</span> <span class="o">*</span><span class="n">state</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">n</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span> <span class="o">?</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">M2</span> <span class="o">/</span> <span class="p">(</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">:</span> <span class="mf">0.0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">double</span> <span class="nf">welford_stddev</span><span class="p">(</span><span class="k">const</span> <span class="n">welford_state</span> <span class="o">*</span><span class="n">state</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nf">sqrt</span><span class="p">(</span><span class="nf">welford_variance</span><span class="p">(</span><span class="n">state</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">welford_state</span> <span class="n">state</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="kt">double</span> <span class="n">test_data</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">,</span> <span class="mf">6.0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="kt">size_t</span> <span class="n">data_size</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">test_data</span><span class="p">)</span> <span class="o">/</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">test_data</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">data_size</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">welford_update</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">test_data</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;Added %.1f: mean=%.3f, variance=%.3f, stddev=%.3f</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">               <span class="n">test_data</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="nf">welford_mean</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">),</span> 
</span></span><span class="line"><span class="cl">               <span class="nf">welford_variance</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">),</span> <span class="nf">welford_stddev</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Для ковариации и корреляции мы можем расширить этот подход:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">long</span> <span class="kt">long</span> <span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">double</span> <span class="n">mean_x</span><span class="p">,</span> <span class="n">mean_y</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">double</span> <span class="n">C2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">welford_cov_state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">welford_cov_update</span><span class="p">(</span><span class="n">welford_cov_state</span> <span class="o">*</span><span class="n">state</span><span class="p">,</span> <span class="kt">double</span> <span class="n">x</span><span class="p">,</span> <span class="kt">double</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">state</span><span class="o">-&gt;</span><span class="n">n</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">double</span> <span class="n">delta_x</span> <span class="o">=</span> <span class="n">x</span> <span class="o">-</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">mean_x</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">double</span> <span class="n">delta_y</span> <span class="o">=</span> <span class="n">y</span> <span class="o">-</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">mean_y</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">state</span><span class="o">-&gt;</span><span class="n">mean_x</span> <span class="o">+=</span> <span class="n">delta_x</span> <span class="o">/</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">state</span><span class="o">-&gt;</span><span class="n">mean_y</span> <span class="o">+=</span> <span class="n">delta_y</span> <span class="o">/</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">state</span><span class="o">-&gt;</span><span class="n">C2</span> <span class="o">+=</span> <span class="n">delta_x</span> <span class="o">*</span> <span class="p">(</span><span class="n">y</span> <span class="o">-</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">mean_y</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">double</span> <span class="nf">welford_covariance</span><span class="p">(</span><span class="k">const</span> <span class="n">welford_cov_state</span> <span class="o">*</span><span class="n">state</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">n</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span> <span class="o">?</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">C2</span> <span class="o">/</span> <span class="p">(</span><span class="n">state</span><span class="o">-&gt;</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">:</span> <span class="mf">0.0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div>]]></content:encoded><category>algorithms</category><category>C</category><category>math</category></item></channel></rss>