機器學習之旅---奇異值分解

     本次的講解主要內容如下:

1.什麼是奇異值分解?爲什麼任意實矩陣都存在奇異值分解?

2.怎麼用C語言代碼實現SVD分解?

3.實際應用:

基於SVD的圖像壓縮

基於SVD的協同過濾推薦系統


一、SVD奇異值分解概念

    在多數情況下,數據中的一小段攜帶了數據集中的大部分信息,其他信息要麼是噪聲,要麼就是毫不相干的信息。在線性代數中有很多矩陣分解技術,通過它們可以將原始矩陣表示成新的易於處理的形式。不同的矩陣分解技術具有不同的性質,有各自特定的應用場景。

    奇異值分解SVD作爲矩陣分解的一種類型,可以使我們只用很小的數據集就能表達原始數據集,它可以從噪聲數據中抽取特徵值。SVD可以將任意維數的原始數據集矩陣Data分解成三個矩陣U、Σ、VT,如下公式所示:


    上述分解中會構建出一個矩陣Σ,該矩陣只有對角元素,其他元素均爲0.此外,Σ的對角元素是從大到小排列的,這些對角線元素稱爲原始數據集矩陣Data的“奇異值”singular value。它與PCA特徵的聯繫:PCA得到的是矩陣的特徵值,奇異值就是矩陣Data*DataT特徵值的平方根。


設A∈Rrm*n,ATA的特徵值爲:



則稱  (i=1,2,…,n)爲矩陣A的奇異值。

      當A爲零矩陣時,它的所有奇異值均爲零。一般的,矩陣A的奇異值個數等於A的列數,A的非零奇異值的個數等於A的秩。

      接下來,首先用數學推導介紹SVD存在的必然性。

一些定義:

正交矩陣

如果方正Q滿足QTQ=QQT=I(或者等價的Q-1=QT),則稱Q爲正交矩陣。

非奇異矩陣

若n階矩陣A的行列式不爲零,即 |A|≠0,則稱A爲非奇異矩陣或滿秩矩陣,否則稱A爲奇異矩陣或降秩矩陣。

正定矩陣

設M是n階方陣,如果對任何非零向量z,都有 zTMz > 0,就稱M正定矩陣。

定理證明:

定理1 (實對稱矩陣正交相似於對角矩陣)

對任意n階實對稱矩陣A,都有n階正交矩陣Q,使得


爲了證明該定理,首先給出並證明兩個引理:

  • . 實對稱矩陣特徵值爲實數
  • 若任意n階矩陣的特徵值爲實數,則有正交矩陣Q,使得


引理1,證明:

設λ爲A的一個特徵值,x爲對應的特徵向量,則有Ax = λx,因此


所以特徵值必爲實數。

引理2,利用數學歸納法證明:

當n=1時,結論顯然成立

設對n-1階矩陣的結論也成立,現在證明對n階矩陣結論依然成立

令λ1爲A的一個實特徵值,相應的特徵向量爲a1,不妨設a1已單位化。把a1擴充爲Rn的標準正交基a1, a2,…, an,構造矩陣X=(a2,…, an)和P=(a1,X),則P爲正交矩陣且有:


於是,AP = (Aa1, AX)= (λ1a1, P(P-1AX)) = (λ11,P(P-1AX)) = P(λ1ε1, P-1AX)。設:


個人理解:A->n*n,X->n*n-1,P->n*n,則P-1AX->n*n-1,正好這麼拆,a->1*n-1

從而有:


根據歸納假設,對於A1有n-1階正交矩陣T,使得T-1A1T = TTA1T= At爲上三角矩陣,現取:


由於P和Q都是正交矩陣,而正交矩陣相乘結果R仍是正交矩陣,因此可以寫作:


現在回到定理1的證明,因爲A爲實對稱矩陣,則有引理1知,A的特徵值均爲實數。由引理2知,存在正交矩陣R,使得


C爲上三角矩陣,而A=AT,則有C=CT,但C是上三角矩陣,CT爲下三角矩陣,所以C必爲對角矩陣。定理1得證。其中,C的對角線元素即爲A的特徵值構成。


定理2(奇異值分解)

設A∈Rrm*n,則存在m階正交矩陣U和n階正交矩陣V,使得

其中,Σ = diag(σ12,…, σr),即σ1, σ2,…,σr是A的非零奇異值,亦記作A=UDVT

證明:

記對稱矩陣ATA的特徵值爲:


由對稱矩陣的正交分解(定理1),我們有:



由第一個公式可以得到,


由第二個公式可以得到,




故有,證畢


若記U=(u1, u2,…,um),V=(v1, v2,…,vn),根據SVD的分解,A可以表示爲:


上式就是A的奇異值分解過程。

【我恨死CSDN的公式編輯了!!每次都貼圖,煩的要命】


二、SVD奇異值分解求解及實現

上面的一堆推導,只是爲了說明對任意矩陣都存在奇異值分解,而沒有講述如何求解奇異值分解。以下內容來自《徐士良C常用算法程序集(第二版)》,一般實矩陣的奇異值分解:

用household變換和變形QR算法對一般實矩陣進行奇異值分解,得到左右奇異向量U、V及奇異值矩陣D。

[個人認爲具體怎麼求SVD,大家不要去記和理解了,要用的時候能找到源碼,方便其他移植即可]






例:求下面兩個矩陣A和B的奇異值分解,ε取0.000001

C語言代碼:

徐士良老頭寫的,可讀性很差

bmuav.c:

#include "stdlib.h"
#include "math.h"
void ppp(double *a, double *e,double *s,double *v,int m,int n);
void sss(double *fg,double *cs);
/************************************************************************/
/* input:
/* a:存放m*n實矩陣A,返回時亦是奇異矩陣
/* m:行數 n:列數
/* u:存放m*m左奇異向量, v:存放n*n右奇異向量 
/* eps:給定精度要求,  ka: max(m,n)+1
/* output:
/* 返回值如果爲負數,表示迭代了60次,還未求出奇異值;返回值爲非負數,正常
/************************************************************************/
int bmuav(double *a,int m,int n,double *u,double *v,double eps,int ka)
{ 
	int i,j,k,l,it,ll,kk,ix,iy,mm,nn,iz,m1,ks;
	double d,dd,t,sm,sm1,em1,sk,ek,b,c,shh,fg[2],cs[2];
	double *s,*e,*w;

	s = (double *)malloc(ka*sizeof(double));
	e = (double *)malloc(ka*sizeof(double));
	w = (double *)malloc(ka*sizeof(double));
	it=60; 
	k=n;
	
	if (m-1<n) 
		k=m-1;
	
	l=m;

	if (n-2<m)
		l=n-2;

	if (l<0)
		l=0;
	ll=k;

	if (l>k) 
		ll=l;

	if (ll>=1)
	{ 
		for (kk=1; kk<=ll; kk++)
		{	
			if (kk<=k)
			{	d=0.0;
				for (i=kk; i<=m; i++)
				{
					ix=(i-1)*n+kk-1; d=d+a[ix]*a[ix];
				}
				s[kk-1]=sqrt(d);
				if (s[kk-1]!=0.0)
				{
					ix=(kk-1)*n+kk-1;
					if (a[ix]!=0.0)
					{
						s[kk-1]=fabs(s[kk-1]);
						if (a[ix]<0.0) 
							s[kk-1]=-s[kk-1];
					}
					for (i=kk; i<=m; i++)
					{ 
						iy=(i-1)*n+kk-1;
						a[iy]=a[iy]/s[kk-1];
					}
					a[ix]=1.0+a[ix];
				}
				s[kk-1]=-s[kk-1];
			}	
			if (n>=kk+1)
			{ 
				for (j=kk+1; j<=n; j++)
				{
					if ((kk<=k)&&(s[kk-1]!=0.0))
					{
						d=0.0;
						for (i=kk; i<=m; i++)
						{ 
							ix=(i-1)*n+kk-1;
							iy=(i-1)*n+j-1;
							d=d+a[ix]*a[iy];
						}
						d=-d/a[(kk-1)*n+kk-1];
						for (i=kk; i<=m; i++)
						{ 
							ix=(i-1)*n+j-1;
							iy=(i-1)*n+kk-1;
							a[ix]=a[ix]+d*a[iy];
						}
					}
					e[j-1]=a[(kk-1)*n+j-1];
				}
			}
			if (kk<=k)
			{ 
				for (i=kk; i<=m; i++)
				{ 
					ix=(i-1)*m+kk-1; iy=(i-1)*n+kk-1;
					u[ix]=a[iy];
				}
			}
			if (kk<=l)
			{ 
				d=0.0;
				for (i=kk+1; i<=n; i++)
					d=d+e[i-1]*e[i-1];
				e[kk-1]=sqrt(d);
				if (e[kk-1]!=0.0)
				{
					if (e[kk]!=0.0)
					{ 
						e[kk-1]=fabs(e[kk-1]);
						if (e[kk]<0.0)
							e[kk-1]=-e[kk-1];
					}
					for (i=kk+1; i<=n; i++)
						e[i-1]=e[i-1]/e[kk-1];
					e[kk]=1.0+e[kk];
				}
				e[kk-1]=-e[kk-1];
				if ((kk+1<=m)&&(e[kk-1]!=0.0))
				{ 
					for (i=kk+1; i<=m; i++) w[i-1]=0.0;
						for (j=kk+1; j<=n; j++)
							for (i=kk+1; i<=m; i++)
								w[i-1]=w[i-1]+e[j-1]*a[(i-1)*n+j-1];
					for (j=kk+1; j<=n; j++)
						for (i=kk+1; i<=m; i++)
						{ 
							ix=(i-1)*n+j-1;
							a[ix]=a[ix]-w[i-1]*e[j-1]/e[kk];
						}
				}
				for (i=kk+1; i<=n; i++)
					v[(i-1)*n+kk-1]=e[i-1];
			}
		}
	}
	mm=n;
	if (m+1<n) 
		mm=m+1;
	if (k<n) 
		s[k]=a[k*n+k];
	if (m<mm) 
		s[mm-1]=0.0;
	if (l+1<mm) 
		e[l]=a[l*n+mm-1];
	e[mm-1]=0.0;
	nn=m;
	if (m>n) 
		nn=n;
	if (nn>=k+1)
	{ 
		for (j=k+1; j<=nn; j++)
		{ 
			for (i=1; i<=m; i++)
			u[(i-1)*m+j-1]=0.0;
			u[(j-1)*m+j-1]=1.0;
		}
	}
	if (k>=1)
	{ 
		for (ll=1; ll<=k; ll++)
		{ 
			kk=k-ll+1; iz=(kk-1)*m+kk-1;
			if (s[kk-1]!=0.0)
			{ 
				if (nn>=kk+1)
				for (j=kk+1; j<=nn; j++)
				{
					d=0.0;
					for (i=kk; i<=m; i++)
					{ 
						ix=(i-1)*m+kk-1;
						iy=(i-1)*m+j-1;
						d=d+u[ix]*u[iy]/u[iz];
					}
					d=-d;
					for (i=kk; i<=m; i++)
					{ 
						ix=(i-1)*m+j-1;
						iy=(i-1)*m+kk-1;
						u[ix]=u[ix]+d*u[iy];
					}
				}
				for (i=kk; i<=m; i++)
				{ 
					ix=(i-1)*m+kk-1; 
					u[ix]=-u[ix];
				}
				u[iz]=1.0+u[iz];
				if (kk-1>=1)
					for (i=1; i<=kk-1; i++)
						u[(i-1)*m+kk-1]=0.0;
			}
			else
			{ 
				for (i=1; i<=m; i++)
					u[(i-1)*m+kk-1]=0.0;
				u[(kk-1)*m+kk-1]=1.0;
			}
		}
	}
	for (ll=1; ll<=n; ll++)
	{ 
		kk=n-ll+1; iz=kk*n+kk-1;
		if ((kk<=l)&&(e[kk-1]!=0.0))
		{ 
			for (j=kk+1; j<=n; j++)
			{
				d=0.0;
				for (i=kk+1; i<=n; i++)
				{ 
					ix=(i-1)*n+kk-1; iy=(i-1)*n+j-1;
					d=d+v[ix]*v[iy]/v[iz];
				}
				d=-d;
				for (i=kk+1; i<=n; i++)
				{ 
					ix=(i-1)*n+j-1; 
					iy=(i-1)*n+kk-1;
					v[ix]=v[ix]+d*v[iy];
				}
			}
		}
		for (i=1; i<=n; i++)
			v[(i-1)*n+kk-1]=0.0;
		v[iz-n]=1.0;
	}
	for (i=1; i<=m; i++)
		for (j=1; j<=n; j++)
			a[(i-1)*n+j-1]=0.0;
	m1=mm; 
	it=60;
	
	while (1==1)
	{
		if (mm==0)
		{ 
			ppp(a,e,s,v,m,n);
			free(s); free(e); free(w); 
			return(1);
		}
		if (it==0)
		{ 
			ppp(a,e,s,v,m,n);
			free(s); free(e); free(w); 
			return(-1);
		}
		kk=mm-1;
		while ((kk!=0)&&(fabs(e[kk-1])!=0.0))
		{
			d=fabs(s[kk-1])+fabs(s[kk]);
			dd=fabs(e[kk-1]);
			if (dd>eps*d) 
				kk=kk-1;
			else
				e[kk-1]=0.0;
		}
		if (kk==mm-1)
		{ 
			kk=kk+1;
			if (s[kk-1]<0.0)
			{
				s[kk-1]=-s[kk-1];
				for (i=1; i<=n; i++)
				{
					ix=(i-1)*n+kk-1; v[ix]=-v[ix];
				}
			}
			while ((kk!=m1)&&(s[kk-1]<s[kk]))
			{ 
				d=s[kk-1]; s[kk-1]=s[kk]; s[kk]=d;
				if (kk<n)
					for (i=1; i<=n; i++)
					{
						ix=(i-1)*n+kk-1; iy=(i-1)*n+kk;
						d=v[ix]; v[ix]=v[iy]; v[iy]=d;
					}
				if (kk<m)
				for (i=1; i<=m; i++)
				{ 
					ix=(i-1)*m+kk-1; iy=(i-1)*m+kk;
					d=u[ix]; u[ix]=u[iy]; u[iy]=d;
				}
				kk=kk+1;
			}
			it=60;
			mm=mm-1;
		}
		else
		{ 
			ks=mm;
			while ((ks>kk)&&(fabs(s[ks-1])!=0.0))
			{
				d=0.0;
				if (ks!=mm)
					d=d+fabs(e[ks-1]);
				if (ks!=kk+1)
					d=d+fabs(e[ks-2]);
				dd=fabs(s[ks-1]);
				if (dd>eps*d)
					ks=ks-1;
				else
					s[ks-1]=0.0;
			}
			if (ks==kk)
			{
				kk=kk+1;
				d=fabs(s[mm-1]);
				t=fabs(s[mm-2]);
				if (t>d)
					d=t;
				t=fabs(e[mm-2]);
				if (t>d)
					d=t;
				t=fabs(s[kk-1]);
				if (t>d)
					d=t;
				t=fabs(e[kk-1]);
				if (t>d)
					d=t;
				sm=s[mm-1]/d;
				sm1=s[mm-2]/d;
				em1=e[mm-2]/d;
				sk=s[kk-1]/d;
				ek=e[kk-1]/d;
				b=((sm1+sm)*(sm1-sm)+em1*em1)/2.0;
				c=sm*em1; c=c*c; shh=0.0;
				if ((b!=0.0)||(c!=0.0))
				{ 
					shh=sqrt(b*b+c);
					if (b<0.0)
						shh=-shh;
					shh=c/(b+shh);
				}
				fg[0]=(sk+sm)*(sk-sm)-shh;
				fg[1]=sk*ek;
				for (i=kk; i<=mm-1; i++)
				{ 
					sss(fg,cs);
					if (i!=kk)
						e[i-2]=fg[0];
					fg[0]=cs[0]*s[i-1]+cs[1]*e[i-1];
					e[i-1]=cs[0]*e[i-1]-cs[1]*s[i-1];
					fg[1]=cs[1]*s[i];
					s[i]=cs[0]*s[i];
					if ((cs[0]!=1.0)||(cs[1]!=0.0))
						for (j=1; j<=n; j++)
						{
							ix=(j-1)*n+i-1;
							iy=(j-1)*n+i;
							d=cs[0]*v[ix]+cs[1]*v[iy];
							v[iy]=-cs[1]*v[ix]+cs[0]*v[iy];
							v[ix]=d;
						}
					sss(fg,cs);
					s[i-1]=fg[0];
					fg[0]=cs[0]*e[i-1]+cs[1]*s[i];
					s[i]=-cs[1]*e[i-1]+cs[0]*s[i];
					fg[1]=cs[1]*e[i];
					e[i]=cs[0]*e[i];
					if (i<m)
						if ((cs[0]!=1.0)||(cs[1]!=0.0))
							for (j=1; j<=m; j++)
							{ 
								ix=(j-1)*m+i-1;
								iy=(j-1)*m+i;
								d=cs[0]*u[ix]+cs[1]*u[iy];
								u[iy]=-cs[1]*u[ix]+cs[0]*u[iy];
								u[ix]=d;
							}
				}
				e[mm-2]=fg[0];
				it=it-1;
			}
			else
			{ 
				if (ks==mm)
				{ 
					kk=kk+1;
					fg[1]=e[mm-2]; e[mm-2]=0.0;
					for (ll=kk; ll<=mm-1; ll++)
					{ 
						i=mm+kk-ll-1;
						fg[0]=s[i-1];
						sss(fg,cs);
						s[i-1]=fg[0];
						if (i!=kk)
						{
							fg[1]=-cs[1]*e[i-2];
							e[i-2]=cs[0]*e[i-2];
						}
						if ((cs[0]!=1.0)||(cs[1]!=0.0))
							for (j=1; j<=n; j++)
							{ 
								ix=(j-1)*n+i-1;
								iy=(j-1)*n+mm-1;
								d=cs[0]*v[ix]+cs[1]*v[iy];
								v[iy]=-cs[1]*v[ix]+cs[0]*v[iy];
								v[ix]=d;
							}
					}
				}
				else
				{ 
					kk=ks+1;
					fg[1]=e[kk-2];
					e[kk-2]=0.0;
					for (i=kk; i<=mm; i++)
					{ 
						fg[0]=s[i-1];
						sss(fg,cs);
						s[i-1]=fg[0];
						fg[1]=-cs[1]*e[i-1];
						e[i-1]=cs[0]*e[i-1];
						if ((cs[0]!=1.0)||(cs[1]!=0.0))
							for (j=1; j<=m; j++)
							{ 
								ix=(j-1)*m+i-1;
								iy=(j-1)*m+kk-2;
								d=cs[0]*u[ix]+cs[1]*u[iy];
								u[iy]=-cs[1]*u[ix]+cs[0]*u[iy];
								u[ix]=d;
							}
					}
				}
			}
		}
	}
	return(1);
}

