• Авторизация


О тонкостях обучения 22-08-2009 18:54 к комментариям - к полной версии - понравилось!


Вчера я таки капитально засел за расшифровку энцефалограмм. Я нашёл замечательную сишную библиотеку для создания многослойных полносвязных перцептронов под названием fann. Её несомненное достоинство в том, что она работает через файлы: из файла читает обучающее множество и в файл пишет обученную нейросеть.
К вопросу обучающего множества я также подошёл основательно: запись ЭЭГ велась почти 10 минут (писал я лёжа с закрытыми глазами, чтобы минимизировать сторонние факторы), за это время было набрано 867 отсчётов, среди которых записанных со звуком и без него было примерно поровну. Кстати, странный факт: при инициализации класса потока, отвечающего за рандомное воспроизведение звука, происходит проверка, за какое время происходит воспроизведение звука, а потом значение этого времени используется для того, чтобы определить время, на которое потоку надо заснуть. Должно. На практике, почему-то, для равномерного распределения пришлось это время удвоить.
После этого я начал гонять множество через нейросеть. Сначала я взял простой однослойный перцептрон. Ну, как однослойный, формально -- трёхслойный: 40 рецепторов, 40 основных нейронов и один выходной, но проще считать только вычислительные слои. Гонял я до ошибки 0,001. Это заняло не так много времени -- около десяти минут, после которых программа радостно объявила о том, что нейросеть готова. Её надо было проверять.
А записал я не только обучающее множество. Ещё я сделал две небольшие записи: с постоянно включённым звуком и постоянно выключенным. Очевидно, что хорошо обученная нейросеть должна была на большинстве отсчётов первой записи ответить, что звук выключен, а на второй -- что включён. Ответ нейросети представлял собой число: -1, если выключен и 1 если включён. Я взял тестовые множества, прогнал их через сеть и посчитал среднее значение ответа. На обоих множествах они слабо отличались от нуля. Прозреваю, что чем больше множества были бы, тем ближе к нулю устремились бы ответы. Иными словами, ответы были по сути случайными.
Потом я подумал -- а что, собственно, я хочу от однослойного перцептрона? У меня тут очень сложна функция, поэтому нужен минимум трёхслойный. Ну ладно, три так три. Сделал три слоя по 80 нейронов. Сначала попробовал тренировать до той-же ошибки 0,001. Результат на тестовых множествах был уже интереснее: около -0,1 на отключённом звуке и около 0,1 на включённом. Это меня впечатлило, и я погнал тренировать её ещё, до 0,0001. Вот это уже было серьёзно. Хоть библиотека и на Си, но моя ееешка думала почти полтора часа на максимальной производительности. И нифига. Почти в пределах статистической погрешности.
Какие можно сделать выводы?
1. Маленькое обучающее множество.
2. Неправильная топология нейросети.
3. Неправильный подход к обучению.
4. Невозможность решения задачи выбранными средствами.
Насчёт множества -- попробую исправить, может, даже где-нибудь с час буду писать.
Насчёт нейросети. Выбор топологии -- тоже хитрость, и я ей не владею. Надо думать.
Обучение. Мне уже приходила в голову мысль, что обучение с учителем в данном случае может быть неприемлем, нужна самообучающаяся сеть, пусть она сама разбирается, где что. Проблема одна: я не видел готовых библиотек для таких сетей. А чтобы писать самому, надо в нейросетях разбираться получше.
И наконец последнее. Вполне возможно, что сходу определить по энцефалограмме факт наличия или отсутствия звука вообще невозможно. Не исключено, что зафиксировать можно только переходы: если иметь две записи: со звуком и без, то определить какая из них какая можно, но по оной это сделать невозможно. Или же нужна нейросеть с обратными связями, иными словами -- с памятью. Но такие топологии -- это уже запредельный хардкор, мне бы сначала с обычными слоистыми структурами разобраться, а потом уже свои топологии лепить.
Алсо, я просто оставлю это здесь:


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pynia
import threading
import mutex
import random
import math
import time

class Singer(threading.Thread):
    def __init__(self, dsp, string_to_sing, repeats, sound_on_mutex, release_mutex):
        self.dsp = dsp
        self.str = string_to_sing
        self.reps = repeats
        self.snd = sound_on_mutex
        self.cangodrink = release_mutex
        time1 = time.time()
        dsp.write(self.str)
        self.dsp.flush()
        time2 = time.time()
        self.delay = time2 - time1
        self.cangodrink.unlock()
        threading.Thread.__init__(self)

    def run(self):
        for i in xrange(self.reps):
            if random.random() < 0.5:
                self.snd.lock(lambda x: x, None)
                self.dsp.write(self.str)
                self.dsp.flush()
                self.snd.unlock()
            else:
                self.snd.unlock()
                time.sleep(2*self.delay)
            print "Завершено", i, "итераций из", self.reps
        self.cangodrink.lock(lambda x: x, None)

class Writer(threading.Thread):
    def __init__(self, nia, sound_on_mutex, release_mutex):
        self.nia = nia
        self.snd = sound_on_mutex
        self.cangodrink = release_mutex
        self.result = []
        threading.Thread.__init__(self)

    def run(self):
        while not self.cangodrink.test():
            self.nia.record()
            self.nia.process()
            #print self.nia.Fourier_Image
            sound = int(self.snd.test())
            if sound == 0:
                sound = -1
            #print sound
            sample = [list(self.nia.Frequencies), sound]
            #print sample
            self.result.append(sample)
        print "Запись завершена. Записано", len(self.result), "отсчётов"

    def get_result(self):
        return self.result

def create_string(coeff, lng):
    lst = [chr(int(math.sin(float(x)/float(coeff))*127.0+127.0)) for x in xrange(lng)]
    string = ""
    for x in lst:
        string +=x
    return string

def main():
    sound_on_mutex = mutex.mutex()
    release_mutex = mutex.mutex()
    nia = pynia.NIA_Data(25)
    dsp = open("/dev/dsp", mode = "write")
    string_to_sing = create_string(1.5, 5000)
    singer = Singer(dsp, string_to_sing, 100, sound_on_mutex, release_mutex)
    writer = Writer(nia, sound_on_mutex, release_mutex)
    nia.calibrate()
    start_time = time.time()
    singer.start()
    writer.start()
    while True:
        if release_mutex.test():
            break
    stop_time = time.time()
    print "Время выполнения работы:", stop_time-start_time, "секунд"
    dsp.write(create_string(0.5, 15000))
    dsp.close()
    print "Звук отключён, запись результата"
    result = writer.get_result()
    print "Результат получен"
    negs = 0
    poss = 0
    for sample in result:
        if sample[1] == 1:
            poss += 1
        else:
            negs += 1
    print poss, "записей со звуком и", negs, "без"
    data = open("nia.data", mode="write")
    print "Файл для результата открыт"
    data.write("%d %d %d\n" % ( len(result), len(result[0][0]), 1 ))
    #print
    random.Random().shuffle(result)
    for sample in result:
        [inputs, output] = sample
        in_str = ""
        for inp in inputs:
            in_str += (str(inp) + ' ')
        in_str = in_str[:-1] + '\n'
        data.write(in_str)
        data.write(str(output) + '\n')
    print "Результат записан"
    data.close
    print "Выход"

if __name__ == "__main__":
    main()
вверх^ к полной версии понравилось! в evernote


Вы сейчас не можете прокомментировать это сообщение.

Дневник О тонкостях обучения | Positron - Проекция мысли | Лента друзей Positron / Полная версия Добавить в друзья Страницы: раньше»