textCNN在tensorflow上的故事——记一个tf入门者的学习之路

背景

这篇博客主要用来记录一个从不会tensorflow到第一个project(textCNN—中文短文本分类)正式开张的故事,用来与同样刚入门tf的童鞋交流,大神就不必看了

  • 本人有一定机器学习的理论基础,如果你对CNN原理不了解的可以去Coursera上看看Andrew Ng的Machine Learning课程关于NN部分,遇到不懂的百度/Google翻阅博客,基本看一部分再把不懂得琢磨查资料思考后对NN大致会有了解了,然后查查各种博客看看CNN的原理与过程,理论上就大致有个印象了
  • 之前在torch上实现了4层CNN对cifar-10的78%准确率识别,之所以没有直接用VGG或者GoogleNet这种已经有的大佬网络,主要是方便自己感受下CNN的搭建使用以及框架的熟悉。tensorflow这次是第一次用。
  • 本人仍然是在笔记本(mac pro 2015)上进行textCNN代码试运行后再考虑去台式机带GPU上调参,本子cpu跑3s一个batch,台式带gpu(linux系统)1s三个batch,心累。。
  • 主要描述我这种业余选手从不会tf到运行project v1的经历,并不高大上。

tensorflow初次见面

  • 安装:直接去官网,上面有完整指南。个人不建议windows, 在linux、mac、windows都装过,在windows上面第一个程序关于mnist手写识别的就崩了,mac安然无恙。可能是我水平次,但win给我的印象太…
  • 官方tf教程:如果英文看起来吃力,给个传送门http://wiki.jikexueyuan.com/project/tensorflow-zh/get_started/introduction.html,中文版,但是像我这种性格官方版本过于官方不够细致和通俗,显然是看不太下的。
  • 如果官方版本看不下去,有个B站的系列视频做的不错,传送门在此http://www.bilibili.com/video/av6653880/,不过up主貌似是双系统(刚刚被我黑的win…)。
  • 经过理论的饲养(Andrew Ng+各种博客),再加上上述官方文档和视频(不懂再去查资料看博客)的熏陶,基本上对tf这个框架和CNN都有个大致了解了。
  • 接下来进行textCNN对短文本分类,因为不像图形处理那样直接数据就是像素矩阵,所以相对麻烦一些,原始代码是Github上的某位大佬的,表示感谢,帮了我大忙链接在此https://github.com/dennybritz/cnn-text-classification-tf
  • 感觉使用实际代码来操作几波对textCNN和tensorflow熟悉感增加不少,直接看论文,真的看不太动。

正片是啥

对上述Github上代码的做一些注释,帮助类似我的初学者理解,代码可能有不同,请与原来的代码对比,下面会陆续贴部分代码,主要用到了data_helpers.py、text_cnn.py、train.py,第三个可以看做是主函数

data_helpers.py相关解释

这部分主要是定义了两个函数:

