【文本分类】TextCNN的实现
模型

论文可以在这里找到Convolutional Neural Networks for Sentence Classification
后面的实现参考文本分类实战(二)—— textCNN 模型
因为用的自己的数据集,所以在此基础上做了一点点改动
代码
import os
import csv
#import time
#import datatime
import random
import json
from collections import Counter
from math import sqrt
import gensim
import pandas as pd
import numpy as np
import tensorflow as tf
#from sklearn.metrics import roc_auc_score,accuray_score,precision,recall_score #导入评价指标

参数设置
#配置参数
#训练参数
class TrainingConfig(object):
epoches=10
evaluateEvery=1000
checkpointEvery=1000
learningRate=0.001
#模型参数
class ModelConfig(object):
embeddingSize=300
numFilters=300
filterSizes=[2,3,4,5]
dropoutKeepProb=0.5
l2RegLambda=0.0
class Config(object):
sequenceLength=100
batchSize=16
trainSource='./data/train_data.csv'
devSource='./data/val_data.csv'
testSource='./data/test_data.csv'
stopWordSource='./data/stopsword.txt'
numClasses=44
rate=0.8
training=TrainingConfig()
model=ModelConfig()
#实例化对象
config=Config()

取数据的方式
我理解这部分,就是你如何从自己的数据读出数据,并处理成你模型能接受的样子,简单来说,就是你处于数据的类。
我的数据是这样的清洗后是这样的,一共是44个类别,属于多分类问题

class Dataset(object):
def __init__(self, config):
self.config = config
self._trainSource = config.trainSource
self._devSource = config.devSource
self._testSource = config.testSource
self._stopWordSource = config.stopWordSource
self._sequenceLength = config.sequenceLength
self._embeddingSize = config.model.embeddingSize
self._batchSize = config.batchSize
self._rate = config.rate
self._stopWordDict = {}
self.trainSentences = []
self.trainLabels = []
self.evalSentences = []
self.evalLabels = []
self.wordEmbedding = None
self.labelList = []
def _readData(self,filePath):
#读数据的方法
lines=open(filePath,encoding='utf-8').read().split('\n')
sentences=''
labels=''
for i in range(len(lines)-1):
item=lines[i].split('\t')
labels+=str(item[0])
labels+='\n'
sentences+=str(item[1])
sentences+='\n'
labels = labels.strip().split('\n')
sentences = sentences.strip().split('\n')
return sentences,labels
def _labelToIndex(self,labels,label2idx):
#将标签转化成索引
labelIds=[label2idx[label] for label in labels]
return labelIds
def _wordToIndex(self,sentences,word2idx):
#将词转换成索引
sentenceIds=[[word2idx.get(item, word2idx["UNK"]) for item in sentence] for sentence in sentences]
return sentenceIds
def _dealDate(self,x,y,word2idx):
#补全或者截取句子的长度
sentences=[]
for sentence in x:
if len(sentence) >= self._sequenceLength:
sentences.append(sentence[:self._sequenceLength]) #截断过长的
else:
sentences.append(sentence+[word2idx["PAD"]]*(self._sequenceLength - len(sentence))) #补长
Sentences=np.array(sentences,dtype="int64")
Labels = np.array(y, dtype="float32")
return Sentences, Labels
def _genVocabulary(self,sentences,labels):
#生成词向量 词汇-索引映射字典
allWords = [word for sentence in sentences for word in sentence]
#取掉停用词
subWords = [word for word in allWords if word not in self.stopWordDict]
wordCount = Counter(subWords) # 统计词频
sortWordCount = sorted(wordCount.items(), key=lambda x: x[1], reverse=True)
#按照词频数排序
# 去除低频词
words = [item[0] for item in sortWordCount if item[1] >= 5]
vocab, wordEmbedding = self._getWordEmbedding(words)
self.wordEmbedding = wordEmbedding #word-embedding的映射
word2idx= dict(zip(vocab, list(range(len(vocab))))) #词表 word-id的映射
uniqueLabel = list(set(labels))
#print(uniqueLabel)
label2idx = dict(zip(uniqueLabel, list(range(len(uniqueLabel))))) #标签表 label-id的映射
self.labelList = list(range(len(uniqueLabel)))
# 将词汇-索引映射表保存为json数据,之后做inference时直接加载来处理数据
with open("./data/word2idx.json", "w", encoding="utf-8") as f:
json.dump(word2idx, f)
with open("./data/label2idx.json", "w", encoding="utf-8") as f:
json.dump(label2idx, f)
return word2idx, label2idx
def _getWordEmbedding(self,words):
#按照我们数据集建立的词表从训练好的word2vec提取出词向量
vocab=[]
wordEmbedding=[]
# 添加 "pad" 和 "UNK",
vocab.append("PAD")
vocab.append("UNK")
wordEmbedding.append(np.zeros(self._embeddingSize))
wordEmbedding.append(np.random.randn(self._embeddingSize))
#加载之前下载训练好的词向量表
wordVec = gensim.models.KeyedVectors.load_word2vec_format("./data/sgns.wiki.word", binary=False)
for word in words:
try:
vector = wordVec.wv[word]
vocab.append(word)
wordEmbedding.append(vector)
except:
print(word + "不存在于词向量中")
return vocab, np.array(wordEmbedding)
def _readStopWord(self, stopWordPath):
"""
读取停用词
"""
with open(stopWordPath, "r") as f:
stopWords = f.read()
stopWordList = stopWords.splitlines()
# 将停用词用列表的形式生成,之后查找停用词时会比较快
self.stopWordDict = dict(zip(stopWordList, list(range(len(stopWordList)))))
def dataGen(self):
#初始化
#加载停用词
self._readStopWord(self._stopWordSource)
# 初始化数据集
trainSentences1, trainLabels1=self._readData(self._trainSource)
evalSentences1, evalLabels1=self._readData((self._devSource))
sentences=trainSentences1+evalSentences1
labels=trainLabels1+evalLabels1
#print(type(labels))
# 初始化词汇-索引映射表和词向量矩阵
word2idx, label2idx = self._genVocabulary(sentences, labels)
# 将训练集和验证集标签和句子数值化
trainlabelIds = self._labelToIndex(trainLabels1, label2idx)
trainsentenceIds = self._wordToIndex(trainSentences1, word2idx)
evallabelIds = self._labelToIndex(evalLabels1, label2idx)
evalsentenceIds = self._wordToIndex(evalSentences1, word2idx)
# 补全或截取训练集和测试集
trainSentences, trainLabels= self._dealDate(trainsentenceIds, trainlabelIds, word2idx)
evalSentences, evalLabels = self._dealDate(evalsentenceIds, evallabelIds, word2idx)
self.trainSentences = trainSentences
self.trainLabels = trainLabels
可以打印几条看看结果
self.evalSentences = evalSentences
self.evalLabels = evalLabels
#print(evalLabels)
#print(evalSentences[1])
print(evalSentences[0])
print(evalLabels[0])

