...

суббота, 6 августа 2016 г.

MirrorMoon EP — в поисках заветной планеты

MirrorMoon EP
Осторожно, спойлеры и артхаус! Если вы в тупике и отчаянии, то эта статья должна помочь вам пройти игру. Плюс, технические подробности.


Предисловие


Это была самая странная, долгая и познавательная охота за ачивкой. Был изучен код игры, воссоздан API и реализован звездный навигатор для этой бесконечной космической одиссеи. Весь путь я проклинал разработчиков. Ведь, по сути, игра длится всего 10 минут. А дальше, дальше только пустота и надежда. Но как я рад теперь! Благодаря разработчикам и моему стремлению я многое узнал. Далее само прохождение.



Обсерватория


Перейдя на сторону B, мы начинаем путешествие. Поначалу, кажется, оно не имеет цели, но найдя первую обсерваторию, мы видим кружок. Это наша цель. В обсерватории мы наблюдаем созвездия. Выбрав ближайшие звезды, начинаем на них охоту. Если эти звезды уже открыты, то мы можем запускать навигатор и вычислять цель.


MirrorMoon EP Constellation


Звезды


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


Финал


Выбираем сезон. Помечаем все нужные звезды. Находясь на планете с обсерваторией, включаем режим OBS. Просчитываем расположение звезд, перекрестие должно указывать на загадочную планету. Выходим из режима OBS. Включаем ортогональную проекцию. Теперь мы видим карту как в игре. Идем в игру и подгоняем вид. Где-то в центре мы найдем далекую звезду и прыгнем туда, в надежде..


MirrorMoon EP Observer


Реализация


MirrorMoon EP создан на Unity 3D. Поэтому было не сложно декомпилировать код. Алгоритм следующий. Открываем урл — http://ift.tt/2aJhkU3. Нас интересует последняя строчка. В ней находится кол-во сезонов. Это число используется для генерации хэша. Также нам потребуется секретный ключ thankyouforhackingmysecretkey1234568. О нет, нас раскусили :)


string getHash(string text) {
    MD5 md5Hash = MD5.Create ();
    byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(text));
    StringBuilder sBuilder = new StringBuilder();
    for (int i = 0; i < data.Length; i++)
        sBuilder.Append(data[i].ToString("x2"));
    return sBuilder.ToString();
}

string availableSeasons = urlopen("http://ift.tt/2aJhkU3").Split('\n')[6];
string hash = getHash (availableSeasons + secretKey);

Теперь мы можем получить список сезонов: 1_000022,2_000077,3_000100,4_000099… Пары из номера и сида, разделенные запятыми.


string baseUrl = "http://ift.tt/2aJgtmg";
string url = string.Format ("seasons.php?Seasons={0}&hash={1}", availableSeasons, hash);
string data = Utils.urlopen (baseUrl + url);

Похожим образом получаем звезды в выбранном сезоне:


string hash = Utils.getHash (seed + secretKey);
string url = string.Format ("full_star.php?Seed={0}&hash={1}", seed, hash);
string data = Utils.urlopen(baseUrl + url);
foreach (string pair in data.Split(',')) {
    string name = pair.Substring(16, 6).Trim();
    string pos = pair.Substring(22);
    float x = Convert.ToSingle(pos.Substring(0, 3));
    float y = Convert.ToSingle(pos.Substring(3, 3));
    float z = Convert.ToSingle(pos.Substring(6));

    GameObject star = Instantiate(starPrefab) as GameObject;
    star.GetComponent<Star>().title = name;
    star.transform.position = new Vector3(x, y, z);
    stars.Add(star);
}

Это пары из координат и название звезды — 000022349205629_LIAMD 349205629,000022490453661_PAX4 490453661,000022570586427_RHUMB 570586427...


Пожалуй, это всё… или нет? За кулисами остался алгоритм добавления звезд. Это я оставлю истинным ценителям цифрового творчества. Не хулиганьте.


P.S.: Прохождение актуально только для онлайн режима. Навигатор можно скачать здесь, исходники там же.


P.P.S.: Описанные события произошли в марте 2014 года. Больше двух лет пост лежал в черновиках, потому что не было технической части в рассказе. Теперь это исправлено.