(1)第一个是数据载入函数
输入样本文档,输出样本data、样本label的list,这个没啥好说的。不过值得注意的是,这里作者是一次性将所有样本塞进去内存了,存在一个隐患问题,样本大了后会爆内存,别问我为什么会知道。解决方案可能稍后会做一些对比尝试或许更新博客,比如对超大大样本随机抽样(有放回和无放回到底哪样最合适待验证)出100个不爆内存的样本,然后按照随机顺序放进模型训练,训练仍采用minibatch。(为什么使用minibatch训练,我直接给个链接http://hp.stuhome.net/index.php/2016/09/20/tensorflow_batch_minibatch/,其实大家应该都知道原委)

这部分代码请根据自己的样本文档特征修改,我的就不贴了,可能大家的样本情况格式都不一样。

(2)第二个函数为一个batch样本生成器

def batch_iter(data, batch_size, num_epochs, shuffle=True):
    """
    Generates a batch iterator for a dataset.批量数据batchsize生成器
    定义一个函数,输出batch样本,参数为data(包括feature和label),batchsize,epoch
    """
    data = np.array(data)#全部数据转化为array
    data_size = len(data)
    num_batches_per_epoch = int((len(data)-1)/batch_size) + 1#每个epoch有多少个batch,个数
    for epoch in range(num_epochs):
        # Shuffle the data at each epoch
        if shuffle:
            shuffle_indices = np.random.permutation(np.arange(data_size))
            shuffled_data = data[shuffle_indices]# shuffled_data按照上述乱序得到新的样本
        else:
            shuffled_data = data
        for batch_num in range(num_batches_per_epoch):#开始生成batch
            start_index = batch_num * batch_size
            end_index = min((batch_num + 1) * batch_size, data_size)#这里主要是最后一个batch可能不足batchsize的处理
            yield shuffled_data[start_index:end_index]
            #yield,在for循环执行时,每次返回一个batch的data,占用的内存为常数

这里可以看出作者的细心,第一个是end_index = min((batch_num + 1) * batch_size, data_size),考虑最后最后一个batch可能大小不够batchsize了,这在上一篇博客也见到过,第二个是生成器,而不是返回一个包含各个batch样本的list(这一点是否是我多虑了呢,没有仔细观察内存状况对比)

text_cnn.py相关解释

这部分主要是建立了一个text_cnn结构的类
结构比较简单,一个embedding layer+一个convolution layer(Relu)+一个maxpooling层+softmax
主要会引起思考的问题在哪呢,如果你一行行敲代码就会发现:

(1)每个层的参数设置问题
(2)embedding layer和所谓的word2vec是个什么关系

先回答第二个问题,一开始我接触这个project的时候,天真的想,word2vec不就是一个把词的one-hot形式转化为稠密的短向量表示的工具么,用过后一个文本就变成了稠密矩阵,再当图像处理不行么?当然不行!
为什么?

reason one > 如果你仔细看下word2vec的原理,会发现vector其实是一个中间产物,本质上是模型的一部分参数,那意味着什么,不同的训练目标和样本结构得到的最优参数是不一样的,所以不存在固定的vector来表示某个word,具体分析贴一个链接http://spaces.ac.cn/archives/4122/,这是找了数篇资料才发现的解答,贼棒!当然,上面提到的B站up主也有相关解释,也不错。

reason two > 图像处理的卷积核比如3*3,可以横向和纵向两个方向移动,而文本是不行的,因为一个word的表示就是一个横向量,你不能通过原来这种卷积核把一个单词的表示拆开,破坏单词的表示,在这里,一个单词应该和图像的像素点对应即最小单元(但是实际效果我还没去对比过,后续有时间我可能会做以下对比试验),所以卷积核只会在一个维度上移动,比如词的vector为256长度,那么卷积核应该为3*256。

另外注意他的maxpooling参数设置,好好感受下,利用CNN解决文本分类问题的文章还是很多的,比如这篇 A Convolutional Neural Network for Modelling Sentences 最有意思的输入是在 pooling 改成 (dynamic) k-max pooling ,pooling阶段保留 k 个最大的信息,保留了全局的序列信息,效果我暂时还没去尝试,后续有兴趣了再更新吧。

然后回答第一个问题:
我当时用torch的时候没有遇到这种问题,因为torch写CNN太轻松了,一个卷积层你直接add一个卷积模块就行了,但是tensorflow很细节化,需要你写出参数矩阵w、b的tensor规格,所以这里涉及卷积核参数维度、移动维度等问题,这里接着贴一个链接,也是找了一些资料才发现回答的比较好的。http://blog.csdn.net/hymanyoung/article/details/65444288直接看“TensorFlow卷积神经网络实践”后面的部分,比较清楚的说明了各参数的结构

这一部分我的整体加注释的代码,可能不专业,但希望有助于理解。

import tensorflow as tf
import numpy as np


class TextCNN(object):#定义了1个TEXTCNN的类,包含一张大的graph
    """
    A CNN for text classification.
    Uses an embedding layer, followed by a convolutional, max-pooling and softmax layer.
    embedding层,卷积层,池化层,softmax层
    """
    def __init__(
      self, sequence_length, num_classes, vocab_size,
      embedding_size, filter_sizes, num_filters, l2_reg_lambda=0.0):#定义各种输入参数,这里的输入是句子各词的索引?

        # Placeholders for input, output and dropout
        self.input_x = tf.placeholder(tf.int32, [None, sequence_length], name="input_x")
        #定义一个operation,名称input_x,利用参数sequence_length,None表示样本数不定,
        #不一定是一个batchsize,训练的时候是,验证的时候None不是batchsize
        #这是一个placeholder,
        #数据类型int32,(样本数*句子长度)的tensor,每个元素为一个单词
        self.input_y = tf.placeholder(tf.float32, [None, num_classes], name="input_y")
        #这个placeholder的数据输入类型为float,(样本数*类别)的tensor
        self.dropout_keep_prob = tf.placeholder(tf.float32, name="dropout_keep_prob")
        #placeholder表示图的一个操作或者节点,用来喂数据,进行name命名方便可视化

        # Keeping track of l2 regularization loss (optional)
        l2_loss = tf.constant(0.0)
        #l2正则的初始化,有点像sum=0
        #其实softmax是需要的

        # Embedding layer
        #参见
        with tf.device('/cpu:0'), tf.name_scope("embedding"):#封装了一个叫做“embedding'的模块,使用设备cpu,模块里3个operation
            self.W = tf.Variable(
                tf.random_uniform([vocab_size, embedding_size], -1.0, 1.0),
                name="W")#operation1,一个(词典长度*embedsize)tensor,作为W,也就是最后的词向量
            self.embedded_chars = tf.nn.embedding_lookup(self.W, self.input_x)
            #operation2,input_x的tensor维度为[none,seq_len],那么这个操作的输出为none*seq_len*em_size
            self.embedded_chars_expanded = tf.expand_dims(self.embedded_chars, -1)
            #增加一个维度,变成,batch_size*seq_len*em_size*channel(=1)的4维tensor,符合图像的习惯

        # Create a convolution + maxpool layer for each filter size
        pooled_outputs = []#空list
        for i, filter_size in enumerate(filter_sizes):#比如(0,3),(1,4),(2,5)
            with tf.name_scope("conv-maxpool-%s" % filter_size):#循环第一次,建立一个名称为如”conv-ma-3“的模块
                # Convolution Layer
                filter_shape = [filter_size, embedding_size, 1, num_filters]
                #operation1,没名称,卷积核参数,高*宽*通道*卷积个数
                W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name="W")
                #operation2,名称”W“,变量维度filter_shape的tensor
                b = tf.Variable(tf.constant(0.1, shape=[num_filters]), name="b")
                #operation3,名称"b",变量维度卷积核个数的tensor
                conv = tf.nn.conv2d(
                    self.embedded_chars_expanded,
                    W,
                    strides=[1, 1, 1, 1],#样本,height,width,channel移动距离
                    padding="VALID",
                    name="conv")
                #operation4,卷积操作,名称”conv“,与w系数相乘得到一个矩阵
                # Apply nonlinearity
                h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu")
                #operation5,加上偏置,进行relu,名称"relu"
                # Maxpooling over the outputs
                pooled = tf.nn.max_pool(
                    h,
                    ksize=[1, sequence_length - filter_size + 1, 1, 1],
                    strides=[1, 1, 1, 1],
                    padding='VALID',
                    name="pool")
                pooled_outputs.append(pooled)
                #每个卷积核和pool处理一个样本后得到一个值,这里维度如batchsize*1*1*卷积核个数
                #三种卷积核,appen3次

        # Combine all the pooled features
        num_filters_total = num_filters * len(filter_sizes)
        #operation,每种卷积核个数与卷积核种类的积
        self.h_pool = tf.concat(pooled_outputs, 3)
        #operation,将outpus在第4个维度上拼接,如本来是128*1*1*64的结果3个,拼接后为128*1*1*192的tensor
        self.h_pool_flat = tf.reshape(self.h_pool, [-1, num_filters_total])
        #operation,结果reshape为128*192的tensor

        # Add dropout
        with tf.name_scope("dropout"):
            self.h_drop = tf.nn.dropout(self.h_pool_flat, self.dropout_keep_prob)
        #添加一个"dropout"的模块,里面一个操作,输出为dropout过后的128*192的tensor

        # Final (unnormalized) scores and predictions
        with tf.name_scope("output"):#添加一个”output“的模块,多个operation
            W = tf.get_variable(
                "W",
                shape=[num_filters_total, num_classes],
                initializer=tf.contrib.layers.xavier_initializer())
            #operation1,系数tensor,如192*2,192个features分2类,名称为"W",注意这里用的是get_variables
            b = tf.Variable(tf.constant(0.1, shape=[num_classes]), name="b")
            #operation2,偏置tensor,如2,名称"b"
            l2_loss += tf.nn.l2_loss(W)
            #operation3,loss上加入w的l2正则
            l2_loss += tf.nn.l2_loss(b)
            #operation4,loss上加入b的l2正则
            self.scores = tf.nn.xw_plus_b(self.h_drop, W, b, name="scores")
            #operation5,scores计算全连接后的输出,如[0.2,0.7]名称”scores“
            self.predictions = tf.argmax(self.scores, 1, name="predictions")
            #operations,计算预测值,输出最大值的索引,0或者1,名称”predictions“

        # CalculateMean cross-entropy loss
        with tf.name_scope("loss"):#定义一个”loss“的模块
            losses = tf.nn.softmax_cross_entropy_with_logits(logits=self.scores, labels=self.input_y)
            #operation1,定义losses,交叉熵,如果是一个batch,那么是一个长度为batchsize1的tensor?
            self.loss = tf.reduce_mean(losses) + l2_reg_lambda * l2_loss
            #operation2,计算一个batch的平均交叉熵,加上全连接层参数的正则

        # Accuracy
        with tf.name_scope("accuracy"):#定义一个名称”accuracy“的模块
            correct_predictions = tf.equal(self.predictions, tf.argmax(self.input_y, 1))
            #operation1,根据input_y和predictions是否相同,得到一个矩阵batchsize大小的tensor
            self.accuracy = tf.reduce_mean(tf.cast(correct_predictions, "float"), name="accuracy")
            #operation2,计算均值即为准确率,名称”accuracy“