data = Dataset(config)
data.dataGen()
这里是用的训练好的word2vec的词向量
后,生成word2id.json和label2id.json文件

生成输入模型的batch数据
#生成batch数据集,用生成器的方式输出
#采用生成器的形式向模型输入batch数据集,(生成器可以避免将所有的数据加入到内存中)
def nextBatch(x,y,batchSize):
prem=np.arange(len(x))
np.random.shuffle(prem) #打乱顺序
#打乱数据集,若是同一个batch反复出现。模型可能会学习到,所以要打乱
x=x[prem]
y=y[prem]
numBatches = len(x) // batchSize
#整除
for i in range(numBatches):#遍历num batch
start=i*batchSize
end=start+batchSize
batchX=np.array(x[start:end],dtype='int64')
batchY = np.array(y[start: end], dtype="float32")
yield batchX, batchY

【注意】 这里有一步是需要打乱训练集中的数据,因为若是同一个batch反复出现。模型可能会学习到,所以要打乱。
模型结构
#构建模型
class TextCNN(object):
#用于文本分类
def __init__(self,config,wordEmbedding):
#定义模型的输入
self.inputX=tf.placeholder(tf.int32,[None,config.sequenceLength],name='inputX')
self.inputY=tf.placeholder(tf.int32,[None],name='inputY')
self.dropoutKeepProb=tf.placeholder(tf.float32,name='dropoutKeepProb')
#定义l2损失,正则项
l2Loss=tf.constant(0.0)
#词嵌入层
with tf.name_scope('embedding'):
"""
tf.name_scope()命名空间的实际作用
在某个tf.name_scope()指定的区域中定义的所有对象及各种操作,
他们的“name”属性上会增加该命名区的区域名,用以区别对象属于哪个区域
"""
#利用预训练的词向量初始化词嵌入矩阵
self.W=tf.Variable(tf.cast(wordEmbedding,dtype=tf.float32,name='word2vec'),name='W')
#tf.cast数据类型转换
#利用词嵌入矩阵将输入的数据中的词转换成词向量,维度[batch_size, sequence_length, embedding_size]
self.embeddedWords=tf.nn.embedding_lookup(self.W,self.inputX)
#卷积的输入是思维[batch_size, width, height, channel],因此需要增加维度,用tf.expand_dims来增大维度
#增加了最后一维[batch_size, seq_len, embedding_size, channel]
self.embeddedWordsExpanded=tf.expand_dims(self.embeddedWords,-1)
#创建卷积和池化
pooledOutputs=[]
#有三种size的filter 2,3, 4, 5,textCNN是个多通道单层卷积的模型,可以看作三个单层的卷积模型的融合
for i,filterSize in enumerate(config.model.filterSizes):
with tf.name_scope('conv-maxpool-%s'%filterSize):
#卷积层,卷积核尺寸为filterSize * embeddingSize,卷积核的个数为numFilters
#初始化权重矩阵和偏置
filterShape=[filterSize,config.model.embeddingSize,1,config.model.numFilters]
#tf.truncated_normal()截断的产生正态分布的随机数,即随机数与均值的差值若大于两倍的标准差,则重新生成
W=tf.Variable(tf.truncated_normal(filterShape,stddev=0.1),name='W')
b=tf.Variable(tf.constant(0.1,shape=[config.model.numFilters]),name='b')
conv=tf.nn.conv2d(
self.embeddedWordsExpanded,
W,
#CNN中的卷积核,它要求是一个Tensor,
#具有[filter_height, filter_width, in_channels, out_channels]这样的shape,
# 具体含义是[卷积核的高度,卷积核的宽度,图像通道数,卷积核个数],
# 要求类型与参数input相同,有一个地方需要注意,第三维in_channels,就是参数input的第四维
strides=[1,1,1,1],
#卷积时在图像每一维的步长,这是一个一维的向量,长度4
padding='VALID',
name='conv'
)
#relu函数
h=tf.nn.relu(tf.nn.bias_add(conv,b),name='relu')
#池化层,最大池化,池化是卷积后的序列取一个最大值
pooled=tf.nn.max_pool(
h,
ksize=[1,config.sequenceLength-filterSize+1,1,1],
#池化窗口的大小,取一个四维向量,一般是[1, height, width, 1],
# 因为我们不想在batch和channels上做池化,所以这两个维度设为了1
strides=[1,1,1,1],
padding='VALID',
name='pool'
)
#将四种size的filter的输出一起加到列表中
pooledOutputs.append(pooled)
#得到cnn网络的输出长度
numFiltersTotal=config.model.numFilters*len(config.model.filterSizes) #总卷积核数目
#池化后的维度不变,按照最后的维度channel来concat
self.hPool=tf.concat(pooledOutputs,3)
#摊平成二维的数据输入到全连接层
self.hPoolFlat=tf.reshape(self.hPool,[-1,numFiltersTotal])
#dropout
with tf.name_scope('dropout'):
self.hDrop=tf.nn.dropout(self.hPoolFlat,self.dropoutKeepProb)
#全连接层的输出
with tf.name_scope('output'):
outputW=tf.get_variable(
'outputW',
shape=[numFiltersTotal,config.numClasses],
initializer=tf.contrib.layers.xavier_initializer()
##用于初始化权重的初始化程序 “Xavier” 。这个初始化器是用来保持每一层的梯度大小都差不多相同。
)
outputB=tf.Variable(tf.constant(0.1,shape=[config.numClasses]),name='outputB')
l2Loss+=tf.nn.l2_loss(outputW)
l2Loss+=tf.nn.l2_loss(outputB)
self.logits=tf.nn.xw_plus_b(self.hDrop,outputW,outputB,name='logits')
#shape=[-1,numFiltersTotal][numFiltersTotal,config.numClasses]=[-1,numClasses]
#多分类用softmax
self.pro=tf.nn.softmax(self.logits)
self.predictions=tf.argmax(self.pro,axis=-1,name='predictions')
#返回最大的那个数值所在的下标,predictions返回的是最大概率所属于的标签ID
print(self.predictions)
create__file('predictions',self.predictions)
#把prediction保存在文档中
#os.makedirs('./result/predictions.txt')
#with open('./result/predictions.txt', "w", encoding="utf-8")as w:
#content=''
#for prediction in predictions:# 把预测的答案写入
#content+=str(prediction)
#content+='\n'
#w.write(str(content))
#w.close()
with tf.name_scope('loss'):
losses=tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.logits,labels=self.inputY)
#计算交叉熵 原因是logits和labels在使用时有labels应该少一维的限制。
#logits应该是batch×classes的一个矩阵,classes为类别数量labels应该是长batch的一个数组
self.loss=tf.reduce_mean(losses)+config.model.l2RegLambda*l2Loss

