Язык программирования D и когнитивная рациональность

Небольшой эксперимент в области принятия решений и рациональности, в котором используются идеи из теоремы Байеса (служит для переоценки некоторых предположений или вариантов после того, как некоторое событие уже произошло) и Теории Ожидаемой Полезности (используется понятие функции полезности, т.е utility function — суммы ценностей некоторых вариантов выбора, умноженных на вероятности этих вариантов).

Удобнее всего это рассмотреть на примере, для чего воспользуемся небольшой программой, написанной на D (исходный код тут — http://code.re/YRbqNJLr):

import
   std.conv,
   std.csv,
   std.file,
   std.getopt,
   std.stdio;

class EUHypothesys
{

private:

    string[] variant;
    float[] beforeCost,beforeProb,
          beforeUtil,afterUtil;

public:

    this(string[] variant,float[] beforeCost,float[] beforeProb)
    {
        this.variant = variant;
        this.beforeCost = beforeCost;
        this.beforeProb = beforeProb;
        for (size_t i = 0; i < this.variant.length; i++)
        {
            this.beforeUtil ~= this.beforeCost[i] * this.beforeProb[i];
        }
    }

    float before()
    {
        float utilityFunc = 0;
        foreach (elem; this.beforeUtil)
        {
            utilityFunc += elem;
        }
        return utilityFunc;
    }

    float[] apriori()
    {
        return this.beforeUtil;
    }

    float[] after()
    {
        float utilityFunc = this.before();
        foreach (elem; this.beforeUtil)
        {
            this.afterUtil ~= elem / utilityFunc;
        }
        return this.afterUtil;
    }

    string[] select()
    {
        string[] choice;
        size_t num = 0;
        this.afterUtil = this.after();
        alias utilf = this.afterUtil;

        for (size_t i = 1; i < utilf.length; i++)         {             if (utilf[i] > utilf[num]) num = i;
        }

        choice ~= this.variant[num];
        choice ~= to!string(utilf[num]);
        return choice;
    }

}

class processFile
{

private:
    string[] variant;
    float[] beforeCost,beforeProb;

public:

    this(string filename)
    {
        struct alternative
        {
            string variant;
            float cost;
            float prob;
        }
        if (exists(filename))
        {
            File file;
            file.open(filename,"r");
            while (!file.eof)
            {
                foreach (s; file.byLine())
                {
                    auto rec = csvReader!alternative(s);
                    foreach (tmp; rec)
                    {
                        variant ~= tmp.variant;
                        beforeCost ~= tmp.cost;
                        beforeProb ~= tmp.prob;
                    }
                }
            }
            file.close();
        }
        else
        {
            writeln("no file found");
            // throw new Exception("pizda !");
        }
    }

    void parseOpt(string mode)
    {
        EUHypothesys result = new EUHypothesys(this.variant,this.beforeCost,beforeProb);
        alias var = this.variant;
        switch (mode)
        {
        case "":
            mode = "apriori";

        case "apriori":
            writeln("Variant","\t","Utility");
            for (size_t i = 0; i < var.length; i++)
            {
                writeln(var[i],"\t",result.apriori[i]);
            }
            writeln("\tUtility function: ",result.before());
            break;

        case "aposteriori":
            writeln("Variant","\t","Probablity");
            for (size_t i = 0; i < var.length; i++)
            {
                writeln(var[i],"\t",result.after[i]);
            }
            break;

        case "select":
            auto tmp = result.select();
            writeln(tmp[0]);
            writeln("\tProbablity: " ~ tmp[1]);
            break;

        default:
            writeln("wrong processing mode");
            break;
        }
    }
}


string filename;
string mode;
void main(string[] args)
{
    getopt(args,std.getopt.config.passThrough,"input-file|f",&filename,"mode",&mode);
    processFile pf = new processFile(filename);
    pf.parseOpt(mode);
}

Для того, чтобы посмотреть как работает эта небольшая консольная программка, рассмотрим такую задачу:

   На заводе есть три рабочих. У первого рабочего вероятность того, что получившаяся деталь бракованная — 0.9, у второго — 0.5, а у третьего — 0.2.
Первый рабочий изготовил за смену 800 деталей, второй — 600 деталей, а третий — 900.
   Контроллер качества берет случайным образом выбранную деталь из всей партии деталей, изготовленных тремя рабочими, и она оказывается бракованная.
   Насколько вероятно предположение, что деталь была изготовлена третьим рабочим ?

Подготовим данные для работы программы и положим их в файл test.csv со следующим содержимым:

Первый рабочий,800,0.9
Второй рабочий,600,0.5
Третий рабочий,900,0.2

Теперь компилируем программу и запускаем из командной строки:

experiment -f test.csv —mode=apriori

Этой командой мы сможем узнать сколько дефектных деталей было сделано каждым из рабочих, а также общее количество дефектных деталей.

Получаем примерно вот такой вывод:

Variant Utility
Первый рабочий 720
Второй рабочий 300
Третий рабочий 180
Utility function: 1200

Небольшой комментарий: здесь роль utility function (функции полезности) служит общее количество дефектных деталей. Понятие «функции полезности» здесь имеет несколько иной смысл, хотя формула для расчета осталась прежней.

После этого мы можем узнать, какова вероятность того, что взятая контроллером дефектная деталь была сделана каждым из рабочих, для чего необходимо программу запустить вот с такими аргументами:

experiment -f test.csv —mode=aposteriori

Получаем вот такой вывод:

Variant Probablity
Первый рабочий 0.6
Второй рабочий 0.25
Третий рабочий 0.15

В принципе, мы уже ответили на вопрос, насколько вероятно то, что бракованная деталь взятая наугад была сделана третьим рабочим — и эта вероятность равна 15 %.

Ради интереса, попробуем заставить программу предположить вероятного виновника брака: запустим программу с ключом

—mode=select

и увидим вот это:

Первый рабочий
Probablity: 0.6

Таким образом с помощью одной этой программы можно сделать несколько интересных вещей: (перечисляю список возможностей, возможно, не всех):

а) посчитать насколько полезным будет некоторый выбор
б) выбрать максимально полезный вариант
в) пересчитать вероятность некоторых предположений, после того, как интересующее событие произошло
г) выбрать максимально правдоподобную гипотезу
д) посчитать правдоподобность некоторых убеждений

и многое другое.

Пара слов о том, как это работает.

Функция полезности отдельных вариантов расчитывается так: u(X) = c(X) * p(X), где u(X) — полезность варианта Х, с(Х) — ценность/стоимость варианта (как в условных очках, так и в других единицах), р(Х) — вероятность реализации варианта Х.

Общая функция полезности (которую принято называть просто функцией полезности) рассчитывается как сумма функций полезности отдельных вариантов.

Именно функции полезности вариантов и общую функцию полезности вы видите, когда запускаете программу с ключом

—mode=apriori

Далее самое интересное — переоценка вероятностей.

Она происходит на основе общей функции полезности и фактически показывает вклад каждого варианта выбора в общую копилку полезности.

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

Кроме того, описанный алгоритм (он описан в терминах теории принятия решений) по сути дела является переоценкой вероятностей некоторых предположений по Байесу (т.е. эквивалентен теореме Байеса).

 

Преподобный Байес говорит:

Теперь вы посвящены в Байесовский Заговор.

 

Ссылки для подробного разбора:

P.S: Рекомендую заинтересованным набрать в поисковике «Интуитивное объяснение теоремы Байеса», а также заглянуть на сайт lesswrong.ru

Добавить комментарий