Chen


  • 首页

  • 分类

  • 归档

  • 标签

STL 源码分析set、map

发表于 2017-12-25 | 分类于 C++ | | 阅读次数

简介

set, map是C++标准库的一种容器。底层实现都是采用红黑树的数据结构。这里要注意和unordered_set、unordered_map的本质区别,它们利用的是hash_table。set和map由于底层是红黑树,因此查找的速度很快,虽然hash_table的查找时间也很短,但是需要额外的存储空间。红黑树的具体实现还是比较复杂,需要先从2-3-4树学起,然后弄懂其原理。最简单的理解是在普通的二叉查找树基础上规避了其极端情况而出现的一种数据结构,它看重的是树的平衡性,下图是红黑树的简单示意图。
rb_tree

红黑树模板

下面是红黑树的模板代码,注释中写到rb_tree的对象所占空间是12字节。简单分析下就是header是一个指针占4个字节,node_count应该是unsigned_int占4字节,key_compare是function object,本身是没有存储空间的,但是为了进行区分它们,编译器会为其分配一个字节的存储空间,这样就可以通过存储地址来识别出不同的object。因此总共大小应该是9个字节,但是由于存在内存对齐机制,默认是4字节对齐的,因此最后的1其实是占用了4个字节。所以,最后是占用了12字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// rb_tree code
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc = alloc>
class rb_tree {
protected:
typedef __rb_tree_node<Value> rb_tree_node;
...
public:
typedef rb_tree_node *link_type;
...
protected:
size_type node_count;
link_type header;
Compare key_compare; //function object 大小为0,因为没有数据,但是编译器实现大小为1,总共大小是9-->12
// 空class都会占据一个byte,因为这样就可以为其提供一个唯一的存储地址,以此来区分不同的对象。
}

set、map源码分析

set、map的key值都不能随意改变,因为这会破坏红黑树的结构,因此在源码中都在key前加上了const限制。另外,set的key和value其实是同一个,而map中key、data却是分开的。侯老师在视频中说set、map更像是适配器,其实很形象,因为两者都是借助红黑树完成了所有功能,套了个壳而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set
{
public:
typedef Key key_type;
typedef Key value_type;
typedef Compare key_compare;
typedef Compare value_type;
private:
typedef rb_tree<key_type, value_type, identity<value_type>, key_compare, Alloc> rep_type;
rep_type t;
public:
typedef typename rep_type::const_iterator iterator;
}
template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
public:
typedef Key key_type;
typedef T data_type;
typedef T mapped_type;
typedef pair<const Key, t> value_type; //key 是 const的不能修改;
typedef Compare key_compare;
private:
typedef rb_tree<key_type, value_type, select1st<value_type>, key_compare, Alloc> rep_type;
rep_type t;
public:
typedef typename rep_type::iterator iterator;
...
}

总结

这篇文章可以说是相当简单的介绍了set、map的结构,其实最难的部分在于红黑树的学习理解上,有机会把红黑树简单梳理下。

声明

若有错误,欢迎讨论。严禁抄袭,仅用于学习。

Deep Learning 的思考

发表于 2017-11-27 | 分类于 think notes | | 阅读次数

正文

