Pytorch: torch.lobpcg selalu rusak untuk autograd

Dibuat pada 23 Mei 2020  ·  67Komentar  ·  Sumber: pytorch/pytorch

Bug

Tampaknya torch.lobpcg (https://pytorch.org/docs/stable/torch.html?highlight=lobpcg#torch.lobpcg) selalu putus ketika mencoba mengambil gradien melalui backward .

Untuk Mereproduksi

Berikut adalah contoh minimalis yang menunjukkan lobpcg melanggar.

# lob.py
import torch as T
T.autograd.set_detect_anomaly(True)

A = T.randn(10, 10)
A.requires_grad_()
S = A.matmul(A.t())
e, v = T.lobpcg(S, k=3)
S_hat = T.einsum('ij,j,kj->ik', v, e, v) # v * diag(e) * v^T
loss = S_hat.abs().sum()
loss.backward() # breaks here

Menjalankan kode itu menghasilkan kesalahan berikut.

Warning: Error detected in MmBackward. Traceback of forward call that caused the error:
  File "lob.py", line 9, in <module>
    e, v = T.lobpcg(S, k=3)
  File "/usr/local/lib/python3.5/dist-packages/torch/_lobpcg.py", line 261, in lobpcg
    worker.run()
  File "/usr/local/lib/python3.5/dist-packages/torch/_lobpcg.py", line 408, in run
    self.update()
  File "/usr/local/lib/python3.5/dist-packages/torch/_lobpcg.py", line 343, in update
    self._update_ortho()
  File "/usr/local/lib/python3.5/dist-packages/torch/_lobpcg.py", line 498, in _update_ortho
    self.X[:, nc:] = mm(S_, Z[:, :n - nc])
 (print_stack at /pytorch/torch/csrc/autograd/python_anomaly_mode.cpp:60)
Traceback (most recent call last):
  File "lob.py", line 12, in <module>
    loss.backward() # breaks here
  File "/usr/local/lib/python3.5/dist-packages/torch/tensor.py", line 198, in backward
    torch.autograd.backward(self, gradient, retain_graph, create_graph)
  File "/usr/local/lib/python3.5/dist-packages/torch/autograd/__init__.py", line 100, in backward
    allow_unreachable=True)  # allow_unreachable flag
RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation: [torch.FloatTensor [10, 5]], which is output 0 of SliceBackward, is at version 14; expected version 11 instead. Hint: the backtrace further above shows the operation that failed to compute its gradient. The variable in question was changed in there or anywhere later. Good luck!

Saya merasa bahwa masalahnya adalah implementasi torch.lobpcg menggunakan operasi di tempat yang seharusnya tidak.

Ini terjadi ketika menjalankan torch.__version__ == '1.5.0+cpu' diinstal dengan pip di Windows 10 WSL (Subsistem Windows untuk Linux) di Python 3.5.2.

Bisakah ini diperbaiki, atau apakah torch.lobpcg tidak dimaksudkan untuk mendukung autograd?

cc @ezyang @albanD @zou3519 @gqchen @pearu @nikitaved @vincentqb @vishwakftw @jianyuh @mruberry @SsnL

autograd linear algebra triaged

Komentar yang paling membantu

Trik matematika menghindari akar kuadrat dari B adalah dengan memperhatikan bahwa inv(B) A simetris dalam produk skalar berbasis B, yaitu x'Bx. Setelah substitusi, di banyak tempat seseorang mendapat inv(B)B sehingga hilang begitu saja.

Semua 67 komentar

@pearu , bisa tolong lihat ini?

Algoritme LOBPCG bersifat iteratif, dan memang, implementasi torch.lobpcg menggunakan operasi di tempat yang tidak dapat dihindari. Namun, masalah saat ini dapat diselesaikan dengan menerapkan dukungan autograd untuk torch.lobpcg : algoritma mundur yang sama yang digunakan dalam torch.eig seharusnya menjadi titik awal yang baik, perlu dimodifikasi untuk k parameter yang membatasi jumlah eigenpairs.

Untuk referensi: https://github.com/pytorch/pytorch/issues/32531 - gradien torch.eig .

Makalah https://people.maths.ox.ac.uk/gilesm/files/NA-08-01.pdf
memperoleh rumus mundur untuk masalah nilai eigen standar yang melibatkan kebalikan dari matriks vektor eigen. Karena lobpcg hanya menyediakan vektor eigen k , rumus tidak berlaku di sini.

@nikitaved bagaimana menurut Anda, apakah masalah dapat dipecahkan ke belakang dengan set eigenpairs yang terbatas?

Fungsi torch.eig memiliki batasan yang sama bahwa jika Anda menetapkan eigenvectors=False , Anda tidak akan dapat menghitung pass mundur.

Dimungkinkan untuk menurunkan gradien untuk k-rank lobpcg, tetapi itu tidak akan menjadi unik karena akan menjadi solusi untuk sistem persamaan linier yang ditentukan lebih. Saya akan menempatkan derivasi saya sedikit kemudian sehingga Anda dapat mengetahui apakah Anda setuju dengan mereka ...

Ah maaf, sistemnya underdetermined, ini dia...
Untuk mempermudah, mari pertimbangkan masalah nilai eigen rank-k dasar dari bentuk AU = UD , di mana A is n by n, rank(U) = rank(D) = k . Kemudian menerapkan operator diff:

dA U + A dU = dU D + U dD,
U^T dA^T = (dU D + U dD - A dU)^T.

Biarkan K := U^T, X:= dA^T, L:= (dU D + U dD - A dU)^T . Kita ingin mencari X dengan menyelesaikan persamaan matriks berikut:

