Я пытаюсь создать простую числовую функцию градиента, и часть ее - это значения параметров обновления цикла for, которые позже будут оцениваться. Код выглядит следующим образом:
import numpy as np
def target_gradient(theta):
e = 10
for i in range(theta.shape[0]):
theta_upper = theta
theta_lower = theta
theta_upper[i] = theta[i] + e
theta_lower[i] = theta[i] - e
print(f"theta_upper {theta_upper}")
print(f"theta_lower {theta_lower}")
return theta_upper, theta_lower
u, l = target_gradient(np.array([1, 1, 1, 1, 1]))
Однако вместо ожидаемого результата я получаю [1 1 1 1 1] для обоих массивов. Операторы печати предназначены для мониторинга и показывают, что на протяжении всего цикла массивы не менялись (т.е. были [1 1 1 1 1]).e=10 так эффект более выражен. Я также попробовал подход enumerate(), но получил тот же результат.
Полная функция градиента будет выглядеть примерно так
def target_gradient(theta, x, y):
e = 0.01
gradient = np.zeros(theta.shape[0])
for i in range(theta.shape[0]):
theta_upper = theta
theta_lower = theta
theta_upper[i] = theta[i] + e
theta_lower[i] = theta[i] - e
gradient[i] = (
foo(theta=theta_upper, x=x, y=y) - foo(theta=theta_lower, x=x, y=y)
) / (2 * e)
return gradient
Поэтому я намеренно объявляю theta_upper = theta внутри цикла, потому что хочу вычислить градиент, для которого мне нужны частные (числовые) производные.
В питоне theta_upper = theta не делает копию; theta_upper — это просто другое название theta.
@hpaulj, значит, после того, что я там сделал, theta, theta_upper и theta_lower были всего лишь тремя именами переменных, указывающими на одно и то же место в памяти, верно?
@ cc88 это три переменные, относящиеся к одному и тому же объекту, да. См.: nedbatchelder.com/text/names.html
Причина, по которой theta_upper и theta_lower не меняются внутри цикла, заключается в том, что вы создаете копии theta и назначаете их theta_upper и theta_lower. Следовательно, когда вы изменяете theta_upper[i] или theta_lower[i], вы не изменяете исходный массив theta.
Чтобы исправить это, вы можете использовать метод copy() для создания копии theta, которую вы можете изменить внутри цикла, например:
def target_gradient(theta):
e = 10
for i in range(theta.shape[0]):
theta_upper = theta.copy()
theta_lower = theta.copy()
theta_upper[i] = theta[i] + e
theta_lower[i] = theta[i] - e
print(f"theta_upper {theta_upper}")
print(f"theta_lower {theta_lower}")
return theta_upper, theta_lower
Они не создают копии theta, поэтому theta_upper — это тот же массив, что и theta_lower. Вы создаете копии theta, поэтому эти переменные теперь независимы. Также обратите внимание, что вы, вероятно, захотите сделать это вне цикла, потому что, если вы сделаете это внутри, результирующие массивы будут отличаться от theta только в последнем индексе.
Кроме того, пожалуйста, не добавляйте нерелевантный текст, например «дайте мне знать, если это поможет» или «надеюсь, это поможет». Это подразумевается тем фактом, что вы разместили ответ
@PranavHosangadi, значит, массив внутри цикла действует как локальная (для цикла) переменная? И если я хочу объявить отдельный массив в памяти, я должен назначить его с помощью .copy()?
@ cc88 нет, циклы не имеют собственной области видимости в python. ЕСЛИ вы хотите создать новый объект в Python, вы должны сделать это явно. Использование .copy() — это один из способов.
@juanpa.arrivillaga, так что предложение g.newt верно, даже если оно небрежно объяснено? (это сработало для меня, кстати)
@cc88 да. «Небрежно» — щедро. Их объяснение совершенно неверно, потому что у них оно наоборот. Это также не лучший способ делать то, что вы хотите, особенно в свете вашего редактирования.
@PranavHosangadi Не могли бы вы предложить альтернативный способ сделать это? Я бы дал ему ответ, если вы это сделаете, очевидно.
@cc88 Уже сделал :) Смотрите мой ответ
Ха-ха, извините, я был занят и написал это на лету. Я должен был перепроверить свое решение.
Лучший подход зависит от того, что такое foo:
Если foo может принимать векторные аргументы и возвращать векторные значения, например.
def foo(theta, x, y):
return x * y * np.sin(theta)
Тогда вы можете просто сделать:
def target_gradient(theta, x, y, e=0.01):
foo_upper = foo(theta + e, x, y) # Add e to the entire theta vector, and call foo
foo_lower = foo(theta - e, x, y) # Subtract e from the entire theta vector, and call foo
return (foo_upper - foo_lower) / (2 * e)
Основываясь на вашем коде, где вы передаете вектор theta_upper в foo, я подозреваю, что это так.
Если foo не может принимать векторные аргументы и возвращать векторные значения, например.
def foo(theta, x, y):
return x * y * math.sin(theta)
Затем вам нужно перебрать theta и вызвать foo для каждого значения.
def target_gradient(theta, x, y, e=0.01):
gradient = np.zeros(theta.shape[0])
for i in range(theta.shape[0]):
foo_upper = foo(theta[i] + e, x[i], y[i]) # Take a single value of theta, and add e
foo_lower = foo(theta[i] - e, x[i], y[i]) # Take a single value of theta, and subtract e
gradient[i] = (foo_upper - foo_lower) / (2 * e)
return gradient
+1, довольно элегантно, просто обратите внимание, что векторное решение не будет работать, потому что оно не будет давать частные производные, необходимые для градиента. Невекторное решение работает.
@ cc88, как я уже сказал, это зависит от foo. В этом случае я предположил, что ваша функция — это x * y * sin(theta), где theta.shape == x.shape == y.shape и все они векторы. Например: thetas = np.linspace(0, 2*np.pi, 100); x = np.ones_like(thetas); y = np.ones_like(thetas) дает одно и то же target_gradient(thetas, x, y) для обеих реализаций. Конечно, это меняется, если ваша функция отличается
Каков ваш ожидаемый результат? Если вы просто добавляете 10 ко всем элементам theta, ваша функция может быть просто return theta + 10, theta - 10, потому что массивы numpy могут добавлять скаляры поэлементно.