<?xml version='1.0' encoding='UTF-8'?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" version="2.0"><channel><atom:id>tag:blogger.com,1999:blog-8217618158692566582</atom:id><lastBuildDate>Fri, 21 Aug 2026 10:56:15 +0000</lastBuildDate><category>post</category><category>Arduino</category><category>FPGA</category><category>machine learning</category><category>microcontrolador</category><category>Electrónica</category><category>Criptografía</category><category>VHDL</category><category>VHDL / Verilog</category><category>EP1C3T144</category><category>MSP430</category><category>Proyectos</category><category>vulnerabilidad</category><category>malware</category><category>virus</category><category>Linux</category><category>Sistemas digitales</category><category>ataques</category><category>atmega328</category><category>kernel</category><category>Altera</category><category>HC-SR04</category><category>Raspberry</category><category>Robots</category><category>buffer overflow</category><category>deep learning</category><category>ingeniería inversa</category><category>módulo</category><category>programación segura</category><category>robot</category><category>stack</category><category>Antivirus</category><category>DHT11</category><category>NSA</category><category>PRISM</category><category>Python</category><category>RSA</category><category>Raspberry Pi Pico</category><category>Snowden</category><category>análisis dinámico</category><category>firma electrónica</category><category>interrupciones</category><category>k nearest neighbors</category><category>k vecinos más cercanos</category><category>k-nn</category><category>keras</category><category>knn</category><category>matriz de leds</category><category>max7219</category><category>pila</category><category>privacidad</category><category>redes neuronales</category><category>reversing</category><category>seguridad</category><category>seguridad informática</category><category>shellcode</category><category>técnicas hacking</category><category>/proc</category><category>5110</category><category>74LS247</category><category>7805cv</category><category>ASLR</category><category>Arduino standalone</category><category>Attiny</category><category>BCD 7 segmentos</category><category>BCD a 7 segmentos</category><category>Bullrun</category><category>CNN</category><category>ChatGPT</category><category>ClamAV</category><category>ConvNet</category><category>Criptografía Cuántica</category><category>Curvas Elípticas</category><category>Cyclone IV</category><category>Display 7 segmentos</category><category>Divisor de frecuencia</category><category>Divisor de tensión</category><category>Divisor de voltaje</category><category>ECDSA</category><category>EP2C5T144</category><category>EP4CE6</category><category>ElGamal</category><category>FSM</category><category>FreeRTOS</category><category>GPIO</category><category>GPT</category><category>Grafos</category><category>HMC5883L</category><category>HPC</category><category>Haar Cascade</category><category>I2C</category><category>Internet de las cosas</category><category>IoT</category><category>KY22 FR4</category><category>Knowlege Graph</category><category>L298N</category><category>LCD 1602</category><category>LLM</category><category>Ley 5/2014</category><category>MMCM</category><category>MNIST</category><category>Microsoft</category><category>ModelSim</category><category>Modelos de Lenguaje IA</category><category>NIST</category><category>NLP</category><category>NetworkX</category><category>NumPy</category><category>OpenCV</category><category>Prompt Engineering</category><category>PyCrypto</category><category>Quartus</category><category>ROP</category><category>RP2040</category><category>Reloj digital</category><category>SG90</category><category>SN74LS247N</category><category>Semáforos</category><category>Sensor temperatura</category><category>Simulación circuitos digitales</category><category>SkLearn</category><category>Speaker</category><category>TMB12A05</category><category>Theremin</category><category>Unified Clock System</category><category>Visión artificial</category><category>Vivado</category><category>Wifly</category><category>XILINX</category><category>antirebote</category><category>análisis de sentimiento</category><category>análisis estático</category><category>análisis forense</category><category>aprendizaje automático</category><category>bajo consumo</category><category>calabaza</category><category>certificados digitales</category><category>ciberseguridad</category><category>convolutional neural network</category><category>data science</category><category>datos</category><category>debilidades</category><category>debouncing</category><category>debugging</category><category>decodificador 7 segmentos</category><category>depuradores</category><category>descenso de gradiente</category><category>detección de caras</category><category>device driver</category><category>fashion MNIST</category><category>finite state machine</category><category>firmas</category><category>función</category><category>gradient descent</category><category>grafos de conocimiento</category><category>halloween</category><category>hardware performance counters</category><category>historias</category><category>integer overflow</category><category>legislación</category><category>lenguaje natural</category><category>linear regression</category><category>llamada</category><category>matrices</category><category>máquina de estados finitos</category><category>nmap</category><category>pandas</category><category>parche</category><category>parámetros</category><category>pentesting</category><category>proc</category><category>procesado del lenguaje natural</category><category>procfs</category><category>pull-up resitor</category><category>redes neuronales convolucionales</category><category>redes neuuronales</category><category>regresión lineal</category><category>regresión polinomial</category><category>regresión polinómica</category><category>reloj</category><category>resistencia pull-up</category><category>sentiment analysis</category><category>servo</category><category>stack frame</category><category>standalone</category><category>tensorflow</category><category>teoría de grafos</category><category>timers</category><category>tutorial</category><title>El laberinto de Falken</title><description>Is this real or is it a game?&#xa;What&#39;s the difference?</description><link>http://www.ellaberintodefalken.com/</link><managingEditor>noreply@blogger.com (Alberto García Serrano)</managingEditor><generator>Blogger</generator><openSearch:totalResults>76</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-818062179664980598</guid><pubDate>Fri, 24 May 2024 07:17:00 +0000</pubDate><atom:updated>2024-05-24T10:53:17.649+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">ChatGPT</category><category domain="http://www.blogger.com/atom/ns#">GPT</category><category domain="http://www.blogger.com/atom/ns#">LLM</category><category domain="http://www.blogger.com/atom/ns#">Modelos de Lenguaje IA</category><category domain="http://www.blogger.com/atom/ns#">Prompt Engineering</category><title>Prompt Engineering, o cómo hablar con una IA.</title><description>&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrb9VP_I3aJqoxvn7sV3I1_pHkV844d0pL2L_vBAuPgAMJgKLn87bpasrTgIgrZASGOAjY-op1nm36zGLxfOxZvdeBbW9UO9D58qm_KWE2359PjczDmiKQElgKJjeMw9bBJu6HDu94gy2dougN3m1BhULxfKrdI3DNbknBs-z8wRtifes_nDT-__eadag/s1280/w1280-p4x3-2023-02-03T170035Z_1732237236_RC2R3Z9GVJHR_RTRMADP_3_OPENAI-CHATGPT.jpg&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;ChatGPT&quot; border=&quot;0&quot; data-original-height=&quot;960&quot; data-original-width=&quot;1280&quot; height=&quot;480&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrb9VP_I3aJqoxvn7sV3I1_pHkV844d0pL2L_vBAuPgAMJgKLn87bpasrTgIgrZASGOAjY-op1nm36zGLxfOxZvdeBbW9UO9D58qm_KWE2359PjczDmiKQElgKJjeMw9bBJu6HDu94gy2dougN3m1BhULxfKrdI3DNbknBs-z8wRtifes_nDT-__eadag/w640-h480/w1280-p4x3-2023-02-03T170035Z_1732237236_RC2R3Z9GVJHR_RTRMADP_3_OPENAI-CHATGPT.jpg&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&amp;nbsp;&lt;p&gt;&lt;/p&gt;&lt;p&gt;En la era de la inteligencia artificial, el &lt;i&gt;prompt engineering&lt;/i&gt; se ha convertido en una habilidad crucial para interactuar eficazmente con modelos de lenguaje como GPT-4. Se trata de diseñar y formular instrucciones claras y efectivas (prompts) para obtener respuestas útiles y precisas de estos modelos generativos.&lt;/p&gt;&lt;h2&gt;¿Qué es el Prompt Engineering?&lt;/h2&gt;&lt;p&gt;El &lt;i&gt;prompt engineering&lt;/i&gt; persigue crear instrucciones detalladas y específicas para modelos de lenguaje con el fin de guiar sus respuestas de manera más efectiva. A medida que los modelos de IA se vuelven más sofisticados, la necesidad de proporcionar instrucciones precisas se vuelve esencial para:&lt;br /&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;b&gt;Obtener Respuestas Precisas&lt;/b&gt;: Prompts bien diseñados ayudan a obtener respuestas más precisas y relevantes de los modelos de IA.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Mejorar la eficiencia en la Comunicación&lt;/b&gt;: Minimiza la necesidad de múltiples iteraciones para obtener la información deseada.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Personalización&lt;/b&gt;: Permite adaptar las respuestas de la IA a contextos y necesidades específicos.&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;Principios básicos del Prompt Engineering&lt;/h2&gt;&lt;h3&gt;Claridad y especificidad&lt;/h3&gt;&lt;p&gt;Para obtener la mejor respuesta posible, es esencial que el prompt sea claro y específico. Un prompt ambiguo puede llevar a respuestas confusas o irrelevantes.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Ejemplo:&lt;/b&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Prompt vago&lt;/b&gt;: &quot;Háblame sobre los gatos.&quot;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Prompt claro&lt;/b&gt;: &quot;Describe las características físicas y el comportamiento típico de los gatos domésticos.&quot;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Contexto relevante&lt;/h3&gt;&lt;p&gt;Proveer contexto suficiente permite que la IA comprenda mejor la solicitud y genere respuestas más pertinentes. Por ejemplo:&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Sin contexto&lt;/b&gt;: &quot;Explícame la teoría de la relatividad.&quot;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Con contexto&lt;/b&gt;: &quot;Explica la teoría de la relatividad de Einstein de manera simple, como si se la estuvieras contando a un estudiante de secundaria.&quot;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Una técnica útil consiste en indicar al LLM cómo debe comportarse:&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Indicar rol&lt;/b&gt;: &quot;Compórtate como un profesor de secundaria.&quot;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Prompt&lt;/b&gt;: &quot;Explícame cómo se resuelve una ecuación paso a paso. Pon un ejemplo.&quot;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&amp;nbsp;&lt;br /&gt;&lt;/p&gt;&lt;div&gt;&lt;h3&gt;Instrucciones paso a paso&lt;/h3&gt;&lt;p&gt;Dividir las solicitudes complejas en pasos más pequeños y manejables puede ayudar a la IA a abordar cada parte de manera ordenada, ya que le ayudará a organizar la información de la forma que más nos convenga.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Ejemplo:&lt;/b&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Instrucción compleja&lt;/b&gt;: &quot;Escribe un ensayo sobre la revolución industrial.&quot;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Instrucción paso a paso&lt;/b&gt;:&lt;ol&gt;&lt;li&gt;&quot;Define qué fue la revolución industrial.&quot;&lt;/li&gt;&lt;li&gt;&quot;Describe las principales innovaciones tecnológicas de la revolución industrial.&quot;&lt;/li&gt;&lt;li&gt;&quot;Analiza el impacto social y económico de la revolución industrial.&quot;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Uso de ejemplos&lt;/h3&gt;&lt;p&gt;Si le damos ejemplos dentro del prompt podremos guiar a la IA en la dirección correcta y establecer el tono o formato esperado de la respuesta.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Ejemplo:&lt;/b&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Sin ejemplo&lt;/b&gt;: &quot;Escribe un poema sobre el otoño.&quot;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Con ejemplo&lt;/b&gt;: &quot;Escribe un poema sobre el otoño similar a este: &#39;Las hojas caen suavemente, doradas bajo el sol, el viento canta susurrante, anunciando el frío en unión.&#39;&quot;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Herramientas y técnicas avanzadas en Prompt Engineering&lt;/h2&gt;&lt;p&gt;Además de seguir los principios básicos, existen técnicas avanzadas y herramientas que pueden mejorar aún más el proceso de prompt engineering.&lt;/p&gt;&lt;h3&gt;Uso de plantillas&lt;/h3&gt;&lt;p&gt;Las plantillas predefinidas son extremadamente útiles para tareas repetitivas o formatos específicos de respuestas. Utilizar plantillas asegura que las respuestas de la IA sean consistentes y estructuradas. Muy útil para usar los LLMs en entornos de trabajo.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Ejemplo: plantillas para recetas de cocina&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Una plantilla puede ayudar a organizar la información de manera coherente y en el formato que más nos interese. Veamos un ejemplo clásico con recetas de cocina.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Plantilla:&lt;/b&gt;&lt;/p&gt;&lt;pre&gt;&lt;div class=&quot;dark bg-gray-950 rounded-md border-[0.5px] border-token-border-medium&quot;&gt;&lt;div class=&quot;flex items-center relative text-token-text-secondary bg-token-main-surface-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-md&quot;&gt;Usa la siguiente plantilla para describir recetas de cocina:
&quot;&quot;&quot;
### Receta [Nombre de la receta]

## Ingredientes
1 - [ingrediente]
2 - [ingrediente]

## Pasos
1 - [Paso 1]
2 - [Paso 2]

## Calorías aproximadas
[calorías totales de la receta]
&quot;&quot;&quot;&lt;code class=&quot;!whitespace-pre hljs language-markdown&quot;&gt;
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;p&gt;Le hemos dicho que lo que sigue es una plantilla, y seguidamente la describimos. Es interesante observar el uso de ciertos marcadores o &lt;i&gt;placeholders&lt;/i&gt; que hemos usado para organizar la plantilla. Las triples comillas dobles (&quot;&quot;&quot;) delimitan la plantilla. Es una forma&amp;nbsp;estructurar prompts complejos, proporcionar contexto adicional y definir claramente las partes de la solicitud. Por ejemplo: Haz un resumen de &quot;&quot;&quot;El fin de la Eternidad&quot;&quot;&quot;. Si no ponemos las triples comillas, el LLM podría no entender que El fin de Eternidad es un libro. &lt;br /&gt;&lt;/p&gt;&lt;p&gt;Entre corchetes hemos puesto una descripción con la información que debe rellenar, y con el carácter # formateamos el texto. Es decir, que podemos usar el formato &lt;i&gt;markdown&lt;/i&gt;.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Ejemplo de prompt usando plantilla:&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Prompt&lt;/b&gt;: &quot;Se hierven los macarrones durante 12 minutos hasta que estén al dente. 
Se escurre el agua. Seguidamente ponemos una lata de atún y tomate 
frito. Una vez emplatado, ponemos queso rallado.&quot;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Respuesta de la IA usando plantilla&lt;/b&gt;:&lt;/p&gt;&lt;pre&gt;&lt;div class=&quot;dark bg-gray-950 rounded-md border-[0.5px] border-token-border-medium&quot;&gt;&lt;div class=&quot;flex items-center relative text-token-text-secondary bg-token-main-surface-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-md&quot;&gt;&lt;span&gt;### Receta Macarrones con Atún y Tomate&lt;br /&gt;&lt;br /&gt;## Ingredientes&lt;br /&gt;1 - 200 gramos de macarrones&lt;br /&gt;2 - 1 lata de atún&lt;br /&gt;3 - 200 ml de tomate frito&lt;br /&gt;4 - Queso rallado al gusto&lt;br /&gt;5 - Agua y sal para hervir los macarrones&lt;br /&gt;&lt;br /&gt;## Pasos&lt;br /&gt;1 - Hervir los macarrones en agua con sal durante 12 minutos hasta que estén al dente.&lt;br /&gt;2 - Escurrir el agua de los macarrones.&lt;br /&gt;3 - Mezclar los macarrones con una lata de atún y tomate frito.&lt;br /&gt;4 - Emplatar y añadir queso rallado al gusto por encima.&lt;br /&gt;&lt;br /&gt;## Calorías aproximadas&lt;br /&gt;450 calorías (por ración)&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;flex items-center relative text-token-text-secondary bg-token-main-surface-secondary px-4 py-2 text-xs font-sans justify-between rounded-t-md&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;h3&gt;&lt;/h3&gt;&lt;p&gt;El LLM no sólo ha descrito los ingredientes y los pasos. También ha añadido un ingrediente que faltaba (la sal), le ha puesto un nombre a la receta, ha añadido las cantidades de cada ingrediente y ha calculado las calorías a partir de los ingredientes.&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;&amp;nbsp;Cadena de pensamiento (Chain of Thought)&lt;/h3&gt;&lt;p&gt;Esta técnica implica
 guiar al modelo a través de una serie de pasos lógicos para resolver un
 problema o generar una respuesta compleja. Al descomponer la tarea en 
pasos más pequeños, se mejora la coherencia y precisión de la respuesta.&lt;/p&gt;&lt;b&gt;&lt;/b&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Ejemplo:&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Prompt&lt;/b&gt;: &quot;Explica cómo se produce la fotosíntesis.&quot;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Cadena de pensamiento&lt;/b&gt;:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&quot;Describe la función de la clorofila en las plantas.&quot;&lt;/li&gt;&lt;li&gt;&quot;Explica cómo la luz solar es utilizada en el proceso.&quot;&lt;/li&gt;&lt;li&gt;&quot;Detalla el papel del dióxido de carbono y el agua.&quot;&lt;/li&gt;&lt;li&gt;&quot;Concluye describiendo el resultado final del proceso de fotosíntesis.&quot;&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;&lt;/h3&gt;&lt;h3&gt;Uso de etiquetas XML&lt;/h3&gt;&lt;p&gt;El uso de etiquetas XML puede ser útil para estructurar prompts complejos, proporcionar contexto adicional y definir claramente las partes de la solicitud.&lt;/p&gt;&lt;h3&gt;Etiquetas XML&lt;/h3&gt;&lt;p&gt;Las etiquetas XML se utilizan para etiquetar y estructurar diferentes partes del prompt, facilitando la interpretación por parte de la IA.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Ejemplo:&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Prompt&lt;/b&gt;:&lt;/p&gt;&lt;pre&gt;&lt;/pre&gt;&lt;pre&gt;&lt;div class=&quot;dark bg-gray-950 rounded-md border-[0.5px] border-token-border-medium&quot;&gt;&lt;div class=&quot;overflow-y-auto p-4 text-left undefined&quot; dir=&quot;ltr&quot;&gt;&lt;code class=&quot;!whitespace-pre hljs language-xml&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;prompt&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;contexto&lt;/span&gt;&amp;gt;&lt;/span&gt;
        Explica cómo se produce la fotosíntesis en las plantas.
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;contexto&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;paso&lt;/span&gt;&amp;gt;&lt;/span&gt;
        1. Describe la función de la clorofila.
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;paso&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;paso&lt;/span&gt;&amp;gt;&lt;/span&gt;
        2. Explica el uso de la luz solar en el proceso.
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;paso&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;paso&lt;/span&gt;&amp;gt;&lt;/span&gt;
        3. Detalla el papel del dióxido de carbono y el agua.
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;paso&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;paso&lt;/span&gt;&amp;gt;&lt;/span&gt;
        4. Concluye con el resultado final de la fotosíntesis.
    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;paso&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;prompt&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;&lt;h3&gt;Modificación y expansión de prompts&lt;/h3&gt;&lt;p&gt;A veces, un prompt necesita ser modificado o expandido para proporcionar respuestas más completas. Añadir contexto adicional, ejemplos y desglosar instrucciones en pasos más pequeños puede mejorar significativamente la calidad de las respuestas.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Ejemplo:&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Prompt inicial&lt;/b&gt;: &quot;Explica la teoría de la relatividad.&quot;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Prompt modificado&lt;/b&gt;: &quot;Explica la teoría de la relatividad de Einstein de manera simple, como si se la estuvieras contando a un estudiante de secundaria. Asegúrate de incluir ejemplos concretos y analogías que faciliten la comprensión.&quot;&lt;/p&gt;&lt;h2&gt;Ejemplos prácticos de Prompt Engineering&lt;/h2&gt;&lt;p&gt;A continuación, hay varios ejemplos prácticos para diferentes escenarios, demostrando cómo el &lt;i&gt;prompt engineering&lt;/i&gt; puede mejorar la interacción con modelos de IA.&lt;/p&gt;&lt;h3&gt;Ejemplo 1: Resumen de texto&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Prompt&lt;/b&gt;: &quot;Resume el siguiente artículo en 100 palabras: [inserta texto del artículo].&quot;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Respuesta mejorada&lt;/b&gt;: Para obtener un resumen preciso, proporciona un poco más de contexto.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Prompt mejorado&lt;/b&gt;: &quot;Resume el siguiente artículo de noticias en 100 palabras, destacando los puntos clave sobre los impactos económicos y sociales: [inserta texto del artículo].&quot;&lt;/p&gt;&lt;h3&gt;Ejemplo 2: Generación de contenido creativo&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Prompt&lt;/b&gt;: &quot;Escribe una historia corta sobre una aventura en el espacio.&quot;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Respuesta mejorada&lt;/b&gt;: Añadir detalles específicos sobre los personajes y el escenario.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Prompt mejorado&lt;/b&gt;: &quot;Escribe una historia corta de 500 palabras sobre una aventura en el espacio, protagonizada por una astronauta llamada Laura y su robot asistente, quienes descubren un planeta desconocido habitado por seres misteriosos.&quot;&lt;/p&gt;&lt;h3&gt;Ejemplo 3: Resolución de problemas matemáticos&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Prompt&lt;/b&gt;: &quot;Resuelve este problema matemático: 3x + 5 = 20.&quot;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Respuesta mejorada&lt;/b&gt;: Guiar a través de los pasos para resolver el problema.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Prompt mejorado&lt;/b&gt;: &quot;Resuelve el siguiente problema matemático mostrando todos los pasos: 3x + 5 = 20. Primero, despeja el término constante y luego divide por el coeficiente de x.&quot;&lt;/p&gt;&lt;h3&gt;Ejemplo 4: Consultoría técnica&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Prompt&lt;/b&gt;: &quot;Dame consejos para mejorar la seguridad informática.&quot;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Respuesta mejorada&lt;/b&gt;: Especificar el tipo de consejos y el contexto.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Prompt mejorado&lt;/b&gt;: &quot;Proporciona cinco consejos avanzados para mejorar la seguridad informática en una empresa de tecnología, enfocándote en la protección de datos sensibles y la prevención de ataques de phishing.&quot;&lt;/p&gt;&lt;h3&gt;Ejemplo 5: Análisis de datos&lt;/h3&gt;&lt;p&gt;&lt;b&gt;Prompt&lt;/b&gt;: &quot;Analiza los datos de ventas del último trimestre.&quot;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Respuesta mejorada&lt;/b&gt;: Incluir detalles sobre qué aspectos analizar y el formato esperado de la respuesta.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Prompt mejorado&lt;/b&gt;: &quot;Analiza los datos de ventas del último trimestre, enfocándote en identificar las tendencias de ventas mensuales, los productos más vendidos y las regiones con mejor desempeño. Presenta los resultados en un formato de informe ejecutivo.&quot;&lt;/p&gt;&lt;h2&gt;Conclusión&lt;/h2&gt;&lt;p&gt;El &lt;i&gt;prompt engineering&lt;/i&gt; es una habilidad importante para interactuar de manera efectiva con modelos de lenguaje avanzados. Al seguir principios básicos como la claridad, especificidad, y el uso de ejemplos, junto con técnicas avanzadas como las que hemos visto más arriba, se pueden obtener respuestas más precisas y relevantes.&lt;/p&gt;&lt;/div&gt;</description><link>http://www.ellaberintodefalken.com/2024/05/Prompt-Engineering-GPT.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrb9VP_I3aJqoxvn7sV3I1_pHkV844d0pL2L_vBAuPgAMJgKLn87bpasrTgIgrZASGOAjY-op1nm36zGLxfOxZvdeBbW9UO9D58qm_KWE2359PjczDmiKQElgKJjeMw9bBJu6HDu94gy2dougN3m1BhULxfKrdI3DNbknBs-z8wRtifes_nDT-__eadag/s72-w640-h480-c/w1280-p4x3-2023-02-03T170035Z_1732237236_RC2R3Z9GVJHR_RTRMADP_3_OPENAI-CHATGPT.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-5288078215965153738</guid><pubDate>Mon, 29 May 2023 17:11:00 +0000</pubDate><atom:updated>2023-05-30T08:55:58.176+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">ciberseguridad</category><category domain="http://www.blogger.com/atom/ns#">nmap</category><category domain="http://www.blogger.com/atom/ns#">pentesting</category><title>Scripts en NMAP</title><description>Cuando pensamos en NMAP, pensamos en el escaneo de puertos de un host objetivo al que estamos relizando una prueba de intrusión, pero gracias a las posibilidades que nos ofrecen su &lt;i&gt;Scripting Engine&lt;/i&gt;, NMAP es mucho más que eso.&lt;p/&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCynEJ7V0YjvVmBJAOXD4fHwIQG6cagFVGjL71OGp6bjanGXvcDlAUeoHPRNopN_2CPVqaTdKyfuu-c9QWihr-EyBPjbMY7og3Lqhlz6gJVXzu4iCfYmr_r0kxPjdk0ydhvL08yO_mzXnLl_bBPn8iGEkIUcX_bx_UcLcqPr29KfEjNSyxCOWeZRbT/s700/nmap-logo.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;NMAP logo&quot; border=&quot;0&quot; width=&quot;600&quot; data-original-height=&quot;375&quot; data-original-width=&quot;700&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCynEJ7V0YjvVmBJAOXD4fHwIQG6cagFVGjL71OGp6bjanGXvcDlAUeoHPRNopN_2CPVqaTdKyfuu-c9QWihr-EyBPjbMY7og3Lqhlz6gJVXzu4iCfYmr_r0kxPjdk0ydhvL08yO_mzXnLl_bBPn8iGEkIUcX_bx_UcLcqPr29KfEjNSyxCOWeZRbT/s600/nmap-logo.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

Antes de continuar, un aviso: algunas de posibilidades que nos ofrecen los scripts de NMAP son bastante intrusivas, por lo que recomiendo hacerlas contra hosts propios, máquinas virtuales como las de Metasploitable, o contrato de pentesting mediante. Para este artículo voy a usar las máquinas de &lt;a href=&quot;https://github.com/rapid7/metasploitable3&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Metasploitable3&lt;/a&gt;.&lt;p/&gt;

No voy a entrar en los detalles sobre el uso básico de NMAP, ya que hay miles de tutoriales en Internet que hablan sobre ello. Lo cierto es que NMAP tiene algunas opciones que permiten obtener información extra, además de qué puertos están abiertos y cuales no. Por ejemplo, la opción -sV trata de obtener el servicio concreto, e incluso la versión del servicio que está corriendo en cada puerto. Otro ejemplo es la opción -O, que intenta averiguar el sistema operativo del host.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;sudo nmap -sV -p80 -O 192.168.56.102
&lt;/pre&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&lt;p/&gt;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQmF4gWqnHBwc7moulijy3BYGodqZcBSE0D39wdJbc3ohtHenEqCgws43mQrjh9Pi33m3fGuqbYjAF3bLp08Xwf7FwcTIp0j49xHkvQMF4tEsw5X7smcRQ9OTJbo8_VNI7d4iHVzxgsB4lXm211ve6GaTvMQnuBq9FmduYT2VhxhGqKc26qYEW4jGm/s1910/f1-OSDetect.png&quot; style=&quot;display: block; padding: 1em 0px; text-align: center;&quot;&gt;&lt;img alt=&quot;Salida NMAP detección del SO&quot; border=&quot;0&quot; data-original-height=&quot;412&quot; data-original-width=&quot;1910&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQmF4gWqnHBwc7moulijy3BYGodqZcBSE0D39wdJbc3ohtHenEqCgws43mQrjh9Pi33m3fGuqbYjAF3bLp08Xwf7FwcTIp0j49xHkvQMF4tEsw5X7smcRQ9OTJbo8_VNI7d4iHVzxgsB4lXm211ve6GaTvMQnuBq9FmduYT2VhxhGqKc26qYEW4jGm/s600/f1-OSDetect.png&quot; width=&quot;600&quot; /&gt;&lt;/a&gt;&lt;/div&gt;

En este caso, le hemos pedido que nos dé información sobre el servicio que está corriendo en el puerto 80, y el sistema operativo que tiene el host. Vemos que ha hecho un buen trabajo averiguando la versión del servidor web (Microsoft IIS httpd 7.5), pero con el sistema operativo no llega a ser muy concreto en cuanto la versión exacta que hay instalada.&lt;p/&gt;

Si miramos el directorio &lt;i&gt;/usr/share/nmap/scripts&lt;/i&gt;, observaremos que hay una gran cantidad de archivos con extensión .nse. Son scripts con diversas funcionalidades. Desde la recogida de información hasta la explotación de vulneravilidades. Pero vamos por pasos. Para poder usar uno de estos scripts, utilizamos la opción &lt;i&gt;--script&lt;/i&gt; de NMAP. En el siguiente ejemplo vamos a usar un script llamado &lt;i&gt;smb-os-discovery.nse&lt;/i&gt;, cuya misión es intentar obtener información del sistema operativo del host objetivo a través del protocolo SMB. Como dicho protocolo usa los puertos 139 y 445, vamos a usar el script usando la siguiente instrucción.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;sudo nmap -p139,445 --script=smb-os-discovery 192.168.56.102
&lt;/pre&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQeTqTIZedJYkooHhGDDKtJTXWqun-xsJ3dMArKUQy-hbit0IsZCy7ps-HPy5rRU1jZv3VIAwiASjZnskDAlVT0gAzo76YJcStco74q9NAPDi6b0eWezpT0wWkm4S-msraCIio9I1lhmFlxbcUaqI-Uhq3gHVoPRaMkdso9XNj1EbkqBBAXV7BMp8Y/s1588/f2-SMBOSDetect.png&quot; style=&quot;display: block; padding: 1em 0px; text-align: center;&quot;&gt;&lt;img alt=&quot;Detección SMB del sistema operativo&quot; border=&quot;0&quot; data-original-height=&quot;505&quot; data-original-width=&quot;1588&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQeTqTIZedJYkooHhGDDKtJTXWqun-xsJ3dMArKUQy-hbit0IsZCy7ps-HPy5rRU1jZv3VIAwiASjZnskDAlVT0gAzo76YJcStco74q9NAPDi6b0eWezpT0wWkm4S-msraCIio9I1lhmFlxbcUaqI-Uhq3gHVoPRaMkdso9XNj1EbkqBBAXV7BMp8Y/s600/f2-SMBOSDetect.png&quot; width=&quot;600&quot; /&gt;&lt;/a&gt;&lt;/div&gt;

Gracias al script, hemos obtenido el sistema operativo exacto que usa la máquina, además de alguna información adicional, como el nombre del equipo o el grupo de trabajo de NetBIOS. No está mal ¿no?&lt;p/&gt;

&lt;h3&gt;Categorías&lt;/h3&gt;&lt;p/&gt;
NMAP viene cargado con más de 600 scripts, y para organizarlos, están divididos en categorías, es decir, que cada script pretenece a una o más categorías. Vamos a hacer un rápido repaso.&lt;p/&gt;

&lt;b&gt;auth.&lt;/b&gt; Scripts relacionados con la autenticación de usuarios.&lt;p/&gt;

&lt;b&gt;broadcast.&lt;/b&gt; Descubrimiento de hosts mediante mensajes de broadcast en la red local.&lt;p/&gt;

&lt;b&gt;brute.&lt;/b&gt; Uso de técnicas de fuerza bruta para descubrir crendenciales sobre diferentes protocolos y aplicaciones.&lt;p/&gt;

&lt;b&gt;default.&lt;/b&gt; Scripts por defecto, que pueden ser lanzados mediante la opción -sC de NMAP. No tengo claro el criterio, pero supongo que son los scripts más usuales.&lt;p/&gt;

&lt;b&gt;discovery.&lt;/b&gt; Trata de hacer descubrimiento de hosts e información mediante consultas a servicios SMB, SNMP, directorios LDAP, etc.&lt;p/&gt;

&lt;b&gt;dos.&lt;/b&gt; Relacionados con ataques tipo DOS (denegación de servicio).&lt;p/&gt;

&lt;b&gt;exploit.&lt;/b&gt; Script pensados para explotar alguna vulnerabilidad.&lt;p/&gt;

&lt;b&gt;external.&lt;/b&gt; Son scripts que realizan conexiones con servicios externos, como por ejemplo whois.&lt;p/&gt;

&lt;b&gt;fuzzer.&lt;/b&gt; Los scripts de esta categoría utilizan técnicas de fuzzing.&lt;p/&gt;

&lt;b&gt;intrusive.&lt;/b&gt; Son scripts extremadamente intrusivos, que podrían incluso bloquear el host objetivo.&lt;p/&gt;

&lt;b&gt;malware.&lt;/b&gt; Tratan de identificar si el host ha sido comprometido por algún tipo de malware o tiene alguna puerta trasera.&lt;p/&gt;

&lt;b&gt;safe.&lt;/b&gt; Son script poco intrusivos que, en principio, no deben dañar el host objetivo.&lt;p/&gt;

&lt;b&gt;version.&lt;/b&gt; Estos scripts son invocados cuando se usa la opción -sV. No se pueden invocar explícitamente el uso de esta categoría.&lt;p/&gt;

&lt;b&gt;vuln.&lt;/b&gt; Hacen una búsqueda de vulnerabilidades en el host.&lt;p/&gt;

Podemos lanzar los scripts individualmente, como ya hemos visto, o por categorías completas. Los scripts en la categoría &lt;i&gt;default&lt;/i&gt; los podemos lanzar con la instrucción &lt;i&gt;nmap -sC&lt;/i&gt;. Si queremos lanzar los scripts de otra categoría usamos el modificador &lt;i&gt;--script&lt;/i&gt;, pero indicando la categoría en lugar de un script concreto.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;nmap --script=vuln&lt;/pre&gt;

Los nombres de los scripts son bastante representativos, pero si queremos obtener detalles de alguno de ellos podemos usar la opción &lt;i&gt;--script-help&lt;/i&gt;.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;nmap --script-help smb-enum-users&lt;/pre&gt;

&lt;h3&gt;Uso de los scripts de NMAP para pentesting&lt;/h3&gt;&lt;p/&gt;

Ahora que tenemos una visión general, vamos a ponerlos en práctica contra un host Windows del Metaesploitable3 que os indicaba al principio.&lt;p/&gt;

Uno de los servicios que muchas veces no está bien configurado en los servidores Windows es el SNMP, así que suele ser un buen lugar donde lanzar la caña. Veamos si podemos recoger algo usando el script &lt;i&gt;snmp-win32-users&lt;/i&gt;. En este caso, como SNMP va por UDP, usaremos el modificador -sU para usar este protocolo, y ya que el puerto de SNMP es el 161, usaremos también el modificador -p161.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
$sudo nmap -sU -p161 --script=snmp-win32-users 192.168.56.102
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-29 17:28 CEST
Nmap scan report for 192.168.56.102
Host is up (0.00069s latency).

PORT    STATE SERVICE
161/udp open  snmp
| snmp-win32-users: 
|   Administrator
|   Guest
|   anakin_skywalker
|   artoo_detoo
|   ben_kenobi
|   boba_fett
|   c_three_pio
|   chewbacca
|   darth_vader
|   greedo
|   han_solo
|   jabba_hutt
|   jarjar_binks
|   kylo_ren
|   lando_calrissian
|   leia_organa
|   luke_skywalker
|   sshd
|   sshd_server
|_  vagrant
MAC Address: 08:00:27:DC:53:82 (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 13.50 seconds

&lt;/pre&gt;

Estupendo, ahí tenemos a una serie de usuarios del sistema. Un autentico fan de Starwars. Veamos si podemos averiguar qué software está instalado en la máquina.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
$sudo nmap -sU -p161 --script=snmp-win32-software 192.168.56.102
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-29 17:32 CEST
Nmap scan report for 192.168.56.102
Host is up (0.00067s latency).

PORT    STATE SERVICE
161/udp open  snmp
| snmp-win32-software: 
|   7-Zip 22.01 (x64); 2023-03-19T02:22:16
|   Java 8 Update 251 (64-bit); 2023-03-19T02:23:56
|   Java SE Development Kit 8 Update 211 (64-bit); 2023-03-19T02:25:46
|   Microsoft .NET Framework 4.5.2; 2023-03-19T02:12:48
|   Microsoft .NET Framework 4.5.2; 2023-03-19T02:12:44
|   Microsoft Visual C++ 2008 Redistributable - x64 9.0.30729.6161; 2023-03-19T02:27:48
|   OpenSSH for Windows 7.1p1-1 (remove only); 2023-03-19T02:06:06
|_  Oracle VM VirtualBox Guest Additions 6.0.8; 2023-03-19T02:45:06
MAC Address: 08:00:27:DC:53:82 (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 13.45 seconds
&lt;/pre&gt;

Y de camino, los servicios que están corriendo en la máquina.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
$sudo nmap -sU -p161 --script=snmp-win32-services 192.168.56.102
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-29 17:33 CEST
Nmap scan report for 192.168.56.102
Host is up (0.00084s latency).

PORT    STATE SERVICE
161/udp open  snmp
| snmp-win32-services: 
|   Apache Tomcat 8.0 Tomcat8
|   Application Host Helper Service
|   Background Intelligent Transfer Service
|   Base Filtering Engine
|   COM+ Event System
|   Certificate Propagation
|   Cryptographic Services
|   DCOM Server Process Launcher
|   DHCP Client
|   DNS Client
|   Desktop Window Manager Session Manager
|   Diagnostic Policy Service
|   Diagnostic System Host
|   Distributed Link Tracking Client
|   Distributed Transaction Coordinator
|   Elasticsearch 1.1.1 (elasticsearch-service-x64)
|   Group Policy Client
|   IKE and AuthIP IPsec Keying Modules
|   IP Helper
|   IPsec Policy Agent
|   MEDC Server Component - Apache
|   MEDC Server Component - Notification Server
|   ManageEngine Desktop Central Server
|   Microsoft FTP Service
|   Network Connections
|   Network List Service
|   Network Location Awareness
|   Network Store Interface Service
|   OpenSSH Server
|   Plug and Play
|   Power
|   Print Spooler
|   RPC Endpoint Mapper
|   Remote Desktop Configuration
|   Remote Desktop Services
|   Remote Desktop Services UserMode Port Redirector
|   Remote Procedure Call (RPC)
|   Remote Registry
|   SNMP Service
|   Security Accounts Manager
|   Server
|   Shell Hardware Detection
|   Software Protection
|   System Event Notification Service
|   TCP/IP NetBIOS Helper
|   Task Scheduler
|   User Profile Service
|   VirtualBox Guest Additions Service
|   Windows Event Log
|   Windows Firewall
|   Windows Font Cache Service
|   Windows Licensing Monitoring Service
|   Windows Management Instrumentation
|   Windows Process Activation Service
|   Windows Remote Management (WS-Management)
|   Windows Update
|   Workstation
|   World Wide Web Publishing Service
|   domain1 GlassFish Server
|   jenkins
|   jmx
|   wampapache
|_  wampmysqld
MAC Address: 08:00:27:DC:53:82 (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 13.55 seconds
&lt;/pre&gt;

Con este listado, ya podríamos consultar la base de datos CVE para ver si hay alguna vulnerabilidad en alguno de los programas o servicios, pero ya que estamos, vamos a pedirle a NMAP que nos adelante algo de trabajo pidiendole que lance los scripts de la categoría &lt;i&gt;vuln&lt;/i&gt; contra el host.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
sudo nmap --script=vuln 192.168.56.102
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-29 17:35 CEST
Nmap scan report for 192.168.56.102
Host is up (0.00055s latency).
Not shown: 981 closed tcp ports (reset)
PORT      STATE SERVICE
21/tcp    open  ftp
22/tcp    open  ssh
80/tcp    open  http
|_http-csrf: Couldn&#39;t find any CSRF vulnerabilities.
|_http-dombased-xss: Couldn&#39;t find any DOM based XSS.
|_http-stored-xss: Couldn&#39;t find any stored XSS vulnerabilities.
135/tcp   open  msrpc
139/tcp   open  netbios-ssn
445/tcp   open  microsoft-ds
3306/tcp  open  mysql
3389/tcp  open  ms-wbt-server
4848/tcp  open  appserv-http
| ssl-dh-params: 
|   VULNERABLE:
|   Diffie-Hellman Key Exchange Insufficient Group Strength
|     State: VULNERABLE
|       Transport Layer Security (TLS) services that use Diffie-Hellman groups
|       of insufficient strength, especially those using one of a few commonly
|       shared groups, may be susceptible to passive eavesdropping attacks.
|     Check results:
|       WEAK DH GROUP 1
|             Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA
|             Modulus Type: Safe prime
|             Modulus Source: RFC2409/Oakley Group 2
|             Modulus Length: 1024
|             Generator Length: 8
|             Public Key Length: 1024
|     References:
|_      https://weakdh.org
7676/tcp  open  imqbrokerd
8009/tcp  open  ajp13
8080/tcp  open  http-proxy
| http-enum: 
|_  ..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f/var/mobile/Library/AddressBook/AddressBook.sqlitedb: Possible iPhone/iPod/iPad generic file sharing app Directory Traversal (iOS)
8181/tcp  open  intermapper
| ssl-dh-params: 
|   VULNERABLE:
|   Diffie-Hellman Key Exchange Insufficient Group Strength
|     State: VULNERABLE
|       Transport Layer Security (TLS) services that use Diffie-Hellman groups
|       of insufficient strength, especially those using one of a few commonly
|       shared groups, may be susceptible to passive eavesdropping attacks.
|     Check results:
|       WEAK DH GROUP 1
|             Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA
|             Modulus Type: Safe prime
|             Modulus Source: RFC2409/Oakley Group 2
|             Modulus Length: 1024
|             Generator Length: 8
|             Public Key Length: 1024
|     References:
|_      https://weakdh.org
8383/tcp  open  m2mservices
| ssl-dh-params: 
|   VULNERABLE:
|   Diffie-Hellman Key Exchange Insufficient Group Strength
|     State: VULNERABLE
|       Transport Layer Security (TLS) services that use Diffie-Hellman groups
|       of insufficient strength, especially those using one of a few commonly
|       shared groups, may be susceptible to passive eavesdropping attacks.
|     Check results:
|       WEAK DH GROUP 1
|             Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA
|             Modulus Type: Safe prime
|             Modulus Source: RFC2409/Oakley Group 2
|             Modulus Length: 1024
|             Generator Length: 8
|             Public Key Length: 1024
|     References:
|_      https://weakdh.org
9200/tcp  open  elasticsearch
49152/tcp open  unknown
49153/tcp open  unknown
49154/tcp open  unknown
49176/tcp open  unknown
MAC Address: 08:00:27:DC:53:82 (Oracle VirtualBox virtual NIC)

Host script results:
|_smb-vuln-ms10-054: false
|_smb-vuln-ms10-061: NT_STATUS_ACCESS_DENIED
| smb-vuln-ms17-010: 
|   VULNERABLE:
|   Remote Code Execution vulnerability in Microsoft SMBv1 servers (ms17-010)
|     State: VULNERABLE
|     IDs:  CVE:CVE-2017-0143
|     Risk factor: HIGH
|       A critical remote code execution vulnerability exists in Microsoft SMBv1
|        servers (ms17-010).
|           
|     Disclosure date: 2017-03-14
|     References:
|       https://blogs.technet.microsoft.com/msrc/2017/05/12/customer-guidance-for-wannacrypt-attacks/
|       https://technet.microsoft.com/en-us/library/security/ms17-010.aspx
|_      https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-0143
|_samba-vuln-cve-2012-1182: NT_STATUS_ACCESS_DENIED

Nmap done: 1 IP address (1 host up) scanned in 152.97 seconds
&lt;/pre&gt;

NMAP ha encontrado por nosotros algunos problemas, como posibles debilidades criptográficas, y lo mejor de todo, vulnerabilidades como la CVE-2017-0143 que tiene SMBv1, que permite la ejecución remota de código. Sólo con esta información, ya tendríamos material interesante para pasar a la fase de explotación de nuestro test de intrusión. Aunque de eso ya hablaremos otro día. Por hoy podemos descansar, que nos lo hemos ganado.
</description><link>http://www.ellaberintodefalken.com/2023/05/scripts-en-nmap.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCynEJ7V0YjvVmBJAOXD4fHwIQG6cagFVGjL71OGp6bjanGXvcDlAUeoHPRNopN_2CPVqaTdKyfuu-c9QWihr-EyBPjbMY7og3Lqhlz6gJVXzu4iCfYmr_r0kxPjdk0ydhvL08yO_mzXnLl_bBPn8iGEkIUcX_bx_UcLcqPr29KfEjNSyxCOWeZRbT/s72-c/nmap-logo.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-5329897924961923838</guid><pubDate>Sat, 08 Jan 2022 00:59:00 +0000</pubDate><atom:updated>2022-01-08T14:52:03.294+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">detección de caras</category><category domain="http://www.blogger.com/atom/ns#">Haar Cascade</category><category domain="http://www.blogger.com/atom/ns#">OpenCV</category><category domain="http://www.blogger.com/atom/ns#">Visión artificial</category><title>Detección de caras con OpenCV</title><description>En visión artificial, una de las tareas básicas es el reconocimiento de objetos, ya que es muy interesante disponer de algún modo de discriminar entre clases de objetos. Por ejemplo, un coche autónomo ha de ser capaz de reconocer a otros coches y distinguirlo de un peatón. Otra utilidad más mundana y que seguro conoces, es el reconocimiento de caras que hace tu móvil cuando vas a hacer una foto. 

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgDmlQkdBVU9mxEuDRId45Z2ATLQY96CUKX4isCTxGIhFrITh9Bje0h_e3SN8fE8Ydv9znQ_WQ2jP51zyo-YmRgt-Dj0WZL1YGEMjq9XftxKNL4-sfztEX0SWoY2xx4JEdwhjST-espo0c66mcianSLRheuHiBxy7doM3I89hWbFpaOFzmIofEbkq_A=s1500&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Detección caras OpenCV&quot; border=&quot;0&quot; width=&quot;600&quot; data-original-height=&quot;500&quot; data-original-width=&quot;1500&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgDmlQkdBVU9mxEuDRId45Z2ATLQY96CUKX4isCTxGIhFrITh9Bje0h_e3SN8fE8Ydv9znQ_WQ2jP51zyo-YmRgt-Dj0WZL1YGEMjq9XftxKNL4-sfztEX0SWoY2xx4JEdwhjST-espo0c66mcianSLRheuHiBxy7doM3I89hWbFpaOFzmIofEbkq_A=s600&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

En este artículos vamos a ver cómo reconocer caras usando OpenCV. Ojo, cuando hablo de reconocer caras, me refiero a distinguir una cara de cualquier otra cosa, no de distinguir entre caras para, por ejemplo, reconocer a quién pertenece esa cara. Ese es otro problema diferente del que os hablaré en otro momento.
Como quiero abordar el problema desde un punto de vista práctico, vamos a usar la librería OpenCV con Python. Como imagen de ejemplo vamos a tomar la siguiente.

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgtkIGMrvU7ugvX8hTRIkhuWp6TL_nuOm_Q9CCLw1zGQgEKGZIKN4AOYSxzGvYMsG7EJ15SZG4wLDMncR0BNPaqANhtp5z5eGFhaR1JtWRy9tEvV-frmdAj3CGtYymLxlwYM295F3xxK7ZzrcJgeYP7WS3IyB702y8cvArRFuw_yY-J9YsHrag8BBQb&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Star Trek TOS&quot; border=&quot;0&quot; data-original-height=&quot;1115&quot; data-original-width=&quot;2329&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgtkIGMrvU7ugvX8hTRIkhuWp6TL_nuOm_Q9CCLw1zGQgEKGZIKN4AOYSxzGvYMsG7EJ15SZG4wLDMncR0BNPaqANhtp5z5eGFhaR1JtWRy9tEvV-frmdAj3CGtYymLxlwYM295F3xxK7ZzrcJgeYP7WS3IyB702y8cvArRFuw_yY-J9YsHrag8BBQb&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

Nuestra misión es detectar las caras que hay en esta imagen, así que fije el rumbo señor Sulu.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
import cv2
from matplotlib import pyplot as plt
img = cv2.imread(&#39;TOS-crew.jpg&#39;)
&lt;/pre&gt;

Tras importar las librerías OpenCV y Matplotlib para visualizar y manipular la imagen, comprobamos que se ha cargado correctamente y verificamos su tamaño.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;img.shape&lt;/pre&gt;
&lt;pre&gt;(1115, 2329, 3)&lt;/pre&gt;

Si queremos visualizar la imagen con Matplotlib primero debemos convertirla a la codificación RGB, ya que OpenCV por defecto trabaja con imagenes BGR.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
img_RGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_RGB)
&lt;/pre&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEj580ZO97tpUT2TNwlCtz_zTA7JkNoQ6AXdH58XSe0UlSgRRjKJrQUccRwJrRUshTXkHcYI2Z2fLmOmkwOvGGTwVJaDYGHrnbqmuV58nQMFnwNjNdiRoSA_xzH6rTjxLWujPvurFbqqdgmqS0XyUiMWIplUjqdkBdkLsW1ie11_Ki1xk5QavZS2nT_p&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Star Trek RGB&quot; border=&quot;0&quot; data-original-height=&quot;195&quot; data-original-width=&quot;381&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEj580ZO97tpUT2TNwlCtz_zTA7JkNoQ6AXdH58XSe0UlSgRRjKJrQUccRwJrRUshTXkHcYI2Z2fLmOmkwOvGGTwVJaDYGHrnbqmuV58nQMFnwNjNdiRoSA_xzH6rTjxLWujPvurFbqqdgmqS0XyUiMWIplUjqdkBdkLsW1ie11_Ki1xk5QavZS2nT_p&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

Si vamos a trabajar con imágenes grandes, a veces es mejor redimensionarlas para hacer las más pequeñas e incluso convertirlas a escala de grises para aliviar su tamaño. Como ejemplo, las líneas siguientes realizan estas dos operaciones.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
img_gray = cv2.cvtColor(img_RGB, cv2.COLOR_RGB2GRAY)
img_small = cv2.resize(img_gray, (800, 400))
plt.imshow(img_small)
&lt;/pre&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhZLKPKtM77HdoagWFuav4yWrBOz5YFAnu85ki2aE53N638HfdmbGZt6Qog_ey5AY4-hY8aVNHA9oWpAkMlmMMxFylb74I223JvJjPDekeEYu4ZsPIQND0Z4iXUF80pt4UcIoMKHFfKwYgppmDlJoDG6lSVDmxrQkL-wsfg6Bn-hMdKNMx3q83-W_hA&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;202&quot; data-original-width=&quot;375&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEhZLKPKtM77HdoagWFuav4yWrBOz5YFAnu85ki2aE53N638HfdmbGZt6Qog_ey5AY4-hY8aVNHA9oWpAkMlmMMxFylb74I223JvJjPDekeEYu4ZsPIQND0Z4iXUF80pt4UcIoMKHFfKwYgppmDlJoDG6lSVDmxrQkL-wsfg6Bn-hMdKNMx3q83-W_hA&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

Ahora vamos a realizar las busqueda de las caras en la imagen. Con OpenCV es de lo más sencillo. Sólo necesitamos dos líneas de código Python. Sin entrar en detalles, os cuento que el clasificador que vamos a usar se conoce como Haar Cascade o también como algoritmo de Viola-Jones. Vamos a usarlo porque es de los clásicos (que no el mejor). Si queréis conocer más a fondo como funciona el algoritmo podéis consultar el &lt;a href=&quot;https://www.face-rec.org/algorithms/Boosting-Ensemble/16981346.pdf&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;artículo original&lt;/a&gt; de 2004 donde Paul Viola y Michael J. Jones presentaron este método en sociedad. 
Para que este algoritmo funcione hay que entrenarlo mostrandole caras, y otras cosas que no sean caras, para que aprenda a distinguirlos. El proceso puede ser tedioso, ya que son necesaria una relativamente grande cantidad de imágenes. Por suerte ya hay clasificadores preentrenados para reconocer caras (y otras muchas cosas), que están almacenas en formato .xml y que podemos cargar desde OpenCV. El que vamos a usar se llama haarcascade_frontalface_default.xml y se puede descargar de &lt;a href=&quot;https://github.com/opencv/opencv/raw/master/data/haarcascades/haarcascade_frontalface_default.xml&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;aquí&lt;/a&gt;. Está entrenado con caras mirando al frente, cómo las que queremos detectar. En &lt;a href=&quot;https://github.com/opencv/opencv/tree/master/data/haarcascades&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;esta página&lt;/a&gt; hay más clasificadores preentrenados.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
# cargamos el clasificador preentrenado
detector_caras = cv2.CascadeClassifier(&#39;haarcascade_frontalface_default.xml&#39;)
detecciones = detector_caras.detectMultiScale(img_RGB)
# array de detecciones. una fila por cada cara. Las columnas con [pos_x, pos_y, size_x, size_y]
detecciones
&lt;/pre&gt;
&lt;pre&gt;
array([[ 936,  211,   58,   58],
       [ 495,   76,  191,  191],
       [ 708,  109,  235,  235],
       [1199,  120,  195,  195],
       [ 221,  124,  212,  212],
       [1810,  225,  216,  216],
       [1464,  325,  215,  215],
       [1540,  326,  215,  215],
       [1435,  564,  352,  352],
       [ 622,  572,  348,  348],
       [1296,  992,   62,   62]], dtype=int32)
&lt;/pre&gt;

El array &lt;i&gt;detecciones&lt;/i&gt; contiene una fila por cada cara detectada, con cuatro columnas cada una indicando la posición X e Y, así como la anchura y altura del recuadro imaginario que contiene la cara que ha detectado. En este caso vemos que ha detectado 11 caras, cuando en nuestra imagen sólo hay seis personajes. Algo no parece ir bien, así que vamos a mostrar la imagen sobre la que vamos a dibujar estos cuadrados que nos ha devuelto el clasificador.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
for (x, y, w, h) in detecciones:
    cv2.rectangle(img_RGB, (x, y), (x+w, y+h), (0,255,0), 10)

plt.imshow(img_RGB)
&lt;/pre&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEh4bm8t9kkV1oqapPnwrbeKVICYqyliZqJFE7YmT1qfMutIDpVRz7jhJhrsmNOD0zuDGJSYiX97yx1i5gMpzu87rb1CnB9KETAMZKAY3CaU0lra5GJk4587JMDw9cIIBul9o_CZ93dHYQLn44RpFUw0agtHlFBYmZKHiZh6C17H5o1Lauv1l0OQ8gRG&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;195&quot; data-original-width=&quot;381&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEh4bm8t9kkV1oqapPnwrbeKVICYqyliZqJFE7YmT1qfMutIDpVRz7jhJhrsmNOD0zuDGJSYiX97yx1i5gMpzu87rb1CnB9KETAMZKAY3CaU0lra5GJk4587JMDw9cIIBul9o_CZ93dHYQLn44RpFUw0agtHlFBYmZKHiZh6C17H5o1Lauv1l0OQ8gRG&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

La buena noticia es que ha detectado todas las caras (no ha habido falsos negativos), pero además de las autenticas caras, ha detectado como caras otras partes de la imagen (falsos positivos). Vamos a tratar de realizar algunos ajustes a ver si conseguimos mejorar el resultado.
La función &lt;i&gt;detectMultiScale&lt;/i&gt; busca las caras realizando varios barridos de la imagen con ventanas de diferentes tamaños (ya que puede haber caras grandes cercanas o pequeñas que estén más lejanas). El tamaño en que se incrementa la ventana en cada barrido se puede cambiar con el parámetro &lt;i&gt;scaleFactor&lt;/i&gt;. Por ejemplo un valor de 1.1 (valor por defecto) quiere decir que la ventana seingrementa en un 10% en cada barrido, y un valor de 1.05, aumentaría la ventana en un 5%. Aquí hay que jugar con el valor según sea el tamaño de las caras que aparecen en la imagen. Para nuestra imagen, un valor de 1.7 parece funcionar bien

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
detecciones = detector_caras.detectMultiScale(img_RGB, scaleFactor = 1.7)
&lt;/pre&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgyrvqXAi9tCkzrYRG9b5EoVDwgocalZG1Ex5XGyZZUyqoV5EN5RWKNcSm-_0xPrNjnGNYm6gU1xLZ16sIVxTZjsJBIxbYk69UGv08z0Bkl4ulyqCtTWcPHnfusdJ9pny4Y4H-9zfErVZFK_5WO1MTL0J5U20FYslphPobypJjtPIA-NcNihXxWC2Gf&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;195&quot; data-original-width=&quot;381&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgyrvqXAi9tCkzrYRG9b5EoVDwgocalZG1Ex5XGyZZUyqoV5EN5RWKNcSm-_0xPrNjnGNYm6gU1xLZ16sIVxTZjsJBIxbYk69UGv08z0Bkl4ulyqCtTWcPHnfusdJ9pny4Y4H-9zfErVZFK_5WO1MTL0J5U20FYslphPobypJjtPIA-NcNihXxWC2Gf&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

Otro parámetro con el que podemos jugar es &lt;i&gt;minNeighbors&lt;/i&gt;. Cuando el algoritmo va recorriendo la imagen, acaba reconociendo la misma cara en múltiples ocasiones (aunque sólo nos devuelva una ventana). Por ejemplo, puede reconocer la misma cara con la misma ventana al desplazarla sólo unos pocos pixeles, o en sus múltiples pasadas con diferentes tamaños de ventana. Con &lt;i&gt;minNeighbors&lt;/i&gt; podemos indicar el número mínimo de detecciones de una cara que han de darse para que se considere una detección válida. Por ejemplo, la siguiente línea es capaz de detectar correctamente las caras a pesar de no utilizar el parámetro &lt;i&gt;scaleFactor&lt;/i&gt;.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
detecciones = detector_caras.detectMultiScale(img_RGB, minNeighbors = 20)
&lt;/pre&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgyrvqXAi9tCkzrYRG9b5EoVDwgocalZG1Ex5XGyZZUyqoV5EN5RWKNcSm-_0xPrNjnGNYm6gU1xLZ16sIVxTZjsJBIxbYk69UGv08z0Bkl4ulyqCtTWcPHnfusdJ9pny4Y4H-9zfErVZFK_5WO1MTL0J5U20FYslphPobypJjtPIA-NcNihXxWC2Gf&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;195&quot; data-original-width=&quot;381&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgyrvqXAi9tCkzrYRG9b5EoVDwgocalZG1Ex5XGyZZUyqoV5EN5RWKNcSm-_0xPrNjnGNYm6gU1xLZ16sIVxTZjsJBIxbYk69UGv08z0Bkl4ulyqCtTWcPHnfusdJ9pny4Y4H-9zfErVZFK_5WO1MTL0J5U20FYslphPobypJjtPIA-NcNihXxWC2Gf&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

Por último tenemos los parámetros &lt;i&gt;minSize&lt;/i&gt; y &lt;i&gt;maxSize&lt;/i&gt;, que permiten indicar el tamaño mínimo y máximo de la cara a detectar. Podemos ver la utilidad práctica con un ejemplo. Veamos qué detecta el clasificador con los siguientes parámetros.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
detecciones = detector_caras.detectMultiScale(img_RGB, minNeighbors = 10)
&lt;/pre&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjnoJDlDaI1wbCi9Kd7cDH6qKj98EoZP54c05zX1FrPys2COR68g1sh5KtVtgO_aeXmcKtkov7CyHTcVzx_pCDUVwlnG6lQiuETjf1DrHhGxwcJdM0gkNkFLr9TDtlSpU62jzp0gYbInhiQfAE0KbVzGBG5CmHnskVfhLog91gbFDwUUKu3rkkRk7Kg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;195&quot; data-original-width=&quot;381&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjnoJDlDaI1wbCi9Kd7cDH6qKj98EoZP54c05zX1FrPys2COR68g1sh5KtVtgO_aeXmcKtkov7CyHTcVzx_pCDUVwlnG6lQiuETjf1DrHhGxwcJdM0gkNkFLr9TDtlSpU62jzp0gYbInhiQfAE0KbVzGBG5CmHnskVfhLog91gbFDwUUKu3rkkRk7Kg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

Vemos que hay un falso positivo en la oreja del capitán Kirk. Una forma de manejar este tipo de problemas, si sabemos que las caras van a ser mayores (o menores) de cierto tamaño, es indicar un tamaño mínimo (o máximo) para la detección.

&lt;pre class=&quot;prettyprint linenums&quot;&gt;
detecciones = detector_caras.detectMultiScale(img_RGB, minNeighbors = 10, minSize = (100,100))
&lt;/pre&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgyrvqXAi9tCkzrYRG9b5EoVDwgocalZG1Ex5XGyZZUyqoV5EN5RWKNcSm-_0xPrNjnGNYm6gU1xLZ16sIVxTZjsJBIxbYk69UGv08z0Bkl4ulyqCtTWcPHnfusdJ9pny4Y4H-9zfErVZFK_5WO1MTL0J5U20FYslphPobypJjtPIA-NcNihXxWC2Gf&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;195&quot; data-original-width=&quot;381&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgyrvqXAi9tCkzrYRG9b5EoVDwgocalZG1Ex5XGyZZUyqoV5EN5RWKNcSm-_0xPrNjnGNYm6gU1xLZ16sIVxTZjsJBIxbYk69UGv08z0Bkl4ulyqCtTWcPHnfusdJ9pny4Y4H-9zfErVZFK_5WO1MTL0J5U20FYslphPobypJjtPIA-NcNihXxWC2Gf&quot;/&gt;&lt;/a&gt;&lt;/div&gt;


Como dije al principio, hay otros tipos de clasificadores que tienen mayor rendimiento, pero este es bastante sencillo y eficiente incluso con baja capacidad de proceso, por lo que es muy interesante. En otra ocasión veremos si es posible, no sólo reconocer que hay una cara, sino, además, intentar saber de quién es la cara. Es lo que se denomina reconocimiento facial, o en general reconocimiento de objetos (o de caracteres, o de tipos de naves de la federación, etc.). Hasta entonces... larga vida y prosperidad.</description><link>http://www.ellaberintodefalken.com/2022/01/deteccion-caras-objetos.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEgDmlQkdBVU9mxEuDRId45Z2ATLQY96CUKX4isCTxGIhFrITh9Bje0h_e3SN8fE8Ydv9znQ_WQ2jP51zyo-YmRgt-Dj0WZL1YGEMjq9XftxKNL4-sfztEX0SWoY2xx4JEdwhjST-espo0c66mcianSLRheuHiBxy7doM3I89hWbFpaOFzmIofEbkq_A=s72-c" height="72" width="72"/><thr:total>1</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-1215859482423375135</guid><pubDate>Wed, 10 Feb 2021 17:20:50 +0000</pubDate><atom:updated>2026-07-15T12:01:23.485+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Electrónica</category><category domain="http://www.blogger.com/atom/ns#">LCD 1602</category><category domain="http://www.blogger.com/atom/ns#">post</category><category domain="http://www.blogger.com/atom/ns#">Raspberry</category><category domain="http://www.blogger.com/atom/ns#">Raspberry Pi Pico</category><category domain="http://www.blogger.com/atom/ns#">Sensor temperatura</category><title>Cómo leer el sensor de temperatura de la Raspberry Pi Pico</title><description>&lt;/p&gt;&lt;p&gt;Leyendo las especificaciones de la Raspberry Pi Pico me llamó la atención que venía equipada con un sensor de temperatura en el propio chip, así que me puse a buscar información. En este artículo os quiero mostrar cómo acceder a él y de camino cómo utilizar el conversor analógico/digital (ADC).&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7ewzagBag8cApGfYxRKwTqD03olm866izVGdJz9LaHtyjxghMISamndqGQByPlBlFKaL0XTgqrvzp8Cqr7bwyEwV5QHGG5e6V8fBEsaf4sev2HjQmFmV9B3IlzRZKcqiPaEb7NFn2V73DM2WZTE4YXYmD34juNAJAR9Jv_lGcw2oHTF4sDCuD0pNYY6I/s1024/8253d-img_20210210_180304.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Raspberry Pi Pico&quot; border=&quot;0&quot; width=&quot;600&quot; data-original-height=&quot;768&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7ewzagBag8cApGfYxRKwTqD03olm866izVGdJz9LaHtyjxghMISamndqGQByPlBlFKaL0XTgqrvzp8Cqr7bwyEwV5QHGG5e6V8fBEsaf4sev2HjQmFmV9B3IlzRZKcqiPaEb7NFn2V73DM2WZTE4YXYmD34juNAJAR9Jv_lGcw2oHTF4sDCuD0pNYY6I/s600/8253d-img_20210210_180304.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;


&lt;/p&gt;&lt;p&gt;El siguiente esquema muestra cómo está conectado el ADC a los GPIO y al sensor de temperatura interno.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhINs88tIEN7LjruLt0Q6kCv1roMxdeo0eHbce57GCPUXuHD219vgqPj0NWEamj6weSjVWoPsWZM4JuLDlDoWzBe8cFI_5rJV3ddTu3_47LRg4WfyTxmPsf8iwn_D2Aty_QlpenoWilvmF5q_gMK6Dpaw8bbEdZEND6N9mfwLLmFfUm7V6TrOOhU22UVbk/s1600/81ad0-captura-de-pantalla-de-2021-02-10-17-17-23.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Multiplexor ADC Raspberry Pi Pico&quot; border=&quot;0&quot; data-original-height=&quot;867&quot; data-original-width=&quot;789&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhINs88tIEN7LjruLt0Q6kCv1roMxdeo0eHbce57GCPUXuHD219vgqPj0NWEamj6weSjVWoPsWZM4JuLDlDoWzBe8cFI_5rJV3ddTu3_47LRg4WfyTxmPsf8iwn_D2Aty_QlpenoWilvmF5q_gMK6Dpaw8bbEdZEND6N9mfwLLmFfUm7V6TrOOhU22UVbk/s1600/81ad0-captura-de-pantalla-de-2021-02-10-17-17-23.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;


&lt;p&gt;Es decir, tenemos un multiplexor de cuatro entradas que permite seleccionar la señal de entrada para el ADC. En nuestro caso, la que nos interesa es la 4, que es la fuente de tensión proveniente del sensor. Para seleccionar la entrada en el multiplexor usamos la siguiente función.&lt;/p&gt;

&lt;/p&gt;&lt;pre&gt;adc_select_input(4);&lt;/pre&gt;

&lt;p&gt;Para hacernos una idea, en la página 574 del &lt;i&gt;datasheet&lt;/i&gt; del RP4020 nos dice que esta tensión es de 0.706V a 27 grados centígrados. La fórmula para aproximar la temperatura con respecto a esta tensión es &lt;/p&gt;

&lt;pre&gt;T = 27 - (ADC_voltage - 0.706)/0.001721&lt;/pre&gt;

&lt;p&gt;También hay que poner a 1 el registro TS_EN, para conectar el sensor. El SDK nos permite hacer esto fácilmente con la función&lt;/p&gt;

&lt;pre&gt;adc_set_temp_sensor_enabled(true);&lt;/pre&gt;

&lt;p&gt;Veamos pues cómo sería el proceso completo con un ejemplo en el que vamos a usar un display LCD 1602 para mostrar la temperatura. El manejo del LCD se hace por I2C y no voy a entrar en mucho detalle aquí, ya que hay bastante documentación al respecto. Además tenéis un ejemplo en la carpeta &lt;i&gt;pico-examples/i2c/lcd_1602_i2c/&lt;/i&gt; que acompaña al SDK.&lt;/p&gt;

&lt;p&gt;Ya os conté en otro artículo los &lt;a href=&quot;https://www.ellaberintodefalken.com/2021/01/primeros-pasos-con-raspberry-pi-pico.html&quot;&gt;pasos para crear un proyecto para la Raspberry Pi Pico&lt;/a&gt;, así que ya sabemos que necesitamos como mínimo dos archivos: CMakeLists.txt y el código fuente, que en este caso lo he llamado termometro.c.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cmake_minimum_required(VERSION 3.12)
#enable_language( C ASM )
include(~/tools/pico/pico-sdk/external/pico_sdk_import.cmake)
#include(pico_sdk_import.cmake)
project(termometro C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(termometro termometro.c)
target_link_libraries(termometro pico_stdlib hardware_i2c hardware_adc)
pico_add_extra_outputs(termometro)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Este es el contenido del archivo CMakeLists.txt, que es muy similar al que ya vimos en el anterior artículo. Se diferencia en la siguiente línea, que indica al compilador que debe añadir las librería para la gestión del bus I2C (para el LCD) y del ADC.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;target_link_libraries(termometro pico_stdlib hardware_i2c hardware_adc)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Antes de poner el código fuente completo, voy a centrarme en los pasos para leer el sensor de temperatura. El código mínimo para leer el sensor tendría una forma como ésta.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#include &quot;hardware/adc.h&quot;
int main() {
    // 12 bits. Vref=3.3V
    const float conv_factor = 3.3f / (1&amp;lt;&amp;lt;12); 
	
    uint16_t lectura;
    float lect_conv, temperatura;
    adc_init();
    adc_set_temp_sensor_enabled(true);
    adc_select_input(4); // imput 4 - sensor temperatura
	    
    while(1) {
        // Consultar ADC
        lectura = adc_read();
        lect_conv = lectura * conv_factor;
        temperatura = 27-(lect_conv-0.706)/0.001721;
        // mostrar temperatura
		printf(&quot;%2.1f&quot;, temperatura);
		    
        sleep_ms(20000);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A la lectura del ADC hay que aplicarle un factor de conversión. En este caso, la tensión máxima es de 3.3V y nuestro ADC es de 12 bits, por lo que este factor es 3.3f/(1&amp;lt;&amp;lt;12). Al resultado le aplicamos la fórmula que habíamos visto más arriba para aproximar la temperatura.&lt;/p&gt;
&lt;p&gt;El programa completo es algo más complicado, ya que incluye el código para el manejo del LCD, y sería el siguiente.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &quot;pico/stdlib.h&quot;
#include &quot;hardware/i2c.h&quot;
#include &quot;hardware/adc.h&quot;
#include &quot;pico/binary_info.h&quot;
/* panel LCD 16x2 con adaptador I2C PCF8574 
   GPIO 20 (pin 26)-&amp;gt; SDA
   GPIO 21 (pin 27)-&amp;gt; SCL
   VSYS (pin 39) -&amp;gt; VCC
   GND (pin 38)  -&amp;gt; GND
*/
// algunas definiciones para controlar el LCD
const int LCD_CLEARDISPLAY = 0x01;
const int LCD_RETURNHOME = 0x02;
const int LCD_ENTRYMODESET = 0x04;
const int LCD_DISPLAYCONTROL = 0x08;
const int LCD_CURSORSHIFT = 0x10;
const int LCD_FUNCTIONSET = 0x20;
const int LCD_SETCGRAMADDR = 0x40;
const int LCD_SETDDRAMADDR = 0x80;
const int LCD_ENTRYSHIFTINCREMENT = 0x01;
const int LCD_ENTRYLEFT = 0x02;
const int LCD_BLINKON = 0x01;
const int LCD_CURSORON = 0x02;
const int LCD_DISPLAYON = 0x04;
const int LCD_MOVERIGHT = 0x04;
const int LCD_DISPLAYMOVE = 0x08;
const int LCD_5x10DOTS = 0x04;
const int LCD_2LINE = 0x08;
const int LCD_8BITMODE = 0x10;
const int LCD_BACKLIGHT = 0x08;
const int LCD_ENABLE_BIT = 0x04;
// dirección del bus I2C
// podría cambiar según modelos
#define I2C_PORT_LCD i2c0
static int addr_led = 0x27;
#define LCD_CHARACTER  1
#define LCD_COMMAND    0
#define MAX_LINES      2
#define MAX_CHARS      16
// pines I2C en la placa
#define LCD_SDA 20
#define LCD_SCL 21
void i2c_write_byte(uint8_t val) {
    i2c_write_blocking(I2C_PORT_LCD, addr_led, &amp;amp;val, 1, false);
}
void lcd_toggle_enable(uint8_t val) {
#define DELAY_US 600
    sleep_us(DELAY_US);
    i2c_write_byte(val | LCD_ENABLE_BIT);
    sleep_us(DELAY_US);
    i2c_write_byte(val &amp;amp; ~LCD_ENABLE_BIT);
    sleep_us(DELAY_US);
}void lcd_send_byte(uint8_t val, int mode) {
    uint8_t high = mode | (val &amp;amp; 0xF0) | LCD_BACKLIGHT;
    uint8_t low = mode | ((val &amp;lt;&amp;lt; 4) &amp;amp; 0xF0) | LCD_BACKLIGHT;
    i2c_write_byte(high);
    lcd_toggle_enable(high);
    i2c_write_byte(low);
    lcd_toggle_enable(low);
}
void lcd_clear(void) {
    lcd_send_byte(LCD_CLEARDISPLAY, LCD_COMMAND);
}
void lcd_set_cursor(int line, int position) {
    int val = (line == 0) ? 0x80 + position : 0xC0 + position;
    lcd_send_byte(val, LCD_COMMAND);
}
static void inline lcd_char(char val) {
    lcd_send_byte(val, LCD_CHARACTER);
}
void lcd_string(const char *s) {
    while (*s) {
        lcd_char(*s++);
    }
}
void lcd_init() {
    lcd_send_byte(0x03, LCD_COMMAND);
    lcd_send_byte(0x03, LCD_COMMAND);
    lcd_send_byte(0x03, LCD_COMMAND);
    lcd_send_byte(0x02, LCD_COMMAND);
    lcd_send_byte(LCD_ENTRYMODESET | LCD_ENTRYLEFT, LCD_COMMAND);
    lcd_send_byte(LCD_FUNCTIONSET | LCD_2LINE, LCD_COMMAND);
    lcd_send_byte(LCD_DISPLAYCONTROL | LCD_DISPLAYON, LCD_COMMAND);
    lcd_clear();
}
int main() {
	uint16_t lectura;
	float lect_conv, temperatura;
	char s[10];
	const float conv_factor = 3.3f / (1&amp;lt;&amp;lt;12); 
    i2c_init(I2C_PORT_LCD, 100 * 1000);
    gpio_set_function(LCD_SDA, GPIO_FUNC_I2C);
    gpio_set_function(LCD_SCL, GPIO_FUNC_I2C);
    gpio_pull_up(LCD_SDA);
    gpio_pull_up(LCD_SCL);
    bi_decl( bi_2pins_with_func(LCD_SDA, LCD_SCL, GPIO_FUNC_I2C));
	
    lcd_init();
    adc_init();
	adc_set_temp_sensor_enabled(true);
	adc_select_input(4); // imput 4 - sensor temperatura
	
	while(1) {
		// Consultar ADC
		lectura = adc_read();
		lect_conv = lectura * conv_factor;
		temperatura = 27-(lect_conv-0.706)/0.001721;
        // mostrar temperatura en el LCD
		sprintf(s, &quot;%2.1f&quot;, temperatura);
		lcd_set_cursor(0,0);
		lcd_string(&quot;Temperatura:&quot;);
		lcd_set_cursor(1,0);
		lcd_string(s);
		sleep_ms(20000);
		lcd_clear();
	}
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Las conexiones entre el LCD y la Raspberry Pi Pico son las siguientes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   GPIO 20 (pin 26)-&amp;gt; SDA en LCD
   GPIO 21 (pin 27)-&amp;gt; SCL en LCD
   VSYS (pin 39) -&amp;gt; VCC en LCD
   GND (pin 38)  -&amp;gt; GND en LCD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ya vimos que para poder compilar el programa tendríamos que invocar a &lt;i&gt;cmake&lt;/i&gt; para que se genere el &lt;i&gt;makefile&lt;/i&gt; correspondiente.&lt;/p&gt;
&lt;pre&gt;cmake .&lt;/pre&gt;
&lt;p&gt;Y finalmente compilamos con &lt;i&gt;make&lt;/i&gt;. El resultado en el siguiente video, donde se observa cómo sube la temperatura al tapar el microcontrolador con el dedo.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe class=&quot;BLOG_video_class&quot; allowfullscreen=&quot;&quot; youtube-src-id=&quot;-eRMOGCJZbo&quot; width=&quot;600&quot; height=&quot;498&quot; src=&quot;https://www.youtube.com/embed/-eRMOGCJZbo&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

</description><link>http://www.ellaberintodefalken.com/2021/02/leer-sensor-temperatura-Raspberry-Pi-Pico.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7ewzagBag8cApGfYxRKwTqD03olm866izVGdJz9LaHtyjxghMISamndqGQByPlBlFKaL0XTgqrvzp8Cqr7bwyEwV5QHGG5e6V8fBEsaf4sev2HjQmFmV9B3IlzRZKcqiPaEb7NFn2V73DM2WZTE4YXYmD34juNAJAR9Jv_lGcw2oHTF4sDCuD0pNYY6I/s72-c/8253d-img_20210210_180304.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-3761156407422505903</guid><pubDate>Sat, 30 Jan 2021 11:50:59 +0000</pubDate><atom:updated>2026-07-15T11:45:20.017+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Electrónica</category><category domain="http://www.blogger.com/atom/ns#">post</category><category domain="http://www.blogger.com/atom/ns#">Raspberry</category><category domain="http://www.blogger.com/atom/ns#">Raspberry Pi Pico</category><category domain="http://www.blogger.com/atom/ns#">RP2040</category><category domain="http://www.blogger.com/atom/ns#">tutorial</category><title>Primeros pasos con Raspberry Pi Pico</title><description>&lt;p&gt;Hace pocos días la fundación Raspberry Pi sacaba al mercado una curiosa plaquita llamada Raspberry Pi Pico. Curiosa porque se separa de lo que venían haciendo.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd6YfVayjRttYYZowavwMUZqJ1b8x60q-BOS7kyfasg67joC97oeQpQC5n5I5cUv8szBGZIPOnismYo95fPZCHN3r2Xj8v0lGORME8cpmXHBaHoKrvaIr5bdQBONDrBzJZX9Y-k_AYyJ7e3ANodEL1i1Gs7iXde0lkH4h239lsCBWTY1vMI4qIP2R30ZA/s1024/73222-img_20210130_120502.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Raspberry Pi Pico&quot; border=&quot;0&quot; width=&quot;600&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd6YfVayjRttYYZowavwMUZqJ1b8x60q-BOS7kyfasg67joC97oeQpQC5n5I5cUv8szBGZIPOnismYo95fPZCHN3r2Xj8v0lGORME8cpmXHBaHoKrvaIr5bdQBONDrBzJZX9Y-k_AYyJ7e3ANodEL1i1Gs7iXde0lkH4h239lsCBWTY1vMI4qIP2R30ZA/s600/73222-img_20210130_120502.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Es decir, nos encontramos ante una placa al estilo del Arduino nano, pero comandada por el microcontrolador RP4020, basado en el ARM Cortex M0+ de doble núcleo, que ha sido también diseñado por ellos. En la placa de desarrollo nos ofrecen 26 GPIOs con soporte para SPI, I2C, ADC, UART y PWM. Acompañado de una completísima documentación y un entorno de desarrollo basado en Visual Studio Code. Y lo que es mejor, sólo por 4 dólares.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEin4sxtrF8I3qPC4nkXOX1C8hGNEBKGIWBtYieA16iFv-hpFUto30O_7fd0oyaP4c2uPncT5AFuG8mCJpaALwRxvVKTJcT5Bk1kOpNqXwfS4_GLkZpL5jn-V54KPqSmPj_JXYYyy3XcZ694jx9gbp0i-ixNWFpjAI2NLR1X0G0A6OlbJYrQt02WbmkvfLQ/s1600/fa5b2-pico-r3-pinout.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Rasperry Pi Pico pinout&quot; border=&quot;0&quot; data-original-height=&quot;1500&quot; data-original-width=&quot;2500&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEin4sxtrF8I3qPC4nkXOX1C8hGNEBKGIWBtYieA16iFv-hpFUto30O_7fd0oyaP4c2uPncT5AFuG8mCJpaALwRxvVKTJcT5Bk1kOpNqXwfS4_GLkZpL5jn-V54KPqSmPj_JXYYyy3XcZ694jx9gbp0i-ixNWFpjAI2NLR1X0G0A6OlbJYrQt02WbmkvfLQ/s1600/fa5b2-pico-r3-pinout.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;La instalación del entorno de desarrollo oficial es muy sencilla y viene bien explicada en la documentación. Aunque si, como yo, prefieres la línea de comando, en este artículo os explicaré como poner en marcha un proyecto para este microcontrolador y programarlo con vuestro editor favorito. En este caso, para equipos con Debian y derivados. Antes de nada necesitaremos instalar el &lt;em&gt;toolchain&lt;/em&gt; para ARM, git para obtener el SDK y los ejemplos del repositorio de Raspberry, y &lt;em&gt;cmake&lt;/em&gt;, que es la herramienta en la que está basada el proceso de compilación en el entorno de desarrollo.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install cmake gcc-arm-none-eabi build-essential git&lt;/code&gt;&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Creamos un directorio que contendrá el SDK y los ejemplos, siempre útiles a la hora de afrontar un proyecto propio.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;pre&gt;&lt;code&gt;mkdir pico
cd pico
git clone -b master https://github.com/raspberrypi/pico-sdk.git
cd pico-sdk/
git submodule update --init
cd ..
git clone -b master https://github.com/raspberrypi/pico-examples.git&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;El directorio &lt;em&gt;pico-sdk&lt;/em&gt; contiene las librerías necesarias para la programación del microcontrolador, y la carpeta &lt;em&gt;pico-examples&lt;/em&gt;, unos cuantos ejemplos. En la documentación oficial y en los miles de tutoriales que aparecerán estos días puedes ver cómo compilar y ejecutar los ejemplos, pero yo voy a ir directo a explicar como crear un proyecto desde cero. He creado una carpeta llamada proyectos dentro del directorio pico, pero esto ya es cuestión de gustos y de la forma que tengas de organizarte.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mkdir proyectos
cd proyectos
mkdir blinky
cd blinky&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;En este caso, el proyecto que vamos a crear se llama blinky, y como adivinarás por el nombre, lo que vamos a hacer es hacer parpadear un LED, el &lt;em&gt;hola mundo&lt;/em&gt; de los microcontroladores. De hecho el código C es el mismo que viene en los ejemplos, así que simplemente crea un archivo llamado blink.c e introduce el siguiente código, que lo que hace parpadear el LED que hay conectado al GPIO25 de la placa.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/**
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 #include &quot;pico/stdlib.h&quot;
 int main() {
    const uint LED_PIN = 25;
    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN, GPIO_OUT);
    while (true) {
        gpio_put(LED_PIN, 1);
        sleep_ms(250);
        gpio_put(LED_PIN, 0);
        sleep_ms(250);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Como ya se ha dicho, el entorno de desarrollo está basado en &lt;em&gt;cmake&lt;/em&gt;, así que necesitamos crear un archivo llamado CMakeLists.txt donde se configura el proyecto. En este caso es el siguiente.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;pre&gt;&lt;code&gt;cmake_minimum_required(VERSION 3.12)
include(~/pico/pico-sdk/external/pico_sdk_import.cmake)
project(blink C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(blink blink.c)
target_link_libraries(blink pico_stdlib)
pico_add_extra_outputs(blink)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;En la línea del &lt;em&gt;include&lt;/em&gt; debéis poner la ruta donde instalasteis el SDK, que puede no coincidir con la mía. Sin entrar en mucho detalle, porque cmake es un universo en sí mismo, en la línea &lt;em&gt;project&lt;/em&gt;, pondremos el nombre de nuestro proyecto. &lt;/p&gt;

&lt;p&gt;En &lt;em&gt;add_executable&lt;/em&gt; indicamos el nombre del ejecutable que se va a generar y el archivo con el código fuente. También hay que añadir este nombre en &lt;em&gt;larget_link_libraries&lt;/em&gt;, para que se enlace con las librerías que vayamos a usar del SDK, en este caso sólo &lt;em&gt;pico_stdlib&lt;/em&gt;, pero su fuéramos a usar otras, como, por ejemplo, las de i2c, añadiríamos también &lt;em&gt;hardware_i2c&lt;/em&gt; detrás de &lt;em&gt;pico_stdlib&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Con &lt;em&gt;pico_add_extra_outputs&lt;/em&gt;, indicamos que queremos obtener el ejecutable, además de en formato elf, en formato .uf2, que es el que usa el microcontrolador.&lt;/p&gt;

&lt;p&gt;Ahora le decimos a &lt;em&gt;cmake&lt;/em&gt; que configure el proyecto y genere los archivos necesarios para poder compilar con &lt;em&gt;make&lt;/em&gt;. Esto lo hacemos con cmake . (importante poner el . después de cmake). Pero antes tendremos que configurar la variable de entorno PICO_SDK_PATH con la ruta del SDK.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;export PICO_SDK_PATH=~/pico/pico-sdk
cmake .&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Deberíamos ver algo parecido a esto.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Using PICO_SDK_PATH from environment (&#39;/home/alberto/tools/pico/pico-sdk&#39;)
Pico SDK is located at /home/alberto/tools/pico/pico-sdk
Defaulting PICO_PLATFORM to rp2040 since not specified.
Defaulting PICO platform compiler to pico_arm_gcc since not specified.
-- Defaulting build type to &#39;Release&#39; since not specified.
PICO compiler is pico_arm_gcc
PICO_GCC_TRIPLE defaulted to arm-none-eabi
-- The C compiler identification is GNU 9.2.1
-- The CXX compiler identification is GNU 9.2.1
-- The ASM compiler identification is GNU
-- Found assembler: /usr/bin/arm-none-eabi-gcc
Defaulting PICO target board to pico since not specified.
Using board configuration from /home/alberto/tools/pico/pico-sdk/src/boards/include/boards/pico.h
-- Found Python3: /usr/bin/python3.8 (found version &quot;3.8.5&quot;) found components: Interpreter 
TinyUSB available at /home/alberto/tools/pico/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040; adding USB support.
-- Could NOT find Doxygen (missing: DOXYGEN_EXECUTABLE) 
ELF2UF2 will need to be built
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alberto/tools/pico/proyectos/blinky&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Si nos fijamos ahora en el directorio, veremos que se han creado una serie de archivos nuevos, entre ellos, por ejemplo, un Makefile. Así que nos resta sólo ejecutar make para que nuestro proyecto se compile y genere un ejecutable.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;make&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Y deberíamos ver algo como lo siguiente.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Scanning dependencies of target ELF2UF2Build
&amp;#091;  1%] Creating directories for &#39;ELF2UF2Build&#39;
&amp;#091;  3%] No download step for &#39;ELF2UF2Build&#39;
&amp;#091;  5%] No patch step for &#39;ELF2UF2Build&#39;
&amp;#091;  6%] No update step for &#39;ELF2UF2Build&#39;
&amp;#091;  8%] Performing configure step for &#39;ELF2UF2Build&#39;
-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alberto/tools/pico/proyectos/blinky/elf2uf2
&amp;#091; 10%] Performing build step for &#39;ELF2UF2Build&#39;
Scanning dependencies of target elf2uf2
&amp;#091; 50%] Building CXX object CMakeFiles/elf2uf2.dir/main.cpp.o
&amp;#091;100%] Linking CXX executable elf2uf2
&amp;#091;100%] Built target elf2uf2
&amp;#091; 11%] No install step for &#39;ELF2UF2Build&#39;
&amp;#091; 13%] Completed &#39;ELF2UF2Build&#39;
&amp;#091; 13%] Built target ELF2UF2Build
Scanning dependencies of target bs2_default
&amp;#091; 15%] Building ASM object pico_sdk/src/rp2_common/boot_stage2/CMakeFiles/bs2_default.dir/boot2_w25q080.S.obj
&amp;#091; 16%] Linking ASM executable bs2_default.elf
&amp;#091; 16%] Built target bs2_default
Scanning dependencies of target bs2_default_bin
&amp;#091; 18%] Generating bs2_default.bin
&amp;#091; 18%] Built target bs2_default_bin
Scanning dependencies of target bs2_default_padded_checksummed_asm
&amp;#091; 20%] Generating bs2_default_padded_checksummed.S
&amp;#091; 20%] Built target bs2_default_padded_checksummed_asm
Scanning dependencies of target blink
&amp;#091; 22%] Building C object CMakeFiles/blink.dir/blink.c.obj
&amp;#091; 23%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_stdlib/stdlib.c.obj
&amp;#091; 25%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_gpio/gpio.c.obj
&amp;#091; 27%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_claim/claim.c.obj
&amp;#091; 28%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_sync/sync.c.obj
&amp;#091; 30%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_platform/platform.c.obj
&amp;#091; 32%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_uart/uart.c.obj
&amp;#091; 33%] Building ASM object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_divider/divider.S.obj
&amp;#091; 35%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/common/pico_time/time.c.obj
&amp;#091; 37%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/common/pico_time/timeout_helper.c.obj
&amp;#091; 38%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_timer/timer.c.obj
&amp;#091; 40%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/common/pico_sync/sem.c.obj
&amp;#091; 42%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/common/pico_sync/lock_core.c.obj
&amp;#091; 44%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/common/pico_sync/mutex.c.obj
&amp;#091; 45%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/common/pico_sync/critical_section.c.obj
&amp;#091; 47%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/common/pico_util/datetime.c.obj
&amp;#091; 49%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/common/pico_util/pheap.c.obj
&amp;#091; 50%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/common/pico_util/queue.c.obj
&amp;#091; 52%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_runtime/runtime.c.obj
&amp;#091; 54%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_clocks/clocks.c.obj
&amp;#091; 55%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_watchdog/watchdog.c.obj
&amp;#091; 57%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_xosc/xosc.c.obj
&amp;#091; 59%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_pll/pll.c.obj
&amp;#091; 61%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_vreg/vreg.c.obj
&amp;#091; 62%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_irq/irq.c.obj
&amp;#091; 64%] Building ASM object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/hardware_irq/irq_handler_chain.S.obj
&amp;#091; 66%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_printf/printf.c.obj
&amp;#091; 67%] Building ASM object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_bit_ops/bit_ops_aeabi.S.obj
&amp;#091; 69%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_bootrom/bootrom.c.obj
&amp;#091; 71%] Building ASM object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_divider/divider.S.obj
&amp;#091; 72%] Building ASM object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_double/double_aeabi.S.obj
&amp;#091; 74%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_double/double_init_rom.c.obj
&amp;#091; 76%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_double/double_math.c.obj
&amp;#091; 77%] Building ASM object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_double/double_v1_rom_shim.S.obj
&amp;#091; 79%] Building ASM object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_int64_ops/pico_int64_ops_aeabi.S.obj
&amp;#091; 81%] Building ASM object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_float/float_aeabi.S.obj
&amp;#091; 83%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_float/float_init_rom.c.obj
&amp;#091; 84%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_float/float_math.c.obj
&amp;#091; 86%] Building ASM object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_float/float_v1_rom_shim.S.obj
&amp;#091; 88%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_malloc/pico_malloc.c.obj
&amp;#091; 89%] Building ASM object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_mem_ops/mem_ops_aeabi.S.obj
&amp;#091; 91%] Building ASM object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_standard_link/crt0.S.obj
&amp;#091; 93%] Building CXX object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_standard_link/new_delete.cpp.obj
&amp;#091; 94%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_standard_link/binary_info.c.obj
&amp;#091; 96%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_stdio/stdio.c.obj
&amp;#091; 98%] Building C object CMakeFiles/blink.dir/home/alberto/tools/pico/pico-sdk/src/rp2_common/pico_stdio_uart/stdio_uart.c.obj
&amp;#091;100%] Linking CXX executable blink.elf
&amp;#091;100%] Built target blink
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Si al final vemos el mensaje&lt;em&gt; Built target blink&lt;/em&gt; es que todo ha ido bien y estamos listos para pasar el ejecutable al microcontrolador. Aquí también han sido originales los chicos de Raspberry. Para mover el ejecutable a la placa hay que seguir este orden. Primero pulsamos el pequeño botón que hay en la placa (hay que mantenerlo pulsado).&lt;/p&gt;
&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9hyTvNSD-GDrKUJq1P6PIocyGVNB5_J968wY-1EKC34M6643P29P-D3SV-Hgss0LnITywTQmx0cqMNe3L7A5GSXoyQxNkQSvGtpMNeCmyXqWOL0KohOyI1FsUsMBnwLnUZE1PgnE0AR9zj4yNGmlvw5TVWGy_sxXwzNfvSG3-7K4bak_EaL8q3S481Ok/s1600/20f1d-img_20210130_115722.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Programar la Raspberry Pi Pico&quot; border=&quot;0&quot; data-original-height=&quot;768&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9hyTvNSD-GDrKUJq1P6PIocyGVNB5_J968wY-1EKC34M6643P29P-D3SV-Hgss0LnITywTQmx0cqMNe3L7A5GSXoyQxNkQSvGtpMNeCmyXqWOL0KohOyI1FsUsMBnwLnUZE1PgnE0AR9zj4yNGmlvw5TVWGy_sxXwzNfvSG3-7K4bak_EaL8q3S481Ok/s1600/20f1d-img_20210130_115722.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

  
&lt;p&gt;Seguidamente se conecta al ordenador mediante el cable USB y, tras esto, se suelta el botón. La tarjeta se monta como si fuera una unidad más en la que encontramos dos archivos.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfMD4g8_mHNkPtJkUT6Z_mZ9q_QdWn4ug-DFzeetjlWzl6KC37d1VzKVs-GxAm9eMzQV3rukQkwtU_Mp57v3fWA-BtZS99q3_5vl0LziTcnTbonBuf6eV7NKJuw9tQfJuQEYY2zWHaC-I9PfW7uw_7sBQp4yOQG3yjyeKlpLk_dskCwelKDUCfKwh70o8/s1600/3ef8c-captura-de-pantalla-de-2021-01-30-12-24-23.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Programar Raspberry Pi Pico&quot; border=&quot;0&quot; data-original-height=&quot;443&quot; data-original-width=&quot;747&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfMD4g8_mHNkPtJkUT6Z_mZ9q_QdWn4ug-DFzeetjlWzl6KC37d1VzKVs-GxAm9eMzQV3rukQkwtU_Mp57v3fWA-BtZS99q3_5vl0LziTcnTbonBuf6eV7NKJuw9tQfJuQEYY2zWHaC-I9PfW7uw_7sBQp4yOQG3yjyeKlpLk_dskCwelKDUCfKwh70o8/s1600/3ef8c-captura-de-pantalla-de-2021-01-30-12-24-23.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;El archivo INDEX.HTM nos lleva directos a la documentación y el archivo INFO_UF2.TXT contiene información sobre la versión de la tarjeta. Sólo tenemos que arrastrar el archivo blink.uf2 a esta ventana, e inmediatamente se desmontará la unidad y comenzará a ejecutarse. El LED de la placa debería empezar a parpadear como en el siguiente vídeo.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe class=&quot;BLOG_video_class&quot; allowfullscreen=&quot;&quot; youtube-src-id=&quot;EmrWmv5iNbY&quot; width=&quot;600&quot; height=&quot;498&quot; src=&quot;https://www.youtube.com/embed/EmrWmv5iNbY&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;p&gt;La intención de este artículo era simple, ver cómo poner en marcha un pequeño proyecto y programar la placa. Espero en un próximo artículo profundizar algo más y crear un proyecto algo más completo y realista. Hasta entonces, a disfrutar.&lt;/p&gt;
</description><link>http://www.ellaberintodefalken.com/2021/01/primeros-pasos-con-raspberry-pi-pico.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd6YfVayjRttYYZowavwMUZqJ1b8x60q-BOS7kyfasg67joC97oeQpQC5n5I5cUv8szBGZIPOnismYo95fPZCHN3r2Xj8v0lGORME8cpmXHBaHoKrvaIr5bdQBONDrBzJZX9Y-k_AYyJ7e3ANodEL1i1Gs7iXde0lkH4h239lsCBWTY1vMI4qIP2R30ZA/s72-c/73222-img_20210130_120502.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-3077656905659251213</guid><pubDate>Wed, 12 Feb 2020 11:55:00 +0000</pubDate><atom:updated>2022-01-14T11:39:48.139+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Grafos</category><category domain="http://www.blogger.com/atom/ns#">grafos de conocimiento</category><category domain="http://www.blogger.com/atom/ns#">Knowlege Graph</category><category domain="http://www.blogger.com/atom/ns#">NetworkX</category><category domain="http://www.blogger.com/atom/ns#">teoría de grafos</category><title>Manejo de grafos con NetworkX en Python</title><description>El aprendizaje computacional es un área de investigación que en los últimos años ha tenido un auge importante, sobre todo gracias al aprendizaje profundo (Deep Learning). Pero no todo son redes neuronales. Paralelamente a estas técnicas, más bien basadas en el aprendizaje de patrones, también hay un auge de otras técnicas, digamos, más basadas en el aprendizaje simbólico. Si echamos la vista algunos años atrás, podemos considerar que quizá, la promesa de la web semántica como gran base de conocimiento ha fracasado, pero no es tan así. Ha ido transmutándose y evolucionando hacia bases de conocimiento basadas en &lt;a href=&quot;https://es.wikipedia.org/wiki/Ontolog%C3%ADa_(inform%C3%A1tica)&quot;&gt;ontologías&lt;/a&gt; a partir de las cuales es posible obtener nuevo conocimiento. Es lo que llamamos razonamiento automático y empresas como Google ya lo utilizan para ofrecerte información adicional sobre tus búsquedas. Ellos lo llaman Grafos de Conocimiento o &lt;a href=&quot;https://googleblog.blogspot.com/2012/05/introducing-knowledge-graph-things-not.html&quot;&gt;Knowledge Graphs&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhddmp8pURokXCoqmc8qohj26WoeW5BlAL7qhJT-erKEMWolwKKhi3bWvbHzGup5d9S4pTvMtvli5JTfmZEidbsjJlhte48-l4rbQx4oSfZqcRKkevKyNBXdGvZ3eLw6tq8A6_eG_JfXTk/s1600/graph.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhddmp8pURokXCoqmc8qohj26WoeW5BlAL7qhJT-erKEMWolwKKhi3bWvbHzGup5d9S4pTvMtvli5JTfmZEidbsjJlhte48-l4rbQx4oSfZqcRKkevKyNBXdGvZ3eLw6tq8A6_eG_JfXTk/s640/graph.png&quot; width=&quot;640&quot; height=&quot;303&quot; data-original-width=&quot;1489&quot; data-original-height=&quot;706&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Gracias a estos grafos de conocimiento, Google puede ofrecerte información adicional sobre tu búsqueda, además de los enlaces a las páginas web. Por ejemplo, en la imagen siguiente vemos que si buscamos Marie Curie, Google nos ofrece datos sobre Marie, y otras personas con las que está relacionada. &lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUHvTMBN7F2-TJkBZ21ROq450bGSZsli_jIHLDvAtiNkHvzIngCqCee7iJO06wrNUsmJJNMr_f5HYcKhW-ranzwIt6PWQ0eU5v3A6oiKMuHplXkeBjl7gZvDL7nXNfxtFilpU0kYLL9QA/s1600/marie+curie.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUHvTMBN7F2-TJkBZ21ROq450bGSZsli_jIHLDvAtiNkHvzIngCqCee7iJO06wrNUsmJJNMr_f5HYcKhW-ranzwIt6PWQ0eU5v3A6oiKMuHplXkeBjl7gZvDL7nXNfxtFilpU0kYLL9QA/s640/marie+curie.png&quot; width=&quot;640&quot; height=&quot;288&quot; data-original-width=&quot;1355&quot; data-original-height=&quot;610&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Para entender como funcionan estos grafos de conocimiento necesitamos tener algunos conceptos previos bien asentados de los que quiero hablaros hoy. Así que para empezar y, como toda esta información se almacena en grandes grafos, hoy vengo hablaros de eso, de grafos.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;¿Qué es un grafo?&lt;/h3&gt;Matemáticamente, un &lt;a href=&quot;https://es.wikipedia.org/wiki/Grafo&quot;&gt;grafo&lt;/a&gt; es un par ordenado \(G=(V,A)\) donde V es un conjunto de vértices (o nodos) y A un conjunto de aristas que relacionan elementos entre sí. Gráficamente se representan como círculos (que son los vértices) unidos por líneas (que son las aristas). Mediante los grafos podemos describir multitud de procesos, situaciones, relaciones, estructuras, etc. Por ejemplo, un grafo nos puede servir para representar los enlaces moleculares de un elemento químico.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJeo0ZsyBWQ3tQapniUpSMjDsz_a7_nbc-tIv1nKgIx049mYAiTtAW9_KR-P2NAlMCl_zL7VXfzKiQ63qdcj0HZpDdgyzWLMQFBwTArjJQ7yk_lRY2k9MVCEZx52OW4NaN_xyt6IWFv-k/s1600/grafonormal.gif&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJeo0ZsyBWQ3tQapniUpSMjDsz_a7_nbc-tIv1nKgIx049mYAiTtAW9_KR-P2NAlMCl_zL7VXfzKiQ63qdcj0HZpDdgyzWLMQFBwTArjJQ7yk_lRY2k9MVCEZx52OW4NaN_xyt6IWFv-k/s640/grafonormal.gif&quot; width=&quot;640&quot; height=&quot;273&quot; data-original-width=&quot;440&quot; data-original-height=&quot;188&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Hay situaciones en las que nos interesa especificar que una relación (arista) tiene una dirección concreta. Por ejemplo, en el siguiente grafo se representan los diferentes estados de un semáforo, cuyas aristas tienen definidas una dirección concreta. Por ejemplo, es posible pasar del color amarillo al rojo, pero no al contrario. Este tipo de grafos se llaman &lt;b&gt;digrafos&lt;/b&gt; o &lt;b&gt;grafos dirigidos&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQj7J_L5qqntTVarH1nBneWVAiVNBPDy5C_JblmCxxmWBU5-MnEgpqR6DgoXQas3npaDI4vdxfk1va2KcaK73tla4qxIMmSSnDqhEi4PttbfFqbCfijo_Hx5PHJJ7VSy91RrAF1rSX0No/s1600/grafosemaforo.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQj7J_L5qqntTVarH1nBneWVAiVNBPDy5C_JblmCxxmWBU5-MnEgpqR6DgoXQas3npaDI4vdxfk1va2KcaK73tla4qxIMmSSnDqhEi4PttbfFqbCfijo_Hx5PHJJ7VSy91RrAF1rSX0No/s640/grafosemaforo.png&quot; width=&quot;640&quot; height=&quot;581&quot; data-original-width=&quot;620&quot; data-original-height=&quot;563&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Las aristas, además de ser dirigidas, pueden tener un valor o un peso que aporta cierta información acerca de las relaciones. Son los llamados &lt;b&gt;grafos ponderados&lt;/b&gt;. En el siguiente grafo, que representa una red de carreteras, vemos que cada arista contiene la distancia en kilómetros que separa cada ciudad. Esto es útil para, por ejemplo, poder calcular la ruta más corta entre dos puntos del grafo.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgz2JQIpvieAiO1ntwlMH9K_rig4dq44SZC7bPT8x-23i58pHgaMdo3WkoA3-p56AYdpteUIGat-Tddyn98a4hr0NxD3wmVQOfyFFb_vN3Pf8BmjJOa9lsfnwD9YvHSR9_ggfBRYvDy0lA/s1600/grafoponderado.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgz2JQIpvieAiO1ntwlMH9K_rig4dq44SZC7bPT8x-23i58pHgaMdo3WkoA3-p56AYdpteUIGat-Tddyn98a4hr0NxD3wmVQOfyFFb_vN3Pf8BmjJOa9lsfnwD9YvHSR9_ggfBRYvDy0lA/s640/grafoponderado.jpg&quot; width=&quot;640&quot; height=&quot;458&quot; data-original-width=&quot;700&quot; data-original-height=&quot;501&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Os resumo algunas de las métricas básicas de un grafo: el &lt;b&gt;orden del grafo&lt;/b&gt; es el número vértices que lo forman, mientras que la &lt;b&gt;medida del grafo&lt;/b&gt; es el número de aristas.&lt;br /&gt;
El &lt;b&gt;grado de un vértice&lt;/b&gt; es el número de aristas que inciden en él. Coincide con el número de &lt;b&gt;vértices adyacentes&lt;/b&gt; (por ejemplo, un vértice de grado=0 sería un vértice aislado).&lt;br /&gt;
La teoría de grafos es un campo amplio de las matemáticas, así que en vez de aburriros con más términos, iremos definiendo el resto de conceptos que vayamos necesitando sobre la marcha.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Manejo de grafos con NetworkX en Python&lt;/h3&gt;Hechas las presentaciones formales, vamos a remangarnos y a ver cómo podemos gestionar grafos en Python, para lo que nos valdremos de la librería &lt;a href=&quot;https://networkx.github.io/&quot;&gt;NetworkX&lt;/a&gt; (hay otras, pero como había que elegir una me he quedado con la que me parecía más sencilla, aunque no necesariamente mejor que las otras). Con el siguiente código vamos a crear un pequeño grafo que relaciona a Kevin Bacon con otros actores con los que ha trabajado (basándonos en la curiosa teoría de los &lt;a href=&quot;https://es.wikipedia.org/wiki/Seis_grados_de_separaci%C3%B3n&quot;&gt;seis grados de separación&lt;/a&gt; que cualquier actor tiene con Kevin Bacon).&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import networkx as nx

G = nx.Graph() # crear un grafo

#Añadir nodos
G.add_node(&quot;Kevin Bacon&quot;)
G.add_node(&quot;Tom Hanks&quot;)
G.add_nodes_from([&quot;Meg Ryan&quot;, &quot;Parker Posey&quot;, &quot;Lisa Kudrow&quot;])

#Añadir aristas
G.add_edge(&quot;Kevin Bacon&quot;, &quot;Tom Hanks&quot;)
G.add_edge(&quot;Kevin Bacon&quot;, &quot;Meg Ryan&quot;)
G.add_edges_from([(&quot;Tom Hanks&quot;, &quot;Meg Ryan&quot;), (&quot;Tom Hanks&quot;, &quot;Parker Posey&quot;)])
G.add_edges_from([(&quot;Parker Posey&quot;, &quot;Meg Ryan&quot;), (&quot;Parker Posey&quot;, &quot;Lisa Kudrow&quot;)])
&lt;/pre&gt;&lt;br /&gt;
Con nx.Graph() creamos el grafo (podemos usar nx.DiGraph() para crear un grafo dirigido). Para añadir nodos usamos add_node() para añadir un sólo nodo o add_nodes_from() para añadir varios de una vez. Para las aristas usamos add_edge() o add_edges_from() dependiendo de si queremos agregar una o varias aristas.&lt;br /&gt;
Si queremos saber el número de nodos y aristas, o qué elementos lo componen, podemos hacerlo fácilmente con:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;print(len(G.nodes))
print(len(G.edges))
print(G.nodes)
print(G.edges)
&lt;/pre&gt;&lt;pre style=&quot;font-size: 10px&quot;&gt;5
6
[&#39;Kevin Bacon&#39;, &#39;Tom Hanks&#39;, &#39;Meg Ryan&#39;, &#39;Parker Posey&#39;, &#39;Lisa Kudrow&#39;]
[(&#39;Kevin Bacon&#39;, &#39;Tom Hanks&#39;), (&#39;Kevin Bacon&#39;, &#39;Meg Ryan&#39;), (&#39;Tom Hanks&#39;, &#39;Meg Ryan&#39;), (&#39;Tom Hanks&#39;, &#39;Parker Posey&#39;), (&#39;Meg Ryan&#39;, &#39;Parker Posey&#39;), (&#39;Parker Posey&#39;, &#39;Lisa Kudrow&#39;)]
&lt;/pre&gt;&lt;br /&gt;
Es posible asignar atributos tanto a los nodos como a las aristas. Por ejemplo, podemos especificar que Tom Hanks tiene dos Oscars. Respecto a las aristas, como representan que un actor ha trabajado con otro, podemos añadir un atributo que sea la película en la que ambos han coincidido. Un uso muy habitual que tienen los atributos sobre las aristas es indicar el peso si se trata de un grafo ponderado, mediante el atributo &lt;i&gt;weight&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# asignar atributos a nodos y aristas
G.nodes[&quot;Tom Hanks&quot;][&quot;oscars&quot;] = 2
G.edges[&quot;Kevin Bacon&quot;, &quot;Tom Hanks&quot;][&quot;pelicula&quot;] = &quot;Apolo 13&quot;
G.edges[&quot;Kevin Bacon&quot;, &quot;Meg Ryan&quot;][&quot;pelicula&quot;] = &quot;En carne viva&quot;
G.edges[&quot;Parker Posey&quot;, &quot;Meg Ryan&quot;][&quot;pelicula&quot;] = &quot;Algo para recordar&quot;
G.edges[&quot;Parker Posey&quot;, &quot;Tom Hanks&quot;][&quot;pelicula&quot;] = &quot;Tienes un email&quot;
G.edges[&quot;Parker Posey&quot;, &quot;Lisa Kudrow&quot;][&quot;pelicula&quot;] = &quot;Esperando la hora&quot;
print(G.nodes(data=True))
print(G.edges(data=True))
print(G[&quot;Kevin Bacon&quot;])
&lt;/pre&gt;&lt;pre style=&quot;font-size: 10px&quot;&gt;[(&#39;Kevin Bacon&#39;, {}), (&#39;Tom Hanks&#39;, {&#39;oscars&#39;: 2}), (&#39;Meg Ryan&#39;, {}), (&#39;Parker Posey&#39;, {}), (&#39;Lisa Kudrow&#39;, {})]
[(&#39;Kevin Bacon&#39;, &#39;Tom Hanks&#39;, {&#39;pelicula&#39;: &#39;Apolo 13&#39;}), (&#39;Kevin Bacon&#39;, &#39;Meg Ryan&#39;, {&#39;pelicula&#39;: &#39;En carne viva&#39;}), (&#39;Tom Hanks&#39;, &#39;Meg Ryan&#39;, {}), (&#39;Tom Hanks&#39;, &#39;Parker Posey&#39;, {&#39;pelicula&#39;: &#39;Tienes un email&#39;}), (&#39;Meg Ryan&#39;, &#39;Parker Posey&#39;, {&#39;pelicula&#39;: &#39;Algo para recordar&#39;}), (&#39;Parker Posey&#39;, &#39;Lisa Kudrow&#39;, {&#39;pelicula&#39;: &#39;Esperando la hora&#39;})]
{&#39;Tom Hanks&#39;: {&#39;pelicula&#39;: &#39;Apolo 13&#39;}, &#39;Meg Ryan&#39;: {&#39;pelicula&#39;: &#39;En carne viva&#39;}}
&lt;/pre&gt;&lt;br /&gt;
Si quieremos mostrar gráficamente el resultado podemos usar &lt;a href=&quot;https://www.graphviz.org/&quot;&gt;graphviz&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import graphviz

A = nx.nx_agraph.to_agraph(G)
A.layout(&#39;dot&#39;)
#A.draw(&#39;salida.png&#39;) # guardar como png

graphviz.Source(A.to_string()) # mostrar en jupyter
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUwKBpPhap8KivZER9E5T_m_nG4sG-0Y76nLZjNZ7ZwHgwRMMqekUuFjHu2Cnug5fuXxWj86Gr3fF3x8g3pmmhrrP38z6nVuXqTrDNDWt4d61c1V3a5VFBk39wFSno5tjeltvZJAFJskc/s1600/salida.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUwKBpPhap8KivZER9E5T_m_nG4sG-0Y76nLZjNZ7ZwHgwRMMqekUuFjHu2Cnug5fuXxWj86Gr3fF3x8g3pmmhrrP38z6nVuXqTrDNDWt4d61c1V3a5VFBk39wFSno5tjeltvZJAFJskc/s1600/salida.png&quot; data-original-width=&quot;246&quot; data-original-height=&quot;443&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Si queremos almacenar nuestro grafo en disco, NetworX tiene una familia de funicones write_*() que facilitan esta operación. Aunque el más habitual es graphml se pueden almacenar en diferentes formatos (como GML, JSON, Pajek, YAML, etc.) &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# guardar grafo
nx.write_graphml(G, &quot;actores.graphml&quot;)
&lt;/pre&gt;&lt;br /&gt;
Para cargar el grafo usamos&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# cargar grafo
G2 = nx.read_graphml(&quot;actores.graphml&quot;)
print(G2.nodes)
&lt;/pre&gt;&lt;br /&gt;
Os he contado los rudimentos básicos del manejo de la librería NetworkX. Obviamente esto es sólo una pequeña parte de lo que puede hacer, pero nos vale como introducción. En próximos artículos os seguiré hablando de grafos y os contaré más cosas interesantes.</description><link>http://www.ellaberintodefalken.com/2020/02/grafos-con-networkx.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhddmp8pURokXCoqmc8qohj26WoeW5BlAL7qhJT-erKEMWolwKKhi3bWvbHzGup5d9S4pTvMtvli5JTfmZEidbsjJlhte48-l4rbQx4oSfZqcRKkevKyNBXdGvZ3eLw6tq8A6_eG_JfXTk/s72-c/graph.png" height="72" width="72"/><thr:total>7</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-3286255473660659924</guid><pubDate>Thu, 05 Dec 2019 14:04:00 +0000</pubDate><atom:updated>2019-12-07T20:09:23.938+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">machine learning</category><category domain="http://www.blogger.com/atom/ns#">regresión polinomial</category><category domain="http://www.blogger.com/atom/ns#">regresión polinómica</category><category domain="http://www.blogger.com/atom/ns#">SkLearn</category><title>Regresión polinómica para Machine Learning</title><description>La &lt;a href=&quot;https://www.ellaberintodefalken.com/2019/03/regresion-lineal-descenso-de-gradiente.html&quot;&gt;regresión lineal&lt;/a&gt; es una herramienta extremadamente potente para realizar análisis de datos y construir modelos de Machine Learning capaces de hacer inferencias sobre datos nuevos. Sin embargo, esto es sólo cierto para datos que tienen un comportamiento lineal, lo que no siempre ocurre. Vamos a trabajar con un dataset sencillo, pero cuyos datos no son lineales: &lt;a href=&quot;https://www.kaggle.com/akram24/position-salaries&quot;&gt;https://www.kaggle.com/akram24/position-salaries&lt;/a&gt; &lt;br /&gt;
Este dataset contiene los salarios según el nivel del empleado en dentro de una empresa.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibwzeY_hIQze_iinKzurZ9fnAnzOrVBSI_CAgRt6KEv7roLbkLwIV4pXP6-2r_16oWo-uZdJL_eMyJemeNz4sElqBnhxows5pWDclNhau7ksf8TK7SggDPv1Fu1xuyuXlt0dkc19nAaZo/s1600/c7217b04fe11f_position1_retina.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibwzeY_hIQze_iinKzurZ9fnAnzOrVBSI_CAgRt6KEv7roLbkLwIV4pXP6-2r_16oWo-uZdJL_eMyJemeNz4sElqBnhxows5pWDclNhau7ksf8TK7SggDPv1Fu1xuyuXlt0dkc19nAaZo/s640/c7217b04fe11f_position1_retina.jpg&quot; width=&quot;640&quot; height=&quot;375&quot; data-original-width=&quot;1024&quot; data-original-height=&quot;600&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Tras descargarlo de Kaggle y descomprimir el fichero tenemos un archivo CSV que podemos cargar con &lt;a href=&quot;https://www.ellaberintodefalken.com/2019/03/manipulacion-de-datos-con-pandas.html&quot;&gt;Pandas&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import pandas as pd

datos = pd.read_csv(&#39;Position_Salaries.csv&#39;)
print (datos)
&lt;/pre&gt;&lt;br /&gt;
&lt;pre&gt;    Position            Level  Salary
0   Business Analyst      1    45000
1  Junior Consultant      2    50000
2  Senior Consultant      3    60000
3            Manager      4    80000
4    Country Manager      5   110000
5     Region Manager      6   150000
6            Partner      7   200000
7     Senior Partner      8   300000
8            C-level      9   500000
9                CEO     10  1000000

&lt;/pre&gt;&lt;br /&gt;
Son muy pocos datos, lo que hace a este conjunto de datos interesante para usarlo como ejemplo. Como siempre, empezamos por el principio. Vamos a visualizar qué forma tienen los datos.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from matplotlib import pyplot as plt

x = datos[&#39;Level&#39;].values.reshape(-1, 1) # necesitamos un array de 2D para SkLearn
y = datos[&#39;Salary&#39;].values.reshape(-1, 1)
plt.scatter(x,y)
&lt;/pre&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCDTnXb2YtNevgMki7Q13EOyHJNIhnNa32udigOYRhH6rf_zczWbL0919HmtEmHu_3jO8wdDesf9CSI_eAhM-501u29YWjzHMC8v84rjUfP0eeP8vmPLAgUd5-ud5J-Rr72Sm5r8W7-Hk/s1600/reg1.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCDTnXb2YtNevgMki7Q13EOyHJNIhnNa32udigOYRhH6rf_zczWbL0919HmtEmHu_3jO8wdDesf9CSI_eAhM-501u29YWjzHMC8v84rjUfP0eeP8vmPLAgUd5-ud5J-Rr72Sm5r8W7-Hk/s1600/reg1.png&quot; data-original-width=&quot;400&quot; data-original-height=&quot;249&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Vamos a ver qué tal se ajusta a un modelo lineal, para lo cuál aplicamos una regresión lineal valiéndonos de la librería SkLearn.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from sklearn.linear_model import LinearRegression

model = LinearRegression()
model.fit(x, y)
y_pred = model.predict(x)

plt.scatter(x, y)
plt.plot(x, y_pred, color=&#39;r&#39;)
plt.show()
&lt;/pre&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5xzeR8tvhpGrez73cEr1GRVPOYrNK3s_2R8PLqcZbCmhdpZE_gOmXEpoT9SEep4bwCvCGWmI1iIXO5saqTrtz29ANzB7JvUE47byriErpnz6aFJxHIubWcwYDMspn6GosVRcfHgNoY98/s1600/reg2.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5xzeR8tvhpGrez73cEr1GRVPOYrNK3s_2R8PLqcZbCmhdpZE_gOmXEpoT9SEep4bwCvCGWmI1iIXO5saqTrtz29ANzB7JvUE47byriErpnz6aFJxHIubWcwYDMspn6GosVRcfHgNoY98/s1600/reg2.png&quot; data-original-width=&quot;400&quot; data-original-height=&quot;249&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
No parece que el modelo lineal se ajuste demasiado bien. Una forma de medir la bondad del ajuste es calcular la media de la raíz del error cuadrático (root mean square error) que nos da una medida del error cometido por el modelo. En concreto se calcula la media de la desviación de los valores estimados. Otra métrica interesante es la medida R2 (R cuadrado), cuyo valor está entre 0 y 1, lo que la hace mejor a la hora de interpretar su valor, y es la fracción de la suma total de cuadrados que se &#39;explica por&#39; la regresión.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import numpy as np
from sklearn.metrics import mean_squared_error, r2_score

rmse = np.sqrt(mean_squared_error(y,y_pred))
r2 = r2_score(y,y_pred)
print (&#39;RMSE: &#39; + str(rmse))
print (&#39;R2: &#39; + str(r2))
&lt;/pre&gt;&lt;br /&gt;
&lt;pre&gt;RMSE: 163388.73519272613
R2: 0.6690412331929895
&lt;/pre&gt;&lt;br /&gt;
Si nos fijamos, sobre todo en R2, vemos que el ajuste no es bueno (mientras más cercano a 1, mejor ajuste). Estaremos de acuerdo en que una curva se ajustaría mejor. Si queremos ajustar una curva a los datos tendremos que trabajar con más dimensiones. Es decir, tendremos que intentar ajustar un polinomio (de segundo grado, por ejemplo). Para ello fijémonos en la ecuación de la recta que hemos ajustado con la regresión lineal.&lt;br /&gt;
\begin{align}&lt;br /&gt;
y=a+bx&lt;br /&gt;
\end{align}&lt;br /&gt;
Si quisieramos ajustar los datos con una función de segundo grado, de decir, una curva, necesitamos añadir un término cuadrático. Recordemos que x es un atributo de nuestros datos, así que vamos a añadir a la función este mismo atributo pero al cuadrado.&lt;br /&gt;
\begin{align}&lt;br /&gt;
y=a+bx+cx^2&lt;br /&gt;
\end{align}&lt;br /&gt;
Esta transformación la podemos realizar fácilmente son SkLearn gracias PolynomialFeatures(). A la que le indicamos el grado de la función que queremos obtener. Haciendo uso del método fit_transform() obtendremos el término cuadrático que andamos buscando para nuestro modelo.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures(degree=2, include_bias=False)
x_poly = poly.fit_transform(x)
print(x)
print(x_poly)
&lt;/pre&gt;&lt;br /&gt;
&lt;pre&gt;[[ 1]
 [ 2]
 [ 3]
 [ 4]
 [ 5]
 [ 6]
 [ 7]
 [ 8]
 [ 9]
 [10]]

[[  1.   1.]
 [  2.   4.]
 [  3.   9.]
 [  4.  16.]
 [  5.  25.]
 [  6.  36.]
 [  7.  49.]
 [  8.  64.]
 [  9.  81.]
 [ 10. 100.]]
&lt;/pre&gt;&lt;br /&gt;
Vemos que lo que ha hecho esta función es transformar una función lineal en otra cuadrática simplemente añadiendo el cuadrado del primer atributo. Vamos a calcular la regresión sobre esta nueva función y a comprobar si se ajusta mejor.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;model.fit(x_poly, y)
y_pred = model.predict(x_poly)

plt.scatter(x, y)
plt.plot(x, y_pred, color=&#39;r&#39;)
plt.show()

rmse = np.sqrt(mean_squared_error(y,y_pred))
r2 = r2_score(y,y_pred)
print (&#39;RMSE: &#39; + str(rmse))
print (&#39;R2: &#39; + str(r2))
&lt;/pre&gt;&lt;br /&gt;
&lt;pre&gt;RMSE: 82212.12400451244
R2: 0.9162082221443943
&lt;/pre&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWVmJ92Hi1jPIkKDfZnU0KXxXDIeM7VFOpUeKKm3HrAWNv6O1U1SK21B90hiFZcC-qkqXyu2kWzV6vyP2muc6uVuLdsWM-cAMPd_mlAZzDKgPaDejhx87yhVm-yi0C1dJmLHkMbGRKbsM/s1600/reg3.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWVmJ92Hi1jPIkKDfZnU0KXxXDIeM7VFOpUeKKm3HrAWNv6O1U1SK21B90hiFZcC-qkqXyu2kWzV6vyP2muc6uVuLdsWM-cAMPd_mlAZzDKgPaDejhx87yhVm-yi0C1dJmLHkMbGRKbsM/s1600/reg3.png&quot; data-original-width=&quot;400&quot; data-original-height=&quot;249&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Definitivamente el ajuste es mucho mejor, y el valor de R2 está mucho más cerca de 1. Si te estás preguntando qué ocurriría si en vez de una función cuadrática usáramos una función cúbica, no te cortes y pruébalo.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;poly = PolynomialFeatures(degree=3, include_bias=False)
x_poly = poly.fit_transform(x)

model.fit(x_poly, y)
y_pred = model.predict(x_poly)

plt.scatter(x, y)
plt.plot(x, y_pred, color=&#39;r&#39;)
plt.show()

rmse = np.sqrt(mean_squared_error(y,y_pred))
r2 = r2_score(y,y_pred)
print (&#39;RMSE: &#39; + str(rmse))
print (&#39;R2: &#39; + str(r2))
&lt;/pre&gt;&lt;br /&gt;
&lt;pre&gt;RMSE: 38931.50401232937
R2: 0.9812097727913365
&lt;/pre&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK1shrShSWTkBob3e6hGR06_GvlMoKBcS7YlhpZFGfVEwmqYI8ohcIKfEb8Gewmm3oMjC-h8ex3HQYLh4OOlmEHTuSKOs23lEs6a6XTi3HaKKoos_pJ1qXtbgWrc2f04Ikh1GemRTRh_c/s1600/reg4.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK1shrShSWTkBob3e6hGR06_GvlMoKBcS7YlhpZFGfVEwmqYI8ohcIKfEb8Gewmm3oMjC-h8ex3HQYLh4OOlmEHTuSKOs23lEs6a6XTi3HaKKoos_pJ1qXtbgWrc2f04Ikh1GemRTRh_c/s1600/reg4.png&quot; data-original-width=&quot;400&quot; data-original-height=&quot;249&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Mucho mejor. ¿Y si seguimos aumentando el grado?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;poly = PolynomialFeatures(degree=4, include_bias=False)
x_poly = poly.fit_transform(x)

model.fit(x_poly, y)
y_pred = model.predict(x_poly)

plt.scatter(x, y)
plt.plot(x, y_pred, color=&#39;r&#39;)
plt.show()

rmse = np.sqrt(mean_squared_error(y,y_pred))
r2 = r2_score(y,y_pred)
print (&#39;RMSE: &#39; + str(rmse))
print (&#39;R2: &#39; + str(r2))
&lt;/pre&gt;&lt;br /&gt;
&lt;pre&gt;RMSE: 14503.2349096276
R2: 0.9973922891706611
&lt;/pre&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbLr-z4AFErJKbIxI9FvVShlTmqxPnrrOTYm5ql0zUyHwmTbDoKfFlM9RLYg63uAYLw8EZunOE8yHOl7cKCxwiH9LVz5qHCmzQoY2Bhi_uCwGwtu5kaldGMFkVrorKa5tiEWXSADDMIiA/s1600/reg5.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbLr-z4AFErJKbIxI9FvVShlTmqxPnrrOTYm5ql0zUyHwmTbDoKfFlM9RLYg63uAYLw8EZunOE8yHOl7cKCxwiH9LVz5qHCmzQoY2Bhi_uCwGwtu5kaldGMFkVrorKa5tiEWXSADDMIiA/s1600/reg5.png&quot; data-original-width=&quot;400&quot; data-original-height=&quot;249&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Guau, el ajuste es casi perfecto. Pues ya puestos vamos a usar una función de grado 100 y será mucho mejor ¿no?&lt;br /&gt;
En realidad no. Como no tenemos datos de validación, estamos un poco a ciegas en cuanto a comprobar si nuestro modelo está sobreaprendiendo (overfitting), aunque un ajuste tan perfecto nos tiene que hacer sospechar. Si tuviéramos que usar el modelo para hacer predicciones sobre datos nuevos, seguramente las dos medidas que hemos usado para comprobar la bondad del ajuste (RMSE y R2) empezarían a degradarse considerablemente, ya que lo que ha hecho nuestro modelo es &quot;aprenderse&quot; los datos de entrenamiento, lo que significa que generalizará mal con nuevos datos. En un caso como este, es mejor un modelo más genérico (como el cuadrático) que uno que ajuste demasiado bien los datos de entrenamiento.&lt;br /&gt;
&lt;br /&gt;
Como siempre, os dejo un enlace al notebook de Jupyter con el código del artículo: &lt;a href=&quot;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/RegresionPolinomica2.ipynb&quot;&gt;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/RegresionPolinomica2.ipynb&lt;/a&gt;</description><link>http://www.ellaberintodefalken.com/2019/12/regresion-polinomica-polinomial.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibwzeY_hIQze_iinKzurZ9fnAnzOrVBSI_CAgRt6KEv7roLbkLwIV4pXP6-2r_16oWo-uZdJL_eMyJemeNz4sElqBnhxows5pWDclNhau7ksf8TK7SggDPv1Fu1xuyuXlt0dkc19nAaZo/s72-c/c7217b04fe11f_position1_retina.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-8457398115785482783</guid><pubDate>Wed, 13 Nov 2019 19:15:00 +0000</pubDate><atom:updated>2019-12-10T08:12:18.132+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">análisis de sentimiento</category><category domain="http://www.blogger.com/atom/ns#">deep learning</category><category domain="http://www.blogger.com/atom/ns#">lenguaje natural</category><category domain="http://www.blogger.com/atom/ns#">machine learning</category><category domain="http://www.blogger.com/atom/ns#">NLP</category><category domain="http://www.blogger.com/atom/ns#">procesado del lenguaje natural</category><category domain="http://www.blogger.com/atom/ns#">redes neuronales</category><category domain="http://www.blogger.com/atom/ns#">sentiment analysis</category><title>Análisis de sentimiento y procesado del lenguaje natural</title><description>El análisis de sentimiento es una técnica de machine learning, basada en el procesado del lenguaje natural, que pretende obtener información subjetiva de una serie de textos o documentos. Un ejemplo clásico de aplicación consiste en dilucidar si un artículo periodístico es favorable o no favorable en relación a un tema determinado. Las aplicaciones en el mundo real son muchas y variadas. No hace mucho leía un artículo sobre la aplicación de estas técnicas para usarlas como un dato más a la hora de predecir índices bursátiles, analizando lo que decía la prensa económica sobre determinadas empresas. El campo del tratamiento del lenguaje natural es una área de estudio bastante activa actualmente, y utiliza técnicas que pueden llegar a ser bastante complejas. Sin embargo, no es necesario complicarse mucho la vida para obtener resultados bastante decentes. &lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAIYMh6ez6wrm_QpHTLdd8wtS8rYEvw1sBr_jkXjGSfOvM9GJc4Nu28c8u5I13DkCOwy8SaJh4fFqmdtzMNCIWrBOZs0f_Jp5oS3xYPLxoi2HhXK00ftqGqvzeN248qHH-uGtWxdsl1CA/s1600/shutterstock_1073953772-860x9999.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAIYMh6ez6wrm_QpHTLdd8wtS8rYEvw1sBr_jkXjGSfOvM9GJc4Nu28c8u5I13DkCOwy8SaJh4fFqmdtzMNCIWrBOZs0f_Jp5oS3xYPLxoi2HhXK00ftqGqvzeN248qHH-uGtWxdsl1CA/s400/shutterstock_1073953772-860x9999.jpg&quot; width=&quot;400&quot; height=&quot;267&quot; data-original-width=&quot;860&quot; data-original-height=&quot;574&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Como ejemplo, vamos a tratar de predecir si un mensaje de Twitter es positivo o negativo. Como datos de entrenamiento usaremos un dataset con 1.600.000 tweets (no los usaremos todos) con su correspondiente etiqueta. Fuente del dataset: &lt;a href=&quot;https://www.kaggle.com/kazanova/sentiment140&quot;&gt;https://www.kaggle.com/kazanova/sentiment140&lt;/a&gt;. Se puede abordar el problema desde diferentes enfoques, pero en este caso entrenaremos una red neuronal con los datos del dataset. El primer paso será descargar el conjunto de datos y cargarlo en memoria con Pandas.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import pandas as pd

columnas = [&#39;target&#39;,&#39;ids&#39;,&#39;date&#39;,&#39;flag&#39;,&#39;user&#39;,&#39;text&#39;]
data = pd.read_csv(&#39;dataset/training.1600000.processed.noemoticon.csv&#39;, encoding = &quot;latin-1&quot;, names=columnas)
# nos quedamos con una selección de 50.000 tweets aleatorios
data = data.sample(n=50000, random_state=42)
data = data.reset_index(drop=True) # reconstruimos los índices para que sean consecutivos
data.head()
&lt;/pre&gt;&lt;br /&gt;
&lt;div  class=&quot;dataframe&quot;&gt;&lt;table class=&quot;dataframe&quot; border=&quot;1&quot;&gt;&lt;thead&gt;
&lt;tr style=&quot;text-align: right&quot;&gt;       &lt;th&gt;&lt;/th&gt;       &lt;th&gt;target&lt;/th&gt;       &lt;th&gt;ids&lt;/th&gt;       &lt;th&gt;date&lt;/th&gt;       &lt;th&gt;flag&lt;/th&gt;       &lt;th&gt;user&lt;/th&gt;       &lt;th&gt;text&lt;/th&gt;     &lt;/tr&gt;
&lt;/thead&gt;   &lt;tbody&gt;
&lt;tr&gt;       &lt;td&gt;0&lt;/td&gt;       &lt;td&gt;0&lt;/td&gt;       &lt;td&gt;2200003196&lt;/td&gt;       &lt;td&gt;Tue Jun 16 18:18:12 PDT 2009&lt;/td&gt;       &lt;td&gt;NO_QUERY&lt;/td&gt;       &lt;td&gt;LaLaLindsey0609&lt;/td&gt;       &lt;td&gt;@chrishasboobs AHHH I HOPE YOUR OK!!!&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;0&lt;/td&gt;       &lt;td&gt;1467998485&lt;/td&gt;       &lt;td&gt;Mon Apr 06 23:11:14 PDT 2009&lt;/td&gt;       &lt;td&gt;NO_QUERY&lt;/td&gt;       &lt;td&gt;sexygrneyes&lt;/td&gt;       &lt;td&gt;@misstoriblack cool , i have no tweet apps  fo...&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;td&gt;2&lt;/td&gt;       &lt;td&gt;0&lt;/td&gt;       &lt;td&gt;2300048954&lt;/td&gt;       &lt;td&gt;Tue Jun 23 13:40:11 PDT 2009&lt;/td&gt;       &lt;td&gt;NO_QUERY&lt;/td&gt;       &lt;td&gt;sammydearr&lt;/td&gt;       &lt;td&gt;@TiannaChaos i know  just family drama. its la...&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;td&gt;3&lt;/td&gt;       &lt;td&gt;0&lt;/td&gt;       &lt;td&gt;1993474027&lt;/td&gt;       &lt;td&gt;Mon Jun 01 10:26:07 PDT 2009&lt;/td&gt;       &lt;td&gt;NO_QUERY&lt;/td&gt;       &lt;td&gt;Lamb_Leanne&lt;/td&gt;       &lt;td&gt;School email won&#39;t open  and I have geography ...&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;td&gt;4&lt;/td&gt;       &lt;td&gt;0&lt;/td&gt;       &lt;td&gt;2256550904&lt;/td&gt;       &lt;td&gt;Sat Jun 20 12:56:51 PDT 2009&lt;/td&gt;       &lt;td&gt;NO_QUERY&lt;/td&gt;       &lt;td&gt;yogicerdito&lt;/td&gt;       &lt;td&gt;upper airways problem&lt;/td&gt;     &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
Los campos son los siguientes (extraído directamente de la documentación del dataset):&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;target: the polarity of the tweet (0 = negative, 2 = neutral, 4 = positive)&lt;/li&gt;
&lt;li&gt;ids: The id of the tweet ( 2087)&lt;/li&gt;
&lt;li&gt;date: the date of the tweet (Sat May 16 23:58:44 UTC 2009)&lt;/li&gt;
&lt;li&gt;flag: The query (lyx). If there is no query, then this value is NO_QUERY.&lt;/li&gt;
&lt;li&gt;user: the user that tweeted (robotickilldozr)&lt;/li&gt;
&lt;li&gt;text: the text of the tweet (Lyx is cool)&lt;/li&gt;
&lt;/ul&gt;Para este ejemplo voy a usar sólo la columna text (contenido del tweet) y target (la etiqueta para entrenar la red neuronal).&lt;br /&gt;
Nos hemos quedado sólo con 50.000 tweets aleatorios, ya que ocuparían demasiada memoria para un ordenador de andar por casa.&lt;br /&gt;
Vamos a echar un vistazo a cómo se reparten las etiquetas (campo target).&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from matplotlib import pyplot as plt
plt.hist(data[&#39;target&#39;], bins=5)
&lt;/pre&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgR5k-5SaiLi0IWt2EJD7gAq7fj3QKNDSu-m9o5sfpHpzFIaCcWH8UbH0ETdcfeQvXnK2zETtN5Yeq2DDAKb2bBEY9kLMLdJBVvFu7b02YLk7lQGZkSuWqCngVfMbOahC-NPe3hH1TeUc/s1600/img1.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgR5k-5SaiLi0IWt2EJD7gAq7fj3QKNDSu-m9o5sfpHpzFIaCcWH8UbH0ETdcfeQvXnK2zETtN5Yeq2DDAKb2bBEY9kLMLdJBVvFu7b02YLk7lQGZkSuWqCngVfMbOahC-NPe3hH1TeUc/s1600/img1.png&quot; data-original-width=&quot;387&quot; data-original-height=&quot;248&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Parece que el dataset sólo etiqueta los tweets positivos(=0) y los negativos(=4), ya que no hay ningún tweet etiquetado como neutral. Además, para alimentar la red neuronal, es más conveniente que tengan valores continuos y que no haya valores que no tengan ningún significado (como el 1 o el 3). Vamos a reasignar las etiquetas como 0=negativo y 1=positivo.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# ahora la etiqueta para los tweets positivos es el 1
data.loc[data[&#39;target&#39;] == 4, &#39;target&#39;] = 1
plt.hist(data[&#39;target&#39;], bins=3)
&lt;/pre&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPp-OV6kX-FXYTEYfggQODE9g4sdJjMNSRdNZzV-mpH5odoJQYf4IsuNm_aj-3M3w01T2NWoT_DmMxmaCuma-Y_N17QcXwtpG6dttmoC7arvApMreHiDfp9mq79_0v_8cQU430GVcbb1Y/s1600/img2.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPp-OV6kX-FXYTEYfggQODE9g4sdJjMNSRdNZzV-mpH5odoJQYf4IsuNm_aj-3M3w01T2NWoT_DmMxmaCuma-Y_N17QcXwtpG6dttmoC7arvApMreHiDfp9mq79_0v_8cQU430GVcbb1Y/s1600/img2.png&quot; data-original-width=&quot;387&quot; data-original-height=&quot;248&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Además vemos que hay aproximadamente el mismo número de ejemplos de tweets negativos y positivos, lo que es muy interesante a la hora de entrenar la red.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Tratamiento de los datos&lt;/h4&gt;&lt;br /&gt;
Vamos a diseñar una red neuronal capaz de aprender si un texto es positivo o negativo (sentimiento). Para ello necesitamos codificar el texto de alguna manera para que la red neuronal, que trabaja con valores numéricos preferentemente, sea capaz de entenderlo. Existen varias técnicas enmarcadas en lo que se conoce como tratamiento del lenguaje natural. En este ejemplo voy a usar una técnica más o menos sencilla pero efectiva. La técnica consiste en codificar un conjunto amplio de palabras (corpus) como valores numéricos, es decir, creamos un índice en el que hacemos corresponder un valor a cada palabra. Cada palabra se codificará luego como un vector donde la posición correspondiente al valor de la palabra contendrá un 1 y las demás posiciones un 0. Es decir, usando una codificación &lt;a href=&quot;https://es.wikipedia.org/wiki/One-hot&quot;&gt;one-hot&lt;/a&gt;. Existen corpus de palabras ya disponibles para usar, pero vamos a complicarnos un poco más la vida y vamos a crear el nuestro a partir del dataset de tweets del que disponemos. La siguiente función construye un corpus de palabras a partir de los tweets. Tenemos cuidado de no añadir las referencias a los usuarios (empiezan por @), los enlaces (contienen http) y eliminamos las palabras menores de 3 caracteres, que no aportan demasiada información.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from keras.preprocessing.text import Tokenizer

# codificar tweets -&gt; corpus -&gt; one-hot
# con limit podemos limitar el número de palabras del corpus
def construir_corpus(tweets, limit=10000):
    corpus=[]
    i=0
    for t in tweets:
        if i&gt;limit:
            break
        for w in t.lower().split():
            # si la palabra no está en el corpus y no empieza por @, no contiene http y es mayor de 3 caracteres.
            if w[0] not in (&#39;@&#39;) and &#39;http&#39; not in w and len(w)&gt;3 and not w in corpus:
                corpus.append(w)
                i=i+1
    
    # codificar palabras como enteros
    t = Tokenizer()
    t.fit_on_texts(corpus)
    encoded_corpus = t.texts_to_matrix(corpus, mode=&#39;count&#39;)
    
    return corpus[:limit], encoded_corpus[:limit]
&lt;/pre&gt;&lt;br /&gt;
Vamos a probarlo.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# Probamos con tres frases para confirmar que todo va bien
tweets = [&#39;Sólo sé que no sé nada&#39;, &#39;Pienso luego existo&#39;, &#39;viva @socrates y @descartes&#39;]

corpus, encoded_corpus = construir_corpus(tweets)
print (corpus)
print (encoded_corpus)
&lt;/pre&gt;&lt;br /&gt;
&lt;pre style=&quot;font-size: 12px&quot;&gt;[&#39;sólo&#39;, &#39;nada&#39;, &#39;pienso&#39;, &#39;luego&#39;, &#39;existo&#39;, &#39;viva&#39;]
[[0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 0. 1.]]
&lt;/pre&gt;&lt;br /&gt;
Se ha construido el siguiente corpus a partir de las tres frases: [&#39;sólo&#39;, &#39;nada&#39;, &#39;pienso&#39;, &#39;luego&#39;, &#39;existo&#39;, &#39;viva&#39;].&lt;br /&gt;
También se muestra como se codifica cada palabra. por ejemplo, la primera (sólo) se codifica como: [0. 1. 0. 0. 0. 0. 0.].&lt;br /&gt;
Obsérvese que si tenemos 10.000 palabras, nuestra matriz de codificación tendrá 10.000 filas. En realidad sólo las he calculado para que veas qué forma tiene la codificación. Como verás más adelante, no vamos a usar esta matriz para nada.&lt;br /&gt;
Ahora vamos a construir el corpus real que vamos a utilizar.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# Ahora usamos los tweets del dataset
tweets = data[&#39;text&#39;]
corpus, encoded_corpus = construir_corpus(tweets)
&lt;/pre&gt;&lt;br /&gt;
Para codificar un tweet, por cada palabra de la que esté compuesto, se pone un 1 en la posición correspondiente a dicha palabra en el vector. Así, conforme al ejemplo de arriba (el de Sócrates y Descartes), el tweet con el texto &quot;Sólo pienso&quot; se codificaría como [0. 1. 0. 1. 0. 0. 0.]. Una vez entendida la idea de cómo se codifica un tweet para que sea útil a la hora de alimentar una red neuronal, vamos a crear una función que codifique todo el conjunto de tweets. Y también necesitamos codificar las etiquetas para el entrenamiento, para lo cuál también vamos a usar una codificación one-hot valiéndonos de la función to_categorical() de Keras. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import numpy as np

from keras.utils import to_categorical

def codifica_tweets(tweets, corpus, corpus_size=10000):
    coded = np.zeros((len(tweets), corpus_size))
    for i, tweet_text in enumerate(tweets):
        words = tweet_text.lower().split()
        for w in words:
            if w in corpus:
                coded[i,corpus.index(w)] = 1

    return coded

x_train = codifica_tweets(data[&#39;text&#39;], corpus)
y_train = to_categorical(data[&#39;target&#39;])
&lt;/pre&gt;&lt;br /&gt;
Seguidamente prepararemos los datos con los que vamos a entrenar la red neuronal, separando del conjunto de entrenaiento una porción de los datos para crear el conjunto de validación y de test. También vamos a barajar los datos para añadir un poco de entropía.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# barajamos el dataset
np.random.seed(42)    
permutation = np.random.permutation(x_train.shape[0])
x_train = x_train[permutation]
y_train = y_train[permutation]
     
# obtenemos el conjunto de validación
num_val = 10000
x_val = x_train[:num_val]
x_train = x_train[num_val:]
y_val = y_train[:num_val]
y_train = y_train[num_val:]

# obtenemos el conjunto de test
x_test = x_train[:num_val]
x_train = x_train[num_val:]
y_test = y_train[:num_val]
y_train = y_train[num_val:]
&lt;/pre&gt;&lt;br /&gt;
Una vez preparados los datos, vamos a definir la arquitectura de la red neuronal. Como los tweets se codifican en vectores de dimensión 10.000 (el tamaño de nuestro corpus de palabras) y queremos que clasifique los tweets en dos categorías (positivos y negativos), nuestra red tendra 10.000 entradas y dos salidas en la última capa. En esta última capa usaremos una función de activación de tipo softmax, que nos devolverá una distribución de probabilidad entre las dos salidas (una para la positiva y otra para la negativa), indicándonos cuál es la probabilidad de que corresponda a cada una de las dos clases. Usaremos dos capas ocultas de 16 neuronas densamente conectadas (todas con todas).&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from keras import models, layers

model = models.Sequential()
model.add(layers.Dense(16, activation=&#39;relu&#39;, input_shape=(10000,)))
model.add(layers.Dense(16, activation=&#39;relu&#39;))
model.add(layers.Dense(2, activation=&#39;softmax&#39;))

model.compile(optimizer=&#39;rmsprop&#39;,
             loss=&#39;categorical_crossentropy&#39;,
             metrics=[&#39;acc&#39;])

train_log = model.fit(x_train, y_train,
                     epochs=5, batch_size=512,
                     validation_data=(x_val, y_val))
&lt;/pre&gt;&lt;br /&gt;
&lt;pre style=&quot;font-size: 12px&quot;&gt;Train on 30000 samples, validate on 10000 samples
Epoch 1/5
30000/30000 [==============================] - 1s 45us/step - loss: 0.6503 - acc: 0.6775 - val_loss: 0.5995 - val_acc: 0.7202
Epoch 2/5
30000/30000 [==============================] - 1s 36us/step - loss: 0.5471 - acc: 0.7519 - val_loss: 0.5465 - val_acc: 0.7296
Epoch 3/5
30000/30000 [==============================] - 1s 40us/step - loss: 0.4922 - acc: 0.7733 - val_loss: 0.5416 - val_acc: 0.7338
Epoch 4/5
30000/30000 [==============================] - 1s 35us/step - loss: 0.4658 - acc: 0.7841 - val_loss: 0.5487 - val_acc: 0.7313
Epoch 5/5
30000/30000 [==============================] - 1s 36us/step - loss: 0.4471 - acc: 0.7949 - val_loss: 0.5568 - val_acc: 0.7285
&lt;/pre&gt;&lt;br /&gt;
Si miramos la salida durante el entrenamiento, vemos que, con el conjunto de validación, los mejores resultados se producen en la iteración 3/5. Después parece que se produce un sobreentrenamiento (overfitting). Si queremos asegurarnos podemos mostrar una gráfica.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import matplotlib.pyplot as plt

acc = train_log.history[&#39;acc&#39;]
val_acc = train_log.history[&#39;val_acc&#39;]
loss = train_log.history[&#39;loss&#39;]
val_loss = train_log.history[&#39;val_loss&#39;]
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, &#39;r&#39;, label=&#39;Training acc&#39;)
plt.plot(epochs, val_acc, &#39;b&#39;, label=&#39;Validation acc&#39;)
plt.title(&#39;Training and validation accuracy&#39;)
plt.legend()
plt.figure()
plt.plot(epochs, loss, &#39;r&#39;, label=&#39;Training loss&#39;)
plt.plot(epochs, val_loss, &#39;b&#39;, label=&#39;Validation loss&#39;)
plt.title(&#39;Training and validation loss&#39;)
plt.legend()
plt.show()
&lt;/pre&gt;&lt;br /&gt;
&lt;table border=&quot;0&quot;&gt;&lt;tr&gt; &lt;td&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTfszyUtzLraAeXMLi8hI2G6zm0HuNdS5PlexvPa-eXp9WkQ80XelnFIFWLe05zzLhDNUPxPzLc7wwIoWwBRfhtF4BRRLhFndgUe8Q7Igr3oUj2pkFrVEtpHJiML1XxgodQ1F83qwfxrc/s1600/gr1.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTfszyUtzLraAeXMLi8hI2G6zm0HuNdS5PlexvPa-eXp9WkQ80XelnFIFWLe05zzLhDNUPxPzLc7wwIoWwBRfhtF4BRRLhFndgUe8Q7Igr3oUj2pkFrVEtpHJiML1XxgodQ1F83qwfxrc/s1600/gr1.png&quot; data-original-width=&quot;378&quot; data-original-height=&quot;264&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTwGLh4_XneR63_LMudB7VPCAg5f7SU5F9ct0z0ho6V0pAIn5furuOMyMXx9qz80vSAfQiGObp1qJhakaWwK_3t8-3oKNDKqUlC28nN5pxjnK-3ge4q3OtCBPCx7LdOeUiMdPvi7RwIoQ/s1600/gr2.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTwGLh4_XneR63_LMudB7VPCAg5f7SU5F9ct0z0ho6V0pAIn5furuOMyMXx9qz80vSAfQiGObp1qJhakaWwK_3t8-3oKNDKqUlC28nN5pxjnK-3ge4q3OtCBPCx7LdOeUiMdPvi7RwIoQ/s1600/gr2.png&quot; data-original-width=&quot;384&quot; data-original-height=&quot;264&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;/table&gt;&lt;br /&gt;
Vamos a evaluar el modelo contra el conjunto de test.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;test_loss, test_accuracy = model.evaluate(x_test, y_test)
print(test_accuracy)
&lt;/pre&gt;&lt;pre style=&quot;font-size: 12px&quot;&gt;10000/10000 [==============================] - 1s 68us/step
0.7278000116348267
&lt;/pre&gt;&lt;br /&gt;
Hemos conseguido un 72% de precisión. Si hubieramos cortado el entrenamiento en la tercera iteración seguramente hubieramos llegado al 73% (te dejo que lo pruebes). No es un mal dato teniendo en cuenta que sólo hemos usado una pequeña fracción de los tweets y el corpus que hemos generado tampoco es demasiado bueno. Con esta técnica, usando todo el dataset y un buen corpus de palabras podríamos esperar superar el 80%, pero como primera aproximación no está mal.&lt;br /&gt;
Ya con el modelo entrenado podemos hacer predicciones sobre cualquier tweet.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;predictions = model.predict(x_test)
print (np.argmax(predictions[1]))# clase más probable
print (y_test[1])
&lt;/pre&gt;&lt;br /&gt;
&lt;pre style=&quot;font-size: 12px&quot;&gt;0
[1. 0.]
&lt;/pre&gt;&lt;br /&gt;
Como os decía al principio, el procesado del lenguaje natural es un campo amplio y actualmente se están realizando bastantes esfuerzos de investigación en esta materia. Como ves, con técnicas más o menos simples podemos acomenter problemas de cierta complejidad. Espero haberte despertado la curiosidad para que te atrevas con técnicas más avanzadas y también más interesantes.&lt;br /&gt;
Os dejo un enlace al notebook de Jupyter con el código de este artículo: &lt;a href=&quot;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/SentimentAnalysis.ipynb&quot;&gt;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/SentimentAnalysis.ipynb&lt;/a&gt;</description><link>http://www.ellaberintodefalken.com/2019/11/analisis-de-sentimiento-NLP.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAIYMh6ez6wrm_QpHTLdd8wtS8rYEvw1sBr_jkXjGSfOvM9GJc4Nu28c8u5I13DkCOwy8SaJh4fFqmdtzMNCIWrBOZs0f_Jp5oS3xYPLxoi2HhXK00ftqGqvzeN248qHH-uGtWxdsl1CA/s72-c/shutterstock_1073953772-860x9999.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-4379227384086278789</guid><pubDate>Mon, 21 Oct 2019 10:46:00 +0000</pubDate><atom:updated>2023-03-10T19:59:13.474+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">CNN</category><category domain="http://www.blogger.com/atom/ns#">ConvNet</category><category domain="http://www.blogger.com/atom/ns#">convolutional neural network</category><category domain="http://www.blogger.com/atom/ns#">deep learning</category><category domain="http://www.blogger.com/atom/ns#">keras</category><category domain="http://www.blogger.com/atom/ns#">redes neuronales convolucionales</category><category domain="http://www.blogger.com/atom/ns#">redes neuuronales</category><title>Visión artificial con redes convolucionales (CNN)</title><description>Las redes neuronales convolucionales o ConvNets son útiles en variadas aplicaciones dentro del machine learning, sin embargo, donde ha conseguido revolucionar el estado del arte es en la visión artificial y el procesado de imágenes. Lo que caracteriza a este tipo de redes es que es capaz de aprender en las primeras capas una serie de características básicas de la imagen, como son líneas o formas más o menos simples. En posteriores fases aprende a discriminar entre elementos más complejos, como ojos u orejas. Finalmente, en capas más profundas son capaces de diferenciar objetos complejos, como personas, perros, gatos o coches. En la siguiente imagen se plasma visualmente lo que quiero decir.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpkChAz70AWS93Pd6EK3VNQNO-ha6Vb3sqFUN9Y2goJgrjHGLEPVeYlg6pW6si3ch8tXe01kB-gJVMmrrL-vm1GOlnPHlp90gH3voKFuJvSyQFohDZzV8CKq-f6-Pr6b9p52c38xhmqNM/s1600/Deep-Neural-Network-What-is-Deep-Learning-Edureka.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpkChAz70AWS93Pd6EK3VNQNO-ha6Vb3sqFUN9Y2goJgrjHGLEPVeYlg6pW6si3ch8tXe01kB-gJVMmrrL-vm1GOlnPHlp90gH3voKFuJvSyQFohDZzV8CKq-f6-Pr6b9p52c38xhmqNM/s640/Deep-Neural-Network-What-is-Deep-Learning-Edureka.png&quot; width=&quot;640&quot; height=&quot;328&quot; data-original-width=&quot;1600&quot; data-original-height=&quot;820&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Antes de hablar de las redes convolucionales convendrá explicar qué es eso de la convolución y para qué sirve. En el ámbito del tratamiento de imágenes, la convolución es una operación que involucra dos matrices. Por un lado la propia imagen a la que queremos aplicar la convolución (en forma de matriz, claro), y otra matriz más pequeña llamada kernel. El resultado de la convolución es otra matriz de las mismas dimensiones que la imagen original. Para calcular la matriz de convolución vamos desplazando el kernel por cada uno de los elementos de la matriz principal y hacemos la suma de los productos de cada uno de los elementos de ambas matrices. En realidad suena peor de los que es. Visualmente podemos imaginarlo así.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3yDNPNTJo2mv2kd2Fc-nGuD42wtYmwC3y8Sj5Ts6ZZPN4yMUaMKZ5sCQ4n9yPUVt6_jA2lY3jQJqnIODp8WltjZqiWcVEPUIayjjw9Bwqkf7PyrgXG_S0ZNWL794W2eMhVf1kSKtdUW0/s1600/conv_illustration.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3yDNPNTJo2mv2kd2Fc-nGuD42wtYmwC3y8Sj5Ts6ZZPN4yMUaMKZ5sCQ4n9yPUVt6_jA2lY3jQJqnIODp8WltjZqiWcVEPUIayjjw9Bwqkf7PyrgXG_S0ZNWL794W2eMhVf1kSKtdUW0/s400/conv_illustration.png&quot; width=&quot;400&quot; height=&quot;241&quot; data-original-width=&quot;364&quot; data-original-height=&quot;219&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
El centro del kernel, pues pasaría por cada uno de los elementos de la matriz para ir generando la matriz de convolución. Mejor verlo con un ejemplo.&lt;br /&gt;
&lt;br /&gt;
$$&lt;br /&gt;
\begin{vmatrix}&lt;br /&gt;
P_{1} &amp; P_{2} &amp; P_{3} &amp; P_{4} &amp; P_{5} \\ &lt;br /&gt;
P_{6} &amp; P_{7} &amp; P_{8} &amp; P_{9} &amp; P_{10} \\&lt;br /&gt;
P_{11} &amp; P_{12} &amp; P_{13} &amp; P_{14} &amp; P_{15} \\&lt;br /&gt;
P_{16} &amp; P_{17} &amp; P_{18} &amp; P_{19} &amp; P_{20} \\&lt;br /&gt;
P_{21} &amp; P_{22} &amp; P_{23} &amp; P_{24} &amp; P_{25}&lt;br /&gt;
\end{vmatrix}&lt;br /&gt;
\circledast&lt;br /&gt;
\begin{vmatrix}&lt;br /&gt;
A &amp; B &amp; C \\ &lt;br /&gt;
D &amp; E &amp; F \\&lt;br /&gt;
G &amp; H &amp; I &lt;br /&gt;
\end{vmatrix}&lt;br /&gt;
=&lt;br /&gt;
\begin{vmatrix}&lt;br /&gt;
C_{1} &amp; C_{2} &amp; C_{3} &amp; C_{4} &amp; C_{5} \\ &lt;br /&gt;
C_{6} &amp; C_{7} &amp; C_{8} &amp; C_{9} &amp; C_{10} \\&lt;br /&gt;
C_{11} &amp; C_{12} &amp; C_{13} &amp; C_{14} &amp; C_{15} \\&lt;br /&gt;
C_{16} &amp; C_{17} &amp; C_{18} &amp; C_{19} &amp; C_{20} \\&lt;br /&gt;
C_{21} &amp; C_{22} &amp; C_{23} &amp; C_{24} &amp; C_{25}&lt;br /&gt;
\end{vmatrix}&lt;br /&gt;
$$&lt;br /&gt;
&lt;br /&gt;
Para calcular, por ejemplo, el elemento \(C_{13}\) de la matriz de convolución haríamos el siguiente cálculo (y así con todos los elementos).&lt;br /&gt;
&lt;br /&gt;
$$C_{13}=A \cdot P_{7} + B \cdot P_{8} + C \cdot P_{9} + D \cdot P_{12} + E \cdot P_{13} + F \cdot C_{14} + G \cdot P_{17} + H \cdot P_{18} + I \cdot P_{19}$$&lt;br /&gt;
&lt;br /&gt;
Un caso especial es cuando aplicamos la convolución sobre un elemento que está en el borde de la matriz. En ese caso, lo más común, es imaginarnos que la matriz está rodeada de ceros.&lt;br /&gt;
¿Y para qué sirve esto? Podemos imaginar un kernel como una especie de filtro que nos permite hacer cosas como detectar en las imágenes bordes, líneas verticales y horizontales, así como desenfocar y enfocar la imagen. Algunos kernels interesantes son los siguientes.&lt;br /&gt;
&lt;br /&gt;
&lt;table border=&quot;0&quot; width=&quot;100%&quot;&gt;&lt;tr&gt; &lt;td&gt;Identidad&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
$$&lt;br /&gt;
\begin{bmatrix}&lt;br /&gt;
0 &amp; 0 &amp; 0 \\&lt;br /&gt;
0 &amp; 1 &amp; 0 \\&lt;br /&gt;
0 &amp; 0 &amp; 0&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
$$&lt;br /&gt;
&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
&lt;img align=&quot;right&quot; border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdaDTVUR9xMF3SmLXA5WqbVVap4ZUaxr_R0btukdWjDM4A3ljP1c2Oq6d8gw3ebmnt7NomfG-wQ7z1IU4dWo4_z4S-vHSGvJuWnxhnTWqi3httONEZgEfEw_WLGij6y4eudNRB_HkRCvQ/s1600/Vd-Orig.png&quot; data-original-width=&quot;100&quot; data-original-height=&quot;100&quot; /&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Detección de bordes&lt;/td&gt; &lt;td colspan=&quot;2&quot;&gt;&lt;br /&gt;
&lt;table border=&quot;0&quot; style=&quot;width: 100%&quot;&gt;&lt;tr&gt; &lt;td&gt;&lt;br /&gt;
$$&lt;br /&gt;
\begin{bmatrix}&lt;br /&gt;
\ \ 1 &amp; 0 &amp; -1 \\&lt;br /&gt;
\ \ 0 &amp; 0 &amp; \ \ 0 \\&lt;br /&gt;
-1 &amp; 0 &amp; \ \ 1&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
$$&lt;br /&gt;
&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
&lt;img align=&quot;right&quot; border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdNOthzAkUVwT9z-fLu1lDM2Vvkf1vyegbpgT5IhpZ_AhWeGhyuGViP9LyGTWPsNNOwOYXIaeobEhdLGf8DZJc62OEDxN54nZPKLwY_N3q2jRL4EHEX1-vJJmvW9_X12AfavtnUUTL4X8/s1600/Vd-Edge1.png&quot; data-original-width=&quot;100&quot; data-original-height=&quot;100&quot; /&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;&lt;br /&gt;
$$&lt;br /&gt;
\begin{bmatrix}&lt;br /&gt;
0 &amp;  \ \ 1 &amp; 0 \\&lt;br /&gt;
1 &amp; -4 &amp; 1 \\&lt;br /&gt;
0 &amp;  \ \ 1 &amp; 0&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
$$&lt;br /&gt;
&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
&lt;img align=&quot;right&quot; border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiZ0KmLVLJjAohcxaa7WC_2Gg0HE4nGaMxGy7gKjVXBn6OxvxfOIOP-jOlC1o4gz_4otcd3YZ1qH8VepUskT0sofgRgOJx4oZYk-ocK_lrfUKiZI_a-oqnfYI5N9mIyJyDfQkUocys7Oo/s1600/Vd-Edge2.png&quot; data-original-width=&quot;100&quot; data-original-height=&quot;100&quot; /&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;&lt;br /&gt;
$$&lt;br /&gt;
\begin{bmatrix}&lt;br /&gt;
-1 &amp;  -1 &amp; -1 \\&lt;br /&gt;
-1 &amp; \ \ 8 &amp; -1 \\&lt;br /&gt;
-1 &amp;  -1 &amp; -1&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
$$&lt;br /&gt;
&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
&lt;img align=&quot;right&quot; border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhMAjWetnu2gf4UMwFDz35oiGko7z2MrPFpCHHXo1OuD-sTuBv3CULRP9huI8xNufFJfO6eAKGoz3LPiZbSt0ct121NroAQDxbuAdRtlqZHdbzshhEvw2PoKGjKgH67JewtAFQdQjlo_c/s1600/Vd-Edge3.png&quot; data-original-width=&quot;100&quot; data-original-height=&quot;100&quot; /&gt;&lt;br /&gt;
&lt;/tr&gt;&lt;br /&gt;
&lt;/table&gt;&lt;br /&gt;
&lt;/td&gt;&lt;td&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Afilado&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
$$&lt;br /&gt;
\begin{bmatrix}&lt;br /&gt;
\ \ 0 &amp; -1 &amp; \ \ 0 \\&lt;br /&gt;
-1 &amp; \ \ 5 &amp; -1 \\&lt;br /&gt;
\ \ 0 &amp; -1 &amp; \ \ 0&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
$$&lt;br /&gt;
&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
&lt;img align=&quot;right&quot; border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOS0VKblGw7EbGdTnO68UNZs53tUYlslzqA9y-oMEOiXmlQijWbILhwyax-zla9r-v1zkyiJiQUFGHYor1NKnIqygqVJJaMRSgawZi2a2mvj5FGIP4QTljdMiltWyoQ3RfbGo4RDFy8Z4/s1600/Vd-Sharp.png&quot; data-original-width=&quot;100&quot; data-original-height=&quot;100&quot; /&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Difuminado&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
$$&lt;br /&gt;
\frac{1}{9}&lt;br /&gt;
\begin{bmatrix}&lt;br /&gt;
1 &amp; 1 &amp; 1 \\&lt;br /&gt;
1 &amp; 1 &amp; 1 \\&lt;br /&gt;
1 &amp; 1 &amp; 1&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
$$&lt;br /&gt;
&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
&lt;img align=&quot;right&quot; border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-iJP881_9QBt_5CbZegcZKe4Dq2doM4ZQYyiksiJ_ZCUksBt5pvPROS8chKI2BkeUPp5RNv14Vd9yIN0dAzdhvHSob7U5EVA1vkqCZIHblkZJtxmY1yz4eS_BkIgzu37Xh4c7JcvbRkw/s1600/Vd-Blur2.png&quot; data-original-width=&quot;100&quot; data-original-height=&quot;100&quot; /&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Desenfoque gausiano 3x3&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
$$&lt;br /&gt;
\frac{1}{16}&lt;br /&gt;
\begin{bmatrix}&lt;br /&gt;
1 &amp; 2 &amp; 1 \\&lt;br /&gt;
2 &amp; 4 &amp; 2 \\&lt;br /&gt;
1 &amp; 2 &amp; 1&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
$$&lt;br /&gt;
&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
&lt;img align=&quot;right&quot; border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_5BMbuIYTIOqFMgQdvk-5GHNQDQIyrv5DQWHf5GUczfy305DS5kF59wI4U6CxE2AeyNJ_Ta2h2vDQPpw__OyTFr1JWFPOJen-Tl8H8Jm-zczH5Ql7JJAjfvewVN19Y0MKFjpx0FRydRA/s1600/Vd-Blur1.png&quot; data-original-width=&quot;100&quot; data-original-height=&quot;100&quot; /&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Desenfoque gausiano 5x5&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
$$&lt;br /&gt;
\frac{1}{256}&lt;br /&gt;
\begin{bmatrix}&lt;br /&gt;
1 &amp; 4 &amp; 6 &amp; 4 &amp; 1 \\&lt;br /&gt;
4 &amp; 16 &amp; 24 &amp; 16 &amp; 4 \\&lt;br /&gt;
6 &amp; 24 &amp; 36 &amp; 24 &amp; 6 \\&lt;br /&gt;
4 &amp; 16 &amp; 24 &amp; 16 &amp; 4 \\&lt;br /&gt;
1 &amp; 4 &amp; 6 &amp; 4 &amp; 1&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
$$&lt;br /&gt;
&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
&lt;img align=&quot;right&quot; border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRvaVDGNR5sWZknEoetFZORbvigwwiD2WU9dF7erADFVblHev_l7EuWYSF32_dkAurY-VtcHpWN-dU4B-2uF6A8pMLdZHn7EaQbvKDKaCZEcTvI6O7NCFmZe0JFNXUcvw5nb6iN9HRM9w/s1600/Vd-Blur_Gaussian_5x5.png&quot; data-original-width=&quot;100&quot; data-original-height=&quot;100&quot; /&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Enfoque&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
$$&lt;br /&gt;
\frac{-1}{256}&lt;br /&gt;
\begin{bmatrix}&lt;br /&gt;
1 &amp; 4 &amp; \ \ 6 &amp; 4 &amp; 1 \\&lt;br /&gt;
4 &amp; 16 &amp; \ \ 24 &amp; 16 &amp; 4 \\&lt;br /&gt;
6 &amp; 24 &amp; -476 &amp; 24 &amp; 6 \\&lt;br /&gt;
4 &amp; 16 &amp; \ \ 24 &amp; 16 &amp; 4 \\&lt;br /&gt;
1 &amp; 4 &amp; \ \ 6 &amp; 4 &amp; 1&lt;br /&gt;
\end{bmatrix}&lt;br /&gt;
$$&lt;br /&gt;
&lt;/td&gt; &lt;td&gt;&lt;br /&gt;
&lt;img align=&quot;right&quot; border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKYOo-BHFzVwHFHJ0Tk-FSKh61FrMbVRHp4h6zCzMHoTULLIXFr1eqsYZtw0WGWosBVGGEo2bF8pBWxtT7yCEUuT9e3RPkSD41UGf4jSvGweZhs3If3WI6DG3BXTdw-tX8E465rxQBDPE/s1600/Vd-Unsharp_5x5.png&quot; data-original-width=&quot;100&quot; data-original-height=&quot;100&quot; /&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;/table&gt;&lt;br /&gt;
En python podemos calcular la matriz de convolución a mano (no es complicado), pero la librería SciPy ya lo hace por nosotros.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from scipy import signal

A=[[255, 7, 3],
   [212, 240, 4],
   [218, 216, 230]]

B= [[1, -1],
    [-1,1]]

conv = signal.convolve(A, B)
print(conv)
&lt;/pre&gt;&lt;br /&gt;
&lt;pre&gt;[[ 255 -248   -4   -3]
 [ -43  276 -232   -1]
 [   6  -30  250 -226]
 [-218    2  -14  230]]
&lt;/pre&gt;&lt;br /&gt;
Con la librería PIL podemos aplicar la operación de convolución a una imagen. Primero la cargamos.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from PIL import Image, ImageFilter
import numpy as np

imgpath = &#39;/home/alberto/Imágenes/imagen.png&#39;
img = Image.open(imgpath)
img = img.convert(&#39;L&#39;)
display(img)
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLUuqrX50AmmUpeZ_9ArNpJPM0bbtBNZjGX3KVIuRNmvLxmT5EWN7xI3dpj5SQdPgvMgrsOvDu3PMDiNQR-mkWDw-gbyJA1q1UFzY35HJQ9UJ1Q2EuwzMQqPRT0VOpARlcyfppcOjG3D4/s1600/img1.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLUuqrX50AmmUpeZ_9ArNpJPM0bbtBNZjGX3KVIuRNmvLxmT5EWN7xI3dpj5SQdPgvMgrsOvDu3PMDiNQR-mkWDw-gbyJA1q1UFzY35HJQ9UJ1Q2EuwzMQqPRT0VOpARlcyfppcOjG3D4/s1600/img1.png&quot; data-original-width=&quot;512&quot; data-original-height=&quot;512&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Hemos convertido la imagen a escala de grises, por lo que nuestra imagen se puede operar como si fuera una sola matriz. Si fuera una imagen en color tendríamos tres matrices, una por cada canal de color RGB.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;imgmatrix = np.asarray(img, dtype=np.uint8)
print (imgmatrix)
&lt;/pre&gt;&lt;br /&gt;
&lt;pre&gt;[[156 164 156 ... 164 156 132]
 [164 164 164 ... 172 156 124]
 [164 156 164 ... 164 156 132]
 ...
 [ 44  44  52 ... 108 100 100]
 [ 44  44  60 ... 100 108 108]
 [ 44  44  52 ... 108 100 108]]
&lt;/pre&gt;&lt;br /&gt;
Así pues, como tenemos una matriz entre manos, podemos aplicar la convolución tal que así.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;kernelValues = [-2,-1,0,-1,1,1,0,1,2] 
kernel = ImageFilter.Kernel((3,3), kernelValues)
 
im2 = img.filter(kernel)

display(im2)
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMSshgaplzy9g0T8AQfBc-fQ3ok_YgWBIdVeGR3AhMu2WbGkIwyJHUZeAPAa_Ka5Myvq84Tn1wEM1XQMaWkJLATOK4J3J2rqQs6EWwJod_r1J8VUyN2UgwoQAJsGGaEkYsp4Z-10AfOYE/s1600/img2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMSshgaplzy9g0T8AQfBc-fQ3ok_YgWBIdVeGR3AhMu2WbGkIwyJHUZeAPAa_Ka5Myvq84Tn1wEM1XQMaWkJLATOK4J3J2rqQs6EWwJod_r1J8VUyN2UgwoQAJsGGaEkYsp4Z-10AfOYE/s1600/img2.png&quot; data-original-width=&quot;512&quot; data-original-height=&quot;512&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Una vez tenemos claro qué hace la operación de convolución sobre una imagen, ya podemos hablar de las redes neuronales convolucionales o CNN (Convolutional Neural Networs).&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Redes neuronales convolucionales (CNN).&lt;/h3&gt;La arquitectura típica de una CNN dedicada al reconocimiento y clasificación de imágenes, está dividida en dos partes: La etapa convolucional y la etapa de clasificación (las CNN pueden usarse en otras aplicaciones, pero aquí os mostraré su uso en la clasificación de imágenes). La fase convolucional utiliza dos tipos de capas: de convolución y de &lt;i&gt;max pooling&lt;/i&gt;. Las capas de convolución que en Keras se crean con la función &lt;i&gt;Conv2D()&lt;/i&gt; del paquete layers de Keras. Veamos un ejemplo.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;Conv2D(64, (3, 3), activation=&#39;relu&#39;)
&lt;/pre&gt;&lt;br /&gt;
El primer parámetro es el número de filtros (kernels) que se va a aplicar a la imagen, y el segundo parámetro es el tamaño de los kernels. Como hemos visto, cada kernel genera un conjunto de características en la imagen inicial. Por ejemplo, algunos kernels detectarán las líneas verticales, otros las horizontales, otros las esquinas, etc. Como resultado vamos a obtener una serie de imágenes (tantas como kernels apliquemos) en las que la red será capaz de reconocer formas simples como líneas y otros tipos de características. Si vamos repitiendo el proceso, y pasamos el resultado por otra capa convolucional, esta será capar de ir reconociendo elementos más complejos, como una oreja o un ojo, y así hasta ser capaz de reconocer un gato o un perro. Evidentemente esto es una simplificación, pero es una idea bastante intuitiva de cómo funciona una red convolucional. Es habitual usar la función de activación relu en este tipo de redes.&lt;br /&gt;
El otro tipo de capa que usa una red convolucional para reconocimiento de imágenes es &lt;i&gt;MaxPooling2D()&lt;/i&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;MaxPooling2D(pool_size=(2, 2))
&lt;/pre&gt;&lt;br /&gt;
Esta capa aplica un filtro que reduce la dimensionalidad de la imagen, esto es, la hace más pequeña. El objetivo es, por un lado, reducir el coste computacional. Además se minimiza la posibilidad de &lt;i&gt;overfitting&lt;/i&gt; y se consigue aumentar la abstracción sobre los datos de entrada. Lo más fácil será verlo con un ejemplo.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSxnbU1NJ5meUWxt1xIHc8aPpCrGEEWl4I7IlUZj-ICvrUumcKQFP17B_8w9VStEPHHYgCd5k6Njyk4s8xWRcPPuvSpOJQsi95g5gnGukssMCCpgLAfWDEe0Je_opNFRcp2l9P0JCRrHU/s1600/Max_Pooling.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSxnbU1NJ5meUWxt1xIHc8aPpCrGEEWl4I7IlUZj-ICvrUumcKQFP17B_8w9VStEPHHYgCd5k6Njyk4s8xWRcPPuvSpOJQsi95g5gnGukssMCCpgLAfWDEe0Je_opNFRcp2l9P0JCRrHU/s1600/Max_Pooling.jpg&quot; data-original-width=&quot;488&quot; data-original-height=&quot;201&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
La imagen muestra el resultado de aplicar una operación de &lt;i&gt;max pooling&lt;/i&gt; de 2x2. Esto es, se van cogiendo grupos de 2x2 y nos quedamos con el máximo valor del grupo de cuatro píxeles. De esta forma reducimos la imagen que originariamente era de 4x4 a una imagen de 2x2.&lt;br /&gt;
Una vez aclarados estos conceptos, vamos a ponernos manos a la obra.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Clasificar las imágenes del &lt;i&gt;dataset&lt;/i&gt; CIFAR10&lt;/h3&gt;Vamos a usar el &lt;i&gt;dataset&lt;/i&gt; &lt;a href=&quot;https://www.cs.toronto.edu/~kriz/cifar.html&quot;&gt;CIFAR10&lt;/a&gt;. Este &lt;i&gt;dataset&lt;/i&gt; contiene 60000 imágenes de tamaño 32x32 dividido en 10 clases diferentes (6000 imágenes por clase). El &lt;i&gt;dataset&lt;/i&gt; se divide en 50000 imágenes para entrenamiento y 10000 para test. &lt;br /&gt;
Las clases son:&lt;br /&gt;
&lt;pre&gt;0 - airplane
1 - automobile
2 - bird
3 - cat
4 - deer
5 - dog
6 - frog
7 - horse
8 - ship
9 - truck
&lt;/pre&gt;&lt;br /&gt;
Obtendremos el dataset importándolo desde Keras, que ya lo incorpora, por lo que no necesitamos descargarlo desde la web.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from keras.datasets import cifar10

(x_train, y_train), (x_test, y_test) = cifar10.load_data()
&lt;/pre&gt;&lt;br /&gt;
Examinemos algunas imágenes para ver qué pinta tienen.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import matplotlib.pyplot as plt

for i in range(9):
    plt.subplot(330 + 1 + i)
    plt.imshow(x_train[i])
    
plt.show()
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAfRMBJCvENZRQusZBtQtKa-W2jSHM8lVg8tMGVbg13r3ej0nnAnU3lmyp5qhXhF10yl8TLMK3NPfuv4zuqd4JVRAbQILo89doVqzqfGzufNSSI1B4ZhJoiUADkS16dX-6HlIXFOjFoRQ/s1600/cifa10.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAfRMBJCvENZRQusZBtQtKa-W2jSHM8lVg8tMGVbg13r3ej0nnAnU3lmyp5qhXhF10yl8TLMK3NPfuv4zuqd4JVRAbQILo89doVqzqfGzufNSSI1B4ZhJoiUADkS16dX-6HlIXFOjFoRQ/s1600/cifa10.png&quot; data-original-width=&quot;334&quot; data-original-height=&quot;251&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Vamos a transformar las etiquetas de números enteros a un vector binario, que es más conveniente a la hora de entrenar la red. Esto lo hacemos con la función &lt;i&gt;to_categorical&lt;/i&gt; de keras. Esta forma de codificar las etiquetas se denomina &lt;a href=&quot;https://es.wikipedia.org/wiki/One-hot&quot;&gt;one-hot&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from keras.utils import to_categorical

y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
&lt;/pre&gt;&lt;br /&gt;
Creamos un conjunto de validación que nos va a servir para ir controlando la calidad del entrenamiento para evitar el &lt;i&gt;overfitting&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import numpy as np

np.random.seed(42)

# barajamos el dataset
permutation = np.random.permutation(x_train.shape[0])
x_train = x_train[permutation]
y_train = y_train[permutation]

# obtenemos el conjunto de validación
num_val = 10000
x_val = x_train[:num_val]
x_train = x_train[num_val:]
y_val = y_train[:num_val]
y_train = y_train[num_val:]
&lt;/pre&gt;&lt;br /&gt;
Finalmente definimos el modelo de nuestra red neuronal.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from keras import layers
from keras import models

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;, input_shape=(32, 32, 3)))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation=&#39;relu&#39;))
model.add(layers.Dense(10, activation=&#39;softmax&#39;))
&lt;/pre&gt;&lt;br /&gt;
Esquemáticamente tenemos lo siguiente.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8Co_L45ZzRSrIGpioUVwSYE6jVXvylHgKLpeYvPusqNOVdq7r6vZ_MG_xqTQ6QGpArHmJ8bTcxoTXEzDA9gfYL6viSqE5Md3Z2n3FylRdEoeXdFexJUTcx_e8LaVXqiYTlrjoVtyjxU8/s1600/esquema.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8Co_L45ZzRSrIGpioUVwSYE6jVXvylHgKLpeYvPusqNOVdq7r6vZ_MG_xqTQ6QGpArHmJ8bTcxoTXEzDA9gfYL6viSqE5Md3Z2n3FylRdEoeXdFexJUTcx_e8LaVXqiYTlrjoVtyjxU8/s1600/esquema.png&quot; data-original-width=&quot;527&quot; data-original-height=&quot;848&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Detengámonos un poco en el modelo que acabamos de definir. A diferencia de la red que vimos en el anterior artículo sobre &lt;a href=&quot;https://www.ellaberintodefalken.com/2019/10/clasificacion-deep-learning-keras.html&quot;&gt;clasificación con deep learning&lt;/a&gt;, la entrada de la red convolucional es una imagen, en este caso de 32x32, y con tres capas, una por cada canal de color. Hay varias cosas que señalar. La primera es que el tamaño de los kernels suele ser de tamaño impar (3x3, 5x5, etc). Por otro lado, es habitual que el número de kernels que se aplica en cada capa convolucional vaya en aumento y tome valores (normalmente potencias de 2) cada vez más grandes según se profundiza en la red. En nuestro ejemplo hay pocas capas para lo que suele ser habitual (para permitir que este código se ejecute en equipo relativamente modestos). En una red más compleja se suele llegar a usar hasta 512, 1024, 2048 y hasta más número de filtros. También es habitual incluir una capa de &lt;i&gt;max pooling&lt;/i&gt; cada pocas capas de convolución (en nuestro ejemplo, entre cada capa).&lt;br /&gt;
La última fase de la red es la de clasificación. Se utiliza una capa densamente conectada. Previamente se aplanan con &lt;i&gt;flatten()&lt;/i&gt; las salidas de la fase convolucional, que recordemos que es una imagen en dos dimensiones, para convertirlo en un vector. En la última fase se usa la función de activación &lt;i&gt;softmax&lt;/i&gt; de diez salidas (una por cada tipo de imagen a clasificar) que nos devuelve una distribución de probabilidad indicando cuál es la probabilidad de que la imagen pertenezca a cada una de las clases.&lt;br /&gt;
Ahora podemos compilar y entrenar el modelo.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from keras import optimizers

model.compile(loss=&#39;categorical_crossentropy&#39;, 
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=[&#39;acc&#39;])

train_log = model.fit(x_train, y_train, 
                      epochs=100, batch_size=128,
                     validation_data=(x_val, y_val))
&lt;/pre&gt;&lt;br /&gt;
Es buena práctica crear un par de gráficas para ver cómo se comporta el modelo durante el entrenamiento. Veamos la gráfica de ajuste con la función de coste y la de exactitud, tanto para el grupo de datos de entrenamiento como los de validación.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import matplotlib.pyplot as plt

acc = train_log.history[&#39;acc&#39;]
val_acc = train_log.history[&#39;val_acc&#39;]
loss = train_log.history[&#39;loss&#39;]
val_loss = train_log.history[&#39;val_loss&#39;]
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, &#39;r&#39;, label=&#39;Training acc&#39;)
plt.plot(epochs, val_acc, &#39;b&#39;, label=&#39;Validation acc&#39;)
plt.title(&#39;Training and validation accuracy&#39;)
plt.legend()
plt.figure()
plt.plot(epochs, loss, &#39;r&#39;, label=&#39;Training loss&#39;)
plt.plot(epochs, val_loss, &#39;b&#39;, label=&#39;Validation loss&#39;)
plt.title(&#39;Training and validation loss&#39;)
plt.legend()
plt.show()
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPGbjUjwikSmpPlVEJXEVK9mvPKkKKNqOta7VlcgqE-UM942XvcU2SSaUJT8jdx1aLYLRConJ0UVUxy76CGpRbGUGyRqSQnao2WBuGRZsIo1SJoX8QxkzTW6TNgyQNEuW_-_h5K-vxI54/s1600/graf1.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPGbjUjwikSmpPlVEJXEVK9mvPKkKKNqOta7VlcgqE-UM942XvcU2SSaUJT8jdx1aLYLRConJ0UVUxy76CGpRbGUGyRqSQnao2WBuGRZsIo1SJoX8QxkzTW6TNgyQNEuW_-_h5K-vxI54/s1600/graf1.png&quot; data-original-width=&quot;372&quot; data-original-height=&quot;264&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjB1pNR7AlZ6RE5g8Pfx9GZ8aYCzaaIlSwMI87HN9F0NJDm5Wc1wz13xTSY7M9SbujI0Z0fJYdO2Ch8-s-jnPKEyge-hQYPF7DydNdGFvG7yeMYawuy041D22vMvyebDYHCs2HaEaA2V_E/s1600/graf2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjB1pNR7AlZ6RE5g8Pfx9GZ8aYCzaaIlSwMI87HN9F0NJDm5Wc1wz13xTSY7M9SbujI0Z0fJYdO2Ch8-s-jnPKEyge-hQYPF7DydNdGFvG7yeMYawuy041D22vMvyebDYHCs2HaEaA2V_E/s1600/graf2.png&quot; data-original-width=&quot;362&quot; data-original-height=&quot;264&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;test_loss, test_accuracy = model.evaluate(x_test, y_test)
print(test_accuracy)
&lt;/pre&gt;&lt;br /&gt;
&lt;pre&gt;10000/10000 [==============================] - 0s 44us/step
0.6504999995231628
&lt;/pre&gt;&lt;br /&gt;
En la segunda gráfica observamos que hay un problema. La red neuronal comienza a tener problemas con el conjunto de validación y vemos como la función de coste crece en vez de minimizarse. Es un claro problema de &lt;i&gt;overfitting&lt;/i&gt;. Vamos a recurrir a una técnica habitual en estos casos, que consiste en eliminar complejidad de la red. Al disminuir esta, conseguimos que se simplifique el modelo y que logre generalizar mejor. Una forma de reducir esta complejidad es eliminar algunas conexiones de forma aleatoria entre dos capas. Esto podemos hacerlo con una capa &lt;i&gt;Dropout()&lt;/i&gt;. Como parámetro pasamos el porcentaje de conexiones que queremos anular (por ejemplo un valor de 0.25 eliminará el 25% de las conexiones).&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;, input_shape=(32, 32, 3)))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Dropout(0.25))
model.add(layers.Conv2D(64, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Dropout(0.25))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation=&#39;relu&#39;))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(10, activation=&#39;softmax&#39;))

model.compile(loss=&#39;categorical_crossentropy&#39;, 
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=[&#39;acc&#39;])

train_log = model.fit(x_train, y_train, 
                      epochs=100, batch_size=128,
                     validation_data=(x_val, y_val))
&lt;/pre&gt;&lt;br /&gt;
Como vemos, ahora la cosa está mucho mejor. Y además llegamos al 71% de exactitud con el conjunto de test frente al 65% que teníamos antes.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgC_KN3IuPFO-ILG7U-G_5jnxiD6C6jkolQwoR-_If9dVUm_Luzc8fpCZbGIv9rCz7Yxkc6z85Ci-4m2_oIl4IiqeyHVjs5z3t1m35IdiR8WB71t0-n8WIo2QnXDO0lMFGrYJqVOtO_ioE/s1600/graf3.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgC_KN3IuPFO-ILG7U-G_5jnxiD6C6jkolQwoR-_If9dVUm_Luzc8fpCZbGIv9rCz7Yxkc6z85Ci-4m2_oIl4IiqeyHVjs5z3t1m35IdiR8WB71t0-n8WIo2QnXDO0lMFGrYJqVOtO_ioE/s1600/graf3.png&quot; data-original-width=&quot;372&quot; data-original-height=&quot;264&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6qUF1kGHdnFKOXMm1brWb7ELkIsNzwtYUEFVE16lqiPNiFgAdz9GLAErROlbhYp5V3_unEoq3Fz_oZ-N43VvV-RQVxFZLYTTqnFPp1tqvjqc9a9_EHTMUDfE-ekyCSF5Yk4Xbkq74230/s1600/graf4.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6qUF1kGHdnFKOXMm1brWb7ELkIsNzwtYUEFVE16lqiPNiFgAdz9GLAErROlbhYp5V3_unEoq3Fz_oZ-N43VvV-RQVxFZLYTTqnFPp1tqvjqc9a9_EHTMUDfE-ekyCSF5Yk4Xbkq74230/s1600/graf4.png&quot; data-original-width=&quot;362&quot; data-original-height=&quot;264&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Aun así, un 71% no es muy espectacular. ¿Se puede mejorar? Rotundamente sí. El modelo que hemos usado es demasiado sencillo para este tipo de tareas. Una red de este tipo tiene muchas más capas, pero claro, también necesitaremos una máquina y una GPU más potente. Por ejemplo, con el siguiente modelo, que sigue siendo relativamente sencillo, llegamos al 77% (eso sí, no lo intentéis en casa si no vais armados con una buena GPU). Con una red aún más compleja he llegado a superar una exactitud del 95%, pero usando técnicas que ya os contaré otro día.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;, input_shape=(32, 32, 3)))
model.add(layers.Conv2D(32, (3, 3)), activation=&#39;relu&#39;)
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Dropout(0.25))
model.add(layers.Conv2D(64, (3, 3), activation=&#39;relu&#39;, padding=&#39;same&#39;)
model.add(layers.Conv2D(64, (3, 3), activation=&#39;relu&#39;)
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Dropout(0.25))
model.add(layers.Flatten())
model.add(layers.Dense(512), activation=&#39;relu&#39;)
model.add(layers.Dropout(0.5))
model.add(layers.Dense(10), activation=&#39;softmax&#39;)

model.compile(loss=&#39;categorical_crossentropy&#39;, 
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=[&#39;acc&#39;])

train_log = model.fit(x_train, y_train, 
                      epochs=100, batch_size=128,
                     validation_data=(x_val, y_val))
&lt;/pre&gt;&lt;br /&gt;
En la práctica se utilizan modelos más complejos creados por investigadores y a las que se le asigna un nombre para diferenciarlos de otras arquitecturas. Algunos ejemplos son VGG16, AlexNet, ResNet o DenseNet, por nombrar algunas de las arquitecturas más usadas. Como ejemplo os muestro la arquitectura de RasNet, para que os hagáis una idea de la complejidad de los modelos con los que se trabaja habitualmente.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiigm3BLARaltsPcwb7II1c-lK-COBbysxvpW_XgvEhUxHL33UQ85VLLmkIGLPHINKKKyoU-cUL4lgZpiQ-wfcdGlICPHqnQsI4lHFuj25HYHM97AsCopE3lYwa20YgS95U2uHxPUn5QRw/s1600/resnet.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiigm3BLARaltsPcwb7II1c-lK-COBbysxvpW_XgvEhUxHL33UQ85VLLmkIGLPHINKKKyoU-cUL4lgZpiQ-wfcdGlICPHqnQsI4lHFuj25HYHM97AsCopE3lYwa20YgS95U2uHxPUn5QRw/s1600/resnet.png&quot; data-original-width=&quot;1314&quot; data-original-height=&quot;572&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Os invito a experimentar añadiendo y quitando capas, modificando sus parámetros, añadiendo o quitando capas dropout, etc. Es un ejercicio interesante para ir viendo cómo mejora o empeora el resultado.</description><link>http://www.ellaberintodefalken.com/2019/10/vision-artificial-redes-convolucionales-CNN.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpkChAz70AWS93Pd6EK3VNQNO-ha6Vb3sqFUN9Y2goJgrjHGLEPVeYlg6pW6si3ch8tXe01kB-gJVMmrrL-vm1GOlnPHlp90gH3voKFuJvSyQFohDZzV8CKq-f6-Pr6b9p52c38xhmqNM/s72-c/Deep-Neural-Network-What-is-Deep-Learning-Edureka.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-5062611680817730780</guid><pubDate>Sun, 06 Oct 2019 00:28:00 +0000</pubDate><atom:updated>2023-03-10T19:59:17.081+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">deep learning</category><category domain="http://www.blogger.com/atom/ns#">fashion MNIST</category><category domain="http://www.blogger.com/atom/ns#">keras</category><category domain="http://www.blogger.com/atom/ns#">machine learning</category><category domain="http://www.blogger.com/atom/ns#">redes neuronales</category><category domain="http://www.blogger.com/atom/ns#">tensorflow</category><title>Clasificación multiclase con deep learning y keras</title><description>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBSUAab-yIV8mwgdyOz7C8Hj4ENKS8m78TrkyIebbd8nCZY4NhoyNt6LboD15u8dVTDbTJI5dAAkqP4sIgCgRHm2sAgKHHb5aIGEgI_iXc5Jb_i6qK-OHzoSRrPb3yCiBn8B4Wb4fVqf8/s1600/Plot-of-a-Subset-of-Images-from-the-Fashion-MNIST-Dataset-1024x768.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBSUAab-yIV8mwgdyOz7C8Hj4ENKS8m78TrkyIebbd8nCZY4NhoyNt6LboD15u8dVTDbTJI5dAAkqP4sIgCgRHm2sAgKHHb5aIGEgI_iXc5Jb_i6qK-OHzoSRrPb3yCiBn8B4Wb4fVqf8/s640/Plot-of-a-Subset-of-Images-from-the-Fashion-MNIST-Dataset-1024x768.png&quot; width=&quot;640&quot; height=&quot;480&quot; data-original-width=&quot;1024&quot; data-original-height=&quot;768&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Dentro de los diferentes tipos de problemas que trata de abordar el aprendizaje automático, uno de los más interesantes es la clasificación. Básicamente consiste en, dado un conjunto de elementos, asignarle a una etiqueta, donde cada etiqueta corresponde a una clase diferente. Por ejemplo, una aplicación interesante y bastante actual es clasificar a los visitantes de una web en dos categorías, los que, con alta probabilidad, pulsarán sobre un anuncio concreto y los que no. Este es un ejemplo de clasificación binaria, porque sólo hay dos categorías. Otro ejemplo puede ser el que ya vimos en el artículo sobre &lt;a href=&quot;https://www.ellaberintodefalken.com/2019/02/knn-mnist.html&quot; target=&quot;blank&quot;&gt;reconocimiento de caracteres manuscritos usando K-NN&lt;/a&gt;. En este caso hablábamos de un problema de clasificación multiclase en el que asignábamos a cada imagen una sola etiqueta de un conjunto de diez posibles (los dígitos del 0 al 9). Vimos que K-NN es un enfoque sencillo y efectivo para este tipo de problemas, pero hay otras alternativas. Vamos a enfrentarnos a un problema similar pero con un dataset y una técnica diferente.&lt;br /&gt;
Vamos a usar una red neuronal para resolver un problema de clasificación multiclase similar a MNIST (si no tienes muy claro qué es eso de una red neuronal puedes leer un artículo que escribí en mi antiguo blog sobre el &lt;a href=&quot;https://divertimentosinformaticos.blogspot.com/2013/10/perceptron-redes-neuronales.html&quot; target=&quot;blank&quot;&gt;perceptrón&lt;/a&gt;). Se trata del conjunto &lt;a href=&quot;https://github.com/zalandoresearch/fashion-mnist&quot; target=&quot;blank&quot;&gt;fashion MNIST&lt;/a&gt;, que creó la empresa Zalando con imágenes de prendas de vestir. Este dataset contiene 60.000 imágenes de prendas (28x28 en escala de grises), y 10.000 imágenes para test. Las categorías con su etiqueta son las siguientes:&lt;br /&gt;
&lt;br /&gt;
0 T-shirt/top&lt;br /&gt;
1 Trouser&lt;br /&gt;
2 Pullover&lt;br /&gt;
3 Dress&lt;br /&gt;
4 Coat&lt;br /&gt;
5 Sandal&lt;br /&gt;
6 Shirt&lt;br /&gt;
7 Sneaker&lt;br /&gt;
8 Bag&lt;br /&gt;
9 Ankle boot&lt;br /&gt;
&lt;br /&gt;
Se trata de, dada una imagen, asignarle su etiqueta numérica correspondiente. Para crear y entrenar la red neuronal vamos a recurrir a la librería &lt;a href=&quot;https://keras.io/&quot; target=&quot;blank&quot;&gt;keras&lt;/a&gt;, que nos ofrece una capa de abstracción sobre &lt;a href=&quot;https://www.tensorflow.org/&quot; target=&quot;blank&quot;&gt;TensorFlow&lt;/a&gt;. Keras, además de una interfaz de alto nivel para el diseño de modelos de aprendizaje basado en redes de neuronas, nos permite utilizar una serie de datasets que están ya integrados. Las siguiente líneas nos permiten descargar el dataset fashion MNIST y ponerlo en memoria. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from keras.datasets import fashion_mnist
(x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()
&lt;/pre&gt;&lt;br /&gt;
Si quiere visualizar una de las imágenes para ver qué pinta tienen puede hacerlo con &lt;i&gt;matplotlib&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import matplotlib.pyplot as plt

plt.imshow(x_test[0], cmap=&quot;gray&quot;)
plt.show()
&lt;/pre&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmh_29ZRvGgq3srZwog-At8vTaBq4mgvq9gaT1nwKUt7IOn2zreOWXEQoNVnH1mP4jkA3f-ZYbPAujXG6ejEoAl_brL0HRiLxr2saZvqriv_DhW1tibojhy9jLWFnVHfVg-4snEaEXSnM/s1600/%25C3%25ADndice.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmh_29ZRvGgq3srZwog-At8vTaBq4mgvq9gaT1nwKUt7IOn2zreOWXEQoNVnH1mP4jkA3f-ZYbPAujXG6ejEoAl_brL0HRiLxr2saZvqriv_DhW1tibojhy9jLWFnVHfVg-4snEaEXSnM/s1600/%25C3%25ADndice.png&quot; data-original-width=&quot;251&quot; data-original-height=&quot;248&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Son imágenes de 28x28 píxeles en escala de grises (con 256 niveles de gris). No son muy vistosas, pero tienen la ventaja de que son muy manejables a la hora de alimentar nuestra red neuronal. Como las imágenes son de 28x28 necesitamos 784 entradas (28x28=784) ya que a cada pixel le corresponde una entrada en la red. Así que lo primero que tenemos que hacer es adaptar las imágenes a un formato más manejable.&lt;br /&gt;
Primero transformamos cada imagen de una matriz de 28x28 a un vector de tamaño 784. Por otra parte, cada pixel es un entero en el rango 0-255. Lo vamos a normalizar para que cada pixel sea un número real que esté en el rango 0-1.&lt;br /&gt;
También vamos a transformar las etiquetas de números enteros a un vector binario (&lt;i&gt;one-hot&lt;/i&gt;), que es más conveniente a la hora de entrenar la red. Esto lo hacemos con la función &lt;i&gt;to_categorical()&lt;/i&gt; de keras, que nos devuelve una matriz en la que cada elemento es un vector de diez posiciones donde todos sus valores están a 0 menos uno de ellos. Por ejemplo, la etiqueta con el valor 2 quedaría como 0010000000 (no se trata del valor binario de la etiqueta, sino de asignar una posición concreta al 1 de cada etiqueta).&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from keras.utils import to_categorical

x_train = x_train.reshape((60000, 784))
x_train = x_train.astype(&#39;float32&#39;) / 255
y_train = to_categorical(y_train)

x_test = x_test.reshape((10000, 784))
x_test = x_test.astype(&#39;float32&#39;) / 255
y_test = to_categorical(y_test)
&lt;/pre&gt;&lt;br /&gt;
Del conjunto de entrenamiento vamos a separar una porción de ellos como conjunto de validación. Este subconjunto lo vamos a utilizar para, durante el entrenamiento, ir viendo si el modelo sigue funcionando bien con datos que no están en el conjunto de entrenamiento. Esto lo hacemos para evitar el sobreentrenamiento u &lt;i&gt;overfitting&lt;/i&gt;, que ocurre cuando un modelo termina &quot;aprendiéndose de memoria&quot; los datos de entrenamiento, con lo cual funciona muy bien con estos datos, pero no con otros datos de fuera del conjunto de entrenamiento. Es decir, el modelo no es capaz de generalizar. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import numpy as np

np.random.seed(42)

# barajamos el dataset
permutation = np.random.permutation(x_train.shape[0])
x_train = x_train[permutation]
y_train = y_train[permutation]

# obtenemos el conjunto de validación
num_val = 10000
x_val = x_train[:num_val]
x_train = x_train[num_val:]
y_val = y_train[:num_val]
y_train = y_train[num_val:]
&lt;/pre&gt;&lt;br /&gt;
Ahora llega el momento de diseñar una arquitectura para nuestra red neuronal. No hay reglas de oro a la hora de diseñar una arquitectura, hay mucho de arte, de intuición y de experiencia. Vamos a partir de una arquitectura con una capa de entrada, dos capas ocultas y una de salida.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;from keras import models, layers

model = models.Sequential()
model.add(layers.Dense(256, activation=&#39;relu&#39;, input_shape=(784,))) # 28x28=784
model.add(layers.Dense(128, activation=&#39;relu&#39;))
model.add(layers.Dense(10, activation=&#39;softmax&#39;))
&lt;/pre&gt;&lt;br /&gt;
Esquemáticamente nuestra red tiene la siguiente arquitectura.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6jb0zPVuDTdZzGRHmwNWoV49ZsE645EAz09guX3_5lFGT5dvjbRe-dMbJkMz8MBVnb4LVXbHTXkvi8Phaxr7ONb3_LAAptI8xpcObkMO-6k_kozDafzdGd5l8VvHhIwduwOqHUZuo_OM/s1600/arquitectura.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6jb0zPVuDTdZzGRHmwNWoV49ZsE645EAz09guX3_5lFGT5dvjbRe-dMbJkMz8MBVnb4LVXbHTXkvi8Phaxr7ONb3_LAAptI8xpcObkMO-6k_kozDafzdGd5l8VvHhIwduwOqHUZuo_OM/s1600/arquitectura.png&quot; data-original-width=&quot;412&quot; data-original-height=&quot;405&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Sin entrar en mucho detalle, nuestra red tiene 784 entradas (28x28 píxeles), que se conectan a una primera capa oculta de 256 neuronas conectadas densamente (todas las entradas se conectan a todas las neuronas de esta capa). Como función de activación se usa la función rectificador o &lt;i&gt;relu&lt;/i&gt;, cuya función es&lt;br /&gt;
&lt;br /&gt;
\[f(x) = \max(0, x)\]&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRaJtHGoIiMJinPjXdZv0gxR_sJbxkR7xnoboJJNvMEZb0w3HtZnsnoxjSN7Tk1wirtuxQ5JKA3Fto2_r_aJfEKgEToPN6HJST77lWgW9bef7_FTDHoG4UcJaWhmCe0dz-ClZ4aaqe-RM/s1600/1*DfMRHwxY1gyyDmrIAd-gjQ.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRaJtHGoIiMJinPjXdZv0gxR_sJbxkR7xnoboJJNvMEZb0w3HtZnsnoxjSN7Tk1wirtuxQ5JKA3Fto2_r_aJfEKgEToPN6HJST77lWgW9bef7_FTDHoG4UcJaWhmCe0dz-ClZ4aaqe-RM/s400/1*DfMRHwxY1gyyDmrIAd-gjQ.png&quot; width=&quot;400&quot; height=&quot;182&quot; data-original-width=&quot;1026&quot; data-original-height=&quot;467&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Hay diversas funciones de activación posibles, aunque en los últimos años la función &lt;i&gt;relu&lt;/i&gt; ha ido ganando popularidad frente a la más clásica función de activación sigmoide.&lt;br /&gt;
La segunda capa oculta, también densamente conectada y usando la función de activación relu, tiene 128 neuronas. Finalmente, la capa de salida tiene 10 neuronas, una por cada clase que queremos clasificar. En esta capa usamos la función de activación &lt;i&gt;softmax&lt;/i&gt;, que nos devuelve una distribución de probabilidad entre las salidas, de forma que cada una de estas salidas nos indica la probabilidad de que la imagen de entrada pertenezca a dicha clase. Es decir, que la suma de todas las salidas sumarán 1.&lt;br /&gt;
&lt;br /&gt;
Las siguientes líneas compilan el modelo y lo parametrizan. En este caso, para optimizar la función de costes, que es la que queremos minimizar, usamos el optimizador &lt;i&gt;rmsprop&lt;/i&gt;, que es un tipo de optimización basada en el &lt;a href=&quot;https://www.ellaberintodefalken.com/2019/03/regresion-lineal-descenso-de-gradiente.html&quot; target=&quot;blank&quot;&gt;descenso de gradiente&lt;/a&gt; que ya os presenté en otro artículo anterior (con algunas diferencias). como función de coste (&lt;i&gt;loss function&lt;/i&gt;) usaremos &lt;i&gt;categorical_crossentropy&lt;/i&gt;, que funciona especialmente bien en problemas de clasificación donde cada ejemplo pertenece exclusivamente a una sola categoría.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;model.compile(optimizer = &#39;rmsprop&#39;,
             loss = &#39;categorical_crossentropy&#39;,
             metrics = [&#39;accuracy&#39;])
&lt;/pre&gt;&lt;br /&gt;
Ya sólo nos resta entrenar el modelo. Vamos a entrenarlo usando lotes (&lt;i&gt;batch&lt;/i&gt;) de 128 ejemplos durante 30 iteraciones (&lt;i&gt;epochs&lt;/i&gt;). Además le indicamos cuál es nuestro conjunto de validación.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;train_log = model.fit(x_train, y_train, 
                      epochs=30, batch_size=128,
                     validation_data=(x_val, y_val))
&lt;/pre&gt;&lt;br /&gt;
Finalizado el entrenamiento, podemos probar el modelo contra el conjunto de test y obtener la exactitud de nuestro modelo resultante.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;test_loss, test_accuracy = model.evaluate(x_test, y_test)
print(test_accuracy)
&lt;/pre&gt;&lt;pre&gt;10000/10000 [==============================] - 0s 12us/step
0.8788999915122986
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Es decir, hemos conseguido una exactitud del 87% asignando las imágenes del conjunto de test a su clase correspondiente. No está mal. Sin embargo, para ir ajustando el modelo necesitamos alguna herramienta que nos permita medir qué tan bien o mal se comporta el modelo. Durante el entrenamiento se genera un histórico con los valores que ha ido tomando la función de coste y la precisión tanto del conjunto de entrenamiento como del de validación. Podemos obtener una representación gráfica.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# grafica con la función de coste

loss = train_log.history[&#39;loss&#39;]
val_loss = train_log.history[&#39;val_loss&#39;]
epochs = range(1, len(loss) + 1)
plt.plot(epochs, loss, &#39;b&#39;, label=&#39;Training loss&#39;)
plt.plot(epochs, val_loss, &#39;r&#39;, label=&#39;Validation loss&#39;)
plt.title(&#39;Training and validation loss&#39;)
plt.xlabel(&#39;Epochs&#39;)
plt.ylabel(&#39;Loss&#39;)
plt.legend()
plt.show()
&lt;/pre&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWaemfjG69QDaf1Uik5uyQ9La2Kil2jqlq5tjfofWkEWKy6bMMDcQrQuRSmX1ORVJjeFp-e68pB7HCRujUQCviMD69zgiQe4TKA2iHcobU-AqDbNuMG7ZeZhgntlcoCpYSWl9LxGi7yOM/s1600/loss.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWaemfjG69QDaf1Uik5uyQ9La2Kil2jqlq5tjfofWkEWKy6bMMDcQrQuRSmX1ORVJjeFp-e68pB7HCRujUQCviMD69zgiQe4TKA2iHcobU-AqDbNuMG7ZeZhgntlcoCpYSWl9LxGi7yOM/s1600/loss.png&quot; data-original-width=&quot;386&quot; data-original-height=&quot;278&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
También nos puede ser útil ver cómo ha ido evolucionando la precisión durante el entrenamiento.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# grafica de la exactitud

plt.clf()
acc = train_log.history[&#39;accuracy&#39;]
val_acc = train_log.history[&#39;val_accuracy&#39;]
epochs = range(1, len(loss) + 1)
plt.plot(epochs, acc, &#39;b&#39;, label=&#39;Training acc&#39;)
plt.plot(epochs, val_acc, &#39;r&#39;, label=&#39;Validation acc&#39;)
plt.title(&#39;Training and validation accuracy&#39;)
plt.xlabel(&#39;Epochs&#39;)
plt.ylabel(&#39;Accuracy&#39;)
plt.legend()
plt.show()
&lt;/pre&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgpvW0Ir1F3uynIA1BCuMwSfNtB7apGOVztKhHAPuPB92wddzZCAUk_zg1p1aik2-NAzfEfnG0EZbBJ93BSOigSj_MQK9eO5e_8pUTZjiwGS0dJn4pp-gXNQAAgE1QTTds4P5EO-33FgA/s1600/acc.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgpvW0Ir1F3uynIA1BCuMwSfNtB7apGOVztKhHAPuPB92wddzZCAUk_zg1p1aik2-NAzfEfnG0EZbBJ93BSOigSj_MQK9eO5e_8pUTZjiwGS0dJn4pp-gXNQAAgE1QTTds4P5EO-33FgA/s1600/acc.png&quot; data-original-width=&quot;392&quot; data-original-height=&quot;278&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
En la primera gráfica, la de la función de costes, vemos que a partir de la octava iteración (&lt;i&gt;epoch&lt;/i&gt;), la función de coste sobre el conjunto de validación comienza a crecer (empeora), mientras que en el caso del conjunto de entrenamiento sigue decreciendo (mejorando). Eso es señal inequívoca de que nuestro modelo esta sobreentrenado (&lt;i&gt;overfitting&lt;/i&gt;). Es decir, que predice muy bien en el conjunto de entrenamiento, pero mal en datos externos a este. Y eso es malo. Podemos hacer varias cosas para evitarlo, pero las dos más sencillas son, quitar neuronas para simplificar el modelo y/o reducir el número de iteraciones. Vamos a probar con la segunda opción (hay otras un poco más complejas y efectivas, pero ya os las cuento otro día). Vamos a ver qué pasa si lo dejamos en 8 iteraciones (que es donde empieza a empeorar la función de coste del conjunto de validación).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;10000/10000 [==============================] - 0s 13us/step
0.8809999823570251
&lt;/pre&gt;&lt;br /&gt;
Tenemos un 88% de exactitud, lo que de por sí ya es una pequeña mejora, pero además nos hemos ahorrado 22 iteraciones, lo que es una buena cantidad de tiempo de cómputo. Si no tienes una buena GPU pueden llegar a ser algunos minutos en un ordenador no muy potente. La gráfica de la evolución de la función de coste ahora es esta.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtAiukIWtMvmvZLcSUPy1sAlnNDYj19bKIGCXOkoJOyBI_cxgKbyEeqPHHkjmmlzY97Cnnx3fsZboG12KlX0jmaaUAuf7BWR8lQFIGKEM4sYGYP9WLtCdmR4Tn2wcRXyw5SYsskglSyME/s1600/loss2.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtAiukIWtMvmvZLcSUPy1sAlnNDYj19bKIGCXOkoJOyBI_cxgKbyEeqPHHkjmmlzY97Cnnx3fsZboG12KlX0jmaaUAuf7BWR8lQFIGKEM4sYGYP9WLtCdmR4Tn2wcRXyw5SYsskglSyME/s1600/loss2.png&quot; data-original-width=&quot;392&quot; data-original-height=&quot;278&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
y la de exactitud es esta.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJbX2HSUmhrj_7z67W7sYD2yLIztSHYKsTa9yn8GD0LfKvBn1EAIrLQnjqnk5ynhbOC2dksx3SZaXK0EGKzmZwXYGXv_jz34peH2EttEKOmaqydtwBmNhSugnDb2IxiNYJ_5gSQGsYe2M/s1600/acc2.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJbX2HSUmhrj_7z67W7sYD2yLIztSHYKsTa9yn8GD0LfKvBn1EAIrLQnjqnk5ynhbOC2dksx3SZaXK0EGKzmZwXYGXv_jz34peH2EttEKOmaqydtwBmNhSugnDb2IxiNYJ_5gSQGsYe2M/s1600/acc2.png&quot; data-original-width=&quot;392&quot; data-original-height=&quot;278&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Ahora que tenemos un modelo entrenado, podemos hacer predicciones. Por ejemplo sobre el conjunto de test.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;predictions = model.predict(x_test)
np.argmax(predictions[0]) # clase más probable
&lt;/pre&gt;&lt;br /&gt;
Esto nos devuelve el valor 9 (Ankle boot), que es precisamente la categoría a la que pertenece la primera imagen del conjunto de test, que ya os mostré al principio del artículo.&lt;br /&gt;
&lt;br /&gt;
Se puede seguir jugando con el modelo añadiendo o quitando neuronas, o capas completas. También cambiando el tipo de función de activación, etc. &lt;a href=&quot;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/FashionMNIST.ipynb&quot; target=&quot;blank&quot;&gt;Aquí&lt;/a&gt; os dejo el notebook de jupyter por si queréis trastear un poco con la red neuronal que acabamos de crear.</description><link>http://www.ellaberintodefalken.com/2019/10/clasificacion-deep-learning-keras.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBSUAab-yIV8mwgdyOz7C8Hj4ENKS8m78TrkyIebbd8nCZY4NhoyNt6LboD15u8dVTDbTJI5dAAkqP4sIgCgRHm2sAgKHHb5aIGEgI_iXc5Jb_i6qK-OHzoSRrPb3yCiBn8B4Wb4fVqf8/s72-c/Plot-of-a-Subset-of-Images-from-the-Fashion-MNIST-Dataset-1024x768.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-844130050519569595</guid><pubDate>Wed, 12 Jun 2019 16:18:49 +0000</pubDate><atom:updated>2026-07-15T11:33:18.391+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">74LS247</category><category domain="http://www.blogger.com/atom/ns#">BCD 7 segmentos</category><category domain="http://www.blogger.com/atom/ns#">BCD a 7 segmentos</category><category domain="http://www.blogger.com/atom/ns#">decodificador 7 segmentos</category><category domain="http://www.blogger.com/atom/ns#">Electrónica</category><category domain="http://www.blogger.com/atom/ns#">post</category><category domain="http://www.blogger.com/atom/ns#">Sistemas digitales</category><category domain="http://www.blogger.com/atom/ns#">SN74LS247N</category><title>Display de 7 segmentos con 74LS247</title><description>&lt;p&gt;Ya hemos hablado del control de un &lt;em&gt;display&lt;/em&gt; de siete segmentos en un &lt;a href=&quot;https://www.ellaberintodefalken.com/2016/09/multiplexado-display-siete-segmentos-vhdl.html&quot;&gt;artículo anterior&lt;/a&gt; (implementando la lógica de control con una FPGA). 
  
&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiG5m5Ez6fESmIGwd7kZLM5-T6WYZZNSQxx5Nr30Xs3eWHMcN5LrtJU8FugRAGo76vba1Z46iF5NDXFJl40cmfBNM2CFLVcNdzTD8lEofqYX1j6Ul9ohevvu4qFdqvZG-PMBlIdcHgT-FWmspcuZJKTMB6kuknHkwpddXPeKKhiAK1vWU5z2CgsN7A37rU/s1024/c0a70-img_20190418_112635.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Circuito display 7 segmentos&quot; border=&quot;0&quot; width=&quot;600&quot; data-original-height=&quot;768&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiG5m5Ez6fESmIGwd7kZLM5-T6WYZZNSQxx5Nr30Xs3eWHMcN5LrtJU8FugRAGo76vba1Z46iF5NDXFJl40cmfBNM2CFLVcNdzTD8lEofqYX1j6Ul9ohevvu4qFdqvZG-PMBlIdcHgT-FWmspcuZJKTMB6kuknHkwpddXPeKKhiAK1vWU5z2CgsN7A37rU/s600/c0a70-img_20190418_112635.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;
  
Las bases son las mismas, y si buscas, encontraras por la red miles de tutoriales sobre como gestionar estos &lt;em&gt;display&lt;/em&gt; con Arduino. &lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0WHLB7wUCJRBbRG3GtfkiSeJH_8LrqJMPzmGVdCshCZ_WOWLqiRbIe3Bt7YAoYdUf3PLM3iPmU_3EhnNnEfOmvZQXbLev5vW_xjMCyvi72Q8tg-KymTYoXO3V39N6_zjBdX-6cLrMR3T_0PswAUIJWRqZNxq-zdEoI9rN8eYjU7IVYQYPnBf_ruLk4WQ/s1600/1f6af-7seg_pinouts.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Esquema 7 segmentos&quot; border=&quot;0&quot; data-original-height=&quot;335&quot; data-original-width=&quot;228&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0WHLB7wUCJRBbRG3GtfkiSeJH_8LrqJMPzmGVdCshCZ_WOWLqiRbIe3Bt7YAoYdUf3PLM3iPmU_3EhnNnEfOmvZQXbLev5vW_xjMCyvi72Q8tg-KymTYoXO3V39N6_zjBdX-6cLrMR3T_0PswAUIJWRqZNxq-zdEoI9rN8eYjU7IVYQYPnBf_ruLk4WQ/s1600/1f6af-7seg_pinouts.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;La verdad es que no es difícil, pero le veo un par de inconvenientes. Por un lado, si te fijas en la figura de arriba, para gestionar un display son necesarias ocho conexiones digitales entre el Arduino y el &lt;em&gt;display&lt;/em&gt;. Y eso sólo para un display, si necesitas más es probable que no tengamos suficientes pines para gestionarlos. Otra pega es que tendremos que programar la lógica del &lt;em&gt;display&lt;/em&gt; en el propio Arduino. Es decir, que si queremos mostrar un número, el programa tendrá que evaluar qué LEDs han de encenderse y cuales han de apagarse, lo que requiere tiempo de proceso y un consumo de energía relativamente alto para ser una gestión secundaria (querremos que el microcontrolador se ocupe de tareas menos mundanas que decidir qué LED encender para mostrar, por ejemplo, la temperatura de la sala). Para la gestión de estos &lt;em&gt;displays&lt;/em&gt; hay circuitos integrados especializados que son bastante más eficientes gestionando los LED de lo que lo pueda ser un microcontrolador. Son los llamados decodificadores de siete segmentos. Por ejemplo, el 74LS247 (en este caso he usado un sn74LS247n). Estos decodificadores aceptan un valor en &lt;a href=&quot;https://en.wikipedia.org/wiki/Binary-coded_decimal&quot;&gt;formato BCD&lt;/a&gt; y activan las salidas correspondientes en el &lt;em&gt;display&lt;/em&gt; para mostrar dicho valor.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Lo primero a tener en cuenta es el tipo de &lt;em&gt;display&lt;/em&gt; que vamos a usar. Hay dos tipos, de ánodo común y de cátodo común. Como puedes observar en el esquema que más abajo, la diferencia es que en el primero todos los LED comparten la conexión a Vcc mientras que en el segundo, comparten  la conexión a tierra. Esto es importante porque dependiendo del tipo de display, tendremos que buscar un integrado que sea capaz de gestionarlo. En este caso he usado un display con ánodo común.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeGenZkgAaAyOkOGagsdGjhKHI_PbTN4qnJFP_xtz7KC468XpUI7atsfFudnVFpX_COSmVF6sSK5W7_JwNxY-fOM4MAMZWfv8tr-utM-5QXZH675mvCJG6qbo09RKnZ8yRQjsx18rJs26mlmSREYzE5xLq2uqvZfGt3e-oAaQ995s5wt3nBOms7QzXBO8/s1600/2361a-configuration.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;7 segmentos doble&quot; border=&quot;0&quot; data-original-height=&quot;278&quot; data-original-width=&quot;390&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeGenZkgAaAyOkOGagsdGjhKHI_PbTN4qnJFP_xtz7KC468XpUI7atsfFudnVFpX_COSmVF6sSK5W7_JwNxY-fOM4MAMZWfv8tr-utM-5QXZH675mvCJG6qbo09RKnZ8yRQjsx18rJs26mlmSREYzE5xLq2uqvZfGt3e-oAaQ995s5wt3nBOms7QzXBO8/s1600/2361a-configuration.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;El 74LS247 viene en formato DIP con 16 patillas. Además de la patilla GND y Vcc (5V), disponemos de cuatro pines de entrada (A, B, C y D) que usaremos para indicar el valor que queremos mostrar en el display, en formato BCD. Las salidas (desde a hasta g) se conectan a las patillas correspondientes en el display (tienen el mismo nombre como puedes observar en los esquemas de más arriba). Además disponemos de tres entradas de control: LT (lamp test), RBI (RB input) y RBO (RB outout). Entre otras cosas, son útiles para comprobar el buen estado del display: cuando RBO está a nivel alto y LT está a nivel bajo, se activan todas las salidas, encendiendo todos los LED del &lt;em&gt;display&lt;/em&gt;, lo que nos permite comprobar que todos los LED funcionan correctamente. Tienen más funcionalidades, pero aquí no las vamos a usar por el momento.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicDkaUkZCnJCZNIKKEBNQp4R0Z1wnIKBTE-3eCe896Gvm6n98Jj1iHw9Ia0lVTSgRrFT5qH2S7HEseeYhUmB3WEGDg5OEmv86ElWWIn04Cx2AHR5i8-MXi9w4v5b85OVE7KYZBT8_GubEQ1PRwNS_RVFy9NqhIa3iGZFZmnZ39Dn08SCOP19NBM1pAPZg/s1600/04243-74247-sn74ls247.gif&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Esquema 74LS247&quot; border=&quot;0&quot; data-original-height=&quot;272&quot; data-original-width=&quot;389&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicDkaUkZCnJCZNIKKEBNQp4R0Z1wnIKBTE-3eCe896Gvm6n98Jj1iHw9Ia0lVTSgRrFT5qH2S7HEseeYhUmB3WEGDg5OEmv86ElWWIn04Cx2AHR5i8-MXi9w4v5b85OVE7KYZBT8_GubEQ1PRwNS_RVFy9NqhIa3iGZFZmnZ39Dn08SCOP19NBM1pAPZg/s1600/04243-74247-sn74ls247.gif&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Así pues, hechas las presentaciones, vamos a ver cómo se conectan el &lt;em&gt;display&lt;/em&gt; y el 74LS247.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOFr44zkVMibcwV5DyVfSpf0q7p3EgHa0UeWnlLCYX46EWS5CMSgQySUllmP7iySfUOSW2GNqSqHMW8IWUcQoyBpgPN95G0IMBj-gmwaop_V8mtLFUptYQH-7xxq2cdwIjG7iU7Zy-sqpVxBMBEBsW-UGJYsI7bfDQcgHIZWMNCKpaUmpkLn98dOHw2-I/s1600/4362a-esquema.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Esquema circuito&quot; border=&quot;0&quot; data-original-height=&quot;792&quot; data-original-width=&quot;1186&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOFr44zkVMibcwV5DyVfSpf0q7p3EgHa0UeWnlLCYX46EWS5CMSgQySUllmP7iySfUOSW2GNqSqHMW8IWUcQoyBpgPN95G0IMBj-gmwaop_V8mtLFUptYQH-7xxq2cdwIjG7iU7Zy-sqpVxBMBEBsW-UGJYsI7bfDQcgHIZWMNCKpaUmpkLn98dOHw2-I/s1600/4362a-esquema.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Las entradas A, B, C y D las he conectado al 74LS247 con unos interruptores DIP mediante unas resistencias pull-down (200Ω). Las salidas de la pastilla las he conectado directamente al display mediante resistencias de 200Ω. Sólo tenemos que manipular los cuatro primeros interruptores para poner el valor binario del dígito que queremos mostrar en el display. Os dejo un vídeo para que veáis como funciona. &lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe class=&quot;BLOG_video_class&quot; allowfullscreen=&quot;&quot; youtube-src-id=&quot;ZMjxIMsTcwA&quot; width=&quot;600&quot; height=&quot;498&quot; src=&quot;https://www.youtube.com/embed/ZMjxIMsTcwA&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
</description><link>http://www.ellaberintodefalken.com/2019/06/display-de-7-segmentos-con-74ls247.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiG5m5Ez6fESmIGwd7kZLM5-T6WYZZNSQxx5Nr30Xs3eWHMcN5LrtJU8FugRAGo76vba1Z46iF5NDXFJl40cmfBNM2CFLVcNdzTD8lEofqYX1j6Ul9ohevvu4qFdqvZG-PMBlIdcHgT-FWmspcuZJKTMB6kuknHkwpddXPeKKhiAK1vWU5z2CgsN7A37rU/s72-c/c0a70-img_20190418_112635.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-2844214356939168610</guid><pubDate>Tue, 09 Apr 2019 20:24:18 +0000</pubDate><atom:updated>2026-07-15T11:21:14.167+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">5110</category><category domain="http://www.blogger.com/atom/ns#">Arduino</category><category domain="http://www.blogger.com/atom/ns#">post</category><category domain="http://www.blogger.com/atom/ns#">Proyectos</category><title>Juegos con Arduino y LCD 5110</title><description>&lt;p&gt;¿Recuerdas el mítico Nokia 5110? El teléfono, obviamente, ya no se vende, pero su pantalla sigue siendo muy valorada por los &lt;em&gt;makers&lt;/em&gt; y aficionados a la electrónica. La razón es que, gracias a su interfaz SPI es muy sencilla de manejar desde un microcontrolador. Como hace tiempo que tengo uno dando vueltas por mis cajones me he animado a probarlo, así que os traigo un pequeño montaje realizado con un Arduino Nano, un potenciómetro y, por supuesto, un LCD 5110. Y que mejor que un juego para probar este pequeño LCD.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijAWcOoKTUsz64FPFenysfrugMxpVEiRS3ZUHGIW0XGyaq_d0NJ8kZHNU02AHzOGttKViY3dBczrsMYYiwuut32ReDXfClp5Phd1gMjuC1jFNR7G1yBQPzMIHmn6147U0j7-Ksoab5-J8Yc7AKscJa0Ls2A22fUaXPiekNGlKTcI8Uw6ubrwpx9xI26DI/s1024/88092-race5110.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Juego con LCD 5110&quot; border=&quot;0&quot; width=&quot;600&quot; data-original-height=&quot;768&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijAWcOoKTUsz64FPFenysfrugMxpVEiRS3ZUHGIW0XGyaq_d0NJ8kZHNU02AHzOGttKViY3dBczrsMYYiwuut32ReDXfClp5Phd1gMjuC1jFNR7G1yBQPzMIHmn6147U0j7-Ksoab5-J8Yc7AKscJa0Ls2A22fUaXPiekNGlKTcI8Uw6ubrwpx9xI26DI/s600/88092-race5110.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Las siguientes imágenes muestran la pantalla LCD por delante y también por la parte trasera donde se pueden ver las patillas de conexión. Hay que tener cuidado, ya que he encontrado este mismo LCD con diferente patillaje. El que yo he usado es como &lt;a href=&quot;https://www.sparkfun.com/products/10168&quot;&gt;éste&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHl7WnvRVs4cEBVBs8KT9NU8Z7qgcRZjpBKcRwGQ24jM8YLuFfFpUz24OT1cPProUxARLSs97X5PkyrOcnBs3rAxwV6lHdqzUCEKsOQHgmKsLSWA0Ire38RC2GGbteDteOq9hZNx-_6Ed27tRWoMCgp9aECJJ1u2shsIdmdfAz0pU_Pvhc7-N5Ik6yT2M/s1600/168e4-delantero.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;LCD 5110 delante&quot; border=&quot;0&quot; data-original-height=&quot;683&quot; data-original-width=&quot;512&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHl7WnvRVs4cEBVBs8KT9NU8Z7qgcRZjpBKcRwGQ24jM8YLuFfFpUz24OT1cPProUxARLSs97X5PkyrOcnBs3rAxwV6lHdqzUCEKsOQHgmKsLSWA0Ire38RC2GGbteDteOq9hZNx-_6Ed27tRWoMCgp9aECJJ1u2shsIdmdfAz0pU_Pvhc7-N5Ik6yT2M/s1600/168e4-delantero.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;


&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnGcrIf1NrfV6OqJ7F8aGvUVFyXTkKZkf-TwNiKDK89FLkNyCtlpO3htaVJFSrvZ62yK2BTdmztUPi28AfNyUoLpi2NatahHmdwgutIFMzBspwrDtpzxnpVZdwnJQ8oM6Xu7-Itn5YzmKKCesbT9KP7fI-KK82yMrVeYH85CWx7KzqRvhMxeJPmryY7pw/s1600/47609-trasero.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;LCD 5110 detras&quot; border=&quot;0&quot; data-original-height=&quot;683&quot; data-original-width=&quot;512&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnGcrIf1NrfV6OqJ7F8aGvUVFyXTkKZkf-TwNiKDK89FLkNyCtlpO3htaVJFSrvZ62yK2BTdmztUPi28AfNyUoLpi2NatahHmdwgutIFMzBspwrDtpzxnpVZdwnJQ8oM6Xu7-Itn5YzmKKCesbT9KP7fI-KK82yMrVeYH85CWx7KzqRvhMxeJPmryY7pw/s1600/47609-trasero.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;
  
&lt;p&gt;La pantalla es monocroma con una resolución de 84x48 píxeles. Utiliza el controlador PCD8544 y dispone de LEDs para iluminar la pantalla. Se alimenta con una tensión de 3.3V, y sus los pines digitales también funcionan a 3.3V, por lo que hay que adaptar las señales digitales del Arduino, que funcionan a 5V. Aunque no es lo más óptimo, con unas cuantas resistencias de 10KΩ podemos apañarnos. El esquema completo queda así.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG8GZqarivTAcUGTzWgt4_GBiinL3whu4Jntdn1HXEd3fPJ6YaGRfJlssT8OUntaXFm0Z7BcDnGavtMevwnUm21KYk-8X-ARNTsa1H7FGGD0sG0xhEK4aSm10mxXqsLh3Eq9JqhDvLlDhmmuiUoW844P34BENuLTdVE99PYC0_wKWA0brfiLKMWJtKzsI/s1600/1dc6b-esquema.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Arduino LCD 5110&quot; border=&quot;0&quot; data-original-height=&quot;1000&quot; data-original-width=&quot;1398&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG8GZqarivTAcUGTzWgt4_GBiinL3whu4Jntdn1HXEd3fPJ6YaGRfJlssT8OUntaXFm0Z7BcDnGavtMevwnUm21KYk-8X-ARNTsa1H7FGGD0sG0xhEK4aSm10mxXqsLh3Eq9JqhDvLlDhmmuiUoW844P34BENuLTdVE99PYC0_wKWA0brfiLKMWJtKzsI/s1600/1dc6b-esquema.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Las resistencias son de 10KΩ, excepto la primera por la izquierda que conecta el pin 7 del Arduino, que es de 1KΩ, y la última de la derecha, que conecta el LCD con el pin 9 del Arduino, que es de 330Ω. Finalmente las conexiones entre el 5110 y el Arduino quedan de la siguiente manera.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;       1-VCC       ----------------  3.3V
       2-GND       ----------------  GND
       3-SCE       ----------------  7
       4-RST       ----------------  6
       5-D/C       ----------------  5
       6-DN(MOSI)  ----------------  11
       7-SCLK      ----------------  13
       8-LED       ----------------  9
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;El código completo del juego es el siguiente.&lt;br&gt;&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;#include &amp;lt;Arduino.h&amp;gt;
#include &amp;lt;SPI.h&amp;gt;

/* Comandos PCD8544 */
#define LCD_COMMAND  0
#define LCD_DATA     1

/* LCD 84x48 */
#define LCD_WIDTH   84 
#define LCD_HEIGHT  48 
#define WHITE       0  
#define BLACK       1  

/* Conexionado de pines */
const int scePin = 7;   // SCE - Chip select, pin 3 en LCD.
const int rstPin = 6;   // RST - Reset, pin 4 en LCD.
const int dcPin = 5;    // DC - Data/Command, pin 5 en LCD.
const int sdinPin = 11;  // DN(MOSI) - Serial data, pin 6 en LCD.
const int sclkPin = 13;  // SCLK - Serial clock, pin 7 en LCD.
const int blPin = 9;    // LED - Backlight LED, pin 8 en LCD.
const int analog_in = A0; // entrada analogica potenciometro

/* frame buffer para el display */
byte displayMap&amp;#091;LCD_WIDTH * LCD_HEIGHT / 8];

/* Envio de datos al LCD
 *  El primer parámetro indica si estamos enviando un comando o un dato
 *  El segundo parámetro es el comando o el dato
 */
void LCDWrite(byte data_or_command, byte data)
{
  digitalWrite(dcPin, data_or_command);
  digitalWrite(scePin, LOW);
  SPI.transfer(data); 
  digitalWrite(scePin, HIGH);
}

/* establece fila columna
 *  x - columna
 *  y - fila
 */
void gotoXY(int x, int y)
{
  LCDWrite(0, 0x80 | x);
  LCDWrite(0, 0x40 | y);
}

/* Cambia el estado de un pixel en el framebuffer
 *  x, y - posicion
 *  bw - BLACK o WHITE
 */
void setPixel(int x, int y, boolean bw)
{
  if ((x &amp;gt;= 0) &amp;amp;&amp;amp; (x &amp;lt; LCD_WIDTH) &amp;amp;&amp;amp; (y &amp;gt;= 0) &amp;amp;&amp;amp; (y &amp;lt; LCD_HEIGHT))
  {
    byte shift = y % 8;
    if (bw) 
      displayMap&amp;#091;x + (y/8)*LCD_WIDTH] |= 1&amp;lt;&amp;lt;shift;
    else   
      displayMap&amp;#091;x + (y/8)*LCD_WIDTH] &amp;amp;= ~(1&amp;lt;&amp;lt;shift);
  }
}

/* Dibuja una línea de x0,y0 a x1, y1
 * bw - BLACK o WHITE
 */
void setLine(int x0, int y0, int x1, int y1, boolean bw)
{
  int dy = y1 - y0; 
  int dx = x1 - x0; 
  int stepx, stepy;
  if (dy &amp;lt; 0)
  {
    dy = -dy;
    stepy = -1;
  }
  else
    stepy = 1;
    if (dx &amp;lt; 0)
  {
    dx = -dx;
    stepx = -1;
  }
  else
    stepx = 1;
    dy &amp;lt;&amp;lt;= 1; // dy = 2*dy
  	dx &amp;lt;&amp;lt;= 1; // dx = 2*dx
  	setPixel(x0, y0, bw); 
  	if (dx &amp;gt; dy)
  		{
    	int fraction = dy - (dx &amp;gt;&amp;gt; 1);
    	while (x0 != x1)
    	{
      		if (fraction &amp;gt;= 0)
      		{
        	y0 += stepy;
        	fraction -= dx;
      		}
      	x0 += stepx;
      	fraction += dy;
      	setPixel(x0, y0, bw);
    	}
  }
  else
  {
    int fraction = dx - (dy &amp;gt;&amp;gt; 1);
    while (y0 != y1)
    {
      if (fraction &amp;gt;= 0)
      {
        x0 += stepx;
        fraction -= dy;
      }
      y0 += stepy;
      fraction += dx;
      setPixel(x0, y0, bw);
    }
  }
}

/* dibuja un rectángulo
 *  x0, y0 - esquina superior izquierda
 *  x1, y1 - esquina inferior derecha
 *  fill - 1: relleno, 0: sólo perímetro
 *  bw - BLACK o WHITE
 */
void setRect(int x0, int y0, int x1, int y1, boolean fill, boolean bw)
{
  if (fill == 1)
  {
    int xDiff;
    if(x0 &amp;gt; x1)
      xDiff = x0 - x1;
    else
      xDiff = x1 - x0;
      while(xDiff &amp;gt; 0)
    	{
      	setLine(x0, y0, x0, y1, bw);ç
      	if(x0 &amp;gt; x1)
        	x0--;
      	else
        	x0++;
            xDiff--;
    }
  }
  else
  {
    setLine(x0, y0, x1, y0, bw);
    setLine(x0, y1, x1, y1, bw);
    setLine(x0, y0, x0, y1, bw);
    setLine(x1, y0, x1, y1, bw);
  }
}

/* 
 *  vuelca el contenido del framebuffer a la memoria del LCD.
 */
void updateDisplay()
{
  gotoXY(0, 0);
  for (int i=0; i &amp;lt; (LCD_WIDTH * LCD_HEIGHT / 8); i++)
  {
    LCDWrite(LCD_DATA, displayMap&amp;#091;i]);
  }
}

/* establece el contraste del LCD
 *  normalmente entre 40 y 60
 */
void setContrast(byte contrast)
{
  LCDWrite(LCD_COMMAND, 0x21); 
  LCDWrite(LCD_COMMAND, 0x80 | contrast); 
  LCDWrite(LCD_COMMAND, 0x20); 
}

//Envía comandos mágicos al PCD8544
/* 
 *  Inicializar el LCD 5110
 */
void lcdBegin(void)
{
  pinMode(scePin, OUTPUT);
  pinMode(rstPin, OUTPUT);
  pinMode(dcPin, OUTPUT);
  pinMode(sdinPin, OUTPUT);
  pinMode(sclkPin, OUTPUT);
  pinMode(blPin, OUTPUT);
  analogWrite(blPin, 255);
  SPI.begin();
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(MSBFIRST);
  digitalWrite(rstPin, LOW);
  digitalWrite(rstPin, HIGH);
  LCDWrite(LCD_COMMAND, 0x21);
  LCDWrite(LCD_COMMAND, 0xB0);
  LCDWrite(LCD_COMMAND, 0x04);
  LCDWrite(LCD_COMMAND, 0x14);
  LCDWrite(LCD_COMMAND, 0x20);
  LCDWrite(LCD_COMMAND, 0x0C);
}

/* borrar pantalla
 *  bw - BLACK o WHITE
 */
void clearDisplay(boolean bw)
{
  for (int i=0; i&amp;lt;(LCD_WIDTH * LCD_HEIGHT / 8); i++)
  {
    if (bw)
      displayMap&amp;#091;i] = 0xFF;
    else
      displayMap&amp;#091;i] = 0;
  }
}

int val, pos, bloq_size = 5;
int way_lenght=5;
int way_pos = (LCD_WIDTH/2)-(way_lenght*bloq_size); // posición inicial
int way_dir;
int way&amp;#091;10] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; // tramos de carretera
int i;

void setup() {
  lcdBegin();
  setContrast(40);
}

void loop() {
  val = analogRead(analog_in); // leer potenciometro
  pos = map(val, 0, 780, LCD_WIDTH-bloq_size, 0);
  way_dir = random(-1,2);
  way_pos = way_pos + (way_dir*bloq_size); // nueva posioción de la carretera
  /* evitar salirnos de la pantalla */
  if (way_pos&amp;lt;0) way_pos = 0;
  if (way_pos&amp;gt;LCD_WIDTH-(bloq_size*way_lenght)) way_pos = LCD_WIDTH-(bloq_size*way_lenght); 
  /* scroll de la carretera */
  for (i=9; i&amp;gt;=1; i--) {
    way&amp;#091;i] = way&amp;#091;i-1];
  }
  way&amp;#091;0] = way_pos;
  
  /* dibujar pantalla */
  clearDisplay(WHITE);
  
  /* dibujar coche */
  setRect(pos, bloq_size*8, pos+bloq_size, bloq_size*8-10+bloq_size, 1, BLACK);
  
  /* dibujar carretera */
  for (i=0; i&amp;lt;=9; i++) {
    if (way&amp;#091;i] &amp;gt; 0) {
      setRect(way&amp;#091;i], i*bloq_size, way&amp;#091;i]+bloq_size, (i*bloq_size)+bloq_size, 0, BLACK);
      setRect(way&amp;#091;i]+(way_lenght*bloq_size), i*bloq_size, way&amp;#091;i]+(way_lenght*bloq_size)+bloq_size, (i*bloq_size)+bloq_size, 0, BLACK);
    }
  }
  
  /* comprobar colisión */
  if ((pos&amp;lt;way&amp;#091;7]+bloq_size) || (pos+bloq_size&amp;gt;way&amp;#091;7]+bloq_size*way_lenght) &amp;amp;&amp;amp; way&amp;#091;7]&amp;gt;0) {
    // hay colision
    setRect(pos, bloq_size*8, pos+bloq_size, bloq_size*8-10+bloq_size, 1, WHITE);
    setRect(pos, bloq_size*8, pos+bloq_size, bloq_size*8-10+bloq_size, 0, BLACK);  
    updateDisplay();
    delay(5000);
    // reiniciar el juego
    for (i=0; i&amp;lt;=9; i++) {
      way&amp;#091;i] = -1;
    }
  }
  
  updateDisplay();
  delay(500);
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Para las funciones de control del LCD me he basado en un &lt;a href=&quot;https://github.com/sparkfun/GraphicLCD_Nokia_5110/&quot;&gt;código de Sparkfun&lt;/a&gt;. Las operaciones de dibujado se realizan sobre una zona de memoria del Arduino (&lt;em&gt;frame buffer&lt;/em&gt;). En este caso esa zona de memoria se almacena en la variable &lt;em&gt;displayMap&lt;/em&gt;, que es un array de representa el contenido de la pantalla. Cuando deseamos volcar el contenido del &lt;em&gt;frame buffer&lt;/em&gt; en la pantalla física, usamos la función &lt;em&gt;LCDWrite&lt;/em&gt;. Esta función hace uso del bus SPI para enviar comandos y el contenido del &lt;em&gt;frame buffer&lt;/em&gt; al 5110. El resto de funciones son bastante auto explicativas y tienen un pequeño encabezado en su cabecera.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe class=&quot;BLOG_video_class&quot; allowfullscreen=&quot;&quot; youtube-src-id=&quot;kxjJWI4KkOk&quot; width=&quot;600&quot; height=&quot;498&quot; src=&quot;https://www.youtube.com/embed/kxjJWI4KkOk&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
</description><link>http://www.ellaberintodefalken.com/2019/04/arduino-lcd-5110.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijAWcOoKTUsz64FPFenysfrugMxpVEiRS3ZUHGIW0XGyaq_d0NJ8kZHNU02AHzOGttKViY3dBczrsMYYiwuut32ReDXfClp5Phd1gMjuC1jFNR7G1yBQPzMIHmn6147U0j7-Ksoab5-J8Yc7AKscJa0Ls2A22fUaXPiekNGlKTcI8Uw6ubrwpx9xI26DI/s72-c/88092-race5110.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-3430502064031138195</guid><pubDate>Sun, 31 Mar 2019 20:50:40 +0000</pubDate><atom:updated>2026-06-22T10:27:50.521+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Arduino</category><category domain="http://www.blogger.com/atom/ns#">interrupciones</category><category domain="http://www.blogger.com/atom/ns#">post</category><title>Gestión de interrupciones con Arduino</title><description>&lt;p&gt;
&lt;p&gt;&lt;br&gt;Cuando se diseña un programa, lo habitual es pensarlo como un flujo de instrucciones que se ejecutan una detrás de otra siguiendo una ejecución secuencial. En el contexto de los microprocesadores y los microcontroladores, si estuviéramos limitados sólo a este modelo secuencial, el desarrollo de aplicaciones sería más complicado. Os pongo un ejemplo muy simplificado: si  el  sistema que estamos diseñando tiene que atender una serie de actuadores (por ejemplo un botón), tendremos que andar sondeándolos periódicamente para ver si se pulsó el botón. ¿Pero qué ocurre si se pulsa un botón mientras el programa anda ocupado en otra tarea y justo en ese momento no se está sondeando la entrada del actuador? En este caso el sistema no será capaz de detectar la pulsación. La solución en este caso es el uso de interrupciones. Cuándo se dispara una interrupción, el procesador detiene la ejecución del programa que está corriendo, almacena el estado de los registros (incluido el contador de programa), y ejecuta un subprograma, llamado rutina de servicio de interrupción, que atiende la interrupción (en este caso la gestión de la pulsación del botón). Una vez finalizada la rutina, el microprocesador restaura los registros y continúa la ejecución del programa principal por el mismo lugar en el que fue interrumpido. Vamos a ver cómo podemos crear una rutina de interrupción en Arduino y hacer que se ejecute tras la pulsación del botón.&lt;/p&gt;


&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsLqdz6e1hkychXX900HFNrstQc-lI1lFWNPfiMiV5t12lNdNGN54-haEApFPnyGomlrqf58SDw0zaRL8WbGrboqqEM315rHKUpB0ubovaTmUNbiz6x-qBxUq5r_m11okom35GwJRY8Y7WIGH2Mn_AEpq3Y7-g_a2h4lllnzEjzrljxgSVh7vBcrO_hXM/s1024/bbbce-arduinointerrupt.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Interruciones Arduino&quot; border=&quot;0&quot; width=&quot;600&quot; data-original-height=&quot;768&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsLqdz6e1hkychXX900HFNrstQc-lI1lFWNPfiMiV5t12lNdNGN54-haEApFPnyGomlrqf58SDw0zaRL8WbGrboqqEM315rHKUpB0ubovaTmUNbiz6x-qBxUq5r_m11okom35GwJRY8Y7WIGH2Mn_AEpq3Y7-g_a2h4lllnzEjzrljxgSVh7vBcrO_hXM/s600/bbbce-arduinointerrupt.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;La siguiente figura muestra el esquema del circuito que vamos a usar. Como se puede observar, el circuito no puede ser más simple. Se trata de un botón conectado al puerto del pin  2 del Arduino y un LED conectado al puerto del pin 13 de Arduino.&lt;/p&gt;


&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWd1zHFRU2aEOjL5leXt5RfgIvn3XVfybmZnk-dDq6BnHtUfFMqvzxNT-KGGPt_SBzx1I-ilrfLno6uQXf9GoBM3xda-WwOdMtLEeTeqTBkdmW05MkwwaF8jyRHoD980NZnhK2MEmFAoUvD3PPIJREoxHlKcnzUao_ihUhG_EXc_CDpvJR4SBowatwX3o/s1600/6839c-circuitoint.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Circuito interrupcion Arduino&quot; border=&quot;0&quot; data-original-height=&quot;861&quot; data-original-width=&quot;1011&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWd1zHFRU2aEOjL5leXt5RfgIvn3XVfybmZnk-dDq6BnHtUfFMqvzxNT-KGGPt_SBzx1I-ilrfLno6uQXf9GoBM3xda-WwOdMtLEeTeqTBkdmW05MkwwaF8jyRHoD980NZnhK2MEmFAoUvD3PPIJREoxHlKcnzUao_ihUhG_EXc_CDpvJR4SBowatwX3o/s1600/6839c-circuitoint.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;


&lt;p&gt;El objetivo es hacer un programa en Arduino que encienda el LED cuando se pulse el botón. El siguiente código realiza esta acción sin usar interrupciones, es decir, a la manera clásica.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;pre&gt;&lt;code&gt;const int pin = 2;
const int pout = 13;
int estadoBoton;
 
void setup() {
  pinMode(pin, INPUT);
  pinMode(pout, OUTPUT);
}
void loop() {
  estadoBoton = digitalRead(pin);
  if (estadoBoton == HIGH) {
    digitalWrite(pout, HIGH);
  } else {
    digitalWrite(pout, LOW);
  }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;El código es bastante simple: se configura el puerto 2 como entrada (aquí se conecta el botón) y el 13 como salida (aquí se conecta el LED). Dentro del bucle &lt;em&gt;loop&lt;/em&gt; leemos constantemente el valor del puerto al que está conectado el botón y si está pulsado encendemos el LED. En caso contrario se apaga. Vamos a hacer algunos cambios para que el programa realice la misma función, pero usando interrupciones.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;const int pin = 2;
const int pout = 13;
int estadoBoton;
 
void setup() {
  pinMode(pin, INPUT);
  pinMode(pout, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(pin), enciendeLED, CHANGE);
}
 
void loop() {
  // nada que hacer aquí. circulen.
}
 
void enciendeLED() {
  estadoBoton = digitalRead(pin);
  if (estadoBoton == HIGH) {
    digitalWrite(pout, HIGH);
  } else {
    digitalWrite(pout, LOW);
  }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Lo primero que nos llama la atención es que en el bucle principal (función &lt;em&gt;loop&lt;/em&gt;) no hay código. Centrémonos en la siguiente línea de código.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;pre&gt;&lt;code&gt;attachInterrupt(digitalPinToInterrupt(pin), enciendeLED, CHANGE);&lt;/code&gt;&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;La función &lt;em&gt;attachInterrupt()&lt;/em&gt; es la encargada de asociar un evento de interrupción con una rutina de servicio de interrupción. El primer parámetro es la interrupción que vamos a tratar. Con &lt;em&gt;digitalPinToInterrupt&lt;/em&gt; convertimos una entrada (un pin) en una interrupción. En este caso estamos creando una interrupción asociada a la pulsación del botón que está en el pin 2. El segundo parámetro permite indicar cuál es la rutina de servicio de interrupción, o lo que es lo mismo, la función que se invoca cuando se dispara dicha interrupción. Por último, podemos indicar cuándo se activa la interrupción. Las opciones son:&lt;/p&gt;
&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;CHANGE: La interrupción se activa cuando cambia el estado del pin.&lt;/li&gt;&lt;li&gt;LOW: La interrupción se activa cuando el pin está a nivel bajo.&lt;/li&gt;&lt;li&gt;RISING: La interrupción se activa cuando el pin pasa de nivel bajo a alto.&lt;/li&gt;&lt;li&gt;FALLING: La interrupción se activa cuando el pin pasa de nivel alto a bajo.&lt;/li&gt;&lt;li&gt;HIGH: La interrupción se activa cuando el pin está en nivel alto. Sólo funciona en Arduino Due, Zero y MKR1000.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Si pruebas este código observarás que el LED se enciende tal y como esperábamos. Sin embargo, si pruebas a conectar el botón a otro pin, digamos el A0, comprobarás que, aunque modifiques el código para usar este pin, el código no funciona ¿por qué?&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Básicamente porque aunque el microcontrolador soporta interrupciones en todos sus pines de entrada y salida, la plataforma Arduino sólo lo implementa en algunos de ellos. En concreto en los siguientes pines según el modelo de Arduino:&lt;/p&gt;
&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Uno, Nano y Mini : pines 2 y 3&lt;/li&gt;&lt;li&gt;Mega (todos los modelos): 2, 3, 18, 19, 20, 21&lt;/li&gt;&lt;li&gt;Micro, Leonardo: 0, 1, 2, 3, 7&lt;/li&gt;&lt;li&gt;Zero: todos los pines excepto el 4&lt;/li&gt;&lt;li&gt;MKR1000: 0, 1, 4, 5, 6, 7, 8, 9, A1, A2&lt;/li&gt;&lt;li&gt;Due: todos&lt;/li&gt;&lt;/ul&gt;
&lt;/p&gt;
&lt;p&gt;¿Quiere eso decir que sólo podemos gestionar dos botones en un Arduino Uno? Para nada. Tenemos dos opciones. La primera es acceder a los registros del microcontrolador a bajo nivel, lo cuál puede llegar a ser algo enrevesado según lo que queramos hacer. La segunda opción es usar una librería externa. Hay varias, en este caso vamos a usar &lt;a href=&quot;https://github.com/NicoHood/PinChangeInterrupt&quot;&gt;PinChangeInterrupt&lt;/a&gt;, que podemos instalar fácilmente desde el propio entorno de Arduino. Para ello accedemos a &lt;em&gt;Programa &amp;gt; Incluir librería &amp;gt; Gestionar librería.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTweRHGusbMEp7iGHXZmaUCYYLSA6QzF9371m44fxhDUMn840yM9-bX2d-YfaC7OYJbHezVUij3zADvhwgwF8wtukky6pwL4DJZhYzsR4WhBnfF2oEdxnhA0E8yibDWqClNW_Ns9mL8D6Xsp6RpTEP8aIxQguT-fUCbgW2YWVbpuxp6Oq8EiKKLXBlUg0/s1600/43d69-captura1.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Gestionar librerias&quot; border=&quot;0&quot; data-original-height=&quot;230&quot; data-original-width=&quot;672&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTweRHGusbMEp7iGHXZmaUCYYLSA6QzF9371m44fxhDUMn840yM9-bX2d-YfaC7OYJbHezVUij3zADvhwgwF8wtukky6pwL4DJZhYzsR4WhBnfF2oEdxnhA0E8yibDWqClNW_Ns9mL8D6Xsp6RpTEP8aIxQguT-fUCbgW2YWVbpuxp6Oq8EiKKLXBlUg0/s1600/43d69-captura1.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;


&lt;p&gt;Buscamos &lt;em&gt;pinchangeinterrupt&lt;/em&gt; y pulsamos el botón instalar.&lt;/p&gt;


&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoQiqJ0x-fnf8CHCMULClXj9irqH3QQ6vmMmboTlLbh6ipQK7QyXaHfnLw5G1uePxXWsBzYVHOFpIBSp9Zfn2o9y5ISNzEpkshk2Z_X5W5GI647RL766rWtJRYPQSar4UfutkuQrb8bQH0RA1CV1UyykzvmCwCLCZBJYwufVUthvquQrSEAb2E42CeuDk/s1600/3ceef-captura2.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;450&quot; data-original-width=&quot;800&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoQiqJ0x-fnf8CHCMULClXj9irqH3QQ6vmMmboTlLbh6ipQK7QyXaHfnLw5G1uePxXWsBzYVHOFpIBSp9Zfn2o9y5ISNzEpkshk2Z_X5W5GI647RL766rWtJRYPQSar4UfutkuQrb8bQH0RA1CV1UyykzvmCwCLCZBJYwufVUthvquQrSEAb2E42CeuDk/s1600/3ceef-captura2.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;


&lt;p&gt;Una vez instalada ya podemos usar interrupciones en cualquier pin usando la librería. El siguiente código hace los mismo que en anterior pero usando el pin A0.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;PinChangeInterrupt.h&amp;gt;
const int pin = A0;
const int pout = 13;
int estadoBoton;
 
void setup() {
  pinMode(pin, INPUT);
  pinMode(pout, OUTPUT);
  attachPCINT(digitalPinToPCINT(pin), enciendeLED, CHANGE);
}
 
void loop() {
  // nada que hacer aquí. circulen.
}
 
void enciendeLED() {
  estadoBoton = digitalRead(pin);
  if (estadoBoton == HIGH) {
    digitalWrite(pout, HIGH);
  } else {
    digitalWrite(pout, LOW);
  }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;El código es casi idéntico salvo por la primera línea, que incluye las cabeceras de la librería. Y también la línea siguiente, que configura la interrupción.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;pre&gt;&lt;code&gt;attachPCINT(digitalPinToPCINT(pin), enciendeLED, CHANGE);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;El formato de la función &lt;em&gt;attachPCINT&lt;/em&gt; es igual a la de la función &lt;em&gt;attachInterrupt&lt;/em&gt; que ya hemos visto más arriba. En el vídeo siguiente podemos ver el código anterior funcionando usando el pin A0.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe class=&quot;BLOG_video_class&quot; allowfullscreen=&quot;&quot; youtube-src-id=&quot;fCye-c-_2Ys&quot; width=&quot;600&quot; height=&quot;498&quot; src=&quot;https://www.youtube.com/embed/fCye-c-_2Ys&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
</description><link>http://www.ellaberintodefalken.com/2019/03/gestion-de-interrupciones-con-arduino.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsLqdz6e1hkychXX900HFNrstQc-lI1lFWNPfiMiV5t12lNdNGN54-haEApFPnyGomlrqf58SDw0zaRL8WbGrboqqEM315rHKUpB0ubovaTmUNbiz6x-qBxUq5r_m11okom35GwJRY8Y7WIGH2Mn_AEpq3Y7-g_a2h4lllnzEjzrljxgSVh7vBcrO_hXM/s72-c/bbbce-arduinointerrupt.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-2716291322692703478</guid><pubDate>Tue, 26 Mar 2019 21:14:00 +0000</pubDate><atom:updated>2019-03-27T10:52:30.765+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">descenso de gradiente</category><category domain="http://www.blogger.com/atom/ns#">gradient descent</category><category domain="http://www.blogger.com/atom/ns#">linear regression</category><category domain="http://www.blogger.com/atom/ns#">machine learning</category><category domain="http://www.blogger.com/atom/ns#">Python</category><category domain="http://www.blogger.com/atom/ns#">regresión lineal</category><title>Regresión lineal y descenso de gradiente con Python</title><description>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg50-r7rBf6_QFvl9kTbzcM6n6R6rqCc_UxjRfycuflIkJ5E4EzJudYCBjoEGM1gZukaIl4MU0aqAgBJmKaKhCEab1MjqeAz3mtY6IoKnDMR61mLswpq79TEg4ObYYgDmrE6TuzLaQ4EaE/s1600/casa.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg50-r7rBf6_QFvl9kTbzcM6n6R6rqCc_UxjRfycuflIkJ5E4EzJudYCBjoEGM1gZukaIl4MU0aqAgBJmKaKhCEab1MjqeAz3mtY6IoKnDMR61mLswpq79TEg4ObYYgDmrE6TuzLaQ4EaE/s640/casa.jpg&quot; width=&quot;640&quot; height=&quot;427&quot; data-original-width=&quot;960&quot; data-original-height=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
En machine learning, el objetivo principal es encontrar un modelo que explique el comportamiento de un sistema (en el amplio sentido de la palabra). A partir de unos datos de entrenamiento, un sistema de aprendizaje automático ha de ser capaz de inferir un modelo capaz de explicar, al menos en su mayoría, los efectos observados. Pero también aplicar ese aprendizaje. Por ejemplo, un sistema de machine learning muy lucrativo para las empresas anunciantes es aquél que dado un perfil de usuario (datos de entrada A), sea capaz de predecir si pinchará o no (salida B) sobre un anuncio publicitario de, por ejemplo, comida para gatos. No es sencillo crear un modelo capaz de predecir el comportamiento del usuario (o sí), pero en todo caso, existen diferentes técnicas que nos permiten abordar el problema. En el caso del ejemplo que acabamos de ver, el modelo debería ser capaz de clasificar a los usuarios en dos clases diferentes, los que pulsarán y los que no pulsarán el anuncio de comida de gatos. Otro tipo de problema es aquél en el que se pretende crear un modelo capaz de predecir un valor o una cantidad. Por ejemplo, si conozco los metros cuadrados, el número de habitaciones y el número de baños de una vivienda en venta, ¿seré capaz de predecir por qué precio se venderá? Uno podría hacerse una idea observando el precio de venta de alguna casa igual o parecida que se haya vendido antes. Si no tenemos ese dato, podremos extrapolar el precio comparando el precio de venta de otras casas y comparando esa casa con la nuestra. Intuitivamente sabemos que a más metros, más precio ¿pero cuanto más? Es probable que entre los metros de la casa y el precio haya una relación lineal, así que aprovechándonos de ello, es relativamente sencillo construir un modelo (lineal, claro) que nos ayude a predecir el precio de una casa. Así que manos a la obra.&lt;br /&gt;
Vamos a basarnos en una técnica estadística llamada regresión lineal, que conceptualmente es bastante sencilla, pero es muy usada en este tipo de problemas. Antes de seguir vamos a generar un conjunto de datos ficticios con los que empezar a trabajar.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import random
import matplotlib.pyplot as plt

def gen_data(n, bias, varianza):
    x = []
    y = []
    for i in range(0, n):
        x.append(i)
        y.append((i + bias) + random.uniform(0, 1) * varianza)
    return x, y

x,y = gen_data(100, 25, 50)
plt.scatter(x, y)
&lt;/pre&gt;&lt;br /&gt;
Este código genera una serie de datos que sigue básicamente una recta alrededor de la cual se crean los puntos de forma aleatoria usando una función de distribución uniforme con una varianza dada (50 en este caso). Echemos un vistazo a como quedan los datos.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHf21LX96eF4YQZdLVQWZ772_7sa0QlKa4U8lVDL_nXxbvHVGLRxbZcEzpezrGkcyM_ZWAGSbIvi4sltfTH40b7QNUZXnNfJhD8VOGu5eY5yty9UF7-lgbudpVtMrHlcOnTcTi7eQkG5Y/s1600/datos.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHf21LX96eF4YQZdLVQWZ772_7sa0QlKa4U8lVDL_nXxbvHVGLRxbZcEzpezrGkcyM_ZWAGSbIvi4sltfTH40b7QNUZXnNfJhD8VOGu5eY5yty9UF7-lgbudpVtMrHlcOnTcTi7eQkG5Y/s1600/datos.png&quot; data-original-width=&quot;378&quot; data-original-height=&quot;252&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Por seguir con nuestro ejemplo de las casas puede imaginar que en el eje de abscisas se representan los metros cuadrados que tiene la casa y en el las ordenadas el precio en miles de euros por el que se vendió la casa.&lt;br /&gt;
La idea es, a partir de todos estos datos, crear un modelo que sea capaz de predecir el precio de cualquier casa dados sus metros cuadrados. En este caso es muy obvio que un buen predictor es una línea recta (por eso lo de lineal) que se ajuste lo más posible a la nube de puntos. Así pues, nuestro problema ahora es encontrar esa recta, la de mejor ajuste.&lt;br /&gt;
La ecuación de una recta en el plano es la siguiente.&lt;br /&gt;
\[y=a+bx\]&lt;br /&gt;
Donde a se corresponde con el punto de corte de la recta con el eje de ordenadas y b es la pendiente de la recta, es decir, lo rápido que crece o decrece. La variable independiente x representaría en este caso los metros cuadrados de la casa y la variable y el precio de venta. Por lo tanto, si somos capaces de buscar unos buenos valores para a y b, tendremos un modelo lineal para predecir el precio.&lt;br /&gt;
Podemos hacerlo de diferentes formas. Típicamente, la recta de regresión se ha calculado a partir de métodos estadísticos, pero (siempre hay un pero) si el número de casas que tenemos es muy grande (no lo llaman big data por gusto) o en vez de una sola variable independiente tenemos varias (metros, número de habitaciones, número de baños, metros de jardín, etc.) puede llegar a ser tedioso cuando no inabordable. Así pues vamos a usar un método para ajustar la recta llamado descenso de gradiente (&lt;i&gt;gradient descent&lt;/i&gt; en la lengua de Shakespeare). Pero vayamos por partes. Lo primero que necesitamos es una forma de medir lo bien o mal que se ajusta una recta a la nube de puntos.&lt;br /&gt;
A cada par de valores que probemos para a y b lo vamos a llamar hipótesis, y seguidamente comprobaremos cómo se ajusta esa hipótesis calculando un factor de error, que es la suma de lo que, de media, se equivoca dicha hipótesis (recta) al predecir cada uno de los puntos de la nube. Este error se calcula con.&lt;br /&gt;
\[e=\frac{1}{2m}\sum_{i=1}^{m}(h(x^{i})-y^{i})^{2}\]&lt;br /&gt;
Donde \(h(x^{i})\) es la hipótesis que queremos probar. Por ejemplo, si asignamos los valores \(a=1\) y \(b=2\), tenemos que \(h(x^{i})=1+2x^{i}\) siendo \(x^{i}\) cada uno de los valores de x para los metros cuadrados de las casas e \(y^{i}\) los precios de dicha casa. Como se puede observar, lo que estamos haciendo es restar al precio de la casa que predice nuestra hipótesis (recta) el valor de venta real. Esa diferencia es el error, que se va sumando para todas las casas de las que disponemos de datos. El resultado de la resta (error) se eleva al cuadrado para evitar que unos errores se compensen con otros, de forma que el error siempre sea positivo. Resumiendo, lo que buscamos son los valores de a y b (nuestra hipótesis) que minimice el error devuelto por esta función. Ahí es donde entra en juego nuestro algoritmo del descenso de gradiente.&lt;br /&gt;
El algoritmo de descenso de gradiente hace uso de las derivadas (sí, eso que en el instituto pensaste que jamás te serviría para nada) y más concretamente de las derivadas parciales (sí, eso que en la universidad pensaste que jamás te serviría para nada). La función de error que acabamos de ver es una función convexa (no voy a demostrarlo aquí), lo que es muy interesante, ya que sólo tiene un valor mínimo que es global a toda la función. Puedes imaginarla con una forma similar a esta.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl7g7nj60H2gXiG07FcKVG_Izguu6orPPkIEvWKx6Q20rcS9d3SCAhpvLleAJMMYdUfO199r6axwI3QmBQQdmsgEDjNbUJnfTV2-tGm69vw8J8RoAWG6gI1Vhw3akH6452GvMP9OM7pAY/s1600/maxresdefault.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl7g7nj60H2gXiG07FcKVG_Izguu6orPPkIEvWKx6Q20rcS9d3SCAhpvLleAJMMYdUfO199r6axwI3QmBQQdmsgEDjNbUJnfTV2-tGm69vw8J8RoAWG6gI1Vhw3akH6452GvMP9OM7pAY/s640/maxresdefault.jpg&quot; width=&quot;640&quot; height=&quot;360&quot; data-original-width=&quot;1280&quot; data-original-height=&quot;720&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Imagina que el punto A es nuestra hipótesis inicial, y como queremos encontrar el valor de a y b que minimicen la función de error, lo que se busca es llegar al punto B. Conceptualmente, la derivada nos indica la velocidad a la que crece o decrece una función en uno de sus puntos. Por lo tanto, lo que debemos hacer es ir moviendo el punto A en la dirección de mayor pendiente para llegar a B. Como hay dos variables en juego, a y b, debemos calcular las derivadas parciales de la función de error con respecto a las dos variables.&lt;br /&gt;
\[a=a-\alpha \frac{\partial }{\partial a}e(a,b)\]&lt;br /&gt;
\[b=b-\alpha \frac{\partial }{\partial b}e(a,b)\]&lt;br /&gt;
El parámetro \(\alpha\) es lo que se llama &lt;i&gt;learning rate&lt;/i&gt;, y permite controlar la velocidad de descenso hacia el punto mínimo de la función. Un valor bajo hará que la convergencia sea lenta, mientras que un valor alto puede hacer que nos pasemos de frenada y acabemos dando santos de un lado a otro de la función sin alcanzar nunca el mínimo. Valores típicos son 0.1, 0.001, etc.&lt;br /&gt;
Si resolvemos las derivadas parciales obtenemos lo siguiente.&lt;br /&gt;
\[a=a-\alpha \frac{1}{m}\sum_{i=1}^{m}(h(x^{i})-y^{i})\]&lt;br /&gt;
\[b=b-\alpha \frac{1}{m}\sum_{i=1}^{m}((h(x^{i})-y^{i})x^{i})\]&lt;br /&gt;
En teoría, cada vez que recalculamos estas dos ecuaciones, estaremos más y más cerca del mínimo de la función de error, y por lo tanto, nuestra recta se ajustará mejor a la nube de puntos.&lt;br /&gt;
He hecho una implementación en Python del algoritmo tal y como os lo he contado, para que sea más claro. Sin embargo, este tipo de implementación no es muy eficiente. Normalmente se usa cálculo matricial, pero me ha parecido más pedagógico aplicar directamente las ecuaciones que acabamos de ver. Empecemos con la función para calcular el error (también llamada función de coste o &lt;i&gt;loss function&lt;/i&gt;).&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;def coste(x, y, a, b):
    m = len(x)
    error = 0.0
    for i in range(m):
        hipotesis = a+b*x[i]
        error +=  (y[i] - hipotesis) ** 2
    return error / (2*m)
&lt;/pre&gt;La función obtiene como parámetros una lista de valores x (metros cuadrados de las casas) e y (precio de las casas), así como los valores a y b para la hipótesis. A partir de ahí simplemente se calcula la ecuación de error que hemos visto más arriba y se devuelve el resultado.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;def descenso_gradiente(x, y, a, b, alpha, epochs):
    m = len(x)
    hist_coste = []
    for ep in range(epochs):
        b_deriv = 0
        a_deriv = 0
        for i in range(m):
            hipotesis = a+b*x[i]
            a_deriv += hipotesis - y[i]
            b_deriv += (hipotesis - y[i]) * x[i]
            hist_coste.append(coste(x, y, a, b))
        a -= (a_deriv / m) * alpha
        b -= (b_deriv / m) * alpha
        
    return a, b, hist_coste
&lt;/pre&gt;La función de descenso de gradiente, además de los parámetros de la función de coste, recibe alpha (&lt;i&gt;learning rate&lt;/i&gt;) y el número de iteraciones, o lo que es lo mismo, el número de veces que vamos a recalcular y aplicar la pendiente de a y b para ir acercándonos al mínimo de la función de error. La función devuelve los valores óptimos de a y b que mejor ajustan la recta a la nube de puntos (o al menos un valor cercano) y una lista con el histórico de la función de coste, para poder estudiar cómo ha ido decreciendo el error. Vamos a probar nuestro algoritmo.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;a=1
b=1
alpha = 0.0001
iters = 100000
a,b, hist_coste = descenso_gradiente(x, y, a, b, alpha, iters)
a,b 
&lt;/pre&gt;(43.41993518666135, 1.08466250771225)&lt;br /&gt;
&lt;br /&gt;
Por lo tanto, nuestra recta será \(y=43.41+1.08x\), que como vemos en la siguiente figura, ajusta bastante bien.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtJC6vjjRP1zceMf1GmQM4fpYJhM68kHl8cpXQjQeegYAM8mTXi9i0fwM1mGW1TBPhNW2VxddYowHPH11nOcBfRfqXFEYVvOxE5c3PjcxiTtRbMBv9rO8Mnt7UxoYWnAwesnCtjLgLYDs/s1600/ajuste.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtJC6vjjRP1zceMf1GmQM4fpYJhM68kHl8cpXQjQeegYAM8mTXi9i0fwM1mGW1TBPhNW2VxddYowHPH11nOcBfRfqXFEYVvOxE5c3PjcxiTtRbMBv9rO8Mnt7UxoYWnAwesnCtjLgLYDs/s1600/ajuste.png&quot; data-original-width=&quot;384&quot; data-original-height=&quot;264&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Si representamos gráficamente el error que hemos ido cometiendo en cada iteración podemos ver cómo va decreciendo (en este caso he omitido las primeras iteraciones porque al principio decrece muy bruscamente y la escala de la gráfica no permite observar bien la curva).&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjtnQgM1OwXLiPxlbhyYGYOMARpTLuJOzhQrIO87blCgBKuCE0dbBFIRwSoVZQpUKZOAxbKtXtSmQ8pbV5azBHEYIyA5pN2xCF-IV7oxY2SXXX_apqCtkK28kaIgYOsATFWJooQtXFM58/s1600/error.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjtnQgM1OwXLiPxlbhyYGYOMARpTLuJOzhQrIO87blCgBKuCE0dbBFIRwSoVZQpUKZOAxbKtXtSmQ8pbV5azBHEYIyA5pN2xCF-IV7oxY2SXXX_apqCtkK28kaIgYOsATFWJooQtXFM58/s1600/error.png&quot; data-original-width=&quot;378&quot; data-original-height=&quot;265&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Así pues, si queremos saber cuanto deberíamos pagar por una casa de, digamos 100 metros, sólo hay que calcularlo sustituyendo b en la ecuación de la recta: \(y=43.41+1.08 \cdot 100=151.41\)&lt;br /&gt;
Es importante elegir bien el número de iteraciones del algoritmo. Si nos quedamos cortos nuestra recta no se ajustará bien, y si nos pasamos llega un momento en el que la mejora no es apreciable. Un método posible es detener las iteraciones cuando se compruebe que la función ya no se minimiza más allá de un umbral pequeño en cada iteración. Para que os hagáis una idea de como afecta este parámetro os dejo una animación en la que se muestra la recta calculada para diferentes valores del número de iteraciones.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGT-jS6dQ5hjOnjJQHR4pxaBW1Lp6gZC5UdIFULljgo7a3TrgY3OHkkb1lin0e3lF7OOiQW1cKYsdFe6riyi1iRvCH_gsff9qQempg9ttq406DXiiKti22bLMC5nLbtMlb2_ehIRo2NQc/s1600/regresion_anim.gif&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGT-jS6dQ5hjOnjJQHR4pxaBW1Lp6gZC5UdIFULljgo7a3TrgY3OHkkb1lin0e3lF7OOiQW1cKYsdFe6riyi1iRvCH_gsff9qQempg9ttq406DXiiKti22bLMC5nLbtMlb2_ehIRo2NQc/s1600/regresion_anim.gif&quot; data-original-width=&quot;384&quot; data-original-height=&quot;264&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Este artículo trata de ser didáctico, por lo que si quieres usar regresión lineal en la vida real, te aconsejo que mejor uses alguna buena librería como Scikit-learn que tiene un estupendo soporte para hacer este tipo de &lt;a href=&quot;https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html&quot;&gt;regresión&lt;/a&gt;.&lt;br /&gt;
Os dejo el enlace a mi GitHub con el notepad de Jupyter que contiene todo el código de este artículo: &lt;a href=&quot;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/Regresion%20lineal.ipynb&quot;&gt;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/Regresion%20lineal.ipynb&lt;/a&gt;</description><link>http://www.ellaberintodefalken.com/2019/03/regresion-lineal-descenso-de-gradiente.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg50-r7rBf6_QFvl9kTbzcM6n6R6rqCc_UxjRfycuflIkJ5E4EzJudYCBjoEGM1gZukaIl4MU0aqAgBJmKaKhCEab1MjqeAz3mtY6IoKnDMR61mLswpq79TEg4ObYYgDmrE6TuzLaQ4EaE/s72-c/casa.jpg" height="72" width="72"/><thr:total>1</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-1186913557558178200</guid><pubDate>Mon, 11 Mar 2019 15:36:00 +0000</pubDate><atom:updated>2019-03-11T21:47:43.225+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">data science</category><category domain="http://www.blogger.com/atom/ns#">datos</category><category domain="http://www.blogger.com/atom/ns#">machine learning</category><category domain="http://www.blogger.com/atom/ns#">pandas</category><title>Manipulación de datos con pandas</title><description>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaeu60jNpWGUgVaGRAr5Wkmy8UNaYU-xaj0NTo4ma819Ak57jiwKAdwbygBWrFY3ZrqQ-lNiN-9h909YNScO8KZcF4m9L7Py6rkSUm8Ty34gvZdyh2rbrg7FKdEu9IsshR5Nxmm7R6Ch4/s1600/pandas_logo.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaeu60jNpWGUgVaGRAr5Wkmy8UNaYU-xaj0NTo4ma819Ak57jiwKAdwbygBWrFY3ZrqQ-lNiN-9h909YNScO8KZcF4m9L7Py6rkSUm8Ty34gvZdyh2rbrg7FKdEu9IsshR5Nxmm7R6Ch4/s1600/pandas_logo.png&quot; data-original-width=&quot;600&quot; data-original-height=&quot;125&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Cuando uno lee un libro o un artículo sobre &lt;i&gt;machine learning&lt;/i&gt; encuentra multitud de explicaciones sobre el algoritmo tal o cual. Sin embargo, no se habla demasiado sobre la manipulación y el limpiado de los datos, que bajo mi punto de vista es tan o incluso más importante que utilizar el algoritmo adecuado. Nuestro aliado en esta tarea es la librería &lt;a href=&quot;https://pandas.pydata.org/&quot;&gt;pandas&lt;/a&gt;.&lt;br /&gt;
En lugar de hacer un recorrido exhaustivo por las funcionalidades de la librería, he preferido hacer uso de ella con un &lt;i&gt;dataset&lt;/i&gt; real, para poder ver así, no sólo cuáles son sus funcionalidades, sino también cómo se aplican a datos reales. Así pues, en lugar de usar un &lt;i&gt;dataset&lt;/i&gt; de los clásicos, he recurrido a uno real sacado de la web de datos abiertos del Ayuntamiento de Málaga. En concreto vamos a trabajar con el siguiente dataset, que se corresponde con las lecturas energéticas de los cuadros eléctricos durante el mes de marzo de 2017: &lt;a href=&quot;https://datosabiertos.malaga.eu/dataset/lecturas-cuadros-electricos-marzo-2017&quot;&gt;https://datosabiertos.malaga.eu/dataset/lecturas-cuadros-electricos-marzo-2017&lt;/a&gt;.&lt;br /&gt;
Como siempre os dejo un enlace al notebook python con todo lo que me dispongo a contaros: &lt;a href=&quot;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/Manipulacion%20datos%20Pandas.ipynb&quot;&gt;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/Manipulacion%20datos%20Pandas.ipynb&lt;/a&gt;&lt;br /&gt;
El &lt;i&gt;dataset&lt;/i&gt; es un archivo CSV y tiene los siguientes campos:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;_id: Id Autonumérico.&lt;/li&gt;
&lt;li&gt;ID_NODO: Identificador del nodo.&lt;/li&gt;
&lt;li&gt;ID_DIA: Identificador del día de la toma (número de día de mes).&lt;/li&gt;
&lt;li&gt;FECHA_TOMA_DATO: Fecha y hora de la toma del dato.&lt;/li&gt;
&lt;li&gt;ENERGIA_ACTIVA_ACUMULADA: Energía activa acumulada.&lt;/li&gt;
&lt;li&gt;LECTURA_ENERGIA_ACTIVA: Energía activa en el momento de la toma.&lt;/li&gt;
&lt;li&gt;ENERGIA_REACTIVA_ACUMULADA: Energía reactiva acumulada.&lt;/li&gt;
&lt;li&gt;LECTURA_ENERGIA_REACTIVA: Energía reactiva en el momento de la toma.&lt;/li&gt;
&lt;li&gt;POTENCIA_TOTAL_ACUMULADA: Acumulado de la potencia total manejada por el cuadro (expresada en W).&lt;/li&gt;
&lt;li&gt;LECTURA_POTENCIA_TOTAL: Potencia total manejada por el cuadro (expresada en W).&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Comenzamos importando y cargando el archivo CSV. Para realizar la carga se utiliza la función &lt;i&gt;read_csv()&lt;/i&gt;. Por defecto, esta función espera los datos separados por comas, pero si echas un ojo a nuestro fichero veras que el separador es el punto y coma. Por eso usamos el parámetro sep=&quot;;&quot;. En la variable data_mar se almacenan los datos. Pandas devuelve los datos en una estructura llamada DataFrame. Con la función &lt;i&gt;head()&lt;/i&gt; podemos ver las primeras filas de los datos que hemos cargado.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import pandas as pd
data_mar = pd.read_csv(&quot;201703.csv&quot;, sep=&quot;;&quot;)
data_mar.head()
&lt;/pre&gt;&lt;div class=&quot;dataframe&quot;&gt;&lt;table border=&quot;1&quot; class=&quot;dataframe&quot;&gt;&lt;thead&gt;
&lt;tr style=&quot;text-align: right;&quot;&gt;       &lt;th&gt;&lt;/th&gt;       &lt;th&gt;ID_NODO&lt;/th&gt;       &lt;th&gt;ID_DIA&lt;/th&gt;       &lt;th&gt;FECHA_TOMA_DATO&lt;/th&gt;       &lt;th&gt;ENERGIA_ACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;LECTURA_ENERGIA_ACTIVA&lt;/th&gt;       &lt;th&gt;ENERGIA_REACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;LECTURA_ENERGIA_REACTIVA&lt;/th&gt;       &lt;th&gt;POTENCIA_TOTAL_ACUMULADA&lt;/th&gt;       &lt;th&gt;LECTURA_POTENCIA_TOTAL&lt;/th&gt;     &lt;/tr&gt;
&lt;/thead&gt;   &lt;tbody&gt;
&lt;tr&gt;       &lt;th&gt;0&lt;/th&gt;       &lt;td&gt;81848&lt;/td&gt;       &lt;td&gt;28&lt;/td&gt;       &lt;td&gt;2017-02-28 23:11:54&lt;/td&gt;       &lt;td&gt;66884.601562&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;46464.699219&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;1&lt;/th&gt;       &lt;td&gt;98372&lt;/td&gt;       &lt;td&gt;28&lt;/td&gt;       &lt;td&gt;2017-02-28 23:11:54&lt;/td&gt;       &lt;td&gt;264010.593750&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;169612.296875&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;2&lt;/th&gt;       &lt;td&gt;98526&lt;/td&gt;       &lt;td&gt;28&lt;/td&gt;       &lt;td&gt;2017-02-28 23:11:54&lt;/td&gt;       &lt;td&gt;56888.000000&lt;/td&gt;       &lt;td&gt;0.398438&lt;/td&gt;       &lt;td&gt;61891.300781&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;14.000000&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;3&lt;/th&gt;       &lt;td&gt;98680&lt;/td&gt;       &lt;td&gt;28&lt;/td&gt;       &lt;td&gt;2017-02-28 23:11:54&lt;/td&gt;       &lt;td&gt;35347.699219&lt;/td&gt;       &lt;td&gt;0.398438&lt;/td&gt;       &lt;td&gt;37424.699219&lt;/td&gt;       &lt;td&gt;0.398438&lt;/td&gt;       &lt;td&gt;77.006493&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;4&lt;/th&gt;       &lt;td&gt;98834&lt;/td&gt;       &lt;td&gt;28&lt;/td&gt;       &lt;td&gt;2017-02-28 23:11:54&lt;/td&gt;       &lt;td&gt;93080.703125&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;47944.500000&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;     &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
Podemos empezar por obtener algunos estadísticos básicos con el objetivo de empezar a conocer con qué datos nos estamos enfrentando. Para ello usamos la función &lt;i&gt;describe()&lt;/i&gt;.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;data_mar.describe()
&lt;/pre&gt;&lt;div  class=&quot;dataframe&quot;&gt;&lt;table border=&quot;1&quot; class=&quot;dataframe&quot;&gt;&lt;thead&gt;
&lt;tr style=&quot;text-align: right;&quot;&gt;       &lt;th&gt;&lt;/th&gt;       &lt;th&gt;ID_NODO&lt;/th&gt;       &lt;th&gt;ID_DIA&lt;/th&gt;       &lt;th&gt;ENERGIA_ACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;LECTURA_ENERGIA_ACTIVA&lt;/th&gt;       &lt;th&gt;ENERGIA_REACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;LECTURA_ENERGIA_REACTIVA&lt;/th&gt;       &lt;th&gt;POTENCIA_TOTAL_ACUMULADA&lt;/th&gt;       &lt;th&gt;LECTURA_POTENCIA_TOTAL&lt;/th&gt;     &lt;/tr&gt;
&lt;/thead&gt;   &lt;tbody&gt;
&lt;tr&gt;       &lt;th&gt;count&lt;/th&gt;       &lt;td&gt;413108.000000&lt;/td&gt;       &lt;td&gt;413108.000000&lt;/td&gt;       &lt;td&gt;4.131080e+05&lt;/td&gt;       &lt;td&gt;4.131080e+05&lt;/td&gt;       &lt;td&gt;4.131080e+05&lt;/td&gt;       &lt;td&gt;4.131080e+05&lt;/td&gt;       &lt;td&gt;413108.000000&lt;/td&gt;       &lt;td&gt;413108.000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;mean&lt;/th&gt;       &lt;td&gt;71962.043165&lt;/td&gt;       &lt;td&gt;15.982503&lt;/td&gt;       &lt;td&gt;2.330070e+05&lt;/td&gt;       &lt;td&gt;1.268065e+02&lt;/td&gt;       &lt;td&gt;1.679989e+05&lt;/td&gt;       &lt;td&gt;1.002321e+02&lt;/td&gt;       &lt;td&gt;2291.475042&lt;/td&gt;       &lt;td&gt;42.950668&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;std&lt;/th&gt;       &lt;td&gt;26718.517878&lt;/td&gt;       &lt;td&gt;8.936663&lt;/td&gt;       &lt;td&gt;1.130742e+06&lt;/td&gt;       &lt;td&gt;2.271224e+04&lt;/td&gt;       &lt;td&gt;8.109683e+05&lt;/td&gt;       &lt;td&gt;1.819730e+04&lt;/td&gt;       &lt;td&gt;12654.772009&lt;/td&gt;       &lt;td&gt;1263.035618&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;min&lt;/th&gt;       &lt;td&gt;540.000000&lt;/td&gt;       &lt;td&gt;1.000000&lt;/td&gt;       &lt;td&gt;0.000000e+00&lt;/td&gt;       &lt;td&gt;0.000000e+00&lt;/td&gt;       &lt;td&gt;0.000000e+00&lt;/td&gt;       &lt;td&gt;0.000000e+00&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;25%&lt;/th&gt;       &lt;td&gt;70605.000000&lt;/td&gt;       &lt;td&gt;8.000000&lt;/td&gt;       &lt;td&gt;0.000000e+00&lt;/td&gt;       &lt;td&gt;0.000000e+00&lt;/td&gt;       &lt;td&gt;0.000000e+00&lt;/td&gt;       &lt;td&gt;0.000000e+00&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;50%&lt;/th&gt;       &lt;td&gt;76150.000000&lt;/td&gt;       &lt;td&gt;16.000000&lt;/td&gt;       &lt;td&gt;2.475400e+03&lt;/td&gt;       &lt;td&gt;0.000000e+00&lt;/td&gt;       &lt;td&gt;3.182920e+04&lt;/td&gt;       &lt;td&gt;0.000000e+00&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;75%&lt;/th&gt;       &lt;td&gt;81540.000000&lt;/td&gt;       &lt;td&gt;24.000000&lt;/td&gt;       &lt;td&gt;1.568925e+05&lt;/td&gt;       &lt;td&gt;0.000000e+00&lt;/td&gt;       &lt;td&gt;9.713650e+04&lt;/td&gt;       &lt;td&gt;0.000000e+00&lt;/td&gt;       &lt;td&gt;3.000000&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;max&lt;/th&gt;       &lt;td&gt;103620.000000&lt;/td&gt;       &lt;td&gt;31.000000&lt;/td&gt;       &lt;td&gt;1.267291e+07&lt;/td&gt;       &lt;td&gt;1.148814e+07&lt;/td&gt;       &lt;td&gt;9.325143e+06&lt;/td&gt;       &lt;td&gt;9.117862e+06&lt;/td&gt;       &lt;td&gt;234922.625488&lt;/td&gt;       &lt;td&gt;216123.260318&lt;/td&gt;     &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
Por cada variable numérica obtenemos el número de elementos, la media, la desviación típica, el máximo y mínimo valor, así como los percentiles 25%, 50% y 75%. También podemos obtener el número de elementos de la tabla, así como el número de columnas.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;data_mar.shape
&lt;/pre&gt;(413108, 9)&lt;br /&gt;
&lt;br /&gt;
El &lt;i&gt;dataset&lt;/i&gt; contiene información sobre lecturas de contadores eléctricos. En concreto mide la energía activa y la reactiva. Muchas veces, cuando uno estudia un &lt;i&gt;dataset&lt;/i&gt;, no domina el campo sobre el cuál tratan los datos, por lo toca investigar un poco. Os resumo un poco en qué consiste esto de la energía activa y reactiva:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Energía activa: los receptores eléctricos alimentados por corriente eléctrica transforman la energía eléctrica en trabajo mecánico y en calor. A este efecto útil se le denomina “energía activa” y se mide en kWh.&lt;/li&gt;
&lt;li&gt;Energía reactiva: existen numerosos receptores, tales como motores, transformadores, reactancias, etc., que para funcionar necesitan que se formen campos magnéticos. Estos equipos, en general inductivos, absorben energía de la red para crear los campos magnéticos y la devuelven mientras desaparecen. Con este intercambio de energía, se provoca un consumo suplementario que no es aprovechable por los receptores. Se mide en kVArh. La energía reactiva provoca una sobrecarga en líneas, transformadores y generadores, sin llegar a producir un rendimiento útil. Sin embargo, la factura de energía sí la contabiliza, por lo que puede llegar a incrementarla en cantidades importantes &lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Vamos a centrarnos en el estudio de la energía acumulada, así que por ahora no nos van a hacer falta los campos de lectura instantánea de energía activa y reactiva. Una buena práctica, sobre todo si el &lt;i&gt;dataset&lt;/i&gt; es muy grande y andamos cortos de memoria, es eliminar las columnas que no vamos a necesitar, así que vamos a eliminarlas.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# Borramos los campos que no vamos a necesitar: 
# LECTURA_ENERGIA_ACTIVA, LECTURA_ENERGIA_REACTIVA, LECTURA_POTENCIA_TOTAL
campos = data_mar.columns.values.tolist()
eliminar = [&quot;LECTURA_ENERGIA_ACTIVA&quot;, &quot;LECTURA_ENERGIA_REACTIVA&quot;, \
    &quot;LECTURA_POTENCIA_TOTAL&quot;]
data_mar = data_mar[[e for e in campos if e not in eliminar]]
&lt;/pre&gt;&lt;br /&gt;
También nos puede interesar a veces crear nuevos campos en los que la misma información esté en un formato diferente que nos facilite la operación los datos. Por ejemplo, el campo de fecha y hora están en formato &lt;i&gt;string&lt;/i&gt;, y nos puede interesar que sea un campo numérico para poder representarlo cómodamente en una gráfica. Como ejemplo, voy a crear un campo nuevo con la fecha en formato numérico.  &lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# vamos a crear un campo nuevo para la fecha en formato timestamp, 
# que nos será más cómodo a la hora de representar las gráficas.
data_mar[&quot;FECHA_NUM&quot;] = pd.to_numeric(pd.to_datetime(data_mar[&quot;FECHA_TOMA_DATO&quot;]))
data_mar.head()
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;dataframe&quot;&gt;&lt;table border=&quot;1&quot; class=&quot;dataframe&quot;&gt;&lt;thead&gt;
&lt;tr style=&quot;text-align: right;&quot;&gt;       &lt;th&gt;&lt;/th&gt;       &lt;th&gt;ID_NODO&lt;/th&gt;       &lt;th&gt;ID_DIA&lt;/th&gt;       &lt;th&gt;FECHA_TOMA_DATO&lt;/th&gt;       &lt;th&gt;ENERGIA_ACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;ENERGIA_REACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;POTENCIA_TOTAL_ACUMULADA&lt;/th&gt;       &lt;th&gt;FECHA_NUM&lt;/th&gt;     &lt;/tr&gt;
&lt;/thead&gt;   &lt;tbody&gt;
&lt;tr&gt;       &lt;th&gt;0&lt;/th&gt;       &lt;td&gt;81848&lt;/td&gt;       &lt;td&gt;28&lt;/td&gt;       &lt;td&gt;2017-02-28 23:11:54&lt;/td&gt;       &lt;td&gt;66884.601562&lt;/td&gt;       &lt;td&gt;46464.699219&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;1488323514000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;1&lt;/th&gt;       &lt;td&gt;98372&lt;/td&gt;       &lt;td&gt;28&lt;/td&gt;       &lt;td&gt;2017-02-28 23:11:54&lt;/td&gt;       &lt;td&gt;264010.593750&lt;/td&gt;       &lt;td&gt;169612.296875&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;1488323514000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;2&lt;/th&gt;       &lt;td&gt;98526&lt;/td&gt;       &lt;td&gt;28&lt;/td&gt;       &lt;td&gt;2017-02-28 23:11:54&lt;/td&gt;       &lt;td&gt;56888.000000&lt;/td&gt;       &lt;td&gt;61891.300781&lt;/td&gt;       &lt;td&gt;14.000000&lt;/td&gt;       &lt;td&gt;1488323514000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;3&lt;/th&gt;       &lt;td&gt;98680&lt;/td&gt;       &lt;td&gt;28&lt;/td&gt;       &lt;td&gt;2017-02-28 23:11:54&lt;/td&gt;       &lt;td&gt;35347.699219&lt;/td&gt;       &lt;td&gt;37424.699219&lt;/td&gt;       &lt;td&gt;77.006493&lt;/td&gt;       &lt;td&gt;1488323514000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;4&lt;/th&gt;       &lt;td&gt;98834&lt;/td&gt;       &lt;td&gt;28&lt;/td&gt;       &lt;td&gt;2017-02-28 23:11:54&lt;/td&gt;       &lt;td&gt;93080.703125&lt;/td&gt;       &lt;td&gt;47944.500000&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;1488323514000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
Vamos a examinar un nodo para ver cómo se comportan los datos para un sólo individuo de la muestra. Primero necesitamos averiguar cuántos nodos (contadores eléctricos) hay. Con la función &lt;i&gt;unique()&lt;/i&gt; podemos filtrar el campo ID_NODO para ver cuántos nodos distintos hay. &lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# ¿cuantos nodos tenemos?
len(data_mar[&quot;ID_NODO&quot;].unique())
&lt;/pre&gt;139&lt;br /&gt;
&lt;br /&gt;
Vamos a estudiar un nodo en concreto. El ID_NODO=542&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# vamos a estudiar un nodo al azar: el ID_NODO = 542
data_mar[data_mar[&quot;ID_NODO&quot;] == 542].plot(x=&quot;FECHA_NUM&quot;, y=&quot;ENERGIA_ACTIVA_ACUMULADA&quot;)
&lt;/pre&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuqxLvGCh29HkGDKOSNZzYXnnEeWWkrHbS5WjSiHpt3HNkJv_bcDwQGdft5uqW9mKRUqSemlGeznUu3azwYzbF_QvNricyUshZIQ_lov0tHchwHo_L46Om3kkrdxG2s0voB8I3mHyboF8/s1600/output_10_1.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuqxLvGCh29HkGDKOSNZzYXnnEeWWkrHbS5WjSiHpt3HNkJv_bcDwQGdft5uqW9mKRUqSemlGeznUu3azwYzbF_QvNricyUshZIQ_lov0tHchwHo_L46Om3kkrdxG2s0voB8I3mHyboF8/s1600/output_10_1.png&quot; data-original-width=&quot;411&quot; data-original-height=&quot;267&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Observamos en la gráfica que a partir de cierta fecha el acumulado de energía activa es cero. Si probamos con otros nodos vemos que el patrón se repite. Además, si examinamos las fechas de las lecturas vemos que hay datos del mes anterior (febrero). Un posible interpretación que podemos hacer es que el periodo de lecturas no comienza exactamente el día 1 del mes en curso. Podemos consultar para qué fecha y hora la lectura del nodo 542 es cero.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# veamos las fechas para las que las lecturas son cero
data_mar[(data_mar[&quot;ENERGIA_ACTIVA_ACUMULADA&quot;]==0) &amp; (data_mar[&quot;ID_NODO&quot;]==542)].head()
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;dataframe&quot;&gt;&lt;table border=&quot;1&quot; class=&quot;dataframe&quot;&gt;&lt;thead&gt;
&lt;tr style=&quot;text-align: right;&quot;&gt;       &lt;th&gt;&lt;/th&gt;       &lt;th&gt;ID_NODO&lt;/th&gt;       &lt;th&gt;ID_DIA&lt;/th&gt;       &lt;th&gt;FECHA_TOMA_DATO&lt;/th&gt;       &lt;th&gt;ENERGIA_ACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;ENERGIA_REACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;POTENCIA_TOTAL_ACUMULADA&lt;/th&gt;       &lt;th&gt;FECHA_NUM&lt;/th&gt;     &lt;/tr&gt;
&lt;/thead&gt;   &lt;tbody&gt;
&lt;tr&gt;       &lt;th&gt;337194&lt;/th&gt;       &lt;td&gt;542&lt;/td&gt;       &lt;td&gt;26&lt;/td&gt;       &lt;td&gt;2017-03-26 06:27:10&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;1490509630000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;337342&lt;/th&gt;       &lt;td&gt;542&lt;/td&gt;       &lt;td&gt;26&lt;/td&gt;       &lt;td&gt;2017-03-26 06:42:10&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;1490510530000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;337468&lt;/th&gt;       &lt;td&gt;542&lt;/td&gt;       &lt;td&gt;26&lt;/td&gt;       &lt;td&gt;2017-03-26 06:57:10&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;1490511430000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;337492&lt;/th&gt;       &lt;td&gt;542&lt;/td&gt;       &lt;td&gt;26&lt;/td&gt;       &lt;td&gt;2017-03-26 07:12:10&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;1490512330000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;337759&lt;/th&gt;       &lt;td&gt;542&lt;/td&gt;       &lt;td&gt;26&lt;/td&gt;       &lt;td&gt;2017-03-26 07:27:10&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;0.0&lt;/td&gt;       &lt;td&gt;1490513230000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
Tenemos dos opciones. Podemos borrar los datos anteriores o posteriores a una fecha concreta. Otra opción es eliminar aquellas lecturas cuyo valor para el campo ENERGIA_ACTIVA_ACUMULADA sea 0. Para ver un ejemplo de cada caso, vamos a borrar las filas que pertenecen al mes anterior y seguidamente vamos a eliminar todas las filas cuyo campo ENERGIA_ACTIVA_ACUMULADA sea 0.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# borramos datos del mes anterior
data_mar = data_mar[data_mar[&quot;FECHA_TOMA_DATO&quot;]&gt;&quot;2017-03&quot;]
data_mar.head()
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;dataframe&quot;&gt;&lt;table border=&quot;1&quot; class=&quot;dataframe&quot;&gt;&lt;thead&gt;
&lt;tr style=&quot;text-align: right;&quot;&gt;       &lt;th&gt;&lt;/th&gt;       &lt;th&gt;ID_NODO&lt;/th&gt;       &lt;th&gt;ID_DIA&lt;/th&gt;       &lt;th&gt;FECHA_TOMA_DATO&lt;/th&gt;       &lt;th&gt;ENERGIA_ACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;ENERGIA_REACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;POTENCIA_TOTAL_ACUMULADA&lt;/th&gt;       &lt;th&gt;FECHA_NUM&lt;/th&gt;     &lt;/tr&gt;
&lt;/thead&gt;   &lt;tbody&gt;
&lt;tr&gt;       &lt;th&gt;556&lt;/th&gt;       &lt;td&gt;542&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;360591.906250&lt;/td&gt;       &lt;td&gt;121981.601562&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;557&lt;/th&gt;       &lt;td&gt;543&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;300521.894531&lt;/td&gt;       &lt;td&gt;242683.304688&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;558&lt;/th&gt;       &lt;td&gt;544&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;2475.300049&lt;/td&gt;       &lt;td&gt;51700.800781&lt;/td&gt;       &lt;td&gt;80.024994&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;559&lt;/th&gt;       &lt;td&gt;545&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;26725.900391&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;560&lt;/th&gt;       &lt;td&gt;546&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;0.000000&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# borramos las filas donde se cumpla que 
# ENERGIA_ACTIVA_ACUMULADA = 0 para todos los nodos
data_mar = data_mar[data_mar[&quot;ENERGIA_ACTIVA_ACUMULADA&quot;]&gt;0]
data_mar.shape
&lt;/pre&gt;(237946, 7)&lt;br /&gt;
&lt;br /&gt;
Podemos volver a examinar la gráfica que, ahora sí, nos permite comprender mejor cómo es la demanda de consumo para ese nodo.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# creamos un dataframe con los datos del nodo 542
data_mar_542 = data_mar[data_mar[&quot;ID_NODO&quot;] == 542]
data_mar_542.plot(x=&quot;FECHA_NUM&quot;, y=&quot;ENERGIA_ACTIVA_ACUMULADA&quot;)
&lt;/pre&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhziLVdtUnxkJl1cEbpJxwAMM0fxIC55YqkCM21d1fPgsE5o-CJ3G0Z_ADpIw4DV7r-CjdAp3yCCfPxI7n75TJnf5ABI6XB_LD7GG2luZ07wFv6Ko2Up8meu793DyyanSCgdw2DlyzH3Fc/s1600/output_17_1.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhziLVdtUnxkJl1cEbpJxwAMM0fxIC55YqkCM21d1fPgsE5o-CJ3G0Z_ADpIw4DV7r-CjdAp3yCCfPxI7n75TJnf5ABI6XB_LD7GG2luZ07wFv6Ko2Up8meu793DyyanSCgdw2DlyzH3Fc/s1600/output_17_1.png&quot; data-original-width=&quot;413&quot; data-original-height=&quot;267&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Vamos a comprar la energía activa y la reactiva poniéndolas juntas en una gráfica.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;data_mar_542.plot(x=&quot;FECHA_NUM&quot;, y=[&quot;ENERGIA_ACTIVA_ACUMULADA&quot;, \
    &quot;ENERGIA_REACTIVA_ACUMULADA&quot;], subplots=True )
&lt;/pre&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7S1UinVB-EDwAGg16EWsh8V14P3QzWt11wWwOPOkB45gK2kbFrAURCigQfAlMVMMaXDk8JkQ9FrrX7iMEBvwx_eAS16BTjD_LL-kBl2dB_x9puJ8Vou-wbCyKe3vAD9_74Oa6CRaf5ks/s1600/output_18_1.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7S1UinVB-EDwAGg16EWsh8V14P3QzWt11wWwOPOkB45gK2kbFrAURCigQfAlMVMMaXDk8JkQ9FrrX7iMEBvwx_eAS16BTjD_LL-kBl2dB_x9puJ8Vou-wbCyKe3vAD9_74Oa6CRaf5ks/s1600/output_18_1.png&quot; data-original-width=&quot;397&quot; data-original-height=&quot;264&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Aunque los valores de la energía reactiva son muy inferiores a los de la energía activa, parece que hay una correlación bastante acusada entre ambos tipos de energía. De hecho, si usamos la función &lt;i&gt;corr()&lt;/i&gt; de Pandas para calcular la correlación entre ambos campos, vemos que es una correlación casi perfecta (0.99).&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;data_mar_542[&quot;ENERGIA_ACTIVA_ACUMULADA&quot;].corr(data_mar_542[&quot;ENERGIA_REACTIVA_ACUMULADA&quot;])
&lt;/pre&gt;0.99990652807701275&lt;br /&gt;
&lt;br /&gt;
La energía reactiva es un tipo de energía eléctrica, que absorben de la red algunos equipos eléctricos pero que luego la devuelven, por lo que no supone un consumo, aunque sí hay que generarla y transportarla hasta los equipos. Por ello las compañías eléctricas penalizan en factura el consumo de energía reactiva a partir de un cierto valor.&lt;br /&gt;
Si quisieramos hacer un análisis de en qué nodos hay un consumo anomalo de energía reactiva, podríamos ver si la relación entre la energía activa y reactiva tienen una baja correlación (aquí estoy haciendo muchas suposiciones que podrían no ser ciertas a nivel eléctrico, pero valga como ejemplo).&lt;br /&gt;
¿Se observará la misma correlación en todos los nodos? Vamos a crear un nuevo DataFrame con la correlación entre ambas lecturas para cada contador. &lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# creamos un nuevo dataframe con los campos NODO_ID y la correlación
c = []
for i in data_mar[&quot;ID_NODO&quot;].unique().tolist():
    c.append([i, data_mar[data_mar[&quot;ID_NODO&quot;]==i][&quot;ENERGIA_ACTIVA_ACUMULADA&quot;].corr(data_mar[&quot;ENERGIA_REACTIVA_ACUMULADA&quot;])])

correlaciones = pd.DataFrame(c, columns=[&quot;ID_NODO&quot;, &quot;CORRELACION&quot;])
correlaciones.head(10)
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;dataframe&quot;&gt;&lt;table border=&quot;1&quot; class=&quot;dataframe&quot;&gt;&lt;thead&gt;
&lt;tr style=&quot;text-align: right;&quot;&gt;       &lt;th&gt;&lt;/th&gt;       &lt;th&gt;ID_NODO&lt;/th&gt;       &lt;th&gt;CORRELACION&lt;/th&gt;     &lt;/tr&gt;
&lt;/thead&gt;   &lt;tbody&gt;
&lt;tr&gt;       &lt;th&gt;0&lt;/th&gt;       &lt;td&gt;542&lt;/td&gt;       &lt;td&gt;0.999907&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;1&lt;/th&gt;       &lt;td&gt;543&lt;/td&gt;       &lt;td&gt;0.961227&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;2&lt;/th&gt;       &lt;td&gt;544&lt;/td&gt;       &lt;td&gt;0.999266&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;3&lt;/th&gt;       &lt;td&gt;547&lt;/td&gt;       &lt;td&gt;0.999963&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;4&lt;/th&gt;       &lt;td&gt;33756&lt;/td&gt;       &lt;td&gt;0.998668&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;5&lt;/th&gt;       &lt;td&gt;71530&lt;/td&gt;       &lt;td&gt;NaN&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;6&lt;/th&gt;       &lt;td&gt;71838&lt;/td&gt;       &lt;td&gt;NaN&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;7&lt;/th&gt;       &lt;td&gt;71992&lt;/td&gt;       &lt;td&gt;NaN&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;8&lt;/th&gt;       &lt;td&gt;72454&lt;/td&gt;       &lt;td&gt;0.999997&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;9&lt;/th&gt;       &lt;td&gt;73224&lt;/td&gt;       &lt;td&gt;NaN&lt;/td&gt;     &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
Observamos que hay correlaciones que toman el valor NaN (Not A Number). ¿Qué ha pasado? Si generamos la gráfica de uno de los nodos cuya correlación es NaN vemos que el valor de ambas variables es siempre constante, es decir, no crece en todo el periodo de tiempo que estamos observando. Por defecto, Pandas calcula la correlación de pearson, cuya formula es cor(i,j) = cov(i,j)/[stdev(i)*stdev(j)]. Como la desviación estandad es cero y un divisor no puede ser cero, obtenemos un bonito NaN. Lo cuál, dicho sea de paso, nos viene de perlas para ver cómo tratar con valores nulos en un DataFrame.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;data_mar[data_mar[&quot;ID_NODO&quot;]==71530].plot(x=&quot;FECHA_NUM&quot;, \
    y=[&quot;ENERGIA_ACTIVA_ACUMULADA&quot;,&quot;ENERGIA_REACTIVA_ACUMULADA&quot;], subplots=True )
&lt;/pre&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg37VCuCQvQZW7ogI7zl1Gd2olWmCHy78dba1X0TGBP-5swZ3dF4QX1V3l3b_KuQOfvwy8-timsdCXEz-9xksBmu_ynZNIgf0UtU0uMsnNS7DO9in_2OHSkT5U4LAjBUeCh9lk4QqfnR4E/s1600/output_24_1.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg37VCuCQvQZW7ogI7zl1Gd2olWmCHy78dba1X0TGBP-5swZ3dF4QX1V3l3b_KuQOfvwy8-timsdCXEz-9xksBmu_ynZNIgf0UtU0uMsnNS7DO9in_2OHSkT5U4LAjBUeCh9lk4QqfnR4E/s1600/output_24_1.png&quot; data-original-width=&quot;397&quot; data-original-height=&quot;264&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Vamos a ver qué opciones tenemos para tratar con los valores nulos. Podemos:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Eliminar las filas con valor nulo (con el método &lt;i&gt;dropna()&lt;/i&gt;)&lt;/li&gt;
&lt;li&gt;Rellenar las filas con un valor calculado (con el método &lt;i&gt;fillna()&lt;/i&gt;)&lt;/li&gt;
&lt;/ul&gt;&lt;pre class=&quot;prettyprint linenums&quot;&gt;# rellenar filas NaN con un valor concreto (0 en este caso)
cor_cero = correlaciones[&quot;CORRELACION&quot;].fillna(0)
cor_cero.head(10)
&lt;/pre&gt;&lt;pre class=&quot;dataframe&quot;&gt;0    0.999907
1    0.961227
2    0.999266
3    0.999963
4    0.998668
5    0.000000
6    0.000000
7    0.000000
8    0.999997
9    0.000000
Name: CORRELACION, dtype: float64
&lt;/pre&gt;O podemos rellenar con la media.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# rellenar filas con el valor medio de la columna (o cualquier otra función))
cor_media = correlaciones[&quot;CORRELACION&quot;].fillna(correlaciones[&quot;CORRELACION&quot;].mean())
cor_media.head(10)
&lt;/pre&gt;&lt;pre class=&quot;dataframe&quot;&gt;0    0.999907
1    0.961227
2    0.999266
3    0.999963
4    0.998668
5    0.971874
6    0.971874
7    0.971874
8    0.999997
9    0.971874
Name: CORRELACION, dtype: float64
&lt;/pre&gt;&lt;br /&gt;
Si preferimos eliminar las filas con NaN podemos usar el método &lt;i&gt;dropna()&lt;/i&gt;.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# pero en este caso vamos a optar por eliminarlas
# si axis=0 borra por filas. Si axis=1 borra por columnas
correlaciones = correlaciones.dropna(axis=0)
correlaciones.head(10)
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;dataframe&quot;&gt;&lt;table border=&quot;1&quot; class=&quot;dataframe&quot;&gt;&lt;thead&gt;
&lt;tr style=&quot;text-align: right;&quot;&gt;       &lt;th&gt;&lt;/th&gt;       &lt;th&gt;ID_NODO&lt;/th&gt;       &lt;th&gt;CORRELACION&lt;/th&gt;     &lt;/tr&gt;
&lt;/thead&gt;   &lt;tbody&gt;
&lt;tr&gt;       &lt;th&gt;0&lt;/th&gt;       &lt;td&gt;542&lt;/td&gt;       &lt;td&gt;0.999907&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;1&lt;/th&gt;       &lt;td&gt;543&lt;/td&gt;       &lt;td&gt;0.961227&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;2&lt;/th&gt;       &lt;td&gt;544&lt;/td&gt;       &lt;td&gt;0.999266&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;3&lt;/th&gt;       &lt;td&gt;547&lt;/td&gt;       &lt;td&gt;0.999963&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;4&lt;/th&gt;       &lt;td&gt;33756&lt;/td&gt;       &lt;td&gt;0.998668&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;8&lt;/th&gt;       &lt;td&gt;72454&lt;/td&gt;       &lt;td&gt;0.999997&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;10&lt;/th&gt;       &lt;td&gt;73840&lt;/td&gt;       &lt;td&gt;0.999995&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;11&lt;/th&gt;       &lt;td&gt;74918&lt;/td&gt;       &lt;td&gt;0.999972&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;12&lt;/th&gt;       &lt;td&gt;75072&lt;/td&gt;       &lt;td&gt;0.999946&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;13&lt;/th&gt;       &lt;td&gt;75534&lt;/td&gt;       &lt;td&gt;0.999648&lt;/td&gt;     &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
Si lo que queremos es estudiar cómo se comporta el consumo en los cuadros eléctricos donde hay baja correlación, podemos ordenar la lista para ver cuáles son más bajas.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;correlaciones = correlaciones.sort_values(by=&quot;CORRELACION&quot;)
correlaciones.head()
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;dataframe&quot;&gt;&lt;table border=&quot;1&quot; class=&quot;dataframe&quot;&gt;&lt;thead&gt;
&lt;tr style=&quot;text-align: right;&quot;&gt;       &lt;th&gt;&lt;/th&gt;       &lt;th&gt;ID_NODO&lt;/th&gt;       &lt;th&gt;CORRELACION&lt;/th&gt;     &lt;/tr&gt;
&lt;/thead&gt;   &lt;tbody&gt;
&lt;tr&gt;       &lt;th&gt;90&lt;/th&gt;       &lt;td&gt;52061&lt;/td&gt;       &lt;td&gt;0.514461&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;61&lt;/th&gt;       &lt;td&gt;75688&lt;/td&gt;       &lt;td&gt;0.617365&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;84&lt;/th&gt;       &lt;td&gt;52775&lt;/td&gt;       &lt;td&gt;0.649950&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;67&lt;/th&gt;       &lt;td&gt;77382&lt;/td&gt;       &lt;td&gt;0.826462&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;40&lt;/th&gt;       &lt;td&gt;100682&lt;/td&gt;       &lt;td&gt;0.885768&lt;/td&gt;     &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
Echemos un vistazo a las gráficas de energía activa y reactiva para el nodo con la correlación más baja.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;data_mar[data_mar[&quot;ID_NODO&quot;]==52061].plot(x=&quot;FECHA_NUM&quot;, \ 
    y=[&quot;ENERGIA_ACTIVA_ACUMULADA&quot;,&quot;ENERGIA_REACTIVA_ACUMULADA&quot;], subplots=True )
&lt;/pre&gt;&lt;br /&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTvS3g4O0EYEbbLsDaZi79n-IkWp5n7wI7SugivYN65GL37vef6xMJaGx9hNjpaF1xsUGuhk2bx9YifebtGFJFqzgExjKBbpfrD9aQRRO4BNUQc2_N54PW4z9yi5kKT3-UKg2P5dcfq5o/s1600/output_31_1.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTvS3g4O0EYEbbLsDaZi79n-IkWp5n7wI7SugivYN65GL37vef6xMJaGx9hNjpaF1xsUGuhk2bx9YifebtGFJFqzgExjKBbpfrD9aQRRO4BNUQc2_N54PW4z9yi5kKT3-UKg2P5dcfq5o/s1600/output_31_1.png&quot; data-original-width=&quot;397&quot; data-original-height=&quot;251&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
A partir de esta información podríamos deducir que a ese cuadro eléctrico se conecta maquinaria que hace uso intensivo de campos eléctricos, como bobinas o motores.&lt;br /&gt;
Vamos ahora a ver cómo se comporta el campo POTENCIA_TOTAL_ACUMULADA. Miramos el primer nodo.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;data_mar[data_mar[&quot;ID_NODO&quot;]==52061].plot(x=&quot;FECHA_NUM&quot;, \
    y=[&quot;POTENCIA_TOTAL_ACUMULADA&quot;], subplots=True )
&lt;/pre&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiziGz3wsUeNKSw4EzMuQai-M8tcfNiXhzhIf0_ZyUPMg5vyDNDf-CT97rmjSMrYFnPgYuD2snm-F2dXWFJxULOkRJB5V8GIv10dcm7EM8yRkP5iHKa93PgYRyj8KERL79htcalXhiAqP4/s1600/output_33_1.png&quot; imageanchor=&quot;1&quot; &gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiziGz3wsUeNKSw4EzMuQai-M8tcfNiXhzhIf0_ZyUPMg5vyDNDf-CT97rmjSMrYFnPgYuD2snm-F2dXWFJxULOkRJB5V8GIv10dcm7EM8yRkP5iHKa93PgYRyj8KERL79htcalXhiAqP4/s1600/output_33_1.png&quot; data-original-width=&quot;390&quot; data-original-height=&quot;251&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Está a cero, y si observamos otros nodos vemos que también. Como no tenemos más detalles sobre el dataset sólo podemos hacer suposiciones (como que por error no se ha rellenado este campo). De todas formas nos viene bien para ver cómo podemos combinar valores de dos o más campos para volcarlos en otro. Vamos a suponer que en este campo, el valor que debería haber es la suma de la energía activa y reactiva (en realidad ambos valores están en unidades distintas, pero por simplicidad supondremos que ambos valores son Kw/h y que podemos sumarlos alegremente). Vamos a combinar pues las suma de los dos valores en el campo POTENCIA_TOTAL_ACUMULADA.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;data_mar[&quot;POTENCIA_TOTAL_ACUMULADA&quot;] = data_mar[&quot;ENERGIA_ACTIVA_ACUMULADA&quot;] \
    + data_mar[&quot;ENERGIA_REACTIVA_ACUMULADA&quot;]
data_mar.head()
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;dataframe&quot;&gt;&lt;table border=&quot;1&quot; class=&quot;dataframe&quot;&gt;&lt;thead&gt;
&lt;tr style=&quot;text-align: right;&quot;&gt;       &lt;th&gt;&lt;/th&gt;       &lt;th&gt;ID_NODO&lt;/th&gt;       &lt;th&gt;ID_DIA&lt;/th&gt;       &lt;th&gt;FECHA_TOMA_DATO&lt;/th&gt;       &lt;th&gt;ENERGIA_ACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;ENERGIA_REACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;POTENCIA_TOTAL_ACUMULADA&lt;/th&gt;       &lt;th&gt;FECHA_NUM&lt;/th&gt;     &lt;/tr&gt;
&lt;/thead&gt;   &lt;tbody&gt;
&lt;tr&gt;       &lt;th&gt;556&lt;/th&gt;       &lt;td&gt;542&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;3.605919e+05&lt;/td&gt;       &lt;td&gt;1.219816e+05&lt;/td&gt;       &lt;td&gt;4.825735e+05&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;557&lt;/th&gt;       &lt;td&gt;543&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;3.005219e+05&lt;/td&gt;       &lt;td&gt;2.426833e+05&lt;/td&gt;       &lt;td&gt;5.432052e+05&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;558&lt;/th&gt;       &lt;td&gt;544&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;2.475300e+03&lt;/td&gt;       &lt;td&gt;5.170080e+04&lt;/td&gt;       &lt;td&gt;5.417610e+04&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;561&lt;/th&gt;       &lt;td&gt;547&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;4.085408e+06&lt;/td&gt;       &lt;td&gt;3.286907e+06&lt;/td&gt;       &lt;td&gt;7.372315e+06&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;566&lt;/th&gt;       &lt;td&gt;33756&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;1.144733e+07&lt;/td&gt;       &lt;td&gt;8.198532e+06&lt;/td&gt;       &lt;td&gt;1.964586e+07&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;     &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
En este momento tenemos, por un lado, el DataFrame con las lecturas de los cuadros y por otro un DataFrame con las correlaciones que hemos calculado. ¿Podemos agregar las correlaciones a la tabla principal? Claro. Esta operación se llama unión (join en inglés). Hay cuatro tipos: inner join, left join, right join y outer join. No voy a entrar en detalle ya que son los mismos que los de SQL. Si queréis saber más podéis consultar aquí: &lt;a href=&quot;https://es.wikipedia.org/wiki/Join&quot;&gt;https://es.wikipedia.org/wiki/Join&lt;/a&gt;&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# unimos los DataFrames con left join, ya que el 
# segundo dataframe (correlaciones) tiene menos filas 
# y por lo tanto, no todos los nodos disponen de datos de correlación 
# (recuerda que borramos los NaN)
data_mar = pd.merge(left=data_mar, right=correlaciones, how=&quot;left&quot;, \
    left_on=&quot;ID_NODO&quot;, right_on=&quot;ID_NODO&quot;)
data_mar.head()
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;dataframe&quot;&gt;&lt;table border=&quot;1&quot; class=&quot;dataframe&quot;&gt;&lt;thead&gt;
&lt;tr style=&quot;text-align: right;&quot;&gt;       &lt;th&gt;&lt;/th&gt;       &lt;th&gt;ID_NODO&lt;/th&gt;       &lt;th&gt;ID_DIA&lt;/th&gt;       &lt;th&gt;FECHA_TOMA_DATO&lt;/th&gt;       &lt;th&gt;ENERGIA_ACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;ENERGIA_REACTIVA_ACUMULADA&lt;/th&gt;       &lt;th&gt;POTENCIA_TOTAL_ACUMULADA&lt;/th&gt;       &lt;th&gt;FECHA_NUM&lt;/th&gt;       &lt;th&gt;CORRELACION&lt;/th&gt;     &lt;/tr&gt;
&lt;/thead&gt;   &lt;tbody&gt;
&lt;tr&gt;       &lt;th&gt;0&lt;/th&gt;       &lt;td&gt;542&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;3.605919e+05&lt;/td&gt;       &lt;td&gt;1.219816e+05&lt;/td&gt;       &lt;td&gt;4.825735e+05&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;       &lt;td&gt;0.999907&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;1&lt;/th&gt;       &lt;td&gt;543&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;3.005219e+05&lt;/td&gt;       &lt;td&gt;2.426833e+05&lt;/td&gt;       &lt;td&gt;5.432052e+05&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;       &lt;td&gt;0.961227&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;2&lt;/th&gt;       &lt;td&gt;544&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;2.475300e+03&lt;/td&gt;       &lt;td&gt;5.170080e+04&lt;/td&gt;       &lt;td&gt;5.417610e+04&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;       &lt;td&gt;0.999266&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;3&lt;/th&gt;       &lt;td&gt;547&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;4.085408e+06&lt;/td&gt;       &lt;td&gt;3.286907e+06&lt;/td&gt;       &lt;td&gt;7.372315e+06&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;       &lt;td&gt;0.999963&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;th&gt;4&lt;/th&gt;       &lt;td&gt;33756&lt;/td&gt;       &lt;td&gt;1&lt;/td&gt;       &lt;td&gt;2017-03-01 00:11:54&lt;/td&gt;       &lt;td&gt;1.144733e+07&lt;/td&gt;       &lt;td&gt;8.198532e+06&lt;/td&gt;       &lt;td&gt;1.964586e+07&lt;/td&gt;       &lt;td&gt;1488327114000000000&lt;/td&gt;       &lt;td&gt;0.998668&lt;/td&gt;     &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
Los parámetros &lt;i&gt;left&lt;/i&gt; y &lt;i&gt;right&lt;/i&gt; indican los DataFrames que vamos a fundir. El parámetro &lt;i&gt;how&lt;/i&gt; nos permite indicar el tipo de join, en este caso left join. Finalmente &lt;i&gt;left_on&lt;/i&gt; y &lt;i&gt;right_on&lt;/i&gt; nos sirve para indicar cuál es el campo clave para unir ambos DataFrames. En este caso el campo común es ID_NODO.&lt;br /&gt;
Como dije al principio del artículo, mi intención no era hacer una descripción exhaustiva de toda la funcionalidad de pandas, ya que creo que podía ser más clarificador ver las operaciones básicas sobre un &lt;i&gt;dataset&lt;/i&gt; real.</description><link>http://www.ellaberintodefalken.com/2019/03/manipulacion-de-datos-con-pandas.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaeu60jNpWGUgVaGRAr5Wkmy8UNaYU-xaj0NTo4ma819Ak57jiwKAdwbygBWrFY3ZrqQ-lNiN-9h909YNScO8KZcF4m9L7Py6rkSUm8Ty34gvZdyh2rbrg7FKdEu9IsshR5Nxmm7R6Ch4/s72-c/pandas_logo.png" height="72" width="72"/><thr:total>2</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-7474483920497816818</guid><pubDate>Thu, 28 Feb 2019 22:10:00 +0000</pubDate><atom:updated>2019-03-10T22:46:22.441+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">k nearest neighbors</category><category domain="http://www.blogger.com/atom/ns#">k vecinos más cercanos</category><category domain="http://www.blogger.com/atom/ns#">k-nn</category><category domain="http://www.blogger.com/atom/ns#">knn</category><category domain="http://www.blogger.com/atom/ns#">machine learning</category><category domain="http://www.blogger.com/atom/ns#">MNIST</category><title>Reconocimiento de caracteres manuscritos con k-nn</title><description>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0dSO-AlEy6sP6Yk6lk7_XKvaS4cgLXgpOo3ljk5fwpcKqTOH3L0CxjuCCLixlH8m9Io3KgQzA1uBDEAzD3msOJ9fRL9Tcn8qgthDf5a81gkyIcmmoEg-dZJy49I0ezSASWZxfzKdDoxk/s1600/mnist.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0dSO-AlEy6sP6Yk6lk7_XKvaS4cgLXgpOo3ljk5fwpcKqTOH3L0CxjuCCLixlH8m9Io3KgQzA1uBDEAzD3msOJ9fRL9Tcn8qgthDf5a81gkyIcmmoEg-dZJy49I0ezSASWZxfzKdDoxk/s400/mnist.png&quot; width=&quot;400&quot; height=&quot;171&quot; data-original-width=&quot;638&quot; data-original-height=&quot;273&quot; alt=&quot;MNIST k-nn&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
En la entrada anterior introducimos el algoritmo &lt;a href=&quot;https://www.ellaberintodefalken.com/2019/02/clasificacion-con-k-nearest-neighbors.html&quot;&gt;k nearest neighbors&lt;/a&gt; o de los vecinos más cercanos. Os conté como funciona el algoritmo y os presenté un ejemplo que clasificaba puntitos en un espacio en dos dimensiones. Como sé que os quedasteis con ganas de más (sí, aún os oigo suplicar que os cuente más) vamos a poner a prueba al algoritmo k-nn para ver si es capaz de aprender a reconocer caracteres manuscritos (problema conocido como OCR o reconocimiento óptico de caracteres). Como en el anterior artículo, os dejo un enlace al notebook de Jupyter que he usado para escribir este post: &lt;a href=&quot;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/KNN%20Image%20Classifier.ipynb&quot;&gt;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/KNN%20Image%20Classifier.ipynb&lt;/a&gt;&lt;br /&gt;
Para entrenar nuestro reconocedor de caracteres vamos a usar un dataset bastante conocido: &lt;a href=&quot;http://yann.lecun.com/exdb/mnist/&quot;&gt;MNIST&lt;/a&gt;. Desde esta web podéis descargar los cuatro archivos que lo componen:&lt;br /&gt;
train-images-idx3-ubyte.gz:  imágenes de entrenamiento&lt;br /&gt;
train-labels-idx1-ubyte.gz:  etiquetas de las imágenes de entrenamiento&lt;br /&gt;
t10k-images-idx3-ubyte.gz:   conjunto de imágenes para test&lt;br /&gt;
t10k-labels-idx1-ubyte.gz:   conjunto de etiquetas de las imágenes de test&lt;br /&gt;
&lt;br /&gt;
Este &lt;i&gt;dataset&lt;/i&gt; contiene miles de imágenes con dígitos escritos a mano. En la imagen de cabecera de este artículo puedes ver qué pinta tienen los dígitos. Las imágenes están codificadas dentro del archivo con un formato que está descrito en la web. No voy a entrar en detalles porque todo está ahí. Simplemente os pongo el código que he usado para cargarlas e introducirlas en matrices de NumPy.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;def loadMNIST( prefix, folder ):
    intType = np.dtype( &#39;int32&#39; ).newbyteorder( &#39;&gt;&#39; )
    nMetaDataBytes = 4 * intType.itemsize

    data = np.fromfile( folder + &quot;/&quot; + prefix + &#39;-images.idx3-ubyte&#39;, dtype = &#39;ubyte&#39; )
    magicBytes, nImages, width, height = np.frombuffer( data[:nMetaDataBytes].tobytes(), intType )
    data = data[nMetaDataBytes:].astype( dtype = &#39;float32&#39; ).reshape( [ nImages, width, height ] )

    labels = np.fromfile( folder + &quot;/&quot; + prefix + &#39;-labels.idx1-ubyte&#39;,
                          dtype = &#39;ubyte&#39; )[2 * intType.itemsize:]

    return data, labels

images_tr, labels_tr = loadMNIST( &quot;train&quot;, &quot;./mnist&quot; )
images_te, labels_te = loadMNIST( &quot;t10k&quot;, &quot;./mnist&quot; )
# imagenes en array de 60000 x 28 x 28 -&gt; 60000 imagenes de 28x28
&lt;/pre&gt;El conjunto de entrenamiento tiene 60.000 imágenes y el de test, es decir, el que usaremos para poner a prueba el sistema una vez entrenado, dispone de 10.000 imágenes. Cada una de estas imágenes es una matriz de 28x28 píxeles con un dígito del 0 al 9 dibujado a mano. Si recordáis, en el ejemplo del artículo anterior manejábamos datos de dos dimensiones (por ejemplo, el sueldo y la deuda a la hora de conceder o no un préstamo). Ahora trabajamos con datos de 784 dimensiones (28*28 píxeles). Así que por lo visto vamos a tener que sumar, restar, elevar al cuadrado y hacer raíces cuadradas de matrices de 784 elementos. No está mal ¿verdad? veremos como le sienta esto a NumPy.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# coger un dígito aleatorio del grupo de test
i = random.randint(0,images_te.shape[0])
img_test = images_te[i].flatten()
label_test = labels_te[i]
&lt;/pre&gt;Empezamos por elegir una imagen aleatoria del conjunto de test para ver si somos capaces de clasificarla correctamente y reconocer el dígito. Para operar con la imagen la aplanamos (&lt;i&gt;flatten&lt;/i&gt;), es decir, ponemos toda la matriz en una sola fila (matriz de 1x784). También guardamos su etiqueta para compararla con la clasificación que haga el algoritmo. &lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# buscamos los vecinos más cercanos (KNN)
k = 5 #número de vecinos

distances = []
for i in range(images_tr.shape[0]):
    dist = np.sqrt(np.sum(np.square(images_tr[i].flatten() - img_test)))
    distances.append((dist, labels_tr[i])) # guardamos las etiquetas y la distancia

#ordenamos por distancia y nos quedamos con los k vecinos más cercanos
distances.sort(key=lambda x: x[0])
neighbors = distances[:k]
&lt;/pre&gt;Ahora buscamos a los k vecinos más cercanos (en este caso k=5). Si recuerdas el ejemplo anterior, el código era algo más complejo debido a que estábamos clasificando varios datos de test. En este caso sólo clasificamos un dato (el dígito que hemos seleccionado aleatoriamente en el código de más arriba). No entro en mucho detalle ya que en el artículo anterior ya os conté cómo funcionaba. Si no entiendes qué hace este fragmento de código dale un repaso a lo que ya os conté sobre &lt;a href=&quot;https://www.ellaberintodefalken.com/2019/02/clasificacion-con-k-nearest-neighbors.html&quot;&gt;k-nn&lt;/a&gt;.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# contamos los votos para ver qué etiqueta gana
votes = [0,0,0,0,0,0,0,0,0,0]
for neighbor in neighbors:
    votes[neighbor[1]] = votes[neighbor[1]] + 1
# obtenemos la etiqueta ganadora
pred_label = votes.index(max(votes))
&lt;/pre&gt;Una vez elegidos los k vecinos más cercanos toca votar (k-nn es una democracia). Por lo tanto, recontamos las etiquetas de los k vecinos más cercanos y la ganadora será nuestra predicción.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;print (&quot;Predicted: &quot; + str(pred_label))
print (&quot;Real: &quot; + str(label_test))
img = plt.imshow(img_test.reshape(28,28), cmap=&quot;gray&quot;)
display(img)
&lt;/pre&gt;La siguiente imagen muestra una captura de la ejecución del código.&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWQ04MUKIYvLk5pQEBel8bZJyNAQ04dUse2jcWETyuwT2bxe3Ovwlrkub7WC6tNKQ7ofS20TUA18dUIgafPX68ZxEigiVcPd_gqqL0sY0xlrAVD5YyJW72rWpPF4DpwAsj4884gqsYaRI/s1600/mnist2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWQ04MUKIYvLk5pQEBel8bZJyNAQ04dUse2jcWETyuwT2bxe3Ovwlrkub7WC6tNKQ7ofS20TUA18dUIgafPX68ZxEigiVcPd_gqqL0sY0xlrAVD5YyJW72rWpPF4DpwAsj4884gqsYaRI/s1600/mnist2.png&quot; data-original-width=&quot;415&quot; data-original-height=&quot;319&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Y ¡parece que funciona!&lt;br /&gt;
Pero ¿qué tasa real de acierto consigue k-nn con el &lt;i&gt;dataset&lt;/i&gt; MNIST? Si tenéis curiosidad os propongo que probéis con todo el conjunto de test. Eso sí, son 10.000 imágenes de prueba, así que un poco de paciencia cuando lancéis la ejecución. En la prueba que yo he hecho contra todo el conjunto de test, de las diez mil imágenes el algoritmo ha clasificado correctamente 9.688. Esto es, una tasa de acierto de 0.968 (9.688 / 10.000). O lo que es lo mismo, ha acertado un 96,8% de las veces. No está nada mal ¿verdad?&lt;br /&gt;
&lt;br /&gt;
</description><link>http://www.ellaberintodefalken.com/2019/02/knn-mnist.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0dSO-AlEy6sP6Yk6lk7_XKvaS4cgLXgpOo3ljk5fwpcKqTOH3L0CxjuCCLixlH8m9Io3KgQzA1uBDEAzD3msOJ9fRL9Tcn8qgthDf5a81gkyIcmmoEg-dZJy49I0ezSASWZxfzKdDoxk/s72-c/mnist.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-8916546784952158611</guid><pubDate>Fri, 22 Feb 2019 22:10:00 +0000</pubDate><atom:updated>2019-03-10T22:45:43.211+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">aprendizaje automático</category><category domain="http://www.blogger.com/atom/ns#">k nearest neighbors</category><category domain="http://www.blogger.com/atom/ns#">k vecinos más cercanos</category><category domain="http://www.blogger.com/atom/ns#">k-nn</category><category domain="http://www.blogger.com/atom/ns#">knn</category><category domain="http://www.blogger.com/atom/ns#">machine learning</category><title>Clasificación con k-nearest neighbors</title><description>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVREhcFvzhw0P5CzoF3emq0J5wPmIw_SDiv019vdM1PSpkr_pQwly-DKhEkavLSwaUOzPaFb0QAZp-Q0CUf-pYuIE-0eKzpLBTuwxyh9U3mz_cXZRDqMlImquAwfDMH8oDTJM9sg5Vwh8/s1600/nearest_neighbor_people.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVREhcFvzhw0P5CzoF3emq0J5wPmIw_SDiv019vdM1PSpkr_pQwly-DKhEkavLSwaUOzPaFb0QAZp-Q0CUf-pYuIE-0eKzpLBTuwxyh9U3mz_cXZRDqMlImquAwfDMH8oDTJM9sg5Vwh8/s400/nearest_neighbor_people.png&quot; width=&quot;400&quot; height=&quot;326&quot; data-original-width=&quot;750&quot; data-original-height=&quot;611&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
En un artículo anterior hablamos de &lt;a href=&quot;https://www.ellaberintodefalken.com/2019/02/matrices-numpy.html&quot;&gt;NumPy&lt;/a&gt; y me hubiera gustado completarlo con algún ejemplo concreto. Así que voy a aprovechar este artículo para presentaros un algoritmo muy usado en machine learning y de camino a ilustrarlo con código Python (y la librería NumPy). &lt;br /&gt;
El algoritmo k-nearest neighbors (abreviado como k-nn) o en español, k vecinos más cercanos, es un algoritmo conceptualmente sencillo y también fácil de implementar, pero que para ciertas tareas puede tener una eficacia más que razonable. En lo que sigue os voy a contar cómo funciona y lo vamos a implementar en Python. En un siguiente artículo, aplicaremos el algoritmo a un problema del mundo real.&lt;br /&gt;
K-nn pertenece a la familia de algoritmos de clasificación supervisada, es decir, que necesitan la intervención humana para realizar su aprendizaje. Esta familia de algoritmos son capaces de clasificar elementos dentro de una serie de categorías prefijadas. De forma genérica, esta tarea se llama entrenamiento, y consiste en ofrecer al sistema ejemplos de cada una de las categorías posibles (a este conjunto de datos se le suele llamar &lt;i&gt;dataset&lt;/i&gt; o conjunto de entrenamiento). Cuando se ingresan nuevos elementos al sistema éste lo asignará una de las categorías basándose en lo &quot;aprendido&quot; durante el entrenamiento. El proceso de asignar una categoría se llama etiquetado. Hay multitud de algoritmos de clasificación siendo K-nn uno de los más sencillos, por lo que es ideal para empezar a dar los primeros pasos en el aprendizaje automático.&lt;br /&gt;
Cuando entrenamos un clasificador hay que elegir en qué características del conjunto de entrenamiento ha de fijarse a la hora de clasificarlos. Por ejemplo, si un banco quiere crear un sistema que ayude a decidir si se aprueba la solicitud de préstamo de un cliente, un par de datos interesantes es el sueldo y las deudas que tenga el cliente (en realidad habría que considerar más, pero vamos a simplificar). En machine learning las características que vamos a usar para entrenar al sistema se llaman atributos (&lt;i&gt;features&lt;/i&gt; en inglés). Como tenemos dos atributos vamos a trabajar con datos de dimensión dos. Esto quiere decir que, por cada ejemplo introducido durante el entrenamiento (obtenidos de antiguos clientes), informaremos de los dos atributos (sueldo y deuda) además de a la clase a la que pertenece, que en este caso podríamos etiquetar como &quot;Pagó&quot; o &quot;No pagó&quot; para indicar si ese cliente en concreto pagó el préstamo o por el contrario tuvo problemas para hacerlo.  &lt;br /&gt;
Dado que sólo tenemos dos atributos (sueldo y deuda) podríamos representar fácilmente en una gráfica. Si el eje de ordenadas representa el sueldo y el de las abscisas representa la deuda, podemos dibujar un punto por cada cliente. Siendo el punto de un color diferente dependiendo de la clase a la que pertenece. En la siguiente figura hemos representado con puntos azules los clientes que pagaron y de rojo los que no (los valores de los ejes están en miles de euros). La pregunta es ¿debemos conceder el préstamo al nuevo cliente representado por el punto verde?&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSewdHlyKKlyAhCghZ-VkaJY4MB0jJgUgCfRDg39sUyIheXf1CaPjncFyMa9oaDaYDgW2NMpJy0GdDgS-XnILrinJExh3RFjc3XYeyskvmowahy1xNn6CDblHEDMOsFyZzn0an_j6eNJU/s1600/prestamo.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSewdHlyKKlyAhCghZ-VkaJY4MB0jJgUgCfRDg39sUyIheXf1CaPjncFyMa9oaDaYDgW2NMpJy0GdDgS-XnILrinJExh3RFjc3XYeyskvmowahy1xNn6CDblHEDMOsFyZzn0an_j6eNJU/s400/prestamo.png&quot; width=&quot;400&quot; height=&quot;262&quot; data-original-width=&quot;789&quot; data-original-height=&quot;516&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
He separado visualmente las dos clases para que quede más claro. Como no siempre será obvio cómo se debe etiquetar el nuevo dato, necesitamos un algoritmo que nos diga si el punto verde debe ser categorizado en una clase o en la otra. El algoritmo es muy sencillo:&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;k = número de vecinos a tener en cuenta&lt;/li&gt;
&lt;li&gt;Calculamos la distancia entre el nuevo punto y todos los del conjunto de entrenamiento&lt;/li&gt;
&lt;li&gt;Nos quedamos con los k vecinos más cercanos y etiquetamos el nuevo punto según la clase a la que pertenezcan la mayoría de sus k vecinos&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
La idea es que el nuevo punto pertenecerá a la misma clase que la de sus vecinos más cercanos, así que la primera decisión a tomar es ¿cuántos vecinos son suficientes vecinos? No hay una respuesta fácil y tampoco quiero entrar en mucho detalle aquí para no confundirte demasiado. La respuesta corta es: prueba con distintos valores para ver cuál funciona mejor con tu conjunto de datos. la respuesta larga la dejamos para otro día. Si observas la siguiente figura, la etiqueta que le corresponde al nuevo punto no es la misma si usamos tres vecinos que si usamos cinco.  &lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjI_TGoiJtpFwq74p_Xz7ml-Fg-RQkwBah554dbqyI38FoKoHmCKAV4uT0qo6YZimZojS8UI6gUh2JvMOH7Bi4cu1lCtCIKWRw6p-77yhvD5QvEZo3at107b8SlTGE6SEKjqv0yXpGtEyU/s1600/knn.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjI_TGoiJtpFwq74p_Xz7ml-Fg-RQkwBah554dbqyI38FoKoHmCKAV4uT0qo6YZimZojS8UI6gUh2JvMOH7Bi4cu1lCtCIKWRw6p-77yhvD5QvEZo3at107b8SlTGE6SEKjqv0yXpGtEyU/s1600/knn.png&quot; data-original-width=&quot;220&quot; data-original-height=&quot;200&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Otra concepto que no he definido es el de la distancia entre puntos. Hay varias medidas de distancia, cada una con sus propiedades, sus ventajas y desventajas. Tampoco quiero entrar ahora a explicar cuál es más conveniente para cada aplicación, así que nos quedamos con una que seguramente te suena de tus días de instituto: la distancia euclidiana.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijiyQpo_aNi5Pk5q93MEBE2cvZvmrpz7lSmkM7As0tfX99LOwwzTKcCpasqCyIUAnJYMy47TeL6K5hyphenhyphenze36Koj9DU2iEA41clZN8ASHryTjizzfCSqKQaAz2-jNhjww1Ca3Hcpp5ecP2o/s1600/Distance_Formula.svg.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijiyQpo_aNi5Pk5q93MEBE2cvZvmrpz7lSmkM7As0tfX99LOwwzTKcCpasqCyIUAnJYMy47TeL6K5hyphenhyphenze36Koj9DU2iEA41clZN8ASHryTjizzfCSqKQaAz2-jNhjww1Ca3Hcpp5ecP2o/s1600/Distance_Formula.svg.png&quot; data-original-width=&quot;250&quot; data-original-height=&quot;200&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Te refresco la memoria. La distancia entre dos puntos P y Q en un plano (si Pitágoras no andaba muy desencaminado) se obtiene con la siguiente ecuación.&lt;br /&gt;
\[d_E(P,Q)=\sqrt{(p_1-q_1)^2 + (p_2-q_2)^2}\]&lt;br /&gt;
Si te estás preguntando qué ocurre si nuestros atributos tienen una dimensionalidad mayor de dos, tranquilo. Lo bueno del Álgebra es que funciona bien en dos dimensiones, en tres, en cuatro o en las que haga falta, así que podemos generalizar la ecuación para cualquier cantidad de atributos. &lt;br /&gt;
\[d_E(P,Q)=\sqrt{\sum_{i=1}^n (p_i-q_i)^2}\]&lt;br /&gt;
Lo bueno de esta ecuación es que usando cálculo matricial un ordenador puede resolverla en un plis plas. Y aquí es donde NumPy nos va a mostrar su verdadero poder (bueno, con dos atributos no lo vamos a notar mucho, pero cuando sean miles será de agradecer).&lt;br /&gt;
Hechas las presentaciones, vamos al lío. He implementado el algoritmo en un notebook para &lt;a href=&quot;https://jupyter.org/&quot;&gt;Jupyter&lt;/a&gt; que os he dejado en el siguiente enlace: &lt;a href=&quot;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/KNN.ipynb&quot;&gt;https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/KNN.ipynb&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Empezamos importando las bibliotecas que vamos a necesitar.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import numpy as np
import random
from IPython.display import display
import matplotlib.pyplot as plt
&lt;/pre&gt;Vamos a utilizar NumPy para las operaciones con matrices y &lt;a href=&quot;https://matplotlib.org/&quot;&gt;Matplotlib&lt;/a&gt; para generar algunas gráficas. Para mostrar las gráficas en el notebook Jupyter se importa también la función display. &lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;n = 50 # 50 muestras aleatorias
# generamos dos centros para las dos clases de puntos para entrenamiento 
c1 = [random.randint(0,1000), random.randint(0,1000)]
c2 = [random.randint(0,1000), random.randint(0,1000)]
# generamos las muestras aleatorias alrededor de los centros
tuplasC1 = []
tuplasC2 = []
labelsC1 = []
labelsC2 = []
for i in range(int(n/2)):
    tuplasC1.append([c1[0] + random.randint(-100,100), c1[1] + random.randint(-100,100)])
    labelsC1.append(0)
    tuplasC2.append([c2[0] + random.randint(-100,100), c2[1] + random.randint(-100,100)])
    labelsC2.append(1)

labels = labelsC1 + labelsC2
puntos = np.matrix(tuplasC1 + tuplasC2)
# dibujamos los puntos
plt.scatter([puntos[:int(n/2),0]], [puntos[:int(n/2),1]], c=&quot;b&quot;)
plt.scatter([puntos[int(n/2):,0]], [puntos[int(n/2):,1]], c=&quot;g&quot;)
display(plt)
&lt;/pre&gt;En machine learning tan importante como el algoritmo que hayamos decidido usar son los datos con los que lo vamos a alimentar. En este caso vamos a fabricarlos de forma aleatoria. El código anterior genera cincuenta muestras (o puntos o ejemplos, como prefieras) pero he hecho un poco de trampa. Los he creado alrededor de dos puntos distantes para que visualmente queden bien diferenciados. Para mantener la generalidad en todo momento he etiquetado con el valor 0 los puntos pertenecientes a la primera clase y con 1 los de la segunda. Una vez tenemos los datos los ponemos en una matriz de NumPy para poder operar con ellos. Finalmente mostramos la gráfica con los puntos, en azul los de la clase 0 y en verde los de la clase 1. Este es el resultado.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjPvl4oXOXXl3FmGk4pBLNZCTHKBScACfVti0aTS2sfrUKd7zjPCzjQe7-tOJfBnibOL7nbtqYKknFTP8ZiStWVt8fSi8Kgh-hNNq_b6J6LrgAslq6An05xyqrt4k7lrDLydtdOxb9DRI/s1600/scatter1.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjPvl4oXOXXl3FmGk4pBLNZCTHKBScACfVti0aTS2sfrUKd7zjPCzjQe7-tOJfBnibOL7nbtqYKknFTP8ZiStWVt8fSi8Kgh-hNNq_b6J6LrgAslq6An05xyqrt4k7lrDLydtdOxb9DRI/s1600/scatter1.png&quot; data-original-width=&quot;387&quot; data-original-height=&quot;252&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
También necesitamos datos nuevos, es decir, aquellos que queremos clasificar.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# generamos puntos aleatorios nuevos para clasificarlos
n_test = 10
tuplas = []
for i in range(n_test):
    tuplas.append([random.randint(0,1000), random.randint(0,1000)])
    
puntos_test = np.matrix(tuplas)
# dibujamos los nuevos puntos junto con los anteriores
plt.scatter([puntos[:int(n/2),0]], [puntos[:int(n/2),1]], c=&quot;b&quot;)
plt.scatter([puntos[int(n/2):,0]], [puntos[int(n/2):,1]], c=&quot;g&quot;)
plt.scatter([puntos_test[:,0]], [puntos_test[:,1]], c=&#39;r&#39;, marker=&#39;x&#39;)
display(plt)
&lt;/pre&gt;Generamos diez puntos nuevos de forma totalmente aleatoria y mostramos la gráfica para ver por donde han caído (se muestran como X de color rojo).&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVhAw4gb26iVbr8ZCcfhEHUxx9qEDQcu03Rm_RUadAD7Z6-bxtjrZKj4cDFm1HoqbfWn10bJqhnBg0TWJBj77JSZ4sBq7eCVWH4IHtr8HCOxQSXbJ67uIDrZ7OrsGxABJ0EIe_0zQNsto/s1600/scatter2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVhAw4gb26iVbr8ZCcfhEHUxx9qEDQcu03Rm_RUadAD7Z6-bxtjrZKj4cDFm1HoqbfWn10bJqhnBg0TWJBj77JSZ4sBq7eCVWH4IHtr8HCOxQSXbJ67uIDrZ7OrsGxABJ0EIe_0zQNsto/s1600/scatter2.png&quot; data-original-width=&quot;388&quot; data-original-height=&quot;252&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Ha llegado el momento de la verdad, veamos cómo se comporta k-nn con nuestros datos.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# usamos KNN para clasificar los nuevos puntos
k = 5 # número de vecinos

pred_label = []
# clasificamos cada uno de los puntos nuevos
for i in range(puntos_test.shape[0]):
    distances = []
    # por cada punto calculamos la distancia con los puntos de entrenamiento 
    for j in range(puntos.shape[0]):
        dist = np.sqrt(np.sum(np.square(puntos[j] - puntos_test[i])))
        distances.append((dist, labels[j])) # guardamos las etiquetas y la distancia

    # ordenamos por distancia y nos quedamos con los k vecinos más cercanos
    distances.sort(key=lambda x: x[0])
    neighbors = distances[:k]
    # contamos los votos para ver qué etiqueta gana
    votes = [0,0]
    for neighbor in neighbors:
        votes[neighbor[1]] = votes[neighbor[1]] + 1
    # obtenemos la etiqueta ganadora
    pred_label.append(votes.index(max(votes)))
&lt;/pre&gt;He preferido hacer una implementación lo más clara posible en detrimento de la eficiencia, ya que con NumPy se puede realizar toda la operación en un sólo paso sin necesidad de usar los dos bucles que he usado en el código, pero creo que así es mucho más intuitivo. Pues bien, el primer bucle &lt;i&gt;i&lt;/i&gt; va recorriendo la matriz de los puntos a etiquetar (recuerda que eran 10) y en el bucle interno &lt;i&gt;j&lt;/i&gt; los comparamos con cada uno de los ejemplos o puntos del &lt;i&gt;dataset&lt;/i&gt; de entrenamiento (recuerda que eran 50). Como ves no hay mucho misterio: calculamos la distancia entre ambos puntos operando matricialmente con NumPy y almacenamos dicha distancia junto a la clase (etiqueta) a la que pertenece el punto con el que estamos comparando. Una vez hemos medido todas las distancias las ordenamos y nos quedamos con las k menores (en este caso k=5). Ahora necesitamos saber a qué clases pertenecen los k vecinos más cercanos. Como hemos almacenado su etiqueta junto con la distancia, sometemos la decisión a un proceso de votación. Cada uno de los k vecinos vota con su etiqueta y la ganadora se asigna al nuevo punto.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;# separar los datos clasificados para dibujarlos
g0 = []
g1 = []
for i in range(len(pred_label)):
    if pred_label[i] == 0:
        g0.append([puntos_test[i,0], puntos_test[i,1]])
    else:
        g1.append([puntos_test[i,0], puntos_test[i,1]])
        
grupo0 = np.matrix(g0)
grupo1 = np.matrix(g1)

# mostrar datos ya clasificados
plt.scatter([puntos[:int(n/2),0]], [puntos[:int(n/2),1]], c=&quot;b&quot;)
plt.scatter([puntos[int(n/2):,0]], [puntos[int(n/2):,1]], c=&quot;g&quot;)
plt.scatter([grupo0[:,0]], [grupo0[:,1]], c=&#39;b&#39;, marker=&#39;x&#39;)
plt.scatter([grupo1[:,0]], [grupo1[:,1]], c=&#39;g&#39;, marker=&#39;x&#39;)
display(plt) 
&lt;/pre&gt;Ahora que hemos clasificados los nuevos puntos los metemos en una matriz simplemente para representarlos en una gráfica, junto con el color definitivo que le corresponde según han sido clasificados por k-nn. De esta forma comprobamos visualmente si la clasificación ha ido según lo esperado.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaZk0nIub89pc2yTa-Dv9fwsbJ5WAxtnQ-GPoNeJnUeLKRCWmpWBG2Hd8-gsg7Q0BE9Q_OQuQNnE4CvUYSNbIsUvJiab6FYSaSh5xX-nlTgnuCJbR0aoe09jK_QuXatlO3CC2Mey75hAA/s1600/scatter3.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaZk0nIub89pc2yTa-Dv9fwsbJ5WAxtnQ-GPoNeJnUeLKRCWmpWBG2Hd8-gsg7Q0BE9Q_OQuQNnE4CvUYSNbIsUvJiab6FYSaSh5xX-nlTgnuCJbR0aoe09jK_QuXatlO3CC2Mey75hAA/s1600/scatter3.png&quot; data-original-width=&quot;388&quot; data-original-height=&quot;252&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Para ser un algoritmo que conceptualmente no es nada complejo parece que realiza un buen trabajo. Aunque claro, nuestro ejemplo no es muy impresionante ¿verdad? te preguntarás si todo esto que te he contado sirve realmente para algo útil. Una de las aplicaciones más interesantes de este algoritmo son los sistemas de recomendación. Eso que hace Netflix cuando te recomienda una película según tu historial de visionado o Amazon cuando te recomienda productos en base a tus compras anteriores. Pero también tiene otras utilidades, algunas de ellas bastante más espectaculares que la que acabamos de ver, pero te dejo con la intriga hasta el próximo artículo en el que espero impresionarte un poco más usando a nuestro nuevo amigo k-nn.</description><link>http://www.ellaberintodefalken.com/2019/02/clasificacion-con-k-nearest-neighbors.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVREhcFvzhw0P5CzoF3emq0J5wPmIw_SDiv019vdM1PSpkr_pQwly-DKhEkavLSwaUOzPaFb0QAZp-Q0CUf-pYuIE-0eKzpLBTuwxyh9U3mz_cXZRDqMlImquAwfDMH8oDTJM9sg5Vwh8/s72-c/nearest_neighbor_people.png" height="72" width="72"/><thr:total>1</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-8295641656115627950</guid><pubDate>Sat, 16 Feb 2019 23:47:00 +0000</pubDate><atom:updated>2019-03-10T22:43:58.987+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">machine learning</category><category domain="http://www.blogger.com/atom/ns#">matrices</category><category domain="http://www.blogger.com/atom/ns#">NumPy</category><title>Breve introducción a las matrices con NumPy</title><description>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOGcMUgiNqtQrlfwmgcZqIwwwB8nyS8WCqyZW5pUY6H104LWWeYoX5KgD933MHLZ2l5Y8QHLkAV7GwIbHsyQEPP0J0K_x9VW7qpzqQiWGYTVnaf3x4lth-_1AZK1qc9y8-pooUeIT-jUg/s1600/1200px-NumPy_logo.svg.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOGcMUgiNqtQrlfwmgcZqIwwwB8nyS8WCqyZW5pUY6H104LWWeYoX5KgD933MHLZ2l5Y8QHLkAV7GwIbHsyQEPP0J0K_x9VW7qpzqQiWGYTVnaf3x4lth-_1AZK1qc9y8-pooUeIT-jUg/s640/1200px-NumPy_logo.svg.png&quot; width=&quot;640&quot; height=&quot;254&quot; data-original-width=&quot;600&quot; data-original-height=&quot;238&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
La librería &lt;a href=&quot;http://www.numpy.org/&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;NumPy&lt;/a&gt; traslada la potencia de entornos como Matlab u Octave al lenguaje de programación Python. Acometer un proyecto de Machine Learning o IA requiere un manejo intensivo y eficaz de cálculo matricial, y NumPy pone a nuestra disposición toda esa potencia. La libraría dispone de un montón de herramientas y capacidades que no es posible acometer en un artículo como este, sin embargo, me dispongo a contaros lo más importante en cuanto al manejo de matrices con NumPy.&lt;br /&gt;
&lt;br /&gt;
Empecemos por un ejemplo sencillo: vamos a crear una matriz.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;import numpy as np

matrix1 = np.matrix([[1, 2, 3, 4], [5, 6, 7, 8]])
print (matrix)
[[1 2 3 4]
 [5 6 7 8]]
&lt;/pre&gt;La primera línea importa la librería para poder usarla en el programa y seguidamente se crea una matriz de dimensión 2x4. Alternativamente, podemos usar una sintaxis al estilo de Matlab.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;matrix1 = np.matrix(&#39;1 2 3 4; 5 6 7 8&#39;)
&lt;/pre&gt;Existen otras tres funciones que nos facilitan la creación de matrices: &lt;i&gt;zeros()&lt;/i&gt; crea una matriz inicializada con el valor 0, &lt;i&gt;ones()&lt;/i&gt; hace lo mismo pero con todos sus valores a 1. Finalmente, &lt;i&gt;eye()&lt;/i&gt; crea la matriz identidad del tamaño que especifiquemos como parámetro. &lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;zeros = np.zeros([2,3])
print (zeros)
[[ 0.  0.  0.]
 [ 0.  0.  0.]]

ones = np.ones([3, 2])
print (ones)
[[ 1.  1.]
 [ 1.  1.]
 [ 1.  1.]]

eye = np.eye(4)
print (eye)
[[ 1.  0.  0.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0.  1.  0.]
 [ 0.  0.  0.  1.]]
&lt;/pre&gt;Podemos acceder a valores concretos de la matriz o a porciones completas. Vamos a verlo con algunos ejemplos:&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;matrix1 = np.matrix(&#39;1 2 3; 4 5 6; 7 8 9&#39;)
print (matrix1)
[[1 2 3]
 [4 5 6]
 [7 8 9]]

print (matrix1[0])
[[1 2 3]]
&lt;/pre&gt;En este ejemplo, estamos accediendo a la primera fila (fila 0) de la matriz. Si queremos acceder a un sólo elemento podemos hacerlo así:&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;print (matrix1[0,1])
2
&lt;/pre&gt;También podemos acceder a una porción de una fila:&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;print (matrix1[1, 1:3])
[[5 6]]
&lt;/pre&gt;Y lo mismo es aplicable a las columnas:&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;print (matrix1[0:3, 0])
[[1]
 [4]
 [7]]
&lt;/pre&gt;Las funciones &lt;i&gt;size()&lt;/i&gt; y &lt;i&gt;shape()&lt;/i&gt; nos devuelven el número de elementos y la dimensión de la matriz respectivamente.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;print (matrix1.size)
9

print (matrix1.shape)
(3, 3)
&lt;/pre&gt;Otra información útil que podemos obtener es el valor máximo, el mínimo y la media de los valores de la matriz:&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;print (matrix1.max())
9

print (matrix1.min())
1

print (matrix1.mean())
5.0&lt;/pre&gt;No sólo es posible acceder a los elementos de la matriz para consultarlos, también pueden ser modificados:&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;matrix1 = np.matrix(&#39;1 2; 3 4&#39;)
print (matrix1)
[[1 2]
 [3 4]]

matrix1[1, 0] = 10
print(matrix1)
[[ 1  2]
 [10  4]]

matrix1[0] = [6, 7]
print (matrix1)
[[ 6  7]
 [10  4]]
&lt;/pre&gt;El primer ejemplo modifica un valor de la matriz y el segundo sustituye una fila entera. Como se puede observar, se sigue la misma notación que hemos usado para acceder a los valores de la matriz un poco más arriba. Otra función útil es &lt;i&gt;flatten()&lt;/i&gt;, que convierte la matriz en un array de una dimensión con todos los elementos de la matriz.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;print (matrix1.flatten())
[[ 6  7 10  4]]
&lt;/pre&gt;Hasta ahora hemos visto como se crean las matrices, como se accede a ellas y como cambiamos sus valores. Abordamos ahora las operaciones matemáticas. Empezamos por una que se usa bastante, la matriz traspuesta.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;print(matrix1.T) 
[[ 6 10]
 [ 7  4]]
&lt;/pre&gt;Sobre las matrices se pueden aplicar las operaciones matemáticas básicas, tanto entre matrices como entre una matriz y un escalar. Los siguientes ejemplos ilustran la suma de una matriz y un escalar y la suma entre dos matrices.&lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;matrix1 = np.matrix(&#39;1 2; 3 4&#39;)
matrix2 = np.matrix(&#39;5 6; 7 8&#39;)
print (matrix1 + 2)
[[3 4]
 [5 6]]

print (matrix1 + matrix2)
[[ 6  8]
 [10 12]]
&lt;/pre&gt;Nos resta por ver la multiplicación de matrices, la más usada junto con la traspuesta. &lt;br /&gt;
&lt;pre class=&quot;prettyprint linenums&quot;&gt;matrix1 = np.matrix(&#39;1 2 3; 4 5 6; 7 8 9&#39;)
matrix2 = np.matrix(&#39;1 2; 3 4; 5 6&#39;)
print (matrix1 * matrix2)
[[ 22  28]
 [ 49  64]
 [ 76 100]]
&lt;/pre&gt;NumPy tiene muchas más posibilidades, pero considero que esto es lo básico para empezar a trabajar con ella. En próximos artículos tendremos la oportunidad de hacer uso de ella en algún que otro ejemplo práctico.</description><link>http://www.ellaberintodefalken.com/2019/02/matrices-numpy.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOGcMUgiNqtQrlfwmgcZqIwwwB8nyS8WCqyZW5pUY6H104LWWeYoX5KgD933MHLZ2l5Y8QHLkAV7GwIbHsyQEPP0J0K_x9VW7qpzqQiWGYTVnaf3x4lth-_1AZK1qc9y8-pooUeIT-jUg/s72-c/1200px-NumPy_logo.svg.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-1940626663211673021</guid><pubDate>Mon, 14 Jan 2019 22:41:36 +0000</pubDate><atom:updated>2026-07-15T11:03:54.275+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Electrónica</category><category domain="http://www.blogger.com/atom/ns#">FreeRTOS</category><category domain="http://www.blogger.com/atom/ns#">MSP430</category><category domain="http://www.blogger.com/atom/ns#">post</category><category domain="http://www.blogger.com/atom/ns#">Semáforos</category><title>Semáforos y más semáforos</title><description>&lt;p&gt;Que levante la mano quién no haya hecho el típico circuito de semáforo para probar una plataforma o un microcontrolador nuevo. El semáforo se podría considerar como el &lt;em&gt;HolaMundo&lt;/em&gt; de la electrónica. Cualquier alumno de tecnología o de ingeniería podrá corroborar lo que digo.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOlwEV72SLGbnFOwxWhBbB7EvFDehF5KMV6pz3st5-KSlp00CNhpJtD3RdXCawEFWT1LWN0wZUOLS1HdP-Ukr5HdlOUEVIgs_aMOFvrmnBuzgAxM0LEMoP9amIqtQ8nrqq_-dtxZr_jilPoMoXV5erQ0u-gqECvJc7ZbAD_Gxqeb_z7ooKkpCNpip3vmo/s1024/a4be0-msp430semaforo-e1618399708341.jpeg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Semaforos&quot; border=&quot;0&quot; width=&quot;600&quot; data-original-height=&quot;576&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOlwEV72SLGbnFOwxWhBbB7EvFDehF5KMV6pz3st5-KSlp00CNhpJtD3RdXCawEFWT1LWN0wZUOLS1HdP-Ukr5HdlOUEVIgs_aMOFvrmnBuzgAxM0LEMoP9amIqtQ8nrqq_-dtxZr_jilPoMoXV5erQ0u-gqECvJc7ZbAD_Gxqeb_z7ooKkpCNpip3vmo/s600/a4be0-msp430semaforo-e1618399708341.jpeg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;
  
  
&lt;p&gt;Sobre este particular, José Antonio Vacas dio una interesante charla en Malakabot 2017 que os dejo aquí debajo.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe class=&quot;BLOG_video_class&quot; allowfullscreen=&quot;&quot; youtube-src-id=&quot;sqzG1P3iVLA&quot; width=&quot;400&quot; height=&quot;322&quot; src=&quot;https://www.youtube.com/embed/sqzG1P3iVLA&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;p&gt;Está claro que como ejemplo inicial, el semáforo puede ser un diseño útil, pero después hay que ir más allá. Si andáis faltos de imaginación, José Antonio os deja en su &lt;a href=&quot;https://github.com/javacasm/NoMasSemaforos&quot;&gt;GitHub&lt;/a&gt; algunas ideas que os pueden servir de inspiración&lt;/p&gt;

&lt;p&gt;Pero si pensabais que tras todo este movimiento se habían acabado los semáforos, estáis muy equivocados. Tras esto de los semáforos se encuentra un interesante reto propuesto por Cesar García del podcast &lt;a href=&quot;https://www.lahoramaker.com/&quot;&gt;La Hora Maker&lt;/a&gt; y &lt;a href=&quot;https://barcelona.lcieducation.com/es/sobre-nosotros/nuestros-profesores/david-cuartielles&quot;&gt;David Cuartielles&lt;/a&gt;. Se trata de un proyecto colectivo para realizar semáforos en la mayor cantidad de modos posible. Dejo que ellos mismos os presenten el proyecto.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe class=&quot;BLOG_video_class&quot; allowfullscreen=&quot;&quot; youtube-src-id=&quot;5icR11wTp58&quot; width=&quot;600&quot; height=&quot;498&quot; src=&quot;https://www.youtube.com/embed/5icR11wTp58&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;p&gt;Si queréis participar sólo tenéis que acudir al &lt;a href=&quot;https://github.com/dcuartielles/semaforos&quot;&gt;GitHub&lt;/a&gt; de David y seguir las instrucciones. Yo por mi parte me he animado a enviar dos diseños: Uno con el TI MSP430 y otro con FreeRTOS.&lt;/p&gt;

&lt;p&gt;El diseño del semáforo es muy simple y se muestra en el siguiente esquema, donde se indica a qué pines han de conectarse los LED. En este caso he usado una placa de desarrollo LaunchPad basada en el microcontrolador MSP430. También os dejo el código C del semáforo.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhg27T1Dhs8oxkc6DmJXYUH2EqPdADHjVcLp20mnFkVHp5kppgNflI8TWgFOdLtKWg_Tkgpvfj5uvRahieE7sMFS2ex3c0m-WrRyLs6J8fSvnke0itU9o2wuWGfuVcoRIbrh6XhE_tj_vHQM5pSW4mH-pacM2bttI0NtA182w8YcetTl-iaZntUWW67OQE/s1600/c62ae-semaforomsp430.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;229&quot; data-original-width=&quot;587&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhg27T1Dhs8oxkc6DmJXYUH2EqPdADHjVcLp20mnFkVHp5kppgNflI8TWgFOdLtKWg_Tkgpvfj5uvRahieE7sMFS2ex3c0m-WrRyLs6J8fSvnke0itU9o2wuWGfuVcoRIbrh6XhE_tj_vHQM5pSW4mH-pacM2bttI0NtA182w8YcetTl-iaZntUWW67OQE/s1600/c62ae-semaforomsp430.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;pre style=&quot;font-size:medium;&quot;&gt;#include &amp;lt;msp430.h&amp;gt;
enum estados { ROJO, AMARILLO, VERDE };
int color;

int main(void)
{
	WDTCTL = WDTPW + WDTHOLD; 	// Detiene el watchdog
	P3DIR |= BIT0;            	// P3.0 (LED rojo) como salida digital
	P3DIR |= BIT1;            	// P3.1 (LED amarillo) como salida digital
	P2DIR |= BIT6;            	// P2.6 (LED verde) como salida digital
	P1REN |= BIT1;		  	// Resistencia en P1.1 (pulsador derecho)
	P3OUT |= BIT1;		  	// Modo pull-up
	P2OUT |= BIT1;
	P1IE  |= BIT1;			// habilita la interrupción de P1.1
	P1IES |= BIT1;			// Interrupción por flanco de bajada
	P1IFG &amp;amp;= ~(BIT1);		// baja flag de interrupción de P1.1 (por si acaso)
    color = ROJO;
	P3OUT |= BIT0; 			// Empezamos encendiendo el rojo
	P3OUT &amp;amp;= ~BIT1;			// apagamos el amarillo
	P2OUT &amp;amp;= ~BIT6;			// apagamos el verde
    // Activar interrupciones
	__bis_SR_register(GIE); 	// también puede usarse: __enable_interrupt()
    // Modo bajo consumo pero atendemos interrupciones
	__low_power_mode_0();		// Entrada LPM0, habilita interrupciones
	__no_operation();         	// Para depuración
    }
    
// Rutina de gestión de interrupciones de P1
#pragma vector=PORT1_VECTOR
__interrupt
void P1_ISR(void) {
	extern int color;
	if (P1IFG &amp;amp; BIT1) {		// ¿fuente de la interrupción es P1.1 (pulsador)?
		switch (color) {
			case ROJO:
				color = AMARILLO;
				P3OUT &amp;amp;= ~BIT0;	// apagamos el rojo
				P3OUT |= BIT1;	// encendemos el amarillo
				break;
			case AMARILLO:
				color = VERDE;
				P3OUT &amp;amp;= ~BIT1;	// apagamos el amarillo
				P2OUT |= BIT6;	// encendemos el verde
				break;
			case VERDE:
				color = ROJO;
				P2OUT &amp;amp;= ~BIT6;	// apagamos el verde
				P3OUT |= BIT0;	// encendemos el rojo
				break;
		}
		P1IFG &amp;amp;= ~BIT1;		// puesta a cero del flag de interrupción P1.1
	}
}
&lt;/pre&gt;

&lt;p&gt;Para el ejemplo de semáforo con FreeRTOS he usado la placa de desarrollo LaunchPad CC3200 que utiliza una MCU ARM Cortex A4. El esquema de conexionado con la placa y el código son los siguientes.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi18cX1wmjYnOLYec4nYJ3qHCDYiXlNmjhRGkrd8pODBK7BDJOkasNpvu8sXmyQ_JWFeTU5sxAvCJai35rH88pRtSMQH4e6zWc6tvBdxnNbUOEUcV4-tKg1-y3NgeLu6OxBsIyY5Vn9uUok0i3WoS5_wl6pMveC91YDUDSvEj3nHV_eR68saMT8_iVeWFc/s1600/f3109-semaforofreertos.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;229&quot; data-original-width=&quot;587&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi18cX1wmjYnOLYec4nYJ3qHCDYiXlNmjhRGkrd8pODBK7BDJOkasNpvu8sXmyQ_JWFeTU5sxAvCJai35rH88pRtSMQH4e6zWc6tvBdxnNbUOEUcV4-tKg1-y3NgeLu6OxBsIyY5Vn9uUok0i3WoS5_wl6pMveC91YDUDSvEj3nHV_eR68saMT8_iVeWFc/s1600/f3109-semaforofreertos.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;pre style=&quot;font-size:medium;&quot;&gt;#include &amp;lt;stdint.h&amp;gt;
#include &amp;lt;stdbool.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &quot;hw_types.h&quot;
#include &quot;hw_ints.h&quot;
#include &quot;hw_memmap.h&quot;
#include &quot;interrupt.h&quot;
#include &quot;rom_map.h&quot;
#include &quot;prcm.h&quot;
#include &quot;gpio.h&quot;
#include &quot;pinmux.h&quot;
#include &quot;FreeRTOS.h&quot;
#include &quot;task.h&quot;

// prototipos de función
void BoardInit(void);

// variables globales
extern void (* const g_pfnVectors[])(void);

//*****************************************************************************
// FreeRTOS User Hook Functions enabled in FreeRTOSConfig.h
//*****************************************************************************
void
vAssertCalled( const char *pcFile, unsigned long ulLine )
{
    //Handle Assert here
    while(1)
    {
    }
}

void vApplicationMallocFailedHook()
{
    //Handle Memory Allocation Errors
    while(1)
    {
    }
}

void vApplicationStackOverflowHook( void *pxTask,
                                   signed char *pcTaskName)
{
    //Handle FreeRTOS Stack Overflow
    while(1)
    {
    }
}

// Board Initialization &amp;amp; Configuration
void BoardInit(void)
{
// Set vector table base
    IntVTableBaseSet((unsigned long)&amp;amp;g_pfnVectors[0]);
    // Enable Processor
    MAP_IntMasterEnable();
    MAP_IntEnable(FAULT_SYSTICK);
    PRCMCC3200MCUInit();
}
  
/*-----------------------------------------------------------*/
//Esta tarea enceinde las luces del semáforo
static void SemaforoBlink( void *pvParameters )
{
//La tarea debe tener un bucle infinito...
	for( ;; )
	{
		// Encender LED rojo
		GPIOPinWrite(GPIOA1_BASE,GPIO_PIN_3,GPIO_PIN_3);
		vTaskDelay(0.5*configTICK_RATE_HZ);
		GPIOPinWrite(GPIOA1_BASE,GPIO_PIN_3,0);
        // Encender LED amarillo
		GPIOPinWrite(GPIOA1_BASE,GPIO_PIN_2,GPIO_PIN_2);
		vTaskDelay(0.5*configTICK_RATE_HZ);
		GPIOPinWrite(GPIOA1_BASE,GPIO_PIN_2,0);
        // Encender LED verde
		GPIOPinWrite(GPIOA1_BASE,GPIO_PIN_1,GPIO_PIN_1);
		vTaskDelay(0.5*configTICK_RATE_HZ);
		GPIOPinWrite(GPIOA1_BASE,GPIO_PIN_1,0);
	}
}
/*-----------------------------------------------------------*/

// Función main
void main()
{ 
	BoardInit();	// Configuración de la placa
    PinMuxConfig();	// Pinmux donde se configuras los puertos para los LED como salida digital
    // Apagamos los LED
    GPIOPinWrite(GPIOA1_BASE,GPIO_PIN_3|GPIO_PIN_2|GPIO_PIN_1,0);
    
	/* creamos la tarea para el semaforo */
	if (xTaskCreate( SemaforoBlink, &quot;Semaforo&quot;, configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY+1, NULL )!=pdPASS)
		while (1);
        vTaskStartScheduler(); // Inicia el planificador de tareas
}
&lt;/pre&gt;

&lt;p&gt;Os animo a que participéis, ya que es un proyecto, además de interesante, bastante divertido.&lt;br&gt;&lt;/p&gt;
</description><link>http://www.ellaberintodefalken.com/2019/01/semaforos-y-mas-semaforos.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOlwEV72SLGbnFOwxWhBbB7EvFDehF5KMV6pz3st5-KSlp00CNhpJtD3RdXCawEFWT1LWN0wZUOLS1HdP-Ukr5HdlOUEVIgs_aMOFvrmnBuzgAxM0LEMoP9amIqtQ8nrqq_-dtxZr_jilPoMoXV5erQ0u-gqECvJc7ZbAD_Gxqeb_z7ooKkpCNpip3vmo/s72-c/a4be0-msp430semaforo-e1618399708341.jpeg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-8445327679146717247</guid><pubDate>Wed, 19 Sep 2018 10:29:00 +0000</pubDate><atom:updated>2019-02-21T23:16:07.358+01:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">machine learning</category><category domain="http://www.blogger.com/atom/ns#">malware</category><title>Selección de atributos relevantes usando la entropía de Shannon</title><description>Hoy en día, la detección de amenazas está cada vez más ligada al aprendizaje automático. Si queremos que un IDS o un antivirus pueda lidiar con amenazas nuevas, además de métodos heurísticos hay que recurrir técnicas de clasificación, una parte importante de la IA. En los problemas de clasificación, una de las tareas a la que nos enfrentamos es seleccionar aquellos atributos que mejor describen la variable objetivo. Esto es, elegir aquellos atributos que aportan más información y tienen mayor correlación con la variable explicada. Por ejemplo, si queremos establecer la probabilidad de que alguien padezca cáncer de pulmón o no, una variable que va a ser muy significativa es si es fumadora. En el otro extremo, el color de los ojos de la persona, por poner un ejemplo muy claro, no aporta información relevante.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWORHbZvWQLnZ9bgiHUPzcLdz-dFUXI7U2laQ8Gc5SQkcXBSjd5dszewp21XUjoWsznOqHRlCvRAjkR1btIl_QAhs7Q9G5Yuk3wnDT7qOt3kt7Vuh1OMFo-9gszJ0IiDw13aPaEwVpuDE/s1600/Claude_Shannon_graffiti.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;300&quot; data-original-width=&quot;400&quot; height=&quot;300&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWORHbZvWQLnZ9bgiHUPzcLdz-dFUXI7U2laQ8Gc5SQkcXBSjd5dszewp21XUjoWsznOqHRlCvRAjkR1btIl_QAhs7Q9G5Yuk3wnDT7qOt3kt7Vuh1OMFo-9gszJ0IiDw13aPaEwVpuDE/s400/Claude_Shannon_graffiti.jpg&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Por desgracia, no siempre es tan obvia la cantidad de información que aporta un atributo, así que vamos a ver un método relativamente sencillo de medirlo.&lt;br /&gt;
&lt;br /&gt;
En 1948, &lt;a href=&quot;http://en.wikipedia.org/wiki/Claude_Shannon&quot;&gt;Claude Shannon&lt;/a&gt; -la fotografía de arriba es un grafitti con su retrato- publicó un artículo llamado &lt;i&gt;&lt;a href=&quot;http://cm.bell-labs.com/cm/ms/what/shannonday/shannon1948.pdf&quot;&gt;A Mathematical Theory of Communication&lt;/a&gt;&lt;/i&gt; donde introducía el concepto de entropía en el contexto de la teoría de la información (de la que Shannon es padre). Simplificando mucho, la entropía es una medida del desorden de un conjunto datos, y se define como: &lt;br /&gt;
&lt;br /&gt;
\[H(X) = -\sum_{i}^{n} p_i \times \log_2(p_i)\] &lt;br /&gt;
&lt;br /&gt;
Donde p&lt;sub&gt;i&lt;/sub&gt; es la probabilidad relativa de aparición de la propiedad i en el conjunto de datos. Por ejemplo, p&lt;sub&gt;1&lt;/sub&gt; podría ser la probabilidad de tener cáncer de pulmón y p&lt;sub&gt;2&lt;/sub&gt; la probabilidad de no tenerlo.&lt;br /&gt;
&lt;br /&gt;
Para entenderlo mejor vamos a ir viéndolo con un ejemplo práctico en R. Vamos a usar el conjunto de datos &lt;i&gt;Titanic&lt;/i&gt; incluido en R. Carguemos el conjunto de datos y veamos cuáles son sus atributos:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&amp;gt; data(Titanic)
&amp;gt; dimnames(Titanic)
$Class
[1] &quot;1st&quot;  &quot;2nd&quot;  &quot;3rd&quot;  &quot;Crew&quot;

$Sex
[1] &quot;Male&quot;   &quot;Female&quot;

$Age
[1] &quot;Child&quot; &quot;Adult&quot;

$Survived
[1] &quot;No&quot;  &quot;Yes&quot;

&amp;gt; margin.table(Titanic)
[1] 2201
&lt;/pre&gt;Así pues, tenemos un conjunto de datos de 2201 pasajeros del Titanic que dispone de tres atributos o variables independientes que son:&lt;br /&gt;
- La clase en la que viajaba cada persona que puede tomar los valores: primera, segunda, tercera y tripulación.&lt;br /&gt;
- El sexo: Hombre o mujer.&lt;br /&gt;
- La edad: Niño o adulto.&lt;br /&gt;
&lt;br /&gt;
Tenemos una cuarta variable, que en este caso será nuestra variable explicada o dependiente, que nos dice si la persona sobrevivió o no al hundimiento del barco.&lt;br /&gt;
Así pues, nuestro objetivo es encontrar alguna relación entre los atributos antes mencionados y la probabilidad de que un pasajero sobreviva o no al naufragio.&lt;br /&gt;
&lt;br /&gt;
Intuitivamente podemos pensar que el índice de mujeres y niños supervivientes debe ser mayor que el de los hombres por aquello de &quot;las mujeres y los niños primero&quot;. Vemos si es cierto.&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&amp;gt; margin.table(Titanic, c(2,4))
Survived
Sex        No  Yes
Male   1364  367
Female  126  344
&lt;/pre&gt;Parece que, en efecto, las mujeres tenían más tendencia a sobrevivir que los hombres. Veámoslo gráficamente.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMK2fpCv1tZ1G_pzlZUDUaiz7zzTDkp5DgtbCUsZJl_-QSx4jNsdt7fFk3UKOQdOZ_j1l8u9PGcPet7wHbyADr4XxTDDWQnW974p0vbNSKJGwVtzURDQKfyc1kllhwYUINFdGqC9lq-7k/s1600/porsexo.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;389&quot; data-original-width=&quot;400&quot; height=&quot;310&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMK2fpCv1tZ1G_pzlZUDUaiz7zzTDkp5DgtbCUsZJl_-QSx4jNsdt7fFk3UKOQdOZ_j1l8u9PGcPet7wHbyADr4XxTDDWQnW974p0vbNSKJGwVtzURDQKfyc1kllhwYUINFdGqC9lq-7k/s320/porsexo.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;br /&gt;
A ver los niños:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&amp;gt; margin.table(Titanic,c(3,4))
       Survived
Age       No  Yes
  Child   52   57
  Adult 1438  654
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh9ZdbaNmWOQe5_DzWZLeivNF4n-jPtuIttxeNGTmqgt9JHRmqxBdSs7ukIAinfGCktoboKXQ5oRgNIfbmzBNkCL1EZbz7G7NMuL3-PMoBKYnozn3cPgjocTnQ1waey9zRY24rJ0ygJyo/s1600/poredad.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;389&quot; data-original-width=&quot;400&quot; height=&quot;310&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh9ZdbaNmWOQe5_DzWZLeivNF4n-jPtuIttxeNGTmqgt9JHRmqxBdSs7ukIAinfGCktoboKXQ5oRgNIfbmzBNkCL1EZbz7G7NMuL3-PMoBKYnozn3cPgjocTnQ1waey9zRY24rJ0ygJyo/s320/poredad.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;br /&gt;
Parece que aquí la cosa ya no está tan clara. ¿Y qué ocurre con la clase en la que viajaba el pasajero?&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&amp;gt; margin.table(Titanic,c(1,4))
      Survived
Class   No Yes
  1st  122 203
  2nd  167 118
  3rd  528 178
  Crew 673 212
&lt;/pre&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrKKcfPtGLzEcgm206rPfw7UPZS_2cenpeUKLlQ6ayjZsfApwAJIo60uyoD0rlrB_OwtEr9cvz0Igr4hBxDSRZeT9tgm7V5B_LxblQCBOK54vt9ftvzd95j0QQVEku-rzVFD2jleUzL1o/s1600/porclase.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;389&quot; data-original-width=&quot;400&quot; height=&quot;310&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrKKcfPtGLzEcgm206rPfw7UPZS_2cenpeUKLlQ6ayjZsfApwAJIo60uyoD0rlrB_OwtEr9cvz0Igr4hBxDSRZeT9tgm7V5B_LxblQCBOK54vt9ftvzd95j0QQVEku-rzVFD2jleUzL1o/s320/porclase.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;br /&gt;
Se observa también cierta correlación (a los de primera parece que les fue algo mejor) pero no tan clara como con el sexo.&lt;br /&gt;
&lt;br /&gt;
Vamos a tratar de medir, usando el concepto de entropía de Shannon, qué cantidad de información aporta cada una de las tres variables independientes.&lt;br /&gt;
&lt;br /&gt;
Calculamos la entropía del conjunto de datos respecto a la variable explicada &lt;i&gt;Survived&lt;/i&gt;.&lt;br /&gt;
p&lt;sub&gt;1&lt;/sub&gt; -&amp;gt; No sobrevive.&lt;br /&gt;
p&lt;sub&gt;2&lt;/sub&gt; -&amp;gt; Sobrevive.&lt;br /&gt;
&lt;br /&gt;
Comenzamos calculando las probabilidades de p&lt;sub&gt;1&lt;/sub&gt; y p&lt;sub&gt;2&lt;/sub&gt;.&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&gt; p&lt;-margin.table(Titanic, c(4))
&gt; p.norm&lt;-p/sum(p)
&gt; p.norm
Survived
No      Yes 
0.676965 0.323035 
&lt;/pre&gt;Así pues, tenemos que:&lt;br /&gt;
p&lt;sub&gt;1&lt;/sub&gt; = 0.676965&lt;br /&gt;
p&lt;sub&gt;2&lt;/sub&gt; = 0.323035&lt;br /&gt;
&lt;br /&gt;
Con las probabilidades ya calculadas, nos resta hacer el sumatorio según la fórmula de Shannon.&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&gt; entropia&lt;-(-sum(log2(p.norm)*p.norm))
&gt; entropia
[1] 0.9076514
&lt;/pre&gt;Lo que nos dice este valor es la medida de desorden de la variable explicada con un valor que va de 0 a 1. Un valor de 0 indica orden total, o lo que es lo mismo, o todos sobrevivieron o todos murieron, pero todos los valores son iguales. Cuanto más cerca esté de 1, mayor desorden. Un valor 1 querría decir que el 50% de pasajeros murieron y el otro 50% sobrevivió (hay que tener en cuenta que la entropía no es una función lineal).&lt;br /&gt;
Usaremos la notación H(survived) para nombrar la entropía producida por la variable &lt;i&gt;survived&lt;/i&gt;. También vamos a definir el concepto de entropía condicionada a la entropía generada fijando, a priori, el valor de una segunda variable. Por ejemplo, H(survived | Sex) es la entropía de la variable &lt;i&gt;survived&lt;/i&gt; condicionada al valor de la variable sexo. Se calcula con:&lt;br /&gt;
&lt;br /&gt;
\[H(X|Y) = \sum_{i}^{n} p_x \times H(X|Y=y)\]&lt;br /&gt;
&lt;br /&gt;
P&lt;sub&gt;x&lt;/sub&gt; es la probabilidad relativa de aparición de la propiedad x en el conjunto de los datos en los que Y=y y H(X|Y=y) es la entropía de la propiedad y en el conjunto de los datos en los que Y=y.&lt;br /&gt;
&lt;br /&gt;
Por ejemplo, tomemos el caso del sexo y calculemos H(Survived|Sex=Male) y H(Survived|Sex=Female).&lt;br /&gt;
&lt;br /&gt;
La entropía para el caso de las mujeres la calculamos de la forma que ya conocemos.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&gt; p&lt;-margin.table(Titanic,c(2,4))
&gt; p
Survived
Sex        No  Yes
Male   1364  367
Female  126  344

&gt; p.male&lt;-p[1,]
&gt; p.female&lt;-p[2,]

&gt; p.female.norm&lt;-p.female/sum(p.female)
&gt; p.female.norm
       No       Yes 
0.2680851 0.7319149 
&gt; entropia.female&lt;-(-sum(log2(p.female.norm)*p.female.norm))
&gt; entropia.female
[1] 0.8387034
&lt;/pre&gt;Ahora calculamos la entropía para el caso de los hombres.&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&gt; p.male.norm&lt;-p.male/sum(p.male)
&gt; p.male.norm
       No       Yes 
0.7879838 0.2120162 
&gt; entropia.male&lt;-(-sum(log2(p.male.norm)*p.male.norm))
&gt; entropia.male
[1] 0.745319
&lt;/pre&gt;Finalmente, calculamos la entropía H(Survived|Sex) aplicando la fórmula de la entropía condicionada que acabamos de ver.&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&gt; p.sex&lt;-margin.table(Titanic,2)
&gt; p.norm.sex&lt;-p.sex/sum(p.sex)
&gt; p.norm.sex
Sex
     Male    Female 
0.7864607 0.2135393 
&gt; entropia.sex&lt;-entropia.male*as.numeric(p.norm.sex[1])+
   entropia.female*as.numeric(p.norm.sex[2])
&gt; entropia.sex 
[1] 0.7652602
&lt;/pre&gt;Así pues, ya podemos calcular la entropía, pero este valor por sí mismo tampoco nos aclara del todo el asunto de qué atributo aporta más información. &lt;br /&gt;
&lt;br /&gt;
Vamos a definir, basándonos en la entropía, un nuevo concepto al que llamaremos &lt;i&gt;ganancia de información&lt;/i&gt; como:&lt;br /&gt;
&lt;br /&gt;
\[GI(X|Y) = H(X) - H(X|Y)\]&lt;br /&gt;
&lt;br /&gt;
En este caso H(X) es la entropía de la variable explicada y H(X|Y) la entropía condicionada que acabamos de calcular.&lt;br /&gt;
&lt;br /&gt;
La ganancia de información nos dice cómo se reduce la entropía cuando añadimos la nueva variable. Una mayor reducción implica una correlación mayor con la variable explicada. Calculémosla con los datos que ya tenemos:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&amp;gt; gi.sex=entropia-entropia.sex
&amp;gt; gi.sex
[1] 0.1423912
&lt;/pre&gt;Analicemos qué ocurre con los otros dos atributos. Hacemos el cálculo para el atributo edad:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&gt; p&lt;-margin.table(Titanic,c(3,4))
&gt; p.child&lt;-p[1,]
&gt; p.adult&lt;-p[2,]
&gt; p.child.norm&lt;-p.child/sum(p.child)
&gt; p.adult.norm&lt;-p.adult/sum(p.adult)
&gt; entropia.child&lt;-(-sum(log2(p.child.norm)*p.child.norm))
&gt; entropia.adult&lt;-(-sum(log2(p.adult.norm)*p.adult.norm))
&gt; p.age&lt;-margin.table(Titanic,3)
&gt; p.norm.age&lt;-p.age/sum(p.age)
&gt; entropia.age&lt;-entropia.child*as.numeric(p.norm.age[1])+
   entropia.adult*as.numeric(p.norm.age[2])
&gt; gi.age&lt;-entropia-entropia.age
&gt; gi.age
[1] 0.006410718
&lt;/pre&gt;Y finalmente hacemos el cálculo para el atributo clase:&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&gt; p&lt;-margin.table(Titanic,c(1,4))
&gt; p.1st&lt;-p[1,]
&gt; p.2nd&lt;-p[2,]
&gt; p.3rd&lt;-p[3,]
&gt; p.crew&lt;-p[4,]
&gt; p.1st.norm&lt;-p.1st/sum(p.1st)
&gt; p.2nd.norm&lt;-p.2nd/sum(p.2nd)
&gt; p.3rd.norm&lt;-p.3rd/sum(p.3rd)
&gt; p.crew.norm&lt;-p.crew/sum(p.crew)
&gt; entropia.1st&lt;-(-sum(log2(p.1st.norm)*p.1st.norm))
&gt; entropia.2nd&lt;-(-sum(log2(p.2nd.norm)*p.2nd.norm))
&gt; entropia.3rd&lt;-(-sum(log2(p.3rd.norm)*p.3rd.norm))
&gt; entropia.crew&lt;-(-sum(log2(p.crew.norm)*p.crew.norm))
&gt; p.class&lt;-margin.table(Titanic,1)
&gt; p.norm.class&lt;-p.class/sum(p.class)
&gt; entropia.class&lt;-entropia.1st*as.numeric(p.norm.class[1])+
   entropia.2nd*as.numeric(p.norm.class[2])+
   entropia.3rd*as.numeric(p.norm.class[3])+
   entropia.crew*as.numeric(p.norm.class[4])
&gt; gi.class&lt;-entropia-entropia.class
&gt; gi.class
[1] 0.05928794
&lt;/pre&gt;En resumen:&lt;br /&gt;
Ganancia de información para el sexo: 0.1423912&lt;br /&gt;
Ganancia de información para la edad: 0.006410718&lt;br /&gt;
Ganancia de información para la clase: 0.05928794&lt;br /&gt;
&lt;br /&gt;
Esto significa que el atributo que más información aporta, con diferencia, es el sexo. La clase aporta bastante menos información, y la edad apenas nada.&lt;br /&gt;
En la práctica, esto significa que al construir nuestro modelo predictivo, podríamos descartar tranquilamente el atributo edad sin miedo a peder capacidad predictiva.&lt;br /&gt;
&lt;br /&gt;
Me ha parecido más didáctico desarrollar manualmente los cálculos, sin embargo R ya &lt;a href=&quot;http://cran.r-project.org/web/packages/entropy/&quot;&gt;dispone de un paquete&lt;/a&gt; para el cálculo de la entropía de Shannon que nos facilitará sin duda la vida. </description><link>http://www.ellaberintodefalken.com/2018/09/seleccion-atributos-relevantes-entropia-shannon.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWORHbZvWQLnZ9bgiHUPzcLdz-dFUXI7U2laQ8Gc5SQkcXBSjd5dszewp21XUjoWsznOqHRlCvRAjkR1btIl_QAhs7Q9G5Yuk3wnDT7qOt3kt7Vuh1OMFo-9gszJ0IiDw13aPaEwVpuDE/s72-c/Claude_Shannon_graffiti.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-7311186188661068902</guid><pubDate>Sun, 23 Jul 2017 18:07:03 +0000</pubDate><atom:updated>2026-07-15T10:49:53.495+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Arduino</category><category domain="http://www.blogger.com/atom/ns#">matriz de leds</category><category domain="http://www.blogger.com/atom/ns#">max7219</category><category domain="http://www.blogger.com/atom/ns#">post</category><category domain="http://www.blogger.com/atom/ns#">Proyectos</category><title>Juegos con matrices de LEDs y Arduino</title><description>&lt;p&gt;En una &lt;a href=&quot;https://www.ellaberintodefalken.com/2016/10/termometro-sentimental-dht11-max7219.html&quot;&gt;entrada anterior&lt;/a&gt; ya utilizamos una matriz de LEDs junto con el integrado max7219. Una de las virtudes de este integrado es que facilita la conexión de varias matrices usando un bus serie &lt;a href=&quot;https://es.wikipedia.org/wiki/Serial_Peripheral_Interface&quot;&gt;SPI&lt;/a&gt;, así como otros tipos de displays, como los de siete segmentos. En este artículo vamos a utilizar dos matrices para construir un pequeño juego tipo Pong, Arkanoid o, en fin, ya sabes a lo que me refiero, una raqueta que controla el jugador y una pelotita que hay que evitar que se salga por la parte inferior.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;He montado el circuito sobre una protoboard usando un Arduino Nano, dos matrices de LEDs y un potenciómetro conectado a una entrada analógica del Arduino para controlar la raqueta. El esquema del circuito es el siguiente.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNFYMd161ri5O580F-Y02EbgJCKleN92QgWUhGTJ0KgVkLcd77u0SrqduSlRBYhPztYtyH8mLMuDbDLdBNF-Jg-Vv4TGHIoq4S8y_jfnZskzRW4-ujLZ7E9LOpCMzZI79JjnzngpviiJ1veSQH76jmUj2wUd0vu8l-zmHXAMpwAgHpjkrlNkgzzaPiM2c/s1600/cb1e3-breakoutled_bb-e1500737018121.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Juego con LEDs&quot; border=&quot;0&quot; data-original-height=&quot;984&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNFYMd161ri5O580F-Y02EbgJCKleN92QgWUhGTJ0KgVkLcd77u0SrqduSlRBYhPztYtyH8mLMuDbDLdBNF-Jg-Vv4TGHIoq4S8y_jfnZskzRW4-ujLZ7E9LOpCMzZI79JjnzngpviiJ1veSQH76jmUj2wUd0vu8l-zmHXAMpwAgHpjkrlNkgzzaPiM2c/s1600/cb1e3-breakoutled_bb-e1500737018121.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

  
&lt;p&gt;Como ves, el circuito no tiene nada de particular. Los pines D8, D9 y D10 de Arduino se conectan a los pines CS, CLK y DIN del bus SPI de la matriz de LEDs. Además, obviamente, de conectar VCC y GND. El integrado max7219 nos provee, en la parte superior de la matriz de LEDS, de las conexiones necesarias para conectar una segunda matriz (y una tercera, cuarta, etc.). También he aprovechado las salidas VCC y GND de la segunda matriz para conectarlos al potenciómetro y así dejar más limpia de cables la protoboard. Finalmente, he conectado el potenciómetro a la entrada analógica A3 de Arduino. La tensión de entrada en el pin se convierte en digital mediante un ADC de 10 bits, así que tenemos un valor entre 0 y 1023, que usando la función &lt;em&gt;map()&lt;/em&gt; de la librería de Arduino, adaptaremos a nuestras necesidades.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;El código para Arduino es el siguiente.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;pre&gt;#include &quot;LedControl.h&quot; 
#define WIDTH 16
#define HEIGHT 8
#define PADLENGTH 3
 
// Parámetros: pines DIN, CLK, CS y 2 (2 displays)
LedControl lc=LedControl(8,10,9,2);
const int analog_in = A3; // entrada analogica potenciometro
int val, pos; 
byte pantalla[8][16]; // pantalla
int x, y; // posición de la bola
int dx, dy; // delta
int gameover = 0;

void plot(int x, int y)
{
  int screen;
  
  if (x&amp;gt;=0 &amp;amp;&amp;amp; x==0 &amp;amp;&amp;amp; y&amp;lt;HEIGHT) { 
    if (x &amp;gt; ((WIDTH/2)-1)) {
      x = x - HEIGHT;
      screen = 1;
    } else {
      screen = 0;
    }
    lc.setLed(screen, x, y, 1);
  }
}

void draw_screen(int x, int y)
{
  int i;     
  // draw PAD
  for (i=0; i&amp;lt;PADLENGTH; i++) {
    plot(pos+i, 0);
  }
  // draw ball
  plot(x, y);
  
}

void move()
{
  x = x + dx;
  y = y + dy;
  if (x&amp;lt;1 || x&amp;gt;=WIDTH-1) {
    dx = -dx;
  } 
  
  if (y&amp;gt;=HEIGHT-1) {
    dy = -dy;
  }
  
  if (y==1 &amp;amp;&amp;amp; x&amp;gt;=pos &amp;amp;&amp;amp; x&amp;lt;=pos+PADLENGTH) {
    // rebota con la raqueta
    dy = -dy;
  } 
  
  if (y &amp;lt; 0) {
    // pelota sale por abajo. Game over
    gameover = 1;  
  }
}
 
void setup() {
  lc.shutdown(0,false);    // Iniciamos la matriz led
  lc.setIntensity(0,5);    // Intensidad de los leds
  lc.clearDisplay(0);      // Apagamos los leds
  lc.shutdown(1,false);    // Iniciamos la matriz led
  lc.setIntensity(1,5);    // Intensidad de los leds
  lc.clearDisplay(1);      // Apagamos los leds
  x = y = 0;
  dx = dy = 1;
  pos = 0;
  Serial.begin(9600);
}
 
void loop() {      
  int i;
  
  while(true) {   
    if (!gameover) {
      // leer entrada
      val = analogRead(analog_in);
      pos = map(val, 0, 1023, WIDTH - PADLENGTH, -1);
      Serial.println(pos);
  
      // actualizar matriz de LEDs
      lc.clearDisplay(0);
      lc.clearDisplay(1);
      draw_screen(x,y);
      move();
      delay(200);
    } else {
      // game over
      for (i=0; i&amp;lt;HEIGHT; i++) {
        lc.setColumn(0,i,B11111111);
        lc.setColumn(1,HEIGHT-i-1,B11111111);
        delay(200);  
      }
      x = y = 0;
      dx = dy = 1;
      gameover = 0;
      
    }
  }
}
&lt;/pre&gt;

&lt;p&gt;La función &lt;em&gt;plot()&lt;/em&gt; se encarga de activar un pixel en la pantalla &quot;virtual&quot; de 16x8 (las dos matrices juntas). Es decir, calcula en cuál de las dos matrices está el LED que se quiere activar y cuál es el LED físico que le corresponde dentro de esa matriz. La función &lt;em&gt;draw_screen()&lt;/em&gt; dibuja la raqueta y la pelota en cada iteración del bucle principal y, finalmente, la función &lt;em&gt;move()&lt;/em&gt; calcula la nueva posición de la pelota, verifica si ha rebotado o si ha tocado la base, en cuyo caso, hemos perdido y se muestra una pequeña animación en las matrices.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Aquí os dejo un vídeo con el resultado final.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe class=&quot;BLOG_video_class&quot; allowfullscreen=&quot;&quot; youtube-src-id=&quot;bkB46-xRdh8&quot; width=&quot;600&quot; height=&quot;498&quot; src=&quot;https://www.youtube.com/embed/bkB46-xRdh8&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

</description><link>http://www.ellaberintodefalken.com/2017/07/juegos-matrices-leds-arduino.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNFYMd161ri5O580F-Y02EbgJCKleN92QgWUhGTJ0KgVkLcd77u0SrqduSlRBYhPztYtyH8mLMuDbDLdBNF-Jg-Vv4TGHIoq4S8y_jfnZskzRW4-ujLZ7E9LOpCMzZI79JjnzngpviiJ1veSQH76jmUj2wUd0vu8l-zmHXAMpwAgHpjkrlNkgzzaPiM2c/s72-c/cb1e3-breakoutled_bb-e1500737018121.png" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-1578253665007152096</guid><pubDate>Sat, 25 Mar 2017 18:06:54 +0000</pubDate><atom:updated>2026-07-15T10:40:24.917+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">finite state machine</category><category domain="http://www.blogger.com/atom/ns#">FPGA</category><category domain="http://www.blogger.com/atom/ns#">FSM</category><category domain="http://www.blogger.com/atom/ns#">máquina de estados finitos</category><category domain="http://www.blogger.com/atom/ns#">post</category><category domain="http://www.blogger.com/atom/ns#">Sistemas digitales</category><category domain="http://www.blogger.com/atom/ns#">VHDL</category><category domain="http://www.blogger.com/atom/ns#">VHDL / Verilog</category><title>Máquinas de estado finito en VHDL</title><description>&lt;p&gt;Las máquinas de estado finito, más conocidas por su acrónimo en inglés FSM (Finite State Machine), se utilizan ampliamente en el diseño de circuitos digitales (además de en otros ámbitos de la ingeniería, como la programación), para describir el comportamiento de un sistema según el valor de sus entradas y de cómo van cambiando en el tiempo. Ésta es una definición parcial pero que nos permite hacernos una primera idea intuitiva.&lt;/p&gt;
  
&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdsNj4mJvCNxieFyPKOvB0W2LNSqzrkO67E_c8HDXZR8rnnkSjTcWnFwXzvwibwdiiLVCBC1WKux2MvZih336WoksEOwLLCGzZ8UDlnH_4Hmc6YHKWqspmvo-ef4Tae8PNthdkcuzG9DRMi4Xj8rZ9LOJo9-0ZzEpmG_20eslFMkYAQes6u733ig0TI4k/s1024/bcc28-fsm_basys3.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Xilinx Basys 3&quot; border=&quot;0&quot; width=&quot;600&quot; data-original-height=&quot;576&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdsNj4mJvCNxieFyPKOvB0W2LNSqzrkO67E_c8HDXZR8rnnkSjTcWnFwXzvwibwdiiLVCBC1WKux2MvZih336WoksEOwLLCGzZ8UDlnH_4Hmc6YHKWqspmvo-ef4Tae8PNthdkcuzG9DRMi4Xj8rZ9LOJo9-0ZzEpmG_20eslFMkYAQes6u733ig0TI4k/s600/bcc28-fsm_basys3.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;
  
&lt;p&gt;Desde el punto de vista de las FSM, un sistema está compuesto de &lt;em&gt;estados&lt;/em&gt; por los que va pasando el sistema, de &lt;em&gt;señales de entrada&lt;/em&gt; que modifican esos estados y de &lt;em&gt;señales de salida&lt;/em&gt; que pueden utilizarse para conocer el estado del sistema y actuar en consecuencia. Un ejemplo muy visual podría ser un semáforo, el cuál dispone de tres estados diferentes, uno para cada color. Las entradas del sistema las podría generar un temporizador que activa una señal cada cierto tiempo, indicando que hay que pasar al siguiente estado. Por último, las salidas del sistema podrían ser tres señales que indiquen qué lámpara, de las tres disponibles, tiene que encenderse. Para representar una máquina de estados se utilizan diagramas de estados como el siguiente.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyiHsmN6az4JKAjh1uCNH524kyM-YUC1Jdk65b1ktZ59hIEAlQ92gYLecFbm3ebU3TEUMpeQhATDIqMSoHYGlGqI7PjAGGRvpDm3HlW9xt-CFysX0lbUo4x-SJvP4BGt96Tp3w3fi7Vi2EwW4MrT09ROraIhYQBId-u3PIt404E5WtHEJLfX20J-5NQB8/s1600/242bc-fsm-1.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Ejemplo FSM&quot; border=&quot;0&quot; data-original-height=&quot;268&quot; data-original-width=&quot;326&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyiHsmN6az4JKAjh1uCNH524kyM-YUC1Jdk65b1ktZ59hIEAlQ92gYLecFbm3ebU3TEUMpeQhATDIqMSoHYGlGqI7PjAGGRvpDm3HlW9xt-CFysX0lbUo4x-SJvP4BGt96Tp3w3fi7Vi2EwW4MrT09ROraIhYQBId-u3PIt404E5WtHEJLfX20J-5NQB8/s1600/242bc-fsm-1.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Cada círculo representa un estado. En este caso tenemos cuatro estados que se llaman S1, S2, S3 y S4. El estado S1 tiene una flecha que indica que es el estado el inicial, al que se entra tras un reset del sistema. El S4 tiene un doble círculo, que indica que es un estado final. Los estados están unidos por una flechas llamadas transiciones, que indican cómo evoluciona el sistema de un estado a otro según se activan las señales de entrada. En este caso concreto hay tres señales de entrada (a, b, c y d). Si estando en el estado S1 se activa la señal a, el sistema evolucionará al estado S2. Del mismo modo, si estando en el estado S2 se activa la señal b, el sistema evolucionará de nuevo al estado S1. En este caso no hay señales de salida. Si las hay pueden ponerse dentro del círculo o, para no sobrecargar el diagrama, se pueden definir fuera de éste, por ejemplo en una tabla. Vamos a crear una FSM muy básica y a implementarla en VHDL, para poder llevarla a una FPGA. La máquina que vamos a implementar es la mostrada en el siguiente diagrama de estados.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNHVqGV92ZtHd9sjP0tcnFX6oho8_thI2SeBiLcVsjlRGi1fWe1pjif_NX683o5jSSeNE-dPAD-dG9mCk-RLS7ef-4YUHQNmHptM_JPqyrV3X9cUjygMzQal8Yn_1ij9ptC0wyjry0kDsVusBBfSM0NcpOMssfQATNe_AXKFjGTV8_TdFq4Mrw7Ynd1bQ/s1600/f8422-diagrama.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;diagrama FSM&quot; border=&quot;0&quot; data-original-height=&quot;230&quot; data-original-width=&quot;229&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNHVqGV92ZtHd9sjP0tcnFX6oho8_thI2SeBiLcVsjlRGi1fWe1pjif_NX683o5jSSeNE-dPAD-dG9mCk-RLS7ef-4YUHQNmHptM_JPqyrV3X9cUjygMzQal8Yn_1ij9ptC0wyjry0kDsVusBBfSM0NcpOMssfQATNe_AXKFjGTV8_TdFq4Mrw7Ynd1bQ/s1600/f8422-diagrama.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Tenemos cuatro estados (S0, S1, S2 y S3) y dos señales de entrada (A y B). No hemos puesto en el diagrama las señales de salida, pero las definimos en la siguiente tabla:&lt;/p&gt;&lt;p&gt;S0 -&amp;gt; Y0&lt;/p&gt;&lt;p&gt;S1 -&amp;gt; Y1&lt;/p&gt;&lt;p&gt;S2 -&amp;gt; Y2&lt;/p&gt;&lt;p&gt;S3 -&amp;gt; Y3&lt;/p&gt;
&lt;p&gt;Por lo tanto, tenemos cuatro señales de salida. La señal Y0 se activa cuando el sistema está en el estado S0, La Y1 cuando el sistema está en el estado S1 y así sucesivamente. Como se puede observar, la salida del sistema depende exclusivamente del estado en el que se encuentra el sistema. Cuando ocurre esto decimos que se trata de una máquina de estados de tipo Moore. Si la salida depende del estado y además de las entradas actuales del sistema, se trataría de una máquina de estados de tipo Mealy. Se puede demostrar que ambos modelos son equivalentes, así que aquí vamos a quedarnos con el primer tipo que es más sencillo.&lt;/p&gt;&lt;p&gt;A nivel físico, una de las formas más efectivas de implementar una FSM es según el siguiente esquema.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghgyCLihvtek12VBBqL6h0FTrQncXW1OowO_bTyx5URCRG3qKLSD7sEuN_mERUus4__ByuX8H_45-3a3esDaHrq2qX4xjIXjXZmddoW-u8nPSOWAXTcD68xmXCZFEv6w37MoTwTHmChgtkYzOoLK5aILOKm_Sq_R2PX4W_Oipc7VIk3pckqDuKES6O6To/s1600/c1886-fsm.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Esquema FSM&quot; border=&quot;0&quot; data-original-height=&quot;200&quot; data-original-width=&quot;731&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghgyCLihvtek12VBBqL6h0FTrQncXW1OowO_bTyx5URCRG3qKLSD7sEuN_mERUus4__ByuX8H_45-3a3esDaHrq2qX4xjIXjXZmddoW-u8nPSOWAXTcD68xmXCZFEv6w37MoTwTHmChgtkYzOoLK5aILOKm_Sq_R2PX4W_Oipc7VIk3pckqDuKES6O6To/s1600/c1886-fsm.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Obviamente necesitamos algún elemento de memoria para almacenar el estado actual del sistema. Nosotros usaremos un registro de biestables de tipo D (es por lo tanto un bloque secuencial). Los otros dos bloques son puramente combinacionales. El primero se encarga de generar el estado siguiente a partir del estado actual y de las entradas del sistema. Además, necesitamos otro circuito para generar las señales de salida a partir del estado actual (recordemos que es una máquina de tipo Moore).&lt;/p&gt;&lt;p&gt;Voy a llevar a la práctica el circuito usando una FPGA. En concreto usaré la placa BASYS 3 con una FPGA Artix 7 de Xilinx. Voy a utilizar el botón 0 como señal de reset y los botones 4 y 2 como señales A y B respectivamente. Las salidas Y0 a Y3 las voy a asociar a los LEDs 0 a 3. Para evitar utilizar circuitos antirebote para los pulsadores, he diseñado el diagrama de forma que dos pulsaciones seguidas del mismo botón no hagan evolucionar el sistema a otro estado.&lt;/p&gt;&lt;p&gt;Como tenemos cuatro estados, podríamos usar dos bit para codificar los estados. Por ejemplo: S0-&amp;gt;00, S1-&amp;gt;01, S2-&amp;gt;10 y S3-&amp;gt;11. Sin embargo, lo más habitual es dejar que el sintetizador lo haga por nosotros definiendo un tipo para los estados tal y como se ve en la siguiente definición. Así, si añadimos nuevos estados, no tenemos que andar redimensionando el registro de estados.
&lt;pre&gt;    -- declaraciones modelo FSM
    type STATES is (S0, S1, S2, S3);
    signal state_reg, state_next: STATES;
&lt;/pre&gt;

Veamos como se implementa en VHDL cada uno de estos tres bloques. Empecemos por el registro de estados.

&lt;pre&gt;    -- registro de estados
    process(CLK)
    begin
        if CLK&#39;event and CLK=&#39;1&#39; then
            if RST=&#39;1&#39; then 
                state_reg &amp;lt;= s0;
            else 
                state_reg &amp;lt;= state_next;
            end if;
         end if;
    end process;
&lt;/pre&gt;
El registro de estados tiene una señal de reset síncrona que lleva al sistema al estado S0. Si la lógica de codificación del estado siguiente genera un nuevo estado, este se almacenará en el registro, si no, la salida del registro se mantiene. Veamos cómo es el circuito que genera el estado siguiente.
&lt;pre&gt;    -- Lógica de estado siguiente (circuito combinacional)
    process (state_reg, A, B)
    begin
        state_next &amp;lt;= state_reg; 
        case state_reg is 
        when S0 =&amp;gt;
            if A=&#39;1&#39; then
                state_next &amp;lt;= S1; 
            end if; 
        when S1 =&amp;gt;
            if B=&#39;1&#39; then
                state_next &amp;lt;= S2; 
            end if; 
        when S2 =&amp;gt; 
            if A=&#39;1&#39; then
                state_next &amp;lt;= S3; 
            end if; 
        when S3 =&amp;gt;
            state_next &amp;lt;= S3;
        end case;
    end process;
&lt;/pre&gt;
El estado siguiente se genera a partir del estado actual y de las entradas A y B, por lo que ponemos estas tres señales en la lista de sensibilidad del proceso. Para evitar que el sintetizador genere algún latch o elemento de memoria, asignamos por defecto el estado actual a la señal de estado siguiente. El código VHDL describe los estados y las transiciones asociadas a loas entradas según el diagrama de estados que definimos más arriba, de forma que, de ser necesario, asigna el valor del estado siguiente a la variable &lt;em&gt;state_next&lt;/em&gt; cuando hay un cambio en las entradas o en el estado. Por último echamos un vistazo al circuito de decodificación para las salidas.
&lt;pre&gt;    -- salida tipo Moore
    process (state_reg)
    begin
        -- estableciendo la salida por defecto
        -- nos aseguramos de crear un circuito
        -- combinacional sin latches.
        Y0 &amp;lt;= &#39;0&#39;;
        Y1 &amp;lt;= &#39;0&#39;;
        Y2 &amp;lt;= &#39;0&#39;;
        Y3 &amp;lt;= &#39;0&#39;; 
        case state_reg is 
        when S0 =&amp;gt; Y0 &amp;lt;= &#39;1&#39;; 
        when S1 =&amp;gt; Y1 &amp;lt;= &#39;1&#39;; 
        when S2 =&amp;gt; Y2 &amp;lt;= &#39;1&#39;; 
        when S3 =&amp;gt; Y3 &amp;lt;= &#39;1&#39;;
        end case; 
    end process;
&lt;/pre&gt;
De nuevo, para evitar que el sintetizador cree elementos de memoria, asignamos un valor por defecto a las salidas (en este caso 0), y según el estado actual, se activa la salida correspondiente según definimos en la tabla de arriba.&lt;/p&gt;
&lt;p&gt;El código VHDL  completo de nuestra FSM es el siguiente.&lt;/p&gt;&lt;p&gt; 
&lt;pre&gt;library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity FSM is
    Port ( CLK : in STD_LOGIC;
           BTN : in STD_LOGIC_VECTOR (4 downto 0);
           LED : out STD_LOGIC_VECTOR (15 downto 0));
end FSM;
architecture Behavioral of FSM is
    -- alias
    alias RST : STD_LOGIC is BTN(0);
    alias A : STD_LOGIC is BTN(4);
    alias B : STD_LOGIC is BTN(2);
    alias Y0 : STD_LOGIC is LED(0);
    alias Y1 : STD_LOGIC is LED(1);
    alias Y2 : STD_LOGIC is LED(2);
    alias Y3 : STD_LOGIC is LED(3);
    
    -- declaraciones modelo FSM
    type STATES is (S0, S1, S2, S3);
    signal state_reg, state_next: STATES;
    begin
    -- registro de estados
    process(CLK)
    begin
        if CLK&#39;event and CLK=&#39;1&#39; then
            if RST=&#39;1&#39; then 
                state_reg &amp;lt;= s0;
            else 
                state_reg &amp;lt;= state_next;
            end if;
         end if;
    end process;
    
    -- Lógica de estado siguiente (circuito combinacional)
    process (state_reg, A, B)
    begin
        state_next &amp;lt;= state_reg; 
        case state_reg is 
        when S0 =&amp;gt;
            if A=&#39;1&#39; then
                state_next &amp;lt;= S1; 
            end if; 
        when S1 =&amp;gt;
            if B=&#39;1&#39; then
                state_next &amp;lt;= S2; 
            end if; 
        when S2 =&amp;gt; 
            if A=&#39;1&#39; then
                state_next &amp;lt;= S3; 
            end if; 
        when S3 =&amp;gt;
            state_next &amp;lt;= S3;
        end case;
    end process;
    
    -- salida tipo Moore
    process (state_reg)
    begin
        -- estableciendo la salida por defecto
        -- nos aseguramos de crear un circuito
        -- combinacional sin latches.
        Y0 &amp;lt;= &#39;0&#39;;
        Y1 &amp;lt;= &#39;0&#39;;
        Y2 &amp;lt;= &#39;0&#39;;
        Y3 &amp;lt;= &#39;0&#39;; 
        case state_reg is 
        when S0 =&amp;gt; Y0 &amp;lt;= &#39;1&#39;; 
        when S1 =&amp;gt; Y1 &amp;lt;= &#39;1&#39;; 
        when S2 =&amp;gt; Y2 &amp;lt;= &#39;1&#39;; 
        when S3 =&amp;gt; Y3 &amp;lt;= &#39;1&#39;;
        end case; 
    end process;    
    end Behavioral;
&lt;/pre&gt;
&lt;p&gt;Os dejo el archivo de restricciones para la placa BASYS 3 que he usado para este ejemplo, donde se definen los puertos de los pulsadores, los LEDs y el reloj del sistema.
&lt;pre&gt;## LEDs
set_property PACKAGE_PIN U16 [get_ports {LED[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[0]}]
set_property PACKAGE_PIN E19 [get_ports {LED[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[1]}]
set_property PACKAGE_PIN U19 [get_ports {LED[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[2]}]
set_property PACKAGE_PIN V19 [get_ports {LED[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[3]}]
set_property PACKAGE_PIN W18 [get_ports {LED[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[4]}]
set_property PACKAGE_PIN U15 [get_ports {LED[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[5]}]
set_property PACKAGE_PIN U14 [get_ports {LED[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[6]}]
set_property PACKAGE_PIN V14 [get_ports {LED[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[7]}]
set_property PACKAGE_PIN V13 [get_ports {LED[8]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[8]}]
set_property PACKAGE_PIN V3 [get_ports {LED[9]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[9]}]
set_property PACKAGE_PIN W3 [get_ports {LED[10]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[10]}]
set_property PACKAGE_PIN U3 [get_ports {LED[11]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[11]}]
set_property PACKAGE_PIN P3 [get_ports {LED[12]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[12]}]
set_property PACKAGE_PIN N3 [get_ports {LED[13]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[13]}]
set_property PACKAGE_PIN P1 [get_ports {LED[14]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[14]}]
set_property PACKAGE_PIN L1 [get_ports {LED[15]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[15]}]

## Botones
set_property IOSTANDARD LVCMOS33 [get_ports {BTN[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {BTN[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {BTN[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {BTN[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {BTN[0]}]
## BTNL -&amp;gt; W19
set_property PACKAGE_PIN W19 [get_ports {BTN[4]}] 
## BTND -&amp;gt; U17
set_property PACKAGE_PIN U17 [get_ports {BTN[3]}] 
## BTNR -&amp;gt; T17
set_property PACKAGE_PIN T17 [get_ports {BTN[2]}] 
## BTNU -&amp;gt; T18
set_property PACKAGE_PIN T18 [get_ports {BTN[1]}] 
## BTNC -&amp;gt; U18
set_property PACKAGE_PIN U18 [get_ports {BTN[0]}] 

## Reloj CLK conectado a W5
set_property IOSTANDARD LVCMOS33 [get_ports {CLK}]
set_property PACKAGE_PIN W5 [get_ports {CLK}]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports CLK]
&lt;/pre&gt;
En la simulación de la FSM vemos como evoluciona el circuito desde S0 a S3 según van cambiando las entradas A y B. También podemos observar cómo se activan las entradas Y0 a Y3.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHb7JI5mPRKHMq661zL2jeGTM6S6_LVq30jakYVugnG_e5wKzFwRr6vXAk0hxXMJ9xD9iGhMgesInXKwlpekrAH_LlBLe-eBTQAVWj077otyn8pskMdflVFdzM6BIkdothffT3-DMSjtNFIgFRhbTAa-GAVeRWL2eJc2l89h-xONla6wATqP03Ee6xMZM/s1600/3be01-simulacion-e1489424566729.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Simulacion FSM&quot; border=&quot;0&quot; data-original-height=&quot;317&quot; data-original-width=&quot;1025&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHb7JI5mPRKHMq661zL2jeGTM6S6_LVq30jakYVugnG_e5wKzFwRr6vXAk0hxXMJ9xD9iGhMgesInXKwlpekrAH_LlBLe-eBTQAVWj077otyn8pskMdflVFdzM6BIkdothffT3-DMSjtNFIgFRhbTAa-GAVeRWL2eJc2l89h-xONla6wATqP03Ee6xMZM/s1600/3be01-simulacion-e1489424566729.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe class=&quot;BLOG_video_class&quot; allowfullscreen=&quot;&quot; youtube-src-id=&quot;doOEfAsoEmg&quot; width=&quot;600&quot; height=&quot;498&quot; src=&quot;https://www.youtube.com/embed/doOEfAsoEmg&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;p&gt;https://youtu.be/doOEfAsoEmg&lt;/p&gt;</description><link>http://www.ellaberintodefalken.com/2017/03/maquinas-de-estado-finito-fsm-vhdl.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdsNj4mJvCNxieFyPKOvB0W2LNSqzrkO67E_c8HDXZR8rnnkSjTcWnFwXzvwibwdiiLVCBC1WKux2MvZih336WoksEOwLLCGzZ8UDlnH_4Hmc6YHKWqspmvo-ef4Tae8PNthdkcuzG9DRMi4Xj8rZ9LOJo9-0ZzEpmG_20eslFMkYAQes6u733ig0TI4k/s72-c/bcc28-fsm_basys3.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-3420263284457090651</guid><pubDate>Sat, 18 Feb 2017 16:55:17 +0000</pubDate><atom:updated>2026-07-15T10:29:47.154+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">FPGA</category><category domain="http://www.blogger.com/atom/ns#">MMCM</category><category domain="http://www.blogger.com/atom/ns#">post</category><category domain="http://www.blogger.com/atom/ns#">VHDL</category><category domain="http://www.blogger.com/atom/ns#">VHDL / Verilog</category><category domain="http://www.blogger.com/atom/ns#">Vivado</category><category domain="http://www.blogger.com/atom/ns#">XILINX</category><title>Relojes MMCM en FPGAs</title><description>&lt;p&gt;Cuando se diseñan sistemas digitales síncronos, una de las reglas de oro es usar un sólo dominio de reloj para todo el circuito. Esto quiere decir que sólo usaremos una única señal de reloj conectada a todos los elementos síncronos del circuito. Por lo tanto debemos evitar una práctica bastante habitual que es usar la salida de un elemento del sistema, como por ejemplo un contador, para obtener la señal de reloj de otro elemento cualquiera. 
  
&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoMpt3A4hnfRSTzRFlj2VZZX5R6r3z6LfTX_FbWorhyphenhyphencGFm_FwNXqvYO0xKaOyyvCMrbYDqi-bWVudSnSYq1oD4cCUIUSlVxo7qCs6YLXynjvw2HINZB6gHVSWwXyIlBJRAEyS-i2CsJlg_C-jgzx-uAYUg_sszgMDsJkAEh-hPgpi05ZE6IhF74cOw-s/s1024/915fc-contador_mmcm.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Placa Xilinx Basys 3&quot; border=&quot;0&quot; width=&quot;600&quot; data-original-height=&quot;576&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoMpt3A4hnfRSTzRFlj2VZZX5R6r3z6LfTX_FbWorhyphenhyphencGFm_FwNXqvYO0xKaOyyvCMrbYDqi-bWVudSnSYq1oD4cCUIUSlVxo7qCs6YLXynjvw2HINZB6gHVSWwXyIlBJRAEyS-i2CsJlg_C-jgzx-uAYUg_sszgMDsJkAEh-hPgpi05ZE6IhF74cOw-s/s600/915fc-contador_mmcm.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;
  
De hecho, aquí lo hicimos (mal) cuando diseñamos &lt;a href=&quot;https://www.ellaberintodefalken.com/2016/09/reloj-digital-vhdl.html&quot;&gt;nuestro reloj digital&lt;/a&gt; (y usamos la salida del contador de 1Hz como señal de reloj para el contador de segundos). Aunque siempre en aras de la simplicidad y la pedagogía :-)&lt;/p&gt;
&lt;p&gt;Así que si queremos que nuestro diseño no tenga problemas de sincronización, el mismo reloj debe ser usado por todos y cada uno de los elementos síncronos. Sin embargo, si nuestro circuito es complejo y sobre todo, si ocupa una gran superficie de la FPGA, la señal de reloj puede sufrir pequeños retardos al distribuirse entre elementos distantes dentro de la FPGA y puede haber un pequeño desfase entre los flancos activos. Para minimizar al máximo este problema, las FPGAs disponen de bloques de reloj dedicados que nos ofrecen módulos PLL y MMCM. Los MMCM (&lt;em&gt;Mixed-Mode Clock Manager&lt;/em&gt;) usan conexiones especiales para repartir la señal de reloj por toda la superficie de la FPGA. Vamos a crear un diseño bastante simple en el que veremos cómo usar el reloj MMCM y de paso aplicaremos buenas prácticas para diseñar un sistema digital con un sólo dominio de reloj. En este caso usaremos una FPGA Artix 7 de XILINX (en la placa Basys 3 de Digilent) y el entorno de desarrollo Vivado. El resultado será un contador binario de 8 bits que se mostrará en los LEDs de la placa, con una velocidad de 1Hz (una cuenta por segundo).
&lt;h3&gt;Utilizando el módulo MMCM&lt;/h3&gt;
Para empezar, crearemos un nuevo proyecto en Vivado y también nuestro reloj MMCM. Vamos a utilizar un core IP del catálogo de Vivado, así que pulsamos sobre &lt;em&gt;IP Catalog&lt;/em&gt; en  el &lt;em&gt;Flow Navigation&lt;/em&gt; de la izquierda del GUI. Hacemos doble click en &lt;em&gt;Vivado Repository &amp;gt; FPGA Features and Design &amp;gt; Clocking &amp;gt; Clocking Wizard&lt;/em&gt;.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMJ8Hl6qd9CYoeF4wEoEsrFVAXGVm1BzzTLBZAO6qeaFoaLIPece5zLmo4q1R0XgFx7Z-hXA5Iuq_kWqKjIWTUVTPM3hjTVJgLV4dMKi7o0hy9HdjQLdfGbKdm3N3TAc9TFOKhLcJcT6LRs2bxaPZOkBCgzsb8cKMK9KgqRxnI4gV5Z06WfUpnEOCUetg/s1600/7532e-2.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Core IP clock wizard&quot; border=&quot;0&quot; data-original-height=&quot;438&quot; data-original-width=&quot;596&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMJ8Hl6qd9CYoeF4wEoEsrFVAXGVm1BzzTLBZAO6qeaFoaLIPece5zLmo4q1R0XgFx7Z-hXA5Iuq_kWqKjIWTUVTPM3hjTVJgLV4dMKi7o0hy9HdjQLdfGbKdm3N3TAc9TFOKhLcJcT6LRs2bxaPZOkBCgzsb8cKMK9KgqRxnI4gV5Z06WfUpnEOCUetg/s1600/7532e-2.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Seleccionamos la primitiva MMCM. en la siguiente imagen vemos qué opciones deben estar marcadas.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh13qd3bsc68gGWaguBNg5VaAF0qUy3AyDCO4657-4kFX6R9exjK34a_QCf3K8CK5T9F45fnUXqbIAP1U5cTOKXQXtvMbokTtcHnjm7pgajgqF085BjT865rvTUUSmAlJ8LSgubkUmrTZwpPg7Hf1AwO9XCrNy_h9Tq9rHIkb6Oq7MLXCeDXKCNHFSFuU/s1600/9eb80-3.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Opciones MMCM 1&quot; border=&quot;0&quot; data-original-height=&quot;859&quot; data-original-width=&quot;915&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh13qd3bsc68gGWaguBNg5VaAF0qUy3AyDCO4657-4kFX6R9exjK34a_QCf3K8CK5T9F45fnUXqbIAP1U5cTOKXQXtvMbokTtcHnjm7pgajgqF085BjT865rvTUUSmAlJ8LSgubkUmrTZwpPg7Hf1AwO9XCrNy_h9Tq9rHIkb6Oq7MLXCeDXKCNHFSFuU/s1600/9eb80-3.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;En la pestaña &lt;em&gt;Output Clocks&lt;/em&gt;, en el campo &lt;em&gt;Output Freq (MHz) Requested&lt;/em&gt; ponemos el valor 50.00 (es decir, usaremos un reloj de 50MHz). Marcamos las casillas &lt;em&gt;Reset&lt;/em&gt; y &lt;em&gt;Locked&lt;/em&gt;.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAqtJ3QAHTfG16zTJQVEkfSEkgvGnFuJ6Ivu5AuRmxSuze5VSFXeEXCJglN1aRMdzNHlGfHSb3VX3CTVRNjgniFu3bs-cGGHIjp6RAjKiCDhawKU72ybenrxcgfI4YOZJkN04Ul-Z8SuAwpuJP5Vkvp0zDTTNttbtjLj2TvDW-ZZaMjcUS71vSTU6GSgY/s1600/9601a-4.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;MMCM Clock 2&quot; border=&quot;0&quot; data-original-height=&quot;859&quot; data-original-width=&quot;915&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAqtJ3QAHTfG16zTJQVEkfSEkgvGnFuJ6Ivu5AuRmxSuze5VSFXeEXCJglN1aRMdzNHlGfHSb3VX3CTVRNjgniFu3bs-cGGHIjp6RAjKiCDhawKU72ybenrxcgfI4YOZJkN04Ul-Z8SuAwpuJP5Vkvp0zDTTNttbtjLj2TvDW-ZZaMjcUS71vSTU6GSgY/s1600/9601a-4.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Pulsamos OK. En la siguiente ventana marcamos &lt;em&gt;Out of context per IP&lt;/em&gt; y pulsamos &lt;em&gt;generate&lt;/em&gt;.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6mrLM6hO89ZYt3RVRzzxW-6QQVPh_xgc6wYDxAwrqQ3Z9HuPr4JrlGGIrWFr9yyfOl32aSVosy3QMb7xeXFVj-Ir-CNG_R6Gk7Qr3DrBmopoGuZlFZTCohztsbqfgmOy4Hx25BwLcxRq6e9XtR0MPvhQhJDQUKSlnazIerVDLgmF1dzg41edA_5T-wRE/s1600/5bc20-5.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;MMCM Clock 3&quot; border=&quot;0&quot; data-original-height=&quot;514&quot; data-original-width=&quot;372&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6mrLM6hO89ZYt3RVRzzxW-6QQVPh_xgc6wYDxAwrqQ3Z9HuPr4JrlGGIrWFr9yyfOl32aSVosy3QMb7xeXFVj-Ir-CNG_R6Gk7Qr3DrBmopoGuZlFZTCohztsbqfgmOy4Hx25BwLcxRq6e9XtR0MPvhQhJDQUKSlnazIerVDLgmF1dzg41edA_5T-wRE/s1600/5bc20-5.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Cuando aparezca la ventana indicando que se ha generado el core, pulsamos OK.&lt;/p&gt;
&lt;p&gt;En el proyecto, añadimos el archivos de restricciones siguiente, en el que se han configurado los pines de la FPGA que vamos a usar (en este caso, el fichero se llama config_GPIO.xdc).
&lt;pre&gt;## LEDs
set_property PACKAGE_PIN U16 [get_ports {LED[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[0]}]
set_property PACKAGE_PIN E19 [get_ports {LED[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[1]}]
set_property PACKAGE_PIN U19 [get_ports {LED[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[2]}]
set_property PACKAGE_PIN V19 [get_ports {LED[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[3]}]
set_property PACKAGE_PIN W18 [get_ports {LED[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[4]}]
set_property PACKAGE_PIN U15 [get_ports {LED[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[5]}]
set_property PACKAGE_PIN U14 [get_ports {LED[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[6]}]
set_property PACKAGE_PIN V14 [get_ports {LED[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[7]}]
set_property PACKAGE_PIN V13 [get_ports {LED[8]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[8]}]
set_property PACKAGE_PIN V3 [get_ports {LED[9]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[9]}]
set_property PACKAGE_PIN W3 [get_ports {LED[10]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[10]}]
set_property PACKAGE_PIN U3 [get_ports {LED[11]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[11]}]
set_property PACKAGE_PIN P3 [get_ports {LED[12]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[12]}]
set_property PACKAGE_PIN N3 [get_ports {LED[13]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[13]}]
set_property PACKAGE_PIN P1 [get_ports {LED[14]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[14]}]
set_property PACKAGE_PIN L1 [get_ports {LED[15]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[15]}]
##7 segment display
## segmento A -&amp;gt; W7
set_property PACKAGE_PIN W7 [get_ports {CAT[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[0]}]
## segmento B -&amp;gt; W6
set_property PACKAGE_PIN W6 [get_ports {CAT[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[1]}]
## segmento C -&amp;gt; U8
set_property PACKAGE_PIN U8 [get_ports {CAT[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[2]}]
## segmento D -&amp;gt; V8
set_property PACKAGE_PIN V8 [get_ports {CAT[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[3]}]
## segmento E -&amp;gt; U5
set_property PACKAGE_PIN U5 [get_ports {CAT[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[4]}]
## segmento F -&amp;gt; V5
set_property PACKAGE_PIN V5 [get_ports {CAT[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[5]}]
## segmento G -&amp;gt; U7
set_property PACKAGE_PIN U7 [get_ports {CAT[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[6]}]
## segmento DP -&amp;gt; V7
set_property PACKAGE_PIN V7 [get_ports {CAT[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[7]}]
## Anodo 0 diplay -&amp;gt; U2
set_property PACKAGE_PIN U2 [get_ports {AN[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {AN[0]}]
## Anodo 1 diplay -&amp;gt; U4
set_property PACKAGE_PIN U4 [get_ports {AN[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {AN[1]}]
## Anodo 2 diplay -&amp;gt; V4
set_property PACKAGE_PIN V4 [get_ports {AN[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {AN[2]}]
## Anodo 3 diplay -&amp;gt; W4
set_property PACKAGE_PIN W4 [get_ports {AN[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {AN[3]}]
## Reloj CLK conectado a W5
set_property IOSTANDARD LVCMOS33 [get_ports {CLK}]
set_property PACKAGE_PIN W5 [get_ports {CLK}]
create_clock -period 10.000 -name CLK -waveform {0.000 5.000} [get_ports CLK]
&lt;/pre&gt;
En la última línea se definen las restricciones para el reloj externo a la FPGA, que es de 100MHz en el caso de la placa Basys 3 (a partir del cual el MMCM generará los 50MHz que hemos configurado).&lt;/p&gt;
&lt;p&gt;El código VHDL del proyecto es el siguiente (nombre del fichero mmcm_top.vhd).
&lt;pre&gt;library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.all;
entity mmcm_top is
Generic ( 
        -- reloj a 50Mhz: 50000000 ciclos en un segundo.
        N : NATURAL := 50000000
    );
    
    Port ( CLK : in STD_LOGIC;
        AN : out STD_LOGIC_VECTOR (3 downto 0);
        LED : out STD_LOGIC_VECTOR (15 downto 0));
end mmcm_top;

architecture Behavioral of mmcm_top is    
    -- contador de 1hz
    signal clk_1hz : NATURAL;
    
    -- señal de habilitación para contador binario
    signal cont_ena : STD_LOGIC;
    
    -- señales para el MMCM
    signal reset_in: STD_LOGIC;
    signal MCLK: STD_LOGIC;
    signal locked: STD_LOGIC;
    
    -- señal de reset global
    signal mrst: STD_LOGIC;
    
    -- registro binario 8 bits para contador
    signal reg_bin: UNSIGNED (7 downto 0);
    
    alias leds_contador is LED (7 downto 0); 
     
begin
-- apagamos displays de 7 seg. (no lo usaremos)
    AN &amp;lt;= &quot;1111&quot;;
    
    -- mostrar contador en los leds
    leds_contador &amp;lt;= std_logic_vector(reg_bin);
    
    -- Control reset global del sistema.
    -- La señal locked se pone a 1 cuando el reloj es estable.
    -- Es entonces cuando lo podemos usar.
    -- Por eso aquí lo usamos como reset global del sistema.
    mrst &amp;lt;= not locked;
    
    -- reset del reloj mmcm no activo
    reset_in &amp;lt;= &#39;0&#39;; 
    
    -- Módulo MMCM (Core IP)
    MMCM_clk: entity WORK.clk_wiz_0 port map ( 
        clk_in1     =&amp;gt; CLK,
        reset       =&amp;gt; reset_in,
        clk_out1    =&amp;gt; MCLK,
        locked      =&amp;gt; locked );
        
  
    -- contador 1Hz
    process(MCLK)
    begin
        if (MCLK&#39;event and MCLK=&#39;1&#39;) then
            if clk_1hz &amp;lt; N then 
                clk_1hz &amp;lt;= clk_1hz + 1;
            else
                clk_1hz &amp;lt;= 0;
            end if;
        end if;
    end process;
    
    -- señal de fin de cuenta 1Hz. Pulso para contador binario,
    cont_ena &amp;lt;= &#39;1&#39; when clk_1hz=N else &#39;0&#39;;
    
    -- contador binario (sincrono)
    process(MCLK)
    begin
        if (MCLK&#39;event and MCLK=&#39;1&#39;) then
            if (mrst = &#39;1&#39;) then
                -- reset síncrono. 
                -- msrt se pone a 0 cuando el reloj es estable.
                reg_bin &amp;lt;= (others =&amp;gt; &#39;0&#39;);
             elsif (cont_ena = &#39;1&#39;) then
                reg_bin &amp;lt;= reg_bin + 1;
            end if;
        end if;    
    end process;
                     
end Behavioral;
&lt;/pre&gt;
Si todo está correcto, en la jerarquía del proyecto debemos ver que MMCM_clk está subordinado a mmcm_top.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyeJpJij81qanoDynqw5tmoymTKn0ay17l-Wm-blDUSOyRscO6dGmw_5QbKYygvVmpmLxstSHMqkGdhWiEMx-IaOGTK5u5ecyUk55-UIDdhc2rIzcDmZWKgPoQUIth6MGfxCepVtQppeckKMyeJjujI030FvzeTdtN0CdI-sOTBoHmA2aESzdtCn3hQFU/s1600/76ab9-6.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Jerarquía proyecto&quot; border=&quot;0&quot; data-original-height=&quot;332&quot; data-original-width=&quot;403&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyeJpJij81qanoDynqw5tmoymTKn0ay17l-Wm-blDUSOyRscO6dGmw_5QbKYygvVmpmLxstSHMqkGdhWiEMx-IaOGTK5u5ecyUk55-UIDdhc2rIzcDmZWKgPoQUIth6MGfxCepVtQppeckKMyeJjujI030FvzeTdtN0CdI-sOTBoHmA2aESzdtCn3hQFU/s1600/76ab9-6.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Si observamos que hay un error como el de la figura siguiente no le hacemos caso, parece ser un fallo del entorno, ya que todo está correcto.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGikvJXOpTrYZ_9NNUnF4NA7l6GFut1CTQOygETtL5S6zCVoXYqCgR7USHWmup6bWjZgxjD6c_G4Nz5dsxpitOhTlBTvnI5KQooXmr0pmBy3mHirpTLtR_HdwHxKxhAiCIsBrmCu0KsBcbKDoSWjSZKmaglh3WPVXm2A06tHrzQJTRbOon1Acon1cvar0/s1600/3bbd2-7.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Error vivado&quot; border=&quot;0&quot; data-original-height=&quot;999&quot; data-original-width=&quot;1280&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGikvJXOpTrYZ_9NNUnF4NA7l6GFut1CTQOygETtL5S6zCVoXYqCgR7USHWmup6bWjZgxjD6c_G4Nz5dsxpitOhTlBTvnI5KQooXmr0pmBy3mHirpTLtR_HdwHxKxhAiCIsBrmCu0KsBcbKDoSWjSZKmaglh3WPVXm2A06tHrzQJTRbOon1Acon1cvar0/s1600/3bbd2-7.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Analicemos el código. El &lt;a href=&quot;https://www.ellaberintodefalken.com/2016/08/divisor-de-frecuencia-reloj-1hz-vhdl.html&quot;&gt;contador de 1Hz&lt;/a&gt; ya nos debe ser familiar, pero ahora lo hemos modificado un poco. La señal de reloj del contador es MCLK, que es generado por el módulo MMCM.
&lt;pre&gt;    -- contador 1Hz
    process(MCLK)
    begin
        if (MCLK&#39;event and MCLK=&#39;1&#39;) then
            if clk_1hz &amp;lt; N then 
                clk_1hz &amp;lt;= clk_1hz + 1;
            else
                clk_1hz &amp;lt;= 0;
            end if;
        end if;
    end process;
    
    -- señal de fin de cuenta 1Hz. Pulso para contador binario,
    cont_ena &amp;lt;= &#39;1&#39; when clk_1hz=N else &#39;0&#39;;
&lt;/pre&gt;
Cuando el contador llega a su fin de cuenta (lo que ocurre con una frecuencia de 1Hz) se pone a 1 la señal cont_ena, que es la señal de habilitación que está conectada al contador binario, cuyo código es el siguiente (en realidad debería ser cuándo llegara a N-1, pero no vamos a perdernos en detalles nimios :-) ).
&lt;pre&gt;    -- contador binario (sincrono)
    process(MCLK)
    begin
        if (MCLK&#39;event and MCLK=&#39;1&#39;) then
            if (mrst = &#39;1&#39;) then
                -- reset síncrono. 
                -- msrt se pone a 0 cuando el reloj es estable.
                reg_bin &amp;lt;= (others =&amp;gt; &#39;0&#39;);
             elsif (cont_ena = &#39;1&#39;) then
                reg_bin &amp;lt;= reg_bin + 1;
            end if;
        end if;    
    end process;
&lt;/pre&gt;
Nuestro contador binario es totalmente síncrono y, como vemos, usa la misma señal de reloj MCLK que el contador de 1Hz (ambos contadores usan la misma señal de reloj). La señal msrt está conectada mediante un inversor a la salida locked del MMCM. Esta señal se mantiene a 0 mientras se estabiliza el reloj, y cuando éste está estable se pone a 1, así que lo usamos como señal de reset global para el sistema. Así mantenemos el contador binario parado hasta que el reloj se estabilice, y después ya podemos empezar a contar de forma segura. Cada vez que la señal cont_ena (señal de habilitación) se pone en alto, el contador binario suma 1. Recordemos que en el &lt;a href=&quot;https://www.ellaberintodefalken.com/2016/09/reloj-digital-vhdl.html&quot;&gt;reloj digital&lt;/a&gt; que diseñamos en un artículo anterior usábamos la salida del contador de 1Hz como señal de reloj para el segundero. Es decir teníamos dos dominios de reloj diferentes, que como hemos visto, en un circuito real puede ser una fuente de problemas inesperados.
El contador binario, que es un registro de 8 bits, está conectado directamente a los LEDs de la placa.
&lt;pre&gt;     -- mostrar contador en los leds
    leds_contador &amp;lt;= std_logic_vector(reg_bin);
&lt;/pre&gt;
Así pues, nuestro diseño sólo tiene una fuente de reloj y, además, al utilizar un módulo de reloj MMCM nos aseguramos de que minimizaremos al máximo los problemas derivados de un posible desfase en los flancos activos en las diferentes partes del circuito.

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe class=&quot;BLOG_video_class&quot; allowfullscreen=&quot;&quot; youtube-src-id=&quot;JC2tqiiipW0&quot; width=&quot;600&quot; height=&quot;498&quot; src=&quot;https://www.youtube.com/embed/JC2tqiiipW0&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
</description><link>http://www.ellaberintodefalken.com/2017/02/reloj-mmcm-fpga-xilinx-vivado.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoMpt3A4hnfRSTzRFlj2VZZX5R6r3z6LfTX_FbWorhyphenhyphencGFm_FwNXqvYO0xKaOyyvCMrbYDqi-bWVudSnSYq1oD4cCUIUSlVxo7qCs6YLXynjvw2HINZB6gHVSWwXyIlBJRAEyS-i2CsJlg_C-jgzx-uAYUg_sszgMDsJkAEh-hPgpi05ZE6IhF74cOw-s/s72-c/915fc-contador_mmcm.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-4453893722485448306</guid><pubDate>Sun, 22 Jan 2017 16:11:56 +0000</pubDate><atom:updated>2026-07-14T12:01:58.892+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">microcontrolador</category><category domain="http://www.blogger.com/atom/ns#">MSP430</category><category domain="http://www.blogger.com/atom/ns#">post</category><category domain="http://www.blogger.com/atom/ns#">timers</category><title>Timers en MSP430</title><description>&lt;p&gt;El MSP430 está provisto de temporizadores (el número de ellos depende de la versión del microcontrolador) que permiten medir intervalos de tiempo y generar eventos basados en tiempo.&lt;/p&gt;
  
&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhapyNqRUNxPRKGSRUkluvFV4D2NkMVN5JD2V9RVjyaaDSFzhtv5vRe63aKHvFwNfG0Go9YATz3bci1DBVT_8RNtq6J4UasmBIWbTC3C_5eSVRpyaUyPA6x7_3xX40qvgiwbL3p_XJzD64hOqbjJuX9XCOza9QuzA_37lbvhdsxSklw3iWxf7T2of0CuBU/s1024/e2f20-launchpadmsp430.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;MSP430 Launchpad&quot; border=&quot;0&quot; width=&quot;600&quot; data-original-height=&quot;576&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhapyNqRUNxPRKGSRUkluvFV4D2NkMVN5JD2V9RVjyaaDSFzhtv5vRe63aKHvFwNfG0Go9YATz3bci1DBVT_8RNtq6J4UasmBIWbTC3C_5eSVRpyaUyPA6x7_3xX40qvgiwbL3p_XJzD64hOqbjJuX9XCOza9QuzA_37lbvhdsxSklw3iWxf7T2of0CuBU/s600/e2f20-launchpadmsp430.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;
  
  
&lt;p&gt;No hay que confundir los timers con los relojes de tiempo real que también equipa el MSP430.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9VU5h7cloF6A9HuuVztEyNEAaBS4N_TPa9tsj9ZHRbrAftPla139oOys4n2kexKTNJUIidXhuLPq7XeVRTt27AtdmkdyK1BT0T9H8kRaIq50F0SdOYuxLLNEAPJYLoeGN483y7vlgrYFlADUFvEmF24yrlMeZ_YM0RIMwq94sje4RCH6_CyZ2c8ceUC8/s1600/99326-msp430_timers-e1484134863394.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Timers MSP430&quot; border=&quot;0&quot; data-original-height=&quot;481&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9VU5h7cloF6A9HuuVztEyNEAaBS4N_TPa9tsj9ZHRbrAftPla139oOys4n2kexKTNJUIidXhuLPq7XeVRTt27AtdmkdyK1BT0T9H8kRaIq50F0SdOYuxLLNEAPJYLoeGN483y7vlgrYFlADUFvEmF24yrlMeZ_YM0RIMwq94sje4RCH6_CyZ2c8ceUC8/s1600/99326-msp430_timers-e1484134863394.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Hay dos tipos de timers, Los Timers_A y los Timers_B. Ambos son muy similares, y aunque los de tipo B poseen alguna característica más que los A, aquí no vamos a profundizar en ello.&lt;/p&gt;&lt;p&gt;Alguna de las características de los timers son:
&lt;ul&gt;
 	&lt;li&gt;Utilizan un contador de 16 bits con cuatro modos de operación.&lt;/li&gt;
 	&lt;li&gt;Pueden seleccionar y usar diferentes fuentes de reloj.&lt;/li&gt;
 	&lt;li&gt;Utilizan registros de captura/comparación para su configuración.&lt;/li&gt;
 	&lt;li&gt;Permiten salidas PWM (Power Width Modulation).&lt;/li&gt;
 	&lt;li&gt;Pueden usar interrupciones.&lt;/li&gt;
&lt;/ul&gt;
Nos apoyaremos en el siguiente esquema de un Timer_A para ver cómo se utiliza.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1JV8hjLCLMV1qK3Q531rtQprAHw3Vb2CxtcTWsIAHNQSOzLA2Dx27MrUj3xIE0MaI6YrxBCe7wVEI49gucFOuLMIlqp2_hQPQLCXs-XEoTKJM13-xoyiIBJDjJ_SpBHh-51t0q2VCY-ZNyh8rIrO4Q5WtvO2ymGXqDEo0lOZb4Rb_VcRjCDNLaEXfcTA/s1600/33e06-timer_a.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Timer A&quot; border=&quot;0&quot; data-original-height=&quot;803&quot; data-original-width=&quot;724&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1JV8hjLCLMV1qK3Q531rtQprAHw3Vb2CxtcTWsIAHNQSOzLA2Dx27MrUj3xIE0MaI6YrxBCe7wVEI49gucFOuLMIlqp2_hQPQLCXs-XEoTKJM13-xoyiIBJDjJ_SpBHh-51t0q2VCY-ZNyh8rIrO4Q5WtvO2ymGXqDEo0lOZb4Rb_VcRjCDNLaEXfcTA/s1600/33e06-timer_a.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;


&lt;p&gt;Hay dos tipos de bloques bien diferenciados. Rodeado de verde está el bloque contador, y de azul el bloque de captura/comparación, de los que puede haber hasta siete en cada timer.
&lt;h3&gt;Bloque contador&lt;/h3&gt;
El bloque contador dispone de un contador de 16 bits llamado TAxR (donde x será 0 para el timer TA0 y así sucesivamente). Para controlar este bloque se utiliza el registro TAxCTL.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqrmK2RUra0RJSoZsJJxtzsLZyTAvgw4K4VCrWqG6hXeePf618sUtRcr7uUfVlquQU1GATojrXDeTZ1eoZGjhrcnL1Vmgc2M5KsK3Di95Cobdg83dWUuNjR38rHBrDj5FA5h8mRFMM_yC_LxNn4qCsRYPjr5Ye7hsOvMxpniC70nJRFi4UuWix15ovqi4/s1600/bfd8a-registro-taxctl.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Registro TAxCTL&quot; border=&quot;0&quot; data-original-height=&quot;752&quot; data-original-width=&quot;836&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqrmK2RUra0RJSoZsJJxtzsLZyTAvgw4K4VCrWqG6hXeePf618sUtRcr7uUfVlquQU1GATojrXDeTZ1eoZGjhrcnL1Vmgc2M5KsK3Di95Cobdg83dWUuNjR38rHBrDj5FA5h8mRFMM_yC_LxNn4qCsRYPjr5Ye7hsOvMxpniC70nJRFi4UuWix15ovqi4/s1600/bfd8a-registro-taxctl.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;


&lt;ul&gt;
 	&lt;li&gt;El campo TASSEL nos permite seleccionar la fuente de reloj.&lt;/li&gt;
 	&lt;li&gt;El campo ID permite dividir la frecuencia del reloj.&lt;/li&gt;
 	&lt;li&gt;El campo MC permite seleccionar el modo de funcionamiento del contador.&lt;/li&gt;
 	&lt;li&gt;El campo TACLR pone a cero el contador TAxR.&lt;/li&gt;
 	&lt;li&gt;El campo TAIE habilita las interrupciones generadas por el timer.&lt;/li&gt;
 	&lt;li&gt;El campo TAIFG indica si se ha producido una interrupción. Generalmente es un campo para consulta.&lt;/li&gt;
&lt;/ul&gt;
Como se puede observar en el campo MC, disponemos de cuatro modos de cuenta:
&lt;ul&gt;
 	&lt;li&gt;Stop mode (00b): El timer está parado.&lt;/li&gt;
 	&lt;li&gt;Up mode (01b): El timer cuenta hasta el valor almacenado en el registro TAxCCR0 y vuelve a empezar desde 0.&lt;/li&gt;
 	&lt;li&gt;Continuous mode (10b): El timer cuenta hasta el valor fijo 0xFFFF y vuelve a contar desde 0.&lt;/li&gt;
 	&lt;li&gt;Up/Down Mode (11b): El timer cuenta ascendentemente hasta el valor almacenado en el registro TAxCCR0 y después descendentemente hasta 0.&lt;/li&gt;
&lt;/ul&gt;
En el esquema vemos que, además del divisor de frecuencia controlado por el campo ID, hay otro más. Este se controla a través del registro TAxEX0.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQ8dw6EdcXfU08XfE7zTRH5D255lqR9HSaZMOmouAbZM4q89MgCeESPxQy7bkQmXQJ-gou93geu29DcJRTlyWxybPS65ku-CovQFUKNHZtrGTGFjKb4i249mivLlXSSkvNiqT7uj77HMTJZplE3ffefGbXCR7igC3MEP-22sufIpARHNPyjm-WcbWjqLg/s1600/74091-taxex0.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Registro TAxEX0&quot; border=&quot;0&quot; data-original-height=&quot;467&quot; data-original-width=&quot;827&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQ8dw6EdcXfU08XfE7zTRH5D255lqR9HSaZMOmouAbZM4q89MgCeESPxQy7bkQmXQJ-gou93geu29DcJRTlyWxybPS65ku-CovQFUKNHZtrGTGFjKb4i249mivLlXSSkvNiqT7uj77HMTJZplE3ffefGbXCR7igC3MEP-22sufIpARHNPyjm-WcbWjqLg/s1600/74091-taxex0.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;h3&gt;Bloque de captura/comparación&lt;/h3&gt;
Este bloque se controla mediante el registro TAxCCTLn.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF_4CYSA2ZQvIaJOOyOAsKCjkM27I09XDfGR7c6PLwJxqP6o21JkiSDi0LFd91NBAxCkNDM4p4MRFMBiO5_rF3eooDI1YPemhGB9e-4GHASfNV3OLHCZ91wAI4juFA8a3CI-VmPkswLiK4n_X_HvLNzhmio16Cd4Q9kUJ68cXj_ahzfYYatwT12JvMaMk/s1600/58a3b-taxcctln_1.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;TAxCCTLn_1&quot; border=&quot;0&quot; data-original-height=&quot;168&quot; data-original-width=&quot;835&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF_4CYSA2ZQvIaJOOyOAsKCjkM27I09XDfGR7c6PLwJxqP6o21JkiSDi0LFd91NBAxCkNDM4p4MRFMBiO5_rF3eooDI1YPemhGB9e-4GHASfNV3OLHCZ91wAI4juFA8a3CI-VmPkswLiK4n_X_HvLNzhmio16Cd4Q9kUJ68cXj_ahzfYYatwT12JvMaMk/s1600/58a3b-taxcctln_1.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3W6ZJAIXWtNcowVFsKJQxFiDkhllxD0-CsWpaUGQyyvQCMya1Ipbo58r9EzZNPAqLKeq7rcQUZ2K1AB-Ll_sL9euscKiYTBLDjgMbc0C8g9iANdpivDAgjncx3fqTfIvTvqmw5bTsoDL5uZOAJ7jhr3pzmYYvni54rAeEzawLDsVBC8KNMp8C9c8mujM/s1600/fd0a8-taxcctln_2.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;TAxCCTLn_2&quot; border=&quot;0&quot; data-original-height=&quot;813&quot; data-original-width=&quot;835&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3W6ZJAIXWtNcowVFsKJQxFiDkhllxD0-CsWpaUGQyyvQCMya1Ipbo58r9EzZNPAqLKeq7rcQUZ2K1AB-Ll_sL9euscKiYTBLDjgMbc0C8g9iANdpivDAgjncx3fqTfIvTvqmw5bTsoDL5uZOAJ7jhr3pzmYYvni54rAeEzawLDsVBC8KNMp8C9c8mujM/s1600/fd0a8-taxcctln_2.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;
  
&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgosBajJCC_C7UOi0LWaswZJBwgM04XMdhY39zQB7e2xylcTJBn1A9rVhxTJgXfcaBKuVV9l2EyrVTZ3a9YA4he3vWRt75YMLB0OKkQw8ZHg20Y1bOeK1KR5y31WOLYsc8eR-HZVyMj6QlhflG1DUoAn3etf0UBCLdh6YCqAS_Dlk5MKUCVdYvtvP9mJDE/s1600/46cb4-taxcctln_3.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;TAxCCTLn_3&quot; border=&quot;0&quot; data-original-height=&quot;198&quot; data-original-width=&quot;818&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgosBajJCC_C7UOi0LWaswZJBwgM04XMdhY39zQB7e2xylcTJBn1A9rVhxTJgXfcaBKuVV9l2EyrVTZ3a9YA4he3vWRt75YMLB0OKkQw8ZHg20Y1bOeK1KR5y31WOLYsc8eR-HZVyMj6QlhflG1DUoAn3etf0UBCLdh6YCqAS_Dlk5MKUCVdYvtvP9mJDE/s1600/46cb4-taxcctln_3.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Vamos a analizar el uso básico de los contadores, así que nos centraremos en el modo comparación. Así pues, el modo captura no lo analizaremos en este artículo.&lt;/p&gt;&lt;p&gt;El campo CAP nos permitirá seleccionar el modo captura o el modo comparación. Por otro lado, el campo CCIFG del registro TAxCCTLn se activa cuando el valor de la cuenta del registro TAxR alcanza el valor del registro TAxCCRn.&lt;/p&gt;&lt;p&gt;El timer puede generar dos tipos de interrupciones. Por un lado, mediante el bit TAIE del registro TAxCTL podemos generar una interrupción cada vez que el contador alcance el valor 0. El segundo tipo de interrupciones se desencadenan cuando el valor de la cuenta del registro TAxR ha alcanzado el valor almacenado en el registro TAxCCRn (TAxCCRn CCIFG), se habilitan mediante el bit CCIE del registro TAxCCTLn.
&lt;h3&gt;Código de ejemplo&lt;/h3&gt;
Para aclarar conceptos los mejor es analizar un código de ejemplo en el que vamos a poner en práctica lo que se ha visto hasta ahora.&lt;/p&gt;&lt;p&gt;&amp;nbsp;
&lt;pre&gt;#include &amp;lt;msp430.h&amp;gt;
void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;    // Detiene Watchdog
  P1DIR |= BIT0;               // P1.0 salida
  P1OUT |= BIT0;               // Enciende LED  
  // Configuración TIMER_A:
  // TimerA1, ACLK/1,modo up, reinicia TACLR
  TA1CTL = TASSEL_1 | TACLR | MC_1; 
  // ACLK tiene una frecuencia de 32768 Hz
  // Carga cuenta en TA1CCR0 0.1seg TA1CCR=(0,1*32768)-1
  TA1CCR0 = 3276;  
  TA1CCTL0 = CCIE; // Habilita interrupción (bit CCIE)
  // LPM0, habilitación general de interrupciones
  __low_power_mode_0(); 
  __no_operation();
}

// Rutina de interrupción de TIMER1_A0  
#pragma vector=TIMER1_A0_VECTOR
__interrupt void TIMER1_A0_ISR(void){
  P1OUT ^= 0x01;       // conmuta LED en P1.0
}
&lt;/pre&gt;
Este código hace cambiar de estado, cada 0,1 segundos, el LED conectado al puerto P1.0. Para ello se ha programado la interrupción asociada al timer A1. Mediante la siguiente línea se configura el timer  A1 para que utilice el reloj ACLK y configuración en modo UP. También se inicializa el contador poniéndolo a cero.
&lt;pre&gt;TA1CTL = TASSEL_1 | TACLR | MC_1;&lt;/pre&gt;
La interrupción se produce cuando el contador llega al valor almacenado en el registro TACCR0, así que sabiendo que, en este caso, ACLK funciona a una frecuencia de 32768Hz podemos calcular qué valor hay que almacenar en dicho registro. Si quisiéramos que el LED cambiara de estado cada segundo lo tendríamos fácil. Simplemente haríamos TACCR0=32768-1 (siempre restaremos uno al valor deseado). Esto es así porque ACLK cambia de estado 32768 veces en un segundo. Para que el LED cambie un valor arbitrario de veces n usamos la formula TACCR0=(tiempo en segundos * freq. de reloj en Hz)-1.&lt;/p&gt;&lt;p&gt;Así pues, para que cambie de estado cada 0,1 segundos hacemos TA1CCR0=(0,1*32768)-1=3276.
&lt;pre&gt;TA1CCR0 = 3276;&lt;/pre&gt;
Finalmente habilitamos la interrupción del timer mediante el bit CCIE.
&lt;pre&gt;TA1CCTL0 = CCIE;&lt;/pre&gt;
Al entrar en modo LPM0 también estamos activando las interrupciones.&lt;/p&gt;&lt;p&gt;En el siguiente vídeo podemos ver el resultado de la ejecución del anterior código.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe class=&quot;BLOG_video_class&quot; allowfullscreen=&quot;&quot; youtube-src-id=&quot;TWvMNtdzRjo&quot; width=&quot;600&quot; height=&quot;498&quot; src=&quot;https://www.youtube.com/embed/TWvMNtdzRjo&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;</description><link>http://www.ellaberintodefalken.com/2017/01/timers-en-msp430.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhapyNqRUNxPRKGSRUkluvFV4D2NkMVN5JD2V9RVjyaaDSFzhtv5vRe63aKHvFwNfG0Go9YATz3bci1DBVT_8RNtq6J4UasmBIWbTC3C_5eSVRpyaUyPA6x7_3xX40qvgiwbL3p_XJzD64hOqbjJuX9XCOza9QuzA_37lbvhdsxSklw3iWxf7T2of0CuBU/s72-c/e2f20-launchpadmsp430.jpg" height="72" width="72"/><thr:total>0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-8217618158692566582.post-8903330482876981015</guid><pubDate>Sun, 15 Jan 2017 10:27:06 +0000</pubDate><atom:updated>2026-07-14T11:51:28.056+02:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">bajo consumo</category><category domain="http://www.blogger.com/atom/ns#">microcontrolador</category><category domain="http://www.blogger.com/atom/ns#">MSP430</category><category domain="http://www.blogger.com/atom/ns#">post</category><title>Modos de bajo consumo en MSP430</title><description>&lt;p&gt;En el artículo hicimos un repaso al &lt;a href=&quot;www.ellaberintodefalken.com/2017/01/reloj-MSP430-Unified-Clock-System.html&quot;&gt;módulo de reloj&lt;/a&gt;, que está íntimamente ligado a los modos de bajo consumo del microcontrolador MSP430. Una de las ventajas de éste sobre otros microcontroladores es la gran flexibilidad que ofrece a la hora de configurar los relojes y, por ende, el modo de bajo consumo.&lt;/p&gt;
&lt;p&gt;El MSP430 permite la desactivación de las fuentes de reloj y de los propios relojes, lo que permite desactivar la CPU y los periféricos mientras no sean necesarios. Además, el módulo de gestión de energía (PMM o Power Management Module) hace posible incluso apagar el core (CPU) del microcontrolador.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7cpU1Wr-7nQRVKXgrjZCjkiW2nJjOTM-cJO02bWJuQW7L-gqfcoI6whcgGeZ0BCzrsUhUJWwOrWS_KanvR6s4u93Cy9thbfASaPhMYmG2MN4Vedj81NcY_8hxhoY6JerApoVMKXysCjuDXMeRZ5Z-KT5-S21SsZuBEW8zV9UylAIYTFhyOPJNhs62cO0/s1024/98826-msp430.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;MSP430 Launchpad&quot; border=&quot;0&quot; width=&quot;600&quot; data-original-height=&quot;576&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7cpU1Wr-7nQRVKXgrjZCjkiW2nJjOTM-cJO02bWJuQW7L-gqfcoI6whcgGeZ0BCzrsUhUJWwOrWS_KanvR6s4u93Cy9thbfASaPhMYmG2MN4Vedj81NcY_8hxhoY6JerApoVMKXysCjuDXMeRZ5Z-KT5-S21SsZuBEW8zV9UylAIYTFhyOPJNhs62cO0/s600/98826-msp430.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;
  
&lt;p&gt;En la práctica, la mayoría de las aplicaciones no requieren que la CPU o los periféricos estén siempre funcionando. Imaginemos un dispositivo IoT que envía información sobre contaminación atmosférica a un servidor remoto. En este caso, no es necesario estar constantemente consultando el estado de los sensores y enviando información, sino que puede hacerse cada pocos segundos o incluso minutos. Si la operación de lectura de los sensores más en envío de los datos necesitan, por simplificar, 1 segundo y esta operación se realiza cada 60 segundos, aproximadamente el 98% del tiempo el microcontrolador está ocioso, pero consumiendo energía. Si &quot;dormimos&quot; la CPU y los periféricos durante ese tiempo, el consumo cae drásticamente, y eso en un dispositivo autónomo es lo más importante. Además de poder &quot;dormirlo&quot; necesitaremos una forma de &quot;despertarlo&quot;, lo que en general conseguiremos mediante el uso de interrupciones. Evidentemente, esta operación de dormir-despertar conlleva una penalización en tiempo que podría no ser viable en un dispositivo que necesita responder en tiempo real.&lt;/p&gt;

&lt;h3&gt;Modos de bajo consumo&lt;/h3&gt;

&lt;p&gt;Los modos de bajo consumo se configuran mediante el registro de estado de la CPU (registro SR).&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg1AuHUcJ6RlDzqn-cA7G6In_6F5fBsLS43S0p-R7gTd9_eGb-U1i90we8yirisDuYxYDNCuf6YoF2AH9Quik-kTcl1KKgTLkwp-ZG8suAVgoXk3mLYUrw7d3w-o33f0f9TGbwuIvezgOdN2tLNZliSevYRysp6WvHVbAXx6GhlO8gK8EMhjyIzYOdt-Y/s1600/5d311-sr_register.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Registro de estado SR&quot; border=&quot;0&quot; data-original-height=&quot;145&quot; data-original-width=&quot;799&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg1AuHUcJ6RlDzqn-cA7G6In_6F5fBsLS43S0p-R7gTd9_eGb-U1i90we8yirisDuYxYDNCuf6YoF2AH9Quik-kTcl1KKgTLkwp-ZG8suAVgoXk3mLYUrw7d3w-o33f0f9TGbwuIvezgOdN2tLNZliSevYRysp6WvHVbAXx6GhlO8gK8EMhjyIzYOdt-Y/s1600/5d311-sr_register.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

 
&lt;p&gt;En concreto se utilizan los bits SCG0, SCG1, OSCOFF y CPUOFF. Mediante la activación de estos bits podemos seleccionar alguno de los modos de bajo consumo definidos en la siguiente tabla.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjUAu6pFizSakywko7BJJsiSFbFMQHz4kObFriRpbScknBd2A5ko0Osui-Jz6BBaJuQp6RDpTOPjJR-La7s-h7HOgeU9vHF4wipaHn2fwqHV7pwE0f5bvIER3YzUNVaMluxGn5f1SPg6jdAEA3e0PWwdZrvAfL59qYCaqA2I6bZtskNUIj-rStSVjhRyc/s1600/3be57-modos_lpm-e1484046423827.png&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Modos de bajo consumon LPM&quot; border=&quot;0&quot; data-original-height=&quot;814&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjUAu6pFizSakywko7BJJsiSFbFMQHz4kObFriRpbScknBd2A5ko0Osui-Jz6BBaJuQp6RDpTOPjJR-La7s-h7HOgeU9vHF4wipaHn2fwqHV7pwE0f5bvIER3YzUNVaMluxGn5f1SPg6jdAEA3e0PWwdZrvAfL59qYCaqA2I6bZtskNUIj-rStSVjhRyc/s1600/3be57-modos_lpm-e1484046423827.png&quot;/&gt;&lt;/a&gt;&lt;/div&gt;


&lt;p&gt;Los modos LPM3.4 y LPM4.5 no están disponibles en todas las versiones del microcontrolador. En el MSP430F5529, que es el que estoy usando, el modo LPM3.5 no está disponible.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Con todos los bits a 0 el microcontrolador se encuentra en estado &lt;strong&gt;activo&lt;/strong&gt;. En este modo, la CPU y las fuentes de reloj están activas, por lo tanto es el modo de mayor consumo. A cambio la respuesta es más rápida.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;En el modo &lt;strong&gt;LPM0&lt;/strong&gt; y &lt;strong&gt;LPM1&lt;/strong&gt; se para la CPU como consecuencia de la desactivación del reloj MCLK. La diferencia entre ambos es que en el modo LPM0 el bloque FLL se desactiva si no se está usando el DCO y en el modo LPM1 el FLL se desactiva siempre.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;En el modo &lt;strong&gt;LMP2&lt;/strong&gt; y &lt;strong&gt;LMP3&lt;/strong&gt; además de MCLK (y por ende la CPU) también se desactiva SMCLK. ACLK permanece activo. Se diferencian en que LPM2 mantiene parte del FLL activo lo que facilita una más rápida estabilización del DCO (y también algo más de consumo).&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;En el modo &lt;strong&gt;LPM4&lt;/strong&gt; la CPU y todos los relojes de desactivan.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Los modos &lt;strong&gt;LPM3.5&lt;/strong&gt; y &lt;strong&gt;LPM4.5&lt;/strong&gt; paran la CPU y todos los relojes, pero además el PMM desactiva la alimentación del core, lo que implica la perdida del contenido de los registros y la memoria RAM dinámica.&lt;/p&gt;

&lt;h3&gt;Código de ejemplo&lt;/h3&gt;

&lt;p&gt;Partimos del mismo código de ejemplo que ya utilizamos en el artículo sobre &lt;a href=&quot;https://www.ellaberintodefalken.com/2016/12/interrupciones-msp430.html&quot;&gt;interrupciones&lt;/a&gt;. La única diferencia con aquel código es una sola línea dentro del bucle &lt;em&gt;while&lt;/em&gt;:&lt;/p&gt;

&lt;pre class=&quot;wp-block-preformatted&quot;&gt;__low_power_mode_3();&lt;/pre&gt;

&lt;p&gt;Esta línea hace que el microcontrolador entre en modo LPM3 (es fácil adivinar cómo se entra en, por ejemplo, LPM2 ¿no?), lo que implica que el reloj MCLK (por lo tanto también la CPU) y SMLK están parados. Sin embargo ACLK está activo, lo que permite que el sistema mantenga los sistemas periféricos en marcha y por lo tanto, aunque la CPU esté parada, en el momento en que se produzca una interrupción, la rutina de gestión la va a tratar. Cuando esto sucede, obviamente el reloj MCLK se pone en marcha y permite que se ejecute dicha rutina. Al terminar la rutina restaura desde la pila los valores del registro de estados SR, que recordemos, permiten gestionar los modos de bajo consumo. Así pues, tras la ejecución de la rutina de interrupción el microcontrolador vuelve al estado de bajo consumo en el que se encontraba antes de entrar, ya que se restaura el registro SR tal y como estaba antes de la interrupción.&lt;/p&gt;

&lt;pre&gt;#include &amp;lt;msp430.h&amp;gt;
int main(void)
{
  WDTCTL = WDTPW + WDTHOLD; // Detiene el watchdog
  P1DIR |= BIT0;            // P1.0 (LED) como salida digital
  P1REN |= BIT1;	    // Resistencia en P1.1 (pulsador derecho)
  P1OUT |= BIT1;	    // Modo pull-up
  P1IE |= BIT1;		    // habilita la interrupción de P1.1
  P1IES |= BIT1;	    // Interrupción por flanco de bajada
  P1IFG &amp;amp;= ~(BIT1);	    // baja flag de interrupción de P1.1
  // Activar interrupciones
  // también puede usarse: __enable_interrupt()
  __bis_SR_register(GIE);   
  // bucle infinito.
  while (1) {
    __low_power_mode_3(); // entramos en modo LPM3
    __no_operation();
  }
}
#pragma vector=PORT1_VECTOR
__interrupt
void P1_ISR(void) {
  if (P1IFG &amp;amp; BIT1) {	// comprobar fuente de la interrupción P1.1
    if (P1IN &amp;amp; BIT1) {	// Estado de P1.1: si está a 1 (no pulsado)
      P1OUT &amp;amp;= ~BIT0;   // Apaga LED
      P1IES |= BIT1;	// activación por flanco de bajada
    } else {            // y si no (pulsado),
      P1OUT |= BIT0;    // Enciende LED
      P1IES &amp;amp;= ~BIT1;	// activación por flanco de subida
    }
    P1IFG &amp;amp;= ~BIT1;	// puesta a cero del flag de interrupción P1.1
  }
}
&lt;/pre&gt;

&lt;p&gt;Vamos a medir, polímetro en mano, qué efecto produce la línea que hemos añadido en el consumo del sistema. Podemos medir la corriente consumida usando el jumper de 3.3V de la placa launchpad (al quitar el jumper tenemos dos pines sobre los que medir).&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhW_no0NUwAZ06SDeEiK6-AKawwgXfnzWp7nCn0mODv-8VM3toMiX_LmkVnpBpkFPRiQj5hFb7CfotkN5s0zWehP2sdG-yQU0aJloCq8y2qkj90pM1gLVJJDtTfkB4qbWQ3VFQlyKUqi2vmVl4dBG5rbIuo737oSwR25WccAzRykv1mLeEGm3tXjA9FwB4/s1600/8a5e1-modo_activo.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Consumo en modo activo&quot; border=&quot;0&quot; data-original-height=&quot;576&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhW_no0NUwAZ06SDeEiK6-AKawwgXfnzWp7nCn0mODv-8VM3toMiX_LmkVnpBpkFPRiQj5hFb7CfotkN5s0zWehP2sdG-yQU0aJloCq8y2qkj90pM1gLVJJDtTfkB4qbWQ3VFQlyKUqi2vmVl4dBG5rbIuo737oSwR25WccAzRykv1mLeEGm3tXjA9FwB4/s1600/8a5e1-modo_activo.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;


&lt;p&gt;Como se puede observar en la imagen, sin utilizar el modo de bajo consumo (modo activo) tenemos un consumo en &lt;em&gt;stand by&lt;/em&gt; (sin tener en cuenta el consumo al pulsar el botón y encender el LED) de alrededor de 1mA. Ahora veamos el consumo en modo LPM3.&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-9bhvgIOT845ALChhZn-eKyj7U0f1O6k-5kpPwZOUCdY0_-XKJNNg__aO-9BkSQ1Wce2sXfbI-_G_pmKy6sjC01ZFJS53UjxsqmMXkqyyrko0FMrTKnD4J-F5CpknZhDJObqjYdK1uPz1QhXxw7GmJTbWqXzYxUT0_nv4WRpPMIz_UYacg43crZlqwgM/s1600/47fd0-modo_lpm3.jpg&quot; style=&quot;display: block; padding: 1em 0; text-align: center; &quot;&gt;&lt;img alt=&quot;Modo bajo consumo LPM3&quot; border=&quot;0&quot; data-original-height=&quot;576&quot; data-original-width=&quot;1024&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-9bhvgIOT845ALChhZn-eKyj7U0f1O6k-5kpPwZOUCdY0_-XKJNNg__aO-9BkSQ1Wce2sXfbI-_G_pmKy6sjC01ZFJS53UjxsqmMXkqyyrko0FMrTKnD4J-F5CpknZhDJObqjYdK1uPz1QhXxw7GmJTbWqXzYxUT0_nv4WRpPMIz_UYacg43crZlqwgM/s1600/47fd0-modo_lpm3.jpg&quot;/&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Tenemos 0,66mA de consumo en LPM3. Este consumo aun puede mejorarse, pero nos vale como ejemplo práctico del uso de los modos de bajo consumo del MSP430.&lt;/p&gt;</description><link>http://www.ellaberintodefalken.com/2017/01/modos-de-bajo-consumo-en-msp430.html</link><author>noreply@blogger.com (Alberto García Serrano)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7cpU1Wr-7nQRVKXgrjZCjkiW2nJjOTM-cJO02bWJuQW7L-gqfcoI6whcgGeZ0BCzrsUhUJWwOrWS_KanvR6s4u93Cy9thbfASaPhMYmG2MN4Vedj81NcY_8hxhoY6JerApoVMKXysCjuDXMeRZ5Z-KT5-S21SsZuBEW8zV9UylAIYTFhyOPJNhs62cO0/s72-c/98826-msp430.jpg" height="72" width="72"/><thr:total>0</thr:total></item></channel></rss>