Цикл 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
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Привет, люди RPA, это снова я и я несу подарки! В очередном моем приключении о том, как создавать ботов для облегчения рутины. Вот, думаю, стоит...
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
Учебник по веб-скрапингу
Учебник по веб-скрапингу
Привет, ребята... В этот раз мы поговорим о веб-скрейпинге. Целью этого обсуждения будет узнать и понять, что такое веб-скрейпинг, а также узнать, как...
Тонкая настройка GPT-3 с помощью Anaconda
Тонкая настройка GPT-3 с помощью Anaconda
Зарегистрируйте аккаунт Open ai, а затем получите ключ API ниже.
Learning Data Analytics Two: Filtering data in a DataFrame.
Learning Data Analytics Two: Filtering data in a DataFrame.
В Learning Data Analytics One: Using Python and Pandas , я рассказываю о:
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

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