open atlas
← Все проекты

data · advanced · 8d

Числовой инструментарий

Собери небольшую библиотеку, на которую втайне опирается любая численная программа: векторы, матрицы, решатель линейных систем и описательную статистику — написанные тобой и проверенные на ответах, которые ты можешь подтвердить вручную. Здесь алгебра, которую ты выучил, перестаёт быть домашним заданием и становится кодом, который вызывает другой код. Ты на собственном опыте поймёшь, почему арифметика с плавающей точкой немного врёт, почему решить Ax = b сложнее, чем подсказывает учебник, и как доказать, что твои числа верны.

Любой инструмент работы с данными, любая симуляция и любая модель машинного обучения стоят на слое, в точности таком, как этот, — векторы, матрицы, линейный решатель и немного статистики, — и почти ни один практикующий инженер ни разу не собирал его с нуля. Сделав это, ты превращаешь символы, которыми орудовал в алгебре, в механизм, на который опираются другие программы, и усваиваешь урок, который не даст никакая чистая математика: компьютер хранит не вещественные числа, а приближения, и корректный алгоритм, запущенный на этих приближениях, всё равно может выдать неверный ответ. Когда ты увидишь, как частичный выбор ведущего элемента спасает решение, запоротое наивной версией, и как устойчивая формула дисперсии остаётся вменяемой там, где учебная ушла в минус, — ты перестанешь слепо доверять численному коду и начнёшь понимать, почему библиотеки, на которые ты будешь опираться всю карьеру, написаны именно так аккуратно.

Результат

Самодостаточная числовая библиотека с типами вектор/матрица и арифметикой над ними, решателем Ax = b методом Гаусса и функциями mean/variance/std — всё это покрыто набором тестов, сверяющим результаты с вычисленными вручную и эталонными значениями, включая хотя бы один плохо обусловленный случай, который вскрывает численную ошибку.

Этапы

