Я пытаюсь создать простую числовую функцию градиента, и часть ее - это значения параметров обновления цикла 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 могут добавлять скаляры поэлементно.