awk

awk是最有用的shell工具之一,名字的来源是三位工程师的名字首字母。

awk introduction

awk主要被用在文字和字符处理领域,作为一个通用的可编程过滤器。

awk面向域(field)而sed面向行(line),awk支持以文件、重定向、管道和标准输入作为程序的输入。
更准确的来讲,awk是一种仅需几行代码就能够处理复杂任务的编程语言,和sed一样,它是一种pattern-action语言,看起来和C语言类似,但是比C语言简单,它能够自动处理输入、域分割、初始化和内存管理等。

awk相比较于sed的特色:

  • 方便的数字处理
  • 在action中的变量和控制流
  • 更便于访问行中的域
  • 灵活的输出
  • 内置丰富的算术和字符串函数
  • 类C风格

awk的程序结构

一段awk程序包括:

  • 一段可选的BEGIN语句块(主要用于做一些预处理)
  • 一些pattern-action组合
  • 一段可选的END语句块(主要用于做一些后处理)
1
2
3
4
5
BEGIN {action}
pattern {action}
...
pattern {action}
END {action}

有几种方式运行一段awk程序:
a. awk ‘program’ input_files
b. awk ‘program’ 标准输入作为程序输入
c. awk -f ‘program_files’ input_files

pattern & action

awk从一系列输入文件中去搜索指定的pattern,然后执行特定的action当在遇到包含特定pattern的行或者域。
awk不会修改输入文件,一次只处理一行。

一个awk程序至少有一个pattern或者action

  • 默认的pattern是匹配所有的lines
  • 默认的action是打印当前的record
  • action包含在{}内,而pattern直接列出。

patterns

pattern是一个决定是否后面action是否被执行的选择器。
pattern可以是:

  • 特殊测token,例如BEGIN和END
  • 正则表达式 包含在//里面 例如 /[a-z]/
  • 字符串匹配表达式
  • !表达式取反
  • 以及通过&& 、|| 链接符连接的上述任意表达式
    • /NYU/
    • x > 0
    • /NYU/ && (name == ‘UNIX Tools’)

特殊的pattern token

  • BEGIN:预处理,常用于初始化一些内置变量例如FS、RS等;
  • END:后处理

actions

action可能包含一系列的类C的代码语句,执行一些类似算术、字符串表达式、声明以及输出。
action当每一行匹配一个pattern就会被执行,如果不指定pattern,action无条件执行,如果action不被指定,默认action是输出到标准设备。

例子:

1
2
3
4
ls|awk 'BEGIN {print "List all C language source files."}
/\.c$/ {print}
END {print "List has been done!"}
'

varaibles

作为一门语言,那么awk脚本当然可以定义和使用变量。
例如:

1
2
3
BEGIN {sum = 0}
{sum ++}
END {print sum}

后面会介绍到,awk预定义了一些有用变量。

records

预定义变量:

  • RS Record Separator
  • NR Number of Records

默认record seprator是newline,默认的awk一次处理一行。
RS可以是任意正则表达式,可以在BEGIN action中更改。

1
cat temp.txt|awk '{print "#"NR, $0}'

上面的$0表示该记录的第0个域,$开头的数字变量都表示域编号。

fields

预定义变量:

  • NF:Number of Field
  • FS:Filed Separator

输入中的每一行都按域分隔符被拆分。

  • awk预定义变量FS Filed Separator默认是空格(一个或多个空格或者tab)
  • awk -Fc可以设置FS为字符c
  • $0是当前所有行
  • $1是第一个域….,$NF是最后一个域,以此类推
    • {print $(NF-2)} 倒数第三个域
  • 计算和打印
    • {print $1, $2*$3}

默认的由”,”连接的项会被print打印为由单个空格连接的项。

格式化输出:

  • printf(fmt, val1, val2, …)
  • {printf(“%-8s %6.2f\n”, $1, $2)}

selectoins

前面提到awk是用作一个可编程的过滤器,用于处理文本和字符串。
除了前面提到的pattern(正则、begin&end等),更有用的的awk pattern还可以是如下这样的表达式pattern:

  • 比较: $2 >= 50 {print}
  • 计算: $2 * $3 > 50 {printf(“%6.2f “, $1)}
  • 文本内容:
    • $1 == “NYU”
    • $2 ~ /NYU/
  • 模式组合: $2 >= 4 || $3 >= 20
  • 行号 NR >= 10 && NR <= 20
  • ….
computings

awk可以方便的对域进行一些基本的计算,例如:

  • counting
1
2
$3 > 15 {emp = emp + 1}
END {printf("%d empoyess worked more than 15 hours.", emp)}
  • average and sum
1
2
3
4
{pay = pay + $2 * $3}
END {print NR, "employes"
print "total pay is ", pay
print "average pay is ", pay / NR}

strings manipulation

awk内置一些函数,用于处理字符串。

  • length(s)
  • substr(s,m,n)
1
2
3
4
5
6
{nc = nc + length($0) + 1
nw = nw + NF}
END {
print NR, "lines"
print nc, "chars"
print nw, "words"}

control flow

awk作为一门编程语言,提供几种基本的类C的流程控制结构。

  • IF THEN ELSE结构
1
2
3
4
5
6
7
$2 > 6 { n = n + 1}
END {
if (n > 0 }
print n
else
print "no employes."
}
  • WHILE
  • DO WHILE
  • FOR
1
2
for (i = 1; i < 10; i = i + 1)
print i

array

  • array下标可以是数字和字符串,当下标是字符串的时候,可以当一个map使用
  • example:a[“tom”] = 0.3

数组遍历:

1
2
for (k in array) {
print k, "==>", array[k]}