0/5 · 0%
  1. 01Векторы и матрицы как данные

    Прежде чем что-то решать, нужен способ хранить числа. Реши, как вектор и матрица живут в памяти — плоский массив со счётчиком строк или вложенные массивы, — и построй небольшие операции, на которых держится всё остальное: сложение, умножение на скаляр, скалярное произведение и умножение матрицы на вектор. Скалярное произведение — место, где новички впервые встречают тихий баг, ведь границы цикла и сопоставление индексов должны совпадать в точности; ошибись в одном — и ответ выглядит правдоподобно, но неверен. Держи операции чистыми: принимай входные данные, возвращай новый вектор или матрицу и никогда не мутируй аргументы, чтобы поздний шаг не испортил ранний. Награда за то, что этот слой получился скучным и правильным, — в том, что каждая более сложная вещь поверх него наследует эту правильность.

    Критерии готовности
    • Сложение, масштабирование и скалярное произведение векторов дают верные результаты на проверенных вручную входах и отклоняют несовпадающие длины, а не выдают мусор.
    • Умножение матрицы на вектор даёт результат, который ты можешь проверить вручную на примере 3x3, и операции возвращают новые объекты, а не мутируют входные данные.
  2. 02Реши Ax = b методом исключения

    Теперь заставь инструментарий делать настоящую работу: решать систему линейных уравнений. Реализуй метод Гаусса — приведи матрицу к верхнетреугольному виду, вычитая масштабированные строки, а затем обратной подстановкой считай неизвестные. Это та же процедура, что ты делал вручную в алгебре, но машина заставляет быть точным в порядке операций и в учёте того, какая строка какую масштабирует. Начни с наивной версии на «удобной» системе 3x3, где ответ уже известен, чтобы неверный результат был очевидно неверным. Ты обнаружишь, что у алгоритма стоимость порядка n в кубе операций — вот почему решение системы из тысячи уравнений это реальные затраты, а не бесплатно: сложность, которую ты изучал, перестаёт быть абстракцией в тот момент, когда твой решатель начинает тормозить.

    Критерии готовности
    • Решатель возвращает верный x для системы 3x3, решение которой ты вычислил вручную.
    • Подстановка возвращённого x обратно в Ax воспроизводит b в пределах малого допуска, подтверждая решение, а не предполагая его.
  3. 03Заставь решатель пережить плохие строки

    Наивный решатель ломается в первый же раз, когда ведущий элемент равен нулю — ты делишь на него и получаешь бесконечность или NaN, — и тихо теряет точность, когда ведущий элемент просто очень мал. Лекарство — частичный выбор ведущего элемента: перед исключением столбца переставь наверх строку с наибольшим по модулю значением в этом столбце, чтобы всегда делить на самое большое доступное число. Построй небольшой пример, на котором наивная версия выдаёт дико неверный ответ, а затем посмотри, как выбор ведущего элемента её спасает; этот контраст и есть весь урок. Это твоя первая настоящая встреча с мыслью, что математически верный алгоритм всё равно может быть численно неверным и что числа с плавающей точкой на числовой прямой — не те точные вещественные числа, которыми ты рассуждал на бумаге.

    Критерии готовности
    • Система, ставящая ноль или крошечное значение на диагональ, больше не падает и не возвращает NaN — выбор ведущего элемента переставляет строки и даёт верный ответ.
    • Тест демонстрирует конкретный случай, где наивный решатель ошибается, а решатель с выбором ведущего элемента прав, бок о бок.
  4. 04Опиши столбец чисел

    Добавь статистическую сторону: по вектору выборки вычисли среднее, дисперсию и стандартное отклонение. Среднее — просто; дисперсия — место, где ждёт тонкая ловушка. Учебная формула, вычитающая среднее квадратов из квадрата среднего, делается в один проход по плавающей точке, но катастрофически теряет значимость, когда числа большие и близкие, возвращая отрицательную дисперсию, что математически невозможно. Реализуй вместо неё численно честный двухпроходный вариант (или бегущую формулу Уэлфорда) и докажи тестом на больших почти равных значениях, что наивная формула проваливается, а твоя держится. Осознанно выбери между дисперсией генеральной совокупности и выборочной — выбор «делить на n» против «делить на n минус один» не косметический, и сеньор-инженер заявляет, какой из них он поставляет и почему.

    Критерии готовности
    • Среднее, дисперсия и стандартное отклонение совпадают с эталоном (например, numpy или ручной счёт) на небольшом известном наборе данных.
    • Тест на больших почти равных значениях показывает, что наивная однопроходная дисперсия проваливается (например, уходит в минус), тогда как твоя реализация остаётся корректной и неотрицательной.
  5. 05Проверь по известной истине

    Числовая библиотека, которой нельзя доверять, ничего не стоит, поэтому этот этап — про то, как доверие заслужить. Построй набор тестов вокруг ответов, которые ты можешь проверить независимо: решений, вычисленных вручную; тождеств, которые обязаны выполняться (матрица, умноженная на своё решение, воспроизводит правую часть); и эталонной реализации, с которой ты сверяешься. Дисциплина здесь — утверждать с допуском, а не на точное равенство: результаты с плавающей точкой почти никогда не совпадают побитово, поэтому ты сравниваешь в пределах эпсилон и выбираешь этот эпсилон осознанно. Покрой скучный счастливый путь, но основную энергию потрать на края: вырожденную матрицу без единственного решения, пустой ввод, вектор из одного элемента. Тесты — не довесок; они единственное, что стоит между «выглядит верно» и «оно верно».

    Критерии готовности
    • У каждой публичной операции есть хотя бы один тест, проверяющий её против проверяемого эталона в пределах выбранного допуска, а не на точное равенство.
    • Граничные случаи — вырожденная матрица, пустой вектор, несовпадающие размерности — протестированы и дают понятную ошибку или документированный результат вместо тихой бессмыслицы.

Стартер

  • README.md
  • src/numeric.ts
  • test/numeric.test.ts
Скачать стартер (.zip)

Распакуй, реализуй заглушки, затем гоняй тесты, пока не позеленеют: bun test

Рубрика

