数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

「深度学习福利」大神带你进阶工程师,立即查看>>>
文本聚类
文本聚类简单点的来说就是将文本视作一个样本,在其上面进行聚类操作。但是与我们机器学习中常用的聚类操作不同之处在于。
我们的聚类对象不是直接的文本本身,而是文本提取出来的特征。因此如何提取特征因而是非常重要的一步。在HanLP中一共有三个文本聚类方法。前两种都基于词袋模式,第一个是最常见的聚类算法:k-means,但HanLP不光实现了k-means,还实现了速度更快效果更好的repeated bisection算法(重复二分法,还是翻译为累次平方法,好像是第一种)。笔者动笔前段时间刚刚添加了一个新的聚类分析器是,基于词向量的kmens聚类分析器。
基于词向量的kmeans聚类分析器,输入的需要时词向量化后的文件。虽然HanLP的词向量在Java实现中,还算可以,但在Python中使用不算太好用,同时Python也不推荐用HanLP做词向量,我们有更好的工具。所以这里我们也就不画蛇添足了。
而对于前两个聚类分析器而言,其聚类模块可以接受任意文本作为文档,而不需要用特殊分隔符隔开单词。另外,该模块还接受单词列表作为输入,用户可以将英文、日文等预先切分为单词列表后输入本模块。统计方法适用于所有语种,不必拘泥于中文。
分词器的性能问题
在repeated bisection算法无论性能还是速度都要优于kmens,但是在本人的测试中,前者速度基本原作者一致约为kmeans的三倍左右,但是性能略低于后者。此处请读者自行斟酌。
分词器的参数
自动判断聚类个数k(此处来自于原文:HanLP中的文本聚类
很多时候用户可能觉得聚类个数k这个超参数很难准确指定。在repeated bisection算法中,有一种变通的方法,那就是通过给准则函数的增幅设定阈值beta来自动判断k。此时算法的停机条件为,当一个簇的二分增幅小于beta时不再对该簇进行划分,即认为这个簇已经达到最终状态,不可再分;当所有簇都不可再分时,算法终止,此时产生的聚类数量就不再需要人工指定了。
在HanLP中,repeated bisection算法提供了3种接口,分别需要指定k、beta或两者同时指定。当同时指定k和beta时,满足两者的停止条件中任意一个算法都会停止。当只指定一个时,另一个停止条件不起作用。这三个接口列举如下:

public List> repeatedBisection(int nclusters)
public List> repeatedBisection(double limit_eval)
public List> repeatedBisection(int nclusters, double limit_eval)

当我们使用analyzer.repeatedBisection(1.0)时,可以进行自动聚类。

from pyhanlp import *
ClusterAnalyzer = SafeJClass('com.hankcs.hanlp.mining.cluster.ClusterAnalyzer')
analyzer = ClusterAnalyzer()
# 我们需要调用并返回自身

analyzer.addDocument("赵一", "流行, 流行, 流行, 流行, 流行, 流行, 流行, 流行, 流行, 流行, 蓝调, 蓝调, 蓝调, 蓝调, 蓝调, 蓝调, 摇滚, 摇滚, 摇滚, 摇滚");
analyzer.addDocument("钱二", "爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲");
analyzer.addDocument("张三", "古典, 古典, 古典, 古典, 民谣, 民谣, 民谣, 民谣");
analyzer.addDocument("李四", "爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 金属, 金属, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲");
analyzer.addDocument("王五", "流行, 流行, 流行, 流行, 摇滚, 摇滚, 摇滚, 嘻哈, 嘻哈, 嘻哈");
analyzer.addDocument("马六", "古典, 古典, 古典, 古典, 古典, 古典, 古典, 古典, 摇滚");

print(analyzer.repeatedBisection(1.0))
[[李四, 钱二], [王五, 赵一], [张三, 马六]]
评测
评测程序仍然使用搜狗文本分类语料库迷你版。过程为首先遍历子目录读取文档,以子目录+文件名作为id将文档传入聚类分析器进行聚类,并且计算F1值返回。该计算过程已被原作者封装为接口,我们可以直接调用

CORPUS_FOLDER = "/home/fonttian/Data/CNLP/textClassification/sogou-mini/搜狗文本分类语料库迷你版"
for i in ["kmeans", "repeated bisection"]:
print(i, ClusterAnalyzer.evaluate(CORPUS_FOLDER, i) * 100)
kmeans 83.97065954968313
repeated bisection 82.71523522720585

文章来源FontTian的博客
人工智能
2018-11-23 08:57:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
欢迎大家前往 腾讯云+社区 ,获取更多腾讯海量技术实践干货哦~ 本文由 腾讯教育云 发表于 云+社区专栏
一般没有网络时,语音识别是这样的

而同等环境下,嵌入式语音识别,是这样的

不仅可以帮您边说边识、出口成章,有个性化名字的时候也难不倒它。
这就是嵌入式语音识别的魅力。
本文将从微信智聆的嵌入式语音识别引擎的实现和优化,
介绍嵌入式语音识别的技术选型。
01
语音识别,大体是这么来的
语音识别,能让机器“听懂”人类的语音,把说话内容识别为对应文本。
开始于上世纪50年代
从最初的 小词量孤立识别 系统
到如今的 大词量连续识别 系统
语音识别系统的发展,性能得到显著的提升,主要得利于以下几个方面:
大数据时代的到来
深度神经网络在语音识别中的应用
GPU硬件的发展
因此,语音识别逐步走向实用化和产品化
语音输入法,语音智能助手,语音车载交互系统……
可以说,语音识别是人类征服人工智能的前沿阵地,是目前机器翻译、自然语言理解、人机交互等的奠基石。
然而,性能的提升基于服务端CPU/GPU高计算能力和大内存, 没有网络的时候将无法享受语音识别的便利 。
为了解决这个问题,微信智聆针对嵌入式语音识别进行研发。嵌入式语音识别,也称为嵌入式LVCSR(或离线LVCSR,Large Vocabulary Continuous Speech Recognition),指全程运行在手机端的语音识别,而不依赖于服务端强大的计算能力。
在一些网络不稳的特殊场景(车载、境外等等),嵌入式语音识别可“曲线救国”。
那么,实现一个嵌入式语音识别,存在哪些难点呢?
语音识别的基本流程
主流的语音识别算法当中,包括声学和语言两大模型。声学模型得利于近十年深度学习的发展,从GMM(高斯模型)到DNN(深度神经网络),再从DNN到LSTM RNN(循环神经网络),识别率不断提升的同时,计算量也不断地飞涨。而语言模型常用的n-gram算法,阶数越高性能越好,常用的模型多达数十G的内存。
所以综合起来,嵌入式语音识别有以下几个难点:
\1. 深度学习运算复杂,仅仅对模型进行裁剪性能损失大,需寻找挽回性能的方法;
\2. 裁剪模型不可避免,在模型训练环节如何避免小模型训练易陷入局部最优的问题;
\3. 如何计算的更快,满足嵌入式的CPU环境;
\4. 如何组织语言模型存储,能在有限的内存下存储更多的语言信息。
本文将以语音识别的技术原理出发,浅谈微信智聆嵌入式的实现技术。
内容将分为四个部分:
\1. 回顾语音识别的基本概念;
\2. 简单介绍在速度和内存优化上我们做的部分工作,侧重于工程应用实现;
\3. 说一说为了更好的性能我们做了哪些事,侧重于算法研究介绍;
\4. 我们进行实验对比,最后我们进行总结。
02
语音识别的各个组件
语音识别“黑盒”
语音识别从输入录音输出文字,黑盒子处理经过特征提取、声学模型、发音词典、语言模型等流程,笔者认为可以把语音识别比作一台计算机。
特征提取相当于是路由器,作为领头羊给后续环节提供源源不断的数据来源。
声学模型相当于语音识别的心脏——CPU,他将最直接影响着识别的准确性能。
语言模型相当于语音识别的硬盘,大量的词汇组合信息存储于此。
发音词典相当于内存条,能有效组织声学模型与语言模型的关系。
除此之外,语音识别包含一个解码工具,他如同计算机的操作系统,有效地组织着各个环节。
接下来,我们基于每个“部件”简介其基本概念,以便后续介绍如何在这些“部件”上对嵌入式ASR工作的展开。
1.特征提取
音识别特征提取包括预加重、分帧、加窗、FFT(Fast Fourier Transform)等一系列流程,常用的特征有PLP、MFCC、FBANK等等。一般来说,语音识别把一秒语音分成100段(之间有互相重叠),而特征提取能把每段语音数据转化为一个向量(常见的有39维MFCC特征)。
为了关联上下文信息,特征作为声学模型的输入时,常将相邻帧拼凑一起。比如以39维特征为例,前后各取5帧信息,那么总共有11帧,输入的向量维度为11*39=429。一般地,语音识别的性能与取帧宽度是正相关的。
作为语音识别的路由器,特征提取环节的运算量并不大。然而其作为声学模型拓扑结构的输入,间接影响着深度学习的运算量,是我们在嵌入式ASR中要考虑的问题。
2.帧率抖动
5s统计一次直播流视频帧率,1min计算一次帧率方差,方差过大,视为推流帧率抖动.
3.声学模型(acoustic model)
声学模型作为语音识别的CPU,其重要性不言自喻。
一般地,它占据着语音识别大部分的运算开销,直接影响着语音识别系统的性能。传统语音识别系统普遍基于GMM-HMM的声学模型,其中GMM对语音声学特征的分布进行建模,HMM则用于对语音信号的时序性进行建模。
2006年深度学习兴起以后,深度神经网络(DNN,Deep Neural Networks)被应用于声学模型。
近十多年,声学模型的上深度学习的发展一路高歌,各种CNN、RNN、TDNN的拓扑结构如雨后春笋一一冒出,关于深度学习在声学模型的更多介绍见文。
对于嵌入式LVCSR来说,选择合适的DNN拓扑结构,并用合理的优化在手机实现结构的运算,是声学模型在其的核心诉求。
4.语言模型(language model)
语言模型,NLP从业者相对更为熟悉。在语音识别里,语言模型用来评估一个句子(即图2的词语序列)出现的概率高低。
在语言模型的实现算法中,最常见的为n-gram模型(n-gram models),利用当前词前面的n个词来计算其概率,是一个上下文有关模型。几年来,神经语言模型(Neural language models)使用词汇Embedding来预测,也得到广泛的发展与应用。
在嵌入式ASR中,由于计算资源要留予声学模型,所以语言模型采用的依旧是n-gram的思想。那么在有限的内存中,如何最大化存储语言模型,是嵌入式ASR要解决的问题。
5.发音词典
发音词典,是语音识别的内存条。内存能将硬盘的数据读入,并使用cpu进行运算。同样的,发音词典,能将语言模型的词条序列转化为音素序列,并用声学模型进行分数评估运算。
发音词典是连接声学模型和语言模型的桥梁,他的大小直接影响声学模型和语言模型的发挥空间。
在嵌入式ASR中,发音词典的大小,与语言模型的规模互相共鸣,所以要解决的问题可以与语言模型归为一谈。
6.解码工具
解码工具,估计这个词的来自英文decoder的直译,笔者认为更恰当的名字应称为识别器。之所以叫解码,还有另外一个比较形象的原因。以16bit语音数据为例,计算机的存储是一堆我们看不懂的short类型数字,如同密码一般。语音识别能破解这些密码,将明文展示在我们面前。
所以通俗来讲,解码就是将语音识别各个流程串联的代码工程。一般云端采用与WFST(带权优有限状态自动机)搭档的静态解码,可以更方便地综合处理语音识别的各个环节。而嵌入式为了节省语言模型的内存开支,采用特定的动态解码。
03
开始优化这些组件——速度和内存优化
为了优化这些“部件”占用的时间与内存,我们做了一系列工作:
neon计算优化,奇异值分解优化,哈夫曼编码优化。
1.neon优化声学模型计算
neon的计算优化,已是广大工程师们的老生常谈,机器学习相关的T族们更是耳熟能详。在嵌入式ASR引擎中,我们对核心高频运算的函数进行了neon优化,采用了汇编语言进行编写,最终有效提高了25%的计算速度。
接下来,本文现以实现char类型向量乘的介绍优化的实现,分三版本来介绍:
A. 优化前的朴素版
B. neon c版
C. neon汇编版
首先,我们将要实现的函数是: /** * 实现两个char类型向量乘 * start_a: 向量A * start_b: 向量B * cnt:向量元素个数 * result:向量乘返回存储变量 */ void vector_product_neon(const char * start_a, const char * start_b, int & result, const int cnt);
A. 优化前朴素版 void vector_product_neon(const char * start_a, const char * start_b, int & result, const int cnt) { int res = 0; for(int j = 0; j < cnt; j++) { res += int(*start_a) * int(*start_b); start_a++; start_b++; } result = res; }
B. neon c版
Neon寄存器能实现128位空间的并行运算,对于char类型的向量乘而言,两两相乘的结果在short类型范围内,故可8个为一组实现。以下代码,8个元素一组,一次循环处理两组。在我们的深度学习运算中,隐层的向量长度保证为16倍数,实现代码如下: void vector_product_neon(const char * start_a, const char * start_b, int & result, const int cnt) { int res = 0; int32x4_t neon_sum = vdupq_n_s32(0); int8x8_t neon_vector1; int8x8_t neon_vector2; for(int j = 0; j < cnt / 16; j++) { neon_vector1 = vld1_s8((char *)start_a); neon_vector2 = vld1_s8((char *)start_b); int16x8_t neon_tmp = vmull_s8(neon_vector1, neon_vector2); start_a += 8; start_b += 8; neon_vector1 = vld1_s8((char *)start_a); neon_vector2 = vld1_s8((char *)start_b); neon_tmp = vmlal_s8(neon_tmp, neon_vector1, neon_vector2); neon_sum = vaddw_s16(neon_sum, vget_low_s16(neon_tmp)); neon_sum = vaddw_s16(neon_sum, vget_high_s16(neon_tmp)); start_a += 8; start_b += 8; } for(int j = 0; j < 4; j++) res += vgetq_lane_s32(neon_sum, j); result = res; }
C. neon汇编版
汇编版本的neon代码编写与维护成本高,但速度比c版本更快。秉着精益求精的态度,我们实现了汇编代码: void vector_product_neon(const char * start_a, const char * start_b, int & result, const int cnt) { int res = 0; asm volatile( "vmov.s32 q2, #0" "\n\t" "lsr %[cnt], %[cnt], #4" "\n\t" ".charloop:" "vld1.s8 {d0}, [%[vec1]]!" "\n\t" "vld1.s8 {d1}, [%[vec2]]!" "\n\t" "vmull.s8 q1, d0, d1" "\n\t" "vld1.s8 {d0}, [%[vec1]]!" "\n\t" "vld1.s8 {d1}, [%[vec2]]!" "\n\t" "vmlal.s8 q1, d0, d1" "\n\t" "vaddw.s16 q2, q2, d2" "\n\t" "vaddw.s16 q2, q2, d3" "\n\t" "subs %[cnt], %[cnt], #1" "\n\t" "bne .charloop" "\n\t" "vadd.s32 d4, d4, d5" "\n\t" "vmov.s32 r4, d4[0]" "\n\t" "add %[sum], r4" "\n\t" "vmov.s32 r4, d4[1]" "\n\t" "add %[sum], r4" "\n\t" : [sum]"+r"(res) : [vec1]"r"(start_a), [vec2]"r"(start_b), [cnt]"r"(cnt) : "r4", "cc", "memory" ); result = res; }
2.奇异值分解优化声学模型运算量
为了降低乘加运算的次数,我们决定利用奇异值分解来对DNN进行重构,通过裁剪掉最小的奇异值及其相对应的特征向量,来达到减少乘加运算数量的目标。奇异值分解将任意矩阵Wm×n(不失一般性,假设m≤n)分解成3个矩阵相乘:Wm×n =Um×mΣm×mVm×n。
其中:Σm×m 为对角矩阵,即Σm×m =diag(σ1,σ2,…,σm),它的对角元素即为Wm×n的奇异值;Um×m 为单位正交矩阵,其列向量为与奇异值对应的特征向量;Vm×n中的行向量是互相单位正交的,也是与奇异值对应的特征向量。
下图是我们以DNN模型其中一层网络作为例子,阐述我们在重构DNN中的模型转化,其中原始DNN模型为图中上方子图(a),新重构DNN模型在下方子图(b)所示:
a:原始DNN模型的一层结构
(b)新DNN模型的两层对应结构
利用SVD对声学模型计算量优化大致分为3个步骤
(1)训练初始DNN神经网络;
(2)对权重矩阵进行奇异值分解;
(3)对重构后的DNN模型重新训练。
通过基于SVD的模型压缩方法,我们可以在稍微降低模型性能的前提下,将声学模型计算量减少30%。
3.哈夫曼优化语言模型内存
一般地,n-gram语言模型可以用一张有向图存储便于介绍存储空间以及快速查询,这张图上的边要存储词汇信息。我们知道以汉语为例,不同词语的出现频率相差极大,如果所有词汇的label id都用int类型存储,那空间的利用率较为低下。
以“我”“要”“吃饭”为例,假设语言模型的词汇频率:我>要>吃饭,那么我们可以构建图3的哈夫曼树,则四个字使用的编号码分别为:我(0),要(10),吃饭(110)
二叉哈夫曼
十六叉哈夫曼树
然而,采用图4的二叉树数据结构,一次只能处理1bit效率较低,也不便于工程实现。所以在工程实现的时候,我们按4bits编码为单位,对词汇进行分类存储处理。
我们使用一棵16叉树的哈夫曼树结构,每层树节点的编号总量是上一层的16倍。树中的所有编号为0的子节点用于储存词汇,越高频的词汇储存于深度越低的节点位置。
通过哈夫曼优化,我们的引擎最终成功降低了25%的内存占用,同时引擎是资源文件也得到50%左右的优化。
04
识别性能的优化
1.基于TDNN优化声学模型
近几年,TDNN(Time-Delay Neural Network,延时神经网络)【5】的拓扑结构被应用于语音识别。事实上,该结构于1989年被提出,随着近几年技术的发展,重新进入了大家的视线。
DNN结构
DNN的拓扑网络仅针对单一特征时刻点建模。
TDNN结构
TDNN的隐层结构,对语音特征多个时刻点进行抽象建模,拥有更强的建模能力。除此之外,TDNN结构的多时刻建模参数是共享的(图中红、绿、紫用的是同样的拓扑矩阵传播)。
所以,TDNN虽然在训练的时候,比DNN需要更多的BP运算。而在语音识别时,由于参数共享的原因,隐层的计算结果可以复用,每一帧仅需对所有参数进行一次运算,大大节省了计算量。最后,我们基于TDNN结构,引擎在保持计算量一致的前提下,识别率提升了相对20%的准确率。
2.基于多任务训练优化性能
采用多任务联合训练,能有效提高声学训练的鲁棒性,避免过早陷入局部最优。在嵌入式的模型中,模型输出目标比较少,训练容易陷入局部最优。所以我们,同时用目标多的大模型联合训练,让训练的隐层结构更为鲁棒。
声学模型多任务训练
在训练的时候,我们网络同时拥有输出1和输出2两个,多任务训练时,逆向迭代需要残差协调,我们采用以下公式分配残差,其中λ权衡两个模型的训练权重:
最终我们采用多任务训练优化性能,对语音识别率带来了一定提升,接下来所有的性能提升我们将在下一章结实验给出。
3.基于区分性训练(Discriminative Training)性能优化
声学模型区分性训练是针对MLE训练的不足而提出的。DT训练通常定义一个目标函数(Objective Function),或者说是准则函数(Criterion Function),来近似一个与分类代价相关的度量。通过区分性训练,我们可以从一定程度上弱化模型假设错误所带来的影响。
同时,由于区分性训练致力于优化与识别效果好坏相关的度量,因此也就为提高识别器性能提供了更直接的途径。形象的说,MLE训练告诉模型“这是椅子,那是桌子”,而区分性训练则告诉模型“这是桌子而不是椅子,那是椅子而不是桌子”。MLE训练更重视调整模型参数以反映训练数据的概率分布,而区分性训练则更重视调整模型之间的分类面,以更好的根据设定的准则对训练数据进行分类。
DT的目标函数是这样的:
对DT的目标函数用一次贝叶斯公司可以得到:
分子正是ML的目标函数;而分母则是所有文本(包括训练文本和它的所有竞争者)产生训练语音的概率的(按语言模型加权的)和。由于分母上要枚举所有可能的文本并不现实,所以实际中,一般是用一个已有的ML训练的语音系别系统对训练语音做一次解码,得到n-best list或lattice,用这里面的文本来近似分母上的求和。n-best list或lattice中包含了训练文本的足够接近的竞争者。
4.基于互信息的新词发现
对于语音识别系统来说,语言模型对结果影响至关重要;而对于语言模型来讲,语言模型的词典是关键。一个好的分词词典,对于得到鲁棒的语言模型是至关重要的,如果才能选出合理正确的“词”所组成的词典,首先最关键的一步就是基于现有语料的新词挖掘。
由于嵌入式系统性能有限,因此选择合适大小的词表,并对语言模型进行适当剪枝头,可以压缩安装包大小、限制内存消耗、提高识别性能。压缩词表可以筛选高频词,并通过一定的模型来识别筛掉截断词,如“新功”、“嘉年”、“扛生”、“鹅卵”、“刘德”、“利亚”等半个高频词。
一个简单而又有效的新词发现和筛选方案可以采用互信息和左右信息熵的计算方法,计算二元的信息熵的分数由三个对应部分组成: 1)点间互信息:点间互信息越高,内部聚合程度越高; 2)两个单词片段信息熵 h_r_l 和 h_l_r 的最小值:这个数值越大,则意味着两个单词一起出现的可能性越小; 3)单词左右信息熵的最小值:这个数值越大就表示着候选词出现的语境越多,越有可能成词因此,分数越高表示成词的可能性越大。
计算完二元的信息熵后,可以依次计算三元、四元的信息熵,三元的新词发现和筛选是将二元替换原有的两个单字做为一个单字继续进行,候选集可以取左信息熵或者右信息熵为0的候选集,四元、五元以此类推。 另外,语言模型直接关系到识别结果输出,因此选与应用场景相对应的语料进行统计尤为重要。
05
实验对比
第二章节和第三章节,介绍了一些我们完成的工作,本章节将分为两部分。首先,我们通过实验对比验证工作的成果。其次,我们将引擎和行业竞品进行对比。
工作成果验证
目前总共有6个通用测试集,测试集大小分别为1220、6917、4069、2977、2946、2500条语音。其中测试集1是手机录制测试集,集2是命令类的录音,集3是麦克风录音涉及一般生活情景,4、5、6集都是线上实网数据,区别是 集4、5背景比较干净,集6背景带噪。
测试集
DNN
TDNN
TDNN优化版
1
10.4
8
6.9
2
13.7
11.3
9.3
3
22.9
18.3
15.6
4
15.8
13.3
12
5
15.3
12.2
10.5
6
22.6
20.3
17.8
在模型选取对比,我们针对DNN、TDNN、以及TDNN优化版(优化内容为第三章的2、3、4小结内容),总共设计出三个不同版本的嵌入式语音识别引擎进行对比。
三个版本的嵌入式语音识别引擎在6个通用测试集上的实验结果如表中所示。表中的数字表示字错误率,即100个字里面识别错字的数量。总体来看,TDNN对识别率带来了20%左右的提升,其他工作也带来了10%左右的提升。
从语音识别的基本概念,到语音识别速度和内存优化的介绍,以及沉淀的一些算法研究、实验结果验证,本文大体讲述了语音识别从原理到实践的基本过程。欢迎同样从事语音AI识别的小伙伴加入我们~ 相关阅读 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识
此文已由作者授权腾讯云+社区发布,更多原文请 点击
搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!
海量技术实践经验,尽在 云加社区 !
人工智能
2018-11-22 17:36:00
「深度学习福利」大神带你进阶工程师,立即查看>>> (Boll)指标是股市技术分析的常用工具之一,通过计算股价的“标准差”,再求股价的“信赖区间”。该指标在图形上画出三条线,其中上下两条线可以分别看成是股价的压力线和支撑线,而在两条线之间还有一条股价平均线,布林线指标的参数最好设为20。一般来说,股价会运行在压力线和支撑线所形成的通道中。
与MACD、RSI、KDJ等指标一样,BOLL指标也是股票市场最实用的技术分析参考指标。

但是这些指标也都是对于历史数据的统计计算形成的,客观的讲是不能绝对预测未来标的物的走势行情的。根据历史的行情数据,计算出的指标,可以作为参考、辅助决策,最终会发现一切都是围绕着概率。

那么 BOLL 指标的实际意义是 标的物价格的 标准差,期货的不同合约之间都有一定的价差,通常情况下 行情平稳的时候价差是保持一定趋势缓慢变动的,遇到“不理性”的订单的时候往往会出现短暂的波动,试想如何抓住这些“不理性”的订单呢?

我们考虑构建一个 《跨期布林对冲策略》 ,从底层撸代码、造车轮 太慢了, 使用 BotVS 分分钟实现你的各种交易思路。

实现这个策略,我们选择标的物为 : OKEX 的 BTC 合约, 稍后我也会写出 标的物 为: 商品期货 合约的该策略 (为什么用BTC 合约? 因为好实现,方便快速写出来 测试!标的物只是一方面,关键是思路 、策略!)

