CNN para reconhecimento de fonemas

7

Atualmente, estou estudando este artigo , no qual a CNN é aplicada para reconhecimento de fonemas usando representação visual de bancos de filtros de registros de mel e esquema de compartilhamento de peso limitado.

A visualização dos bancos de filtros de log mel é uma maneira de representar e normalizar os dados. Eles sugerem visualizar como um espectograma com cores RGB, que o mais próximo que eu poderia chegar seria matplotlibsplotá- lo usando o mapa de cores cm.jet. Eles (sendo o papel) também sugerem que cada quadro seja empilhado com suas energias do banco de filtros [static delta delta_delta]. É assim:

insira a descrição da imagem aqui

A entrada consistir em um patch de imagem de 15 quadros definido [formato delta estático delta_detlta] seria (40,45,3)

O compartilhamento de peso limitado consiste em limitar o compartilhamento de peso a uma área específica do banco de filtros, pois a fala é interpretada de maneira diferente em diferentes áreas de frequência, portanto, um compartilhamento de peso total conforme a convolução normal se aplica não funcionaria.

Sua implementação do compartilhamento limitado de pesos consiste em controlar os pesos na matriz de pesos associados a cada camada convolucional. Então eles aplicam uma convolução na entrada completa. O artigo aplica apenas uma camada convolucional, pois o uso de múltiplas destrói a localidade dos mapas de recursos extraídos da camada convolucional. A razão pela qual eles usam as energias do banco de filtros em vez do coeficiente normal do MFCC é porque o DCT destrói a localidade das energias dos bancos de filtros.

insira a descrição da imagem aqui

Em vez de controlar a matriz de peso associada à camada de convolução, optei por implementar a CNN com várias entradas. portanto, cada entrada consiste em um (pequeno intervalo de banco de filtros, total_frames_with_deltas, 3). Assim, por exemplo, o artigo declara que um tamanho de filtro de 8 deve ser bom, então decidi um intervalo de banco de filtros de 8. Portanto, cada pequeno retalho de imagem é de tamanho (8,45,3). Cada parte da pequena imagem é extraída com uma janela deslizante com um passo de 1 - para que haja muita sobreposição entre cada entrada - e cada entrada tenha sua própria camada convolucional.

insira a descrição da imagem aqui

(entrada_3, entrada_3, entrada3, deveria ter sido entrada_1, entrada_2, entrada_3 ...)

Dessa maneira, é possível usar várias camadas convolucionais, já que a localidade não é mais um problema, uma vez que é aplicada dentro de uma área de banco de filtros, essa é a minha teoria.

O artigo não afirma explicitamente, mas acho que a razão pela qual eles reconhecem fonemas em vários quadros é ter alguns dos contextos esquerdo e direito, de modo que apenas o quadro intermediário esteja sendo previsto / treinado. Portanto, no meu caso, os 7 primeiros quadros definem a janela de contexto esquerda - o quadro do meio está sendo treinado e os últimos 7 quadros são a janela de contexto correta. Assim, considerando vários quadros, apenas um fonema será reconhecido como sendo o meio.

Minha rede neural atualmente se parece com isso:

def model3():

    #stride = 1
    #dim = 40
    #window_height = 8
    #splits = ((40-8)+1)/1 = 33
    next(test_generator())
    next(train_generator(batch_size))

    kernel_number = 200#int(math.ceil(splits))
    list_of_input = [Input(shape = (window_height,total_frames_with_deltas,3)) for i in range(splits)]
    list_of_conv_output = []
    list_of_conv_output_2 = []
    list_of_conv_output_3 = []
    list_of_conv_output_4 = []
    list_of_conv_output_5 = []
    list_of_max_out = []
    for i in range(splits):
        #list_of_conv_output.append(Conv2D(filters = kernel_number , kernel_size = (15,6))(list_of_input[i]))
        #list_of_conv_output.append(Conv2D(filters = kernel_number , kernel_size = (window_height-1,3))(list_of_input[i]))
        list_of_conv_output.append(Conv2D(filters = kernel_number , kernel_size = (window_height,3), activation = 'relu')(list_of_input[i]))
        list_of_conv_output_2.append(Conv2D(filters = kernel_number , kernel_size = (1,5))(list_of_conv_output[i]))
        list_of_conv_output_3.append(Conv2D(filters = kernel_number , kernel_size = (1,7))(list_of_conv_output_2[i]))
        list_of_conv_output_4.append(Conv2D(filters = kernel_number , kernel_size = (1,11))(list_of_conv_output_3[i]))
        list_of_conv_output_5.append(Conv2D(filters = kernel_number , kernel_size = (1,13))(list_of_conv_output_4[i]))
        #list_of_conv_output_3.append(Conv2D(filters = kernel_number , kernel_size = (3,3),padding='same')(list_of_conv_output_2[i]))
        list_of_max_out.append((MaxPooling2D(pool_size=((1,11)))(list_of_conv_output_5[i])))

    merge = keras.layers.concatenate(list_of_max_out)
    print merge.shape
    reshape = Reshape((total_frames/total_frames,-1))(merge)

    dense1 = Dense(units = 1000, activation = 'relu',    name = "dense_1")(reshape)
    dense2 = Dense(units = 1000, activation = 'relu',    name = "dense_2")(dense1)
    dense3 = Dense(units = 145 , activation = 'softmax', name = "dense_3")(dense2)
    #dense4 = Dense(units = 1, activation = 'linear', name = "dense_4")(dense3)


    model = Model(inputs = list_of_input , outputs = dense3)
    model.compile(loss="categorical_crossentropy", optimizer="SGD" , metrics = [metrics.categorical_accuracy])

    reduce_lr=ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, verbose=1, mode='auto', epsilon=0.001, cooldown=0)
    stop  = EarlyStopping(monitor='val_loss', min_delta=0, patience=5, verbose=1, mode='auto')

    print model.summary()

    raw_input("okay?")
    hist_current = model.fit_generator(train_generator(batch_size),
                        steps_per_epoch=10,
                        epochs = 10000,
                        verbose = 1,
                        validation_data = test_generator(),
                        validation_steps=1)
                        #pickle_safe = True,
                        #workers = 4)

