That's basically outer-equality comparison keeping the first of those mask axes aligned. We can leverage matrix-multiplication with the idea that the matrix-multiplication of the masks themselves and their simulataneous negated versions would lead us to those outer equality-summations. Hence, for the summations of equalities, simply do -
out = (~masksB).astype(int).dot(~masksA.T) + masksB.astype(int).dot(masksA.T)
Alternatively, get the int version and use it to get the negated version too -
mB = masksB.astype(int)
out = (1-mB).dot(~masksA.T) + mB.dot(masksA.T)
The final output would be out/float(M). Or we can replace those int conversions with float ones and then divide the output by M.
Timings (with N as 1000 instead) to get equality summations
# Setup
In [39]: np.random.seed(0)
...: N = 1000
...: M = 580
...: masksA = np.array(np.random.randint(0,2, size=(N,M)), dtype=np.bool)
...: masksB = np.array(np.random.randint(0,2, size=(N,M)), dtype=np.bool)
# Approach #1 with int dtype converison
In [40]: %timeit (~masksB).astype(int).dot(~masksA.T) + masksB.astype(int).dot(masksA.T)
1 loop, best of 3: 870 ms per loop
# Approach #1 with float dtype converison
In [41]: %timeit (~masksB).astype(float).dot(~masksA.T) + masksB.astype(float).dot(masksA.T)
10 loops, best of 3: 74 ms per loop
# Approach #2 with int dtype converison
In [42]: %%timeit
...: mB = masksB.astype(int)
...: out = (1-mB).dot(~masksA.T) + mB.dot(masksA.T)
1 loop, best of 3: 882 ms per loop
# Approach #2 with float dtype converison
In [43]: %%timeit
...: mB = masksB.astype(float)
...: out = (1-mB).dot(~masksA.T) + mB.dot(masksA.T)
10 loops, best of 3: 59.3 ms per loop
So, the preferred way would be to go with the float conversion on the second approach and divide the output by M.
Use one matrix-multiplication with stacking
Since the equality summations would essentially be int numbers, we can use a lower precision dtype for the matrix-multiplication. Also, one more idea would be to stack the original masks with their negated versions and then perform the matrix-multiplication. Hence, for equality summations, we would have -
m1 = np.hstack((masksA,~masksA))
m2 = np.hstack((masksB,~masksB))
out = m2.astype(np.float32).dot(m1.T)
Timings (same setup as earlier) -
In [49]: %%timeit
...: m1 = np.hstack((masksA,~masksA))
...: m2 = np.hstack((masksB,~masksB))
...: out = m2.astype(np.float32).dot(m1.T)
10 loops, best of 3: 36.8 ms per loop