【补上】:在动手的时候,有很多疑惑,找了一些解答得非常清晰明了的博客解答
交叉熵函数的选择:Tensorflow入门——分类问题cross_entropy的选择
损失函数的计算:损失函数 - 交叉熵损失函数
为什么要加上weight_decay,什么情况用:l2RegLambda的经验设定
初始参数会决定模型训练的好坏,很重要:tensorflow的几种参数初始化方法
Xavier:牛逼的初始化xavier(tensorflow)
变量作用域:TENSORFLOW变量作用域(VARIABLE SCOPE)
评价指标
这部分没什么好说的
#定义各类性能指标
def mean(item:list) -> float:
"""
计算列表中元素的平均值
:param item: 列表对象
:return:
"""
res=sum(item)/len(item) if len(item)>0 else 0
return res
def accuracy(pred_y,true_y):
"""
计算二分类或多分类的准确率
:param pred_y: 预测结果
:param true_y: 真实结果
:return:
"""
if isinstance(pred_y[0],list):
pred_y=[item[0] for item in pred_y]
corr=0
for i in range(len(pred_y)):
if pred_y[i]==true_y[i]:
corr+=1
acc=corr/len(pred_y) if len(pred_y)>0 else 0
return acc
def binary_precision(pred_y,true_y,positive=1):
"""
二分类的精确率计算
对于预测为正的样本中有多少是真正的正例
:param pre_y: 预测结果
:param true_y:真实的结果
:param positive: 正例的索引表示
:return:
"""
corr=0
pred_corr=0
for i in range(len(pred_y)):
if pred_y[i]==positive:
pred_corr+=1
if pred_y[i]==true_y[i]:
corr+=1
prec=corr/pred_corr if pred_corr>0 else 0
return prec
def binary_recall(pred_y,true_y,positive=1):
"""
二分类的召回率
样本中的正例有多少是预测正确的
:param pred_y: 预测结果
:param true_y: 真实结果
:param positive:正例的索引表示
:return:
"""
corr=0
true_corr=0
for i in range(len(pred_y)):
if true_y[i]==positive:
true_corr+=1
if pred_y[i]==true_y[i]:
corr+=1
rec=corr/true_corr if true_corr>0 else 0
return rec
def binary_f_beta(pred_y,true_y,beta=1.0,positive=1):
"""
二分类的F1值
:param pred_y: 预测结果
:param true_y: 真实结果
:param beta: beta值
:param positive: 正例的索引值
:return:
"""
precision=binary_precision(pred_y,true_y,positive)
recall=binary_recall(pred_y,true_y,positive)
try:
f_b=(1+beta*beta)*precision*recall/(beta*beta*precision+recall)
#beta=0 计算的是f1分数
#beta=1,F2分数认为召回率的重要程度是精确率的2倍
except:
f_b=0
return f_b
def multi_precision(pred_y,true_y,labels):
"""
多类的精确率
对于预测为正的样本中有多少是真正的正例
:param pre_y: 预测结果
:param true_y: 真实标签
:param labels: 标签列表
:return:
"""
if isinstance(pred_y[0],list):
pred_y=[item[0] for item in pred_y]
precisions=[binary_precision(pred_y,true_y,label) for label in labels]
#把每个类别作为正例,其他作为负例计算,放进列表中
prec=mean(precisions)
return prec
def multi_recall(pred_y,true_y,labels):
"""
多分类的召回率
对数据中正例有多少被预测正确
:param pre_y: 预测结果
:param true_y: 真实结果
:param labels: 标签列表
:return:
"""
if isinstance(pred_y[0],list):
pred_y=[item[0] for item in pred_y]
recalls=[binary_recall(pred_y,true_y,label) for label in labels]
rec=mean(recalls)
return rec
def multi_f_beta(pred_y,true_y,labels,beta=1.0):
"""
多分类的f值
:param pred_y: 预测结果
:param true_y: 真实结果
:param labels: 标签列表
:param beta: beta值
:return:
"""
if isinstance(pred_y[0],list):
pred_y=[item[0] for item in pred_y]
f_betas = [binary_f_beta(pred_y, true_y, beta, label) for label in labels]
f_beta = mean(f_betas)
return f_beta
def get_binary_metrics(pred_y,true_y,f_beta=1.0):
"""
得到二分类的性能指标
:param pred_y:
:param true_y:
:param f_beta:
:return:
"""
acc = accuracy(pred_y, true_y)
recall = binary_recall(pred_y, true_y)
precision = binary_precision(pred_y, true_y)
f_beta = binary_f_beta(pred_y, true_y, f_beta)
return acc, recall, precision, f_beta
def get_multi_metrics(pred_y,true_y,labels,f_beta=1.0):
"""
多分类的性能指标
:param pred_y:
:param true_y:
:param labels:
:param f_beta:
:return:
"""
acc = accuracy(pred_y, true_y)
recall = multi_recall(pred_y, true_y, labels)
precision = multi_precision(pred_y, true_y, labels)
f_beta = multi_f_beta(pred_y, true_y, labels, f_beta)
return acc, recall, precision, f_beta