跨期布林对冲策略 var P_isSetOneKLine = true; var isShowChart = true; var retrySlidePrice = 2; // 全局变量 var KPeriod = [PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_D1][P_KPeriod]; var DiffData = { // 差价K线数据 plusKLine : [], // 差价K线数组 minusKLine : [], KPeriod : KPeriod, // 差价K线周期 }; var distance = 0; // 距离 var IDLE = 0; var PLUS = 1; var MINUS = 2; var State = IDLE; // 策略 运行状态状态 var perState = IDLE; var OPEN = 3; // 用于区分 是那种操作 在 BuyA_SellB 、 SellA_BuyB 函数中 var COVER = 4; var isTradeOnThisBar = false; // 是否在当前Bar 交易了 废弃 var isCoverOnthisBar = false; // 废弃 var upRatio = 0; var downRatio = 0; var P = null; // 交易控制对象 var ManagerData = { // 控制对象 initAccount : null, // 初始账户 信息 Account : null, // 当前账户 信息 perAccount : null, MaxUsedRatio : MaxUsedRatio, // 最大 保证金使用率 MaxUsedMargin : 0, // 每次 平仓后 重新计算的 最大保证金使用量 APosition : null, // 多头 仓位信息 BPosition : null, // 空头 仓位信息 floatProfit : 0, // 浮动盈亏 CoverProfit : 0, // 平仓盈亏 MarginLevel : MarginLevel, // 杠杆数 Margin : 0, // 当前保证金使用 // 行情数据 DepthA : null, // A 合约的 深度数据 DepthB : null, // B 合约的 深度数据 // 错误信息 ErrorStr : "", // 错误信息字符串 ErrorTimeStamp : 0, // 错误信息 时间戳 } var DiffDataOneKLine = { // K线结构 KLine : [], // K线数组 ,整理后的差价 KPeriod : KPeriod, // K线周期 } var test = { // 测试 模式 使用的结构 amount : 4, ProfitDiff : 0, ID : 0, OpenDiff : 0, }; var perRecordsTime = 0; // 合成的 K线 上一个bar 的时间戳 var PeriodBeginTime = 0; // 周期起始时间 function UpdateDiffData(DepthA, DepthB, RecordsA, RecordsB){ // 处理差价K线数据的更新 var plusDiff = DepthA.Bids[0].Price - DepthB.Asks[0].Price; var minusDiff = DepthA.Asks[0].Price - DepthB.Bids[0].Price; if(P_isSetOneKLine){ var Diff = _N((plusDiff + minusDiff) / 2 , 4); } if(RecordsA[RecordsA.length - 1].Time == RecordsB[RecordsB.length - 1].Time && RecordsB[RecordsB.length - 1].Time !== PeriodBeginTime){ if(!P_isSetOneKLine){ // 新K线周期出现 var plusBar = { Open : plusDiff, Close : plusDiff, High : plusDiff, Low : plusDiff, Time : 0, }; var minusBar = { Open : minusDiff, Close : minusDiff, High : minusDiff, Low : minusDiff, Time : 0, }; PeriodBeginTime = RecordsB[RecordsB.length - 1].Time; plusBar.Time = PeriodBeginTime; minusBar.Time = PeriodBeginTime; DiffData.plusKLine.push(plusBar); DiffData.minusKLine.push(minusBar); }else{ var Bar = { Open : Diff, Close : Diff, High : Diff, Low : Diff, Time : 0, }; PeriodBeginTime = RecordsB[RecordsB.length - 1].Time; Bar.Time = PeriodBeginTime; DiffDataOneKLine.KLine.push(Bar); } }else{ if(!P_isSetOneKLine){ if(plusDiff > DiffData.plusKLine[DiffData.plusKLine.length - 1].High){ DiffData.plusKLine[DiffData.plusKLine.length - 1].High = plusDiff; }else if(plusDiff < DiffData.plusKLine[DiffData.plusKLine.length - 1].Low){ DiffData.plusKLine[DiffData.plusKLine.length - 1].Low = plusDiff; } DiffData.plusKLine[DiffData.plusKLine.length - 1].Close = plusDiff; if(minusDiff > DiffData.minusKLine[DiffData.minusKLine.length - 1].High){ DiffData.minusKLine[DiffData.minusKLine.length - 1].High = minusDiff; }else if(minusDiff < DiffData.minusKLine[DiffData.minusKLine.length - 1].Low){ DiffData.minusKLine[DiffData.minusKLine.length - 1].Low = minusDiff; } DiffData.minusKLine[DiffData.minusKLine.length - 1].Close = minusDiff; }else{ if(Diff > DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].High){ DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].High = Diff; }else if(Diff < DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Low){ DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Low = Diff; } DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close = Diff; } } if(!P_isSetOneKLine){ if(DiffData.plusKLine.length > 100){ DiffData.plusKLine.shift(); } if(DiffData.minusKLine.length > 100){ DiffData.minusKLine.shift(); } }else{ if(DiffDataOneKLine.KLine.length > 100){ DiffDataOneKLine.KLine.shift(); } } } function UseAPI(fun, symbol, param){ exchange.SetContractType(symbol); if(typeof(param) == "undefined"){ return fun(); }else{ return fun(param); } } function GetPositions(){ var positions = _C(exchange.GetPosition); for(var i = 0; i < positions.length; i++){ if(positions[i].ContractType == P_SymbolA){ ManagerData.APosition = positions[i]; }else if(positions[i].ContractType == P_SymbolB){ ManagerData.BPosition = positions[i]; }else if(i == 2){ throw "error:" + JSON.stringify(positions); } } if(positions.length == 0){ ManagerData.APosition = null; ManagerData.BPosition = null; } return positions; } function SellA_BuyB(DiffPrice, Action){ // GetBestAmount var Piece = P_piece; if(Action == OPEN){ // 检查 是否超出 保证金 var MarginAndAmount_A = CalcPieceEqualAmount(ManagerData.DepthA.Bids[0].Price, Piece); var MarginAndAmount_B = CalcPieceEqualAmount(ManagerData.DepthB.Asks[0].Price, Piece); if(MarginAndAmount_A.NeedMargin + MarginAndAmount_B.NeedMargin + ManagerData.Margin > ManagerData.MaxUsedMargin){ ManagerData.ErrorStr = "超过最大可用保证金:" + ManagerData.MaxUsedMargin; ManagerData.ErrorTimeStamp = new Date().getTime(); return false; } // 交易 _C(exchange.SetContractType, P_SymbolA); var infoA = P.OpenShort(P_SymbolA, Piece, ManagerData.DepthA.Bids[0].Price); if(infoA.amount == 0){ return false; } _C(exchange.SetContractType, P_SymbolB); var dealAmount = infoA.amount; var infoB = P.OpenLong(P_SymbolB, dealAmount, ManagerData.DepthB.Asks[0].Price); var slidePrice = retrySlidePrice; while((dealAmount -= infoB.amount) > 0){ infoB = P.OpenLong(P_SymbolB, dealAmount, ManagerData.DepthB.Asks[0].Price + slidePrice); slidePrice += retrySlidePrice; LogStatus("while(), P_SymbolB:", P_SymbolB, infoB, dealAmount, slidePrice); } // 更新数据 ManagerData.perAccount = ManagerData.Account; ManagerData.Account = _C(exchange.GetAccount); ManagerData.Margin += (ManagerData.perAccount.Stocks - ManagerData.Account.Stocks); Log("持仓信息:", GetPositions(), "本次使用保证金:", (ManagerData.perAccount.Stocks - ManagerData.Account.Stocks)); perState = State; State = PLUS; }else if(Action == COVER){ // 全平 _C(exchange.SetContractType, P_SymbolA); var infoA = P.Cover(P_SymbolA, ManagerData.DepthA.Bids[0].Price, Math.min(ManagerData.APosition.Amount, Math.max(_N(ManagerData.APosition.Amount * CoverRatio, 0), minCoverAmount)), PD_LONG); if(infoA == 0){ return false; } _C(exchange.SetContractType, P_SymbolB); var dealAmount = infoA; var infoB = P.Cover(P_SymbolB, ManagerData.DepthB.Asks[0].Price, dealAmount, PD_SHORT); var slidePrice = retrySlidePrice; while((dealAmount -= infoB) > 0){ infoB = P.Cover(P_SymbolB, ManagerData.DepthB.Asks[0].Price + slidePrice, dealAmount, PD_SHORT); slidePrice += retrySlidePrice; LogStatus("while(), P_SymbolB:", P_SymbolB, infoB, dealAmount, slidePrice); } Log("持仓信息:", GetPositions()); if(ManagerData.APosition == null && ManagerData.BPosition == null){ perState = State; State = IDLE; upRatio = 0; downRatio = 0; Log("完全平仓!#FF0000"); Log("更新最大保证金使用量:", DealManagerData()); LogProfit(ManagerData.Account.Stocks - ManagerData.initAccount.Stocks); // ceshi }else{ ManagerData.perAccount = ManagerData.Account; ManagerData.Account = _C(exchange.GetAccount); Log("部分平仓:", infoA); } } return true; } function BuyA_SellB(DiffPrice, Action){ // GetBestAmount var Piece = P_piece; if(Action == OPEN){ // 检查 是否超出 保证金 var MarginAndAmount_A = CalcPieceEqualAmount(ManagerData.DepthA.Asks[0].Price, Piece); var MarginAndAmount_B = CalcPieceEqualAmount(ManagerData.DepthB.Bids[0].Price, Piece); if(MarginAndAmount_A.NeedMargin + MarginAndAmount_B.NeedMargin + ManagerData.Margin > ManagerData.MaxUsedMargin){ ManagerData.ErrorStr = "超过最大可用保证金:" + ManagerData.MaxUsedMargin; ManagerData.ErrorTimeStamp = new Date().getTime(); return false; } // 交易 _C(exchange.SetContractType, P_SymbolA); var infoA = P.OpenLong(P_SymbolA, Piece, ManagerData.DepthA.Asks[0].Price); if(infoA.amount == 0){ return false; } _C(exchange.SetContractType, P_SymbolB); var dealAmount = infoA.amount; var infoB = P.OpenShort(P_SymbolB, dealAmount, ManagerData.DepthB.Bids[0].Price); var slidePrice = retrySlidePrice; while((dealAmount -= infoB.amount) > 0){ infoB = P.OpenShort(P_SymbolB, dealAmount, ManagerData.DepthB.Bids[0].Price - slidePrice); slidePrice += retrySlidePrice; LogStatus("while(), P_SymbolB:", P_SymbolB, infoB, dealAmount, slidePrice); } // 更新数据 ManagerData.perAccount = ManagerData.Account; ManagerData.Account = _C(exchange.GetAccount); ManagerData.Margin += (ManagerData.perAccount.Stocks - ManagerData.Account.Stocks); Log("持仓信息:", GetPositions(), "本次使用保证金:", (ManagerData.perAccount.Stocks - ManagerData.Account.Stocks)); perState = State; State = MINUS; }else if(Action == COVER){ // 全平 _C(exchange.SetContractType, P_SymbolA); var infoA = P.Cover(P_SymbolA, ManagerData.DepthA.Asks[0].Price, Math.min(ManagerData.APosition.Amount, Math.max(_N(ManagerData.APosition.Amount * CoverRatio, 0), minCoverAmount)), PD_SHORT); if(infoA == 0){ return false; } _C(exchange.SetContractType, P_SymbolB); var dealAmount = infoA; var infoB = P.Cover(P_SymbolB, ManagerData.DepthB.Bids[0].Price, dealAmount, PD_LONG); var slidePrice = retrySlidePrice; while((dealAmount -= infoB) > 0){ infoB = P.Cover(P_SymbolB, ManagerData.DepthB.Bids[0].Price - slidePrice, dealAmount, PD_LONG); slidePrice += retrySlidePrice; LogStatus("while(), P_SymbolB:", P_SymbolB, infoB, dealAmount, slidePrice); } Log("持仓信息:", GetPositions()); if(ManagerData.APosition == null && ManagerData.BPosition == null){ perState = State; State = IDLE; upRatio = 0; // 重置 downRatio = 0; // 重置 Log("完全平仓!#FF0000"); Log("更新最大保证金使用量:", DealManagerData()); LogProfit(ManagerData.Account.Stocks - ManagerData.initAccount.Stocks); // ceshi }else{ ManagerData.perAccount = ManagerData.Account; ManagerData.Account = _C(exchange.GetAccount); Log("部分平仓:", infoA); } } return true; } function DealManagerData(){ // 处理 ManagerData ManagerData.perAccount = ManagerData.Account; ManagerData.Account = _C(exchange.GetAccount); // 获取 平仓后的 账户信息 var Position = _C(exchange.GetPosition); // 获取 平仓后的持仓信息 if(Position.length == 0){ // 如果 平仓完成 ,没有持仓了 重置 ManagerData 的 持仓信息属性 ManagerData.APosition = null; ManagerData.BPosition = null; ManagerData.Margin = 0; // 重置 当前使用的保证金 为 0 }else{ // 如果有持仓 报错,打印持仓 Log("平仓后还有持仓!", Position, "#FF0000"); throw "error!"; } ManagerData.MaxUsedMargin = _N(ManagerData.Account.Stocks * ManagerData.MaxUsedRatio, 2); // 计算下一轮 最大的 保证金使用量 return ManagerData.MaxUsedMargin; } function CalcPieceEqualAmount(Price, Piece){ return { EqualAmount : (100 / Price * Piece), NeedMargin : (100 / Price * Piece) / ManagerData.MarginLevel, } } function Loop(nowTime){ // 主循环 var DepthA = UseAPI(exchange.GetDepth, P_SymbolA); var DepthB = UseAPI(exchange.GetDepth, P_SymbolB); var RecordsA = UseAPI(exchange.GetRecords, P_SymbolA, KPeriod); var RecordsB = UseAPI(exchange.GetRecords, P_SymbolB, KPeriod); // 过滤数据 if(!RecordsA || !RecordsB || !DepthA || !DepthB || DepthA.Bids.length == 0 || DepthA.Asks.length == 0 || DepthB.Bids.length == 0 || DepthB.Asks.length == 0 || RecordsA.length == 0 || RecordsB.length == 0){ return; } ManagerData.DepthA = DepthA; ManagerData.DepthB = DepthB; // 更新 差价K线数据 UpdateDiffData(DepthA, DepthB, RecordsA, RecordsB); if(isShowChart == true){ if(!P_isSetOneKLine){ $.PlotRecords(DiffData.plusKLine, 'plus'); }else{ $.PlotRecords(DiffDataOneKLine.KLine, 'KLine'); } } // BOLL if(P_isSetOneKLine && DiffDataOneKLine.KLine.length > 20){ var boll = TA.BOLL(DiffDataOneKLine.KLine); var up = boll[0]; var mid = boll[1]; var down = boll[2]; if(perRecordsTime !== DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Time){ if(isShowChart == true){ $.PlotLine("up", up[up.length - 2], DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 2].Time); $.PlotLine("mid", mid[mid.length - 2], DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 2].Time); $.PlotLine("down", down[down.length - 2], DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 2].Time); $.PlotLine("up", up[up.length - 1], DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Time); $.PlotLine("mid", mid[mid.length - 1], DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Time); $.PlotLine("down", down[down.length - 1], DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Time); // $.PlotHLine(up[up.length - 1], 'UpTrack', "red"); // $.PlotHLine(down[down.length - 1], 'DownTrack', "green"); } perRecordsTime = DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Time; isTradeOnThisBar = false; // 重置 是否当前Bar 交易的标记为 false isCoverOnthisBar = false; }else{ if(isShowChart == true){ $.PlotLine("up", up[up.length - 1], DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Time); $.PlotLine("mid", mid[mid.length - 1], DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Time); $.PlotLine("down", down[down.length - 1], DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Time); // $.PlotHLine(up[up.length - 1], 'UpTrack', "red"); // $.PlotHLine(down[down.length - 1], 'DownTrack', "green"); } } // 突破判断 // 计算加仓距离 distance = _N((up[up.length - 1] - down[down.length - 1]) * 0.5, 0); if(distance * 2 < 20 && (upRatio == 0 && downRatio == 0)){ // 计算安全间距 return; } if(DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close > up[up.length - 1]){ if((State == IDLE || State == PLUS) && (DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close > (up[up.length - 1] + upRatio * distance)) /*&& isTradeOnThisBar == false*/){ // Open PLUS if(P_isSetTest){ // 模拟 $.PlotFlag(nowTime, 'plus', 'P', 'flag', 'red'); State = PLUS; exchange.Log(LOG_TYPE_SELL, test.ID++, DepthA.Bids[0].Price, test.amount, "Open:" + P_SymbolA); exchange.Log(LOG_TYPE_BUY, test.ID++, DepthB.Asks[0].Price, test.amount, "Open:" + P_SymbolB); test.OpenDiff = DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close; }else{ var info = SellA_BuyB(DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close, OPEN); if(info == false){ return; } $.PlotFlag(nowTime, 'plus', 'P', 'flag', 'red'); isTradeOnThisBar = true; upRatio += Add_lv; } }else if(State == MINUS /*&& isCoverOnthisBar == false*/){ // Cover MINUS if(P_isSetTest){ // 模拟 $.PlotFlag(nowTime, 'Cover_Minus', 'CM', 'circlepin', 'blue'); State = IDLE; exchange.Log(LOG_TYPE_SELL, test.ID++, DepthA.Bids[0].Price, test.amount, "Cover:" + P_SymbolA); exchange.Log(LOG_TYPE_BUY, test.ID++, DepthB.Asks[0].Price, test.amount, "Cover:" + P_SymbolB); test.ProfitDiff += DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close - test.OpenDiff; Log("本次盈亏:" + (DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close - test.OpenDiff)); LogProfit(test.ProfitDiff); }else{ // SellA_BuyB var info = SellA_BuyB(DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close, COVER); if(info == false){ Log("MINUS 平仓失败!"); return; } $.PlotFlag(nowTime, 'Cover_Minus', 'CM', 'circlepin', 'blue'); isCoverOnthisBar = true; } } }else if(DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close < down[down.length - 1]){ if((State == IDLE || State == MINUS) && (DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close < (down[down.length - 1] - downRatio * distance)) /*&& isTradeOnThisBar == false*/){ if(P_isSetTest){ // 模拟 $.PlotFlag(nowTime, 'minus', 'M', 'circlepin', 'green'); // DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Time State = MINUS; exchange.Log(LOG_TYPE_BUY, test.ID++, DepthA.Asks[0].Price, test.amount, "Open:" + P_SymbolA); exchange.Log(LOG_TYPE_SELL, test.ID++, DepthB.Bids[0].Price, test.amount, "Open:" + P_SymbolB); test.OpenDiff = DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close; }else{ // BuyA_SellB var info = BuyA_SellB(DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close, OPEN); if(info == false){ return; } $.PlotFlag(nowTime, 'minus', 'M', 'circlepin', 'green'); isTradeOnThisBar = true; downRatio += Add_lv; } }else if(State == PLUS /*&& isCoverOnthisBar == false*/){ if(P_isSetTest){ // 模拟 $.PlotFlag(nowTime, 'Cover_Plus', 'CP', 'flag', 'blue'); State = IDLE; exchange.Log(LOG_TYPE_BUY, test.ID++, DepthA.Asks[0].Price, test.amount, "Cover:" + P_SymbolA); exchange.Log(LOG_TYPE_SELL, test.ID++, DepthB.Bids[0].Price, test.amount, "Cover:" + P_SymbolB); test.ProfitDiff += test.OpenDiff - DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close; Log("本次盈亏:" + (test.OpenDiff - DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close)); LogProfit(test.ProfitDiff); }else{ // BuyA_SellB var info = BuyA_SellB(DiffDataOneKLine.KLine[DiffDataOneKLine.KLine.length - 1].Close, COVER); if(info == false){ Log("PLUS 平仓失败!"); return; } $.PlotFlag(nowTime, 'Cover_Plus', 'CP', 'flag', 'blue'); isCoverOnthisBar = true; } } } } } function main(){ // 初始化工作 if(isLogReset){ LogReset(1); } Log("最大保证金使用量:", DealManagerData()); ManagerData.initAccount = ManagerData.Account; ManagerData.perAccount = ManagerData.Account; exchange.SetMarginLevel(ManagerData.MarginLevel); // 初始设置杠杆 // 设置禁用汇率 exchange.SetRate(1); Log("已禁用汇率,显示价格为美元价格!"); // 创建交易控制对象 P = $.NewPositionManager(exchange); //主要循环 while(true){ var nowTime = new Date().getTime(); Loop(nowTime); LogStatus("时间:", _D(nowTime), '\n', P_isSetTest ? JSON.stringify(test) : "Margin: " + ManagerData.Margin, '\n', ManagerData.ErrorStr, _D(ManagerData.ErrorTimeStamp), '\n', ManagerData.initAccount, '\n', ManagerData.Account, '\n', ManagerData.APosition, '\n', ManagerData.BPosition, '\n', "upRatio:" + upRatio, '\n', "downRatio:" + downRatio, "distance:" + distance); Sleep(P_Interval); } }

编程语言使用的是 JavaScript , 引用了 “ 模板-画线类库” 和 “ OKCoin期货跨期对冲策略(模板测试使用)” 两个模板, 策略公开地址: https://www.botvs.com/strategy/43049** 声明:

该策略为平台用户 小小梦 分享,主要用于发明者量化平台策略编写学习, 实盘虽可用,风险自担! 欢迎大家讨论,多交流,多学习,多提升! 作者本人其实不喜欢闭门造车,进步就要互相学习!

