← Tensor by Tensor
§ Math Fundamentals for AI
Tensor by Tensor · Complete Guide

Hadamard products

Fourteen lessons that connect high-school mathematics to AI. Start with the plain-language explanation and a small example. Then read the full formulas, derivations, and Python code at your own pace.

How to study a lesson

Read the opening example. Try the question before revealing its answer. Then work through the detailed notes below it. You do not need to understand every proof on the first reading.

Symbols: a subscript such as xi identifies one entry. Σ means add a collection of terms. ∈ means “belongs to”. The symbol ≈ means “approximately equal”, not exactly equal. A parameter is an adjustable number in a model.

01.10 · Hadamard Product (Element-wise Operations)

Introduction

Sometimes two matrices should interact cell by cell rather than row by column. Multiplying matching entries of two two-by-two matrices produces values 5, 12, 21, and 32 without mixing positions.

Learning goal

Calculate element-wise matrix products and distinguish them from matrix multiplication, masking, gating, and broadcasting behavior.

Before you start

Vectors, matrices, shapes, ordinary multiplication, and basic NumPy array operations.

Lesson plan

  1. Multiply matching matrix entries and confirm that the output shape stays unchanged.
  2. Contrast element-wise multiplication with matrix multiplication using the same inputs.
  3. Apply the operation to masks and gates, then inspect broadcasting behavior in code.

Keep two products separate. The Hadamard product of [2,3] and [4,5] is [8,15]. Their dot product is the single number 23. Same inputs, different output shape, different purpose.

Element-wise multiplication

When two arrays have the same shape, you can multiply them position-by-position. This is the Hadamard product, written $\odot$:

$$\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} \odot \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} = \begin{bmatrix} 5 & 12 \\ 21 & 32 \end{bmatrix}$$

The top-right entry is $2 \times 6 = 12$. It does not mix numbers from other positions.

Definition

For $A, B \in \mathbb{R}^{m \times n}$, the Hadamard product $A \odot B$ has entries $(A \odot B)_{ij} = A_{ij} B_{ij}$. It requires $A$ and $B$ to have the same shape.

Hadamard vs. matrix multiplication

Using the same two matrices:

$$\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} = \begin{bmatrix} 19 & 22 \\ 43 & 50 \end{bmatrix}$$

The top-right entry here is $1 \times 6 + 2 \times 8 = 22$ — a dot product of row 1 and column 2. Very different from the Hadamard result of 12.

Shape trap

Both products produce a $2 \times 2$ result here, so checking shape alone won't catch confusing them. Always be clear about whether you want element-wise ($\odot$) or matrix ($\cdot$ or $@$) multiplication.

Masks and gating

The Hadamard product is how you apply masks — zeroing out some entries while keeping others:

$$\begin{bmatrix} 3 \\ 7 \\ 2 \end{bmatrix} \odot \begin{bmatrix} 1 \\ 0 \\ 1 \end{bmatrix} = \begin{bmatrix} 3 \\ 0 \\ 2 \end{bmatrix}$$

The mask $[1, 0, 1]$ hides the middle entry. This pattern appears everywhere in ML:

  • Dropout: multiply activations by a random binary mask
  • Attention: mask out padding tokens before softmax
  • Gating (LSTM, GRU): element-wise multiply gates with hidden states

In code

import numpy as np

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])

# Element-wise (Hadamard)
print((a * b).tolist())   # [[5, 12], [21, 32]]

# Matrix multiplication
print((a @ b).tolist())   # [[19, 22], [43, 50]]
Example · Backprop through element-wise ops

If $c = a \odot b$, then $\frac{\partial L}{\partial a} = \frac{\partial L}{\partial c} \odot b$ and $\frac{\partial L}{\partial b} = \frac{\partial L}{\partial c} \odot a$. The gradient flows through element-wise, scaled by the other operand.

Broadcasting

NumPy extends element-wise operations to compatible but unequal shapes via broadcasting. For example, adding a vector to every row of a matrix:

X = np.array([[1, 2, 3], [4, 5, 6]])  # shape (2, 3)
b = np.array([10, 20, 30])             # shape (3,)
print(X + b)  # [[11, 22, 33], [14, 25, 36]]