train.py相关解释

就是主函数了,输入样本和超参数,训练网络、存储参数、计算准确率等,带“summary”的函数或者模块是tensorflow储存参数和可视化的工具,我暂时也不怎么熟练,就没怎么写注释,不过不影响功能。

这一节没什么大的引起思考的问题,加了一点点注释和改了一点点参数:

import tensorflow as tf
import numpy as np
import os
import time
import datetime
import data_helpers
from text_cnn import TextCNN
from tensorflow.contrib import learn

# Parameters
# ==================================================

# Data loading params,
#数据集里10%为验证集
tf.flags.DEFINE_float("dev_sample_percentage", .1, "Percentage of the training data to use for validation")
#原数据的文件路径
tf.flags.DEFINE_string("data_file", "/Users/xuhy/Downloads/cnn-text-wujun/data/after_fenci_wujuncnndata", "Data source.")

# Model Hyperparameters
#embedding维度256,4种卷积核,每种128个,0.5的dropout
tf.flags.DEFINE_integer("embedding_dim", 256, "Dimensionality of character embedding (default: 128)")
tf.flags.DEFINE_string("filter_sizes", "2,3,4,5", "Comma-separated filter sizes (default: '2,3,4,5')")
tf.flags.DEFINE_integer("num_filters", 128, "Number of filters per filter size (default: 128)")
tf.flags.DEFINE_float("dropout_keep_prob", 0.5, "Dropout keep probability (default: 0.5)")
tf.flags.DEFINE_float("l2_reg_lambda", 0.0, "L2 regularization lambda (default: 0.0)")

