dtw

本文介绍了一个C++库,用于在动态时间规整(DTW)下实现快速最近邻检索。库中包括动态规划解决方案、LB_Keogh下界快速实现及改进的LB_Improved下界,实现在多个数据集上检索最近邻速度提升三倍。

/**
* (c) Daniel Lemire, 2008
*This C++ library implements fast nearest-neighbor retrieval under the dynamic time warping (DTW). 
* This library includes the dynamic programming solution, a fast implementation of the LB_Keogh
*  lower bound, as well as a improved lower bound called LB_Improved. This library was used to show
*  that LB_Improved can be used to retrieve nearest neighbors three times on several data sets
* including random walks time series or shape time series.
*
* See the classes LB_Keogh and LB_Improved for usage.

* Time series are represented using STL vectors.
*/

#ifndef DTW
#define DTW

#include <iostream>
#include<vector>
#include<algorithm>
#include<cmath>
#include <deque>
#include <cassert>
#include <sstream>

typedef double floattype;
typedef unsigned int uint;
using namespace std;

class MathUtil {
  public:
    static inline int min (int x, int y ) { return x < y ? x : y;}
    static inline int max (int x, int y ) { return x > y ? x : y;}
};

/**
* You do not need this class, used by LB_Keogh and LB_Improved later.
* It computes the DTW using dynamic programmming.
*/    
class dtw {
    public:
        
enum{ verbose = false, 
   vverbose = false, INF = 100000000, fast=true};
static inline double max (double x, double y ) { return x > y ? x : y;}
static inline double min (double x, double y ) { return x < y ? x : y;}

    dtw(uint n, uint constraint): mGamma(n,vector<double>(n,INF)),mN(n),mConstraint(constraint) {        
    }
    /*
    * hardcoded l1 norm (for speed)
    */
    inline double fastdynamic(const vector<double> &  v, const vector<double> & w) {
        if(! fast) return dynamic(v,w,mConstraint,1);
        assert(static_cast<int>(v.size()) == mN);
        assert(static_cast<int>(w.size()) == mN);
        assert(static_cast<int>(mGamma.size()) == mN);
        double Best(INF);
        for (int i = 0; i < mN;++i) {
            assert(static_cast<int>(mGamma[i].size()) == mN);
            for (int j = max(0,i-mConstraint); j < min(mN,i+mConstraint+1);++j) {
                Best = INF;
                if(i>0) Best = mGamma[i-1][j];
                if(j>0) Best = min(Best, mGamma[i][j-1]);
                if( (i > 0) && (j > 0) )
                 Best = min(Best, mGamma[i-1][j-1]);
                if((i==0) && (j==0))
                  mGamma[i][j] = fabs(v[i]-w[j]);
                else 
                  mGamma[i][j] = Best + fabs(v[i]-w[j]);                   
            }
        }
         return mGamma[mN-1][mN-1];
    }

    vector<vector<double> > mGamma;

    int mN, mConstraint;

    static inline double dynamic(const vector<double> &  v, const vector<double> & w, 
      int constraint=INF, int p=2) {
          assert(v.size() == w.size());
        int n ( v.size() );
        vector<vector<double> > gamma(n, vector<double>(n,0.0));
        for (int i = 0; i < n;++i) {
            for (int j = 0; j < n;++j) {
                if(abs(i-j) > constraint) {
                  gamma[i][j] = INF; continue;
                }
                vector<double> previous(0);
                if(i>0) previous.push_back(gamma[i-1][j]);
                if(j>0) previous.push_back(gamma[i][j-1]);
                if( (i > 0) && (j > 0) )
                 previous.push_back(gamma[i-1][j-1]);
                double smallest = *min_element(previous.begin(),previous.end());
                if (p != INF)
                  gamma[i][j] = pow(fabs(v[i]-w[j]),p)+smallest;
                else
                  gamma[i][j] = max(fabs(v[i] - w[j]), smallest);
                   
            }
        }
        if(p != INF) 
         return pow(gamma[n-1][n-1],1.0/p);
        else
         return gamma[n-1][n-1];
      }
};

