什么是TensorFlow?

前言只有光头才能变强。文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y回顾前面:从零开始学TensorFlow【01-搭建环境、HelloWorld篇】TensorFlow是什么意思?Tensor?Flow?这篇文章介绍TensorFlow一些最基础的知识。一、Tensor介绍在介绍之前,首先要记住一个结论:TensorFlow使用Tensor来表示数据接着我们来看看什么是Tensor,在官网的文档中,Tensor被翻译成”张量“。其中也给出了一个定义:张量是对矢量和矩阵向潜在的更高维度的泛化,TensorFlow 在内部将张量表示为基本数据类型的n维数组。不知道你们看完这句话有啥感受,反正我当时就看不懂,啥是”张量“?。于是,我就跑去知乎里边用关键字搜了一下:”张量是什么“。果真给我搜到了相关的问题:《怎么通俗地理解张量?》https://www.zhihu.com/question/23720923我本以为通过知乎,就可以通俗易懂地理解什么是张量,能给我一个清晰的认识。殊不知,大多数答主都在回答在物理和数学中张量的定义,随后贴出了一堆我看不懂的公式。其中,也看到了一种相对通俗易懂的定义:一个量, 在不同的参考系下按照某种特定的法则进行变换, 就是张量.把所有答主的回答都阅读了一遍,看完就更加抽象了。再回到官方文档中,看看官方介绍张量的例子,貌似有点懂了。目前为止我们有两个结论:TensorFlow使用Tensor来表示数据TensorFlow 在内部将张量表示为基本数据类型的n维数组我再翻译一下上面的两句话:在TensorFlow所有的数据都是一个n维的数组,只是我们给它起了个名字叫做张量(Tensor)中间折腾了一大堆,实际上还是将最开头的结论和官方的定义再翻译成自己觉得好理解的话…但很多时候,学习就这么一个过程。1.1Tensor的基础从上面我们已经得知,Tensor(张量)实际上就是一个n维的数组。这就延伸了几个的术语:阶(秩)形状1.1.1阶(秩)其实上,阶就是平时我们所说的维数。比如我们有一个二维的数组,那么这个阶就是2比如我们有一个三维的数组,那么这个阶就是3以前在写Java的时候,可能一般接触到的都是二维的,但在机器学习上就很可能有很高的维度,那维数我们怎么数?很简单,我们数括号就行了。举个例子,我们可能会看到有下面的一个数组输出形式:[[[9 6] [6 9] [8 8] [7 9]] [[6 1] [3 5] [1 7] [9 4]]]我们直接看第一个括号到第一个数字,有多少个括号就知道了。[[[9可以发现有3个括号,那这个就是一个三维的数组,它的阶(秩)就是31.1.2形状张量的形状可以让我们看到每个维度中元素的数量。比如我们在Java中创建出一个二维的数组:int [][] array = new int[3][4],我们就可以知道这个数组有三行有四列。但如果我们创建出一个多维的数组,单单只用行和列就描述不清了。所以,在TensorFlow一般我们会这样描述:在维度一上元素的个数有3个,在维度二上元素的个数有4个。其实说到底还是一个意思,但只是说法变了而已。如果我们要打印上面数组的形状时,我们可以得到这样的结果:shape = (3,4)。我们再看看第一篇写”机器学习HelloWorld“的时候,再来看看当时打印的结果:shape = (60000, 28, 28)。通过shape我们就可以得到一些信息:当前数组是三维的在第一维中有60000个元素在第二维中有28个元素在第三维中有28个元素那我们如果拿到一个数组,怎么通过肉眼看他的shape呢?比如说:m = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],这个很简单,一眼就可以看出这个是一个二维数组(矩阵),有三行三列。所以shape的结果应该是(3,3)再来看一个:t = [[[2], [4], [6]], [[8], [10], [12]], [[14], [16], [18]]],从多个括号上我们可以看出,这是三维的。我们先把最外层括号去掉得到的结果是[[2], [4], [6]], [[8], [10], [12]], [[14], [16], [18]]Ok,到这一步,我们可以理解成有三个子数组,于是我们的shape可以先写成shape(3,?,?)我们从括号上判断一定是三维的,所以肯定是(?,?,?)的。从“子数组”的个数我们将第一个“?”号填充为3随后,我们继续把外层的括号去除,得到这样的结果:[2], [4], [6],也是有三个元素,于是我们的shape就可以填成shape(3,3,?)最后,再把括号去掉,我们可以发现只有一个元素,于是最后的结果就是shape(3,3,1)我们可以看下图来巩固一下上面所说的概念:1.1.3 Tensor数据类型TensorFlow 在内部将张量表示为基本数据类型的 n维数组,没错的。在一个数组里边,我们总得知道我们的存进去的数据究竟是什么类型。我们可以将任意数据结构序列化为 string 并将其存储在 tf.Tensor 中。通过 tf.cast可以将 tf.Tensor 从一种数据类型转型为另一种。Tensor的数据类型如下所示:二、特殊的张量特殊的张量由一下几种:tf.Variable— 变量tf.constant— 常量tf.placeholder—占位符tf.SparseTensor—稀疏张量这次,我们先来讲讲前三种(比较好理解),分别是变量、常量和占位符。2.1 常量常量就是常量的意思,一经创建就不会被改变。(相信大家还是能够理解的)在TensorFlow中,创建常量的方式十分简单:a = tf.constant(2)b = tf.constant(3)2.2变量变量也挺好理解的(就将编程语言的概念跟这里类比就好了)。一般来说,我们在训练过程中的参数一般用变量进行存储起来,因为我们的参数会不停的变化。在TensorFlow创建变量有两种方式:# 1.使用Variable类来创建# tf.random_normal 方法返回形状为(1,4)的张量。它的4个元素符合均值为100、标准差为0.35的正态分布。W = tf.Variable(initial_value=tf.random_normal(shape=(1, 4), mean=100, stddev=0.35), name=“W”)b = tf.Variable(tf.zeros([4]), name=“b”)# 2.使用get_variable的方式来创建my_int_variable = tf.get_variable(“my_int_variable”, [1, 2, 3], dtype=tf.int32, initializer=tf.zeros_initializer)值得注意的是:当我们创建完变量以后,我们每次使用之前,都需要为其进行初始化!tf.global_variables_initializer()2.3占位符我最早接触占位符这个概念的时候是在JDBC的时候。因为SQL需要传入的参数才能确定下来,所以我们可能会写出这样的SQL语句:select * from user where id =?同样地,在TensorFlow占位符也是这么一个概念,可能需要等到运行的时候才把某些变量确定下来,于是我们就有了占位符。在TensorFlow使用占位符也很简单:# 文件名需要等到运行的时候才确定下来train_filenames = tf.placeholder(tf.string, shape=[None])# ..省略一堆细节# 运行的时候,通过feed_dict将占位符具体的值给确定下来feed_dict={train_filenames: training_filenames}上面的东西说白了在编程语言中都是有的,只是语法变了而已。三、Flow?介绍图和节点我们将Flow翻译成中文:流,所以现在是Tensor流?其实,在TensorFlow中,使用图 (graph) 来表示计算任务。其实TensorFlow默认会给我们一张空白的图,一般我们会叫这个为”数据流图“。数据流图由有向边和节点组成,在使用TensorFlow的时候我们会在图中创建各种的节点,而Tensor会在这些节点中流通。所以,就叫做TensorFlow那有人就会好奇,我们执行什么操作会创建节点呢?在TensorFlow中,节点的类型可以分为三种:存储节点:有状态的变量操作,通常用于存储模型参数计算节点:无状态的计算和控制操作,主要负责算法的逻辑或流程的控制数据节点:数据的占位符操作,用于描述图外输入的数据看到这里的同学,可能就反应过来了:原来在上面创建的变量、常量和占位符在TensorFlow中都会生成一个节点!对于这类的操作Operation(行为)一般大家会简说成op所以,op就是在TensorFlow中所执行的一个操作统称而已(有可能是创建变量的操作、也有可能是计算的操作)。在TensorFlow的常见的op有以下:其实说白了就是TensorFlow会给我们一张空白的数据流图,我们往这张数据流图填充(创建节点),从而实现想要效果。开局一张图,内容全靠编!我们来看看官方的给出数据流图的gif,加深下印象。TensorFlow使用数据流图来表示计算任务TensorFlow使用Tensor来表示数据,Tensor在数据流图中流动。在TensorFlow中”创建节点、运算“等行为统称为op四、啥是session?TensorFlow程序通常被组织成一个构建阶段和执行阶段. 在构建阶段, op的执行步骤被描述成一个图. 在执行阶段, 使用会话执行执行图中的op。注意:因为是有向边,所以只有等到之前的入度节点们的计算状态完成后,当前节点才能执行操作。说白了,就是当我们在编写代码的时候,实际上就是在将TensorFlow给我们的空白图描述成一张我们想要的图。但我们想要运行出图的结果,那就必须通过session来执行。举个小例子:import tensorflow as tf# 创建数据流图:y = W * x + b,其中W和b为存储节点,x为数据节点。x = tf.placeholder(tf.float32)W = tf.Variable(1.0)b = tf.Variable(1.0)y = W * x + b# =========如果不使用session来运行,那上面的代码只是一张图。我们通过session运行这张图,得到想要的结果with tf.Session() as sess: tf.global_variables_initializer().run() # Operation.run fetch = y.eval(feed_dict={x: 3.0}) # Tensor.eval print(fetch) # fetch = 1.0 * 3.0 + 1.04.1 Fetch是啥?Fetch就时候可以在session.run的时候传入多个op(tensor),然后返回多个tensor(如果只传入一个tensor的话,那就是返回一个tensor)4.2tensor.eval()和Operation.run()有的同学在查阅资料的时候,发现可能调用的不是session.run,而是tensor.eval()和Operation.run()。其实,他们最后的调用的还是session.run。不同的是session.run可以一次返回多个tensor(通过Fetch)。最后曾经看到一段话总结得不错:使用 tensor 表示数据.使用图 (graph) 来表示计算任务.在会话(session)中运行图通过 变量 (Variable) 维护状态.TensorFlow 是一个编程系统, 使用图来表示计算任务. 图中的节点被称之为 op (operation 的缩写). 一个 op 获得 0 个或多个 Tensor, 执行计算, 产生 0 个或多个 Tensor. 每个 Tensor 是一个类型化的多维数组.这篇文章简单讲了TensorFlow是啥意思以及一些基础的概念。但我也只是简单以我的理解方式来说了一些常见概念。里头的知识点还是比较多的(比如创建变量的时候一般我们会指定哪些参数….),这些就交由大家去官网、博客、书籍去学习了。我相信,只要了解了这些概念,那学习一定可以事半功倍!下一篇TensorFlow文章敬请期待~参考资料:https://juejin.im/post/5b345a49f265da599c561b25https://github.com/geektime-geekbang/tensorflow-101/tree/master/notebook-examples乐于输出干货的Java技术公众号:Java3y。公众号内有200多篇原创技术文章、海量视频资源、精美脑图,不妨来关注一下!觉得我的文章写得不错,不妨点一下赞! ...

March 12, 2019 · 1 min · jiezi

从Word2Vec到Bert

Word2Vec模型Word2Vec有两种训练方法:CBOW和Skip-gram。CBOW的核心思想是上下文预测某个单词,Skip-gram正好相反,输入单词,要求网络预测它的上下文。如上图所示,一个单词表达成word embedding后,很容易找到词义相近的其它词汇。word embedding使用:句子中的单词以one-hot的形式作为输入,然后乘以学好的word embedding矩阵Q,就直接取出单词对应的word embedding了。word embedding矩阵Q其实就是网络one-hot层到embedding层映射的网络参数矩阵。所以,word embedding等价于把one-hot层到embedding层的网络用预训练好的参数矩阵Q初始化了。不过,word embedding只能初始化第一层网络参数,再高层就无能为力了。下游NLP任务使用word embedding的时候与图像类似,有两种方法,一种是frozen:word embedding层的网络参数固定不动;另一种是fine-tuning,即:word embedding这层参数使用新的训练集合训练。word embedding存在的问题如图所示,多义词bank有两种含义,但是word embedding在对bank这个词进行编码的时候无法区分这两个含义。尽管上下文环境中出现的单词相同,但是在语言模型训练的时候,不论什么上下文的句子经过word2vec,都是预测相同的单词bank,而同一个单词占用的同一行的参数空间,这导致两种不同的上下文信息都会编码到相同的word embedding空间去。所以,word embedding无法区分多义词的不同语义,这是它一个比较严重的问题。BertBert采用transformer作为特征提取器,并采用双向语言模型。此外,Bert预训练的数据规模非常庞大。NLP的四大类任务:序列标注:中文分词,词性标注,命名实体识别,语义角色标注等。特点是,句子中的每个单词要求模型根据上下文给出一个分类类别。分类任务:文本分类,情感计算。特点是,不管文章有多长,总体给出一个分类类别即可。句子关系判断:问答,语义改写,自然语言推理等任务。特点是,给定两个句子,模型判断两个句子是否具有某种语义关系。生成式任务:机器翻译,文本摘要,写诗造句,看图说话等。特点是,输入文本后,需要自主生成另外一种文字。对于句子关系类任务,加上一个其实符号和终结符号,句子之间加上分隔符号即可。对输出来说,把第一个起始符号对应的transformer最后一层位置上串联一个softmax分类层。对于分类问题,增加起始和终结符号,输出部分和句子关系类任务类似。序列标注问题:输入部分和单句分类问题一样,只需要输出部分transformer最后一层每个单词对应位置都进行分类即可。从这里可以看出,NLP四大类任务都可以比较方便的改造bert能够接受的方式,这意味着bert具有很强的普适性。bert构造双向语言模型Masked双向语言模型,随机选择语料中15%的单词,其中80%替换成mask标记,10%随机替换另一个单词,10%不做改动。Next Sentence Prediction,分两种情况选择句子,一种是在语料中选择真正顺序相连的两个句子;另一种方式是,第二个句子随机选择拼接到第一个句子的后面。要求模型做句子关系预测,判断第二个句子是不是真的第一个句子的后续句子。这么做的目的是,在很多NLP任务中是句子关系判断任务,单词预测颗粒度的训练到不了句子关系这个层级,增加这个任务有助于下游句子关系判断任务。由此可以看到,bert的预训练是个多任务过程。bert输入部分处理bert输入是一个线性序列,两个句子通过分隔符分割,前后两端分别增加标识符号。每个单词有三个embedding。位置embedding:NLP中单词顺序是重要特征,需要对位置进行编码。单词embedding句子embedding:前面提到的训练数据都是由两个句子构成,那么每个句子有个句子整体的embedding对应每个单词。三者叠加,就形成了bert的输入。bert输出处理bert评价从模型或者方法的角度来看,bert借鉴了ELMO,GPT以及CBOW,主要提出了masked语言模型和next sentence prediction。训练采用两阶段模型,第一阶段双向语言模型预训练,第二阶段采用具体任务fine-tuning或者做特征集成;第二是特征提取采用transformer作为特征提取器而不是rnn或cnn。bert最大的两点是效果好普适性强,几乎所有的NLP任务都可以套用bert这种两阶段解决思路。案例实现:Predicting Movie Review Sentiment with BERT on TF Hubbert已经添加到TF-Hub模块,可以快速集成到现有项目中。bert层可以替代之前的elmo,glove层,并且通过fine-tuning,bert可以同时提供精度,训练速度的提升。此案例中,我们将在tensorflow中使用bert训练一个模型用于判断电影评论的情绪是消极还是积极。导入模块from sklearn.model_selection import train_test_splitimport pandas as pdimport tensorflow as tfimport tensorflow_hub as hubfrom datetime import datetime!pip install bert-tensorflowimport bertfrom bert import run_classifierfrom bert import optimizationfrom bert import tokenization数据下载# 读取文件,创建dataframedef load_directory_data(directory): data={} data[‘sentence’]=[] data[‘sentiment’]=[] for file_path in os.listdir(directory): with tf.gfile.GFile(os.path.join(directory,file_path),‘r’) as f: data[‘sentence’].append(f.read()) data[‘sentiment’].append(re.match(’\d+_(\d+).txt’,file_path).group(1)) return pd.DataFrame.from_dict(data)# 添加新列,并打乱数据def load_dataset(directory): # 积极情绪文件 pos_df=load_directory_data(os.path.join(directory,‘pos’)) # 消极情绪 neg_df=load_directory_data(os.path.join(directory,’neg’)) pos_df[‘polarity’]=1 neg_df[‘polarity’]=0 # sample 参数frac,返回的比例,如:df中有10行数据,想返回30%,设置值为:0.3 return pd.concat([pos_df,neg_df]).sample(frac=1).reset_index(drop=True)# 下载并加载数据def download_and_load_datasets(force_download=False): dataset=tf.keras.utils.get_file( fname=‘aclImdb.tar.gz’, origin=‘http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz', extract=True ) train_df=load_dataset(os.path.join(os.path.dirname(dataset),‘aclImdb’,’train’)) test_df=load_dataset(os.path.join(os.path.dirname(dataset),‘aclImdb’,’test’)) return train_df,test_dftrain,test=download_and_load_datasets()# 去前5000个样本train=train.sample(5000)test=test.sample(5000)DATA_COLUMN=‘sentence’LABEL_COLUMN=‘polarity’label_list=[0,1]数据处理我们需要将数据转换成bert能够处理的格式。我们首先创建InputExample构造函数:test_a:我们要分类的数据,如:DATA_COLUMNtest_b:用于句子关系判断,如:问答,翻译等label:数据标签train_InputExample=train.apply(lambda x:bert.run_classifier.InputExample( guid=None, text_a=x[DATA_COLUMN], text_b=None, label=x[LABEL_COLUMN]),axis=1)test_InputExample=test.apply(lambda x:bert.run_classifier.InputExample( guid=None, text_a=x[DATA_COLUMN], text_b=None, label=x[LABEL_COLUMN]),axis=1)接下来,我们需要处理数据以适合bert进行训练。步骤依次如下:单词全部小写将文本转换成序列(如:‘sally says hi’ -> [‘sally’,‘says’,‘hi’])将单词分解为wordpieces(如:‘calling’->[‘call’,’##ing’])用bert提供的词汇文件进行单词索引映射添加‘CLS’,‘SEP’标记符每次输入添加‘index’和‘segment’标记# lowercase bert版本BERT_MODEL_HUB=‘https://tfhub.dev/google/bert_uncased_L-12_H-768_A-12/1'# 获取词表文件,小写数据处理def create_tokenizer_from_hub_module(): with tf.Graph().as_default(): bert_module = hub.Module(BERT_MODEL_HUB) tokenization_info = bert_module(signature=‘tokenization_info’, as_dict=True) with tf.Session() as sess: vocab_file,do_lower_case=sess.run([tokenization_info[‘vocab_file’],tokenization_info[‘do_lower_case’]]) return bert.tokenization.FullTokenizer( vocab_file=vocab_file, do_lower_case=do_lower_case )tokenizer=create_tokenizer_from_hub_module()# 序列最长MAX_SEQ_LENGTH=128# 将训练,测试数据特征转换成bert需要的格式train_features=bert.run_classifier.convert_examples_to_features(train_InputExample,label_list,MAX_SEQ_LENGTH,tokenizer)test_features=bert.run_classifier.convert_examples_to_features(test_InputExample,label_list,MAX_SEQ_LENGTH,tokenizer)创建模型def create_model(is_predicting,input_ids,input_mask,segment_ids,labels,num_labels): # 创建分类模型 bert_module=hub.Module( BERT_MODEL_HUB, trainable=True ) bert_inputs=dict( input_ids=input_ids, input_mask=input_mask, segment_ids=segment_ids ) bert_outputs=bert_module( inputs=bert_inputs, signature=‘tonkens’, as_dict=True ) output_layer=bert_outputs[‘pooled_output’] hidden_size=output_layer.shape[-1].value output_weights=tf.get_variable( ‘output_weights’,[num_labels,hidden_size], initializer=tf.truncated_normal_initializer(stddev=0.02) ) output_bias=tf.get_variable( ‘output_bias’,[num_labels],initializer=tf.zeros_initializer() ) with tf.variable_scope(’loss’): # dropout用于防止过拟合,仅训练时使用 output_layer=tf.nn.dropout(output_layer,keep_prob=0.9) logits=tf.matmul(output_layer,output_weights,transpose_b=True) logits=tf.nn.bias_add(logits,output_bias) log_prob=tf.nn.log_softmax(logits,axis=-1) # 将标签转为one-hot格式 one_hot_labels=tf.one_hot(labels,depth=num_labels) predcited_labels=tf.squeeze(tf.argmax(log_prob,axis=-1)) if is_predicting: return (predcited_labels,log_prob) per_example_loss=-tf.reduce_sum(one_hot_labelslog_prob,axis=-1) loss=tf.reduce_mean(per_example_loss) return (loss,predcited_labels,log_prob)创建InputFndef model_fn_builder(num_labels,learning_rate,num_train_steps,num_warmup_steps): def model_fn(features,labels,mode,params): input_idx=features[‘input_idx’] input_mask=features[‘input_mask’] segment_ids=features[‘segment_ids’] lable_ids=features[’labels_ids’] is_predicting=(mode == tf.estimator.ModeKeys.PREDICT) if not is_predicting: (loss,predicted_labels,log_probs)=create_model( is_predicting,input_idx,input_mask,segment_ids,lable_ids,num_labels ) train_op = bert.optimization.create_optimizer( loss, learning_rate, num_train_steps, num_warmup_steps, use_tpu=False) def metric_fn(label_ids,predicted_labels): accuracy=tf.metrics.accuracy(labels=lable_ids,predictions=predicted_labels) f1_score = tf.contrib.metrics.f1_score( label_ids, predicted_labels) auc = tf.metrics.auc( label_ids, predicted_labels) recall = tf.metrics.recall( label_ids, predicted_labels) precision = tf.metrics.precision( label_ids, predicted_labels) true_pos = tf.metrics.true_positives( label_ids, predicted_labels) true_neg = tf.metrics.true_negatives( label_ids, predicted_labels) false_pos = tf.metrics.false_positives( label_ids, predicted_labels) false_neg = tf.metrics.false_negatives( label_ids, predicted_labels) return { “eval_accuracy”: accuracy, “f1_score”: f1_score, “auc”: auc, “precision”: precision, “recall”: recall, “true_positives”: true_pos, “true_negatives”: true_neg, “false_positives”: false_pos, “false_negatives”: false_neg } eval_metrics=metric_fn(lable_ids,predicted_labels) if mode == tf.estimator.ModeKeys.TRAIN: return tf.estimator.EstimatorSpec( mode=mode, loss=loss, train_op=train_op ) else: return tf.estimator.EstimatorSpec( mode=mode, loss=loss, eval_metric_ops=eval_metrics ) else: (predicted_labels, log_probs) = create_model( is_predicting, input_idx, input_mask, segment_ids, lable_ids, num_labels) predictions = { ‘probabilities’: log_probs, ’labels’: predicted_labels } return tf.estimator.EstimatorSpec(mode, predictions=predictions) return model_fn参数配置BATCH_SIZE=32LEARNING_RATE=2e-5NUM_TRAIN_EPOCHS=3WARMUP_PROPORTION=0.1SAVE_CHECKEPOINTS_STEPS=500SAVE_SUMMARY_STEPS=100# 计算train,warmup总训练步数num_train_steps=int(len(train_features)/BATCH_SIZENUM_TRAIN_EPOCHS)num_warmup_steps=int(num_train_steps*WARMUP_PROPORTION)# 设置模型保存路径/次数,图信息保存次数run_config=tf.estimator.RunConfig( model_dir=OUTPUT_DIR, save_checkpoints_steps=SAVE_CHECKEPOINTS_STEPS, save_summary_steps=SAVE_SUMMARY_STEPS)模型训练,验证modle_fn=model_fn_builder( num_labels=len(label_list), learning_rate=LEARNING_RATE, num_train_steps=num_train_steps, num_warmup_steps=num_train_steps)estimator=tf.estimator.Estimator( model_fn=modle_fn, config=run_config, params={‘batch_size’:BATCH_SIZE})train_input_fn=bert.run_classifier.input_fn_builder( features=train_features, seq_length=MAX_SEQ_LENGTH, is_training=True, drop_remainder=False)estimator.train(input_fn=train_input_fn,max_steps=num_train_steps) ...

March 7, 2019 · 2 min · jiezi

使用 TensorFlow Serving 和 Docker 快速部署机器学习服务

从实验到生产,简单快速部署机器学习模型一直是一个挑战。这个过程要做的就是将训练好的模型对外提供预测服务。在生产中,这个过程需要可重现,隔离和安全。这里,我们使用基于Docker的TensorFlow Serving来简单地完成这个过程。TensorFlow 从1.8版本开始支持Docker部署,包括CPU和GPU,非常方便。获得训练好的模型获取模型的第一步当然是训练一个模型,但是这不是本篇的重点,所以我们使用一个已经训练好的模型,比如ResNet。TensorFlow Serving 使用SavedModel这种格式来保存其模型,SavedModel是一种独立于语言的,可恢复,密集的序列化格式,支持使用更高级别的系统和工具来生成,使用和转换TensorFlow模型。这里我们直接下载一个预训练好的模型:$ mkdir /tmp/resnet$ curl -s https://storage.googleapis.com/download.tensorflow.org/models/official/20181001_resnet/savedmodels/resnet_v2_fp32_savedmodel_NHWC_jpg.tar.gz | tar –strip-components=2 -C /tmp/resnet -xvz如果是使用其他框架比如Keras生成的模型,则需要将模型转换为SavedModel格式,比如:from keras.models import Sequentialfrom keras import backend as Kimport tensorflow as tfmodel = Sequential()# 中间省略模型构建# 模型转换为SavedModelsignature = tf.saved_model.signature_def_utils.predict_signature_def( inputs={‘input_param’: model.input}, outputs={’type’: model.output})builder = tf.saved_model.builder.SavedModelBuilder(’/tmp/output_model_path/1/’)builder.add_meta_graph_and_variables( sess=K.get_session(), tags=[tf.saved_model.tag_constants.SERVING], signature_def_map={ tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: signature })builder.save()下载完成后,文件目录树为:$ tree /tmp/resnet/tmp/resnet└── 1538687457 ├── saved_model.pb └── variables ├── variables.data-00000-of-00001 └── variables.index部署模型使用Docker部署模型服务:$ docker pull tensorflow/serving$ docker run -p 8500:8500 -p 8501:8501 –name tfserving_resnet --mount type=bind,source=/tmp/resnet,target=/models/resnet -e MODEL_NAME=resnet -t tensorflow/serving其中,8500端口对于TensorFlow Serving提供的gRPC端口,8501为REST API服务端口。-e MODEL_NAME=resnet指出TensorFlow Serving需要加载的模型名称,这里为resnet。上述命令输出为2019-03-04 02:52:26.610387: I tensorflow_serving/model_servers/server.cc:82] Building single TensorFlow model file config: model_name: resnet model_base_path: /models/resnet2019-03-04 02:52:26.618200: I tensorflow_serving/model_servers/server_core.cc:461] Adding/updating models.2019-03-04 02:52:26.618628: I tensorflow_serving/model_servers/server_core.cc:558] (Re-)adding model: resnet2019-03-04 02:52:26.745813: I tensorflow_serving/core/basic_manager.cc:739] Successfully reserved resources to load servable {name: resnet version: 1538687457}2019-03-04 02:52:26.745901: I tensorflow_serving/core/loader_harness.cc:66] Approving load for servable version {name: resnet version: 1538687457}2019-03-04 02:52:26.745935: I tensorflow_serving/core/loader_harness.cc:74] Loading servable version {name: resnet version: 1538687457}2019-03-04 02:52:26.747590: I external/org_tensorflow/tensorflow/contrib/session_bundle/bundle_shim.cc:363] Attempting to load native SavedModelBundle in bundle-shim from: /models/resnet/15386874572019-03-04 02:52:26.747705: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:31] Reading SavedModel from: /models/resnet/15386874572019-03-04 02:52:26.795363: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:54] Reading meta graph with tags { serve }2019-03-04 02:52:26.828614: I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA2019-03-04 02:52:26.923902: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:162] Restoring SavedModel bundle.2019-03-04 02:52:28.098479: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:138] Running MainOp with key saved_model_main_op on SavedModel bundle.2019-03-04 02:52:28.144510: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:259] SavedModel load for tags { serve }; Status: success. Took 1396689 microseconds.2019-03-04 02:52:28.146646: I tensorflow_serving/servables/tensorflow/saved_model_warmup.cc:83] No warmup data file found at /models/resnet/1538687457/assets.extra/tf_serving_warmup_requests2019-03-04 02:52:28.168063: I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: resnet version: 1538687457}2019-03-04 02:52:28.174902: I tensorflow_serving/model_servers/server.cc:286] Running gRPC ModelServer at 0.0.0.0:8500 …[warn] getaddrinfo: address family for nodename not supported2019-03-04 02:52:28.186724: I tensorflow_serving/model_servers/server.cc:302] Exporting HTTP/REST API at:localhost:8501 …[evhttp_server.cc : 237] RAW: Entering the event loop …我们可以看到,TensorFlow Serving使用1538687457作为模型的版本号。我们使用curl命令来查看一下启动的服务状态,也可以看到提供服务的模型版本以及模型状态。$ curl http://localhost:8501/v1/models/resnet{ “model_version_status”: [ { “version”: “1538687457”, “state”: “AVAILABLE”, “status”: { “error_code”: “OK”, “error_message”: "" } } ]}查看模型输入输出很多时候我们需要查看模型的输出和输出参数的具体形式,TensorFlow提供了一个saved_model_cli命令来查看模型的输入和输出参数:$ saved_model_cli show –dir /tmp/resnet/1538687457/ –allMetaGraphDef with tag-set: ‘serve’ contains the following SignatureDefs:signature_def[‘predict’]: The given SavedModel SignatureDef contains the following input(s): inputs[‘image_bytes’] tensor_info: dtype: DT_STRING shape: (-1) name: input_tensor:0 The given SavedModel SignatureDef contains the following output(s): outputs[‘classes’] tensor_info: dtype: DT_INT64 shape: (-1) name: ArgMax:0 outputs[‘probabilities’] tensor_info: dtype: DT_FLOAT shape: (-1, 1001) name: softmax_tensor:0 Method name is: tensorflow/serving/predictsignature_def[‘serving_default’]: The given SavedModel SignatureDef contains the following input(s): inputs[‘image_bytes’] tensor_info: dtype: DT_STRING shape: (-1) name: input_tensor:0 The given SavedModel SignatureDef contains the following output(s): outputs[‘classes’] tensor_info: dtype: DT_INT64 shape: (-1) name: ArgMax:0 outputs[‘probabilities’] tensor_info: dtype: DT_FLOAT shape: (-1, 1001) name: softmax_tensor:0 Method name is: tensorflow/serving/predict注意到signature_def,inputs的名称,类型和输出,这些参数在接下来的模型预测请求中需要。使用模型接口预测:REST和gRPCTensorFlow Serving提供REST API和gRPC两种请求方式,接下来将具体这两种方式。REST我们下载一个客户端脚本,这个脚本会下载一张猫的图片,同时使用这张图片来计算服务请求时间。$ curl -o /tmp/resnet/resnet_client.py https://raw.githubusercontent.com/tensorflow/serving/master/tensorflow_serving/example/resnet_client.py以下脚本使用requests库来请求接口,使用图片的base64编码字符串作为请求内容,返回图片分类,并计算了平均处理时间。from future import print_functionimport base64import requests# The server URL specifies the endpoint of your server running the ResNet# model with the name “resnet” and using the predict interface.SERVER_URL = ‘http://localhost:8501/v1/models/resnet:predict’# The image URL is the location of the image we should send to the serverIMAGE_URL = ‘https://tensorflow.org/images/blogs/serving/cat.jpg'def main(): # Download the image dl_request = requests.get(IMAGE_URL, stream=True) dl_request.raise_for_status() # Compose a JSON Predict request (send JPEG image in base64). jpeg_bytes = base64.b64encode(dl_request.content).decode(‘utf-8’) predict_request = ‘{“instances” : [{“b64”: “%s”}]}’ % jpeg_bytes # Send few requests to warm-up the model. for _ in range(3): response = requests.post(SERVER_URL, data=predict_request) response.raise_for_status() # Send few actual requests and report average latency. total_time = 0 num_requests = 10 for _ in range(num_requests): response = requests.post(SERVER_URL, data=predict_request) response.raise_for_status() total_time += response.elapsed.total_seconds() prediction = response.json()[‘predictions’][0] print(‘Prediction class: {}, avg latency: {} ms’.format( prediction[‘classes’], (total_time*1000)/num_requests))if name == ‘main’: main()输出结果为$ python resnet_client.pyPrediction class: 286, avg latency: 210.12310000000002 msgRPC让我们下载另一个客户端脚本,这个脚本使用gRPC作为服务,传入图片并获取输出结果。这个脚本需要安装tensorflow-serving-api这个库。$ curl -o /tmp/resnet/resnet_client_grpc.py https://raw.githubusercontent.com/tensorflow/serving/master/tensorflow_serving/example/resnet_client_grpc.py$ pip install tensorflow-serving-api脚本内容:from future import print_function# This is a placeholder for a Google-internal import.import grpcimport requestsimport tensorflow as tffrom tensorflow_serving.apis import predict_pb2from tensorflow_serving.apis import prediction_service_pb2_grpc# The image URL is the location of the image we should send to the serverIMAGE_URL = ‘https://tensorflow.org/images/blogs/serving/cat.jpg'tf.app.flags.DEFINE_string('server', ’localhost:8500’, ‘PredictionService host:port’)tf.app.flags.DEFINE_string(‘image’, ‘’, ‘path to image in JPEG format’)FLAGS = tf.app.flags.FLAGSdef main(_): if FLAGS.image: with open(FLAGS.image, ‘rb’) as f: data = f.read() else: # Download the image since we weren’t given one dl_request = requests.get(IMAGE_URL, stream=True) dl_request.raise_for_status() data = dl_request.content channel = grpc.insecure_channel(FLAGS.server) stub = prediction_service_pb2_grpc.PredictionServiceStub(channel) # Send request # See prediction_service.proto for gRPC request/response details. request = predict_pb2.PredictRequest() request.model_spec.name = ‘resnet’ request.model_spec.signature_name = ‘serving_default’ request.inputs[‘image_bytes’].CopyFrom( tf.contrib.util.make_tensor_proto(data, shape=[1])) result = stub.Predict(request, 10.0) # 10 secs timeout print(result)if name == ‘main’: tf.app.run()输出的结果可以看到图片的分类,概率和使用的模型信息:$ python resnet_client_grpc.pyoutputs { key: “classes” value { dtype: DT_INT64 tensor_shape { dim { size: 1 } } int64_val: 286 }}outputs { key: “probabilities” value { dtype: DT_FLOAT tensor_shape { dim { size: 1 } dim { size: 1001 } } float_val: 2.4162832232832443e-06 float_val: 1.9012182974620373e-06 float_val: 2.7247710022493266e-05 float_val: 4.426385658007348e-07 …(中间省略) float_val: 1.4636580090154894e-05 float_val: 5.812107133351674e-07 float_val: 6.599806511076167e-05 float_val: 0.0012952701654285192 }}model_spec { name: “resnet” version { value: 1538687457 } signature_name: “serving_default”}性能通过编译优化的TensorFlow Serving二进制来提高性能TensorFlows serving有时会有输出如下的日志:Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMATensorFlow Serving已发布Docker镜像旨在尽可能多地使用CPU架构,因此省略了一些优化以最大限度地提高兼容性。如果你没有看到此消息,则你的二进制文件可能已针对你的CPU进行了优化。根据你的模型执行的操作,这些优化可能会对你的服务性能产生重大影响。幸运的是,编译优化的TensorFlow Serving二进制非常简单。官方已经提供了自动化脚本,分以下两部进行:# 1. 编译开发版本$ docker build -t $USER/tensorflow-serving-devel -f Dockerfile.devel https://github.com/tensorflow/serving.git#:tensorflow_serving/tools/docker# 2. 生产新的镜像$ docker build -t $USER/tensorflow-serving –build-arg TF_SERVING_BUILD_IMAGE=$USER/tensorflow-serving-devel https://github.com/tensorflow/serving.git#:tensorflow_serving/tools/docker之后,使用新编译的$USER/tensorflow-serving重新启动服务即可。总结上面我们快速实践了使用TensorFlow Serving和Docker部署机器学习服务的过程,可以看到,TensorFlow Serving提供了非常方便和高效的模型管理,配合Docker,可以快速搭建起机器学习服务。参考Serving ML Quickly with TensorFlow Serving and DockerTrain and serve a TensorFlow model with TensorFlow ServingGitHub repo: qiwihui/blogFollow me: @qiwihuiSite: QIWIHUI ...