static void ppp(double a[], double e[],double s[],double v[],int m,int n)
{ 
	int i,j,p,q;
	double d;
	if (m>=n) i=n;
	else i=m;
	for (j=1; j<=i-1; j++)
	{
		a[(j-1)*n+j-1]=s[j-1];
		a[(j-1)*n+j]=e[j-1];
	}
	a[(i-1)*n+i-1]=s[i-1];
	if (m<n) 
		a[(i-1)*n+i]=e[i-1];
	for (i=1; i<=n-1; i++)
		for (j=i+1; j<=n; j++)
		{
			p=(i-1)*n+j-1; q=(j-1)*n+i-1;
			d=v[p]; v[p]=v[q]; v[q]=d;
		}
	return;
}

static void sss(double fg[],double cs[])
{ 
	double r,d;
	if ((fabs(fg[0])+fabs(fg[1]))==0.0)
	{ 
		cs[0]=1.0; cs[1]=0.0; d=0.0;
	}
	else 
	{
		d=sqrt(fg[0]*fg[0]+fg[1]*fg[1]);
		if (fabs(fg[0])>fabs(fg[1]))
		{ 
			d=fabs(d);
			if (fg[0]<0.0) d=-d;
		}
		if (fabs(fg[1])>=fabs(fg[0]))
		{ 
			d=fabs(d);
			if (fg[1]<0.0) d=-d;
		}
		cs[0]=fg[0]/d; cs[1]=fg[1]/d;
	}
	r=1.0;
	if (fabs(fg[0])>fabs(fg[1])) 
		r=cs[1];
	else
		if (cs[0]!=0.0) r=1.0/cs[0];
	fg[0]=d;
	fg[1]=r;
	return;
}
brmul.c:矩陣乘法
void brmul(double *a,double *b,int m,int n,int k,double *c)
{
	int i,j,l,u;
	for (i=0; i<=m-1; i++)
		for (j=0; j<=k-1; j++)
		{
			u=i*k+j; c[u]=0.0;
			for (l=0; l<=n-1; l++)
				c[u]=c[u]+a[i*n+l]*b[l*k+j];
		}
	return;
}