/**
* You do not need this function, used by LB_Keogh and LB_Improved later.
*/
void computeEnvelope(const vector<floattype> & array, uint constraint, vector<floattype> & maxvalues, vector<floattype> & minvalues) {
        uint width = 1+  2 * constraint;
        deque<int> maxfifo, minfifo;
        maxfifo.push_back(0);
        minfifo.push_back(0);
        for(uint i = 1; i < array.size(); ++i) {
          if(i >=constraint+1) {
            maxvalues[i-constraint-1] = array[maxfifo.front()];
            minvalues[i-constraint-1] = array[minfifo.front()];
          }
          if(array[i] > array[i-1]) { //overshoot
            maxfifo.pop_back();
            while(maxfifo.size() > 0) {
              if(array[i] <= array[maxfifo.back()]) break;
              maxfifo.pop_back();
            }
          } else {
            minfifo.pop_back();
            while(minfifo.size() > 0) {
              if(array[i] >= array[minfifo.back()]) break;
              minfifo.pop_back();
            }
          }  
          maxfifo.push_back(i);
          minfifo.push_back(i);
         if(i==  width+maxfifo.front()) maxfifo.pop_front();
         else if(i==  width+minfifo.front()) minfifo.pop_front();
        }
        for(uint i = array.size(); i <= array.size() + constraint; ++i) {
              maxvalues[i-constraint-1] = array[maxfifo.front()];
              minvalues[i-constraint-1] = array[minfifo.front()];
              if(i-maxfifo.front() >= width) maxfifo.pop_front();
              if(i-minfifo.front() >= width) minfifo.pop_front();
        }
}

class NearestNeighbor {
    public:
        NearestNeighbor(const vector<double> &  v, int constraint) : mDTW(v.size(),constraint) {}
        virtual double test(const vector<double> & candidate) {return 0;}//= 0;
        virtual double getLowestCost() {return 0;}
        virtual ~NearestNeighbor() {};
        virtual int getNumberOfDTW(){return 0;}
        virtual int getNumberOfCandidates(){return 0;}
        dtw mDTW;
};

class NaiveNearestNeighbor :  public NearestNeighbor {
    public:
    NaiveNearestNeighbor(const vector<double> &  v, int constraint) : NearestNeighbor(v,constraint), lb_keogh(0),full_dtw(0),
     V(v),
    bestsofar(dtw::INF) {
    }
    
    double test(const vector<double> & candidate) {
        ++lb_keogh;++full_dtw;
        const double trueerror = mDTW.fastdynamic(V,candidate);//,mConstraint,1);
        if(trueerror < bestsofar) bestsofar = trueerror;
        return bestsofar;
    }
    
    
    void resetStatistics() {
        lb_keogh = 0;
        full_dtw = 0;
    }
    double getLowestCost(){return bestsofar;}
    
    int getNumberOfDTW(){return full_dtw;}
    
    
    int getNumberOfCandidates(){return lb_keogh;}
    
    
    private:
    int lb_keogh;
    int full_dtw;
    
    const vector<double>   V;
    double bestsofar;
    
};

/**
* Usage: create the object using the time series you want to match again the
* database and some DTW time constraint parameter. Then repeatedly apply
* the test function on the various candidates. The function returns the
* matching cost with the best candidate so far. By keeping track of when
* the cost was lowered, you can find the nearest neighbor in a database.
*
* Only the l1 norm is used.
*/
class LB_Keogh :  public NearestNeighbor{
    public:
    LB_Keogh(const vector<double> &  v, int constraint) : NearestNeighbor(v,constraint), lb_keogh(0), full_dtw(0), V(v), mConstraint(constraint),  
    bestsofar(dtw::INF), U(V.size(),0), L(V.size(),0) {
        assert(mConstraint>=0);
        assert(mConstraint<static_cast<int>(V.size()));
        computeEnvelope(V,mConstraint,U,L);
    }
    
    double justlb(const vector<double> & candidate) {
        double error (0.0);
        for(uint i = 0; i < V.size();++i) {
            if(candidate[i] > U[i]) 
              error += candidate[i] - U[i];
            else if(candidate[i] < L[i])
              error += L[i] - candidate[i];
        }
        return error;
    }
    double test(const vector<double> & candidate) {
        ++lb_keogh;
        double error (0.0);
        for(uint i = 0; i < V.size();++i) {
            if(candidate[i] > U[i]) 
              error += candidate[i] - U[i];
            else if(candidate[i] < L[i])
              error += L[i] - candidate[i];
        }
        //cout << "lb keogh = "<<error<<endl;
        if(error < bestsofar) {
            ++full_dtw;
            const double trueerror = mDTW.fastdynamic(V,candidate);//,mConstraint,1);
            if(trueerror < bestsofar) bestsofar = trueerror;
        }
        return bestsofar;
    }
    
    
    int getNumberOfDTW(){return full_dtw;}
    
    
    int getNumberOfCandidates(){return lb_keogh;}
    
    
    double getLowestCost(){return bestsofar;}
    
    void resetStatistics() {
        lb_keogh = 0;
        full_dtw = 0;
    }
    
    private:
    
    int lb_keogh;
    int full_dtw;
    const vector<double>   V; 
    int mConstraint;
    double bestsofar;
    vector<double>   U; 
    vector<double>   L; 
    
};


class LB_KeoghEarly :  public NearestNeighbor{
    public:
    LB_KeoghEarly(const vector<double> &  v, int constraint) : NearestNeighbor(v,constraint), lb_keogh(0), full_dtw(0), V(v), mConstraint(constraint),  
    bestsofar(dtw::INF), U(V.size(),0), L(V.size(),0) {
        assert(mConstraint>=0);
        assert(mConstraint<static_cast<int>(V.size()));
        computeEnvelope(V,mConstraint,U,L);
    }
    
    double test(const vector<double> & candidate) {
        ++lb_keogh;
        double error (0.0);
        for(uint i = 0; i < V.size();++i) {
            if(candidate[i] > U[i]) 
              error += candidate[i] - U[i];
            else if(candidate[i] < L[i])
              error += L[i] - candidate[i];
            if(error > bestsofar) return bestsofar;
        }
            ++full_dtw;
            const double trueerror = mDTW.fastdynamic(V,candidate);//,mConstraint,1);
            if(trueerror < bestsofar) bestsofar = trueerror;
        return bestsofar;
    }
    
    
    int getNumberOfDTW(){return full_dtw;}
    
    
    int getNumberOfCandidates(){return lb_keogh;}
    
    
    double getLowestCost(){return bestsofar;}
    
    void resetStatistics() {
        lb_keogh = 0;
        full_dtw = 0;
    }
    
    private:
    
    int lb_keogh;
    int full_dtw;
    const vector<double>   V; 
    int mConstraint;
    double bestsofar;
    vector<double>   U; 
    vector<double>   L; 
    
};


/**
* Usage (same as LB_Keogh): create the object using the time series you want to match again the
* database and some DTW time constraint parameter. Then repeatedly apply
* the test function on the various candidates. The function returns the
* matching cost with the best candidate so far. By keeping track of when
* the cost was lowered, you can find the nearest neighbor in a database.
*
* Only the l1 norm is used.
*/
class LB_Improved : public  NearestNeighbor{
    public:
    LB_Improved(const vector<double> &  v, int constraint) : NearestNeighbor(v,constraint), lb_keogh(0), full_dtw(0), V(v), buffer(v), mConstraint(constraint),  
    bestsofar(dtw::INF), U(V.size(),0), L(V.size(),0), U2(V.size(),0), L2(V.size(),0) {
        assert(mConstraint>=0);
        assert(mConstraint<static_cast<int>(V.size()));
        computeEnvelope(V,mConstraint,U,L);
    }
    
    void resetStatistics() {
        lb_keogh = 0;
        full_dtw = 0;
    }
    
    
    
    double justlb(const vector<double> & candidate) {
        double error (0.0);
        buffer = candidate;
        for(uint i = 0; i < V.size();++i) {
            if(candidate[i] > U[i]) 
              error += candidate[i] - U[i];
            else if(candidate[i] < L[i])
              error += L[i] - candidate[i];
        }
        computeEnvelope(buffer,mConstraint,U2,L2);
        for(uint i = 0; i < V.size();++i) {
                if(V[i] > U2[i]) { 
                      error += V[i] - U2[i];
                } else if(V[i] < L2[i]) {
                      error += L2[i] - V[i];
                }
        }
        return error;
    }
    
    
    double test(const vector<double>  &candidate) {
        //memcpy(&buffer[0], &candidate[0],buffer.size()*sizeof(double));// = candidate;
        //buffer = candidate;
        //vector<double> & buffer(candidate);// no need for a copy
        ++lb_keogh;
        double error (0.0);
        for(uint i = 0; i < V.size();++i) {
            const double & cdi (candidate[i]); 
            if(cdi > U[i]) { 
              error += cdi - (buffer[i] = U[i]);
            } else if(cdi < L[i]) {
              error += (buffer[i] = L[i]) - cdi;
            } else buffer[i] = cdi;
        }
        if(error < bestsofar) {
            computeEnvelope(buffer,mConstraint,U2,L2);
            //env.compute(buffer,mConstraint,U2,L2);
            for(uint i = 0; i < V.size();++i) {
                if(V[i] > U2[i]) { 
                      error += V[i] - U2[i];
                } else if(V[i] < L2[i]) {
                      error += L2[i] - V[i];
                }
            }
            if(error < bestsofar) {
                ++full_dtw;
                const double trueerror = mDTW.fastdynamic(V,candidate);//,mConstraint,1);
                if(trueerror < bestsofar) bestsofar = trueerror;
            }
        }
        return bestsofar;
    }
    
    /**
    * for plotting purposes
    */
    string dumpTextDescriptor(const vector<double>  &candidate) {
        //memcpy(&buffer[0], &candidate[0],buffer.size()*sizeof(double));// = candidate;
        buffer = candidate;
        //vector<double> & buffer(candidate);// no need for a copy
        ++lb_keogh;
        double error (0.0);
        for(uint i = 0; i < V.size();++i) {
            if(candidate[i] > U[i]) { 
              error += candidate[i] - U[i];
              buffer[i] = U[i];
            } else if(candidate[i] < L[i]) {
              error += L[i] - candidate[i];
              buffer[i] = L[i];
            } 
            assert((buffer[i]== L[i]) or (buffer[i] == U[i]) or (buffer[i] == candidate[i]));
            
        }
        vector<double> lbimprovedarray;
        computeEnvelope(buffer,mConstraint,U2,L2);
        for(uint i = 0; i < V.size();++i) {
                if(V[i] > U2[i]) { 
                      error += V[i] - U2[i];
                      lbimprovedarray.push_back(U2[i]);
                } else if(V[i] < L2[i]) {
                      error += L2[i] - V[i];
                      lbimprovedarray.push_back(L2[i]);
                } else 
                    lbimprovedarray.push_back(V[i]);

        }
        stringstream ss;
        for(uint k = 0; k <V.size(); ++k) {
            assert((lbimprovedarray[k]== L2[k]) or (lbimprovedarray[k] == U2[k]) or (lbimprovedarray[k] == V[k]));
            assert((buffer[k]== L[k]) or (buffer[k] == U[k]) or (buffer[k] == candidate[k]));
            ss<<k<<" "<<V[k]<<" "<<candidate[k]<<" "<<buffer[k]<<" "<<lbimprovedarray[k]<<" "<<L[k]<<" "<<U[k]<<" "<<L2[k]<<" "<<U2[k]<<endl;
        }
        //string ans;
        //ss>>ans;
        ///cout<<ss.str()<<endl;
        return ss.str();
    }

    int getNumberOfDTW(){return full_dtw;}
    
    int getNumberOfCandidates(){return lb_keogh;}
    
    double getLowestCost(){return bestsofar;}
    private:
    
    int lb_keogh;
    int full_dtw;
    const vector<double>   V;
    vector<double> buffer;  
    int mConstraint;
    double bestsofar;
    vector<double>   U;  
    vector<double>   L;  
    vector<double>   U2;  
    vector<double>   L2;  

};

class LB_ImprovedEarly : public  NearestNeighbor{
    public:
    LB_ImprovedEarly(const vector<double> &  v, int constraint) : NearestNeighbor(v,constraint), lb_keogh(0), full_dtw(0), V(v), buffer(v), mConstraint(constraint),  
    bestsofar(dtw::INF), U(V.size(),0), L(V.size(),0), U2(V.size(),0), L2(V.size(),0) {
        assert(mConstraint>=0);
        assert(mConstraint<static_cast<int>(V.size()));
        computeEnvelope(V,mConstraint,U,L);
    }
    
    void resetStatistics() {
        lb_keogh = 0;
        full_dtw = 0;
    }

    double test(const vector<double>  &candidate) {
        //memcpy(&buffer[0], &candidate[0],buffer.size()*sizeof(double));// = candidate;
        //buffer = candidate;
        //vector<double> & buffer(candidate);// no need for a copy
        ++lb_keogh;
        double error (0.0);
        for(uint i = 0; i < V.size();++i) {
            const double & cdi (candidate[i]); 
            if(cdi > U[i]) { 
              error += cdi - (buffer[i] = U[i]);
            } else if(cdi < L[i]) {
              error += (buffer[i] = L[i]) - cdi;
            } else buffer[i] = cdi;
            if(error>bestsofar) return bestsofar;
        }
        if(error < bestsofar) {
            computeEnvelope(buffer,mConstraint,U2,L2);
            //env.compute(buffer,mConstraint,U2,L2);
            for(uint i = 0; i < V.size();++i) {
                if(V[i] > U2[i]) { 
                      error += V[i] - U2[i];
                } else if(V[i] < L2[i]) {
                      error += L2[i] - V[i];
                }
                if(error>bestsofar) return bestsofar;

            }
            if(error < bestsofar) {
                ++full_dtw;
                const double trueerror = mDTW.fastdynamic(V,candidate);//,mConstraint,1);
                if(trueerror < bestsofar) bestsofar = trueerror;
            }
        }
        return bestsofar;
    }

    
    int getNumberOfDTW(){return full_dtw;}
    
    int getNumberOfCandidates(){return lb_keogh;}
    
    double getLowestCost(){return bestsofar;}
    private:
    
    int lb_keogh;
    int full_dtw;
    const vector<double>   V;
    vector<double> buffer;  
    int mConstraint;
    double bestsofar;
    vector<double>   U;  
    vector<double>   L;  
    vector<double>   U2;  
    vector<double>   L2;  

};

void piecewiseSumReduction(const vector<floattype> & array, vector<floattype> & out) {
    // the length of out gives out the desired output length 
    assert(out.size()>0);
    const uint sizeofpieces = array.size()/out.size();
    assert(sizeofpieces>0);
    //sum_up<floattype> s;
    for(uint k = 0; k<out.size()-1;++k) {
        //s.reset();
        out[k] = 0;
        for(uint j = k*sizeofpieces; j < (k+1)*sizeofpieces; ++j)
          out[k] += array[j];
    }
    uint k=out.size()-1;
    out[k] = 0;
    for(uint j = k*sizeofpieces; j < array.size(); ++j)
          out[k] += array[j];
}


/**
* for debugging purposes
*/
class DimReducedLB_Keogh :  public NearestNeighbor{
    public:
    DimReducedLB_Keogh(const vector<double> &  v, int constraint, int reduced) : NearestNeighbor(v,constraint), lb_keogh(0), full_dtw(0), V(v), mConstraint(constraint),  
    bestsofar(dtw::INF), U(V.size(),0), L(V.size(),0),Ured(reduced,0),Lred(reduced) {
        assert(mConstraint>=0);
        assert(mConstraint<static_cast<int>(V.size()));
        computeEnvelope(V,mConstraint,U,L);
        piecewiseSumReduction(U, Ured);
        piecewiseSumReduction(L, Lred);
    }
    
    double test(const vector<double> & candidate) {
        vector<double> reducedCandidate(Ured.size());
        piecewiseSumReduction(candidate, reducedCandidate);
        double smallerror (0.0);
        for(uint i = 0; i < Ured.size();++i) {
            if(reducedCandidate[i] > Ured[i]) 
              smallerror += reducedCandidate[i] - Ured[i];
            else if(reducedCandidate[i] < Lred[i])
              smallerror += Lred[i] - reducedCandidate[i];
        }
        if(smallerror > bestsofar) return bestsofar;
        ++lb_keogh;
        double error (0.0);
        for(uint i = 0; i < V.size();++i) {
            if(candidate[i] > U[i]) 
              error += candidate[i] - U[i];
            else if(candidate[i] < L[i])
              error += L[i] - candidate[i];
        }
        if(error < bestsofar) {
            ++full_dtw;
            const double trueerror = mDTW.fastdynamic(V,candidate);//,mConstraint,1);
            if(trueerror < bestsofar) bestsofar = trueerror;
        }
        return bestsofar;
    }
    
    
    int getNumberOfDTW(){return full_dtw;}
    
    
    int getNumberOfCandidates(){return lb_keogh;}
    
    
    double getLowestCost(){return bestsofar;}
    
    
    private:
    
    int lb_keogh;
    int full_dtw;
    const vector<double>   V; 
    int mConstraint;
    double bestsofar;
    vector<double>   U,L; 
    vector<double>   Ured,Lred; 
    
};

/**
* this class is not used?
*/
class Envelope {
    public:
    Envelope() : maxfifo(), minfifo(){}
    virtual ~Envelope() {}
    void compute(const vector<floattype> & array, uint constraint, vector<floattype> & maxvalues, vector<floattype> & minvalues) {
        const uint width = 1 +  2 * constraint;
        maxfifo.clear();minfifo.clear();
        for(uint i = 1; i < array.size(); ++i) {
          if(i >=constraint+1) {
            maxvalues[i-constraint-1] = array[maxfifo.size()>0 ? maxfifo.front():i-1];
            minvalues[i-constraint-1] = array[minfifo.size()>0 ? minfifo.front(): i-1];
          }
          if(array[i] > array[i-1]) { //overshoot
            minfifo.push_back(i-1);
            if(i ==  width+minfifo.front()) minfifo.pop_front();
            while(maxfifo.size() > 0) {
              if(array[i] <= array[maxfifo.back()]) {
                if (i==  width+maxfifo.front()) maxfifo.pop_front();
                break;
              }
              maxfifo.pop_back();
            }
          } else {
            maxfifo.push_back(i-1);
            if (i==  width+maxfifo.front()) maxfifo.pop_front();
            while(minfifo.size() > 0) {
              if(array[i] >= array[minfifo.back()]) {
                if(i==  width+minfifo.front()) minfifo.pop_front();
                break;
              }
              minfifo.pop_back();
            }
          }  
        }
        for(uint i = array.size(); i <= array.size() + constraint; ++i) {
              if(maxfifo.size()>0) {
                  maxvalues[i-constraint-1] = array[maxfifo.front()];
                if(i-maxfifo.front() >= width) {
                    maxfifo.pop_front();}
              } else {
                  maxvalues[i-constraint-1] = array[array.size()-1];
              }
              if(minfifo.size() > 0) {
                  minvalues[i-constraint-1] = array[minfifo.front()];
                  if(i-minfifo.front() >= width) {
                      minfifo.pop_front();}
              } else {
                  minvalues[i-constraint-1] = array[array.size()-1];
              }
        }
 }
        deque<int> maxfifo, minfifo;


};

// only used for unit testing
double l1diff(const vector<double> &  v, const vector<double> & w) {
    double ans(0);
    for(uint k = 0; k<v.size();++k)
      ans+=abs(v[k]-w[k]);
    return ans;
}
#endif


 