March 4, 2019 · 4 min · jiezi

使用权重正则化较少模型过拟合

介绍权重正则化可以减轻深度神经网络模型的过拟合问题,可以提升对新数据的泛化能力。有多种正则方法可供选择,如:L1,L2正则化,每种方法在使用前需要超参数配置。在这篇文章中,你将学习在keras如何使用权重正则化的方法来减轻模型过拟合问题。读完本篇文章,你将学习到:如何在keras中使用权重正则化应用到MLP,CNN,或者LSTM神经网络任务中一些常见论文在模型中使用权重正则化的方法和经验通过一个案例学习如何使用权重正则化解决过拟合问题## keras中权重正则化方法 ##keras提供了权重正则化方法,可以在损失函数中通过添加惩罚系数来使用。keras提供了三种正则化方法:L1:绝对值权重之和L2:平方权重之和L1L2:两者累加之和tf.keras.regularizers.l1(l=0.01)tf.keras.regularizers.l2(l=0.01)tf.keras.regularizers.l1_l2(l1=0.01,l2=0.01)keras中,权重正则化可以应用到任意一层,不过,模型默认不使用任何权重正则化。全连接层使用权重正则化全连接层使用L2权重正则化:import tensorflow as tfmodel=tf.keras.models.Sequential( # 权重正则化,bias正则化(应用较少)tf.keras.layers.Dense(512,activation=tf.nn.relu,kernel_regularizer=tf.keras.regularizers.l2(l=0.001),bias_regularizer=tf.keras.regularizers.l2(l=0.001)))卷积层使用权重正则化同全连接层一样,卷积层也使用kernel_regularizer和bias_regularizer参数添加正则化。代码展示在卷积层中使用L2正则化。import tensorflow as tfmodel=tf.keras.models.Sequential( tf.keras.layers.Conv2D(32,3,activation=tf.nn.relu,kernel_regularizer=tf.keras.regularizers.l2(l=0.001),bias_regularizer=tf.keras.regularizers.l2(l=0.001)))RNN网络中使用权重正则化代码展示在LSTM网络中使用L2权重正则化import tensorflow as tfmodel=tf.keras.models.Sequential( tf.keras.layers.LSTM(32,activation=tf.nn.tanh,recurrent_regularizer=tf.keras.regularizers.l2(l=0.001),kernel_regularizer=tf.keras.regularizers.l2(l=0.001),bias_regularizer=tf.keras.regularizers.l2(l=0.001)))权重正则化使用经验最常见的权重正则化是L2正则化,数值通常是0-0.1之间,如:0.1,0.001,0.0001。找到最优的系数并不容易,需要尝试不同的权重系数,找到模型表现最平稳优秀的系数L2正则化在CNN网络中,建议系数设置小一些,如:0.0005少量的权重正则对模型很重要,可以减少模型训练误差LSTM网络中L2权重系数通常更小,如:10^-6权重正则化案例学习我们将使用标准二元分类问题来定义两个半圆观察:每个类一个半圆。每个观测值都有两个输入变量,它们具有相同的比例,类输出值为0或1.该数据集称为“月亮”数据集,因为绘制时每个类中的观测值的形状。# 导入sklearn中的数据集from sklearn.datasets import make_moonsfrom matplotlib import pyplotfrom pandas import DataFrame# 生成2分类数据集X, y = make_moons(n_samples=100, noise=0.2, random_state=1)print(X.shape)print(X[:6])print(y.shape)print(y[:6])df = DataFrame(dict(x=X[:,0], y=X[:,1], label=y))colors = {0:‘red’, 1:‘blue’}fig, ax = pyplot.subplots()grouped = df.groupby(’label’)for key, group in grouped: group.plot(ax=ax, kind=‘scatter’, x=‘x’, y=‘y’, label=key, color=colors[key])pyplot.show()sklearn常用数据集:数据集格式:matplot结果显示:如图所示,该问题是非线性问题,可以使用神经网络来解决。我们只生成了100个样本,对神经网络来说数据量很少,这很容易造成过拟合问题。我们使用正则化,添加噪声数据来处理问题。虽然卷积神经网络(CNN)功能强大,并广泛应用于各种计算机视觉任务中,但由于参数过多而导致过度拟合[22]。神经网络的最初发展受到人脑机制的启发[18],它不像计算机那样精确。受到差异的启发,我们推断在训练过程中添加噪音可能会指示CNN学习更强大的特征表示以抵消噪音的影响,从而降低过度拟合的风险。许多正则化方法通过向训练数据添加噪声来防止过拟合。数据增强的输入图像,如随机裁剪,翻转和阻塞[9,21,30]已广泛用于提高CNNs的泛化能力。 Adversarial Training [1]被提出来通过在图像中添加基于梯度的扰动来调整网络。 DisturbLabel [26]随机地将样本的一小部分子集的标签改变为不正确的值,从而在损失层上规则化CNN。过拟合模型我们创建一个只有一层隐藏层的MLP模型,并让神经元数量大于样本数量,然后过长时间训练模型,以此来人为造成过拟合问题。训练模型之前,我们拆分下数据集,训练数据30%,验证数据70%,来训练模型表现。X, y = make_moons(n_samples=100, noise=0.2, random_state=1)# 拆分数据集n_train = 30trainX, testX = X[:n_train, :], X[n_train:, :]trainy, testy = y[:n_train], y[n_train:]模型隐藏层有500个神经元,激活函数使用relu,输出层使用sigmoid激活函数,输出一项类别。模型使用bind_crossentropy损失函数,adam优化器。model = Sequential()model.add(Dense(500, input_dim=2, activation=‘relu’))model.add(Dense(1, activation=‘sigmoid’))model.compile(loss=‘binary_crossentropy’, optimizer=‘adam’, metrics=[‘accuracy’])模型迭代数据集400次,batch_size=32。model.fit(trainX, trainy, epochs=4000, verbose=0)在测试集上评估模型表现:# model.evaluate返回:loss value;metrics value_, train_acc = model.evaluate(trainX, trainy, verbose=0), test_acc = model.evaluate(testX, testy, verbose=0)print(‘Train: %.3f, Test: %.3f’ % (train_acc, test_acc))模型输出结果:我们看到训练表现远大于测试表现,这是过拟合问题的典型标志。我们将train和test训练精度过程图形化展示出来。from sklearn.datasets import make_moonsfrom keras.layers import Densefrom keras.models import Sequentialfrom matplotlib import pyplotX, y = make_moons(n_samples=100, noise=0.2, random_state=1)n_train = 30trainX, testX = X[:n_train, :], X[n_train:, :]trainy, testy = y[:n_train], y[n_train:]# 创建模型model = Sequential()model.add(Dense(500, input_dim=2, activation=‘relu’))model.add(Dense(1, activation=‘sigmoid’))model.compile(loss=‘binary_crossentropy’, optimizer=‘adam’, metrics=[‘accuracy’])# 返回训练,验证集的损失和准确率history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=4000, verbose=0)pyplot.plot(history.history[‘acc’], label=‘train’)pyplot.plot(history.history[‘val_acc’], label=‘test’)pyplot.legend()pyplot.show()如图所示,在某一点,train和test的准确率出现分叉口。使用正则化的模型我们将在隐藏层中使用权重正则化,并设置系数为0.001,以此来减轻过拟合问题。model.add(Dense(500, input_dim=2, activation=‘relu’, kernel_regularizer=l2(0.001)))完整代码如下:from sklearn.datasets import make_moonsfrom keras.layers import Densefrom keras.models import Sequentialfrom keras.regularizers import l2# 创建数据集X, y = make_moons(n_samples=100, noise=0.2, random_state=1)# 拆分数据集n_train = 30trainX, testX = X[:n_train, :], X[n_train:, :]trainy, testy = y[:n_train], y[n_train:]# 创建模型model = Sequential()# 设置权重正则化model.add(Dense(500, input_dim=2, activation=‘relu’, kernel_regularizer=l2(0.001)))model.add(Dense(1, activation=‘sigmoid’))model.compile(loss=‘binary_crossentropy’, optimizer=‘adam’, metrics=[‘accuracy’])# 训练模型model.fit(trainX, trainy, epochs=4000, verbose=0)# 评估模型, train_acc = model.evaluate(trainX, trainy, verbose=0)_, test_acc = model.evaluate(testX, testy, verbose=0)print(‘Train: %.3f, Test: %.3f’ % (train_acc, test_acc))乍一看,好像除了test准确率降低些,其它也没什么了。让我们画图看下train和test的训练过程。from sklearn.datasets import make_moonsfrom keras.layers import Densefrom keras.models import Sequentialfrom keras.regularizers import l2from matplotlib import pyplotX, y = make_moons(n_samples=100, noise=0.2, random_state=1)n_train = 30trainX, testX = X[:n_train, :], X[n_train:, :]trainy, testy = y[:n_train], y[n_train:]model = Sequential()model.add(Dense(500, input_dim=2, activation=‘relu’, kernel_regularizer=l2(0.001)))model.add(Dense(1, activation=‘sigmoid’))model.compile(loss=‘binary_crossentropy’, optimizer=‘adam’, metrics=[‘accuracy’])history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=4000, verbose=0pyplot.plot(history.history[‘acc’], label=‘train’)pyplot.plot(history.history[‘val_acc’], label=‘test’)pyplot.legend()pyplot.show()如图所示,现在就很清楚了,test与train一致。网格搜索正则化超参数当确定权重正则化可以改善模型的时候,这时你可以尝试不同的权重系数值。首先对0.0到0.1之间的一些数量级进行网格搜索,然后再找到一个级别后进行网格搜索,这是一个很好的做法。# 待测权重正则化值values = [1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6]all_train, all_test = list(), list()for param in values: … model.add(Dense(500, input_dim=2, activation=‘relu’, kernel_regularizer=l2(param))) … all_train.append(train_acc) all_test.append(test_acc)我们依然可以图形化展示训练过程:pyplot.semilogx(values, all_train, label=‘train’, marker=‘o’)pyplot.semilogx(values, all_test, label=‘test’, marker=‘o’)完整代码如下:from sklearn.datasets import make_moonsfrom keras.layers import Densefrom keras.models import Sequentialfrom keras.regularizers import l2from matplotlib import pyplot# 创建数据集X, y = make_moons(n_samples=100, noise=0.2, random_state=1)# 拆分数据集n_train = 30trainX, testX = X[:n_train, :], X[n_train:, :]trainy, testy = y[:n_train], y[n_train:]# 待测权重系数值values = [1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6]all_train, all_test = list(), list()for param in values: # 创建模型 model = Sequential() model.add(Dense(500, input_dim=2, activation=‘relu’, kernel_regularizer=l2(param))) model.add(Dense(1, activation=‘sigmoid’)) model.compile(loss=‘binary_crossentropy’, optimizer=‘adam’, metrics=[‘accuracy’]) # 训练模型 model.fit(trainX, trainy, epochs=4000, verbose=0) # 评估模型 _, train_acc = model.evaluate(trainX, trainy, verbose=0) _, test_acc = model.evaluate(testX, testy, verbose=0) print(‘Param: %f, Train: %.3f, Test: %.3f’ % (param, train_acc, test_acc)) all_train.append(train_acc) all_test.append(test_acc)# plot train and test meanspyplot.semilogx(values, all_train, label=‘train’, marker=‘o’)pyplot.semilogx(values, all_test, label=‘test’, marker=‘o’)pyplot.legend()pyplot.show()总结降低过拟合问题,我们一般需要从“数据”,“模型结构”,“模型参数”,“模型训练方法”等角度,采用的方法如下:数据增强:图像的平移,旋转,裁剪等;利用GAN生成新数据;利用机器翻译生成新数据。降低模型复杂度:减少网络层数,神经元个数;添加正则化项,如:L1,L2集成学习:神经网络,dropout避免过长时间训练模型,设置提前终止。 ...

March 4, 2019 · 2 min · jiezi

Tensorflow源码解析3 -- TensorFlow核心对象 - Graph

1 Graph概述计算图Graph是TensorFlow的核心对象,TensorFlow的运行流程基本都是围绕它进行的。包括图的构建、传递、剪枝、按worker分裂、按设备二次分裂、执行、注销等。因此理解计算图Graph对掌握TensorFlow运行尤为关键。2 默认Graph默认图替换之前讲解Session的时候就说过,一个Session只能run一个Graph,但一个Graph可以运行在多个Session中。常见情况是,session会运行全局唯一的隐式的默认的Graph,operation也是注册到这个Graph中。也可以显示创建Graph,并调用as_default()使他替换默认Graph。在该上下文管理器中创建的op都会注册到这个graph中。退出上下文管理器后,则恢复原来的默认graph。一般情况下,我们不用显式创建Graph,使用系统创建的那个默认Graph即可。print tf.get_default_graph()with tf.Graph().as_default() as g: print tf.get_default_graph() is g print tf.get_default_graph()print tf.get_default_graph()输出如下<tensorflow.python.framework.ops.Graph object at 0x106329fd0>True<tensorflow.python.framework.ops.Graph object at 0x18205cc0d0><tensorflow.python.framework.ops.Graph object at 0x10d025fd0>由此可见,在上下文管理器中,当前线程的默认图被替换了,而退出上下文管理后,则恢复为了原来的默认图。默认图管理默认graph和默认session一样,也是线程作用域的。当前线程中,永远都有且仅有一个graph为默认图。TensorFlow同样通过栈来管理线程的默认graph。@tf_export(“Graph”)class Graph(object): # 替换线程默认图 def as_default(self): return _default_graph_stack.get_controller(self) # 栈式管理,push pop @tf_contextlib.contextmanager def get_controller(self, default): try: context.context_stack.push(default.building_function, default.as_default) finally: context.context_stack.pop()替换默认图采用了堆栈的管理方式,通过push pop操作进行管理。获取默认图的操作如下,通过默认graph栈_default_graph_stack来获取。@tf_export(“get_default_graph”)def get_default_graph(): return _default_graph_stack.get_default()下面来看_default_graph_stack的创建_default_graph_stack = _DefaultGraphStack()class _DefaultGraphStack(_DefaultStack): def init(self): # 调用父类来创建 super(_DefaultGraphStack, self).init() self._global_default_graph = None class _DefaultStack(threading.local): def init(self): super(_DefaultStack, self).init() self._enforce_nesting = True # 和默认session栈一样,本质上也是一个list self.stack = []_default_graph_stack的创建如上所示,最终和默认session栈一样,本质上也是一个list。3 前端Graph数据结构Graph数据结构理解一个对象,先从它的数据结构开始。我们先来看Python前端中,Graph的数据结构。Graph主要的成员变量是Operation和Tensor。Operation是Graph的节点,它代表了运算算子。Tensor是Graph的边,它代表了运算数据。@tf_export(“Graph”)class Graph(object): def init(self): # 加线程锁,使得注册op时,不会有其他线程注册op到graph中,从而保证共享graph是线程安全的 self._lock = threading.Lock() # op相关数据。 # 为graph的每个op分配一个id,通过id可以快速索引到相关op。故创建了_nodes_by_id字典 self._nodes_by_id = dict() # GUARDED_BY(self._lock) self._next_id_counter = 0 # GUARDED_BY(self._lock) # 同时也可以通过name来快速索引op,故创建了_nodes_by_name字典 self._nodes_by_name = dict() # GUARDED_BY(self.lock) self.version = 0 # GUARDED_BY(self.lock) # tensor相关数据。 # 处理tensor的placeholder self.handle_feeders = {} # 处理tensor的read操作 self.handle_readers = {} # 处理tensor的move操作 self.handle_movers = {} # 处理tensor的delete操作 self.handle_deleters = {}下面看graph如何添加op的,以及保证线程安全的。 def add_op(self, op): # graph被设置为final后,就是只读的了,不能添加op了。 self.check_not_finalized() # 保证共享graph的线程安全 with self.lock: # 将op以id和name分别构建字典,添加到_nodes_by_id和_nodes_by_name字典中,方便后续快速索引 self.nodes_by_id[op.id] = op self.nodes_by_name[op.name] = op self.version = max(self.version, op.id)GraphKeys 图分组每个Operation节点都有一个特定的标签,从而实现节点的分类。相同标签的节点归为一类,放到同一个Collection中。标签是一个唯一的GraphKey,GraphKey被定义在类GraphKeys中,如下@tf_export(“GraphKeys”)class GraphKeys(object): GLOBAL_VARIABLES = “variables” QUEUE_RUNNERS = “queue_runners” SAVERS = “savers” WEIGHTS = “weights” BIASES = “biases” ACTIVATIONS = “activations” UPDATE_OPS = “update_ops” LOSSES = “losses” TRAIN_OP = “train_op” # 省略其他name_scope 节点命名空间使用name_scope对graph中的节点进行层次化管理,上下层之间通过斜杠分隔。# graph节点命名空间g = tf.get_default_graph()with g.name_scope(“scope1”): c = tf.constant(“hello, world”, name=“c”) print c.op.name with g.name_scope(“scope2”): c = tf.constant(“hello, world”, name=“c”) print c.op.name输出如下scope1/cscope1/scope2/c # 内层的scope会继承外层的,类似于栈,形成层次化管理4 后端Graph数据结构Graph先来看graph.h文件中的Graph类的定义,只看关键代码 class Graph { private: // 所有已知的op计算函数的注册表 FunctionLibraryDefinition ops; // GraphDef版本号 const std::unique_ptr<VersionDef> versions; // 节点node列表,通过id来访问 std::vector<Node*> nodes; // node个数 int64 num_nodes = 0; // 边edge列表,通过id来访问 std::vector<Edge*> edges; // graph中非空edge的数目 int num_edges = 0; // 已分配了内存,但还没使用的node和edge std::vector<Node*> free_nodes; std::vector<Edge*> free_edges; }后端中的Graph主要成员也是节点node和边edge。节点node为计算算子Operation,边为算子所需要的数据,或者代表节点间的依赖关系。这一点和Python中的定义相似。边Edge的持有它的源节点和目标节点的指针,从而将两个节点连接起来。下面看Edge类的定义。Edgeclass Edge { private: Edge() {} friend class EdgeSetTest; friend class Graph; // 源节点, 边的数据就来源于源节点的计算。源节点是边的生产者 Node* src; // 目标节点,边的数据提供给目标节点进行计算。目标节点是边的消费者 Node* dst; // 边id,也就是边的标识符 int id; // 表示当前边为源节点的第src_output_条边。源节点可能会有多条输出边 int src_output; // 表示当前边为目标节点的第dst_input_条边。目标节点可能会有多条输入边。 int dst_input;};Edge既可以承载tensor数据,提供给节点Operation进行运算,也可以用来表示节点之间有依赖关系。对于表示节点依赖的边,其src_output, dst_input_均为-1,此时边不承载任何数据。下面来看Node类的定义。Nodeclass Node { public: // NodeDef,节点算子Operation的信息,比如op分配到哪个设备上了,op的名字等,运行时有可能变化。 const NodeDef& def() const; // OpDef, 节点算子Operation的元数据,不会变的。比如Operation的入参列表,出参列表等 const OpDef& op_def() const; private: // 输入边,传递数据给节点。可能有多条 EdgeSet in_edges; // 输出边,节点计算后得到的数据。可能有多条 EdgeSet out_edges;}节点Node中包含的主要数据有输入边和输出边的集合,从而能够由Node找到跟他关联的所有边。Node中还包含NodeDef和OpDef两个成员。NodeDef表示节点算子的信息,运行时可能会变,创建Node时会new一个NodeDef对象。OpDef表示节点算子的元信息,运行时不会变,创建Node时不需要new OpDef,只需要从OpDef仓库中取出即可。因为元信息是确定的,比如Operation的入参个数等。由Node和Edge,即可以组成图Graph,通过任何节点和任何边,都可以遍历完整图。Graph执行计算时,按照拓扑结构,依次执行每个Node的op计算,最终即可得到输出结果。入度为0的节点,也就是依赖数据已经准备好的节点,可以并发执行,从而提高运行效率。系统中存在默认的Graph,初始化Graph时,会添加一个Source节点和Sink节点。Source表示Graph的起始节点,Sink为终止节点。Source的id为0,Sink的id为1,其他节点id均大于1.5 Graph运行时生命周期Graph是TensorFlow的核心对象,TensorFlow的运行均是围绕Graph进行的。运行时Graph大致经过了以下阶段图构建:client端用户将创建的节点注册到Graph中,一般不需要显示创建Graph,使用系统创建的默认的即可。图发送:client通过session.run()执行运行时,将构建好的整图序列化为GraphDef后,传递给master图剪枝:master先反序列化拿到Graph,然后根据session.run()传递的fetches和feeds列表,反向遍历全图full graph,实施剪枝,得到最小依赖子图。图分裂:master将最小子图分裂为多个Graph Partition,并注册到多个worker上。一个worker对应一个Graph Partition。图二次分裂:worker根据当前可用硬件资源,如CPU GPU,将Graph Partition按照op算子设备约束规范(例如tf.device(’/cpu:0’),二次分裂到不同设备上。每个计算设备对应一个Graph Partition。图运行:对于每一个计算设备,worker依照op在kernel中的实现,完成op的运算。设备间数据通信可以使用send/recv节点,而worker间通信,则使用GRPC或RDMA协议。这些阶段根据TensorFlow运行时的不同,会进行不同的处理。运行时有两种,本地运行时和分布式运行时。故Graph生命周期到后面分析本地运行时和分布式运行时的时候,再详细讲解。本文作者:扬易阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 22, 2019 · 2 min · jiezi

集成Netty|tensorflow实现 聊天AI--PigPig养成记(2)

集成Netty项目github链接通过上一节的学习我们已经可以训练得到一只傲娇的聊天AI_PigPig了。本章将介绍项目关于Netty的集成问题,将其我们的AI_PigPig可以通过web应用与大家日常互撩。由于只是一个小测试,所以不考虑性能方面的问题,在下一章我们将重点处理效率难关,集成Redis。关于Netty的学习大家可以看我的另一篇文章,本节中关于Netty部分的代码改编自该文章中的netty聊天小练习,文章中会有详细的讲解。Python代码改动首先对测试训练结果的代码进行改动,将输入输出流重定向自作为中间媒介的测试文件中。完整代码链接with tf.Session() as sess:#打开作为一次会话 # 恢复前一次训练 ckpt = tf.train.get_checkpoint_state(’.’)#从检查点文件中返回一个状态(ckpt) #如果ckpt存在,输出模型路径 if ckpt != None: print(ckpt.model_checkpoint_path) model.saver.restore(sess, ckpt.model_checkpoint_path)#储存模型参数 else: print(“没找到模型”) #测试该模型的能力 while True: #从文件中进行读取 #input_string = input(‘me > ‘) #测试文件输入格式为"[内容]:[名字]" #eg.你好:AI【表示AI的回复】 #你好:user【表示用户的输入】 with open(’./temp.txt’,‘r+’,encoding=‘ANSI’) as myf: #从文件中读取用户的输入 line=myf.read() list1=line.split(’:’) #长度为一,表明不符合输入格式,设置为"no",则不进行测试处理 if len(list1)==1: input_string=‘no’ else: #符合输入格式,证明是用户输入的 #input_string为用户输入的内容 input_string=list1[0] myf.seek(0) #清空文件 myf.truncate() #写入"no",若读到"no",则不进行测试处理 myf.write(’no’) # 退出 if input_string == ‘quit’: exit() #若读到"no",则不进行测试处理 if input_string != ’no’: input_string_vec = []#输入字符串向量化 for words in input_string.strip(): input_string_vec.append(vocab_en.get(words, UNK_ID))#get()函数:如果words在词表中,返回索引号;否则,返回UNK_ID bucket_id = min([b for b in range(len(buckets)) if buckets[b][0] > len(input_string_vec)])#保留最小的大于输入的bucket的id encoder_inputs, decoder_inputs, target_weights = model.get_batch({bucket_id: [(input_string_vec, [])]}, bucket_id) #get_batch(A,B):两个参数,A为大小为len(buckets)的元组,返回了指定bucket_id的encoder_inputs,decoder_inputs,target_weights _, _, output_logits = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True) #得到其输出 outputs = [int(np.argmax(logit, axis=1)) for logit in output_logits]#求得最大的预测范围列表 if EOS_ID in outputs:#如果EOS_ID在输出内部,则输出列表为[,,,,:End] outputs = outputs[:outputs.index(EOS_ID)] response = “".join([tf.compat.as_str(vocab_de[output]) for output in outputs])#转为解码词汇分别添加到回复中 print(‘AI-PigPig > ’ + response)#输出回复 #将AI的回复以要求的格式进行写入,方便Netty程序读取 with open(’./temp1.txt’,‘w’,encoding=‘ANSI’) as myf1: myf1.write(response+’:AI’)Netty程序完整代码参见链接netty包下。在原本的ChatHandler类中添加了从文件中读取数据的方法readFromFile,以及向文件中覆盖地写入数据的方法writeToFile。 //从文件中读取数据 private static String readFromFile(String filePath) { File file=new File(filePath); String line=null; String name=null; String content=null; try { //以content:name的形式写入 BufferedReader br=new BufferedReader(new FileReader(file)); line=br.readLine(); String [] arr=line.split(”:"); if(arr.length==1) { name=null; content=null; }else { content=arr[0]; name=arr[1]; } br.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return content; } //向文件中覆盖地写入 private static void writeToFile(String filePath,String content) { File file =new File(filePath); try { FileWriter fileWriter=new FileWriter(file); fileWriter.write(""); fileWriter.flush(); fileWriter.write(content); fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } }对原来的channelRead0方法进行修改,将输入输出流重定向到临时文件中。 @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { System.out.println(“channelRead0”); //得到用户输入的消息,需要写入文件/缓存中,让AI进行读取 String content=msg.text(); if(content==null||content=="") { System.out.println(“content 为null”); return ; } System.out.println(“接收到的消息:"+content); //写入 writeToFile(writeFilePath, content+":user”); //给AI回复与写入的时间,后期会增对性能方面进行改进 Thread.sleep(1000); //读取AI返回的内容 String AIsay=readFromFile(readFilePath); //读取后马上写入 writeToFile(readFilePath,“no”); //没有说,或者还没说 if(AIsay==null||AIsay==""||AIsay==“no”) { System.out.println(“AIsay为空或no”); return; } System.out.println(“AI说:"+AIsay); clients.writeAndFlush( new TextWebSocketFrame( “AI_PigPig在”+LocalDateTime.now() +“说:"+AIsay)); } 客户端代码<!DOCTYPE html><html> <head> <meta charset=“utf-8” /> <title></title> </head> <body> <div>发送消息:</div> <input type=“text” id=“msgContent”/> <input type=“button” value=“点我发送” onclick=“CHAT.chat()”/> <div>接受消息:</div> <div id=“receiveMsg” style=“background-color: gainsboro;"></div> <script type=“application/javascript”> window.CHAT = { socket: null, init: function() { if (window.WebSocket) { CHAT.socket = new WebSocket(“ws://192.168.0.104:8088/ws”); CHAT.socket.onopen = function() { console.log(“连接建立成功…”); }, CHAT.socket.onclose = function() { console.log(“连接关闭…”); }, CHAT.socket.onerror = function() { console.log(“发生错误…”); }, CHAT.socket.onmessage = function(e) { console.log(“接受到消息:” + e.data); var receiveMsg = document.getElementById(“receiveMsg”); var html = receiveMsg.innerHTML; receiveMsg.innerHTML = html + “<br/>” + e.data; } } else { alert(“浏览器不支持websocket协议…”); } }, chat: function() { var msg = document.getElementById(“msgContent”); CHAT.socket.send(msg.value); } }; CHAT.init(); </script> </body></html>测试结果客户端发送消息用户与AI日常互撩 ...

February 16, 2019 · 2 min · jiezi

tensorflow实现 聊天AI--PigPig养成记(1)