K X = L, where K,L in (k, n), X in (n, n).

kita dapat membaginya menjadi n sistem persamaan linear berbentuk

K X[i] = L[i].

Seperti yang Anda lihat, kita memiliki persamaan k < n dengan n tidak diketahui, jadi itu kurang ditentukan.
Salah satu solusi yang mungkin adalah ini:

Let I = {i_i,...i_k} column indices, such that rank(K[I]) = k, then
X[i]_I = K[I]^{-1} L[i] for indices in I, and X[i]_{[n] \ I} = 0.

Ketidak-unik-an berasal dari sistem yang underdetermined...

Tapi saya kira kita mungkin mengalami masalah ketika kedua matriks input untuk lobpcg memerlukan gradien dan mereka bukan fungsi satu sama lain ...

Tetapi dalam kasus ini, jika Anda mempertimbangkan masalah rank-k, AU = UD secara umum tidak benar. Tidak ada matriks rank-k U dan diagonal D yang memverifikasi ini. Jadi Anda tidak dapat mendasarkan turunan gradien Anda pada persamaan ini kan?

Ambil k vektor eigen dari A sebagai U dan isi diagonal D dengan nilai eigen yang sesuai, ini dia...

@albanD Saya tidak yakin saya mengikuti Anda. Kita punya

E, V = torch.lobpcg(A, k=k)

yang seperti itu

torch.mm(A, V) == torch.mm(V, torch.diag(E))

memegang.

Mengganti k dengan n akan menghasilkan k eigenpairs pertama yang sama seperti saat menggunakan k .

Ya, tepatnya, lobpcg adalah fungsi A -> U, D, sehingga AU = UD, dalam kasus masalah nilai eigne dasar. Di belakang kami menerima dU, dD dan menyebarkannya ke dA melalui persamaan.

Mengganti k dengan n akan menghasilkan k eigenpairs pertama yang sama seperti saat menggunakan k .

Ini sebenarnya tidak benar karena lobpcg hanya berfungsi ketika 3*k<=n . Maaf bila membingungkan. Tetapi lobpcg akan mengembalikan eigenpairs k .

Salah satu solusi yang mungkin adalah ini:

Let I = {i_i,...i_k} column indices, such that rank(K[I]) = k, then
X[i]_I = K[I]^{-1} B[i] for indices in I, and zero otherwise.

Metode ini tampaknya menghasilkan X sedemikian rupa sehingga K X=L hanya menampung kolom k tetapi untuk yang lain tidak (karena kolom yang sesuai di L tidak sepele ).

DIPERBARUI: B -> L

Salah satu solusi yang mungkin adalah ini:

Let I = {i_i,...i_k} column indices, such that rank(K[I]) = k, then
X[i]_I = K[I]^{-1} B[i] for indices in I, and zero otherwise.

Metode ini tampaknya menghasilkan X sedemikian rupa sehingga K X=B hanya menampung kolom k tetapi untuk yang lain tidak (karena kolom yang sesuai di B tidak sepele ).

Saya tidak yakin saya mengerti ... Di X hanya submatriks ukuran ukuran (k, n) bukan nol, sisanya nol.
X[i][I] = K[I]^{-1} L[i] , dan X[i][[n] \ I] = 0 . Lebih tepatnya, X memiliki k baris bukan nol, sisanya nol.

@pearu Kekhawatiran saya adalah bagaimana nilai eigen teratas k bisa tepat jika A memiliki peringkat > k:

import torch

A = torch.rand(10, 10) # Most likely rank > 3
k = 3

E, V = torch.lobpcg(A, k=k)

# This will print != 0
print(( torch.mm(A, V) - torch.mm(V, torch.diag(E)) ).abs().max())

@albanD , ambil k eigenvectors/eigenvalues ​​dari A, sehingga kita memiliki Au_i = lambda_i u_i. Sekarang, Misalkan U = (u_1, ... u_k), D = diag(lambda_1,..., lambda_k). Sekarang Anda memiliki AU = UD.

Ho benar sistem ini hanya memiliki k kolom! Terima kasih !

Kekhawatiran saya adalah bagaimana k nilai eigen teratas bisa tepat jika A memiliki peringkat > k

Input ke lobpcg harus berupa matriks terdefinisi positif simetris. Jadi cobalah

A = torch.mm(A.transpose(-2, -1), A)

sebelum menelepon lobpcg .

Dalam hal ini Anda mendapatkan kesalahan dalam urutan 1e-3 (dengan parameter yang sama, n=10, k=3). Yang merupakan kasus terburuk jadi ok saya pikir.
Tetapi kekhawatiran saya terpecahkan dengan melihat bahwa kami hanya memiliki k vektor yang untuk itu kami memverifikasi kesetaraan ini. Jadi masuk akal bahwa k eigenpairs sudah cukup.

Saya tidak yakin saya mengerti ... Di X hanya submatriks ukuran ukuran (k, n) bukan nol, sisanya nol.
X[i][I] = K[I]^{-1} L[i] , dan X[i][[n] \ I] = 0 . Untuk lebih harga, X memiliki k baris bukan nol, sisanya adalah nol.

@nikitaved , sepertinya baik-baik saja (saya punya sedikit bug sebelumnya). Berikut adalah contoh cepat dari metode Anda:

import torch as T