上个回测:
因为频率挺高的,回测滑点等因素影响较小,所以回测仅供参考,策略应该可以进一步优化,小心参数撞车!原创文章, 发明者量化 版权所有。
人工智能
2018-11-22 17:14:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
云计算从不被看好到成长为势不可挡的技术潮流,仅仅用了十年的时间。如今“云原生”又被企业以及开发者奉为圭臬,并被认为是云计算的未来。
阿里云容器技术负责人易立认为云计算有三个阶段:云搬迁、云就绪和云原生。
第一个阶段是为了降低成本的搬迁上云,将运行在物理机中的应用迁移到虚拟化环境中,应用的开发和运维方式并没有很大不同。
第二个阶段云就绪,企业希望利用云计算提升整体效率,开始尝试微服务架构,并关注标准化、自动化、可扩展性和高可用性等指标。
而第三个阶段,也就是当下,企业开始完全拥抱云计算,很多应用从最开始就生长在云上,敏捷、弹性、容错、可移植等也成为重点特性,软件的架构设计、开发、构建、交付等整个生命周期都被重塑了。
在首届国内KubeCon + CloudNativeCon 2018上,云原生的应用实践与落地成为全球的顶级科技企业、开发者关注的焦点,大会keynote环节中,阿里云研究员马涛分享了云原生生态系统在中国的现状,云原生技术已经融入了大众的衣食住行。
阿里云研究员马涛在现场作主题演讲
从双11谈起,云原生在中国的应用实践
“云原生技术已经慢慢地融入了大众衣食住行。”马涛表示。
举个例子,2018天猫双11期间,Pouch、阿里云容器镜服务就为广大消费者“买买买”提供了稳定的支撑。
一般情况下,在应对流量洪峰场景之前都需要对流量提前预估,以预先准备计算好的资源和应用容量,但仅完成这一步,还不足以支撑流量高峰,紧急扩容的能力也极为重要。事实证明,容器技术的特点非常适合此类场景,在需要时可以快速、自动弹性伸缩。
早在2011年,阿里巴巴就开始展开容器技术的探索。2016年双11将所有交易核心应用容器化,几十万容器撑起了双11交易17.5万笔/秒的下单峰值;去年双11,阿里巴巴集团实现在线服务全部容器化,内部已达到百万级容器部署规模,其中双11交易峰值到32.5万笔/秒,并且可以实现十分钟内万台服务器的快速部署。
到现在,容器镜像服务支撑了阿里巴巴集团10万镜像的托管,并累积了2亿次镜像下载量。
云原生技术已经在各行各业得到了应用:饿了么使用TiKV承载随时可能暴增的外卖订单;思岚基于阿里云和Kubernetes、Harbor构建了扫地机器人方案;微博更是构建了深度学习训练框架,用Kubernetes统一管理异构资源和AI任务调度。
阿里云专家与海外专家
阿里云云原生实践的方法论
云原生概念其实由来已久,最早是2015年Pivotal的Matt Stine首次提出,但很长一段时间业界对其定义都很模糊。
今年,CNCF云原生基金会对云原生给出了具体定义:云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更。
如果在容器化基础架构和服务治理层,构建最上层的业务抽象,这需要一整个的云原生操作系统。所以云原生更多的是一套技术体系和方法论,阿里云容器服务就是基于这一理念打造的。
阿里云容器服务Kubernetes版(ACK)同时支持公共云、专有云和混合云模式,优化整合阿里云基础能力,使得容器应用轻松地利用底层强大的计算、存储、网络等能力。
例如,面向追求极致效率的深度学习、高性能计算场景: 可以采用神龙弹性裸金属服务器或者GPU、FPGA实例等异构计算能力; 配合阿里云Terway网络驱动使用弹性网卡可以几乎无损耗地达到 9Gb网络带宽,或者采用融合以太网的RDMA协议(RoCE)技术25Gb网络; 还可以基于CPFS这样并行文件系统提升处理效率,提供高达1亿IOPS和1TBps吞吐的能力。
至于服务网格,可以在阿里云Kubernetes作为基础架构之上,结合Istio服务网格,支持多云、混合云,针对微服务提供有效的治理能力,并以Kubernetes和Istio为基础,提供针对特定应用负载的不同支持,例如针对Kubeflow服务的流量治理、为Knative提供负载的路由管理能力等。
容器已经成为一种技术平台,需要满足各种应用场景的需求,例如IoT、深度学习、区块链等。阿里云区块链高级技术专家余珊认为,“区块链业务应用没有统一的标准,会涉及到各种类型的应用。而容器提供了标准的软件打包、分发能力,实现了运行环境的一致性以及与底层的解耦,并且可以充分发挥Kubernetes的资源调度、运维管理、安全隔离、微服务架构等优势。”
2017年9月,阿里云推出基于容器的区块链解决方案,用户可以通过Kubernetes和Helm/Chart模板快速搭建企业级区块链环境。今年8月,发布了区块链即服务(Blockchain as a Service),在系统开发、运维各个环节,支持一键快速部署区块链环境,实现跨企业、跨区域的区块链应用。
原文链接
人工智能
2018-11-22 16:40:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
KVM Forum是由Linux基金会组织的高端技术论坛会议,主要为社区各个维护者,开发人员,和用户提供一个讨论Linux虚拟化技术发展趋势以及挑战的交流场所。参会人员都集中在KVM虚拟化相关领域,是KVM社区最为重要和权威的大会。
概述
2018年度的KVM Forum于10月24-26日在英国爱丁堡召开。本次会议吸引了众多技术专家参与会议。同时,参会公司聚集了各家云厂商以及Red Hat、Intel、IBM、AMD等系统和硬件厂商。
先来看看KVM Forum的超高人气。
第一天的keynote被围得水泄不通。
满满当当的就餐区
本届大会总共有51个主题演讲,覆盖虚拟化各个领域,比如:热迁移、嵌套虚拟化、ARM虚拟化、设备直通、轻量虚拟化、安全及性能优化等方面。
热门议题
从议题数量和现场效果来看,以下三个领域比较受到关注: 嵌套虚拟化 虚拟化性能优化 热迁移
嵌套虚拟化
简单来说,嵌套虚拟化就是解决如何在虚拟机里嵌套运行虚拟机的问题。作为近期新起的热门话题,嵌套虚拟化相关演讲在本次KVM Forum上多达4个。
会议介绍了半虚拟化(para-virtualization)VMCS,半虚拟化MSR-bitmap和时钟优化方面的技术,可以提升嵌套时vCPU之间的切换性能,帮助KVM更好地运行在虚拟机里。
另外,会议还涉及了嵌套虚拟化的中断虚拟化性能优化,以及嵌套虚拟化对PowerPC处理器的支持。
可以看到,嵌套虚拟化技术在商用中初步取得了一定的成果,但在安全性上带来的挑战仍然是大家提问的热点。
虚拟化性能优化
事实上,虚拟化的性能优化在近几年的KVM Forum中都比较受关注。在去年的大会上,阿里云高级技术专家张扬就对消息传递类业务在KVM上的性能比较差的问题,虚拟时钟开销的问题,以及消息中间件在KVM看到的性能下降等问题提出过有效的解决方案。
此次,相关的演讲涵盖了vCPU优化,内存优化,和I/O优化。vCPU优化包含了半虚拟化(para-virtualization)方面的优化,如PV TLB shootdown,PV IPI性能优化;内存优化涉及了virtio-mem引入半虚拟化接口,支持内存热插拔等等;I/O优化介绍了vHost Virtio-net 网络接收队列的零页拷贝(zero copy)。
热迁移
弹性调度和动态伸缩是云技术的核心特性。然而,它们都非常依赖底层热迁移的技术性能完善。同时,通过热迁移还可以很大程度降低物理故障对客户的影响。在本届大会上,热迁移相关的演讲全部人气高涨。
在过去的一年,阿里云虚拟化团队对热迁移领域大量投入,获得了大量宝贵的经验。本次参会的3个议题也全都关于热迁移。在此,我们为大家展开详细介绍。
阿里云在KVM Forum 2018
今年的KVM Forum,热迁移的相关主题演讲一共有五个,阿里云占了其中之三。
《Cloudatlas: Ways to Make Live Migration Easy and Expectable》
演讲者:阿里云高级技术专家 张超;阿里云技术专家 谢峰
阿里云高级技术专家 张超
阿里云技术专家 谢峰
如何平衡热迁移资源占用与性能开销,同时最小化热迁移对客户的业务影响,是所有公有云厂商都面临的挑战。阿里云创新性地利用机器学习的方法来预测热迁移的代价以及虚拟机的负载,从而确定一个合理的热迁移的时机和策略,最终提升迁移成功率以及降低热迁移对用户的影响。
阿里云虚拟化团队设计了一个完备的系统来执行热迁移任务的执行。在演讲中,提到了机器学习算法部分,阿里云使用FFT来判断是否是周期性负载VM,并对周期性负载VM算出周期然后进行预测,对于没有明显周期性负载的VM,团队采用ARIMA和LSTM两个时间序列模型来做回归预测,通过以上三种算法的结合可以快速并准确地预测出95%以上VM在未来24小时的负载趋势,从而找到一个最优的迁移时间。
本次演讲涉及了大量的算法研究,现场听众对使用案例、方案原理、预测效果等非常细节的实现问题进行了进一步的提问。有一位行业专家现场说:这是真正在解决公有云厂商关心的问题。
《A Perfect Solution for Live Migration with Pass-through Devices》
演讲者:阿里云技术专家 徐权
该演讲直击了现有直通设备热迁移的问题:即如何传输设备DMA的内存和如何保存和恢复设备的状态。
阿里云技术专家 徐权
演讲深入分析了现有的一些方法和存在的问题。比如Intel在82599网卡上,在虚拟机内部设备驱动引入self emulation layer,模拟设备DMA写操作,和恢复保存设备状态。但由于特定的设备驱动才能工作,而且设备本身设计的缺陷(寄存器只读,在恢复中无法100%恢复),并不能很好地解决问题。而如果从新的硬件和设备驱动的角度切入,在成本投入上是巨大的,而且增加了软件的维护成本。
阿里云扩展了virtio硬件的功能,如感知虚拟机热迁移,提供设备bitmap记录设备DMA访问内存,以及设备所有的寄存器能够动态地保存和恢复,同时修改现有的热迁移和VFIO软件框架。这样能最大限度地利用现有设备驱动,很好地支持Windows和Linux虚拟机,并不需要单独维护各个版本的设备驱动,大大降低了成本。
演讲结束后,现场有十几位专家的技术提问。会后徐权还和Linux 社区VFIO maintainer Alex Wiliamson多次讨论如何改进现有的VFIO来支持直通设备热迁移的通用框架。
《Live Migration Support for GPU with SRIOV: Challenges and Solution》
阿里云与AMD联合演讲,阿里云演讲者:阿里云高级技术专家 郑晓
GPU的热迁移支持是业界的难点。首先,GPU硬件的调度与上下文切换是以millisecond 为单位的,是CPU的好几个数量级,由此会引发GPU任务的抢占问题。再者,GPU在云计算领域的拓展是最近几年出现的热点,而GPU硬件本身对于虚拟化热迁移的支持尚未完善,比如GPU对local memory的dirty track,对non local memory的dirty track的硬件支持等。这些都为GPU的热迁移带来很多挑战。
阿里云高级技术专家 郑晓
演讲中提到了GPU 任务在迁移途中的时效性,抢占问题,比如GPU硬件本身在尚未支持framebuffer dirty track的时候,如何通过hypervisor的措施,通过系统软件的方法来弥补,以及GPU本身上下文的切换需要处理的细节等等。
所有上面提到的问题在阿里云与AMD联合开发的第一天就开始考虑与设计。其中有众多独有的创新点。例如,在某些型号GPU硬件不支持dirty track的时候,通过软件的方式来跟踪GPU的Framebuffer dirty page;在Service downtime等关键性能指标不符合预期的时候,如何把数据从6秒优化到了0.35秒左右;功能方面,从单机迁移完善到多机多卡迁移;在稳定性方面,从一开始的做一次就宕机,到后续连续上千次的压力测试……
此外,演讲还加入了现场的Live Demo,从使用体验上面来说,已经可以做到GPU渲染任务的流畅迁移。
该演讲得到了高度关注。会后,部分顶级硬件厂商还与演讲者详细讨论了更多的热迁移细节和性能指标。
小结
参加完本次KVM Forum,我们深刻感觉到,如何解决实际生产环境中遇见的现实问题,正受到业界越来越多的重视。
通过参会,促进了我们和虚拟化业界的交流,也让团队获取了大量信息和宝贵经验,这些都可以帮助我们更好地提升,并且不断打磨提升弹性计算产品的底层能力。
原文链接
人工智能
2018-11-22 16:11:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
云计算从不被看好到成长为势不可挡的技术潮流,仅仅用了十年的时间。如今“云原生”又被企业以及开发者奉为圭臬,并被认为是云计算的未来。
阿里云容器技术负责人易立认为云计算有三个阶段:云搬迁、云就绪和云原生。
第一个阶段是为了降低成本的搬迁上云,将运行在物理机中的应用迁移到虚拟化环境中,应用的开发和运维方式并没有很大不同。
第二个阶段云就绪,企业希望利用云计算提升整体效率,开始尝试微服务架构,并关注标准化、自动化、可扩展性和高可用性等指标。
而第三个阶段,也就是当下,企业开始完全拥抱云计算,很多应用从最开始就生长在云上,敏捷、弹性、容错、可移植等也成为重点特性,软件的架构设计、开发、构建、交付等整个生命周期都被重塑了。
在首届国内KubeCon + CloudNativeCon 2018上,云原生的应用实践与落地成为全球的顶级科技企业、开发者关注的焦点,大会keynote环节中,阿里云研究员马涛分享了云原生生态系统在中国的现状,云原生技术已经融入了大众的衣食住行。
阿里云研究员马涛在现场作主题演讲
从双11谈起,云原生在中国的应用实践
“云原生技术已经慢慢地融入了大众衣食住行。”马涛表示。
举个例子,2018天猫双11期间,Pouch、阿里云容器镜服务就为广大消费者“买买买”提供了稳定的支撑。
一般情况下,在应对流量洪峰场景之前都需要对流量提前预估,以预先准备计算好的资源和应用容量,但仅完成这一步,还不足以支撑流量高峰,紧急扩容的能力也极为重要。事实证明,容器技术的特点非常适合此类场景,在需要时可以快速、自动弹性伸缩。
早在2011年,阿里巴巴就开始展开容器技术的探索。2016年双11将所有交易核心应用容器化,几十万容器撑起了双11交易17.5万笔/秒的下单峰值;去年双11,阿里巴巴集团实现在线服务全部容器化,内部已达到百万级容器部署规模,其中双11交易峰值到32.5万笔/秒,并且可以实现十分钟内万台服务器的快速部署。
到现在,容器镜像服务支撑了阿里巴巴集团10万镜像的托管,并累积了2亿次镜像下载量。
云原生技术已经在各行各业得到了应用:饿了么使用TiKV承载随时可能暴增的外卖订单;思岚基于阿里云和Kubernetes、Harbor构建了扫地机器人方案;微博更是构建了深度学习训练框架,用Kubernetes统一管理异构资源和AI任务调度。
阿里云专家与海外专家
阿里云云原生实践的方法论
云原生概念其实由来已久,最早是2015年Pivotal的Matt Stine首次提出,但很长一段时间业界对其定义都很模糊。
今年,CNCF云原生基金会对云原生给出了具体定义:云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更。
如果在容器化基础架构和服务治理层,构建最上层的业务抽象,这需要一整个的云原生操作系统。所以云原生更多的是一套技术体系和方法论,阿里云容器服务就是基于这一理念打造的。
阿里云容器服务Kubernetes版(ACK)同时支持公共云、专有云和混合云模式,优化整合阿里云基础能力,使得容器应用轻松地利用底层强大的计算、存储、网络等能力。
例如,面向追求极致效率的深度学习、高性能计算场景: 可以采用神龙弹性裸金属服务器或者GPU、FPGA实例等异构计算能力; 配合阿里云Terway网络驱动使用弹性网卡可以几乎无损耗地达到 9Gb网络带宽,或者采用融合以太网的RDMA协议(RoCE)技术25Gb网络; 还可以基于CPFS这样并行文件系统提升处理效率,提供高达1亿IOPS和1TBps吞吐的能力。
至于服务网格,可以在阿里云Kubernetes作为基础架构之上,结合Istio服务网格,支持多云、混合云,针对微服务提供有效的治理能力,并以Kubernetes和Istio为基础,提供针对特定应用负载的不同支持,例如针对Kubeflow服务的流量治理、为Knative提供负载的路由管理能力等。
容器已经成为一种技术平台,需要满足各种应用场景的需求,例如IoT、深度学习、区块链等。阿里云区块链高级技术专家余珊认为,“区块链业务应用没有统一的标准,会涉及到各种类型的应用。而容器提供了标准的软件打包、分发能力,实现了运行环境的一致性以及与底层的解耦,并且可以充分发挥Kubernetes的资源调度、运维管理、安全隔离、微服务架构等优势。”
2017年9月,阿里云推出基于容器的区块链解决方案,用户可以通过Kubernetes和Helm/Chart模板快速搭建企业级区块链环境。今年8月,发布了区块链即服务(Blockchain as a Service),在系统开发、运维各个环节,支持一键快速部署区块链环境,实现跨企业、跨区域的区块链应用。
原文链接
本文为云栖社区原创内容,未经允许不得转载。
人工智能
2018-11-22 15:09:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
欢迎大家前往 腾讯云+社区 ,获取更多腾讯海量技术实践干货哦~ 本文由 forrestlin 发表于 云+社区专栏
导语:转置卷积层(Transpose Convolution Layer)又称反卷积层或分数卷积层,在最近提出的卷积神经网络中越来越常见了,特别是在对抗生成神经网络(GAN)中,生成器网络中上采样部分就出现了转置卷积层,用于恢复减少的维数。那么,转置卷积层和正卷积层的关系和区别是什么呢,转置卷积层实现过程又是什么样的呢,笔者根据最近的预研项目总结出本文。
1. 卷积层和全连接层
在CNN提出之前,我们所提到的人工神经网络应该多数情况下都是前馈神经网络,两者区别主要在于CNN使用了卷积层,而前馈神经网络用的都是全连接层,而这两个layer的区别又在于全连接层认为上一层的所有节点下一层都是需要的,通过与权重矩阵相乘层层传递,而卷积层则认为上一层的有些节点下一层其实是不需要的,所以提出了卷积核矩阵的概念,如果卷积核的大小是n m,那么意味着该卷积核认为上一层节点每次映射到下一层节点都只有n m个节点是有意义的,具体的映射方式下一节会讲到。到这里,有些初学者会认为全连接层也可以做到,只要让权重矩阵某些权重赋值为0就可以实现了,例如假设在计算当前层第2个节点时认为上一层的第1个节点我不需要,那么设置w01=0就可以了。其实没错,卷积层是可以看做全连接层的一种特例,卷积核矩阵是可以展开为一个稀疏的包含很多0的全连接层的权重矩阵,下图就是一个由4 4图片经过3 3卷积核生成一个大小为2*2output时,卷积核所展开的全连接层的权重矩阵。
卷积核对应的全连接层权重矩阵
可以看到,上面的矩阵大小为4 16,比卷积核3 3大了不少,因此使用卷积层而不用全连接层第一个原因就是可以极大的减少参数的个数,第二个原因就是卷积核关注的是某几个相邻的节点之间的关系,学习了图片的局部特征,可以说是带有目的性的学习,例如3 3的卷积核学习的就是相互距离为2的节点之间的关系。这与全连接层无区别的对待所有节点进行学习有极大的差别,这样一来就解决了前馈神经网络不能学习位移不变性的缺点。举个栗子,当我们在前馈神经网络中学习一个4 4的图片中是否有横折图案时,使用下图中4个训练数据进行训练,那么最终只会对5,6,9,a这四个节点的权重有所调节,然后如果出现如下图最后一张图片作为测试时,就会导致网络无法识别,而由于卷积核在不同节点间权重是共享的,所以就自然而然克服了这个问题。
卷积克服平移不变性
2. 卷积层的运算过程
2.1 最简单的卷积
卷积层的运算其实就是将多个卷积核作用于输入上,如下图所示,是最简单的一个卷积核所做的运算,no padding,no stride,底下蓝色方块看做是输入的图片,阴影部分就是3 3的卷积核(一般卷积核是个正方形,且边长为奇数),卷积核扫过时便与输入相乘再相加,最终得到2 2的输出,对应青色区域。
no padding, no stride的卷积
通常一层卷积层会包含多个卷积核,代表着卷积层的输出深度,例如下图就是我们经常在论文中看到的深度网络的架构,其中第一层就是卷积层+最大池化层,先不管最大池化层,至少我们可以明确卷积核的大小是5 5,卷积核个数是16,该层输出的size是18 18。
论文常见的卷积层
2.2 带padding的卷积
从最简单的卷积动图中我们可以看到,经过卷积操作,输出会比输入要小,但是有时候我们希望输出的size要与输入保持一致,而padding就是为了这个而引入的,而这种为了让输入和输出size保持一样的padding,我们会称之为"same padding",可参考下面的动图,卷积核大小是3*3,padding是1,padding实际的表现就是在输入的四周补0,padding是多少就补多少层,且上限是卷积核大小-1,正如下图中虚线区域,一般来说,论文中是不会给出padding的大小,需要我们自己推导,推导公式可见下文。
padding=1的卷积
根据padding大小不同,我们可以分为三种padding: same padding: 为了让输出和输入的size一样而补上的padding,例如3 3的核,same padding = 1,5 5的核,same padding = 2。 full padding: padding = kernel size - 1 valid padding: padding = 0
2.3 stride大于1的卷积
stride就是步长,表示卷积核两次卷积操作的距离,默认是1,上述讲的两个例子步长都是1,而下面两个动图展示的是stride为2的情况,分别是无padding和有padding的情况。通常stride大于1时我们称为等距下采样,因为这样输出肯定会丢失信息,size比输入的小。
no padding, stride=2的卷积
padding=1, stride=2的卷积
2.4 卷积核输入输出size与卷积核的关系
上文中我们提到padding通常需要我们自己算出来,那么我们该怎么算呢,其实就是根据输入输出size和卷积核大小的关系算出来的,上面提到的几种卷积,其实就是卷积操作的三个参数,核大小(F)、padding(P)和stride(S),如果细心的读者在看动图时就会发现输出size是可以根据输入size和那三个参数计算出来的,公式如下,这里只给出宽度的计算,高度也是一样的。
W2=(W1−F+2P)÷S+1
这里我们注意到上面的公式是有除法的,所以就会存在除不尽的情况,这时候我们需要向下取整,这种情况我们称为odd卷积,其过程可参考下面动图。
odd卷积
3. 转置卷积层
讲完卷积层后,我们来看CNN中另一个进行卷积操作的层次转置卷积层,有时我们也会称做反卷积层,因为他的过程就是正常卷积的逆向,但是也只是size上的逆向,内容上不一定,所以有些人会拒绝将两者混为一谈。转置卷积层最大的用途就是上采样了,刚刚我们说到在正常卷积中stride大于1时我们进行的是等距下采样,会让输出的size比输入小,而转置卷积层我们就会用stride小于1的卷积进行上采样,使输出的size变大,所以转置卷积层还有个别称就是分数卷积层。上采样最常见的场景可以说就是GAN中的生成器网络,如下图所示,虽然论文作者使用的是conv,但由于它的步长为1/2,所以代表的就是转置卷积层。
转置卷积例子
为了理解转置卷积层,我们需要明白什么叫做正常卷积的逆向,这通常也是新手难以理解的地方,下面笔者通过两个图来更好的解释,第一个图是正常卷积的过程,第二个图就是其对应的转置卷积,在第一个图中,大的正方形中数字1只参与小正方形中数字1的计算,那么在转置卷积中,大正方形的1也只能由小正方形的1生成,这就是逆向的过程。
no padding, no stride的卷积
转置卷积.png
和讲述正常卷积的过程一样,笔者下面也会一一给出相对应的转置卷积。
3.1 no padding no stride的卷积对应的转置卷积
上面用作解释转置卷积的逆向过程时用到的图其实就是最简单(no padding, no stride)卷积以及其对应的转置卷积,这里给出它的动图。
no padding, no stride的卷积转置
3.2 带padding的卷积的转置卷积
在正卷积中如果是有padding,那么在转置卷积中不一定会有padding,其计算公式下文会给出,这里先给出2.2对应的转置卷积动图。
padding为1的卷积转置
3.3 stride大于1的卷积的转置卷积
在本节一开始就讲到,stride大于1的卷积是下采样,那么其对应的转置卷积便是stride小于1的上采样,但是不管是在pyTorch还是TensorFlow中,convTranspose函数的参数都是整数,不可能将stride设置为小于1的浮点数,那么我们会依然给convTranspose函数传入正卷积的stride,而convTranspose是怎么做的呢,可见下面的动图,它是2.3中无padding卷积对应的转置卷积,我们先不看转置卷积中的转置padding,也就是动图中外部的虚线区域,然后会发现每两个蓝色块之间都插入了白色块,也就是0,这样一来,卷积核每移动一步不就相当于是只移动了1/2步嘛,所以我们可以得出每两个蓝色块之间需要插入stride -1个0。
stride为2的卷积转置
3.4 正卷积和转置卷积的换算关系
3.4.1 转置卷积的padding
从上面3个例子的转置卷积中我们可以发现,如果用正卷积实现转置卷积时,卷积核的大小是保持不变的,而stride是为正卷积stride的倒数(只是我们插入0来模拟分数移动),最后,转置卷积的padding要怎么算呢,虽然如果我们调用pyTorch或TensorFlow时不需要管,传入正卷积的padding即可,但是理解convTranspose是怎么做的也有助于我们理解转置卷积。说了这么多,其实在我们为了让转置卷积保证是正卷积的逆向时,我们就不得不补充转置padding,我们用PT表示,其计算公式为:PT=F−P−1,其中F为正卷积的核大小,P为正卷积的padding。
3.4.2 转置卷积的输出size
这个其实很好算,因为我们都说转置卷积的逆向,所以我们只需在2.4给出公式中转换下求出W1即可,公式如下:
W1=(W2−1)×S−2P+F
其中S是正卷积的stride,P是正卷积的padding,F是正卷积的核边长。
3.4.3 odd卷积的转置卷积
这个可以说是转置卷积中最难理解的一种情况,在2.4中我们提到在除以stride时可能会除不尽要向下取整,那么我们在求W1时就会有不确定性,举个栗子,还是第3节一开始给出的图,我们是希望将W/4的图放大到W/2的程度,这是一个转置卷积的过程,我们先算一遍正卷积,从W/2下采样到W/4,k代表核边长为3,s是stride为1/2的倒数,即2,padding根据2.4的公式推导为1,所以正卷积的计算公式是:(W2−3+2)÷2+1=W4+12,然后向下取整就是W4,和图上显示的是一样,但是如果我们通过3.4.2的公式反过来计算,就是(W4−1)×2−2+3=W2−1,这就是odd转置卷积的不确定性,我们再回头看2.4给出的动图,会发现右边和下边的填充区域我们并没有进行卷积运算,因为向下取整而忽略了,所以我们在转置卷积时需要将这部分加回来,因此,在PyTorch中convTranspose函数还有一个参数output_padding就是负责处理这个的,TensorFlow应该也有相应的参数,笔者不太熟悉,下面就是PyTorch对该参数的描述,和我们遇到的情形一模一样。
PyTorch中转置卷积的output_padding参数
至于output_padding的值,应该为(W1−F+2P)%S,在上面提到的例子中就应该是1。
4. 总结
本文先是介绍了卷积神经网络和传统的前馈神经网络的联系和区别,然后再通过不同参数的卷积过程阐述卷积运算,最后再介绍刚入门深度学习时晦涩难懂的转置卷积,给出不同参数下正卷积所对应的转置卷积,最后总结出在卷积运算中所用到的公式。希望笔者上述的分析和解释能对刚入门CNN的同学有所帮助,而且笔者是从事iOS开发的,对于CNN和深度学习也是刚刚入门,希望各位AI大牛们不吝指教。
5. 参考文档 知乎上对CNN的直观解释,平移不变性笔者是从这里了解到的 《A guide to convolution arithmetic for deep learning》的github,本文的动图都来自于此 关于转置卷积和卷积的联系和区别 相关阅读 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识
此文已由作者授权腾讯云+社区发布,更多原文请 点击
搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!
海量技术实践经验,尽在 云加社区 !
人工智能
2018-11-22 11:36:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 近几年来,人工智能逐渐火热起来,特别是和大数据一起结合使用。人工智能的主要场景又包括图像能力、语音能力、自然语言处理能力和用户画像能力等等。这些场景我们都需要处理海量的数据,处理完的数据一般都需要存储起来,这些数据的特点主要有如下几点: 大:数据量越大,对我们后面建模越会有好处; 稀疏:每行数据可能拥有不同的属性,比如用户画像数据,每个人拥有属性相差很大,可能用户A拥有这个属性,但是用户B没有这个属性;那么我们希望存储的系统能够处理这种情况,没有的属性在底层不占用空间,这样可以节约大量的空间使用; 列动态变化:每行数据拥有的列数是不一样的。
近几年来,人工智能逐渐火热起来,特别是和大数据一起结合使用。人工智能的主要场景又包括图像能力、语音能力、自然语言处理能力和用户画像能力等等。这些场景我们都需要处理海量的数据,处理完的数据一般都需要存储起来,这些数据的特点主要有如下几点: 大:数据量越大,对我们后面建模越会有好处; 稀疏:每行数据可能拥有不同的属性,比如用户画像数据,每个人拥有属性相差很大,可能用户A拥有这个属性,但是用户B没有这个属性;那么我们希望存储的系统能够处理这种情况,没有的属性在底层不占用空间,这样可以节约大量的空间使用; 列动态变化:每行数据拥有的列数是不一样的。
为了更好的介绍 HBase 在人工智能场景下的使用,下面以某人工智能行业的客户案例进行分析如何利用 HBase 设计出一个快速查找人脸特征的系统。
目前该公司的业务场景里面有很多人脸相关的特征数据,总共3400多万张,每张人脸数据大概 3.2k。这些人脸数据又被分成很多组,每个人脸特征属于某个组。目前总共有近62W个人脸组,每个组的人脸张数范围为 1 ~ 1W不等,每个组里面会包含同一个人不同形式的人脸数据。组和人脸的分布如下: 43%左右的组含有1张人脸数据; 47%左右的组含有 2 ~ 9张人脸数据; 其余的组人脸数范围为 10 ~ 10000。
现在的业务需求主要有以下两类: 根据人脸组 id 查找该组下面的所有人脸; 根据人脸组 id +人脸 id 查找某个人脸的具体数据。
MySQL + OSS 方案
之前业务数据量比较小的情况使用的存储主要为 MySQL 以及 OSS(对象存储)。相关表主要有人脸组表group和人脸表face。表的格式如下:
group表:
group_id size
1
2
face表:
face_id group_id feature
"c5085f1ef4b3496d8b4da050cab0efd2"
1
"cwI4S/HO/nm6H……"
其中 feature 大小为3.2k,是二进制数据 base64 后存入的,这个就是真实的人脸特征数据。
现在人脸组 id 和人脸 id 对应关系存储在 MySQL 中,对应上面的 group 表;人脸 id 和人脸相关的特征数据存储在 OSS 里面,对应上面的 face 表。
因为每个人脸组包含的人类特征数相差很大(1 ~ 1W),所以基于上面的表设计,我们需要将人脸组以及每张人脸特征id存储在每一行,那么属于同一个人脸组的数据在MySQL 里面上实际上存储了很多行。比如某个人脸组id对应的人脸特征数为1W,那么需要在 MySQL 里面存储 1W 行。
我们如果需要根据人脸组 id 查找该组下面的所有人脸,那么需要从 MySQL 中读取很多行的数据,从中获取到人脸组和人脸对应的关系,然后到 OSS 里面根据人脸id获取所有人脸相关的特征数据,如下图的左部分所示。
我们从上图的查询路径可以看出,这样的查询导致链路非常长。从上面的设计可看出,如果查询的组包含的人脸张数比较多的情况下,那么我们需要从 MySQL 里面扫描很多行,然后再从 OSS 里面拿到这些人脸的特征数据,整个查询时间在10s左右,远远不能满足现有业务快速发展的需求。
HBase 方案
上面的设计方案有两个问题: 原本属于同一条数据的内容由于数据本身大小的原因无法存储到一行里面,导致后续查下需要访问两个存储系统; 由于MySQL不支持动态列的特性,所以属于同一个人脸组的数据被拆成多行存储。
针对上面两个问题,我们进行了分析,得出这个是 HBase 的典型场景,原因如下: HBase 拥有动态列的特性,支持万亿行,百万列; HBase 支持多版本,所有的修改都会记录在 HBase 中; HBase 2.0 引入了 MOB(Medium-Sized Object) 特性,支持小文件存储。HBase 的 MOB 特性针对文件大小在 1k~10MB 范围的,比如图片,短视频,文档等,具有低延迟,读写强一致,检索能力强,水平易扩展等关键能力。
我们可以使用这三个功能重新设计上面 MySQL + OSS 方案。结合上面应用场景的两大查询需求,我们可以将人脸组 id 作为 HBase 的 Rowkey,系统的设计如上图的右部分显示,在创建表的时候打开 MOB 功能,如下: create 'face', {NAME => 'c', IS_MOB => true, MOB_THRESHOLD => 2048}
上面我们创建了名为 face 的表, IS_MOB 属性说明列簇 c 将启用 MOB 特性, MOB_THRESHOLD 是 MOB 文件大小的阈值,单位是字节,这里的设置说明文件大于 2k 的列都当做小文件存储。大家可能注意到上面原始方案中采用了 OSS 对象存储,那我们为什么不直接使用 OSS 存储人脸特征数据呢,如果有这个疑问,可以看看下面表的性能测试:
对比属性 对象存储 云 HBase 建模能力 KV KV、表格、稀疏表、SQL、
全文索引、时空、时序、图查询
查询能力 前缀查找 前缀查找、过滤器、索引
性能 优,特别对小对象有更低的延迟;在复杂
查询场景下,比对象存储有10倍以上的性能提升
成本 按流量,请求次数计费,
适合访问频率低的场景
托管式,在高并发,高吞吐场景有更低的成本
扩展性
适用对象范围
通用
<10MB
根据上面的对比,使用 HBase MOB特性来存储小于10MB的对象相比直接使用对象存储有一些优势。
我们现在来看看具体的表设计,如下图:
上面 HBase 表的列簇名为c,我们使用人脸id作为列名。我们只使用了 HBase 的一张表就替换了之前方面的三张表!虽然我们启用了 MOB,但是具体插入的方法和正常使用一样,代码片段如下: String CF_DEFAULT = "c"; Put put = new Put(groupId.getBytes()); put.addColumn(CF_DEFAULT.getBytes(),faceId1.getBytes(), feature1.getBytes()); put.addColumn(CF_DEFAULT.getBytes(),faceId2.getBytes(), feature2.getBytes()); …… put.addColumn(CF_DEFAULT.getBytes(),faceIdn.getBytes(), featuren.getBytes()); table.put(put);
用户如果需要根据人脸组id获取所有人脸的数据,可以使用下面方法: Get get = new Get(groupId.getBytes()); Result re=table.get(get);
这样我们可以拿到某个人脸组id对应的所有人脸数据。如果需要根据人脸组id+人脸id查找某个人脸的具体数据,看可以使用下面方法: Get get = new Get(groupId.getBytes()); get.addColumn(CF_DEFAULT.getBytes(), faceId1.getBytes()) Result re=table.get(get);
经过上面的改造,在2台 HBase worker 节点内存为32GB,核数为8,每个节点挂载四块大小为 250GB 的 SSD 磁盘,并写入 100W 行,每行有1W列,读取一行的时间在100ms-500ms左右。在每行有1000个face的情况下,读取一行的时间基本在20-50ms左右,相比之前的10s提升200~500倍。
下面是各个方案的对比性能对比情况。
对比属性 对象存储 MySQL+对象存储 HBase MOB 读写强一致 Y N Y
查询能力
查询响应时间
运维成本 水平扩展
Y
Y
Y
使用 Spark 加速数据分析
我们已经将人脸特征数据存储在阿里云 HBase 之中,这个只是数据应用的第一步,如何将隐藏在这些数据背后的价值发挥出来?这就得借助于数据分析,在这个场景就需要采用机器学习的方法进行聚类之类的操作。我们可以借助 Spark 对存储于 HBase 之中的数据进行分析,而且 Spark 本身支持机器学习的。但是如果直接采用开源的 Spark 读取 HBase 中的数据,会对 HBase 本身的读写有影响的。
针对这些问题,阿里云 HBase 团队对 Spark 进行了相关优化,比如直接读取 HFile、算子下沉等;并且提供全托管的 Spark 产品,通过SQL服务ThriftServer、作业服务LivyServer简化Spark的使用等。目前这套 Spark 的技术栈如下图所示。
通过 Spark 服务,我们可以和 HBase 进行很好的整合,将实时流和人脸特征挖掘整合起来,整个架构图如下:
我们可以收集各种人脸数据源的实时数据,经过 Spark Streaming 进行简单的 ETL 操作;其次,我们通过 Spark MLib 类库对刚刚试试收集到的数据进行人脸特征挖掘,最后挖掘出来的结果存储到 HBase 之中。最后,用户可以通过访问 HBase 里面已经挖掘好的人脸特征数据进行其他的应用。
原文链接
人工智能
2018-11-22 11:13:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
【一】本文内容综述
1. keras使用流程分析(模型搭建、模型保存、模型加载、模型使用、训练过程可视化、模型可视化等)
2. 利用keras做文本数据预处理
【二】环境准备
1. 数据集下载:http://ai.stanford.edu/~amaas/data/sentiment/
2.安装Graphviz ,keras进行模型可视化时,会用到该组件: https://graphviz.gitlab.io/_pages/Download/Download_windows.html
【三】数据预处理
将imdb压缩包解压后,进行数据预处理。
1. 将每条影评中的部分词去掉
2. 将影评与label对应起来
3. 将影评映射为int id,同时将每条影评的长度固定,好作为定长输入数据 # -*- coding:utf-8 -*- import keras import os import numpy as np import re from keras.preprocessing import text from keras.preprocessing import sequence from keras.utils import plot_model import matplotlib.pyplot as plt Reg = re.compile(r'[A-Za-z]*') stop_words = ['is','the','a'] max_features = 5000 word_embedding_size = 50 maxlen = 400 filters = 250 kernel_size = 3 hidden_dims = 250 def prepross(file): with open(file,encoding='utf-8') as f: data = f.readlines() data = Reg.findall(data[0]) # 将句子中的每个单词转化为小写 data = [x.lower() for x in data] # 将句子中的部分词从停用词表中剔除 data = [x for x in data if x!='' and x not in stop_words] # 返回值必须是个句子,不能是单词列表 return ' '.join(data) def imdb_load(type): root_path = "E:/nlp_data/aclImdb_v1/aclImdb/" # 遍历所有文件 file_lists = [] pos_path = root_path + type + "/pos/" for f in os.listdir(pos_path): file_lists.append(pos_path + f) neg_path = root_path + type + "/neg/" for f in os.listdir(neg_path): file_lists.append(neg_path + f) # file_lists中前12500个为pos,后面为neg,labels与其保持一致 labels = [1 for i in range(12500)] labels.extend([0 for i in range(12500)]) # 将文件随机打乱,注意file与label打乱后依旧要通过下标一一对应。 # 否则会导致 file与label不一致 index = np.arange(len(labels)) np.random.shuffle(index) # 转化为numpy格式 labels = np.array(labels) file_lists = np.array(file_lists) labels[index] file_lists[index] # 逐个处理文件 sentenses = [] for file in file_lists: #print(file) sentenses.append(prepross(file)) return sentenses,labels def imdb_load_data(): x_train,y_train = imdb_load("train") x_test,y_test = imdb_load("test") # 建立单词和数字映射的词典 token = text.Tokenizer(num_words=max_features) token.fit_on_texts(x_train) # 将影评映射到数字 x_train = token.texts_to_sequences(x_train) x_test = token.texts_to_sequences(x_test) # 让所有影评保持固定长度的词数目 x_train = sequence.pad_sequences(x_train,maxlen=maxlen) x_test = sequence.pad_sequences(x_test,maxlen=maxlen) return (x_train,y_train),(x_test,y_test)
【四】模型搭建与训练 def train(): (x_train, y_train), (x_test, y_test) = imdb_load_data() model = keras.Sequential() # 构造词嵌入层 model.add(keras.layers.Embedding(input_dim=max_features,output_dim=word_embedding_size,name="embedding")) # 通过layer名字获取layer的信息 print(model.get_layer(name="embedding").input_shape) # 基于词向量的堆叠方式做卷积 model.add(keras.layers.Conv1D(filters=filters,kernel_size=kernel_size,strides=1 ,activation=keras.activations.relu,name="conv1d")) # 对每一个卷积出的特征向量做最大池化 model.add(keras.layers.GlobalAvgPool1D(name="maxpool1d")) # fc,输入是250维,输出是hidden_dims model.add(keras.layers.Dense(units=hidden_dims,name="dense1")) # 添加激活层 model.add(keras.layers.Activation(activation=keras.activations.relu,name="relu1")) # fc,二分类问题,输出维度为1 model.add(keras.layers.Dense(units=1,name="dense2")) # 二分类问题,使用sigmod函数做分类器 model.add(keras.layers.Activation(activation=keras.activations.sigmoid,name="sigmoe")) # 打印模型各层layer信息 model.summary() # 模型编译,配置loss,optimization model.compile(optimizer=keras.optimizers.Adam(), loss=keras.losses.binary_crossentropy, metrics=['accuracy']) # 模型训练 ''' # 如果想保存每一个batch的loss等数据,需要传递一个callback history = LossHistory() train_history = model.fit(x=x_train, y=y_train, batch_size=128, epochs=1, validation_data=(x_test,y_test), callbacks=[history]) show_train_history2(history) # 结果可视化 ''' # fit 返回的log中,有 epochs 组数据,即只保存每个epoch的最后一次的loss等值 train_history = model.fit(x=x_train, y=y_train, batch_size=128, epochs=1, validation_data=(x_test,y_test)) show_train_history(train_history) # 模型保存 model.save(filepath="./models/demo_imdb_rnn.h5") # 模型保存一份图片 plot_model(model=model,to_file="./models/demo_imdb_rnn.png", show_layer_names=True,show_shapes=True)
【五】模型训练过程中loss的曲线绘制 class LossHistory(keras.callbacks.Callback): def on_train_begin(self, logs={}): self.losses = [] def on_batch_end(self, batch, logs={}): self.losses.append(logs.get('loss')) def show_train_history2(history): plt.plot(history.losses) plt.title("model losses") plt.xlabel('batch') plt.ylabel('losses') plt.legend() # 先保存图片,后显示,不然保存的图片是空白 plt.savefig("./models/demo_imdb_rnn_train.png") plt.show() def show_train_history(train_history): print(train_history.history.keys()) print(train_history.epoch) plt.plot(train_history.history['acc']) plt.plot(train_history.history['val_acc']) plt.title("model accuracy") plt.xlabel("epoch") plt.ylabel("accuracy") plt.legend() plt.show() plt.plot(train_history.history['loss']) plt.plot(train_history.history['val_loss']) plt.title("model loss") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.show()
【六】基于训练好的模型做预测 def gen_predict_data(path): sent = prepross(path) x_train,t_train = imdb_load("train") token = text.Tokenizer(num_words=max_features) token.fit_on_texts(x_train) x = token.texts_to_sequences([sent]) x = sequence.pad_sequences(x,maxlen=maxlen) return x RESULT = {1:'pos',0:'neg'} def predict(path): x = gen_predict_data(path) model = keras.models.load_model("./models/demo_imdb_rnn.h5") y = model.predict(x) print(y) y= model.predict_classes(x) print(y) print(RESULT[y[0][0]]) predict(r"E:\nlp_data\aclImdb_v1\aclImdb\test\neg\0_2.txt") predict(r"E:\nlp_data\aclImdb_v1\aclImdb\test\pos\0_10.txt")
预测结果如下: [[0.16223338]] [[0]] neg [[0.8812848]] [[1]] pos
【七】整体代码如下 # -*- coding:utf-8 -*- import keras import os import numpy as np import re from keras.preprocessing import text from keras.preprocessing import sequence from keras.utils import plot_model import matplotlib.pyplot as plt Reg = re.compile(r'[A-Za-z]*') stop_words = ['is','the','a'] max_features = 5000 word_embedding_size = 50 maxlen = 400 filters = 250 kernel_size = 3 hidden_dims = 250 class LossHistory(keras.callbacks.Callback): def on_train_begin(self, logs={}): self.losses = [] def on_batch_end(self, batch, logs={}): self.losses.append(logs.get('loss')) def prepross(file): with open(file,encoding='utf-8') as f: data = f.readlines() data = Reg.findall(data[0]) # 将句子中的每个单词转化为小写 data = [x.lower() for x in data] # 将句子中的部分词从停用词表中剔除 data = [x for x in data if x!='' and x not in stop_words] # 返回值必须是个句子,不能是单词列表 return ' '.join(data) def imdb_load(type): root_path = "E:/nlp_data/aclImdb_v1/aclImdb/" # 遍历所有文件 file_lists = [] pos_path = root_path + type + "/pos/" for f in os.listdir(pos_path): file_lists.append(pos_path + f) neg_path = root_path + type + "/neg/" for f in os.listdir(neg_path): file_lists.append(neg_path + f) # file_lists中前12500个为pos,后面为neg,labels与其保持一致 labels = [1 for i in range(12500)] labels.extend([0 for i in range(12500)]) # 将文件随机打乱,注意file与label打乱后依旧要通过下标一一对应。 # 否则会导致 file与label不一致 index = np.arange(len(labels)) np.random.shuffle(index) # 转化为numpy格式 labels = np.array(labels) file_lists = np.array(file_lists) labels[index] file_lists[index] # 逐个处理文件 sentenses = [] for file in file_lists: #print(file) sentenses.append(prepross(file)) return sentenses,labels def imdb_load_data(): x_train,y_train = imdb_load("train") x_test,y_test = imdb_load("test") # 建立单词和数字映射的词典 token = text.Tokenizer(num_words=max_features) token.fit_on_texts(x_train) # 将影评映射到数字 x_train = token.texts_to_sequences(x_train) x_test = token.texts_to_sequences(x_test) # 让所有影评保持固定长度的词数目 x_train = sequence.pad_sequences(x_train,maxlen=maxlen) x_test = sequence.pad_sequences(x_test,maxlen=maxlen) return (x_train,y_train),(x_test,y_test) def train(): (x_train, y_train), (x_test, y_test) = imdb_load_data() model = keras.Sequential() # 构造词嵌入层 model.add(keras.layers.Embedding(input_dim=max_features,output_dim=word_embedding_size,name="embedding")) # 通过layer名字获取layer的信息 print(model.get_layer(name="embedding").input_shape) # 基于词向量的堆叠方式做卷积 model.add(keras.layers.Conv1D(filters=filters,kernel_size=kernel_size,strides=1 ,activation=keras.activations.relu,name="conv1d")) # 对每一个卷积出的特征向量做最大池化 model.add(keras.layers.GlobalAvgPool1D(name="maxpool1d")) # fc,输入是250维,输出是hidden_dims model.add(keras.layers.Dense(units=hidden_dims,name="dense1")) # 添加激活层 model.add(keras.layers.Activation(activation=keras.activations.relu,name="relu1")) # fc,二分类问题,输出维度为1 model.add(keras.layers.Dense(units=1,name="dense2")) # 二分类问题,使用sigmod函数做分类器 model.add(keras.layers.Activation(activation=keras.activations.sigmoid,name="sigmoe")) # 打印模型各层layer信息 model.summary() # 模型编译,配置loss,optimization model.compile(optimizer=keras.optimizers.Adam(), loss=keras.losses.binary_crossentropy, metrics=['accuracy']) # 模型训练 ''' # 如果想保存每一个batch的loss等数据,需要传递一个callback history = LossHistory() train_history = model.fit(x=x_train, y=y_train, batch_size=128, epochs=1, validation_data=(x_test,y_test), callbacks=[history]) show_train_history2(history) # 结果可视化 ''' # fit 返回的log中,有 epochs 组数据,即只保存每个epoch的最后一次的loss等值 train_history = model.fit(x=x_train, y=y_train, batch_size=128, epochs=10, validation_data=(x_test,y_test)) show_train_history(train_history) # 模型保存 model.save(filepath="./models/demo_imdb_rnn.h5") # 模型保存一份图片 plot_model(model=model,to_file="./models/demo_imdb_rnn.png", show_layer_names=True,show_shapes=True) def show_train_history2(history): plt.plot(history.losses) plt.title("model losses") plt.xlabel('batch') plt.ylabel('losses') plt.legend() # 先保存图片,后显示,不然保存的图片是空白 plt.savefig("./models/demo_imdb_rnn_train.png") plt.show() def show_train_history(train_history): print(train_history.history.keys()) print(train_history.epoch) plt.plot(train_history.history['acc']) plt.plot(train_history.history['val_acc']) plt.title("model accuracy") plt.xlabel("epoch") plt.ylabel("accuracy") plt.legend() plt.show() plt.plot(train_history.history['loss']) plt.plot(train_history.history['val_loss']) plt.title("model loss") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.show() def gen_predict_data(path): sent = prepross(path) x_train,t_train = imdb_load("train") token = text.Tokenizer(num_words=max_features) token.fit_on_texts(x_train) x = token.texts_to_sequences([sent]) x = sequence.pad_sequences(x,maxlen=maxlen) return x RESULT = {1:'pos',0:'neg'} def predict(path): x = gen_predict_data(path) model = keras.models.load_model("./models/demo_imdb_rnn.h5") y = model.predict(x) print(y) y= model.predict_classes(x) print(y) print(RESULT[y[0][0]]) #train() predict(r"E:\nlp_data\aclImdb_v1\aclImdb\test\neg\0_2.txt") predict(r"E:\nlp_data\aclImdb_v1\aclImdb\test\pos\0_10.txt")
【八】结果对比与分析
本文主要参考keras example示例(https://github.com/keras-team/keras/blob/master/examples/imdb_cnn.py),该示例的imdb数据已经预处理好了。所以尝试重新对数据进行预处理,和keras示例相比,精度基本一致。
keras模型png图片如下:
也可以使用工具Netron(https://github.com/lutzroeder/Netron)打开keras保存的.h5格式的模型
Netron是个可视化模型的神器,可以可视化caffe/tensorflow/keras等模型
【九】可视化
上面提到了三种可视化,一是利用callback回调,记录单个epoch下逐个batch的loss等数据,然后绘制曲线图,或者利用history绘制多个epoch下的loss等变化曲线图,二是将模型保存为图片,三是利用Netron查看.h5模型。现在介绍第四种可视化方式,即 利用tensorboard来显示训练过程与模型参数
使用方式比较简单,给fit函数传递一个 keras.callbacks.TensorBoard 作为callback对象即可。 tensorboard = keras.callbacks.TensorBoard(log_dir="./logs/") train_history = model.fit(x=x_train, y=y_train, batch_size=128, epochs=1, validation_data=(x_test,y_test), callbacks=[tensorboard])
启动tensorboard(tensorboard --logdir=./logs/)之后,然后在浏览器输入:http://localhost:6006 ,即可看到各种信息
【十】关于其中的Embedding层
前面介绍过,可以使用word2vec或者fasttext或者gensim训练出词向量,而这里的Embedding好像也没有使用训练好的词向量啊?原因是这里的embedding也是参与训练的,他是整个流程的一部分。所以,embedding的参数解释如下: # 构造词嵌入层 # input_dim ----> 词典的最大词数目,即V # output_dim ---->词向量的维度大小,即m # input_length---->数据数据x的大小,即句子长度。也就是一个句子有多少个词。由于句子长度不一,这也是前面为什么需要 # 将句子截断或者填充 model.add(keras.layers.Embedding(input_dim=max_features,output_dim=word_embedding_size,name="embedding"))
那如果需要使用fasttext训练好的词向量,怎么办呢?其实这个好办,也就是一个fine-tuning的过程,不过针对上述网络而言,仅仅对embedding层进行fine-tuning。
分为如下三个步骤:
1. 获取预训练的词向量,将其解析出来,可以解析到一个map或者dict中,其中key=token,value=word vector。 V*M
2. 将训练的语料(如imdb)预处理后,通过查表方式,从上述map中得到对应词的向量,然后得到当前语料库的词向量(V1*M)。注意,这里词向量的size依旧为M,只是词典的大小换成了V1。如果当前语料库中的某个词不再预训练的词典中,则可以将该词的词向量随机初始化。
3. 将当前语料库的word embedding,填充到Embeeding layer的参数中。
代码如下:
这里以斯坦福大学通过glove训练好的word embedding为例
下载网址:https://nlp.stanford.edu/projects/glove/ # 初始化词典 embedding_matrix = np.zeros(shape=(V,m)) word_index = {} embedding_index = {} # 选择m=50的预训练数据,将预训练的词与vector提取到embedding_index中存储起来 with open("glove.6B.50d.txt") as f: for line in f: values = line.split() word = values[0] coefs = np.asarray(values[1:],dtype=np.float32) embedding_index[word] = coefs ''' x_train,t_train = imdb_load("train") token = text.Tokenizer(num_words=max_features) token.fit_on_texts(x_train) ''' # 获取当前语料(imdb)的词 word_index = token.word_index not_find = 0 for word,i in word_index.items(): if i < V: # 查预训练的词表 embedding_vec = embedding_index.get(word) if embedding_vec is not None: embedding_matrix[i] = embedding_vec else: not_find += 1 # 将权值设置到embedding layer中 model.layers[0].set_weigth([embedding_matrix]) # frozen embedding layer,也可以不冻结。不冻结的话就可以fine-tuning该层 model.layers[0].trainable = False

人工智能
2018-11-21 22:42:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 什么是机器学习,为什么学习机器学习,如何学习机器学习,这篇文章都告诉给你。
目前,人工智能(AI)非常热门,许多人都想一窥究竟。如果你对人工智能有所了解,但对机器学习(Machine Learning)的理解有很多的困惑,那么看完本文后你将会对此有进一步深入理解。在这里,不会详细介绍机器学习算法的基本原理,而是通过将比较有意思的视频(YouTube)和文字相结合,逐渐增加对机器学习基础的理解。
当看到本文时,请坐下来放松一下。因为下面的这些视频需要花费一点时间,但看完视频后,你肯定会被吸引继续阅读下去。此外,当阅读完本文后,你应该会对现在最热门的技术——机器学习有了全面的知识基础,并对此产生学习热情,最终能学到什么程度完全取决于个人的努力,本文只是一块敲门砖。
为什么机器学习现在如此热门
人工智能总是很酷,从科幻电影到现实中的阿法狗、聊天机器人等,一直吸引人们的关注。长久以来,人们认为人工智能一直围绕着程序员对某些事情应该如何表现的功能性猜测。然而,程序员并不总是像我们经常看到的那样对人工智能编程同样有着天赋。正如我们经常看到的那样,比如谷歌“史诗游戏失败”中在人工智能、物理、有时甚至是经验丰富的人类玩家中都存在有过失。
无论如何,人工智能有一种新的天赋——通过该项技术,我们可以教电脑玩游戏、理解语言、甚至识别人或物。这个只显露冰山一角的新技术来源一个旧的概念——机器学习,直到最近几年,它才获得了理论之外的处理能力,这源于数据量的爆炸、计算机性能的提升以及算法理论的突破。
通过人工智能这项技术,我们不再需要人为地提出高级算法,只需要教会计算机自己来提出高级算法即可。
那么这样的事情是如何实现的呢?机器学习算法并没有真正被类似于程序员编程那样进行编写,而是自动生成。观看下面这个简短的视频,该视频为创建人工智能的高级概念提供了出色的注释和动画。
https://www.youtube.com/watch?v=R9OHn5ZF4Uo
是不是一个很疯狂的处理过程?并且,当算法完成后,我们甚至无法理它,它就像一个黑匣子。比如,该项技术应用于视觉领域中是用人工智能玩马里奥游戏。作为一个人,我们都知道如何躲避障碍物和吃金币,但人工智能识别所产生的预测策略是疯狂的,见下面的视频:
https://youtu.be/qv6UVOQ0F44
是不是很吃惊?看完上述视频后,我们的问题是对机器学习不了解,并且不知道如何将它与电子游戏联系起来。
幸运的是, Elon Musk已经提供了一家非营利性公司来做后者 。通过十几行代码,你可以将人工智能与任何你想要玩的游戏/任务联系起来, 快去尝试一下吧 。
为什么要使用机器学习?
关于为什么要关心机器学习,这里有两个很好的答案。 首先,机器学习使计算机可以做到计算机以前不能实现的事情。如果你想尝试一些新事物,或者不仅仅是新事物,而是影响全世界,你都可以用机器学习来完成。 其次,如果你不影响世界,世界将影响你。
现在,很多大型公司在机器学习上投入了很多的研发和投资,我们已经看到它正在改变世界。思想领袖警告我们不能让这个新的算法时代存在于公众视线之外。想象一下,如果一些企业巨头控制着互联网,如果我们不掌握这项武器,科学的真理将不会被我们占据。 Christian Heilmann 在他关于机器学习的谈话中说得很好: “我们能够希望其他人善用这种力量。对于个人而言,不要认为这是一个好的赌注。我宁愿玩,也要参加这场科技革命,你也应该参与。”—— Chris Heilmann的机器学习谈话视频
对机器学习感兴趣
机器学习这个概念很有用而且很酷,上述内容让我们比较抽象地了解了它,但机器学习算法究竟发生了什么?它是如何运作的?我们还不是很清楚。
如果你想直接进入到理论研究,建议你跳过这一部分继续下一个“如何开始”部分。如果你有动力成为机器学习的实干者,那么就不需要看下面的视频了。如果你仍然试图了解机器学习可能是什么,下面的使用机器学习完成数字手写体识别的视频非常适合引导读者建立一种机器学习的逻辑:
https://youtu.be/aircAruvnKk
是不是很酷?该视频显示每个层变得更简单,而不是变得更复杂。就像函数将数据分解成较小的部分一样,以抽象的概念结束。你可以在该网站( Adam Harley )与此流程进行交互。
http://scs.ryerson.ca/~aharley/vis/conv/flat.html
此外,机器学习的经典实例之一是1936年的鸢尾花数据集。在参加 JavaFXpert 的机器学习概述的演示中,我学会了如何使用工具来可视化调整和反向传播神经网络上神经元的权重。可视化过程可以让我们看到它是如何训练神经模型。
使用 Jim可视化工具 训练鸢尾花神经网络
即使你不是一个Java爱好者,Jim提供了一个1.5小时的机器学习概念介绍也是比较有用的,其中包含上述许多例子的更多详细信息。
这些概念令人兴奋,你准备好成为这个新时代的爱因斯坦吗?机器学习算法每天都在发生突破,所以现在就开始吧。
至于如何进行深入学习,我推荐下面两种方法:
从头到尾拧完n颗螺栓
在这种方法中,将需要你全面了解机器学习算法和相关的数学知识。我知道,这种方式听起来很难完成,但要想真正地了解算法细节,就必须从头开始编码实现。
如果你想成为机器学习中的一员,并在核心圈中占据一席之地,那么这就是你的选择。我建议你试试一些公开课app(比如,course、Brilliant.org),并参加人工神经网络课程。经典的网络课程主推 Andrew Ng 老师的机器学习课程以及周志华老师的书籍等。
在学习的同时,可以完成对应的线下作业。通过完成对应的作业,会进一步加深对知识的理解,因为这些作业并不简单。但更重要的是,如果确实完成了这项工作,你将对机器学习的实施有进一步深刻的理解,这将使得你以新的和改变世界的方式成功地将其应用到对应的场景中。
快速上手
如果你对编写算法并不感兴趣,但仍想要使用它们来创建一个令人惊叹的网站/应用程序,你应该跳转到学习TensorFlow和对应的速成课程。
TensorFlow是用于机器学习的开源软件库。它可以以无数种方式使用,甚至可以使用 JavaScript ,这里是一个 速成课程 。 更多课程信息请点击此处查看 。
如果选修课程不适合你的学习方式,那你仍然是很幸运的。如今不必学习机器学习的细节就可以掌握如何使用它。此外,还可以通过多种方式有效地机器学习作为服务成为技术巨头。
数据是这项技术很重要的原材料,如果你的数据比较合适,那么使用机器学习建模可能是最佳解决方案。无论是使用机器学习中的哪一种算法,现在就开始吧。
成为创造者
我要对所有上述的人和视频说声谢谢,它们是我学习机器学习起步的灵感,虽然我在该领域仍然是个新手,但是当我们拥抱这个令人敬畏的时代时,我很高兴为他人指明一条学习道路。
如果你想学习这门技术,就必须与机器学习领域的研究者有所联系。没有友好的面孔、回答和讨论,任何事情都将变得很难。一般技术圈的人都是比较热心肠的,遇到问题先google,找不到答案就咨询圈内人,相信会有友好的同行给出友好的建议。
我希望这篇文章能激励你和周围的人学习机器学习,我也很乐意和你一起寻找酷炫有趣的机器学习代码,希望本文对你有所帮助。
原文链接
人工智能
2018-11-21 17:17:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
有一个业务是查询最新审核的5条数据 SELECT `id`, `title` FROM `th_content` WHERE `audit_time` < 1541984478 AND `status` = 'ONLINE' ORDER BY `audit_time` DESC, `id` DESC LIMIT 5;
查看当时的监控情况 cpu 使用率是超过了100%, show processlist 看到很多类似的查询都是处于 create sort index 的状态。
查看该表的结构 CREATE TABLE `th_content` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(500) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '内容标题', `content` mediumtext CHARACTER SET utf8 NOT NULL COMMENT '正文内容', `audit_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '审核时间', `last_edit_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最近编辑时间', `status` enum('CREATED','CHECKING','IGNORED','ONLINE','OFFLINE') CHARACTER SET utf8 NOT NULL DEFAULT 'CREATED' COMMENT '资讯状态', PRIMARY KEY (`id`), KEY `idx_at_let` (`audit_time`,`last_edit_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
索引有一个 audit_time 在左边的联合索引,没有关于 status 的索引。
分析上面的sql执行的逻辑: 从联合索引里找到所有小于该审核时间的主键id(假如在该时间戳之前已经审核了100万条数据,则会在联合索引里取出对应的100万条数据的主键 id) 对这100万个 id 进行排序(为的是在下面一步回表操作中优化 I/O 操作,因为很多挨得近的主键可能一次磁盘 I/O 就都取到了) 回表,查出100万行记录,然后逐个扫描,筛选出 status='ONLINE' 的行记录 最后对查询的结果进行排序(假如有50万行都是ONLINE,则继续对这50万行进行排序)
最后因为数据量很大,虽然只取5行,但是按照我们刚刚举的极端例子,实际查询了100万行数据,而且最后还在内存中进行了50万行数据库的内存排序。
所以是非常低效的。
画了一个示意图,说明第一步的查询过程, 粉红色部分 表示最后需要回表查询的数据行。
图中我按照索引存储规律来YY伪造填充了一些数据,如有不对请留言指出。 希望通过这张图大家能够看到联合索引存储的方式和索引查询的方式
改进思路 1
范围查找向来不太好使用好索引的,如果我们增加一个 audit_time , status 的联合索引,会有哪些改进呢? ALTER TABLE `th_content` ADD INDEX `idx_audit_status` (`audit_time`, `status`); mysql> explain select `id`, `title` from `th_content` where `audit_time` < 1541984478 and `status` = 'ONLINE' order by `audit_time` desc, `id` desc limit 5; +----+-------------+------------+-------+------------------------------------------+------------------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+------------------------------------------+------------------+---------+------+--------+-------------+ | 1 | SIMPLE | th_content | range | idx_at_ft_pt_let,idx_audit_status | idx_audit_status | 4 | NULL | 209754 | Using where | +----+-------------+------------+-------+------------------------------------------+------------------+---------+------+--------+-------------+ 细节:因为 audit_time 是一个范围查找,所以第二列的索引用不上了,只能用到 audit_time ,所以 key_len 是4。而下面 思路2 中,还是这两个字段 key_len 则是5。
还是分析下在添加了该索引之后的执行过程: 从联合索引里找到小于该审核时间的 audit_time 最大的一行的联合索引 然后依次往下找,因为 < audit_time 是一个范围查找,而第二列索引的值是分散的。所以需要依次往前查找,匹配出满足条件( status ='ONLINE')的索引行,直到取到第5行为止。 回表查询需要的具体数据
在上面的示意图中,粉红色标识满足第一列索引要求的行,依次向前查询,本个叶子节点上筛选到了3条记录,然后需要继续向左,到前一个叶子节点继续查询。直到找到5条满足记录的行,最后回表。
改进之处
因为在索引里面有 status 的值,所以在筛选满足 status ='ONLINE'行的时候,就不用回表查询了。在回表的时候只有5行数据的查询了,在 iops 上会大大减少。
该索引的弊端
如果 idx_audit_status 里扫描5行都是 status 是 ONLINE ,那么只需扫描5行;
如果 idx_audit_status 里扫描前100万行中,只有4行 status 是 ONLINE ,则需要扫描100万零1行,才能得到需要的5行记录。 索引需要扫描的行数不确定 。
改进思路 2 ALTER TABLE `th_content` DROP INDEX `idx_audit_status`; ALTER TABLE `th_content` ADD INDEX `idx_status_audit` (`status`, `audit_time`);
这样不管是排序还是回表都毫无压力啦。
原文链接
本文为云栖社区原创内容,未经允许不得转载。
人工智能
2018-11-21 17:09:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
http://www.learn4master.com/big-data/pyspark/run-pyspark-on-oozie
使用Oozie在YARN上安排PySpark程序 考虑一个用Spark Python API编写的简单字数统计应用程序。以下步骤说明如何使用Oozie在YARN上安排和启动此PySpark作业。完整的程序列表显示在该部分的末尾。
首先,这里有一些关于在多节点集群上运行带有纱线集群模式的PySpark的先决条件的注释:
提交Spark作业时,Spark代码会检查PYSPARK_ARCHIVES_PATH环境变量。如果找不到PYSPARK_ARCHIVES_PATH,Spark会查找SPARK_HOME。您可以使用oozie.launcher.yarn.app.mapreduce.am.env属性设置PYSPARK_ARCHIVES_PATH。 py4j-0.10.4-src.zip和pyspark.zip文件(版本可能因Spark版本而异)是在Spark中运行Python脚本所必需的。因此,在脚本运行时,两个文件都必须存在于类路径中。只需将它们放在工作流的lib /目录下即可。 必须配置-py-files选项并在选项中传递。 创建工作流定义(workflow.xml)。以下简单工作流定义执行一个Spark作业: oozie.launcher.yarn.app.mapreduce.am.env PYSPARK_ARCHIVES_PATH=pyspark.zip ${jobTracker} ${nameNode} ${master} MyApp ${nameNode}/user/${wf:user()}/${examplesRoot}/MyPython.py --conf spark.driver.extraJavaOptions=-Diop.version=4.2.5.0 --conf spark.yarn.archive= hdfs://nn:8020/iop/apps/4.2.5.0-0000/spark2/spark2-iop-yarn-archive.tar.gz --py-files pyspark.zip,py4j-0.10.4-src.zip ${nameNode}/user/${wf:user()}/${examplesRoot}/input-data ${nameNode}/user/${wf:user()}/${examplesRoot}/output-data Workflow failed, error message[${wf:errorMessage(wf:lastErrorNode())}] 创建Oozie作业配置(job.properties)。 nameNode= hdfs://nn:8020 jobTracker=rm:8050 master=yarn-cluster queueName=default examplesRoot=spark-example oozie.use.system.libpath=true oozie.wf.application.path=${nameNode}/user/${user.name}/${examplesRoot} 创建一个Oozie应用程序目录。使用工作流定义和资源创建应用程序目录结构,如以下示例所示: +-~/spark-example/ +-job.properties +-workflow.xml +-MyPython.py +-WordCount.txt +-lib 将应用程序复制到HDFS。将spark-example /目录复制到HDFS中的用户HOME目录。确保HDFS中的spark-example位置与job.properties中的oozie.wf.application.path值相匹配。 $ hadoop fs -put spark-example /user/ambari-qa/ 将py4j-0.10.4-src.zip和pyspark.zip复制到HDFS。 $ hadoop fs -put /usr/iop/current/spark-client/python/lib/pyspark.zip /user/ambari-qa/spark-example/lib $ hadoop fs -put /usr/iop/current/spark-client/python/lib/py4j-0.10.4-src.zip /user/ambari-qa/spark-example/lib 运行示例作业。 通过运行以下命令提交Oozie作业: $ cd ~/spark-example
$ oozie job -oozie http://oozie-host:11000/oozie -config ./job.properties –run job: 0000031-161115185001062-oozie-oozi-W 检查工作流作业状态: $ oozie job -oozie http://oozie-host:11000/oozie -info 0000031-161115185001062-oozie-oozi-W
Job ID : 0000031-161115185001062-oozie-oozi-W
Workflow Name : PySpark App Path : hdfs://oozie-host:8020/user/ambari-qa/spark-example Status : SUCCEEDED Run : 0 User : ambari-qa Group : - Created : 2016-11-16 08:21 GMT Started : 2016-11-16 08:21 GMT Last Modified : 2016-11-16 08:22 GMT Ended : 2016-11-16 08:22 GMT CoordAction ID: -
Actions
ID Status Ext ID Ext Status Err Code
0000031-161115185001062-oozie-oozi-W@:start: OK - OK -
0000031-161115185001062-oozie-oozi-W@spark-node OK job_1479264601071_0068 SUCCEEDED -
0000031-161115185001062-oozie-oozi-W @end OK - OK -
完整的PySpark计划 from pyspark import SparkConf, SparkContext from operator import add def main(): conf = SparkConf().setAppName("MyApp") sc = SparkContext(conf=conf) lines = sc.textFile("/user/ambari-qa/spark-example/WordCount.txt") words = lines.flatMap(lambda line: line.split(' ')) wc = words.map(lambda x:(x,1)) counts = wc.reduceByKey(add) counts.saveAsTextFile("wcres12") if name == ' main ': main()
人工智能
2018-11-21 15:40:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
神经网络定义
人工神经网络,简称神经网络,在机器学习和认知科学领域,是一种模仿生物神经网络(动物的中枢神经系统,特别是大脑)的结构和功能的数学模型或计算模型,用于对函数进行估计或近似。
为了描述神经网络,我们先从最简单的神经网络讲起,这个神经网络仅由一个“神经元”构成,以下即是这个“神经元”的图示:

可以看出,这个单一“神经元”的输入-输出映射关系其实就是一个逻辑回归(logistic regression)。
神经网络模型
所谓神经网络就是将许多个单一“神经元”联结在一起,这样,一个“神经元”的输出就可以是另一个“神经元”的输入。例如,下图就是一个简单的神经网络:

Keras实战
使用keras实现如下网络结构, 并训练模型:
输入值(x1,x2,x3)代表人的身高体重和年龄, 输出值(y1,y2) importnumpyasnp # 总人数是1000, 一半是男生 n =1000 # 所有的身体指标数据都是标准化数据, 平均值0, 标准差1 tizhong = np.random.normal(size = n) shengao = np.random.normal(size=n) nianling = np.random.normal(size=n) # 性别数据, 前500名学生是男生, 用数字1表示 gender = np.zeros(n) gender[:500] =1 # 男生的体重比较重,所以让男生的体重+1 tizhong[:500] +=1 # 男生的身高比较高, 所以让男生的升高 + 1 shengao[:500] +=1 # 男生的年龄偏小, 所以让男生年龄降低 1 nianling[:500] -=1
创建模型 fromkerasimportSequential fromkeras.layersimportDense, Activation model = Sequential() # 只有一个神经元, 三个输入数值 model.add(Dense(4, input_dim=3, kernel_initializer='random_normal', name="Dense1")) # 激活函数使用softmax model.add(Activation('relu', name="hidden")) # 添加输出层 model.add(Dense(2, input_dim=4, kernel_initializer='random_normal', name="Dense2")) # 激活函数使用softmax model.add(Activation('softmax', name="output"))
编译模型
需要指定优化器和损失函数: model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
训练模型 # 转换成one-hot格式 fromkerasimportutils gender_one_hot = utils.to_categorical(gender, num_classes=2) # 身体指标都放入一个矩阵data data = np.array([tizhong, shengao, nianling]).T # 训练模型 model.fit(data, gender_one_hot, epochs=10, batch_size=8) 输出(stream): Epoch1/10 1000/1000[==============================] -0s235us/step - loss:0.6743- acc:0.7180 Epoch2/10 1000/1000[==============================] -0s86us/step - loss:0.6162- acc:0.7310 Epoch3/10 1000/1000[==============================] -0s88us/step - loss:0.5592- acc:0.7570 Epoch4/10 1000/1000[==============================] -0s87us/step - loss:0.5162- acc:0.7680 Epoch5/10 1000/1000[==============================] -0s89us/step - loss:0.4867- acc:0.7770 Epoch6/10 1000/1000[==============================] -0s88us/step - loss:0.4663- acc:0.7830 Epoch7/10 1000/1000[==============================] -0s87us/step - loss:0.4539- acc:0.7890 Epoch8/10 1000/1000[==============================] -0s86us/step - loss:0.4469- acc:0.7920 Epoch9/10 1000/1000[==============================] -0s88us/step - loss:0.4431- acc:0.7940 Epoch10/10 1000/1000[==============================] -0s88us/step - loss:0.4407- acc:0.7900 输出(plain)://Python学习开发705673780
进行预测 test_data = np.array([[0,0,0]]) probability = model.predict(test_data) ifprobability[0,0]>0.5: print('女生') else: print('男生') ### 输出(stream): 女生
关键词解释
input_dim: 输入的维度数
kernel_initializer: 数值初始化方法, 通常是正太分布
batch_size: 一次训练中, 样本数据被分割成多个小份, 每一小份包含的样本数叫做batch_size
epochs: 如果说将所有数据训练一次叫做一轮的话。epochs决定了总共进行几轮训练。
optimizer: 优化器, 可以理解为求梯度的方法
loss: 损失函数, 可以理解为用于衡量估计值和观察值之间的差距, 差距越小, loss越小
metrics: 类似loss, 只是metrics不参与梯度计算, 只是一个衡量算法准确性的指标, 分类模型就用accuracy

看完觉得有所收获的朋友可以点赞加关注哦,谢谢支持!
人工智能
2018-11-21 15:14:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
还记得去年双11,秋裤厂商带着“五彩斑斓的黑”需求找设计师的故事吗?
现在,已经有超过20万客户把这个AI设计师鹿班带回家。
今年,鹿班除了为天猫淘宝平台提供服务之外,还通过阿里云全面为各行业客户输出AI设计能力。2018天猫双11期间,已累计为20万企业、商家设计近600万张图片,你打开天猫看到的各种海报和活动商品图片几乎都出自鹿班。
某淘宝商家表示,“我们有400多个产品,仅靠自己无法完成这么多图片,所以过去在双11这样的重要活动节点,图片设计环节基本都是放弃的。但今年双11,鹿班完成了所有图片的设计。”
据了解,鹿班已经达到了高级设计师的水准,每秒可以提供几十种方案,每秒设计8000张不同的海报……当然,这些能力都要归功于背后的人工智能技术。
● 设计 知识图谱:鹿班的知识图谱是图片效果的数据底座
鹿班将各类成图设计,通过内容、视觉、结构几大维度进行标注和分析,建立对于构图、配色、搭配和文案几个方面在设计元素级别的认知。通过对千万套营销设计图片的全方位分析,建立了国内最大的营销类图片的知识图谱库。
在知识图谱的底层构建过程中,利用无向图进行建模,利用关系型数据库和NoSQL数据库混合搭建存储设施。通过数据同步,将经过后处理的数据,同步到算法依赖的ES索引上。
● 智能设计深度学习算法:基于图像的人工智能深度学习算法是鹿班的大脑
基于深度学习的智能设计算法,鹿班改变了传统的图片设计流程。用户可以仅通过输入想达成的设计风格和图片尺寸,即可由鹿班代替人工完成素材分析、抠图、配色、排版等耗时耗力的设计工作。
人工智能
2018-11-21 14:29:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
2018天猫双11已经过去一周,各路快递也在快马加鞭送到大家手中。但对于剁手党而言,天猫双11也不仅仅是简单意义上的“买买买”,更是一场边看边玩的狂欢盛宴。
作为双11的必备节目,今年的猫晚通过优酷、浙江卫视、东方卫视进行了全程网络直播和电视直播,吸引了超过全球超过2.4亿人收看。猫晚期间,优酷基于 阿里云最新的广播级高可靠直播方案, 为近2500万的观众带来了超 高清、流畅的观看体验 。
大家一定还记得今年俄罗斯世界杯期间,阿里云承包了全网70%的直播流量,据相关技术负责人介绍,本次猫晚直播解决方案带来了全新的能力,例如首次引入了4K以及视频智能生产技术,观看体验得到了进一步提升。
从上行链路、云端内容生产到极速分发的全链路支持
阿里云视频云直播服务为今年猫晚直播提供了从上行,云端内容再生产到分发的全链路技术支持和保障,整套直播系统达到广播级的稳定性:
l ● 上行链路采用多网接入和视频流合并功能,确保上行链路的高可用;
l ● 视频处理中心采用异地多中心多机房部署,即一个直播频道同时通过多个区域的直播集群进行处理,一个区域出现任何故障,可快速切换至新区域避免中断,确保视频处理环节的高可用;
l ● 采用阿里云 CDN进行全球直播分发,受益于CDN系统针对突发活动的流量预测和实时调度系统,保证视频播放的流畅。
人工智能
2018-11-21 14:28:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
云网融合大趋势下,云网络逐渐成为企业构建网络的首选方案。11月16日,在刚刚结束的全球网络技术大会GNTC 2018上,阿里云凭借灵活多变的全球混合云网络解决方案斩获GNTC Innovation Award创新大奖,成为首家唯一入选该奖项的云服务商。
GNTC是全球规模最大的网络届盛会之一,由国家下一代互联网工程中心主办,旨在全面推动全球网络重构发展进程,大会颁发的GNTC Award也被誉为网络技术领域的“奥斯卡”。
安全、可靠、灵活的网络能力是阿里云给评委会留下的深刻印象。本次大会上,阿里云展示了最新的企业级混合云网络解决方案,可分钟级构建跨地域全连接的混合云网络,消除企业信息孤岛,具备方式灵活、全互联、免配置等特点,为各行各业的企业机构提供云下(IDC/总部/分支)到云上安全、可靠、灵活的网络连接。GNTC首次将Innovation Award大奖颁发给传统网络运营商以外的云服务商,是对云网融合技术发展趋势的肯定和鼓励。
Gartner预测,到2020年将会有90%的企业采用混合云。来自另一家咨询公司Mordor Intelligence的报告指出,全球混合云市场将持续以22.7%的年复合增长率高速增长,到2023年达到1380亿美元的庞大规模。
人工智能
2018-11-21 14:27:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
背景
在之前的 文章 中,我们已经提到过团队在UI自动化这方面的尝试,我们的目标是实现基于 单一图片到代码 的转换,在这个过程不可避免会遇到一个问题,就是为了从单一图片中提取出足够的有意义的结构信息,我们必须要拥有从图片中切割出想要区块(文字、按钮、商品图片等)的能力,而传统切割算法遇到复杂背景图片往往就捉襟见肘了(见下图),这个时候,我们就需要有能力把复杂前后景的图片划分为各个层级图层,再交给切割算法去处理,拿到我们期望的结构信息。
经过传统切割算法处理,会无法获取图片结构信息,最终只会当成一张图片处理。
在业界,图片前后景分离一直是个很麻烦的命题,业界目前比较普遍采用的解决方案是计算机视觉算法提取,或是引入人工智能来解决,但直到现在,都没有百分百完美的解决方案。那是否能引入AI来解决这个问题呢,我们来看一下,目前使用AI并拿到比较不错结果的解法是fcn+crf,基本上能够把目标物体的前景轮廓框出来,但缺点也很明显: 准确率只有80%左右 边缘切割无法达到像素级别 打标成本非常大 难以训练 AI是个黑盒,结果不可控
在考虑到使用AI伴随的问题之外,咱们也一起来思考下,难道AI真的是解决前后景分离的最佳解法吗?
其实不是的,我们知道,一个页面,或者说设计稿,一个有意义的前景,是具有比较明显特征的,比如说: 规则的形状:线段、矩形、圆形、圆角、是否对称等 形状上是否有文字,或者说是类似于文字的信息 是否闭合
让我们一起来验证下这个思路的可行性。
实践结果
在尝试了非常的多计算机视觉算法之后,你会发现,没有一种算法是能够解决掉这个问题的,基本上是可能一种算法,在某种场景下是有效的,到了另外一个场景,就又失效了,而且就算是有效的场景,不同颜色复杂度下,所需要的最佳算法参数又是不相同的。如果case by case来解决的话,可以预期未来的工程会变得越来越冗杂且不好维护。
那是不是可以这样呢,找到尽可能多的前景区域,加一层过滤器过滤掉前景可能性低的,再加一层层级分配器,对搜索到的全部前景进行前后层级划分,最后对图像进行修复,填补空白后景。
咱们先来看看效果,以下查找前景的过程:
为了避免有的前景被忽略(图片大部分是有多层的,前景里面还会嵌套前景),所以一个前景被检测到之后不会去隐藏它,导致会出现一个前景被多次检测到的情况,不过这块加一层层级分配算法就能解决了,最终得到出来的分离结果如下:
逻辑概要
文字处理
OCR获取文字粗略位置
来看看例子,以下左图是闲鱼首页,右图是基于OCR给出的文字位置信息对文字区域进行标记(图中白色部分),可以看到,大致上位置是准确的 但比较粗糙 无法精确到每个文字本身 而且同一行的不同文字片段 OCR会当成一行去处理。
同时,也会有部分非文字的部分 也被当成文字,比如图中的banner文案:
切割、CNN鉴别器
对以上结果标注的位置进行切割,切割出尽可能小的单个文字区域,交给CNN判断,该文字是否是可编辑的文字,还是属于图片文案,后者将当作图片进行处理,以下是CNN代码: """ ui基础元素识别 """ # TODO 加载模型 with ui_sess.as_default(): with g2.as_default(): tf.global_variables_initializer().run() # Loads label file, strips off carriage return ui_label_lines = [line.rstrip() for line in tf.gfile.GFile("AI_models/CNN/ui-elements-NN/tf_files/retrained_labels.txt")] # Unpersists graph from file with tf.gfile.FastGFile("AI_models/CNN/ui-elements-NN/tf_files/retrained_graph.pb", 'rb') as f: ui_graph_def = tf.GraphDef() ui_graph_def.ParseFromString(f.read()) tf.import_graph_def(ui_graph_def, name='') # Feed the image_data as input to the graph and get first prediction ui_softmax_tensor = ui_sess.graph.get_tensor_by_name('final_result:0') # TODO 调用模型 with ui_sess.as_default(): with ui_sess.graph.as_default(): # UI原子级元素识别 def ui_classify(image_path): # Read the image_data image_data = tf.gfile.FastGFile(image_path, 'rb').read() predictions = ui_sess.run(ui_softmax_tensor, {'DecodeJpeg/contents:0': image_data}) # Sort to show labels of first prediction in order of confidence top_k = predictions[0].argsort()[-len(predictions[0]):][::-1] for node_id in top_k: human_string = ui_label_lines[node_id] score = predictions[0][node_id] print('%s (score = %s)' % (human_string, score)) return human_string, score
文字抽离
如果是纯色背景,文字区域很好抽离,但如果是复杂背景就比较麻烦了。举个例子:
基于以上,我们能拿到准确的文本信息,我们逐一对各个文本信息做处理,文本的特征还是比较明显的,比如说含有多个角点,在尝试了多种算法:Harris角点检测、Canny边缘检测、SWT算法,KNN算法(把区域色块分成两部分)之后,发现KNN的效果是最好的。代码如下: Z = gray_region.reshape((-1,1)) Z = np.float32(Z) criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) ret,label,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS) center = np.uint8(center) res = center[label.flatten()] res2 = res.reshape((gray_region.shape))
抽离后结果如下:
查找前景
强化图片边缘,弱化非边缘区域
使用卷积核对原图进行卷积,该卷积核可以强化边缘,图像平滑区域会被隐藏。 conv_kernel = [ [-1, -1, -1], [-1, 8, -1], [-1, -1, -1] ]
卷积后,位与操作隐藏文字区域,结果如下:
降噪
对卷积后的图,加一层降噪处理,首先把图像转为灰度图,接着二值化,小于10像素值的噪点将被隐藏,最后使用cv2.connectedComponentsWithStats()算法消除小的噪点连通区域。
基于文字位置,开始查找轮廓
我们基于前面拿到的文字信息,选中文字左上角坐标,以这个点为种子点执行漫水填充算法,之后我们会得到一个区域,我们用cv2.findContours()来获取这个区域的外部轮廓,对轮廓进行鉴别,是否符合有效前景的特征,之后对区域取反,重新执行cv2.findContours()获取轮廓,并鉴别。
判断内外部轮廓
如果文字在轮廓内部,那拿到的区域将不会包含该区域的border边框,如果文字在轮廓外部,就能拿到包含边框的一整个有效区域(边框应该隶属于前景),所以咱们要判断文字和轮廓的位置关系(cv2.pointPolygonTest),如果在内部,会使轮廓往外扩散,知道拿到该轮廓的边框信息为止。
前景鉴别器
基于前面的步骤,我们会拿到非常多非常多的轮廓,其实绝大部分是无效轮廓以及重复检测到的轮廓,咱们需要加一层鉴别器来对这些轮廓进行过滤,来判断它是否是有效前景。
定义有效shape
我们会预先定义我们认为有意义的形状shape,比如说矩形、正方形、圆形,只要检测到的轮廓与这三个的相似度达到了设定的阀值要求,并且轮廓中还包含了文字信息,我们就认为这是一个有意义的前景,见代码: # TODO circle circle = cv2.imread(os.getcwd()+'/fgbgIsolation/utils/shapes/circle.png', 0) _, contours, _ = cv2.findContours(circle, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) self.circle = contours[0] # TODO square square = cv2.imread(os.getcwd()+'/fgbgIsolation/utils/shapes/square.png', 0) _, contours, _ = cv2.findContours(square, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) self.square = contours[0] # TODO rect rect = cv2.imread(os.getcwd()+'/fgbgIsolation/utils/shapes/rect.png', 0) _, contours, _ = cv2.findContours(rect, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) self.rect = contours[0]
匹配shape相似度
多次尝试之后 发现score设置为3的效果是最好的。代码如下: # TODO 检测图形相似度 def detect(self, cnt): shape = "unidentified" types = [self.square, self.rect, self.circle] names = ['square', 'rect', 'circle'] for i in range(len(types)): type = types[i] score = cv2.matchShapes(type, cnt, 1, 0.0) # score越小越相似 # TODO 一般小于3是有意义的 if score<3: shape = names[i] break return shape, score
单一匹配shape相似度的鲁棒性还是不够健壮,所以还引入了其他过滤逻辑,这里不展开。
图像修复
可以预见的,我们传入的图片只有一张,但我们划分图层之后,底层的图层肯定会出现“空白”区域,我们需要对这些区域进行修复。
计算重叠区域
需要修复的区域只在于重叠(重叠可以是多层的)的部分,其他部分我们不应该去修复。计算重叠区域的解决方案沿用了mask遮罩的思路,我们只需要计算当前层有效区域和当前层之上层有效区域的交集即可,使用cv2.bitwise_and # mask是当前层的mask layers_merge是集合了所有前景的集合 i代表当前层的层级数 # inpaint_mask 是要修复的区域遮罩 # TODO 寻找重叠关系 UPPER_level_mask = np.zeros(mask.shape, np.uint8) # 顶层的前景 UPPER_level_mask = np.where(layers_merge>i, 255, 0) UPPER_level_mask = UPPER_level_mask.astype(np.uint8) _, contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 查找当前层的每个前景外轮廓 overlaps_mask = np.zeros(mask.shape, np.uint8) # 当前层的所有前景的重叠区域 for cnt in contours: cnt_mask = np.zeros(mask.shape, np.uint8) cv2.drawContours(cnt_mask, [cnt], 0, (255, 255, 255), cv2.FILLED, cv2.LINE_AA) overlap_mask = cv2.bitwise_and(inpaint_mask, cnt_mask, mask=UPPER_level_mask) overlaps_mask = cv2.bitwise_or(overlaps_mask, overlap_mask) # TODO 将当前层重叠区域的mask赋值给修复mask inpaint_mask = overlaps_mask
修复
使用修复算法cv2.INPAINT_TELEA,算法思路是:先处理待修复区域边缘上的像素点,然后层层向内推进,直到修复完所有的像素点。 # img是要修复的图像 inpaint_mask是上面提到的遮罩 dst是修复好的图像 dst = cv2.inpaint(img, inpaint_mask, 3, cv2.INPAINT_TELEA)
延展
本文大概介绍了通过计算机视觉为主,深度学习为辅的图片复杂前后景分离的解决方案,除了文中提到的部分,还有几层轮廓捕获的逻辑因为篇幅原因,未加展开,针对比较复杂的case,本方案已经能够很好的实现图层分离,但对于更加复杂的场景,比如边缘颜色复杂度高,噪点多,边缘轮廓不明显等更复杂的case,分离的精确度还有很大的提升空间。
原文链接
人工智能
2018-11-21 12:04:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
一. 概述
首先需要先介绍一下无监督学习,所谓无监督学习,就是训练样本中的标记信息是位置的,目标是通过对无标记训练样本的学习来揭示数据的内在性质以及规律。通俗得说,就是根据数据的一些内在性质,找出其内在的规律。而这一类算法,应用最为广泛的就是“聚类”。
聚类算法可以对数据进行数据归约,即在尽可能保证数据完整的前提下,减少数据的量级,以便后续处理。也可以对聚类数据结果直接应用或分析。
而Kmeans 算法可以说是聚类算法里面较为基础的一种算法。
二. 从样例开始
我们现在在二维平面上有这样一些点 x y 1.658985 4.285136 -3.453687 3.424321 4.838138 -1.151539 -5.379713 -3.362104 0.972564 2.924086 -3.567919 1.531611 0.450614 -3.302219 -3.487105 -1.724432 2.668759 1.594842 -3.156485 3.191137 3.165506 -3.999838 -2.786837 -3.099354 4.208187 2.984927 -2.123337 2.943366 0.704199 -0.479481 -0.392370 -3.963704 2.831667 1.574018 -0.790153 3.343144 2.943496 -3.357075 ... 它在二维平面上的分布大概是这样的:
好,这些点看起来隐约分成4个“簇”,那么我们可以假定它就是要分成4个“簇”。(虽然我们可以“看”出来是要分成4个“簇”,但实际上也可以分成其他个,比如说5个。)这里分成“4个簇“是我们看出来的。而在实际应用中其实应该由机器算得,下面也会有介绍的。
找出4个”簇”之后,就要找出每个“簇”的中心了,我们可以“看出”大概的中心点,但机器不知道啊。那么机器是如何知道的呢?答案是通过向量距离,也叫向量相似性。这个相似性计算有多种方法,比如欧式距离,曼哈顿距离,切比雪夫距离等等。
我们这里使用的是欧式距离,欧式距离其实就是反应空间中两点的直线距离。
知道这些后,我们就可以开始让机器计算出4个“簇”了。
主要做法是这样,先随机生成4个点,假设这4个点就是4个“簇”的中心。计算平面中每个点到4个中心点的距离,平面中每个点选取距离最近的那个中心作为自己的中心。
此时我们就完成第一步,将平面中所有点分成4个”簇“。但是刚刚那几个中心都是随机的,这样分成的4个簇明显不是我们想要的结果。怎么办呢?做法如下:
现在有4个簇,根据每个簇中所有点计算出每个簇的新中心点。这个新中心点就会比上一个旧的中心点更优,因为它更加中心。然后使用新中心点重复第一步的步骤。即再对平面中所有点算距离,然后分发到4个新簇中。不断迭代,直到误差较小。
这就是 Kmeans 算法的过程了。
三. 知识点浅析
3.1 确定“簇”的个数
上面所说的分成 4 个簇,这个 4 其实就是 Kmeans 中的K。要使用 Kmeans 首先就是要选取一个 K 作为聚类个数。而上面的例子其实是我们主观”看“出来的,但多数情况下我们是无法直观”看“出分多少个 K 比较好。那怎么办呢?
我们可以从较低的 K 值开始。使用较简单的 Kmeans 算法的结果(即较少的迭代次数,不求最佳结果,但求最快)。计算每个点到其归属的“簇”的中心点的距离,然后求和,求和结果就是误差值。 然后再增加 K 值,再计算误差值。比如上面的例子,我们可以从 K=2 开始,计算 K 值从 2 到 7 的 Kmeans 算法的误差值。 这样会得到类似这样一张图:
里面的 Error 可以理解未 Kmeans 的误差,而当分成越多“簇”的适合,误差肯定是越来越小。
但是不是“簇”越多越好呢?答案是否定的,有时候“簇”过多的话是不利于我们得到想要的结果或是做下一步操作的。
所以我们通常会选择误差减小速度比较平缓的那个临界点,比如上图中的 4 。
可以发现,在分成 4 个簇之后,再增加簇的数量,误差也不会有很大的减少。而取 4 个簇也和我们所看到的相符。
3.2 欧式距离
3.2 欧式距离
欧氏距离是一个通常采用的距离定义,指在m维空间中两个点之间的真实距离,计算公式如下:
而本例种的是在二维空间种,故而本例的计算公式如下:
四. 代码和结果
加载数据的代码,使用了 numpy ,先是将代码加载成 matrix 类型。 import numpy as np def loadDataSet(fileName): ''' 加载数据集 :param fileName: :return: ''' # 初始化一个空列表 dataSet = [] # 读取文件 fr = open(fileName) # 循环遍历文件所有行 for line in fr.readlines(): # 切割每一行的数据 curLine = line.strip().split('\t') # 将数据转换为浮点类型,便于后面的计算 # fltLine = [float(x) for x in curLine] # 将数据追加到dataMat fltLine = list(map(float,curLine)) # 映射所有的元素为 float(浮点数)类型 dataSet.append(fltLine) # 返回dataMat return np.matrix(dataSet) 接下来需要生成 K 个初始的质点,即中心点。这里采用随机生成的方法生成 k 个“簇”。 def randCent(dataMat, k): ''' 为给定数据集构建一个包含K个随机质心的集合, 随机质心必须要在整个数据集的边界之内,这可以通过找到数据集每一维的最小和最大值来完成 然后生成0到1.0之间的随机数并通过取值范围和最小值,以便确保随机点在数据的边界之内 :param dataMat: :param k: :return: ''' # 获取样本数与特征值 m, n = np.shape(dataMat) # 初始化质心,创建(k,n)个以零填充的矩阵 centroids = np.mat(np.zeros((k, n))) # 循环遍历特征值 for j in range(n): # 计算每一列的最小值 minJ = min(dataMat[:, j]) # 计算每一列的范围值 rangeJ = float(max(dataMat[:, j]) - minJ) # 计算每一列的质心,并将值赋给centroids centroids[:, j] = np.mat(minJ + rangeJ * np.random.rand(k, 1)) # 返回质心 return centroids 欧式距离计算 def distEclud(vecA, vecB): ''' 欧氏距离计算函数 :param vecA: :param vecB: :return: ''' return np.sqrt(sum(np.power(vecA - vecB, 2))) cost 方法将执行一个简化的 kMeans ,即较少次数的迭代,计算出其中的误差(即当前点到簇质心的距离,后面会使用该误差来评价聚类的效果) def cost(dataMat, k, distMeas=distEclud, createCent=randCent,iterNum=300): ''' 计算误差的多少,通过这个方法来确定 k 为多少比较合适,这个其实就是一个简化版的 kMeans :param dataMat: 数据集 :param k: 簇的数目 :param distMeans: 计算距离 :param createCent: 创建初始质心 :param iterNum:默认迭代次数 :return: ''' # 获取样本数和特征数 m, n = np.shape(dataMat) # 初始化一个矩阵来存储每个点的簇分配结果 # clusterAssment包含两个列:一列记录簇索引值,第二列存储误差(误差是指当前点到簇质心的距离,后面会使用该误差来评价聚类的效果) clusterAssment = np.mat(np.zeros((m, 2))) # 创建质心,随机K个质心 centroids = createCent(dataMat, k) clusterChanged = True while iterNum > 0: clusterChanged = False # 遍历所有数据找到距离每个点最近的质心, # 可以通过对每个点遍历所有质心并计算点到每个质心的距离来完成 for i in range(m): minDist = np.inf minIndex = -1 for j in range(k): # 计算数据点到质心的距离 # 计算距离是使用distMeas参数给出的距离公式,默认距离函数是distEclud distJI = distMeas(centroids[j, :], dataMat[i, :]) # print(distJI) # 如果距离比minDist(最小距离)还小,更新minDist(最小距离)和最小质心的index(索引) if distJI < minDist: minDist = distJI minIndex = j # 更新簇分配结果为最小质心的index(索引),minDist(最小距离)的平方 clusterAssment[i, :] = minIndex, minDist ** 2 iterNum -= 1; # print(centroids) # 遍历所有质心并更新它们的取值 for cent in range(k): # 通过数据过滤来获得给定簇的所有点 ptsInClust = dataMat[np.nonzero(clusterAssment[:, 0].A == cent)[0]] # 计算所有点的均值,axis=0表示沿矩阵的列方向进行均值计算 centroids[cent, :] = np.mean(ptsInClust, axis=0) # 返回给定迭代次数后误差的值 return np.mat(clusterAssment[:,1].sum(0))[0,0]
最后可以调用 Kmeans 算法来进行计算。
def kMeans(dataMat, k, distMeas=distEclud, createCent=randCent):
'''
创建K个质心,然后将每个店分配到最近的质心,再重新计算质心。
这个过程重复数次,直到数据点的簇分配结果不再改变为止
:param dataMat: 数据集
:param k: 簇的数目
:param distMeans: 计算距离
:param createCent: 创建初始质心
:return:
'''
# 获取样本数和特征数
m, n = np.shape(dataMat)
# 初始化一个矩阵来存储每个点的簇分配结果
# clusterAssment包含两个列:一列记录簇索引值,第二列存储误差(误差是指当前点到簇质心的距离,后面会使用该误差来评价聚类的效果)
clusterAssment = np.mat(np.zeros((m, 2)))
# 创建质心,随机K个质心
centroids = createCent(dataMat, k)
# 初始化标志变量,用于判断迭代是否继续,如果True,则继续迭代
clusterChanged = True
while clusterChanged:
clusterChanged = False
# 遍历所有数据找到距离每个点最近的质心,
# 可以通过对每个点遍历所有质心并计算点到每个质心的距离来完成
for i in range(m):
minDist = np.inf
minIndex = -1
for j in range(k):
# 计算数据点到质心的距离
# 计算距离是使用distMeas参数给出的距离公式,默认距离函数是distEclud
distJI = distMeas(centroids[j, :], dataMat[i, :])
# 如果距离比minDist(最小距离)还小,更新minDist(最小距离)和最小质心的index(索引)
if distJI < minDist:
minDist = distJI
minIndex = j
# 如果任一点的簇分配结果发生改变,则更新clusterChanged标志
if clusterAssment[i, 0] != minIndex: clusterChanged = True
# 更新簇分配结果为最小质心的index(索引),minDist(最小距离)的平方
clusterAssment[i, :] = minIndex, minDist ** 2
# print(centroids)
# 遍历所有质心并更新它们的取值
for cent in range(k):
# 通过数据过滤来获得给定簇的所有点
ptsInClust = dataMat[np.nonzero(clusterAssment[:, 0].A == cent)[0]]
# 计算所有点的均值,axis=0表示沿矩阵的列方向进行均值计算
centroids[cent, :] = np.mean(ptsInClust, axis=0)
# 返回所有的类质心与点分配结果
return centroids, clusterAssment
选取不同的 k 值对结果影响有多大呢?我们来看看就知道了,下面给出的是 k 值为 2 到 6 的效果。 图中红色方块即为“簇”的中心点,每个“簇”所属的点用不同的颜色表示。 K = 2
K = 3
K = 4
K = 5
K = 6
人工智能
2018-11-21 11:48:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
概 述
HanLP 是基于 Java开发的 NLP工具包,由一系列模型与算法组成,目标是普及自然语言处理在生产环境中的应用。而且 HanLP具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点,因此十分好上手,本文就结合 Spring Boot来将 HanLP用起来!
下载 HanLP数据和程序
由于 HanLP库将数据与代码分离,因此我们需要分别下载所需数据和 jar包:
(1)所需 data数据包下载地址为 data.zip
(2)所需 jar包下载地址为 hanlp-release.zip
工程搭建
(1)创建一个普通的 Spring Boot工程,不赘述
(2)引入 HanLP数据 和 配置
下载完成以后,首先解压 hanlp-release.zip压缩包,然后将解压出的 HanLP的 jar包引入 Spring Boot工程,然后需要来放置 HanLP所需配置和数据:
(1)将解压后 hanlp-release.zip压缩包中的 hanlp.properties配置文件置于项目的 resources资源目录下
(2)然后解压 data.zip压缩包,将解压出的 data目录同样至于 resources目录下( data 中的数据包很重要,是 HanLP工作所需的词典和模型 )
创建 IO适配器
HanLP 提供了IO适配器,用户可以实现其提供的 com.hankcs.hanlp.corpus.io.IIOAdapter 接口以在不同的平台(HDFS、Redis等)上运行HanLP,默认的 IO适配器 IOAdapter = com.hankcs.hanlp.corpus.io.FileIOAdapter 是基于普通文件系统的。
接下来我们重写一下 IOAdapter类,使用读写静态资源文件的方法来读取HanLP所需的词典和模型数据( 即resources目录下刚放置的 data目录 )
然后我们配置一下 HanLP的配置文件hanlp.properties,有两处需要改为以下配置:
root= // 我们不再需要这种指定data目录的方式
IOAdapter=cn.codesheep.springbt_hanlp_userdefine.config.ResourceFileIoAdapter // 指定自定义的IOAdapter
好,现在项目就可以工作了,我们接下来写几个测试用例测试体验一把 !
实验测试
随便写几个例子来感受一番: 分词功能
@Test
public void testSegment() {
System.out.println( HanLP.segment("www.codesheep.cn是一个技术博客!") );
}
分词结果如下:
[www/nx, ./w, codesheep/nx, ./w, cn/nx, 是/vshi, 一个/mq, 技术/n, 博客/n, !/w]
每个词段后的 /nx,/w之类的是 HanLP定义的词性,可以去看 HanLP的接口来获取详情 文本推荐
三个关键字的语句推荐结果为:
机器学习 → [人工智能如今是非常火热的一门技术”]
危机公共 → [威廉王子发表演说 呼吁保护野生动物]mayun → [《时代》年度人物最终入围名单出炉 普京马云入选] 关键字提取
@Test
public void testKeyExtract() {
String content = "苹果公司(Apple Inc. )是美国一家高科技公司。由史蒂夫·乔布斯、斯蒂夫·沃兹尼亚克和罗·韦恩(Ron Wayne)等人于1976年4月1日创立," + "并命名为美国苹果电脑公司(Apple Computer Inc. ),2007年1月9日更名为苹果公司,总部位于加利福尼亚州的库比蒂诺。"; List keywordList = HanLP.extractKeyword(content, 5); System.out.println(keywordList);

提取结果为:
[公司, 苹果, 美国, Inc, Apple]
体验一番我们发现其自带的模型、字典等数据给出的实验效果已经是非常不错了,而且用户还可以自定义或修改 data目录下的模型、字典等数据来满足特定需求,因此还是十分强大的。
作者:CodeSheep
來源:简书
人工智能
2018-11-21 11:02:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 2018 年 11 月 14 日至 11 月 18 日,第二十届中国国际高新技术成果交易会(简称高交会)在深圳成功举办,七牛云作为国内领先的以数据智能和视觉智能为核心的企业级云计算服务商受邀参展。
11 月 16 日,在以「数造中国 定义未来」为主题的 2018 中国人工智能领袖峰会上,七牛云获得了「AIC 青藤奖」。同时七牛云人工智能实验室创始人彭垚为大家带来了题为《智能平台的创新和发展》的内容分享。
(「AIC 青藤奖」)
以下是关于演讲内容的实录整理。
七牛云是一家专注在智能视频云领域的企业,像秒拍、抖音、美图秀秀这些大家日常在用的耳熟能详的 APP,它们所有的数据都存在七牛云,图像、音视频、语音的处理,以及内容的分发都由我们来做。
(七牛云人工智能实验室创始人彭垚)
今天给大家分享的主要是七牛云在 AI 领域所做的事情。
整个人工智能平台上 80% 以上的数据均来源于视觉,其中以音视频、图像为主,所以整个智能体系中最重要的环节就是视觉智能。视觉智能主要还是针对于互联网上,每天通过手机上的 APP 自拍,在美拍、秒拍自动上传之后,再通过人脸识别、深度学习等进行处理。除此之外,在这些过程中也会产生一些其他的结构化或日志的数据,所以会通过我们的数据智能做一些数据的分析,包括一些机器语言的理解。下面是七牛云之前比较重要的直播、点播、实时音视频互动和播放器的能力,底层还有一些基础的能力,包括云主机、容器计算、海量存储和智能网络。
AtLab 核心创新体系
七牛云整个 AI 逻辑的产生用这张图就可以表示出来。
我们有三大 AI 平台,一个是智能多媒体 API 平台。把视频和图像导入,通过智能多媒体 API 平台可以把图像和视频认知的结果,比如说人脸识别的结果输出,这个平台就会支撑各种各样的模型。在这个过程中会产生很多的数据,这些数据我们会把它结构化,把视频图像标准化,建立整个的视频图谱。利用视频图谱把数据入库制作成想要的样本,然后再做一些标注。做完之后会再把这些数据标注完的信息资料导入到深度学习平台中去做学习,学习完就可以把视频生成,这是三大云平台的逻辑。
在这三大平台之上,内容安全是第一重要的模块;第二块是城市慧眼,我们做了各式各样的识别场景,包括各类车辆的检测;第三块是媒资智能,有很多广电的客户找到我们,这些客户同样有大量的视频数据,我们可以帮他们做快速的编辑、审核、检索等业务;第四块是创新计划,我们有一个专门的投资,来做各种有意思的创新任务。
一站式多维度内容审核—「明瞳」
「明瞳」是一站式内容审核的产品,主要面向广大的互联网企业。大家都了解,最近很多的互联网 APP 都被关停,就是因为上面的违规违法内容太多,对我们生活的影响,特别是青少年的影响是比较大的。一方面七牛云在服务这些互联网企业,一方面我们也在给政府,包括网信办、网安、广电总局提供我们的系统,协助他们去做审核。
传统的审查方式有:平台自查,可能会招一万个人去做人工审核。然后也有政府的监管方式,比如说群众举报,通过很多热线电话、网上举报的渠道。在举报之后,像网信办、网安这些政府机构一般都会去查处。现在也会利用七牛云的平台,自动地做爬虫检查,检查这些违规违法的行为。这三个方式方法实际上还是有非常多的痛点,因为有很多违规违法的内容还是一直在互联网上流传。
传统方法怎么用机器去查违规违法内容?
最早网安提出的一个方案是拿图像视频 MD5 的文件,把 MD5 值求出来,如果找到一样的就认为它是有问题的。一旦有一些新的违规违法视频,再通过人工把它转进来。违规内容持续流传,只是说相同的文件会找到,但实际上大部分的文件还是存在略微的差别,所以是完全的找不到。
现在市面上也有很多类似的企业,他们基本上采用的方法是用视频单帧的考核模型,这个现在比较主流,有很多插帧的视频会遗漏掉,如果抽走一小段视频,这可能是行为分析,也不会被留意到,所以通过单帧的识别模型还是做不到。
我们现在用的是视频结构化多维度识别,国家网信办、中央网信办和很多地方网信办现在都在沿用这一套系统,我们和网安总局、公安部还开设了联合实验室。
给大家分享一下整个做视频监控云这一套技术的方案。
对一个视频来说首先要提前做分镜头切割。镜头切割就是,比如你有一个长视频,一个镜头是对的,你可以切过来放到这里。首先要把视频段切割出来,每一个视频大多是定景的拍摄。切割完之后对每个视频段进行评估,包括行为的识别、涉黄低俗的识别、片段中的语音提取。语音提取需要做特殊声音的比对,包括低俗的、奇怪的声音,都是要通过特殊的声音去识别出来;文字的识别,包括像自然的语言处理、字幕翻译;然后还有动作的识别,动作其实是比较难捕捉的,比如性暗示行为的识别;还有场景和物品的识别,有一些特殊的物品和场景是违规的。通过这五大板块内容的识别,加上视频时序特征的叠加,我们就可以查处到这个内容的合法性,包括追溯到这个内容的区域、网络,去做整体的查处。
「明瞳」内容安全产品具有较多的优势,视频算法团队在国际上获得了非常多的奖,在视频竞赛中拿到了很多国际大奖。目前已经服务了很多互联网企业,每天审核超过 2 亿的视频,当然这个数据量还在增长,日均的审核量我们预测会超过 10 亿。
「明瞳」内容安全现在审核的主要类别有:涉色情内容、涉敏感内容、涉暴恐内容、涉低俗内容,每一个大分类下还有非常非常多的细分类,所以对审核的认知,首先它有一个很大的审核知识图谱,它建立的过程会通过一些新的东西去学习、去增加。
这个审核对互联网公司来说,看起来非常简单,如果你是存储在七牛云上的,直接打开开关,它就会开始自动审核。而且我们后台也有人工复核,不需要搭建任何的审核系统,就可以帮你全部做到。
智能化城市管理新模式—「城市慧眼」
下面介绍城市慧眼相关的内容。在上海、广东以及全国各地很多城市进行的智能化城市建设中,我们主要承担的是 AI 人工智能和视频的建设工作。
我们把城市慧眼 AI 分为这六大能力:城市安防,包括人脸动态静态的比对、人员立体追踪、重点人群管理、走失人群的搜寻。还可以做一些在岗督查的工作,监督这个人员上班是不是在待工,有没有玩手机、抽烟、睡觉。还有车辆管理,包括像渣土、环卫等整体的管理,每一种车辆的违规标准都可以识别出来的。一些违法的拉横幅行为也都可以识别。
在上海外滩陆家嘴滨江大道,我们用一个高速的算法做目标跟踪,技术是非常精准的。可以洞察关键路口多方向人流量变化情况,掌握人流规律,适时引导人流方向。
在重点区域还可设置违规告警。在黄线外面,保安在的地方是不允许溜狗的,就会告警让他回去。共享单车不允许停靠的地方,我们也会第一时间识别出来。
这是黄埔外滩的人流密度方向监控。监控的方法实际上是人流密度的计算。如果人头密度非常低的时候,它会画一条线,有可能人数增长就会出现拥堵的现象,上面这个图就是通过人头的方式去做人员密度的整体评估,包括很多的方位。下面这个图实际上有很多的箭头,它可以通过不同的颜色标注不同的人群,通过箭头知道人的行走方向,所以通过这两个视频综合判定出人流的走向和人流聚集的方向,这样就可以去做快速的人员疏导和预警。
大型马拉松活动是有一个大部队,这时候人是非常多的,行人乱窜或者车辆的危险进入可以通过无人机去做全程的跟踪。
整个城市慧眼安防监控的系统由这几个部分组成:人体、物体、场景,还有人像的一套识别系统,我们对应可以去做的能力,包括检测识别、智能检索、行为分析。整个安防监控产品,通过计算机视觉 + 人工智能的能力,可以去做智能发现、实时预警、快速处置、长效治理。
然后是做行人再识别。其实能做人脸识别的摄像头是非常少的,就算这个摄像头能做人脸识别,但如果人背对着摄像头也是识别不了的。ReID,通过行人行走的姿态,上中下的特征,识别出真正人的定位,七牛云在这一方面的学术领域水准非常高,在整个榜上是排名第一的。
早期做深度学习平台的时候,我们觉得管理数据太累,写代码重复率太高,所以最初只是想做一种工具平台。后面发现深度学习平台能够给很多学术的朋友带来很多帮助,特别是学校的学生。这是整个深度学习平台的发布流程,从模型的开发到调整,到调参,自动的训练、验证,到发布上线,整体是一个闭环。
这是AVA 深度学习平台的架构。从底层可以支持的所有存储,上面有一套分布式系统,有一些高级的应用,包括 AI 学习、增量学习,包括模型流程。
LEGO 大数据富媒体知识库是后续更智能去投入的。我们要做视频的结构化,把场景切开,特色的内容识别出来,建立一整套的搜索引擎,构建一套图谱。这个知识图谱是通过爬虫体系不断完善的制度,最后对外的其实是大数据的结构引擎,把里面的样本内容做结合,可以以图搜视频、以视频搜视频,还能得到你想要的数据样本。我们现在得到的数据量非常大,有好几十个 TB,整个样本的量都是在 LEGO 里面。
(优秀展示奖+优秀产品奖)
同时在第二十届高交会展览期间,经由专家评审和高交会组委会评选,七牛云凭借先进的产品创新优势在众多参展企业中脱颖而出,「七牛云海量存储系统」荣获高交会「优秀产品奖」。现场人气爆棚的七牛云展台还获得了本届高交会「优秀展示奖」。
人工智能
2018-11-20 20:45:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
一. 写在前面的话
有人认为价格围绕价值上下波动,研究投资标的内在价值,于是就出现了基本面派;

有人为价格反映了一切,所有的信息(包括基本面)都反映到了盘面价格中,于是就有了技术面派;

从技术面派的角度讲:
有人认为市场价格运动的方式是随机的,下一刻的价格涨跌的概率各是50%。于是就出现了被动仓位管理的网格类交易策略;

有人认为市场中80%的行情是震荡行情,只要把抓住震荡行情的利润,将足以弥补20%的趋势行情所带来的亏损。于是就出现了专门做短线震荡的交易策略;

有人认为 “ 得趋势者得天下 ” ,别的不做,只做趋势策略。“ 截断亏损,让利润奔跑 ” 对了就抱,错了就砍。长期下来20%的趋势行情利润,就能弥补80%震荡行情所带来的亏损。

等等等等……交易策略的种类犹如武功门派,数目众多,究其原因,每个人对事的看法不同,往大了说是人生观的不同。才导致了交易策略的不同。

最终,你恍然大悟,你会发现大部分交易策略都是能够赚钱的。最终亏钱的人是信念不够,或者太过贪婪,在一个又一个交易策略里跳进跳出,寻找所谓只赚不亏的 “ 圣杯 ” 。

我一直重复,做交易就是做人。

二 . 大道至简
说到技术分析,就离不开技术指标。关于技术指标常用的至少有几十种。衍生的就更多了。

技术指标没用。这应该是大多数人对技术指标的态度吧。我能说世界顶级管理期货基金也用指标吗?有用没用关键不在于指标本身,而在于你有没有一个正期望值的交易策略。

往往赚钱的策略,大多是经典的,经典的又是简单的,而简单的背后却蕴含着大智慧。

不管是股票、期货,还是外汇,当你打开行情时,默认显示的技术指标都是MACD(当然K线、均线、成交量这三个是必须的),为何百年来MACD指标经久不衰,投资领域的泰山北斗都如此重视MACD指标?为何它能从成百上千个技术指标中脱颖而出成为 “ 指标之王 ” 呢?要解答这些问题,我们必须从MACD的原理和特点来寻找答案。 看到这,可能就有人嗤之以鼻了。MACD?我下海第一天就知道了。知道和真正明白,是两码事,好吗!

三 . MACD 公式
MACD 中文名称叫指数平滑异同移动平均线。由Gerald Appel于1970年代提出,严格来说MACD是均线的一个变种,跟MA均线不同的是它在计算时赋予了权重,时间越近赋予权重越大。

计算公式:
第1步:计算EMA12和EMA26
EMA12 = XAverage( Close, 12 )
EMA26 = XAverage( Close, 26 )

第2步:计算 DIF
DIF = EMA12 - EMA26

第3步:计算 DEA
DEA = XAverage( DIF, 9 )

第4步:计算 Histogram
Histogram = DIF - DEA

代码展示:

四 . MACD背后的原理
第1步:先对杂乱的K线均值处理,即EMA12和EMA26。均线的优点是比较稳定,但缺点是比较滞后、频繁发出无效信号。

EMA其实是另一种复杂均线。在EMA指标中,每天价格的权重系数以指数等比形式缩小。时间越靠近当今时刻,它的权重越大,说明EMA函数对近期的价格加强了权重比,更能及时反映近期价格波动情况。

第2步:为了解决滞后和频繁发出无效信号问题又不失稳定,对两根均线差值处理,即DIF。均线差值可以快速反映两根均线的相互关系,同时具有又稳又快的特点。

DIF上升意味着什么?意味着短期成本的涨速高于长期成本的涨速,市场短期内资金买入的意愿更强。

第3步:重复第1步,对DIF均值处理,即DEA。

第4步:重复第2步,对DIF、DEA差值处理,即Histogram,也就是我们常说的红绿柱子。

整个过程,如下图:

五 . MACD究竟该怎么使用
网上关于MACD的用法层出不穷:有DIF与DEA金叉死叉做趋势的、有结合价格看顶底背离做抄底的、也有辅助其他技术分析的…等等….

哪个对?没有哪个是对的,也没有哪个是错的。这些方法,只在特定的时间有效。交易策略的关键不是用了哪个万能指标,而是制定一个正期望值的交易策略,然后重复执行,仅此而已。

六 . 绩效展示

策略源码关注宽客在线公众回复“领取”即可;

延伸阅读: 基于CCI周期性区间交易策略
人工智能
2018-11-19 17:02:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
https://github.com/prakhar1989/awesome-courses
https://github.com/EbookFoundation/free-programming-books
https://github.com/Blankj/awesome-java-leetcode
https://github.com/iluwatar/java-design-patterns
人工智能
2018-11-19 16:05:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 阿里搜索技术体系演进至今天,基本形成了由offline、nearline、online三层体系,分工协作,保证电商平台上,既能适应日常平稳流量下稳定有效的个性化搜索及推荐,也能够去满足电商平台对促销活动的技术支持,实现在短时高并发流量下的平台收益最大化。
阿里搜索技术体系演进至今天,基本形成了由 offline 、 nearline 、 online 三层体系,分工协作,保证电商平台上,既能适应日常平稳流量下稳定有效的个性化搜索及推荐,也能够去满足电商平台对促销活动的技术支持,实现在短时高并发流量下的平台收益最大化。
可以看到,十年双 11 的考验后,搜索智能化体系逐渐打造成型,已经成为电商平台稳定健康发展的核动力,主要分为四个阶段: 自主研发的流式计算引擎 Pora 初露锋芒;双链路实时体系大放异彩; “ 深度学习 + 强化学习 ” 初步探路;全面进入深度学习时代。 下面我们就来一起看一下。
四大演进阶段:业务、算法、系统同步发展
阶段一:初露锋芒 —— 自主研发的流式计算引擎 Pora
技术的演进是伴随解决实际业务问题和痛点发展和进化的。 2014 年双 11 ,通过 BI 团队针对往年双 11 的数据分析,发现即将售罄的商品仍然获得了大量流量,剩余库存无法支撑短时间内的大用户量。主售款(热销 sku )卖完的商品获得了流量,用户无法买到商品热销的 sku ,转化率低;与之相对,一些在双 11 当天才突然展露出来的热销商品却因为历史成交一般没有得到足够的流量。
针对以上问题,通过搜索技术团队自主研发的流式计算引擎 Pora ,收集预热期和双 11 当天全网用户的所有点击、加购、成交行为日志,按商品维度累计相关行为数量,并实时关联查询商品库存信息,提供给算法插件进行实时售罄率和实时转化率的计算分析,并将计算结果实时更新同步给搜索和推荐引擎,影响排序结果。第一次在双 11 大促场景下实现了大规模的实时计算。算法效果上,也第一次让大家感受到了实时计算的威力, PC 端和移动端金额也得到显著提升。
阶段二:大放异彩 —— 双链路实时体系
2014 年双 11 ,实时技术在大促场景上,实现了商品和用户的特征实时,表现不俗。
2015 年搜索技术和算法团队继续推动在线计算的技术升级,基本确立了构筑基于实时计算体系的【在线学习 + 决策】搜索智能化的演进路线。
早先的搜索学习能力,是基于批处理的离线机器学习。在每次迭代计算过程中,需要把全部的训练数据加载到内存中计算。虽然有分布式大规模的机器学习平台,在某种程度上批处理方法对训练样本的数量还是有限制的。在线学习不需要缓存所有数据,以流式的处理方式可以处理任意数量的样本,做到数据的实时消费。
接下来,我们要明确两个问题:为什么需要在线学习呢?以及为什么实现秒级的模型更新?
在批量学习中,一般会假设样本独立服从一个未知的分布,但如果分布变化,模型效果会明显降低。而在实际业务中,很多情况下,一个模型生效后,样本的分布会发生大幅变化,因此学到的模型并不能很好地匹配线上数据。实时模型,能通过不断地拟合最近的线上数据,解决这一问题,因此效果会较离线模型有较大提升。那么为什么实现秒级分钟级的模型更新?在双 11 这种成交爆发力强、变化剧烈的场景,秒级实时模型相比小时级实时模型时效性的优势会更加明显。 根据 2015 年双 11 实时成交额情况,前面 1 小时已经完成了大概总成交的 1/3 ,小时模型就无法很好地捕获这段时间里面的变化。
基于此,搜索技术团队基于 Pora 开发了基于 parameter server 的在线学习框架,如下图所示,实现了在线训练,开发了基于 pointwise 的实时转化率预估模型,以及基于 pairwise 的在线矩阵分解模型。并通过 swift 输送模型到引擎,结合实时特征,实现了特征和模型双实时的预测能力。
在线学习框架
但是,无论是离线训练还是在线学习,核心能力是尽可能提高针对单一问题的算法方案的准确度,却忽视了人机交互的时间性和系统性,从而很难对变幻莫测的用户行为以及瞬息万变的外部环境进行完整的建模。典型问题是在个性化搜索系统中容易出现反复给消费者展现已经看过的商品。
如何避免系统过度个性化,通过高效的探索来增加结果的丰富性?我们开始探索机器智能技术的另一方向 —— 强化学习,运用强化学习技术来实现决策引擎。我们可以把系统和用户的交互过程当成是在时间维度上的【 state , action , reward 】序列,决策引擎的目标就是最优化这个过程。
在线决策方面,我们首次尝试了运用 MAB 和 zero-order 优化技术实现多个排序因子的最优融合策略,取代以前依靠离线 Learningto rank 学到的排序融合参数。其结果是显著的,在双 11 当天我们也观察到,通过实时策略寻优,一天中不同时间段的最优策略是不同的,这相比全天使用离线学习得到的一套固定排序权重是一个大的进步。
2015 年双 11 双链路实时计算体系如下图所示:
2015 年双 11 的实时计算体系
阶段三:初步探路 ——“ 深度学习 + 强化学习 ”
2015 年双 11 ,在线学习被证明效果显著,然而回顾当天观察到的实时效果,也暴露出一些问题。 ● 问题一:在线学习模型方面,该模型过度依赖从 0 点开始的累积统计信号,导致后场大部分热销商品都无法在累积统计信号得到有效的差异化表示,模型缺少针对数据的自适应能力。
● 问题二:在线决策方面。 2015 年双 11 ,宏观实时体系中的 MAB( Multi-ArmedBandit) 实时策略寻优发挥了重要作用,通过算法工程师丰富经验制定的离散排序策略集合, MAB 能在双 11 当天实时选择出最优策略进行投放;然而,同时暴露出 MAB 基于离散策略空间寻优的一些问题,离散策略空间仍然是拍脑袋的智慧。同时为了保证 MAB 策略寻优的统计稳定性,几十分钟的迭代周期仍然无法匹配双 11 当天流量变化的脉搏。
针对第一个问题,我们在 2016 年双 11 中也进行了优化和改进。对于从 0 点的累积统计信号到后场饱和以及统计值离散化缺少合理的抓手的问题,我们参考 Facebook 在 AD-KDD 的工作,在此基础上,结合在线学习,我们研发了 Streaming FTRL stacking on DeltaGBDT 模型,如下图所示。
Streaming FTRL stacking on DeltaGBDT 模型
分时段 GBDT 模型会持续为实时样本产出其在双 11 当天不同时段的有效特征,并由 onlineFTRL 去学习这些时效性特征的相关性。
对于在线决策方面的问题,我们进行了策略空间的最优化探索,分别尝试了引入 delay reward 的强化学习技术,即在搜索中采用强化学习( ReinforcementLearning )方法对商品排序进行实时调控优化。我们把搜索引擎看作智能体( Agent )、把用户看做环境( Environment ),则商品的搜索问题可以被视为典型的顺序决策问题( Sequential Decision-making Problem )。我们的目标就是要实现用平台长期累积收益的最大化。
系统方面, 2016 年双 11 我们的实时计算引擎从 istream 时代平稳升级到到 Blink/Flink 时代,实现 24 小时不间断无延迟运转,机器学习任务从几个扩大到上百个 job 。为算法实现大规模在线深度学习和强化学习等前沿技术打下了坚实的基础。
阶段四:全面进入 —— 深度学习时代
由于在线深度学习需要强大的计算资源来支持, 2017 年系统上我们重构了流式计算平台、机器学习平台和支持 CPU/GPU 的异构在线服务平台,能够支持更大规模的流式数据计算,超大规模深度模型在线学习和在线预估。
依托强大的计算能力,实现了深度学习在搜索的全面落地,包括语义搜索,深度用户兴趣感知,商品多模表示学习,在线深度机制模型,多场景协同智能决等技术创新:
a) 深度用户感知模型:搜索或推荐中个性化的重点是用户的理解与表达,基于淘宝的用户画像静态特征和用户行为动态特征,我们提出基于 multi-modals learning 、 multi-task representation learning 以及 LSTM 的相关技术,从海量用户行为日志中直接学习用户的通用表达,该学习方法善于 “ 总结经验 ” 、 “ 触类旁通 ” ,使得到的用户表达更基础且更全面,能够直接用于用户行为识别、偏好预估、个性化召回、个性化排序等任务,在搜索、推荐和广告等个性化业务中有广泛的应用场景。(相关成果的论文已被 KDD 2018 录用)
b) 大规模商品多模表示学习:淘宝商品有文本、图像、标签、 id 、品牌、类目、店铺,统计特征等多模态信息,这些特征彼此有一定程度的冗余和互补,我们利用多模学习将多维度特征融合在一起学习一个统一的商品向量,并通过 attention 机制实现不同特征维度在不同场景下的差异,比如女装下图片特征比较重要, 3C 下价格,销量比较重要等。
c)在线深度机制模型:由于不同用户和不同场景的优化目标不一样,我们把用户状态,场景相关特征加入到了机制模型中,实现了千人千面的排序机制模型。同时由于各种基础实时日志的qps和延迟都不太一样,为了保证在线学习的稳定性,我们构建了实时样本pool来维护一个稳定的样本集合供在线学习使用。
d) 全局排序:传统的排序模型只对单个文档打分,然后按照分数从高到底排序。这样方法无法考虑到商品之间相互的影响;传统的针对单个商品 ctr 、 cvr 都基于这样一个假设:商品的 ctr 、 cvr 不会受到同时展示出来的其他商品 ( 我们称为展示 context) 的影响。而实际上一个商品的展示 context 可以影响到用户的点击或者购买决策:假如同一个商品周边的商品都和它比较类似,而且价格都比它便宜,那么用户买它的概率不会高;反之如果周边差不多的商品都比它贵,那么用户买它的概率就会大增。而全局排序就要解决这个问题,考虑商品之间的相互影响,实现整页效率的最大化。(相关成果的论文已被 IJCAI2018 录用)
e) 多场景协同智能决策:搜索多个不同的产品都是依托个性化来实现 GMV 最大化,导致的问题是不同产品的搜索结果趋同。而导致这个问题的根本原因是不同场景的算法各自为战,缺乏合作和关联。今年做的一个重要工作是利用多智能体协同学习技术,实现了搜索多个异构场景间的环境感知、场景通信、单独决策和联合学习,实现联合收益最大化,而不是此消彼长(相关成果的论文已被 www 2018 录用)。
驱动搜索智能化体系的演进的三点
我们再回过头来看,是什么驱动了搜索智能化体系的演进?
目前,各大互联网公司的主流技术路线主要是运用机器学习技术来提升搜索 / 推荐平台的流量投放效率,随着计算力和数据的规模增长,大家都在持续地优化和深入。是什么驱动我们推动搜索的智能化体系从离线建模、在线预测向在线学习和实时决策方向演进呢?概括来说,主要有以下三点。
首先,众所周知,淘宝搜索具有很强的动态性,宝贝的循环搁置,新卖家加入,卖家新商品的推出,价格的调整,标题的更新,旧商品的下架,换季商品的促销,宝贝图片的更新,销量的变化,卖家等级的提升等等,都需要搜索引擎在第一时间捕捉到这些变化,并在最终的排序环节,把这些变化及时地融入匹配和排序,带来结果的动态调整。
其次,从 2013 年起,淘宝搜索就进入千人千面的个性化时代,搜索框背后的查询逻辑,已经从基于原始 Query 演变为【 Query+ 用户上下文 + 地域 + 时间】,搜索不仅仅是一个简单根据输入而返回内容的不聪明的 “ 机器 ” ,而是一个能够自动理解、甚至提前猜测用户意图(比如用户浏览了一些女士牛仔裤商品,然后进入搜索输入查询词 “ 衬衫 ” ,系统分析用户当前的意图是找女性相关的商品,所以会展现更多的女士衬衫,而不是男生衬衫),并能将这种意图准确地体现在返回结果中的聪明系统,这个系统在面对不同的用户输入相同的查询词时,能够根据用户的差异,展现用户最希望看到的结果。变化是时刻发生的,商品在变化,用户个体在变化,群体、环境在变化。在搜索的个性化体系中合理地捕捉变化,正是实时个性化要去解决的课题。
最后,电商平台也完成了从 PC 时代到移动时代的转变,随着移动时代的到来,人机交互的便捷、碎片化使用的普遍性、业务切换的串行化,要求我们的系统能够对变化莫测的用户行为以及瞬息万变的外部环境进行完整的建模。基于监督学习时代的搜索和推荐,缺少有效的探索能力,系统倾向于给消费者推送曾经发生过行为的商品或店铺。
真正的智能化搜索和推荐,需要作为投放引擎的 agent 有决策能力,这个决策不是基于单一节点的直接收益来确定,而是当作一个人机交互的过程,消费者与平台的互动看成是一个马尔可夫决策过程,运用强化学习框架,建立一个消费者与系统互动的回路系统,而系统的决策是建立在最大化过程收益基础上。
未来展望 —— 让淘宝搜索拥有智慧化的体验
经过这么十年双 11 大促的技术锤炼后,围绕在线 AI 技术的智能框架初具规模,基本形成了在线学习加智能决策的智能搜索系统,为电商平台实现消费者、卖家、平台三方利益最大化奠定了坚实的基础。这套具备学习加决策能力的智能系统也让搜索从一个简单的找商品的机器,慢慢变成一个会学习会成长,懂用户,体贴用户的 “ 人 ” 。
但在这个过程中,搜索排序学习到的知识更多都是通过已有的商品标签数据和用户行为数据来获取的,还缺少对商品和用户更深层次的认知,还无法完全理解用户的多元意图表达的真实需求。
比如,用户搜索了 “ 性感连衣裙 ” ,可能是想找 “ 去参加晚场 party 的低胸晚装裙 ” ,也可能是想找 “ 去海边度假的露肩沙滩裙 ” ;用户收藏了 “ 登山鞋 ” 和 “ 拐杖 ” ,可能有 “ 登山装备 ” 的需求,需要发现更多的和登山装备相关的其它品类商品。一个有孩子的爸爸,在暑假刚开始时,挑选 “ 转换接头 ” ,查看 “ 大英博物馆门票 ” ,可能是想带着家人一起 “ 暑期英国亲子游 ” ,需要发现更多相关的其它品类商品。
究其原因,目前机器智能技术特别是以深度学习为代表的模型,在现实应用中快速发展,最主要受益于海量大数据以及大规模计算能力,通过对物理世界的数字化抽象和程式化学习,使得机器智能具备很强的限定知识的获取能力,而很难获得数据之外的知识,就更不用说知识的类比、迁移和推理了。
而机器的认知智能,比如自主学习和发现,甚至创造能力才是人工智能的更高境界。当然通用的机器智能还有很多工作要做,在这个过程中,怎么样首先结合人类知识和机器智能做到初步的认知智能,让淘宝搜索拥有智慧化的体验是未来值得探索的方向。
原文链接
人工智能
2018-11-19 13:33:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 什么是机器学习,为什么学习机器学习,如何学习机器学习,这篇文章都告诉给你。
目前,人工智能(AI)非常热门,许多人都想一窥究竟。如果你对人工智能有所了解,但对机器学习(Machine Learning)的理解有很多的困惑,那么看完本文后你将会对此有进一步深入理解。在这里,不会详细介绍机器学习算法的基本原理,而是通过将比较有意思的视频(YouTube)和文字相结合,逐渐增加对机器学习基础的理解。
当看到本文时,请坐下来放松一下。因为下面的这些视频需要花费一点时间,但看完视频后,你肯定会被吸引继续阅读下去。此外,当阅读完本文后,你应该会对现在最热门的技术——机器学习有了全面的知识基础,并对此产生学习热情,最终能学到什么程度完全取决于个人的努力,本文只是一块敲门砖。
为什么机器学习现在如此热门
人工智能总是很酷,从科幻电影到现实中的阿法狗、聊天机器人等,一直吸引人们的关注。长久以来,人们认为人工智能一直围绕着程序员对某些事情应该如何表现的功能性猜测。然而,程序员并不总是像我们经常看到的那样对人工智能编程同样有着天赋。正如我们经常看到的那样,比如谷歌“史诗游戏失败”中在人工智能、物理、有时甚至是经验丰富的人类玩家中都存在有过失。
无论如何,人工智能有一种新的天赋——通过该项技术,我们可以教电脑玩游戏、理解语言、甚至识别人或物。这个只显露冰山一角的新技术来源一个旧的概念——机器学习,直到最近几年,它才获得了理论之外的处理能力,这源于数据量的爆炸、计算机性能的提升以及算法理论的突破。
通过人工智能这项技术,我们不再需要人为地提出高级算法,只需要教会计算机自己来提出高级算法即可。
那么这样的事情是如何实现的呢?机器学习算法并没有真正被类似于程序员编程那样进行编写,而是自动生成。观看下面这个简短的视频,该视频为创建人工智能的高级概念提供了出色的注释和动画。
https://www.youtube.com/watch?v=R9OHn5ZF4Uo
是不是一个很疯狂的处理过程?并且,当算法完成后,我们甚至无法理它,它就像一个黑匣子。比如,该项技术应用于视觉领域中是用人工智能玩马里奥游戏。作为一个人,我们都知道如何躲避障碍物和吃金币,但人工智能识别所产生的预测策略是疯狂的,见下面的视频:
https://youtu.be/qv6UVOQ0F44
是不是很吃惊?看完上述视频后,我们的问题是对机器学习不了解,并且不知道如何将它与电子游戏联系起来。
幸运的是, Elon Musk已经提供了一家非营利性公司来做后者 。通过十几行代码,你可以将人工智能与任何你想要玩的游戏/任务联系起来, 快去尝试一下吧 。
为什么要使用机器学习?
关于为什么要关心机器学习,这里有两个很好的答案。 首先,机器学习使计算机可以做到计算机以前不能实现的事情。如果你想尝试一些新事物,或者不仅仅是新事物,而是影响全世界,你都可以用机器学习来完成。 其次,如果你不影响世界,世界将影响你。
现在,很多大型公司在机器学习上投入了很多的研发和投资,我们已经看到它正在改变世界。思想领袖警告我们不能让这个新的算法时代存在于公众视线之外。想象一下,如果一些企业巨头控制着互联网,如果我们不掌握这项武器,科学的真理将不会被我们占据。 Christian Heilmann 在他关于机器学习的谈话中说得很好: “我们能够希望其他人善用这种力量。对于个人而言,不要认为这是一个好的赌注。我宁愿玩,也要参加这场科技革命,你也应该参与。”—— Chris Heilmann的机器学习谈话视频
对机器学习感兴趣
机器学习这个概念很有用而且很酷,上述内容让我们比较抽象地了解了它,但机器学习算法究竟发生了什么?它是如何运作的?我们还不是很清楚。
如果你想直接进入到理论研究,建议你跳过这一部分继续下一个“如何开始”部分。如果你有动力成为机器学习的实干者,那么就不需要看下面的视频了。如果你仍然试图了解机器学习可能是什么,下面的使用机器学习完成数字手写体识别的视频非常适合引导读者建立一种机器学习的逻辑:
https://youtu.be/aircAruvnKk
是不是很酷?该视频显示每个层变得更简单,而不是变得更复杂。就像函数将数据分解成较小的部分一样,以抽象的概念结束。你可以在该网站( Adam Harley )与此流程进行交互。
http://scs.ryerson.ca/~aharley/vis/conv/flat.html
此外,机器学习的经典实例之一是1936年的鸢尾花数据集。在参加 JavaFXpert 的机器学习概述的演示中,我学会了如何使用工具来可视化调整和反向传播神经网络上神经元的权重。可视化过程可以让我们看到它是如何训练神经模型。
使用 Jim可视化工具 训练鸢尾花神经网络
即使你不是一个Java爱好者,Jim提供了一个1.5小时的机器学习概念介绍也是比较有用的,其中包含上述许多例子的更多详细信息。
这些概念令人兴奋,你准备好成为这个新时代的爱因斯坦吗?机器学习算法每天都在发生突破,所以现在就开始吧。
如何开始?
至于如何进行深入学习,我推荐下面两种方法:
从头到尾拧完n颗螺栓
在这种方法中,将需要你全面了解机器学习算法和相关的数学知识。我知道,这种方式听起来很难完成,但要想真正地了解算法细节,就必须从头开始编码实现。
如果你想成为机器学习中的一员,并在核心圈中占据一席之地,那么这就是你的选择。我建议你试试一些公开课app(比如,course、Brilliant.org),并参加人工神经网络课程。经典的网络课程主推 Andrew Ng 老师的机器学习课程以及周志华老师的书籍等。
在学习的同时,可以完成对应的线下作业。通过完成对应的作业,会进一步加深对知识的理解,因为这些作业并不简单。但更重要的是,如果确实完成了这项工作,你将对机器学习的实施有进一步深刻的理解,这将使得你以新的和改变世界的方式成功地将其应用到对应的场景中。
快速上手
如果你对编写算法并不感兴趣,但仍想要使用它们来创建一个令人惊叹的网站/应用程序,你应该跳转到学习TensorFlow和对应的速成课程。
TensorFlow是用于机器学习的开源软件库。它可以以无数种方式使用,甚至可以使用 JavaScript ,这里是一个 速成课程 。 更多课程信息请点击此处查看 。
如果选修课程不适合你的学习方式,那你仍然是很幸运的。如今不必学习机器学习的细节就可以掌握如何使用它。此外,还可以通过多种方式有效地机器学习作为服务成为技术巨头。
数据是这项技术很重要的原材料,如果你的数据比较合适,那么使用机器学习建模可能是最佳解决方案。无论是使用机器学习中的哪一种算法,现在就开始吧。
成为创造者
我要对所有上述的人和视频说声谢谢,它们是我学习机器学习起步的灵感,虽然我在该领域仍然是个新手,但是当我们拥抱这个令人敬畏的时代时,我很高兴为他人指明一条学习道路。
如果你想学习这门技术,就必须与机器学习领域的研究者有所联系。没有友好的面孔、回答和讨论,任何事情都将变得很难。一般技术圈的人都是比较热心肠的,遇到问题先google,找不到答案就咨询圈内人,相信会有友好的同行给出友好的建议。
我希望这篇文章能激励你和周围的人学习机器学习,我也很乐意和你一起寻找酷炫有趣的机器学习代码,希望本文对你有所帮助。
原文链接
人工智能
2018-11-19 12:24:02
「深度学习福利」大神带你进阶工程师,立即查看>>>
首先安装Python的包管理工具 sudo apt-get install python-pip
然后安装tensorflow pip install tensorflow
测试一下安装是否成功
人工智能
2018-11-19 11:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
命运的玩笑
就是在最需要她的时候失去她
也是在最不希望见到她的地方见到她