Chapter1.代码详解完整代码github链接,Untitled.ipynb文件内。【里面的测试是还没训练完的时候测试的,今晚会更新训练完成后的测试结果】修复了网上一些代码的bug,解决了由于tensorflow版本不同引起的一些问题。数据集链接 ,下载数据集后,解压提取dgk_shooter_min.conv文件,最好进行转码操作。建议用记事本打开后将其另存为,选择编码为utf-8后进行保存。代码详解(1)数据预处理#coding=utf-8#(1)数据预处理import osimport randomfrom io import openconv_path = ‘dgk_shooter_min.conv.txt’#判断数据集是否存在?if not os.path.exists(conv_path): print(‘数据集不存在’) exit() # 数据集格式"““EM 畹/华/吾/侄/M 你/接/到/这/封/信/的/时/候/M 不/知/道/大/伯/还/在/不/在/人/世/了/EM 咱/们/梅/家/从/你/爷/爷/起/M 就/一/直/小/心/翼/翼/地/唱/戏/M 侍/奉/宫/廷/侍/奉/百/姓/M 从/来/不/曾/遭/此/大/祸/M 太/后/的/万/寿/节/谁/敢/不/穿/红/M 就/你/胆/儿/大/M 唉/这/我/舅/母/出/殡/M 我/不/敢/穿/红/啊/M 唉/呦/唉/呦/爷/M 您/打/得/好/我/该/打/M 就/因/为/没/穿/红/让/人/赏/咱/一/纸/枷/锁/M 爷/您/别/给/我/戴/这/纸/枷/锁/呀/EM 您/多/打/我/几/下/不/就/得/了/吗/M 走/M 这/是/哪/一/出/啊/…/ / /这/是/M 撕/破/一/点/就/弄/死/你/M 唉/M 记/着/唱/戏/的/再/红/M 还/是/让/人/瞧/不/起/M 大/伯/不/想/让/你/挨/了/打/M 还/得/跟/人/家/说/打/得/好/M 大/伯/不/想/让/你/再/戴/上/那/纸/枷/锁/M 畹/华/开/开/门/哪/E…””" # 我首先使用文本编辑器sublime把dgk_shooter_min.conv文件编码转为UTF-8,一下子省了不少麻烦convs = [] # 对话集合with open(conv_path, encoding=“utf8”) as f: one_conv = [] # 一次完整对话 for line in f: line = line.strip(’\n’).replace(’/’, ‘’)#将分隔符去掉 if line == ‘’: continue if line[0] == ‘E’: if one_conv: convs.append(one_conv) one_conv = [] elif line[0] == ‘M’: one_conv.append(line.split(’ ‘)[1])#将对话转成utf-8格式,并将其保存在dgk_shooter_min.conv文件中 print(convs[:3]) # 个人感觉对白数据集有点不给力啊#[ [‘畹华吾侄’, ‘你接到这封信的时候’, ‘不知道大伯还在不在人世了’],# [‘咱们梅家从你爷爷起’, ‘就一直小心翼翼地唱戏’, ‘侍奉宫廷侍奉百姓’, ‘从来不曾遭此大祸’, ‘太后的万寿节谁敢不穿红’, ‘就你胆儿大’, ‘唉这我舅母出殡’, ‘我不敢穿红啊’, ‘唉呦唉呦爷’, ‘您打得好我该打’, ‘就因为没穿红让人赏咱一纸枷锁’, ‘爷您别给我戴这纸枷锁呀’],# [‘您多打我几下不就得了吗’, ‘走’, ‘这是哪一出啊 ‘, ‘撕破一点就弄死你’, ‘唉’, ‘记着唱戏的再红’, ‘还是让人瞧不起’, ‘大伯不想让你挨了打’, ‘还得跟人家说打得好’, ‘大伯不想让你再戴上那纸枷锁’, ‘畹华开开门哪’], ….] # 把对话分成问与答ask = [] # 问response = [] # 答for conv in convs: if len(conv) == 1: continue if len(conv) % 2 != 0: # 奇数对话数, 转为偶数对话 conv = conv[:-1] for i in range(len(conv)): if i % 2 == 0: ask.append(conv[i])#偶数对,填写问题 else: response.append(conv[i])#回答 print(len(ask), len(response))print(ask[:3])print(response[:3])#[‘畹华吾侄’, ‘咱们梅家从你爷爷起’, ‘侍奉宫廷侍奉百姓’]#[‘你接到这封信的时候’, ‘就一直小心翼翼地唱戏’, ‘从来不曾遭此大祸’] def convert_seq2seq_files(questions, answers, TESTSET_SIZE=8000): # 创建文件 train_enc = open(’train.enc’, ‘w’,encoding=‘utf-8’) # 问 train_dec = open(’train.dec’, ‘w’,encoding=‘utf-8’) # 答 test_enc = open(’test.enc’, ‘w’,encoding=‘utf-8’) # 问 test_dec = open(’test.dec’, ‘w’,encoding=‘utf-8’) # 答 # 选择8000数据作为测试数据 test_index = random.sample([i for i in range(len(questions))], TESTSET_SIZE) for i in range(len(questions)): if i in test_index:#创建测试文件 test_enc.write(questions[i] + ‘\n’) test_dec.write(answers[i] + ‘\n’) else:#创建训练文件 train_enc.write(questions[i] + ‘\n’) train_dec.write(answers[i] + ‘\n’) if i % 1000 == 0:#表示处理了多少个i print(len(range(len(questions))), ‘处理进度:’, i) train_enc.close() train_dec.close() test_enc.close() test_dec.close() convert_seq2seq_files(ask, response)# 生成的*.enc文件保存了问题# 生成的*.dec文件保存了回答将数据集进行处理后分成问与答的形式进行保存,选择其中的8000数据作为测试数据。处理完毕后生成的.enc文件保存了问题,.dec文件保存了回答。问题文件*.enc预览:爷爷您戏改得真好您怎么不进去呀王老板见过地球再也无法承受人类的数量我现在是和摩兰达说话吗?我们不是告诉他们应该想什么回答文件*.dec预览:这回跟您可真是一棵菜了我等人拿钥匙呢唉什么事我们发现了一个新的太阳系不是我们仅仅是想告诉他们应该怎么想(2)创建词汇表#coding=utf-8#(2)创建词汇表# 前一步生成的问答文件路径train_encode_file = ’train.enc’train_decode_file = ’train.dec’test_encode_file = ’test.enc’test_decode_file = ’test.dec’ print(‘开始创建词汇表…’)# 特殊标记,用来填充标记对话PAD = “PAD“GO = “GO“EOS = “EOS” # 对话结束UNK = “UNK” # 标记未出现在词汇表中的字符START_VOCABULART = [PAD, GO, EOS, UNK]PAD_ID = 0GO_ID = 1EOS_ID = 2UNK_ID = 3# 参看tensorflow.models.rnn.translate.data_utils vocabulary_size = 5000 # 生成词汇表文件def gen_vocabulary_file(input_file, output_file): vocabulary = {} with open(input_file, encoding=“utf8”) as f: counter = 0 for line in f: counter += 1 tokens = [word for word in line.strip()] for word in tokens: if word in vocabulary: vocabulary[word] += 1 else: vocabulary[word] = 1 vocabulary_list = START_VOCABULART + sorted(vocabulary, key=vocabulary.get, reverse=True) # 取前5000个常用汉字, 应该差不多够用了(额, 好多无用字符, 最好整理一下. 我就不整理了) if len(vocabulary_list) > 5000: vocabulary_list = vocabulary_list[:5000] print(input_file + " 词汇表大小:”, len(vocabulary_list)) with open(output_file, “w”, encoding=“utf8”) as ff: for word in vocabulary_list: ff.write(word + “\n”) gen_vocabulary_file(train_encode_file, “train_encode_vocabulary”)gen_vocabulary_file(train_decode_file, “train_decode_vocabulary”) train_encode_vocabulary_file = ’train_encode_vocabulary’train_decode_vocabulary_file = ’train_decode_vocabulary’ print(“对话转向量…”) # 把对话字符串转为向量形式def convert_to_vector(input_file, vocabulary_file, output_file): tmp_vocab = [] with open(vocabulary_file, “r”, encoding=“utf8”) as f: tmp_vocab.extend(f.readlines()) tmp_vocab = [line.strip() for line in tmp_vocab] vocab = dict([(x, y) for (y, x) in enumerate(tmp_vocab)]) # {‘硕’: 3142, ‘v’: 577, ‘I’: 4789, ‘\ue796’: 4515, ‘拖’: 1333, ‘疤’: 2201 …} output_f = open(output_file, ‘w’) with open(input_file, ‘r’, encoding=“utf8”) as f: for line in f: line_vec = [] for words in line.strip(): line_vec.append(vocab.get(words, UNK_ID)) output_f.write(” “.join([str(num) for num in line_vec]) + “\n”) output_f.close() convert_to_vector(train_encode_file, train_encode_vocabulary_file, ’train_encode.vec’)convert_to_vector(train_decode_file, train_decode_vocabulary_file, ’train_decode.vec’) convert_to_vector(test_encode_file, train_encode_vocabulary_file, ’test_encode.vec’)convert_to_vector(test_decode_file, train_decode_vocabulary_file, ’test_decode.vec’)提取前5000个常用的汉字创建词汇表词汇表文件*_vocabulary预览:__PAD____GO____EOS____UNK__我的你是,不了们对话转向量,把对话字符串转为向量形式向量文件*.vec预览:6 269 31 13 1022 157 5 60 19028 14 226 92 113 2047 2047 98 909 724137 22 9 644 1331 278 63 168528 6 1363 118 634 9 652 514 824 88433 131 51 24 4 127 1311093 433 94 81 4 884 13 840 3435 1010 366生成的train_encode.vec和train_decode.vec用于训练,对应的词汇表train_encode_vocabulary和train_decode_vocabulary。(3)训练这里选取部分代码进行讲解,完整代码链接。导入向量文件进行训练,定义一个read_data的函数对训练集与测试集的问题向量文件encode.vec,回答向量文件decode.vec,进行读取。读取的时候将问题向量文件encode.vec中的每一行默认以空格为分隔符,构成一个源序列。将回答向量文件decode.vec中的每一行默认以空格为分隔符,构成一个目标序列。然后将两个序列添加到data_set中。对文件中的每一行都进行处理与添加后,将得到的data_set返回。# 读取encode.vec和decode.vec数据(数据还不算太多, 一次读入到内存)def read_data(source_path, target_path, max_size=None): data_set = [[] for _ in buckets]#生成了[[],[],[],[]],即当值与参数不一样 with tf.gfile.GFile(source_path, mode=“r”) as source_file:#以读格式打开源文件(source_file) with tf.gfile.GFile(target_path, mode=“r”) as target_file:#以读格式打开目标文件 source, target = source_file.readline(), target_file.readline()#只读取一行 counter = 0#计数器为0 while source and target and ( not max_size or counter < max_size):#当读入的还存在时 counter += 1 source_ids = [int(x) for x in source.split()]#source的目标序列号,默认分隔符为空格,组成了一个源序列 target_ids = [int(x) for x in target.split()]#target组成一个目标序列,为目标序列 target_ids.append(EOS_ID)#加上结束标记的序列号 for bucket_id, (source_size, target_size) in enumerate(buckets):#enumerate()遍历序列中的元素和其下标 if len(source_ids) < source_size and len(target_ids) < target_size:#判断是否超越了最大长度 data_set[bucket_id].append([source_ids, target_ids])#读取到数据集文件中区 break#一次即可,跳出当前循环 source, target = source_file.readline(), target_file.readline()#读取了下一行 return data_set构建模型model = seq2seq_model.Seq2SeqModel(source_vocab_size=vocabulary_encode_size, target_vocab_size=vocabulary_decode_size, buckets=buckets, size=layer_size, num_layers=num_layers, max_gradient_norm=5.0, batch_size=batch_size, learning_rate=0.5, learning_rate_decay_factor=0.97, forward_only=False)开始训练with tf.Session(config=config) as sess: # 恢复前一次训练 ckpt = tf.train.get_checkpoint_state(’.’) if ckpt != None: print(ckpt.model_checkpoint_path) model.saver.restore(sess, ckpt.model_checkpoint_path) else: sess.run(tf.global_variables_initializer()) train_set = read_data(train_encode_vec, train_decode_vec) test_set = read_data(test_encode_vec, test_decode_vec) train_bucket_sizes = [len(train_set[b]) for b in range(len(buckets))]#分别计算出训练集中的长度【1,2,3,4】 train_total_size = float(sum(train_bucket_sizes))#训练实例总数 train_buckets_scale = [sum(train_bucket_sizes[:i + 1]) / train_total_size for i in range(len(train_bucket_sizes))]#计算了之前所有的数的首战百分比 loss = 0.0#损失置位0 total_step = 0 previous_losses = [] # 一直训练,每过一段时间保存一次模型 while True: random_number_01 = np.random.random_sample()#每一次循环结果不一样 #选出最小的大于随机采样的值的索引号 bucket_id = min([i for i in range(len(train_buckets_scale)) if train_buckets_scale[i] > random_number_01]) encoder_inputs, decoder_inputs, target_weights = model.get_batch(train_set, bucket_id) #get_batch()函数首先获取bucket的encoder_size与decoder_size _, step_loss, _ = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, False)#损失 loss += step_loss / 500 total_step += 1 print(total_step) if total_step % 500 == 0: print(model.global_step.eval(), model.learning_rate.eval(), loss) # 如果模型没有得到提升,减小learning rate if len(previous_losses) > 2 and loss > max(previous_losses[-3:]):#即损失比以前的大则降低学习率 sess.run(model.learning_rate_decay_op) previous_losses.append(loss) # 保存模型 checkpoint_path = “./chatbot_seq2seq.ckpt” model.saver.save(sess, checkpoint_path, global_step=model.global_step) #返回路径checkpoint_file = “%s-%s” % (save_path, “{:08d}".format(global_step)) loss = 0.0#置当前损失为0 # 使用测试数据评估模型 for bucket_id in range(len(buckets)): if len(test_set[bucket_id]) == 0: continue #获取当前bucket的encoder_inputs, decoder_inputs, target_weights encoder_inputs, decoder_inputs, target_weights = model.get_batch(test_set, bucket_id) #计算bucket_id的损失权重 _, eval_loss, _ = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True) eval_ppx = math.exp(eval_loss) if eval_loss < 300 else float(‘inf’) print(bucket_id, eval_ppx)#输出的是bucket_id与eval_ppx(4)模型测试#coding=utf-8#(4)使用训练好的模型import tensorflow as tf # 0.12# from tensorflow.models.rnn.translate import seq2seq_modelfrom tensorflow.models.tutorials.rnn.chatbot import seq2seq_model#注意 seq2seq_model这个需要自己去下载,根据自己的路径进行导入# 本人将seq2seq_model模块下载后 复制到tensorflow/models/tutorials/rnn/chatbot/路径下,所以才这样进行导入import osimport numpy as np PAD_ID = 0GO_ID = 1EOS_ID = 2UNK_ID = 3tf.reset_default_graph()#词汇表路径pathtrain_encode_vocabulary = ’train_encode_vocabulary’train_decode_vocabulary = ’train_decode_vocabulary’ #读取词汇表def read_vocabulary(input_file): tmp_vocab = [] with open(input_file, “r”,encoding=‘utf-8’) as f: tmp_vocab.extend(f.readlines())#打开的文件全部读入input_file中 tmp_vocab = [line.strip() for line in tmp_vocab]#转换成列表 vocab = dict([(x, y) for (y, x) in enumerate(tmp_vocab)]) return vocab, tmp_vocab#返回字典,列表 vocab_en, , = read_vocabulary(train_encode_vocabulary)#得到词汇字典, vocab_de, = read_vocabulary(train_decode_vocabulary)#得到词汇列表 # 词汇表大小5000vocabulary_encode_size = 5000vocabulary_decode_size = 5000 buckets = [(5, 10), (10, 15), (20, 25), (40, 50)]layer_size = 256 # 每层大小num_layers = 3 # 层数batch_size = 1 model = seq2seq_model.Seq2SeqModel(source_vocab_size=vocabulary_encode_size, target_vocab_size=vocabulary_decode_size, buckets=buckets, size=layer_size, num_layers=num_layers, max_gradient_norm=5.0, batch_size=batch_size, learning_rate=0.5, learning_rate_decay_factor=0.99, forward_only=True)#模型说明:源,目标词汇尺寸=vocabulary_encode(decode)_size;batch_size:训练期间使用的批次的大小;#forward_only:仅前向不传递误差 model.batch_size = 1#batch_size=1 with tf.Session() as sess:#打开作为一次会话 # 恢复前一次训练 ckpt = tf.train.get_checkpoint_state(’.’)#从检查点文件中返回一个状态(ckpt) #如果ckpt存在,输出模型路径 if ckpt != None: print(ckpt.model_checkpoint_path) model.saver.restore(sess, ckpt.model_checkpoint_path)#储存模型参数 else: print(“没找到模型”) #测试该模型的能力 while True: input_string = input(‘me > ‘) # 退出 if input_string == ‘quit’: exit() input_string_vec = []#输入字符串向量化 for words in input_string.strip(): input_string_vec.append(vocab_en.get(words, UNK_ID))#get()函数:如果words在词表中,返回索引号;否则,返回UNK_ID bucket_id = min([b for b in range(len(buckets)) if buckets[b][0] > len(input_string_vec)])#保留最小的大于输入的bucket的id encoder_inputs, decoder_inputs, target_weights = model.get_batch({bucket_id: [(input_string_vec, [])]}, bucket_id) #get_batch(A,B):两个参数,A为大小为len(buckets)的元组,返回了指定bucket_id的encoder_inputs,decoder_inputs,target_weights _, _, output_logits = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True) #得到其输出 outputs = [int(np.argmax(logit, axis=1)) for logit in output_logits]#求得最大的预测范围列表 if EOS_ID in outputs:#如果EOS_ID在输出内部,则输出列表为[,,,,:End] outputs = outputs[:outputs.index(EOS_ID)] response = “".join([tf.compat.as_str(vocab_de[output]) for output in outputs])#转为解码词汇分别添加到回复中 print(‘AI–PigPig > ’ + response)#输出回复测试结果:以下为训练5500步后的测试结果:【最终结果有待更新】傲娇属性get ...

February 15, 2019 · 5 min · jiezi

Transformer-XL: Unleashing the Potential of Attention Models

简介现实远程依赖问题,比如要正确理解文章内容,有时需要阅读多处段落,这对人来说轻松自如。但是,对神经网络来说,远程依赖问题依然是一个挑战。虽然基于门控的RNN(LSTM,GRU等)和梯度裁剪等技术提高了对远程依赖建模的能力,但仍不足以解决问题。其中一个解决方法就是使用Transformers,Transformers允许数据单元直接连接,可以更好的捕获远距离的数据关系。但是,在语音模型中,Transformers一般使用固定长度context实现,即:把文本序列截断为几个固定长度的序列,然后分别单独处理。这存在两个问题:无法计算超过固定长度的依赖关系。序列截断后,造成段落边界破碎,从而造成低效优化,即使是短序列这也是严重问题。为了解决这些问题,可以尝试使用Transformers-XL模型。Transformers-XL由两种技术构成:Segment-level Recurrence和Relative Positional Encodings。Segment-level Recurrence在训练期间,当模型处理下一个新段落时,将前一个段落的计算表示固定并且缓存以作为重用扩展上下文。此附加连接将最大可能的将依赖性长度增加N倍,其中N是网络的深度,因为上下文信息现在能够跨越段落边界流动。此外,这种重复机制还解决了上下文碎片问题。Relative Positional Encodings在标准的Transformer中,序列顺序的信息,都是由一组位置编码提供,每一个位置都有绝对的位置信息。但将这个逻辑应用到重用机制中时,会导致性能损失。这个问题的解决思路是,对隐藏状态中的相对位置信息进行编码。从概念上讲,位置编码为模型提供了关于应如何收集信息的时间线索,即应该在哪里介入处理。以相对的方式定义时间线索,将相同的信息注入每层的注意分数,更加直观,也更通用。基于这个思路,可以创建一组相对位置编码,使得重用机制变得可行,也不会丢失任何的时间信息。将相对位置嵌入Transformer之中,并配合重用机制,就得到了Transformer-XL的架构。基于这些改进,Transformer-XL在相关的数据集上都取得了很好的成绩。论文中表示,这是第一个在字符级和单词级建模方面比RNN结果更好的自注意力模型。总结Transformer-XL 在几种不同的数据集(大 / 小,字符级别 / 单词级别等)均实现了最先进的语言建模结果。它结合了深度学习的两个重要概念——循环机制和注意力机制,允许模型学习长期依赖性,且可能可以扩展到需要该能力的其他深度学习领域,例如音频分析(如每秒 16k 样本的语音数据)等。

February 13, 2019 · 1 min · jiezi

ApacheCN 数据科学/人工智能/机器学习知识树 2019.2

【主页】 apachecn.org【Github】@ApacheCN暂时下线: 社区暂时下线: cwiki 知识库自媒体平台微博:@ApacheCN知乎:@ApacheCNCSDN简书OSChina博客园我们不是 Apache 的官方组织/机构/团体,只是 Apache 技术栈(以及 AI)的爱好者!合作or侵权,请联系【fonttian】<fonttian@gmail.com> | 请抄送一份到 <apachecn@163.com>预处理离散化等值分箱等量分箱独热 one-hot标准化最小最大 min-maxz-scorel2 标准化归一化特征选择ANOVA信息增益/信息增益率模型验证评价指标回归MSER 方分类准确率精确率召回率F1 得分宏平均 F1微平均 F1聚类互信息轮廓距离交叉验证K 折网格搜索最优化方法梯度下降随机梯度下降 SGD牛顿法/拟牛顿法动量法RMSPropAdam传统机器学习基本概念欠拟合/过拟合距离汉明距离曼哈顿距离欧几里得距离切比雪夫距离余弦相似度pearson 相似度损失函数MSE交叉熵Hinge线性模型线性回归Lasso/岭回归正则化逻辑回归softmax 回归支持向量机拉格朗日对偶软边界支持向量机核方法树和森林决策树随机森林GDBT/XGBoostLightGBM集成学习BaggingBoostingAdaboostBlending/StackingKNN聚类KMenas层次聚类凝聚聚类分裂聚类DBSCAN谱聚类高斯混合模型 GMM概率图朴素贝叶斯隐马尔科夫 HMM降维PCA/SVDT-SNE深度学习基本概念正向传播反向传播激活函数sigmoidsoftmaxtanhReLUELULeaky ReLU丢弃 Dropout微调 Fine-Tune批量归一化 BatchNorm前馈神经网络 DNN/多层感知机 MLP输入层隐层输出层卷积神经网络 CNN层卷积层池化层全连接层经典结构LeNetAlexNetZFNetGoogLeNetVGGResNetDenseNet循环神经网络 RNN循环层经典结构LSTMGRUBiLSTM注意力Seq2Seq自编码器栈式自编码器稀疏自编码器去噪自编码器变分自编码器生成对抗网络 GANDCGAN应用领域(待扩展)推荐系统机器视觉 CV自然语言处理 NLP生物信息常用工具数据分析NumPyPandas科学计算SciPy可视化MatplotlibSeaborn机器学习scikit-learn/sklearnXGBoostLightGBM深度学习KerasTensorFlowPyTorch

February 4, 2019 · 1 min · jiezi

Tensorflow分类器项目自定义数据读入

Tensorflow分类器项目自定义数据读入在照着Tensorflow官网的demo敲了一遍分类器项目的代码后,运行倒是成功了,结果也不错。但是最终还是要训练自己的数据,所以尝试准备加载自定义的数据,然而demo中只是出现了fashion_mnist.load_data()并没有详细的读取过程,随后我又找了些资料,把读取的过程记录在这里。首先提一下需要用到的模块:import osimport kerasimport matplotlib.pyplot as pltfrom PIL import Imagefrom keras.preprocessing.image import ImageDataGeneratorfrom sklearn.model_selection import train_test_split图片分类器项目,首先确定你要处理的图片分辨率将是多少,这里的例子为30像素:IMG_SIZE_X = 30IMG_SIZE_Y = 30其次确定你图片的方式目录:image_path = r’D:\Projects\ImageClassifier\data\set’path = “.\data”# 你也可以使用相对路径的方式# image_path =os.path.join(path, “set”)目录下的结构如下:相应的label.txt如下:动漫风景美女物语樱花接下来是接在labels.txt,如下:label_name = “labels.txt"label_path = os.path.join(path, label_name)class_names = np.loadtxt(label_path, type(”"))这里简便起见,直接利用了numpy的loadtxt函数直接加载。之后便是正式处理图片数据了,注释就写在里面了:re_load = Falsere_build = False# re_load = Truere_build = Truedata_name = “data.npz"data_path = os.path.join(path, data_name)model_name = “model.h5"model_path = os.path.join(path, model_name)count = 0# 这里判断是否存在序列化之后的数据,re_load是一个开关,是否强制重新处理,测试用,可以去除。if not os.path.exists(data_path) or re_load: labels = [] images = [] print(‘Handle images’) # 由于label.txt是和图片防止目录的分类目录一一对应的,即每个子目录的目录名就是labels.txt里的一个label,所以这里可以通过读取class_names的每一项去拼接path后读取 for index, name in enumerate(class_names): # 这里是拼接后的子目录path classpath = os.path.join(image_path, name) # 先判断一下是否是目录 if not os.path.isdir(classpath): continue # limit是测试时候用的这里可以去除 limit = 0 for image_name in os.listdir(classpath): if limit >= max_size: break # 这里是拼接后的待处理的图片path imagepath = os.path.join(classpath, image_name) count = count + 1 limit = limit + 1 # 利用Image打开图片 img = Image.open(imagepath) # 缩放到你最初确定要处理的图片分辨率大小 img = img.resize((IMG_SIZE_X, IMG_SIZE_Y)) # 转为灰度图片,这里彩色通道会干扰结果,并且会加大计算量 img = img.convert(“L”) # 转为numpy数组 img = np.array(img) # 由(30,30)转为(1,30,30)(即channels_first),当然你也可以转换为(30,30,1)(即channels_last)但为了之后预览处理后的图片方便这里采用了(1,30,30)的格式存放 img = np.reshape(img, (1, IMG_SIZE_X, IMG_SIZE_Y)) # 这里利用循环生成labels数据,其中存放的实际是class_names中对应元素的索引 labels.append([index]) # 添加到images中,最后统一处理 images.append(img) # 循环中一些状态的输出,可以去除 print(”{} class: {} {} limit: {} {}” .format(count, index + 1, class_names[index], limit, imagepath)) # 最后一次性将images和labels都转换成numpy数组 npy_data = np.array(images) npy_labels = np.array(labels) # 处理数据只需要一次,所以我们选择在这里利用numpy自带的方法将处理之后的数据序列化存储 np.savez(data_path, x=npy_data, y=npy_labels) print(“Save images by npz”)else: # 如果存在序列化号的数据,便直接读取,提高速度 npy_data = np.load(data_path)[“x”] npy_labels = np.load(data_path)[“y”] print(“Load images by npz”)image_data = npy_datalabels_data = npy_labels到了这里原始数据的加工预处理便已经完成,只需要最后一步,就和demo中fashion_mnist.load_data()返回的结果一样了。代码如下:# 最后一步就是将原始数据分成训练数据和测试数据train_images, test_images, train_labels, test_labels = \ train_test_split(image_data, labels_data, test_size=0.2, random_state=6)这里将相关信息打印的方法也附上:print("")print("%-28s %-s" % (“Name”, “Shape”))print("=================================================================")print("%-28s %-s" % (“Image Data”, image_data.shape))print("%-28s %-s" % (“Labels Data”, labels_data.shape))print("=================================================================")print(‘Split train and test data,p=%’)print("")print("%-28s %-s" % (“Name”, “Shape”))print("=================================================================")print("%-28s %-s" % (“Train Images”, train_images.shape))print("%-28s %-s" % (“Test Images”, test_images.shape))print("%-28s %-s" % (“Train Labels”, train_labels.shape))print("%-28s %-s" % (“Test Labels”, test_labels.shape))print("=================================================================")之后别忘了归一化哟:print(“Normalize images”)train_images = train_images / 255.0test_images = test_images / 255.0最后附上读取自定义数据的完整代码:import osimport kerasimport matplotlib.pyplot as pltfrom PIL import Imagefrom keras.layers import *from keras.models import *from keras.optimizers import Adamfrom keras.preprocessing.image import ImageDataGeneratorfrom sklearn.model_selection import train_test_splitos.environ[‘TF_CPP_MIN_LOG_LEVEL’] = ‘2’# 支持中文plt.rcParams[‘font.sans-serif’] = [‘SimHei’] # 用来正常显示中文标签plt.rcParams[‘axes.unicode_minus’] = False # 用来正常显示负号re_load = Falsere_build = False# re_load = Truere_build = Trueepochs = 50batch_size = 5count = 0max_size = 2000000000IMG_SIZE_X = 30IMG_SIZE_Y = 30np.random.seed(9277)image_path = r’D:\Projects\ImageClassifier\data\set’path = “.\data"data_name = “data.npz"data_path = os.path.join(path, data_name)model_name = “model.h5"model_path = os.path.join(path, model_name)label_name = “labels.txt"label_path = os.path.join(path, label_name)class_names = np.loadtxt(label_path, type(””))print(‘Load class names’)if not os.path.exists(data_path) or re_load: labels = [] images = [] print(‘Handle images’) for index, name in enumerate(class_names): classpath = os.path.join(image_path, name) if not os.path.isdir(classpath): continue limit = 0 for image_name in os.listdir(classpath): if limit >= max_size: break imagepath = os.path.join(classpath, image_name) count = count + 1 limit = limit + 1 img = Image.open(imagepath) img = img.resize((30, 30)) img = img.convert(“L”) img = np.array(img) img = np.reshape(img, (1, 30, 30)) # img = skimage.io.imread(imagepath, as_grey=True) # if img.shape[2] != 3: # print(”{} shape is {}".format(image_name, img.shape)) # continue # data = transform.resize(img, (IMG_SIZE_X, IMG_SIZE_Y)) labels.append([index]) images.append(img) print(”{} class: {} {} limit: {} {}" .format(count, index + 1, class_names[index], limit, imagepath)) npy_data = np.array(images) npy_labels = np.array(labels) np.savez(data_path, x=npy_data, y=npy_labels) print(“Save images by npz”)else: npy_data = np.load(data_path)[“x”] npy_labels = np.load(data_path)[“y”] print(“Load images by npz”)image_data = npy_datalabels_data = npy_labelsprint("")print("%-28s %-s" % (“Name”, “Shape”))print("=================================================================")print("%-28s %-s" % (“Image Data”, image_data.shape))print("%-28s %-s" % (“Labels Data”, labels_data.shape))print("=================================================================")train_images, test_images, train_labels, test_labels = \ train_test_split(image_data, labels_data, test_size=0.2, random_state=6)print(‘Split train and test data,p=%’)print("")print("%-28s %-s" % (“Name”, “Shape”))print("=================================================================")print("%-28s %-s" % (“Train Images”, train_images.shape))print("%-28s %-s" % (“Test Images”, test_images.shape))print("%-28s %-s" % (“Train Labels”, train_labels.shape))print("%-28s %-s" % (“Test Labels”, test_labels.shape))print("=================================================================")# 归一化# 我们将这些值缩小到 0 到 1 之间,然后将其馈送到神经网络模型。为此,将图像组件的数据类型从整数转换为浮点数,然后除以 255。以下是预处理图像的函数:# 务必要以相同的方式对训练集和测试集进行预处理:print(“Normalize images”)train_images = train_images / 255.0test_images = test_images / 255.0 ...

February 4, 2019 · 3 min · jiezi

TensorFlow Object Detection API Custom Object Hangs On

TensorFlow Object Detection API Hangs On — Training and Evaluating Custom Object DetectorFirst of AllInstall TensorFlow (I am using TensorFlow CPU this time. In the next post, I will explain how to install TensorFlow GPU)https://www.tensorflow.org/in…Clone the TensorFlow object detection repositoryhttps://github.com/tensorflow…Install the Object Detection API package.MotivationCurrently, I am in charge of an ad-hoc project that used machine vision to detect whether a person is wearing safety goggles. I researched online and tried myself, and it goes well. Then I am thinking of sharing my learning and obstacles as not many people talk about how to deploy the custom trained model. In this post, I will explain all the necessary steps to train your own detector. The process of doing it is shown below:Project Structureimages/ — our dataset of images.labels/ - labels for our dataset.data/ — records and .csv files.train/ — our trained model.eval/ — evaluation results of trained model.output/ - inference graphApp/ - deployment of application.Creating DatasetYou need to prepare images as many as you can for training, but at least need to be more than 5 images per frame. Then hand-labeled them manually with LabelImg. LabelImg is a graphical image annotation tool that is written in Pyandn and uses Qt for the graphical interface. It’s super easy to use and the annotations are saved as XML files in the PASCAL VOC format to be used by the create_tfrecord.py script.After labeling the images, use the xml_to_csv.py script that converts the XML files to a .csv file and then created the TFRecords. I used 80/20 rule for training and testing.Tensorflow Object Detection API uses the TFRecord file format, you need to convert our dataset to this file format. There are several options to generate the TFRecord files. Either you have a dataset that has a similar structure to the PASCAL VOC dataset or the Oxford Pet dataset, then they have ready-made scripts for this case (see create_pascal_tf_record.py and create_pet_tfd_record.py). If you don’t have one of those structures you need to write your own script to generate the TFRecords. I used a custom made script for this!After labeling the images using LabelImg, labeled xml files will be generated. Run the xml_to_csv.py, record down the number of Test Cases printed out in the console. Then generate TF Records for both training and testing using generate_tfrecord.py.To generate train.record file use the code as shown below:python generate_tfrecord.py –csv_input=data/train_labels.csv –output_path=data/train.record –image_dir=imagesTo generate test.record file use the code as shown below:python generate_tfrecord.py –csv_input=data/test_labels.csv –output_path=data/test.record –image_dir=imagesTraining the modelOnce our records files are ready, you are almost ready to train the model.Firstly, you need to decide the pre-trained model to be used. There’s a tradeoff between detection speed and accuracy, higher the speed lower the accuracy and vice versa. After some trails, I am using faster_rcnn_inception_v2_coco for my project.After deciding the model to be used, you will need an object detection training pipeline. They also provide sample config files on the repo. For my training, I will download faster_rcnn_inception_v2_coco.config.Then you will need the dataset’s (TFRecord files) corresponding label map. Example of how to create label maps can be found here. Here is also my label map which was very simple since I had only two classes, make a new file pascal_label_map.pbtxt which looks like this:item { id: 1 name: ‘pos’}item { id: 2 name: ’neg’}It is important to configure the faster_rcnn_inception_v2_coco.config file. You need to change it based on your configurations.Change the number of classes in the file according to our requirement.#beforenum_classes: 90#Afternum_classes: 2Change the total number of steps, depends on the complexity.#beforenum_steps: 200000#Afternum_steps: 1000If your PC does not have good GPU then you need to decrease the batch_size.#beforebatch_size: 24#Afterbatch_size: 1Give the path to downloaded model i.e faster_rcnn_inception_v2_coco, the model we decided to be used.#beforefine_tune_checkpoint: “PATH_TO_BE_CONFIGURED/model.ckpt”#afterfine_tune_checkpoint: “faster_rcnn_inception_v2_coco/model.ckpt"Give the path to train.record file.#beforetrain_input_reader: { tf_record_input_reader { input_path: “PATH_TO_BE_CONFIGURED/mscoco_train.record”}label_map_path: “PATH_TO_BE_CONFIGURED/mscoco_label_map.pbtxt”}#aftertrain_input_reader: { tf_record_input_reader { input_path: “data/train.record”}label_map_path: “data/pascal_label_map.pbtxt”}Give path for test.record file#beforeeval_input_reader: { tf_record_input_reader {input_path: “PATH_TO_BE_CONFIGURED/mscoco_val.record” }label_map_path: “PATH_TO_BE_CONFIGURED/mscoco_label_map.pbtxt” shuffle: falsenum_readers: 1}#aftereval_input_reader: { tf_record_input_reader {input_path: “data/test.record” }label_map_path: “data/pascal_label_map.pbtxt” shuffle: falsenum_readers: 1}Now, copy train.py from models/research/object-detection directory of the TensorFlow object detection repo.python train.py –logtostderr –train_dir=train/ –pipeline_config_path=faster_rcnn_inception_v2_coco.configIf everything goes right, you will see the loss at a particular step.Training can be either done locally or on the cloud (AWS, Google Cloud etc.). If you have a good GPU at home then you can do it locally otherwise I would recommend going with the cloud. In my case, a 3.8G Hz i5 processor takes about 2 hours for the training, still acceptable.Evaluating the modelThe final step is to evaluate the trained model saved in train/ directory. You need to edit the faster_rcnn_inception_v2_coco.config file change to num_examples to the number of the Test Cases that be printed out of xml_to_csv.py.eval_config: { num_examples: 31 # Note: The below line limits the evaluation process to 10 evaluations. # Remove the below line to evaluate indefinitely. max_evals: 10}You need to copy the eval.py file from the repo and evaluate using the following command:python eval.py –logtostderr –pipeline_config_path=data/faster_rcnn_inception_v2_coco.config –checkpoint_dir=train/ –eval_dir=eval/This will save the eval results in eval/ directory. To visualize the results we will use tensorboard.#To visualize the eval resultstensorboard –logdir=eval/#TO visualize the training resultstensorboard –logdir=training/Open the link in a browser and under Images tag you can see the results.Exporting the modelCopy the exporter.py and export_inference_graph.py from the object detection repo and run the following command, the number of steps depends on your configuration:python export_inference_graph.py –pipeline_config_path=faster_rcnn_inception_v2_coco.config –output_directory=output –trained_checkpoint_prefix=train/model.ckpt-1000Deployment of the modelYou need to copy the utils folder from the object detection repo to the App folder and create a app.py file.#app.pyimport numpy as npimport osimport six.moves.urllib as urllibimport sysimport tarfileimport tensorflow as tfimport zipfilefrom distutils.version import StrictVersionfrom collections import defaultdictfrom io import StringIOfrom matplotlib import pyplot as pltfrom PIL import Imageimport cv2import timefrom object_detection.utils import ops as utils_ops# This is needed since the notebook is stored in the object_detection folder.sys.path.append(”..")if StrictVersion(tf.version) < StrictVersion(‘1.9.0’): raise ImportError(‘Please upgrade your TensorFlow installation to v1.9.* or later!’)DIR = “/Users/iot_imac/Safety_Goggles/“from utils import label_map_utilfrom utils import visualization_utils as vis_util# Path to frozen detection graph. This is the actual model that is used for the object detection.PATH_TO_FROZEN_GRAPH = os.path.join(DIR, ‘output’, ‘frozen_inference_graph.pb’)# List of the strings that is used to add correct label for each box.PATH_TO_LABELS = os.path.join(DIR, ‘data/pascal_label_map.pbtxt’)detection_graph = tf.Graph()with detection_graph.as_default(): od_graph_def = tf.GraphDef() with tf.gfile.GFile(PATH_TO_FROZEN_GRAPH, ‘rb’) as fid: serialized_graph = fid.read() od_graph_def.ParseFromString(serialized_graph) tf.import_graph_def(od_graph_def, name=’’) sess = tf.Session() # Get handles to input and output tensors ops = tf.get_default_graph().get_operations() all_tensor_names = { output.name for op in ops for output in op.outputs} tensor_dict = {} for key in [ ’num_detections’, ‘detection_boxes’, ‘detection_scores’, ‘detection_classes’, ‘detection_masks’ ]: tensor_name = key + ‘:0’ if tensor_name in all_tensor_names: tensor_dict[key] = tf.get_default_graph().get_tensor_by_name( tensor_name) if ‘detection_masks’ in tensor_dict: # The following processing is only for single image detection_boxes = tf.squeeze( tensor_dict[‘detection_boxes’], [0]) detection_masks = tf.squeeze( tensor_dict[‘detection_masks’], [0]) # Reframe is required to translate mask from box coordinates to # image coordinates and fit the image size. real_num_detection = tf.cast( tensor_dict[’num_detections’][0], tf.int32) detection_boxes = tf.slice(detection_boxes, [0, 0], [ real_num_detection, -1]) detection_masks = tf.slice(detection_masks, [0, 0, 0], [ real_num_detection, -1, -1]) detection_masks_reframed = utils_ops.reframe_box_masks_to_image_masks( detection_masks, detection_boxes, image.shape[0], image.shape[1]) detection_masks_reframed = tf.cast( tf.greater(detection_masks_reframed, 0.5), tf.uint8) # Follow the convention by adding back the batch dimension tensor_dict[‘detection_masks’] = tf.expand_dims( detection_masks_reframed, 0) image_tensor = tf.get_default_graph().get_tensor_by_name(‘image_tensor:0’)category_index = label_map_util.create_category_index_from_labelmap(PATH_TO_LABELS, use_display_name=True)def run_inference_for_single_image(image): # Run inference output_dict = sess.run(tensor_dict, feed_dict={image_tensor: np.expand_dims(image, 0)}) # all outputs are float32 numpy arrays, so convert types as # appropriate output_dict[’num_detections’] = int( output_dict[’num_detections’][0]) output_dict[‘detection_classes’] = output_dict[ ‘detection_classes’][0].astype(np.uint8) output_dict[‘detection_boxes’] = output_dict[‘detection_boxes’][0] output_dict[‘detection_scores’] = output_dict[ ‘detection_scores’][0] if ‘detection_masks’ in output_dict: output_dict[‘detection_masks’] = output_dict[ ‘detection_masks’][0] return output_dictcap = cv2.VideoCapture(0)cap.set(3, 640)cap.set(4, 480)time.sleep(2)while(True): start = time.time() ret, frame = cap.read() output_dict = run_inference_for_single_image(frame) vis_util.visualize_boxes_and_labels_on_image_array( frame, output_dict[‘detection_boxes’], output_dict[‘detection_classes’], output_dict[‘detection_scores’], category_index, instance_masks=output_dict.get(‘detection_masks’), use_normalized_coordinates=True, line_thickness=8) cv2.imshow(‘frame’, frame) end = time.time() print(‘Time Taken: %f’ % (end - start)) if cv2.waitKey(1) & 0xFF == ord(‘q’): breakcap.release()cv2.destroyAllWindows() ...