Комментарии (0)

    Let's block ads! (Why?)

    Аппроксимация числа Пи с помощью множества Мандельброта

    [Из песочницы] Датчик абсолютного давления BMP180

    [Из песочницы] Заставляем FFMPEG менять HLS потоки в зависимостри от текущей пропускной способности

    Привет, жители Хабра. Сегодня хочу рассказать историю о том, как пришлось нырять в глубины ffmpeg без подготовки. Эта статья будет руководством для тех, кому нужна возможность корректной работы FFMPEG c HLS стримами (а именно — смена потоков в зависимостри от текущей пропускной способности сети).

    Начнем немного с предыстории. Не так давно у нас появился проект, android tv, в котором одна из фич была воспроизведение сразу несколько видео одновременно, то есть юзер смотрит на экран и видит 4 видео. Потом выбирает одно из них и смотрит его уже в фул скрине. Задача ясна, осталось только сделать. Особенность в том, что видео приходит в формате HLS. Я думаю, что если вы читаете это, то уже знакомы с HLS, но все же вкратце — нам дается файл, в котом есть ссылки на несколько потоков, которые должны меняться в зависимости от текущей скорости интернета.
    Пример:
    #EXTM3U
    #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=688301
    http://ift.tt/2b3mBYi
    #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=165135
    http://ift.tt/2b1iaRh
    #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=262346
    http://ift.tt/2b3lUy9
    #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=481677
    http://ift.tt/2b1iKyt
    #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1308077
    http://ift.tt/2b3mf42
    #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1927853
    http://ift.tt/2b1ikrV
    #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=2650941
    http://ift.tt/2b3mBaP
    #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=3477293
    http://ift.tt/2b1iuPL
    
    


    Первым же делом мы начали реализывать даную фичу черер EXOPlayer. Что было достаточно логично, так как EXOPlayer использует аппаратные кодеки для воспроизведения видео потока. Но оказалось, что у EXO есть своя темная сторона. Когда с помощью EXO запускается больше, чем один поток, никто не знает, что произойдет. В нашем случае, когда мы запускали 4 потока, на некоторых девайсах все работало хорошо, на некоторых работало только 3, а четвертый не запускался, а на некоторых, например, на Nexus 7 2013 происходило кое-что другое. Когда Nexus 72013 запускал больше 1 потока, аппаратные кодеки просто падали и ни одно видео нb работало, не только в нашем приложение, а и в других приложениях, которые используют аппаратные кодеки. Единственный способ поднять их — это перезагрузить девайс. Как оказалось, этой задаче была посвящена тема на гитхабе. Как стало ясно, использовать аппаратные кодеки мы не можем, значит, нужно использовать программные кодеки и я напомню, что основная задача была играть 4 видео одновременно.

    И начался велики пойиск и искали мы долго и пробовали мы многое, но единственное, что нас устроило, было IJKPlayer. Это плеер, который является оберткой ffmep. Он воспроизводил HLS, играл их в 4 потока, а так же воспроизводил другие потоки, которые EXOplayer играл не на всех девайсах (например, HEVC). И очень долго все было хорошо, пока мы не начали замечать, что плеер всегда играет один и тот же поток и не меняет его в зависимости от пропускной способности сети. Для маленьких видео привью это не было проблемой, а вот для фул скрина это была проблема.

    Поискав, оказалось, что потоки не меняются, а сам хозяин IJKPplayer посоветовал парсить потоки отдельно от плеера и запускать именно тот, что нужен (так же тикет с ffmpeg). Естественно, это не подходило потому что плеер должен сам подстраиваться относительно интернета. Проблема проблемой, а решать ее надо. В интернете ничего не получилось найти так, что было принято решение самолично добавить в либу логику по смене потоков. Но перед, тем как что-то делать, надо понять, где это делать. Сам FFMPEG является очень большой либой и не так просто понять, что есть что, но я выделил для вас несколько основных мест, с которыми нам нужно будет работать.

    Итак, основные моменты, которые нам нужно знать:

    • Есть метод read_data, который находится в libavformat/hls.c, здесь происходит основная магия. Здесь мы скачиваем поток и кладем его в буффер. А в конце метода есть goto restart, где и происходит смена сигмента. Перед этим рестартом мы и будем заменять поток, если это будет нам нужно.
    • Второй объект, который нас интересует — это libavformat/avio.c. Здесь есть метод ffurl_close, который вызывается когда ссылка закрывается, а значит, здесь мы будем подытоживать текущую пропускную способность. А так же метод ffurl_open, который, конечно же, открывает наш поток, а значит здесь мы будем обнулять счетчик загруженных данных, а так же перезапускать таймер.
    • Так же будет не плохо обратить ваше внимание на методы new_variant и new_playlist — в них создается плейлист со всех возможных битрейтов. По моим наблюдениям плеер берет первый айтем из списка и играет его, если произошла какая-то ошибка, то он берет второй айтем. Если вам необходимо сделать так, что бы игрался только самый маленький (что логично, если воспроизводить 4 потока одновременно) или самый большой поток, то обратите внимание на эти методы.

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

    Листинг:
    bitrate_manager.h
    #include <stdint.h>
    #ifndef IJKPLAYER_TEST_H
    #define IJKPLAYER_TEST_H
    
    extern int64_t start_loading;
    extern int64_t end_loading ;
    extern int64_t loaded_bytes;
    extern int64_t currentBitrate;
    extern int64_t diff;
    
    //массив ссылков
    extern char** urls;
    //массив пропускных способнойстей, соответствующий массиву ссылок выше
    extern int64_t* bandwidth;
    extern int n_arrays_items;
    extern char* selected_url;
    extern int current_url_index;
    extern int64_t current_bandwidth;
    
    void saveStartLoadingData();
    
    int64_t getStartLoading();
    
    //проверяем инициализирован ли менеджер
    int isInited();
    
    //добавляем к счетчику скачаных байтов, количество скачаных байт за один раз
    void addToLoadingByte(int64_t bytesCount);
    
    //конец загрузки данного сегмента, считаем время затраченое на текущую операцию загрузки сегмента
    void endOfLoading();
    
    //высчитываем текущий битрейт
    void calculateAndSaveCurrentBitrate();
    
    int64_t getDiff();
    
    int64_t getLoadedBites();
    
    int64_t getEndLoading();
    
    int64_t getCurrentBitrate();
    
    void setFullUrl(char* url);
    void setParturlParts();
    
    //Есть ли у нас вообще битрейты  
    int doWeHaveBadwidth();
    //создаем массив ссылок
    void createDataArrays(int n_items);
    
    //заполняем массив ссылок
    void addData(int i, char* url, int64_t band_width);
    
    //освобождаем память
    void freeData();
    
    //возвращаем текущую выбранную ссылку
    char* getCurrentUrl();
    
    //сравниваем ссылку с текущей выбранной ссылкой
    int compareUrl(char* url);
    
    //находим поток подходящий под тукущую пропускную способность
    void findBestSolutionForCurrentBandwidth();
    
    char* getUrlString(int index);
    
    #endif //IJKPLAYER_TEST_H
    
    

    bitrate_manager.c
    #include "test.h"
    #include <time.h>
    #include <stdint.h>
    #include <string.h>
    #include "libavutil/log.h"
    
    static const int64_t ONE_SECOND= 1000000000LL;
    
    int64_t start_loading;
    int64_t end_loading ;
    int64_t loaded_bytes;
    int64_t currentBitrate;
    int64_t diff;
    
    char** urls;
    int64_t* bandwidth;
    int n_arrays_items;
    char* selected_url;
    int current_url_index;
    int64_t current_bandwidth;
    
    /*
     * It conyains current last index + 1
     */
    int pointerAfterLastItem;
    
    int isInitedData = 0;
    
    int64_t now_ms() {
        struct timespec now;
        clock_gettime(CLOCK_MONOTONIC, &now);
        return (int64_t) now.tv_sec*1000000000LL + now.tv_nsec;
    }
    
    void saveStartLoadingData(){
        loaded_bytes = 0LL;
        start_loading =  now_ms();
    }
    
    int64_t getStartLoading(){
        return start_loading;
    }
    
    int isInited(){
        return isInitedData;
    }
    
    void addToLoadingByte(int64_t bytesCount){
        loaded_bytes += bytesCount;
    }
    
    void endOfLoading(){
        end_loading = now_ms();
        diff = end_loading - start_loading;
    }
    
    void calculateAndSaveCurrentBitrate(){
        if(loaded_bytes != 0) {
            currentBitrate = loaded_bytes * ONE_SECOND / diff;
        }
        loaded_bytes = 0;
    }
    
    int64_t getDiff(){
        return diff;
    }
    int64_t getLoadedBites(){
        return loaded_bytes;
    }
    int64_t getEndLoading(){
        return end_loading;
    }
    int64_t getCurrentBitrate(){
        return currentBitrate;
    }
    
    int doWeHaveBadwidth(){
        if(bandwidth && pointerAfterLastItem != 0){
            return 1;
        }
        return 0;
    }
    void createDataArrays(int n_items){
        isInitedData = 1;
        pointerAfterLastItem = 0;
        n_arrays_items = n_items;
        bandwidth = (int64_t*) malloc(n_items * sizeof(int64_t));
        urls = (char**) malloc(n_items * sizeof(char*));
        for(int i =0; i < n_items; i++){
            urls[i] = (char*) malloc(sizeof(char));
        }
    }
    
    void addData(int i, char* url, int64_t band_width){
        if(band_width == 0LL){
            return;
        }
        free(urls[i]);
        urls[i] = (char*) malloc(strlen(url) * sizeof(char));
        strcpy(urls[pointerAfterLastItem], url);
        bandwidth[pointerAfterLastItem] = band_width;
        pointerAfterLastItem++;
    }
    
    void freeData(){
        if(isInitedData == 0){
            return;
        }
        isInitedData = 0;
        for(int i = 0;i < pointerAfterLastItem;++i) free(urls[i]);
        free(urls);
        free(bandwidth);
    }
    
    char* getCurrentUrl(){
        return selected_url;
    }
    
    int compareUrl(char* url){
        if(selected_url){
            int n_selected_url = strlen(selected_url);
            int n_url = strlen(url);
            if(n_selected_url != n_url)
                return 0;
    
            int index = 0;
            while(index < n_selected_url){
                if(selected_url[index] != url[index]){
                    return 0;
                }
                index++;
            }
        }
        return 1;
    }
    
    void findBestSolutionForCurrentBandwidth() {
        if (currentBitrate == 0) {
            selected_url = urls[0];
            current_url_index = 0;
            current_bandwidth = bandwidth[0];
            return;
        }
        if (currentBitrate == current_bandwidth) return;
    
        int index = 0;
        int64_t selectedBitrate = bandwidth[index];
        int start = 0;
        int length = pointerAfterLastItem;
        for (int i = start; i < length; i++) {
           if (currentBitrate >= bandwidth[i]
                && selectedBitrate <= bandwidth[i]) {
                index = i;
                selectedBitrate = bandwidth[i];
            }
        }
        if (current_bandwidth != selectedBitrate) {
            selected_url = urls[index];
            current_url_index = index;
            current_bandwidth = selectedBitrate;
        }
    }
    
    

    Теперь переходим к листингу самого ffmpeg
    В avio.c добавляем

    int ffurl_open(URLContext **puc, const char *filename, int flags,
                   const AVIOInterruptCB *int_cb, AVDictionary **options)
    {
        if(isInited() == 1) {
            saveStartLoadingData();
        }
     ….
    }
    ….
    
    int ffurl_close(URLContext *h)
    {
        if( isInited() == 1) {
            endOfLoading();
            calculateAndSaveCurrentBitrate();
        }
        return ffurl_closep(&h);
    }
    
    
    

    В hls.c метод read_data будет выглядеть так
    static int read_data(void *opaque, uint8_t *buf, int buf_size)
    {
        struct playlist *v = opaque;
        HLSContext *c = v->parent->priv_data;
    
    // инициализируем плейлист
        if (isInited() == 0) {
            createDataArrays(c->n_variants);
            for (int i = 0; i < c->n_variants; i++) {
                 addData(i, c->playlists[i]->url, c->variants[i]->bandwidth);
            }
        }
    //при необходимости, подменяем ссылки
        if(doWeHaveBadwidth() == 1 && isInited() == 1 && compareUrl(v->url) == 0){
            strcpy(v->url, getCurrentUrl());
        }
        
        int ret, i;
        int just_opened = 0;
    
    restart:
        if (!v->needed)
            return AVERROR_EOF;
    
        if (!v->input) {
            int64_t reload_interval;
    
            /* Check that the playlist is still needed before opening a new
             * segment. */
            if (v->ctx && v->ctx->nb_streams &&
                v->parent->nb_streams >= v->stream_offset + v->ctx->nb_streams) {
                v->needed = 0;
                for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams;
                    i++) {
                    if (v->parent->streams[i]->discard < AVDISCARD_ALL)
                        v->needed = 1;
                }
            }
            if (!v->needed) {
                av_log(v->parent, AV_LOG_INFO, "No longer receiving playlist %d\n",
                    v->index);
                return AVERROR_EOF;
            }
    
            /* If this is a live stream and the reload interval has elapsed since
             * the last playlist reload, reload the playlists now. */
            reload_interval = default_reload_interval(v);
    
    reload:
            if (!v->finished &&
                av_gettime_relative() - v->last_load_time >= reload_interval) {
                if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) {
                    av_log(v->parent, AV_LOG_WARNING, "Failed to reload playlist %d\n",
                           v->index);
                    return ret;
                }
    //добавляем количество загруженных байт в счетчик
                if(isInited() == 1 && doWeHaveBadwidth() == 1) {
                    addToLoadingByte(ret);
                }
                /* If we need to reload the playlist again below (if
                 * there's still no more segments), switch to a reload
                 * interval of half the target duration. */
                reload_interval = v->target_duration / 2;
            }
            if (v->cur_seq_no < v->start_seq_no
                  || v->cur_seq_no > (v->start_seq_no + (v->n_segments * 5)) ) {
                av_log(NULL, AV_LOG_WARNING,
                       "skipping %d segments ahead, expired from playlists\n",
                       v->start_seq_no - v->cur_seq_no);
                v->cur_seq_no = v->start_seq_no;
            }
            if (v->cur_seq_no >= v->start_seq_no + v->n_segments) {
                if (v->finished)
                    return AVERROR_EOF;
                while (av_gettime_relative() - v->last_load_time < reload_interval) {
                    if (ff_check_interrupt(c->interrupt_callback))
                        return AVERROR_EXIT;
                    av_usleep(100*1000);
                }
                /* Enough time has elapsed since the last reload */
                goto reload;
            }
    
            ret = open_input(c, v);
    //добавляем количество загруженных байт в счетчик
            if(isInited() == 1 && doWeHaveBadwidth() == 1) {
                addToLoadingByte(ret);
            }
            if (ret < 0) {
                if (ff_check_interrupt(c->interrupt_callback))
                    return AVERROR_EXIT;
                av_log(v->parent, AV_LOG_WARNING, "Failed to open segment of playlist %d\n",
                       v->index);
                v->cur_seq_no += 1;
                goto reload;
            }
            just_opened = 1;
        }
    
        ret = read_from_url(v, buf, buf_size, READ_NORMAL);
    //добавляем количество загруженных байт в счетчик    
    if(isInited() == 1 && doWeHaveBadwidth() == 1) {
            addToLoadingByte(ret);
        }
        if (ret > 0) {
            if (just_opened && v->is_id3_timestamped != 0) {
                /* Intercept ID3 tags here, elementary audio streams are required
                 * to convey timestamps using them in the beginning of each segment. */
                intercept_id3(v, buf, buf_size, &ret);
            }
    
            return ret;
        }
        ffurl_close(v->input);
        v->input = NULL;
        v->cur_seq_no++;
    
        c->cur_seq_no = v->cur_seq_no;
    // загрузка была завершена. Ищем подходящюю ссылку для текущего bandwidth если она отличается то заменяем страую ссылку на новую
        if(isInited() == 1
               && doWeHaveBadwidth() == 1) {
            findBestSolutionForCurrentBandwidth();
            if (compareUrl(v->url) == 0) {
                strcpy(v->url, getCurrentUrl());
            }
        }
        goto restart;
    }
    
    

    Остались мелочи добавляем новые файлы в makefile внутри libavformat в HEADERS и OBJS добвляем соответсвтующие упоминания

    NAME = avformat
    
    HEADERS = avformat.h                                                    \
              avio.h                                                        \
              version.h                                                     \
              avc.h                                                         \
              url.h                                                         \
              internal.h                                                    \
              bitrate_mamnger.h                                                        \
    
    
    OBJS = allformats.o         \
           avio.o               \
           aviobuf.o            \
           cutils.o             \
           dump.o               \
           format.o             \
           id3v1.o              \
           id3v2.o              \
           metadata.o           \
           mux.o                \
           options.o            \
           os_support.o         \
           riff.o               \
           sdp.o                \
           url.o                \
           utils.o              \
           avc.o                \
           bitrate_mamnger.o               \
    
    

    Так же добавляем метод IjkMediaPlayer_freeBitateWorkData в ijkplayer_jni.c, который будем вызывать после завершения просмотра, что бы очистить данные.

    static void
    IjkMediaPlayer_freeBitateWorkData(JNIEnv *env, jclass clazz){
        freeData();
    }
    //и добавляем данный метод в массив g_methods
    ...
    { "_freeBitateWorkData", "()V",  (void *)IjkMediaPlayer_freeBitateWorkData },
    ...
    
    

    Все, наша реализация готова, теперь остается пересобрать и смотреть видео с меняющимися потокоми.

    Комментарии (0)

      Let's block ads! (Why?)

      Это маленькое чудо — алгоритм Кнута-Морриса-Пратта (КМП)

      Новые возможности x:Bind в UWP

      Image


      Кроме расширений и множества других вещей, в Anniversary Update сильно расширили возможности компилируемых привязок (x:Bind). Давайте посмотрим, что изменилось.


      Звездочкой (*) будут помечены возможности, требующие нового SDK.


      Коллекции


      Теперь вы можете использовать синтаксис вида {x:Bind Collection[0]}, чтобы привязываться к определенным элементам списка. Более того, если исходный список реализует INotifyCollectionChanged, а Mode установлен в OneWay или TwoWay, привязка будет обновляться при изменении списка (даже если изменение не затрагивает привязанный элемент). Для использования этой возможности список должен реализовывать IList<T> или IVector<T>.


      Такой синтаксис доступен не только для списков, но также для словарей и Map'ов — {x:Bind Dictionary['Key']}. Правда типом ключа может быть только string. Аналогично спискам, словарь или Map может реализовывать IObservableMap для обновления привязок. Для экранирования кавычек в строке используется символ ^.


      Attached Properties


      Появилась возможность привязываться к значениям Attached Properties. Например, {x:Bind MyButton.(Grid.Row)} привяжется к номеру строки, в которой находится MyButton. Если Attached Property не объявлена в стандартном пространстве имен, вам нужно добавить соответствующий префикс с указанием нужного namespace.


      Преобразования*


      В отличие от классического Binding, x:Bind строго типизирован, и невалидные привязки выдают ошибки в compile-time. Но бывает так, что вы привязываетесь к свойству с каким-то объявленным типом, но само свойство является объектом более конкретного типа. И даже если вы уверены, что член, которого нет в базовом типе, есть в его наследнике, просто так привязаться к этому члену вы не сможете. Для решения этой проблемы в 1607 добавили возможность кастинга — {x:Bind ((MyObject) Property).Member} или {x:Bind Property.(MyObject.Member)} (рекомендуется первый вариант). Это также бывает полезно для преобразования nullable в обычный тип (например, bool? -> bool).


      BooleanToVisiblityConverter*


      Теперь не нужен. Булевые значения будут неявно конвертироваться в Visibility. Наконец-то.


      Привязка к функциям*


      Это, пожалуй, самая мощная новая возможность x:Bind. Теперь вы можете привязываться непосредственно к возвращаемым значениям функций. Например, {x:Bind MyNumber.ToString('F4')} будет возвращать MyNumber с 4 знаками после запятой.


      • В качестве аргументов вы можете передавать числа, строки, булевые значения (x:True и x:False), null (x:Nul) и пути к вашим свойствам. К тому же, если Mode установлен в OneWay или TwoWay, привязка будет обновляться при изменении аргументов.
      • Вы можете привязываться и к статическим методам, используя нотацию пространствоимен:Класс.Метод.
      • Для использования TwoWay-привязки вам потребуется указать BindBack. Эта функция будет вызываться при изменении значения в пользовательском интерфейсе, и она должна принимать один аргумент (новое значение). Пример — {x:Bind MyFunction(MyProperty), BindBack=Update}

      Разумеется, есть ограничения:


      • Функция должна быть доступна в месте вызова
      • Перегрузка выполняется по количеству, а не типам аргументов. То есть, выбрана будет первая функция, имеющая подходящее количество аргументов.
      • Передаваемые типы должны точно совпадать с принимаемыми.
      • Возвращаемый тип должен точно совпадать с значением свойства, к которому идет привязка.

      Как мы видим, XAML, хоть и неспешно, но развивается, что очень радует. Вполне возможно, что в RS2 и RS3 тоже добавят что-нибудь новое. А пока можно потихоньку осваивать новые вещи и избавляться от ненужного теперь кода.

      Комментарии (0)

        Let's block ads! (Why?)

        Кому ИКРА? Школа креативного мышления для мира digital

        Помните, как в фильме «День выборов?» «А что, Слава тоже креативщик? А я думала, он нормальный мужик…» «Анечка, сколько тебе говорить: слова «креативщик», «саундпродюсер» точно так же, как «педиатр» или «гомеопат», никакого отношения к человеческой сексуальной ориентации не имеют!» Шутка, между прочем, не родилась ниоткуда  —  слово «креативный» в последние несколько лет обрело несколько негативный, насмешливый окрас. А зря  —  ведь креативность как умение создавать находить нестандартные выходы, генерировать крутые идеи присуща всем: и учёным, и программистам, и сисадминам, и дизайнерам, и коммерсантам. Более того, креативное мышление можно и нужно развивать, чтобы оказываться впереди других профессионалов, ведь и потребители продукта, и топ-менеджеры компаний ценят нестандартный подход. особенно, если он даёт профит. ИКРА в этом  деле уже 7 лет, и нам есть, что сказать и куда пригласить.

        Давайте знакомиться

        ИКРА — это школа креативного мышления. Мы обучаем применять и интегрировать креативные методологии в различные сферы: реклама, видео, продюсирование, event, информационные технологии, финансы, издательское дело и не только.

        В основе программ ИКРЫ — индуктивный подход. Мы уверены, только таким образом можно получить опыт и знания, которые приносят пользу. Как это устроено? Сперва вы совершаете ошибку, после — самостоятельно отправляетесь на поиски решения задачи, и только потом — получаете ответ и инструкцию к действию. Иначе говоря, мы прокладываем вам путь к новым умениям и профессиям через ошибки и эксперименты.

        За 7 лет мы выпустили около 2000 выпускников, большинство из которых работает в рекламных компаниях и агентствах. Мы обучили креативному мышлению порядка 3000 сотрудников крупнейших российских компаний и продолжаем внедрять креативное мышление в самые разные профессиональные области.

        Для кого ИКРА?

        ИКРА для всех тех, кто ищет нестандартные способы решения профессиональных задач. Для специалистов в области креатива, digital, рекламы, коммуникаций, IT, образования и других сфер. Это не тренинги личностного роста, не невнятные позывы к формированию креативности, а настоящая школа, с важной теорией, колоссальным объёмом кейсов и практики. Фактически это та информация, которую получил, взял, вышел и на следующий день внедрил в ежедневную работу.

        Мы знаем как минимум 8 мотивов, которыми руководствуются люди из ИТ и смежных отраслей, приходя в ИКРУ.

        1. Разработчики, программисты и дизайнеры учатся воспринимать проекты целостно, как единый творческий процесс своей команды.
        2. Тимлиды и менеджеры учатся проводить яркие продающие презентации, подавать проект с выгодных т коммерчески привлекательных сторон. Ведь мало иметь гениальный код  —  его нужно гениально продать.
        3. Технические писатели и пиарщики приходят прокачивать скиллы копирайтинга. Откроем секрет, иногда за этим же приходят и люди из айтишного менеджмента.
        4. Все учатся грамотно коммуницировать.
        5. HR-служба узнаёт, как креативно, грамотно и корректно работать с соискателями.
        6. К нам приходят программисты, которые поняли, что программирование это не совсем их, и обучаются копирайтингу, стратегиям, вникают в механизм креативности и становятся сильными менеджерами в digital-проектах, поскольку знают продукт не только снаружи, но и понимают тончайшие нюансы изнутри.
        7. Маркетологи, пиарщики и другие сотрудники коммерческих служб проходят воркшопы по брендингу и контакту с целевой аудиторией  —  это им помогает вписывать свой продукт в систему ценностей потребителя, продавать эффективно и приносить деньги компании.
        8. ИКРА  —  это ещё и нетворкинг: вы не поверите, насколько полезные знакомства можно завязать в дружелюбной и интеллектуальной атмосфере ИКРЫ.

        Приглашаем в гости. В гости, значит, даром

        С 14 августа по 19 августа наша школа креативного мышления ИКРА откроет двери и не будет их закрывать целых 6 дней! Эта школа для тех, кто развивает и применяет креативное мышление в различных профессиональных сферах: IT, финансы, реклама, digital, издательское дело и многое другое. На Неделе открытых дверей можно будет побывать в отправной точке каждого будущего курса ИКРЫ до его начала. Тест-драйв обещает быть крутым!

        Каждый день будут проходить креативные занятия, презентации новых программ. Один день — одно образовательное направление. Вас ждут воркшопы по креативным методикам, пониманию целевой аудитории, разработке бренда и многому другому.

        Программа Недели Открытых Дверей:

        • День коммуникаций, 14 августа, с 12.00 до 19.00
        • День Креатива, 15 августа, с 19.30 до 21.30
        • День креативного мышления в HR, 16 августа, с 19.30 до 21.30
        • День Мастерских, 17 августа, с 19.30 до 21.30
        • День Копирайтинга, 18 августа, с 19.30 до 21.30
        • День Креативное мышление в Event, 19 августа, с 19.30 до 21.30

        Вам будет полезно, если вы:

        • Хотите научиться применять креативные методологии (ТРИЗ, латеральное мышление, коммуникационное мышление, дизайн-мышление) в своей сфере
        • Если вы хотите развиваться в творческих профессиях, но не знаете, с чего начать
        • Любите все новое и горите желанием получать знания
        • Слышали про ИКРУ и хотите побывать в «шкуре» наших студентов
        • Хотите узнать подробнее о методиках обучения, планируемых программах

        Что вас ждет:

        • Всю неделю будут проходить воркшопы и презентации по креативному мышлению в различных областях
        • Каждый день вы сможете попробовать свои креативные силы в разных направлениях: коммуникации, копирайтинг, HR, образовательные проекты, Event, создание брендов.
        • Помимо воркшопов вас ждут презентации программ
        • Новые знакомства и головокружительные знания

        Неделя Открытых Дверей в ИКРЕ — это отличный способ получить заряд новых знаний и запомнить это лето надолго, потому что один день в ИКРЕ может действительно перевернуть вашу жизнь. Вход свободный, количество мест ограничено. Регистрируйтесь по ссылке и приходите! Безвозмездно, то есть даром.

        В общем, проведите семь вечеров ради себя, любимого  —  результат не заставит себя ждать.

        Комментарии (0)

          Let's block ads! (Why?)

          пятница, 5 августа 2016 г.

          HEIST позволяет получить зашифрованную информацию в HTTPS канале в виде открытого текста

          8 лекций, которые помогут разобраться в машинном обучении и нейросетях


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

          Машинное обучение


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

          Математические методы прогнозирования объемов продаж


          Другая лекция от ПостНауки — член Российской академии наук Константин Воронцов показывает частный пример применения методов машинного обучения в бизнесе. Математик объясняет, как его команда построила модель прогнозирования объемов продаж для крупной розничной сети.

          Прекрасные и ужасные последствия самообучения компьютеров


          Спикер TED, специалист по машинному обучению и CEO компании Enlitic Джереми Говард делает свои прогнозы о том, что произойдет, когда мы научим компьютеры учиться.

          Как мы учим компьютеры понимать изображения


          Еще одна лекция в рамках TED. Эксперт по компьютерному зрению Фей-Фей Ли описывает последние достижения машинного обучения, включая базу данных, содержащую 15 миллионов фотографий, которую создала её команда, чтобы научить компьютер понимать изображения.

          Как мы обучаем технику не смотреть и слушать, а видеть и слышать?


          Несмотря на первые 20 минут тишины, довольно бодрая лекция по глубинному обучению от теххаба KL10CH и инженера в области машинного обучения, компьютерного зрения и обработки сигналов в Исследовательского центра Samsung Дмитрия Коробченко. Для самых стойких и продвинутых.

          И бонус для тех, кто настроен серьезно:

          Recent Developments in Deep Learning


          Лекция известного специалиста по искусственным нейросетям Джеффри Хинтона, прочитанная в Торонтском университете. Профессор Хинтон рассказывает об основных достижениях в области глубинного обучения.

          Deep Learning, Self-Taught Learning and Unsupervised Feature Learning


          Один из основателей Coursera, доцент Стэнфорда и специалист в области машинного обучения и робототехники Эндрю Ын объясняет тонкости обучения «с учителем» и без.

          Machine Learning for Video Games


          За пять минут на примере Марио вам расскажут, как машинное обучение применяется в разработке видеоигр.

          Комментарии (0)

            Let's block ads! (Why?)

            Вести с полей: кто и как применял качественные методы в UX Research для разработки IT-продуктов. Часть 2 из 6

            Начало статьи — см. публикацию от 2 августа.


            «Они все хотели быть на главной странице»: разработка общей платформы для нескольких групп пользователей

            До сих пор мы рассматривали ситуации, когда нужно сделать продукт для конкретной группы пользователей. Теперь усложним ситуацию, добавив новую переменную, а точнее — еще одну или несколько групп пользователей. В известной степени мы уже подошли к этому в кейсе сайта для American Society for Aesthetic Plastic Surgery, но теперь «заострим» проблемы, которые возникают в таких условиях.

            Хорошо, когда вы четко представляете свою целевую аудиторию. Очень хорошо, если это действительно гомогенная группа и вы уверены в этом на сто процентов. А если вам нужно сделать продукт, которым будут пользоваться разные группы пользователей? И у каждой из них свои потребности, проблемы и ожидания от вашего продукта. Та неловкая ситуация, когда очень хочется создать, например, два разных сайта, а нельзя — либо средств недостаточно, либо структура одна (университет, компания). Вот и приходится проектной команде ломать голову, как сделать так, чтобы никто не ушел обиженным, а показатель отказов не зашкаливал.

            Где и как здесь может помочь качественная методология? Предоставить команде пищу для размышлений — потребности, ожидания и проблемы каждой группы пользователей. Смотрим, как это получилось у зарубежных коллег.

            Кейс 6. The Understanding Group: сайт для American Concrete Institute

            Кейс 6. The Understanding Group: сайт для American Concrete Institute


            Американский институт бетона объединяет около 20 тысяч человек: инженеров, подрядчиков, преподавателей и студентов. Такая крупная организация не может обойтись без своего сайта. Такого же большого, но, к сожалению, сложного и неудобного. Пользователи жалуются, нервничают — нужно что-то делать. Проблема очевидна: сайтом пользуются разные группы сотрудников с различными потребностями. Попытки помочь всем и сразу привели к тому, что сайт стал трещать по швам. Руководство института обратилось к The Understanding Group. Задача — изучить потребности всех сотрудников и попробовать их удовлетворить на одном ресурсе.

            Первым делом команда проинтервьюировала представителей заказчика для выяснения бизнес-целей, а потом уже принялась за сотрудников института.

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

            • Инженеры. Ищут информацию о стандартах и мероприятиях для профессионального развития (например, конференции).
            • Поставщики. Хотят регулярно обновлять материалы по стандартам и сертификатам, а также ищут способы повысить квалификацию на местных мероприятиях.
            • Преподаватели. Стремятся опубликовать свои исследования, участвуют в работе комитетов и комиссий, продвигают студенческие олимпиады.
            • Студенты. Ищут информацию о курсах и конкурсах для студентов.

            Потом ввели дополнительный критерий — отношение пользователей к Американскому институту бетона: вовлеченные vs. реактивные, исследователи и ученые vs. практики индустрии. По результатам интервью создали персонажей и прописали ключевые сценарии:
            • найти техническую статью;
            • найти руководство для профессиональных комитетов;
            • получить стандарт.

            Персонажи и сценарии помогли составить карту сайта и обеспечить удобную навигацию при выполнении основных задач. Проверили статистику по сайту: 15% заходов — с мобильных устройств. Предоставили заказчику спецификацию для сайта при различной ширине области просмотра.

            В итоге получился удобный для пользователей сайт. Увеличилось время, проводимое на сайте (+40 с), и количество просмотренных страниц (+15%). Повысилась конверсия и средняя стоимость заказов (+38%). Проблема помочь всем на одном сайте была решена.

            Кейс 7. The Understanding Group: сайт для Анн-Арбор

            Кейс 7. The Understanding Group: сайт для Анн-Арбор


            У университетского городка Анн-Арбор был сайт, с помощью которого разные департаменты решали свои задачи. Цели у всех разные, задач много. Вырос сайт большим и совсем недружелюбным к пользователю. IT-команда городка это понимала: данные веб-аналитики и обращения в службу поддержки говорили о том, что сайт явно нуждается в реорганизации. Пользоваться им было неудобно даже самим IT-специалистам. Чтобы решить проблему, обратились к The Understanding Group. Поставили им задачу: изменить домашнюю страницу и страницу департамента парков и отдыха, чтобы она стала образцом для других городских структур.

            Команда The Understanding Group посмотрела сначала данные веб-аналитики, а затем приступила к интервью. Начали с представителей заказчика, а потом уже к другим пользователям пошли:

            • к городским властям;
            • к веб-редакторам городских департаментов;
            • к руководителям IT-отделов и их подчиненным;
            • к жителям;
            • к местным владельцам бизнеса;
            • к туристам.

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

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

            • развлечения в Анн-Арбор;
            • бизнес в Анн-Арбор,
            • демократия в Анн-Арбор.

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

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

            Данные веб-аналитики показали, что после запуска главной страницы и страницы департамента парков и отдыха число отказов сократилось на 8%. Служба поддержки стала получать меньше жалоб от людей, которые не могли найти нужную информацию. Кроме того, снизилась конкуренция за место на главной странице среди департаментов. Сотрудники подразделений теперь были уверены, что пользователи сумеют найти нужную информацию.

            * * *


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

            Что именно делали привлеченные специалисты? Просто переделывали структуру сайта с учетом реальных жизненных ситуаций пользователей и их потребностей. Эту информацию о пользователях собирали с помощью качественных методов. И это очень важный момент — эффективная структура сайта для нескольких групп пользователей может быть создана только на основании глубокого анализа их потребностей и жизненных ситуаций. Именно здесь пригодится качественная методология.

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

            Кейс 8. U1: сайт для Adult Community and Further Education Board (AFCE)

            Кейс 8. U1: сайт для Adult Community and Further Education Board (AFCE)


            AFCE, Комитет по муниципальному, внешкольному и дополнительному образованию, — это государственный департамент, предлагающий образовательные услуги через различных местных поставщиков под девизом Learn Local. Инициатива хорошая, но, как и любая другая, она нуждается в эффективном продвижении. Как это можно сделать? У AFCE было два варианта. Одни сотрудники хотели «навороченный» сайт с разными интересными «фишками». Другие считали, что достаточно активно работать со страницей в Facebook. Дешево и сердито. К единому мнению прийти не получалось, поэтому решили обратиться за помощью к сторонней организации — U1 Group. Нужно было найти способ продвижения, который учитывает специфику работы департамента — большое количество местных поставщиков образовательных услуг.

            Команде U1 предстояло ответить на несколько важных вопросов.

            • Что работает или не работает в целом в секторе образовательных услуг?
            • Как поставщики образовательных услуг связываются с нынешними и потенциальными студентами?
            • Считают ли они сайт удобным средством коммуникации?
            • Как взаимодействуют с брендом Learn Local студенты?

            Чтобы ответить на эти вопросы, проектная команда организовала серию интервью с представителями заказчика и поставщиками образовательных услуг. Дополнительно изучили материалы исследований пользовательской аудитории, собранные заказчиком. Все это позволило получить ценные данные, необходимые для разработки стратегии развития сайта. И ответить на вопрос: «Что делать будем?»

            Амбиций AFCE было не занимать. Эта организация хотела выйти в лидеры на рынке образовательных услуг, но обнаружила, что сделать это не так просто. Рынок оказался очень разнородным. Чтобы занять ведущую позицию, требуется очень много самых различных компетенций. Стало понятно, что общий «навороченный» сайт — не панацея. Лучшая стратегия — создать простой сайт бренда Learn Local для поддержки и обеспечения развития сектора образовательных программ. Сайт лишь направляет студентов к местным поставщикам образовательных услуг, а те уже сами рассказывают о плюсах и минусах своих учебных программ.

            Рекомендации команды U1 включали:

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

            Для обеспечения наибольшего эффекта U1 рекомендовала создать систему управления ресурсами, поддерживаемую командой веб-менеджеров. В настоящий момент сайт находится в разработке.
            Кейс 9. The Understanding Group: сайт для Национального совета по технике безопасности США (National Safety Council)

            Кейс 9. The Understanding Group: сайт для Национального совета по технике безопасности США (National Safety Council)


            У Национального совета по технике безопасности понятная, но очень сложная миссия — спасать жизни людей, предотвращая травмы и смерти на работе, дома и в общественных местах. Более ста лет НСБ проводит исследования по технике безопасности, пропагандирует передовые практики и проводит обучение методам обеспечения безопасности. Его образовательные ресурсы помогают бизнесу, государству и потребителям предупреждать несчастные случаи в повседневной деятельности.

            Для достижения своих целей НСБ поддерживает несколько сайтов и порталов с огромным количеством информации. Большой объем данных породил серьезную проблему: полезный контент, способный спасти человеческие жизни, было трудно найти и использовать. Еще одна проблема заключалась в отсутствии баланса между образовательными, пропагандистскими целями и коммерческой деятельностью. НСБ хотела расширить возможности своей группы маркетинга по размещению контента: они не должны были зависеть от IT-команды. Нужна была новая многоцелевая платформа, которая поддерживает просвещение, пропаганду и коммерческую деятельность и при этом обеспечивает удобную связь с НСБ. Задача была не из легких, и представители НСБ решили обратиться к профессионалам из The Understanding Group.

            На первом этапе перед командой The Understanding Group стояли две серьезные задачи:

            • получить представление о бизнесе заказчика;
            • провести аудит — анализ существующих цифровых ресурсов НСБ, аналитики компании, маркетинга, CRM и бизнес-процессов.

            Для изучения коммерческой и образовательной деятельности, а также истории НСБ проектная команда провела интервью с руководителями организации. Потом поговорили с пользователями сайта. Всего провели более 30 интервью с представителями заказчика и пользователями сайта. Полученные данные позволили уточнить цели и задачи проекта.

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

            НСБ и команда The Understanding Group определили основные задачи проекта: привлекать в организацию новых членов, стимулировать участие в образовательных мероприятиях, внедрение и соблюдение стандартов техники безопасности. Раньше избыток контента на сайте мешал пользователям находить ответы на свои вопросы. В ходе исследования было обнаружено, что часть пользователей интересуется в основном обучающими программами, а часть ориентирована на коммерческую составляющую. Данные о них были обработаны и переданы заказчику в формате персонажей.

            Было решено упростить структуру сайта, — теперь она состояла из пяти ключевых блоков: вступление в НСБ, обучение, оценка безопасности (например, на предприятии), действия (например, стать волонтером, пожертвовать деньги, принять участие в событии) и обратная связь. Чтобы сбалансировать просветительскую и коммерческую составляющую, тематический контент по обоим направлениям размещали на одних и тех же страницах, а не отдельных вкладках. Команда The Understanding Group определила основные принципы контентной стратегии и разработала 12 шаблонов, которые упрощали размещение контента на сайте.

            Результатом работы стал сайт, который запустили в 2014 году. Для его оценки провели опрос пользователей, в котором приняли участие 800 человек. 75% респондентов заявили, что новый сайт оказался лучше, чем они ожидали. Основным достоинством стала простота навигации. Ранее из-за проблем с навигацией подразделения НСБ состязались за место на первом уровне сайта. Теперь эта конкуренция снизилась.

            * * *


            Эти два кейса отличаются от рассмотренных ранее прежде всего тем, что изначально исследователи не могли жестко определить все пользовательские группы — они обозначались «широкими мазками» (например, поставщики образовательных услуг, пользователи сайта СНБ). Более четкое представление о пользовательских группах формировалось уже на стадии анализа материала, собранного с помощью интервью. Это важный момент, так как не всегда можно определить пользовательские группы на стадии разработки дизайна исследования, иногда выявить все категории пользователей удается лишь на более поздних этапах. Основанием для их выделения может быть общность задач, информационных потребностей, проблем. Качественные методы позволяют получить необходимую информацию для группировки пользователей, что необходимо для разработки качественной структуры сайта. В этом аспекте весьма любопытен кейс AFCE, где пользователи оказались настолько разными, что оптимальным решением стала их фактически индивидуальная поддержка сайтом и командой веб-мастеров.

            Подведем итоги по анализу опыта применения качественной методологии для разработки продукта для нескольких групп пользователей.

            • Качественная методология помогает решить основные проблемы IT-продуктов (в данном случае сайтов), разрабатываемых для нескольких групп пользователей: слишком громоздкую структуру, избыток информации и сложную навигацию. Последовательный анализ потребностей, проблем и ожиданий каждой группы пользователей позволяет создать удобную структуру с учетом реальных жизненных ситуаций.
            • В некоторых случаях можно определить группы пользователей на стадии разработки дизайна исследования. Задача исследователей — последовательно изучить потребности и проблемы каждой группы пользователей.
            • Существуют ситуации, когда четко определить пользовательские группы на стадии разработки дизайна исследования невозможно. В этом случае состав и характеристики групп уточняются уже на стадии обработки данных.



            Продолжение следует через пару рабочих дней.

            Комментарии (0)

              Let's block ads! (Why?)

              Производительность Java: настоящее и будущее

              [Из песочницы] Выбираем длинный путь (или прощай MAX_PATH)

              Чебурашка и торренты (сказка)

              Делаем работу нашего гейм-дизайнера в Unity удобнее и приятнее

                  Добрый день, Хабр. В эфире снова я, Илья Кудинов. В свободное от основной работы время я занимаюсь разработкой игрушек на Unity 3D и решил в качестве эксперимента написать статью об одной из проблем, с которой столкнулась наша команда. Я являюсь основным разработчиком, и наш гейм-дизайнер в «гробу видал» копание в моем коде с какой бы то ни было целью (разделение труда — одно из величайших достижений цивилизации), значит, моя обязанность — предоставить ему все необходимые рычаги управления и настройки геймплея в виде удобных визуальных интерфейсов. Благо Unity сам по себе имеет достаточно удобные (кхе-кхе) готовые интерфейсы и ряд методов их расширения. И сегодня я расскажу вам о некоторых приемах, которые делают жизнь нашего гейм-дизайнера проще и удобнее, а мне позволяют не биться головой о клавиатуру после каждого его запроса. Надеюсь, они смогут помочь каким-нибудь начинающим командам или тем, кто просто упустил эти моменты при изучении Unity.

                  Сразу скажу, что наша команда все еще активно учится и развивается, хоть мы уже и выпустили дебютную игру. И если «дедлайны не горят», то я предпочитаю разбираться в каких-то вещах сам, а не обращаться к экспертам и различным best practices. Поэтому что-то из рассказанного мною может оказаться не оптимальным или банальным. Буду очень рад, если в таких случаях вы подскажете мне более удобные решения в комментариях и личных сообщениях. Ну и в целом информация здесь скорее базового уровня.

                  Код для Unity я пишу исключительно на C#, поэтому все выкладки в статье будут именно на этом языке.

              Singleton-объекты


                  В архитектуре любой игры зачастую предусмотрены различные классы менеджеров, фабрик и хелперов, которым не нужны физические представления в игровом мире. В идеальном случае можно было бы реализовать их классами со статическими методами, не создавать в сцене никаких GameObject для их работы и спокойно пользоваться кодом вида GameController.MakeEverybodyHappy(). Однако у этого подхода есть два существенных минуса в нашем случае:
              • для изменения каких-либо параметров гейм-дизайнерам придется лазить напрямую в код, а они это очень не любят;
              • будет сложнее использовать ссылки на любые ассеты в Unity (префабы, текстуры и т.д.), так как придется загружать их через Resources.load(), а такой код поддерживать существенно труднее, чем те ссылки, которые можно создавать через интерфейс Unity.

                  Решение проблемы? Наследовать ваши классы от MonoBehaviour и создавать для каждого из них объект в сцене. Минусы этого подхода? При обращении к этим объектам придется пользоваться извращенными вызовами типа FindObjectOfType<GameController>() или даже GameObject.Find(”GameController”).GetComponent<GameController>(). Ни один уважающий себя разработчик делать так на каждом шагу не захочет. Дополнительные проблемы и возможные ошибки начинают возникать при необходимости переносить такие объекты между сценами или при возникновении нескольких объектов с таким классом (либо их полном отсутствии).
                  Значит, нам нужен какой-то механизм, который позволит получать интересующий нас объект без какой-либо магии и контролировать, что на момент каждого обращения в нашей сцене будет один и ровно один объект этого класса.
                  Наше решение выглядит следующим образом («костяк» класса я когда-то давно нашел на просторах интернета и слегка доработал для собственного удобства):
              using UnityEngine;
              public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
              {
                  private static T _instance;
              
                  public void Awake()
                  {
                      // Если в сцене уже есть объект с таким компонентом, то
                      // он пропишет себя в _instance при инициализации
                      if (!_instance) {
                          _instance = gameObject.GetComponent<T>();    
                      } else {
                          Debug.LogError("[Singleton] Second instance of '" + typeof (T) + "' created!");
                      }
                  }
              
                  public static T Instance
                  {
                      get 
                      {
                          if (_instance == null) {
                              _instance = (T) FindObjectOfType(typeof(T));
              
                              if (FindObjectsOfType(typeof(T)).Length > 1) {
                                  Debug.LogError("[Singleton] multiple instances of '" + typeof (T) + "' found!");
                              }
              
                              if (_instance == null) {
                                  // Если в сцене объектов с этим классом нет - создаём
                                  // новый GameObject и лепим ему наш компонент
                                  GameObject singleton = new GameObject();
                                  _instance = singleton.AddComponent<T>();
                                  singleton.name = "(singleton) " + typeof(T).ToString();
                                  DontDestroyOnLoad(singleton);
                                  Debug.Log("[Singleton] An instance of '" + typeof(T) + "' was created: " + singleton);
                              } else {
                                  Debug.Log("[Singleton] Using instance of '" + typeof(T) + "': " + _instance.gameObject.name);
                              }
                          }        
                          return _instance;
                      }
                  }
              }
              
              

                  Как этим пользоваться? Наследуем свои классы от Singleton, указав самого себя в шаблоне:

               public class GameController : Singleton<GameController>
              
                  В дальнейшем мы сможем обращаться к полям и методам нашего класса как GameController.Instance.MakeEverybodyHappy() в любом месте кода. Чтобы создавать ссылки на ассеты, достаточно добавить этот компонент на любой объект в сцене и настраивать его привычным образом (и сохранять в префаб для верности). Мы используем объект Library в корне сцены для хранения всех Singleton-классов, настройка которых может понадобиться нашему гейм-дизайнеру, чтоб ему не приходилось их искать по всей сцене.

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

              Кастомные поля инспектора класса


                  Итак, гейм-дизайнер получил свои визуальные интерфейсы и начал творить геймплей. И достаточно быстро начал ненавидеть меня, невинного разработчика, за все неудобства, которые на него наваливаются. Массив сериализованных объектов? Многомерные массивы? Почему это все настолько неудобно настраивать? Как бы вы ни старались сделать универсальную и расширяемую систему на стороне кода, ваш гейм-дизайнер предпочел бы видеть минимальное количество выпадающих списков и уж тем более массивов элементов с названиями вроде Element 73. Но разве мы можем что-то с этим поделать?
                  На самом деле можем. Предположим, в нашей игре появляются настройки сложности, и в данный момент вариантов три. Но, возможно, станет больше. Поэтому мы смотрим в будущее и для упрощения дальнейшего ВОЗМОЖНОГО увеличения количества сложностей создаем вот такой замечательный класс «зависящих-от-сложности-интов» и заменяем в нужных местах обычные инты на него:
              [System.Serializable]
              public class DifDepInt 
              {
                  public int[] values = {0, 0, 0};
              
                  static public implicit operator int (DifDepInt val)
                  {
                      return val.Get();
                  }
              
                  public int Get()
                  {
                      return values[GameConfig.Instance.difficulty];
                  }
              }
              
              

                  DifDep означает Difficulty Dependant. Конечно, с точки зрения архитектуры лучше было бы сделать вместо этого специфического класса шаблон DifDep<T>, принимающий любые типы данных, но, к сожалению, я не нашел способа создания кастомных полей редактора для шаблонов.

                  Итак, мы довольны собой, мы получили возможность без особых трудов ввести в игру варьирующиеся параметры. Но наш гейм-дизайнер, которому надо это все настраивать, почему-то недоволен. Надо бы спросить его, что происходит… Ах вот оно что!


                  Да, однозначно, это не очень интуитивно и удобно. Давайте сделаем так, чтобы все выглядело иначе! Для этого мы воспользуемся вышеназванной «плюшкой» Unity — возможностью создавать кастомных инспекторов для отображения различных классов. Это достаточно хитрая система, позволяющая вам делать практически все что угодно, но в ней не так просто разобраться с первого взгляда (с самого начала она меня отпугнула, и поэтому какое-то время мы-таки страдали со стандартным инспектором, но в конце концов момент истины настал).
              Итак, мы пишем следующий код:
              #if UNITY_EDITOR
              using UnityEditor;
              
              [CustomPropertyDrawer(typeof(DifDepInt))]
              public class DifDepIntDrawer : PropertyDrawer 
              {
                  int difCount = 3;
              
                  public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) 
                  {
                      EditorGUI.BeginProperty(position, label, property);
              
                      Rect contentPosition = EditorGUI.PrefixLabel(position, label);
                      contentPosition.width *= 1 / difCount;
                      float width = contentPosition.width;
              
                      SerializedProperty values = property.FindPropertyRelative ("values");
              
                      for (int i = 0; i < difCount; i++) {
                          EditorGUI.PropertyField (contentPosition, values.GetArrayElementAtIndex(i), GUIContent.none);
                          contentPosition.x += width;
                     }
              
                      EditorGUI.EndProperty();
                  }
              }
              #endif
              
              

                  Давайте разберемся, что тут происходит. Директива компилятора #if UNITY_EDITOR сообщает Unity, что она должна компилировать этот класс только во время разработки в редакторе. В противном случае она будет пытаться собрать этот код при сборке билда игры, а модуль UnityEditor там недоступен целиком, и это может вызвать сбивающие с толку ошибки.
                  [CustomPropertyDrawer(typeof(DifDepInt))] говорит Unity, что для отрисовки полей классов типа DifDepInt ей нужно использовать предоставленный ниже код вместо стандартного. Таких директив можно указать сколько угодно подряд для всех DifDep-классов, которые вам понадобятся — сам код кастомного редактора написан так, что примет любые классы, имеющие в себе массив элементов под названием values, поэтому этот класс у меня обслуживает и int, и float, и даже Sprite и GameObject.
                  Мы перегружаем метод OnGUI(), который и занимается отрисовкой области редактирования поля в инспекторе. Unity вызывает его иногда несколько раз за кадр — это нужно иметь в виду. Не забываем оставлять методы EditorGUI.BeginProperty() и EditorGUI.EndProperty(), без них корректно работать ваш код не будет.
                  Остальной код достаточно интуитивно понятен, если заглянуть в документацию Unity. Вместо магии с contentPosition можно использовать методы отрисовки из класса EditorGUILayout, а не EditorGUI, однако они не всегда ведут себя очевидным образом и в некоторых плохих случаях разбираться с ними себе дороже.
                  Ради чего же мы этим занимались? Смотрите, какая красота!


                  Это однозначно удобнее того, что было. Возможности, которые дает подобный функционал, практически безграничны — вы можете отображать самые сложные структуры максимально удобным для редактирования способом. Но не думайте, что гейм-дизайнер будет вам благодарен. Он примет это как должное, я вам гарантирую (:

              Кастомные редакторы целого класса


                  Окей, красиво рисовать отдельные поля мы научились, а можем ли мы рисовать что-то, что охватывает весь класс? Конечно же да! Например, параметры всех грейдов отдельно взятого вида оружия мы задаем вот так:

                  Помимо редактирования полей здесь присутствует еще и калькулятор, значения в котором изменяются автоматически при изменении параметров оружия (на самом деле они read-only, вид инпутов они имеют только для консистентности и удобства выравнивания).
                  Как же сделать что-то подобное? Очень просто и схоже с тем, что мы делали до этого! Продемонстрирую на простом примере— добавлении простенького калькулятора DPS перед всеми остальными полями в классе поведения монстра:
              #if UNITY_EDITOR
              using UnityEditor;
              
              [CustomEditor(typeof(EnemyBehaviour), true)]
              public class EnemyCalculatorDrawer : Editor 
              {
                  public override void OnInspectorGUI() {
                      EnemyBehaviour enemy = (EnemyBehaviour)target;
                      float dps1, dps20;
              
                      dps1 = enemy.damageLeveling.Get(1) / enemy.getAttackDelay(1);
                      dps20 = enemy.damageLeveling.Get(20) / enemy.getAttackDelay(20);
              
                      GUIStyle myStyle = new GUIStyle ();
                      myStyle.richText = true;
                      myStyle.padding.left = 50;
                      EditorGUILayout.LabelField("<b>Calculator</b>", myStyle);
                      EditorGUILayout.LabelField("DPS on level 1: " + dps1.ToString("0.00"), myStyle);
                      EditorGUILayout.LabelField("DPS on level 20: " + dps20.ToString("0.00"), myStyle);
                      EditorGUILayout.Separator();
              
                      base.OnInspectorGUI();
                  }
              }
              #endif
              
              

                  Ситуация очень похожая: сначала мы сообщаем Unity о желании заменить отрисовщик для этого класса с помощью директивы [CustomEditor(typeof(EnemyBehaviour), true)]. Затем переопределяем метод OnInspectorGUI() (да, в этот раз не OnGUI(), потому что разработчик должен страдать), пишем в нем свою кастомную логику (унаследованное от класса Editor поле под названием target содержит в себе ссылку на отображаемый объект как на Object) и затем вызываем base.OnInspectorGUI(), чтобы Unity отрисовал все остальные поля так же, как и обычно. GUIStyle позволяет нам изменять внешний вид отображаемых данных. В этом случае я использовал методы из EditorGUILayout просто потому, что здесь совершенно не надо было беспокоиться о выравненном позиционировании.
                  Итог же выглядит так:


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

              Всякие мелочи


                  Конечно, можно делать огромное количество других вещей, чтобы спасти глаза и мозг ваших коллег. Unity предлагает целый набор директив для того, чтобы превратить простыню public-полей в структурированное целое. Самая важная из них, это, конечно, [HideInInspector], которая позволяет скрыть public-поле из инспектора. И больше не нужно вопить: «Пожалуйста, не трогайте эти галочки, они служебные!», и затем все равно часами разбираться, почему все монстры внезапно начали ходить задом наперед. Помимо этого есть еще приятные вещи вроде [Header(«Stats»)], которые позволяют отображать аккуратный заголовок перед блоком полей, и [Space], который просто делает небольшой отступ между полями, помогая разбивать их на смысловые группы. Все три эти директивы нужно писать непосредственно перед объявлением public-поля (если вы поставите [Header()] перед приватным полем, то ругаться Unity не станет, но никакого заголовка не отобразит).
                  Небольшая подсказка: если ваш сериализуемый объект имеет в себе string-поле под названием name, то когда вы засовываете несколько таких объектов в публичный массив, его “имя” будет отображаться вместо неинтуитивного Element X в инспекторе.
                  И ещё один полезный совет: даже если какой-то настраиваемый объект лежит у вас в сцене и является единственным представителем своего рода, все равно имеет смысл сделать из него префаб. Тогда, в случае совместной работы над проектом, не произойдут конфликты из-за одновременного редактирования сцены: правки, внесённые в инстанс префаба и применённые с помощью кнопки Apply, никак не аффектят файл сцены.

                  Любой проект, над которым трудится более одного человека, заставляет своих участников чем-то жертвовать ради других, особенно когда область деятельности каждого из них очень сильно отличается. Разработка компьютерных игр — дело для специалистов из множества разных областей. В небольшой сплоченной команде долг каждого стараться все делать для того, чтобы минимизировать суммарные жертвы. А значит, потраченные пара часов для изучения кастомных редакторов или еще каких-либо приемов, которые кажутся не очень важными с точки зрения разработки кода — замечательное вливание в ваш проект, которое способно спасти не один час работы и миллионы нервных клеток ваших коллег. Товарищи разработчики-программисты, давайте жить дружно с теми, для кого ваш код все равно что поэмы на традиционном китайском языке для вас.

                  Товарищи разработчики-программисты, в совершенстве владеющие традиционным китайским языком, — глубокий поклон вам и извинения за такие предрассудки.

              Комментарии (0)

                Let's block ads! (Why?)

                Для квантового компьютера IBM опубликованы первые программы

                [Перевод] Прощай, объектно-ориентированное программирование

                Новое исследование Ассоциации полупроводниковой промышленности: «Через 5 лет закон Мура перестанет действовать»

                Логистическая регрессия в пакете машинного обучения «XGboost»

                image


                В этой статье речь пойдет о логистической регрессии и ее реализации в одном из наиболее производительных пакетов машинного обучения "R" — "XGboost" (Extreme Gradient Boosting).
                В реальной жизни мы довольно часто сталкиваемся с классом задач, где объектом предсказания является номинативная переменная с двумя градациями, когда нам необходимо предсказать результат некого события или принять решения в бинарном выражении на основании модели данных. Например, если мы оцениваем ситуацию на рынке и нашей целью является принятие однозначного решения, имеет ли смысл инвестировать в определенный инструмент в данный момент времени, купит ли покупатель исследуемый продукт или нет, расплатится ли заемщик по кредиту или уволится ли сотрудник из компании в ближайшее время и.т.д.


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


                Сразу оговорюсь, что в "R" существует несколько линейных функций для обучения логит-модели, таких как "glm" из стандартного пакета функций, но здесь мы рассмотрим более продвинутый вариант, имплементированный в пакете "XGboost". Эта модель, многократный победитель соревнований Kaggle, основана на построении бинарных деревьев решений способна поддерживать многопоточную обработку данных. Об особенностях реализации семейства моделей "Gradient Boosting" можно прочитать здесь:


                http://ift.tt/1jNBslt
                http://ift.tt/1MIDWAH


                Возьмем тестовый набор данных (Train) и построим модель для предсказания выживаемости пассажиров при катастрофе :


                    data(agaricus.train, package='xgboost')    
                    data(agaricus.test, package='xgboost')    
                    train <- agaricus.train    
                    test <- agaricus.test    
                

                Если после преобразования матрица содержит много нулей, то такой массив данных нужно предварительно преобразовать в sparse matrix — в таком виде данные займут намного меньше места, а соответственно и время обработки данных намного сократится. Здесь нам поможет библиотека ‘Matrix’ на сегодняшний день последняя доступная версия 1.2-6 содержит в себе набор функция для преобразования в dgCMatrix на колоночной основе.
                В случае, когда уже уплотненная матрица (sparse matrix) после всех преобразований не помещается в оперативной памяти, то в таких случаях используют специальную программу “Vowpal Wabbit”. Это внешняя программа, которая может обработать датасеты любых размеров, читая из многих файлов или баз данных. “Vowpal Wabbit” представляет собой оптимизированную платформу для параллельного машинного обучения, разработанную для распределенных вычислений компанией “Yahoo!” Про нее довольно подробно можно прочесть по этим ссылкам:


                http://ift.tt/2azBhzi
                http://ift.tt/1nK3YJP


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


                    library(xgboost)    
                    library(Matrix)     
                    library(DiagrammeR)    
                

                При конвертации в матрицу все категориальные переменные будут транспониваны, соответственно функция со стандартным бустером включит их значения в модель. Первое что нужно сделать, это удалить из набора данных переменные с уникальными значениями, такими как “Passenger ID”, “Name” и “Ticket Number”. Такие же действия проводим и с тестовым набором данных, по которым будут рассчитывается прогнозные исходы. Для наглядности я загрузил данные из локальных файлов, которые скачал в соответствующем датасете Kaggle. Для модели, ним понадобятся следующие колонки таблицы:


                    input.train <- train[, c(3,5,6,7,8,10,11,12)]    
                    input.test <-  test[, c(2,4,5,6,7,9,10,11)]    
                

                image
                отдельно формируем вектор известных исходов для обучения модели


                    train.lable <- train$Survived    
                

                Теперь необходимо выполнить преобразование данных, дабы при обучении модели в учет были приняты статистически значимые переменные. Выполним следующие преобразования:
                Заменим переменные содержащие категориальные данные на числовые значения. При этом нужно учитывать что упорядоченные категории, такие как 'good', 'normal', 'bad' можно заменить на 0,1,2. Не упорядоченные данные с относительно небольшой селективностью, такие как ‘gender’ или ‘Country Name’ можно оставить факторными без изменения, после преобразование в матрицу они транспонируются в соответствующее количество столбцов с нулями и единицами. Для числовых переменных, необходимо обработать все неприсвоенные и пропущенные значения. Здесь есть как минимум три варианта: их можно подменить на 1, 0 либо более приемлемый вариант будет замена на среднее значение по колонке этой переменной.
                При использовании пакета “XGboost” со стандартным бустером (gbtree), масштабирование переменных можно не выполнять, в отличии от других линейных методов, таких как “glm” или “xgboost” c линейным бустером (gblinear).


                Основную информацию о пакете можно найти по следующим ссылкам:


                http://ift.tt/1NBNNwI
                http://ift.tt/1OdPM58


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


                image


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


                    if (class(inp.column) %in% c('numeric', 'integer')) {
                            inp.table[is.na(inp.column), i] <- mean(inp.column, na.rm=TRUE)     
                

                после предварительной обработки делаем преобразование в "dgCMatrix":


                    sparse.model.matrix(~., inp.table)    
                

                Имеет смысл создать отдельную функцию для предварительной обработки предикторов и преобразования в sparse.model.matrix формат, например вариант с "for" циклом приведен ниже. C целью оптимизации производительности можно векторизовать выражение используя функцию "apply".


                    spr.matrix.conversion <- function(inp.table) {    
                      for (i in 1:ncol(inp.table)) {    
                        inp.column <- inp.table [ ,i]       
                        if (class(inp.column) == 'character') {    
                          inp.table [is.na(inp.column), i] <- 'NA'     
                          inp.table [, i] <- as.factor(inp.table [, i])    
                        }     
                        else    
                          if (class(inp.column) %in% c('numeric', 'integer')) {    
                            inp.table [is.na(inp.column), i] <- mean(inp.column, na.rm=TRUE)        
                          }    
                      }    
                  return(sparse.model.matrix(~.,inp.table))
                    }    
                

                Тогда воспользуемся нашей функцией и преобразуем фактическую и тестовую таблицы в разреженные матрицы:


                    sparse.train <- preprocess(train)    
                    sparse.test <- preprocess(test)    
                

                image


                Для построения модели нам потребуется два набора данных: матрица данных, которую мы только что создали и вектор фактических исходов с бинарным значением (0,1).
                Функция «xgboost» является наиболее удобной в использовании. В “XGBoost” имплементирован стандартный бустер основан на бинарных деревьях решений.
                Для использования “XGboost”, мы должны выбрать один из трех параметров: общие параметры, параметры бустера и параметров назначения:
                • Общие параметры – определяем, какой бустер будет использован, линейный или стандартный.
                Остальные параметры бустера зависят от того, какой бустер мы выбрали на первом шаге:
                • Параметры задач обучения – определяем назначение и сценарий обучения
                • Параметры командной строки — используются для определения режима командной строки при использовании “xgboost.”.


                Общий вид функции “xgboost” который мы используем:


                     xgboost(data = NULL, label = NULL, missing = NULL, params = list(), nrounds, verbose = 1, print.every.n = 1L, early.stop.round = NULL, maximize = NULL, ...)   
                

                "data" – данные матричном формате ( "matrix", "dgCMatrix", local data file or "xgb.DMatrix".)
                "label" – вектор зависимой переменной. Если данное поле было составляющей исходной таблицы параметров, то перед обработкой и преобразованием в матрицу, его следует исключить, дабы избежать транзитивности связей.
                "nrounds" –количество построенных деревьев решений в финальной модели.
                "objective" – через данный параметр мы передаем задачи и назначения обучения модели. Для логистической регрессии существуют 2 варианта:
                "reg:logistic" – логистическая регрессия с непрерывной величиной оценки от 0 до 1;
                "binary:logistic" – логистическая регрессия с бинарной величиной предсказания. Для этого параметра можно задать специфическую пороговую величину перехода от 0 к 1. По умолчанию это значение 0.5.
                Детально про параметризацию модели можно прочесть по этой ссылке


                http://xgboost/parameter.md%20at%20master%20·%20dmlc/xgboost%20·%20GitHub
                Теперь приступаем к созданию и обучению модели “XGBoost”:


                    set.seed(1)    
                    xgb.model <- xgboost(data=sparse.train, label=train$Survived, nrounds=100, objective='reg:logistic')     
                

                при желании можно извлечь структуру деревьев с помощью функции xgb.model.dt.tree( model = xgb). Далее, используем стандартную функцию “predict” для формирования прогнозного вектора:


                    prediction <- predict(xgb.model, sparse.test)     
                

                и наконец, сохраним данные в приемлемом для чтения формате


                    solution <- data.frame(prediction = round(prediction, digits = 0), test)    
                    write.csv(solution, 'solution.csv', row.names=FALSE, quote=FALSE)    
                

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


                image


                Теперь немного вернемся и вкратце рассмотрим саму модель, которую мы только что создали. Для отображения деревьев решения, можно воспользоваться функциями "xgb.model.dt.tree" и "xgb.plot.tree". Так, последняя функция выдаст нам список выбранных деревьев с коэффициентом подгонки модели:


                image


                Используя функцию xgb.plot.tree мы также увидим графическое представление деревьев, хотя нужно отметить что в текущей версии, оно далеко не лучшим способом имплементировано в данной функции и является мало полезным. По этому, для наглядности, мне пришлось воспроизвести вручную элементарное дерево решений на базе стандартной модели данных Train.


                image


                Проверка статистической значимости переменных в модели подскажет нам как оптимизировать матрицу предикторов для обучения XGB-модели. Лучше всего использовать функцию xgb.plot.importance в которую мы передадим агрегированную таблицу важности параметров.


                    importance_frame <- xgb.importance(sparse.train@Dimnames[[2]],  model = xgb)    
                    xgb.plot.importance(importance_frame)       
                

                image


                Итак, мы рассмотрели одну из возможных реализаций логистической регрессии на базе пакета функции “xgboost” со стандартным бустером. На данный момент я рекомендую использовать пакет “XGboost” как наиболее продвинутую группу моделей машинного обучения. В настоящие время предиктивные модели на базе логики “XGboost” широко используются в финансовом и рыночном прогнозировании, маркетинге и многих других областях прикладной аналитики и машинного интеллекта.

                Комментарии (0)

                  Let's block ads! (Why?)

                  NIST: SMS нельзя использовать в качестве средства аутентификации

                  Azure Service Fabric: вторые шаги


                  Снова Чарли Чаплин на фабрике в фильме «Новые времена»


                  Продолжаем разговор про Azure Service Fabric. В предыдущей статье я упомянул о планах написать сначала про stateful сервисы, а затем уже перейти к модели акторов в ASF. Концепция изменилась — подумалось мне, что неплохо бы для примеров использовать если уж не production-решение, то что-то близкое, чтобы была теоретическая польза и практический смысл. Можно объединить все компоненты ASF в одном флаконе — чтобы и корованы набигали, и лунапарк, и Винни-Пух и все-все-все. Вот с такими мыслями я и пошел на кладбище домашних проектов в поисках кандидата на оживление.



                  Живые мертвецы


                  И такой кандидат нашелся. Как любят писать в «железных» статьях, «порывшись в ящиках с запчастями» выудил оттуда когда-то недописанную библиотеку работы с протоколом Advanced Direct Connect. «На лицо ужасная, страшная внутри», однако, смахнув пыль напильником, убедился, что внутри еще теплится жизнь. Библиотека когда-то предназначалась для так и ненаписанного ADC-клиента, однако почему бы на ее основе не написать ADC-хаб? Вот это идея, свежая и оригинальная — можно попробовать.
                  Что за Advanced Direct Connect?

                  Протокол ADC появился больше 10 лет назад, в 2004 году (последняя версия 1.0.3 в 2013 году) как наследник NMDC (он же просто DC). Как и в NMDC узлы сети ADC обмениваются простыми текстовыми командами. Типов узлов всего два — это центральный узел (хаб) и конечные клиенты, который присоединяются к хабу и друг к другу. Через хаб клиенты могут общаться (чатиться) и искать нужные файлы, а вот передача данных осуществляется напрямую между клиентами, минуя хаб. Грубо говоря, хаб служит для аутентификации клиентов, хранения их списка и пересылки команд между клиентами. Единственный известный мне существующий транспорт это TCP, схема адресации adc://, порт стандартом не определяется, но на практике используется привычный для DC 411. У клиентов есть собственный Private ID, но важнее, что каждому подключившемуся клиенту в первую очередь выдается SID (session identifier), по которому его (клиента) и адресуют другие клиенты.



                  Типовая схема соединений P2P справедлива и для ADC


                  Протокол расширяем — можно добавлять как совершенно новые команды, так и параметры к уже существующим командам. Список поддерживаемых возможностей каждый хаб/клиент объявляют при установлении соединения. Обязательна поддержка базовых возможностей ADC для хаба и клиента, плюс TCPx/UDPx для клиента для обмена файлами. Расширения достаточно мощный механизм наращивания возможностей протокола — скажем, в расширения вынесены как несколько дополнительных функций хеширования (согласование используемой функции есть в базовых возможностях на этапе установки соединения клиент-хаб), так и защищенное соединение ADCS.


                  Что касается современного состояния дел, то, как ни странно, область P2P еще жива (разумеется, я не считаю торрентов, которые всегда на слуху). И хабы и наследники StrongDC++ (когда-то я и сам пользовался этим клиентом) до сих пор разрабатываются, так что решаемая задача еще не совсем потеряла свой прикладной смысл, перейдя в разряд академических.


                  К сожалению, спецификация ADC оставляет простор для фантазии — не то, чтобы совсем «музыка к игре написана профессиональными программистами», но и до, скажем, точности RFC или стандартов W3C ей далеко. Из-за этого по ходу дела пришлось уточнять детали по существующим реализациям ADC (ADCH++, AirDC++ и пр.).


                  Модель для сборки


                  Начну, как обычно, с общеархитектурных прикидок. Для простоты хаб будет открытый, то есть пользователи могут заходить без регистрации и паролей, но их имена должны быть уникальны. С ходу видны три логических части общей картины — это сервер соединений TCP, затем объекты, реализующие ADC для обмена с клиентами, по одному на каждого клиента (назову их пользователями) и, наконец, общий каталог всех активных клиентов.

                  TCP-сервер будет заниматься установкой TCP-соединений и обменом сериализованными ADC-сообщениями с клиентом, пользователи отвечают за корректную реализацию протокола согласно спецификации, а на каталог возлагаются задачи по поддержанию списка активных клиентов (в том числе выдаче их идентификаторов) и широковещательной рассылке сообщений. Вот от этой печки и буду плясать.


                  Привязка к местности


                  Теперь каждой логической части надо подыскать соответствие среди компонентов ASF. Основное ограничение — минимум самописного кода и максимум существующих в ASF возможностей.


                  TCP-сервер


                  TCP-сервер состояния не имеет и нужен по одному экземпляру на каждом узле ASF-кластера — очевидно, это stateless сервис (описывал в предыдущей статье, так что повторяться не буду).


                  Пользователи


                  Обмен ADC-командами с клиентами попробую возложить на акторов.


                  Акторы

                  Модель акторов (actor model) появилась, как ни странно, из научной среды (впрочем, в 1973 году для тогдашних computer science это неудивительно). Согласно этой модели, актором называется некоторая сущность, которая (как правило) имеет состояние, функционирует независимо от остальных акторов, может асинхронно посылать и принимать сообщения, а также создавать других акторов. Совокупность (или система) акторов может объединять акторов разных типов. Для обмена сообщениями у каждого актора есть специальный адрес (как правило, числовой или текстовый). Важно понимать, что все акторы функционируют параллельно, обмен сообщениями асинхронен, но обработка этих сообщений идет последовательно — актор “однопоточен”, служит как точка синхронизации и в каждый момент времени обрабатывает не более одного сообщения.


                  Реализовать эту умозрительную модель можно различно — мне в разной степени известны три реализации акторов Akka.NET, Orleans и акторы ASF. В Akka.NET явно выделены и создание актора, и его удаление из системы, и посылка ему сообщений. Акторы Akka.NET образуют иерархию, так что “родители” могут следить за поведением “детей”. В Orleans и ASF пошли по другому пути — здесь взяли на вооружение плоскую модель распределенных виртуальных акторов, в которой все акторы существуют всегда, но до первого вызова в неявном состоянии. Будучи вызван, актор создается и далее его состояние сохраняется между перезагрузками. Асинхронная передача сообщений моделируется вызовами интерфейса C#, а для полной “гальванической развязки” в Orleans присутствуют персистентные Streams, которые реализуют Pub-Sub. В ASF Streams нет, зато есть события актора, на которые также можно подписаться. И (как и в Akka.NET) есть удаление актора и очистка его состояния — после удаления актор снова переходит в “мир теней” до очередного вызова. Как видно, во всех реализациях есть и похожие, и различающиеся черты.


                  Как ни странно, довольно част вопрос — чем ASF-актор отличается от ASF-сервиса? Смотрите сами — экземпляр сервиса создается однократно при старте кластера, работает затем до остановки или перезагрузки ASF, имеет внешние и/или внутренние точки коммуникаций. Экземпляры акторов создаются и удаляются внешними запросами, а их количество ограничено только физическими возможностями кластера. Поэтому если необходимо реализовать коллекцию объектов (возможно, разнотипных), которая должна динамически изменяться по ходу работы — это акторы. Если нужно создать известное наперед количество объектов, особенно если эти объекты реализуют внешние коммуникации или внутреннюю функциональность — это сервисы. И важно помнить, что акторы обрабатывают обращения к ним последовательно, а сервисы — параллельно.


                  Каталог пользователей


                  Возвращаясь к нашим компонентам — для каталога активных клиентов логично выбрать stateful сервис.


                  Stateful сервисы

                  Если коротко — stateful сервисы такие же, как и stateless, но с состоянием. И действительно, жизненный цикл у них похож — те же OnOpen, OnClose и RunAsync. Так же есть слушатели для коммуникаций, так же возможен ASF Remoting и пр. Однако наличие состояния привносит и заметные различия. В первую очередь это различия в терминологии — если для stateless сервисов имеет смысл говорить об экземплярах, то stateful сервисы имеют реплики. Реплики объединяются в наборы реплик (replica set), каждый набор реплик имеет свое состояние, независимое от остальных наборов в этом сервисе. В каждом наборе только одна реплика является первичной, а остальные вторичными — состояние может менять только первичная реплика (и только для нее вызываются функции жизненного цикла), все остальные состояние только читают. Если сервис партиционирован, то каждая партиция представляет собой такой набор реплик и, соответственно, имеет свое отдельное состояние. И партиции, и количество реплик в них задаются — опять же как и для stateless сервисов — через конфигурацию приложения. В случае падения первичной реплики одна из вторичных берет на себя эту роль, поэтому для надежности ASF раскидывает все реплики одного набора по нодам кластера, а состояние сервиса реплицируется между нодами. В состоянии можно хранить только объекты, порожденные от интерфейса IReliableState. Сам по себе интерфейс напоминает маркерный, реализовать его толком невозможно, так что “из коробки” фактически есть только два reliable-объекта: ReliableDictionary и ReliableQueue (словарь и FIFO-очередь). Для синхронизации изменений в нескольких reliable-объектах можно использовать транзакции.


                  Итого выходит:



                  Дьявол в деталях


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

                  TCP-сервер


                  Для TCP-сервера можно использовать стандартные TcpListener/TcpClient, которые оборачиваются в нестандартный TcpCommuncationListener. TcpListener запускается на OpenAsync, останавливается на CloseAsync. а сам TcpCommuncationListener создается в CreateServiceInstanceListeners.
                  В конфигурировании есть мелкая деталь — для локальной отладки в Local.xml я ставлю />, а в параметрах для Azure будет />. Это потому, что узлы отладочного кластера запускаются локально на одном компьютере и поделить 411 порт между собой никак не могут, соответственно, нужен только 1 экземпляр сервиса. ‘-1’ означает, что один экземпляр сервиса должен быть запущен на каждом узле ASF-кластера — в Azure узлы ASF размещаются на разных VM и у каждой есть свой 411 порт.


                  Как связаться с нужным актором и какой у него вообще адрес? Это будет обсуждено далее в реализации акторов, а пока вот первая проблема (впрочем, предсказуемая) — установка TCP-соединения, создавшая экземпляр TcpClient, намертво приколачивает его же к узлу кластера, с которым соединился клиент. Актор может перемещаться по узлам, но его вызвать несложно — он адресуем, а вот как из актора вызвать TcpClient? Ведь сообщения надо как принимать, так и отправлять. Через сам stateless сервис нельзя — неизвестно, в каком из экземпляров сервиса находится нужный TcpClient. И тут на помощь приходят события акторов — TcpClient может подписаться на события “своего” актора и получать исходящие сообщения через них (я предпочел бы потоки, но стараюсь обходиться тем, что есть).


                  Пользователи


                  Функций у актора всего две — получить и обработать очередное сообщение, и отправить сообщение клиенту. И тем не менее это самая запутанная часть хаба, поскольку именно на актора возлагается поддержка протокола. Не бином Ньютона, но тоже есть над чем поразмыслить. Для начала нужно выбрать, чем адресовать актора. В протоколе клиент адресуется SID, который выдается хабом — отчего бы не использовать этот SID как адрес актора ASF? Далее, ADC предполагает четыре состояния протокола для хаба — PROTOCOL (согласование параметров подключения), IDENTIFY (информация о клиенте), VERIFY (проверка пароля), NORMAL (обычная работа). Поскольку хаб открытый, то паролей нет и VERIFY не требуется. А поскольку актор в ASF виртуальный, то имеет смысл добавить состояние UNKNOWN для актора, который вроде бы жив, но еще/уже никуда не подключен.


                  Машина с состояниями

                  Как только есть состояния, то тут же появляется и state machine — приходящие ADC-команды будут обрабатываться текущим состоянием и послужат триггерами для перехода между состояниями. ASF насквозь асинхронна, так что и state machine нужна асинхронная. Поиск готовых библиотек выдал не так уж много вариантов. В качестве претендентов рассматривались stateless за авторством небезызвестного Nicholas Blumhardt (он же автор Autofac и Serilog) и Appcelerate (в девичестве bbv.Common). Обе не подошли, поскольку совершенно синхронны. Есть еще асинхронный наследник stateless LiquidState, но она не подошла уже по недостатку функциональности. Очень не люблю писать велосипеды, но тут пришлось, благо несложно. За основу взято описание UML State Machine (кстати, рекомендую — это неплохая формальная модель, включающая автоматы Мили и Мура).


                  В этой части разработки проблем возникло аж две. Первая и главная — “однопоточность” актора. Формально это означает, что вызвать другой объект из актора можно, а вот из этого вызова обратно вызвать актор уже не получится. Фактически же ASF все-таки поддерживает реентерабельность актора, но только если цепочка вызовов началась с него же самого. Причем касается такое разрешение только вызовов между акторами — вызвать, скажем, сервис из актора и обратно вызвать актор опять нельзя. А такая функциональность нужна, например, для широковещательной рассылки, которую инициирует актор, а выполняет каталог, причем сообщение должно уйти и самому автору рассылки. Хорошего решения я не нашел, так что просто разделил актора на две части — одна основная, принимающая сообщения от клиента и управляющая состоянием актора, и вторая дополнительная, которая просто отсылает сообщения клиенту. В результате имеем два актора, через которых идут однонаправленные потоки данных. Типы акторов различны, поэтому адресовать их можно одинаково через SID (в ASF тип актора входит в его адрес, так что даже для одинаковых SID адреса акторов будут различны).


                  Вторая проблема — время установки соединения (в терминах ADC это переход к состоянию NORMAL). Его имеет смысл ограничить, а ситуация усугубляется еще и тем, что прародитель DC использует тот же порт, причем в DC первое сообщение отправляет именно хаб. В результате DC-клиенты, подключившись, будут ждать ответа до второго пришествия. Самое время применить таймеры и ремайндеры ASF.


                  Таймеры и ремайндеры

                  Таймеры и ремайндеры в ASF служат, как следует из их названий, будильниками. Они похожи, но только тем, что вызывают заданные функции согласно заданным интервалам. Таймер — это более легковесный объект, который существует только при активном акторе, а при деактивации актора пропадает. Ремайндер сохраняется и работает отдельно от актора, так что может активировать даже деактивированного актора. Вызовы методов актора для таймеров и ремайндеров, как и все прочие, исполняются все так же “однопоточно”, но вот вызов от ремайндера считается “полноценным” вызовом, который учитывается при деактивации бездействующих акторов. Вызов от таймера использованием актора не считается, так что таймером удержать актора от деактивации невозможно.


                  Для ограничения времени соединения проще всего использовать одноразовый ремайндер, который посылает псевдо ADC-команду ConnectionTimedOut — состояние NORMAL ее просто проглотит, а все прочие разорвут соединение согласно протоколу.


                  Каталог пользователей


                  Внешних коммуникаций сервис не имеет и предназначен исключительно для выполнения внутренних задач, так что для общения с ним логично использовать Remoting. Партиционировать каталог особого смысла нет — состояние сервиса хранит коллекцию идентификаторов, а сам сервис реализует операции над ней в целом. Однако широковещательные рассылки могут создавать нагрузку, для которой одной primary-реплики сервиса может не хватить. И вот тут можно воспользоваться любопытной особенностью stateful сервисов — они могут открывать слушателей даже на вторичных репликах, но только для чтения. Поскольку рассылки в состоянии каталога ничего не меняют, то ничто не мешает для них задействовать все реплики сервиса.


                  Список текущих SID хранится в словаре ReliableDictionary — в рамках транзакции его можно перечислить, а в значениях ключей хранится информация о пользователе (имя, поддерживаемые возможности клиента и пр.). Отдельный ReliableDictionary приходится использовать и для проверки уникальности имени клиента — просто как уникальный ключ без значений. В разработке каталога каких-то особых граблей не нашлось, но есть нюанс, связанный с отключением клиента.


                  Причин для отключения клиента всего две — это ошибка в канале связи (источник — TCP-сервер) и ошибка в протоколе (источник — актор). Обе ошибки буду считать фатальными и обрывать соединение с клиентом. Обрабатывать ошибки хотелось бы централизованно, и логично это делать в акторе, как отвечающем за протокол. Для этого можно поступить так же, как и с ремайндером — добавить еще одно псевдо ADC-сообщение DisconnectOccured и включить его в конечный автомат актора. Для полной очистки хорошо бы совсем удалять актора, но как это сделать, если инициатором отключения является сам актор? Эту функциональность можно передать в каталог, в котором воспользоваться ReliableQueue — в эту очередь складываются SID отключаемых клиентов, а в RunAsync каталога эта очередь обрабатывается в бесконечном цикле, который получает из очереди идентификаторы и удаляет соответствующих акторов.


                  Вот такой вот state выходит:



                  И в целом разработка подошла к концу. В тестировании на локальном кластере два ADC-клиента (я использовал AirDC++), подключенные к свежеиспеченному хабу, “увидели” друг друга и смогли обменяться файлами. Еще не ура, но уже можно попробовать развернуть хаб в Azure.


                  Развертывание в Azure


                  Самая скучная часть повествования, поскольку развертывание прошло как по нотам. Для развертывания использовал Free Trial подписку, управление ASF доступно с нового портала. Для начала выбирается имя кластера, данные RDP-пользователя, группа ресурсов и местоположение. Для количества узлов кластера выбрал три, поскольку “Choosing less than 5 initial VM capacity for your primary node type will designate this cluster as a test cluster.” — не нашел, что такое test cluster, но эту рекомендацию пишет сам портал. В custom endpoints надо не забыть поставить 411, чтобы load balancer пропускал соединения по этому порту. Для тестовых целей проще создать unsecure кластер — вот в целом и все. Несмотря на видимую простоту ASF-кластера в Azure создается целый набор ресурсов:



                  В список входят хранилища vhd для VM (неясно, почему их 5, а не 3), хранилища для логов и информации Azure Diagnostics, набор виртуальных машин, объединенных в VPN, и прикрывающий все это load balancer с приданным ему публичным IP-адресом.


                  Развертывание в Azure все же выявило одну проблему, которую я так и оставил нерешенной. Похоже, что Azure проверяет доступность 411 порта, создавая и сразу обрывая TCP-соединения по нему. TCP-сервер же сразу по возникновении соединения создает SID, который тут же оказывается ненужным. Теоретически это правильное поведение — сервер может быть полон (в ADC есть предел количества пользователей для одного хаба), но вот на практике такой подход дает сбой. С другой стороны, для простой проверки эти пустые соединения слишком часты — в общем, пока это тайна, покрытая мраком.


                  Впрочем, на функционирование это не влияет, так что снова тест, снова два клиента ADC, снова обмен файлами — похоже, все работает. Вот теперь совсем ура.


                  Заключение


                  В целом идея себя оправдала — удалось пройтись по всей ASF, и если не задействовать, то хотя бы ознакомиться с различным возможностями и деталями ее функционирования. И хотя не все легло гладко на ASF, но конечный результат не так уж плох для тестового проекта. Надеюсь, что и читателям было любопытно. Исходный код всего проекта можно найти на GitHub — засим прощаюсь до новых статей.

                  Комментарии (0)

                    Let's block ads! (Why?)