我是「鉴黄师」,这是我的故事

 2013年 冬

 这一年有很多现象级人物,比如跳着骑马舞的鸟叔,唱着「法海不懂爱」的龚琳娜,而对我影响最大的,当属穿着白色背心的「鉴黄师唐马儒」。
在他出现前,我是一名「网络内容审查员」,他出现后,我就成了大家口中的「鉴黄师」。
虽然我不穿白色背心,不秃顶,不叫唐马儒,看太阳也不是绿色的。
据我所知,我百分之九十五的好友,都把我的备注名改成了「鉴黄师」。
还有 13 位热心损友,给我发来了各式憨态可掬的唐马儒高清大图,供我头像之用。
其中包括我的女朋友。
有时候我们明知道是玩笑,但是开玩笑的人多了也容易当真。
很多命运里的注定,都是在不经意间的玩笑里留下伏笔。

 2014年 夏

梅西在马拉卡纳体育场痛失大力神杯,我在无休止的加班和争吵中痛失女友。
作为一个铁杆球迷,那年夏天我一场比赛都没看,足球群里都笑称:他这是上班的时候,「球儿」看够了!
我确实是看够了。从刺激到麻木到恶心,其实用不了多久。但比「看够了」更可怕的是「受够了」。
「你能一整天不和我说话。你能加班一个星期。你能忘了我的生日。你曾经是我的不可或缺,但现在,我受够了。」
她虽然这么说,但我始终觉得,她离开我的真正原因只因我是个「鉴黄师」。
「你男朋友是唐马儒么?」
「他看过那么多爱情动作片,你该幸福了」
「把你男朋友扣扣号给我下,求种子!」
这些玩笑和调侃,或许是压垮她的最后一根稻草。
我理解她。
她如愿考上了计算机专业的研究生,顺理成章地离开了我。
我祝福她。
如果说「鉴黄师」也有原罪,那么第一宗或许就是「不被理解」吧!
耳机里是《泡沫》的单曲循环,也希望有一天,她能理解我。

 2017年 春