深度学习的浪潮已向我们袭来已久,可惜自己的研究方向不是深度学习,倍感惋惜。最近在和同学讨论时,对深度学习方法和传统方法进行了一些思考。现在,我做的仍然是传统的研究方法,在传统方法中,我们趋向于用简单的公式,优化函数等将问题进行建模,然后去寻找最优解。这样的方法简单,有较好的理论基础,并且我们发现这些方法可以适合很多实际应用中的问题,也就是说泛化性能更强。传统的方法更符合我们的认知,即偏向于把复杂的问题进行归纳,简化,然后进行建模求解。传统的方法研究多年,性能提升越来越慢,甚至有的已经到了瓶颈。后来深度学习的方法出现,又刷新了我们新的认知,但是深度学习的理论基础却相对薄弱,这两者对比其实是个有意思的事情。
传统的方法公式函数简单,因此描述能力有限,而各种问题也许并不是我们想像的那么直接。我们以前学过的定理公式很简单优美,但是在有些方面却有着很多近似条件的约束,因此要想精确的描述问题,公式还是会偏向复杂化。深度学习方法中,我把它看作是很多个基函数的组合,这些函数组合在一起经过训练最终能描述一个复杂的问题,至于理论这也许就很难去归纳,推倒。但是,它却能通过函数的组合,网络深度的加深来更好地描述问题,从而也能更好的去解决问题。深度学习方法也有其局限,复杂的东西可能对特定问题解决能力更强,但是对另外的相似问题可能不怎么work,也就是说神经网络需要对特定问题进行训练,它的泛化能力较弱。
我认为深度学习方法是一种更直接的认知问题的方式,问题本身也许就是复杂的,那就用复杂的方法去建模求解,至于理论,很难通过单纯的数学建模问题去解释。目前,我们传统方法的发展也是在偏向复杂化,很多时候,我们发现多加一些约束,或者多设置一些参数,可能就会达到比以前更好的效果。未来,也许就是这样,特定问题用特殊的方法去求解,而不是用一套方法去解决很多问题。

声明

若有错误,欢迎讨论。该博客欢迎分享,转载,但请声明出处,严禁抄袭,仅用于学习。

STL 源码分析deque

发表于 2017-11-18 | 分类于 C++ | | 阅读次数

简介

deque是双端队列,支持快速随机访问。在前后位置插入/删除元素较快,它是vector和array的组合体,等下从结构中就可以看到,其实还应该加上list。string在侯捷老师后面的视频没有具体说明,但是在primer中说它和vector相似,因此结构不会很难。

deque结构

deque

deque的结构设计很巧妙。deque中是以buffer为存储单位,当存储空间不够时,它会增加一个buffer的空间,buffer的大小在旧版中可以指定,后面则默认设置了。上图中的buffer就是可以存储8个int的空间。中间的map其实是一个vector,注意这个vector是用来模拟deque的,意思就是vector的元素不是从头往后放置的,而是从中间到两边放置存储,因此当vector需要扩充时,元素是被copy到中间位置。vector中放置的是指针,该指针指向相应的buffer,这里就有点像list了。

iterator包含了四个指针,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator {
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T** map_pointer;
typedef __deque_iterator self;
T* cur;
T* first;
T* last;
map_pointer node;
}

其中cur指向当前元素值,first指向buffer的首元素,finish则指向buffer的尾元素。node类型为指针的指针,指向map即vector容器中的指向buffer的指针。