January 31, 2019 · 6 min · jiezi

ApacheCN 学习资源汇总 2019.1

【主页】 apachecn.org【Github】@ApacheCN暂时下线: 社区暂时下线: cwiki 知识库自媒体平台微博:@ApacheCN知乎:@ApacheCNCSDN简书OSChina博客园我们不是 Apache 的官方组织/机构/团体,只是 Apache 技术栈(以及 AI)的爱好者!合作or侵权,请联系【fonttian】<fonttian@gmail.com> | 请抄送一份到 <apachecn@163.com>Java 基础Java 编程思想Java Web 和大数据Spark 2.2.0 中文文档Storm 1.1.0 中文文档Kafka 1.0.0 中文文档Beam 中文文档Zeppelin 0.7.2 中文文档Elasticsearch 5.4 中文文档Kibana 5.2 中文文档Kudu 1.4.0 中文文档Spring Boot 1.5.2 中文文档Airflow 中文文档区块链Solidity 中文文档数学笔记MIT 18.06 线性代数笔记Python 数据科学NumPy 中文文档Pandas 中文文档Matplotlib 中文文档UCB Data8 课本:计算与推断思维UCB Prob140 课本:面向数据科学的概率论UCB DS100 课本:数据科学的原理与技巧利用 Python 进行数据分析 · 第 2 版fast.ai 数值线性代数讲义 v2Pandas Cookbook 带注释源码statsmodels 中文文档数据科学 IPython 笔记本CS 教程LeetCode 中文文档GeeksForGeeks 翻译计划UCB CS61a 课本:SICP Python 描述UCB CS61b 课本:Java 中的数据结构数据结构思维中国大学 MOOC 计算机操作系统笔记简单数据结构实现AI 教程AILearning - 机器学习实战Sklearn 与 TensorFlow 机器学习实用指南面向机器学习的特征工程Python 数据分析与挖掘实战(带注释源码)SciPyCon 2018 Sklearn 教程TensorFlow 学习指南fast.ai 机器学习和深度学习中文笔记HackCV 网站文章翻译台湾大学林轩田机器学习笔记Scikit-learn 秘籍写给人类的机器学习数据科学和人工智能技术笔记AI 文档Sklearn 0.19 中文文档PyTorch 1.0 中文文档XGBoost 中文文档LightGBM 中文文档FastText 中文文档Gensim 中文文档AI 比赛Kaggle 项目实战教程:文档 + 代码 + 视频比赛收集平台其它独立开发/自由职业/远程工作资源列表通往财富自由之路精细笔记5 分钟商学院精细笔记 ...

January 29, 2019 · 1 min · jiezi

TensorFlow 2.0深度强化学习指南

摘要: 用深度强化学习来展示TensorFlow 2.0的强大特性!在本教程中,我将通过实施Advantage Actor-Critic(演员-评论家,A2C)代理来解决经典的CartPole-v0环境,通过深度强化学习(DRL)展示即将推出的TensorFlow2.0特性。虽然我们的目标是展示TensorFlow2.0,但我将尽最大努力让DRL的讲解更加平易近人,包括对该领域的简要概述。事实上,由于2.0版本的焦点是让开发人员的生活变得更轻松,所以我认为现在是使用TensorFlow进入DRL的好时机,本文用到的例子的源代码不到150行!代码可以在这里或者这里获取。建立由于TensorFlow2.0仍处于试验阶段,我建议将其安装在独立的虚拟环境中。我个人比较喜欢Anaconda,所以我将用它来演示安装过程:> conda create -n tf2 python=3.6> source activate tf2> pip install tf-nightly-2.0-preview # tf-nightly-gpu-2.0-preview for GPU version让我们快速验证一切是否按能够正常工作:>>> import tensorflow as tf>>> print(tf.version)1.13.0-dev20190117>>> print(tf.executing_eagerly())True不要担心1.13.x版本,这只是意味着它是早期预览。这里要注意的是我们默认处于eager模式!>>> print(tf.reduce_sum([1, 2, 3, 4, 5]))tf.Tensor(15, shape=(), dtype=int32)如果你还不熟悉eager模式,那么实质上意味着计算是在运行时被执行的,而不是通过预编译的图(曲线图)来执行。你可以在TensorFlow文档中找到一个很好的概述。深度强化学习一般而言,强化学习是解决连续决策问题的高级框架。RL通过基于某些agent进行导航观察环境,并且获得奖励。大多数RL算法通过最大化代理在一轮游戏期间收集的奖励总和来工作。基于RL的算法的输出通常是policy(策略)-将状态映射到函数有效的策略中,有效的策略可以像硬编码的无操作动作一样简单。在某些状态下,随机策略表示为行动的条件概率分布。演员,评论家方法(Actor-Critic Methods)RL算法通常基于它们优化的目标函数进行分组。Value-based诸如DQN之类的方法通过减少预期的状态-动作值的误差来工作。策略梯度(Policy Gradients)方法通过调整其参数直接优化策略本身,通常通过梯度下降完成的。完全计算梯度通常是难以处理的,因此通常要通过蒙特卡罗方法估算它们。最流行的方法是两者的混合:actor-critic方法,其中代理策略通过策略梯度进行优化,而基于值的方法用作预期值估计的引导。深度演员-批评方法虽然很多基础的RL理论是在表格案例中开发的,但现代RL几乎完全是用函数逼近器完成的,例如人工神经网络。具体而言,如果策略和值函数用深度神经网络近似,则RL算法被认为是“深度”。异步优势演员-评论家(actor-critical)多年来,为了提高学习过程的样本效率和稳定性,技术发明者已经进行了一些改进。首先,梯度加权回报:折现的未来奖励,这在一定程度上缓解了信用分配问题,并以无限的时间步长解决了理论问题。其次,使用优势函数代替原始回报。优势在收益与某些基线之间的差异之间形成,并且可以被视为衡量给定值与某些平均值相比有多好的指标。第三,在目标函数中使用额外的熵最大化项以确保代理充分探索各种策略。本质上,熵以均匀分布最大化来测量概率分布的随机性。最后,并行使用多个工人加速样品采集,同时在训练期间帮助它们去相关。将所有这些变化与深度神经网络相结合,我们得出了两种最流行的现代算法:异步优势演员评论家(actor-critical)算法,简称A3C或者A2C。两者之间的区别在于技术性而非理论性:顾名思义,它归结为并行工人如何估计其梯度并将其传播到模型中。有了这个,我将结束我们的DRL方法之旅,因为博客文章的重点更多是关于TensorFlow2.0的功能。如果你仍然不了解该主题,请不要担心,代码示例应该更清楚。如果你想了解更多,那么一个好的资源就可以开始在Deep RL中进行Spinning Up了。使用TensorFlow 2.0的优势演员-评论家让我们看看实现现代DRL算法的基础是什么:演员评论家代理(actor-critic agent)。如前一节所述,为简单起见,我们不会实现并行工作程序,尽管大多数代码都会支持它,感兴趣的读者可以将其用作锻炼机会。作为测试平台,我们将使用CartPole-v0环境。虽然它有点简单,但它仍然是一个很好的选择开始。在实现RL算法时,我总是依赖它作为一种健全性检查。通过Keras Model API实现的策略和价值首先,让我们在单个模型类下创建策略和价值估计NN:import numpy as npimport tensorflow as tfimport tensorflow.keras.layers as klclass ProbabilityDistribution(tf.keras.Model): def call(self, logits): # sample a random categorical action from given logits return tf.squeeze(tf.random.categorical(logits, 1), axis=-1)class Model(tf.keras.Model): def init(self, num_actions): super().init(‘mlp_policy’) # no tf.get_variable(), just simple Keras API self.hidden1 = kl.Dense(128, activation=‘relu’) self.hidden2 = kl.Dense(128, activation=‘relu’) self.value = kl.Dense(1, name=‘value’) # logits are unnormalized log probabilities self.logits = kl.Dense(num_actions, name=‘policy_logits’) self.dist = ProbabilityDistribution() def call(self, inputs): # inputs is a numpy array, convert to Tensor x = tf.convert_to_tensor(inputs, dtype=tf.float32) # separate hidden layers from the same input tensor hidden_logs = self.hidden1(x) hidden_vals = self.hidden2(x) return self.logits(hidden_logs), self.value(hidden_vals) def action_value(self, obs): # executes call() under the hood logits, value = self.predict(obs) action = self.dist.predict(logits) # a simpler option, will become clear later why we don’t use it # action = tf.random.categorical(logits, 1) return np.squeeze(action, axis=-1), np.squeeze(value, axis=-1)验证我们验证模型是否按预期工作:import gymenv = gym.make(‘CartPole-v0’)model = Model(num_actions=env.action_space.n)obs = env.reset()# no feed_dict or tf.Session() needed at allaction, value = model.action_value(obs[None, :])print(action, value) # [1] [-0.00145713]这里要注意的事项:模型层和执行路径是分开定义的;没有“输入”图层,模型将接受原始numpy数组;可以通过函数API在一个模型中定义两个计算路径;模型可以包含一些辅助方法,例如动作采样;在eager的模式下,一切都可以从原始的numpy数组中运行;随机代理现在我们可以继续学习一些有趣的东西A2CAgent类。首先,让我们添加一个贯穿整集的test方法并返回奖励总和。class A2CAgent: def init(self, model): self.model = model def test(self, env, render=True): obs, done, ep_reward = env.reset(), False, 0 while not done: action, _ = self.model.action_value(obs[None, :]) obs, reward, done, _ = env.step(action) ep_reward += reward if render: env.render() return ep_reward让我们看看我们的模型在随机初始化权重下得分多少:agent = A2CAgent(model)rewards_sum = agent.test(env)print("%d out of 200" % rewards_sum) # 18 out of 200离最佳转台还有很远,接下来是训练部分!损失/目标函数正如我在DRL概述部分所描述的那样,代理通过基于某些损失(目标)函数的梯度下降来改进其策略。在演员评论家中,我们训练了三个目标:用优势加权梯度加上熵最大化来改进策略,并最小化价值估计误差。import tensorflow.keras.losses as klsimport tensorflow.keras.optimizers as koclass A2CAgent: def init(self, model): # hyperparameters for loss terms self.params = {‘value’: 0.5, ’entropy’: 0.0001} self.model = model self.model.compile( optimizer=ko.RMSprop(lr=0.0007), # define separate losses for policy logits and value estimate loss=[self._logits_loss, self._value_loss] ) def test(self, env, render=True): # unchanged from previous section … def _value_loss(self, returns, value): # value loss is typically MSE between value estimates and returns return self.params[‘value’]*kls.mean_squared_error(returns, value) def _logits_loss(self, acts_and_advs, logits): # a trick to input actions and advantages through same API actions, advantages = tf.split(acts_and_advs, 2, axis=-1) # polymorphic CE loss function that supports sparse and weighted options # from_logits argument ensures transformation into normalized probabilities cross_entropy = kls.CategoricalCrossentropy(from_logits=True) # policy loss is defined by policy gradients, weighted by advantages # note: we only calculate the loss on the actions we’ve actually taken # thus under the hood a sparse version of CE loss will be executed actions = tf.cast(actions, tf.int32) policy_loss = cross_entropy(actions, logits, sample_weight=advantages) # entropy loss can be calculated via CE over itself entropy_loss = cross_entropy(logits, logits) # here signs are flipped because optimizer minimizes return policy_loss - self.params[’entropy’]*entropy_loss我们完成了目标函数!请注意代码的紧凑程度:注释行几乎比代码本身多。代理训练循环最后,还有训练回路本身,它相对较长,但相当简单:收集样本,计算回报和优势,并在其上训练模型。class A2CAgent: def init(self, model): # hyperparameters for loss terms self.params = {‘value’: 0.5, ’entropy’: 0.0001, ‘gamma’: 0.99} # unchanged from previous section … def train(self, env, batch_sz=32, updates=1000): # storage helpers for a single batch of data actions = np.empty((batch_sz,), dtype=np.int32) rewards, dones, values = np.empty((3, batch_sz)) observations = np.empty((batch_sz,) + env.observation_space.shape) # training loop: collect samples, send to optimizer, repeat updates times ep_rews = [0.0] next_obs = env.reset() for update in range(updates): for step in range(batch_sz): observations[step] = next_obs.copy() actions[step], values[step] = self.model.action_value(next_obs[None, :]) next_obs, rewards[step], dones[step], _ = env.step(actions[step]) ep_rews[-1] += rewards[step] if dones[step]: ep_rews.append(0.0) next_obs = env.reset() _, next_value = self.model.action_value(next_obs[None, :]) returns, advs = self._returns_advantages(rewards, dones, values, next_value) # a trick to input actions and advantages through same API acts_and_advs = np.concatenate([actions[:, None], advs[:, None]], axis=-1) # performs a full training step on the collected batch # note: no need to mess around with gradients, Keras API handles it losses = self.model.train_on_batch(observations, [acts_and_advs, returns]) return ep_rews def _returns_advantages(self, rewards, dones, values, next_value): # next_value is the bootstrap value estimate of a future state (the critic) returns = np.append(np.zeros_like(rewards), next_value, axis=-1) # returns are calculated as discounted sum of future rewards for t in reversed(range(rewards.shape[0])): returns[t] = rewards[t] + self.params[‘gamma’] * returns[t+1] * (1-dones[t]) returns = returns[:-1] # advantages are returns - baseline, value estimates in our case advantages = returns - values return returns, advantages def test(self, env, render=True): # unchanged from previous section … def value_loss(self, returns, value): # unchanged from previous section … def logits_loss(self, acts_and_advs, logits): # unchanged from previous section …训练和结果我们现在已经准备好在CartPole-v0上训练我们的单工A2C代理了!训练过程不应超过几分钟,训练完成后,你应该看到代理成功达到200分中的目标。rewards_history = agent.train(env)print(“Finished training, testing…")print("%d out of 200” % agent.test(env)) # 200 out of 200在源代码中,我包含了一些额外的帮助程序,可以打印出运行的奖励和损失,以及rewards_history的基本绘图仪。静态计算图有了所有这种渴望模式的成功的喜悦,你可能想知道静态图形执行是否可以。当然!此外,我们还需要多一行代码来启用它!with tf.Graph().as_default(): print(tf.executing_eagerly()) # False model = Model(num_actions=env.action_space.n) agent = A2CAgent(model) rewards_history = agent.train(env) print(“Finished training, testing…”) print("%d out of 200" % agent.test(env)) # 200 out of 200有一点需要注意,在静态图形执行期间,我们不能只有Tensors,这就是为什么我们在模型定义期间需要使用CategoricalDistribution的技巧。事实上,当我在寻找一种在静态模式下执行的方法时,我发现了一个关于通过Keras API构建的模型的一个有趣的低级细节。还有一件事…还记得我说过TensorFlow默认是运行在eager模式下吧,甚至用代码片段证明它吗?好吧,我错了。如果你使用Keras API来构建和管理模型,那么它将尝试将它们编译为静态图形。所以你最终得到的是静态计算图的性能,具有渴望执行的灵活性。你可以通过model.run_eagerly标志检查模型的状态,你也可以通过设置此标志来强制执行eager模式变成True,尽管大多数情况下你可能不需要这样做。但如果Keras检测到没有办法绕过eager模式,它将自动退出。为了说明它确实是作为静态图运行,这里是一个简单的基准测试:# create a 100000 samples batchenv = gym.make(‘CartPole-v0’)obs = np.repeat(env.reset()[None, :], 100000, axis=0)Eager基准%%timemodel = Model(env.action_space.n)model.run_eagerly = Trueprint(“Eager Execution: “, tf.executing_eagerly())print(“Eager Keras Model:”, model.run_eagerly) = model(obs)######## Results #######Eager Execution: TrueEager Keras Model: TrueCPU times: user 639 ms, sys: 736 ms, total: 1.38 s静态基准%%timewith tf.Graph().as_default(): model = Model(env.action_space.n) print(“Eager Execution: “, tf.executing_eagerly()) print(“Eager Keras Model:”, model.run_eagerly) _ = model.predict(obs)######## Results #######Eager Execution: FalseEager Keras Model: FalseCPU times: user 793 ms, sys: 79.7 ms, total: 873 ms默认基准%%timemodel = Model(env.action_space.n)print(“Eager Execution: “, tf.executing_eagerly())print(“Eager Keras Model:”, model.run_eagerly) = model.predict(obs)######## Results #######Eager Execution: TrueEager Keras Model: FalseCPU times: user 994 ms, sys: 23.1 ms, total: 1.02 s正如你所看到的,eager模式是静态模式的背后,默认情况下,我们的模型确实是静态执行的。结论希望本文能够帮助你理解DRL和TensorFlow2.0。请注意,TensorFlow2.0仍然只是预览版本,甚至不是候选版本,一切都可能发生变化。如果TensorFlow有什么东西你特别不喜欢,让它的开发者知道!人们可能会有一个挥之不去的问题:TensorFlow比PyTorch好吗?也许,也许不是。它们两个都是伟大的库,所以很难说这样谁好,谁不好。如果你熟悉PyTorch,你可能已经注意到TensorFlow 2.0不仅赶上了它,而且还避免了一些PyTorch API的缺陷。在任何一种情况下,对于开发者来说,这场竞争都已经为双方带来了积极的结果,我很期待看到未来的框架将会变成什么样。本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 28, 2019 · 4 min · jiezi

使用DeepLab进行语义分割

介绍DeepLab是谷歌使用tensorflow基于CNN开发的语义分割模型,至今已更新4个版本。最新版本是DeepLabv3+,在此模型中进一步将深度可分离卷积应用到孔空间金字塔池化和解码器模块,从而形成更快,更强大的语义分割编码器-解码器网络。本文从官方案例出发,介绍如何训练以及使用DeepLabv3+模型。模型安装1,下载tensorflow model,然后将解压后的文件重命名为”models“并移动到tensorflow文件夹下。2,添加环境变量cd /anaconda3/lib/python3.6/site-packages/tensorflow/models/researchexport PYTHONPATH=$PYTHONPATH:pwd:pwd/slim3,测试是否安装成功cd /anaconda3/lib/python3.6/site-packages/tensorflow/models/researchpython deeplab/model_test.py结果出现如下错误:解决方法如下:准备数据下载数据:cd /anaconda3/lib/python3.6/site-packages/tensorflow/models/research/deeplab/datasets/sh download_and_convert_voc2012.sh具体执行步骤:下载并解压VOC2012数据集移除colormap在数据集中创建TFRecord文件下载预训练模型官方提供了多种模型,可以根据自己的实际需求下载安装。使用PASCAL VOC2012数据集训练文件结构:tensorflow deeplab文件夹:训练:cd /anaconda3/lib/python3.6/site-packages/tensorflow/models/research填写相关路径,执行训练python deeplab/train.py \ –logtostderr \ –training_number_of_steps=30000 \ –train_split=“train” \ –model_variant=“xception_65” \ –atrous_rates=6 \ –atrous_rates=12 \ –atrous_rates=18 \ –output_stride=16 \ –decoder_output_stride=4 \ –train_crop_size=513 \ –train_crop_size=513 \ –train_batch_size=1 \ –dataset=“pascal_voc_seg” \ –tf_initial_checkpoint=${PATH_TO_INITIAL_CHECKPOINT} \ –train_logdir=${PATH_TO_TRAIN_DIR} \ –dataset_dir=${PATH_TO_DATASET}PATH_TO_INITIAL_CHECKPOINT:初始checkpoint文件路径(迁移学习)PATH_TO_TRAIN_DIR:训练模型保存路径PATH_TO_DATASET:数据集路径需要注意的问题:当GPU显存不够,使用迁移学习方式进行训练并减少学习率大小,设置”fine_tune_batch_norm=False“当设置”output_stride=8“,”atrous_rates“取值区间应由[6,12,18]改为[12,24,36]。当不想使用解码器结构,需要注释掉”decoder_output_stride“。验证cd /anaconda3/lib/python3.6/site-packages/tensorflow/models/researchpython deeplab/eval.py \ –logtostderr \ –eval_split=“val” \ –model_variant=“xception_65” \ –atrous_rates=6 \ –atrous_rates=12 \ –atrous_rates=18 \ –output_stride=16 \ –decoder_output_stride=4 \ –eval_crop_size=513 \ –eval_crop_size=513 \ –dataset=“pascal_voc_seg” \ –checkpoint_dir=${PATH_TO_CHECKPOINT} \ –eval_logdir=${PATH_TO_EVAL_DIR} \ –dataset_dir=${PATH_TO_DATASET}PATH_TO_CHECKPOINT:训练阶段checkpoint文件路径PATH_TO_EVAL_DIR:评估模型保存路径PATH_TO_DATASET:数据集路径可视化模型输出cd /anaconda3/lib/python3.6/site-packages/tensorflow/models/researchpython deeplab/vis.py \ –logtostderr \ –vis_split=“val” \ –model_variant=“xception_65” \ –atrous_rates=6 \ –atrous_rates=12 \ –atrous_rates=18 \ –output_stride=16 \ –decoder_output_stride=4 \ –vis_crop_size=513 \ –vis_crop_size=513 \ –dataset=“pascal_voc_seg” \ –checkpoint_dir=${PATH_TO_CHECKPOINT} \ –vis_logdir=${PATH_TO_VIS_DIR} \ –dataset_dir=${PATH_TO_DATASET}PATH_TO_CHECKPOINT:训练阶段checkpoint文件路径PATH_TO_VIS_DIR:评估模型保存路径PATH_TO_DATASET:数据集路径需要注意的问题:当需要保存分割结果时,需要设置”also_save_raw_predictions = True“Tensorboardtensorboard –logdir=${PATH_TO_LOG_DIRECTORY}将同时显示”train“,”eval“,”vis“结果,如需要单独显示一类,可以指定显示类别,如:tensorboard –logdir train/对于voc2012这样的数据集,Tensorboard显示数据可能需要几分钟的时间。模型导出cd /anaconda3/lib/python3.6/site-packages/tensorflow/models/researchpython deeplab/export_model.py \ –logtostderr \ –checkpoint_path="${PATH_TO_CHECKPOINT}" \ –export_path="${PATH_TO_EXPORT_PD}" \ –model_variant=“xception_65” \ –atrous_rates=6 \ –atrous_rates=12 \ –atrous_rates=18 \ –output_stride=16 \ –decoder_output_stride=4 \ –num_classes=21 \ –crop_size=513 \ –crop_size=513 \ –inference_scales=1.0PATH_TO_CHECKPOINT:checkpoint文件路径PATH_TO_EXPORT_PD:导出PD文件路径(加后缀.pd)案例cd /anaconda3/lib/python3.6/site-packages/tensorflow/models/research/deeplabsh local_test.shGoogle Colab在线测试地址,可选择预训练模型,测试分割照片来查看模型输出结果。总结基于深度学习的语义分割与传统基于随机森林的方法相比有了很大的进步,虽然目前基于深度学习的方法有多种思路,不过基本都是基于全卷积(FCN)衍变而来。FCN将网络全连接层使用卷积替代,因此可以接受任意大小的输入并且针对下采样存在位置信息丢失等问题,采用转置卷积用于恢复图片尺寸,方便后续进行逐像素预测。DeepLab模型为了改善分割效果,使用atrous卷积(空洞卷积),ASPP,全连接条件随机场等技术。 ...

January 21, 2019 · 1 min · jiezi

TensorFlow分布式实践

作者:个推数据建模工程师 陈玉琪大数据时代,基于单机的建模很难满足企业不断增长的数据量级的需求,开发者需要使用分布式的开发方式,在集群上进行建模。而单机和分布式的开发代码有一定的区别,本文就将为开发者们介绍,基于TensorFlow进行分布式开发的两种方式,帮助开发者在实践的过程中,更好地选择模块的开发方向。基于TensorFlow原生的分布式开发分布式开发会涉及到更新梯度的方式,有同步和异步的两个方案,同步更新的方式在模型的表现上能更快地进行收敛,而异步更新时,迭代的速度则会更加快。两种更新方式的图示如下:同步更新流程(图片来源:TensorFlow:Large-Scale Machine Learning on Heterogeneous Distributed Systems)异步更新流程(图片来源:TensorFlow:Large-Scale Machine Learning on Heterogeneous Distributed Systems)TensorFlow是基于ps、work 两种服务器进行分布式的开发。ps服务器可以只用于参数的汇总更新,让各个work进行梯度的计算。基于TensorFlow原生的分布式开发的具体流程如下:首先指定ps 服务器启动参数 –job_name=ps:python distribute.py –ps_hosts=192.168.100.42:2222 –worker_hosts=192.168.100.42:2224,192.168.100.253:2225 –job_name=ps –task_index=0接着指定work服务器参数(启动两个work 节点) –job_name=work2:python distribute.py –ps_hosts=192.168.100.42:2222 –worker_hosts=192.168.100.42:2224,192.168.100.253:2225 –job_name=worker –task_index=0python distribute.py –ps_hosts=192.168.100.42:2222 –worker_hosts=192.168.100.42:2224,192.168.100.253:2225 –job_name=worker –task_index=1之后,上述指定的参数 worker_hosts ps_hosts job_name task_index 都需要在py文件中接受使用:tf.app.flags.DEFINE_string(“worker_hosts”, “默认值”, “描述说明”)接收参数后,需要分别注册ps、work,使他们各司其职:ps_hosts = FLAGS.ps_hosts.split(",")worker_hosts = FLAGS.worker_hosts.split(",")cluster = tf.train.ClusterSpec({“ps”: ps_hosts, “worker”: worker_hosts})server = tf.train.Server(cluster,job_name=FLAGS.job_name,task_index=FLAGS.task_index)issync = FLAGS.issyncif FLAGS.job_name == “ps”: server.join()elif FLAGS.job_name == “worker”: with tf.device(tf.train.replica_device_setter( worker_device="/job:worker/task:%d" % FLAGS.task_index, cluster=cluster)):继而更新梯度。(1)同步更新梯度:rep_op = tf.train.SyncReplicasOptimizer(optimizer, replicas_to_aggregate=len(worker_hosts), replica_id=FLAGS.task_index, total_num_replicas=len(worker_hosts), use_locking=True)train_op = rep_op.apply_gradients(grads_and_vars,global_step=global_step)init_token_op = rep_op.get_init_tokens_op()chief_queue_runner = rep_op.get_chief_queue_runner()(2)异步更新梯度:train_op = optimizer.apply_gradients(grads_and_vars,global_step=global_step)最后,使用tf.train.Supervisor 进行真的迭代另外,开发者还要注意,如果是同步更新梯度,则还需要加入如下代码:sv.start_queue_runners(sess, [chief_queue_runner])sess.run(init_token_op)需要注意的是,上述异步的方式需要自行指定集群IP和端口,不过,开发者们也可以借助TensorFlowOnSpark,使用Yarn进行管理。基于TensorFlowOnSpark的分布式开发作为个推面向开发者服务的移动APP数据统计分析产品,个数所具有的用户行为预测功能模块,便是基于TensorFlowOnSpark这种分布式来实现的。基于TensorFlowOnSpark的分布式开发使其可以在屏蔽了端口和机器IP的情况下,也能够做到较好的资源申请和分配。而在多个千万级应用同时建模的情况下,集群也有良好的表现,在sparkUI中也能看到相对应的资源和进程的情况。最关键的是,TensorFlowOnSpark可以在单机过度到分布式的情况下,使代码方便修改,且容易部署。基于TensorFlowOnSpark的分布式开发的具体流程如下:首先,需要使用spark-submit来提交任务,同时指定spark需要运行的参数(–num-executors 6等)、模型代码、模型超参等,同样需要接受外部参数:parser = argparse.ArgumentParser()parser.add_argument("-i", “–tracks”, help=“数据集路径”) args = parser.parse_args()之后,准备好参数和训练数据(DataFrame),调用模型的API进行启动。其中,soft_dist.map_fun是要调起的方法,后面均是模型训练的参数。estimator = TFEstimator(soft_dist.map_fun, args) \ .setInputMapping({’tracks’: ’tracks’, ’label’: ’label’}) \ .setModelDir(args.model) \ .setExportDir(args.serving) \ .setClusterSize(args.cluster_size) \ .setNumPS(num_ps) \ .setEpochs(args.epochs) \ .setBatchSize(args.batch_size) \ .setSteps(args.max_steps) model = estimator.fit(df)接下来是soft_dist定义一个 map_fun(args, ctx)的方法:def map_fun(args, ctx):…worker_num = ctx.worker_num # worker数量job_name = ctx.job_name # job名task_index = ctx.task_index # 任务索引if job_name == “ps”: # ps节点(主节点) time.sleep((worker_num + 1) * 5) cluster, server = TFNode.start_cluster_server(ctx, 1, args.rdma) num_workers = len(cluster.as_dict()[‘worker’]) if job_name == “ps”: server.join() elif job_name == “worker”: with tf.device(tf.train.replica_device_setter(worker_device="/job:worker/task:%d" % task_index, cluster=cluster)):之后,可以使用tf.train.MonitoredTrainingSession高级API,进行模型训练和预测。总结基于TensorFlow的分布式开发大致就是本文中介绍的两种情况,第二种方式可以用于实际的生产环境,稳定性会更高。在运行结束的时候,开发者们也可通过设置邮件的通知,及时地了解到模型运行的情况。同时,如果开发者使用SessionRunHook来保存最后输出的模型,也需要了解到,框架代码中的一个BUG,即它只能在规定的时间内保存,超出规定时间,即使运行没有结束,程序也会被强制结束。如果开发者使用的版本是未修复BUG的版本,则要自行处理,放宽运行时间。 ...

January 17, 2019 · 1 min · jiezi

tensorflow 多线程队列读取

1队列队列和变量类似,都是计算图上有状态的节点,可以通过赋值操作修改变量的取值。对于队列,队列的操作主要有Enqueue、EnqueueMany和Dequeue。以下代码展示如何进行队列的初始化 入队 出队# coding utf-8import tensorflow as tf# 创建一个先进先出队列,指定队列中可以保存两个元素,并指定类型为整数。q = tf.FIFOQueue(2, ‘int32’)# 使用enqueue_many函数来初始化队列中的元素。和变量初始化类似,在使用队列之前# 需要明确调用这个初始化过程.init = q.enqueue_many(([0, 10],))# 使用Dequeue函数将队列中的第一个元素出队列。这个元素值,将被存在变量x中。x = q.dequeue()y = x + 1# 将加1后的值在重新加入队列中。q_inc = q.enqueue_many([y])with tf.Session() as sess: # 运行初始化队列的操作。 init.run() for _ in range(5): # 运行q_inc将执行数据出队列、出队的元素+1,、重新加入队列的整个过程。 v, _ = sess.run([x, q_inc]) # 打印出队元素的值。 print(v)2线程在TensorFlow中,队列不仅仅是一种数据结构,还是异步计算张量取值的一个重要机制。比如多个线程可以同时向一个队列中写元素,或者同时读取一个队列中的元素。TF提供了tf.Coordinator和tf.QueueRunner两个类来完成多线程协同的功能。2.1 tf.Coordinatortf.Coordinator主要用于协同多个线程一起停止,并提供了should_stop、request_stop和join三个函数。在启动线程之前需要声明一个tf.Coordinator类,并将这个类传入每一个创建的线程中。启动的线程需要一直查询tf.Coordinatorl类中提供的should_stop函数,当这个函数的返回值为Truez时,则当前线程也需要退出。每一个启动的线程都可以通过调用request_stop函数来通知其他线程退出。当某一个线程调用request_stop函数之后,should_stop函数的返回值将被设置为True,这样其他线程就可以同时终止。tf.Coordinator演示代码如下# coding utf-8import tensorflow as tfimport numpy as npimport threadingimport time# 线程中运行的程序,这个程序每隔1秒判断是否需要停止并打印自己的ID。def MyLoop(coord, worker_id): # 使用tf.Coordinator类提供的协同工具判断当前线程是否需要停止并打印自己的ID while not coord.should_stop(): # 随机停止所有线程 if np.random.rand() < 0.1: print(‘Stoping from id: %d\n’ % worker_id) # 调用coord.request_stop()函数来通知其他线程停止 coord.request_stop() else: # 打印当前线程的ID print(‘Working on id: %d\n’ % worker_id) # 暂停1秒 time.sleep(1)# 声明一个tf.train.Coordinator类来协同多个线程coord = tf.train.Coordinator()# 声明创建5个线程threads = [threading.Thread(target=MyLoop, args=(coord, i, )) for i in range(5)]# 启动所有的线程for t in threads: t.start()# 等待所有线程退出coord.join(threads)2.2 tf.QueueRunnertf.QueueRunner主要用于启动多个线程来操作同一个队列,启动的这些线程可以通过上面介绍的tf.Coordinator类来统一管理。以下代码展示如何使用tf.QueueRunner和tf.Coordinator来管理多线程队列操作。import tensorflow as tf # 声明一个先进先出的队列,队列中最多100个元素,类型为实数queue = tf .FIFOQueue(100, ‘float’)# 定义队列的入队操作enqueue_op = queue.enqueue([tf.random_normal([1])])# 使用 tf.train.QueueRunner来创建多个线程运行队列的入队操作# tf.train.QueueRunner给出了被操作的队列,[enqueue_op] * 5# 表示了需要启动5个线程,每个线程中运行的是enqueue_op操作qr = tf.train.QueueRunner(queue, [enqueue_op] * 5)# 将定义过的QueueRunner加入TensorFlow计算图上指定的集合# tf.train.add_queue_runner函数没有指定集合,# 则加入默认集合tf.GraphKeys.QUEUE_RUNNERS。# 下面的函数就是将刚刚定义的qr加入默认的tf.GraphKeys.QUEUE_RUNNERS结合tf.train.add_queue_runner(qr)# 定义出队操作out_tensor = queue.dequeue()with tf.Session() as sess: # 使用tf.train.Coordinator来协同启动的线程 coord = tf.train.Coordinator() # 使用tf.train.QueueRunner时,需要明确调用tf.train.start_queue_runners # 来启动所有线程。否则因为没有线程运行入队操作,当调用出队操作时,程序一直等待 # 入队操作被运行。tf.train.start_queue_runners函数会默认启动 # tf.GraphKeys.QUEUE_RUNNERS中所有QueueRunner.因为这个函数只支持启动指定集合中的QueueRunner, # 所以一般来说tf.train.add_queue_runner函数和tf.train.start_queue_runners函数会指定同一个结合 threads = tf.train.start_queue_runners(sess=sess, coord=coord) # 获取队列中的取值 for _ in range(3): print(sess.run(out_tensor)[0]) # 使用tf.train.Coordinator来停止所有线程 coord.request_stop() coord.join(threads) ...

January 17, 2019 · 1 min · jiezi

tensorflow安装教程

安装Windows下安装python环境安装pip安装virtualenv (pip3 install -U pip virtualenv)pip 安装TensorFlow步骤:创建虚拟环境 virtualenv –system-site-packages -p python3 ./venv进入虚拟环境 venv\Scripts\activate更新pip pip install –upgrade pip展示 venv 中已安装的软件包 pip list使用完 TensorFlow 后,方可推出 venv 虚拟环境 deactivate安装TensorFlow pip install –upgrade tensorflow这里需要注意python3.6在Windows下必须使用64 bit的版本,否则安装不成功验证安装 python -c “import tensorflow as tf;tf.enable_eager_execution();print(tf.reduce_sum(tf.random_normal([1000, 1000])))“安装 Jupyter NoteBook (venv) $ pip install jupyter (venv) $ python -m ipykernel install –user –name=venvdocker下安装安装镜像 docker pull tensorflow/tensorflow:nightly-jupyter启动镜像docker run -it -p 8888:8888 -v $(notebook-examples-path):/tf/notebookstensorflow/tensorflow:nightly-jupyter感谢阅读,希望阁下在AI领域越走越远~