又是一年春天,我似乎忘了她。
直播的火热、短视频的兴起,让日复一日的审查工作更加繁重。
我进入了一重「化境」。
从感官刺激到司空见惯,那些声色早就没了声色,屏幕上的那些凶猛和情欲,是我的修行。
但身边的同路人越来越少总让人揪心,曾经坐满一间办公室的审核团队,除了我这个绝对「老人」,能坚持下来的寥寥无几。
眼看我这个主管,就要变成「光杆司令」了。
人越来越少,工作量越来越多。头发越来越少,加班越来越多。
累点没什么,但尽管如此,公司还是因为违规内容被有关部门约谈了。
整改、处罚、下架……慢慢变成我的梦魇,压着我喘不过气来。
原来最可怕的不是用尽全力,是已然用尽全力,却还是没有想要的结果。
曾考虑过放弃,但又总想起分手那天和她说过的话:
「之前的种种都是我不对,不该因为我的坚持而忽略了你。但是有些坚持总需要有人坚持。希望有一天你能理解我。」
这时我才发现,不是我忘了她,只是我忘记了想起她。
我的坚持,究竟是什么呢?

 2018年 夏
又是一年世界杯。梅球王留起了大胡子,「慌得一比」,我仍坚守在内容审核岗位,故作镇定。
野蛮生长带来的内容井喷,让内容的审核任务逐渐变成「生命不能承受之重」。随之而来的,是技术的发展。
「AI 审核」、「音频鉴黄」已经不是什么新鲜事了。曾经给我发「唐马儒头像」的热心损友开始关心,我这个「鉴黄师」,会不会「被机器人淘汰」?
自顾不暇的同时,因为内容违规问题而受到处罚的公司越来越多。
我们公司也又一次被约谈了。
面对其他厂商被「下架」、「永久关停」的严厉处罚,老板有点坐不住了。
会主动停摆避避风头?
还是会招来更多的鉴黄师?
或者直接让机器人代劳?
这些问题的背后是人力成本、运营风险等一些列更多的问题。稍有不慎,这些年的坚持和努力便毁于一旦。
网络安全无小事。深知责任重大,无奈力不从心。
怎么办?

 2018年 秋

打着鸡血咬着牙,直道天凉好个秋。
刺激的事情,发生在这个周一的上午。
我一如往常地审核那些「你懂的」内容,然后——
竟然,看见了,我的,前女友。
不要想歪了。她漂亮的脸庞当然不是出现在我的「工作内容」里。
「好久不见啊,没想到你还坚持着你的坚持呢!」
这个声音我太熟悉了,一扭头,她在我身边眼带笑意。
「是你!?」
「先不多聊了,晚上一起吃饭吧。老地方见。」
还是那家店,还是那只猫,还是那个老板娘
五年过去了,竟一如往常。
我们默契的点了对方最爱吃的菜,却不知道话从何说起。
「你还是做内容审核么?」
「嗯……」
「我也是做内容审核。」
「哈?……」
「我在七牛云的人工智能实验室,这次可是来帮你们的!」
「啊?……」
「我们训练人工智能来进行内容识别,有个「明瞳」产品,可以精准识别色情、暴恐、政治敏感人物等各种内容,有我们产品的帮忙,你身上的担子应该能减轻很多啦!」
「这……」
「你今天怎么啦吞吞吐吐的?是怕我们不靠谱么?放心吧,SOUL、汽车之家、糖豆等好多知名企业都是我们的客户。高达 99% 的识别准确率,怕是某些老司机也比不了吧~」
「你……」
「嗨,别担心,我可不是来抢你饭碗,是来雪中送炭哒。我们的「明瞳」能帮助你们节约 80%~90% 的人力成本,不仅不用担心人手不够,同时人工智能 1 小时就能干完你 1 个月的审核量,也可以把你从重复繁重的审核工作里解放出来。看你这黑眼圈之前就没少加班吧……不过毕竟标准还是人定的,你这种老法师,也是不可或缺的呢!」
……
……
「我还能成为你的不可或缺么?」
她低头浅笑:
「明天一起吃早餐吧~」
在最需要她的时候失去她
也在最需要她的时候再遇见她
最坚硬的「不被理解」
原来也可以被理解
足够认真,就不怕命运的玩笑
我仍有我的坚持,我又有了新的坚持
点击 「阅读原文」
即刻体验七牛云「明瞳」内容安全
人工智能
2018-11-13 14:48:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
“打击论文造假,维护科研正义”。这不是一句喊口号的话。
今年6月,斯坦福大学微生物学家分析了2009-2016年发表在分子与细胞生物学(MCB)上的960篇论文,发现其中59篇(6.1%)含有“不适当的”重复图像,约有 2%值得再去进行图像证伪 。
不过,斯坦福大学微生物学家的工作完全依靠手动,五位研究人员靠十只手从近1000篇论文里总结出了这一成果。
现在,AI的介入让论文中的可疑图像被发现的概率大大提升,一个显著的成果是,在生物医学领域的论文中,AI已经搜索出 9%的图像是高度重复的, 0.59%的论文被认为存在欺诈嫌疑 。
人工智能
2018-11-13 13:53:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1. 谷歌如何利用强化学习来提出正确的问题
当搜索引擎找不到你想要的答案时,你会怎么做?你会试着重新组织提问吧。
谷歌正试图通过新的 Active Question Answering(Active QA) 智能体来模仿这一点,这是一个使用强化学习来训练AI进行问答的系统。当你提出一个问题时,它会产生很多有相似含义的问题。它会搜索所有问题的答案,然后选择最好的答案返回给你。
这有助于聊天机器人取得进步。与其让你在它们不明白的时候重新组织措辞提问,不如让它们自己去做!
而且,它是开源的。
阅读:
What’s New in Deep Learning Research: How Google Uses Reinforcement Learning to Ask All the Right Questions— from Jesus Rodriguez
https://towardsdatascience.com/whats-new-in-deep-learning-research-how-google-uses-reinforcement-learning-to-ask-all-the-right-69c172f113c4
人工智能
2018-11-13 13:52:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 计算机视觉与最前沿技术如何结合?
计算机视觉软件正在改变行业,使用户的生活变得不仅更容易,而且更有趣。作为一个有潜力的领域,计算机视觉已经获得了大量的投资。北美计算机视觉软件市场的总投资额为1.2亿美元,而中国市场则飙升至39亿美元。让我们来看看一些最有前途和更有趣的技术,因为这些技术可以让计算机视觉软件开发市场增长的更快。
一、深度学 习 的 进 步
深度学习因其在提供准确结果方面而广受欢迎。
传统的机器学习算法尽管很复杂,但其核心仍然非常简单。他们的训练需要大量的专业领域的知识和数据(这是昂贵的),在训练发生错误时需要进行人为干预,而且,他们只擅长于他们接受过训练的任务。
另一方面,深度学习算法通过将任务映射为概念层次结构的神经元网络了解手头的任务。每个复杂的概念都由一系列更简单的概念组合定义,而所有这些算法都可以自己完成。在计算机视觉的背景下,图像分类需要首先识别亮区和暗区,然后在移向全画面识别之前对线进行分类,然后进行形状分类。
当你为他们提供更多数据时,深度学习算法也会表现得更好,这是典型的机器学习算法做不到的。对于计算机视觉,深度学习是一个好的方向。它不仅允许在深度学习算法的训练中使用更多的图片和视频,而且还减轻了许多与注释和标记数据相关的工作。
零售业一直是实施计算机视觉软件的先驱。2017年,ASOS在为他们的应用添加了一个按照照片搜索的选项,之后许多零售商都跟进了。有些人甚至更进一步,并使用计算机视觉软件将在线和离线体验更紧密地结合在一起。
一家名为Lolli&Pops的美食糖果零售商使用面部识别来识别经常走进商店的购物者。因此,商店的员工可以通过提供个性化的产品推荐和千人千面的折扣来个性化购物体验。
特殊待遇可以提升品牌忠诚度,并将偶尔的购物者转变为经常性购物者。
二、边缘计算的兴 起
连接到互联网和云的机器能够从整个网络收集的数据中学习并相应地进行调整,从而优化系统的性能。但是,并不能保证机器能够始终连接到互联网和云,这就是边缘计算的用武之地。
边缘计算是指附接到物理机器的技术,例如燃气轮机,喷气发动机或MRI扫描仪。它允许在收集数据的地方处理和分析数据,而不是在云中或数据中心。
边缘计算不能取代云。它只是允许机器在需要时单独处理新的数据。换句话说,边缘的机器可以根据自己的经验学习和调整,而不依赖于更大的网络。
边缘计算解决了网络可访问性和延迟的问题。在边缘计算的发展下,设备可以放置在网络连接不良或不存在的区域,此外,边缘计算还可以抵消用于数据共享的云计算的使用和维护的一些成本。
对于计算机视觉软件,这意味着可以实时更好地响应,并且只将相关数据发送到云中进行进一步分析,此功能对自动驾驶汽车特别有用。
为了安全运行,车辆将需要收集和分析与其周围环境,方向和天气状况有关的大量数据,更不用说与路上的其他车辆通信,所有这些都没有延迟。如果通过云中心化的解决方案来分析数据可能很危险,因为延迟可能导致事故。
三、点云( point cloud )对 象 识别
最近在对象识别和对象跟踪中更频繁使用的技术是点云。简而言之,点云是在三维坐标系内定义的数据点的集合。
该技术通常在空间(例如房间或容器)内使用,其中每个对象的位置和形状由坐标列表(X,Y和Z)表示,坐标列表称为“点云”。
该技术准确地表示了物体在空间中的位置,并且可以精确地跟踪任何移动。点云的应用是无止境的。以下是一些行业的例子以及他们从这项技术中获得的好处:
· 记录:资产监测,跟踪施工现场,故意破坏检测; · 分类:城市规划,审计工具,便于分析,绘制必要的公用事业工作 · 变更检测:资产管理,货物跟踪,自然灾害管理。 · 预测性维护:持续监控资产和基础设施,以预测何时需要维修。