awk predefined variales

  • $0 $1 $2 $NF
  • NF NR NF
  • FS RS
  • FILENAME
  • OFS:output field separator default is single space

built-in functions

  • arithmetic
    • sin, cos, tan, exp, log, sqrt
  • strings
    • length, substr, split
  • output
    • print, printf
  • special
    • system(“cmd”) eg: system(“clear”)
    • exit -直接跳转到ENDpattern-action块

code snippets

  1. filter blank lines while you want to count your code
1
cat /path/to/yourcode/*.py | awk NF | wc -l

因为NF表示Number of Filed,默认的FS为空格,当awk遇见空行的时候,作为pattern的NF值为0,因此该行不会被输出。

Notes:Temporal Relational Reasoning in Videos

Notes:Temporal Relational Reasoning in Videos

Abstract:

时序关系推理能够关联物体随时间的变化,而时序关系推理是完成一些重要的视频识别任务的关键。
该文提出了一个被称为Temporal Relation Network(TRN)的网络模块,可以用来在多时间尺度上学习和推理时序关系。该文在Something-Something,Jester和Charades三个数据集做了实验,得到了SOTA的结果。TRN模块是一个可插拔的模块,可以基于常见的CNN架构,在Charades数据集上,采用TRN模块的网络比双流、3D卷积取得了更好的结果。

1. Introduction

该文一开始突出介绍了时序推理能力是智能决策的关键能力,具有时序推理的物种能够结合当下情形对过去和未来作出推理和预测。文章举了一个简单的例子说明人类具有这种时序推理能力,而常见的模型似乎很难学会。

image.png

人类很容易通过观察一件事情的始末状态,可以推理中间发生的过程,而对于普通的神经网络模型却有点困难。在CV领域视频的行为识别是一个核心的研究领域,一些常见的数据集例如UCF101,Sport1M和THUMOS等,包含许多人类行为,这些行为不需要通过长范围的时序推理就能够被识别出来(基于外观和光流等特征),例如常见的双流卷积网络和I3D等网络。

然而,当视频的长度有限以及某些需要通过物体的变换和时序关系而非外观来刻画的视频,这时候上述的那些网络往往不能够很好的工作。例如上面的图片的例子,每一组的两张图片在apperance差距不大,也很难仅仅根据外观来判断发生了什么,因此需要网络能够基于光流和图像里面物体的外观等因素,去发现一种视觉常识

TRN模块描述了视频采样的数帧之间的时序关系,且TRN能够学习和发现多尺度的时序关系。TRN是一个很容易被结合到现有的成熟的网络结构上,因此在使用上也是低成本的。

该文回顾总结了常见用于行为识别的方案,双流卷积、CNN+LSTM、3D卷积以及最近的I3D网络。然后该文基本上指出了这些网络结构常见的问题:

  • 双流网络基本就是光流的计算耗时,效率低下;
  • 3D卷积计算复杂度高,3D卷积在连续的图像帧上做卷积有些冗余;
  • 这些网络的输入通常是20到30帧,因此很难学习到长范围的时序关系;

为了解决这些问题,TRN稀疏采样帧,然后学习他们之间的因果关系,这相比于密集采样然后对他们做卷积运算要有效率得多。

下图是TRN对一个video的表示。

image.png

2. Temporal Relation Network

该文后续的实验表明:装配有TRN模块的网络能够发现可解释的视觉常识。

2.1 时序关系定义

pair-wise的时序关系定义如下:

image.png
其中输入是原始视频采样的有序帧的特征表示,例如一些CNN架构的某层的activation。函数和函数融合不同的pair-wise帧的时序关系,文章中提出的TRN仅仅使用了MLP来表示两个函数。另外,为了计算的有效性,文章中并没有全部计算采样的n个帧特征的pair-wise时序关系,而是随机选择(均匀采样)了个pair-wise关系来计算。


上面考虑的是2-frame的关系,其中还有3-frame,4-frame,……,n-frame的时序关系。
对于3-frame的时序关系:
image.png
以此类推计算其他d-frame的时序关系。

2.2 多尺度时序关系

为了捕获多尺度的时序关系,文章将上诉的d-frame的时序关系简单的组合起来。
image.png
每个时序关系项都表示了d张有序的frame的时序关系,其中每一个时序关系项都对应两个函数

2.3 采样规则

为了有效的训练,降低计算复杂度,文章提出了一个有效的采样策略。给定均匀采样的N帧,对于d-frame关系的计算,当时,直接计算。当时,则在所有可能的d-frame的排列中均匀选择个排列,按照公式计算,将其表示d-frame的时序关系。这样在给定张frame的情况下,可以计算个多尺度的时序关系。

3. 实验

因为文章想说明TRN模块的有效性,因此文章所有用来对比的模型均采用BN-Inception作为基础的CNN网络结构,唯一不同的就是是否使用TRN模块。文章的函数是一个每层256神经元的两层MLP,而则是一层MLP。文章设置k=3,d=8。文章和TSN模型在Something-V1和Something-V2数据集上做了对比,结果如下:


image.png


可以看出TRN模型确实outperform TSN模型,但是用single-frame作为base-line来对比说明TRN模型的有效性有点难以服众,毕竟base-line只利用了single frame的信息。

另外文章还在Jester和Charades上做了实验:

  • Jester数据集

image.png

  • Charades数据集

在该数据集上,TRN模型打败了2-stream卷积网络和C3D网络,以及最近的Asyn-chronous Temporal Field方法。

image.png

TRN中的可解释的视觉常识

TRN模块选择最能表示视频的帧(Representative Frame)

首先计算不同的视频帧排列的经过TRN模块的时序关系response,然后将这些response排序,选择最高response的视频帧排列。这些视频帧在TRN模块下,是该视频的表示帧。

image.png

视频的时序对齐

基于上面的representative frame,TRN可以用于对于同一个action类别的不同视频进行时序对齐。首先利用TRN找出这些representative frame的index作为该视频时序上的landmark,然后调整采样率,使得每一个视频同时到达每一个landmark。

image.png

时序对于行为识别的重要性

文章为了表明时序的重要性,在Something和UCF101数据集进行了一个对比实验,一组是按原始顺序输入视频帧,一组是shuffle顺序输入视频帧。结果比较有意思:在Something数据集上来看,原始顺序输入的实验结果明显好于shuffle顺序输入,而对于UCF101,视频帧的输入顺序对结果影响不大。

image.png

文章给出的解释是对于视频中持续的行为过程,时序关系对于UCF-101数据集的行为识别用处不大。为了进一步分析这种差异,文章画出了两种顺序输入后准确度有显著差异和没有显著差异的活动分类。

对于一些有很强的方向性的、大的动作,保留时序顺序对于准确识别行为类别是重要的,这也和我们的直觉相符,而对于一些看起来似乎静止的活动,例如“将一个不会滚动的球放在桌上”这个行为准确的识别不太需要motion,而是需要一些物品的co-occurence信息即可。

早期行为识别

文章认为TRN模块可以在给定少量的帧的情况下,利用帧之间的关系可以预测出该活动的类别。
这样在训练好模型后,在实际预测的时候,就可以在早期就给出行为类别的预测。
image.png

4. 结论

文章认为TRN可以让神经网络对视频的时序关系进行推理,并且在几个数据集上取得了competitive的效果。同时,通过一系列的分析,文章认为TRN发现了视频中的一些可解释的视觉常识。

Notes:Learning Spatiotemporal Features with 3D Convolutional Networks

Abstract:

本文提出了一个简单有效的方案,使用3D卷积在大规模视频中来学习空间时序(Spatiotemporal)特征,本文的贡献如下:

  • 相比于2D卷积,3D卷积更加适合学习空间时序特征;
  • 作者通过实验不同的网络结构,发现3*3*3的卷积核是最好的网络结构;
  • 作者通过学习到的C3D特征去作为视频的描述子,采用简单的线性分类器,在4个benchmarks上,取得了比SOTA好的结果,同时在另外2个benchmarks上取得了和SOTA相当的结果;
  • C3D学习到特征是压缩的、具有判别性的,通过PCA降维到10维,也能在UCF101上达到52.8%的准确度,相比于其他方法学到的特征,C3D特征更加压缩和具有判别性,在现实环境中可以更容易提高准确度和计算复杂度的trade-off;

Introduction

作者表明虽然在不同的视频任务领域,存在很多好的解决方案,但是仍然需要一个__通用的视频描述子(generic video desciptor)__,这样会以一种统一的方式去解决大规模视频任务中的问题。

对于一个通用的视频描述子,以下简称GVD,作者认为有效的GVD应该具有如下四个性质:

  1. 通用性,这样才能才具有判别性的同时可以表示不同类型的视频,例如自然风景、体育运动、电视节目、电影等;
  2. 压缩性,因为当处理数百万个视频时,压缩的特征可以跟有效的处理、存储和检索;
  3. 计算有效性,在现实系统中,我们需要每分钟处理数千个视频;
  4. 易于实现,经过该描述子提取的特征,即使采用简单的分类器,例如线性分类器,也应该能取得不错的效果;

使用深度学习提取的图像特征不能直接适用于视频任务,因为这些图像特征缺乏对动作的建模。

作者人为本文的贡献如下:

  • 作者做了一系列实验,包括视频分类、动作识别、场景识别和对象检测等,表明了3d深度卷积网络是好的模型来对外观(apperance)和动作(motion)同时建模;
  • 作者通过有限的实验探索了各种不同的3d卷积网络的结构(kernel的大小不同),最终发现3*3*3的kernel是经验最优的结构,这一点和2D卷积中kernel的选取经验相似;
  • 使用作者提出的C3D网络结构提取的特征,使用简单的线性SVM分类器,在4个不同的任务和6个不同的benchmark中取得了最优的效果;

Related Work

作者回顾了一些传统的视频描述子的方法,重点提到了目前最好的称为iDT的方法,并指出这个方法计算量大,难以扩展到大规模视频的任务中。

随着机器的计算力提高和数据量的增多,卷积神经网络在很多任务中有所突破。有些人利用深度学习学习到Image特征,通过迁移学习的方式在一些任务上取得了不错的效果,但是这些方法都是不适用于视频特征学习的。

在作者这篇文章之前,有几篇文章已经开始或多或少的提到了利用3D卷积去进行视频特征的学习,其中一篇文章是2013年的 *3d convolutional neural networks for human action recognition,文章*利用预处理的方式将视频中的头和身体进行分割,然后将分割后的视频送入3d卷积网络中做动作识别;另一篇是2014年__Karpathy__等人训练了深度网络去进行视频分类。

Learning features with 3D ConvNets

3D Convolution、3D Pooling、search for Network Architecture

3D Convolution VS 2D Convolution

和2D卷积不同,3D卷积有能力通过3D卷积和3D池化对时间信息进行建模。

2D卷积和3D卷积的区别

image.png | left | 747x170

在一张图片上进行一次2D卷积,输出是一张图片(Feature Map),在一个视频进行一次2D卷积,输出同样是一张图片(Feature Map),在一个视频上进行一次3D卷积,输出则是另一个立方体,这个立方体保留了输入信号的时序信息。

总结: 2D卷积在每次卷积和池化和都丢失了时序信息,而3D卷积和池化则保留了时序信息。

search for Network Architecture

因为训练这种大型的网络是十分耗费时间的,因此作者根据2D卷积的经验,空间维度的卷积核采用3*3,而时间维度的卷积深度则通过不同的实验来选择,因此3D卷积核的大小为d*3*3。

为了找到时间维度上最优的卷积深度d,作者在中等规模的UCF101数据集上进行了3类实验:

  • 全局采用一致的时间维度卷积深度d;
  • 从前往后,时间维度卷积深度d逐层递增,3-3-5-5-7;
  • 从前往后,时间维度卷积深度d逐层递减,7-5-5-3-3;

作者在搜索最优的时间卷积深度的时候,由于是在中等规模数据集上进行的,网络结构为:5层卷积和2层全连接。
卷积层的配置逐层如下:

  • 64 kernels
  • 128 kernels
  • 256 kernels
  • 256 kernels
  • 256 kernels

所有的卷积层在空间和时间维度都采用SAME padding,因此卷积前后的size是不变的。每一层卷积后都接一层3D pooling层,除了第一层池化层的核为1*2*2外,其余池化层全部为2*2*2,因此经过池化层的size会缩小为原来的1/8。

说明:作者强调了第一层池化层在时间维度不进行池化有两个目的:

  • 不想过早的合并时序信息;
  • 输入是16帧图像,因此时间维度最多进行4次池化;

image.png | left | 400x220

最终作者发现时间维度的卷积深度为3的时候,网络性能最佳,因此3*3*3是经验最优的3D卷积核,这也和2D卷积的3*3卷积核是统一的。

Spatiotemporal Feature learning

为了进行空间时序特征的学习,作者在一个大规模的数据集Sport-1M上进行了训练。Sport-1M包含100万个视频,每个视频属于487种运行类别种的一种,和UCF101相比,Sports-1M的视频数量是它的100倍,类别数目是它的5倍。因此作者也设计了容量更大的网络结构,如下所示:

image.png | left | 747x127

网络一共8层卷积层,每一层紧接一个池化层,最后两个全连接层。
作者的实验结果如下:

image.png | left | 747x139

  • C3D视频描述子:在Sport-1M上训练过的网络可以用来作为视频分析任务的特征提取器,因为该c3d视频描述子具有很多优良的性质;
  • C3D网络学习到了什么?
    • 作者通过反卷积可视化技术,观察到对于视频的前几帧,网络学习到了外观apperance,在接下来的图像帧中,网络重点学习到了显著的动作。这表明和2D卷积网络不同,3D卷积网络选择性的学习到了动作和外观特征,而这些特征对于视频分类等问题,具有很好的判别性。

Experiment

作者后续又先后在

  • Action Recognition
  • Action Similarity Labeling
  • Scene and Object Recognition
    等任务中进行了实验,实验结果几乎都是属于SOTA结果,这也证明了C3D网络是一种优秀的网络结构用于处理视频任务。

作者其中还通过可视化技术t-SNE,将C3D学习到特征通过特征嵌入的方式,和ImageNet的特征进行了对比,表明了C3D学习到特征是压缩的。

image.png | center | 401x295

image.png | center | 364x274

Runtime Analysis

作者通过和传统方法iDT、深度学习方法Temporal Stream Network TSN方法的运行时间做了对比,表明c3d网络是高效的、计算成本低的方法。这也表明c3d可以很好的运行在现实系统中,因此具有很好的现实意义。

image.png | center | 534x232

Conclusion

  • C3D能同时对空间和时间信息建模;
  • C3D特征是有效的、通用的、压缩的且易于实现的;
  • C3D网络中
    • 浅层的卷积层学习到了低层次的运动模式:例如颜色的改变、移动的边缘和边缘方向的改变;
    • 中层的卷积层学习到了高层次、更大的运动模式:例如纹理的移动、身体躯干的移动、轨迹的移动等;
    • 高层的卷积层学习到了更复杂的运行模式:例如移动的圆形物体、移动的自行车形状的物体;

image.png | left | 747x644

image.png | left | 747x582

1. 数据分布不一致

训练集和测试集(包含开发集)数据分布不一致被称为Data Mismatch。

我们知道机器学习模型会存在两种异常情况:

  • 高方差:过拟合
  • 高偏差:欠拟合

数据集一般划分为:train、dev、test。
其中当dev/test 和 train的数据分布存在不一致(data mismatch)的时候,则模型的高方差可能来源于两个原因:

  • 过拟合
  • 数据分布不一致

1.1 如何判断高方差来源于data mismatch呢?

将train划分一部分作为train-dev,并在train-dev上对模型进行评价。这样我们可以得到:

  • train
  • train-dev
  • dev
  • test

如果高方差问题主要源于data mismatch,就错误率来讲,train 约等于 train-dev,而train则远小于 dev/test。

1.2 如何解决Data Mismatch问题?

Data Mismatch的解决方法自然是让train和dev/test的分布一致。
首先进行错误分析,观察dev/test上错误的样本和训练集上的样本的差别,然后采用人工合成数据来尽量让训练集与dev/test集合相似,保持相同或相似的分布。

例如对于一个图像分类任务中,被错误分类的dev/test的图像大多都有雾气背景,则可以将train中的一些样本进行雾气背景的人工合成。

2. 迁移学习

将在数据集A上训练的模型运用在数据集B上的过程叫做迁移学习。

迁移学习包含两个重要的概念:

  • pre-training:预先在某个数据集上进行模型的训练。
  • fine-tuning:在目标数据集上对预训练模型进行训练,微微调整其模型参数。

迁移学习的目的是为了让一些任务相关的模型可以共享一些低层次的特征,这样即减少了模型的训练时间,也降低了对海量数据集的要求。

ps:马克思主义中国化历程其实可以看做是一次迁移学习,中国共产党人将得到验证的马克思主义理论(pre-training)运用到中国,并对其改革创新、继承发展(fine-tuning),最终创造了毛泽东思想,中国特色社会主义理论体系,习近平新时代中国特色社会主义思想等理论成果。

transfer-learning.png | center | 747x166

对于一个预训练模型,如上图所示,对其进行迁移学习和微调的做法不拘一格。

  • 固定前面n-1层权重不变,将输出层替换为满足自己任务需求的输出层,然后在自己的数据集上进行训练,只调整替换的输出层的权重。
  • 前面的n-1层的权重作为新模型的初始化权重,并对新模型的n层网络进行训练。

选择保留哪些网络层,新增多少网络层以及权重的更新策略等都是视具体任务而定的。

迁移学习的应用场景:

  • 任务A和任务B有相同的输入,例如输入都是图像、文本等;
  • 任务A的数据集大于任务B的数据集;
  • 任务A的低层次的特征对提升任务B的性能大有帮助;

例子:一个在海量红绿灯数据集上训练的__识别红绿灯__的模型,可以采用迁移学习去训练一个__识别黄灯__的模型,因为都是交通灯,仅仅是颜色不同,这两个任务非常相近。

3. 多任务学习

多任务学习和迁移学习不一样,多任务学习不是先学习一个模型,然后再去训练另一个模型,而是同时进行多个模型的训练(多个任务的学习)。

例如:检测一个图片中是否有行人、汽车、停止标志和红绿灯,在一个神经网络中,这些任务都可以同时进行学习。不像softmax作为输出层,该任务的预测结果不是每张图片具有多个label,而是是否存在该label。同时,对于一些标注样本,如果某个样本在某个label上缺失,也不影响整个训练,其同样可以作为有效的样本存在于训练集中。

多任务学习的使用场景没有迁移学习常见,适合多任务学习的场景主要是:在一系列相关的任务中,这些任务有共享的低层次的特征,例如计算机视觉领域的对象检测。

4. 端到端学习

传统的一些计算机任务例如语音识别,需要手动的设计特征(hand-craft feature),例如MFCC,然后人为的划分很多个步骤,每个步骤都有特定的研究者在该领域耕耘数十年。我们在采用这种pipline的方式进行机器学习的时候,很多时候我们强迫算法去学习我们人为定义的概念,例如语音识别中的音素。

端到端的学习则不是采用这一套pipline的方式,而是利用原始的数据去学习原始输入和输出之间复杂的映射。端到端的学习没有引入人类的一些强迫的概念,而是从数据本身去发现潜在的输入和输出的关系。端到端的学习的成功很大部分取决于海量的数据,而传统的方法并不是一无是处,在一些数据量小的情况下,传统方法具有明显的优势。

在数据量小的时候,传统的方法可能更适合,在数据量巨大的时候,端到端的学习方法更可取,然而,现实世界中,没有足够的数据去进行端到端的学习,因此在数据量适中的情况下,我们通常将端到端的学习拆分为多个步骤,并同时采用一些传统的方法,相当于除了数据之外,我们还为模型注入一些人类的知识,这样整个模型表达知识的能力才会到达最优。

例如:人脸识别门禁。

  • 端到端的方案:输入是一张站着的人的图片,输出是这个人的id,显然站着的人的脸的大小会随着人距离摄像头的远近不同,还有角度等因素,因此采集海量的数据是十分困难的,因此端到端的学习在这不太实用。
  • 多步骤分开建模方案:首先利用成熟的模型将人脸抠出来,然后再resize的固定的大小进行人脸识别,这样就将人脸识别的大问题拆分为两个易于解决的子问题,而每个子问题可利用的数据集也是巨大的,可以单独的采用端到端的方案去学习。

端到端学习的优缺点:

  • 优点
    • 充分发挥数据的价值
    • 花费很少的精力去设计特征
  • 缺点
    • 要求海量数据
    • 对于一些很有用的人工特征没有充分利用

端到端的学习不是万能的,传统方法并不是一无是处。

前段时间实验室的电脑磁盘卡盘彻底报废,最近想用github.io上的博客恢复之前的hexo环境,但不幸的发现托管在github.io上的代码仅仅是hexo渲染过后的html css javascript等静态文件,而原始的MarkDown文件和一些hexo site的配置文件却没有同步到github上。在打算从头开始配置一个的时候,意外发现之前的一个备份。为了不吃二次亏,我决定将博客的原始markdown文件也上传到github上。于是我的需求是:在每一次同步hexo渲染后的静态文件后,自动备份原始markdown等文件到github的另一个repo。

1. 新建一个blog repo

在github上新建一个blog repo来保存博客原始的markdown等文件。然后在本地电脑的hexo博客目录下,运行:

1
git init
1
git remote add origin your_blog_git_url
1
git pull origin master --allow-unrelated-hostories

到这里就将本地的markdown文件和git blog repo关联起来了。

2. 配置git免密提交

这个可以参考其他资料,此处略。

3. 修改当前用户目录下.bashrc 中的alias

如果终端使用的是bash,就修改.bashrc,是zsh则修改.zshrc文件,下面以.zshrc举例。

在.zshrc加上一行:

1
alias hexod = "hexo g --deploy && git add --all && git commit -m'Update' && git push origin HEAD"
1
source .zshrc #生效

写完博客,运行hexod:

可以看到每次部署博客都进行了两个git repo的push。

由于之前没有备份hexo博客源码,实验室电脑硬盘卡盘报废后,一度以为博客gg了。今天打开之前的笔记本意外发现9月1日做过一次备份,失而复得的喜悦溢于言表,就此写一篇博文来纪念一下,同时也告诫自己重要的东西要经常备份,做好高可用性。

6. Deploy your app

迭代和删除

1
docker stack deploy -c docker-compose.yml getstartedlab
1
docker stack rm getstartedlab

5. Stacks

介绍

在第4部分,你已经学会如何配置一个蜂群,蜂群是一个运行着docker的集群,然后你将app部署到这个蜂群上,容器运行在多个机器上。

在第5部分,我们马上介绍分布式应用层级的最上层的概念stack。一个stack是一组共享依赖的内部相关的服务,这些服务能够被一起编排和伸缩。单独的一个stack能够定义和协调整个应用的功能,一些复杂的应用可能需要定义多个stack。

不要慌,从第3部分开始,我们就已经接触到了stack,前面我们创建compose文件,然后运行docker stack deploy,这些都是和stack相关的内容。但是前面我们接触的都是运行在单台机器上的包含单个服务的stack,这个在生产环境中不常见。接下来,运用之前学到的知识,我们来创建多个相关的服务,然后运行在多台机器上。

增加一个Service 重新部署

修改docker-compose.yml文件很容易添加一个服务。首先我们增加一个免费的可视化服务,这个服务可以让我们看我们的蜂群是如何调度我们的容器的。

  1. 打开docker-compose.yml文件,修改为如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
version: "3"
services:
web:
# replace username/repo:tag with your name and image details
image: dockerjie/get-started:part2
deploy:
replicas: 5
resources:
limits:
cpus: "0.1"
memory: 50M
restart_policy:
condition: on-failure
ports:
- "4000:80"
networks:
- webnet
visualizer:
image: dockersamples/visualizer:stable
ports:
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
placement:
constraints: [node.role == manager]
networks:
- webnet
networks:
webnet:

注意上面有两个新的配置项,volumes,运行可视化服务访问宿主机的Docker的socket文件,placement则确保服务被部署在蜂群管理器swarm manager上面,而不是worker上,因为可视化服务是显示蜂群概览信息的,仅仅管理器才有权限获取到这些信息。

  1. 确保你的shell连接到myvm1机器上。docker-machine start $(docker-machine ls -q)启动所有机器
    1. 运行docker-machine ls确保你连接到myvm1上。如果出现ip地址对应的cert不对应,则重新运行命令生成cert,docker-machine regenerate-certs $(docker-machine ls -q)

image.png | left | 625x59

1
2. 如有必要运行 eval \$(docker-machine env myvm1)

image.png | left | 625x72

  1. 在蜂窝管理器上重新运行 docker stack deploy
1
docker stack deploy -c docker-compose-yml getstartedlab

部署完成后查看服务:

image.png | left | 681x47

访问可视化服务:

image.png | left | 681x666

可以看到两个服务:visualizer和web均已经启动,且web服务的副本数量为5,分布为:

  • myvm1*1
  • myvm2*2
  • myvm3*2

也可以使用docker命令查看:

1
docker stack ps getstartedlab

image.png | left | 681x75

持久化数据

到现在为止,我们的web服务中依赖的redis服务还没有配置。

  1. 修改docker-compose文件,增加redis服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
version: "3"
services:
web:
# replace username/repo:tag with your name and image details
image: dockerjie/get-started:part2
deploy:
replicas: 5
resources:
limits:
cpus: "0.1"
memory: 50M
restart_policy:
condition: on-failure
ports:
- "4000:80"
networks:
- webnet
visualizer:
image: dockersamples/visualizer:stable
ports:
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
placement:
constraints: [node.role == manager]
networks:
- webnet
redis:
image: redis
ports:
- "6379:6379"
volumes:
- "/home/docker/data:/data"
deploy:
placement:
constraints: [node.role == manager]
command: redis-server --appendonly yes
networks:
- webnet
networks:
webnet:

redis有官方的镜像,因此具有一个short name redis。6379是redis容器预先配置的端口,需要对外暴露的,我们也暴露这个端口给宿主机。有两个额外的配置只得注意:

  • redis一直运行在manager上,因此它一直运行在相同的文件系统上,因为是同一个宿主机。
  • redis访问宿主机上的文件夹作为/data目录,redis将数据存储到这里。

redis需要持久化数据到容器里的/data目录,而容器启动, 停止后,里面的数据都没有了。因此为了持久化数据,需要将容器的某些目录映射到宿主机上,这样数据才会被真正的持久化。

  1. 在manager宿主机上创建data目录
1
docker-machine ssh myvm1 "mkdir ./data"
  1. 部署
1
docker stack deploy -c docker-compose.yml getstartedlab

image.png | left | 681x61

查看浏览器验证:

image.png | left | 747x698

查看持久化生效:

image.png | left | 681x214

4. Swarms

介绍

在前面的部分,你知道了如何写一个应用以及如何运行在生产环境中,然后将它变为一个服务,在同一个进程中将服务能力伸缩到原来的5倍。

在本部分,你将在集群上部署一个应用,运行在多台机器上。通过将多个机器加入到docker化的集群中,多容器多机器的应用是可能的,这个docker化的集群被称为蜂群。

理解蜂群

一个蜂群是一组运行docker的机器,组合成了一个集群。在蜂群上,你也可以运行docker命令,但是在集群上,docker命令是被蜂群管理器(swarm manager)执行的。蜂群中的机器可以是物理机,也可以是虚拟机。这些机器加入某个蜂群后,他们被称做节点(node)。

蜂群能使用多种策略来运行容器,例如“emptiest node”,这个策略将首先在资源利用最小的机器上运行容器,还有“global”策略,这个确保每个机器都恰好有一个容器运行。通过Compose文件,你可以指定swarm manager使用何种策略。

蜂群管理器是蜂群中唯一能执行你命令或者授权其他机器加入蜂群的机器。works是那些提供资源但是没有能力指挥其他机器的机器。

到目前为止,你使用docker还都是停留在单机模式。docker也可以切换到swarm模式,该模式下可以组建蜂群。启用swarm模式使得你当前的机器变为一个swarm manager。接下来,Docker将会在整个蜂群上执行你的docker命令,而非你当前的机器上。

设置蜂群

一个蜂群是由多个节点组成的,这些节点要么是物理的或者虚拟的。使用以下命令启用swarm模式:

1
docker swarm init

该命令启用了swarm模式,并把当前机器设置为了swarm manager。
对去其他机器,你可以运行如下命令来加入到某个蜂群成为里面的一个worker。

1
docker swarm join

下面我们使用虚拟机来创建一个包含两个机器的集群,以此来组建一个蜂群。

创建集群

我们需要一个hypervisor来创建虚拟机,因此安装Oracal VirtualBox。

安装完毕后,我们使用docker-machine来创建3个虚拟机。

1
2
3
docker-machine create --driver virtualbox myvm1
docker-machine create --driver virtualbox myvm2
docker-machine create --driver virtualbox myvm3

image.png | left | 681x463

列出虚拟机查看对应的IP地址

1
docker-machine ls

image.png | left | 681x82

2376是虚拟机上docker守护进程的端口号。

初始化蜂群,然后增加节点

我们选择第一台虚拟机作为swarm manager,这台机器执行docker命令和授权其他worker加入到这个蜂群,剩下的两台虚拟机作为worker。

我们可以使用docker-machine ssh到虚拟机上执行shell命令。
先连上myvm1虚拟机:

1
docker-machine ssh myvm1 "docker swarm init --advertise-addr 192.168.99.100"

上面的命令ssh连接到myvm1,然后执行初始化蜂群的命令。

image.png | left | 681x99

可以看到myvm1上初始化蜂群成功,且作为了swarm manager,暴露了2377端口用于其他机器与其通信加入蜂群。

接下来我们需要将剩余两台虚拟机作为worker加入到蜂群中:

1
2
3
docker-machine ssh myvm2 "docker swarm join --token \
SWMTKN-1-623gp73gn96xqhjclwob4lipvuis34lnshb0g5c7uu31s5c2sx-f3ux24m2vqdecn29oxurs2kim \
192.168.99.100:2377"
1
2
3
docker-machine ssh myvm3 "docker swarm join --token \
SWMTKN-1-623gp73gn96xqhjclwob4lipvuis34lnshb0g5c7uu31s5c2sx-f3ux24m2vqdecn29oxurs2kim \
192.168.99.100:2377"

image.png | left | 681x194

ssh到swarm manager上使用docker node ls查看节点概况,只有是swarm manager才可以使用该命令查看节点概况。

1
docker-machine ssh myvm1 "docker node ls"

image.png | left | 681x59

在蜂群上部署app

接下来的部署和前面在单机上部署service类似。

上面我们一致通过docker-machine ssh到远程虚拟机上执行shell命令,因此不太方便,现在我们先配置一个docker-machine shell到swarm manager这个远程虚拟机。这样配置好后,就运训使用本地的docker-compose.yml文件来远程部署app了,而不用把这个文件拷贝来拷贝去。

1
docker-machine env myvm1

image.png | left | 681x151

运行命令:

1
eval $(docker-machine env myvm1) #这一步在设置当前shell的环境变量

运行docker-machine ls验证一下当前的shell确实是连接到myvm1上的。

1
docker-machine ls

image.png | left | 681x82

在swarm manager上部署app

和之前的内容一样,使用docker stack deploy进行部署:

1
docker stack deploy -c docker-compose.yml getstartedlab

image.png | left | 681x62

查看部署的结果:

1
docker stack ps getstartedlab

image.png | left | 681x60

可以看到5个容器实例分散在蜂群中的不同节点上。

访问集群

web访问一下试试:

使用3个虚拟机的IP地址访问服务:

image.png | left | 681x150

image.png | left | 681x162

image.png | left | 681x170

不同的IP地址随机出现不同的主机名,表明多容器、多机器集群部署完成。

所有IP地址都能访问部署的app的原因是蜂群中的节点组成了一个ingress网络,构建了一个路由网络。这个保证了部署蜂群上的某个端口上的某个服务,蜂群上的节点都要保留这个端口,无论哪个节点运行容器。下面是一个暴露8080端口的蜂群的网络路由示意图:

image.png | left | 681x303

迭代和扩容app

如果需要扩缩容,只需要修改docker-compose文件即可完成。
如果需要对app的功能进行迭代,那么需要编码、重新构建、发布新的镜像。最后只需要重新运行docker stack deploy重新部署即可。

清理和重启

Stacks和Swarms

拆除部署的stack

1
docker stack rm getstartedlab

重置docker-machine shell变量

1
eval $(docker-machine env -u)

重启机器:

1
docker-machine start <machine-name>

cheet sheet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
docker-machine create --driver virtualbox myvm1 # Create a VM (Mac, Win7, Linux)
docker-machine create -d hyperv --hyperv-virtual-switch "myswitch" myvm1 # Win10
docker-machine env myvm1 # View basic information about your node
docker-machine ssh myvm1 "docker node ls" # List the nodes in your swarm
docker-machine ssh myvm1 "docker node inspect <node ID>" # Inspect a node
docker-machine ssh myvm1 "docker swarm join-token -q worker" # View join token
docker-machine ssh myvm1 # Open an SSH session with the VM; type "exit" to end
docker node ls # View nodes in swarm (while logged on to manager)
docker-machine ssh myvm2 "docker swarm leave" # Make the worker leave the swarm
docker-machine ssh myvm1 "docker swarm leave -f" # Make master leave, kill swarm
docker-machine ls # list VMs, asterisk shows which VM this shell is talking to
docker-machine start myvm1 # Start a VM that is currently not running
docker-machine env myvm1 # show environment variables and command for myvm1
eval $(docker-machine env myvm1) # Mac command to connect shell to myvm1
& "C:\Program Files\Docker\Docker\Resources\bin\docker-machine.exe" env myvm1 | Invoke-Expression # Windows command to connect shell to myvm1
docker stack deploy -c <file> <app> # Deploy an app; command shell must be set to talk to manager (myvm1), uses local Compose file
docker-machine scp docker-compose.yml myvm1:~ # Copy file to node's home dir (only required if you use ssh to connect to manager and deploy the app)
docker-machine ssh myvm1 "docker stack deploy -c <file> <app>" # Deploy an app using ssh (you must have first copied the Compose file to myvm1)
eval $(docker-machine env -u) # Disconnect shell from VMs, use native docker
docker-machine stop $(docker-machine ls -q) # Stop all running VMs
docker-machine rm $(docker-machine ls -q) # Delete all VMs and their disk images

3. Services

介绍

在本部分,我们伸缩我们的应用,并且完成负载均衡功能。为了完成这个,我们必须在前面提到的层级结构中上移一层:service。

  • stack
  • service
  • container

什么是服务?

在一个分布式应用中,应用的不同功能部分被称为服务。例如如果你想像一个视频分享网站,它可能包含存储数据在数据库的服务,视频转码的后台服务以及前段用户上传视频的服务。

在docker情景中,简单来说__服务就是生产环境中运行的容器__。一个服务仅仅运行一个镜像,但是它组织镜像如何运行,使用哪个端口,运行多少个副本等等。伸缩一个服务,仅仅需要指定更多的计算资源到每个进程中的服务,从而改变容器实例的数量。

在docker平台上,很容易定义、运行和伸缩服务,通过使用docker-compose.yml文件。

docker-compose.ymls文件

一个docker-compose.yml文件是一个YAML文件,用来定义生产环境中容器的运行方式。

新建一个docker-compose.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: "3"
services:
web:
# replace username/repo:tag with your name and image details
image: dockerjie/get-started:part2
deploy:
replicas: 5
resources:
limits:
cpus: "0.1"
memory: 50M
restart_policy:
condition: on-failure
ports:
- "4000:80"
networks:
- webnet
networks:
webnet:

上面的docker-compose.yml文件描述了:

  • 从什么地方去拉取需要的镜像文件
  • 运行含有5个实例的镜像作为一个web服务,限制每个实例使用的cpu比例和内存大小
  • 重启策略:如果容器fail,立即重启该容器。
  • 端口映射:宿主机的4000端口到容器的80端口
  • 构建web服务的容器们共享80端口,通过一个被称为webnet的负载均衡的网络。
  • 使用默认配置定义webnet网络。

运行可负载均衡的app

在使用docker stack deploy命令之前,先运行:

1
docker swarm init

image.png | left | 681x102

现在运行app

1
docker stack deploy -c docker-compose.yml getstartedlab

这个服务stack正在一个宿主机上运行我们部署的镜像的5个容器实例。

查看服务列表:

1
docker service ls

image.png | left | 681x39

上面的输出是关于web服务的信息,分别是服务ID,服务名字,模式,副本的数量,镜像以及暴露的端口信息。

运行服务里面的单个容器叫做task。每一个task有一个递增的数字ID。

列出服务包含的tasks。

1
docker service ps getstarted_web

image.png | left | 681x60

同样的查看容器列表也能观察到web服务包m含的task:

1
docker container ls

image.png | left | 681x65

测试效果:

image.png | left | 681x192

image.png | left | 681x171

可以看到每次访问对应的hostname都不相同,因此部署的web服务已经具有负载均衡的能力。

扩容on-the-fly

修改docker-compose.yml文件中的replicas的值,重新运行:

1
docker stack deploy -c docker-compose.yml getstartedlab

image.png | left | 681x49

docker执行一个in-place的更新,没必要停止stack或者停止任何容器。

现在重新查看容器列表:

1
docker container ls -q

image.png | left | 681x203

同样的,如果我们扩容了,除了容器增加了,服务包含的task也会相应的增加。

停止app和swarm

  • 停止app
1
docker stack  rm getstartedlab

image.png | left | 636x108

  • 停止swarm
1
docker swarm leave --force

image.png | left | 576x76

使用Docker很容易构建app和扩缩容。

1
2
3
4
5
6
7
8
docker stack ls                                            # List stacks or apps
docker stack deploy -c <composefile> <appname> # Run the specified Compose file
docker service ls # List running services associated with an app
docker service ps <service> # List tasks associated with an app
docker inspect <task or container> # Inspect task or container
docker container ls -q # List container IDs
docker stack rm <appname> # Tear down an application
docker swarm leave --force # Take down a single node swarm from the manager
0%