/
inv.py
156 lines (133 loc) · 5.26 KB
/
inv.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import numpy.linalg
import chainer
from chainer.backends import cuda
from chainer import function_node
import chainer.functions
from chainer.functions.math import matmul
from chainer import utils
from chainer.utils import precision
from chainer.utils import type_check
def _inv_gpu(b):
# We do a batched LU decomposition on the GPU to compute the inverse
# Change the shape of the array to be size=1 minibatch if necessary
# Also copy the matrix as the elements will be modified in-place
a = matmul._as_batch_mat(b).copy()
n = a.shape[1]
n_matrices = len(a)
# Pivot array
p = cuda.cupy.empty((n, n_matrices), dtype=numpy.int32)
# Output array
c = cuda.cupy.empty_like(a)
# These arrays hold information on the execution success
# or if the matrix was singular
info = cuda.cupy.empty(n_matrices, dtype=numpy.int32)
ap = matmul._mat_ptrs(a)
cp = matmul._mat_ptrs(c)
_, lda = matmul._get_ld(a)
_, ldc = matmul._get_ld(c)
handle = cuda.Device().cublas_handle
if b.dtype == numpy.float32:
cuda.cublas.sgetrfBatched(
handle, n, ap.data.ptr, lda, p.data.ptr, info.data.ptr, n_matrices)
cuda.cublas.sgetriBatched(
handle, n, ap.data.ptr, lda, p.data.ptr, cp.data.ptr, ldc,
info.data.ptr, n_matrices)
elif b.dtype == numpy.float64:
cuda.cublas.dgetrfBatched(
handle, n, ap.data.ptr, lda, p.data.ptr, info.data.ptr, n_matrices)
cuda.cublas.dgetriBatched(
handle, n, ap.data.ptr, lda, p.data.ptr, cp.data.ptr, ldc,
info.data.ptr, n_matrices)
else:
assert False
return c, info
class Inv(function_node.FunctionNode):
def check_type_forward(self, in_types):
type_check._argname(in_types, ('a',))
a_type, = in_types
type_check.expect(a_type.dtype.kind == 'f')
# Only 2D array shapes allowed
type_check.expect(a_type.ndim == 2)
# Matrix inversion only allowed for square matrices
type_check.expect(a_type.shape[0] == a_type.shape[1])
@precision._fp16_mixed_precision_helper
def forward_cpu(self, x):
self.retain_outputs((0,))
try:
invx = utils.force_array(numpy.linalg.inv(x[0]))
except numpy.linalg.LinAlgError:
raise ValueError('Input has singular matrices.')
return invx,
@precision._fp16_mixed_precision_helper
def forward_gpu(self, x):
self.retain_outputs((0,))
shape = x[0].shape
invx, info = _inv_gpu(x[0].reshape(1, *shape))
if chainer.is_debug():
if cuda.cupy.any(info != 0):
raise ValueError('Input has singular matrices.')
invx = invx.reshape(shape)
return invx,
def backward(self, x, gy):
invx, = self.get_retained_outputs()
# Gradient is - x^-T (dx) x^-T
invxT = chainer.functions.transpose(invx)
gx = chainer.functions.matmul(
chainer.functions.matmul(- invxT, gy[0]), invxT)
return gx,
class BatchInv(function_node.FunctionNode):
def check_type_forward(self, in_types):
type_check._argname(in_types, ('a',))
a_type, = in_types
type_check.expect(a_type.dtype.kind == 'f')
# Only a minibatch of 2D array shapes allowed
type_check.expect(a_type.ndim == 3)
# Matrix inversion only allowed for square matrices
# so assert the last two dimensions are equal
type_check.expect(a_type.shape[-1] == a_type.shape[-2])
@precision._fp16_mixed_precision_helper
def forward_cpu(self, x):
self.retain_outputs((0,))
try:
invx = utils.force_array(numpy.linalg.inv(x[0]))
except numpy.linalg.LinAlgError:
raise ValueError('Input has singular matrices.')
return invx,
@precision._fp16_mixed_precision_helper
def forward_gpu(self, x):
self.retain_outputs((0,))
invx, info = _inv_gpu(x[0])
if chainer.is_debug():
if cuda.cupy.any(info != 0):
raise ValueError('Input has singular matrices.')
return invx,
def backward(self, x, gy):
invx, = self.get_retained_outputs()
# Unpack 1-length tuples
gy, = gy
# Gradient is - x^-T (dx) x^-T
ret = chainer.functions.matmul(-invx, gy, transa=True)
ret2 = chainer.functions.matmul(ret, invx, transb=True)
return ret2,
def inv(a):
"""Computes the inverse of square matrix.
a (:class:`~chainer.Variable` or :ref:`ndarray`):
Input array to compute the inverse for. Shape of
the array should be ``(n, n)`` where ``n`` is the dimensionality of
a square matrix.
Returns:
~chainer.Variable: Matrix inverse of ``a``.
"""
return Inv().apply((a,))[0]
def batch_inv(a):
"""Computes the inverse of a batch of square matrices.
Args:
a (:class:`~chainer.Variable` or :ref:`ndarray`):
Input array to compute the inverse for. Shape of
the array should be ``(m, n, n)`` where ``m`` is the number of
matrices in the batch, and ``n`` is the dimensionality of a square
matrix.
Returns:
~chainer.Variable: Inverse of every matrix in the batch of matrices.
"""
return BatchInv().apply((a,))[0]