四、融合现实:VR和AR增强
今天,任何VR或AR系统都会创建一个沉浸式3D环境,但它与用户所处的真实环境几乎没有关系。大多数AR设备可以执行简单的环境扫描(例如,Google ARCore可以检测平面和光线条件的变化),VR系统可以通过头部跟踪,控制器等检测用户的运动,但他们的功能也就这样了。
计算机视觉软件正在推动VR和AR进入下一阶段的开发,有些人称之为Merged Reality(MR)。
借助外部摄像头和传感器映射环境,以及眼动跟踪解决方案和陀螺仪来定位用户,VR和AR系统能够: · 感知环境并引导用户远离墙壁,物品或其他用户等障碍物。 · 检测用户的眼睛和身体运动并相应地采用VR环境。 · 提供室内环境,公共场所,地下等的指引。
Lowe's五金店已在他们的商店中使用它,每个购物者都可以借用AR设备来制作他们的购物清单,并获得商店中每件商品的指示。AR设备可以实时使用楼层平面图,库存信息和环境映射以给出准确的指示。
我们也可以通过实时3D面部识别功能更新虚拟艺术家应用程序,让客户可以看到不同的化妆产品在他们的脸上和不同光线条件下的外观。
五、语义实例分割
为了理解语义实例分割是什么,让我们首先将这个概念分为两​​部分:语义分割和实例分割。
实例分割在像素级别识别对象轮廓,而语义分割仅将像素分组到特定对象组。让我们使用气球图像来说明与其他技术相比的两种技术:
· 分类:此图像中有一个气球; · 语义分割:这些都是气球像素; · 物体检测:此图像中有7个气球,我们开始考虑重叠的对象; · 实例分割:这些位置有7个气球,这些是属于每个气球的像素;
如果放在一起,语义实例分割方法将成为一个强大的工具。该工具不仅可以检测属于图片中对象的所有像素,还可以确定哪些像素属于哪个对象以及对象所在的图片中的位置。
语义实例分割是土地覆盖分类的有用工具,具有各种应用。通过卫星图像进行的土地制图可以用于政府机构监测森林砍伐(特别是非法),城市化交通等。
许多建筑师事务所也将这些数据用于城市规划和建筑开发,有些人甚至更进一步将其与AR设备相结1合,以了解他们的设计在现实生活中的样子。
原文链接
人工智能
2018-11-19 11:38:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
]出现错误ImportError: cannot import name cbook。
进入python环境发现,运行import caffe 和 import matplotlib 出现Import: No module named functools_lru_cache
卸载之前的matplotlib:
sudo pip uninstall matplotlib
使用该命令重新安装:
sudo apt-get install python-matplotlib
或者: https://my.oschina.net/u/3726752/blog/2877910
人工智能
2018-11-18 23:48:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
ValueError: Cannot center sparse matrices: pass `with_mean=False` instead. See docstring for motivation and alternatives.
在X = StandardScaler().fit_transform(X)添加with_mean=False
X = StandardScaler(with_mean=False).fit_transform(X)
人工智能
2018-11-18 23:34:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
pandas 数据结构简单理解为一维,二维,三维 #Series (collection of values 亦称系列) 类似一维ndarray,可以用numpy部分操作处理(np.sin(),np.max()) #DataFrame (collection of Series objects 亦称数据帧),结构若有行名和列名的二维矩阵,类似一个表格 #Panel (collection of DataFrame objects 亦称面板),多个DataFrame的集合,即多张表格数据 pandas Series数据类型 #object - For string values #int - For integer values #float - For float values #datetime - For time values #bool - For Boolean values
pandas.Series( data, index, dtype, copy)。 1 data 数据采取各种形式,如:ndarray,list,constants 2 index 索引值必须是唯一的和散列的,与数据的长度相同。 默认np.arange(n)如果没有索引被传递。 3 dtype dtype用于数据类型。如果没有,将推断数据类型 4 copy 复制数据,默认为false。 data = np.array(['a','b','c','d']) s = pd.Series(data) data = np.array(['a','b','c','d']) data = {'a' : 0., 'b' : 1., 'c' : 2.} s = pd.Series(data) data = {'a' : 0., 'b' : 1., 'c' : 2.} s = pd.Series(data,index=['b','c','d','a']) -> d NaN s = pd.Series(data,index=[100,101,102,103]) s = pd.Series(5, index=[0, 1, 2, 3]) 索引:s[2],s[1:3],s["a"] 系列基本功能 编号 属性或方法 描述 1 axes 返回行轴标签列表。 2 dtype 返回对象的数据类型(dtype)。 3 empty 如果系列为空,则返回True。 4 ndim 返回底层数据的维数,默认定义:1。 5 size 返回基础数据中的元素数。 6 values 将系列作为ndarray返回。 7 head() 返回前n行。 8 tail() 返回最后n行。 from pandas import Series pd.Series([1, 2, 3]).values -> array([1, 2, 3]) original_index = series_custom.index.tolist() sorted_index = sorted(original_index) sorted_by_index = series_custom.reindex(sorted_index) sc2 = series_custom.sort_index() sc3 = series_custom.sort_values() np.add(series_custom, series_custom) #series 对应相加 #data alignment same index,赋予相同的index,然后对应操作 rt_critics = Series(fandango['RottenTomatoes'].values, index=fandango['FILM']) rt_users = Series(fandango['RottenTomatoes_User'].values, index=fandango['FILM']) rt_mean = (rt_critics + rt_users)/2 print(rt_mean)
pandas.DataFrame( data, index, columns, dtype, copy) 1 data 数据采取各种形式,如:ndarray,series,map,lists,dict,constant和另一个DataFrame。 2 index 对于行标签,要用于结果帧的索引是可选缺省值np.arrange(n),如果没有传递索引值。 3 columns 对于列标签,可选的默认语法是 - np.arange(n)。 这只有在没有索引传递的情况下才是这样。 4 dtype 每列的数据类型。 5 copy 如果默认值为False,则此命令(或任何它)用于复制数据。 选择列:print df ['one'] 添加列:df['four']=df['one']+df['three'] ,将两列的和做新的列 df['new name'] = pd.Series() 删除列:df.pop('two') 按标签选择行:df.loc['b'] 按整数位置选择行:df.iloc[2] 行切片:df[2:4] 增加新行:df.append(df2) 删除行:df.drop(0)#标签重复则删除多行 DataFrame基本功能 编号 属性或方法 描述 1 T 转置行和列。 2 axes 返回一个列,行轴标签和列轴标签作为唯一的成员。 3 dtypes 返回此对象中的数据类型(dtypes)。 4 empty 如果NDFrame完全为空[无项目],则返回为True; 如果任何轴的长度为0。 5 ndim 轴/数组维度大小。 6 shape 返回表示DataFrame的维度的元组。 7 size NDFrame中的元素数。 8 values NDFrame的Numpy表示。 9 head() 返回开头前n行。 10 tail() 返回最后n行。
面板:pandas.Panel(data, items, major_axis, minor_axis, dtype, copy) data 数据采取各种形式,如:ndarray,series,map,lists,dict,constant和另一个数据帧(DataFrame) items axis=0 major_axis axis=1 minor_axis axis=2 dtype 每列的数据类型 copy 复制数据,默认 - false 初始化:3Dndarray: data = np.random.rand(2,4,5) p = pd.Panel(data) DataFrame: data = {'Item1' : pd.DataFrame(np.random.randn(4, 3)), 'Item2' : pd.DataFrame(np.random.randn(4, 2))} p = pd.Panel(data) 索引: p['Item1'] p.major_xs(1) p.minor_xs(1)
下面重点是Dataframe的介绍:
导入 import pandas food_info = pandas.read_csv("food_info.csv") print(type(food_info)) -> pandas.read_csv(filepath_or_buffer, sep=',', delimiter=None, header='infer', names=None, index_col=None, usecols=None) #index_col 指定某列作为索引,names指定列名,skiprows跳过指定行数 pd.read_excel
初始化 #初始化 data = [1,2,3,4,5] df = pd.DataFrame(data) data = [['Alex',10],['Bob',12],['Clarke',13]] df = pd.DataFrame(data,columns=['Name','Age'],dtype=float) data = {'Name':['Tom', 'Jack', 'Steve', 'Ricky'],'Age':[28,34,29,42]} df = pd.DataFrame(data) data = {'Name':['Tom', 'Jack', 'Steve', 'Ricky'],'Age':[28,34,29,42]} df = pd.DataFrame(data, index=['rank1','rank2','rank3','rank4']) data = [{'a': 1, 'b': 2},{'a': 5, 'b': 10, 'c': 20}] df = pd.DataFrame(data) #没有的为NaN data = [{'a': 1, 'b': 2},{'a': 5, 'b': 10, 'c': 20}] df = pd.DataFrame(data, index=['first', 'second']) data = [{'a': 1, 'b': 2},{'a': 5, 'b': 10, 'c': 20}] df1 = pd.DataFrame(data, index=['first', 'second'], columns=['a', 'b']) d = {'one' : pd.Series([1, 2, 3], index=['a', 'b', 'c']), 'two' : pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])} df = pd.DataFrame(d)
索引以及行列操作 .loc() 基于标签、单个标量标签、标签列表、切片对象、一个布尔数组 df.loc[:,'A'] df.loc[:,['A','C']] df.loc['a':'h'] .iloc() 基于整数、整数列表、系列值 df.iloc[:4] df.iloc[1:5, 2:4] df.iloc[[1, 3, 5], [1, 3]] .ix() 基于标签和整数 df.ix[:4] df.ix[:,'A'] 属性. df = pd.DataFrame(np.random.randn(8, 4), columns = ['A', 'B', 'C', 'D']) print (df.A) #pandas从0开始索引 print food_info.loc[0] #第一行 food_info.loc[6] #第七行,超出范围会报错 # DataFrame 的3,4,5,6行,注意:左闭右闭 print food_info.loc[3:6] two_five_ten = [2,5,10] print food_info.loc[two_five_ten] #2,5,10行 print food_info.loc[[2,5,10]] #同上 ndb_col = food_info["NDB_No"] #指定列 col_name = "NDB_No" ndb_col = food_info[col_name] columns = ["Zinc_(mg)", "Copper_(mg)"] zinc_copper = food_info[columns] zinc_copper = food_info[["Zinc_(mg)", "Copper_(mg)"]] food_info.columns #显示列名,但是不是list -> Index([u'NDB_No', u'Shrt_Desc'],dtype='object') col_names = food_info.columns.tolist() #将列名转为list -> ['NDB_No', 'Shrt_Desc',] div_1000 = food_info["Iron_(mg)"] / 1000 #对该列每行数据除以1000(可以实现+-*/,返回是series) water_energy = food_info["Water_(g)"] * food_info["Energ_Kcal"] #列与列之间运算 max_calories = food_info["Energ_Kcal"].max() #某列中的最大值 gram_columns = [] print type(col_names) for c in col_names: if c.endswith("(g)"): gram_columns.append(c) gram_df = food_info[gram_columns] print(gram_df.head(3)) DataFrame.sort_values(by, axis=0, ascending=True, inplace=False, kind='quicksort', na_position='last') axis:{0 or ‘index’, 1 or ‘columns’}, default 0,默认按照索引排序,即纵向排序,如果为1,则是横向排序 by:str or list of str;如果axis=0,那么by="列名";如果axis=1,那么by="行名"; ascending:布尔型,True则升序,可以是[True,False],即第一字段升序,第二个降序 inplace:布尔型,是否用排序后的数据框替换现有的数据框 kind:排序方法,{‘quicksort’, ‘mergesort’, ‘heapsort’}, default ‘quicksort’。 na_position : {‘first’, ‘last’}, default ‘last’,默认缺失值排在最后面 example: 沿着轴方向按指定值排序 x.sort_values(by="x1",ascending= False)
是否为NaN age = titanic_survival["Age"] #某列 age_is_null = pd.isnull(age) #返回该列是否是NaN的bool值 age_null_true = age[age_is_null] #返回是True所在行,但是值是NaN age_null_count = len(age_null_true) #统计非空数量 mean_age = sum(titanic_survival["Age"]) / len(titanic_survival["Age"]) #为NaN,则该列含有NaN,含NaN的运算可能导致结果为NaN good_ages = titanic_survival["Age"][age_is_null == False] #与前面结合,将NaN值踢掉 titanic_survival["Age"].mean() #则是自动剔除NaN计算
计算每个班的平均分 passenger_classes = [1, 2, 3] fares_by_class = {} for this_class in passenger_classes: pclass_rows = titanic_survival[titanic_survival["Pclass"] == this_class] pclass_fares = pclass_rows["score"] fare_for_class = pclass_fares.mean() fares_by_class[this_class] = fare_for_class
数据透视表(刚接触的比较难理解) pandas.pivot_table() data:DataFrame对象 values:源数据中的一列,数据透视表中用于观察分析的数据值,类似Excel中的值字段 index:源数据中的一列,数据透视表用于行索引的数据值,类似Excel中的行字段 columns:源数据中的一列,数据透视表用于列索引的数据值,类似Excel中列字段 aggfunc:根据当前的行、列索引生成的数据透视表中有多个数据需要进行聚合时,对这多个数据需要进行的操作,默认为np.mean()
pandas.DataFrame.dropna DataFrame.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False) 功能:根据各标签的值中是否存在缺失数据对轴标签进行过滤,可通过阈值调节对缺失值的容忍度 参数:axis : {0 or ‘index’, 1 or ‘columns’},或 tuple/list how : {‘any’, ‘all’} any : 如果存在任何NA值,则放弃该标签 all : 如果所以的值NA值,则放弃该标签 thresh : int, 默认值 None int value :要求每排至少N个非NA值   subset : 类似数组,列名 ["Age", "Sex"] inplace : boolean, 默认值 False 如果为True,则进行操作并返回None。 返回:被删除的DataFrame titanic_survival.loc[1,"Age"] #索引,第2行,"Age"列的值,选择多行返回dataframe,选择单行返回Series df1 = df.loc[[0,2,3],:] df2 = df1.reset_index() #df1按顺序加上新的索引,原来的索引列名为"index" reset_index,通过函数 drop=True 删除原行索引 df3 = df1.reset_index(drop=True)
默认情况下会以列为单位,分别对列应用函数,将df按行或列传给函数f处理,函数需要返回值 t1=df.apply(f) t2=df.apply(f,axis=1) def which_class(row): pclass = row['Pclass'] if pd.isnull(pclass): return "Unknown" elif pclass == 1: return "First Class" elif pclass == 2: return "Second Class" elif pclass == 3: return "Third Class" classes = titanic_survival.apply(which_class, axis=1)
重置索引 DataFrame可以通过set_index方法,可以设置单索引和复合索引。 DataFrame.set_index(keys, drop=True, append=False, inplace=False, verify_integrity=False) append添加新索引,drop为False,inplace为True时,索引将会还原为列] data: a b c d 0 bar one z 1.0 1 bar two y 2.0 2 foo one x 3.0 3 foo two w 4.0 indexed1 = data.set_index('c') a b d c z bar one 1.0 y bar two 2.0 x foo one 3.0 w foo two 4.0 indexed2 = data.set_index(['a', 'b']) c d a b bar one z 1.0 two y 2.0 foo one x 3.0 two w 4.0
groupby: group1 = df.groupby('key1') group2 = df.groupby(['key1','key2']) group1.size()/sum()/count() #分组统计 对于分组的某一列或者多个列,应用agg(func)可以对分组后的数据应用func函数 group1['data1'].agg('mean') group1['data1'].agg(['mean','sum']) group1['data1','data2'].agg(['mean','sum']) apply()不同于agg()的地方在于:前者应用于dataframe的各个列,后者仅作用于指定的列。 交叉表crosstab 可以按照指定的行和列统计分组频数 pd.crosstab(df.key1,df.key2, margins=True)
常用函数 print food_info.dtypes (返回各个列的数据类型) NDB_No int64 Shrt_Desc object Water_(g) float64 Energ_Kcal int64 first_rows = food_info.head() #默认5行 print first_rows print(food_info.head(3)) #print food_info.columns print food_info.shape #不包括第一行head行
重要函数 - 编号 函数 描述 1 count() 非空观测数量 2 sum() 所有值之和 3 mean() 所有值的平均值 4 median() 所有值的中位数 5 mode() 值的模值 6 std() 值的标准偏差 7 min() 所有值中的最小值 8 max() 所有值中的最大值 9 abs() 绝对值 10 prod() 数组元素的乘积 11 cumsum() 累计总和 12 cumprod() 累计乘积 注 - 由于DataFrame是异构数据结构。通用操作不适用于所有函数。 类似于:sum(),cumsum()函数能与数字和字符(或)字符串数据元素一起工作,不会产生任何错误。字符聚合从来都比较少被使用,虽然这些函数不会引发任何异常。 由于这样的操作无法执行,因此,当DataFrame包含字符或字符串数据时,像abs(),cumprod()这样的函数会抛出异常。 表明智函数应用:pipe() 行或列函数应用:apply() 元素函数应用:applymap()
遍历DataFrame: 要遍历数据帧(DataFrame)中的行,可以使用以下函数 - iteritems() - 迭代(key,value)对 iterrows() - 将行迭代为(索引,系列)对 itertuples() - 以namedtuples的形式迭代行 for col in df: print (col)#列名 for key,value in df.iteritems(): print (key,value) #列为键 for row_index,row in df.iterrows(): print (row_index,row)#行为键 for row in df.itertuples(): print (row) #每行一个元组(index,行值)
排序: sorted_df=unsorted_df.sort_index(axis=1) 按行or列排序(按index或者列名) sorted_df = unsorted_df.sort_values(by='col1') #按值排序,某列的值
pandas 对字符串操作函数 1 lower() 将Series/Index中的字符串转换为小写。 2 upper() 将Series/Index中的字符串转换为大写。 3 len() 计算字符串长度。 4 strip() 帮助从两侧的系列/索引中的每个字符串中删除空格(包括换行符)。 5 split(' ') 用给定的模式拆分每个字符串。 6 cat(sep=' ') 使用给定的分隔符连接系列/索引元素。 7 get_dummies() 返回具有单热编码值的数据帧(DataFrame)。 8 contains(pattern) 如果元素中包含子字符串,则返回每个元素的布尔值True,否则为False。 9 replace(a,b) 将值a替换为值b。 10 repeat(value) 重复每个元素指定的次数。 11 count(pattern) 返回模式中每个元素的出现总数。 12 startswith(pattern) 如果系列/索引中的元素以模式开始,则返回true。 13 endswith(pattern) 如果系列/索引中的元素以模式结束,则返回true。 14 find(pattern) 返回模式第一次出现的位置。 15 findall(pattern) 返回模式的所有出现的列表。 16 swapcase 变换字母大小写。 17 islower() 检查系列/索引中每个字符串中的所有字符是否小写,返回布尔值 18 isupper() 检查系列/索引中每个字符串中的所有字符是否大写,返回布尔值 19 isnumeric() 检查系列/索引中每个字符串中的所有字符是否为数字,返回布尔值。 s = pd.Series(['Tom', 'William Rick', 'John', 'Alber[@t](https://my.oschina.net/u/104868)', np.nan, '1234','SteveMinsu']) print (s.str.lower()) s = pd.Series(['Tom', 'William Rick', 'John', 'Alber[@t](https://my.oschina.net/u/104868)', np.nan, '1234','SteveMinsu']) print (s.str.len())
pct_change()函数 系列,DatFrames和Panel都有pct_change()函数。此函数将每个元素与其前一个元素进行比较,并计算变化百分比。 协方差 协方差适用于系列数据。Series对象有一个方法cov用来计算序列对象之间的协方差。NA将被自动排除。 s1 = pd.Series(np.random.randn(10)) s2 = pd.Series(np.random.randn(10)) print (s1.cov(s2)) print (frame['a'].cov(frame['b'])) print (frame.cov())
相关性 相关性显示了任何两个数值(系列)之间的线性关系。有多种方法来计算pearson(默认),spearman和kendall之间的相关性。 (frame['a'].corr(frame['b'])) print (frame.corr()) 排名s.rank() Rank可选地使用一个默认为true的升序参数; 当错误时,数据被反向排序,也就是较大的值被分配较小的排序。 Rank支持不同的tie-breaking方法,用方法参数指定 - average - 并列组平均排序等级 min - 组中最低的排序等级 max - 组中最高的排序等级 first - 按照它们出现在数组中的顺序分配队列
清理/填充缺少数据 Pandas提供了各种方法来清除缺失的值。fillna()函数可以通过几种方法用非空数据“填充”NA值 df.fillna(0) pad/fill 填充方法向前 bfill/backfill 填充方法向后 丢失缺少的值 如果只想排除缺少的值,则使用dropna函数和axis参数。 默认情况下,axis = 0,即在行上应用,这意味着如果行内的任何值是NA,那么整个行被排除。 替换丢失(或)通用值 很多时候,必须用一些具体的值取代一个通用的值。可以通过应用替换方法来实现这一点。 用标量值替换NA是fillna()函数的等效行为 df = pd.DataFrame({'one':[10,20,30,40,50,2000], 'two':[1000,0,30,40,50,60]}) print (df.replace({1000:10,2000:60}))
连接,拼接,可视化,时间日期,
人工智能
2018-11-18 23:21:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
上了RTX 2080Ti显卡后,原来Ubuntu18.04内置的驱动和PPA安装的都不运作了,安装NVidia官网下载的驱动可以跑起来,但是需要费一些周折。
1、禁用系统默认显卡驱动
打开系统黑名单: sudo gedit /etc/modprobe.d/blacklist.conf
将下列代码填入文件末尾: # for nvidia display device install blacklist vga16fb blacklist nouveau blacklist rivafb blacklist rivatv blacklist nvidiafb
保存,然后更新initramfs: sudo update-initramfs -u
重启电脑: sudo reboot
查看是否禁用成功,无输出则禁用成功: lsmod | grep nouveau
2、安装RTX2080Ti显卡驱动 NVidia官网,驱动下载网址: https://www.geforce.com/drivers
或者,命令行直接下载410.78版本: wget -c http://cn.download.nvidia.com/XFree86/Linux-x86_64/410.78/NVIDIA-Linux-x86_64-410.78.run
进入命令行终端: sudo chmod +x NVIDIA-Linux-x86_64-410.78.run sudo ./NVIDIA-Linux-x86_64-410.78.run
如果原来安装过,需要先卸载: sudo apt-get --purge remove nvidia-*
或者: sudo ./NVIDIA-Linux-x86_64-410.57.run -uninstall
让后,更新内核,重启电脑: sudo update-initramfs -u sudo reboot now
注意: 采用这种方法安装的驱动,每次内核更新后,都要按照上面的方法搞一遍才能启用新的驱动。 原来的方法通过添加PPA,可以自动更新,但是没有最新的驱动程序版本(Ubuntu18.04上最高390,Ubuntu16.04上为396),目前还不能支持2080Ti显卡。 参见: Ubuntu 16.04安装NVIDIA的显卡驱动396和CUDA9.1
3、安装Cuda支持库
下载: https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&target_distro=Ubuntu&target_version=1804&target_type=deblocal
直接下载: wget -c https://developer.download.nvidia.com/compute/cuda/10.0/secure/Prod/local_installers/cuda-repo-ubuntu1804-10-0-local-10.0.130-410.48_1.0-1_amd64.deb?70QzyncVNRDMRovRKQM1527D5fHj-zEzR6VNho3u5OOVRYmpaVuk_5ua6-gC06pTo0MsvzxZ3B3L2zUnHY3rY1l0AeN5nOS6JMob5ibT7Y9kGJIZZ0VJl2dLSsAdh2xaDf37iLKe0E2WyoBlKo5WNlatJpO9lbp3K0366nvdZOl-QtKROQIWmXzY9182HaUpanBSee7tMRvCIoOYuZJNUddfTNEDWK6wDAnY-po
安装: sudo dpkg -i cuda-repo-ubuntu1804-10-0-local-10.0.130-410.48_1.0-1_amd64.deb sudo apt-key add /var/cuda-repo-/7fa2af80.pub sudo apt-get update sudo apt-get install cuda
4、深度学习框架
NVidia已经是深度学习的标配了,可以安装和运行多种深度学习的计算框架。参见: https://developer.nvidia.com/deep-learning-frameworks
参考文献: Ubuntu, https://askubuntu.com/questions/1079852/how-do-i-configure-an-nvidia-rtx-2080-ti-with-ubuntu Driver download, https://www.geforce.com/drivers/results/140230 Driver Install, https://blog.csdn.net/VcosmosV/article/details/83022682
人工智能
2018-11-18 17:35:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
【一】关于fasttext
fasttext是Facebook开源的一个工具包,用于词向量训练和文本分类。该工具包使用C++11编写,全部使用C++11 STL(这里主要是thread库),不依赖任何第三方库。具体使用方法见:https://fasttext.cc/ ,在Linux 使用非常方便。fasttext不仅提供了软件源码,还提供了训练好的一些模型(多语种的词向量:英文、中文等150余种)
源码地址:https://github.com/facebookresearch/fastText/
gensim也对该功能进行了封装,可以直接使用。
fasttext的源码实现非常优雅,分析源码,带来以下几方面的收获:
1. 如何组织文本数据?
2. CBOW和skip-gram是如何实现的?
3. 模型如何量化?
【二】fasttext整体结构

