-
Notifications
You must be signed in to change notification settings - Fork 321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
unexpected behavior in [
strided_slice
#230
Comments
@goldingn Is the above expected behavior? |
Ye gads! Since the tensorflow APIs are so awful, it might be best to just throw an error when presented with non-contiguous bracket function subset ranges until tensorflow itself is improved. Just a cheap proposal... See also several open/gripe issues on tensorflow related to this like tensorflow/tensorflow#4638 and tensorflow/tensorflow#206 For complete reference, the actual low-level tensorflow implementation is here: https://github.com/tensorflow/tensorflow/blob/7a0def60d45c1841a4e79a0ddf6aa9d50bf551ac/tensorflow/core/ops/array_ops.cc#L1501 edit: PyTorch does this right: http://pytorch.org/docs/master/torch.html#torch.index_select -- tensorflow should follow that lead. |
Wow, that's really disappointing that TF isn't smarter about this. @goldingn Do you agree that we should throw an error in this case? If so could you propose a change to that effect? |
would it be difficult to split the slicing index vector into contiguous sequences, call |
Yeah, unfortunately this is the expected behaviour, because I agree we should error on this, rather than silently ignoring the intermediate values. We should also mention the limitations of this extraction here. This sentence is misleading:
I'll add the error handling, a test, and edit the docs then send a PR. I wonder if there's a more visible place in the docs we can discuss what the extract syntax can do? |
It would be possible to do a bunch of x[0, 0:2, :] but you couldn't pass a vector to the second slice dimension (as far as I know anyway!). We previously discussed whether to the port full R extract syntax to I'm planning to trim some fat off that greta implementation, but I think it makes sense to keep the I think the best way to achieve the subsetting in your example with the current API would be: indices <- c(1L, 3L)
x_1_3 <- tf$gather(x, indices - 1L, axis = 1L)
x_1_3 <- x_1_3[1, , ] *The separate slice and concat operations probably wouldn't be too slow, but R's extract syntax handles a lot of different cases. E.g.: d <- c(2, 4, 2)
X <- array(seq_len(prod(d)), dim = d)
X[c(TRUE, FALSE, TRUE)]
I think it makes sense to be consistent with either python tensorflow or full R, and providing all this R extraction functionality is expensive. |
There's a PR for the error message and a test. I see that tensorflow API doc isn't in the package repo any more. Is it editable on github or just internal now @jjallaire? |
It is currently internal but I'm hoping to make it public soon. In the meantime if you just suggest some copy here I'll make the change on the website (in the meantime I'll merge the PR) |
OK great. Here's some text.
|
@goldingn, thank you for the pointer to The reason I encountered this issue is that I have time series data, which is library(tensorflow)
library(reticulate)
d <- c(2, 4, 2); X <- array_reshape(1:prod(d), d)
py$X <- X
py_run_string("
import tensorflow as tf
x = tf.convert_to_tensor_or_sparse_tensor(X)
sess = tf.Session()
x_1_3 = sess.run(x[0,::2,:])
")
py$x_1_3
#> [,1] [,2]
#> [1,] 1 2
#> [2,] 5 6 Do you have any suggestions about how to achieve this from the R side? Created on 2018-04-04 by the reprex package (v0.2.0). |
You can use
You can drop the singleton dimensions on the R side:
I can't quite figure out to invoke the |
I think you can do something like this: x$`__getItem__`(index) |
Thanks @goldingn, just incorporated your updated docs. |
Here is a quick-n-dirty mockup of another approach to enabling the python slicing syntax for tensorflow tensors. The key differences with this approach are
This enables new syntax for tensorflow subsetting in R, namely, something like There are a bunch of loose ends and this is just a conceptual sketch (0-based vs 1-based indexing isn't coherent yet, subsetting by a tensor isn't supported yet, library(tensorflow)
library(reticulate)
d <- c(2, 4, 2); X <- array_reshape(1:prod(d), d)
# some helpers
as_nullable_integer <- keras:::as_nullable_integer
parse1 <- function(...) parse(text = paste0(...), keep.source = FALSE)[[1]]
is_scalar <- function(x) identical(length(x), 1L)
ndots <- function(...) length(eval(substitute(alist(...))))
# python style slicing, mainly for the `step` functionality
py_slice <- function(start = NULL, stop = NULL, step = NULL) {
import_builtins()$slice(
as_nullable_integer(start),
as_nullable_integer(stop),
as_nullable_integer(step))
}
as.py_slice <- function(x) {
if(identical(x, quote(expr=)) || is.null(x)) {
py_slice()
} else if (inherits(x, "python.builtin.slice")) {
x
} else if (is_scalar(x)) {
stopifnot(is.numeric(x))
x <- x - 1L # 1-based indexing -> 0-based indexing
py_slice(x, x + 1L)
} else { # atomic index of length greater than 1
stopifnot(
is.numeric(x), # logical, character subsetting not supported
x[1] > 0, x[length(x)] > 0, # negative indexes not supported
x[1] == min(x), x[length(x)] == max(x),
all(x == x[1]:x[length(x)])) # missing indexes not supported
start <- x[1] - 1L # subtract 1 for 1-based -> 0-based indexing
stop <- x[length(x)] - 1L
py_slice(start, stop)
}
}
`[.tensorflow.tensor` <- function(x, ..., drop = TRUE) {
shp <- x$get_shape()
stopifnot(ndots(...) == py_len(shp))
args <- vector("list", py_len(shp))
for (i in seq_len(py_len(shp)))
args[[i]] <- as.py_slice(eval(parse1("..", i)))
out <- x$`__getitem__`(tuple(args))
if(isTRUE(drop))
out <- tf$squeeze(out)
out
}
sess = tf$Session()
x = tf$convert_to_tensor_or_sparse_tensor(X)
sess$run( x[1, py_slice(,,2), ] )
#> [,1] [,2]
#> [1,] 1 2
#> [2,] 5 6 Created on 2018-04-04 by the reprex package (v0.2.0). |
Thanks @t-kalinowski! Yeah, it would be nice to have strides available in this syntax. That function looks good - it's a bit more verbose than in python, but it's explicit what it does. Another syntax option is to use non-standard evaluation to handle double colon notation when extracting from tensors. I.e. we can make it so that syntax like What do people think? Would this more concise python-like syntax be a nice addition, or would the non-standard evaluation confuse matters? Here's code to mock up the strided indexing on a regular array: parse_strides <- function (x, text, dim = 10) {
chunks <- strsplit(text, ":")[[1]]
if (length(chunks) == 3) {
suppressWarnings(chunks <- vapply(chunks, as.numeric, 1))
# handle missing indices
missing <- is.na(chunks)
if (any(missing)) {
if (missing[1]) {
chunks[1] <- 1
}
if (missing[2]) {
chunks[2] <- dim
}
if (missing[3]) {
chunks[3] <- 1
}
}
x <- seq(from = chunks[1], to = chunks[2], by = chunks[3])
}
x
}
weird_array <- function(data = NA, dim = length(data), dimnames = NULL) {
x <- array(data, dim, dimnames)
class(x) <- c("weird_array", class(x))
x
}
# this demo version is hard-coded to be 3D, and missing indices aren't supported
`[.weird_array` <- function (x, i, j, k) {
dims <- dim(x)
text_i <- deparse(substitute(i))
text_j <- deparse(substitute(j))
text_k <- deparse(substitute(k))
i <- parse_strides(i, text_i, dims[1])
j <- parse_strides(j, text_j, dims[2])
k <- parse_strides(k, text_k, dims[3])
x <- unclass(x)
x[i, j, k]
}
# test run
d <- c(2, 10, 2)
x <- weird_array(seq_len(prod(d)), dim = d)
x[1, 1:8, 2]
x[1, 1:8:2, 2]
x[1, 1:.:2, 2]
x[1, .:.:., 2] |
Thanks @goldingn , If we do decide to go with the more compact syntax, I don't like using I played around with the parser a bit just now, how's this for a compact syntax notation that gets past the R parser: x[ , `::2`, ] or even more simply: x[ , "::2", ] |
@jjallaire I just realized that the format of this section seems to be messed up. Maybe missing a closing bracket or something? |
Thanks, just fixed! (I needed to escape the |
So the syntax options for implementing python-like strided slice indexing via python versionwe can't do this in R because the parser won't allow x[, 1:10:2, ]
x[, ::2, ] R option 1 - a slice functionx[, py_slice(1, 10, 2), ]
x[, py_slice(, , 2), ] R option 2 - quotedthis could alternatively (or also) use backticks: x[, '1:10:2', ]
x[, '::2', ] R option 3 - some character for missing argumentsthe missing value character could be x[, 1:10:2, ]
x[, ?:?:2, ] In all of these, standard increasing indices like I'm torn between making the syntax minimal and close to the python syntax, and reducing the amount of NSE people need to get their head around. What does everyone think? |
I like the backticks for consistency with code people may be porting, but I realize that might seem odd to some. I don't love the |
Is it possible to just use |
That would definitely work for the first of those cases (and perhaps we should enable that too), but it's not so easy for the second case, where the start and end integers don't need to be specified. We could perhaps parse |
I would prefer to avoid NSE in |
actually, nevermind my last comment. There is no reason why |
OK, unless someone else (@goldingn ?) wants to take the lead here, I volunteer to work up a PR later this week/weekend. The general strategy will be similar to the one in the mockup above. ( Some NSE will be used for the case of 3 x[,`::step`,]
x[,start:stop:step,] otherwise, standard evaluation will be used, but we'll accept a sequence with a strided step. x[,start:stop,]
x[,seq(start, stop, by = step),] |
Sounds perfect! I won't have much time for the next couple of weeks, so very happy for you to take the lead! Feel free to tag me if any of the existing code doesn't make sense though. One query - would it be worth putting |
Great. Good point on the clean NSE separation. 👍 |
missing elements in an index seem to be ignored.
Created on 2018-04-02 by the reprex package (v0.2.0).
The text was updated successfully, but these errors were encountered: