Shark源码分析(三):数据预处理之正则化
在机器学习算法中,获取训练数据后首先要做的不是将输入投入训练方法中进行学习,而是应该对数据进行预处理。预处理过程输出数据的质量能够对之后算法的结果起着至关重要的作用。预处理过程含有非常多的操作,在我目前阅读代码的过程中只碰到了正则化这一过程。那我们就先来讨论正则化,如果之后再碰到了其他的方法再补充。
Shark将对输入数据进行正则化的模型也看作是一个线性模型。Shark给出了两种正则化的方法,分别是NormalizeComponentsUnitInterval, NormalizeComponentsUnitVariance。对于这两种不同的正则化方法有两个不同的trainer(联系上一篇博客的内容)进行训练。
第一种方法是将每一维度的特征都缩小到[0,1]范围内,这对于特征值是有界的情况来说是有效的。而第二种方法是将每一维度的方差都调整到1。对于『将均值变为0』这一操作来说是可选的。如果不包含这一操作,那么这一方法对于高维度的稀疏特征向量来说是有效的。
Normalizer类
该类定义在<include/shark/Models/Normalizer.h>
文件中。
其与普通线性模型的不同之处在于:
- 输入与输出的维度必须是相同的
- 对于每一维度需要单独进行计算
template <class DataType = RealVector>
class Normalizer : public AbstractModel<DataType, DataType>
{
protected:
RealVector m_A;
RealVector m_b;
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>
文件中。
在这个方法中,零均值化默认是被关闭的。因为对输入是一个稀疏矩阵的情况来说,零均值话是会破坏它的稀疏性。如果当输入数据不是稀疏的话,可以开启这一项。
其正则化方法是x′=x−μσ=1σ⋅x−μσ
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>
文件中。
该方法是将数据的范围都变换到[0,1]区间内,这样做的话势必会破坏数据的稀疏性,所以可能会偏向于选择NormalizeComponentsUnitVariance这一方法。
该方法的公式是x′=x−minmax−min
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;
}
else
{
double n = 1.0 / (max(d) - min(d));
diagonal(d) = n;
offset(d) = -min(d) * n;
}
}
model.setStructure(diagonal, offset);
}
};