Джуниор Миддл Сеньор
Осведомлённость об ошибках плавающей точки Знает, что результаты с плавающей точкой неточны, и использует допуск в утверждениях вместо строгого равенства. Указывает конкретную операцию, вызывающую катастрофическую потерю значимости в наивной дисперсии (вычитание двух больших почти равных чисел), реализует двухпроходную формулу или формулу Уэлфорда для её устранения и доказывает исправление тестом на больших почти равных значениях, где наивная формула даёт отрицательную дисперсию. Умеет поместить ошибку наивной формулы дисперсии в контекст относительного эпсилон машины: для значений порядка M абсолютная ошибка вычитания ~M * epsilon, что делает формулу корректной только при дисперсии >> M^2 * epsilon. Умеет сформулировать, когда суммирование Кахана дополнительно уменьшит накопленную ошибку в скалярных произведениях или нарастающих суммах.
Выбор ведущего элемента и численная устойчивость Реализует метод Гаусса корректно для хорошо обусловленных квадратных систем без нулевых ведущих элементов; обратная подстановка даёт верный x. Добавляет частичный выбор ведущего элемента (перед каждым шагом исключения переставляет строку с наибольшим по модулю значением в текущем столбце) и строит тест, в котором наивная версия даёт NaN или грубо неверный результат, а версия с выбором ведущего элемента — точный. Объясняет, почему частичный выбор ведущего элемента ограничивает фактор роста до 2^(n-1) в худшем случае (что теоретически позволяет построить патологическую матрицу) и почему на практике это редко проблема; умеет описать число обусловленности как отношение максимального к минимальному сингулярному значению и сформулировать, что число обусловленности 1e12 означает достоверность лишь ~4 значащих цифр решения в double precision.
Обработка граничных случаев и покрытие тестами Тестирует счастливый путь: известная система 3x3 с чистым целочисленным решением, среднее небольших целых чисел, скалярное произведение коротких векторов. Покрывает канонические режимы отказа: вырожденная матрица (обнаружена и сообщена, а не тихо NaN), несовпадающие длины векторов (отклонены с понятной ошибкой), пустой ввод, вектор из одного элемента и плохо обусловленная почти вырожденная система, которая вскрывает потери плавающей точки. Выбирает допуски осознанно: эпсилон для проверки невязки (||Ax - b|| / ||b|| < 1e-10) отличается от эпсилон для проверки неотрицательности дисперсии; умеет объяснить, почему относительный допуск почти всегда верен, а абсолютный — почти всегда источник ошибок. Документирует, какие операции возвращают NaN, а какие бросают исключение, и почему контракт намеренный.
Эталонный разбор (спойлер)

Катастрофическая потеря значимости в наивной дисперсии: учебная формула E[X^2] - E[X]^2 вычитает два числа, близкие по величине, когда дисперсия мала относительно среднего. В IEEE 754 double precision значения порядка M имеют абсолютную ошибку ~M * 2^-52 (~2,2e-16). Если дисперсия меньше M^2 * epsilon, вычитание возвращает ноль или отрицательное число. Бегущая формула Уэлфорда полностью избегает этого, накапливая отклонения от текущего среднего.

Почему частичный выбор ведущего элемента: без него деление на малый (близкий к нулю) ведущий элемент усиливает ошибку округления на всех последующих шагах исключения. Частичный выбор всегда делит на наибольшее доступное число в столбце, ограничивая усиление. Режим отказа без выбора ведущего: система 2x2 [[0.0001, 1], [1, 1]] даёт грубо неверный ответ при наивном исключении, но верный ответ после перестановки строк.

Дисперсия генеральной совокупности против выборочной: деление на n даёт оценку максимального правдоподобия дисперсии генеральной совокупности (смещённую для малых выборок); деление на n-1 даёт поправку Бесселя — несмещённую оценку дисперсии по выборке. Для задач анализа данных при n > 30 разница пренебрежимо мала; для небольших статистических тестов (A/B при n=10) она важна, и выбор должен быть явным в API.

Проверка невязки как прокси корректности: после решения Ax = b вычисли r = Ax - b и проверь ||r|| / ||b|| < epsilon. Это не доказывает точность решения — плохо обусловленная матрица может иметь малую невязку при большой ошибке, — но ловит грубые баги (неверный порядок обратной подстановки, ошибка на единицу в исключении). Для продакшен-решателя проверка невязки плюс оценка числа обусловленности даёт полную картину.

Сделай по-сеньорски

  • Замени повторное исключение LU-разложением: разложи A один раз на нижне- и верхнетреугольную пару, а затем дёшево решай множество разных правых частей, переиспользуя множители, — стандартный приём, когда ты решаешь Ax = b для одной и той же A снова и снова.
  • Добавь осведомлённость о числе обусловленности: оцени, насколько твоя матрица усиливает входную ошибку, и сигнализируй, когда система плохо обусловлена, чтобы вызывающий узнал «этот ответ хрупок» вместо доверия к цифрам, которые суть чистый шум плавающей точки.
  • Прогони бенчмарк решателя по мере роста размера системы и подтверди, что время работы действительно масштабируется как n в кубе, превратив изученный класс сложности в кривую, которую ты измерил сам.

Навыки

representing vectors and matrices as dataimplementing matrix arithmetic correctlyGaussian elimination with partial pivotingcomputing mean, variance, and standard deviation numerically stablywriting tests against verifiable reference answersreasoning about floating-point error and conditioning

Рекомендуемый стек

typescript or pythona test runner (vitest / pytest)a reference implementation for cross-checking (numpy or a known closed-form)