調用:

// svd.cpp : 定義控制檯應用程序的入口點。
//

#include "stdafx.h"
#include "bmuav.c"
#include "brmul.c"

extern void brmul(double *a,double *b,int m,int n,int k,double *c);
extern int bmuav(double *a,int m,int n,double *u,double *v,double eps,int ka);

int _tmain(int argc, _TCHAR* argv[])
{
	int i,j;
    static double a[4][3]={ {1.0,1.0,-1.0},{2.0,1.0,0.0},
                           {1.0,-1.0,0.0},{-1.0,2.0,1.0}};
    static double b[3][4]={ {1.0,1.0,-1.0,-1.0},{2.0,1.0,
                            0.0,2.0},{1.0,-1.0,0.0,1.0}};
    static double u[4][4],v[3][3],c[4][3],d[3][4];
    double eps;
    eps=0.000001;
    i=bmuav(&a[0][0],4,3,&u[0][0],&v[0][0],eps,5);
    printf("\n");
    printf("EXAMPLE(1)\n");
    printf("\n");
    printf("i=%d\n",i);
    printf("\n");
    printf("MAT U IS:\n");
    for (i=0; i<=3; i++)
      { for (j=0; j<=3; j++)
          printf("%13.7e ",u[i][j]);
        printf("\n");
      }
    printf("\n");
    printf("MAT V IS:\n");
    for (i=0; i<=2; i++)
      { for (j=0; j<=2; j++)
          printf("%13.7e ",v[i][j]);
        printf("\n");
      }
    printf("\n");
    printf("MAT A IS:\n");
    for (i=0; i<=3; i++)
      { for (j=0; j<=2; j++)
          printf("%13.7e ",a[i][j]);
        printf("\n");
      }
    printf("\n\n");
    printf("MAT UAV IS:\n");
    brmul(&u[0][0],&a[0][0],4,4,3,&c[0][0]);
    brmul(&c[0][0],&v[0][0],4,3,3,&a[0][0]);
    for (i=0; i<=3; i++)
      { for (j=0; j<=2; j++)
          printf("%13.7e ",a[i][j]);
        printf("\n");
      }
    printf("\n\n");
    printf("EXAMPLE(2)\n");
    printf("\n");
    i=bmuav(&b[0][0],3,4,&v[0][0],&u[0][0],eps,5);
    printf("i=%d\n",i);
    printf("\n");
    printf("MAT U IS:\n");
    for (i=0; i<=2; i++)
      { for (j=0; j<=2; j++)
          printf("%13.7e ",v[i][j]);
        printf("\n");
      }
    printf("\n");
    printf("MAT V IS:\n");
    for (i=0; i<=3; i++)
      { for (j=0; j<=3; j++)
          printf("%13.7e ",u[i][j]);
        printf("\n");
      }
    printf("\n");
    printf("MAT B IS:\n");
    for (i=0; i<=2; i++)
      { for (j=0; j<=3; j++)
          printf("%13.7e ",b[i][j]);
        printf("\n");
      }
    printf("\n\n");
    printf("MAT UBV IS:\n");
    brmul(&v[0][0],&b[0][0],3,3,4,&d[0][0]);
    brmul(&d[0][0],&u[0][0],3,4,4,&b[0][0]);
    for (i=0; i<=2; i++)
      { for (j=0; j<=3; j++)
          printf("%13.7e ",b[i][j]);
        printf("\n");
      }
    printf("\n");
	return 0;
}

程序結果:




三、SVD應用實例

1.      基於SVD的圖像壓縮

這個例子比較簡單,首先進行奇異值分解,得到奇異值矩陣,和左右奇異向量。然後由於只要很少的奇異值,就能包含絕大部分被分解的矩陣信息,因此我們挑選不同數量的奇異值,重構圖像,比較差異。這邊分別實現了灰度圖、RGB三色圖的SVD分解。【奇異值到底選多少,自己打印奇異值矩陣,從大到小排序的,小到什麼程度就捨棄,實際情況實際操作。。。】

#include "stdafx.h"
#include "cv.h"
#include "highgui.h"
#include "bmuav.c"
#include "brmul.c"
#define max(a,b)            (((a) > (b)) ? (a) : (b))

extern void brmul(double *a,double *b,int m,int n,int k,double *c);
extern int bmuav(double *a,int m,int n,double *u,double *v,double eps,int ka);
int Process(IplImage *src);

int _tmain(int argc, _TCHAR* argv[])
{
	IplImage *src = cvLoadImage("test1.jpg", CV_LOAD_IMAGE_GRAYSCALE);
	Process(src);
	return 0;
}

int Process(IplImage *src)
{
	double *data, *u, *v, *c;
	int height, width, i, j, ka;
	double eps;
	int scale;
	IplImage *dst;
	
	dst = cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,1);

	eps=0.000001;
	height = src->height;
	width = src->widthStep;
	
	//allocate memory for matrix
	data = (double *)malloc(sizeof(double)*height*width);
	u = (double *)malloc(sizeof(double)*height*height);
	v = (double *)malloc(sizeof(double)*width*width);
	memset(u,0,sizeof(double)*height*height);
	memset(v,0,sizeof(double)*width*width);
	
	if(NULL == data || NULL == u || NULL == v)
	{
		return -1;
	}

	//assign value
	for(i = 0;i < height;i++)
	{
		for(j = 0;j < width;j++)
		{
			data[i*width+j] = (double)(unsigned char)src->imageData[i*width+j];
		}
	}
	
	ka = max(height,width) + 1;
	bmuav(data, height, width, u, v, eps, ka);
	
	//dump svd, scale is selected by watching top xxx large data
	/*for (i=0; i<=100; i++)
	{ 
		for (j=0; j<=100; j++)
			printf("%f", data[i*width+j]);
		printf("\n");
	}*/
	
	//reconstruction
	scale = 50;
	for(i = scale;i<height;i++)
	{
		data[i*width+i] = 0;
	}

	/*c needs to be initilized here ,but in matrix mutiply funciton*/
	c = (double *)malloc(sizeof(double)*height*width);
	
	brmul(u, data ,height, height, width, c);
	brmul(c, v, height, width, width, data);

	//assign value
	for(i = 0;i < height;i++)
	{
		for(j = 0;j < width;j++)
		{
			dst->imageData[i*width+j] = (unsigned char)data[i*width+j];
		}
	}
	cvSaveImage("result.jpg",dst);
	free(data);free(u);free(v);
	cvReleaseImage(&dst);
	return 0;
}

結果:




彩色圖:

#include "stdafx.h"
#include "cv.h"
#include "highgui.h"
#include "bmuav.c"
#include "brmul.c"
#define max(a,b)            (((a) > (b)) ? (a) : (b))

extern void brmul(double *a,double *b,int m,int n,int k,double *c);
extern int bmuav(double *a,int m,int n,double *u,double *v,double eps,int ka);
int Process(IplImage *src);

int _tmain(int argc, _TCHAR* argv[])
{
	IplImage *src = cvLoadImage("test3.jpg", CV_LOAD_IMAGE_UNCHANGED);
	Process(src);
	return 0;
}

int Process(IplImage *src)
{
	double *data_r, *u_r, *v_r, *c_r;
	double *data_g, *u_g, *v_g, *c_g;
	double *data_b, *u_b, *v_b, *c_b;
	int height, width, i, j, ka;
	double eps;
	int scale;
	IplImage *dst;
	
	dst = cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,3);

	eps=0.000001;
	height = src->height;
	width = src->width;
	
	//allocate memory for matrix
	data_r = (double *)malloc(sizeof(double)*height*width);
	u_r = (double *)malloc(sizeof(double)*height*height);
	v_r = (double *)malloc(sizeof(double)*width*width);

	data_g = (double *)malloc(sizeof(double)*height*width);
	u_g = (double *)malloc(sizeof(double)*height*height);
	v_g = (double *)malloc(sizeof(double)*width*width);

	data_b = (double *)malloc(sizeof(double)*height*width);
	u_b = (double *)malloc(sizeof(double)*height*height);
	v_b = (double *)malloc(sizeof(double)*width*width);

	memset(u_r,0,sizeof(double)*height*height);
	memset(v_r,0,sizeof(double)*width*width);

	memset(u_g,0,sizeof(double)*height*height);
	memset(v_g,0,sizeof(double)*width*width);

	memset(u_b,0,sizeof(double)*height*height);
	memset(v_b,0,sizeof(double)*width*width);

	//assign value
	for(i = 0;i < height;i++)
	{
		for(j = 0;j < width;j++)
		{
			data_r[i*width+j] = (double)(unsigned char)src->imageData[i*src->widthStep+j*src->nChannels+2];
			data_g[i*width+j] = (double)(unsigned char)src->imageData[i*src->widthStep+j*src->nChannels+1];
			data_b[i*width+j] = (double)(unsigned char)src->imageData[i*src->widthStep+j*src->nChannels+0];
		}
	}
	
	ka = max(height,width) + 1;
	bmuav(data_r, height, width, u_r, v_r, eps, ka);
	bmuav(data_g, height, width, u_g, v_g, eps, ka);
	bmuav(data_b, height, width, u_b, v_b, eps, ka);
	
	//dump svd, scale is selected by watching top xxx large data
	/*for (i=0; i<=100; i++)
	{ 
		for (j=0; j<=100; j++)
			printf("%f", data[i*width+j]);
		printf("\n");
	}*/
	
	//reconstruction
	scale = 50;
	for(i = scale;i<height;i++)
	{
		data_r[i*width+i] = 0;
		data_g[i*width+i] = 0;
		data_b[i*width+i] = 0;
	}

	/*c needs to be initilized here ,but in matrix mutiply funciton*/
	c_r = (double *)malloc(sizeof(double)*height*width);
	c_g = (double *)malloc(sizeof(double)*height*width);
	c_b = (double *)malloc(sizeof(double)*height*width);
	
	brmul(u_r, data_r ,height, height, width, c_r);
	brmul(c_r, v_r, height, width, width, data_r);

	brmul(u_g, data_g ,height, height, width, c_g);
	brmul(c_g, v_g, height, width, width, data_g);

	brmul(u_b, data_b ,height, height, width, c_b);
	brmul(c_b, v_b, height, width, width, data_b);

	//assign value
	for(i = 0;i < height;i++)
	{
		for(j = 0;j < width;j++)
		{
			dst->imageData[i*src->widthStep+j*src->nChannels + 0] = (unsigned char)data_b[i*width+j];
			dst->imageData[i*src->widthStep+j*src->nChannels + 1] = (unsigned char)data_g[i*width+j];
			dst->imageData[i*src->widthStep+j*src->nChannels + 2] = (unsigned char)data_r[i*width+j];
		}
	}
	cvSaveImage("result.jpg",dst);
	cvReleaseImage(&dst);
	free(u_r);free(v_r);free(c_r);free(data_r);
	free(u_g);free(v_g);free(c_g);free(data_g);
	free(u_b);free(v_b);free(c_b);free(data_b);
	return 0;
}

效果圖:

  

注意:SVD壓縮只是爲了存儲更少的數據來表達原始圖像,在重構圖像時,奇異值矩陣仍舊是要和原始圖像大小一樣的,只不過大部分地方用0填充罷了。


2 .     基於SVD的協同過濾推薦系統

這個例子比較有趣,推薦系統已存在很多年了。大家在網購時,商家總會根據大家的購買的歷史記錄給大家推薦新的商品及服務。實現方法有多種,這次只講基於協同過濾的方法。所謂協同過濾,就是將用戶和其他用戶的數據進行對比來實現推薦的。前端輸入的原始數據如下:【評分:0-5】


【後話:很嚴重的問題,就是當用戶間沒有交集,推薦系統就無法工作咯】

    下面就構建一個餐飲網站的推薦系統。假設一個人在家決定外出吃飯,但是他並不知道該到哪去吃飯,該點什麼菜。我們可以構建一個基本的推薦引擎,它能夠幫助用戶尋找沒有嘗過的菜餚,然後通過SVD來減少特徵空間並提高反饋的速度。

a.      基本的推薦引擎

推薦系統的工作過程:給定一個用戶,系統會爲此用戶返回N個最好的推薦,爲了實現這一點我們需要做:

  •  尋找用戶沒有評級的菜餚
  •  在用戶沒有評級的物品中,對每個物品預計一個可能的評級分數。也就是說,我們的系統認爲用戶可能會對物品的打分
  •  對這些物品的評分從高到低排序,返回前N個

     之前說將用戶和其他用戶的數據進行對比來實現推薦,那麼我們藉助什麼手段預測評分呢?有兩種方案:

基於用戶的相似度:行與行之間的比較

基於菜餚的相似度:列與列之間的比較

【距離公式可採用歐氏距離、餘弦距離、皮爾遜距離等等】

     那麼到底使用哪一種相似度呢?這取決於菜餚和用戶的數目。無論基於哪種相似度,它的計算時間都會隨着用戶/物品的數量的增加而增加。對於大部分產品導向的推薦引擎而言,用戶的數量往往大於商品的數量。因此這裏採用基於菜餚的相似度計算。

各種相似度計算代碼:

void matrix_reverse(double *src,double *dest,int row,int col)
{
	int i,j;

	for(i = 0;i < col;i++)
	{
		for(j = 0;j < row;j++)
		{
			dest[i * row + j] = src[j * col + i];
		}
	}
}

/*歐氏距離*/
double ecludSim(double *dataMat,int *overLap, int n, int count, int item, int j)
{
	double total = 0.0;
	int i=0, index=0;

	for(i = 0;i < count;i++)
	{
		index = overLap[i];
		total = total + (dataMat[index*n+item] - dataMat[index*n+j]) * (dataMat[index*n+item] - dataMat[index*n+j]);
	}
	total = sqrt(total);

	return 1.0/(1.0 + total);
}
double ecludSim2(double *dataMat,int *overLap, int n, int scale, int item, int j)
{
	double total = 0.0;
	int i = 0;

	for(i = 0;i < scale;i++)
	{
		total = total + (dataMat[item*n+i] - dataMat[j*n+i]) * (dataMat[item*n+i] - dataMat[j*n+i]);
	}
	total = sqrt(total);

	return 1.0/(1.0 + total);
}

