Вчера я таки капитально засел за расшифровку энцефалограмм. Я нашёл замечательную сишную библиотеку для создания многослойных полносвязных перцептронов под названием 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 ))
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()