gorgonia / tensor

package tensor provides efficient and generic n-dimensional arrays in Go that are useful for machine learning and deep learning purposes
Apache License 2.0
359 stars 49 forks source link

Rotating Tensors/Denses, similar to numpy implementation #37

Open BryceBeagle opened 5 years ago

BryceBeagle commented 5 years ago

Numpy has a way to rotate matrices by increments of 90 degrees using their rot90 method that uses transposes and flips.

There is a transpose method for Tensors, but no way of flipping the Tensor that I see.

Is there a way of properly rotating a Tensor/Dense currently, other than through manual iteration over the data? If not, should this be added?

chewxy commented 5 years ago

This is what was used for my AlphaGo reimplementation:

func RotateBoard(board []float32, m, n int) ([]float32, error) {
    if m != n {
        return nil, errors.Errorf("Cannot handle m %d, n %d. This function only takes square boards", m, n)
    }
    copied := make([]float32, len(board))
    copy(copied, board)
    it := MakeIterator(copied, m, n)
    for i := 0; i < m/2; i++ {
        mi1 := m - i - 1
        for j := i; j < mi1; j++ {
            mj1 := m - j - 1
            tmp := it[i][j]
            // right to top
            it[i][j] = it[j][mi1]

            // bottom to right
            it[j][mi1] = it[mi1][mj1]

            // left to bottom
            it[mi1][mj1] = it[mj1][i]

            // tmp is left
            it[mj1][i] = tmp
        }
    }
    ReturnIterator(m, n, it)
    return copied, nil
}
func ReturnIterator(m, n int, it [][]float32) {
    if d, ok := iterPool[m]; ok {
        if _, ok := d[n]; ok {
            iterPool[m][n].Put(it)
        } else {
            iterPool[m][n] = &sync.Pool{
                New: func() interface{} {
                    retVal := make([][]float32, m)
                    for i := range retVal {
                        retVal[i] = make([]float32, n)
                    }
                    return retVal
                },
            }
            iterPool[m][n].Put(it)
        }
    } else {
        iterPool[m] = make(map[int]*sync.Pool)
        iterPool[m][n] = &sync.Pool{
            New: func() interface{} {
                retVal := make([][]float32, m)
                for i := range retVal {
                    retVal[i] = make([]float32, n)
                }
                return retVal
            },
        }
        iterPool[m][n].Put(it)
    }
}

func MakeIterator(board []float32, m, n int) (retVal [][]float32) {
    retVal = borrowIterator(m, n)
    for i := range retVal {
        start := i * int(m)
        hdr := (*reflect.SliceHeader)(unsafe.Pointer(&retVal[i]))
        hdr.Data = uintptr(unsafe.Pointer(&board[start]))
        hdr.Len = int(n)
        hdr.Cap = int(n)
    }
    return

}

func borrowIterator(m, n int) [][]float32 {
    if d, ok := iterPool[m]; ok {
        if d2, ok := d[n]; ok {
            return d2.Get().([][]float32)
        }
    }
    retVal := make([][]float32, m)
    for i := range retVal {
        retVal[i] = make([]float32, n)
    }
    return retVal
}

I agree it needs to be added. I don't quite have the bandwidth right now but these two snippets may be useful

omarsamhan-zz commented 4 years ago

I can take this issue. I am a newbie in terms of open-source collaboration so please bear with me :)

I might start with dense matrices first and then move on to tensors.

The rotate function can be implemented in dense_matop_memmove.go and/or dense_matop.go by allocating a new matrix and/or performing an "in-place" transformation, respectively.

Which implementation should I aim for first?

Note: The use of the term "in-place" is a slight abuse of notation

chewxy commented 4 years ago

safety first, so memmove - i.e. allocating a new matrix fist. then a unsafe version (i.e. in place).

Send PR :)