/*餘弦距離*/
double cosSim(double *dataMat,int *overLap, int n, int count, int item, int j)
{
	double totalA=0.0, totalB=0.0, totalM=0.0;
	double result=0.0;
	int i, index;

	for(i = 0;i < count;i++)
	{
		index = overLap[i];
		totalA = totalA + dataMat[index*n+item] * dataMat[index*n+item];
		totalB = totalB + dataMat[index*n+j] * dataMat[index*n+j];
		totalM = totalM + dataMat[index*n+item] * dataMat[index*n+j];
	}
	result = totalM / (sqrt(totalA) * sqrt(totalB));

	return 0.5 + 0.5 * result;
}

double cosSim2(double *dataMat,int *overLap, int n, int scale, int item, int j)
{
	double totalA=0.0, totalB=0.0, totalM=0.0;
	double result=0.0;
	int i;

	for(i=0;i<scale;i++)
	{
		totalA = totalA + dataMat[item*n+i] * dataMat[item*n+i];
		totalB = totalB + dataMat[j*n+i] * dataMat[j*n+i];
		totalM = totalM + dataMat[item*n+i] * dataMat[j*n+i];
	}
	result = totalM / (sqrt(totalA) * sqrt(totalB));
	return 0.5 + 0.5 * result;
}

【機器學習是用Python寫的,操作矩陣很方便,這邊C語言很笨重,所以沒有做到代碼重用,XXX2表示SVD推薦系統時用的距離函數,XXX是標準推薦系統】

     預測評價的原理:假設要預測用戶A的第k個未評價菜餚的評分,我們可以遍歷整個數據矩陣尋找用戶A和其他用戶都評價過的其他菜餚j,計算用戶A和其他用戶針對菜餚j的評價的相似距離,若有多個這種共同的評價,則累加相似度。最後的計算公式:

第k個菜餚的評分 =  sum(累加的相似度 * A對j的評分 ) / 累加相似度

下面先給出主函數吧,否則太亂了:

// recommand.cpp : 定義控制檯應用程序的入口點。
//

#include "stdafx.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "bmuav.c"
#include "brmul.c"
#define max(a,b)            (((a) > (b)) ? (a) : (b))

typedef double (*func)(double *dataMat,int *overLap, int n, int count, int item, int j);
typedef double (*Est)(double *dataMat, int user, func simMeas, int item, int m, int n);

extern void brmul(double *a,double *b,int m,int n,int k,double *c);
extern int bmuav(double *a,int m,int n,double *u,double *v,double eps,int ka);
extern void matrix_reverse(double *src,double *dest,int row,int col);

double standEst(double *dataMat, int user, func simMeas, int item, int m, int n);
double svdEst(double *dataMat, int user, func simMeas, int item, int m, int n);
double cosSim(double *dataMat,int *overLap, int n, int count, int item, int j);
double cosSim2(int *dataMat,int *overLap, int n, int scale, int item, int j);
double ecludSim(double *dataMat,int *overLap, int n, int count, int item, int j);
double ecludSim2(double *dataMat,int *overLap, int n, int count, int item, int j);
double recommend(double *dataMat,int n, int m, int user, func simMeas=cosSim, Est estMethod=standEst);

int _tmain(int argc, _TCHAR* argv[])
{
	int user;
	double data[7][5] = {{4, 4, 0, 2, 2},
					{4, 0, 0, 3, 3},
					{4, 0, 0, 1, 1},
					{1, 1, 1, 2, 0},
					{2, 2, 2, 0, 0},
					{5, 5, 5, 0, 0},
					{1, 1, 1, 0, 0}};
	double data_2[11][11] = {{2, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0},
						{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5},
						{0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0},
						{3, 3, 4, 0, 3, 0, 0, 2, 2, 0, 0},
						{5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0},
						{0, 0, 0, 0, 0, 0, 5, 0, 0, 5, 0},
						{4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5},
						{0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4},
						{0, 0, 0, 0, 0, 0, 5, 0, 0, 5, 0},
						{0, 0, 0, 3, 0, 0, 0, 0, 4, 5, 0},
						{1, 1, 2, 1, 1, 2, 1, 0, 4, 5, 0}};
	
	
	user = 2;

    //recommend(&data[0][0], 5, 7, user, cosSim, standEst);
	//recommend(&data[0][0], 5, 7, user, ecludSim, standEst);
	//recommend(&data_2[0][0], 11, 11, user, ecludSim2, svdEst);
	recommend(&data_2[0][0], 11, 11, user, cosSim2, svdEst);
	return 0;
}

double recommend(double *dataMat,int n, int m, int user, func simMeas, Est estMethod)
{
	int i,count, item,j;
	int *record=0;
	double *temp_vote=0;
	double temp=0, temp2=0;

	record = (int *)malloc(sizeof(int)*n);
	memset(record, 0, sizeof(int)*n);
	count = 0;

	//尋找user用戶未評價物品
	for(i = 0;i < n;i++)
	{
		if(dataMat[user*n+i] == 0)
		{
			record[count++] = i;
		}
	}
	if (count == 0)
	{
		printf("該用戶評價了所有的物品\n");
		return -1; //用戶評價了所有的物品
	}

	temp_vote = (double *)malloc(sizeof(double)*count);
	memset(temp_vote,0,sizeof(double)*count);

	for(i=0;i<count;i++)
	{
		item = record[i];
		temp_vote[i] = estMethod(dataMat, user, simMeas, item, m, n);
	}

	//排序
	for(i=0;i<count;i++)
	{
		for(j=0;j<count - i;j++)
		{
			if(temp_vote[j]<temp_vote[j+1])
			{
				temp = temp_vote[j];
				temp_vote[j] = temp_vote[j+1];
				temp_vote[j+1] = temp;

				temp2 = record[j];
				record[j] = record[j+1];
				record[j+1] = temp2;
			}
		}
	}

	//dump result
	for(i = 0;i < count;i++)
	{
		printf("food label %d,value to recommand %f\n", record[i], temp_vote[i]);
	}
	free(record);
	return 0;
}