# Training parameters
#batchsize为64,20个epoch,每100个batch后,计算验证集上的表现,每100个batch后保存模型,checkpoint是个啥?
tf.flags.DEFINE_integer("batch_size", 64, "Batch Size (default: 64)")
tf.flags.DEFINE_integer("num_epochs", 20, "Number of training epochs (default: 200)")
tf.flags.DEFINE_integer("evaluate_every", 100, "Evaluate model on dev set after this many steps (default: 100)")
tf.flags.DEFINE_integer("checkpoint_every", 100, "Save model after this many steps (default: 100)")
tf.flags.DEFINE_integer("num_checkpoints", 5, "Number of checkpoints to store (default: 5)")

# Misc Parameters
#true表示自动寻找一个存在并支持的cpu或者gpu,防止指定的设备不存在
#如果将False改为True,可以看到operations被指派到哪个设备运行
tf.flags.DEFINE_boolean("allow_soft_placement", True, "Allow device soft device placement")
tf.flags.DEFINE_boolean("log_device_placement", False, "Log placement of ops on devices")

FLAGS = tf.flags.FLAGS#FLAGS是一个对象,保存了解析后的命令行参数
FLAGS._parse_flags()
print("\nParameters:")
for attr, value in sorted(FLAGS.__flags.items()):
    print("{}={}".format(attr.upper(), value))