January 11, 2019 · 1 min · jiezi

CNN卷积神经网络实现-人脸性别识别模型-可视化各层卷积特征

本文主要是实现了根据人脸识别性别的卷积神经网络,并对卷积过程中的提取特征进行了可视化. Github地址:https://github.com/chenlinzho…卷积神经网络卷积神经网络最早是为了解决图像识别的问题,现在也用在时间序列数据和文本数据处理当中,卷积神经网络对于数据特征的提取不用额外进行,在对网络的训练的过程当中,网络会自动提取主要的特征.卷积神经网络直接用原始图像的全部像素作为输入,但是内部为非全连接结构.因为图像数据在空间上是有组织结构的,每一个像素在空间上和周围的像素是有关系的,和相距很远的像素基本上是没什么联系的,每个神经元只需要接受局部的像素作为输入,再将局部信息汇总就能得到全局信息. 权值共享和池化两个操作使网络模型的参数大幅的减少,提高了模型的训练效率.卷积神经网络主要特点权值共享: 在卷积层中可以有多个卷积核,每个卷积核与原始图像进行卷积运算后会映射出一个新的2D图像,新图像的每个像素都来自同一个卷积核.这就是权值共享.池化: 降采样,对卷积(滤波)后,经过激活函数处理后的图像,保留像素块中灰度值最高的像素点(保留最主要的特征),比如进行 2X2的最大池化,把一个2x2的像素块降为1x1的像素块.卷积网络的训练数据(112923图形)从data目录读取数据,famale存放女性图片,male存放男性图片def read_img(list,flag=0): for i in range(len(list)-1): if os.path.isfile(list[i]): images.append(cv2.imread(list[i]).flatten()) labels.append(flag)read_img(get_img_list(‘male’),[0,1])read_img(get_img_list(‘female’),[1,0])images = np.array(images)labels = np.array(labels)重新打乱permutation = np.random.permutation(labels.shape[0])all_images = images[permutation,:]all_labels = labels[permutation,:]训练集与测试集比例 8:2train_total = all_images.shape[0]train_nums= int(all_images.shape[0]0.8)test_nums = all_images.shape[0]-train_nums#训练集images = all_images[0:train_nums,:]labels = all_labels[0:train_nums,:]#测试集test_images = all_images[train_nums:train_total,:]test_labels = all_labels[train_nums:train_total,:]训练参数train_epochs=3000 # 训练轮数batch_size= random.randint(6,18) # 每次训练数据,随机drop_prob = 0.4 # 正则化,丢弃比例learning_rate=0.00001 # 学习效率网络结构输入层为输入的灰度图像尺寸: -1 x 112 x 92 x 3 第一个卷积层,卷积核的大小,深度和数量 (3, 3, 3, 16)池化后的特征张量尺寸: -1 x 56 x 46 x 16第二个卷积层,卷积核的大小,深度和数量 (3, 3, 16, 32)池化后的特征张量尺寸: -1 x 28 x 23 x 32第三个卷积层,卷积核的大小,深度和数量 (3, 3, 32, 64)池化后的特征张量尺寸: -1 x 14 x 12 x 64全连接第一层权重矩阵: 10752 x 512全连接第二层权重矩阵: 512 x 128输出层与全连接隐藏层之间: 128 x 2辅助函数# 权重初始化(卷积核初始化)# tf.truncated_normal()不同于tf.random_normal(),返回的值中不会偏离均值两倍的标准差# 参数shpae为一个列表对象,例如[5, 5, 1, 32]对应# 5,5 表示卷积核的大小, 1代表通道channel,对彩色图片做卷积是3,单色灰度为1# 最后一个数字32,卷积核的个数,(也就是卷基层提取的特征数量)def weight_init(shape): weight = tf.truncated_normal(shape,stddev=0.1,dtype=tf.float32) return tf.Variable(weight)#偏执初始化def bias_init(shape): bias = tf.random_normal(shape,dtype=tf.float32) return tf.Variable(bias)#全连接矩阵初始化def fch_init(layer1,layer2,const=1): min = -const * (6.0 / (layer1 + layer2)); max = -min; weight = tf.random_uniform([layer1, layer2], minval=min, maxval=max, dtype=tf.float32) return tf.Variable(weight) # 源码的位置在tensorflow/python/ops下nn_impl.py和nn_ops.py# 这个函数接收两个参数,x 是图像的像素, w 是卷积核# x 张量的维度[batch, height, width, channels]# w 卷积核的维度[height, width, channels, channels_multiplier]# tf.nn.conv2d()是一个二维卷积函数,# stirdes 是卷积核移动的步长,4个1表示,在x张量维度的四个参数上移动步长# padding 参数’SAME’,表示对原始输入像素进行填充,卷积后映射的2D图像与原图大小相等# 填充,是指在原图像素值矩阵周围填充0像素点# 如果不进行填充,假设 原图为 32x32 的图像,卷积和大小为 5x5 ,卷积后映射图像大小 为 28x28def conv2d(images,weight): return tf.nn.conv2d(images,weight,strides=[1,1,1,1],padding=‘SAME’) Padding#池化卷积核在提取特征时的动作成为padding,它有两种方式:SAME和VALID。卷积核的移动步长不一定能够整除图片像素的宽度,所以在有些图片的边框位置有些像素不能被卷积。这种不越过边缘的取样就叫做 valid padding,卷积后的图像面积小于原图像。为了让卷积核覆盖到所有的像素,可以对边缘位置进行0像素填充,然后在进行卷积。这种越过边缘的取样是 same padding。如过移动步长为1,那么得到和原图一样大小的图像。如果步长很大,超过了卷积核长度,那么same padding,得到的特征图也会小于原来的图像。def max_pool2x2(images,tname): return tf.nn.max_pool(images,ksize=[1,2,2,1],strides=[1,2,2,1],padding=‘SAME’,name=tname)#images_input 为输入的图片,labels_input为输入的标签images_input = tf.placeholder(tf.float32,[None,112923],name=‘input_images’)labels_input = tf.placeholder(tf.float32,[None,2],name=‘input_labels’)#把图像转换为112923的形状x_input = tf.reshape(images_input,[-1,112,92,3])训练第一层卷积+池化# 卷积核333 16个 第一层卷积w1 = weight_init([3,3,3,16])b1 = bias_init([16])conv_1 = conv2d(x_input,w1)+b1relu_1 = tf.nn.relu(conv_1,name=‘relu_1’)max_pool_1 = max_pool2x2(relu_1,‘max_pool_1’)第二层卷积+池化# 卷积核3316 32个 第二层卷积w2 = weight_init([3,3,16,32])b2 = bias_init([32])conv_2 = conv2d(max_pool_1,w2) + b2relu_2 = tf.nn.relu(conv_2,name=‘relu_2’)max_pool_2 = max_pool2x2(relu_2,‘max_pool_2’)第三层卷积+池化w3 = weight_init([3,3,32,64])b3 = bias_init([64])conv_3 = conv2d(max_pool_2,w3)+b3relu_3 = tf.nn.relu(conv_3,name=‘relu_3’)max_pool_3 = max_pool2x2(relu_3,‘max_pool_3’)全连接第一层#把第三层的卷积结果平铺成一维向量f_input = tf.reshape(max_pool_3,[-1,141264])#全连接第一层 313132,512f_w1= fch_init(141264,512)f_b1 = bias_init([512])f_r1 = tf.matmul(f_input,f_w1) + f_b1#激活函数,relu随机丢掉一些权重提供泛华能力f_relu_r1 = tf.nn.relu(f_r1)# 为了防止网络出现过拟合的情况,对全连接隐藏层进行 Dropout(正则化)处理,在训练过程中随机的丢弃部分# 节点的数据来防止过拟合.Dropout同把节点数据设置为0来丢弃一些特征值,仅在训练过程中,# 预测的时候,仍使用全数据特征# 传入丢弃节点数据的比例f_dropout_r1 = tf.nn.dropout(f_relu_r1,drop_prob)全连接第二层f_w2 = fch_init(512,128)f_b2 = bias_init([128])f_r2 = tf.matmul(f_dropout_r1,f_w2) + f_b2f_relu_r2 = tf.nn.relu(f_r2)f_dropout_r2 = tf.nn.dropout(f_relu_r2,drop_prob)全连接输出层f_w3 = fch_init(128,2)f_b3 = bias_init([2])f_r3 = tf.matmul(f_dropout_r2,f_w3) + f_b3最后输出结果,可能是这样的[[0.0001,0.99999] ,那个位置的结果大就属于哪个分类f_softmax = tf.nn.softmax(f_r3,name=‘f_softmax’)损失函数#交叉熵代价函数cross_entry = tf.reduce_mean(tf.reduce_sum(-labels_inputtf.log(f_softmax)))#优化器,自动执行梯度下降算法optimizer = tf.train.AdamOptimizer(learning_rate).minimize(cross_entry)计算准确率&损失arg1 = tf.argmax(labels_input,1)arg2 = tf.argmax(f_softmax,1)#每个样本的预测结果是一个(1,2)的vectorcos = tf.equal(arg1,arg2)# tf.cast把bool值转换为浮点数acc = tf.reduce_mean(tf.cast(cos,dtype=tf.float32))启动会话开始训练init = tf.global_variables_initializer()sess = tf.Session()sess.run(init)Cost = []Accuracy=[]for i in range(train_epochs): idx=random.randint(0,len(train_data.images)-20) batch= random.randint(6,18) train_input = train_data.images[idx:(idx+batch)] train_labels = train_data.labels[idx:(idx+batch)] result,acc1,cross_entry_r,cos1,f_softmax1,relu_1_r= sess.run([optimizer,acc,cross_entry,cos,f_softmax,relu_1],feed_dict={images_input:train_input,labels_input:train_labels}) print acc1 Cost.append(cross_entry_r) Accuracy.append(acc1)# 代价函数曲线fig1,ax1 = plt.subplots(figsize=(10,7))plt.plot(Cost)ax1.set_xlabel(‘Epochs’)ax1.set_ylabel(‘Cost’)plt.title(‘Cross Loss’)plt.grid()plt.show()# 准确率曲线fig7,ax7 = plt.subplots(figsize=(10,7))plt.plot(Accuracy)ax7.set_xlabel(‘Epochs’)ax7.set_ylabel(‘Accuracy Rate’)plt.title(‘Train Accuracy Rate’)plt.grid()plt.show()测试集验证#测试arg2_r = sess.run(arg2,feed_dict={images_input:train_data.test_images,labels_input:train_data.test_labels})arg1_r = sess.run(arg1,feed_dict={images_input:train_data.test_images,labels_input:train_data.test_labels})#使用混淆矩阵,打印报告print (classification_report(arg1_r, arg2_r))验证通过,保存模型#保存模型saver = tf.train.Saver()saver.save(sess, ‘./model/my-gender-v1.0’)使用已训练好的模型参考:gender_model_use.py结果: 迭代3000次,模型的准确率达到93%训练交叉熵代价训练的准确率训练数据中的一个样本 第一层卷积提取的特征2x2池化后特征第二层卷积提取的特征2x2池化后特征第三层卷积提取的特征2x2池化后特征参考https://blog.csdn.net/u014281… ...

December 30, 2018 · 2 min · jiezi

cnn卷积神经网络打造人脸登录系统

git地址:https://github.com/chenlinzho…本文主要介绍了系统涉及的人脸检测与识别的详细方法,该系统基于python2.7.10/opencv2/tensorflow1.7.0环境,实现了从摄像头读取视频,检测人脸,识别人脸的功能由于模型文件过大,git无法上传,整个项目放在百度云盘,地址:https://pan.baidu.com/s/1Taal…人脸识别是计算机视觉研究领域的一个热点。目前,在实验室环境下,许多人脸识别已经赶上(超过)人工识别精度(准确率:0.9427~0.9920),比如face++,DeepID3,FaceNet等(详情可以参考:基于深度学习的人脸识别技术综述)。但是,由于光线,角度,表情,年龄等多种因素,导致人脸识别技术无法在现实生活中广泛应用。本文基于python/opencv/tensorflow环境,采用FaceNet(LFW:0.9963 )为基础来构建实时人脸检测与识别系统,探索人脸识别系统在现实应用中的难点。下文主要内容如下 :利用htm5 video标签打开摄像头采集头像并使用jquery.faceDeaction组件来粗略检测人脸将人脸图像上传到服务器,采用mtcnn检测人脸利用opencv的仿射变换对人脸进行对齐,保存对齐后的人脸采用预训练的facenet对检测的人脸进行embedding,embedding成512维度的特征;对人脸embedding特征创建高效的annoy索引进行人脸检测人脸采集采用html5 video标签可以很方便的实现从摄像头读取视频帧,下文代码实现了从摄像头读取视频帧,faceDection识别人脸后截取图像上传到服务器功能在html文件中添加video,canvas标签<div class=“booth”> <video id=“video” width=“400” height=“300” muted class=“abs” ></video> <canvas id=“canvas” width=“400” height=“300”></canvas> </div>打开网络摄像头var video = document.getElementById(‘video’),var vendorUrl = window.URL || window.webkitURL;//媒体对象navigator.getMedia = navigator.getUserMedia || navagator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;navigator.getMedia({video: true, //使用摄像头对象audio: false //不适用音频}, function(strem){ video.src = vendorUrl.createObjectURL(strem); video.play();});利用jquery的facetDection组件检测人脸$(’#canvas’).faceDetection()检测出人连脸的话截图,并把图片转换为base64的格式,方便上传context.drawImage(video, 0, 0, video.width, video.height);var base64 = canvas.toDataURL(‘images/png’);将base64格式的图片上传到服务器//上传人脸图片function upload(base64) { $.ajax({ “type”:“POST”, “url”:"/upload.php", “data”:{‘img’:base64}, ‘dataType’:‘json’, beforeSend:function(){}, success:function(result){ console.log(result) img_path = result.data.file_path } });}图片服务器接受代码,php语言实现function base64_image_content($base64_image_content,$path){ //匹配出图片的格式 if (preg_match(’/^(data:\s*image/(\w+);base64,)/’, $base64_image_content, $result)){ $type = $result[2]; $new_file = $path."/"; if(!file_exists($new_file)){ //检查是否有该文件夹,如果没有就创建,并给予最高权限 mkdir($new_file, 0700,true); } $new_file = $new_file.time().".{$type}"; if (file_put_contents($new_file, base64_decode(str_replace($result[1], ‘’, $base64_image_content)))){ return $new_file; }else{ return false; } }else{ return false; }}人脸检测人脸检测方法有许多,比如opencv自带的人脸Haar特征分类器和dlib人脸检测方法等。对于opencv的人脸检测方法,有点是简单,快速;存在的问题是人脸检测效果不好。正面/垂直/光线较好的人脸,该方法可以检测出来,而侧面/歪斜/光线不好的人脸,无法检测。因此,该方法不适合现场应用。对于dlib人脸检测方法 ,效果好于opencv的方法,但是检测力度也难以达到现场应用标准。本文中,我们采用了基于深度学习方法的mtcnn人脸检测系统(mtcnn:Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Neural Networks)。mtcnn人脸检测方法对自然环境中光线,角度和人脸表情变化更具有鲁棒性,人脸检测效果更好;同时,内存消耗不大,可以实现实时人脸检测。本文中采用mtcnn是基于python和tensorflow的实现(代码来自于davidsandberg,caffe实现代码参见:kpzhang93)model= os.path.abspath(face_comm.get_conf(‘mtcnn’,‘model’))class Detect: def init(self): self.detector = MtcnnDetector(model_folder=model, ctx=mx.cpu(0), num_worker=4, accurate_landmark=False) def detect_face(self,image): img = cv2.imread(image) results =self.detector.detect_face(img) boxes=[] key_points = [] if results is not None: #box框 boxes=results[0] #人脸5个关键点 points = results[1] for i in results[0]: faceKeyPoint = [] for p in points: for i in range(5): faceKeyPoint.append([p[i], p[i + 5]]) key_points.append(faceKeyPoint) return {“boxes”:boxes,“face_key_point”:key_points}具体代码参考fcce_detect.py人脸对齐有时候我们截取的人脸了头像可能是歪的,为了提升检测的质量,需要把人脸校正到同一个标准位置,这个位置是我们定义的,假设我们设定的标准检测头像是这样的假设眼睛,鼻子三个点的坐标分别是a(10,30) b(20,30) c(15,45),具体设置可参看config.ini文件alignment块配置项采用opencv仿射变换进行对齐,获取仿射变换矩阵dst_point=【a,b,c】tranform = cv2.getAffineTransform(source_point, dst_point)仿射变换:img_new = cv2.warpAffine(img, tranform, imagesize)具体代码参考face_alignment.py文件产生特征对齐得到后的头像,放入采用预训练的facenet对检测的人脸进行embedding,embedding成512维度的特征,以(id,vector)的形式保存在lmdb文件中 facenet.load_model(facenet_model_checkpoint) images_placeholder = tf.get_default_graph().get_tensor_by_name(“input:0”) embeddings = tf.get_default_graph().get_tensor_by_name(“embeddings:0”) phase_train_placeholder = tf.get_default_graph().get_tensor_by_name(“phase_train:0”) face=self.dectection.find_faces(image) prewhiten_face = facenet.prewhiten(face.image) # Run forward pass to calculate embeddings feed_dict = {images_placeholder: [prewhiten_face], phase_train_placeholder: False} return self.sess.run(embeddings, feed_dict=feed_dict)[0]具体代码可参看face_encoder.py人脸特征索引:人脸识别的时候不能对每一个人脸都进行比较,太慢了,相同的人得到的特征索引都是比较类似,可以采用KNN分类算法去识别,这里采用是更高效annoy算法对人脸特征创建索引,annoy索引算法的有个假设就是,每个人脸特征可以看做是在高维空间的一个点,如果两个很接近(相识),任何超平面都无法把他们分开,也就是说如果空间的点很接近,用超平面去分隔,相似的点一定会分在同一个平面空间(具体参看:https://github.com/spotify/annoy)#人脸特征先存储在lmdb文件中格式(id,vector),所以这里从lmdb文件中加载lmdb_file = self.lmdb_fileif os.path.isdir(lmdb_file): evn = lmdb.open(lmdb_file) wfp = evn.begin() annoy = AnnoyIndex(self.f) for key, value in wfp.cursor(): key = int(key) value = face_comm.str_to_embed(value) annoy.add_item(key,value) annoy.build(self.num_trees) annoy.save(self.annoy_index_path)具体代码可参看face_annoy.py人脸识别经过上面三个步骤后,得到人脸特征,在索引中查询最近几个点并就按欧式距离,如果距离小于0.6(更据实际情况设置的阈值)则认为是同一个人,然后根据id在数据库查找到对应人的信息即可#根据人脸特征找到相似的def query_vector(self,face_vector): n=int(face_comm.get_conf(‘annoy’,’num_nn_nearst’)) return self.annoy.get_nns_by_vector(face_vector,n,include_distances=True)具体代码可参看face_annoy.py安装部署系统采用有两个模块组成:face_web:提供用户注册登录,人脸采集,php语言实现face_server: 提供人脸检测,裁剪,对齐,识别功能,python语言实现模块间采用socket方式通信通信格式为: length+contentface_server相关的配置在config.ini文件中1.使用镜像face_serverdocker镜像: shareclz/python2.7.10-face-imageface_web镜像: skiychan/nginx-php7假设项目路径为/data1/face-login2.安装face_server容器docker run -it –name=face_server –net=host -v /data1:/data1 shareclz/python2.7.10-face-image /bin/bashcd /data1/face-loginpython face_server.py3.安装face_web容器docker run -it –name=face_web –net=host -v /data1:/data1 skiychan/nginx-php7 /bin/bashcd /data1/face-login;php -S 0.0.0.0:9988 -t ./web/ 最终效果:face_server加载mtcnn模型和facenet模型后等待人脸请求未注册识别失败人脸注册注册后登录成功参考https://zhuanlan.zhihu.com/p/…https://github.com/spotify/annoyhttps://blog.csdn.net/just_so...https://blog.csdn.net/oTengYu… ...

December 24, 2018 · 2 min · jiezi

决胜圣诞,女神心情不用猜!

