[PYTHON] Risque de mélange! ndarray et matrice

TL;DR

numpy.matrix est obsolète. Utilisez les opérateurs «numpy.ndarray» et «@».

introduction

Il y a quelque temps, quand j'ai écrit un article "Essayez la décomposition en valeurs singulières de la matrice daimyo", un ancien collègue a dit que "numpy.matrix` est obsolète. J'ai été surpris d'entendre cela [^ 1].

[^ 1]: Actuellement, il a été modifié pour que matrix ne soit pas utilisé, mais il a été utilisé lors de son apparition.

Quand je l'ai recherché, il y avait une explication très détaillée dans StackOverflow, donc je vais l'expliquer en fonction de cela.

Décomposition de singularité et classe de matrice

Il existe un processus appelé Décomposition en Valeur Singulière (SVD) en algèbre linéaire. La matrice m-par-n X est divisée en produits de la matrice unitaire m-par-m U, de la matrice diagonale m-par-n S et de la matrice unitaire n-par-n V. Faisons une matrice appropriée.

import numpy as np
from scipy import linalg

X = (np.arange(6)+1).reshape(2,3)

Maintenant X est une matrice 2x3 comme celle-ci:

[[1 2 3]
 [4 5 6]]

Let's SVD ceci.

U, s, V = linalg.svd(X)

linalg.svd renvoie troisndarrays pour la décomposition de singularité. U correspond à m lignes et n colonnes, V correspond à n lignes et n colonnes. La matrice au milieu est une matrice diagonale avec m lignes et n colonnes, mais c'est un tableau unidimensionnel car il n'y a que des éléments diagonaux.

print(s) # => [9.508032   0.77286964]

Maintenant, la décomposition en valeurs singulières revient à la matrice d'origine en prenant le produit de la matrice dans l'ordre U, S, V. Cependant, comme la matrice diagonale au centre est un tableau unidimensionnel, il est nécessaire de la remodeler une fois en une matrice. Il existe une fonction appelée linalg.diagsvd pour cela.

S = linalg.diagsvd(s, X.shape[0], X.shape[1])

Maintenant S est une matrice diagonale 2x3.

print(S)
# => 
# [[9.508032   0.         0.        ]
#  [0.         0.77286964 0.        ]]

Après cela, si vous prenez le produit de trois matrices, il reviendra à la matrice d'origine, mais le produit * of numpy.ndarray sera le produit de chaque élément. Vous devez utiliser numpy.ndarray.dot pour le produit matriciel, qui est un calcul écrasant.

print(U.dot(S.dot(V)))
# => 
# [[1. 2. 3.]
# [4. 5. 6.]]

C'est revenu à la normale, mais je veux utiliser des opérateurs infixes comme * au lieu d'appels de méthode comme ʻU.dot (S.dot (V)) `. Pour cela, «numpy.matrix» a été préparé. Changeons la matrice diagonale «S» de «ndarray» en «matrice».

Z = np.matrix(S)

«S» est «numpy.ndarray», mais «Z» est «numpy.matrix».

type(S) # => numpy.ndarray
type(Z) # => numpy.matrix

numpy.matrix devient un produit matriciel lorsque * est utilisé comme opérateur d'infixe. Il fait également du produit «numpy.ndarray» et «numpy.matrix» un produit matriciel. De là, vous pouvez écrire quelque chose comme ʻU * Z * V`.

print(U * Z * V)
# =>
# [[1. 2. 3.]
# [4. 5. 6.]]

C'est pratique.

Problèmes avec la matrice

Comme vous pouvez le voir, «matrice» a été utilisé plutôt parce que c'était pratique, mais il y avait quelques problèmes. En particulier, il peut se comporter de manière non intuitive lorsqu'il est mélangé avec «ndarray». Permettez-moi de vous donner un exemple de la précédente OS.

Préparons une matrice ndarray appropriée ʻarr et faisons unmat qui en fait une matrice`.

arr = np.zeros((3,4))
mat = np.matrix(arr)

«Ar» et «mat» représentent une matrice 3 par 4.

Tout d'abord, ajoutons les deux.

(arr+mat).shape # => (3, 4)

Il n'y a pas de problème car nous ajoutons des matrices de même forme. Ensuite, coupons-le. Ajoutons seulement la première ligne de chacun. Comme j'ai ajouté uniquement la première ligne de 3 lignes et 4 colonnes, je voudrais que ce soit une matrice à 1 ligne et 4 colonnes.

(arr[0,:] + mat[0,:]).shape # => (1, 4)

Ce n'est pas non plus un problème.

Ajoutons maintenant les colonnes au lieu des lignes. Ajoutons la première ligne de chacun. Devrait être 3 lignes et 1 colonne.

(arr[:,0] + mat[:,0]).shape # => (3, 3)

Il est devenu 3 lignes et 3 colonnes. Qu'est-il arrivé?

C'est parce que la forme de ʻarr [: .0] ʻis(3,), tandis que la forme de mat [:, 0] ʻest (3,1)`, donc Diffusion Rules a été appliqué.