print("")


# Data Preparation
# ==================================================

# Load data

print("Loading data...")
print("start_time"+"\t\t"+str(datetime.datetime.now().isoformat()))
x_text, y = data_helpers.load_data_and_labels(FLAGS.data_file)
print("end_time"+"\t\t"+str(datetime.datetime.now().isoformat()))
#这里的y是数值,x还是单词序列
#!!!这里一次性载入所有数据,注意考虑内存,大数据的情况下如何载入需要分析

# Build vocabulary
print("生成单词索引,构成样本索引矩阵...")
print("start_time"+"\t\t"+str(datetime.datetime.now().isoformat()))
max_document_length = 298#每一条评价的最多单词数字
vocab_processor = learn.preprocessing.VocabularyProcessor(max_document_length)
#单词转化为在字典中的位置,这是一个操作
x = np.array(list(vocab_processor.fit_transform(x_text)))
y = np.array(y)
print("end_time"+"\t\t"+str(datetime.datetime.now().isoformat()))
#在不够长度的评价最后加0,样本变成了索引数值矩阵,这里的x已经是索引序列了,n*seq_len的tensor

# Randomly shuffle data
print("打乱样本顺序...")
print("start_time"+"\t\t"+str(datetime.datetime.now().isoformat()))
np.random.seed(10)
shuffle_indices = np.random.permutation(np.arange(len(y)))#打乱样本
x_shuffled = x[shuffle_indices]#新的乱序样本
y_shuffled = y[shuffle_indices]#新的乱序label
print("end_time"+"\t\t"+str(datetime.datetime.now().isoformat()))

# Split train/test set
# TODO: This is very crude, should use cross-validation训练集、验证集划分完毕,全部是索引数值
print("生成训练集和验证集...")
print("start_time"+"\t\t"+str(datetime.datetime.now().isoformat()))
dev_sample_index = -1 * int(FLAGS.dev_sample_percentage * float(len(y)))#负数,倒过来数
x_train, x_dev = x_shuffled[:dev_sample_index], x_shuffled[dev_sample_index:]#切片
y_train, y_dev = y_shuffled[:dev_sample_index], y_shuffled[dev_sample_index:]
print("Vocabulary Size: {:d}".format(len(vocab_processor.vocabulary_)))#字典长度
print("Train/Dev split: {:d}/{:d}".format(len(y_train), len(y_dev)))#训练集和验证集长度
print("end_time"+"\t\t"+str(datetime.datetime.now().isoformat()))

# Training
# ==================================================

with tf.Graph().as_default():
    session_conf = tf.ConfigProto(
      allow_soft_placement=FLAGS.allow_soft_placement,
      log_device_placement=FLAGS.log_device_placement)#这个session配置,按照前面的gpu,cpu自动选择
    sess = tf.Session(config=session_conf)#建立一个配置如上的会话
    with sess.as_default():#在上述session填充内容
        cnn = TextCNN(
            sequence_length=x_train.shape[1],#[0]是样本维度,样本数量,[1]是单个样本的长度
            num_classes=y_train.shape[1],#同理,这里是类别数量
            vocab_size=len(vocab_processor.vocabulary_),#字典长度
            embedding_size=FLAGS.embedding_dim,
            filter_sizes=list(map(int, FLAGS.filter_sizes.split(","))),
            num_filters=FLAGS.num_filters,
            l2_reg_lambda=FLAGS.l2_reg_lambda)  #包含一个CNN
        #TextCNN是一个类,输入参数,得到一个CNN结构

        # Define Training procedure
        global_step = tf.Variable(0, name="global_step", trainable=False)#定义一个变量step
        optimizer = tf.train.AdamOptimizer(1e-3)#里面是学习速率,选择优化算法,建立优化器
        grads_and_vars = optimizer.compute_gradients(cnn.loss)#选择目标函数,计算梯度;返回的是梯度和变量
        #函数minimize() 与compute_gradients()都含有一个参数gate_gradient,用于控制在应用这些梯度时并行化的程度。这里没有?
        train_op = optimizer.apply_gradients(grads_and_vars, global_step=global_step)#运用梯度

