I have a weird problem that I ran into the other day and I wondered if someone knows the reason for this weirdness. I'm sorry if it's a duplicate of some other posts, but I couldn't find similar posts on this.
I was testing the transpose property on matrix multiplications where (A @ B)T == BT @ AT holds true.
I wrote a simple code to test it out and to my surprise when the last dimension for the array is 1, the array is "close" but not "equal" in some cases (ignore the ugly code...).
def transpose_property(last_dim: int = 1):
A = np.random.rand(4, 3)
B = np.random.rand(3, last_dim)
AT = A.transpose((1, 0)).copy()
BT = B.transpose((1, 0)).copy()
M_left = (A @ B).transpose(1, 0).copy()
M_right = BT @ AT
equal = np.array_equal(M_left, M_right)
close = np.allclose(M_left, M_right)
return [equal, close]
I ran some tests where I change the last dimension (last_dim) of B from 1 to 3 and counted how many times (A@B)T and BT @ AT were "equal" (np.array_equal) and "close" (np.allclose):
np.random.seed(0)
num_tests = 10000
for dim in range(1, 4):
results = [transpose_property(last_dim=dim) for _ in range(num_tests)]
equals = [r[0] for r in results if r[0]]
closes = [r[1] for r in results if r[1]]
print(f"dim={dim}:")
print(f"\tequals: {len(equals)}/{num_tests}")
print(f"\tcloses: {len(closes)}/{num_tests}")
Results of this script are shown below:
dim=1:
equals: 3452/10000
closes: 10000/10000
dim=2:
equals: 10000/10000
closes: 10000/10000
dim=3:
equals: 10000/10000
closes: 10000/10000
I'm puzzled that when the last dimension is 1, the number of equals is low, but when the last dimension is greater than 1, all of them match.
I think it might be due to floating-point precision rounding, but I don't understand why it's only when the last dimension is 1. How could this be?
I would like to note that:
- this happens when you add more dimensions (e.g.,
A.shape: (3, 3, 3)andB.shape: (3, 3, 1)and transpose the last two dimensions). - this will not happen when the array is an integer (e.g.,
A = np.arange(12).reshape(6, 2)andB = np.arange(2).reshape(2, 1)). - This problem is not detrimental since the end arrays are "close". It's just that this has been on my mind and wanted to figure out why.