Créons en fait une matrice de (3,1) et une matrice de (3,) et ajoutons-les.

x = np.ones(3)
y = np.arange(3).reshape(3,1)
x.shape # => (3,)
y.shape # => (3, 1)

La situation est la même qu'avant. Ajoutons-le.

(x+y).shape # => (3, 3)

C'est (3, 3). Au fait, la valeur ressemble à ceci.

print(x)
# => 
# [1. 1. 1.]
print(y)
# => 
#[[0]
# [1]
# [2]]
print(x+y)
# =>
#[[1. 1. 1.]
# [2. 2. 2.]
# [3. 3. 3.]]

De plus, "*" de "matrice" est le produit de la matrice, mais "/" est la division pour chaque élément.

a = np.matrix([[2,4],[6,8]])
b = np.matrix(([2,2],[2,2]])
print(a)
# => 
# [[2 4]
#  [6 8]]
print(b)
# =>
# [[2 2]
#  [2 2]]
print(a*b)
# => 
# [[12 12]
# [28 28]]← Je comprends cela
print(a/b)
# =>
# [[1. 2.]
# [3. 4.]] ← !!!

Ceci est également peu intuitif.

Introduction de l'opérateur @

Maintenant, la raison pour laquelle nous voulions utiliser «matrice» était que le produit matriciel de «ndarray» était difficile à utiliser dans la méthode, et nous voulions écrire le produit matriciel en notation neutre, comme «A * B». Cependant, depuis Python 3.5, l'opérateur infixe @, qui représente le produit matriciel de ndarray, a été introduit (PEP 465. )). Maintenant, la décomposition des valeurs singulières et le processus d'annulation peuvent être écrits comme suit.

import numpy as np
from scipy import linalg

X = (np.arange(6)+1).reshape(2,3)
print(X)
# =>
# [[1 2 3]
# [4 5 6]]
U, s, V = linalg.svd(X)
S = linalg.diagsvd(s, X.shape[0], X.shape[1])
print(U @ S @ V)
# =>
# [[1. 2. 3.]
# [4. 5. 6.]]

C'est facile. Si $ m <n $ en m lignes et n colonnes, tous les éléments de $ V $ ne sont pas utilisés, vous pouvez donc écrire comme suit en utilisant np.diag au lieu de linalg.diagsvd.

U, s, V = linalg.svd(X)
S = np.diag(s)
print(U @ S @ V[:2,:])

Même en effectuant une décomposition de valeur singulière et en effectuant une approximation de rang bas du rang r, il peut être écrit comme «np.diag» comme suit. Ce qui suit est un échantillon d'une matrice de 200 par 50 qui est singulièrement décomposée et de faible rang. Tout d'abord, créons une matrice X avec 200 lignes et 50 colonnes.

m = 200
n = 50
X = np.arange(m*n).reshape(m,n)
print(X)

X est une telle matrice.

[[   0    1    2 ...   47   48   49]
 [  50   51   52 ...   97   98   99]
 [ 100  101  102 ...  147  148  149]
 ...
 [9850 9851 9852 ... 9897 9898 9899]
 [9900 9901 9902 ... 9947 9948 9949]
 [9950 9951 9952 ... 9997 9998 9999]]

Pour approximer cela avec le rang r = 10, procédez comme suit:

U, s, V = linalg.svd(X)
S = np.diag(s)
r = 10
Ur = U[:, :r]
Sr = S[:r, :r]
Vr = V[:r, :]
Y = Ur @ Sr @ Vr
print(np.array(Y, np.int))

Y est un nombre réel, mais il ressemble à ceci lorsqu'il est converti en entier.

[[   0    0    1 ...   46   47   48]
 [  50   50   51 ...   97   98   98]
 [ 100  101  102 ...  146  147  149]
 ...
 [9850 9851 9851 ... 9897 9897 9899]
 [9900 9901 9901 ... 9947 9947 9949]
 [9950 9951 9952 ... 9996 9998 9999]]

Ça fait du bien.

Résumé

Avec l'introduction de l'opérateur «@», qui représente le produit de la matrice dans la notation en incrustation de «numpy.ndarray», le plus grand avantage de l'utilisation de «numpy.matrix» a disparu. A partir de maintenant, utilisons numpy.ndarray et @.

Recommended Posts

Risque de mélange! ndarray et matrice
Distribution des valeurs propres de la matrice laplacienne
Le problème des menteurs et de l'honnêteté
Mécanisme de pyenv et virtualenv
Pré-traitement et post-traitement de pytest
Combinaison de récursif et de générateur
Combinaison de anyenv et direnv
Explication et mise en œuvre de SocialFoceModel
Différenciation du tri et généralisation du tri
Coexistence de pyenv et autojump
Utilisation et intégration de "Shodan"
Le problème des menteurs et de l'honnêteté
Occurrence et résolution de tensorflow.python.framework.errors_impl.FailedPreconditionError
Comparaison d'Apex et de Lamvery
Installation source et installation de Python
Introduction et astuces de mlflow.
Trouver l'intersection d'un cercle et d'une droite (matrice sympy)