训练模型
#训练模型
#生成训练集和验证集
trainSentences=data.trainSentences
trainLabels=data.trainLabels
evalSentences=data.evalSentences
evalLabels=data.evalLabels
wordEmbedding=data.wordEmbedding
labelList=data.labelList
#定义图计算
with tf.Graph().as_default():
#使用tf.ConfigProto()配置Session参数
session_conf=tf.ConfigProto(allow_soft_placement=True,log_device_placement=False)
#是配置tf.Session的运算方式,比如gpu运算或者cpu运算
#log_device_placement=True 设置为True时,会打印出TensorFlow使用了那种操作
session_conf.gpu_options.allow_growth=True #动态申请显存
session_conf.gpu_options.per_process_gpu_memory_fraction=0.9 #限制GPU的使用率
sess=tf.Session(config=session_conf)
#定义会话
with sess.as_default():
cnn=TextCNN(config,wordEmbedding)
#权重优化,也就是更新的步数
globalStep=tf.Variable(0,name='gloabalStep',trainable=False)
#定义优化器,传入学习速率参数
optimizer=tf.train.AdamOptimizer(config.training.learningRate)
#计算梯度,得到梯度和变量
gradsAndVars=optimizer.compute_gradients(cnn.loss)
#返回的是元组列表[(gradient,variable),…]
#将梯度应用在变量下,生成训练器
trainOp=optimizer.apply_gradients(gradsAndVars,global_step=globalStep)
#用summary绘制tensorBoard
gradSummaries=[]
for g,v in gradsAndVars:
if g is not None:
tf.summary.histogram('{}/grad/his'.format(v.name),g) #用来显示直方图信息
tf.summary.scalar('{}/grad/sparsity'.format(v.name),tf.nn.zero_fraction((g)))#用来显示标量信息
outDir=os.path.abspath(os.path.join(os.path.curdir,'summarys'))
#返回绝对路径
print("Writing to {}\n".format(outDir))
lossSummary=tf.summary.scalar('loss',cnn.loss)
summaryOp=tf.summary.merge_all()
trainSummaryDir = os.path.join(outDir, "train")
trainSummaryWriter = tf.summary.FileWriter(trainSummaryDir, sess.graph)
evalSummaryDir = os.path.join(outDir, "eval")
evalSummaryWriter = tf.summary.FileWriter(evalSummaryDir, sess.graph)
#初始化所有变量
#保存模型的对象saver
saver=tf.train.Saver(tf.global_variables(),max_to_keep=5) #max_to_keep=5,保存最近的5个模型
#保存模型的一种方式,保存为pb文件
savedModelPath='./model/textCNN/savedModel'
if os.path.exists(savedModelPath):
os.rmdir(savedModelPath)
builder=tf.saved_model.builder.SavedModelBuilder(savedModelPath)
sess.run(tf.global_variables_initializer())
#global_variables_initializer 返回一个用来初始化 计算图中所有global variable的 op
def trainStep(batchX,batchY):
"""
训练函数 定义1batch
"""
feed_dict={
cnn.inputX:batchX,
cnn.inputY:batchY,
cnn.dropoutKeepProb:config.model.dropoutKeepProb
}
_,summary,step,loss,predictions=sess.run(
[trainOp,summaryOp,globalStep,cnn.loss,cnn.predictions],
feed_dict
)
"""
sess每执行完一个batch,就给global_step加1,用来统计目前执行的batch数
"""
timeStr=datetime.datetime.now().isoformat()
acc,recall,prec,f_beta=get_multi_metrics(pred_y=predictions,true_y=batchY,labels=labelList)
trainSummaryWriter.add_summary(summary,step)
return loss,acc,prec,recall,f_beta
def devStep(batchX,batchY):
"""
验证函数
"""
feed_dict={
cnn.inputX:batchX,
cnn.inputY:batchY,
cnn.dropoutKeepProb:1.0
}
summary,step,loss,predictions=sess.run(
[summaryOp,globalStep,cnn.loss,cnn.predictions],
feed_dict
)
acc,precision,recall,f_beta=get_multi_metrics(pred_y=predictions,true_y=batchY,labels=labelList)
evalSummaryWriter.add_summary(summary,step)
return loss,acc,precision,recall,f_beta
for i in range(config.training.epoches):#轮数
#训练模型
print('start training model')
for batchTrain in nextBatch(trainSentences,trainLabels,config.batchSize): #总批次
loss,acc,prec,recall,f_beta=trainStep(batchTrain[0],batchTrain[1])
currentStep=tf.train.global_step(sess,globalStep)
#global_step记录的其实是train阶段每一步的索引
#currentStep记录执行的第几个batch
print('train:step:{},loss:{},acc:{},recall:{},precision:{},f_beta:{}'.format
(currentStep,loss,acc,recall,prec,f_beta))
if currentStep%config.training.evaluateEvery==0: #每100步训练输出一次1轮的评估
print('\nEvalution:')
losses=[]
accs=[]
f_betas=[]
precisions=[]
recalls=[]
for batchEval in nextBatch(evalSentences,evalLabels,config.batchSize):
loss,acc,precision,recall,f_beta=devStep(batchEval[0],batchEval[1])
losses.append(loss)
accs.append(acc)
f_betas.append(f_beta)
precisions.append(precision)
recalls.append(recall)
time_str=datetime.datetime.now().isoformat()
print('{},step:{},loss:{},acc:{},precision:{},recall:{},f_beta:{}'.format(
time_str,currentStep,mean(losses),mean(accs),mean(precisions),mean(recalls),mean(f_betas)
))
if currentStep%config.training.checkpointEvery==0:
#每100步保存模型
#保存模型的另一种方法,保存checkpoint文件
path=saver.save(sess,'./model/textCNN/model/my-model',global_step=currentStep)
print('Saved model checkpoint to {}\n'.format(path))
inputs={'inputX':tf.saved_model.utils.build_tensor_info(cnn.inputX),
'keepProb':tf.saved_model.utils.build_tensor_info(cnn.dropoutKeepPro)}
#tf.saved_model.utils.build_tensor_info是把变量变成可缓存对象的函数
outputs = {"predictions": tf.saved_model.utils.build_tensor_info(cnn.predictions)}
prediction_signature=tf.saved_model.signature_def_utils.build_signature_def(inputs=inputs,outputs=outputs,
method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME)
#SignatureDef的主要作用是定义输出和输入接口协议
legacy_init_op = tf.group(tf.tables_initializer(), name="legacy_init_op")
builder.add_meta_graph_and_variables(sess, [tf.saved_model.tag_constants.SERVING],
signature_def_map={"predict": prediction_signature},
legacy_init_op=legacy_init_op)
builder.save()
"""
先构建builder对象;再用builder.add_meta_graph_and_variables(...)方法将与训练模型的图和参数值添加到builder中;
最后调用builder.save()将pretrain model保存成saved model。
"""

权重更新:tensorflow——optimizer.minimize()、optimizer.compute_gradients()、optimizer.apply_gradients()
将学习速率传给优化器
通过损失计算梯度和变量
将梯度给优化器,优化器来优化参数
模型的恢复与保存:tensorflow 1.0 学习:模型的保存与恢复(Saver)
模型的保存Tensorflow serving 学习(一)
开始训练模型


分享一个tensorboard的界面介绍tensorflow学习笔记----TensorBoard讲解
可以打开随时观察loss变化