本文将结合移动设备摄像能力与 TensorFlow.js,在浏览器里实现一个实时的人脸情绪分类器。鉴于文章的故事背景较长,对实现本身更有兴趣的同学可直接跳转至技术方案概述。DEMO 试玩前言看遍了 25 载的雪月没风花,本旺早已不悲不喜。万万没想到,在圣诞节前夕,女神居然答应了在下的约会请求。可是面对这么个大好机会,本前端工程狮竟突然慌张起来。想在下正如在座的一些看官一样,虽玉树临风、风流倜傥,却总因猜不透女孩的心思,一不留神就落得个母胎单身。如今已是 8102 年,像我等这么优秀的少年若再不脱单,党和人民那都是一万个不同意!痛定思痛,在下这就要发挥自己的技术优势,将察「颜」观色的技能树点满,做一个洞悉女神喜怒哀愁的优秀少年,决胜圣诞之巅!正题开始需求分析我们前端工程师终于在 2018 年迎来了 TensorFlow.js,这就意味着,就算算法学的再弱鸡,又不会 py 交易,我们也能靠着 js 跟着算法的同学们学上个一招半式。如果我们能够在约会期间,通过正规渠道获得女神的照片,是不是就能用算法分析分析女神看到在下的时候,是开心还是…不,一定是开心的!可是,约会的战场瞬息万变,我们总不能拍了照就放手机里,约完会回到静悄悄的家,再跑代码分析吧,那可就 「too young too late」 了!时间就是生命,如果不能当场知道女神的心情,我们还不如给自己 -1s!因此,我们的目标就是能够在手机上,实时看到这样的效果(嘛,有些简陋,不过本文将专注于功能实现,哈哈):技术方案概述很简单,我们需要的就两点,图像采集 & 模型应用,至于结果怎么展示,嗨呀,作为一个前端工程师,render 就是家常便饭呀。对于前端的同学来说,唯一可能不熟悉的也就是算法模型怎么用;对于算法的同学来说,唯一可能不熟悉的也就是移动设备怎么使用摄像头。我们的流程即如下图所示(下文会针对计算速度的问题进行优化):下面,我们就根据这个流程图来梳理下如何实现吧!核心一:图像采集与展示图像采集我们如何使用移动设备进行图像或者视频流的采集呢?这就需要借助 WebRTC 了。WebRTC,即网页即时通信(Web Real-Time Communication),是一个支持网页浏览器进行实时语音对话或视频对话的 API。它于 2011 年 6 月 1 日开源,并在 Google、Mozilla、Opera 支持下被纳入万维网联盟的 W3C 推荐标准。拉起摄像头并获取采集到的视频流,这正是我们需要使用到的由 WebRTC 提供的能力,而核心的 API 就是 navigator.mediaDevices.getUserMedia。该方法的兼容性如下,可以看到,对于常见的手机来说,还是可以较好支持的。不过,不同手机、系统种类与版本、浏览器种类与版本可能还是存在一些差异。如果想要更好的做兼容的话,可以考虑使用 Adapter.js 来做 shim,它可以让我们的 App 与 Api 的差异相隔离。此外,在这里可以看到一些有趣的例子。具体 Adapter.js 的实现可以自行查阅。那么这个方法是如何使用的呢?我们可以通过 MDN 来查阅一下。MediaDevices getUserMedia() 会向用户申请权限,使用媒体输入,获得具有指定类型的 MediaStream(如音频流、视频流),并且会 resolve 一个 MediaStream 对象,如果没有权限或没有匹配的媒体,会报出相应异常:navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { /* use the stream /}).catch(function(err) { / handle the error */});因此,我们可以在入口文件统一这样做:class App extends Component { constructor(props) { super(props); // … this.stream = null; this.video = null; // … } componentDidMount() { // … this.startMedia(); } startMedia = () => { const constraints = { audio: false, video: true, }; navigator.mediaDevices.getUserMedia(constraints) .then(this.handleSuccess) .catch(this.handleError); } handleSuccess = (stream) => { this.stream = stream; // 获取视频流 this.video.srcObject = stream; // 传给 video } handleError = (error) => { console.log(’navigator.getUserMedia error: ‘, error); } // …}实时展示为什么需要 this.video 呢,我们不仅要展示拍摄到的视频流,还要能直观的将女神的面部神情标记出来,因此需要通过 canvas 来同时实现展示视频流和绘制基本图形这两点,而连接这两点的方法如下:canvas.getContext(‘2d’).drawImage(video, 0, 0, canvas.width, canvas.height);当然,我们并不需要在视图中真的提供一个 video DOM,而是由 App 维护在实例内部即可。canvas.width 和 canvas.height 需要考虑移动端设备的尺寸,这里略去不表。而绘制矩形框与文字信息则非常简单,我们只需要拿到算法模型计算出的位置信息即可:export const drawBox = ({ ctx, x, y, w, h, emoji }) => { ctx.strokeStyle = EmojiToColor[emoji]; ctx.lineWidth = ‘4’; ctx.strokeRect(x, y, w, h);}export const drawText = ({ ctx, x, y, text }) => { const padding = 4 ctx.fillStyle = ‘#ff6347’ ctx.font = ‘16px’ ctx.textBaseline = ’top’ ctx.fillText(text, x + padding, y + padding)}核心二:模型预测在这里,我们需要将问题进行拆解。鉴于本文所说的「识别女神表情背后的情绪」属于图像分类问题,那么这个问题就需要我们完成两件事:从图像中提取出人脸部分的图像;将提取出的图像块作为输入交给模型进行分类计算。下面我们来围绕这两点逐步讨论。人脸提取我们将借助 face-api.js 来处理。face-api.js 是基于 tensorflow.js 核心 API (@tensorflow/tfjs-core) 来实现的在浏览器环境中使用的面部检测与识别库,本身就提供了 SSD Mobilenet V1、Tiny Yolo V2、MTCNN 这三种非常轻量的、适合移动设备使用的模型。很好理解的是效果自然是打了不少折扣,这些模型都从模型大小、计算复杂度、机器功耗等多方面做了精简,尽管有些专门用来计算的移动设备还是可以驾驭完整模型的,但我们一般的手机是肯定没有卡的,自然只能使用 Mobile 版的模型。这里我们将使用 MTCNN。我们可以小瞄一眼模型的设计,如下图所示。可以看到,我们的图像帧会被转换成不同 size 的张量传入不同的 net,并做了一堆 Max-pooling,最后同时完成人脸分类、bb box 的回归与 landmark 的定位。大致就是说,输入一张图像,我们可以得到图像中所有人脸的类别、检测框的位置信息和比如眼睛、鼻子、嘴唇的更细节的位置信息。当然,当我们使用了 face-api.js 时就不需要太仔细的去考虑这些,它做了较多的抽象与封装,甚至非常凶残的对前端同学屏蔽了张量的概念,你只需要取到一个 img DOM,是的,一个已经加载好 src 的 img DOM 作为封装方法的输入(加载 img 就是一个 Promise 咯),其内部会自己转换成需要的张量。通过下面的代码,我们可以将视频帧中的人脸提取出来。export class FaceExtractor { constructor(path = MODEL_PATH, params = PARAMS) { this.path = path; this.params = params; } async load() { this.model = new faceapi.Mtcnn(); await this.model.load(this.path); } async findAndExtractFaces(img) { // …一些基本判空保证在加载好后使用 const input = await faceapi.toNetInput(img, false, true); const results = await this.model.forward(input, this.params); const detections = results.map(r => r.faceDetection); const faces = await faceapi.extractFaces(input.inputs[0], detections); return { detections, faces }; }}情绪分类好了,终于到了核心功能了!一个「好」习惯是,扒一扒 GitHub 看看有没有开源代码可以参考一下,如果你是大佬请当我没说。这里我们将使用一个实时面部检测和情绪分类模型来完成我们的核心功能,这个模型可以区分开心、生气、难过、恶心、没表情等。对于在浏览器中使用 TensorFlow.js 而言,很多时候我们更多的是应用现有模型,通过 tfjs-converter 来将已有的 TensorFlow 的模型、Keras 的模型转换成 tfjs 可以使用的模型。值得一提的是,手机本身集成了很多的传感器,可以采集到很多的数据,相信未来一定有 tfjs 发挥的空间。具体转换方法可参考文档,我们继续往下讲。那么我们可以像使用 face-api.js 一样将 img DOM 传入模型吗?不行,事实上,我们使用的模型的输入并不是随意的图像,而是需要转换到指定大小、并只保留灰度图的张量。因此在继续之前,我们需要对原图像进行一些预处理。哈哈,躲得了初一躲不过十五,我们还是来了解下什么是张量吧!TensorFlow 的官网是这么解释的:张量是对矢量和矩阵向潜在的更高维度的泛化。TensorFlow 在内部将张量表示为基本数据类型的 n 维数组。算了没关系,我们画个图来理解张量是什么样的:因此,我们可将其简单理解为更高维的矩阵,并且存储的时候就是个数组套数组。当然,我们通常使用的 RGB 图像有三个通道,那是不是就是说我们的图像数据就是三维张量(宽、高、通道)了呢?也不是,在 TensorFlow 里,第一维通常是 n,具体来说就是图像个数(更准确的说法是 batch),因此一个图像张量的 shape 一般是 [n, height, width, channel],也即四维张量。那么我们要怎么对图像进行预处理呢?首先我们将分布在 [0, 255] 的像素值中心化到 [-127.5, 127.5],然后标准化到 [-1, 1]即可。const NORM_OFFSET = tf.scalar(127.5);export const normImg = (img, size) => { // 转换成张量 const imgTensor = tf.fromPixels(img); // 从 [0, 255] 标准化到 [-1, 1]. const normalized = imgTensor .toFloat() .sub(NORM_OFFSET) // 中心化 .div(NORM_OFFSET); // 标准化 const { shape } = imgTensor; if (shape[0] === size && shape[1] === size) { return normalized; } // 按照指定大小调整 const alignCorners = true; return tf.image.resizeBilinear(normalized, [size, size], alignCorners);}然后将图像转成灰度图:export const rgbToGray = async imgTensor => { const minTensor = imgTensor.min() const maxTensor = imgTensor.max() const min = (await minTensor.data())[0] const max = (await maxTensor.data())[0] minTensor.dispose() maxTensor.dispose() // 灰度图则需要标准化到 [0, 1],按照像素值的区间来标准化 const normalized = imgTensor.sub(tf.scalar(min)).div(tf.scalar(max - min)) // 灰度值取 RGB 的平均值 let grayscale = normalized.mean(2) // 扩展通道维度来获取正确的张量形状 (h, w, 1) return grayscale.expandDims(2)}这样一来,我们的输入就从 3 通道的彩色图片变成了只有 1 个通道的黑白图。注意,我们这里所做的预处理比较简单,一方面我们在避免去理解一些细节问题,另一方面也是因为我们是在使用已经训练好的模型,不需要做一些复杂的预处理来改善训练的效果。准备好图像后,我们需要开始准备模型了!我们的模型主要需要暴露加载模型的方法 load 和对图像进行分类的 classify 这两个方法。加载模型非常简单,只需要调用 tf.loadModel 即可,需要注意的是,加载模型是一个异步过程。我们使用 create-react-app 构建的项目,封装的 Webpack 配置已经支持了 async-await 的方法。class Model { constructor({ path, imageSize, classes, isGrayscale = false }) { this.path = path this.imageSize = imageSize this.classes = classes this.isGrayscale = isGrayscale } async load() { this.model = await tf.loadModel(this.path) // 预热一下 const inShape = this.model.inputs[0].shape.slice(1) const result = tf.tidy(() => this.model.predict(tf.zeros([1, …inShape]))) await result.data() result.dispose() } async imgToInputs(img) { // 转换成张量并 resize let norm = await prepImg(img, this.imageSize) // 转换成灰度图输入 norm = await rgbToGrayscale(norm) // 这就是所说的设置 batch 为 1 return norm.reshape([1, …norm.shape]) } async classify(img, topK = 10) { const inputs = await this.imgToInputs(img) const logits = this.model.predict(inputs) const classes = await this.getTopKClasses(logits, topK) return classes } async getTopKClasses(logits, topK = 10) { const values = await logits.data() let predictionList = [] for (let i = 0; i < values.length; i++) { predictionList.push({ value: values[i], index: i }) } predictionList = predictionList .sort((a, b) => b.value - a.value) .slice(0, topK) return predictionList.map(x => { return { label: this.classes[x.index], value: x.value } }) }}export default Model我们可以看到,我们的模型返回的是一个叫 logits 的量,而为了知道分类的结果,我们又做了 getTopKClasses 的操作。这可能会使得较少了解这块的同学有些困惑。实际上,对于一个分类模型而言,我们返回的结果并不是一个特定的类,而是对各个 class 的概率分布,举个例子:// 示意用const classifyResult = [0.1, 0.2, 0.25, 0.15, 0.3];也就是说,我们分类的结果其实并不是说图像中的东西「一定是人或者狗」,而是「可能是人或者可能是狗」。以上面的示意代码为例,如果我们的 label 对应的是 [‘女人’, ‘男人’, ‘大狗子’, ‘小狗子’, ‘二哈’],那么上述的结果其实应该理解为:图像中的物体 25% 的可能性为大狗子,20% 的可能性为一个男人。因此,我们需要做 getTopKClasses,根据我们的场景我们只关心最可能的情绪,那么我们也就会取 top1 的概率分布值,从而知道最可能的预测结果。怎么样,tfjs 封装后的高级方法是不是在语义上较为清晰呢?最终我们将上文提到的人脸提取功能与情绪分类模型整合到一起,并加上一些基本的 canvas 绘制: // 略有调整 analyzeFaces = async (img) => { // … const faceResults = await this.faceExtractor.findAndExtractFaces(img); const { detections, faces } = faceResults; // 对提取到的每一个人脸进行分类 let emotions = await Promise.all( faces.map(async face => await this.emotionModel.classify(face)) ); // … } drawDetections = () => { const { detections, emotions } = this.state; if (!detections.length) return; const { width, height } = this.canvas; const ctx = this.canvas.getContext(‘2d’); const detectionsResized = detections.map(d => d.forSize(width, height)); detectionsResized.forEach((det, i) => { const { x, y } = det.box const { emoji, name } = emotions[i][0].label; drawBox({ ctx, …det.box, emoji }); drawText({ ctx, x, y, text: emoji, name }); }); }大功告成!实时性优化事实上,我们还应该考虑的一个问题是实时性。事实上,我们的计算过程用到了两个模型,即便已经是针对移动设备做了优化的精简模型,但仍然会存在性能问题。如果我们在组织代码的时候以阻塞的方式进行预测,那么就会出现一帧一帧的卡顿,女神的微笑也会变得抖动和僵硬。因此,我们要考虑做一些优化,来更好地画出效果。笔者这里利用一个 flag 来标记当前是否有正在进行的模型计算,如果有,则进入下一个事件循环,否则则进入模型计算的异步操作。同时,每一个事件循环都会执行 canvas 操作,从而保证标记框总是会展示出来,且每次展示的其实都是缓存在 state 中的前一次模型计算结果。这种操作是具有合理性的,因为人脸的移动通常是连续的(如果不连续这个世界可能要重新审视一下),这种处理方法能较好的对结果进行展示,且不会因为模型计算而阻塞,导致卡顿,本质上是一种离散化采样的技巧吧。 handleSnapshot = async () => { // … 一些 canvas 准备操作 canvas.getContext(‘2d’).drawImage(video, 0, 0, canvas.width, canvas.height); this.drawDetections(); // 绘制 state 中维护的结果 // 利用 flag 判断是否有正在进行的模型预测 if (!this.isForwarding) { this.isForwarding = true; const imgSrc = await getImg(canvas.toDataURL(‘image/png’)); this.analyzeFaces(imgSrc); } const that = this; setTimeout(() => { that.handleSnapshot(); }, 10); } analyzeFaces = async (img) => { // …其他操作 const faceResults = await this.models.face.findAndExtractFaces(img); const { detections, faces } = faceResults; let emotions = await Promise.all( faces.map(async face => await this.models.emotion.classify(face)) ); this.setState( { loading: false, detections, faces, emotions }, () => { // 获取到新的预测值后,将 flag 置为 false,以再次进行预测 this.isForwarding = false; } ); }效果展示我们来在女神这试验下效果看看:嗯,马马虎虎吧!虽然有时候还是会把笑容识别成没什么表情,咦,是不是 Gakki 演技还是有点…好了好了,时间紧迫,赶紧带上武器准备赴约吧。穿上一身帅气格子衫,打扮成程序员模样~结尾约会当晚,吃着火锅唱着歌,在下与女神相谈甚欢。正当气氛逐渐暧昧,话题开始深入到感情方面时,我自然的问起女神的理想型。万万没想到,女神突然说了这样的话:那一刻我想起了 Eason 的歌:Lonely Lonely christmas Merry Merry christmas 写了卡片能寄给谁 心碎的像街上的纸屑参考https://developer.mozilla.org…https://github.com/webrtc/ada...https://github.com/justadudew...https://github.com/tensorflow...[Zhang K, Zhang Z, Li Z, et al. Joint face detection and alignment using multitask cascaded convolutional networks[J]. IEEE Signal Processing Letters, 2016, 23(10): 1499-1503.][7]文章可随意转载,但请保留此 原文链接。非常欢迎有激情的你加入 ES2049 Studio,简历请发送至 caijun.hcj(at)alibaba-inc.com 。 ...

December 24, 2018 · 5 min · jiezi

TensorFlow框架 入门笔记

背景略基础介绍 略TensorFlow安装 linkTensorFlow 主要概念使用图(graph)来表示计算任务(执行流程).在被称之为会话(session)的上下文(context)中执行图.使用tensor表示数据结构, 如下是特殊的tensortf.Variabletf.constanttf.placeholdertf.SparseTensor使用 feed 和 fetch 可以为任意的操作(arbitrary operation) 赋值或者从其中获取数据.

December 24, 2018 · 1 min · jiezi

深度学习常见激活函数介绍及代码实现

作用深度神经网络引入非线性单元,使训练问题不再是一个凸优化问题,虽然我们很难得到最优解,但是可以通过梯度下降去寻找局部最小值。增强模型的拟合能力,理论上只要有足够的神经元,一层隐藏层就可以表达任意函数。性质可微(多元函数):函数可微保证使用梯度下降优化的可计算性。单调性:保证梯度方向相对稳定。输出值范围:当输出有限,由于特征表示受有限权值影响,基于梯度的优化方法会更加稳定;当输出无限,特征表示不受影响,但由于高梯度需要小学习率。非饱和性:当激活函数满足如下要求,称为右饱和:当激活函数满足如下要求,称为左饱和:激活函数饱和会造成梯度值接近0,导致梯度消失使模型无法收敛。sigmoidsigmoid函数,导函数图像:sigmoid激活函数具有“连续可微”,“单调性”,“输出值有限”。通过查看导函数图像,sigmoid激活函数最大的问题就是两端饱和,造成梯度消失(解决办法:使用relu激活函数,BN等),此外输出不以0中心(以0中心的好处是可以加快模型收敛)。目前sigmoid激活函数多使用在二分类问题(对于大于二分类问题,如果类别之间存在相互关系使用sigmoid,反之使用softmax),门控机制的判断等。import tensorflow as tftf.enable_eager_execution()sigmoid_test=tf.nn.sigmoid([-3.,-2.,-1.,0.0,1.,2.,3.],name=‘sigmoid_op’)print(sigmoid_test)输出:tf.Tensor([0.04742587 0.11920292 0.26894143 0.5 0.7310586 0.880797 0.95257413], shape=(7,), dtype=float32)tanh tanh函数,导函数图像:tanh激活函数输出区间[-1,1],输出值以0为中心,与sigmoid激活函数相比具有更大的梯度值,再加上输出值以0为中心,模型收敛更快。不过它依然存在两端饱和,梯度消失问题还是存在,tanh激活函数在RNN模型中应用较多。import tensorflow as tftf.enable_eager_execution()tanh_test=tf.nn.tanh([-3.,-2.,-1.,0.0,1.,2.,3.],name=‘tanh_op’)print(tanh_test)输出:tf.Tensor([-0.9950547 -0.9640276 -0.7615942 0. 0.7615942 0.9640276 0.9950547], shape=(7,), dtype=float32)relurelu函数,导函数图像:relu与线性单元的区别是在其一半的定义域上输出为0,这使得它易于优化,计算。通过图像可得,relu激活函数的梯度不仅大,而且一致,更重要的是它没有sigmoid,tanh激活函数的饱和性,有效缓解了梯度消失问题。目前,relu激活函数是神经网络隐藏层的首选。但是,它最大的问题是当输入小于0时,输出值为0,此时神经元将无法学习。import tensorflow as tftf.enable_eager_execution()relu_test=tf.nn.relu([-3.,-2.,-1.,0.0,1.,2.,3.],name=‘relu_op’)tf.nn.reluprint(relu_test)输出:tf.Tensor([0. 0. 0. 0. 1. 2. 3.], shape=(7,), dtype=float32)leakyreluleakyrelu函数,导函数图像:leakyrelu激活函数是relu的衍变版本,主要就是为了解决relu输出为0的问题。如图所示,在输入小于0时,虽然输出值很小但是值不为0。leakyrelu激活函数一个缺点就是它有些近似线性,导致在复杂分类中效果不好。import tensorflow as tftf.enable_eager_execution()# alpha: Slope of the activation function at x < 0leaky_relu_test=tf.nn.leaky_relu([-3.,-2.,-1.,0.0,1.,2.,3.],alpha=0.2,name=‘leaky_relu_op’)print(leaky_relu_test)输出:tf.Tensor([-0.6 -0.4 -0.2 0. 1. 2. 3. ], shape=(7,), dtype=float32)eluelu函数,导函数图像:elu和relu的区别在负区间,relu输出为0,而elu输出会逐渐接近-,更具鲁棒性。elu激活函数另一优点是它将输出值的均值控制为0(这一点确实和BN很像,BN将分布控制到均值为0,标准差为1)。import tensorflow as tftf.enable_eager_execution()elu_relu_test=tf.nn.elu([-10000,-100.,-3.,-2.,-1.,0.0,1.,2.,3.],name=‘elu_relu_op’)print(elu_relu_test)输出:tf.Tensor([-1. -1. -0.95021296 -0.86466473 -0.63212055 0. 1. 2. 3. ], shape=(9,), dtype=float32)softmaxsoftmax单元常作为网络的输出层,它很自然地表示了具有 k 个可能值的离散型随机变量的概率分布。softmax将向量等比例压缩到[0,1]之间,且保证所有元素之和为1。import tensorflow as tftf.enable_eager_execution()softmax_test=tf.nn.softmax([-3.,-2.,-1.,0.0,1.,2.,3.],name=‘softmax_op’)print(softmax_test)softmax_test_sum=tf.reduce_sum(softmax_test)print(softmax_test_sum)输出:tf.Tensor([0.0015683 0.00426308 0.01158826 0.03150015 0.0856263 0.23275642 0.6326975 ], shape=(7,), dtype=float32)tf.Tensor(1.0, shape=(), dtype=float32)总结激活函数的选择还要根据项目实际情况,考虑不同激活函数的优缺点。 ...

December 17, 2018 · 1 min · jiezi

深度学习实现自动生成图片字幕

介绍本次项目使用深度学习自动生成图像字幕。如上图,模型自动生成“The person is riding a surfboard in the ocean”字幕。我们具体该如何实现呢?如图所示,我们需要分别使用CNN和RNN模型来实现。CNN模型:利用卷积网络对图像特征提取的强大能力,来提取特征信息。我们的CNN模型需要有强大的识别能力,因此该模型需要使用过大量,多类别的训练集进行训练,并且识别准确率较高。本次,我们利用迁移学习使用Inception模型实现此功能。通过迁移学习实现OCT图像识别 文章中有迁移学习的相关介绍。RNN模型:对于文本序列数据,目前我们最好的选择依然是RNN模型。为了提升模型预测能力,我们使用注意力机制实现文本预测。注意力机制实现机器翻译 文章中有注意力机制的相关介绍。对模型的细节要求我们将在对应代码实现里进行介绍。数据集介绍我们使用MS-COCO数据集进行训练,为方便理解,简单介绍下数据格式。COCO数据有5种类型,分别是: object detection, keypoint detection, stuff segmentation, panoptic segmentation,image captioning。基础数据结构如下图所示:具体样例(部分):本次项目使用的是Image Captioning其中,每张照片不少于5个字幕:数据下载处理import tensorflow as tf# 开启eager模式tf.enable_eager_execution()import matplotlib.pyplot as pltfrom sklearn.model_selection import train_test_splitfrom sklearn.utils import shuffleimport reimport numpy as npimport osimport timeimport jsonfrom glob import globfrom PIL import Imageimport pickleannotation_zip=tf.keras.utils.get_file( # cache_dir(默认值): ~/.keras # cache_subdir: datasets, # /.keras/datasets/captions.zip fname=‘captions.zip’, cache_subdir=os.path.abspath(’.’), origin=‘http://images.cocodataset.org/annotations/annotations_trainval2014.zip', # 解压 extract=True)# 返回文件夹名,实现:split(file)[0]annotation_file = os.path.dirname(annotation_zip)+’/annotations/captions_train2014.json’name_of_zip=‘train2014.zip’if not os.path.exists(os.path.abspath(’.’)+"/"+name_of_zip): image_zip=tf.keras.utils.get_file( fname=name_of_zip, cache_subdir=os.path.abspath(’.’), origin=‘http://images.cocodataset.org/zips/train2014.zip', extract=True ) PATH=os.path.dirname(image_zip)+‘train2014/’else: PATH=os.path.abspath(’.’)+’/train2014/‘读取字幕和图片:# 读取注释json文件with open(annotation_file,‘r’) as f: annotations=json.load(f)# 保存全部字幕all_captions=[]# 保存全部图片all_img_name_vecotr=[]# json格式参考COCO数据集官网for annot in annotations[‘annotations’]: # 添加开始和结束标记 caption=’<start>’+annot[‘caption’]+’<end>’ # 获取图片名字 image_id=annot[‘image_id’] # 参考文章开始给出的“具体样例” full_coco_image_path=PATH+‘COCO_train2014_’+’%012d.jpg’%(image_id) all_img_name_vecotr.append(full_coco_image_path) all_captions.append(caption)# random_state 随机种子,确保每次数据一致train_captions,img_name_vector=shuffle( all_captions, all_img_name_vecotr, random_state=1 ) # 使用训练集前30000样本 num_examples=30000 train_captions=train_captions[:num_examples] img_name_vector=img_name_vector[:num_examples]重训练InceptionV3:简单介绍下InceptionV3模型:Inception模型结构中最重要的思想就是卷积核分解。通过上图可知,5x5的卷积可由2个3x3的卷积代替,3x3卷积可由一个3x1卷积和一个1x3卷积代替,代替的好处是减少了权重参数量,增加了网络非线性(层增多)。比如,一个5x5卷积的权重参数量和2个3x3卷积的权重参数量分别是(5x5):(3x3)x2。InceptionV3中就将7x7的卷积分解成7x1卷积和1x7卷积。批标准化(BN)正式提出是在InceptionV2,BN通过将输入分布转变成均值为0,标准差为1的正态分布,将值域处于激活函数敏感范围从而防止梯度消失问题。正因为梯度消失问题的解决,我们可以使用更大的学习率进行训练从而加快模型收敛。由于BN有类似Dropout的正则化作用,因此在训练的时候不使用或少使用Dropout,并减轻L2正则。使用非对称卷积,如:1x3卷积,3x1卷积(论文作者指出在feature map的大小12x1220x20之间效果最好)。使用Label Smoothing对损失修正。下图是新损失函数:网络各层信息如下图所示:# 使用inception V3 要求图片分辨率:299,299# 输入值范围[-1,1]def load_image(image_path): img=tf.image.decode_jpeg(tf.read_file(image_path)) img_reshape=tf.image.resize_images(img,(299,299)) # 像素范围[-1,1] # (-255)/255 img_range=tf.keras.applications.inception_v3.preprocess_input(img_reshape) return img_range,image_path使用迁移学习构建新模型:# 最后一层卷积输入shape(882048),并将结果向量保存为dictimage_model=tf.keras.applications.InceptionV3( # 不使用最后全连接层 include_top=False, # inception模型的训练集是imagenet weigths=‘imagenet’)# shape:(batch_size,299,299,3)new_input=image_model.input# hidden_layer shape:(batch_size,8,8,2048)hidden_layer=image_model.layers[-1].output# 创建新模型image_features_extract_model=tf.keras.Model( new_input, hidden_layer)保存通过使用InceptionV3获得的特征:encode_train=sorted(set(img_name_vector))# map:可以并行处理数据,默认读取的文件具有确定性顺序# 取消顺序可以加快数据读取# 通过设置参数num_parallel_calls实现image_dataset=tf.data.Dataset.from_tensor_slices(encode_train).map(load_image).batch(16)for img,path in image_dataset: # inception v3得到的feature batch_features=image_features_extract_model(img) batch_features=tf.reshape( # shape:(batch_size,8,8,2048) reshape:(batch_size,64,2048) batch_features,shape=(batch_features.shape[0],-1,batch_features[3]) )# 保存for bf,p in zip(batch_features,path): path_of_feature=p.numpy().decode(‘utf-8’) # 文件后缀.npy np.save(path_of_feature,bf.numpy())文本处理文本处理方式还是老规矩,先将文本转成字典表示然后创建字符转ID,ID转字符,最后补长到预设长度。# 计算最大长度def calc_max_length(tensor): return max(len(t)for t in tensor)top_k=5000tokenizer=tf.keras.preprocessing.text.Tokenizer( num_words=top_k, # 字典中没有的字符用<unk>代替 oov_token=’<unk>’, # 需要过滤掉的特殊字符 filters=’!"#$%&()+.,-/:;=?@[]^_`{|}~’)# 要用以训练的文本列表tokenizer.fit_on_texts(train_captions)# 转为序列列表向量train_seqs=tokenizer.texts_to_sequences((train_captions))tokenizer.word_index[’<pad>’]=0# 如果没有指定最大长度,pad_sequences会自动计算最大长度cap_vector=tf.keras.preprocessing.sequence.pad_sequences( sequences=train_seqs, # 后置补长 padding=‘post’)max_length=calc_max_length(train_seqs)模型训练参数拆分训练集,验证集:img_name_train,img_name_val,cap_trian,cap_val=train_test_split( img_name_vector, cap_vector, # 验证数据集占20% test_size=0.2, # 确保每次数据一致 random_state=0# 最好是2的次幂,更适合GPU运算(加快二进制运算)BATCH_SIZE=64# shuffle 缓冲区大小BUFFER_SIZE=1000# 词嵌入维度embedding_dim=256units=512vocab_size=len(tokenizer.word_index)# 后面会将(8,8,2048)转为(64,2048)# 维度一定要一致feature_shape=2048attention_features_shape=64# 加载保存的之前feature文件def map_func(img_name,cap): img_tensor=np.load(img_name.decode(‘utf-8’)+’.npy’) return img_tensor,capdataset=tf.data.Dataset.from_tensor_slices((img_name_train,cap_trian))# num_parallel_calls 根据自己的CPU而定dataset=dataset.map(lambda item1,item2:tf.py_func( map_func,[item1,item2],[tf.float32,tf.int32]),num_parallel_calls=4)# prefetch 可以合理利用CPU准备数据,GPU计算数据之间的空闲时间,加快数据读取dataset=dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE).prefetch(1)创建模型编码器模型:# 一层使用relu的全连接层class CNN_Encoder(tf.keras.Model): def init(self,embedding_dim): super(CNN_Encoder, self).init() # fc shape:(batch_size,64,embedding_dim) self.fc=tf.keras.layers.Dense(embedding_dim) def call(self,x): x=self.fc(x) x=tf.nn.relu(x) return x注意力层:详细介绍可以查看文章开始给出的链接,这里给出计算方程式:class BahdanauAttention(tf.keras.Model): def init(self,units): super(BahdanauAttention, self).init() self.W1=tf.keras.layers.Dense(units) self.W2=tf.keras.layers.Dense(units) self.V=tf.keras.layers.Dense(1) def call(self, features,hidden): # 参考注意力机制计算的方程 # feature shape:(batch_size,64,embedding_dim) # hidden_state shape:(batch_size,hidden_size) hidden_with_time_axis=tf.expand_dims(hidden,1) # score shape:(batch_size,64,hidden_size) score=tf.nn.tanh(self.W1(features)+self.W2(hidden_with_time_axis)) # attention_weights shape:(batch_size,64,1) attention_weights=tf.nn.softmax(self.V(score),axis=1) context_vector=tf.reduce_sum(attention_weightsfeatures,axis=1) return context_vector,attention_weights解码器中的GRU:# 相比LSTM因为减少了一个门,参数少,收敛快def gru(units): if tf.test.is_gpu_available(): # 使用GPU加速计算 return tf.keras.layers.CuDNNGRU( units=units, return_state=True, return_sequences=True, # 循环核的初始化方法 # glorot_uniform是sqrt(2 / (fan_in + fan_out))的正态分布产生 # 其中fan_in和fan_out是权重张量的扇入扇出(即输入和输出单元数目) recurrent_initializer=‘glorot_uniform’ ) else: return tf.keras.layers.GRU( return_sequences=True, return_state=True, # 默认:hard_sigmoid <= -1 输出0,>=1 输出1 ,中间为线性 recurrent_activation=‘sigmoid’, recurrent_initializer=‘glorot_uniform’ )解码器模型:# 使用注意力模型class RNN_Decoder(tf.keras.Model): def init(self,embedding_dim,units,vocab_size): super(RNN_Decoder, self).init() self.units=units # 词嵌入将高维离散数据转为低维连续数据,并表现出数据之间的相似性(向量空间) self.embedding=tf.keras.layers.Embedding(input_shape=vocab_size,output_dim=embedding_dim) self.gru=gru(units) self.fc1=tf.keras.layers.Dense(self.units) self.fc2=tf.keras.layers.Dense(vocab_size) self.attention=BahdanauAttention(self.units) def call(self,x,features,hidden): # 获取注意力模型输出 context_vector,attention_weights=self.attention(features,hidden) # x shape:(batch_size,1,embedding_dim) x=self.embedding(x) # 注意力,当前输入合并 # 注意力shape:(batch_size,1,hidden) x shape:(batch_size,1,embedding_size) # x shape:(batch_size, 1, embedding_dim + hidden_size) x=tf.concat([tf.expand_dims(context_vector,1),x],axis=-1) output,state=self.gru(x) # x shape:(batch_size,max_length,hidden_size) x=self.fc1(output) # x shape:(batch_sizemax_length,hidden_size) x=tf.reshape(x,shape=(-1,x.shape[2])) # x shape:(batch_sizemax_length,vocab_size) x=self.fc2(x) return x,state,attention_weights def reset_state(self, batch_size): return tf.zeros((batch_size, self.units))模型训练实例化模型:encoder = CNN_Encoder(embedding_dim)decoder = RNN_Decoder(embedding_dim, units, vocab_size)损失函数,优化器设置:# InceptionV3模型使用的不是Adam优化器# 各种优化器以后放到一篇单独的文章详细介绍optimizer=tf.train.AdamOptimizer(learning_rate=0.0001)def loss_function(real,pred): mask=1-np.equal(real,0) # 带mask的交叉熵损失 loss_=tf.nn.sparse_softmax_cross_entropy_with_logits( labels=real, logits=pred )*mask return tf.reduce_mean(loss_)训练:将使用InceptionV3模型提取的特征作为编码器输入编码器输出,hidden_state,字幕文本作为解码器输入解码器hidden_state作为下一次输入,预测值用于计算模型损失使用标签文本作为解码器输入(teacher-forcing模式)梯度计算及应用loss_plot=[]EPOCHS=20for epoch in range(EPOCHS): start=time.time() total_loss=0 for (batch,(img_tensor,target)) in enumerate(dataset): loss=0 # 每迭代一次batch后重置 hidden_state hidden=decoder.reset_states(batch_size=target.shape[0]) # input维度是3维 dec_input=tf.expand_dims([tokenizer.word_index[’<start>’]*BATCH_SIZE],1) # eager模式下记录梯度 with tf.GradientTape() as tape: # inception模式提取的特征 features=encoder(img_tensor) # 每张照片不止一个captions for i in range(1,target.shape[1]): # attention_weights此处暂不需要 predictions,hidden,_=decoder(dec_input,features,hidden) loss+=loss_function(target[:,i],predictions) # teacher forcing 使用标签数据作为输入替代hidden-output dec_input=tf.expand_dims(target[:,i],1) total_loss+=(loss/int(target.shape[1])) # 总训练参数 variables=encoder.variables+decoder.variables # 梯度计算及应用 gradients=tape.gradient(loss,variables) optimizer.apply_gradients(zip(gradients,variables)) if batch%100 == 0: print(’epoch{},batch{},loss{:.4}’.format( epoch+1, batch, loss.numpy()/int(target.shape[1]) )) loss_plot.append(total_loss/len(cap_vector))plt.plot(loss_plot)plt.xlabel(’epochs’)plt.ylabel(’loss’)plt.show()模型预测模型预测不使用Teacher forcing模式,当遇到预设的结束标记“<end>”时模型结束训练。def evaluate(image): attention_plot = np.zeros((max_length, attention_features_shape)) # 初始化hidden-state hidden = decoder.reset_state(batch_size=1) # shape:(1,299,299,3) temp_input = tf.expand_dims(load_image(image)[0], 0) # 特征提取 img_tensor_val = image_features_extract_model(temp_input) # shape:(1,8,8,2048) reshape:(1,64,2048) img_tensor_val = tf.reshape(img_tensor_val, (img_tensor_val.shape[0], -1, img_tensor_val.shape[3])) # shape:(1,64,256) features = encoder(img_tensor_val) # 增加batchsize维度 dec_input = tf.expand_dims([tokenizer.word_index[’<start>’]], 0) result = [] for i in range(max_length): predictions, hidden, attention_weights = decoder(dec_input, features, hidden) attention_plot[i] = tf.reshape(attention_weights, (-1, )).numpy() # 我们使用softmax归一化结果,使用argmax查询最大值 # 对于分类数量大于2,softmax和sigmoid的区别是 # 类别之间有相互关系的使用sigmoid,反之使用softmax predicted_id = tf.argmax(predictions[0]).numpy() # ID转字符,获取文本结果 result.append(tokenizer.index_word[predicted_id]) # 判断是否是预设的结束标记 if tokenizer.index_word[predicted_id] == ‘<end>’: return result, attention_plot # 将预测值作为输入,预测下一个结果(teacher-forcing在这里使用数据标签作为输入) dec_input = tf.expand_dims([predicted_id], 0) attention_plot = attention_plot[:len(result), :] return result, attention_plot以下用于可视化注意力机制训练过程:此处代码主要是图像展示就不做过多介绍了。def plot_attention(image, result, attention_plot): temp_image = np.array(Image.open(image)) fig = plt.figure(figsize=(10, 10)) len_result = len(result) for l in range(len_result): temp_att = np.resize(attention_plot[l], (8, 8)) ax = fig.add_subplot(len_result//2, len_result//2, l+1) ax.set_title(result[l]) img = ax.imshow(temp_image) ax.imshow(temp_att, cmap=‘gray’, alpha=0.6, extent=img.get_extent()) plt.tight_layout() plt.show()rid = np.random.randint(0, len(img_name_val))image = img_name_val[rid]real_caption = ’ ‘.join([tokenizer.index_word[i] for i in cap_val[rid] if i not in [0]])result, attention_plot = evaluate(image)print (‘Real Caption:’, real_caption)print (‘Prediction Caption:’, ’ ‘.join(result))plot_attention(image, result, attention_plot)Image.open(img_name_val[rid])总结想要对图像生成字幕,首先需要提取图像特征,本文我们利用迁移学习使用Inception模型来提取特征,对于Inception模型,我们重点理解卷积核分解。至于文本预测部分与使用注意力机制实现机器翻译大体一致。有一点想说的是,类似这样的项目维度转换会比较多,也是很容易出错的地方,这一点需要格外留意。本文代码内容来自 Yash Katariya在此表示感谢。 ...

December 11, 2018 · 3 min · jiezi

注意力机制实现机器翻译

介绍Attention模型形象的比喻就是“图像对焦”。上图是Encoder-Decoder模型,Decoder中每个单词生成过程如下:其中C是“语义编码C”,f是Decoder的非线性变换函数。由此,我们可以看出生成目标句子的每个单词都使用同一个语义编码C,即:源句子中的每个单词的影响力都是一样的,这如同图像没有对焦的情况,现实项目中也存在明显的不合理。比如一个机器翻译模型,输入是“Tom chase Jerry”,模型输出:“汤姆”,“追逐”,“杰瑞”。在翻译“杰瑞”的时候显然“Jerry”的贡献值最大,如果每个单词的贡献值相同明显不合理。这个问题在输入句子长度较短时问题不大,但是当输入句子较长时会丢失很多细节信息(个人觉得此处类似平均池化和最大值池化)。正因为如此,我们引入了Attention思想。Soft Attention模型使用Attention模型翻译“杰瑞”的时候,我们可以得到输入句子中的每个单词对输出当前单词的贡献值大小如:(Tom,0.3)(Chase,0.2) (Jerry,0.5)。这意味着生成每个单词yi时不再使用同一个语义编码C,而是根据yi使用不同的Ci。在引入Attention模型后yi的计算过程改变如下所示:每个Ci对应源句子中每个单词的注意力分配概率,示例如下:f2是Encoder对每个单词的变换函数,g函数代表整个源句子的中间语义表示的变换函数,一般形式是加权求和:aji代表注意力分配系数,hj代表源句子中某个单词的语义编码,Lx代表源句子中单词数量。g函数的计算过程如下图所示:Attention模型概率计算如果所示,当我们要生成yi单词,此时我们用i-1时刻的隐藏节点输出值Hi-1去和源句子中的每个单词对应RNN隐藏节点状态hj依次进行对比,即:通过函数F(hj,Hi-1)来获得yi对源句子中每个单词对应的对齐可能性,函数F常见方法如下图所示:然后使用Softmax函数进行数值归一化处理。如对“对齐概率”不理解的朋友,可以查看下图英语-德语翻译系统中加入Attention机制后,Encoder和Decoder两个句子中每个单词对应注意力分配概率分布。Self Attention模型在Soft Attention模型中,Attention机制发生在Decoder中Yi和Encoder中的所有元素之间。Self Attention模型不是在两者之间,而是Decoder内部元素之间或者Encoder内部元素之间发生的Attention机制,计算方法和Soft Attention模型一致。那么Self Attention模型有什么好处?我们依然以机器翻译为例:如图所示,Self Attention模型在内部可以捕获一些句法特征或语义特征。Self Attention模型相比传统RNN模型需要依次序序列计算,它的感受野更大,可以直接将句子中的任意两个单词的联系通过一个计算步骤联系起来,可以捕获远距离的相互依赖特征(就像列表和数组的区别)。此外,Self Attention模型对于增加计算的并行性也有帮助。案例我们使用的语言数据集是“英语-西班牙语”,数据集样本如下图所示:数据导入# 数据下载path_to_zip=tf.keras.utils.get_file( fname=‘spa-eng.zip’, origin=‘http://download.tensorflow.org/data/spa-eng.zip', # 解压tar zip文件 extract=True)path_to_file=os.path.dirname(path_to_zip)+’/spa-eng/spa.txt’转码:def unicode_to_ascii(sen): return ‘’.join( char for char in unicodedata.normalize(‘NFD’,sen) if unicodedata.category(char) != ‘Mn’ )数据预处理每条训练语句添加开始和结束标记移除句子中的特殊字符字符转ID,ID转字符并排序将句子补长到预设的最大长度def preprocess_sentence(w): w = unicode_to_ascii(w.lower().strip()) # 在单词和标点之间创建空格 # 如: “he is a boy.” => “he is a boy .” w = re.sub(r"([?.!,¿])", r" \1 “, w) w = re.sub(r’[” “]+’, " “, w) # 特殊字符以空格代替 w = re.sub(r”[^a-zA-Z?.!,¿]+”, " “, w) w = w.rstrip().strip() # 添加开始和结束标记 w = ‘<start> ’ + w + ’ <end>’ return w创建数据集:def create_dataset(path, num_examples): lines = open(path, encoding=‘UTF-8’).read().strip().split(’\n’) word_pairs = [[preprocess_sentence(w) for w in l.split(’\t’)] for l in lines[:num_examples]] # 返回格式:[ENGLISH, SPANISH] return word_pairs字符转ID,ID转字符,并排序:class LanguageIndex(): def init(self,lang): self.lang=lang self.wrod2idx={} self.id2word={} self.vacab=set() self.create_index() def create_index(self): for phrase in self.lang: # 添加到集合中,重复内容不添加 self.vacab.update(phrase.split(’ ‘)) self.vacab=sorted(self.vacab) self.wrod2idx[’<pad>’]=0 #字符-ID转换 for index,word in enumerate(self.vacab): self.wrod2idx[word]=index+1 for word,index in self.wrod2idx.items(): self.id2word[index]=word加载数据集:# 计算最大长度def max_length(tensor): return max(len(t) for t in tensor)def load_dataset(path,num_example): #get inputs outputs pairs=create_dataset(path,num_example) # 获取ID表示 inp_lang=LanguageIndex(sp for en,sp in pairs) targ_lang=LanguageIndex(en for en,sp in pairs) # LanguageIndex 不包含重复值,以下包含重复值 input_tensor=[[inp_lang.wrod2idx[s]for s in sp.split(’ ‘)]for en,sp in pairs] target_tensor=[[targ_lang.wrod2idx[s]for s in en.split(’ ‘)]for en,sp in pairs] max_length_inp,max_length_tar=max_length(input_tensor),max_length(target_tensor) # 将句子补长到预设的最大长度 # padding: post:后补长,pre:前补长 input_tensor=tf.keras.preprocessing.sequence.pad_sequences( sequences=input_tensor, maxlen=max_length_inp, padding=‘post’ ) target_tensor=tf.keras.preprocessing.sequence.pad_sequences( sequences=target_tensor, maxlen=max_length_tar, padding=‘post’ ) return input_tensor,target_tensor,inp_lang,targ_lang,max_length_inp,max_length_tar创建训练集验证集:# 本次项目只使用前30000条数据num_examples = 30000input_tensor, target_tensor, inp_lang, targ_lang, max_length_inp, max_length_targ = load_dataset(path_to_file, num_examples)# 训练集80%,验证集20%input_tensor_train, input_tensor_val, target_tensor_train, target_tensor_val = train_test_split(input_tensor, target_tensor, test_size=0.2)模型训练配置# 打乱数据集BUFFER_SIZE=len(input_tensor_train)BATCH_SIZE=64# 每个epoch迭代次数N_BATCH=BUFFER_SIZE // BATCH_SIZE# 词嵌入维度embedding_dim=256# 隐藏神经元数量units=1024vocab_inp_size=len(inp_lang.wrod2idx)vocab_tar_size=len(targ_lang.wrod2idx)dataset=tf.data.Dataset.from_tensor_slices((input_tensor_train,target_tensor_train)).shuffle(BUFFER_SIZE)# drop_remainder 当剩余数据量小于batch_size时候,是否丢弃dataset=dataset.batch(BATCH_SIZE,drop_remainder=‘True’)案例Attention模型计算文章开始我们介绍了Attention模型的计算过程,相信你会很容易理解上图的内容。对每个节点具体方程实现如下:FC=全连接层,EO=编码器输出,H=隐藏层状态,X=解码器输入,模型计算过程如下表示:score = FC(tanh(FC(EO) + FC(H)))attention weights = softmax(score, axis = 1)context vector = sum(attention weights * EO, axis = 1)embedding output=解码器输入X,输入词嵌入层merged vector=concat(embedding output, context vector)将merged vector输入到GRU创建模型GRU配置:def gru(units): # 使用GPU加速运算 if tf.test.is_gpu_available(): return tf.keras.layers.CuDNNGRU(units, return_sequences=True, return_state=True, # 循环核的初始化方法 # glorot_uniform是sqrt(2 / (fan_in + fan_out))的正态分布产生 # 其中fan_in和fan_out是权重张量的扇入扇出(即输入和输出单元数目) recurrent_initializer=‘glorot_uniform’) else: return tf.keras.layers.GRU(units, return_sequences=True, return_state=True, # hard_sigmoid <= -1 输出0,>=1 输出1 ,中间为线性 recurrent_activation=‘sigmoid’, recurrent_initializer=‘glorot_uniform’)编码器:class Encoder(tf.keras.Model): def init(self, vocab_size, embedding_dim, enc_units, batch_sz): super(Encoder, self).init() self.batch_sz = batch_sz self.enc_units = enc_units self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim) self.gru = gru(self.enc_units) def call(self, x, hidden): x = self.embedding(x) output, state = self.gru(x, initial_state = hidden) return output, state def initialize_hidden_state(self): return tf.zeros((self.batch_sz, self.enc_units))解码器:class Decoder(tf.keras.Model): def init(self,vocab_size,embedding_dim,dec_units,batch_sz): super(Decoder, self).init() self.batch_sz=batch_sz self.dec_units=dec_units self.embedding=tf.keras.layers.Embedding( input_shape=vocab_size, output_dim=embedding_dim ) self.gru=gru(self.dec_units) self.fc=tf.keras.layers.Dense(units=vocab_size) # 用于计算score,即:注意力权重系数 self.W1=tf.keras.layers.Dense(self.dec_units) self.W2=tf.keras.layers.Dense(self.dec_units) self.V=tf.keras.layers.Dense(units=1) def call(self,x,hidden,ec_output): # tf.expand_dims:在指定索引出增加一维度,值为1,从索引0开始 # axis: 取值范围是[-阶数,阶数],二维的时候0指的是列,1指的是行, # 更高维度的时候,数值是由外向里增加,如:3维向量,外向内依次是:0,1,2 # 通过计算score公式可得,需要将hidden维度扩展至:[batch_size,1,hidden_size] hidden_with_time_axis=tf.expand_dims(hidden,axis=1) # score=[batch_size, max_length, 1] score=self.V(tf.nn.tanh(self.W1(ec_output)+self.W2(hidden_with_time_axis))) # 数值归一化和为1的概率分布值 attention_weight=tf.nn.softmax(score,axis=1) context_vetor=attention_weightec_output # 求和平均 context_vetor=tf.reduce_sum(context_vetor,axis=1) X=self.embedding(x) # 合并解码器embedding输出和context vector x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1) # output shape=(batch_size,time_step,hidden_size) # state shape=(batch_size,hidden_size) output,state=self.gru(x) # output[batch_size1,hidden_size] output=tf.reshape(output,shape=(-1,output.shape[2])) x-self.fc(output) return x,state,attention_weight def initilize_hidden_size(self): return tf.zeros((self.batch_sz,self.dec_units))实例化模型:encoder = Encoder(vocab_inp_size, embedding_dim, units, BATCH_SIZE)decoder = Decoder(vocab_tar_size, embedding_dim, units, BATCH_SIZE)损失函数,优化器:optimizer = tf.train.AdamOptimizer()def loss_function(real, pred): mask = 1 - np.equal(real, 0) loss_ = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=real, logits=pred) * mask return tf.reduce_mean(loss_)模型保存:checkpoint_dir = ‘./training_checkpoints’checkpoint_prefix = os.path.join(checkpoint_dir, “ckpt”)checkpoint = tf.train.Checkpoint(optimizer=optimizer, encoder=encoder, decoder=decoder)训练由于我们使用Teacher Forcing进行训练,所以我们简单介绍下。如图所示Teacher Forcing与Free-running不同,在训练过程中不再是前一时刻的hidden-state作为当前输入,而是在Ground Truth中找到对应的上一项作为当前输入。早期的RNN很弱,如果生成了非常差的结果Free-running的运行方式会导致后面的hidden-state都受到影响。Teacher Forcing运行方式就可以避免这种问题,缺点也很明显它严重依赖标签数据。# 迭代10次训练集EPOCHS = 10for epoch in range(EPOCHS): start = time.time() hidden = encoder.initialize_hidden_state() total_loss = 0 for (batch, (inp, targ)) in enumerate(dataset): loss = 0 # 先记录梯度 with tf.GradientTape() as tape: # 编码器输出 enc_output, enc_hidden = encoder(inp, hidden) dec_hidden = enc_hidden dec_input = tf.expand_dims([targ_lang.word2idx[’<start>’]] * BATCH_SIZE, 1) # 使用Teacher forcing运行方式 for t in range(1, targ.shape[1]): # 解码器输出 predictions, dec_hidden, _ = decoder(dec_input, dec_hidden, enc_output) loss += loss_function(targ[:, t], predictions) # 样本标签作为输入 dec_input = tf.expand_dims(targ[:, t], 1) batch_loss = (loss / int(targ.shape[1])) # one_loss++;batch_loss++ total_loss += batch_loss variables = encoder.variables + decoder.variables gradients = tape.gradient(loss, variables) optimizer.apply_gradients(zip(gradients, variables)) if batch % 100 == 0: print(‘Epoch {} Batch {} Loss {:.4f}’.format(epoch + 1, batch, batch_loss.numpy())) # 每迭代2次训练集保存一次模型 if (epoch + 1) % 2 == 0: checkpoint.save(file_prefix = checkpoint_prefix)翻译评估函数我们不使用teacher-forcing模式,解码器的每步输入是它前一时刻的hidden-state和编码器输出,当模型遇到 <end>标记停止运行。# 和训练模型函数代码基本一致def evaluate(sentence, encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ): attention_plot = np.zeros((max_length_targ, max_length_inp)) # 数据预处理 sentence = preprocess_sentence(sentence) # 向量化表示输入数据 inputs = [inp_lang.word2idx[i] for i in sentence.split(’ ‘)] # 后置补长 inputs = tf.keras.preprocessing.sequence.pad_sequences([inputs], maxlen=max_length_inp, padding=‘post’) inputs = tf.convert_to_tensor(inputs) result = ’’ hidden = [tf.zeros((1, units))] enc_out, enc_hidden = encoder(inputs, hidden) dec_hidden = enc_hidden # 维度扩展batch_size dec_input = tf.expand_dims([targ_lang.word2idx[’<start>’]], 0) for t in range(max_length_targ): predictions, dec_hidden, attention_weights = decoder(dec_input, dec_hidden, enc_out) # 保存权重用于稍后可视化展示 attention_weights = tf.reshape(attention_weights, (-1, )) attention_plot[t] = attention_weights.numpy() predicted_id = tf.argmax(predictions[0]).numpy() # 获取文本翻译结果 result += targ_lang.idx2word[predicted_id] + ’ ’ # 预设的结束标记 if targ_lang.idx2word[predicted_id] == ‘<end>’: return result, sentence, attention_plot # 预测值作为输入,以此输出下一时刻单词 dec_input = tf.expand_dims([predicted_id], 0) return result, sentence, attention_plot可视化权重值: fig = plt.figure(figsize=(10,10)) ax = fig.add_subplot(1, 1, 1) ax.matshow(attention, cmap=‘viridis’) fontdict = {‘fontsize’: 14} ax.set_xticklabels([’’] + sentence, fontdict=fontdict, rotation=90) ax.set_yticklabels([’’] + predicted_sentence, fontdict=fontdict) plt.show()总结本篇文章篇幅较多,不过项目的重点是Attention思想的理解,Self Attention模型具有更长的感受野,更容易捕获长距离的相互依赖特征,目前Google机器翻译模型就大量使用到Self Attention。Attention模型目前在机器翻译,图片描述任务,语音识别都有大量应用,熟练使用Attention对于解决实际问题会有很大的帮助。文章部分内容参考 Yash Katariya 和 张俊林,在此表示感谢。 ...

December 8, 2018 · 4 min · jiezi

使用Keras构建CNN网络识别森林卫星图

介绍本文我们将使用tf.keras构建一个卷积神经网络,用于识别森林卫星图。tf.keras是Tensorflow的高阶API,具有模块性,易扩展性,相比Tensorflow的Low-level API可以更快速的实现模型。Pytorch也是相当不错的框架,感兴趣的读者可以查看官方文档。伴随Tensorflow对Keras的支持,目前Keras功能已十分强大,比如对TPU,多GPU的分布式策略支持等。数据下载下载链接(文件较大)图片数据集包含4万张照片,每张照片包含两种标签:天气:晴天,阴天,雾霾等地面:农业区,居住区,道路等下载链接CSV文件包含“图片名字”,“天气标签”,“地面标签”我们希望训练的模型可以准确的预测新卫星图片的上述标签。我们的模型将会有天气,地面的两个输出,两种不同的损失函数。训练完成后,我们会导出模型使用Tensorflow Serving部署。创建模型import tensorflow as tfIMG_SIZE=128img_input=tf.keras.Input(shape=(IMG_SIZE,IMG_SIZE,3),name=‘input_layer’)conv_1_32=tf.keras.layers.Conv2D( filters=32, # 卷积核为奇数:1,奇数具有中心点 2,图像两边可以对称padding kernel_size=3, padding=‘same’, # 激活函数在输入为负值时激活值为0,此时神经元无法学习,LeakyReLU在输入为负值时不为0(但值很小) activation=‘relu’)(img_input)pool_1_2=tf.keras.layers.MaxPooling2D( # 默认过滤器大小和步长(2,2) padding=‘same’)(conv_1_32)conv_2_32=tf.keras.layers.Conv2D( filters=32, kernel_size=3, padding=‘same’, activation=‘relu’)(pool_1_2)pool_2_2=tf.keras.layers.MaxPooling2D( padding=‘same’)(conv_2_32)# 将输出展开conv_flat=tf.keras.layers.Flatten()(pool_2_2)fc_1_128=tf.keras.layers.Dense( units=128, activation=‘relu’)(conv_flat)# 仅训练时设置fc_1_drop=tf.keras.layers.Dropout( rate=0.2)(fc_1_128)fc_2_128=tf.keras.layers.Dense( units=128, activation=‘relu’)(fc_1_drop)fc_2_drop=tf.keras.layers.Dropout( rate=0.2)(fc_2_128)# 天气标签输出weather_output=tf.keras.layers.Dense( units=4, activation=‘softmax’, name=‘weather’)(fc_2_drop)# 地面标签输出ground_outpout=tf.keras.layers.Dense( units=13, # 对于类别大于2的分类问题,如果类别互斥使用softmax,反之使用sigmoid activation=‘sigmoid’, name=‘ground’)(fc_2_drop)model=tf.keras.Model( inputs=img_input, outputs=[weather_output,ground_outpout])各层参数数量模型配置:这里有必要简单介绍下Adam算法:Adam 通过计算梯度的一阶矩估计和二阶矩估计而为不同的参数设计独立的自适应性学习率。Adam 算法同时获得了 AdaGrad 和 RMSProp 算法的优点。Adam 不仅如 RMSProp 算法那样基于一阶矩均值计算适应性参数学习率,它同时还充分利用了梯度的二阶矩均值。梯度对角缩放的不变性适用于解决包含很高噪声或稀疏梯度的问题model.compile( # 也可尝试下带动量的SGD # Adam 默认参数值:lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0. optimizer=‘adam’, loss={ # 注意这里损失函数对应的激活函数 “weather”:‘categorical_crossentropy’, ‘ground’:‘binary_crossentropy’ })模型训练import astimport numpy as npimport mathimport osimport randomimport pandas as pdfrom tensorflow.keras.preprocessing.image import img_to_arrayfrom tensorflow.keras.preprocessing.image import load_imgdef load_image(img_path,img_size): # /255 将像素值由 0-255 转为 0-1 区间 return img_to_array(load_img(img_path,target_size=(img_size,img_size)))/255.class KagglePlanetSequence(tf.keras.utils.Sequence): def init(self,df_path,data_path,img_size,batch_size,mode=‘train’): self.df=pd.read_csv(df_path) self.img_size=img_size self.batch_size=batch_size self.mode=mode # ast.literal_eval(x) 功能同 eval,如:"[1,2,3]“转为[1,2,3],但增加了非法字符处理 self.w_lables=self.df[‘weather_labels’].apply(lambda x:ast.literal_eval(x)).tolist() self.g_lables=self.df[‘ground_labels’].apply(lambda x:ast.literal_eval(x)).tolist() self.imges_list=self.df[‘image_name’].apply(lambda x:os.path.join(data_path,x+’.jpg’)).tolist() def len(self): # math.ceil 向上取整,返回:大于或等于输入数值 # 计算每个epoch内训练步数 return int(math.ceil(len(self.df)/float(self.batch_size))) # 打乱数据 def on_epoch_end(self): self.indexes=range(len(self.imges_list)) if self.mode == ’train’: self.indexes=random.sample(self.indexes,k=len(self.indexes)) # 以下较简单,别把区间算错就好 def get_batch_labels(self,idx): return [ self.w_lables[idx*self.batch_size:(idx+1)self.batch_size], self.g_lables[idxself.batch_size:(idx+1)self.batch_size] ] def get_batch_feature(self,idx): batch_images=self.imges_list[ idxself.batch_size:(idx+1)*self.batch_size ] return np.array([load_image(img,self.img_size) for img in batch_images]) def getitem(self, idx): batch_x=self.get_batch_feature(idx) batch_y=self.get_batch_labels(idx) return batch_x,batch_y seq=KagglePlanetSequence(’./KagglePlaneMCML.csv’,’./data/train/’, img_size=IMG_SIZE,batch_size=32)在训练期间通过添加callbacks,可以实现“保存模型”,‘提前停止训练’等功能。本次,我们使用ModelCheckPoint在每迭代一次训练集后保存模型。callbacks=[ # .h5 保存参数和图 # 1为输出进度条记录,2为每个epoch输出一行记录 tf.keras.callbacks.ModelCheckpoint(’./models.h5’,verbose=1)]# fit_generator分批次产生数据,可以节约内存model.fit_generator( generator=seq, verbose=1, epochs=1, # 使用基于进程的线程 use_multiprocessing=True, # 进程数量 workers=4, callbacks=callbacks)读取已保存的模型,用于训练anther_model=tf.keras.models.load_model(’./model.h5’)anther_model.fit_generator(generator=seq,verbose=1,epochs=1)测试模型test_sq=KagglePlanetSequence( ‘./KagglePlaneMCML.csv’, ‘./data/train/’, img_size=IMG_SIZE, batch_size=32, mode=‘test’)predictons=model.predict_generator( generator=test_sq,verbose=1)使用DataSet作为输入TFRecord文件中的数据是通过tf.train.Example Protocol Buffer格式存储,其中包含一个从属性名称到取值的字典,属性的取值可以为”BytesList“,”FloatList“或者”Int64List“。此外,TFRecord的值可以作为Cloud MLEngine的输入。我们首先将图片和标签保存为TFRecord文件def bytes_feature(value): return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))tf_records_filename=’./data/KajgglePlaneTFRecord{}’.format(IMG_SIZE)writer=tf.python_io.TFRecordWriter(tf_records_filename)# 获取对应数据df_train={}img_list=[os.path.join(’./data/train’,v+’.jpg’) for v in df_train[‘image_name’].tolist()]w_lables_arr=np.array([ast.literal_eval(l) for l in df_train[‘weather_labels’]])g_lables_arr=np.array([ast.literal_eval(l) for l in df_train[‘ground_labels’]])# 文件写入for i in range(len(df_train)): w_labels=w_lables_arr[i] g_lables=g_lables_arr[i] img=np.array([load_image(img_list[i],IMG_SIZE)]) example=tf.train.Example( features=tf.train.Feature( # 读取的时候使用该key feattures={ ‘image’:_bytes_feature(img.tostring()), ‘weather_labels’:_bytes_feature(w_lables_arr.tostring()), ‘ground_lables’:_bytes_feature(g_lables_arr.tostring()) } ) ) writer.write(example.SerizlizeToString())writer.close()DataSet读取TFRecord文件”“”提供两种解析方法,一种是tf.FixedLenFeature,解析结果为tensor,另一种是tf.VarLenFeature得到的结果是SparseTensor,用于处理稀疏数据。当然,读取数据和写入数据的格式要一致。“”“featdef={ ‘image’:tf.FixedLenFeature(shape=[],dtype=tf.string), ‘weather_lables’:tf.FixedLenFeature(shape=[],dtype=tf.string), ‘ground_labels’:tf.FixedLenFeature(shape=[],dtype=tf.string)}def parse_record(tfre_file,clip=False): file=tf.parse_single_example(tfre_file,features=featdef) # tf.decode_raw 将字符串解析成图像对应的像素数组 img=tf.reshape(tf.decode_raw(file[‘image’],tf.float32),shape=(IMG_SIZE,IMG_SIZE,3)) weather=tf.decode_raw(file[‘weather_lables’],tf.float32) ground=tf.decode_raw(file[‘ground_lables’],tf.float32) return img,weather,groundds_train=tf.data.TFRecordDataset(filenames=’./data/KanglePlaneTFRecord{}’.format(IMG_SIZE),map=_parse_record)ds_train=ds_train.shuffle(buffer_size=1000).batch(32)模型训练model=tf.keras.Model(inputs=img_input,outputs=[weather_output,ground_outpout])model.compile( optimizer=‘adam’, loss={ ‘weather’:‘categorical_crossentropy’, ‘ground’:‘binary_crossentropy’ })# history.history:loss values and metrics values# 通过history_rest.history可以获取loss value metrics value等信息history_rest=model.fit(ds_train,steps_per_epoch=100,epochs=1)模型导出Tensorflow Serving框架 如需了解更多有关内容,请查看官网介绍。 使用TensorFlow Serving 部署我们只能使用SaveModel方法。# 模型导出,官方推荐使用save_model# 如果需要更多自定义功能请使用SavedModelBuilder# 返回训练模式/测试模式的flag# learning_phase: 0 train model 1 test modeltf.keras.backend.set_learning_phase(1)model=tf.keras.models.load_model(’./model.h5’)export_path=’./PlaneModel/1’with tf.keras.backend.get_session() as sess: tf.saved_model.simple_save( session=sess, export_dir=export_path, inputs={ ‘input_image’:model.input }, outputs={ t.name:t for t in model.outputs } )模型保存后的文件结构:$ tree.└── 1 ├── saved_model.pb └── variables ├── variables.data-00000-of-00001 └── variables.index重新训练的模型可以放到”PlanetModel/2“文件夹内,此时TensorFlow Serving会自动更新。TensorFlow Serving安装1.安装Docker2.安装Serving imagedocker pull tensorflow/serving3,运行Tensorflow ServinggRPC默认端口:8500REST API默认端口:8501默认环境变量MODEL_NAME:“model” MODEL_BASE_PATH:"/models"tensorflow_model_server –model_base_path=$(pwd) –rest_api_port=9000 –model_name=PlanetModel4,模型预测请求格式要求{ # 如多人开发,最好自定义 “signature_name”: <string>, # 只可包含以下任意一项 “instances”: <value>|<(nested)list>|<list-of-objects> “inputs”: <value>|<(nested)list>|<object>}import requestsimport json# 不要忘了归一化处理image = img_to_array(load_img(’./data/train/train_10001.jpg’, target_size=(128,128))) / 255.payload={ ‘instances’:[{‘input_images’:image.tolist()}]}result=requests.post(‘http://localhost:9000/v1/models/PlanetModel:predict’,json=payload)json.load(result.content)5,预测结果返回格式{ “predictions”: <value>|<(nested)list>|<list-of-objects>}{u’predictions’: [ {u’ground_2/Sigmoid:0’: [ 0.153237, 0.000527727, 0.00555856, 0.00542973, 0.00105254, 0.000256282, 0.103614, 0.0325185, 0.998204, 0.072204, 0.00745501, 0.00326175, 0.0942268], u’weather_2/Softmax:0’: [ 0.963947, 0.000207846, 0.00113924, 0.0347063] }]}总结本次项目只是简单的案例,主要是为了熟悉相关模块的使用,项目本身还有很多需要优化的地方。keras清晰,友好可以快速实现你的想法,还可以结和“Eager Execution” “Estimator”使用。虽然keras优点很多,但由于它高度封装,所以想进一步了解Tensorflow的朋友,掌握Low-level API还是很有必要的。本文实现参考Stijn Decubber的文章,欢迎关注他的博客。 ...

December 4, 2018 · 2 min · jiezi

什么是TensorFlow会话?

我已经看到了很多人对TensorFlow的tf.Graph和tf.Session的规则感到困惑。其实很简单:Graph(图形)定义了计算。但它不计算任何东西,也不包含任何值,它只是定义您在代码中指定的操作。Session(会话)允许执行图形或部分图形。它为此分配资源(在一台或多台机器上)并保存中间结果和变量的实际值。我们来看一个例子。定义图表我们使用一个变量和三个操作定义一个Graph(图形):variable 返回变量的当前值。initialize 将初始值42分配给该变量。assign 将新值13赋给该变量。graph = tf.Graph()with graph.as_default(): variable = tf.Variable(42, name=‘foo’) initialize = tf.global_variables_initializer() assign = variable.assign(13)旁注:TensorFlow 为您创建一个默认图形,因此我们不需要上面代码的前两行。默认图表也是下一小节中的会话在不手动指定图表时使用的缺省值。在Session(会话)中运行计算要运行三个定义的操作中的任何一个,我们需要为该图创建一个会话。会话还将分配内存来存储变量的当前值。with tf.Session(graph=graph) as sess: sess.run(initialize) sess.run(assign) print(sess.run(variable)) # Output: 13如您所见,我们的变量值仅在一个会话中有效。如果我们尝试在第二个会话中查询该值,TensorFlow 将引发错误,因为该变量未在那里初始化。with tf.Session(graph=graph) as sess: print(sess.run(variable))# Error: Attempting to use uninitialized value foo当然,我们可以在多个会话中使用该图,我们只需要再次初始化变量。新会话中的值将完全独立于第一个:with tf.Session(graph=graph) as sess: sess.run(initialize) print(sess.run(variable)) # Output: 42希望这个简短的工作可以帮助您更好地理解tf.Session。随意在评论中提问。更新(时间2017-07-12):在TensorFlow 1.0中初始化变量的操作已更改。您可以在开放的CC BY-SA 3.0 许可下使用此帖子, 并将其引用为:@misc {hafner2016tfsession, 作者= {Hafner,Danijar}, title = {什么是TensorFlow会话?}, 年= {2016}, howpublished = {博客文章}, url = {https://danijar.com/what-is-a-tensorflow-session/}}

December 4, 2018 · 1 min · jiezi

用机器学习来做人脸性别识别

原博地址https://laboo.top/2018/12/02/tfjs-face/#more在传统编程中, 图像识别一直是一个难点, 虽然人能轻松做到, 但是用逻辑来描述这个过程, 并转换成程序是很难的。机器学习的出现让图像识别技术有了突破性的进展, 卷积神经网络的出现, 又使图像识别更上了一次层次。卷积神经网络由一个或多个卷积层和顶端的全连通层组成, 这一结构使得卷积神经网络能够利用输入数据的二维结构。与其他深度学习结构相比,卷积神经网络在图像和语音识别方面能够给出更好的结果。这里我们使用卷积神经网络对人脸进行性别识别, 项目中使用了TensorFlow机器学习库。项目地址face-gender-classification数据收集与处理机器学习的基础就是大量的数据。我以前从网上爬了一万张证件照, 现在正好用上, 作为训练数据。简便的也可以从谷歌直接搜搜索 男(女)性证件照也可以得到并且有标签的数据。由于我收集的照片没有标签, 于是我花了一点时间从其中人工选出男女照片各200张并打上标记。为了使识别更加准确, 项目中利用openCV裁剪出人脸部分的图像, 并缩放至28*28大小。recognizer = cv2.CascadeClassifier(“model/haarcascade_frontalface_default.xml”)crop(img_path): try: img = cv2.imread(img_path) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = recognizer.detectMultiScale(gray) if len(faces): x, y, w, h = faces[0] c_img = img[y:y + h, x:x + w] return cv2.resize(c_img, (28, 28), interpolation=cv2.INTER_AREA) except: pass return None对所有的数据都进行这样处理, 结果如下:最后我们还需要清理异常的数据, 过一遍训练集, 把其中没有定位到人脸的图片去除掉。训练模型读取训练数据。def read_img(files): arr = [] for file in files: img = Image.open("%s" % file) pix = img.load() view = np.zeros((IMAGE_H, IMAGE_W, 1), dtype=np.float) for x in range(IMAGE_H): for y in range(IMAGE_W): r, g, b = pix[y, x] view[x, y, 0] = (r + g + b) // 3 arr.append(view) return np.array(arr)这里对训练图像灰度化, 并且将训练数据中的一小部分作为验证集。开始创建模型。model = keras.Sequential([ keras.layers.Conv2D(32, (3, 3), input_shape=(IMAGE_W, IMAGE_H, 1), strides=(1, 1), activation=‘relu’), keras.layers.MaxPool2D(pool_size=(2, 2)), keras.layers.Conv2D(64, (3, 3), strides=(1, 1), activation=‘relu’), keras.layers.MaxPool2D(pool_size=(2, 2)), keras.layers.Flatten(), keras.layers.Dense(128, activation=tf.nn.relu), keras.layers.Dropout(0.2), keras.layers.Dense(2, activation=tf.nn.softmax)])选择适当的优化器和损失函数编译模型。model.compile(optimizer=tf.train.AdamOptimizer(learning_rate=0.001), loss=‘categorical_crossentropy’, metrics=[‘accuracy’])开始训练模型。model.fit(x=train_x, y=train_y, batch_size=32, epochs=30, verbose=1, callbacks=my_callbacks, validation_split=0.05, shuffle=True )测试模型这里使用matplotlib来显示测试图片及结果。predictions = model.predict(test_x)class_names = [“Female”, “Male”]plt.figure(figsize=(12, 6))for i in range(min(9, len(test_y))): result = predictions[i] max_label = int(np.argmax(result)) correct_label = int(np.argmax(test_y[i])) plt.subplot(3, 6, 2 * i + 1) plt.grid(False) plt.xticks([]) plt.yticks([]) img = test_x.reshape(test_x.shape[0], IMAGE_W, IMAGE_H)[i] plt.imshow(img, cmap=“gray”) plt.xlabel("{} - prob:{:2.0f}%".format(class_names[max_label], 100 * np.max(result))) plt.subplot(3, 6, 2 * i + 2) plt.grid(False) plt.yticks([]) plt.ylim([0, 1]) bar = plt.bar(range(2), result) bar[max_label].set_color(‘red’) bar[correct_label].set_color(‘green’)plt.show()脸部头像右侧的两列分别代表女性概率和男性概率。这里我们看到全都对了, 正确率非常高。模型并不复杂, 大部分工作都在收集数据和调整训练参数上, 这也体现出了卷积神经网络对图像强大的处理能力。欢迎关注我的博客公众号 ...

December 2, 2018 · 1 min · jiezi

TensorFlow.js 卷积神经网络手写数字识别

原博地址https://laboo.top/2018/11/21/tfjs-dr/源码digit-recognizerdemohttps://github-laziji.github.io/digit-recognizer/演示开始时需要加载大概100M的训练数据, 稍等片刻调整训练集的大小, 观察测试结果的准确性数据来源数据来源与 https://www.kaggle.com 中的一道题目 digit-recognizer题目给出42000条训练数据(包含图片和标签)以及28000条测试数据(只包含图片)要求给这些测试数据打上标签[0,1,2,3….,9] 要尽可能的准确网站中还有许多其他的机器学习的题目以及数据, 是个很好的练手的地方实现这里我们使用TensorFlow.js来实现这个项目创建模型卷积神经网络的第一层有两种作用, 它既是输入层也是执行层, 接收IMAGE_H * IMAGE_W大小的黑白像素最后一层是输出层, 有10个输出单元, 代表着0-9这十个值的概率分布, 例如 Label=2 , 输出为[0.02,0.01,0.9,…,0.01]function createConvModel() { const model = tf.sequential(); model.add(tf.layers.conv2d({ inputShape: [IMAGE_H, IMAGE_W, 1], kernelSize: 3, filters: 16, activation: ‘relu’ })); model.add(tf.layers.maxPooling2d({ poolSize: 2, strides: 2 })); model.add(tf.layers.conv2d({ kernelSize: 3, filters: 32, activation: ‘relu’ })); model.add(tf.layers.maxPooling2d({ poolSize: 2, strides: 2 })); model.add(tf.layers.conv2d({ kernelSize: 3, filters: 32, activation: ‘relu’ })); model.add(tf.layers.flatten({})); model.add(tf.layers.dense({ units: 64, activation: ‘relu’ })); model.add(tf.layers.dense({ units: 10, activation: ‘softmax’ })); return model;}训练模型我们选择适当的优化器和损失函数, 来编译模型async function train() { ui.trainLog(‘Create model…’); model = createConvModel(); ui.trainLog(‘Compile model…’); const optimizer = ‘rmsprop’; model.compile({ optimizer, loss: ‘categoricalCrossentropy’, metrics: [‘accuracy’], }); const trainData = Data.getTrainData(ui.getTrainNum()); ui.trainLog(‘Training model…’); await model.fit(trainData.xs, trainData.labels, {}); ui.trainLog(‘Completed!’); ui.trainCompleted();}测试这里测试一组测试数据, 返回对应的标签, 即十个输出单元中概率最高的下标function testOne(xs){ if(!model){ ui.viewLog(‘Need to train the model first’); return; } ui.viewLog(‘Testing…’); let output = model.predict(xs); ui.viewLog(‘Completed!’); output.print(); const axis = 1; const predictions = output.argMax(axis).dataSync(); return predictions[0];}欢迎关注我的博客公众号 ...

November 21, 2018 · 1 min · jiezi

Tensorflow学习之建造神经网络

经过前期的学习,这一节来学习稍微综合一点的,建造一个完整的神经网络,包括添加神经层,计算误差,训练步骤,判断是否在学习。添加层构造添加一个神经层的函数。def add_layer(inputs, in_size, out_size, activation_function=None): Weights = tf.Variable(tf.random_normal([in_size, out_size])) biases = tf.Variable(tf.zeros([1, out_size]) + 0.1) Wx_plus_b = tf.matmul(inputs, Weights) + biases if activation_function is None: outputs = Wx_plus_b else: outputs = activation_function(Wx_plus_b) return outputs导入数据构建所需的数据。 这里的x_data和y_data并不是严格的一元二次函数的关系,因为我们多加了一个noise,这样看起来会更像真实情况。x_data = np.linspace(-1,1,300, dtype=np.float32)[:, np.newaxis]noise = np.random.normal(0, 0.05, x_data.shape).astype(np.float32)y_data = np.square(x_data) - 0.5 + noise利用占位符定义我们所需的神经网络的输入。 tf.placeholder()就是代表占位符,这里的None代表无论输入有多少都可以,因为输入只有一个特征,所以这里是1。xs = tf.placeholder(tf.float32, [None, 1])ys = tf.placeholder(tf.float32, [None, 1])接下来,我们就可以开始定义神经层了。 通常神经层都包括输入层、隐藏层和输出层。这里的输入层只有一个属性, 所以我们就只有一个输入;隐藏层我们可以自己假设,这里我们假设隐藏层有10个神经元; 输出层和输入层的结构是一样的,所以我们的输出层也是只有一层。 所以,我们构建的是——输入层1个、隐藏层10个、输出层1个的神经网络。搭建网络下面,我们开始定义隐藏层,利用之前的add_layer()函数,这里使用 Tensorflow 自带的激励函数tf.nn.relu。l1 = add_layer(xs, 1, 10, activation_function=tf.nn.relu)接着,定义输出层。此时的输入就是隐藏层的输出——l1,输入有10层(隐藏层的输出层),输出有1层。prediction = add_layer(l1, 10, 1, activation_function=None)计算预测值prediction和真实值的误差,对二者差的平方求和再取平均。loss = tf.reduce_mean(tf.reduce_sum(tf.square(ys - prediction), reduction_indices=[1]))接下来,是很关键的一步,如何让机器学习提升它的准确率。tf.train.GradientDescentOptimizer()中的值通常都小于1,这里取的是0.1,代表以0.1的效率来最小化误差loss。train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)使用变量时,都要对它进行初始化,这是必不可少的。# init = tf.initialize_all_variables() # tf 马上就要废弃这种写法init = tf.global_variables_initializer() # 替换成这样就好定义Session,并用 Session 来执行 init 初始化步骤。 (注意:在tensorflow中,只有session.run()才会执行我们定义的运算。)sess = tf.Session()sess.run(init)训练下面,让机器开始学习。比如这里,我们让机器学习1000次。机器学习的内容是train_step, 用 Session 来 run 每一次 training 的数据,逐步提升神经网络的预测准确性。 (注意:当运算要用到placeholder时,就需要feed_dict这个字典来指定输入。)for i in range(1000): # training sess.run(train_step, feed_dict={xs: x_data, ys: y_data})每50步我们输出一下机器学习的误差。 if i % 50 == 0: # to see the step improvement print(sess.run(loss, feed_dict={xs: x_data, ys: y_data}))完整代码:import tensorflow as tfimport numpy as np# 构造添加神经层函数def add_layer(inputs, in_size, out_size, activation_function=None): Weights = tf.Variable(tf.random_normal([in_size, out_size])) biases = tf.Variable(tf.zeros([1, out_size]) + 0.1) Wx_plus_b = tf.matmul(inputs, Weights) + biases if activation_function is None: outputs = Wx_plus_b else: outputs = activation_function(Wx_plus_b) return outputsx_data = np.linspace(-1, 1, 300, dtype=np.float32)[:, np.newaxis]noise = np.random.normal(0, 0.05, x_data.shape).astype(np.float32)y_data = np.square(x_data) - 0.5 + noise# 定义占位符xs = tf.placeholder(tf.float32, [None, 1])ys = tf.placeholder(tf.float32, [None, 1])# 假设一个输入层,十个隐藏层,一个输出层l1 = add_layer(xs, 1, 10, activation_function=tf.nn.relu)# 定义输出层prediction = add_layer(l1, 10, 1, activation_function=None)# 计算预测值prediction和真实值的误差,对二者差的平方求和再取平均loss = tf.reduce_mean(tf.reduce_sum(tf.square(ys - prediction), reduction_indices=[1]))# 梯度下降train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)# 初始化变量init = tf.global_variables_initializer()# Sessionsess = tf.Session()sess.run(init)# 机器开始学习for i in range(1000): # training if i % 50 == 0: # to see the step improvement print(sess.run(train_step, feed_dict={xs:x_data,ys:y_data})) 注:本文转载自莫烦说TensorFlow ...

November 6, 2018 · 2 min · jiezi

ApacheCN 学习资源汇总 2018.11

首页地址:http://www.apachecn.org关于我们:http://www.apachecn.org/about我们不是 Apache 的官方组织/机构/团体,只是 Apache 技术栈(以及 AI)的爱好者!Java 基础Java 编程思想Java Web 和大数据Spark 中文文档Storm 中文文档Kafka 中文文档Flink 中文文档Beam 中文文档Zeppelin 0.7.2 中文文档Elasticsearch 5.4 中文文档Kibana 5.2 中文文档Kudu 1.4.0 中文文档Spring Boot 中文文档 1.5.2.RELEASE区块链Solidity 中文文档数学笔记MIT 18.06 线性代数笔记Python 数据科学numpy 中文文档pandas 中文文档matplotlib 中文文档UCB Data8 课本:计算与推断思维UCB Prob140 课本:面向数据科学的概率论利用 Python 进行数据分析 · 第 2 版Quant Wikifast.ai 数值线性代数讲义 v2Pandas Cookbook 带注释源码LeetCode 刷题LeetCode 中文文档CS 教程GeeksForGeeks 翻译计划UCB CS61a 课本:SICP Python 描述数据结构思维AI 教程Machine Learning in Action - 机器学习实战(已追加部分 自然语言处理+深度学习 相关内容)Sklearn 与 TensorFlow 机器学习实用指南面向机器学习的特征工程自然语言处理综论(第三版)Python 数据分析与挖掘实战(带注释源码)SciPyCon 2018 Sklearn 教程TensorFlow 学习指南fast.ai 机器学习和深度学习中文笔记HackCV 网站文章翻译台湾大学林轩田机器学习笔记AI 文档sklearn 中文文档pytorch 0.3 中文文档TensorFlow R1.2 中文文档xgboost 中文文档lightgbm 中文文档fasttext 中文文档gensim 中文文档AI 比赛Kaggle 中文文档比赛收集平台 ...

November 5, 2018 · 1 min · jiezi

用“活着的”CNN进行验证码识别

1 验证码验证码( CAPTCHA )是一种区分用户是计算机或人的公共全自动程序。在 CAPTCHA 测试中,作为服务器的计算机会自动生成一个问题由用户来解答。这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答 CAPTCHA 的问题,所以回答出问题的用户就可以被认为是人类。2 CNN 验证码识别传统的方法是通过两个不相关的步骤来进行文字识别:1)将图片中的文字的位置进行定位,然后通过“小框”来切分,将图片中的文字剪切下来 2)再进行识别。但是在现今的验证码识别中,当要识别的图片中的文字变成手写体互相重叠,这种“切分”法就难以排上用场。因此卷积神经网络(CNN)就被用来识别这些无从下手的手写体。这种CNN 是通过一个或多个卷积层和顶端的全连通层(对应经典的神经网络)组成来对图像识别。CNN 训练模型需要大量的人工标注的图片来训练,但是本文方法就是自主产生随机的字符并产生相应的图片来在运行过程中调整参数。本文关注具有 4 个字符的的验证码图片。每个字符在输出层被表现为 62 个神经元。我们可以假设一个映射函数即:将前 62 个神经元分配给序列中的第一个字符,第二批 62 个神经元分配给序列中的第二个字符。因此,对于字符xi所对应的神经元的索引为输出层一共有 462=128 个。如果第一个预测字符的索引为 c0=52,因此可以反推预测的字符为3 实现步骤1 验证码生成1 验证码中的字符number = [‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’]ALPHABET = [‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’, ‘N’, ‘O’, ‘P’, ‘Q’, ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’]alphabet = [‘a’, ‘b’, ‘c’, ’d’, ’e’, ‘f’, ‘g’, ‘h’, ‘i’, ‘j’, ‘k’, ’l’, ’m’, ’n’, ‘o’, ‘p’, ‘q’, ‘r’, ’s’, ’t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’]gen_char_set = number + ALPHABET # 用于生成验证码的数据集2 生成验证码的字符 # char_set=number + alphabet + ALPHABET, char_set=gen_char_set, # char_set=number, captcha_size=4): """ 生成随机字符串,4位 :param char_set: :param captcha_size: :return: """ captcha_text = [] for i in range(captcha_size): c = random.choice(char_set) captcha_text.append(c) return captcha_text3 按照字符生成对应的验证码def gen_captcha_text_and_image(): """ 生成字符对应的验证码 :return: """ image = ImageCaptcha() captcha_text = random_captcha_text() captcha_text = ‘’.join(captcha_text) captcha = image.generate(captcha_text) captcha_image = Image.open(captcha) captcha_image = np.array(captcha_image) return captcha_text, captcha_image4 训练def crack_captcha_cnn(w_alpha=0.01, b_alpha=0.1): “““1 定义CNN cnn在图像大小是2的倍数时性能最高, 如果你用的图像大小不是2的倍数,可以在图像边缘补无用像素。 np.pad(image,((2,3),(2,2)), ‘constant’, constant_values=(255,)) # 在图像上补2行,下补3行,左补2行,右补2行 "”” x = tf.reshape(X, shape=[-1, IMAGE_HEIGHT, IMAGE_WIDTH, 1]) # w_c1_alpha = np.sqrt(2.0/(IMAGE_HEIGHTIMAGE_WIDTH)) # # w_c2_alpha = np.sqrt(2.0/(3332)) # w_c3_alpha = np.sqrt(2.0/(3364)) # w_d1_alpha = np.sqrt(2.0/(83264)) # out_alpha = np.sqrt(2.0/1024) # 3 conv layer w_c1 = tf.Variable(w_alpha * tf.random_normal([3, 3, 1, 32])) b_c1 = tf.Variable(b_alpha * tf.random_normal([32])) conv1 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(x, w_c1, strides=[1, 1, 1, 1], padding=‘SAME’), b_c1)) conv1 = tf.nn.max_pool(conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding=‘SAME’) conv1 = tf.nn.dropout(conv1, keep_prob) w_c2 = tf.Variable(w_alpha * tf.random_normal([3, 3, 32, 64])) b_c2 = tf.Variable(b_alpha * tf.random_normal([64])) conv2 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(conv1, w_c2, strides=[1, 1, 1, 1], padding=‘SAME’), b_c2)) conv2 = tf.nn.max_pool(conv2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding=‘SAME’) conv2 = tf.nn.dropout(conv2, keep_prob) w_c3 = tf.Variable(w_alpha * tf.random_normal([3, 3, 64, 64])) b_c3 = tf.Variable(b_alpha * tf.random_normal([64])) conv3 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(conv2, w_c3, strides=[1, 1, 1, 1], padding=‘SAME’), b_c3)) conv3 = tf.nn.max_pool(conv3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding=‘SAME’) conv3 = tf.nn.dropout(conv3, keep_prob) # Fully connected layer w_d = tf.Variable(w_alpha * tf.random_normal([8 * 20 * 64, 1024])) b_d = tf.Variable(b_alpha * tf.random_normal([1024])) dense = tf.reshape(conv3, [-1, w_d.get_shape().as_list()[0]]) dense = tf.nn.relu(tf.add(tf.matmul(dense, w_d), b_d)) dense = tf.nn.dropout(dense, keep_prob) w_out = tf.Variable(w_alpha * tf.random_normal([1024, MAX_CAPTCHA * CHAR_SET_LEN])) b_out = tf.Variable(b_alpha * tf.random_normal([MAX_CAPTCHA * CHAR_SET_LEN])) out = tf.add(tf.matmul(dense, w_out), b_out) # 36*4 # out = tf.reshape(out, (CHAR_SET_LEN, MAX_CAPTCHA)) # 重新变成4,36的形状 # out = tf.nn.softmax(out) return out由于时间和设备的限制,我在验证码生成字符串中删去了英文字母只剩下了数字进行训练。要不然可以算到地老天荒也还是3%的准确率。下图是gen_char_set = number + ALPHABET的训练1万多步的结果的训练截图5 总结本文采用了“活着的 CNN”进行验证码识别,可以免去大量进行人工标注的步骤,对工作效率有不小的提升。文 / JoeCDC数学爱好者编 / 荧声本文已由作者授权发布,版权属于创宇前端。欢迎注明出处转载本文。本文链接:https://knownsec-fed.com/2018…想要订阅更多来自知道创宇开发一线的分享,请搜索关注我们的微信公众号:乐趣区。欢迎留言讨论,我们会尽可能回复。欢迎点赞、收藏、留言评论、转发分享和打赏支持我们。打赏将被完全转交给文章作者。感谢您的阅读。 ...

