Moyen rapide / efficace de décomposer des coefficients de filtre 2D entiers séparables

21

Je voudrais pouvoir déterminer rapidement si un noyau 2D donné de coefficients entiers est séparable en deux noyaux 1D avec des coefficients entiers. Par exemple

 2   3   2
 4   6   4
 2   3   2

est séparable en

 2   3   2

et

 1
 2
 1

Le test réel de séparabilité semble être assez simple en utilisant l'arithmétique entière, mais la décomposition en filtres 1D avec des coefficients entiers s'avère être un problème plus difficile. La difficulté semble résider dans le fait que les ratios entre les lignes ou les colonnes peuvent être non entiers (fractions rationnelles), par exemple dans l'exemple ci-dessus, nous avons des ratios de 2, 1/2, 3/2 et 2/3.

Je ne veux pas vraiment utiliser une approche lourde comme SVD parce que (a) c'est relativement coûteux en calcul pour mes besoins et (b) ça n'aide pas toujours nécessairement à déterminer des coefficients entiers .

Des idées ?


PLUS D'INFORMATIONS

Les coefficients peuvent être positifs, négatifs ou nuls, et il peut y avoir des cas pathologiques où la somme de l'un ou des deux vecteurs 1D est nulle, par exemple

-1   2  -1
 0   0   0
 1  -2   1

est séparable en

 1  -2   1

et

-1
 0
 1
Paul R
la source
1
Je me souviens avoir essayé de comprendre cela à l'université. J'ai presque réussi, mais je ne me souviens pas comment. =) Je ne peux pas m'arrêter d'y penser maintenant que vous l'avez mentionné!
Phonon
@ Phonon: heh - bien continuez à penser - je pourrais utiliser de l'inspiration sur celui-ci. ;-)
Paul R
Est-il possible de faire la même chose mais pour des valeurs doubles ou flottantes?
Diego Catalano
@DiegoCatalano: voir la réponse de Denis ci-dessous et la question à laquelle il renvoie sur math.stackexchange.com - Je pense que cela pourrait fonctionner pour le cas plus général des coefficients à virgule flottante.
Paul R
@PaulR, comment peut-on vous contacter par e-mail? Merci.
Royi

Réponses:

11

J'ai pris @Phononla réponse de et l ' ai quelque peu modifiée pour qu'elle utilise l' approche GCD uniquement sur la ligne du haut et la colonne de gauche, plutôt que sur les sommes de ligne / colonne. Cela semble mieux gérer les cas pathologiques. Il peut toujours échouer si la ligne supérieure ou la colonne de gauche sont toutes des zéros, mais ces cas peuvent être vérifiés avant d'appliquer cette méthode.

function [X, Y, valid] = separate(M)    % separate 2D kernel M into X and Y vectors 
  X = M(1, :);                          % init X = top row of M
  Y = M(:, 1);                          % init Y = left column of M
  nx = numel(X);                        % nx = no of columns in M
  ny = numel(Y);                        % ny = no of rows in M
  gx = X(1);                            % gx = GCD of top row
  for i = 2:nx
    gx = gcd(gx, X(i));
  end
  gy = Y(1);                            % gy = GCD of left column
  for i = 2:ny
    gy = gcd(gy, Y(i));
  end
  X = X / gx;                           % scale X by GCD of X
  Y = Y / gy;                           % scale Y by GCD of Y
  scale = M(1, 1) / (X(1) * Y(1));      % calculate scale factor
  X = X * scale;                        % apply scale factor to X
  valid = all(all((M == Y * X)));       % result valid if we get back our original M
end

Un grand merci à @Phononet @Jason Rpour les idées originales pour cela.

Paul R
la source
10

Je l'ai! Publication du code MATLAB, affichera une explication ce soir ou demain

% Two original arrays
N = 3;
range = 800;
a = round( range*(rand(N,1)-0.5) )
b = round( range*(rand(1,N)-0.5) )

% Create a matrix;
M = a*b;
N = size(M,1);

% Sanity check
disp([num2str(rank(M)) ' <- this should be 1!']);

% Sum across rows and columns
Sa = M * ones(N,1);
Sb = ones(1,N) * M;

% Get rid of zeros
SSa = Sa( Sa~=0 );
SSb = Sb( Sb~=0 );

if isempty(SSa) | isempty(SSb)
    break;
end

% Sizes of array without zeros
Na = numel(SSa);
Nb = numel(SSb);

% Find Greatest Common Divisor of Sa and Sb.
Ga = SSa(1);
Gb = SSb(1);

for l=2:Na
    Ga = gcd(Ga,SSa(l));