標準推薦系統:

double standEst(double *dataMat, int user, func simMeas, int item, int m, int n)
{
	double simTotal=0, ratSimTotal=0, userRating=0, similarity=0;
	int j=0, k=0, count=0;
	int *overLap=0;	//記錄交集
	
	overLap = (int *)malloc(sizeof(int)*m);
	
	for (j = 0;j < n;j++)
	{
		userRating = dataMat[user*n+j];		//user用戶評價過的
		if(userRating == 0)
			continue;
		
		count = 0;
		memset(overLap, 0, sizeof(int)*m);

		for(k = 0;k < m;k++)
		{
			if(dataMat[k*n+item] > 0 && dataMat[k*n+j] > 0)		//尋找用戶都評級的兩個物品
			{	
				overLap[count++] = k;
			}
		}

		if (count == 0)
		{
			similarity = 0;
		}else
		{
			similarity = simMeas(dataMat, overLap, n, count, item, j);
		}
		
		simTotal += similarity;
		ratSimTotal += similarity * userRating;
	}

	free(overLap);
	if(0 == simTotal)
	{
		return 0.0;
	}else
	{
		return ratSimTotal/simTotal;
	}
}
對於data矩陣,用戶2,對應第三行,預測結果:


b.      基於SVD的推薦引擎

在數據矩陣非常稀疏時,基於SVD的推薦引擎性能就會比標準的好很多。我們利用SVD將所有菜餚隱射到一個低維空間中,再利用和前面一樣的相似度計算方法來進行推薦。

double svdEst(double *dataMat, int user, func simMeas, int item, int m, int n)
{
	double *u, *v, *data_new, *I, *dataMat2, *dataMatCopy, *svdMat;
	double simTotal=0, ratSimTotal=0, userRating=0, similarity=0, eps=0;
	int i=0, j=0, k=0, count=0, ka=0,scale=0;

	u = (double *)malloc(sizeof(double)*m*m);
	v = (double *)malloc(sizeof(double)*n*n);
	dataMat2 = (double *)malloc(sizeof(double)*m*n);
	dataMatCopy = (double *)malloc(sizeof(double)*m*n);
	for(i=0;i<m*n;i++) 
		dataMatCopy[i] = dataMat[i];
	for(i=0;i<m*m;i++) 
		u[i] = 0;
	for(i=0;i<n*n;i++) 
		v[i] = 0;

	eps = 0.000001;
	ka = max(m,n) + 1;

	//奇異值分解
	bmuav(&dataMatCopy[0], m, n, u, v, eps, ka);

	//挑選合適的奇異值:打印出所有的,再挑選,此處打印略
	scale = 4;
	I = (double *)malloc(sizeof(double)*scale*scale);
	for(i=0;i<scale*scale;i++) 
		I[i] = 0.0;

	for(i = 0;i < scale;i++)
	{
		I[i*scale+i] = dataMatCopy[i*n+i];
		//printf("%f ", I[i*scale+i]);
	}
	//printf("\n");

	//將物品轉換到低維空間,data_new = dataMat' * U[:,0:scale] * I
	data_new = (double *)malloc(sizeof(double)*n*scale);
	svdMat = (double *)malloc(sizeof(double)*n*scale);
	
	matrix_reverse(dataMat, dataMat2, m, n);
    brmul(dataMat2, u, n, m, scale, data_new);
	brmul(data_new, I, n, scale, scale, svdMat);

	for (j = 0;j < n;j++)
	{
		userRating = dataMat[user*n+j];		//user用戶評價過的
		if((userRating == 0) || (j == item))
			continue;

		similarity = simMeas(svdMat, NULL, scale, scale, item, j);  //由於是SVD壓縮的,不用考慮交集了		
		simTotal += similarity;
		ratSimTotal += similarity * userRating;
	}

	free(u);free(v);
	free(data_new);free(dataMat2);
	free(dataMatCopy);free(I);free(svdMat);

	if(0 == simTotal)
	{
		return 0.0;
	}else
	{
		return ratSimTotal/simTotal;
	}
	
}
對於矩陣 data_2,用戶2,對應第三行,預測結果:


c.      現實中的挑戰

(1)如何對推薦引擎進行評價

    我們既沒有預測的目標值,也沒有用戶來調查對他們預測結果的滿意度。我們這時可以將已知的評價結果去掉,然後對他們進行預測,最後計算預測值和真實值之間的差異。通常用於推薦引擎評價的指標是最小均方根誤差,它 首先計算均方誤差的平均值,然後取其平方根 【sqrt((sum((a-b)*(a-b)))/num)】

 (2)真實的系統

   這邊代碼爲了保證可讀性,沒有效率:

a.我們不必在每次預測評分時都對數據矩陣進行SVD分解。在大規模數據集上,頻繁進行SVD分解,只會拖慢效率。在真實系統中,SVD每天運行一次,並且還要離線運行。

b.規模擴展性的挑戰:

數據矩陣很稀疏,我們有很多0,我們能否只存儲非零元素來節省內存計算開銷。

相似度計算也導致了計算資源浪費,每次需要一個推薦得分時,都要計算很多物品的相似度得分,這些相似度得分能否被其他用戶重複使用?實際系統中,會進行離線計算,並保存相似度得分。

c.如何在缺乏數據時,給出好的推薦?

實際系統將推薦系統看成是搜索問題,我們可能要使用需要推薦物品的屬性。在上述餐館例子裏,我們可以通過各種標籤來標記菜餚,比如素食、美式烤肉、價格貴等。同時我們也可以將這些屬性作爲相似度計算所需要的數據。這個被稱爲基於內容的推薦。



所有代碼下載地址:

http://download.csdn.net/detail/jinshengtao/8188243



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章