November 1, 2018 · 2 min · jiezi

基于Opencv&Tensorflow实现实时查找停车位置

摘要: 一个小例子带你玩转Opencv+tensorflow~介绍你是不是经常在停车场周围转来转去寻找停车位。如果你的手机能准确地告诉你最近的停车位在哪里,那是不是很爽?事实证明,基于深度学习和OpenCV解决这个问题相对容易,只需获取停车场的实时视频即可。下面的GIF图片突出显示洛杉矶机场停车场上所有可用的停车位,并显示可用停车位的数量,关键是这一过程是实时的!你可以在Github repo找到我曾用过的代码。步骤概述构建这个停车检测模型主要有两个步骤:1.检测所有可用的停车位;2.确定停车位是空的还是被占用的;因为安装了摄像头视图,我们可以使用OpenCV对每个停车位进行一次映射。一旦你知道了每个停车位的位置,你就可以用深度学习来预测它是否空着。我已经在我的博客上分享了所涉及的一个高层次步骤的概述。如果你对详细代码感兴趣,请看看我的博客。检测所有可用停车位的位置检测停车位的基本思路是,所有的停车点的分隔线都是处于水平线,而一列中的停车点之间的间隔大致相等。首先使用Canny边缘检测来获得边缘图像。我还把没有停车的地方遮了起来。如下所示:然后我在边缘图像上做了霍夫变换,画出了它能识别的所有线。我只选择斜率接近于零的直线来隔离水平线。霍夫变换的输出如下:正如你所看到的那样,霍夫变换在识别停车线方面做得相当好,但是输出有噪声——几个停车线被多次检测到,而有些被遗漏了。那么我们如何解决这个问题呢?然后我用观察和直觉开始,通过使用霍夫变换返回的坐标,聚集x坐标来确定主要的停车道。利用聚类逻辑来识别车道线x坐标的间隔。这样就能识别出这里的12个车道。如下:如果所有这些看起来都很复杂,别担心5——我已经在github的jupyter notebook上一步一步地记录了代码。现在我已经知道了所有的停车道都在哪里,通过合理地假设所有的停车点大小都一样来识别每个单独的停车位。我仔细观察了结果,以确保尽可能准确地捕捉到点之间的边界。我终于能够标出每个停车位。如下:当完成了每个车位的定位之后,我们可以为每个点分配一个ID,并将其坐标保存在字典中并将它pickled了一下,以便以后能取回。这是可以实现的,因为安装了摄像头,我们不需要一次又一次地计算每个点在视图中的位置。更多详情请登录我的博客。识别该车位是否有标记现在我们有了每个停车位的映射,可以通过以下几种方式来确定这个停车位是否被占用:1.使用OpenCV检查视频图像中停车位的像素颜色是否与空停车位的颜色一致。这是一种简单的方法,但容易出错。例如,灯光的变化将会改变一个空停车位的颜色,在一天的光照变化中,这种方法将无法正确工作。如果有可能的话,这种逻辑会把灰色的汽车当成空停车位;2.使用目标检测来识别所有的汽车,然后检查汽车的位置是否与停车位重叠。我做了尝试,发现实时检测模型真的很难检测小尺寸的对象,检测到的车辆不超过30%;3.使用CNN检测每个停车位,并预测是否有人停车,这种方法最终效果最好。要构建一个CNN,我们需要停车位有车以及无车这两种情况的图像。我提取了每个停车位的图像,并将其保存在文件夹中,然后将这些图像分组。我还在Github上分享了这个训练文件夹。因为在1280x720像素的图像中有近550个车位,所以每个车位的大小只有15x60像素左右。下面是空车位和被占用车位的图像:然而,由于被占用车位和空车位看起来有很大的不同,这对CNN来说应该不是一个具有挑战性的问题。然而,我只有大约550张关于这两个类的图片,所以决定利用VGG的前10层,并在VGG模型的输出添加一个单一的softmax图层来进行迁移学习。你可以在这里找到这个迁移学习模型的代码,准确率达到94%。见下文:现在,我将停车位检测和CNN预测器结合起来,构建了一个停车位检测器,准确率非常高。我还在notebook记录了在视频流上运行这个的代码。结论对于联合不同的工具和使用深度学习来构建实际应用程序这么容易我感到很惊奇,我在两个下午完成了这项工作。准备进一步探索的其他几个想法:1.如果可以将车位检测逻辑扩展到任何可能使用深度学习的车位地图上,那就太好了,OpenCV对每个用例进行调优有限制;2.CNN使用的VGG模型是一个重量化模型,想要尝试更轻量化的模型。云服务器99元拼团购!拉新还可赢现金红包!300万等你瓜分!马上一键开团赢红包: http://click.aliyun.com/m/1000019899/本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。