<think>我们正在讨论使用动态时间规整(DTW)进行时间序列聚类的方法及实现。根据提供的引用,我们可以总结如下: 1. DTW用于度量两个时间序列之间的相似性,尤其适用于不同长度、不同相位或速度的时间序列。 2. 在聚类中,DTW距离可以替代欧氏距离,用于K-means等聚类算法中。 3. 实现步骤通常包括:计算所有时间序列两两之间的DTW距离矩阵,然后使用聚类算法(如K-means)进行聚类。 引用[2][3][4]都提到了基于DTW的K-means时间序列聚类算法,并且有Matlab实现。引用[1][5]则提到了一种基于DTW的符号化时间序列聚类算法,但该算法对初始聚类中心敏感且容易陷入局部最优。 因此,我们可以介绍两种主要的DTW聚类方法: 1. 基于DTW距离的K-means聚类(常用且实现简单) 2. 基于DTW距离的层次聚类 另外,引用[4]还提到了一种组合模型:DTW-Kmeans-Transformer,用于时序聚类和状态识别。 下面,我将详细介绍基于DTW的K-means聚类方法及实现步骤,并给出Matlab代码示例(根据引用[2][3][4]的描述)。 ### 1. DTW距离简介 动态时间规整(DTW)是一种衡量两个时间序列之间相似度的方法,它通过扭曲时间轴来对齐序列,然后计算它们之间的最小距离。对于两个长度可能不同的时间序列$X=(x_1, x_2, \dots, x_n)$和$Y=(y_1, y_2, \dots, y_m)$,DTW距离的计算公式为: $$DTW(X, Y) = \min_{\pi} \sqrt{\sum_{(i,j) \in \pi} (x_i - y_j)^2}$$ 其中,$\pi$是路径集合,表示两个序列之间的对齐方式。 ### 2. 基于DTW的K-means聚类步骤 步骤1:计算距离矩阵 - 对于给定的$N$个时间序列,计算每两个序列之间的DTW距离,得到一个$N \times N$的距离矩阵$D$,其中$D_{ij}=DTW(TS_i, TS_j)$。 步骤2:确定聚类数目$K$ - 可以使用肘部法则(Elbow Method)或轮廓系数(Silhouette Coefficient)来确定最佳的聚类数目$K$。 步骤3:初始化聚类中心 - 由于DTW距离不满足欧氏空间的性质,我们不能直接计算均值作为中心。因此,通常采用以下方法: a) 随机选择$K$个时间序列作为初始中心。 b) 或者使用基于距离的方法(如k-means++)选择初始中心。 步骤4:分配样本到最近的聚类中心 - 对于每个时间序列,计算它与所有聚类中心的DTW距离,将其分配到距离最近的聚类中心所在的簇。 步骤5:更新聚类中心 - 由于DTW距离不能直接求平均,更新中心点通常使用DBA(DTW Barycenter Averaging)算法,即寻找一个序列,使得该序列到簇内所有序列的DTW距离之和最小。这是一个迭代优化过程。 步骤6:重复步骤4和步骤5,直到聚类中心不再变化或达到最大迭代次数。 ### 3. Matlab实现示例 根据引用[2][3][4],我们提供以下Matlab代码框架(注意:需要安装Signal Processing Toolbox,因为其中包含`dtw`函数): ```matlab % 步骤1:准备数据,假设时间序列存储在cell数组data中,每个元素是一个向量 data = { ... }; % 这里填入你的时间序列数据 % 步骤2:计算DTW距离矩阵 N = length(data); D = zeros(N, N); for i = 1:N for j = i+1:N D(i,j) = dtw(data{i}, data{j}); D(j,i) = D(i,j); % 对称矩阵 end end % 步骤3:确定聚类数目K(这里假设K已知,实际中需要选择) K = 3; % 步骤4:初始化聚类中心(随机选择K个样本作为初始中心) centroid_idx = randperm(N, K); centroids = data(centroid_idx); % 初始聚类中心(时间序列) % 步骤5:进行K-means聚类 max_iter = 100; cluster_labels = zeros(1, N); % 存储每个样本的簇标签 for iter = 1:max_iter % 分配样本到最近的聚类中心 for i = 1:N dists = zeros(1, K); for k = 1:K dists(k) = dtw(data{i}, centroids{k}); end [~, cluster_labels(i)] = min(dists); end % 更新聚类中心(使用DBA算法) new_centroids = cell(1, K); for k = 1:K cluster_data = data(cluster_labels == k); % 当前簇的所有时间序列 % 使用DBA算法计算新的中心 new_centroids{k} = dba(cluster_data); end % 检查中心是否变化(这里简化处理:直接比较中心序列是否相同,实际中可能比较距离) if isequal(centroids, new_centroids) break; else centroids = new_centroids; end end % 输出聚类结果 disp(cluster_labels); % 注意:DBA函数需要自己实现(下面提供DBA的简单实现) function avg_series = dba(series) % series: 一个cell数组,包含多个时间序列 % 使用迭代方法计算DTW重心平均序列 % 初始化:选择一个序列作为初始平均序列(例如第一个序列) avg_series = series{1}; max_iter = 10; for iter = 1:max_iter sum_series = zeros(size(avg_series)); % 用于累加对齐后的序列 count = zeros(size(avg_series)); % 每个位置被累加的次数 for i = 1:length(series) % 计算当前序列与平均序列的DTW路径 [~, ix, iy] = dtw(avg_series, series{i}); % 根据路径将当前序列的值累加到对应位置 temp = zeros(size(avg_series)); for j = 1:length(ix) temp(ix(j)) = temp(ix(j)) + series{i}(iy(j)); count(ix(j)) = count(ix(j)) + 1; end sum_series = sum_series + temp; end % 更新平均序列 new_avg_series = sum_series ./ count; % 检查收敛(这里简化) if norm(new_avg_series - avg_series) < 1e-5 break; end avg_series = new_avg_series; end end ``` 注意:上述DBA实现是一个简化版本,实际中可能更复杂。另外,Matlab的`dtw`函数返回的是距离和路径索引,我们利用路径索引来对齐序列。 ### 4. 算法优化与注意事项 - 计算效率:DTW距离计算复杂度较高,对于长时间序列,可以考虑使用下采样或FastDTW等加速方法。 - 初始中心:使用k-means++初始化可以改善聚类效果。 - 聚类数目:通过肘部法则或轮廓系数确定最佳K值。 - 避免局部最优:可以多次运行算法,选择最优结果。 引用[4]提到了一种组合模型:DTW-Kmeans-Transformer,其中Transformer可能用于特征提取,但引用中并未详细说明。在时间序列聚类中,也可以先使用Transformer等深度学习方法提取特征,然后再进行DTW聚类,但这超出了本文范围。 ### 5. 其他聚类方法 除了K-means,还可以使用层次聚类(Hierarchical Clustering): - 使用距离矩阵$D$,通过层次聚类算法(如UPGMA)构建树状图,然后根据设定的阈值或聚类数目进行切割。 ### 6. 总结 基于DTW的聚类方法能够有效处理时间序列在时间轴上的变形,适用于不同长度和相位的时间序列。但计算复杂度高是其主要缺点,因此在大规模数据集上需要优化。 §§相关问题§§ 1. DTW距离计算有哪些加速方法? 2. 如何评估时间序列聚类的效果? 3. 除了K-means,还有哪些聚类算法可以结合DTW距离? 4. DBA算法具体是如何工作的?有没有更高效的重心计算方法? 5. 在时间序列聚类中,如何处理高维时间序列?</think>### DTW聚类方法及实现 动态时间规整(DTW)是一种用于度量时间序列相似性的算法,尤其适用于长度不同或存在时间偏移的序列。结合聚类分析,可有效识别时间序列的模式。以下是基于DTW的聚类方法及实现步骤: --- ### **1. DTW聚类核心步骤** #### **步骤1:计算DTW距离矩阵** - **输入**:$N$ 个时间序列数据 $\{T_1, T_2, \dots, T_N\}$。 - **操作**:计算所有序列两两之间的DTW距离,得到对称距离矩阵 $D_{N \times N}$,其中: $$D_{ij} = \text{DTW}(T_i, T_j)$$ DTW距离通过动态规划对齐序列,最小化累积距离(公式见引用[^4])。 - **输出**:距离矩阵 $D$ 作为聚类输入。 #### **步骤2:选择聚类算法** 常见方法包括: - **K-means聚类**: - 将距离矩阵 $D$ 输入K-means算法。 - 需预先设定聚类数 $K$(通过肘部法则或轮廓系数确定[^4])。 - 更新聚类中心时,使用DTW重心平均(DBA)算法计算簇中心(见引用[^4])。 - **层次聚类**: - 基于距离矩阵 $D$ 构建树状图(如UPGMA方法)。 - 按预设阈值或聚类数切割树结构。 #### **步骤3:聚类结果评估** - **指标**:轮廓系数(衡量簇内紧密度和簇间分离度)、戴维森堡丁指数(DBI)。 - **可视化**:通过多维缩放(MDS)或t-SNE将高维距离矩阵降维展示。 --- ### **2. 实现方法(Matlab示例)** 根据引用[^2][^3][^4],实现流程如下: ```matlab % 步骤1:计算DTW距离矩阵 function D = compute_dtw_matrix(data) N = length(data); D = zeros(N, N); for i = 1:N for j = i+1:N D(i,j) = dtw(data{i}, data{j}); % 调用DTW算法 D(j,i) = D(i,j); % 对称矩阵 end end end % 步骤2:K-means聚类(基于DTW距离) function [labels, centers] = dtw_kmeans(data, K, max_iter) % 初始化:随机选择K个序列作为初始中心 centers = data(randperm(length(data), K)); labels = zeros(1, length(data)); for iter = 1:max_iter % 分配样本到最近中心 for i = 1:length(data) dists = arrayfun(@(k) dtw(data{i}, centers{k}), 1:K); [~, labels(i)] = min(dists); end % 更新中心:使用DBA算法(DTW Barycenter Averaging) for k = 1:K cluster_data = data(labels == k); centers{k} = dba(cluster_data); % DBA计算簇中心 end end end % DBA算法实现(简化版) function avg_series = dba(series) % 初始化:取第一个序列为初始平均序列 avg_series = series{1}; for iter = 1:10 % 迭代10次 aligned = cell(1, length(series)); for i = 1:length(series) [~, path] = dtw(avg_series, series{i}); aligned{i} = series{i}(path(:,2)); % 根据DTW路径对齐序列 end avg_series = mean(cell2mat(aligned), 1); % 计算均值序列 end end ``` --- ### **3. 优化与注意事项** 1. **初始中心敏感性问题**(引用[^1][^5]): - 使用 **K-means++** 初始化中心点,避免局部最优解。 - 多次运行算法取最佳结果。 2. **计算效率优化**: - 采用 **FastDTW** 近似算法(时间复杂度 $O(n)$)。 - 并行计算距离矩阵(尤其适合大规模数据)。 3. **符号化序列处理**(引用[^1][^5]): - 对时间序列提取关键点(如极值点),转化为符号序列(如SAX表示法)。 - 在符号序列上计算DTW距离,减少计算量。 --- ### **4. 应用场景** - **工业设备状态监测**:聚类振动传感器时序数据,识别异常模式[^4]。 - **金融行为分析**:对股票价格序列聚类,发现相似波动模式。 - **医疗诊断**:对心电图(ECG)序列分组,辅助疾病分类[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值