Цикл Python `for` не обновляет значения массива `numpy`

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

Каков ваш ожидаемый результат? Если вы просто добавляете 10 ко всем элементам theta, ваша функция может быть просто return theta + 10, theta - 10, потому что массивы numpy могут добавлять скаляры поэлементно.

Pranav Hosangadi 17.02.2023 18:36

В питоне theta_upper = theta не делает копию; theta_upper — это просто другое название theta.

hpaulj 17.02.2023 18:38

@hpaulj, значит, после того, что я там сделал, theta, theta_upper и theta_lower были всего лишь тремя именами переменных, указывающими на одно и то же место в памяти, верно?

cc88 17.02.2023 18:53

@ cc88 это три переменные, относящиеся к одному и тому же объекту, да. См.: nedbatchelder.com/text/names.html

juanpa.arrivillaga 17.02.2023 18:55
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
4
56
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Причина, по которой 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 только в последнем индексе.

Pranav Hosangadi 17.02.2023 18:34

Кроме того, пожалуйста, не добавляйте нерелевантный текст, например «дайте мне знать, если это поможет» или «надеюсь, это поможет». Это подразумевается тем фактом, что вы разместили ответ

Pranav Hosangadi 17.02.2023 18:39

@PranavHosangadi, значит, массив внутри цикла действует как локальная (для цикла) переменная? И если я хочу объявить отдельный массив в памяти, я должен назначить его с помощью .copy()?

cc88 17.02.2023 18:51

@ cc88 нет, циклы не имеют собственной области видимости в python. ЕСЛИ вы хотите создать новый объект в Python, вы должны сделать это явно. Использование .copy() — это один из способов.

juanpa.arrivillaga 17.02.2023 18:56

@juanpa.arrivillaga, так что предложение g.newt верно, даже если оно небрежно объяснено? (это сработало для меня, кстати)

cc88 17.02.2023 19:00

@cc88 да. «Небрежно» — щедро. Их объяснение совершенно неверно, потому что у них оно наоборот. Это также не лучший способ делать то, что вы хотите, особенно в свете вашего редактирования.

Pranav Hosangadi 17.02.2023 19:03

@PranavHosangadi Не могли бы вы предложить альтернативный способ сделать это? Я бы дал ему ответ, если вы это сделаете, очевидно.

cc88 17.02.2023 19:07

@cc88 Уже сделал :) Смотрите мой ответ

Pranav Hosangadi 17.02.2023 19:09

ха-ха, извините, я был занят и написал это на лету. Я должен был перепроверить свое решение.

g.newt 22.02.2023 19:39
Ответ принят как подходящий

Лучший подход зависит от того, что такое 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 17.02.2023 19:29

@ 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) для обеих реализаций. Конечно, это меняется, если ваша функция отличается

Pranav Hosangadi 17.02.2023 19:36

Другие вопросы по теме