October 30, 2018 · 1 min · jiezi

Docker 中快速安装tensorflow环境

Docker 中快速安装tensorflow环境,并使用TensorFlow。一、下载TensorFlow镜像docker pull tensorflow/tensorflow二、 创建TensorFlow容器docker run –name corwien-tensortflow -it -p 8888:8888 -v /Users/kaiyiwang/Code/ai/notebooks:/notebooks/data tensorflow/tensorflow命令说明docker run 运行镜像,–name 为容器创建别名,-it 保留命令行运行,-p 8888:8888 将本地的8888端口 http://localhost:8888/ 映射,-v /Users/kaiyiwang/Code/ai/notebooks:/notebooks/data 将本地的/Users/kaiyiwang/Code/ai/notebooks文件夹挂载到新建容器的/notebooks/data下(这样创建的文件可以保存到本地/Users/kaiyiwang/Code/ai/notebooks)tensorflow/tensorflow 为指定的镜像,默认标签为latest(即tensorflow/tensorflow:latest)执行上边的命令:我们可以看到,创建了TensorFlow容器,并给了一个默认登录JupiterNotebook的页面。我们可以通过下面的命令在新的命令窗口看正在执行的容器,及容器所对应的映射端口docker ps三、开启TensorFlow容器1.可以直接从命令行中右键打开连接,或者在浏览器中输入http://127.0.0.1:8888,然后将命令行中的token粘贴上去。四、开始TensorFlow编程1、点击登录进去可以看到界面了,并且可以new一个项目2、tensorflow示例源码解读from future import print_function#导入tensorflowimport tensorflow as tf#输入两个数组,input1和input2然后相加,输出结果with tf.Session(): input1 = tf.constant([1.0, 1.0, 1.0, 1.0]) input2 = tf.constant([2.0, 2.0, 2.0, 2.0]) output = tf.add(input1, input2) result = output.eval() print(“result: “, result)3、运行程序,输出的结果为(运行成功)result: [ 3. 3. 3. 3.]五、相关命令1、关闭或开启TensorFlow环境#关闭tensorflow容器docker stop corwien-tensortflow#开启TensorFlow容器docker start corwien-tensortflow#浏览器中输入 http://localhost:8888/2、文件的读写权限修改#查看读写权限ls -l#将tensorflow 变为属于corwien(系统默认)用户sudo chown -R corwien tensorflow/#将tensorflow 变为属于corwien(系统默认)用户组sudo chgrp -R corwien tensorflow/ ...

October 27, 2018 · 1 min · jiezi

基于TensorFlow Serving的深度学习在线预估

一、前言随着深度学习在图像、语言、广告点击率预估等各个领域不断发展,很多团队开始探索深度学习技术在业务层面的实践与应用。而在广告CTR预估方面,新模型也是层出不穷: Wide and Deep[1]、DeepCross Network[2]、DeepFM[3]、xDeepFM[4],美团很多篇深度学习博客也做了详细的介绍。但是,当离线模型需要上线时,就会遇见各种新的问题: 离线模型性能能否满足线上要求、模型预估如何镶入到原有工程系统等等。只有准确的理解深度学习框架,才能更好地将深度学习部署到线上,从而兼容原工程系统、满足线上性能要求。本文首先介绍下美团平台用户增长组业务场景及离线训练流程,然后主要介绍我们使用TensorFlow Serving部署WDL模型到线上的全过程,以及如何优化线上服务性能,希望能对大家有所启发。二、业务场景及离线流程2.1 业务场景在广告精排的场景下,针对每个用户,最多会有几百个广告召回,模型根据用户特征与每一个广告相关特征,分别预估该用户对每条广告的点击率,从而进行排序。由于广告交易平台(AdExchange)对于DSP的超时时间限制,我们的排序模块平均响应时间必须控制在10ms以内,同时美团DSP需要根据预估点击率参与实时竞价,因此对模型预估性能要求比较高。2.2 离线训练离线数据方面,我们使用Spark生成TensorFlow[5]原生态的数据格式tfrecord,加快数据读取。模型方面,使用经典的Wide and Deep模型,特征包括用户维度特征、场景维度特征、商品维度特征。Wide 部分有 80多特征输入,Deep部分有60多特征输入,经过Embedding输入层大约有600维度,之后是3层256等宽全连接,模型参数一共有35万参数,对应导出模型文件大小大约11M。离线训练方面,使用TensorFlow同步 + Backup Workers[6]的分布式框架,解决异步更新延迟和同步更新性能慢的问题。在分布式ps参数分配方面,使用GreedyLoadBalancing方式,根据预估参数大小分配参数,取代Round Robin取模分配的方法,可以使各个PS负载均衡。 计算设备方面,我们发现只使用CPU而不使用GPU,训练速度会更快,这主要是因为尽管GPU计算上性能可能会提升,但是却增加了CPU与GPU之间数据传输的开销,当模型计算并不太复杂时,使用CPU效果会更好些。同时我们使用了Estimator高级API,将数据读取、分布式训练、模型验证、TensorFlow Serving模型导出进行封装。使用Estimator的主要好处在于:单机训练与分布式训练可以很简单的切换,而且在使用不同设备:CPU、GPU、TPU时,无需修改过多的代码。Estimator的框架十分清晰,便于开发者之间的交流。初学者还可以直接使用一些已经构建好的Estimator模型:DNN模型、XGBoost模型、线性模型等。三、TensorFlow Serving及性能优化3.1 TensorFlow Serving介绍TensorFlow Serving是一个用于机器学习模型Serving的高性能开源库,它可以将训练好的机器学习模型部署到线上,使用gRPC作为接口接受外部调用。TensorFlow Serving支持模型热更新与自动模型版本管理,具有非常灵活的特点。下图为TensorFlow Serving整个框架图。Client端会不断给Manager发送请求,Manager会根据版本管理策略管理模型更新,并将最新的模型计算结果返回给Client端。TensorFlow Serving架构,图片来源于TensorFlow Serving官方文档 美团内部由数据平台提供专门TensorFlow Serving通过YARN分布式地跑在集群上,其周期性地扫描HDFS路径来检查模型版本,并自动进行更新。当然,每一台本地机器都可以安装TensorFlow Serving进行试验。在我们站外广告精排的场景下,每来一位用户时,线上请求端会把该用户和召回所得100个广告的所有信息,转化成模型输入格式,然后作为一个Batch发送给TensorFlow Serving,TensorFlow Serving接受请求后,经过计算得到CTR预估值,再返回给请求端。部署TensorFlow Serving的第一版时,QPS大约200时,打包请求需要5ms,网络开销需要固定3ms左右,仅模型预估计算需要10ms,整个过程的TP50线大约18ms,性能完全达不到线上的要求。接下来详细介绍下我们性能优化的过程。3.2 性能优化3.2.1 请求端优化线上请求端优化主要是对一百个广告进行并行处理,我们使用OpenMP多线程并行处理数据,将请求时间性能从5ms降低到2ms左右。#pragma omp parallel for for (int i = 0; i < request->ad_feat_size(); ++i) { tensorflow::Example example; data_processing();}3.2.2 构建模型OPS优化在没有进行优化之前,模型的输入是未进行处理的原格式数据,例如,渠道特征取值可能为:‘渠道1’、‘渠道2’ 这样的string格式,然后在模型里面做One Hot处理。最初模型使用了大量的高阶tf.feature_column对数据进行处理, 转为One Hot和embedding格式。 使用tf.feature_column的好处是,输入时不需要对原数据做任何处理,可以通过feature_column API在模型内部对特征做很多常用的处理,例如:tf.feature_column.bucketized_column可以做分桶,tf.feature_column.crossed_column可以对类别特征做特征交叉。但特征处理的压力就放在了模型里。 为了进一步分析使用feature_column的耗时,我们使用tf.profiler工具,对整个离线训练流程耗时做了分析。在Estimator框架下使用tf.profiler是非常方便的,只需加一行代码即可。with tf.contrib.tfprof.ProfileContext(job_dir + ‘/tmp/train_dir’) as pctx: estimator = tf.estimator.Estimator(model_fn=get_model_fn(job_dir), config=run_config, params=hparams) 下图为使用tf.profiler,网络在向前传播的耗时分布图,可以看出使用feature_column API的特征处理耗费了很大时间。优化前profiler记录, 前向传播的耗时占总训练时间55.78%,主要耗费在feature_column OPS对原始数据的预处理 为了解决特征在模型内做处理耗时大的问题,我们在处理离线数据时,把所有string格式的原生数据,提前做好One Hot的映射,并且把映射关系落到本地feature_index文件,进而供线上线下使用。这样就相当于把原本需要在模型端计算One Hot的过程省略掉,替代为使用词典做O(1)的查找。同时在构建模型时候,使用更多性能有保证的低阶API替代feature_column这样的高阶API。下图为性能优化后,前向传播耗时在整个训练流程的占比。可以看出,前向传播的耗时占比降低了很多。优化后profiler记录,前向传播耗时占总训练时间39.53%3.2.3 XLA,JIT编译优化TensorFlow采用有向数据流图来表达整个计算过程,其中Node代表着操作(OPS),数据通过Tensor的方式来表达,不同Node间有向的边表示数据流动方向,整个图就是有向的数据流图。XLA(Accelerated Linear Algebra)是一种专门对TensorFlow中线性代数运算进行优化的编译器,当打开JIT(Just In Time)编译模式时,便会使用XLA编译器。整个编译流程如下图所示: TensorFlow计算流程 首先TensorFlow整个计算图会经过优化,图中冗余的计算会被剪掉。HLO(High Level Optimizer)会将优化后的计算图 生成HLO的原始操作,XLA编译器会对HLO的原始操作进行一些优化,最后交给LLVM IR根据不同的后端设备,生成不同的机器代码。JIT的使用,有助于LLVM IR根据 HLO原始操作生成 更高效的机器码;同时,对于多个可融合的HLO原始操作,会融合成一个更加高效的计算操作。但是JIT的编译是在代码运行时进行编译,这也意味着运行代码时会有一部分额外的编译开销。网络结构、Batch Size对JIT性能影响[7]上图显示为不同网络结构,不同Batch Size下使用JIT编译后与不使用JIT编译的耗时之比。可以看出,较大的Batch Size性能优化比较明显,层数与神经元个数变化对JIT编译优化影响不大。在实际的应用中,具体效果会因网络结构、模型参数、硬件设备等原因而异。3.2.4 最终性能经过上述一系列的性能优化,模型预估时间从开始的10ms降低到1.1ms,请求时间从5ms降到2ms。整个流程从打包发送请求到收到结果,耗时大约6ms。模型计算时间相关参数:QPS:1308,50line:1.1ms,999line:3.0ms。下面四个图分别为:耗时分布图显示大部分耗时控制在1ms内;请求次数显示每分钟请求大约8万次,折合QPS为1308;平均耗时时间为1.1ms;成功率为100%3.3 模型切换毛刺问题通过监控发现,当模型进行更新时,会有大量的请求超时。如下图所示,每次更新都会导致有大量请求超时,对系统的影响较大。通过TensorFlow Serving日志和代码分析发现,超时问题主要源于两个方面,一方面,更新、加载模型和处理TensorFlow Serving请求的线程共用一个线程池,导致切换模型时候无法处理请求;另一方面,模型加载后,计算图采用Lazy Initialization方式,导致第一次请求需要等待计算图初始化。模型切换导致请求超时问题一主要是因为加载和卸载模型线程池配置问题,在源代码中:uint32 num_load_threads = 0;uint32 num_unload_threads = 0;这两个参数默认为 0,表示不使用独立线程池,和Serving Manager在同一个线程中运行。修改成1便可以有效解决此问题。模型加载的核心操作为RestoreOp,包括从存储读取模型文件、分配内存、查找对应的Variable等操作,其通过调用Session的run方法来执行。而默认情况下,一个进程内的所有Session的运算均使用同一个线程池。所以导致模型加载过程中加载操作和处理Serving请求的运算使用同一线程池,导致Serving请求延迟。解决方法是通过配置文件设置,可构造多个线程池,模型加载时指定使用独立的线程池执行加载操作。对于问题二,模型首次运行耗时较长的问题,采用在模型加载完成后提前进行一次Warm Up运算的方法,可以避免在请求时运算影响请求性能。这里使用Warm Up的方法是,根据导出模型时设置的Signature,拿出输入数据的类型,然后构造出假的输入数据来初始化模型。通过上述两方面的优化,模型切换后请求延迟问题得到很好的解决。如下图所示,切换模型时毛刺由原来的84ms降低为4ms左右。优化后模型切换后,毛刺降低四、总结与展望本文主要介绍了用户增长组基于Tensorflow Serving在深度学习线上预估的探索,对性能问题的定位、分析、解决;最终实现了高性能、稳定性强、支持各种深度学习模型的在线服务。在具备完整的离线训练与在线预估框架基础之后,我们将会加快策略的快速迭代。在模型方面,我们可以快速尝试新的模型,尝试将强化学习与竞价结合;在性能方面,结合工程要求,我们会对TensorFlow的图优化、底层操作算子、操作融合等方面做进一步的探索;除此之外,TensorFlow Serving的预估功能可以用于模型分析,谷歌也基于此推出What-If-Tools来帮助模型开发者对模型深入分析。最后,我们也会结合模型分析,对数据、特征再做重新的审视。参考文献[1] Cheng, H. T., Koc, L., Harmsen, J., Shaked, T., Chandra, T., Aradhye, H., … & Anil, R. (2016, September). Wide & deep learning for recommender systems. In Proceedings of the 1st Workshop on Deep Learning for Recommender Systems (pp. 7-10). ACM.[2] Wang, R., Fu, B., Fu, G., & Wang, M. (2017, August). Deep & cross network for ad click predictions. In Proceedings of the ADKDD'17 (p. 12). ACM. [3] Guo, H., Tang, R., Ye, Y., Li, Z., & He, X. (2017). Deepfm: a factorization-machine based neural network for ctr prediction. arXiv preprint arXiv:1703.04247.[4] Lian, J., Zhou, X., Zhang, F., Chen, Z., Xie, X., & Sun, G. (2018). xDeepFM: Combining Explicit and Implicit Feature Interactions for Recommender Systems. arXiv preprint arXiv:1803.05170.[5] Abadi, M., Barham, P., Chen, J., Chen, Z., Davis, A., Dean, J., … & Kudlur, M. (2016, November). TensorFlow: a system for large-scale machine learning. In OSDI (Vol. 16, pp. 265-283).[6] Goyal, P., Dollár, P., Girshick, R., Noordhuis, P., Wesolowski, L., Kyrola, A., … & He, K. (2017). Accurate, large minibatch SGD: training imagenet in 1 hour. arXiv preprint arXiv:1706.02677.[7] Neill, R., Drebes, A., Pop, A. (2018). Performance Analysis of Just-in-Time Compilation for Training TensorFlow Multi-Layer Perceptrons.作者简介仲达,2017年毕业于美国罗彻斯特大学数据科学专业,后在加州湾区Stentor Technology Company工作,2018年加入美团,主要负责用户增长组深度学习、强化学习落地业务场景工作。鸿杰,2015年加入美团点评。美团平台与酒旅事业群用户增长组算法负责人,曾就职于阿里,主要致力于通过机器学习提升美团点评平台的活跃用户数,作为技术负责人,主导了美团DSP广告投放、站内拉新等项目的算法工作,有效提升营销效率,降低营销成本。廷稳,2015年加入美团点评。在美团点评离线计算方向先后从事YARN资源调度及GPU计算平台建设工作。招聘美团DSP是美团在线数字营销的核心业务方向,加入我们,你可以亲身参与打造和优化一个可触达亿级用户的营销平台,并引导他们的生活娱乐决策。同时,你也会直面如何精准,高效,低成本营销的挑战,也有机会接触到计算广告领域前沿的AI算法体系和大数据解决方案。你会和美团营销技术团队一起推动建立流量运营生态,支持酒旅、外卖、到店、打车、金融等业务继续快速的发展。我们诚邀有激情、有想法、有经验、有能力的你,和我们一起并肩奋斗!参与美团点评站外广告投放体系的实现,基于大规模用户行为数据,优化在线广告算法,提升DAU,ROI, 提高在线广告的相关度、投放效果。欢迎邮件wuhongjie#meituan.com咨询。 ...

October 12, 2018 · 2 min · jiezi

走进机器学习世界之TensorFlow.js快速上手

前言近两年人工智能,机器学习等各种概念漫天飞舞,那人工智能,机器学习,深度学习这些名词之间是什么关系呢?如果用三个同心圆来解释的话,人工智能是最大的圆,机器学习是中间的圆,深度学习是最小的圆。具体解释就是:机器学习是实现人工智能的一种手段深度学习是实现机器学习的一种技术今天我们要介绍的TensorFlow.js是由Google的AI团队发布一款机器学习框架,基于DeepLearn.js(已经停止更新)。这款机器学习框架的特点是使用JavaScript语言,在浏览器中就可以使用它提供的各种API来进行建模和训练,并且支持Node.js。所以对于前端来说,是走进机器学习世界最便捷的路径了。这里有一个利用TensorFlow.js实现的机器学习的小游戏demo,大家可以感受一下。尝试一下这篇文章基于TensorFlow.js的英文官方文档写成,重点在于TensorFlow.js的入门,关于机器学习更多的知识点可参考Google机器学习课程。让我们开始吧!安装直接引入第一种方式是通过<script></script>直接引入,在浏览器中运行下面的代码,在控制台中可以看到结果。<html> <head> <!– Load TensorFlow.js –> <script src=“https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.13.0"> </script> <!– Place your code in the script tag below. You can also use an external .js file –> <script> // Notice there is no ‘import’ statement. ’tf’ is available on the index-page // because of the script tag above. // Define a model for linear regression. const model = tf.sequential(); model.add(tf.layers.dense({units: 1, inputShape: [1]})); // Prepare the model for training: Specify the loss and the optimizer. model.compile({loss: ‘meanSquaredError’, optimizer: ‘sgd’}); // Generate some synthetic data for training. const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]); const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]); // Train the model using the data. model.fit(xs, ys, {epochs: 10}).then(() => { // Use the model to do inference on a data point the model hasn’t seen before: // Open the browser devtools to see the output model.predict(tf.tensor2d([5], [1, 1])).print(); }); </script> </head> <body> </body></html>npm或yarn第二种方式是通过npm或yarn将TensorFlow.js的库引入到你的项目中。yarn add @tensorflow/tfjs npm install @tensorflow/tfjs你可以在你的main.js中添加如下代码:import * as tf from ‘@tensorflow/tfjs’;// Define a model for linear regression.const model = tf.sequential();model.add(tf.layers.dense({units: 1, inputShape: [1]}));// Prepare the model for training: Specify the loss and the optimizer.model.compile({loss: ‘meanSquaredError’, optimizer: ‘sgd’});// Generate some synthetic data for training.const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]);const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]);// Train the model using the data.model.fit(xs, ys, {epochs: 10}).then(() => { // Use the model to do inference on a data point the model hasn’t seen before: model.predict(tf.tensor2d([5], [1, 1])).print();});如果不懂上面代码的含义不要着急,继续看后面的一些基础概念和用法。Tensor和VariableTensor和Variable是TensorFlow.js中最基础的两种数据形式。那他们到底是什么意思呢?Tensor在谷歌翻译中是“张量”的意思,“张量”这个词是数学和物理中的一个术语,我们暂且不深究它的意思,你只需要记住,Tensor(张量)是不可变的,类似于const,一旦定义就不能改变它的值。Variable就很容易理解了,它是变量的意思,顾名思义,它的值是可以改变的。总之,Tensor(张量)不可变,Variable(变量)可变。Tensor张量通常是一个0到多维的数组,构造张量时会用到shape属性,用来规定这是一个几行几列的数组。请看下面构造一个张量的例子。shape用来规定这个张量是两行三列的数组,然后可以看到最后的输出,我们得到了一个两行三列的二维数组。// 2x3 Tensorconst shape = [2, 3]; // 2 rows, 3 columnsconst a = tf.tensor([1.0, 2.0, 3.0, 10.0, 20.0, 30.0], shape);a.print(); // print Tensor values// Output: [[1 , 2 , 3 ],// [10, 20, 30]]也可以用下面这种方式,直接表示这是一个两行三列的二维数组。// The shape can also be inferred:const b = tf.tensor([[1.0, 2.0, 3.0], [10.0, 20.0, 30.0]]);b.print();// Output: [[1 , 2 , 3 ],// [10, 20, 30]]然而实际上,我们通常使用 tf.scalar, tf.tensor1d, tf.tensor2d, tf.tensor3d 和 tf.tensor4d来构造张量。tf.scalar是构造一个零维数组,也就是一个数字,tf.tensor1d是构造一位数组,tf.tensor2d是构造二维数组,以此类推。例如:const c = tf.tensor2d([[1.0, 2.0, 3.0], [10.0, 20.0, 30.0]]);c.print();// Output: [[1 , 2 , 3 ],// [10, 20, 30]]或者使用tf.zeros生成全是0的数组,tf.ones生成全是1的数组,例如:// 3x5 Tensor with all values set to 0const zeros = tf.zeros([3, 5]);// Output: [[0, 0, 0, 0, 0],// [0, 0, 0, 0, 0],// [0, 0, 0, 0, 0]]Variable而Variable(变量)只能通过Tensor(张量)生成。我们可以使用assign给变量重新赋值。例如:const initialValues = tf.zeros([5]);const biases = tf.variable(initialValues); // initialize biasesbiases.print(); // output: [0, 0, 0, 0, 0]const updatedValues = tf.tensor1d([0, 1, 0, 1, 0]);biases.assign(updatedValues); // update values of biasesbiases.print(); // output: [0, 1, 0, 1, 0]OperationsTensorFlow.js提供了各种向量运算的API,我们可以称这些为Operations。下面是张量平方和张量相加的例子:const d = tf.tensor2d([[1.0, 2.0], [3.0, 4.0]]);const d_squared = d.square();d_squared.print();// Output: [[1, 4 ],// [9, 16]]const e = tf.tensor2d([[1.0, 2.0], [3.0, 4.0]]);const f = tf.tensor2d([[5.0, 6.0], [7.0, 8.0]]);const e_plus_f = e.add(f);e_plus_f.print();// Output: [[6 , 8 ],// [10, 12]]而且TensorFlow.js还提供了链式运算,请看例子:const sq_sum = e.add(f).square();sq_sum.print();// Output: [[36 , 64 ],// [100, 144]]// All operations are also exposed as functions in the main namespace,// so you could also do the following:const sq_sum = tf.square(tf.add(e, f));Model上面我们介绍了张量,变量和一些基础运算,下面我们引入“Model(模型)”这个概念。模型就是一个函数,给定这个函数特定的输入,会返回特定的输出。所以请记住,模型就是一个函数而已。我们来看一个定义模型的例子, 以下代码构造了一个 y = a x ^ 2 + b x + c 的函数表达式,给定一个x,我们会得到一个y。代码中tf.tidy()看不懂请忽略,我们将在下一节介绍,它只是用来清除内存。// Define functionfunction predict(input) { // y = a * x ^ 2 + b * x + c // More on tf.tidy in the next section return tf.tidy(() => { const x = tf.scalar(input); const ax2 = a.mul(x.square()); const bx = b.mul(x); const y = ax2.add(bx).add(c); return y; });}// Define constants: y = 2x^2 + 4x + 8const a = tf.scalar(2);const b = tf.scalar(4);const c = tf.scalar(8);// Predict output for input of 2const result = predict(2);result.print() // Output: 24但是通常,我们会使用一个更高级的API去构造模型,那就是用 tf.model 的形式,这里的model只是模型的总称,并没有 tf.modal 这个方法。TensorFlow中最常用的是 tf.sequential,例如:const model = tf.sequential();model.add( tf.layers.simpleRNN({ units: 20, recurrentInitializer: ‘GlorotNormal’, inputShape: [80, 4] }));const optimizer = tf.train.sgd(LEARNING_RATE);model.compile({optimizer, loss: ‘categoricalCrossentropy’});model.fit({x: data, y: labels});上面代码中一定有很多你不理解的地方,比如什么是 tf.layer?什么是 tf.train.sgd?这里可以先忽略细节,先从总体上体会这些基本概念,关于 tf.train.sg 等我们在后面的文章介绍。如果你忍不住,就自己去查吧!给你 官方API文档 好了。内存管理TensorFlow.js使用GPU来加速运算,所以合理地释放内存是一件很必要的事情。TensorFlow.js提供了dispose函数来释放内存,请看例子:const x = tf.tensor2d([[0.0, 2.0], [4.0, 6.0]]);const x_squared = x.square();x.dispose();x_squared.dispose();但是通常实际中我们会面对很多的张量和操作,这时候 tf.tidy 更加方便,因为它是批量释放内存,请看例子:// tf.tidy takes a function to tidy up afterconst average = tf.tidy(() => { // tf.tidy will clean up all the GPU memory used by tensors inside // this function, other than the tensor that is returned. // // Even in a short sequence of operations like the one below, a number // of intermediate tensors get created. So it is a good practice to // put your math ops in a tidy! const y = tf.tensor1d([1.0, 2.0, 3.0, 4.0]); const z = tf.ones([4]); return y.sub(z).square().mean();});average.print() // Output: 3.5使用 tf.tidy 有两个要点:传递给 tf.tidy 的函数必须是同步的。tf.tidy 不会清理变量,你只能通过 dispose 手动清理。总结关于 TensorFlow.js 的基础概念介绍完了,但是这只是我们探索机器学习的一个工具而已,具体的实践还需要更多的学习,后面有时间我也会跟大家一起学习,并及时分享。 ...

October 10, 2018 · 4 min · jiezi

python综合学习七之TensorFlow初识

从这一节开始,将系统的学习TensorFlow这个开源包。一、TensorFlow概念TensorFlow是Google开发的一款神经网络的Python外部的结构包, 也是一个采用数据流图来进行数值计算的开源软件库.TensorFlow 让我们可以先绘制计算结构图, 也可以称是一系列可人机交互的计算操作, 然后把编辑好的Python文件 转换成 更高效的C++, 并在后端进行计算。为何要使用TensorFlow?TensorFlow 无可厚非地能被认定为 神经网络中最好用的库之一。 它擅长的任务就是训练深度神经网络。通过使用TensorFlow我们就可以快速的入门神经网络, 大大降低了深度学习(也就是深度神经网络)的开发成本和开发难度. TensorFlow 的开源性, 让所有人都能使用并且维护, 巩固它. 使它能迅速更新, 提升.

September 7, 2018 · 1 min · jiezi