end

for l=2:Nb
    Gb = gcd(Gb,SSb(l));
end

%Divide by the greatest common divisor
Sa = Sa / Ga;
Sb = Sb / Gb;

%Scale one of the vectors
MM = Sa * Sb;
Sa = Sa * (MM(1) / M(1));

disp('Two arrays found:')
Sa
Sb
disp('Sa * Sb = ');
Sa*Sb
disp('Original = ');
M
Phonon
la source
Merci - c'est super - j'étais couché éveillé la nuit dernière en pensant à factoriser les coefficients, etc. Malheureusement, il y a encore une ride à corriger - elle doit fonctionner avec des coefficients positifs et négatifs et cela peut conduire à des cas dégénérés, par exemple A=[-2 1 0 -1 2]; B=[2 -3 6 0 -1]; M=A'*B;. Le problème ici est que sum(A) = 0oui Sb = [0 0 0 0 0]. Je vais essayer de modifier votre algorithme pour qu'il utilise la somme des valeurs absolues des coefficients et voir si cela aide. Merci encore pour votre aide.
Paul R
OK - il semble que vous pouvez toujours obtenir les PGCD et faire la mise à l' échelle en utilisant abs(M), par exemple Sa=abs(M)*ones(N,1); Sb=ones(1,N)*abs(M);, puis continuer comme ci - dessus, mais je ne peux encore voir comment restaurer les panneaux Sa, Sbà la fin. J'ai ajouté un exemple pathologique qui illustre le problème dans la question d'origine ci-dessus.
Paul R
Je pense que j'ai une solution de travail maintenant - je l'ai posté comme une réponse séparée, mais le mérite vous revient pour l'idée sous-jacente. Merci encore !
Paul R
7

Peut-être que je banalise le problème, mais il semble que vous pourriez:

  • NMAaii=0,1,,N1
  • j>0

    • aja0jrj
    • rj
    • rjaja0j0x
    • aja0
  • x

xk,norm=xkmini=0N1xi
  • xnorm
    xscaled=Kxnorm,K=1,2,,M
    KM

Ce n'est pas la méthode la plus élégante, et il y a probablement une meilleure façon, mais cela devrait fonctionner, est assez simple à implémenter et devrait être relativement rapide pour les matrices de taille modeste.

Jason R
la source
Merci - je pense que je me dirigeais probablement dans quelque chose comme cette direction avant de m'enliser dans les détails. Je ne suis pas sûr à 100% que vous arriverez toujours à une solution en utilisant cette méthode, mais de toute façon, je devrais probablement coder cela et l'essayer avec quelques exemples. J'ai un pressentiment qu'il peut être nécessaire d'appliquer à la fois en ligne et en colonne pour voir ce qui donne la "meilleure" solution. Merci d'avoir pris le temps de préciser les détails - je m'occuperai de cela et je vous ferai savoir comment cela fonctionne.
Paul R
Ne pourriez-vous pas trouver le plus grand diviseur commun des premiers éléments des lignes et l'utiliser pour déterminer votre vecteur de base?
Jim Clay
@ JimClay: Oui, c'est effectivement ce que vous faites à la fin, si vous avez cette fonctionnalité disponible.
Jason R
3

xyzA|Axyz|
x y z
yzxx y z x y z ... à son tour.

(De approximative-a-convolution-as-a-sum-of-separable-convolutions sur math.stackexchange.)

denis
la source
1
Essayez de ne pas répondre aux questions avec des liens inexpliqués. Il est préférable d'expliquer les détails nécessaires dans votre réponse et d'inclure le lien uniquement pour référence; de cette façon, si le lien se casse, les détails essentiels de la réponse sont toujours là.
Sam Maloney
@SamMaloney: Je ne vois aucune raison pour laquelle cela est nécessaire. Le lien explique tout en détail. Il apparaîtra toujours dans la recherche Q&R. Alors pourquoi pas?
Naresh
1
@Naresh Je ne le mentionne que parce que l'un des objectifs des sites d'échange de pile est de créer un référentiel de questions avec réponses pour référence future. Donc, même si je comprends que ce lien particulier est vers un autre site SE et devrait être assez sûr, c'est une meilleure pratique générale de ne pas compter sur des liens qui fonctionnent encore dans plusieurs années. Donner un aperçu général de ces "deux méthodes faciles" dans la réponse garantirait que les informations sont conservées même si quelque chose arrive à la question liée. Comme je l'ai dit cependant, il s'agissait plutôt d'un commentaire général sur les meilleures pratiques concernant les liens dans les réponses.
Sam Maloney