pytorch --词相似度计算

Pytorch的执行流程

Pytorch是基于动态图搭建的深度学习框架,在执行过程中,主要依照计算图。因此编写pytorch程序主要是写好以下几步。

  • 准备数据集(数据预处理)
  • 创建模型,搭建计算图(继承nn.module)
  • 定义loss和optimizer
  • 循环epoch(forward、backward、update)

Pytorch与Tensorflow

和1.x版本的Tensorflow不同的是,Pytorch通过backward函数完成自动求导,结束后自动销毁计算图。而Tensorflow静态图版本则是通过创建session,在内部完成计算,也就是说,静态图方法在session外部的Tensor操作是没有用的!这也就给debuger带来了不小的烦恼。py2.jpg
而且还是要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!

参考文献