中间summary环节我就没怎么写,就不贴了,直接帖后面的。

        # Initialize all variables
        sess.run(tf.global_variables_initializer())#初始化所有变量

        #定义了一个函数,输入为1个batch
        def train_step(x_batch, y_batch):
            """
            A single training step
            """
            feed_dict = {
              cnn.input_x: x_batch,
              cnn.input_y: y_batch,
              cnn.dropout_keep_prob: FLAGS.dropout_keep_prob
            }
            _, step, summaries, loss, accuracy = sess.run(
                [train_op, global_step, train_summary_op, cnn.loss, cnn.accuracy],
                feed_dict)
            #梯度更新(更新模型),步骤加一,存储数据,计算一个batch的损失,计算一个batch的准确率
            time_str = datetime.datetime.now().isoformat()#当时时间
            print("{}: step {}, loss {:g}, acc {:g}".format(time_str, step, loss, accuracy))
            train_summary_writer.add_summary(summaries, step)

        #定义了一个函数,用于验证集,输入为一个batch
        def dev_step(x_batch, y_batch, writer=None):
            """
            Evaluates model on a dev set
            """
            #验证集太大,会爆内存,采用batch的思想进行计算,下面生成多个子验证集
            num=20
            x_batch=x_batch.tolist()
            y_batch=y_batch.tolist()
            l=len(y_batch)
            l_20=int(l/num)
            x_set=[]
            y_set=[]
            for i in range(num-1):
                x_temp=x_batch[i*l_20:(i+1)*l_20]
                x_set.append(x_temp)
                y_temp=y_batch[i*l_20:(i+1)*l_20]
                y_set.append(y_temp)
            x_temp=x_batch[(num-1)*l_20:]
            x_set.append(x_temp)
            y_temp=y_batch[(num-1)*l_20:]
            y_set.append(y_temp)

            #每个batch验证集计算一下准确率,num个batch再平均
            lis_loss=[]
            lis_accu=[]
            for i in range(num):    
                feed_dict = {
                cnn.input_x: np.array(x_set[i]),
                cnn.input_y: np.array(y_set[i]),
                cnn.dropout_keep_prob: 1.0
                }
                step, summaries, loss, accuracy = sess.run(
                    [global_step, dev_summary_op, cnn.loss, cnn.accuracy],
                    feed_dict)
                lis_loss.append(loss)
                lis_accu.append(accuracy)
                time_str = datetime.datetime.now().isoformat()
                print("{}: step {}, loss {:g}, acc {:g}".format(time_str, step, loss, accuracy))
            print("test_loss and test_acc"+"\t\t"+str(sum(lis_loss)/num)+"\t\t"+str(sum(lis_accu)/num))
            if writer:
                writer.add_summary(summaries, step)


        # Generate batches(生成器),得到一个generator,每一次返回一个batch,没有构成list[batch1,batch2,batch3,...]
        batches = data_helpers.batch_iter(
            list(zip(x_train, y_train)), FLAGS.batch_size, FLAGS.num_epochs)
        #zip将样本与label配对,
        # Training loop. For each batch...
        for batch in batches:
            x_batch, y_batch = zip(*batch)#unzip,将配对的样本,分离出来data和label
            train_step(x_batch, y_batch)#训练,输入batch样本,更新模型
            current_step = tf.train.global_step(sess, global_step)
            if current_step % FLAGS.evaluate_every == 0:#每多少步,算一下验证集效果
                print("\nEvaluation:")
                dev_step(x_dev, y_dev, writer=dev_summary_writer)#喂的数据为验证集,此时大小不止一个batchsize1的大小
                print("")
            if current_step % FLAGS.checkpoint_every == 0:#每多少步,保存模型
                path = saver.save(sess, checkpoint_prefix, global_step=current_step)
                print("Saved model checkpoint to {}\n".format(path))

这里当时遇到了一个问题,就是每次验证集计算的时候,内存就爆了,然后我就采用minibatch的想法改进了以下,没毛病了。

最后贴一个效果

初级版本,后续会尽量优化
目前,23w+样本,类别25+,下面是训练不到2个epoch的情况,测试集准确率86.17%:

test_loss and test_acc 0.475186523795 0.861730447412

Saved model checkpoint to /Users/xuhy/runs/1498792747/checkpoints/model-6800

后面有空更。
谢谢!

  • 13
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值