Showing posts with label performance. Show all posts
Showing posts with label performance. Show all posts

Wednesday, July 15, 2020

การคูณเมตริกซ์แบบต่างๆ

ในโปรแกรม MATLAB เป็นที่รู้กันว่ามีการคูณmatrix 2 แบบ

  1. Element wise multiplication แทนด้วย A.*B คือการคูณแบบตำแหน่งต่อตำแหน่ง
  2. Matrix multiplication แทนด้วย A*B คือการคูณเมตริกซ์
โพสที่แล้วเราโชว์การคูณแบบ element wise ให้ดู โพสนี้เราจะมาแสดงการคูณแบบเมตริกซ์ให้ดู 

แต่ก่อนอื่นเรามาทบทวนนิยามการคูณแบบเมตริกซ์ก่อน


สมมติเรามี matrix A และ B ดังนี้


ผลคูณ A*B = C จะได้

โดยที่ 

การคูณใน MATLAB อาจทำได้ 3 แบบ แต่วันนี้จะลองแค่ 2 แบบให้ดูเป็นตัวอย่าง

A = [2,3;3,5];
B = [3,5;2,7];
tic
C1 = A*B;
toc
tic
C2 = sum(bsxfun(@times, permute(A,[1,3,2]),permute(B,[3,2,1])),3);
toc
C1 ใช้เวลา 0.000134 seconds.
C2 ใช้เวลา 0.000244 seconds.

แบบแรกใช้คำสั่งคูณธรรมดา ใช้เวลาน้อยกว่าแบบที่2 ที่ให้ bsxfun .... ก็สรุปว่าการคูณแบบธรรมดา เร็วกว่าการคูณแบบพิสดารนะคะ

แต่ค่ะแต่...เราคงไม่แสดงวิธีที่สองให้ดูถ้ามันไม่มีอะไรน่าสนใจ
สำหรับ 2D matrix การคูณธรรมดานั้นเร็วที่สุด แต่ถ้าสมมติว่าเรามี 2D matrix หลายๆอันมาต่อกันในมิติที่สาม, k,แล้วเราต้องการคูณเมตริกซ์ k ครั้ง เราควรจะใช้วิธีไหน


โค้ดที่ใช้ในการทดสอบเป็นดังนี้


n = 3;
A = rand(n,n);
B = rand(n,n);
[m,n] = size(A);
vecK = [10,50,100,200,500,1000,3000,1e4];
t1 = zeros(1,length(vecK));
t2 = t1;
t3 = t1;
for ii = 1:length(vecK)
    K = vecK(ii);
    k = 1:K;
    A1 = ones(1,1,K).*A;
    B1 = ones(1,1,K).*B;
    C1 = zeros(m,n,K);
    tic;
    for k = 1:K
      C1(:,:,k) = A1(:,:,k) * B1(:,:,k);
    end
    t1(ii)=toc;

    tic;
    C2 = zeros(m,n,k);
    for i = 1:m
      for j = 1:m
        for k = 1:n
          C2(i,k,:) = C2(i,k,:) + A1(i,j,:).*B1(j,k,:);
        end
      end
    end
    t2(ii)=toc;

    tic;
    C3 = zeros(m,n,K);
    for j = 1:size(A,2)
      C3 = C3 + bsxfun(@times,A1(:,j,:),B1(j,:,:));
    end
    t3(ii)=toc;
end
h=plot(vecK,t1,'-*b',vecK,t2,'-*r',vecK,t3,'-*g');
h(1).LineWidth = 2;
h(2).LineWidth = 2;
h(3).LineWidth = 2;
set(gca,'FontSize',14);
axis tight;
xlabel('k');ylabel('time(s)');grid on;
legend('A*B', 'vectorized', 'bsxfun');
ใน code ข้างบน เราแสดงวิธีคูณให้ดู 3 แบบค่ะ
C1 คือ แบบ A*B ใน for loop ธรรมดา
C2 คือ การคูณตามนิยามการคูณเมตริกส์ในรูปแบบ vectorized
C3 คือ การคูณโดยใช้ bsxfun

จะเห็นได้ว่าการคูณแบบ bsxfun นั้นเร็วกว่าการคูณธรรมดาไปหลายขุมเลยค่ะ แต่ค่ะแต่ สมมติว่าเราเปลี่ยนขนาด matrix n จาก 3 เป็น 50 ล่ะ
พอขนาดเมตริกซ์ที่ต้องการคูณใหญ่ขึ้น การคูณแบบ vectorized กลับใช้เวลามากสุดและการคูณธรรมดาใช้เวลาน้อยสุด

ดังนั้นโพสนี้จึงขอสรุปว่า แม้ว่าการคูณเมตริกซ์สามารถทำได้หลายวิธี แต่ขนาดของเมตริกซ์ก็มีผลกับการเลือกใช้วิธีในการคูณเมตริกซ์นั้น ดังนั้นเลือกวิธีให้เหมาะกับงานค่ะ

ความเร็วในการคูณ 2D matrix กับ 3D array แบบ slice by slice

โพสนี้จะขอเปรียบเทียบความเร็วในการคูณ element-wise 3D matrix แบบ slice by slice อธิบายเป็นภาษาไทยลำบาก วาดรูปให้ดูเลยดีกว่า
สมมติเรามี 2D matrix A ที่อยากจะเอาไปคูณแบบ element by element กับ 2D matrix B ที่มีจำนวน r ชั้น เราสามารถเขียนโค้ดได้หลายแบบ แต่ในที่นี้จะเปรียบเทียบแค่ 2 แบบ คือแบบ vectorization กับ แบบใช้คำสั่ง bsxfun เราจะข้ามการคูณแบบใช้ for loop ไป เพราะรู้คำตอบอยู่แล้วว่าช้ากว่าทั้ง2แบบนี้มาก แต่เนื่องจาก MATLAB เค้าเป็นจ้าวแห่ง matrix operation (ชื่อเค้าก็บอกอยู่ MATrix LABoratory) และ bsxfun เป็น operation ที่ low level มากๆ เลยน่าสนใจว่าใครจะไวกว่ากัน 

โค้ดที่ใช้ในการเปรียบเทียบเป็นดังนี้

close all; clear; clc;

% 2D matrix times each slice of 3D matrix
N = [10,50,100,200,500,700];
t1 = zeros(1,length(N));
t2 = t1;
for ii = 1:length(N)
    n = N(ii);
    A  = rand(n,n);
    B  = rand(n,n,n);
    tic;
    C = bsxfun(@times, A, B);
    t1(ii) = toc;

    tic;
    D = ones(1,1,n);
    A = A.*D;
    C = A.*B;
    t2(ii) = toc;
end
h = plot(N,t1,'-*b',N,t2,'-*r');axis tight;
h(1).LineWidth = 2;
h(2).LineWidth = 2;
set(gca,'FontSize',14);
legend('bsxfun','vectorization');
xlabel('n'); ylabel('time(s)');


ผลสรุปออกมา คืออออออ...... bsxfun ชนะค่าาาาา ยิ่งmatrixมีขนาดใหญ่มากยิ่งทิ้งห่าง เพราะงั้นหากใครต้องการความไวในการเขียนโค้ด ดิชั้นแนะนำให้หันมาใช้ bsxfun จ้าา