class LOBPCG2(T.autograd.Function):

    <strong i="14">@staticmethod</strong>
    def forward(ctx, A):
        k = 2
        e, v = T.lobpcg(A, k=k)
        res = T.mm(A, v) - T.mm(v, T.diag(e))
        assert (res.abs() < 1e-5).all()
        ctx.save_for_backward(e, v, A)
        return e, v

    <strong i="15">@staticmethod</strong>
    def backward(ctx, de, dv):
        """
        solve `dA v + A dv = dv diag(e) + v diag(de)` for `dA`
        """
        e, v, A = ctx.saved_tensors

        vt = v.transpose(-2, -1)
        print('vt=', vt)
        print('de=', de)
        print('dv=', dv)
        rhs = (T.mm(dv, T.diag(e)) + T.mm(v, T.diag(de)) - T.mm(A, dv)).transpose(-2, -1)
        print('rhs=', rhs)

        n, k = v.shape
        K = vt[:, :vt.shape[0]]
        print('K.det=', K.det())  # should be > 0
        iK = K.inverse()

        dAt = T.zeros((n, n))
        dAt[:k] = T.mm(iK, rhs)[:k]
        print('dAt=', dAt)
        dA = dAt.transpose(-2, -1)

        res = T.mm(dA, v) + T.mm(A, dv) - T.mm(dv, T.diag(e)) - T.mm(v, T.diag(de))
        print('res=', res)
        return dA

T.random.manual_seed(123)

A = T.randn(6, 6)
S = A.matmul(A.t())
S.requires_grad_()

e, v = LOBPCG2.apply(S)

S_hat = T.einsum('ij,j,kj->ik', v, e, v) # v * diag(e) * v^T
loss = S_hat.abs().sum()
loss.backward()


( res dicetak mendekati nol).

@pearu , jadi, apakah itu berhasil? :)

Ya, panggilan backward berfungsi tetapi metode ini memerlukan verifikasi terhadap kasus torch.symeig dan memahami konsekuensi dari kesewenang-wenangan dA .

Anda dapat membandingkannya untuk kasus k=n , misalnya. Dengan torch.symeig maksud saya.

Tapi itu analitik. Saya yakin pasti ada pemecah persamaan linier di suatu tempat di PyTorch..

Kami memiliki torch.solve .
Juga jika Anda ingin membandingkan perbedaan hingga, Anda dapat menggunakan torch.autograd.gradcheck (dengan input ganda) dengan:

S = S.double().detach().requires_grad_(True)

# You need to make sure in your backward that your call to torch.zeros() creates the proper dtype
# by passing `dtype=A.dtype`.
T.autograd.gradcheck(LOBPCG2.apply, S)

Menariknya, Jacobian wrt nilai eigen yang diberikan oleh perbedaan hingga tidak cocok karena menghasilkan gradien "padat" sementara metode Anda memberikan gradien hanya untuk dua kolom pertama A :/
Tidak dapat melihat wrt Jacobian ke vektor eigen karena yang lain gagal sebelumnya: /

Nah, masalahnya adalah kita memiliki banyak sekali solusi untuk sistem persamaan linear K X = L, where X in (n, n), K, L in (k, n) . Wlog berasumsi bahwa k kolom pertama dari K bebas linier, sebut saja K_k . Kami membagi X menjadi dua submatriks X_k dari ukuran (k, n) dan X_-k dari ukuran (n-k, n) , dan serupa kami membagi K (tetapi berdasarkan kolom), maka kita mendapatkan:

K X = L => [K_k K_-k] [X_k / X_-k] = L => K_k X_k + K_-k X_-k = L. K_k is full rank, so
X_k = K_k^{-1} (L - K_-k * X_-k).

Kami memutuskan untuk memilih satu solusi dengan X_-k = 0 , tetapi Anda dapat mengatur X_-k ke matriks arbitrer dan mendapatkan solusi yang berbeda X = [K_k^{-1} (L - K_-k * X_-k) / X_-k] .
Jadi, saya kembali ke klaim saya, kami memiliki jumlah gradien valid yang tak terbatas ;)

Apakah semua ini subgradien (lokal)?

Oh, tunggu, kami baru saja menemukan diferensial maju dengan X , itu bukan rumus mundur, namun :) Maaf untuk kebingungan, saya salah. Saya akan menuliskan rumus mundur besok ....

Mungkinkah seperti ini? Sekali lagi, dengan asumsi bahwa

A is symmetric (n, n), U is (n, k) and orthogonal, i.e. U^T T = I, D = diag(d_1,...,d_k).
We have AU = UD. Applyting diff we get:
dA U + A dU = dU D + U dD [multiply by U^T on the left, orthogonality of U]
U^T dA U + U^T A dU = U^T dU D + dD, [make a substitute dU = U dC, dC in (k, k)]
U^T dA U + U^T (AU) dC = (U^T U) dC D + dD, [AU = UD, U^T U = I]
U^T dA U + D dC = dC D + dD. (*)

Consider D dC - dC D, since D is a scalar matrix, it can be represented as
D dC - dC D = E o dC, where o is the Hadamard product and E_ij = d_i - d_j.

Substituting it all back into (*) gives:
U^T dA U + E o dC = dD. 
Note that E o dC has zero diagonal, while dD is a scalar matrix, hence
dD = I o (U^T dA U) (**) and
dC = F o (U^T dA U) (***), where F_ij = (d_i - d_j)^{-1} for i != j if d_i = d_j, then F_ij = 0.
Recalling our substitute dU = U dC we get:
dU = U (F o (U^T dA U)) (****)

Now we have all the ingredients for the backward AD:
Tr(D.grad^T dD + U.grad^T dU) =
Tr(D.grad^T ( I o (U^T dA U))) + Tr(U.grad^T dU) = 
Tr(D.grad U^T dA U) + Tr(U.grad^T dU) = [cyclic property of trace]
Tr(U D.grad U^T dA) + Tr(U.grad^T dU) =
[1] + [2],