Então .. agora vem a questão ..

Eu tenho treinado a rede e só consegui obter uma validação_ precisão de 0,17, e a precisão após várias épocas acaba sendo 1,0. insira a descrição da imagem aqui (A plotagem está sendo feita no momento)

quadro fixo:

insira a descrição da imagem aqui (trama ainda está sendo feita)

Não sei por que não estou obtendo melhores resultados. Por que essa alta taxa de erros? Estou usando o conjunto de dados TIMIT que os outros também usam. Então, por que estou obtendo resultados piores?

E desculpe pelo longo post - espero que mais informações sobre minha decisão de design possam ser úteis - e ajude a entender como eu entendi o artigo e como eu me inscrevi ajudaria a identificar onde estaria meu erro.

Carlton Banks
fonte
11
Você poderia explicar a forma da entrada (40,45,3)? Eu acho que deveria ser (40,15,3) ou seja (número de bancos de filtros, contexto (esquerda + direita), 3 (estático + delta + delta_delta)).
Arduinolover
Eu acho que você deve sobre isso 3 frames. mas 45 no total desde 15x3 = 45.
Carlton Banks
Você poderia tentar repassar as saídas? Btw você usa kaldi?
Arduinolover
@arduinolover sim, eu uso o kaldi .. E está em execução no momento. as energias fbank são extraídas usando a estrutura kaldi.
Carlton Banks
Por favor, vá através deste tutorial na CNN para reconhecimento de voz github.com/botonchou/libdnn/wiki/...
arduinolover

Respostas:

2

Pode ser sua estrutura de rede:

O artigo afirma que seu experimento é realizado usando:

conv
pool
dense
dense
dense(softmax)

Então, algo assim para fws:

def fws():
    #Input shape: (batch_size,40,45,3)
    #output shape: (1,15,50)
    # number of unit in conv_feature_map = splitd

    filter_size = 8
    pooling_size = 28
    stride_step = 2
    pool_splits = ((splits - pooling_size)+1)/2
    conv_featur_map = []
    pool_feature_map = []
    print "Printing shapes"


    list_of_input = [Input(shape = (window_height,total_frames_with_deltas,3)) for i in range(splits)]


    #convolution
    shared_conv = Conv2D(filters = 150, kernel_size = (filter_size,45), activation='relu')
    for i in range(splits):
        conv_featur_map.append(shared_conv(list_of_input[i]))

    #Pooling
    input = Concatenate()(conv_featur_map)
    input = Reshape((splits,-1))(input)
    pooled = MaxPooling1D(pool_size = pooling_size, strides = stride_step)(input)

    #reshape = Reshape((3,-1))(pooled)

    #fc
    dense1 = Dense(units = 1000, activation = 'relu',    name = "dense_1")(pooled)
    dense2 = Dense(units = 1000, activation = 'relu',    name = "dense_2")(dense1)
    dense3 = Dense(units = 50 , activation = 'softmax', name = "dense_3")(dense2)

Alterar a posição do loop for em fws (movendo-o 2 linhas para cima) torna-o em lws (além de alguns ajustes com a camada de pool).

Lamda
fonte
Parece maneira mais simples do que o que eu estava indo para ...
Carlton Banks