了解完deque的基本结构,下面看deque中定义的函数。下面是insert函数,在position处插入一个元素其值为x。(注意position是一个位置迭代器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
iterator insert(iterator position, const value_type &x)
{
if (position.cur == start.cur){ //如果安插点在最前端
push_front(x);
return start;
}
else if (position.cur == finish.cur){
push_back(x);
iterator tmp = finish;
--tmp;
return tmp;
}
else {
return insert_aux(position, x);
}
}

当元素插入位置在最前端或者后端时都可以借助push_front()和push_back()函数来完成。当插入位置在中间时需要借助辅助函数,其操作和vector的insert_aux类似。另外一个比较重要的操作是设置指针值,见下面代码:

1
2
3
4
5
6
//指向指针的指针
void set_node(map_pointer new_node) {
node = new_node;
first = *new_node;
last = first + difference_type(buffer_size());
}

new_node解引用得到一个指针即map中所存的值,该值又指向buffer。last指针只需要在first上移动buffer_size()即可。

最后说一下stack 和 queue的底层容器都可选择list 或 dequeue,但是两者都不允许遍历,也不提供iterator。

声明

若有错误,欢迎讨论。严禁抄袭,仅用于学习。

STL 源码分析vector,array,forward_list

发表于 2017-11-12 | 分类于 C++ | | 阅读次数

简介

vector,array,forward_list应该是容器中较简单的类型。下面对部分顺序容器类型进行一个简单的总结。
vector是可变大小数组,元素是连续存储的,因此支持随机访问。在尾部之外的其他位置插入删除元素很慢,因为需要进行元素的移动操作。array是固定大小的容器,编译期间就可以确定大小。同样是顺序存储支持快速随机访问。forward_list和list相似,只不过前者是单向,后者是双向,等下文章会说forward_list的结构。两者都不是顺序存储的,因此不支持随机访问的操作,但是优点是插入删除的速度很快。deque,string在后面的文章继续说明。

vector

vector定义的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
template <class T, class Alloc=alloc>
class vector {
public:
typedef T value_type;
typedef value_type* iterator; // T*
typedef value_type& reference;
typedef size_t size_type;
protected:
iterator start;
iterator finish;
iterator end_of_storage;
public:
iterator begin() {return start;}
iterator end() {return finish;}
size_type size() const {
return size_type(end() - begin());
}
size_type capacity() const {
return size_type(end_of_storage - begin());
}
bool empty() const {
return begin() == end();
}
reference operator[] (size_type n) {
return *(begin() + n);
}
reference front() {return *begin;}
reference back() {return *(end() - 1);}
}

定义比较简单,很好理解。其中要注意的是其迭代器是指针。每个操作都进行了相应的定义,注意大多数容器是前闭后开的区间,因此注意back()函数的定义,end()返回的是尾后迭代器,因此要减1。
vector有一个很重要的函数就是push_back()。下面是push_back的源代码和相应的注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void push_back(const T &x)
{
if (finish != end_of_storage) {
//判断还有存储空间可用,直接在尾后迭代器
//位置构造元素
construct(finish, x);
++finish;
}
else
//没有存储空间可以使用
insert_aux(end(), x);
}
// 函数模板insert_aux()定义
template <class T, calss Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T &x) {
if (finish != end_of_storage) {
//复制最后位置元素
construct(finish, *(finish -1));
++finish;
T x_copy = x;
//从后向前复制元素,相当于把position到(finish-2)
//的元素统一向后移动一位。
copy_backward(position, finish-2, finish-1);
*position = x_copy;
}
else {
//没有备用空间
const size_type old_size = size();
const size_type len = old_size != 0 ? 2*old_size:1;
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
try {
//建立完新空间开始拷贝元素,第一步拷贝position位置前的元素
new_finish = uninitialized_copy(start, position, new_start);
//构造新元素x
construct(new_finish, x);
++new_finish;
//第二步拷贝position位置后的元素
new_finish = uninitialized_copy(position, finish, new_finish);
}
catch(...) {
//异常处理
//销毁元素
destroy(new_start, new_finish);
//收回空间
data_allocator::deallocate(new_start, len);
//抛出异常
throw;
}
}
}

这里有一个设计就是在push_back()函数和insert_aux函数中都进行了是否还有存储空间的判断,原因是不仅push_back函数会用到insert_aux函数,其他函数也可能用到该函数,所以这样增加了该函数的通用性。

array

array 和 vector 一样都支持随机访问,但是其大小是固定的。下面看看array的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename _Tp, std::size_t _Nm>
struct array{
typedef _Tp value_type;
typedef _Tp* pointer;
typedef value_type* iterator;
value_type _M_instance[_Nm ? _Nm :1];
iterator begin() {
return iterator(&_M_instance[0]);
}
iterator end() {
return iterator(&_M_instance[_Nm]);
}
}

其中iterator也是指针,不是定义的一个复杂的模板类。

forward_list

forward_list是单向链表,应该说实现上和list很像。下面看forward_list的模型图。
forward_list
在forward_list中有两个函数可以注意下

1
2
3
4
5
6
iterator begin() noexcept {
return iterator(this->_M_impl._M_head._M_next);
}
iterator end() noexcept {
return iterator(0);
}

forward_list中有一个伪头节点蓝色标记的,end()返回的实际上是一个空的节点。

声明

若有错误,欢迎讨论。严禁抄袭,仅用于学习。

STL源码分析iterator

发表于 2017-11-08 | 分类于 C++ | | 阅读次数

简介

迭代器在容器中扮演着重要角色。在使用算法的过程中,我们也需要用到迭代器。迭代器可以看作是smart pointer,它是标准库中定义的一个class type。

Iterator 定义

下面这段代码是标准库中对list_iterator的一个定义。