[2] =  Tr(U.grad^T dU) = Tr(U.grad^T U (F o (U^T dA U))) = [Tr(A (B o C)) = Tr((A o B^T) C)]
Tr((U.grad^T U o F^T)(U^T dA U)) =
Tr(U (U.grad^T U o F^T) U^T dA)

In [1], [2] we sum the terms to the left from the dA and transpose them to get:
A.grad = U (D.grad + (U^T U.grad o F)) U^T.

gradcheck memberikan:

RuntimeError: Jacobian mismatch for output 0 with respect to input 0,
numerical:tensor([[ 5.1314e-04,  1.3259e-01],
        [-2.1311e-02,  5.7138e-02],
        [-1.1582e-02, -5.3796e-01],
        [-2.1237e-02, -1.5204e-01],
        [-2.2161e-02,  3.7977e-01],
        [-2.2827e-02,  4.2838e-03],
        [ 0.0000e+00,  0.0000e+00],
        [ 2.2126e-01,  6.1558e-03],
        [ 2.4050e-01, -1.1592e-01],
        [ 4.4100e-01, -3.2760e-02],
        [ 4.6018e-01,  8.1831e-02],
        [ 4.7400e-01,  9.2305e-04],
        [ 0.0000e+00,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00],
        [ 6.5354e-02,  5.4569e-01],
        [ 2.3968e-01,  3.0844e-01],
        [ 2.5010e-01, -7.7046e-01],
        [ 2.5761e-01, -8.6907e-03],
        [ 0.0000e+00,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00],
        [ 2.1974e-01,  4.3584e-02],
        [ 4.5860e-01, -2.1774e-01],
        [ 4.7237e-01, -2.4561e-03],
        [ 0.0000e+00,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00],
        [ 2.3927e-01,  2.7195e-01],
        [ 4.9292e-01,  6.1352e-03],
        [ 0.0000e+00,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00],
        [ 2.5386e-01,  3.4603e-05]], dtype=torch.float64)
analytical:tensor([[ 5.1314e-04,  1.3259e-01],
        [-1.0655e-02,  2.8569e-02],
        [-5.7910e-03, -2.6898e-01],
        [-1.0619e-02, -7.6018e-02],
        [-1.1081e-02,  1.8989e-01],
        [-1.1413e-02,  2.1419e-03],
        [-1.0655e-02,  2.8569e-02],
        [ 2.2126e-01,  6.1558e-03],
        [ 1.2025e-01, -5.7958e-02],
        [ 2.2050e-01, -1.6380e-02],
        [ 2.3009e-01,  4.0916e-02],
        [ 2.3700e-01,  4.6153e-04],
        [-5.7910e-03, -2.6898e-01],
        [ 1.2025e-01, -5.7958e-02],
        [ 6.5354e-02,  5.4569e-01],
        [ 1.1984e-01,  1.5422e-01],
        [ 1.2505e-01, -3.8523e-01],
        [ 1.2881e-01, -4.3453e-03],
        [-1.0619e-02, -7.6018e-02],
        [ 2.2050e-01, -1.6380e-02],
        [ 1.1984e-01,  1.5422e-01],
        [ 2.1974e-01,  4.3584e-02],
        [ 2.2930e-01, -1.0887e-01],
        [ 2.3619e-01, -1.2281e-03],
        [-1.1081e-02,  1.8989e-01],
        [ 2.3009e-01,  4.0916e-02],
        [ 1.2505e-01, -3.8523e-01],
        [ 2.2930e-01, -1.0887e-01],
        [ 2.3927e-01,  2.7195e-01],
        [ 2.4646e-01,  3.0676e-03],
        [-1.1413e-02,  2.1419e-03],
        [ 2.3700e-01,  4.6153e-04],
        [ 1.2881e-01, -4.3453e-03],
        [ 2.3619e-01, -1.2281e-03],
        [ 2.4646e-01,  3.0676e-03],
        [ 2.5386e-01,  3.4602e-05]], dtype=torch.float64)


Perhatikan bahwa beberapa elemen pendekatan numerik dan analitis cocok, beberapa tidak.

Bisakah Anda menunjukkan kode Anda? Dan mungkinkah itu karena pendekatan ke ruang eigen yang sebenarnya? Kita bisa mengujinya dengan menjalankan lobpcg lebih lama...

@albanD itu perilaku berikut dari gradcheck diharapkan?:

import torch as T
T.random.manual_seed(123)
A = T.randn(2, 2).double()
S = A.matmul(A.t())
S = S.double().detach().requires_grad_(True)
def mysymeig(A):
    return T.symeig(A, eigenvectors=True)
T.autograd.gradcheck(mysymeig, S)

hasil

RuntimeError: Jacobian mismatch for output 0 with respect to input 0,
numerical:tensor([[ 0.9947,  0.0053],
        [-0.1449,  0.1449],
        [ 0.0000,  0.0000],
        [ 0.0053,  0.9947]], dtype=torch.float64)
analytical:tensor([[ 0.9947,  0.0053],
        [-0.0724,  0.0724],
        [-0.0724,  0.0724],
        [ 0.0053,  0.9947]], dtype=torch.float64)

@albanD , perhatikan bahwa nilai numerik adalah jumlah dari segitiga bawah dan atas dari hasil analitik, seolah-olah gradcheck memperhitungkan simetri input...

Masalahnya di sini adalah bahwa gradcheck hanya melakukan diferensiasi terbatas.
Tapi kami melakukan ini dengan cara yang tidak terstruktur yang merusak simetri A.
Berikut ini berfungsi untuk saya:

import torch as T
T.random.manual_seed(123)
A = T.randn(2, 2).double()
S = A.double().detach().requires_grad_(True)
def mysymeig(A):
    A = A.matmul(A.t())
    return T.symeig(A, eigenvectors=True)
T.autograd.gradcheck(mysymeig, S)

@albanD , perilaku ini cukup mengejutkan karena mysymeig dimodifikasi tidak melakukan apa yang seharusnya dilakukan lagi: selesaikan masalah eigen untuk A , bukan untuk A A^T .

Apakah ada referensi untuk desain gradcheck atau autograd ini secara umum?

Untuk kasus khusus ini, Anda dapat memodifikasi implementasi gradcheck untuk memastikan Anda mengikuti hyperplane dari matriks simetris.
Mengubah l153 dari gradcheck:

            x_tensor = x_tensor.data
            for d_idx, x_idx in enumerate(product(*[range(m) for m in x_tensor.size()])):
                assert len(x_idx) == 2
                if x_idx[0] != x_idx[1]:
                    mul = 2
                else:
                    mul = 1
                orig = x_tensor[x_idx].item()
                x_tensor[x_idx] = orig - eps
                x_tensor[tuple(reversed(x_idx))] = orig - eps
                outa = fn(input).clone()
                x_tensor[x_idx] = orig + eps
                x_tensor[tuple(reversed(x_idx))] = orig + eps
                outb = fn(input).clone()
                x_tensor[x_idx] = orig
                x_tensor[tuple(reversed(x_idx))] = orig
                r = (outb - outa) / (mul * 2 * eps)
                d_tensor[d_idx] = r.detach().reshape(-1)

Setelah perubahan ini, kode asli Anda lulus ujian.

Apakah ada referensi untuk desain gradcheck atau autograd ini secara umum?

Sayangnya tidak :/ Ini "hanya" melakukan diferensiasi terbatas di sepanjang setiap vektor ei.
Jika Anda mengabaikan hal-hal yang kompleks, jarang, xla, intinya adalah:

def get_numerical_jacobian(fn, input, target=None, eps=1e-3):
    if target is None:
        target = input
    output_size = fn(input).numel()
    # Make the empty jacobian will proper size
    jacobian = make_jacobian(target, output_size)

    # It's much easier to iterate over flattened lists of tensors.
    # These are reference to the same objects in jacobian, so any changes
    # will be reflected in it as well.
    x_tensors = iter_tensors(target, True)
    j_tensors = iter_tensors(jacobian)

    for x_tensor, d_tensor in zip(x_tensors, j_tensors):
        # Use .data here to get around the version check
        x_tensor = x_tensor.data
        for d_idx, x_idx in enumerate(product(*[range(m) for m in x_tensor.size()])):
            # Original value
            orig = x_tensor[x_idx].item()
            # Value on left
            x_tensor[x_idx] = orig - eps
            outa = fn(input).clone()
            # value on right
            x_tensor[x_idx] = orig + eps
            outb = fn(input).clone()
            x_tensor[x_idx] = orig
            # Compute the ratio
            r = (outb - outa) / (2 * eps)
            d_tensor[d_idx] = r.detach().reshape(-1)

    return jacobian

Yang untuk fungsi f dengan satu input/output tidak:
grad = ( f(x + eps) - f(x - eps)) / ( 2 * eps) .
Dengan for-loop untuk beberapa input/output.

Terima kasih untuk detail ini.

Untuk mengatasi masalah ini, kita perlu mengimplementasikan lobpcg_backward . Rumus untuk mundur adalah (menggunakan notasi dari NA-08-01.pdf ):

A_bar = U (D_bar + ((U^T U_bar) o F)) U^T

Pertanyaan utamanya adalah, lobpcg_backward harus dikembalikan agar sesuai dengan harapan autograd?
Saya akan berasumsi bahwa itu harus mengembalikan A_bar .

Perhatikan bahwa implementasi symeig_backward mengembalikan (A_bar + A_bar^T)/2 .

Tampaknya untuk menggunakan gradcheck dalam pengujian, seseorang harus menggunakan modifikasi Anda (yang juga ada di test_autograd.py::test_symeig ).

Biarkan saya menambahkan 2 sen saya.In

A_bar = U (D_bar + ((U^T U_bar) o F)) U^T

U dan D secara unik menentukan matriks A n-kali-n dan gangguannya secara unik menentukan A yang terganggu, dan sebaliknya. lobpcg setup beroperasi hanya dengan subset kecil dari k vektor eigen, kolom dari n-by-n matriks U, yang tidak mungkin merekonstruksi n-by-n matriks A secara unik tanpa memaksakan kendala pada A. Satu mungkin masuk akal kendala mengasumsikan bahwa A memiliki peringkat k (dengan asumsi semua nilai eigen bukan nol), yang akan memungkinkan formula mundur yang unik dan mungkin stabil secara numerik, saya kira.

@pearu , maaf atas keterlambatannya. Jadi, mundur untuk k -rank eigenproblem dari atas tampaknya baik-baik saja, saya hanya memperbarui definisi E , yang salah, itu harus E_ij = d_i - d_j dan serupa untuk F_ij . Dan, seperti yang telah kita periksa, rumusnya sama seperti pada symeig_backward .

Sekarang, pertimbangkan versi umum AU = BUD . Kita tahu bahwa B adalah pasti positif, jadi ini adalah peringkat penuh, maka B^{-1} ada. Biarkan C = B^{-1}A , maka masalah umum dapat ditulis ulang sebagai CU = UD . Untuk masalah ini kita sudah tahu bagaimana cara mundur, dengan kata lain, selama AD mundur:

Tr(U_grad^T dU) + Tr(D_grad^T dD) = Tr(C_grad^T dC), where
C_grad is the backward result of an eig problem with the input C=B^{-1}A and the output (U, D), i.e.
C_grad = symeig_backward(U_grad, D_grad, U, D, C).

Jadi, kita dapat menemukan C_grad , sekarang kita perlu menemukan A_grad dan B_grad .

C = B^{-1}A => dC = dB^{-1}A + B^{-1}dA                                               (1)

Recall that BB^{-1} = I => dB B^{-1} + B dB^{-1} = 0 => dB^{-1} = -B^{-1} dB B^{-1}   (2)

Plug (2) into (1):

dC = -B^{-1} dB B^{-1} A + B^{-1} dA

Kembali ke AD mundur:

Tr(C_grad^T dC) = Tr(-C_grad^T B^{-1} dB B^{-1} A) + Tr(C_grad^T B^{-1} dA)
= Tr(-B^{-1} A C_grad^T B^{-1} dB) + Tr(C_grad^T B^{-1} dA), hence [B^{-1}^T = B^{-1}]

A_grad = B^{-1} C_grad,
B_grad = -B^{-1} C_grad C^T.

Tolong, periksa matematikanya.

Juga, untuk meningkatkan stabilitas numerik,

C = B^{-1} A is the solution to [BX = A],
A_grad = B^{-1} C_grad is the solution to [B X = C_grad],
B_grad = -B^{-1} C_grad C^T is the X solution to [solve for Y: B Y = -C_grad A, solve for X: XB = Y]

Semua sistem melibatkan B , sehingga dimungkinkan untuk menghitung beberapa faktorisasi (Cholesky), dan menggunakannya kembali untuk sistem persamaan matriks linier

@pearu , @albanD , lobpcg tidak didefinisikan dalam native_functions.yaml . Itu tidak akan menjadi masalah dengan derivatives.yaml , kan? Bagaimana saya bisa mengekstrak skema yang relevan yang dapat digunakan dalam derivatives.yaml ?
Atau, dengan kata lain, bagaimana saya bisa mengimplementasikan fungsi mundur dari fungsi apa pun yang tidak didefinisikan dalam native_functions.yaml ?

Hai,

