Stack Overflow en español Asked on January 6, 2021
Tengo dos conjuntos de datos:
x: [(1, -14580.0, -3389.0), (1, -18403.0, 19052.0), (1, -31845.0, -39952.0), (1, -10634.0, -220.0), (1, -9180.0, 18411.0), (1, -27952.0, -8415.0), (1, -25686.0, 8325.0), (1, -22570.0, 8041.0), (1, -34124.0, -5791.0), (1, -20119.0, 2468.0), (1, -12164.0, 53333.0), (1, -14587.0, -66443.0), (1, -5928.0, -7212.0), (1, -25489.0, 5610.0), (1, -33283.0, -18741.0), (1, -18729.0, 1234.0), (1, -25495.0, -361.0), (1, -33315.0, -6149.0), (1, -28093.0, 11441.0), (1, -36642.0, -3513.0), (1, -26365.0, 8214.0), (1, -36366.0, 10423.0)]
ts:
[131479.0, 128090.0, 147142.0, 107190.0, 106970.0, 125381.0, 116966.0, 125291.0, 133332.0, 127541.0, 130009.0, 183342.0, 116899.0, 109687.0, 115297.0, 96556.0, 97790.0, 97429.0, 91280.0, 102721.0, 99208.0, 107422.0, 117845.0, 168755.0, 110971.0, 84198.0, 82014.0, 77827.0, 72295.0, 64114.0, 63187.0, 66079.0, 72843.0, 71056.0]
value: 422080882435.25397
y quiero hacer una regresión lineal múltiple para predecir el beta.
import random
def dot(v,w):
return sum(v_i*w_i for v_i, w_i in zip(v,w))
def predict(x_i, beta):
"""assumes that the first element of each x_i is one"""
return dot(x_i, beta)
def error(x_i, y_i, beta):
return y_i - predict(x_i, beta)
def squarred_error(x_i, y_i, beta):
return error(x_i, y_i, beta)**2
def squarred_error_gradient(x_i, y_j, beta):
return [-2 * x_ij * error(x_i, y_j, beta)
for x_ij in x_i]
def in_random_order(data):
"""generator that returns the elements if data in random order"""
indexes = [i for i, _ in enumerate(data)] # create a list of indexes
random.shuffle(indexes) # suffle them
for i in indexes:
# print("data[i]: ", data[i])
return data[i]
def minimize_stochastic(target_fn, gradient_fn, x,y, theta_0, alpha_0=0.01):
data = zip(x,y)
theta = theta_0 #initial guess
almpah = alpha_0 # initial step size
min_theta, min_value = None, float('inf') # the minimum so far
iterations_with_no_improvment = 0
# if we ever go 100 iterations with no improvment, stop
while iterations_with_no_improvment < 100:
value = sum(target_fn(x_i, y_i, theta) for x_i, y_i in data)
print("value: ", value)
if value < min_value:
# if we've found a new minimum, remember it
# and go back to the original step size
min_theta, min_value = theta, value
iterations_with_no_improvment = 0
alpha = alpha_0
else:
# otherwise we're not improving, so try shrinking the step size
iterations_with_no_improvment +=1
alpha *=0.9
# and take a gradient step for each of the data points
# print("data: ", [x for x in data])
# print("data: ", data)
for x_i, y_i in in_random_order(data):
gradient_i = gradient_fn(x_i, y_i, theta)
theta = vector_substract(theta, scalar_multiply(alpha_gradient_i))
return min_theta
def estimate_beta(x,y):
beta_initial = [random.random() for x_i in x[0]]
return minimize_stochastic(squarred_error,
squarred_error_gradient,
x,y,
beta_initial,
0.001)
random.seed(0)
beta = estimate_beta(x, ts)
Sin embargo, parece que in_random_order(data)
no funciona porque el data es un zip
que esta None
. En efecto obtengo:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-109-c0f3fa276b50> in <module>()
69
70 random.seed(0)
---> 71 beta = estimate_beta(x, ts)
1 frames
<ipython-input-109-c0f3fa276b50> in minimize_stochastic(target_fn, gradient_fn, x, y, theta_0, alpha_0)
54 # print("data: ", [x for x in data])
55 # print("data: ", data)
---> 56 for x_i, y_i in in_random_order(data):
57 gradient_i = gradient_fn(x_i, y_i, theta)
58 theta = vector_substract(theta, scalar_multiply(alpha_gradient_i))
TypeError: 'NoneType' object is not iterable
Ya que el error salta en la línea que dice:
for x_i, y_i in in_random_order(data):
el problema es que in_random_order(data)
ha generado como resultado None
(o un iterable cuyo primer elemento es None
en cuyo caso fallaría el desempaquetado con el mismo mensaje de error).
¿Por qué estará devolviendo None
esa la función? Veamos su código:
def in_random_order(data):
"""generator that returns the elements if data in random order"""
indexes = [i for i, _ in enumerate(data)] # create a list of indexes
random.shuffle(indexes) # suffle them
for i in indexes:
return data[i]
Vemos que debería estar devolviendo data[i]
, pero el data
que le estás pasando se inicializa en esta otra línea, de otra función:
data = zip(x,y)
O sea que data
no es una lista, sino el resultado de un zip()
. Ocurre que este resultado, aunque es un iterable, no es indexable. No puedes usarlo como si fuera una lista para tratar de obtener su resultado i-ésimo, como puedes comprobar con este experimento:
>>> test = zip(x, y)
>>> test[1]
TypeError: 'zip' object is not subscriptable
Entonces la función debería haber generado esa excepción al llegar al return data[i]
. Sin embargo no ha generado tal excepción sino que ha retornado None
¿cómo pudo suceder esto?
Sólo hay una forma. No llegó nunca a ejecutar esa línea. Y ésto solo puede suceder si este bucle:
for i in indexes:
return data[i]
no itera ni una sola vez. Es decir, si el iterable indexes
está vacío. Esto es de hecho lo que ocurre como puedes comprobar si añades un print(indexes)
delante del bucle.
Esto significa por tanto que el valor de data
que le llega es un objeto zip()
agotado.
Es un iterable por el que ya has iterado una vez. Los iterables en python, una vez llegas a su final, no puedes volver a iterar por ellos. Rápida demostración:
>>> a = zip(x, y)
>>> test = [ v for v in a ]
# La línea anterior no da problemas. `a` es un iterable, y podemos iterar por él
# Si imprimiéramos ahora el valor de `test` veríamos todos sus elementos
# No lo hago porque es largo
>>> len(test)
22
>>> test2 = [ v for v in a ]
# Esta línea tampoco daría problemas, pero ya que el objeto zip se ha agotado
# no se realizará niguna iteración. test2 resulta vacío
>>> len(test2)
0
Por tanto tu código itera sobre data
en algún momento antes de pasarlo como parámetro a in_random_order()
.
Efectivamente vemos que lo hace en la línea:
value = sum(target_fn(x_i, y_i, theta) for x_i, y_i in data)
Es aquí donde data
queda agotado, y por tanto al pasarlo después como parámetro a in_random_order()
ya no se obtienen más elementos al iterar sobre data
.
Esto nos lleva a tu pregunta. Seguramente tú te sospechabas que estaba pasando algo de esto pues preguntabas "usar un objeto zip de forma permanente", que entiendo que viene a significar que quieres iterar sobre él varias veces.
Ya hemos visto que sobre un iterador sólo se puede iterar una vez. Pero si a partir del iterador construyes una lista, matas dos pájaros de un tiro ya que si data
fuese una lista:
data[i]
, resolviendo otro problema que tenías antes.Así pues, basta cambiar esta asignación:
data = zip(x, y)
por esta otra:
data = list(zip(x, y))
El constructor list()
itera por los contenidos del objeto zip()
, creando con ellos una lista.
Con ese cambio ahora rompe por otra razón:
---> 57 for x_i, y_i in in_random_order(data):
58 gradient_i = gradient_fn(x_i, y_i, theta)
59 theta = vector_substract(theta, scalar_multiply(alpha_gradient_i))
ValueError: too many values to unpack (expected 2)
Ahora resulta que return data[i]
tiene más valores de los que se esperaban (se esperaban 2, uno para x_i
y otro para y_i
. En vez de eso se obtienen tres. ¿Por qué?
En este caso el error es más sutil. Y es que tu función in_random_order()
no hace en realidad lo que tú querías. Pretendías que devolviera los elementos de data
en orden aleatorio, pero en realidad sólo te devuelve el primer elemento de data
, debido a que en este bucle:
for i in indexes:
return data[i]
retornas inmediatamente en la primera iteración. Así que la función en realidad devuelve data[indexes[0]]
y termina. Lo que devuelve será una tupla de este estilo: ((1, 2, 3), 4)
con un elemento de x
y otro de y
.
El bucle for x_i, y_i in ....
iterará sobre esa tupla, que tiene dos elementos, por tanto hará dos iteraciones. En la primera de ellas encontrará el valor (1,2,3)
que intentará asignar a x_i, y_i
y por eso rompe.
El problema aquí es que tu función in_random_order()
debería ser un generador, pero al no contener instrucciones yield
es una función normal.
Es decir, debes cambiar el return
por un yield
:
def in_random_order(data):
"""generator that returns the elements if data in random order"""
indexes = [i for i, _ in enumerate(data)] # create a list of indexes
random.shuffle(indexes) # suffle them
for i in indexes:
yield data[i]
Otra versión más simple sería que en lugar de ir devolviendo con yield
los elementos cambiados de orden, directamente devuelva una lista completa con los elementos desordenados. Es decir, puedes sacar una copia de data
, desordenarla, y devolverla:
def in_random_order(data):
aux = data[:]
random.shuffle(aux)
return aux
Cualquiera de estas soluciones arregla todos los problemas que se pueden arreglar de momento, pues aparece uno nuevo `NameError: name 'vector_substract' is not defined, pero ese ya no lo puedo solucionar con el código que proporcionas.
Correct answer by abulafia on January 6, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP