Basic tensor manipulation in C++

Hi,
I am trying to learn how to use the C++ API and for that I am porting basic examples from the “Deep Learning with PyTorch” book.

I am stuck with simple things like iterating through the values of a 1-D tensor to put them on a std::vector. I have not been successful in finding any documentation with such examples.

For example, let’s say that I have this:

 auto X = torch::tensor({3.3,4.4,5.5,6.71,6.93,4.168,9.779,6.182,
                          7.59,2.167, 7.042,10.791,5.313,7.997,5.654,9.27,
                          3.1}, 
    torch::requires_grad(false).dtype(torch::kFloat32))
    .view({17,1});

How can I write a loop looking like:

std::vector<float> vec;
for(const auto& element : X) vec.push_back(element);

I guess I have to access the scalar data in X. I have tried storage() or data(), but it does not work.

Is there a place where I could look up some examples?

Thanks.

3 Likes

For the record, this is what I found:

  std::vector<float> xv;
  size_t x_size = x.numel();
  auto p = static_cast<float*>(x.storage().data());
  for(size_t i=0; i<x_size; i++)
  {
    xv.push_back(p[i]);
  }

I am not happy with it. There is maybe a more succinct way?

x.data<float>() perhaps.

Note that you need to care about stride etc. yourself, though.
This does it for you, but you need to know the dimension (here 2):

auto a = x.accessor<float, 2>();
for (int64_t i = 0; i < a.size(0); i++) {
  auto a1 = a[i];
  for (int64_t j = 1; i < a1.size(0); j++) {
    xv.push_back(a1[j]);
  }
}

Best regards

Thomas

1 Like

Nice. Thanks.

Going further, I am not able to update a tensor using its gradient. The following code raises a exception:

float lr = 1e-4;
at::Tensor b = torch::randn({1}, torch::requires_grad(true));
[...]
b -= lr * b.grad();

The exception says:

a leaf Variable that requires grad has been used in an in-place operation.

The python version of the code reads:

b.data -= learning_rate * b.grad.data

I am having lots of trouble finding the correspondence between the 2 API. Is there any documentation that could help me there?

Thanks!

Hi,

Sorry for the late response from my side. @tom already gave a great answer. I just want to add that in many cases, you can convert using just .data<float>(), possibly preceded by a call to .contiguous() if you suspect the tensor is non-contiguous (like after slicing). For example, if you have a simple flat tensor:

at::Tensor my_tensor = torch::randn(5);
std::vector<float> v(my_tensor.data<float>(), my_tensor.data<float>() + my_tensor.numel());

For you second question, the error you get is very much expected. In fact, your C++ code and Python code have a subtle but important difference. Your python code is updating b.data, while your C++ code is updating the variable itself. This would give you the same error in Python:

b -= learning_rate * b.grad

which is what your C++ code is doing. So an easy fix is to write the following in C++:

b.data() -= lr * b.grad().data();

The difference, just like in Python, is that Variables that require gradients may not be updated.

However, both the Python and C++ version using .data() are not quite the most modern/idiomatic PyTorch. When you want to update a variable in place without recording a gradient for this (as, commonly, in optimizer code), you should instead temporarily disable gradient recording. In Python you would write this as:

with torch.no_grad():
    b -= learning_rate * b.grad

and in C++ as

NoGradGuard guard;
b -= lr * b.grad();

Notice that in both cases we need not use .data(), which is much better. Hope this helps.

6 Likes

The .data is tricking you here. I always recommend not using .data.
Looking at the PyTorch C++ api, I think the right thing to do is

    NoGradGuard guard;
    b -= ...

Best regards

Thomas

Race condition with @tom :wink:

Thanks Peter! Good to know I wasn’t totally off. :slight_smile:

1 Like

Hi,

Thanks! That works now. @goldsborough explanation is very helpful.

Any pointers to docs where I could learn how to use the c++ API? I feel like I am just trying random things and I am sure there is a more efficient way! :sweat:

Our docs for now have a reference for the whole API: https://pytorch.org/cppdocs/api/library_root.html

I know we’re still missing an overall tutorial for the C++ frontend. I will write one soon. For now, I would try to look at the implementation and tests for the C++ frontend for examples. For example, check out the implementation of Adam, which uses NoGradGuard in the way I mentioned: https://github.com/goldsborough/pytorch/blob/master/torch/csrc/api/src/optim/adam.cpp#L52

Our integration tests are also good “end-to-end” examples: https://github.com/pytorch/pytorch/blob/master/test/cpp/api/integration.cpp

Let me know if you run into any other issues or would like more help with something.

I’ll have a look at those links. Thanks again for your help.
I can beta test a tutorial if you write one :wink:

In the meantime, thanks to @tom and you, I was able to implement a linear regression example which works and is the equivalent of the python version in the book.

I’ll keep on porting other examples, so I will bother you again :wink:

Cheers

1 Like

can write like this ?:

auto a = x.accessor<float, 2>();
for (int64_t i = 0; i < a.size(0); i++) {
  for (int64_t j = 1; i < a1.size(0); j++) {
    xv.push_back(a[i][j]);
  }
}

Hi Peter,

I know its an old thread, however, I think it might be related to my question. I started a new topic on this a few days ago which is here. I also saw one of your replies on github which was super helpful.

My problem is that when a tensor object is passed to a opencv object and then back which is then used in calculating the loss, it appears that the weights don’t update. Am I missing something?

Thanks for your help.