Преди около седмица усъвършенствах статистическия модул от пакета Lathe, когато се натъкнах на нещо, което според мен е много интересно и нелепо „нещо“ в езика Julia. Julia е език за програмиране, добре известен със своята статистическа мощ и ефективност при решаване на сложни проблеми с машинно обучение за миг на око. Имайки предвид репутацията на Джулия, това прави проблема, с който се сблъсках, още по-интересен.

Биномиално разпределение

Биномиалното разпределение е страхотно разпределение за определяне на валидността на булево значение. Това разпределение е математически в основата на безброй алгоритми и може да бъде направено така, че да включва Inferential, както и Bayesian статистика, което го прави невероятно гъвкав. Не само това, биномното разпределение може да се приложи дори за по-нататъшно обучение с подобно на валидиране на точността, както и категориални модели.

Така че по очевидни причини, човек би искал биномно разпределение в своя арсенал за различни машинно обучение и статистически задачи. В конкретния случай подготвях тест за знаци. Тестът за знаци е готин малък инференциален тест, който включва изваждане на два масива с точка за откриване на статистическа значимост. Въпреки че не бих казал, че това е най-стабилният инференциален тест, с който разполагат статистиците, определено ми харесва компанията му.

Така че за моя тест за знаци написах малка функция, която изглежда нещо подобно:

function sign(var1,var2)
    sets = var1 .- var2
    positives = []
    negatives = []
    zeros = []
    for i in sets
        if i == 0
            zeros.append!(i)
        elseif i > 0
            append!(positives,i)
        elseif i < 0
            append!(negatives,i)
        end
    end
    totalpos = length(positives)
    totallen = length(var1) + length(var2)
    ans = binomialdist(totalpos,totallen)
    return(ans)
end

И няма нужда да се притеснявате, както винаги ще разбия функцията. Първият ред е това, което обсъдих по-горе; изваждане на точки на двата масива. Под него се наблюдават три празни масива. По мое мнение добавянето на масив е сред най-добрите начини за извличане на частни затъмнения от итеративен цикъл, така че доста често моите програми ще виждат празни масиви на върха на for циклите.

Що се отнася до for цикъла с условните изрази, разбира се целият тест се върти около това дали дадено число е положително или отрицателно. Имайки предвид това, можем да добавим заедно масиви въз основа на релевантността на стойностите до нула, отдолу са отрицателни, а отгоре са положителни. Друго нещо, което бих искал да спомена е, че в по-голямата си част не е необходим отрицателен списък и вероятно ще го откажа изцяло в даден момент, тъй като нулите и положителните са единствените изчисления, необходими за тест за знаци.

Проблемът в тази функция идва към дъното, където се натъкваме на функцията на биномно разпределение. Разбира се, тази функция не е от никой друг, а от мозъка ми, така че ще разгледаме това.

Биномиалното разпределение се изчислява с формулата по-горе, вероятността за X е равна на факториела на размера на извадката, разделен на факториела на положителните резултати... и т.н.

В първоначалния подход към писането на функция за извършване на тази математика се сетих за времето, когато написах функция за квадратен корен. Тъй като намерението на Lathe е да остане с нисък внос, прецених, че просто трябва да преоткрия колелото, за да избегна нуждата от много пакети. Това се оказа грешка, тъй като разбрах, след като написах функцията, че Джулия има своя собствена функция за квадратен корен, изпечена в основния език. Не от math import sqrt, без използване на math: sqrt, само sqrt(). Бях върнат обратно и всъщност бях доста развълнуван да науча, че такова нещо е било изпечено в Джулия.

Така че този път, вместо да преминавам през болката да напиша собствената си функция, за да правя проста математика като факториели или квадратни корени, реших първо да проверя дали Джулия има базова функция, за да го направи. И със сигурност,

Стана!

Факториална таблица???

Така че с това новооткрито знание за функцията факториел се заех да напиша моята функция на биномно разпределение и най-накрая сложих край на този тест за знаци.

function binomialprob(positives,size)
    # p = n! / x!(n-x!)*π^x*(1-π)^N-x
    n = size
    x = positives
    factn = factorial(n)
    factx = factorial(x)
    p = factn / factx * (n-factx) * π ^ x * (1-π)^n - x
    return(p)
end

Разбира се, разпределението не е необходимо, за да се получи P стойност от тест за знаци, така че действителният тест за разпределение се изключва от функцията и се връща само инференциалната вероятност. И добре, това работи. Тествах функцията си по начина, по който го правя обикновено, като дефинирах някои малки масиви, които можех да напиша за няколко секунди:

array1 = [1,4,3,5,4]
array2 = [5,6,8,4,5]

И след това ги хвърляме във функцията:

p = sign(array1,array2)

С връщане на:

P - 2.082207943758608e11

Та какъв е проблема?

Няколко месеца по-късно с неохота отидох отново да използвам теста за знаци, отчасти само за да го измъкна от депресивното и самотно съществуване и за моя изненада функцията сега имаше сериозен проблем.

Дължината на генералната съвкупност (n) беше твърде голяма, за да може да се обработи факторната функция на Юлия. Това е така, защото функцията факториел на Джулия всъщност използва таблица с факториели за функцията факториел.

Какво?!

Това би било еквивалентно на създаване на функция за добавяне на единица към число и вместо просто да добавим единица към споменатото число, ние потърсихме в списък с числа каква е сумата на това число и единица и върнахме това! И така, къде се ограничава факторната функция и колко лесно е това да се заобиколи?

След някои бързи тестове факторната функция работи само в диапазона 1–20. Разбира се, няма съмнение, че можете да правите много неща с факторен диапазон от 1–20, но за статистиката, като се има предвид, че Julia е статистически език, това е почти безполезно, тъй като тестването на размерите на популацията не може да бъде валидно с размерите са ограничени до двадесет. Това е особено проблематично, когато се прилага към нещо като тест за знаци, където всяко число има пряк ефект върху върнатата P стойност.

Изчисляване

С тези нови открития реших да погледна по-задълбочено и да проуча как Scipy се справя с този проблем, все още не желая да се справя със собствената си функция за решаване на този проблем, тъй като колкото повече мислите за решението, толкова по-сложно става то.

Факториалната функция на Scipy се съдържа в scipy.special и отнема толкова много копаене, колкото бихте очаквали да намерите...

Имайки това предвид, бързо реших да скоча в тетрадка на Google Colab, за да видя дали функцията на Scipy е ограничена като тази на Джулия.

не!

Заключение

Разбирам, че има много неща, които се влагат в разработването на език и много неща се пренебрегват в такъв мек процес. Факториалите са проблясък в очите на Джулия, сигурен съм, че ролята им в статистиката не е достатъчно адекватна, за да оправдае огромното време, прекарано в писането им на езика на Джулия, и това е добре.

Обаче, като се има предвид, че факториелът всъщност е функция в основата Джулия, която се оказва функцията, изпълнена наполовина, никакви правилни математически функции на факториел, които искат да наложат името factorial(), не могат. Разбира се, това не би било проблем, ако обикновената функция не беше проблем.

Не мога да похваля достатъчно Джулия за това колко красиво и освежаващо е според мен. Julia всъщност е любимият ми език за програмиране и ми харесва да работя на него много повече от други езици. Освен това мисля, че обратната връзка е абсолютно жизненоважна за развитието на език като Julia. И това е една от различните ми критики към Джулия. За да бъда ясен: обичам Джулия до смърт и същите критики могат да бъдат отправени от мен за всеки език.