6.17. Солвери та випадкові числа

Коли функція, що вивчається, визначена кодом Python, а не буфером зразків, підходить інше сімейство інструментів: де корінь функції, її мінімум, її інтеграл на заданому інтервалі? Підмодулі scipy.integrate та scipy.optimize охоплюють цю роботу. Кожен алгоритм виконує зворотний виклик до наданої користувачем функції Python, тому вартість кожної ітерації вища, ніж у скороченні буфера; зручність полягає в тому, що не потрібно писати солвер самостійно.

Підмодуль scipy.special охоплює статистичні спеціальні функції (функцію помилок, гамму), що виникають при обчисленні функції розподілу (CDF, імовірність того, що зразок не перевищує задане значення) або функції щільності ймовірності (PDF, відносна ймовірність при заданому значенні). numpy.random охоплює псевдовипадковий генератор для дизеринга, симуляції та синтетичних тестових даних.

6.17.1. Числове інтегрування виклику

Коли підінтегральна функція є функцією Python, а не буфером зразків, scipy.integrate надає чотири алгоритми квадратури:

  • quad() – адаптивний метод Гауса-Кронрода. Правильний вибір за замовчуванням для гладких підінтегральних функцій. Повертає (value, error).

  • romberg() – класичний метод Ромберга / Ньютона-Котеса. Повертає одне число з рухомою крапкою. Застарілий у вищому проекті; включений для сумісності.

  • simpson() – адаптивне правило Сімпсона. Повертає одне число з рухомою крапкою.

  • tanhsinh() – квадратура подвійної експоненти. Використовуйте, коли підінтегральна функція має особливості на кінцях або нескінченну межу. Повертає (value, error).

Інтеграл Гауса, обчислений за правилом подвійної експоненти (tanhsinh):

from math import exp
from math import pi
from math import sqrt
from ulab import numpy as np
from ulab import scipy as sp

f = lambda x: exp(-x * x)
value, err = sp.integrate.tanhsinh(f, -np.inf, np.inf)
print("approx:", value, "   exact:", sqrt(pi))

Вивід:

approx: 1.7724538...   exact: 1.7724538...

6.17.2. Пошук кореня та мінімізація

scipy.optimize охоплює три класичних солвери для однієї змінної. Кожна ітерація виконує зворотний виклик до наданої користувачем функції Python, тому прискорення порівняно з чистим Python-солвером скромне (приблизно 2x); зручність – у тому, що не потрібно писати солвер самостійно.

  • bisect() – пошук кореня f на [a, b] методом половинного ділення. f(a) та f(b) мають мати протилежні знаки:

    def f(x):
        return x * x - 1
    
    sp.optimize.bisect(f, 0, 4)        # ~1.0
    
  • newton() – пошук кореня методом хорди / Ньютона-Рафсона:

    def f(x):
        return x * x * x - 2.0
    
    sp.optimize.newton(f, 3., tol=0.001, rtol=0.01)
    # ~1.260
    
  • fmin() – пошук локального мінімуму методом низхідного симплексу (Нелдера-Міда):

    def f(x):
        return (x - 1) ** 2 - 1
    
    sp.optimize.fmin(f, 3.0)           # ~1.0
    

Область однієї змінної достатня для більшості оптимізацій на стороні камери – константа калібрування датчика, підсилення, що максимізує вимірювання контрасту, поріг, де двомодальність гістограми найбільш виражена. Для задач з кількома змінними правильним рішенням зазвичай є переформулювання задачі як невеликого лінійно-алгебраїчного розв’язку, а не використання загального нелінійного оптимізатора.

6.17.3. Спеціальні функції

scipy.special надає кілька статистичних та ймовірнісних функцій, що поводяться як універсальні функції – вони приймають скаляр, ітерований об’єкт або ndarray і повертають ndarray з рухомою крапкою:

x = np.linspace(0, 4, num=8)

sp.special.erf(x)         # error function
sp.special.erfc(x)        # complementary error function
sp.special.gamma(x + 1)   # gamma function
sp.special.gammaln(x + 1) # log-gamma function

Функція помилок та її доповнення з’являються в CDF гауссівського розподілу – основне застосування для перетворення між виміряним z-балом та ймовірністю або обчислення хвостового інтеграла нормального розподілу. Гамма та логарифмічна гамма-функції зустрічаються в обчисленнях бета/хі-квадрат/t-розподілу; gammaln є чисельно стійкою формою для великих аргументів, де gamma сама по собі переповнюється.

6.17.4. Випадкові числа

numpy.random надає клас Generator, який генерує зразки з поширених розподілів. Генератор є статeful: кожен виклик просуває його внутрішній стан, тому послідовні виклики повертають незалежні зразки:

from ulab import numpy as np

rng = np.random.Generator(seed=42)

rng.random(size=5)             # 5 uniform [0.0, 1.0) samples
rng.uniform(low=-1.0, high=1.0, size=10)
rng.normal(loc=0.0, scale=1.0, size=(2, 4))

Тип вихідних даних завжди float. size= приймає ціле число (одновимірний вивід) або кортеж (n-вимірний вивід); якщо опущено, повертається одне число Python з рухомою крапкою.

Генератор підходить для симуляції, дизеринга, синтетичних тестових даних та будь-яких інших застосувань, де не вимагається криптографічна стійкість. Він не придатний для ключів або токенів; для цього використовуйте системне джерело випадкових чисел через os.

6.17.5. Доступність на час збірки

Наявність кожного підмодуля залежить від того, як була зібрана камера. scipy.optimize та scipy.special не ввімкнені на кожній камері; виклик функції, якої немає на камері, викликає AttributeError. dir(sp), dir(sp.optimize), dir(np.random) та подібні функції повідомляють, що доступно на цільовій камері.