如何为Kronecker-Produkt

时间:2018-01-16 19:44:00

标签: c++ templates matrix product tensor

目前,我发现了一篇名为Kronecker-Produkt的有趣文章。与此同时,我正在研究我的神经网络库。 所以我的算法工作,我需要一个张量类,我可以用一个重载的*运算符得到两个张量的乘积。

请考虑以下示例/问题:

  1. 如何有效地构建/存储嵌套矩阵?
  2. 如何执行两个张量的产品?
  3. 如何尽可能简单地显示张量c?
  4. 我的3级张量,目前只支持3个维度:

    #pragma once
    
    #include <iostream>
    #include <sstream>
    #include <random>
    #include <cmath>
    #include <iomanip>
    
    template<typename T>
    class tensor {
    public:
        const unsigned int x, y, z, s;
    
        tensor(unsigned int x, unsigned int y, unsigned int z, T val) : x(x), y(y), z(z), s(x * y * z) {
            p_data = new T[s];
            for (unsigned int i = 0; i < s; i++) p_data[i] = val;
        }
    
        tensor(const tensor<T> & other) : x(other.x), y(other.y), z(other.z), s(other.s) {
            p_data = new T[s];
            memcpy(p_data, other.get_data(), s * sizeof(T));
        }
    
        ~tensor() {
            delete[] p_data;
            p_data = nullptr;
        }
    
        T * get_data() {
            return p_data;
        }
    
        static tensor<T> * random(unsigned int x, unsigned int y, unsigned int z, T val, T min, T max) {
            tensor<T> * p_tensor = new tensor<T>(x, y, z, val);
    
            std::random_device rd;
            std::mt19937 mt(rd());
            std::uniform_real_distribution<T> dist(min, max);
    
            for (unsigned int i = 0; i < p_tensor->s; i++) {
                T rnd = dist(mt);
                while (abs(rnd) < 0.001) rnd = dist(mt);
                p_tensor->get_data()[i] = rnd;
            }
    
            return p_tensor;
        }
    
        static tensor<T> * from(std::vector<T> * p_data, T val) {
            tensor<T> * p_tensor = new tensor<T>(p_data->size(), 1, 1, val);
    
            for (unsigned int i = 0; i < p_tensor->get_x(); i++) p_tensor->set_data(i + 0 * p_tensor->get_x() * + 0 * p_tensor->get_x() * p_tensor->get_y(), p_data->at(i));
    
            return p_tensor;
        }
    
        friend std::ostream & operator <<(std::ostream & stream, tensor<T> & tensor) {
            stream << "(" << tensor.x << "," << tensor.y << "," << tensor.z << ") Tensor\n";
    
            for (unsigned int i = 0; i < tensor.x; i++) {
                for (unsigned int k = 0; k < tensor.z; k++) {
                    stream << "[";
    
                    for (unsigned int j = 0; j < tensor.y; j++) {
                        stream << std::setw(5) << roundf(tensor(i, j, k) * 1000) / 1000;
                        if (j + 1 < tensor.y) stream << ",";
                    }
    
                    stream << "]";
    
                }
    
                stream << std::endl;
            }
    
            return stream;
        }
    
        tensor<T> & operator +(tensor<T> & other) {
            tensor<T> result(*this);
    
            return result;
        }
    
        tensor<T> & operator -(tensor<T> & other) {
            tensor<T> result(*this);
    
            return result;
        }
    
        tensor<T> & operator *(tensor<T> & other) {
            tensor<T> result(*this);
    
            return result;
        }
    
        T & operator ()(unsigned int i, unsigned int j, unsigned int k) {
            return p_data[i + (j * x) + (k * x * y)];
        }
    
        T & operator ()(unsigned int i) {
            return p_data[i];
        }
    
    private:
        T * p_data = nullptr;
    };
    
    int main() {
        tensor<double> * p_tensor_input = tensor<double>::random(6, 2, 3, 0.0, 0.0, 1.0);
        tensor<double> * p_tensor_weight = tensor<double>::random(2, 6, 3, 0.0, 0.0, 1.0);
    
        std::cout << *p_tensor_input << std::endl;
        std::cout << *p_tensor_weight << std::endl;
    
        tensor<double> p_tensor_output = *p_tensor_input + *p_tensor_weight;
    
        return 0;
    }
    

2 个答案:

答案 0 :(得分:3)

你的第一步是#2 - 并使其正确。

之后,优化。

从容器C<T>开始。

在其上定义一些操作。 wrap(T)会返回包含C<T>的{​​{1}}。地图在T C<T>上使用T和一个函数,然后返回U f(T)。 flatten需要C<U>并返回C<C<U>>

定义C<U>,其中包含scale( T, C<T> )T,并返回带有缩放元素的C<T>。 Aka,标量乘法。

C<T>

然后我们有:

template<class T>
C<T> scale( T scalar, C<T> container ) {
  return map( container, [&](T t){ return t*scalar; } );
}

是你的张量产品。是的,这可能是您的实际代码。为了提高效率,我会调整一下。

(注意我使用了不同的术语,但我基本上用不同的词来描述monadic操作。)

完成后,测试,优化和迭代。

对于3,张量积的结果变得庞大而复杂,对于大张量没有简单的可视化。

哦,保持简单并将数据存储在template<class T> C<T> tensor( C<T> lhs, C<T> rhs ) { return flatten( map( lhs, [&](T t) { return scale( t, rhs ); } ) ); } 中以开始。

答案 1 :(得分:1)

以下是我在课堂上学到的有效矢量的一些技巧,但它们对于张量应该同样有用。

定义一个空构造函数和赋值运算符。例如

tensor(unsigned int x, unsigned int y, unsigned int z) : x(x), y(y), z(z), s(x * y * z) {
    p_data = new T[s];
}

tensor& operator=( tensor const& that ) {
    for (int i=0; i<size(); ++i) {
        p_data[i] = that(i) ;
    }
    return *this ;
}

template <typename T>
tensor& operator=( T const& that ) {
    for (int i=0; i<size(); ++i) {
        p_data[i] = that(i) ;
    }
    return *this ;
}

现在我们可以使用延迟评估来实现添加和缩放等功能。例如:

template<typename T1, typename T2>
class tensor_sum {
    //add value_type to base tensor class for this to work
    typedef decltype( typename T1::value_type() + typename T2::value_type() ) value_type ;

    //also add function to get size of tensor

    value_type operator()( int i, int j, int k ) const {
        return t1_(i,j,k) + v2_(i,j,k) ;
    }

    value_type operator()( int i ) const {
        return t1_(i) + v2_(i) ;
    }

    private:
    T1 const& t1_;
    T2 const& t2_;
}

template <typename T1, typename T2>
tensor_sum<T1,T2> operator+(T1 const& t1, T2 const& t2 ) {
    return vector_sum<T1,T2>(t1,t2) ;
}

此tensor_sum的行为与任何正常张量完全相同,只是我们不必分配内存来存储结果。所以我们可以这样做:

tensor<double> t0(...);
tensor<double> t1(...);
tensor<double> t2(...);
tensor<double> result(...); //define result to be empty, we will fill it later

result = t0 + t1 + 5.0*t2;

编译器应将其优化为一个循环,而不存储中间结果或修改原始张量。您可以为缩放和kronecker产品做同样的事情。根据您对张量的要求,这可能是一个很大的优势。但要小心,这并不总是最好的选择。

在实现kronecker产品时,您应该注意循环的顺序,尝试按照存储顺序浏览张量以提高缓存效率。