[PYTHON] Traitement de la voix par apprentissage profond: identifions qui est l'acteur vocal à partir de la voix

Préface

L'autre jour, j'ai acheté un livre intitulé "Utilisation sur le terrain! Introduction au développement TensorFlow". C'était un livre très facile à comprendre et expliquait un large éventail de la classification d'images au GAN. Le script est un peu ancien et écrit en TensorFlow 1.x, il y a donc de nombreux endroits où il ne fonctionne pas avec l'actuel TensorFlow 2.x, Quant à Keras, il est presque compatible, il peut donc être utilisé presque sans problème après le chapitre 4.

Maintenant que j'ai réellement lu et étudié un livre, je voulais faire une sorte de sortie. Mais ce n'est pas amusant de faire la même chose. Donc, cette fois, je vais utiliser Keras pour faire l'analyse vocale qui m'intéresse personnellement.

Je voulais faire quelque chose comme devenir-yukarin qui transforme ma voix, mais d'abord C'est un peu trop difficile à faire, donc cette fois en guise d'introduction "** Identifions le doubleur à partir de la voix **" J'ai décidé du thème.

Étant donné que la plupart des livres ci-dessus étaient des analyses d'images, j'ai décidé de les mettre en œuvre dans le flux de conversion audio en images et d'apprentissage. Je n'ai jamais appris l'analyse vocale auparavant, donc je pense qu'il y a quelque chose d'étrange à ce sujet. Dans ce cas, veuillez commenter.

environnement

contour

Comme je l'ai écrit ci-dessus, cette fois, je prendrai l'approche de la conversion en image (= données bidimensionnelles). Par conséquent, implémentons-le dans le flux suivant.

  1. Collectez des données
  2. Convertissez l'audio en MFCC
  1. Formatez et ajustez les données d'image
  2. Construire et former un modèle

la mise en oeuvre

1. Collectez des données

Cette fois, nous utiliserons l'ensemble de données du site Japan Voice Actor Statistics Society.

Il s'agit d'un ensemble de données dans lequel trois acteurs de la voix (annonceurs), Maki Tsuchiya, Ayako Uemura et Tomonatsu Fujito, ont enregistré 100 lignes dans une pièce silencieuse avec les trois émotions «normal», «joie» et «colère». Il y a un total de 900 données avec 100 lignes x 3 personnes x 3 émotions. (Depuis que je lis Wikipédia à haute voix, quand j'entends exprimer des sentiments de joie et de colère avec des lignes dénuées de sens, je me sens bien en tant qu'acteur.)

Cette fois, faisons un modèle qui peut distinguer ces trois personnes. Les données sont «* .tar.gz», mais elles peuvent être décompressées sous Windows en utilisant un logiciel de décompression.

2. Convertissez l'audio en MFCC

Convertissons maintenant les données en quelque chose appelé MFCC (Mel Frequency Keptram Coefficient). Si vous expliquez MFCC en détail, ce sera long, donc je ne l'expliquerai que comme "quantité de fonctionnalités vocales". Pour convertir des données audio (wav) en une valeur numérique appelée MFCC, utilisez quelque chose appelé librosa.

pip install librosa

Peut être installé avec.

2.1. Lecture des données

Tout d'abord, lisons les données. Par exemple, chargez fujitou_normal_001.wav.

from matplotlib import pyplot as plt
import librosa
import librosa.display
import os

#Ecrire le chemin des données
WAV_DATA_PATH = os.path.join("Dataset","fujitou_normal","fujitou_normal_001.wav") 

x, fs = librosa.load(WAV_DATA_PATH, sr=44100)

librosa.display.waveplot(x, sr=fs, color='blue');

wavdata.png

La valeur de retour x de librosa.load est les données (le format est numpy), et fs est le taux d'échantillonnage. Vous pouvez maintenant importer des données Wav.

2.2. Conversion au MFCC

Convertissons ces données en MFCC.

mfccs = librosa.feature.mfcc(x, sr=fs)
librosa.display.specshow(mfccs, sr=fs, x_axis='time')
plt.colorbar();

mfcc.png

Pour MFCC, l'axe horizontal est le temps et l'axe vertical est la valeur à 20 dimensions.

print(mfccs.shape) # -> (20, 630)

2.3. Mise en forme des données