1
2
3
4
5
6
7
8
template<class T, class Ref, class Ptr>
struct __list_iterator{
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef ptrdiff_t difference_type;
}

从定义中我们可以看到iterator内部有5种类型,而算法在在调用iterator过程中需要知道这5种类型是什么,然后才有接下来的操作。这时候就会出现一个问题,那么我们怎么区分普通的指针和iterator呢?普通指针也是可以用于标准算法的,例子如下:

1
2
vector<int> vec = { 1, 4, 8,10 };
auto m = find_if(vec.begin(), vec.end(), [](const int &a) {return a > 6; });

而对于iterator我们获取其中的5种类型方式是:

1
2
3
4
template <class I>
struct iterator_traits{
typedef typename I::value_type value_type;
};

Iterator_traits是萃取机,用于获取不同迭代器或者指针对应的5种类型。这种方式对于普通指针肯定是不行的,下面就利用了partial template specialization 的方法。代码如下:

1
2
3
4
5
6
7
8
9
//partial specialization
template <class T>
struct iterator_traits(T*) {
typedef T value_type;
}
template <class T>
struct iterator_traits(const T*) {
typedef T value_type;
}

于是当我们需要知道指针或者Iterator的类型时就可以这样写:

1
2
3
4
5
6
//需要知道I的value type时可这么写:
template <typename I, ...>
void algorithm()
{
typename iterator_traits<I>::value_type v1;
}

这里实际上是用了template的参数推导机制。根据变量I的类型来进行选择,这样就得到了相应的5种类型,使得普通指针也能用到我们的Algorithm中。

声明

若有错误,欢迎讨论。严禁抄袭,仅用于学习。

STL源码分析List

发表于 2017-11-06 | 分类于 C++ | | 阅读次数

简介

最近在看侯捷老师的STL剖析课程。边看课顺便记下笔记,算是对自己的监督吧。侯捷老师的课程选用的是cygnus C++ 2.91.57 for windows 版本,虽然年代久远但是看起来要容易很多,而我看的是SGI的STL版本,内容上应该差不多,主要是阅读起来方便。最近在看书,我很喜欢侯捷老师书中的一句话,“天下大事必作于细”,与君共勉。

List内容介绍

List 在标准库中指的是双向链表,而我们在数据结构中自己定义的可能是单向链表,基本原理差不多。另外,要注意的是标准库的双向链表其实是环状链表,示意图如下:
list2
注意图中有一个灰色的节点,该节点就是我们常用的list.end()尾后迭代器所指向的地方。但是要注意该节点不属于我们定义的list内容,因此对它解引用会出现越界的错误。

List代码简析

下面的代码是对list_node的定义,从类模板的定义中可以知道,一个节点包括两个空指针和一个T类型的data。空指针其实可以换为list_node类型指针,后面的标准库中进行了更换。空指针是无类型指针,任何指针都可以直接赋值给它。

1
2
3
4
5
6
7
8
template <class T>
struct __list_node
{
typedef void* void_pointer;
void_pointer prev;
void_pointer next;
T data;
};

接下来看下运算符重载,这主要是针对list中iterator的运算符。iterator其实也是一个类模板。下面看看主要代码,代码中的类型定义不要太过于纠结,主要看其实现原理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template <class T, class Alloc=alloc>
class list{
protected:
typedef __list_node<T> list_node;
public:
typedef list_node* link_type;
typedef __list_iterator<T, T&, T*> iterator;
protected:
link_type node;
};
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;
typedef ptrdiff_t difference_type;
link_type node;
reference operator*() const {return (*node).data;}
pointer operator->() const {return &(operator*());}
self & operator++()
{node = (link_type)((*node).next); return *this;}
self operator++(int)
{self tmp = *this; ++*this; return tmp;}
}

其中,我觉得首先要注意的是运算符重载的顺序,我们可以发现iterator的解引用重载发生在(->)操作符重载之前,而后置递增运算符在前置运算符之后,其中的目的很明显,主要是后一个要用到前一个,所以我们在用到运算符重载的时候也应该注意这样的顺序,减少代码量。

