Shark源码分析(三):数据预处理之正则化
在机器学习算法中,获取训练数据后首先要做的不是将输入投入训练方法中进行学习,而是应该对数据进行预处理。预处理过程输出数据的质量能够对之后算法的结果起着至关重要的作用。预处理过程含有非常多的操作,在我目前阅读代码的过程中只碰到了正则化这一过程。那我们就先来讨论正则化,如果之后再碰到了其他的方法再补充。
Shark将对输入数据进行正则化的模型也看作是一个线性模型。Shark给出了两种正则化的方法,分别是NormalizeComponentsUnitInterval,NormalizeComponentsUnitVariance。对于这两种不同的正则化方法有两个不同的trainer(联系上一篇博客的内容)进行训练。
第一种方法是将每一维度的特征都缩小到
Normalizer类
该类定义在<include/shark/Models/Normalizer.h>
文件中。
其与普通线性模型的不同之处在于:
- 输入与输出的维度必须是相同的
- 对于每一维度需要单独进行计算
template <class DataType = RealVector>
class Normalizer : public AbstractModel<DataType,DataType>
{
protected:
RealVector m_A; //权值向量
RealVector m_b; //偏置向量 bool m_hasOffset; //表示是否需要偏置向量
public:
typedef AbstractModel<DataType,DataType> base_type;
typedef Normalizer<DataType> self_type;
typedef typename base_type::BatchInputType BatchInputType;
typedef typename base_type::BatchOutputType BatchOutputType;
Normalizer()
{ }
Normalizer(const self_type& model)
: m_A(model.m_A),m_b(model.m_b),m_hasOffset(model.m_hasOffset)
{ }
Normalizer(std::size_t dimension,bool hasOffset = false)
: m_A(dimension,dimension),m_b(dimension),m_hasOffset(hasOffset)
{ }
Normalizer(RealVector diagonal)
: m_A(diagonal),m_hasOffset(false)
{ }
Normalizer(RealVector diagonal,RealVector vector)
: m_A(diagonal),m_b(vector),m_hasOffset(true)
{ }
std::string name() const
{ return "Normalizer"; }
friend void swap(const Normalizer& model1,const Normalizer& model2)
{
std::swap(model1.m_A,model2.m_A);
std::swap(model1.m_b,model2.m_b);
std::swap(model1.m_hasOffset,model2.m_hasOffset);
}
const self_type operator = (const self_type& model)
{
m_A = model.m_A;
m_b = model.m_b;
m_hasOffset = model.m_hasOffset;
}
boost::shared_ptr<State> createState() const
{
return boost::shared_ptr<State>(new EmptyState());
}
//判断模型是否有被正确地初始化
bool isValid() const
{
return (m_A.size() != 0);
}
bool hasOffset() const
{
return m_hasOffset;
}
//返回权值向量
RealVector const& diagonal() const
{
SHARK_CHECK(isValid(),"[Normalizer::matrix] model is not initialized");
return m_A;
}
//返回偏置向量
RealVector const& offset() const
{
SHARK_CHECK(isValid(),"[Normalizer::vector] model is not initialized");
return m_b;
}
std::size_t inputSize() const
{
SHARK_CHECK(isValid(),"[Normalizer::inputSize] model is not initialized");
return m_A.size();
}
std::size_t outputSize() const
{
SHARK_CHECK(isValid(),"[Normalizer::outputSize] model is not initialized");
return m_A.size();
}
//将权值向量与偏置向量合在一起进行输出
RealVector parameterVector() const
{
SHARK_CHECK(isValid(),"[Normalizer::parameterVector] model is not initialized");
std::size_t dim = m_A.size();
if (hasOffset())
{
RealVector param(2 * dim);
init(param)<<m_A,m_b;
return param;
}
else
{
RealVector param(dim);
init(param)<<m_A;
return param;
}
}
//可以更改权值向量与偏置向量的值
void setParameterVector(RealVector const& newParameters)
{
SHARK_CHECK(isValid(),"[Normalizer::setParameterVector] model is not initialized");
std::size_t dim = m_A.size();
if (hasOffset())
{
SIZE_CHECK(newParameters.size() == 2 * dim);
init(newParameters)>>m_A,m_b;
}
else
{
SIZE_CHECK(newParameters.size() == dim);
init(newParameters)>>m_A;
}
}
std::size_t numberOfParameters() const
{
SHARK_CHECK(isValid(),"[Normalizer::numberOfParameters] model is not initialized");
return (m_hasOffset) ? m_A.size() + m_b.size() : m_A.size();
}
//在训练完成之后,将权值向量与偏置向量保存到模型中
void setStructure(RealVector const& diagonal)
{
m_A = diagonal;
m_hasOffset = false;
}
void setStructure(std::size_t dimension,bool hasOffset = false)
{
m_A.resize(dimension);
m_hasOffset = hasOffset;
if (hasOffset) m_b.resize(dimension);
}
void setStructure(RealVector const& diagonal,RealVector const& offset)
{
SHARK_CHECK(diagonal.size() == offset.size(),"[Normalizer::setStructure] dimension conflict");
m_A = diagonal;
m_b = offset;
m_hasOffset = true;
}
using base_type::eval;
//对输入利用权值向量以及偏置向量进行正则化
void eval(BatchInputType const& input,BatchOutputType& output) const
{
SHARK_CHECK(isValid(),"[Normalizer::eval] model is not initialized");
output.resize(input.size1(),input.size2());
noalias(output) = input * repeat(m_A,input.size1());
if (hasOffset())
{
noalias(output) += repeat(m_b,input.size1());
}
}
void eval(BatchInputType const& input,BatchOutputType& output,State& state) const
{
eval(input,output);
}
void read(InArchive& archive)
{
archive & m_A;
archive & m_b;
archive & m_hasOffset;
}
void write(OutArchive& archive) const
{
archive & m_A;
archive & m_b;
archive & m_hasOffset;
}
};
注意到这个类也是继承自AbstractModel。
正则化模型的使用方式是,利用输入的训练数据训练正则化模型的参数。如果之后有测试数据输入进来,可以使用同样的模型对测试数据进行正则化,而不需要再重新训练模型。
NormalizeComponentsUnitVariance类
该类定义在<include/shark/Algorithms/Trainers/NormalizeComponentsUnitVariance.h>
文件中。
在这个方法中,零均值化默认是被关闭的。因为对输入是一个稀疏矩阵的情况来说,零均值话是会破坏它的稀疏性。如果当输入数据不是稀疏的话,可以开启这一项。
其正则化方法是
template <class DataType = RealVector>
class NormalizeComponentsUnitVariance : public AbstractUnsupervisedTrainer< Normalizer<DataType> >
{
public:
typedef AbstractUnsupervisedTrainer< Normalizer<DataType> > base_type;
NormalizeComponentsUnitVariance(bool zeroMean)
: m_zeroMean(zeroMean){ }
std::string name() const
{ return "NormalizeComponentsUnitVariance"; }
void train(Normalizer<DataType>& model,UnlabeledData<DataType> const& input)
{
SHARK_CHECK(input.numberOfElements() >= 2,"[NormalizeComponentsUnitVariance::train] input needs to consist of at least two points");
std::size_t dc = dataDimension(input);
RealVector mean;
RealVector variance;
meanvar(input,mean,variance); //计算数据的均值,方差
RealVector diagonal(dc);
RealVector vector(dc);
for (std::size_t d=0; d != dc; d++){
double stddev = std::sqrt(variance(d));
if (stddev == 0.0)
{
diagonal(d) = 0.0;
vector(d) = 0.0;
}
else
{
diagonal(d) = 1.0 / stddev;
vector(d) = -mean(d) / stddev;
}
}
if (m_zeroMean)
model.setStructure(diagonal,vector);
else
model.setStructure(diagonal);
}
protected:
bool m_zeroMean;
};
注意到该方法是继承自AbstractUnsupervisedTrainer,也把它作为无监督学习方法。
NormalizeComponentsUnitInterval类
该类定义在<include/shark/Algorithms/Trainers/NormalizeComponentsUnitInterval.h>
文件中。
该方法是将数据的范围都变换到
该方法的公式是
template <class DataType = RealVector>
class NormalizeComponentsUnitInterval : public AbstractUnsupervisedTrainer< Normalizer<DataType> >
{
public:
typedef AbstractUnsupervisedTrainer< Normalizer<DataType> > base_type;
NormalizeComponentsUnitInterval()
{ }
std::string name() const
{ return "NormalizeComponentsUnitInterval"; }
void train(Normalizer<DataType>& model,UnlabeledData<DataType> const& input)
{
std:: size_t ic = input.numberOfElements();
SHARK_CHECK(ic >= 2,"[NormalizeComponentsUnitInterval::train] input needs to consist of at least two points");
std::size_t dc = dataDimension(input);
//取出每一维度的第一个数据
//求出每一维度的最大值和最小值
RealVector min = input.element(0);
RealVector max = input.element(0);
for(std::size_t i=1; i != ic; i++){
for(std::size_t d = 0; d != dc; d++){
double x = input.element(i)(d);
min(d) = std::min(min(d),x);
max(d) = std::max(max(d),x);
}
}
RealVector diagonal(dc);
RealVector offset(dc);
for (std::size_t d=0; d != dc; d++)
{
if (min(d) == max(d)) //这一维数据的值相同
{
diagonal(d) = 0.0;
offset(d) = -min(d) + 0.5;//偏移设置成0.5就好
}
else
{
double n = 1.0 / (max(d) - min(d));
diagonal(d) = n;
offset(d) = -min(d) * n;
}
}
model.setStructure(diagonal,offset);
}
};