Excluons les données de la première dimension (en bas du graphique). Je n'expliquerai pas cela en détail non plus, mais comme vous pouvez le voir sur le graphique, la plage de données est devenue importante en raison des données de la première dimension, ce qui rend difficile la compréhension des caractéristiques des valeurs au-dessus de la première dimension. ..

mfccs = mfccs[1:]
librosa.display.specshow(mfccs, sr=fs, x_axis='time')
plt.colorbar();

mfcc_skip.png

De plus, comme vous pouvez le voir à partir des données wav, il n'y a presque pas de son après 7,5 secondes. Cette zone est également un obstacle, alors coupons-la.

import numpy as np

def cut_silence(wavdata, eps=0.01):
    st = 0
    gl = len(wavdata)
    data = np.abs(wavdata)
    threshold = np.max(data) * eps
    for i,a in enumerate(data):
        if a > threshold:
            st = i - 1
            break

    for i,a in reversed(list(enumerate(data))):
        if a > threshold:
            gl = i
            break
    return wavdata[st:gl]

Cette fois, les valeurs «de 0 seconde à la valeur maximale des données x 0,01 ou plus» et «de la fin à la valeur maximale des données x 0,01 ou plus» ont été coupées.

x = cut_silence(x)
librosa.display.waveplot(x, sr=fs, color='blue');

Cela donne au graphique l'aspect suivant: wav_cut.png

La conversion en mfccs donne à nouveau:

mfccs = librosa.feature.mfcc(x, sr=fs)
mfccs = mfccs[1:]
librosa.display.specshow(mfccs, sr=fs, x_axis='time')
plt.colorbar();

mfcc_skip_cut.png

Avec cela, il était possible de créer des données d'image.

2.4. Convertir et enregistrer toutes les données

Comme MFCC est un processus qui prend beaucoup de temps, je pense qu'il est plus facile de le sauvegarder en tant que données numpy (* .npy) après avoir utilisé librosa.feature.mfcc. Convertissons toutes les données et sauvegardons-les dans des données numpy. La structure de répertoires suivante est supposée.

.
|-Dataset
|   |-fujitou_angry
|   |   |-fujitou_angry_001.wav
|   |   |-fujitou_angry_002.wav
|   |   |-fujitou_angry_003.wav
|   |   |-...
|   |
|   |-fujitou_happy
|   |   |-fujitou_happy_001.wav
|   |   |-...
|   |
|   |-...
|
|-ImageData
|   |-fujitou_angry
|   |   |-fujitou_angry_001.npy
|   |   |-fujitou_angry_002.npy
|   |   |-fujitou_angry_003.npy
|   |   |-...
|   |
|   |-fujitou_happy
|   |   |-fujitou_happy_001.npy
|   |   |-...
|   |
|   |-...
|

Tout d'abord, utilisez ʻos.listdir` pour obtenir le chemin de toutes les données.

import os
import random

DATASET_DIR="Dataset"

wavdatas = []

dirlist = os.listdir(DATASET_DIR)
for d in dirlist:
    d = os.path.join(DATASET_DIR, d)
    datalist = os.listdir(d)
    y = [d[d.find("\\")+1:d.find("_")], d[d.find("_") + 1:]] #Détermination des données de réponse correctes à partir du nom de fichier
    datalist = [[os.path.join(d,x), y] for x in datalist]
    wavdatas.extend(datalist)

Ensuite, créez un répertoire pour placer des données numpy.

IMAGE_DATA = "ImageData"

dirlist = os.listdir(DATASET_DIR)
for d in dirlist:
    os.makedirs(os.path.join(IMAGE_DATA, d), exist_ok=True)

Puis convertissez toutes les données et disons np.save.

def get_mfcc(datadir):
    x, fs = librosa.load(datadir, sr=44100)
    x = cut_silence(x)
    mfccs = librosa.feature.mfcc(x, sr=fs)
    mfccs = mfccs[1:]
    return mfccs, x, fs

nn = len(wavdatas)
for i, data in enumerate(wavdatas):
    path_list = data[0].split("\\")
    path_list[0] = IMAGE_DATA
    path_list[2] = path_list[2].replace(".wav", ".npy")
    image_path = "\\".join(path_list)
    mfcc,x,fs = get_mfcc(data[0])
    if i%10 == 0:
        print(i, "/", nn)
    np.save(image_path, mfcc)

