Литвек - электронная библиотека >> Евгений Столов >> Самиздат, сетевая литература >> Обнаружение скрытых эмоций в голосе >> страница 3
oneStep(T):

'''

One step of the Otsu algo

0<T<NumBins

'''

Bins1 = Bins[: T]

Bins2 = Bins[T:]

Prob1 = Bins1.sum()

Prob2 = Bins2.sum()

Aver1 = sum(Bins1 * Middles[: T])/Prob1

Aver2 = sum(Bins2 * Middles[T: ])/Prob2

return Prob1 * (Aver1 — Aver)**2 \

+ Prob2 *(Aver2 — Aver) **2

Bins = np.float_(Bins)

NumBins = len(Bins)

Middles = np.zeros(NumBins)

for I in range(len(Middles)):

Middles[I] = Interv[I] + Interv[I+1]

Middles *= 0.5

BinsSum = Bins.sum()

Bins /= BinsSum # Probabilities

Aver = sum(Bins * Middles)

Results = np.zeros(NumBins — 1)

for I in range(1,NumBins):

Results[I -1] = oneStep(I)

MxRes = np.amax(Results)

Pos = np.where(Results == MxRes)

return Middles[Pos[0][0] + 1]

Посмотрим на результат обработки того-же файла с помощью алгоритма Отсу

Bins,Interv = np.histogram(Std)

Level = otsu(Bins,Interv)

Result = np.where(Std<Level,0,1)

Мы разбили все интервалы на два класса, и теперь можем раскрасить тот же файл согласно новом разбиению. Полученный график имеет вид схожий с рисунком, полученным на основе kmeans.


Обнаружение скрытых эмоций в голосе. Иллюстрация № 3

Разбиение на слова

Имея классификацию интервалов, можно попытаться выделить отдельные слова в файле. В основе процедуры "разделение" лежит следующая гипотеза. Слова разделяются последовательностью ШИ. Если ШИ оказался внутри слова, то это интервал между слогами. ШИ предшествующие слову и завершающие его включаются в слово. Заменяя каждый интервал нулем и единицей в зависимости от отнесения его к шуму или информации, получим ступенчатую последовательность Step. Разбиение на слова производится на основе этой последовательности.

Первая проблема, которую нужно решить — найти длины интервалов из нулей (или единиц) в этой ступенчатой последовательности. Таким образом вычисляется истинная длина интервала между отдельными информационным отрезками файла. Фактически мы пытаемся выделить границы слова, поэтому ищем интервалы, состоящие из 1. Такие интервалы назовем сегментами. Выделение сегментов из ступенчатой функции осуществляется с помощью функций 𝑠𝑒𝑙𝑒𝑐𝑡𝑆𝑒𝑔𝑚 и 𝑝𝑎𝑖𝑟𝑠11.

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

def selectSegm(Step):

Marks2Str = [str(X) for X in Step]

Str = ''.join(Marks2Str)

Segms = pairs11(Str)

return Segms

Теперь в этой строке надо найти начало и конец каждого интервала, состоящего из 1.

def pairs11(A):

Out = []

if A[-1] == '1':

A = A + '0'

if A[0] == '1':

End =A.find('10')

Out.append(((0,End+1)))

Beg = End + 1

else:

Beg = 0

while True:

Beg = A.find('01',Beg)

if Beg == -1:

break

else:

End = Beg+1

End = A.find('10',End)

if End == -1:

break

else:

Out.append([Beg+1,End+1])

Beg = End +1

return Out

Вот пример, поясняющий работу этой функции. Исходная последовательность Step = np.int_([1,0,0,1,1,1,0,1,1]) превращается в строку A=’100111011’, а затем находим границы интервалов из 1.

Pairs = selectSegm(А)

print(Pairs)

[(0, 1), [3, 6], [7, 9]]

Следующий шаг является эмпирическим. Дело в том, что среди информационных интервалов, принадлежащих одному слову, может случайно попасть один или несколько интервалов из класса 0. Это приведет к разрыву слова на несколько частей. Чтобы избежать этого явления, вводим эмпирический параметр 𝐿𝑖𝑚, определяющий максимальный возможный разрыв между парами информационных интервалов одного слова. На данном этапе нам известны начала и концы сегментов из единиц. Если конец сегмента отстоит менее чем на 𝐿𝑖𝑚 интервалов от начала следующего сегмента, то оба сегмента объединяются путем замены интервалов между сегментами единицами. Наконец, заменив каждый интервал соответствующим отрезком исходного потока, получаем разбиение этого потока на слова. При частоте стробирования Fr=44100 и длине интервалов Fr/1000 выбор 𝐿𝑖𝑚 осуществляется из промежутка [25,45]. В результате проделанных манипуляций получаем функцию wordBorders(In, SizeFragm), возвращающую список пар из начал и концов «слов». Слово взято в кавычки, поскольку таким образом могут быть выделены как фрагменты слова, произнесенного по слогам, так и целые предложения. Как будет показано ниже, это не имеет принципиального значения.

Описание больших фрагментов файла

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

При втором подходе на входе в систему используют «сырые» данные, исходный речевой файл, а для анализа применяют сложную нейронную сеть, требующую значительных вычислительных ресурсов (на этапе тренировки). Второй подход становится превалирующим в настоящее время, поскольку его применение сводится к выбору подходящей архитектуры сети, не вникая в сущность проблемы. Автор книги придерживается первого подхода, поскольку он предполагает наличие скромных вычислительных ресурсов, и данный раздел посвящён сбору необходимых параметров, связанных с сущностью проблемы. Выше было показано, каким образом исходный файл разбивается на фрагменты, условно названные словами. Теперь определим параметры, связанные с каждым из таких фрагментов.

Энергия сигнала

Если сигнал представлен numpy массивом, то средняя энергия вычисляется с помощью функции

def myEnergy(In):

Ln =len(In)

return sum(In*In)/ln

Частота основного тона

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