Это выдержка из восьмого раздела главы 2 книги Глубокое обучение с Tensorflow 2.0.

Разложение по сингулярным значениям (SVD) — еще один способ разложения матрицы на сингулярные векторы и сингулярные значения. SVD позволяет нам обнаруживать ту же информацию, что и собственное разложение, однако SVD более широко применим. Каждая вещественная матрица имеет разложение по сингулярным значениям, но это не так для разложения по собственным значениям. СВД можно записать так:

A = UDV^T

Предположим, что A является матрицей mxn, тогда U определяется как матрица вращения mxm, D — матрица масштабирования и проецирования матрицы mxn, а V — матрица вращения nxn.

Каждая из этих матриц имеет особую структуру. Матрицы U и V определены как ортогональные матрицы U^T = U^(-1) и V^T = V^(-1). Матрица D определяется как диагональная матрица.

Элементы по диагонали D называются сингулярными значениями матрицы A. Столбцы U известны как лево-сингулярные векторы. Столбцы V известны как правосингулярные векторы.

# mxn matrix A
svd_matrix_A = tf.constant([[2, 3], [4, 5], [6, 7]], dtype=tf.float32)
print(“Matrix A: \n{}\n”.format(svd_matrix_A))
# Using tf.linalg.svd to calculate the singular value decomposition where d: Matrix D, u: Matrix U and v: Matrix V
d, u, v = tf.linalg.svd(svd_matrix_A, full_matrices=True, compute_uv=True)
print(“Diagonal D: \n{} \n\nMatrix U: \n{} \n\nMatrix V^T: \n{}”.format(d, u, v))
Matrix A:
[[2. 3.]
 [4. 5.]
 [6. 7.]]
Diagonal D:
[11.782492 0.41578525]
Matrix U:
[[ 0.30449855 -0.86058956 0.40824753]
 [ 0.54340035 -0.19506174 -0.81649673]
 [ 0.78230214 0.47046405 0.40824872]]
Matrix V^T:
[[ 0.63453555 0.7728936 ]
 [ 0.7728936 -0.63453555]]

# Lets see if we can bring back the original matrix from the values we have
# mxm orthogonal matrix U
svd_matrix_U = tf.constant([[0.30449855, -0.86058956, 0.40824753], [0.54340035, -0.19506174, -0.81649673], [0.78230214, 0.47046405, 0.40824872]])
print(“Orthogonal Matrix U: \n{}\n”.format(svd_matrix_U))
# mxn diagonal matrix D
svd_matrix_D = tf.constant([[11.782492, 0], [0, 0.41578525], [0, 0]], dtype=tf.float32)
print(“Diagonal Matrix D: \n{}\n”.format(svd_matrix_D))
# nxn transpose of matrix V
svd_matrix_V_trans = tf.constant([[0.63453555, 0.7728936], [0.7728936, -0.63453555]], dtype=tf.float32)
print(“Transpose Matrix V: \n{}\n”.format(svd_matrix_V_trans))
# UDV(^T)
svd_RHS = tf.tensordot(tf.tensordot(svd_matrix_U, svd_matrix_D, axes=1), svd_matrix_V_trans, axes=1)
predictor = tf.reduce_all(tf.equal(tf.round(svd_RHS), svd_matrix_A))
def true_print(): print(“It WORKS. \nRHS: \n{} \n\nLHS: \n{}”.format(tf.round(svd_RHS), svd_matrix_A))
def false_print(): print(“Condition FAILED. \nRHS: \n{} \n\nLHS: \n{}”.format(tf.round(svd_RHS), svd_matrix_A))
tf.cond(predictor, true_print, false_print)
Orthogonal Matrix U:
[[ 0.30449855 -0.86058956 0.40824753]
 [ 0.54340035 -0.19506174 -0.81649673]
 [ 0.78230214 0.47046405 0.40824872]]
Diagonal Matrix D:
[[11.782492 0. ]
 [ 0. 0.41578525]
 [ 0. 0. ]]
Transpose Matrix V:
[[ 0.63453555 0.7728936 ]
 [ 0.7728936 -0.63453555]]
It WORKS.
RHS:
[[2. 3.]
 [4. 5.]
 [6. 7.]]
LHS:
[[2. 3.]
 [4. 5.]
 [6. 7.]]

Матрицу A можно рассматривать как линейное преобразование. Это преобразование можно разложить на три подпреобразования:

1. Вращение,
2. Изменение масштаба и проецирование,
3. Вращение.

Эти три шага соответствуют трем матрицам U, D и V.

Давайте посмотрим, как происходят эти преобразования по порядку.

# Let’s define a unit square
svd_square = tf.constant([[0, 0, 1, 1],[0, 1, 1, 0]], dtype=tf.float32)
# a new 2x2 matrix
svd_new_matrix = tf.constant([[1, 1.5], [0, 1]])
# SVD for the new matrix
new_d, new_u, new_v = tf.linalg.svd(svd_new_matrix, full_matrices=True, compute_uv=True)
# lets’ change d into a diagonal matrix
new_d_marix = tf.linalg.diag(new_d)
# Rotation: V^T for a unit square
plot_transform(svd_square, tf.tensordot(new_v, svd_square, axes=1), “$Square$”, “$V^T \cdot Square$”, “Rotation”, axis=[-0.5, 3.5 , -1.5, 1.5])
plt.show()
# Scaling and Projecting: DV^(T)
plot_transform(tf.tensordot(new_v, svd_square, axes=1), tf.tensordot(new_d_marix, tf.tensordot(new_v, svd_square, axes=1), axes=1), “$V^T \cdot Square$”, “$D \cdot V^T \cdot Square$”, “Scaling and Projecting”, axis=[-0.5, 3.5 , -1.5, 1.5])
plt.show()
# Second Rotation: UDV^(T)
trans_1 = tf.tensordot(tf.tensordot(new_d_marix, new_v, axes=1), svd_square, axes=1)
trans_2 = tf.tensordot(tf.tensordot(tf.tensordot(new_u, new_d_marix, axes=1), new_v, axes=1), svd_square, axes=1)
plot_transform(trans_1, trans_2,”$U \cdot D \cdot V^T \cdot Square$”, “$D \cdot V^T \cdot Square$”, “Second Rotation”, color=[‘#1190FF’, ‘#FF9A13’], axis=[-0.5, 3.5 , -1.5, 1.5])
plt.show()

Вышеупомянутые подпреобразования можно найти для каждой матрицы следующим образом:

- U соответствует собственным векторам AA^T
- V соответствует собственным векторам A^TA
- D соответствует собственным значениям AA^T или A^TA, которые совпадают.

В качестве упражнения попробуйте доказать, что это так.

Возможно, наиболее полезной функцией SVD является то, что мы можем использовать ее для частичного обобщения обращения матриц на неквадратные матрицы, как мы увидим в следующем разделе.

Вы можете прочитать этот раздел и следующие темы:

на Глубокое обучение с TF 2.0: 02.00-Линейная алгебра. Вы можете получить код этой статьи и остальной части главы здесь. Ссылки на блокнот в Google Colab и Jupyter Binder находятся в конце блокнота.