Vous devriez maintenant avoir des données, comme indiqué dans l'image ci-dessous.

directory.png

3. Formatez et ajustez les données d'image

Avec cela, les données peuvent être transformées en données bidimensionnelles. Ensuite, formatez les données bidimensionnelles afin qu'elles soient faciles à manipuler.

3.1. Lecture des données

Commençons par charger les données numpy enregistrées ci-dessus.

IMAGE_DATA = "ImageData"
numpy_datas = []

dirlist = os.listdir(IMAGE_DATA)
for d in dirlist:
    d = os.path.join(IMAGE_DATA, d)
    datalist = os.listdir(d)
    datalist = [[np.load(os.path.join(d,x)), os.path.join(d,x)] for x in datalist]
    numpy_datas.extend(datalist)

3.2. Normalisation

Premièrement, les données ci-dessus sont comprises entre -200 et 100. Normalisons cela dans la plage 0 ~ 1.

#Obtenez les valeurs maximales et minimales pour l'ensemble des données
data = numpy_datas[0][0]
maximum = np.max(data)
minimum = np.min(data)
for i, data in enumerate(numpy_datas):
    M = np.max(data[0])
    m = np.min(data[0])
    if maximum < M:
        maximum = M
    if minimum > m:
        minimum = m

# 0~Dans la gamme de 1
normalize = lambda x: (x - minimum)/(maximum - minimum)
for i, data in enumerate(numpy_datas):
    numpy_datas[i][0] = normalize(data[0])

3.3. Aligner la taille des données

