Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Date
Notes
Status Finished
Tecnica muy usada para optimizar ciertos problemas, los cuales poseén:
Una Subestructura Óptima: Una solución global que se puede encontrar al combinar soluciones óptimas de
subproblemas locales.
Problemas empalmados: Una solución óptima que involucra resolver el mismo problema en varias ocasiones.
Lo que hace es evitar computos adicionales guardando el resultado de computaciones previas dentro de una estructura
de datos. (diccionarios, set, tuplas)
Normalmente se usa un diccionario donde las consultas se puden hacer en O(1) . O sea, sea cual sea el input o tamaño
de el diccionario siempre va a tardar lo mismo.
Intercambia tiempo por espacio. Este concepto sucede muchas veces en conceptos de la computación. Cuando
queremos optimizar tiempo posiblemente requeramos más espacio, espacio de disco o memoria.
Optimización de Fibonacci
Recordando que los numeros de fibonacci los podemos definir como la siguiente formula.
Fn = Fn−1 + Fn−2
Explicando esta formula podemos resumir que el número de fibonacci se puede obtener si obtenemos el numero de
fibonacci de n-1 y n-2 , y para obtener el número de fibonacci 1 debemos usar la misma formula, o sea, restarle 1 y 2 para
obtenerlo.
Como te das cuenta es un proceso de alta recursividad y a la vez poco eficiente (en tiempo) cuando vamos a números
grandes. Este algoritmo es de big O exponencial. O(2**n) , ya que estamos llamando a la función 2 veces por cada vez. Lo
que significa peligro.
Nuestro trabajo ahora sera optimizar el algoritmo recursivo añadiendole memoization o memorización.
import sys
def fibonacci_recursivo(n):
if n == 1 or n == 0:
return 1
try:
return memory[n]
except KeyError:
r = fibonacci_dinamico(n-1, memory) + fibonacci_dinamico(n-2, memory)
memory[n] = r
#print(memory)
return r
sys.setrecursionlimit(inp+10)
print(fibonacci_dinamico(inp))
En este codigo definimos la función fibonacci_recursivo y fibonacci_dinammico , lo interesante del fibonacci dinamico es que no
posee las mismas limitaciones del codigo recursivo, se puede decir que su crecimiento es ahora n log n , crece rapido pero
no de manera exponencial como lo hace el codigo recursivo.
Esta optimiziación de crecimiento (en tiempo) es gracias al uso del diccionario memory , el cual le ayuda al sistema a recordar
si el calculo x numero fibonacci ya fue realizado.
Con esto finalizamos la optimización, ya sabemos lo suficiente como para optimizar cualquier codigo recursivo empalmado
poco eficiente gracias a la tecnica de Memorizacion . Empalmado=Que se repite una y otra vez los mismo resultados uhff)
Caminos Aleatorios
Todos los programas hasta ahora han sido deterministas, lo que significa que al mismo input obtenemos el mismo output.
Esta tecnica es una formalización matemática de la trayectoria que resulta de hacer sucesivos pasos aleatorios.
Existen problemas que por su naturaleza incluyen aleatorieadad, lo que significa que incluyen elementos aleatorios y
debemos saber como incorporar estos elementos en nuestros programas.
Con esto nos introducimos a un tipo de simulación muy especifica llamado Caminos Aleatorios .
Tipo de simulación que elige aleatoriamente una decisión dentro de un conjunto de decisiones válidas.
Se utiliza en campos del conocimiento cuando los sistemas no son deterministas e incluyen elementos de aleatoriedad.
Todos estos ejemplos son imposibles de realizar de forma deterministica, dado esto podemos extraer el comportamiento
de una particula y multiplicarlo por miles y poder generar estas simulaciones de colisiones gracias a las nociones de caminos
aleatorios .
Cuando queremos simular si una acción sube o baja debemos usar caminos aleatorios, generando así simulación de los
precios de compra/venta en el mercado.
Con estos ejemplos en mente podemos asumir que los caminos aleatorios son una herramiento lagica conceptual bastante
util y poderosa.
Aleatoriedad en Python
Proyecto de los 3 borrachos.
for _ in range(pasos):
campo.mover_borracho(borracho)
return inicio.distancia(campo.obtener_coordernada(borracho))
for _ in range(numero_de_intentos):
campo = Campo()
campo.anadir_borracho(borracho, origen)
simulacion_caminata = caminata(campo, borracho, pasos)
distancias.append(round(simulacion_caminata, 1))
return distancias
show(grafica)
distancias_media_por_caminata.append(distancia_media)
graficar(distancias_de_caminata,distancias_media_por_caminata)
if __name__ == '__main__':
distancias_de_caminata = [10, 100, 1000, 10000]
numero_de_intentos = 100
Los programas deterministicos son muy importantes, pero existen problemas que no pueden resolverse de esta
manera.
Aquí aparece la programación estocástica, la cual permite introducir aleatoriedad a nuestros programas para crear
simulaciones que permiten resolver otro tipo de problemas. Como lo puede ser el manejo de trafico, simulaciones
financieras, efectos de drogas, vehiculos autonomos, etc..
Los programas estocásticso se aprovechan de que las distribuciones de probabilisticas de un problema se conocen o
pueden ser estimadas.
Cálculo de Probabilidades
Ya sabiendo que debemos usar la programación estocástica en problemas probabilisticos o problemas que existen con un
elemeneto de aleatoriedad, es importante comprender probabilidades.
La probabilidad es una medida de certidumbre asociada a un evento o suceso futuro y suele expresarse como un
número entre 0 y 1.
Al hablar de probabilidad preguntamos qué fracción de todos los posibles eventos tiene la propiedad que buscamos.
Por eso es importante poder calcular todas las posibilidades de un evento para entender su probabilidad.
P (A) + P (∼ A) = 1
Ley multiplicativa
Independiente uno de otro
Una ley general dentro de esta ley es que siempre el resultado de la probabilida de ocurrencia de ambos sucesos es
menor a la de un sucede independiente.
P (A ÷ B) = P (B ÷ A) ⋅ P (A ÷ B)
Ley Aditiva
Mutuamente Exclusivos
¿Cual es la probabilidad de que A suceda o que B suceda? Al ser eventos mutuamente exclusivos, o sea, uno a la vez,
se suman ambas probabilidadaes de ocurrencia de los sucesos.
Ejemplo: ¿Cual es la probabilidad de que vaya al supermercado y vaya a un restaurante? Ambos sucecos no pueden
suceder a la vez, dado eso se le denomina mutuamente exclusivos.
No Exclusivos
Y al no ser exclusivos, o sea, que sucedan a la vez, sumamos ambas probabilidades de ocurrencia, P (A) + P (B) y
restamos la probabilidad de que ocurran ambas a la vez P (A y B), lo que seria equivalente a la ley multiplicativa.
P (A y B) = P (A) ⋅ P (B)
Ejemplo: La probabilidad de que este usando mi computador en el día es del 0.5 y la probabilidad de que ese día
amanezca es 1 , con esto podemos pensar que la probabilidad seria 1.5 , pero esto estaria erroneo ya que las
probabilidades son de 0 a 1 , entonces además sabiendo que estos sucedos son no exclusivos, lo que quiere decir que
pueden suceder a la vez, debemos restar la probabilidad de que ocurran ambos eventos a la vez. Y la probabilidad de
que ocurran ambos a la vez es de un 0.5 .
Esto nos da como resultado 1 , es 100% probable de que ese día amaneza y use mi computador.
Simulación de probabilidades
Al ser las probabilidades un tema algo complejo, lo más facil es crear simulaciónes en nuestra computadora.
Simulando!
import random
def tirar_dado(numero_de_tiros):
secuencia_de_tiros = []
for _ in range(numero_de_tiros):
tiro = random.choice( [1, 2, 3, 4, 5, 6] ) # random.randint(1,7)
secuencia_de_tiros.append(tiro)
return secuencia_de_tiros
for _ in range(numero_de_intentos):
secuencia_de_tiros = tirar_dado(numero_de_tiros)
tiros.append(secuencia_de_tiros)
tiros_con_1 = 0
for tiro in tiros:
if 1 not in tiro:
tiros_con_1 += 1
return probabilidad_tiros_con_1
if __name__ == "__main__":
numero_de_tiros = int(input('Tiros del dado: '))
numero_de_intentos = int(input('Cuantas veces correr simulacion: '))
promedio = []
for _ in range(numero_de_intentos):
probabilidad = main(numero_de_tiros, numero_de_intentos)
promedio.append(probabilidad)
Inferencia Estadistica
Con las simulaciones podemos calcular las probabilidades de eventos complejos sabiendo las probabilidades de
eventos simples.
Precisamente como dice el titulo: Las tecnicas de inferencia estadistica nos permiten inferir/concluir las propiedades de
un población a partir de una muestra aleatoria.
Tenemos una población objetiva y sacamos una muestra representativa y aleatoria (esto debido a que si sesgamos nuestra
muestra nuestras inferencias estaran sesgadas). Ya una vez con la muestra, podremos obtener conclusiones de la
población.
Resumen: Conforme generamos más y más pruebas la probabilidad de que las pruebas se alejen directamente de la
probabilidad real empieza a ser 0 conforme la cantidad de muestreos se vuelva infinita.
Es decir, la probabilidad de que la media de las muestras sea igual a la media de la población total conforme las muestras
se vuelven infinitas es igual a 1.
La regresión a la media señala que después de un evento aleatoria extremo, el siguiente evento probablemente sera
menos extremo.
Media
Uno de los elementos más importantes de la inferencia estadistica es el calculo de la media.
La media de una población se denota con le simbolo μ (miu). La media de una muestra se denota con X .
Calculo de media.
La varianza y desviación estandar son medidas de propagación. Que tan a diespersos u homogeneos a la media se
encuentran los valores.
Varianza
Nos permite entender que tan dispersos estan los datos.
Desviación Estandar
La desviación estándar es la raíz cuadrada de la varianza.
Nos permite entender la propagación y también se debe entender siempre relacionado a la media.
La ventaja sobre la varianza es que la desviación estándar está en las misma unidades que la media.
import random
import math
def varianza(X):
mu = media(X)
acumulador = 0
for x in X:
acumulador += (x - mu)**2
def desviacion_estandar(X):
return math.sqrt(varianza(X))
if __name__ == '__main__':
X = [random.randint(9,12) for i in range(20)]
mu = media(X)
sigma_cuadrado = varianza(X)
sigma = desviacion_estandar(X)
print(f'Arreglo: {X}')
print(f'Media: {mu}')
print(f'Varianza: {sigma_cuadrado}')
print(f'Desviacion Estandar: {sigma}')
Recap
Mientras la media es una medida de tendencia central, los datos pueden esparcirse mucho o mantenerse muy cercanos a la
media, y estas medidas nos las va a otorgar la varianza y la desviación estandar.
Distribución Normal
Es una de las distribuciones más recurrentes en cualquier ambito. ¿Cual es el lenguaje de programación del año?,
¿Cuales son tus ingresos?
Fijate que solo debemos obtener la media y la desviación estandar como parametros para
poder calcular la distribución normal.
Regla Empírica
Señala cuál es la dispersión de los datos en una distribución normal a uno, dos y tres sigmas o desviaciones estandar.
Regla que nos permite analizar como se distribuyen los datos en una distribución normal.
Analizamos la distribución de los datos sabiendo cuales son los datos que podemos encontrar a un, dos y tres sigma de
distancia.
Esto quiere decir que a una , a dos y a tres deviaciónes estandar.
Esto nos habla de como se estan distribuyendo los datos, podemos decir que sabemos que un evento ocurrira con cierta
probabilidad más o menos el intervalo de confianza.
Ahora podemos entender porque cuando se levanta una encuesta existe un grado de confiabilidad, y es justamente este
95% que te habla del grado de confiabilidad, el cual son las desviaciones estandar que estamos teniendo de las cuales
podemos concluir que las muestras que saquemos de la distribucion se encontraran en ese rango.
Se habla de más menos dado que puede ser más 1na desviaciones media, hacia la derecha y
menos 1na desviación media, hacia la izquierda. Mira el grafico
https://www.youtube.com/watch?v=phY8Z9TXCY
menos más
Simulaciones de Montecarlo
Permite crear simulaciones para predecir el resultado de un problema.
Simulación de Barajas
En esta simulación queremos saber la posibilidad de obtener un par de cartas al iniciar la partida.
Para eso tuvimos que crear una función que genera un mazo aleatorio a paritir de una baraja, tambien generada por una
función, sin repetición de cartas gracias al método sample .
import random
import collections
def crear_baraja():
baraja = []
for tipo in TIPOS:
for valor in VALORES:
baraja.append( (tipo, valor) )
return baraja
mazos = [] # Contiene listas con tuplas [ (),().. ],[ (),().. ],[ (),().. ].....
for _ in range(intentos):
mazo = obtener_mazo(baraja, tamano_mazo)
mazos.append(mazo)
pares = 0
for mazo in mazos:
valores = []
for carta in mazo:
valores.append(carta[1]) # El elemento 1 es el valor
counter = dict(collections.Counter(valores)) # La clase counter cuenta lo repetido de los elementos en la lista valores, al usar la
if __name__ == "__main__":
tamano_mazo = int(input('Tamano de mazo: '))
intentos = int(input('Intentos: '))
main(tamano_mazo, intentos)
Cálculo de PI
Vamos a calcular PI con el método de montecarlo, el cual usa número aleatorios. Es sencillo, imagina que tienes un
cuadrado y dentro de este dibujas un circulo. La formula para calcular el area de circulo es π ⋅ r2 y la formula para calcular
el area de un cuadrado es (2 ⋅ r)2 o l2 su lado al cuadrado.
Nuestro procedimiento sera disparar agujas de forma aleatoria al cuadrado (pudiendo caer dentro del circulo o fuera de el,
o sea, en el cuadrado). Para calcular PI debemos dividir el area de ambas figuras y despejar π .
Y esto es igual a:
π ⋅ r 2 Agujas al interior del circulo
=
(2 ⋅ r) 2 Agujas totales
π ⋅ r 2 Agujas al interior del circulo
2 2
=
2 ⋅ r Agujas totales
π ⋅ r 2 Agujas al interior del circulo
2
=
4 ⋅ r Agujas totales
π Agujas al interior del circulo
=
4 Agujas totales
Los radios se van a su casa. Y queda finalmente la magnifica formula para calcular π .
4 ⋅ Agujas al interior del circulo
π=
Agujas totales
La pregunta del millon es, ¿Como sabemos que agujas estan dentro del circulo?
Muy facil, usando pitagoras.
Como sabemos las ubicaciones de las agujas podemos calcular si estas forman parte del
area de la circunferencia midiendo la distancia con x2 + y 2 , si la distancia desde el
origen hasta la aguja es menor o igual a 1 quiere decir que esta al interior de la
circunferencia. Si es mayor que 1 quiere decir que esta fuera de la circunferencia.
import random
from math import sqrt
from estadisticas import desviacion_estandar, media
def lanzar_agujas(numero_de_agujas):
dentro_circulo = 0
for _ in range(numero_de_agujas):
x = random.random() * random.choice([ -1, 1 ]) # random() Genera un número entre 0 y 1 luego se multiplica por -1 o 1. Ex: (0.4 * -1
y = random.random() * random.choice([ -1, 1 ]) # y = ]0,1[ * -1 o 1
for _ in range(numero_de_intentos):
estimacion_pi = lanzar_agujas(numero_de_agujas) # Llamaremos a la función anterior x veces
estimados.append(estimacion_pi) # Agregaremos el resultado de pi a la lista "estimados"
media_estimados = media(estimados) # Al finalizar la iteración sacaremos la media de los valores al interior de la lista "estimados"
sigma = desviacion_estandar(estimados) # Y su desviación media
print(f'Est={round(media_estimados,5)}, sigma={round(sigma, 5)}, agujas={numero_de_agujas}') # Imprimieremos la estimación de pi, su des
Función main que llama a las demás y incrementa el número de agujas por cada iteración
# Mientras la precision, o sea, cuantas desviaciones estandar queremos sea mayor a la precision partida en la medida de fiabilidad, ejec
while sigma >= precision / 3.00: # 1 para una desviacion estandar=68%, 2=1.96 para dos desviaciones=95% y 3 para el 100%
estimacion(numero_de_agujas, numero_de_intentos) # ... la estimacion
numero_de_agujas *= 2 # y al terminar la estimación, duplica el numero de agujas.
if __name__ == '__main__':
estimar_pi(0.01, 1000, 1000)
Codigo completo:
import random
from math import sqrt
from estadisticas import desviacion_estandar, media
def lanzar_agujas(numero_de_agujas):
dentro_circulo = 0
for _ in range(numero_de_agujas):
x = random.random() * random.choice([ -1, 1 ]) # x = ]0,1[ * -1, 1
y = random.random() * random.choice([ -1, 1 ])
if distancia_desde_centro <= 1:
dentro_circulo += 1
for _ in range(numero_de_intentos):
estimacion_pi = lanzar_agujas(numero_de_agujas)
estimados.append(estimacion_pi)
media_estimados = media(estimados)
sigma = desviacion_estandar(estimados)
print(f'Est={round(media_estimados,5)}, sigma={round(sigma, 5)}, agujas={numero_de_agujas}')
while sigma >= precision / 3.00: # 1 para una desviacion estandar=68%, 2=1.96 para dos desviaciones=95% y 3 para el 100%
estimacion(numero_de_agujas, numero_de_intentos)
numero_de_agujas *= 2
Es importante cuando no tenemos la capacidad computacional para calcular toda la población. Imagine que tenemos un
data set enorme y queremos calcularlo rapidamente con un muestro de la población.
Hay ocasiones en las que no tenemos acceso a toda la población que queremos explorar.
Uno de los grandes descubrimientos de la estadística es que las muestras aleatorias tienden a mostrar las mismas
propiedades que la población objetivo. Con esto podemos sacar conclusiones validas sin tener que explorar toda la
población.
En un muestro estratificado tomamos en consideración las características de la población para partirla en subgrupos y
luego tomamos muestras de cada subgrupo. Esto es para no sesgar grupos de cada población.
Establece que muestras aleatorias de cualquier distribución van a tener una disitribución normal.
Permite entender cualquier distribución como la distribución normal de su media y eso nos permite aplicar todo lo que
sabemos.
Sucede que si tomamos cualquier distribución y le tomamos una muestra y a esa muestra le sacamos su media la
distribución de sus medias tendera a una distribución normal y conforme aumentemos el tamaño de la muestra cada
vez tendera a ser más una distribución normal.
Mientras más muestras obtengamos, mayor será la similitud con la distribución normal.
Del lado izquierdo una muestra binominial, o sea, con dos picos. Tiene una
media y una desviación estandar definida por σ .
Ejemplo:
Datos Experimentales
Es la aplicación del método científico.
Es necesario comenzar con una teoría o hipótesis sobre el resultado al que se quiere llegar.
Se valida o falsea una hipótesis midiendo la diferencia entre las mediciones experimentales y aquellas mediciones
predichas por la hipótesis.
Un ejemplo sería la teoria de la relatividad, en su momento no se pudo comprobar pero en estos días se ha podido
parcialmente comprobar esta teoría.
Regresión Lineal
Permite aproximar una función a un conjunto de datos obtenidos de manera experimental.
No necesariamente permite aproximar funciones linales, sino que sus variantes permiten aproximar cualquier función
polinómica.
Conclusiones
Aprendimos:
Como generar simulaciones computaciones para responder preguntas del mundo real.
Como la inferencia estadística nos permite tener confianza de que nuestras simulaciones arrijan resultados válidos.