Как реализовать функцию плотности вероятности распределения Гаусса

Мне нужно реализовать класс в Python, который представляет одномерное (на данный момент) нормальное распределение. Я имею в виду следующее

class Norm():
    def __init__(self, mu=0, sigma_sq=1):
        self.mu = mu
        self.sigma_sq = sigma_sq
        # some initialization if necessary

    def sample(self):
        # generate a sample, where the probability of the value 
        # of the sample being generated is distributed according
        # a normal distribution with a particular mean and variance
        pass

N = Norm()
N.sample()

Сгенерированные выборки должны быть распределены в соответствии со следующей функцией плотности вероятности

функция плотности вероятности для нормального распределения

Я знаю, что scipy.stats и Numpy предоставляют для этого функции, но мне нужно понять, как эти функции реализованы. Любая помощь будет оценена, спасибо :)


person ironstein    schedule 31.08.2017    source источник
comment
Что не так с обзором вики?. Это хорошо настроенные методы именно для этого распределения. (И numpy + scipy с открытым исходным кодом, вы тоже можете посмотреть код)   -  person sascha    schedule 01.09.2017


Ответы (2)


Используя метод Бокса-Мюллера:

def sample(self):
  x = np.random.uniform(0,1,[2])
  z = np.sqrt(-2*np.log(x[0]))*np.cos(2*np.pi*x[1])
  return z * self.sigma_sq + self.mu
person prometeu    schedule 01.09.2017
comment
Это немного забавно: однажды я почувствовал, что я единственный, кто хочет дать его назовите умляут. Но знайте, я думаю, что каждый немец делает. Тем не менее, кажется, что его действительно зовут Мюллер. (привет от КА) - person sascha; 01.09.2017
comment
:p проверьте это - Англизирование имен - person prometeu; 02.09.2017

В итоге я воспользовался советом @sascha. Я просмотрел как эту статью в Википедии, так и источник Numpy и нашел это randomkit.c, реализующий функции rk_gauss (которые реализуют преобразование Бокса-Мюллера), rk_double и rk_random (который реализует генератор случайных чисел Mersenne Twister, который имитирует равномерно распределенную случайную величину, требуемую преобразованием Бокса-Мюллера).

Затем я адаптировал генератор Mersenne Twister из здесь и применил преобразование Box Muller для имитации гауссова (более подробная информация о генераторе Random Twister Generator здесь).

Вот код, который я закончил писать:

import numpy as np
from datetime import datetime
import matplotlib.pyplot as plt

class Distribution():
    def __init__(self):
        pass

    def plot(self, number_of_samples=100000):
        # the histogram of the data
        n, bins, patches = plt.hist([self.sample() for i in range(number_of_samples)], 100, normed=1, facecolor='g', alpha=0.75)
        plt.show()

    def sample(self):
        # dummy sample function (to be overridden)
        return 1

class Uniform_distribution(Distribution):
    # Create a length 624 list to store the state of the generator
    MT = [0 for i in xrange(624)]
    index = 0

    # To get last 32 bits
    bitmask_1 = (2 ** 32) - 1

    # To get 32. bit
    bitmask_2 = 2 ** 31

    # To get last 31 bits
    bitmask_3 = (2 ** 31) - 1

    def __init__(self, seed):
        self.initialize_generator(seed)

    def initialize_generator(self, seed):
        "Initialize the generator from a seed"
        global MT
        global bitmask_1
        MT[0] = seed
        for i in xrange(1,624):
            MT[i] = ((1812433253 * MT[i-1]) ^ ((MT[i-1] >> 30) + i)) & bitmask_1

    def generate_numbers(self):
        "Generate an array of 624 untempered numbers"
        global MT
        for i in xrange(624):
            y = (MT[i] & bitmask_2) + (MT[(i + 1 ) % 624] & bitmask_3)
            MT[i] = MT[(i + 397) % 624] ^ (y >> 1)
            if y % 2 != 0:
                MT[i] ^= 2567483615

    def sample(self):
        """
        Extract a tempered pseudorandom number based on the index-th value,
        calling generate_numbers() every 624 numbers
        """
        global index
        global MT
        if index == 0:
            self.generate_numbers()
        y = MT[index]
        y ^= y >> 11
        y ^= (y << 7) & 2636928640
        y ^= (y << 15) & 4022730752
        y ^= y >> 18

        index = (index + 1) % 624
        # divide by 4294967296, which is the largest 32 bit number 
        # to normalize the output value to the range [0,1]
        return y*1.0/4294967296

class Norm(Distribution):
    def __init__(self, mu=0, sigma_sq=1):
        self.mu = mu
        self.sigma_sq = sigma_sq
        self.uniform_distribution_1 = Uniform_distribution(datetime.now().microsecond)
        self.uniform_distribution_2 = Uniform_distribution(datetime.now().microsecond)
        # some initialization if necessary

    def sample(self):
        # generate a sample, where the value of the sample being generated 
        # is distributed according a normal distribution with a particular 
        # mean and variance
        u = self.uniform_distribution_1.sample()
        v = self.uniform_distribution_2.sample()
        return ((self.sigma_sq**0.5)*((-2*np.log(u))**0.5)*np.cos(2*np.pi*v)) + self.mu

Это работает отлично и генерирует довольно хорошую гауссову

Norm().plot(10000)

Смоделированное распределение Гаусса

person ironstein    schedule 01.09.2017