Pytorch的执行流程
Pytorch是基于动态图搭建的深度学习框架,在执行过程中,主要依照计算图。因此编写pytorch程序主要是写好以下几步。
- 准备数据集(数据预处理)
- 创建模型,搭建计算图(继承nn.module)
- 定义loss和optimizer
- 循环epoch(forward、backward、update)
Pytorch与Tensorflow
和1.x版本的Tensorflow不同的是,Pytorch通过backward函数完成自动求导,结束后自动销毁计算图。而Tensorflow静态图版本则是通过创建session,在内部完成计算,也就是说,静态图方法在session外部的Tensor操作是没有用的!这也就给debuger带来了不小的烦恼。
而且还是要diss一下Tensorflow1.x的官方文档,同一个功能函数居然在不同的模块下有不同的实现方法,而且区别是真的不大(也没有附加文档说明)。相比pytorch的文档,看起来还是比较舒服滴~
Pytorch c++的基本知识
Tensor作为pytorch框架的基本类型,网上关于python版本的说明文档已经足够多了,但是c++版本还是比较少,下面写一些经验和采坑实录。
关于创建Tensor
- 每个Tensor会有data和grad两个“内存区”,data存放数据,grad存放反向传播的计算梯度。其中Tensor类型都可以通过.item()转化成标量,注意如果需要从Tensor获取特定类型标量最好用
tensor.item\<Type>()
- Tensor存放的基本数据类型:kUInt8, kInt8, kInt16, kInt32, kInt64, kFloat32 and kFloat64。值得注意的是,不同的dtype不能混合运算(kFloat32和kFloat64就是个坑...),Tensor内数据类型转换可以用.float()这些投射机制实现[1]。
- Tensor的存放位置:一般是kcpu或者kcuda,可以通过tensor.device()查看。如果需要通过cpu转存cuda可以通过以下两种方法实现转换。
tensor.to(device) / tensor.cuda()
GPU上计算的Tensor结果一般会存放在GPU上,但是还是要注意中间变量在创建的时候要在同一个设备上计算,否则可能会产生错误:
Expected object of device type cuda but got device type cpu
- 可以通过requires_grad设置是否求导。
- Tensor可以通过tensor.sizes()查看维度,在debug的时候往往是不错的办法。返回类型IntArrayInf。
- c++ vector也可以转成Tensor类型,通过调用函数
Tensor from_blob(void *data, IntArrayRef sizes, IntArrayRef strides, const TensorOptions &options = {})
注意data是一维指针,需要对高维度的vector先做flatten。
关于模型
- 包含构造函数(__init__ python版本)和forward等函数。TORCH_MODULE(model_name)起别名可以创建对象指针。
- 可以将模型转到GPU上,通过以下方法转移到GPU。
model->to(device)
必须注意中间的变量是否转移到GPU(例如Embedding层的pretrained_weight)否则出现cuda-cpu数据位置的错误要注意检查:
- 模型是否都在cuda上。
- 中间变量是否转移到了cuda。
- batch_size是否合理(cuda memory exceed)
词向量余弦相似度计算
- embedding:glove6B.300d.txt
- pytorch c++
首先,按照C++的套路,name.h定义类,name.cpp对函数实现。
Similarity.h
class SimilarityModelImpl : public torch::nn::Module {
public:
int64_t topk; // num of top words;
Dictionary dict; //自定义字典类
int64_t vocab_size;
int64_t embedding_dim;
torch::nn::Embedding embedding{nullptr};
vector<vector<float> > vec_embed;
SimilarityModelImpl(unordered_map<string, string> args, int64_t vocab_size, int64_t embed_dim, torch::Device device);
tuple<torch::Tensor, torch::Tensor> forward(torch::Tensor x, torch::Device device);
};
TORCH_MODULE(SimilarityModel);
Similarity.cpp
SimilarityModelImpl::SimilarityModelImpl(unordered_map<string, string> args, int64_t vocab_size, int64_t embed_dim, torch::Device device)
:embedding(vocab_size, embed_dim) { //实例化Embedding,试了很多办法但还是在这里实例化有效
this->topk = stoi(args["topk"]);
vector<vector<float> > pre_embed;
tie(pre_embed, dict) = loadwordvec(args); //从文件读取pretrained_embedding
this->vocab_size = int64_t(dict.size());
this->embedding_dim = int64_t(pre_embed[0].size());
this->vec_embed = pre_embed;
this->dict = dict;
vector<float> temp_embed;
for(const auto& i : pre_embed) //flatten to 1-d
for(const auto& j : i)
temp_embed.push_back(j);
torch::Tensor data = torch::from_blob(temp_embed.data(), {this->vocab_size, this->embedding_dim}, torch::TensorOptions().dtype(torch::kFloat32)).clone(); //vector2Tensor
data = data.to(device);
register_module("embedding", embedding); //embedding
this->embedding = embedding.from_pretrained(data, torch::nn::EmbeddingFromPretrainedOptions().freeze(true)); //加载pretrained_weight,我在这坑呆了好久hhh,data没转到gpu上...
}
tuple<torch::Tensor, torch::Tensor> SimilarityModelImpl::forward(torch::Tensor x, torch::Device device) { //结果转到cpu方便后续操作
torch::Tensor wordvec;
wordvec = this->embedding->forward(x); //获得pretrained_embedding
torch::Tensor similarity_score = wordvec.matmul(this->embedding->weight.transpose(0, 1));
torch::Tensor score, indice;
tie(score, indice) = similarity_score.topk(this->topk+1, 1, true, true);
score = score.slice(1, 1, score.size(1)); //切片
Tensor.slice(int64_t dim, int64_t start, int64_t end, int64_t step)
indice = indice.slice(1, 1, indice.size(1));
return {score.cpu(), indice.cpu()};
}
main.cpp
int main()
{
std::unordered_map<std::string, std::string> args;
args.insert(std::make_pair<std::string, std::string>("topk", "100"));
args.insert(std::make_pair<std::string, std::string>("wordvec_file", "/home/switchsyj/Downloads/glove.6B.300d.txt"));
args.insert(std::make_pair<std::string, std::string>("batch_size", "4")); //cuda out of memory issue
args.insert(std::make_pair<std::string, std::string>("print_result", "true"));
auto cuda_available = torch::cuda::is_available();
torch::Device device(cuda_available ? torch::kCUDA : torch::kCPU);
std::cout << (cuda_available ? "CUDA available. Training on GPU." : "Training on CPU.") << '\n';
SimilarityModel simiModel(args, 400000, 300, device);
simiModel->to(device);
torch::NoGradGuard no_grad; //计算图不求导
int64_t batch_size = stoi(args["batch_size"]);
int64_t num_batches = simiModel->vocab_size / batch_size;
vector<int64_t> temp_embed; //flatten word index
for(unsigned int i = 0; i < simiModel->dict.size(); i++)
temp_embed.push_back(int64_t(i));
torch::Tensor ids = torch::from_blob(temp_embed.data(), {simiModel->vocab_size}, torch::TensorOptions().dtype(torch::kInt64)).clone(); //vector2Tensor
cout << "start training!" << endl;
torch::Tensor score, indice;
for(int64_t i = 0; i < num_batches; i++) { //batch+forward
torch::Tensor data = ids.index({Slice(i*batch_size, (i+1)*batch_size)}).to(torch::kInt64).to(device); //slice
tie(score, indice) = simiModel->forward(data, device);
if(args["print_result"] == "true") {
print_result(args, score, indice, simiModel->dict, i); //打印结果
}
}
if(batch_size * num_batches < simiModel->vocab_size) { //最后一个batch
torch::Tensor data = ids.index({Slice(num_batches*batch_size, NULL)}).to(torch::kInt64).to(device);
tie(score, indice) = simiModel->forward(data, device);
if(args["print_result"] == "true") {
print_result(args, score, indice, simiModel->dict, num_batches+1);
}
}
return 0;
}
经验总结
- C++API的报错机制有时候看得云里雾里,需要通过报错位置查看源码,多看源码总是对的!
- python中的( . )操作往往对应c++版本的( :: )操作。
- stackoverflow YES!