← jax4jChapter 1 · Working with tensors7 min read

Matrix multiplication and linear algebra

Turn three features into two outputs, solve a small system, and reconstruct a matrix from its Cholesky factor.

Java notebookJava kernel · starts on first runWebGPU · not requested

Run cells from top to bottom. Typed declarations and imports carry into later cells. Shift + Enter runs a cell. Reloading or leaving this page discards edits and Java state.

Ready to start

Three features become two outputs

A batch contains two observations, each with three features. You want two weighted combinations of those features for every observation. This is the shape of a small linear layer, a pair of scoring rules, or a change of coordinates. Matrix multiplication expresses the weighted sums in one operation.

Our feature rows are [1, 2, 3] and [4, 5, 6]. The first output adds feature one to feature three; the second adds feature two to feature three. That gives [4, 5] and [10, 11]. We can check every entry before running anything.

A feature matrix of shape 2,3 multiplied by weights of shape 3,2 gives outputs of shape 2,2: rows 4,5 and 10,11. The shared feature dimension is three.

Later in this notebook we will reverse a different calculation by solving a small linear system, then reconstruct a matrix from a triangular factor. These are constructed examples with simple reference answers, chosen to make the dimensions and numerical checks visible.

Choose Run all for the worked examples, then change individual cells. This page starts a separate Java session, loads the bundled jax4j 0.2.1 core, and uses CPU execution. Earlier lesson imports do not carry across pages. The browser JVM is emulated; its timings are not native performance results.

1. Match the shared dimension

For rank-two matrices, [M, K] × [K, N] produces [M, N]. The two occurrences of K must match. Each output entry sums K pairwise products. Here M = 2, K = 3 and N = 2: observations by features, multiplied by features by outputs.

In [ ]:

1. Multiply features by weights

Expect shape [2, 2] and flat output [4, 5, 10, 11]. The top-left entry is 1 × 1 + 2 × 0 + 3 × 1 = 4. The top-right uses the other weight column: 1 × 0 + 2 × 1 + 3 × 1 = 5. An output column chooses one set of feature weights; an output row retains one observation.

Try this: change the last row of the weight matrix from [1, 1] to [2, 2], rerun setup, and predict the result. You should get [7, 8, 16, 17]. Both output rules now give the third feature twice the influence. Restore the original weights for the next reference answer.

mul would ask for elementwise multiplication with broadcasting. It does not perform these weighted sums. Do not use the two method names interchangeably: a shape can tell you that an operation is invalid, but cannot tell you that you chose the operation your model intended.

2. Add one bias per output

After multiplication, each observation has two outputs. A bias vector of shape [2] aligns with that final output axis and applies to both observations. This is the same broadcasting rule as in the first notebook of this chapter.

In [ ]:

2. Complete a small linear layer

With the original weights, expect [4.5, 4.5, 10.5, 10.5]. Change the bias to [0, 10]: only the second output of each observation should shift. Then add another observation to features, update its row count, and rerun. The weight matrix remains [3, 2]; a different batch size does not require different feature weights.

That last edit is worth doing deliberately. The factory checks that the flat array length matches its requested shape. Matrix multiplication then checks the shared dimensions. Those checks catch different mistakes at different boundaries, before you interpret the output numbers.

Restore the original feature and weight arrays before comparing the following reference outputs.

3. Transpose when the question changes

x.transpose().matmul(x) forms a feature-by-feature matrix: [3, 2] × [2, 3] produces [3, 3]. Each entry is a dot product between feature columns across observations. This uncentered Gram matrix is useful for understanding the geometry of the data, but it is not automatically a covariance matrix.

In [ ]:

3. Form a Gram matrix

The rows are [17, 22, 27], [22, 29, 36] and [27, 36, 45]. For example, the first diagonal entry is 1² + 4² = 17. The matrix is symmetric because the dot product between columns A and B equals the dot product between B and A.

Try x.matmul(xt) instead. You get a two-by-two matrix comparing observations, with rows [14, 32] and [32, 77]. The same values answer a different geometric question. A transpose changes both which dimensions align and which entities the output compares.

Now deliberately reverse the original product to w.matmul(x). It is valid for these dimensions and gives a three-by-three result, but it is not the original observation-to-output mapping. “The multiplication worked” is only the beginning of the correctness check.

4. Solve for an unknown vector

Suppose two unknowns satisfy 3u + v = 9 and u + 2v = 8. In matrix form, this is A × solution = b. The answer is u = 2, v = 3. solve expresses that question directly without requiring you to construct an inverse matrix.

The system 3u plus v equals 9 and u plus 2v equals 8 has solution u equals 2 and v equals 3. Multiplying the solution by A reconstructs b, and subtracting b gives a zero residual.
In [ ]:

4. Solve a two-equation system

The solution is [2, 3], and this example's maximum absolute residual is zero. A residual is the discrepancy left after substituting your answer back into the original equation. It is a much more informative check than simply printing the solver's output and hoping it looks plausible.

Change b to [5, 5] and predict [1, 2]. Check the residual again. Then use non-integer inputs: a tiny nonzero residual is normal in floating-point arithmetic. In general, interpret it relative to the scale of your inputs; a small residual alone does not guarantee an accurate solution when the system is poorly conditioned, meaning small input changes can strongly affect the answer.

In jax4j 0.2.1, solve and these dense factorization routines download to the host, perform their calculation there, and upload results to the original device. They are not native GPU factorization kernels. This notebook explicitly uses CPU, where that distinction is straightforward. The ordinary tensor matmul operation is dispatched through its selected backend.

5. Rebuild a matrix from a factor

For a symmetric positive-definite matrix, Cholesky factorization finds a lower triangular matrix L such that A = L × Lᵀ. Positive definite means every nonzero vector has a positive quadratic form vᵀAv. Our example [[4, 2], [2, 5]] has the particularly friendly factor [[2, 0], [1, 2]].

In [ ]:

5. Reconstruct a Cholesky factorization

Expect [2, 0, 1, 2] for the factor and [4, 2, 2, 5] for the reconstruction. Multiply the small matrices by hand to verify each entry. As a coding exercise, add a residual calculation like the solver cell's rather than relying only on the printed arrays.

Try replacing the final diagonal entry 5 with 3. The matrix remains positive definite, but the last factor entry becomes approximately √2. Your reconstruction can now have a small rounding difference. Keep it separate from a genuinely invalid factorization, such as a matrix that is not positive definite.

6. Catch a dimension mismatch

The original feature matrix has three columns. A weight matrix with two rows cannot consume it. This deliberate failure is caught so the rest of the notebook remains runnable.

In [ ]:

6. Reject incompatible inner dimensions

For a final experiment, choose one output instead of two. Make the weights [1, 0, 1] with shape [3, 1]. Predict shape [2, 1] and values [4, 10]. Then change the weights so the output is the average of the three features. You have built two different linear maps without changing the observations.

Every temporary in these examples has an explicit owner in a try resource list. That includes transposes, residuals and factor tensors. If you explore factorizations returning several tensors, inspect their result object and close each owned tensor; a Java record holding results is not automatically a resource scope.

You can now distinguish elementwise arithmetic, axis reductions and matrix products by the question each answers. Revisit broadcasting to add a per-output bias, or reshape, slice and reduce to select one run before a linear transformation. Continue with differentiating a scalar loss to use these building blocks in Chapter 2.


Questions or corrections? open an issue