【三】fasttext参数配置
主要参数如下:
具体参数使用可以参考:https://fasttext.cc/docs/en/support.html
【四】dict相关源码分析
1. 从输入数据构造词典的整体流程 void Dictionary::readFromFile(std::istream& in) { std::string word; int64_t minThreshold = 1; // 1. 逐词读取 while (readWord(in, word)) { // 2. 将词添加到词典中 add(word); if (ntokens_ % 1000000 == 0 && args_->verbose > 1) { std::cerr << "\rRead " << ntokens_ / 1000000 << "M words" << std::flush; } // 如果超出词典容量,则去除低频词 if (size_ > 0.75 * MAX_VOCAB_SIZE) { minThreshold++; // 去除低频词 threshold(minThreshold, minThreshold); } } // 去除低频词,并按照词频降序排序 threshold(args_->minCount, args_->minCountLabel); initTableDiscard(); // 基于n-gram,初始化sub-word initNgrams(); if (args_->verbose > 0) { std::cerr << "\rRead " << ntokens_ / 1000000 << "M words" << std::endl; std::cerr << "Number of words: " << nwords_ << std::endl; std::cerr << "Number of labels: " << nlabels_ << std::endl; } if (size_ == 0) { throw std::invalid_argument( "Empty vocabulary. Try a smaller -minCount value."); } }
2. 面对不同的语言,如何读取一个词? // 1. 对于词向量训练,需要先分词,然后词之前用空格隔开 bool Dictionary::readWord(std::istream& in, std::string& word) const { int c; // 1. 获取文件流的data指针 std::streambuf& sb = *in.rdbuf(); word.clear(); // 2. 循环读取,每次从文件流中读取一个char while ((c = sb.sbumpc()) != EOF) { // 3. 对c读取的字符做不同的处理,如果不是空格等,则继续读取下一个字符 if (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\f' || c == '\0') { if (word.empty()) { if (c == '\n') { word += EOS; return true; } continue; } else { if (c == '\n') sb.sungetc(); return true; } } // 4. 将char添加到word中,继续读取下一个字符 word.push_back(c); } // trigger eofbit in.get(); return !word.empty(); }
3. 如何将一个词添加到词典中? void Dictionary::add(const std::string& w) { // 1. 通过find获取词的hash值 int32_t h = find(w); ntokens_++; // 2. 通过hash值,查询该词是否在表word2int_中。 // 该表的下标为词的hash值,value为词的id,容量为 MAX_VOCAB_SIZE if (word2int_[h] == -1) { // 3. 新词,将其添加到词典 words_中 entry e; e.word = w; e.count = 1; // 新词,词频为1 e.type = getType(w); // 词的类型,分类则为label,词向量则为word,即将所有的词放在一个词典中的 // 没有分开存储label与word words_.push_back(e); word2int_[h] = size_++; // 添加词的id,id就是个顺序值,和普通的for循环中的i作为id是一样的 } else { // 词典中已存在的词,仅增加词频 words_[word2int_[h]].count++; } }
4. 如何去低频词? void Dictionary::threshold(int64_t t, int64_t tl) { // 1. 先对词典中的词按照词频排序, sort(words_.begin(), words_.end(), [](const entry& e1, const entry& e2) { if (e1.type != e2.type) { return e1.type < e2.type; } // 词频降序排列 return e1.count > e2.count; }); // 2. 将 word 词频小于t的删除,将label词频小于t1的删除 words_.erase( remove_if( words_.begin(), words_.end(), [&](const entry& e) { return (e.type == entry_type::word && e.count < t) || (e.type == entry_type::label && e.count < tl); }), words_.end()); // 3. 词典容量调整,前面删除了部分词。 words_.shrink_to_fit(); // 4. 重置词典数据 size_ = 0; nwords_ = 0; nlabels_ = 0; std::fill(word2int_.begin(), word2int_.end(), -1); // 将词典中的数据重新计算id值 for (auto it = words_.begin(); it != words_.end(); ++it) { int32_t h = find(it->word); word2int_[h] = size_++; if (it->type == entry_type::word) { nwords_++; } if (it->type == entry_type::label) { nlabels_++; } } }
5. initTableDiscard void Dictionary::initTableDiscard() { // 将 大小调整为词典大小 pdiscard_.resize(size_); for (size_t i = 0; i < size_; i++) { // 计算概率,词频/词总数 real f = real(words_[i].count) / real(ntokens_); pdiscard_[i] = std::sqrt(args_->t / f) + args_->t / f; } }
6. initNgrams void Dictionary::initNgrams() { for (size_t i = 0; i < size_; i++) { // 1. 从词典中获取一个词,并给该词加上"<"与">",例如:北京---->"<北京>" std::string word = BOW + words_[i].word + EOW; words_[i].subwords.clear(); // 该词的子词列表,首先添加全词的id,全词也算一个子词 words_[i].subwords.push_back(i); if (words_[i].word != EOS) { // 依据n-gram,计算子词 computeSubwords(word, words_[i].subwords); } } } // word ---->原始的词 // ngrams --->依据n-gram分割出的子词,出参 // substrings --->默认值为nullptr void Dictionary::computeSubwords( const std::string& word, std::vector& ngrams, std::vector* substrings) const { // 1. 获取词的大小,一个词可能是由多个字符组成的 // 例如:word = "<终南山>" for (size_t i = 0; i < word.size(); i++) { std::string ngram; // 2. 这里是为了解决utf-8编码问题 // 参考:https://stackoverflow.com/questions/3911536/utf-8-unicode-whats-with-0xc0-and-0x80 if ((word[i] & 0xC0) == 0x80) { continue; } // args_->maxn --->配置参数,表示n-gram中的n的最大值,默认为maxn = 6; // args_->minn --->配置参数,表示n-gram中的n的最小值,默认为minn = 3; // args_->bucket--->配置参数,表示bucket = 2000000; // 进行n-gram切分:例如:终南山---->终南、南山 for (size_t j = i, n = 1; j < word.size() && n <= args_->maxn; n++) { ngram.push_back(word[j++]); while (j < word.size() && (word[j] & 0xC0) == 0x80) { ngram.push_back(word[j++]); } if (n >= args_->minn && !(n == 1 && (i == 0 || j == word.size()))) { int32_t h = hash(ngram) % args_->bucket; // 这里面会建立一个sub-word的hash索引 pushHash(ngrams, h); if (substrings) { substrings->push_back(ngram); } } } } }
至此,依据数据数据构建词典的流程已经完成。主要是完成了word的去重、词频统计、词频排序、基于n-gram的sub-word预处理、word2id等处理。
【五】train流程分析
1. train的主流程 void FastText::train(const Args args) { args_ = std::make_shared(args); dict_ = std::make_shared(args_); if (args_->input == "-") { // manage expectations throw std::invalid_argument("Cannot use stdin for training!"); } std::ifstream ifs(args_->input); if (!ifs.is_open()) { throw std::invalid_argument( args_->input + " cannot be opened for training!"); } // 1. 词典构造 dict_->readFromFile(ifs); ifs.close(); // 2. 如果有与训练的向量,则加载 if (args_->pretrainedVectors.size() != 0) { loadVectors(args_->pretrainedVectors); } else { // 3. 构造输入数据矩阵的大小,这里也就是embidding的大小 // V*m input_ = std::make_shared(dict_->nwords() + args_->bucket, args_->dim); // 初始化词嵌入矩阵 input_->uniform(1.0 / args_->dim); } if (args_->model == model_name::sup) { // 隐层输出矩阵大小,分类: n*m,词向量 V*m output_ = std::make_shared(dict_->nlabels(), args_->dim); } else { output_ = std::make_shared(dict_->nwords(), args_->dim); } output_->zero(); // 启动计算 startThreads(); model_ = std::make_shared(input_, output_, args_, 0); if (args_->model == model_name::sup) { model_->setTargetCounts(dict_->getCounts(entry_type::label)); } else { model_->setTargetCounts(dict_->getCounts(entry_type::word)); } }
2. 单线程训练流程 void FastText::trainThread(int32_t threadId) { std::ifstream ifs(args_->input); // 1. 按照线程数,将输入数据平均分配给各个线程, // 各个线程之间不存在数据竞争,英雌不需要加锁 utils::seek(ifs, threadId * utils::size(ifs) / args_->thread); // 2. 初始化一个model Model model(input_, output_, args_, threadId); // 3. setTargetCounts 接口内部会完成tree或者负采样的数据初始化 if (args_->model == model_name::sup) { model.setTargetCounts(dict_->getCounts(entry_type::label)); } else { model.setTargetCounts(dict_->getCounts(entry_type::word)); } const int64_t ntokens = dict_->ntokens(); int64_t localTokenCount = 0; std::vector line, labels; while (tokenCount_ < args_->epoch * ntokens) { // 计算处理进度,动态调整学习率 real progress = real(tokenCount_) / (args_->epoch * ntokens); real lr = args_->lr * (1.0 - progress); // 每次读取一行数据,依据模型不同,调用不同接口处理 if (args_->model == model_name::sup) { // 文本分类 localTokenCount += dict_->getLine(ifs, line, labels); supervised(model, lr, line, labels); } else if (args_->model == model_name::cbow) { // cbow localTokenCount += dict_->getLine(ifs, line, model.rng); cbow(model, lr, line); } else if (args_->model == model_name::sg) { // sg localTokenCount += dict_->getLine(ifs, line, model.rng); skipgram(model, lr, line); } if (localTokenCount > args_->lrUpdateRate) { tokenCount_ += localTokenCount; localTokenCount = 0; if (threadId == 0 && args_->verbose > 1) loss_ = model.getLoss(); } } if (threadId == 0) loss_ = model.getLoss(); ifs.close(); }
3. 层次softmax的tree的构造 void Model::buildTree(const std::vector& counts) { tree.resize(2 * osz_ - 1); for (int32_t i = 0; i < 2 * osz_ - 1; i++) { tree[i].parent = -1; tree[i].left = -1; tree[i].right = -1; tree[i].count = 1e15; tree[i].binary = false; } for (int32_t i = 0; i < osz_; i++) { tree[i].count = counts[i]; } int32_t leaf = osz_ - 1; int32_t node = osz_; for (int32_t i = osz_; i < 2 * osz_ - 1; i++) { int32_t mini[2]; for (int32_t j = 0; j < 2; j++) { if (leaf >= 0 && tree[leaf].count < tree[node].count) { mini[j] = leaf--; } else { mini[j] = node++; } } tree[i].left = mini[0]; tree[i].right = mini[1]; tree[i].count = tree[mini[0]].count + tree[mini[1]].count; tree[mini[0]].parent = i; tree[mini[1]].parent = i; tree[mini[1]].binary = true; } for (int32_t i = 0; i < osz_; i++) { std::vector path; std::vector code; int32_t j = i; while (tree[j].parent != -1) { // 节点路径,即从root到label的路径 // 路径哈夫曼编码,即从root到label的路径的哈夫曼编码 // 后面会借用这两个变量,计算loss path.push_back(tree[j].parent - osz_); code.push_back(tree[j].binary); j = tree[j].parent; } paths.push_back(path); codes.push_back(code); } }
4. 负采样 void Model::initTableNegatives(const std::vector& counts) { real z = 0.0; for (size_t i = 0; i < counts.size(); i++) { z += pow(counts[i], 0.5); } for (size_t i = 0; i < counts.size(); i++) { real c = pow(counts[i], 0.5); for (size_t j = 0; j < c * NEGATIVE_TABLE_SIZE / z; j++) { negatives_.push_back(i); } } std::shuffle(negatives_.begin(), negatives_.end(), rng); }
5. 参数更新 void Model::update(const std::vector& input, int32_t target, real lr) { assert(target >= 0); assert(target < osz_); if (input.size() == 0) { return; } // 1. 计算隐层的输出值。如果是分类,则是labels_number * 1 // 如果是word2vec,则是V*1 computeHidden(input, hidden_); // 依据模型类型调用不同的接口计算loss if (args_->loss == loss_name::ns) { loss_ += negativeSampling(target, lr); } else if (args_->loss == loss_name::hs) { loss_ += hierarchicalSoftmax(target, lr); } else { loss_ += softmax(target, lr); } nexamples_ += 1; // 梯度计算,参数更新 if (args_->model == model_name::sup) { grad_.mul(1.0 / input.size()); } for (auto it = input.cbegin(); it != input.cend(); ++it) { wi_->addRow(grad_, *it, 1.0); } }
具体计算的代码这里就不分析了。
【六】总结
其余部分的代码(如:预测、评估等),这里就不分析了,顺着代码看就可以了。fasttext的代码结构还是比较简单的。代码阅读的难点在于算法的理解。后续再结合算法,对代码细节做分析。
fasttext是一个很好的工具,但要训练出一个合适的模型,需要对模型的参数有所理解,然而一般情况下,默认的参数就能满足要求了。
人工智能
2018-11-17 15:32:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
18/11/16 08:33:34 ERROR client.TransportResponseHandler: Still have 4 requests outstanding when connection from /100.66.0.1:52752 is closed18/11/16 08:33:34 ERROR spark.ContextCleaner: Error cleaning broadcast 687org.apache.spark.SparkException: Exception thrown in awaitResult:
人工智能
2018-11-16 16:37:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1 概述
“猜你喜欢”是美团流量最大的推荐展位,位于首页最下方,产品形态为信息流,承担了帮助用户完成意图转化、发现兴趣、并向美团点评各个业务方导流的责任。经过多年迭代,目前“猜你喜欢”基线策略的排序模型是业界领先的流式更新的Wide&Deep模型[1]。考虑Point-Wise模型缺少对候选集Item之间的相关性刻画,产品体验中也存在对用户意图捕捉不充分的问题,从模型、特征入手,更深入地理解时间,仍有推荐体验和效果的提升空间。近年来,强化学习在游戏、控制等领域取得了令人瞩目的成果,我们尝试利用强化学习针对以上问题进行优化,优化目标是在推荐系统与用户的多轮交互过程中的长期收益。
在过去的工作中,我们从基本的Q-Learning着手,沿着状态从低维到高维,动作从离散到连续,更新方式从离线到实时的路径进行了一些技术尝试。本文将介绍美团“猜你喜欢”展位应用强化学习的算法和工程经验。第2节介绍基于多轮交互的MDP建模,这部分和业务场景强相关,我们在用户意图建模的部分做了较多工作,初步奠定了强化学习取得正向收益的基础。第3节介绍网络结构上的优化,针对强化学习训练不稳定、难以收敛、学习效率低、要求海量训练数据的问题,我们结合线上A/B Test的线上场景改进了DDPG模型,取得了稳定的正向收益。第4节介绍轻量级实时DRL框架的工作,其中针对TensorFlow对Online Learning支持不够好和TF serving更新模型时平响骤升的问题做了一些优化。
图1 美团首页“猜你喜欢”场景

2 MDP建模
在“猜你喜欢“展位中,用户可以通过翻页来实现与推荐系统的多轮交互,此过程中推荐系统能够感知用户的实时行为,从而更加理解用户,在接下来的交互中提供更好的体验。“猜你喜欢”用户-翻页次数的分布是一个长尾的分布,在图2中我们把用户数取了对数。可知多轮交互确实天然存在于推荐场景中。
图2 “猜你喜欢”展位用户翻页情况统计
在这样的多轮交互中,我们把推荐系统看作智能体(Agent),用户看作环境(Environment),推荐系统与用户的多轮交互过程可以建模为MDP: State:Agent对Environment的观测,即用户的意图和所处场景。 Action:以List-Wise粒度对推荐列表做调整,考虑长期收益对当前决策的影响。 Reward:根据用户反馈给予Agent相应的奖励,为业务目标直接负责。 P(s,a):Agent在当前State s下采取Action a的状态转移概率。
图3 推荐系统与用户交互示意图
我们的优化目标是使Agent在多轮交互中获得的收益最大化:
具体而言,我们把交互过程中的MDP建模如下:
2.1 状态建模
状态来自于Agent对Environment的观察,在推荐场景下即用户的意图和所处场景,我们设计了如图4所示的网络结构来提取状态的表达。网络主要分为两个部分:把用户实时行为序列的Item Embedding作为输入,使用一维CNN学习用户实时意图的表达;推荐场景其实仍然相当依赖传统特征工程,因此我们使用Dense和Embedding特征表达用户所处的时间、地点、场景,以及更长时间周期内用户行为习惯的挖掘。
图4 状态建模网络结构
这里我们介绍一下使用Embedding特征表达用户行为习惯挖掘的Binary Sequence[2] 方法。我们通过特征工程对用户行为序列做各种维度的抽象,做成一些列离散的N进制编码,表示每一位有N种状态。例如统计用户在1H/6H/1D/3D/1W不同时间窗口内是否有点击行为编码成5位2进制数,把这些数字作为离散特征学习Embedding表达,作为一类特征处理方法。除此之外,还有点击品类是否发生转移、点击间隔的gap等等,在“猜你喜欢”场景的排序模型和强化学习状态建模中都取得了很不错的效果。原因是在行为数据非常丰富的情况下,序列模型受限于复杂度和效率,不足以充分利用这些信息,Binary Sequence可以作为一个很好的补充。
图5 序列模型和特征工程效果对照
图5左侧是序列模型的部分,分别使用不同的Pooling方式和一维CNN离线效果的对比,右侧是Dense和Embedding特征的部分,分别加入用户高频行为、距离、行为时间间隔、行为次数、意图转移等特征,以及加入所有显著正向特征的离线效果。
2.2 动作设计
“猜你喜欢”目前使用的排序模型由两个同构的Wide&Deep模型组成,分别以点击和支付作为目标训练,最后把两个模型的输出做融合。融合方法如下图所示:
图6 排序模型示意图
超参数 θ的物理意义是调整全量数据集中点击和下单模型的Trade Off,通过综合考虑点击和下单两个任务的AUC确定,没有个性化的因素。我们以此为切入点,使用Agent的动作调整融合超参数,令:
a是由Agent的策略生成Action,这样做有两个好处:其一,我们知道一个较优解是a=1,这种情况下强化学习策略和基线的排序策略保持一致,由于强化学习是个不断试错的过程,我们可以很方便地初始化Agent的策略为a=1,从而避免在实验初期伤害线上效果。其二,允许我们根据物理意义对Action做Clip,从而减轻强化学习更新过程不稳定造成的实际影响。
2.3 奖励塑形
“猜你喜欢”展位的优化核心指标是点击率和下单率,在每个实验分桶中分母是基本相同的,因此业务目标可以看成优化点击次数和下单次数,我们尝试将奖励塑形如下:
相对于关注每个Item转化效率的Point Wise粒度的排序模型,强化学习的目标是最大化多轮交互中的奖励收益,为业务目标直接负责。 图7 加入惩罚项前后的相对效果变化
在实验过程中我们发现,强化学习的策略可能上线初期效果很好,在点击和下单指标上都取得了一定的提升,但在后续会逐渐下降,如图7前半段所示。在逐层转化效率的分析中,我们发现强化学习分桶的设备曝光率和UV维度点击率有所降低,而用户停留时长和浏览深度稳定提升,这说明Agent学习到了让用户与推荐系统更多交互,从而获取更多曝光和转化机会的策略,但这种策略对于部分强烈下单意图用户的体验是有伤害的,因为这部分用户意图转化的代价变高了,因而对展位的期望变低。针对这种情况,我们在奖励塑形中加入两个惩罚项: 惩罚没有发生任何转化(点击/下单)行为的中间交互页面(penalty1),从而让模型学习用户意图转化的最短路; 惩罚没有发生任何转化且用户离开的页面(penalty2),从而保护用户体验。
修正后的奖励为:
由于用户体验是时间连续的,UV维度的效果在报表上有一定的滞后性,约一周后点击率和万订单恢复到正向水平,同时用户停留时长和浏览深度有进一步提升,说明Agent确实学到了在避免伤害用户的前提下,从多轮交互中获取更多转化的策略,如图7后半段所示。
这一节我们介绍了MDP建模相关的工作。MDP跟业务场景是强相关的,经验不是很容易迁移。就本文的场景而言,我们花了较多精力做状态表达的特征,这部分工作使强化学习得到了在自己的目标上取得正向收益的能力,因此对这部分介绍比较细致。动作设计是针对多目标模型融合的场景,是个业界普遍存在并且监督学习不太适用的场景,也能体现强化学习的能力。奖励塑形是为了缩小强化学习的目标和业务目标之间的Gap,需要在数据洞察和业务理解上做一些工作。完成上述工作后强化学习在自己的目标和业务指标上已经能取得了一些正向效果,但不够稳定。另外由于策略迭代是个Online Learning的过程,实验上线后需要实时训练一周才能收敛并观察效果,这也严重影响了我们的迭代效率。针对这些情况我们针对模型做了一些改进。
3 改进的DDPG模型
在模型方面,我们在不断改进MDP建模的过程中先后尝试了Q-Learning、DQN[3]和DDPG[4]模型,也面临着强化学习中普遍存在更新不够稳定、训练过程容易不收敛、学习效率较低(这里指样本利用效率低,因此需要海量样本)的问题。具体到推荐场景中,由于List-Wise维度的样本比Point-Wise少得多,以及需要真实的动作和反馈作为训练样本,因此我们只能用实验组的小流量做实时训练。这样一来训练数据量相对就比较少,每天仅有几十万,迭代效率较低。为此我们对网络结构做了一些改进,包括引入具体的Advantage函数、State权值共享、On-Policy策略的优化,结合线上A/B Test框架做了十数倍的数据增强,以及对预训练的支持。接下来我们以DDPG为基石,介绍模型改进的工作。
图8 DDPG模型
如图8所示,基本的DDPG是Actor-Critic架构。线上使用Actor网络,预测当前State下最好的动作a,并通过Ornstein-Uhlenbeck过程对预测的Action加一个随机噪声得到a',从而达到在最优策略附近探索的目的。将a' 作用于线上,并从用户(Environment)获得相应的收益。训练过程中,Critic学习估计当前状态s下采取动作a获得的收益,使用MSE作为Loss Function:
对参数求导:
Actor使用Critic反向传播的策略梯度,使用梯度上升的方法最大化Q估计,从而不断优化策略:
在确定性策略梯度的公式中,θ是策略的参数,Agent将使用策略μθ(s)在状态s 生成动作a,ρμ(指数关系)表示该策略下的状态转移概率。在整个学习过程中,我们不需要真的估计策略的价值,只需要根据Critic返回的策略梯度最大化Q估计。Critic不断优化自己对Q(s,a)的估计,Actor通过Critic的判断的梯度,求解更好的策略函数。如此往复,直到Actor收敛到最优策略的同时,Critic收敛到最准确的Q(s,a)估计。
接下来基于这些我们介绍的DDPG模型改进的工作。
3.1 Advantage函数
借鉴DDQN[5]的优势函数Advantage的思路,我们把critic估计的Q(s,a)拆分成两个部分:只与状态相关的V(s),与状态、动作都相关的Advantage函数A(s,a),有Q(s,a) = V(s) + A(s,a),这样能够缓解critic对Q过高估计的问题。具体到推荐环境中,我们的策略只是对排序模型的融合参数做调整,收益主要是由状态决定的。
图9 实验组与基线的Q值对比
如图9所示,在实际实验中观察V(s)和A(s,a)均值的比值大约为97:3,可以验证我们的这一判断。在实际训练过程中,我们先根据状态和收益训练V(s),再使用Q(s,a)-V(s)的残差训练A(s,a),很大程度上提升了训练稳定性,并且我们可以通过残差较为直观地观测到到当前策略是否优于基线。图8中A(s,a)稳定大于0,可以认为强化学习在自己的目标上取得了稳定的正向收益。
3.2 State权值共享
受A3C[6]网络的启发,我们观察到DDPG的网络中Actor和Critic网络中都有State的表达,而在我们的场景中大部分参数都集中在State的部分,在十万量级,其他参数只有数千,因此我们尝试把State部分的权重做共享,这样可以减少约一半的训练参数。 图10 使用advantage函数并做state权值共享
改进后的网络结构如图10所示。对于这个网络结构,我们注意到有V(s)的分支和动作不相关,意即我们不需要具体的Action也可以学习该State下Q的期望,这就允许我们在线下使用基线策略千万级的数据量做 预训练 ,线上也同时使用基线和实验流量做实时更新,从而提升训练的效果和稳定性。又因为这条更新路径包含了所有State的参数,模型的大部分参数都可以得到充分的预训练,只有Action相关的参数必须依赖Online Learning的部分,这就大幅提高了我们的实验迭代效率。原来我们需要上线后等待一周训练再观察效果,改进后上线第二天就可以开始观察效果。
3.3 On-policy
在A2C[7]的论文里作者论述了他们的见解:同步A2C实现比异步实现的A3C表现要好。目前尚未看到任何证据证明异步引入的噪声能够提供任何性能收益,因此为了提升训练效率,我们采取了这个做法,使用同一套参数估计Q_{t+1}和更新Q_t,从而使模型参数再次减半。
3.4 扩展到多组并行策略
考虑多组强化学习实验同时在线的情况,结合A/B Test环境特点,我们把以上网络框架扩展到多Agent的情况。 图11 支持多组线上实验DDPG模型
如图11所示,线上多组实验共享State表达和V(s)的估计,每个策略训练自己的A(s,a)网络且能快速收敛,这样的结构一方面使训练过程更加稳定,另一方面为强化学习策略全量提供了可能性。
图12 点击率分天实验效果
在DDPG的改造工作中,我们使用Advantage函数获得更稳定的训练过程和策略梯度。State权值共享和On-Policy方法使我们的模型参数减少75%。Advantage函数和State权值共享结合,允许我们使用基线策略样本做数据增强,使每天的训练样本从十万量级扩展到百万量级,同时充分的预训练保证策略上线后能迅速收敛。经过这些努力,强化学习线上实验取得了稳定的正向效果,在下单率效果持平的情况下,周效果点击率相对提升0.5%,平均停留时长相对提升0.3%,浏览深度相对提升0.3%。修改过的模型与A2C的主要区别是我们仍然使用确定性策略梯度,这样我们可以少估计一个动作的分布,即随机策略方差降至0的特例。图12表明强化实习的效果是稳定的,由于“猜你喜欢”的排序模型已经是业界领先的流式DNN模型,我们认为这个提升是较为显著的。
4 基于TF的轻量级实时DRL系统
强化学习通常是在一次次试错(Trial-and-Error)中学习,实时地改进策略并获得反馈能大幅提升学习效率,尤其在连续策略中。这一点在游戏场景下很容易理解,相应地,我们也在推荐系统中构建了实时深度学习系统,让策略更新更加高效。为了支持实时更新的DRL模型和高效实验,我们针对Online Learning的需求,基于TensorFlow及TF Serving做了一些改进和优化,设计并实现了一套特征配置化的实时更新的DRL框架,在实验迭代过程中沉淀了DQN、DDQN、DDPG、A3C、A2C、PPO[8]等模型。系统架构如图13所示: 图13 实时更新的强化学习框架
训练部分工作流如下: Online Joiner从Kafka中实时收集特征和用户反馈,拼接成Point-Wise粒度的Label-Feature样本,并把样本输出到Kafka和HDFS,分别支持在线和离线更新。 Experience Collector收集上述样本,合并为List-Wise的请求粒度,并根据请求时间戳拼接成[]列表形式的MC Episode,再经过状态转移计算拆分成 形式的TD Instance,输出MC或TD格式的样本支持RL训练。 Trainer做输入特征的预处理,使用TensorFlow训练DRL模型。 Version Controller负责调度任务保证实效性和质量,并把训练完成且指标符合预期模型推送到TF Serving和Tair中,这部分只需要Actor相关的参数。Tair作为弥补TF在Online Learning短板辅助的PS,后面会具体介绍。 Monitor监控和记录整个训练流程中的数据量和训练指标,对不符合预期的情况发出线上告警。 新模型上线前会先做离线的Pre-Train,使用基线策略的数据学习State的表达和Value net。上线后实时同时更新Actor,Advantage和Value的参数。
线上预测部分,推荐系统的Agent从Tair获取预处理参数,并将处理后的特征喂给TF Serving做前向传播,得到Action并对展现给用户的排序结果做相应的干预。
针对TensorFLow对Online Learning支持比较弱,Serving对千万级Embedding处理效率不高的问题,我们做了一些改进: 在线上特征的分布会随时间而改变,针对Dense特征我们自己维护增量的Z-Score算法对特征做预处理。 Embedding特征的输入维度也经常发生变化,而TF不支持变长的Input Dimention,为此我们维护了全量的ID-Embedding映射,每次训练让模型加载当前样本集合中的高频Embedding。 千万级Item Embedding会大幅降低训练和预测的效率,为此我们把这部分映射在预处理中,把映射后的矩阵直接作为CNN的输入。 为了提升特征工程的实验效率,支持特征配置化生成模型结构。 此外,TF serving在更新模型一两分钟内响应时间会骤然升高,导致很多请求超时,原因有二,其一,serving的模型加载和请求共用一个线程池,导致切换模型使阻塞处理请求;其二,计算图初始化是lazy的,这样新模型后的第一次请求需要等待计算图初始化。这个问题在更新模型频Low对online learning支持比较弱繁的Online Learning场景影响较大,我们采用切分线程池和warm up初始化的方式解决。更具体的方案和效果可以参考美团另一篇技术博客[9]。
5 总结和展望
强化学习是目前深度学习领域发展最快的方向之一,其与推荐系统和排序模型的结合也有更多价值等待发掘。本文介绍了强化学习在美团“猜你喜欢”排序场景落地的工作,包括根据业务场景不断调整的MDP建模,使强化学习能够取得一定的正向收益;通过改进DDPG做数据增强,提升模型的鲁棒性和实验效率,从而取得稳定的正向收益;以及基于TensorFlow的实时DRL框架,为高效并行策略迭代提供了基础。
经过一段时间的迭代优化,我们在强化学习方面也积累了一些经验,与传统的监督学习相比,强化学习的价值主要体现在: 灵活的奖励塑形,能支持各种业务目标建模,包括不限于点击率、转化率、GMV、停留时长、浏览深度等,支持多目标融合,为业务目标直接负责。 充满想象空间的动作设计,不需要直接的Label,而是通过网络来生成和评价策略,适合作为监督学习的补充。这点和GAN有相通之处。 考虑优化长期收益对当前决策造成的影响,Agent与Environment交互频繁的场景更加能体现强化学习的价值。
同时强化学习作为机器学习的一个分支,很多机器学习的经验仍然适用于此。比如数据和特征决定效果的上限,模型和算法只是不断逼近它。对于强化学习而言特征空间主要包含在状态的建模中,我们强烈建议在状态建模上多做一些尝试,并信任模型有能力从中做出判断。再如,使用更多的训练数据降低经验风险,更少的参数降低结构风险的思路对强化学习仍然适用,因此我们认为DDPG的改进工作能够推广到不同业务的线上A/B Test场景中。此外,我们在训练过程中也遇到了强化学习对随机性敏感的问题[10],为此我们线上使用了多组随机种子同时训练,选择表现最好的一组参数用于实际参数更新。
在目前的方案中,我们尝试的Action是调整模型融合参数,主要考虑这是个排序问题中比较通用的场景,也适合体现强化学习的能力,而实际上对排序结果的干预能力是比较有限的。未来我们会探索不同品类、位置、价格区间等跟用户意图场景强相关属性的召回个数,调整排序模型隐层参数等方向。另外在解决学习效率低下的问题上,还将尝试Priority Sampling 提高样本利用效率,Curious Networks提升探索效率等方法。也欢迎对强化学习感兴趣的朋友们与我们联系,一起交流探索强化学习在工业界的应用与发展,同时对文章的错漏之处也欢迎大家批评指正。
参考文献
[1] Heng-Tze Cheng, Levent Koc, Jeremiah Harmsen, Tal Shaked, Tushar Chandra, Hrishi Aradhye, Glen Anderson, Greg Corrado, Wei Chai, Mustafa Ispir, Rohan Anil, Zakaria Haque, Lichan Hong, Vihan Jain, Xiaobing Liu, and Hemal Shah. Wide & deep learning for recommender systems. CoRR, 2016. [2] Yan, P., Zhou, X., Duan, Y. E-commerce item recommendation based on field-aware Factorization Machine. In: Proceedings of the 2015 International ACM Recommender Systems Challenge, 2015. [3] Mnih, Volodymyr, Kavukcuoglu, Koray, Silver, David, Rusu, Andrei A, Veness, Joel, Bellemare, Marc G, Graves, Alex, Riedmiller, Martin, Fidjeland, Andreas K, Ostrovski, Georg, et al. Humanlevel control through deep reinforcement learning. Nature, 2015. [4] Lillicrap, T., Hunt, J., Pritzel, A., Heess, N., Erez, T., Tassa, Y., Silver, D., and Wierstra, D. Continuous control with deep reinforcement learning. In International Conference, 2015 on Learning Representations, 2016. [5] Wang, Z., de Freitas, N., and Lanctot, M. Dueling network architectures for deep reinforcementlearning. Technical report, 2015. [6] Volodymyr Mnih, Adri`a Puigdom`enech Badia, Mehdi Mirza, Alex Graves, Tim-othy P. Lillicrap, Tim Harley, David Silver, and Koray Kavukcuoglu. Asyn-chronous methods for deep reinforcement learning. ICML, 2016 [7] Y. Wu, E. Mansimov, S. Liao, R. Grosse, and J. Ba. Scalable trust-region method for deep reinforcementlearning using kronecker-factored approximation. arXiv preprint arXiv:1708.05144, 2017. [8] Schulman, J.; Wolski, F.; Dhariwal, P.; Radford, A.; and Klimov,O. Proximal policy optimization algorithms. arXiv preprintarXiv:1707.06347, 2017 [9] 仲达, 鸿杰, 廷稳. 基于TensorFlow Serving的深度学习在线预估. MT Bolg, 2018 [10] P. Henderson, R. Islam, P. Bachman, J. Pineau, D. Precup, and D. Meger. Deep reinforcement learningthat matters. arXiv:1709.06560, 2017.
作者简介
段瑾,2015年加入美团点评,目前负责强化学习在推荐场景的落地工作。
人工智能
2018-11-16 11:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
hanlp的词典模式
之前我们看了hanlp的词性标注,现在我们就要使用自定义词典与停用词功能了,首先关于HanLP的词性标注方式具体请看HanLP词性标注集。
其核心词典形式如下:
自定义词典
自定义词典有多种添加模式,首先是展示的一个小例子,展示了词汇的动态增加与强行插入,删除等。更复杂的内容请参考后边的第二段代码。
简单的例子
from pyhanlp import *
text = "攻城狮逆袭单身狗,迎娶白富美,走上人生巅峰" # 怎么可能噗哈哈!
print(HanLP.segment(text))
CustomDictionary = JClass("com.hankcs.hanlp.dictionary.CustomDictionary")
CustomDictionary.add("攻城狮") # 动态增加
CustomDictionary.insert("白富美", "nz 1024") # 强行插入
#CustomDictionary.remove("攻城狮"); # 删除词语(注释掉试试)
CustomDictionary.add("单身狗", "nz 1024 n 1")

# 展示该单词词典中的词频统计 展示分词
print(CustomDictionary.get("单身狗"))
print(HanLP.segment(text))

# 增加用户词典,对其他分词器同样有效
# 注意此处,CRF分词器将单身狗分为了n 即使单身狗:"nz 1024 n 1"
CRFnewSegment = HanLP.newSegment("crf")
print(CRFnewSegment.seg(text))
[攻城狮, 逆袭, 单身狗, ,, 迎娶, 白富美, ,, 走上, 人生, 巅峰]
nz 1024 n 1
[攻城狮, 逆袭, 单身狗, ,, 迎娶, 白富美, ,, 走上, 人生, 巅峰]
[攻城, 狮逆袭, 单身狗, ,, 迎娶, 白富美, ,, 走, 上, 人生, 巅峰]
复杂的例子

""" 演示自定义词性,以及往词典中插入自定义词性的词语
!!!由于采用了反射技术,用户需对本地环境的兼容性和稳定性负责!!!

TO-DO
如果使用了动态词性之后任何类使用了switch(nature)语句,必须注册每个类
"""
# 对于系统中已有的词性,可以直接获取
Nature = JClass("com.hankcs.hanlp.corpus.tag.Nature")
pc_nature = Nature.fromString("n")
print(pc_nature)
# 此时系统中没有"电脑品牌"这个词性
pc_nature = Nature.fromString("电脑品牌")
print(pc_nature)
# 我们可以动态添加一个
pc_nature = Nature.create("电脑品牌");
print(pc_nature)
# 可以将它赋予到某个词语
LexiconUtility = JClass("com.hankcs.hanlp.utility.LexiconUtility")
LexiconUtility.setAttribute("苹果电脑", pc_nature)
# 或者
LexiconUtility.setAttribute("苹果电脑", "电脑品牌 1000")
# 它们将在分词结果中生效
term_list = HanLP.segment("苹果电脑可以运行开源阿尔法狗代码吗")
print(term_list)
for term in term_list:
if term.nature == pc_nature:
print("找到了 [{}] : {}\n".format(pc_nature, term.word))

# 还可以直接插入到用户词典
CustomDictionary = JClass("com.hankcs.hanlp.dictionary.CustomDictionary")
CustomDictionary.insert("阿尔法狗", "科技名词 1024")
StandardTokenizer = JClass("com.hankcs.hanlp.tokenizer.StandardTokenizer")
StandardTokenizer.SEGMENT.enablePartOfSpeechTagging(True) # 依然支持隐马词性标注
term_list = HanLP.segment("苹果电脑可以运行开源阿尔法狗代码吗")
print(term_list)
n
None
电脑品牌
[苹果电脑/电脑品牌, 可以/v, 运行/vn, 开源/v, 阿尔法/nrf, 狗/n, 代码/n, 吗/y]
找到了 [电脑品牌] : 苹果电脑

[苹果电脑/电脑品牌, 可以/v, 运行/vn, 开源/v, 阿尔法狗/科技名词, 代码/n, 吗/y]
关于自定义词典的说明(原作者的原文)
说明 CustomDictionary是一份全局的用户自定义词典,可以随时增删,影响全部分词器。另外可以在任何分词器中关闭它。通过代码动态增删不会保存到词典文件。 中文分词≠词典,词典无法解决中文分词,Segment提供高低优先级应对不同场景,请参考FAQ。
追加词典 CustomDictionary主词典文本路径是data/dictionary/custom/CustomDictionary.txt,用户可以在此增加自己的词语(不推荐);也可以单独新建一个文本文件,通过配置文件CustomDictionaryPath=data/dictionary/custom/CustomDictionary.txt; 我的词典.txt;来追加词典(推荐)。 始终建议将相同词性的词语放到同一个词典文件里,便于维护和分享。
词典格式 每一行代表一个单词,格式遵从[单词] [词性A] [A的频次] [词性B] [B的频次] ... 如果不填词性则表示采用词典的默认词性。 词典的默认词性默认是名词n,可以通过配置文件修改:全国地名大全.txt ns;如果词典路径后面空格紧接着词性,则该词典默认是该词性。 在统计分词中,并不保证自定义词典中的词一定被切分出来。用户可在理解后果的情况下通过Segment#enableCustomDictionaryForcing强制生效。 关于用户词典的更多信息请参考词典说明一章(请看本文最后)。
停用词
关于停用词,我同样先给出了一个简单的例子,你可以使用这个例子来完成你所需要的功能。要注意的一点是,因为java中的类所返回的数据类型与Python不统一,所以当你使用不同的函数的时候,一定要先检查输出结果在Python中的类型,不然可能会出现意想不到的问题。
假如你想了解更多,可以看第二个更复杂的例子。
简单的例子
# 使用停用词的简单例子

text = "小区居民有的反对喂养流浪猫"
CRFnewSegment = HanLP.newSegment("crf")
term_list = CRFnewSegment.seg(text)
# BasicTokenizer = SafeJClass("com.hankcs.hanlp.tokenizer.BasicTokenizer")
# term_list = BasicTokenizer.segment(text)

CoreStopWordDictionary = JClass("com.hankcs.hanlp.dictionary.stopword.CoreStopWordDictionary")
CoreStopWordDictionary.apply(term_list)
HanLP.Config.ShowTermNature = False

print(term_list)
print([i.word for i in term_list])
[小区, 居民, 反对, 养, 流, 浪, 猫]
['小区', '居民', '反对', '养', '流', '浪', '猫']
复杂的例子
# 停用词

# 在import pyhanlp之前编译自己的Java class,并放入pyhanlp/static中
import os

from pyhanlp.static import STATIC_ROOT, HANLP_JAR_PATH

java_code_path = os.path.join(STATIC_ROOT, 'MyFilter.java')
with open(java_code_path, 'w') as out:
java_code = """
import com.hankcs.hanlp.dictionary.stopword.CoreStopWordDictionary;
import com.hankcs.hanlp.dictionary.stopword.Filter;
import com.hankcs.hanlp.seg.common.Term;

public class MyFilter implements Filter
{
public boolean shouldInclude(Term term)
{
if (term.nature.startsWith('m')) return true; // 数词保留
return !CoreStopWordDictionary.contains(term.word); // 停用词过滤
}
}
"""
out.write(java_code)
os.system('javac -cp {} {} -d {}'.format(HANLP_JAR_PATH, java_code_path, STATIC_ROOT))
# 编译结束才可以启动hanlp

CoreStopWordDictionary = JClass("com.hankcs.hanlp.dictionary.stopword.CoreStopWordDictionary")
Filter = JClass("com.hankcs.hanlp.dictionary.stopword.Filter")
Term = JClass("com.hankcs.hanlp.seg.common.Term")
BasicTokenizer = JClass("com.hankcs.hanlp.tokenizer.BasicTokenizer")
NotionalTokenizer = JClass("com.hankcs.hanlp.tokenizer.NotionalTokenizer")

text = "小区居民有的反对喂养流浪猫,而有的居民却赞成喂养这些小宝贝"
# 可以动态修改停用词词典
CoreStopWordDictionary.add("居民")
print(NotionalTokenizer.segment(text))
CoreStopWordDictionary.remove("居民")
print(NotionalTokenizer.segment(text))

# 可以对任意分词器的结果执行过滤
term_list = BasicTokenizer.segment(text)
print(term_list)
CoreStopWordDictionary.apply(term_list)
print(term_list)

# 还可以自定义过滤逻辑
MyFilter = JClass('MyFilter')
CoreStopWordDictionary.FILTER = MyFilter()
print(NotionalTokenizer.segment("数字123的保留")) # “的”位于stopwords.txt所以被过滤,数字得到保留
[小区/n, 反对/v, 喂养/v, 流浪猫/nz, 赞成/v, 喂养/v, 小宝贝/nz]
[小区/n, 居民/n, 反对/v, 喂养/v, 流浪猫/nz, 居民/n, 赞成/v, 喂养/v, 小宝贝/nz]
[小区/n, 居民/n, 有/vyou, 的/ude1, 反对/v, 喂养/v, 流浪猫/nz, ,/w, 而/cc, 有的/rz, 居民/n, 却/d, 赞成/v, 喂养/v, 这些/rz, 小宝贝/nz]
[小区/n, 居民/n, 反对/v, 喂养/v, 流浪猫/nz, 居民/n, 赞成/v, 喂养/v, 小宝贝/nz]
[数字/n, 123/m, 保留/v]
词典说明(原作者原文)
本章详细介绍HanLP中的词典格式,满足用户自定义的需要。HanLP中有许多词典,它们的格式都是相似的,形式都是文本文档,随时可以修改。
基本格式
词典分为词频词性词典和词频词典。
词频词性词典(如CoreNatureDictionary.txt) 每一行代表一个单词,格式遵从[单词] [词性A] [A的频次] [词性B] [B的频次] ...。 支持省略词性和频次,直接一行一个单词。 .txt词典文件的分隔符为空格或制表符,所以不支持含有空格的词语。如果需要支持空格,请使用英文逗号,分割的纯文本.csv文件。在使用Excel等富文本编辑器时,则请注意保存为纯文本形式。
词频词典(如CoreNatureDictionary.ngram.txt) 每一行代表一个单词或条目,格式遵从[单词] [单词的频次]。 每一行的分隔符为空格或制表符。
少数词典有自己的专用格式,比如同义词词典兼容《同义词词林扩展版》的文本格式,而转移矩阵词典则是一个csv表格。
下文主要介绍通用词典,如不注明,词典特指通用词典。
数据结构
Trie树(字典树)是HanLP中使用最多的数据结构,为此,我实现了通用的Trie树,支持泛型、遍历、储存、载入。
用户自定义词典采用AhoCorasickDoubleArrayTrie和二分Trie树储存,其他词典采用基于双数组Trie树(DoubleArrayTrie)实现的AC自动机AhoCorasickDoubleArrayTrie。关于一些常用数据结构的性能评估,请参考wiki。
储存形式
词典有两个形态:文本文件(filename.txt)和缓存文件(filename.txt.bin或filename.txt.trie.dat和filename.txt.trie.value)。
文本文件 采用明文储存,UTF-8编码,CRLF换行符。
缓存文件 就是一些二进制文件,通常在文本文件的文件名后面加上.bin表示。有时候是.trie.dat和.trie.value。后者是历史遗留产物,分别代表trie树的数组和值。 如果你修改了任何词典,只有删除缓存才能生效。
修改方法
HanLP的核心词典训练自人民日报2014语料,语料不是完美的,总会存在一些错误。这些错误可能会导致分词出现奇怪的结果,这时请打开调试模式排查问题:( 本文作者FontTian注:在本文动笔前,原词典一进变为了9970万版本的最大中文语料。但是词典说明中原作者没改)
HanLP.Config.enableDebug();
核心词性词频词典 比如你在data/dictionary/CoreNatureDictionary.txt中发现了一个不是词的词,或者词性标注得明显不对,那么你可以修改它,然后删除缓存文件使其生效。 目前CoreNatureDictionary.ngram.txt的缓存依赖于CoreNatureDictionary.txt的缓存,修改了后者之后必须同步删除前者的缓存,否则可能出错
核心二元文法词典 二元文法词典data/dictionary/CoreNatureDictionary.ngram.txt储存的是两个词的接续,如果你发现不可能存在这种接续时,删掉即可。 你也可以添加你认为合理的接续,但是这两个词必须同时在核心词典中才会生效。
命名实体识别词典 基于角色标注的命名实体识别比较依赖词典,所以词典的质量大幅影响识别质量。 这些词典的格式与原理都是类似的,请阅读相应的文章或代码修改它。

文章来源 FonTIan 的博客
人工智能
2018-11-16 09:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
条件概率
事物A独立发生的概率为 ,事物B独立发生的概率为 ,那么有:
表示事物B发生之后事物A发生的概率;
表示事物A发生之后事物B发生的概率;
全概率
我们可以将公式写成全量的形式:
表示全量相互排斥且性质关联的事物,即:
,
那么可以得到
,这就是全概率公式。
全概率公式的意义在于:无法知道一个事物独立发生的概率,但是我们可以将其在各种条件下发生的概率进行累加获得。
全概率的例子
例1,已知某种疾病的发病率是0.001,即1000人中会有1个人得病。现有一种试剂可以检验患者是否得病,它的准确率是0.99,即在患者确实得病的情况下,它有99%的可能呈现阳性。它的误报率是5%,即在患者没有得病的情况下,它有5%的可能呈现阳性。一个人检测为阳性的概率是多少。

例2,袋子中50个球,20个黄球,30个白球。2个人一次从袋中各获取一个球,且不放回,求第二个人取得黄球的概率。

从另外一个角度说,无论前面的人抽了多少次,后面的人抽签总体概率是不变的。
例3,5张卡片上分别标记了1,2,3,4,5,每次取2张,连续取2次,取出后不放回。求第二次取出的卡片,比第一次取出的卡片大的概率。



例4,甲袋有5只白球、7个红球,乙袋有4只白球、2只红球。任意取一个袋子,求从袋子取得白球的概率。


*贝叶斯公式
贝叶斯公式的理解 :
可以理解他是全概率公式的反向应用,他是求某个条件出现时某个事件发生的概率。定义如下:

沿用前面医学的例子:
例1,已知某种疾病的发病率是0.001,即1000人中会有1个人得病。现有一种试剂可以检验患者是否得病,它的准确率是0.99,即在患者确实得病的情况下,它有99%的可能呈现阳性。它的误报率是5%,即在患者没有得病的情况下,它有5%的可能呈现阳性。一个人检测为阳性时候,他确切患病的几率是多少。

从结论看,这个试剂挺不可靠的。
将贝叶斯公式的底部展开为全概率公式:
使用全概率公式展开之后有个很直观的发现: 当我们考察某一个事件的条件概率时——事件 发生之后 发生的概率,需要将整个样本空间中其他概率事件也加入到其中来。
似然函数
似然函数个人理解是一种更加“公式化”的条件概率表达式,因为他书写的形式和条件概率相比并没有太大区别—— ,只是解读方式不同。这里的 表示样本特征数据, 表示模型参数。
如果 已知并且固定,那么表示这个是一个概率计算模型,表示:不同的样本 在固定的模型参数 的概率值。
如果 已经并且固定,表示这是一个似然计算模型(统计模型),表示不同的样本用于求解模型参数 。
极大似然估计
按照前面似然函数 的介绍,似然函数可以看做 是已知的, 是未知的,极大似然估计就是在已知 的情况下求取 。
在现实的生产生活中也常常会遇到这样的问题。我们以及有了 样本 以及对应的 标签(结论) ,如何根据这些样本来计算(推算)条件 是一件很困难的事情。而极大似然估计就是一个根据样本值 和结论数据 计算条件参数 的过程。
总的来说,极大似然估计是一种 参数估计算法 。使用极大似然估计有一个很重要的先决条件——每 一组样本都是独立的,并且有充分的训练样本 。
先看看样本独立的判断公式: ,即2个事物同时发生的概率等于事物独立发生概率的乘积。
极大似然评估的公式及像这个公式。
设有一组样本 ,所有样本的联合概率密度 称为相对于样本 的似然函数。那么由独立判定公式推断出所有样本的概率为:

设 是使得 取得最大值的 值,那么 是 的极大似然估计量。可以使用下面的公式表示 与 的关系:
,
实际计算时,计算连乘比较麻烦,我们可以引入对数将其转换为一个求和的过程:
,因为 。 也称为对数似然函数。
如果 连续可微,那么可以使用导数为0求函数的凸点。即:

将条件因子扩展为M个,即 ,则似然函数(对数似然函数变成):
此时每一个 的求导变成一个求偏导数的过程:
,每一个 都要对 求导。
最大似然评估的案例
最大似然评估计算
最大似然评估(也称为极大似然评估)的用处是什么?首先可以将每个字眼拆解开来看。 最大 就是要找最大值 ,似然 说明并不精确似乎就是这个值 ,评估 指的是这是一个过程。
现实生活中的例子:2对夫妇 和 和一个小孩 。从外观上看,小孩 长相比较接近夫妇 ,有点像 ,不像 ,让你猜测 是谁的小孩。思维正常一点的人肯定会说 是 的小孩,这本身就是一个自然而然的判断过程,用数学解释:
使用似然评估,就可以断定小孩更像谁:

参数&模型评估
最大似然估计更多的应用是在有一定样本数据的情况下用于模型评估,更准确的说是模型中的参数评估。因为似然评估来自于概率独立判决公式—— ,所以要求用于评估的样本数据相互独立。
先说一个很直观的案例解释这个问题:
例1,从盒子里连续取球,已知取得红球的概率 ,求当P取何值时最有可能连续三次拿到红球。
只管上来说,肯定是概率越高取得红球的几率越高,所以不做推断也知道 时拿到红球的几率更高。下面通过数学过程来说明这个问题。
设条件 ,表示取得红球, 表示没取得红球,所以用最大似然评估来计算参数得:
,只管的看就知道取值0.5似然评估最大。
人工智能
2018-11-15 14:55:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 1.前言 闲鱼质量团队一直致力于交付高质量的app给用户,当前随着AI技术不断发展,TensorFlow大热,也给测试手段带来了更多种可能,本文接下来给大家介绍AI在闲鱼测试的一点实践:如何应用AI技术通过图片找bug。
作者:闲鱼技术-镇雷
1.前言
闲鱼质量团队一直致力于交付高质量的app给用户,当前随着AI技术不断发展,TensorFlow大热,也给测试手段带来了更多种可能,本文接下来给大家介绍AI在闲鱼测试的一点实践:如何应用AI技术通过图片找bug。
2.模型选型
不需要理解业务就能发现的bug主要有整体页面空白、部分控件显示异常和文本异常这几类。对于整体空白图片,发现它们的共同特征是比较明显:大面积空白或者中心区域报错,所以选择使用TensorFlow搭建的简单CNN模型来识别正常图片和异常图片。对于文本异常这类包含乱码的图片,则是用OCR+LSTM建立了一个简单的汉字识别模型来识别图片中的文本内容后判断是否存在乱码。
训练以上模型的样本则来源于bug历史截图和mock的正向数据样本。
3.模型重训练——提高模型识别准确率
初始模型在训练时样本有限,但随着app不停更新迭代,图片检测样本数量的逐渐增多,会出现某些新页面被错误分类,要解决这类误报问题,亟需加入模型重训练。
显然靠人肉启动模型重训练并替换旧模型成本太高,所以在前端实现了个勾选图片去重训练的入口,通过Jenkins定时任务,读取所有重训练图片并执行重训练脚本,并把旧模型替换成新生成的即可。经过几轮自动迭代后模型识别准确率有大幅提升。
4.图片处理——提升结果的人工甄别效率
4.1特殊截图
有些截图可能是存在大面积空白,但从业务角度上来说这类图片是正确的,比如搜索中间页。此类若不处理,每次都会被识别为异常图片上报,浪费大家check的时间,若放入模型进行重训练又有导致模型不收敛的风险。为了解决这类图片,维护了一个图库,对模型识别为异常的图片,会与图库中的图片进行对比,如果与图库中任意一张相似度超过设定阈值,即认为该图片可被忽略不用上报。
4.2图片去重
目前遍历截图任务为了保证页面上所有元素都取到,单次遍历任务会至少访问同一个页面两次;同时遍历时,为了方便页面上下文分析,会对点击元素用红框标记。这带来了一个问题:待识别图片集中,同一个页面会有多张重复截图、且同一页面上可能会在不同的地方有红框标记。人工检验大量重复图片识别结果难免视觉疲劳,所以展示去重后的结果可以大大提升人工甄别效率,减少成本。
4.2.1解决办法
图片数量大,且不确定这次遍历截图了多少不同页面时,可以使用层次聚类算法解决这个问题。本文采用的是自底向上的聚类方法,即先将每一张截图分别看成一个簇,然后找出距离最小的两个簇进行合并,不断重复到预期簇或者满足其他终止条件。
4.2.2实现[2]
1)计算图片之间的距离
先将图片转换成w*h*3维向量,把向量间的欧式距离作为图片之间的距离,图片越相似,距离越小。 def get_pic_array(url,w,h): file = cStringIO.StringIO(urllib2.urlopen(url).read()) img = Image.open(file) # PIL打开图片 img=img.resize((w, h)) try: r, g, b, k = img.split() # rgb通道分离,兼容4通道情况 except ValueError: r, g, b = img.split() # 获得长度为(w*h)的一维数组 r_arr = np.array(r).reshape(w * h) g_arr = np.array(g).reshape(w * h) b_arr = np.array(b).reshape(w * h) #将RGB三个一维数组(w*h)拼接成一个一维数组(w*h*3) image_arr = np.concatenate((r_arr, g_arr, b_arr)) return image_arr
一次app遍历得到的n张图片要完成聚类,先单张图片按照上述处理后,再整体拼接成 n*(w*h*3)的矩阵,做为样本集。
2)计算簇之间距离的方法
single:两个簇中距离最近的两个样本的距离作为这簇间的距离
complete:两个簇中距离最远的两个样本的距离作为这簇间的距离
average:两个簇间样本两两距离的平均值决定,解决个别异常样本对结果对影响,但计算量比较大
ward:离差平方和,计算公式较复杂,要想了解具体计算公式和其他计算方法见 计算簇之间距离的方法 。
通过尝试后发现ward效果比较好,所以最终选用ward作为计算簇之间距离的方法。 Z = linkage(X, 'ward')
执行上述语句后,聚类完成。
3)临界距离选择
该值直接影响聚类的效果,临界距离过小,会导致某些相似图片不能聚集到一类,临界距离过大,又会导致不是同一个页面的图片聚在一起,所以如何选一个合适的距离非常重要。
实验发现,如果图片被页面异常模型识别为异常图片时,往往这类图片之间的相似性越高,为了不错误聚类不同的异常页面,分别对识别为异常和正常的图片进行聚类,并且异常类的临界距离会设置更小一点。
5.总结与展望
目前该工具对整体页面异常的识别效果较好,文本异常的识别准确率也在丰富样本的过程中不断提升。

接下来我们会集成LabelImg工具,用TensorFlow搭建SSD模型来识别控件异常的图片,此外元素/文字布局错乱等问题页面识别、页面操作预期结果识别也在不断尝试中。使用图片处理和错误识别技术,作为质量保证的一种方法,我们会持续探索下去。
参考文档:
[1]图片聚类计算: https://haojunsui.github.io/2016/07/16/scipy-hac/
原文链接
人工智能
2018-11-15 11:53:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
msg : msg files are simple text files that describe the fields of a ROS message. They are used to generate source code for messages in different languages.
srv : an srv file describes a service. It is composed of two parts: a request and a response.

devel目录是执行caikin_make install命令安装路径。C++版本在 include文件夹,Python 在 lib,Lisp在share文件夹。
人工智能
2018-11-15 10:18:00
12345678910