Les données créées dans la section 2 sont de 19 $ \ fois T $. Ici, $ T $ est le temps (pour être exact, secondes x taux d'échantillonnage). Pour simplement plonger dans un réseau de neurones, il est plus facile de comprendre si toutes les tailles de données sont identiques.

from PIL import Image
import numpy as np

img_datas = []

for i,data in enumerate(numpy_datas):
    imgdata = Image.fromarray(data[0])
    imgdata = imgdata.resize((512,19))
    numpy_datas[i][0] = np.array(imgdata)

Ici, tous ont été convertis en données 512 × 19.

3.4. Sauvegarde des données d'image

Sauvegardez les données comme dans la section 2.

Commencez par créer un répertoire.

NORMALIZE_DATA = "NormalizeData"

dirlist = os.listdir(DATASET_DIR)
for d in dirlist:
    os.makedirs(os.path.join(NORMALIZE_DATA, d), exist_ok=True)

Et enregistrer.

for i, data in enumerate(numpy_datas):
    path_list = data[1].split("\\")
    path_list[0] = NORMALIZE_DATA
    image_path = "\\".join(path_list)
    np.save(image_path, data[0])

4. Construire et former un modèle

Apprenons avec l'apprentissage en profondeur

4.1. Séparation des données d'entraînement et des données d'essai

Divisons 900 données en données d'entraînement, données de test et données de vérification.

import numpy as np
import random, os

NORMALIZE_DATA="NormalizeData"

N_TRAIN = 0.8
N_TEST = 0.1
N_VALID = 0.1

train_data = []
test_data = []
valid_data = []

dirlist = os.listdir(NORMALIZE_DATA)
for d in dirlist:
    d = os.path.join(NORMALIZE_DATA, d)
    datalist = os.listdir(d)
    y = [d[d.find("\\")+1:d.find("_")], d[d.find("_") + 1:]] #Détermination des données de réponse correctes à partir du nom de fichier
    datalist = [[np.load(os.path.join(d,x)), y, os.path.join(d,x)] for x in datalist]
    random.shuffle(datalist)
    train_data.extend(datalist[:int(len(datalist)*N_TRAIN)])
    test_data.extend(datalist[int(len(datalist)*N_TRAIN): int(len(datalist)*N_TRAIN) + int(len(datalist)*N_TEST)])
    valid_data.extend(datalist[int(len(datalist)*N_TRAIN) + int(len(datalist)*N_TEST): ])

random.shuffle(train_data)
random.shuffle(test_data)
random.shuffle(valid_data)

Toutes les données ont été remplacées par des données d'entraînement: données de test: données de vérification = 0,8: 0,1: 0,1. Puisque 900 données sont utilisées, il est de 720: 90: 90. Il est également mélangé deux fois pour une bonne distribution. Après avoir récupéré les données dans un répertoire, elles sont mélangées une fois, puis à nouveau mélangées après avoir rejoint.

--Données d'entrée: train_datas [i] [0]

Il a une structure de données comme celle-ci.

4.2. Convertir les entrées et corriger les données pour la formation

Avant d'apprendre avec les keras de tensorflow, convertissons les données d'entrée et de réponse correcte pour une facilité d'utilisation. Cette fois, train_datas [i] [1] [0] est la réponse correcte pour classer les noms des acteurs vocaux.

#Convertir en numpy
input_data_train = np.array([train_data[i][0] for i in range(len(train_data))])
input_data_test = np.array([test_data[i][0] for i in range(len(test_data))])
input_data_valid = np.array([valid_data[i][0] for i in range(len(valid_data))])

#Répertorier les bonnes réponses
label_data_train = [train_data[i][1][0] for i in range(len(train_data))]
label_data_test = [test_data[i][1][0] for i in range(len(test_data))]
label_data_valid = [valid_data[i][1][0] for i in range(len(valid_data))]

Modifions les données de réponse correctes en 1-hot.

from tensorflow.keras.utils import to_categorical

label_dict={"tsuchiya": 0, "fujitou":1, "uemura":2}

label_no_data_train = np.array([label_dict[label] for label in label_data_train])
label_no_data_test = np.array([label_dict[label] for label in label_data_test])
label_no_data_valid = np.array([label_dict[label] for label in label_data_valid])

label_no_data_train = to_categorical(label_no_data_train, 3)
label_no_data_test = to_categorical(label_no_data_test, 3)
label_no_data_valid = to_categorical(label_no_data_valid, 3)

4.3. Construction / apprentissage du modèle

Construisons maintenant un modèle pour le réseau neuronal. Je ne connais rien de tel que les règles de fer, alors j'ai décidé de répéter l'opération de convolution et de normalisation pour le moment. La fonction d'activation utilise relu.

from tensorflow.keras.layers import Input, Conv2D, Conv2DTranspose,\
                                    BatchNormalization, Dense, Activation,\
                                    Flatten, Reshape, Dropout
from tensorflow.keras.models import Model

inputs = Input((19,512))
x = Reshape((19,512,1), input_shape=(19,512))(inputs)
x = Conv2D(12, (1,4), strides=(1,2), padding="same")(x)
x = BatchNormalization()(x)
x = Activation("relu")(x)
x = Conv2D(12, (1,4), strides=(1,2), padding="same")(x)
x = BatchNormalization()(x)
x = Activation("relu")(x)
x = Conv2D(12, (2,2), strides=(1,1), padding="same")(x)
x = BatchNormalization()(x)
x = Activation("relu")(x)
x = Conv2D(12, (2,2), strides=(1,1), padding="same")(x)
x = BatchNormalization()(x)
x = Activation("relu")(x)
x = Flatten()(x)
x = Dense(3)(x)
output = Activation("softmax")(x)

model = Model(inputs=inputs, outputs=output)
model.summary()

Le résultat de model.summary () est le suivant.

Model: "functional_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 19, 512)]         0         
_________________________________________________________________
reshape (Reshape)            (None, 19, 512, 1)        0         
_________________________________________________________________
conv2d (Conv2D)              (None, 19, 256, 12)       60        
_________________________________________________________________
batch_normalization (BatchNo (None, 19, 256, 12)       48        
_________________________________________________________________
activation (Activation)      (None, 19, 256, 12)       0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 19, 128, 12)       588       
_________________________________________________________________
batch_normalization_1 (Batch (None, 19, 128, 12)       48        
_________________________________________________________________
activation_1 (Activation)    (None, 19, 128, 12)       0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 19, 128, 12)       588       
_________________________________________________________________
batch_normalization_2 (Batch (None, 19, 128, 12)       48        
_________________________________________________________________
activation_2 (Activation)    (None, 19, 128, 12)       0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 19, 128, 12)       588       
_________________________________________________________________
batch_normalization_3 (Batch (None, 19, 128, 12)       48        
_________________________________________________________________
activation_3 (Activation)    (None, 19, 128, 12)       0         
_________________________________________________________________
flatten (Flatten)            (None, 29184)             0         
_________________________________________________________________
dense (Dense)                (None, 3)                 87555     
_________________________________________________________________
activation_4 (Activation)    (None, 3)                 0         
=================================================================
Total params: 89,571
Trainable params: 89,475
Non-trainable params: 96
_________________________________________________________________

Laissez-les apprendre.

```python
model.compile(
    optimizer="adam",
    loss="categorical_crossentropy"
)

model.fit(
    input_data_train,
    label_no_data_train,
    batch_size=30,
    epochs=50,
    validation_data=(input_data_valid, label_no_data_valid)
)

4.4.Prédiction de précision avec les données de test

Enfin, faisons une prédiction en utilisant les données de test que nous avons créées en premier.

out = model.predict(input_data_test)
predict = np.argmax(out, axis=1)
answer =np.argmax(label_no_data_test, axis=1)

print("correct:", np.sum(predict == answer), "/", len(predict))
print("rate:", np.sum(predict == answer)/len(predict) * 100, "%")
correct: 90 / 90
rate: 100.0 %

J'ai pu répondre correctement à toutes les questions. Génial.

Je l'ai essayé plusieurs fois, mais c'est environ 90%La précision ci-dessus est sortie. Si vous réglez un peu plus le modèle, ce sera certainement 100%Je pense que je peux y arriver.

4.5.Classification des émotions

Alors, essayons de classer non seulement le nom du doubleur mais aussi l'émotion.

Republier

-Des données d'entrée: train_datas[i][0] -Corriger les données de réponse: train_datas[i][1] -Nom du doubleur: train_datas[i][1][0] -Sentiments: train_datas[i][1][1]

Detrain_datas[i][1][1]Essayons de donner des réponses correctes. Le résultat est,

correct: 88 / 90
rate: 97.77777777777777 %

Et le même résultat a été obtenu.

#Résumé Cette fois, en classant les acteurs de la voix à partir de la voix, nous avons converti la voix en image et utilisé un réseau de neurones utilisant une couche de convolution. Le résultat est 90%Sur ce, je pense que je l'ai bien dit.

À l'avenir, j'aimerais pouvoir distinguer les voix de Yui Ogura en utilisant les voix de l'anime et de la radio.

Recommended Posts

Traitement de la voix par apprentissage profond: identifions qui est l'acteur vocal à partir de la voix
Déterminer s'il s'agit de mon enfant à partir de l'image du chien Shiba par apprentissage profond (3) Visualisation par Grad-CAM
Déterminez s'il s'agit de mon enfant à partir de l'image du chien Shiba par apprentissage profond (4) Visualisation par Grad-CAM et Grad-CAM guidé
[Deep Learning from scratch] J'ai implémenté la couche Affine
Apprendre en profondeur à partir des bases mathématiques Partie 2 (pendant la fréquentation)
Othello ~ De la troisième ligne de "Implementation Deep Learning" (4) [Fin]
La méthode de copie de pandas.DataFrame est une copie profonde par défaut
Apprentissage profond à partir de zéro
(Deep learning) J'ai collecté des images de l'API Flickr et essayé de discriminer en transférant l'apprentissage avec VGG16
[Deep Learning from scratch] Implémentez le traitement de rétropropagation dans le réseau neuronal par la méthode de propagation de retour d'erreur
[Partie 4] Utilisez le Deep Learning pour prévoir la météo à partir d'images météorologiques
[Partie 1] Utilisez le Deep Learning pour prévoir la météo à partir d'images météorologiques
[Partie 3] Utilisez le Deep Learning pour prévoir la météo à partir d'images météorologiques
Créez une IA qui identifie le visage de Zuckerberg grâce à l'apprentissage en profondeur ③ (Apprentissage des données)
[Partie 2] Utilisez le Deep Learning pour prévoir la météo à partir d'images météorologiques
[Deep Learning from scratch] À propos des couches requises pour implémenter le traitement de rétropropagation dans un réseau neuronal
Apprentissage profond à partir de zéro 1 à 3 chapitres
Cherchons à partir de la ligne
Juger si c'est mon enfant à partir de l'image du chien Shiba par apprentissage en profondeur (2) Augmentation des données / transfert d'apprentissage / réglage fin
Version Lua Apprentissage profond à partir de zéro Partie 6 [Traitement d'inférence de réseau neuronal]
Évaluer la précision du modèle d'apprentissage par test croisé de scikit learn