Shark源码分析(一):数据的存储(1)
Shark是一个使用C++开发的机器学习库,里面还有一些深度学习的算法例如:自编码器、限制玻尔兹曼机、循环神经网络等。对于熟悉机器学习、深度学习算法的流程还是很有帮助的,同时能够增强你对于C++的理解能力,特别是对于模板的应用能力。源码的可读性我觉得也比较不错,就是在有一些注释写的不是非常清楚。
该项目的主页是http://image.diku.dk/shark/sphinx_pages/build/html/index.html,里面的介绍也是非常的详尽,可以使你的学习更加方便。下面就开始进入正题。
对于任何一个算法来说,首先都需要对输入数据进行处理。对于Shark来说,这个处理还没有到预处理这一步,这里的处理指的是将输入的数据变为矩阵的形式,在Shark中称之为batch。这是为了利用矩阵的运算来加快算法的运行速度。我觉得这个思想就类似于梯度下降算法与批梯度下降算法。
为了之后能够更好地理解具体算法的过程,了解数据的存储结构还是非常有必要的。下面就结合具体的代码来分析。
Batch类
该类的定义在<include/shark/Data/BatchInterface.h>
这个文件里。
下面的这一段代码,针对的是将输入为vector的形式转变为matrix的形式,这应该还是非常好理解的。注意到这里的vector和matrix都是Shark自己实现的,具体的代码我还并没有阅读。
template<class T>
struct Batch<blas::vector<T> >{
typedef shark::blas::matrix<T> type;
typedef blas::vector<T> value_type;
//MatrixRowReference这个模板是将matrix的行重新封装了一下,还提供了将
//一行转变为vector形式的接口,这个模板的定义也在同一个文件中
typedef detail::MatrixRowReference<type,value_type> reference;
typedef detail::MatrixRowReference<const type,value_type> const_reference;
//ProxyIterator是Shark实现的一个迭代器,这里指的是能够访问batch中每一个
//元素的迭代器
typedef ProxyIterator<type, value_type,reference > iterator;
typedef ProxyIterator<const type, value_type, const_reference > const_iterator;
//将输入数据分割成大小为size的batch
template<class Element>
static type createBatch(Element const& input, std::size_t size = 1){
return type(size,input.size());
}
//将range范围内的数据分隔成batch的形式
template<class Range>
static type createBatchFromRange(Range const& range){
type batch(range.size(),range.begin()->size());
std::copy(range.begin(),range.end(),boost::begin(batch));
return batch;
}
//调整batch的大小
static void resize(type& batch, std::size_t batchSize, std::size_t elements){
ensure_size(batch,batchSize,elements);
}
};
如果你从未阅读过开源代码,一定会很奇怪为什么会有这么多typedef。这是为了将之前那一长串的内容替换成更短且更易读的内容,增加了代码的可读性。如果有其他类的代码引用了该代码中的内容,也能够使得命名具有一致性。
如果输入数据的形式是compressed_vector(这也是Shark实现的类型,应该是将输入的稀疏向量进行压缩后的形式),matrix,其对应的batch也有相对应的模板类来定义。分别是compressed_matrix,matrix_set。
template<class T>
struct Batch<shark::blas::compressed_vector<T> >{
typedef shark::blas::compressed_matrix<T> type;
typedef shark::blas::compressed_vector<T> value_type;
};
template<class T>
struct Batch<blas::matrix<T> >{
typedef shark::blas::matrix_set<blas::matrix<T> > type;
typedef blas::matrix<T> value_type;
};
从上面的代码中可以看出,Batch这个类模板只是将输入数据转变为相对应的batch形式,并没有真正地存储数据。那么算法输入的数据到底是存储在哪呢?通过下面这一个类就能够了解的比较清楚了。
SharedContainer类
该类定义在<include/shark/Data/Impl/Dataset.inl>
这个文件中。可以看到这个文件的后缀名还是比较诡异的,至于为什么要这样命名我也不知道。还是直接上代码。
template <class Type>
class SharedContainer : public ISerializable
{
public:
typedef typename Batch<Type>::type BatchType;
typedef typename Batch<Type>::reference reference;
typedef typename Batch<Type>::const_reference const_reference;
template <class T> friend bool operator == (const SharedContainer<T>& op1, const SharedContainer<T>& op2);
private:
typedef Batch<Type> BatchTraits;
//注意到这个vector,这就是存储数据的所在。vector中的每一项都是一个智能指针,
//存储指针的好处就是当数据量太大时避免了复制操作,而且智能指针的使用能够很好地
//避免内存泄露。智能指针中存储的就是输入数据对应的batch。
typedef std::vector<boost::shared_ptr<BatchType> > Container;
public:
SharedContainer(){}
SharedContainer(std::size_t numBatches){
m_data.resize(numBatches);
for(std::size_t i = 0; i != numBatches; ++i){
m_data[i].reset(new