Fungsi tersebut diimplementasikan di sini .
Jadi saya pikir cara paling sederhana untuk melakukannya adalah dengan benar-benar membuat autograd.Function kustom (https://pytorch.org/docs/stable/notes/extending.html) dengan penerusan yang sudah ada dan penerusan baru Anda ke belakang.
Kemudian Anda dapat membuat torch.lobpcg menunjuk ke LOBPCGFunction.apply .

@nikitaved , lihat https://github.com/Quansight/pearu-sandbox/blob/master/pytorch/issue38948.py untuk mengimplementasikan lobpcg mundur dengan Python. Berhati-hatilah, ini adalah skrip yang kotor.

Ah, maaf, komentar saya terhapus secara tidak sengaja. derivatives.yaml memungkinkan untuk menentukan lulusan untuk setiap subset input. Bagaimana saya melakukannya dengan pendekatan autograd.Function ? Ada 3 kasus: grad wrt ke joint (A, B), wrt A, wrt B.

Anda dapat memeriksa contoh di dokumen yang saya kirim. Tetapi Anda memiliki daftar ctx.needs_input_grad yang memberi tahu Anda apa yang Anda butuhkan untuk menghitung gradien. Anda dapat menggunakannya untuk mengkondisikan kode mundur Anda agar hanya melakukan pekerjaan yang diperlukan (dan Anda harus mengembalikan None untuk hal-hal yang tidak memerlukan gradien).

Saya mengerti apa yang Anda katakan, jadi saya dapat secara eksplisit memeriksa input yang membutuhkan lulusan, saya mengerti. Terima kasih!

Jika saya memahami dengan benar di atas, perhitungan mundur adalah algoritma agnostik, yaitu berasal dari teori gangguan nilai eigen dan vektor eigen yang diasumsikan dihitung dengan tepat. LOBPCG hanya menghitung kira-kira, jadi hasil perhitungan mundur akan cocok hanya cukup kira-kira. Misalnya, dalam kasus ekstrim di mana LOBPCG melakukan hanya satu iterasi, hasil perhitungan maju dan mundur akan sangat cocok satu sama lain. Ini sangat berbeda dari apa yang biasanya dimaksudkan. Mohon konfirmasi atau koreksi saya.

Jika saya mengerti dengan benar di atas, perhitungan mundur adalah algoritma agnostik

Ya

LOBPCG hanya menghitung kira-kira, jadi hasil perhitungan mundur akan cocok hanya cukup kira-kira. Misalnya, dalam kasus ekstrim di mana LOBPCG melakukan hanya satu iterasi, hasil perhitungan maju dan mundur akan sangat cocok satu sama lain. Ini sangat berbeda dari apa yang biasanya dimaksudkan. Mohon konfirmasi atau koreksi saya.

ya, itu semua benar, namun, saya tidak melihat masalah dengan hal di atas dalam arti bahwa jika pengguna meminta akurasi yang lebih rendah dari lobpcg, dia juga tidak dapat mengharapkan maju/mundur dengan tepat.

Terima kasih atas konfirmasinya! Saya harap perbedaan ini dibuat jelas untuk menghindari kebingungan. Ada metode iteratif sederhana di mana pasangan maju/mundur dapat diimplementasikan dengan cukup akurat, untuk perkiraan awal yang tetap.

Dikirim dari Perangkat 4G LTE T-Mobile saya
Dapatkan Outlook untuk Android https://aka.ms/ghei36


Dari: Pearu Peterson [email protected]
Dikirim: Rabu, 12 Agustus 2020 11:59:33
Kepada: pytorch/pytorch [email protected]
Cc: Knyazev, Andrew [email protected] ; Komentar [email protected]
Subjek: Re: [pytorch/pytorch] torch.lobpcg selalu rusak untuk autograd (#38948)

Jika saya mengerti dengan benar di atas, perhitungan mundur adalah algoritma agnostik

Ya

LOBPCG hanya menghitung kira-kira, jadi hasil perhitungan mundur akan cocok hanya cukup kira-kira. Misalnya, dalam kasus ekstrim di mana LOBPCG melakukan hanya satu iterasi, hasil perhitungan maju dan mundur akan sangat cocok satu sama lain. Ini sangat berbeda dari apa yang biasanya dimaksudkan. Mohon konfirmasi atau koreksi saya.

ya, itu semua benar, namun, saya tidak melihat masalah dengan hal di atas dalam arti bahwa jika pengguna meminta akurasi yang lebih rendah dari lobpcg, dia juga tidak dapat mengharapkan maju/mundur dengan tepat.


Anda menerima ini karena Anda berkomentar.
Balas email ini secara langsung, lihat di GitHub https://github.com/pytorch/pytorch/issues/38948#issuecomment-672962269 , atau berhenti berlangganan https://github.com/notifications/unsubscribe-auth/AKFMTPOSBMKOOUYKNYHUUVTSAK36LANCNFSM4NIJFXFA .

@albanD , lobpcg menerima 14 parameter. Apakah mungkin untuk mengimplementasikan forward/backward sebagai metode kelas, dan bukan sebagai metode statis, untuk membuat forward/backward menerima dan mengembalikan hanya 2 parameter?

Kamu bisa melakukan apapun yang kamu mau :D
Anda dapat membuat semuanya menjadi Fungsi khusus yang mengambil semua 14 parameter (dan akan mengembalikan gradien Tidak Ada untuk sebagian besar dari mereka karena tidak dapat dibedakan).
Atau Anda dapat memiliki beberapa pemrosesan pra/pasca yang dilakukan secara otomatis. Dan hanya inti yang merupakan Fungsi kustom yang lebih sederhana dengan sejumlah argumen yang Anda terapkan maju/mundur.

@nikitaved Karena algoritma mundur yang diusulkan adalah generik dan dengan demikian tidak terkait dengan lobpcg, antarmukanya tidak boleh meniru lobpcg.

Tidak ada yang bisa saya lakukan, forward mereplikasi antarmuka lobpcg, dan mundur membutuhkan lulusan untuk setiap input forward. Saya mungkin dapat menggunakan beberapa gaya autograd.Function dengan konstruktor untuk menyimpan data yang relevan lobpcg dan mengimplementasikan maju/mundur untuk Generalized Eigenvalue Solver yang sempurna, tetapi gaya baru, dengan metode statis, mungkin lebih baik. Aku bisa saja salah, namun.

Mungkin sebenarnya masuk akal untuk membuat metode dengan nama seperti geigk (masalah nilai eigen umum peringkat k persis dengan pembungkus autograd.Function ), kemudian mengimplementasikannya ke belakang, dan kemudian dalam status dokumentasi bahwa penerusannya diimplementasikan dengan lobpcg. Metode ini dapat menggunakan lebih sedikit parameter dengan mengabaikan sebagian besar parameter opsional dari antarmuka lobpcg.

Saya mungkin bisa menggunakan beberapa autograd lama. Gaya fungsi

Tidak, sebagian besar kode telah dihapus. Ini tidak bekerja lagi.

Metode ini dapat menggunakan lebih sedikit parameter dengan mengabaikan sebagian besar parameter opsional dari antarmuka lobpcg.

Apakah pemecah umum seperti itu ada di python lib lain seperti scipy? Jika demikian, kita dapat mencoba mereplikasi API mereka.
Jika tidak ada, kami memiliki sedikit lebih banyak kebebasan di sini.

Apakah pemecah umum seperti itu ada di python lib lain seperti scipy? Jika demikian, kita dapat mencoba mereplikasi API mereka.
Jika tidak ada, kami memiliki sedikit lebih banyak kebebasan di sini.

Ya, eig scipy mendukung antarmuka umum. Bagaimana dengan eigk , untuk membedakan dari "peringkat penuh" eig ?

Nah, rumus di atas untuk kasus simetris, seperti yang ditunjukkan oleh autograd, salah. Setelah melihat lebih dekat, saya menyadari bahwa saya tidak dapat membenarkan pengganti dU = U dC , karena tidak ada jaminan bahwa span(dU) is a subspace of span(U) .

Ada turunan alternatif di bawah ini di mana saya terjebak pada satu titik.

U^T U = I => dU^T U + U^T dU = 0 => du := U^T dU is skew-symmetric, so diag(du) = 0.
AU = UD => 
dA U + A dU = dU D + U dD [left-multiply by U^T]               (*)
U^T dA U + U^T A dU = du D + dD [AU = UD => U^T A = D U^T]
U^T dA U + D du = du D + dD
U^T dA U = (du D - D du) + dD [because du is skew symmetric, diag(du D) = diag(D du) = 0]

so we get
dD = I o (U^T dA U), and, similar to the derivation from above (**)
du = F o (U^T dA U), where F_ij = (d_j - d_i)^{-1}, F_ii = 0   (***)

Ini dia bagian kritisnya

Let U_ortho be an orthonormal basis of a subspace orthogonal to the span(U).
Then we can write
dU = U du + U_ortho dX, where dX is (m-k) x k. This is true for some dX because <U, U_ortho> form a basis of R^m.

By Left-multipling (*) by U_ortho^T, and using the decomposition of dU and orthogonality we get:
U_ortho^T dA U + U_ortho^T A U_ortho dX = dX D. (x)

(x) adalah persamaan Sylvester wrt dX dan kita perlu menyelesaikannya secara eksplisit, sehingga dA memasukkan solusi ini sebagai bagian dari produk matriks. Apakah Anda tahu bagaimana menyelesaikannya? D diagonal.

Semua masalah akan hilang, tentu saja, setelah kita mengetahui seluruh ruang eigen dari A, karena persamaan Sylvester kemudian dapat ditulis sebagai B = dX o C untuk beberapa matriks B dan C.

Persamaan Sylvester dari atas dapat ditulis sebagai

C dX + dX D = E.

symeig_backward mengasumsikan bahwa A memiliki nilai yang berbeda, jadi, di bawah asumsi ini kita memiliki spec(C) intersect spec(D) = the empty set , sehingga sistem memiliki solusi unik.

Kertas ini

Hu, Q., & Cheng, D. (2006). 
The polynomial solution to the Sylvester matrix equation.
Applied mathematics letters, 19(9), 859-864.

menyatakan bahwa persamaan dari atas dapat diselesaikan secara eksplisit sebagai:

dX = p_D(C)^{-1} \sum_{i=1}^k b_i \sum_{j=1}^{i-1} C^{i-1-j} E D^j,
where p_D is a characteristic polynomial of D with coefficients b_i.

Karena D diagonal, polinomial karakteristiknya sederhana dan koefisiennya dapat ditemukan melalui aturan Horner.
Sejak E = U_ortho^T dA U , kita dapat melihat bahwa solusi eksplisit untuk dX telah dA dimasukkan dengan kekuatan 1 dan hanya sebagai bagian dari produk matriks, yang persis seperti yang kita butuhkan dapat mengumpulkan istilah yang dikalikan dengan dA di AD mundur.

PR yang mengimplementasikan dukungan untuk kasus simetris dengan B=I telah digabungkan dan tersedia di master. Setelah istirahat sejenak, saya berencana untuk mengimplementasikan dua kasus yang tersisa, yaitu

  1. Grad wrt A untuk B sewenang-wenang ketika B.requires_grad == False dan
  2. Lulus wrt A dan B ketika keduanya membutuhkan lulusan.

Mungkin ada beberapa masalah numerik yang muncul setelah k cukup besar. Umpan balik apa pun tentang perilaku seperti itu sangat dihargai!

Hai @nikitaved , Saya telah menerapkan mundur untuk A & B simetris di xitorch untuk referensi Anda. Saya harus mengakui itu bukan tugas yang mudah. Saya dapat menuliskan turunan matematika jika Anda mau.

@mfkasim1 , hai! Kasing simetris sudah ada di PyTorch. Kasus untuk B membutuhkan akar kuadrat matriks, ada masalah yang ditugaskan kepada saya juga. Mungkin bisa dihindari. Saya akan membuat tulisan. Kita bisa membandingkan matematika atau menjadi rekan penulis, terserah Anda! Anda dapat memeriksa kode di torch/_lobpcg.py.

Sepertinya Anda menyebar ke seluruh eigenspace, kan? Di sini masalahnya adalah Anda hanya memiliki subruang-k, di mana k umumnya jauh lebih kecil daripada dimensi n.

@nikitaved Ah, saya pikir Anda belum menerapkan untuk non-identitas B .
Saya menggunakan operator matriks/linier implisit untuk A dan B di xitorch (hanya perlu mengetahui Av dan Bv ), jadi ini berfungsi untuk sparse matriks jika Anda tertarik untuk menerapkannya juga. Namun, ini melibatkan pemecahan berulang (misalnya cg, bicgstab, dll).

Sepertinya Anda menyebar ke seluruh eigenspace, bukan? Di sini masalahnya adalah Anda hanya memiliki subruang-k, di mana k umumnya jauh lebih kecil daripada dimensi n

Fungsi xitorch.linalg.symeig sebenarnya terlihat sangat mirip dengan lobpcg mana ia hanya mengambil k eigenpairs untuk k << n dan ditujukan untuk aplikasi di mana Anda tidak dapat menyimpan n-by-n matriks

Tulisan itu terdengar menarik. Di mana Anda berencana untuk menulisnya?

Saya mengerti, terima kasih! Maka Anda benar! B hanya identitas sampai sekarang. Akan keren untuk melihat alternatif yang melibatkan metode yang berbeda.

Yah, setidaknya arXiv akan bagus untuk pemula...

Trik matematika menghindari akar kuadrat dari B adalah dengan memperhatikan bahwa inv(B) A simetris dalam produk skalar berbasis B, yaitu x'Bx. Setelah substitusi, di banyak tempat seseorang mendapat inv(B)B sehingga hilang begitu saja.

Setelah pemeriksaan lebih lanjut, saya dapat melihat bagaimana memperluas metode yang sudah diterapkan ke masalah umum. Dan, ya, itu tidak memerlukan akar kuadrat matriks, meskipun itu menjadi lebih berat secara komputasi karena saya tidak lagi dapat mengeksploitasi diagonalisasi simultan dalam solusi eksplisit untuk persamaan Sylvester (perlu periksa).

Saya akan mencoba dan membandingkan solusi ini dengan solusi dengan akar kuadrat matriks. Tidak yakin mana yang akan lebih cepat.

@nikitaved untuk referensi Anda, saya telah menuliskan turunan symeig di xitorch: https://xitorch.readthedocs.io/en/latest/notes/deriv_symeig.html

Apakah halaman ini membantu?
0 / 5 - 0 peringkat