Instalar la IA de MediNako
Ahora procederemos a Instalar la IA de MediNako y para poder utilizar la IA de MediNako se requiere tener instalado Python 3.6 a 64bits ademas es necesario tener instalados TensorFlow y MySQL Connector. A continuación, se describen brevemente los pasos para instalar cada uno de estos componentes:
Instalar Python
Instalar Python: Python es un lenguaje de programación utilizado en el desarrollo de la IA de MediNako. Para instalar Python, se puede descargar el instalador correspondiente a la versión deseada desde el sitio web oficial de Python y seguir las instrucciones del instalador.
Además debemos actualizar las librerías de pip asi:
python -m pip install --upgrade pip
Instalar TensorFlow: TensorFlow es una plataforma de código abierto para el aprendizaje automático desarrollada por Google. Para instalar TensorFlow, se puede utilizar este comando.
pip install tensorflow
Instalar MySQL Connector: MySQL Connector es una biblioteca que permite la conexión de Python con la base de datos MySQL. Para instalar MySQL Connector, se puede utilizar este comando.
pip install mysql-connector
Instalar Flask: Flask es un popular framework web escrito en Python que permite a los desarrolladores construir aplicaciones web de manera rápida y sencilla que MediNako utiliza para mostrar la informacion de las mediciones tomadas. Su instalación es la siguiente:
pip install flask
Instalar librosa: Librosa es una biblioteca de Python utilizada para el análisis y procesamiento de señales de audio que MediNako usa para manipular las señales de audio recibidas y la instalamos de de la siguiente manera:
pip install librosa
Instalar Pandas: Pandas es una biblioteca de Python utilizada para el análisis y manipulación de datos que MediNako usa para manipular la informacion de la sondas de audio y permitir que la inteligencia artifical nos entregue las predicciones. Inhalaremos Pandas asi:
pip install pandas
Es importante tener en cuenta que la instalación de estos componentes puede variar según el sistema operativo que se esté utilizando. También es recomendable verificar que se están instalando las versiones más recientes y compatibles de cada componente para asegurar su correcto funcionamiento con la IA de MediNako.
Configuración de la base de datos
Creamos una base de datos en MariaDB llamada medinako asi:
CREATE DATABASE IF NOT EXISTS `medinako` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE `medinako`;
Creamos una tabla llamada mediciones así:
DROP TABLE IF EXISTS `mediciones`; CREATE TABLE `mediciones` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ip` varchar(100) NOT NULL, `idDispositivo` varchar(100) NOT NULL, `archivoAudio` varchar(200) NOT NULL, `estado` varchar(100) NOT NULL, `fechaCreacion` timestamp NOT NULL DEFAULT current_timestamp(), PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
Ejecutar archivos de Python
Descargamos y descomprimimos los archivo desde github.com medinako asi:
python Entrenar.py python LeeParaEntrenar.py python Predecir.py
CargaModelo.py
from keras.models import load_model import librosa import numpy as np import pandas as pd #Extraemos los MFCC de cada archivo (osea convertimos el audio en una representacion grafica) def extract_features(file_name): try: audio, sample_rate = librosa.load(file_name, res_type='kaiser_fast') mfccs = librosa.feature.mfcc(y=audio, sr=sample_rate, n_mfcc=40) mfccsscaled = np.mean(mfccs.T,axis=0) except Exception as e: print("Error encountered while parsing file: ", file_name) return None return mfccsscaled def nivelDeLleno(rutaModelo,archivoAudio): model = load_model(rutaModelo) datosAudio = [] #Extraemos el MFCC y los guardamos en rasgosArchivo rasgosArchivo = extract_features(archivoAudio) datosAudio.append([rasgosArchivo]) datosAudioDataframe = pd.DataFrame(datosAudio, columns=['feature']) datosAudioNP = np.array(datosAudioDataframe.feature.tolist()) prediccion = model.predict(datosAudioNP) listaEstados = np.array(['Vacio', 'Medio', 'Lleno'], dtype=np.object) numeroPrediccion = np.argmax(prediccion, axis = 1) exactitud=prediccion[0][numeroPrediccion[0]] return listaEstados[numeroPrediccion[0]],exactitud #print(listaEstados[numeroPrediccion[0]]) #estadoLleno=nivelDeLleno(rutaModelo='saved_models/weights.best.basic_mlp.hdf5',archivoAudio='audio/medio_05.wav') #print(estadoLleno)
Conexion.py
host="localhost" user="root" passwd="" database="medinako"
Entrenar.py
''' ENTRENAR: CREA EL ARCHIVO hdf5 EXCLUSIVO DE ESTE RECIPIENTE 1.- LEEMOS LOS ARCHIVOS VACIO,MEDIO,LLENO (20 ARCHIVOS DE CADA UNO) 2.- EXTRAEMOS LOS MFCC DE CADA ARCHIVO 3.- GUARDAMOS LOS MFCC EN UNA MATRIZ 4.- GUARDAMOS LA MATRIZ EN UN ARCHIVO 5.- CARGAMOS EL MODELO 6.- ENTRENAMOS EL MODELO 7.- GUARDAMOS EL MODELO ''' import os from flask import Flask,request from os import mkdir import librosa import numpy as np import pandas as pd import mysql.connector import Conexion mydb = mysql.connector.connect( host=Conexion.host, user=Conexion.user, passwd=Conexion.passwd, database=Conexion.database ) cantidadAudios=20 #Extraemos los MFCC de cada archivo (osea convertimos el audio en una representacion grafica) def extract_features(file_name): try: audio, sample_rate = librosa.load(file_name, res_type='kaiser_fast') mfccs = librosa.feature.mfcc(y=audio, sr=sample_rate, n_mfcc=40) mfccsscaled = np.mean(mfccs.T,axis=0) except Exception as e: print("Error encountered while parsing file: ", file_name) return None return mfccsscaled app=Flask(__name__) @app.route('/listo-entrenar') def index(): macAddress=request.args.get('mac') macAddressDir=macAddress.replace(':', '') #macAddressDir="8CAAB5D6F806" carpeta="audioRecibido/"+macAddressDir vacio=0 medio=0 lleno=0 for estado in ["Vacio", "Medio", "Lleno"]: for i in range(0, cantidadAudios, 1): nombreArchivo=carpeta+"/"+estado+str(i)+".wav" if os.path.isfile(nombreArchivo): print("El archivo existe:",nombreArchivo) if estado=="Vacio": vacio=vacio+1 elif estado=="Medio": medio=medio+1 elif estado=="Lleno": lleno=lleno+1 else: print("El no archivo existe.",nombreArchivo) resultado="{\"data\":[" resultado=resultado+"{\"Vacio\":\""+str(vacio)+"\",\"Medio\":\""+str(medio)+"\",\"Lleno\":\""+str(lleno)+"\"}" resultado=resultado+"]}" return resultado @app.route('/entrenar') def index2(): features=[] macAddress=request.args.get('mac') macAddressDir=macAddress.replace(':', '') #Creamos el nombre del modelo archivoModeo='modelos/'+macAddressDir+'.hdf5' #Si el archivo del modelo existe lo borramos if os.path.exists(archivoModeo): os.remove(archivoModeo) carpeta="audioRecibido/"+macAddressDir+"/" class_label=1 for estado in ["Vacio", "Medio", "Lleno"]: for i in range(0, cantidadAudios, 1): #file_name=carpeta+"/"+estado+str(i)+".wav" file_name = os.path.join(os.path.abspath(carpeta),estado+str(i)+".wav") if os.path.isfile(file_name): #print("file_name=",file_name,"----class_label=",class_label) #Extraemos el MFCC y los guardamos en data data = extract_features(file_name) #Insertamos en el arreglo el valor del MFCC junto a su etiqueta features.append([data, str(class_label)]) class_label=class_label+1 # convertimos el arreglo en un DataFrame de pandas y lo dividimos en 2 columnas ('feature','class_label') featuresdf = pd.DataFrame(features, columns=['feature','class_label']) print('finalizo la extraccion de ', len(featuresdf), ' archivos') features = featuresdf.loc[1] print(list(features)) ############################ #Convertir los datos y etiquetas ############################ ''' Para transformar los datos categóricos a numéricos usaremos «LabelEncoder» y así conseguiremos que el modelo sea capaz de entenderlos. ''' from sklearn.preprocessing import LabelEncoder from tensorflow.keras.utils import to_categorical # Convertimos los rasgos MFCC a arreglo numpy X = np.array(featuresdf.feature.tolist()) # Convertimos las etiquetas a arreglo numpy y = np.array(featuresdf.class_label.tolist()) # Codificamos las etiquetas con sklearn.preprocessing para que coloque un 1 y lo demas lo rellene con 0 por ejemplo le = LabelEncoder() yy = to_categorical(le.fit_transform(y)) print("Mostramos el valor de \"X\" que son los rasgos en MFCC") print(X) print("Mostramos el valor de \"y\" que son las etiquetas osea classID que extrajimos de UrbanSound8K_csv") print(y) print("Mostramos el valor de \"yy\" que son las etiquetas (classID) pero transformadas con sklearn.preprocessing. Por ejemplo:") print("El classID=3 no dara una arreglo asi [0.0.0.1.0.0.0.0.0.0]") print("El classID=2 no dara una arreglo asi [0.0.1.0.0.0.0.0.0.0]") print("El classID=1 no dara una arreglo asi [0.1.0.0.0.0.0.0.0.0]") print(yy) ########################################### #Dividir los datos en entrenamiento y test ########################################### #Dividimos el conjunto de datos en dos bloques (80% y 20%) y de ellos sacamos valores de X y de Y from sklearn.model_selection import train_test_split x_train, x_test, y_train, y_test = train_test_split(X, yy, test_size=0.2, random_state = 42) ##################### # Construir el modelo ##################### ''' Construimos una red neuronal mediante un perceptrón multicapa (MLP) usando Keras y un backend de Tensorflow. Se plantea un modelo secuencial para que podamos construir el modelo capa por capa. Se plantea una arquitectura de modelo simple, compuesta por: - Capa de entrada con 40 nodos, ya que la función MFCC de extracción de características nos devuelve un conjunto de datos de 1×40 - Capas ocultas de 256 nodos, estas capas tendrán una capa densa con una función de activación de tipo ReLu, (se ha demostrado que esta función de activación funciona bien en redes neuronales). También destacar que aplicaremos un valor de Dropout del 50% en nuestras dos primeras capas. Esto excluirá al azar los nodos de cada ciclo de actualización, lo que a su vez da como resultado una red que es capaz de responder mejor a la generalización y es menos probable que se produzca sobreajuste en los datos de entrenamiento. - Capa de salida de 10 nodos, que coinciden con el número de clasificaciones posibles. La activación es para nuestra capa de salida una función softmax. Softmax hace que la salida sume 1, por lo que la salida puede interpretarse como probabilidades. El modelo hará su predicción según la opción que tenga la mayor probabilidad ''' from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten from tensorflow.keras.layers import Convolution2D, MaxPooling2D from tensorflow.keras.optimizers import Adam from sklearn import metrics num_labels = yy.shape[1] # Declaramos un modelo model = Sequential() #Creamos una capa que recibira 40 nodos de entrada y 256 capas ocultas model.add(Dense(256, input_shape=(40,))) model.add(Activation('relu')) model.add(Dropout(0.5)) model.add(Dense(256)) model.add(Activation('relu')) model.add(Dropout(0.5)) model.add(Dense(num_labels)) model.add(Activation('softmax')) ##################### #Compilar el modelo ##################### ''' Para compilar nuestro modelo, utilizaremos los siguientes tres parámetros: - Función de pérdida: utilizaremos categorical_crossentropy. Esta es la opción más común para la clasificación. Una puntuación más baja indica que el modelo está funcionando mejor. - Métricas: utilizaremos la métrica de accuracy que nos permitirá ver la precisión en los datos de validación cuando entrenemos el modelo. - Optimizador: aquí usaremos adam, que generalmente es un buen optimizador para muchos casos de uso. ''' # Compilamos el modelo model.compile(loss='categorical_crossentropy', metrics=['accuracy'], optimizer='adam') # Imprimimos el resultado de la compilacion model.summary() # Calcular la precisión previa al entrenamiento score = model.evaluate(x_test, y_test, verbose=0) accuracy = 100*score[1] print("Precisión previa al entrenamiento: %.4f%%" % accuracy) ##################### #Entrenar el modelo ##################### ''' Se empieza probando con un número de épocas bajo y se prueba hasta ver donde alcanza un valor asintótico en el que por más que subamos las épocas no conseguimos que el modelo mejore significativamente. Por otro lado, el tamaño del lote debe ser suficientemente bajo, ya que tener un tamaño de lote grande puede reducir la capacidad de generalización del modelo. ''' from tensorflow.keras.callbacks import ModelCheckpoint from datetime import datetime num_epochs = 100 num_batch_size = 32 #Creamos un el archivo del modelo checkpointer = ModelCheckpoint(filepath=archivoModeo, verbose=0, save_best_only=True) start = datetime.now() model.fit(x_train, y_train, batch_size=num_batch_size, epochs=num_epochs,validation_data=(x_test, y_test), callbacks=[checkpointer], verbose=0) duration = datetime.now() - start print("Entrenamiento completado en un tiempo de: ", duration) ######################## #Evaluar el modelo ######################## ''' Finalmente, para determinar la precisión del modelo generado, llamamos a la función evaluate y le pasamos los datos de test que hemos definido previamente. ''' # Evaluamos el modelo con el set de datos de testing score = model.evaluate(x_test, y_test, verbose=0) print("Testing Accuracy: ", score[1]) return str(score[1]) @app.route('/existe-modelo') def existeModelo(): macAddress=request.args.get('mac') macAddressDir=macAddress.replace(':', '') archivoModelo="modelos/"+macAddressDir+".hdf5" #Validar que el archivo existe if os.path.isfile(archivoModelo): return "1" else: return "0" @app.route('/lista-mediciones') def listaMediciones(): import json from datetime import date, datetime if 'mac' in request.args: macAddress=request.args.get('mac') fecha=request.args.get('fecha') #macAddressDir=macAddress.replace(':', '') sql = "SELECT ip,idDispositivo,archivoAudio,estado,fechaCreacion FROM mediciones WHERE idDispositivo='"+macAddress+"' and fechaCreacion>='"+fecha+" 00:00:00' AND fechaCreacion<='"+fecha+" 23:59:59' order by fechaCreacion desc; "; print(sql) mycursor = mydb.cursor() mycursor.execute(sql) row_headers=[x[0] for x in mycursor.description] #this will extract row headers rv = mycursor.fetchall() mydb.commit() json_data=[] for result in rv: print(result) json_data.append(dict(zip(row_headers,result))) jsonString="{\"data\":" jsonString=jsonString + json.dumps(json_data, default=str) jsonString=jsonString + "}" mycursor.close() return jsonString else: jsonString="{\"data\":" jsonString=jsonString + "[]" jsonString=jsonString + "}" return jsonString #INICIAMSO FLASK if __name__ == "__main__": app.run(host="0.0.0.0")
LeeParaEntrenar.py
''' LEE PARA ENTRENAR: ESTE PROGRAMA RECIBE LOS AUDIOS DE LOS DISPOSITIVOS QUE DESPUES SERAN LEIDOS PARA CREAR UN MODELO ''' from datetime import datetime import socket import wave from itertools import groupby import os sockEntrenar = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Recibimos datos desde cualquier ip al puerto 8080 sockEntrenar.bind(("", 8080)) # Si el socket se tarda mas de 1 segundo en recibir datos, se cierra sockEntrenar.settimeout(2) #Iniciamos los frames para guardar el archivo frames = [] #Almacenamos la direccion ip de del paquete recibido direccionIP="" #Almacenamos el puerto de del paquete recibido data="" # Se sale si precionamos ctrl + c try: # Creamos un ciclo infinito while True: # Se sale si no se reciben datos por mas de 1 segundo try: #Creamos un bucle infinito para que siempre este escuchando while True: data, addr = sockEntrenar.recvfrom(1024) direccionIP=str(addr[0]) print (len(data)) frames.append({'data':data,'direccion':direccionIP}) #Si se dejo de recibir datos por mas de 1 segundo, se sale del ciclo except socket.timeout: # Si se recibio algo, se guarda en un archivo if(len(data)>0): #Agrupamos los frames por direccion ip frames.sort(key=lambda content: content['direccion']) groups = groupby(frames, lambda content: content['direccion']) #Hacemos un ciclo por cada direccion ip (grupo) for direccionIP, group in groups: #Esta variable almacenara los frames (sonido/segundo) que despues se guardaran en un archivo framesData=[] macAddress="" estado="" contadorPaq=0 contadorArch=0 print ('direccion', direccionIP) #Ciclo que leera cada frame (sonido/segundo) y lo guardara en la variable framesData que despues se guardara en un archivo for content in group: print ('\ttam=', len(content["data"])," contadorPaq=",contadorPaq) #Si el tamaño es menor a 50 entonces es macadress del esp8266 if(len(content["data"])<100): contenido=content["data"].decode("utf-8") #Si el contenido recibido es igual a algun estado entonces guardamos ese estado if(contenido=="Vacio" or contenido=="Medio" or contenido=="Lleno"): estado=contenido #Si el contenido recibido es para inicializar el servidor y escuchar todo elif(contenido=="1"): pass #Si el contenido no es ningun estado ni 1 entonces es una macadress else: macAddress=contenido macAddress=content["data"].decode("utf-8") else: framesData.append(content["data"]) if( (contadorPaq%8)==0 and contadorPaq>0 ): #Creamos el nombre del archivo con la direccion ip y la fecha de hoy nombreArchivo = estado+str(contadorArch)+".wav" print(nombreArchivo) macAddressDir=macAddress.replace(':', '') try: os.mkdir("audioRecibido/"+macAddressDir) except FileExistsError: pass nombreArchivo="audioRecibido/"+macAddressDir+"/"+nombreArchivo #Si el archivo del modelo existe lo borramos if os.path.exists(nombreArchivo): os.remove(nombreArchivo) # Creamos un archivo con la fecha y hora y la direccion ip file = wave.open(nombreArchivo, 'wb') file.setnchannels(1) file.setframerate(11111) # en bytes. 1->8 bits, 2->16 bits file.setsampwidth(1) file.writeframes(b''.join(framesData)) file.close() contadorArch=contadorArch+1 framesData=[] contadorPaq=contadorPaq+1 frames=[] framesData=[] data="" except KeyboardInterrupt: print("Cerrando...") sockEntrenar.close()
Predecir.py
''' PREDECIR ESTE PROGRAMA SE ENCARGA DE RECIBIR LOS AUDIOS DE LOS DISPOSITIVOS PREDECIR SU ESTADO (VACIO,MEDIO,LLENO) Y AGREGARLAS A LA BASE DE DATOS ''' from datetime import datetime import socket import wave from itertools import groupby import CargaModelo as model #pip install mysql-connector import mysql.connector import Conexion import os mydb = mysql.connector.connect( host=Conexion.host, user=Conexion.user, passwd=Conexion.passwd, database=Conexion.database ) mycursor = mydb.cursor() sockPredecir = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Recibimos datos desde cualquier ip al puerto 80 sockPredecir.bind(("", 80)) # Si el socket se tarda mas de 1 segundo en recibir datos, se cierra sockPredecir.settimeout(2) #Iniciamos los frames para guardar el archivo frames = [] #Almacenamos la direccion ip de del paquete recibido direccionIP="" #Almacenamos el puerto de del paquete recibido data="" # Se sale si precionamos ctrl + c try: # Creamos un ciclo infinito while True: # Se sale si no se reciben datos por mas de 1 segundo try: #Creamos un bucle infinito para que siempre este escuchando while True: data, addr = sockPredecir.recvfrom(1024) direccionIP=str(addr[0]) print (len(data)) frames.append({'data':data,'direccion':direccionIP}) #Si se dejo de recibir datos por mas de 1 segundo, se sale del ciclo except socket.timeout: # Si se recibio algo, se guarda en un archivo if(len(data)>0): #Agrupamos los frames por direccion ip frames.sort(key=lambda content: content['direccion']) groups = groupby(frames, lambda content: content['direccion']) #Hacemos un ciclo por cada direccion ip (grupo) for direccionIP, group in groups: #Esta variable almacenara los frames (sonido/segundo) que despues se guardaran en un archivo framesData=[] macAddress="" print ('direccion', direccionIP) #Ciclo que leera cada frame (sonido/segundo) y lo guardara en la variable framesData que despues se guardara en un archivo for content in group: print ('\t', len(content["data"])) # Si el tamano es menor a 50 entonces es macadress del esp8266 if(len(content["data"])<100): contenido=content["data"].decode("utf-8") #Si el contenido recibido es para inicializar el servidor y escuchar todo if(contenido=="1"): pass else: macAddress=content["data"].decode("utf-8") else: framesData.append(content["data"]) macAddressDir=macAddress.replace(':', '') #Creamos el nombre del archivo con la direccion ip y la fecha de hoy nombreArchivoAudio = direccionIP+"-"+datetime.now().strftime('%Y-%m-%d-%H-%M-%S')+".wav" print("nombreArchivoAudio=",nombreArchivoAudio) #Si estamos en windows, se crea un archivo en la carpeta de windows, si no, en la carpeta de linux nombreArchivoAudio="audioRecibido/"+nombreArchivoAudio # Creamos un archivo con la fecha y hora y la direccion ip file = wave.open(nombreArchivoAudio, 'wb') file.setnchannels(1) file.setframerate(11111) # en bytes. 1->8 bits, 2->16 bits file.setsampwidth(1) file.writeframes(b''.join(framesData)) file.close() file_size = os.path.getsize(nombreArchivoAudio) #Si el tamano del archivo es mayor a 5KB hacemos la prediccion del sonido if( file_size>5000 ): #Si el modelo de esta mac existe la usamos de lo contrario usamos el archivo estandar archivoModelo="modelos/"+macAddressDir+".hdf5" if os.path.isfile(archivoModelo)==False: archivoModelo="modelos/estandar.hdf5" print("archivoModelo=",archivoModelo) try: estadoLleno,exactitud=model.nivelDeLleno(rutaModelo=archivoModelo,archivoAudio=nombreArchivoAudio) print("---ESTADO:",estadoLleno," | Exactitud de prediccion:",exactitud) except: estadoLleno="Intente de nuevo" print("---ESTADO:",estadoLleno) #Insertamos en la tabla mediociones las mediciones de este dispositivo sql = "INSERT INTO mediciones(ip,idDispositivo,archivoAudio,estado) VALUES (%s,%s,%s,%s)" val = (direccionIP,macAddress,nombreArchivoAudio,estadoLleno) #validar si se inserto correctamente try: mycursor.execute(sql, val) mydb.commit() except mysql.connector.Error as error: print("Error: No se pudo insertar la medicion ",error) finally: if os.path.exists(nombreArchivoAudio): #os.remove(nombreArchivoAudio) #print(nombreArchivoAudio," borrado") pass #mycursor.close() pass frames=[] framesData=[] data="" except KeyboardInterrupt: print("Cerrando...") sockPredecir.close()