递增运算符重载肯定返回的是调用者本身的类型即iterator类型。所以是返回*this。而前置递增运算符返回的是+1之后的iterator,而这里的+1其实就是让原先的指针node变成下一个node。注意前置运算符返回的是引用,而后置运算符不是。后置运算符多加了一个函数参数int,加上该参数的目的是为了区分前置与后置运算符,理由是当我们在调用这两种运算符时,只有一个函数参数即iterator的对象,所以无法区分,因此加上了一个函数参数int。而我们在调用时却不用写成i++5,理由是编译器会给我们加上,然后就可以区分了。后置递增运算符需要先保存以前的对象,然后再利用前置运算符进行递增,饭后旧的iterator。

解引用和(->)运算符返回的类型有一定差别,分别是引用和指针。这两者最终还是调用的built-in type的操作。举个例子:

1
2
3
4
list<Foo>::iterator ite;
ite->method();
//相当于(*ite).method, *ite获得一个 Foo object;最后就是 object.method()
//相当于(&(*ite))->method(); &(*ite)得到一个对象指针,然后就相当于指针调用一个method;

另外需要注意的是在后置运算符重载的定义中 self tmp = *this 调用的是复制构造函数而不是*重载运算符。++*this 调用的是前置运算符重载,因为这是存在的运算符优先级关系。

声明

若有错误,欢迎讨论。严禁抄袭,仅用于学习。

A generative vision model - RCN

发表于 2017-10-30 | 分类于 classic Papers | | 阅读次数

简介

Recursive cortical network (RCN) 是发表在science期刊上的一篇论文。论文中我觉得最好的想法是将contour和surface结合起来生成网络,英文翻译过来是递归皮质层网络(笑😀)。该模型对contour和surface分别进行了建模,其中surface使用的是条件随机场,这属于概率图模型的范畴(我仅仅是了解过,不太懂,据说在深度学习之前是CV的一个重要研究领域,有机会好好学习),contour采用的是compositional hierarchy of features (组合特征层级结构)。模型中对这两者进行了交互,用到了置信机制和动态规划来进行求解。论文中的细节没有看太懂,因为对这两者了解的都不够。后面补充知识后再补充笔记(😭)。目前,从别人复现代码看test要比train慢很多,所以从这点看论文是不太好的。

github 代码链接 https://github.com/vicariousinc/science_rcn

张量的奇异值分解(t-SVD)

发表于 2017-10-27 | 分类于 tensor decomposition | | 阅读次数

SVM

发表于 2017-09-20 | | 阅读次数

低秩和稀疏分离的模型推导

发表于 2017-09-02 | 分类于 Matrix in mathematics | | 阅读次数

综述

低秩和稀疏分离的模型又有一个说法叫鲁棒的主成分分析(Robust Principal Component Analysis),简称RPCA。
RPCA
在上图中低秩矩阵就是对应矩阵中的主成分,而sparse error matrix就是稀疏成分。RPCA在信号处理中有着很多实际的应用。
rpca_application
上图中两个典型的应用分别是视频中的背景建模和人脸图像去阴影。在监控视频中,背景往往是固定不动的,是对应视频中的主成分,而运动部分在每一帧图像中所处的位置不同,并且所占比例很小,可以看作是稀疏成分。人脸图像去阴影,是对多张同一人脸进行去阴影,而每一张人脸图像中的阴影比例较小,并且分布的位置不同,所以阴影部分就是稀疏成分,而无阴影图像对应主成分。因此,这两个应用都可以通过RPCA来进行建模处理。注意上述的稀疏成分更多是经验性的描述,并没有给出明确的数学定义,具体的定义在文献中也没有给出,具体问题应该具体分析吧。

RPCA数学建模及求解

这篇博客中介绍的是典型的RPCA数学建模方法,也是在这篇论文中“Robust Principal Analysis ?”提出的RPCA凸优化模型。这里应该注意RPCA分析的具体操作流程是首先把每一帧图像或者每一张图片进行了列向量化,然后把列向量按列排在一起,这样便构成了RPCA中的矩阵。对应的数学模型是:
$$M=L_0 + S_0$$
其中$M$对应的是列向量化的矩阵,$L_0$则对应的是矩阵中的低秩成分,$S_0$是稀疏成分。上述问题可以建模为:
$$ \text{min} \quad rank(L) + ||S||_0 \quad \text{s. t.} \quad M = L + S $$
模型的直观解释是主成分是低秩的,稀疏成分的非零元更少,所以对两者之和进行最小化求解就可以得到低秩和稀疏成分。但是,上述模型是非凸的,为了转化为凸优化模型求解的问题,需要进行凸松弛(凸放缩),把$rank(L)$转化为核范数$||L||_\ast$即奇异值之和,零范数转化为1范数。那么凸放缩后的数学模型是:
$$\text{min} \quad ||L||_\ast + || S ||_1 \quad \text{s. t.} \quad M = L + S $$
求解该模型的常用方法是Alternating Direction Method of Multipliers(ADMM)算法。首先写出模型的增广拉格朗日式子:
$$l(L,S,Y) = ||L||_\ast + \lambda ||S||_1 + \langle Y,M-L-S \rangle +\frac{\mu}{2}||M-L-S||_F^2$$,
ADMM 算法的更新公式为:
$$L_{k+1} = \text{arg min}_L ||L||_\ast + \frac{\mu}{2} ||M-L-S_k+\frac{Y}{\mu}||_F^2 \\
S_{k+1} = \text{arg min}_S \lambda ||S||_1 + \frac{\mu}{2} ||M-L_k-S+\frac{Y}{\mu}||_F^2 \\
Y_{k+1} = Y_k + \mu (M-L_k-S_k)
$$
关于ADMM算法会在下一篇博客中进行介绍。
其中$L$的更新式子可以简化为求解如下问题
$$\text{arg min}_X \frac{1}{2}||X-Y||_F^2 + \tau ||X||_\ast$$
由于该问题是凸问题因此最小值的解唯一,我们假设其解为$\hat{X} = \mathcal{D}_{\tau}(Y)$,其中$\mathcal{D}_{\tau}(Y)=U\mathcal{S}_{\tau}(\Sigma)V^T$,截断操作$\mathcal{S}_{\tau}[x] = \text{sgn}(x)\text{max}(|x|-\tau,0)$。下面我们需要证明$\hat{X}$为该凸问题的解。这里只需要证明下式成立:
$$Y- \hat{X} \in \tau \partial ||\hat{X}||_\ast$$
假设矩阵$Y$可以分解成$Y=U_0\Sigma_0 V_0^T + U_1\Sigma_1 V_1^T$,其中$\Sigma_0$奇异值大于$\tau$,$\Sigma_1$奇异值小于$\tau$。因此我们可以得到
$\hat{X} = U_0 (\Sigma_0 -\tau I)V_0^T$,$Y-\hat{X} = \tau (U_0 V_0^T + W), W=\frac{1}{\tau} U_1 \Sigma_1 V_1^T$核范数导数的定义是一个集合,即

$$\partial ||E||_\ast = \lbrace A B^T + D : A^TD=\mathbf{0}, DB= \mathbf{0}, ||D||_2 \leq 1 \rbrace$$

令$A=U_0, B=V_0, D=W$,由Y的SVD分解定义可知:$U_0^T U_1=\mathbf{0}$,因此,$A^TD=\mathbf{0}, DB= \mathbf{0}$。又因为$\Sigma_1$奇异值小于$\tau$,所以$||D||_2 \leq 1$。结论$Y-\hat{X} \in \tau \partial ||\hat{X}||_\ast$得证。
更新$S$的式子只需要进行求导就可以得到解,在此不再赘述,需要注意的是$||X||_1$是各元素的绝对值之和,因此求导时需要注意。
最后的更新算法即:

RPCA_algorithm

总结

这篇文章对RPCA问题作了简单的介绍和推导,文中的算法是比较经典的用凸优化的方法来求解RPCA问题,当然还有很多其他经典的方法,因为数学太差不想多说。若有错误,欢迎讨论。该博客欢迎分享,转载,但请声明出处,严禁抄袭,仅用于学习。

123
chenlongxi

chenlongxi

Everything is possible !

25 日志
7 分类
11 标签
GitHub qqEMail hotmail
© 2018 chenlongxi
由 Hexo 强力驱动
主题 - NexT.Muse