Tmux使用教程–保持服务后台运行

Tmux 是一个终端复用器(terminal multiplexer),非常有用,属于常用的开发工具。

github: https://github.com/tmux/tmux

Tmux 快捷键 & 速查表 & 简明教程

启动新会话:

tmux [new -s 会话名 -n 窗口名]

恢复会话:

tmux at [-t 会话名]

列出所有会话:

tmux ls

关闭会话:

tmux kill-session -t 会话名

关闭所有会话:

tmux ls | grep : | cut -d. -f1 | awk '{print substr($1, 0, length($1)-1)}' | xargs kill

在 Tmux 中,按下 ctrl+b,然后:

会话

:new<回车>  启动新会话
s           列出所有会话
$           重命名当前会话

窗口 (标签页)

c  创建新窗口
w  列出所有窗口
n  后一个窗口
p  前一个窗口
f  查找窗口
,  重命名当前窗口
&  关闭当前窗口

调整窗口排序

swap-window -s 3 -t 1  交换 3 号和 1 号窗口
swap-window -t 1       交换当前和 1 号窗口
move-window -t 1       移动当前窗口到 1 号

窗格(分割窗口)

%  垂直分割
"  水平分割
o  交换窗格
x  关闭窗格
⍽  左边这个符号代表空格键 - 切换布局
q 显示每个窗格是第几个,当数字出现的时候按数字几就选中第几个窗格
{ 与上一个窗格交换位置
} 与下一个窗格交换位置
z 切换窗格最大化/最小化

同步窗格

这么做可以切换到想要的窗口,输入 Tmux 前缀和一个冒号呼出命令提示行,然后输入:

:setw synchronize-panes

你可以指定开或关,否则重复执行命令会在两者间切换。 这个选项值针对某个窗口有效,不会影响别的会话和窗口。 完事儿之后再次执行命令来关闭。帮助

调整窗格尺寸

如果你不喜欢默认布局,可以重调窗格的尺寸。虽然这很容易实现,但一般不需要这么干。这几个命令用来调整窗格:

PREFIX : resize-pane -D          当前窗格向下扩大 1 格
PREFIX : resize-pane -U          当前窗格向上扩大 1 格
PREFIX : resize-pane -L          当前窗格向左扩大 1 格
PREFIX : resize-pane -R          当前窗格向右扩大 1 格
PREFIX : resize-pane -D 20       当前窗格向下扩大 20 格
PREFIX : resize-pane -t 2 -L 20  编号为 2 的窗格向左扩大 20 格

文本复制模式:

按下 PREFIX-[ 进入文本复制模式。可以使用方向键在屏幕中移动光标。默认情况下,方向键是启用的。在配置文件中启用 Vim 键盘布局来切换窗口、调整窗格大小。Tmux 也支持 Vi 模式。要是想启用 Vi 模式,只需要把下面这一行添加到 .tmux.conf 中:

setw -g mode-keys vi

启用这条配置后,就可以使用 h、j、k、l 来移动光标了。

想要退出文本复制模式的话,按下回车键就可以了。然后按下 PREFIX-] 粘贴刚才复制的文本。

一次移动一格效率低下,在 Vi 模式启用的情况下,可以辅助一些别的快捷键高效工作。

例如,可以使用 w 键逐词移动,使用 b 键逐词回退。使用 f 键加上任意字符跳转到当前行第一次出现该字符的位置,使用 F 键达到相反的效果。

vi             emacs        功能
^              M-m          反缩进
Escape         C-g          清除选定内容
Enter          M-w          复制选定内容
j              Down         光标下移
h              Left         光标左移
l              Right        光标右移
L                           光标移到尾行
M              M-r          光标移到中间行
H              M-R          光标移到首行
k              Up           光标上移
d              C-u          删除整行
D              C-k          删除到行末
$              C-e          移到行尾
:              g            前往指定行
C-d            M-Down       向下滚动半屏
C-u            M-Up         向上滚动半屏
C-f            Page down    下一页
w              M-f          下一个词
p              C-y          粘贴
C-b            Page up      上一页
b              M-b          上一个词
q              Escape       退出
C-Down or J    C-Down       向下翻
C-Up or K      C-Up         向下翻
n              n            继续搜索
?              C-r          向前搜索
/              C-s          向后搜索
0              C-a          移到行首
Space          C-Space      开始选中
               C-t          字符调序

杂项:

d  退出 tmux(tmux 仍在后台运行)
t  窗口中央显示一个数字时钟
?  列出所有快捷键
:  命令提示符

配置选项:

# 鼠标支持 - 设置为 on 来启用鼠标(与 2.1 之前的版本有区别,请自行查阅 man page)
* set -g mouse on

# 设置默认终端模式为 256color
set -g default-terminal "screen-256color"

# 启用活动警告
setw -g monitor-activity on
set -g visual-activity on

# 居中窗口列表
set -g status-justify centre

# 最大化/恢复窗格
unbind Up bind Up new-window -d -n tmp \; swap-pane -s tmp.1 \; select-window -t tmp
unbind Down
bind Down last-window \; swap-pane -s tmp.1 \; kill-window -t tmp

参考配置文件(~/.tmux.conf):

下面这份配置是我使用 Tmux 几年来逐渐精简后的配置,请自取。

# -----------------------------------------------------------------------------
# Tmux 基本配置 - 要求 Tmux >= 2.3
# 如果不想使用插件,只需要将此节的内容写入 ~/.tmux.conf 即可
# -----------------------------------------------------------------------------

# C-b 和 VIM 冲突,修改 Prefix 组合键为 Control-Z,按键距离近
set -g prefix C-z

set -g base-index         1     # 窗口编号从 1 开始计数
set -g display-panes-time 10000 # PREFIX-Q 显示编号的驻留时长,单位 ms
set -g mouse              on    # 开启鼠标
set -g pane-base-index    1     # 窗格编号从 1 开始计数
set -g renumber-windows   on    # 关掉某个窗口后,编号重排

setw -g allow-rename      off   # 禁止活动进程修改窗口名
setw -g automatic-rename  off   # 禁止自动命名新窗口
setw -g mode-keys         vi    # 进入复制模式的时候使用 vi 键位(默认是 EMACS)

# -----------------------------------------------------------------------------
# 使用插件 - via tpm
#   1. 执行 git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm
#   2. 执行 bash ~/.tmux/plugins/tpm/bin/install_plugins
# -----------------------------------------------------------------------------

setenv -g TMUX_PLUGIN_MANAGER_PATH '~/.tmux/plugins'

# 推荐的插件(请去每个插件的仓库下读一读使用教程)
set -g @plugin 'seebi/tmux-colors-solarized'
set -g @plugin 'tmux-plugins/tmux-pain-control'
set -g @plugin 'tmux-plugins/tmux-prefix-highlight'
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'tmux-plugins/tmux-yank'
set -g @plugin 'tmux-plugins/tpm'

# tmux-resurrect
set -g @resurrect-dir '~/.tmux/resurrect'

# tmux-prefix-highlight
set -g status-right '#{prefix_highlight} #H | %a %Y-%m-%d %H:%M'
set -g @prefix_highlight_show_copy_mode 'on'
set -g @prefix_highlight_copy_mode_attr 'fg=white,bg=blue'

# 初始化 TPM 插件管理器 (放在配置文件的最后)
run '~/.tmux/plugins/tpm/tpm'

# -----------------------------------------------------------------------------
# 结束
# ---------------

tmux中多窗口表示时,没有滚动条,可以使用下面的方法翻页查看信息

  1. 绑定的快捷开始键 默认 ctrl +b + [
  2. 使用 PgUp PgDn 实现上下翻页
  3. 键 q 退出

meta 标签

在学习前端过程中,对于head标签内部的内容没有进行深入的了解过,今天来总结一下。



head里可以放这些标签:

<head>元素包含了所有的头部标签元素,在head中你可以插入脚本(scripts),样式文件(CSS),及各种meta信息,可以添加到头部区域的元素为

<title> 定义网页的标题(浏览器工具栏标题,搜素引擎结果页面标题,收藏夹标题)

<meta> 用来定义页面的特殊信息(页面关键字,页面描述) ,描述了一些基本的元数据

<link> 定义了文档与外部资源之间的关系,通常用来引入外部样式(css文件)     

<style>用来定义元素的css样式

 <script> 用来定义页面的JavaScript 代码 也可用来引入文件

 <base> 可以用来统一设置当前页面上的超链接的跳转方式 使用了 <base> 标签,则必须具备 href 属性或者 target 属性或者两个属性都具备。

<noscript>是一个相当古老的标签,其被引入的最初目的是帮助老旧浏览器的平滑升级更替,因为早期的浏览器并不能支持 JavaScript。noscript 标签在不支持JavaScript 的浏览器中显示替代的内容。这个元素可以包含任何 HTML 元素。

在平常的使用中,一般利用vscode插件自动生成html框架,因此基本很少去编写meta标签。自动生成的meta标签:

 <meta charset="UTF-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">

重点:meta标签

meta 元素往往不会引起用户的注意,但是meta对整个网页有影响,会对网页能否被搜索引擎检索,和在搜索中的排名起着关键性的作用。


                 <meta http-equiv=" " name=" " content=" ">

meta有个必须的属性content用于表示需要设置的项的值。

meta存在两个非必须的属性http-equiv和name, 用于表示要设置的项

比如<meta http-equiv=”Content-Security-Policy” content=”upgrade-insecure-requests”>,设置的项是Content-Security-Policy设置的值是upgrade-insecure-requests

1. http-equiv 属性

http-equiv一般设置的都是与http请求头相关的信息,设置的值会关联到http头部。也就是说浏览器在请求服务器获取html的时候,服务器会将html中设置的meta放在响应头中返回给浏览器。常见的类型比如content-type, expires, refresh, set-cookie, window-target, charset, pragma等等。

1.1 http-equiv 属性类型:content-type

比如:<meta http-equiv=”content-type” content=”text/html charset=utf8″>可以用来声明文档类型,设置字符集,content-type几乎所有的属性都可以在meta中进行设置。这样设置浏览器的头信息就会包含:content-type: text/html charset=utf8

1.2 expires

用于设置浏览器的过期时间, 其实就是响应头中的expires属性。

<meta http-equiv="expires" content="31 Dec 2021"> expires:31 Dec 2008

1.3 refresh

该种设定表示5秒自动刷新并且跳转到指定的网页。如果不设置url的值那么浏览器则刷新本网页。<meta http-equiv="refresh" content="5 url=http://www.zhiqianduan.com">

1.4 window-target

强制页面在当前窗口以独立页面显示, 可以防止别人在框架中调用自己的页面。

<meta http-equiv="window-target" content="_top >

1.5 pragma

禁止浏览器从本地计算机的缓存中访问页面的内容

<meta http-equiv="pragma" content="no-cache">

2. name 属性

name属性主要用于描述网页,与对应的content中的内容主要是便于搜索引擎查找信息和分类信息用的, 用法与http-equiv相同,name设置属性名,content设置属性值。

1. author

author用来标注网页的作者<meta name="author" content="aaa@mail.abc.com">

2. description

description用来告诉搜素引擎当前网页的主要内容,是关于网站的一段描述信息。<meta name="description" content="这是我的HTML">

3. keywords

keywords设置网页的关键字,来告诉浏览器关键字是什么。是一个经常被用到的名称。它为文档定义了一组关键字。某些搜索引擎在遇到这些关键字时,会用这些关键字对文档进行分类。<meta name="keywords" content="Hello world">

4. generator

表示当前html是用什么工具编写生成的,并没有实际作用,一般是编辑器自动创建的。<meta name="generator" content="vscode">

5. revised

指定页面的最新版本<meta name="revised" content="V2,2015/10/1">

6. robots

告诉搜索引擎机器人抓取哪些页面,all / none / index / noindex / follow / nofollow。<meta name="robots" content="all"> all:文件将被检索,且页面上的链接可以被查询;none:文件将不被检索,且页面上的链接不可以被查询;index:文件将被检索;follow:页面上的链接可以被查询;noindex:文件将不被检索,但页面上的链接可以被查询;nofollow:文件将不被检索,页面上的链接可以被查询。

3. scheme 属性

scheme 属性用于指定要用来翻译属性值的方案。此方案应该在由 head 标签的 profile 属性指定的概况文件中进行了定义。html5不支持该属性。

5. base 标签

base标签定义了文档的基础url地址,在文档中所有的相对地址形式的url都是相对于这里定义的url而言的。为页面上的链接规定默认地址或目标。

base标签包含的属性。

1. href

href是必选属性,指定了文档的基础url地址。例如,如果希望将文档的基础URL定义为https://www.abc.com,则可以使用如下语句:<base href=”http://www.abc.com”>如果文档的超链接指向welcom.html,则它实际上指向的是如下url地址:https://www.abc.com/welocme.html。

2. target

定义了当文档中的链接点击后的打开方式_blank,_self,_parrent,_top。

6. link 标签

link用于引入外部样式表,在html的头部可以包含任意数量的link,link标签有以下常用属性。<link type="text/css" rel="stylesheet" href="github-markdown.css">

1. type

定义包含的文档类型,例如text/css

2. rel

定义html文档和所要包含资源之间的链接关系,可能的值有很多,最为常用的是stylesheet,用于包含一个固定首选样式的表单。

3. href

表示指向被包含资源的url地址

7. style 标签

编写内部样式表的标签。<style>    body {        background: #f3f5f9;    }</style>

8. script 标签

加载javascript脚本的标签。加载的脚本会被默认执行。默认情况下当浏览器解析到script标签的时候会停止html的解析而开始加载script代码并且执行。<script src="script.js"></script>

1. type

指示脚本的MIME类型。<script type="text/javascript">

2. async

规定异步执行脚本,仅适用于通过src引入的外部脚本。设置的async属性的script加载不会影响后面html的解析,加载是与文档解析同时发生的。加载完成后立即执行。执行过程会停止html文档解析。<script async src="script.js"></script>

3. charset

规定在外部脚本文件中使用的字符编码。<script type="text/javascript" src="script.js" charset="UTF-8"></script>

4. defer

规定是否对脚本执行进行延迟,直到页面加载为止。设置了defer属性的script不会阻止后面html的解析,加载与解析是共同进行的,但是script的执行要在所有元素解析完成之后,DOMContentLoaded事件触发之前完成。<script defer src="script.js"></script>

5. language

规定脚本语言,与“type“`功能类似,不建议使用该字段。

6. src

外部脚本的地址。<script src="script.js"></script>

9. bgsound

网站背景音乐。<bgsound src="music.mp4" autostart="true" loop="5">

1. src

表示背景音乐的url值。

2. autostart

是否自动播放ture自动播放,false不播放,默认为false。

3. loop

是否重复播放,值为数字或者infinite,表示重复具体次或无限次。

编码器—解码器(seq2seq)

当输入和输出都是不定长序列时(比如机器翻译),我们可以使用编码器—解码器(encoder-decoder) 或者seq2seq模型。这两个模型本质上都用到了两个循环神经网络,分别叫做编码器和解码器。编码器用来分析输入序列,解码器用来生成输出序列。

以机器翻译为例,输入可以是一段不定长的英语文本序列,输出可以是一段不定长的法语文本序列,例如

英语输入:“They”、“are”、“watching”、“.”

法语输出:“Ils”、“regardent”、“.”

下图描述了使用编码器—解码器将上述英语句子翻译成法语句子的一种方法。在训练数据集中,我们可以在每个句子后附上特殊符号“<eos>”(end of sequence)以表示序列的终止。编码器每个时间步的输入依次为英语句子中的单词、标点和特殊符号“<eos>”。下图中使用了编码器在最终时间步的隐藏状态作为输入句子的表征或编码信息。解码器在各个时间步中使用输入句子的编码信息和上个时间步的输出以及隐藏状态作为输入。我们希望解码器在各个时间步能正确依次输出翻译后的法语单词、标点和特殊符号”<eos>”。需要注意的是,解码器在最初时间步的输入用到了一个表示序列开始的特殊符号”<bos>”(beginning of sequence)。

使用编码器—解码器将句子由英语翻译成法语。编码器和解码器分别为循环神经网络

编码器的作用是把一个不定长的输入序列变换成一个定长的背景变量c,并在该背景变量中编码输入序列信息。常用的编码器是循环神经网络。

根据最大似然估计,我们可以最大化输出序列基于输入序列的条件概率,并得到该输出序列的损失,在模型训练中,所有输出序列损失的均值通常作为需要最小化的损失函数。

个人理解:

最后decoder每一步输出的是一个字典大小的概率值向量,分别表示这一步输出所有值的概率,一般取最大的值作为输出。

字典大小为4

因此那么在模型预测的时候就需要进行搜索,选择不同的搜索方式决定每一时间步的输出是字典里的那个值,选择不同的值会影响下一时间步的输出概率。

模型预测 Model Prediction

为了搜索该条件概率最大的输出序列,一种方法是穷举所有可能输出序列的条件概率,并输出条件概率最大的序列。我们将该序列称为最优序列,并将这种搜索方法称为穷举搜索 (exhaustive search)。

贪婪搜索 Greedy Search

我们还可以使用贪婪搜索 (greedy search) 。也就是说,对于输出序列任一时间步 [公式] ,从 [公式] 个词中搜索出输出词

[公式]

且一旦搜索出 “<eos>” 符号即完成输出序列。贪婪搜索的计算开销是 [公式] 。它比起穷举搜索的计算开销显著下降。例如,当 [公式] 且 [公式] 时,我们只需评估 [公式] 个序列。

下面我们来看一个例子。假设输出词典里面有 “A”、“B”、“C”和 “<eos>” 这四个词。下图中每个时间步下的四个数字分别代表了该时间步生成 “A”、“B”、“C”和 “<eos>” 这四个词的条件概率。在每个时间步,贪婪搜索选取生成条件概率最大的词。因此,将生成序列 “ABC<eos>” 。该输出序列的条件概率是 [公式] 。

束搜索 Beam Search

束搜索 (beam search) 是比贪婪搜索更加广义的搜索算法。它有一个束宽 (beam size) 超参数。我们将它设为 [公式] 。在时间步1时,选取当前时间步生成条件概率最大的 [公式] 个词,分别组成 [公式] 个候选输出序列的首词。在之后的每个时间步,基于上个时间步的 [公式] 个候选输出序列,从 [公式] 个可能的输出序列中选取生成条件概率最大的 [公式] 个,作为该时间步的候选输出序列。

最终,我们在各个时间步的候选输出序列中筛选出包含特殊符号 “<eos>” 的序列,并将它们中所有特殊符号 “<eos>” 后面的子序列舍弃,得到最终候选输出序列。在这些最终候选输出序列中,取以下分数最高的序列作为输出序列:

[公式]
[公式]

其中 [公式] 为最终候选序列长度, [公式] 一般可选为0.75。分母上的 是为了惩罚较长序列在以上分数中较多的对数相加项。分析可得,束搜索的计算开销为 [公式] 。这介于穷举搜索和贪婪搜索的计算开销之间。

  • 预测不定长序列的方法包括贪婪搜索、穷举搜索和束搜索。
  • 束搜索通过灵活的束宽来权衡计算开销和搜索质量。

注意力机制

在普通的编码器-解码器模型中,有一个很大的局限性。那就是上下文变量对于 Decoding 阶段每个时间步都是一样的,这可能是模型性能的一个瓶颈。我们希望不同时间步的解码能够依赖于与之更相关的上下文信息,换句话说,Decoding 往往并不需要整个输入序列的信息,而是要有所侧重。于是,Bengio 团队的 Bahdanau 在 2014年首次在编码器-解码器模型中引入了注意力机制 (Attention Mechanism):

注意力机制通过注意力汇聚将查询(自主性提示)和键(非自主性提示)结合在一起,实现对值(感官输入)的选择倾向

动机 Motivation

以英语-法语翻译为例,给定一对英语输入序列 “They”、“are”、“watching”、“.” 和法语输出序列 “Ils”、“regardent”、“.”。解码器可以在输出序列的时间步1使用更集中编码了 “They”、“are” 信息的上下文变量来生成 “Ils”,在时间步2使用更集中编码了 “watching” 信息的上下文变量来生成“regardent”,在时间步3使用更集中编码了 “.” 信息的上下文变量来生成 “.”。这看上去就像是在解码器的每一时间步对输入序列中不同时间步编码的信息分配不同的注意力。这也是注意力机制的由来。它最早由 Bahanau 等人提出。

仍然以循环神经网络为例,注意力机制通过对编码器所有时间步的隐藏状态做加权平均来得到背景变量。解码器在每一时间步调整这些权重,即注意力权重,从而能够在不同时间步分别关注输入序列中的不同部分并编码进相应时间步的背景变量。

我们先描述第一个关键点,即计算背景变量。下图描绘了注意力机制如何为解码器在时间步2计算背景变量。首先,函数a根据解码器在时间步1的隐藏状态和编码器在各个时间步的隐藏状态计算softmax运算的输入。softmax运算输出概率分布并对编码器各个时间步的隐藏状态做加权平均,从而得到背景变量。

本质上,注意力机制能够为表征中较有价值的部分分配较多的计算资源。这个有趣的想法自提出后得到了快速发展,特别是启发了依靠注意力机制来编码输入序列并解码出输出序列的变换器(Transformer)模型的设计 [2]。变换器抛弃了卷积神经网络和循环神经网络的架构。它在计算效率上比基于循环神经网络的编码器—解码器模型通常更具明显优势。含注意力机制的变换器的编码结构在后来的BERT预训练模型中得以应用并令后者大放异彩:微调后的模型在多达11项自然语言处理任务中取得了当时最先进的结果 [3]。不久后,同样是基于变换器设计的GPT-2模型于新收集的语料数据集预训练后,在7个未参与训练的语言模型数据集上均取得了当时最先进的结果 [4]。除了自然语言处理领域,注意力机制还被广泛用于图像分类、自动图像描述、唇语解读以及语音识别

评价机器翻译结果

评价机器翻译结果通常使用BLEU(Bilingual Evaluation Understudy)[1]。对于模型预测序列中任意的子序列,BLEU考察这个子序列是否出现在标签序列中。

总结 Conclusions

让我们回顾一下带注意力机制的编码器-解码器的整个设计:

  1. Encoder 总结输入序列的信息,得到上下文变量 [公式]
  2. Decoder 将上下文变量 [公式] 中的信息解码生成输出序列
  3. 设计 [公式] 函数
  4. 计算当前时间步的隐藏状态 [公式]
  5. 计算当前时间步的解码器输出概率 [公式]
  6. 得到输出序列的联合概率 [公式] 并最大化
  7. 根据 MLE,就是最小化联合概率的负对数
  8. 得到 loss function
  9. 用优化方法降低 loss,学习模型参数
  10. 为了避免相同的上下文变量对模型性能的限制,给编码器-解码器模型加入了注意力机制。

Convolutional Neural Networks for Sentence Classification

https://arxiv.org/abs/1408.5882

github实现

https://github.com/yoonkim/CNN_sentence

https://github.com/Cheneng/TextCNN

对于文本分类,我们能不能用CNN来做,用某种模型初始化,进而做fine-tune呢?答案是肯定的,用于文本分析的CNN—TextCNN。

text-cnn用于情感分类:

与二维卷积层一样,一维卷积层使用一维的互相关运算。在一维互相关运算中,卷积窗口从输入数组的最左方开始,按从左往右的顺序,依次在输入数组上滑动。当卷积窗口滑动到某一位置时,窗口中的输入子数组与核数组按元素相乘并求和,得到输出数组中相应位置的元素。

多输入通道的一维互相关运算也与多输入通道的二维互相关运算类似:在每个通道上,将核与相应的输入做一维互相关运算,并将通道之间的结果相加得到输出结果。

由二维互相关运算的定义可知,多输入通道的一维互相关运算可以看作单输入通道的二维互相关运算。

类似地,我们有一维池化层。textCNN中使用的时序最大池化(max-over-time pooling)层实际上对应一维全局最大池化层:假设输入包含多个通道,各通道由不同时间步上的数值组成,各通道的输出即该通道所有时间步中最大的数值。因此,时序最大池化层的输入在各个通道上的时间步数可以不同。

简单来说,时序最大池化层就是沿着时序方向进行最大池化。

textCNN模型主要使用了一维卷积层和时序最大池化层。假设输入的文本序列由n个词组成,每个词用d维的词向量表示。那么输入样本的宽为n,高为1,输入通道数为d。textCNN的计算主要分为以下几步。(输入通道就是每个词的d为维度表示,宽就是时序长度)

词用d维的词向量表示 :一般使用词嵌入模型word2vec.

  1. 定义多个一维卷积核,并使用这些卷积核对输入分别做卷积计算。宽度不同的卷积核可能会捕捉到不同个数的相邻词的相关性。
  2. 对输出的所有通道分别做时序最大池化,再将这些通道的池化输出值连结为向量。
  3. 通过全连接层将连结后的向量变换为有关各类别的输出。这一步可以使用丢弃层应对过拟合。

下图用一个例子解释了textCNN的设计。这里的输入是一个有11个词的句子,每个词用6维词向量表示。因此输入序列的宽为11,输入通道数为6。给定2个一维卷积核,核宽分别为2和4,输出通道数分别设为4和5。因此,一维卷积计算后,4个输出通道的宽为11−2+1=10,而其他5个通道的宽为11−4+1=8。尽管每个通道的宽不同,我们依然可以对各个通道做时序最大池化,并将9个通道的池化输出连结成一个9维向量。最终,使用全连接将9维向量变换为2维输出,即正面情感和负面情感的预测。

Dive-into-DL-PyTorch
pytorch代码实现:
https://github.com/chenpaopao/TextCNN

总结:

  • 可以使用一维卷积来表征时序数据。
  • 多输入通道的一维互相关运算可以看作单输入通道的二维互相关运算。
  • 时序最大池化层的输入在各个通道上的时间步数可以不同。
  • textCNN主要使用了一维卷积层和时序最大池化层。
https://www.pexels.com/zh-cn/photo/977739/

torchtext.vocab的学习

NLP常见的数据预处理工作如下:

  1. Load File:数据文件加载;
  2. Tokenization:分词;
  3. Create Vocabulary:创建字典;
  4. Indexify:将词与索引进行映射;
  5. Word Vectors:创建或加载词向量;
  6. Padding or Fix Length:按长度对文本进行补齐或截取;
  7. Dataset Splits:划分数据集(如将数据集划分问训练集、验证集、测试集);
  8. Batching and Iterators:将数据集按固定大小划分成Batch;

使用torchtext完成以上工作:

  • 使用torchtext.data.Field定义样本各个字段的处理流程(分词、数据预处理等);
  • 使用torchtext.data.Example将torchtext.data.Field处理成一条样本;
  • 使用torchtext.data.Dataset将torchtext.data.Example处理成数据集,也可对数据集进行划分等工作;
  • 使用torchtext.data.Iterators将torchtext.data.Dataset按照batch_size组装成Batch供模型训练使用;
  • 使用torchtext.data.vocab和torchtext.data.Vectors创建词典、词和索引的一一对应、下载或使用预训练的词向量等;

  • vocab
  • 一句话概括主要是用来建立词汇表创建词典、词和索引的一一对应、下载或使用预训练的词向量等

    常见的词嵌入模型:word2vec Glove

    Pretrained Word Embeddings

    CLASS torchtext.vocab.GloVe(name='840B', dim=300, **kwargs)
    
    CLASS torchtext.vocab.FastText(language='en', **kwargs)
    
    CLASS torchtext.vocab.ChaarNGram(**kwargs)

    返回的实例主要有以下三个属性:

    • stoi: 词到索引的字典:
    • itos: 一个列表,索引到词的映射;
    • vectors: 词向量。

    通过上面的模块,实现由词到向量之间的转换!!!

    vocab.Vocab 是一个词汇表对象(由 下面的vocab 生成 Vocab 对象),使用counter创建词汇表

    collections.Counter 构建词汇表

    class collections.Counter([iterable-or-mapping])

    一个 Counter 是一个 dict 的子类,用于计数可哈希对象。它是一个集合,元素像字典键(key)一样存储,它们的计数存储为值。计数可以是任何整数值,包括0和负数。 Counter 类有点像其他语言中的 bags或multisets。Counter实际上也是dict的一个子类,之不多可以统计不同的值出现的次数。

    CLASS torchtext.vocab.Vocab(counter, max_size=None, min_freq=1, 
      specials=('<unk>', '<pad>'), vectors=None, unk_init=None, vectors_cache=None,
      specials_first=True)
      定义词汇表。属性:Vocab.freqs, Vocab.stoi, Vocab.itos
      __init__(counter,...)
          从 collections.Counter 构建词汇表
      load_vectors(vectors, **kwargs)
          vectors - GloVe, CharNGram, Vectors 实例,或者可用的预训练向量。
      set_vectors(stoi, vectors, dim, unk_init=...)
          从一个张量集合中设置词汇表实例的向量。
          stoi - 字符串 到 `vectors` 相应向量的索引 的字典
          vectors - 该参数支持索引 __getitem__。输入一个索引值,返回索引对应词条的向量(FloatTensor)。
                例如:vector[stoi["string"]] 应该返回 “string" 的词向量。
          dim - 词向量的维度

    torchtext.vocab.vocab 使用dict创建词汇表对象

    torchtext.vocab.vocabordered_dict: Dict , min_freq: int = 1 ) → torchtext.  vocab.Vocab[来源]

    用于创建将标记映射到索引的vocab对象的工厂方法。

    请注意,在构建vocab时,将遵守在ordered_dict中插入键值对的顺序。因此,如果按标记频率排序对用户很重要,则应以反映这一点的方式创建ordered_dict。

    参数

    • ordered_dict – 有序字典将标记映射到它们相应的出现频率。
    • min_freq – 在词汇表中包含一个标记所需的最小频率。

    Returns

    A Vocab objectReturn type

    torchtext.vocab.Vocab

    根据分好词的训练数据集来创建词典,过滤掉了出现次数少于5的词。

    
    #实例
    def get_vocab_imdb(data):
        tokenized_data = get_tokenized_imdb(data)
        counter = collections.Counter([tk for st in tokenized_data for tk in st])
        return Vocab.vocab(counter, min_freq=5)
    
    vocab = get_vocab_imdb(train_data)
    '# words in vocab:', len(vocab)
    
    输出:('# words in vocab:', 46151)
    
    

    SubwordVocab: 构建子词汇表

    CLASS torchtext.vocab.SubwordVocab(counter, max_size=None, specials='<pad>'
        vectors=None, unk_init=...)
      __init__(counter, ...)
          从 collections.Counter 构建子词词汇表,
          specials - padding or eos 等

    Vectors:返回词向量

    Look up embedding vectors of tokens

    CLASS torchtext.vocab.Vectors(name, cache=None, url=None, unk_init=None,
        max_vectors=None)
      __init__(name, cache=None, url=None, unk_init=None, max_vectors=None)
          name - 包含向量的文件名
          cache - 缓存向量文件的目录
          url - 如果缓存中没有找到向量文件,则从该链接下载
          max_vectors - 用于限制加载的预训练此向量的数量。大部分预训练的向量集都是按照词频降序排列
             在整个集合不适合内存或者不需要全部加载的情况下,可以加此限制。
      get_vecs_by_tokens(tokens, lower_case_backup=False)
          用于查找词条的嵌入向量。
          tokens - 一个词条或词条列表。如果一个词条,返回 self.dim 形状的一维张量;如果列表,返回
                 (len(tokens), self.dim)形状的二维张量。
          lower_case_backup -  是否查找小写的词条。如果为True,先按原格式在 `stoi` 查找,
                没找到的话,再查小写格式
    
    examples = ['chip', 'baby', 'Beautiful']
    vec = text.vocab.GloVe(name='6B', dim=50)
    ret = vec.get_vecs_by_tokens(tokens, lower_case_backup=True)

    build_vocab_from_iterator :从迭代器创建vocab

    torchtext.vocab.build_vocab_from_iterator(iterator, num_lines=None)
        从 迭代器 建立词汇表
        iterator - 必须产生词条的列表或迭代器
        num_lines - 迭代器返回元素的预期数量。
    
    
    torchtext.vocab.build_vocab_from_iterator(iterator: Iterable, min_freq: int = 1, specials: Optional[List[str]] = None, special_first: bool = True) → torchtext.vocab.vocab.Vocab[SOURCE]
    Build a Vocab from an iterator.
    
    Parameters
    iterator – Iterator used to build Vocab. Must yield list or iterator of tokens.
    
    min_freq – The minimum frequency needed to include a token in the vocabulary.
    
    specials – Special symbols to add. The order of supplied tokens will be preserved.
    
    special_first – Indicates whether to insert symbols at the beginning or at the end.
    https://pixabay.com/

    python 通过分享的链接下载抖音视频

    """下载抖音无水印的视频"""
    import re
    import requests
    
    useragent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36'
    headers = {'User-Agent': useragent}
    
    
    def dy_url_op(data):
        '''从data中找到url,返回str'''
        url=''
        url=re.findall(r'(https?://[^\s]+)',data)
        return url[0]
    
    def get_real_url(url,headers):
        session = requests.Session()
        req = session.get(url , timeout = 5 , headers = headers)
        vid = req.url.split("/")[4].split("?")[0]
        videoInfo = session.get("https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=" + vid,
            timeout = 5 , headers = headers)
        playAddr = re.findall(r'https://aweme.snssdk.com/aweme[\S]*?"',videoInfo.text)[0][:-1]
        parsedAddr = playAddr.replace("/playwm/","/play/")
        return vid, parsedAddr, session
    
    def downlowd_video(video_url):
        '''下载视频'''
        video_data=requests.get(video_url,headers=headers)
        with open('video.mp4','wb') as f:
            f.write(video_data.content)
    
    print("请输入如下格式:\n8.94 mdN:/ 孙红雷的瓜保熟么?%扫黑风暴开播   https://v.douyin.com/eEPNXH4/ 復制佌鏈接,打开Dou音搜索,直接观看视频!")
    data = input("请输入抖音视频链接:")
    vid, parsedAddr, session = get_real_url(dy_url_op(data),headers)
    downlowd_video(parsedAddr)
    print("下载完成!")

    爬虫过程中URL的变化:

    • 第1个URL(抖音分享的URL):https://v.douyin.com/JuGgK3M/
    • 第2个URL:https://www.iesdouyin.com/share/video/6893421572934159629/……
    • 第3个URL:https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=6893421572934159629
    • 第4个URL(视频URL):
      https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f350000bul5ktfae8n8kibjf990&ratio=720p&line=0

    python 正则表达式和re模块

    正则表达式

    . 点号,在默认模式下,匹配除换行以外的任意字符。如果 DOTALL 标志被指定, 则匹配包括换行符在内的所有字符。

    ^    乘方运算符或脱字节符,在默认模式下匹配字符串的起始位置,在MULTILINE模式下也匹配换行符之后的位置。
    $    匹配字符串的末尾或者字符串末尾换行符之前的位置,在MULTILINE模式下还匹配换行符之前的位置。
    *    匹配前面重复出现的正则表达式零次或多次,尽可能多的匹配(greedy 贪婪型)。
    +    匹配前面RE 1次或多次(贪婪型,尽可能多的匹配)。
    ?    匹配前面的RE 0次或1次。
    *?,+?,??    '*'、'+'和'?'限定符是贪婪的;它们匹配尽可能多的文本。在限定符之后加上'?'将使得匹配以非贪婪的或最小的方式进行。
    {m}        表示精确匹配前面的正则表达式的m个拷贝,较少的匹配将导致整个表达式不能匹配。
    {m,n}    匹配前导正则表达式的m到n个重复,尝试匹配尽可能多的重复(greedy 贪婪型)。
    {m,}    匹配前导正则表达式的至少m次,尝试匹配尽可能多的重复(greedy 贪婪型)。
    {,n}    匹配前导正则表达式的至多n次,尝试匹配尽可能多的重复(greedy 贪婪型)。
    {m,n}?    匹配前导正则表达式的m到n个重复,尝试匹配尽可能少的重复(Non-greedy 非贪婪型)。
    \        对特殊符号进行转义
    []        用来表示一个字符集合。
            字符可以一个一个的列出来,如[abcd],则可以匹配'a','b','c','d'。
            通过给出两个字符并用'-'分隔,可以给出一段范围的字符,如[a-z]匹配小写字母,[A-Z]匹配大写字母,[0-9]匹配0-9的数字。
            在集合内部,特殊字符将失去它们特殊的含义,如[(+*)]将匹配'(','+','*',')'。
            在集合中接受字符类别\s,\S,\w等。
            可以使用[^RE]作为字符集的补集,^必须为集合第一个字符,如[^a-z]可以匹配除小写字母外所有的字符。
    |        a|b 匹配a或b,(Non-greedy 非贪婪型),匹配上正则a后,就不会再去尝试匹配正则b。
    (...)    被圆括号括起来的表达式将作为分组,分组表达式作为一个整体,后面可以接数量词,表达式中|仅在该组中有效。
            如(a-z|A-Z){2,3}表示匹配字母2至3次。
    (?aiLmsux)    给整个正则表达式设置相应的标记:re.A(ASCII码模式),re.I(忽略大小写),re.L(依赖区域设置);
                re.M(多行模式),re.S(点号匹配所有字符),re.U(依赖Unicode),re.X(详细模式)
    (?:...)    # 当你要将一部分规则作为一个整体对它进行某些操作,可以使用(?:RE)将正则表达式RE包裹起来。
    (?P<name>...)    # 将RE字符串包裹进来作为一个命名组。
    (?P=name)        # 使用命名组进行匹配。匹配前面定义的命名组匹配到的字符串。
    (?#...)            # 添加备注,忽略指定的字符。
    (?='...')        # 如果指定的字符在匹配到的字符后面,才算匹配成功。s='Isaac Asimov'   m=re.findall("Isaac (?=Asimov)",s)
    (?!...)            # 如果指定的字符不在匹配到的字符后面,才算匹配成功。s='Isaac Asimov'   m=re.findall("Isaac (?!Asimov)",s)
    (?<=...)         # 如果指定的字符在匹配到的字符前面,才算匹配成功。s='Isaac Asimov'   m=re.findall("(?<=Isaac )Asimov",s)
    (?<!...)        # 如果指定的字符不在匹配到的字符前面,才算匹配成功。s='Isaac Asimov'   m=re.findall("(?<!Isaac )Asimov",s)
    (?(id/name)yes|no)        #选择性匹配 (?(id/name)yes-pattern|no-pattern) 的作用是:
                                对于给出的id或者name,先尝试去匹配 yes-pattern部分的内容;
                                如果id或name条件不满足,则去匹配no-pattern部分的内容;no-pattern部分可以省略;
                                此处的name或id,是针对(当前位置的)条件性匹配之前的,某个已经通过group去分组的内容
                                如果是有命名的分组,即named group,则对应的该分组就有对应的name,即此处所指的就是对应的name;
                                如果是无命名的分组,即unnamed group,则对应的该分组也有对应的分组的编号,称为group的number,
                                也叫做id,对应的就是这里的id。
        *** 预定义字符集
    \\        匹配反斜杠
    \A        匹配字符串开头,同^
    \Z        匹配字符串结尾,同$
    \number    匹配相同编号的组的内容
    \b        匹配空字符串,仅在词的开头和结尾
    \B        匹配空字符串,不在词的开头和结尾,与\b相反
    \d        匹配数字,等同于[0-9]
    \D        匹配非数字,等同于\d的补集,即[^\d]
    \s        匹配whitespace字符串,同等于[ \t\n\r\f\v]
    \S        匹配非whitespace字符串,\s的补集,[^\s]
    \w        匹配字母,数字,下划线,等同于[a-zA-Z0-9_]
    \W        \w的补集

    使用re模块的步骤

    我们有必要对re模块中所包含的类及其工作流程进行一下简单的、整体性的说明,这讲有利于我们对下面内容的理解。

    使用re模块进行正则匹配操作的步骤:

    • 编写表示正则表达式规则的Python字符串str;
    • 通过re.compile()函数编译该Python字符串获得一个正则表达式对象(Pattern Object)p;
    • 通过正则表达式对象的p.match()或p.fullmatch()函数获取匹配结果–匹配对象(Match Object)m;
    • 通过判断匹配对象m是否为空可知是否匹配成功,也可以通过匹配对象m提供的方法获取匹配内容。

    使用re模块进行内容查找、替换和字符串分隔操作的步骤:

    • 编写表示正则表达式规则的Python字符串str;
    • 通过re.compile()函数编译该Python字符串获得一个正则表达式对象(Pattern Object)p;
    • 通过正则表达式对象的p.search()或p.findall()或p.finditer()或p.sub()或p.subn()或p.split()函数完内容查找、替换和字符串分隔操作并获取相应的操作结果;

    总结:

    • 根据上面的描述可知,将一个表示正则表达式的Python字符串编译成一个正则表达式对象是使用正则表达式完成相应功能的首要步骤.
    • re模块中用于完成正则表达式编译功能的函数为re.compile()。

    re.compile(patternflags=0)将正则表达式的样式编译为一个 正则表达式对象 (正则对象),可以用于匹配,通过这个对象的方法 match()search() 以及其他如下描述。

     

    prog = re.compile(pattern)
    result = prog.match(string)
    

    等价于

    result = re.match(pattern, string)
    re.search(patternstringflags=0)

    扫描整个 字符串 找到匹配样式的第一个位置,并返回一个相应的 匹配对象。如果没有匹配,就返回一个 None ; 注意这和找到一个零长度匹配是不同的。

    re.match(patternstringflags=0)

    如果 string 开始的0或者多个字符匹配到了正则表达式样式,就返回一个相应的 匹配对象 。 如果没有匹配,就返回 None ;注意它跟零长度匹配是不同的。

    注意即便是 MULTILINE 多行模式, re.match() 也只匹配字符串的开始位置,而不匹配每行开始。

    如果你想定位 string 的任何位置,使用 search() 来替代(也可参考 search() vs. match() )

    re.fullmatch(patternstringflags=0)

    如果整个 string 匹配到正则表达式样式,就返回一个相应的 匹配对象 。 否则就返回一个 None ;注意这跟零长度匹配是不同的。

    3.4 新版功能.

    re.split(patternstringmaxsplit=0flags=0)

    用 pattern 分开 string 。 如果在 pattern 中捕获到括号,那么所有的组里的文字也会包含在列表里。如果 maxsplit 非零, 最多进行 maxsplit 次分隔, 剩下的字符全部返回到列表的最后一个元素。

    re.findall(patternstringflags=0)

    Return all non-overlapping matches of pattern in string, as a list of strings or tuples. The string is scanned left-to-right, and matches are returned in the order found. Empty matches are included in the result.

    The result depends on the number of capturing groups in the pattern. If there are no groups, return a list of strings matching the whole pattern. If there is exactly one group, return a list of strings matching that group. If multiple groups are present, return a list of tuples of strings matching the groups. Non-capturing groups do not affect the form of the result.

     

    字符串分割

    使用re.split(pattern, string, maxsplit=0, flags=0)进行字符分割:

    字符串替换

    使用re.sub(pattern, repl, string, count=0, flags=0)对字符串进行替换:

    字符转义

    使用re.escape将所有字符转义:

    数据扩充和增广

    chenpaopao

    最近在学习 torch,对于图像数据的预处理, torchvision 提供了torchvision.transforms 模块,用于预处理。

    1. 1. 裁剪——Crop 中心裁剪:transforms.CenterCrop 随机裁剪:transforms.RandomCrop 随机长宽比裁剪:transforms.RandomResizedCrop 上下左右中心裁剪:transforms.FiveCrop 上下左右中心裁剪后翻转,transforms.TenCrop
    2. 2. 翻转和旋转——Flip and Rotation 依概率p水平翻转:transforms.RandomHorizontalFlip(p=0.5) 依概率p垂直翻转:transforms.RandomVerticalFlip(p=0.5) 随机旋转:transforms.RandomRotation
    3. 3. 图像变换 resize:transforms.Resize 标准化:transforms.Normalize 转为tensor,并归一化至[0-1]:transforms.ToTensor 填充:transforms.Pad 修改亮度、对比度和饱和度:transforms.ColorJitter 转灰度图:transforms.Grayscale 线性变换:transforms.LinearTransformation() 仿射变换:transforms.RandomAffine 依概率p转为灰度图:transforms.RandomGrayscale 将数据转换为PILImage:transforms.ToPILImage transforms.Lambda:Apply a user-defined lambda as a transform.
    4. 4. 对transforms操作,使数据增强更灵活 transforms.RandomChoice(transforms), 从给定的一系列transforms中选一个进行操作 transforms.RandomApply(transforms, p=0.5),给一个transform加上概率,依概率进行操作 transforms.RandomOrder,将transforms中的操作随机打乱

    此外,还提供了 torchvision.transforms.Compose( ),可以同时传递多个函数

    mytransform = transforms.Compose([
    transforms.ToTensor()
    ]
    )
    
    # torch.utils.data.DataLoader
    cifarSet = torchvision.datasets.CIFAR10(root = "../data/cifar/", train= True, download = True, transform = mytransform )
    cifarLoader = torch.utils.data.DataLoader(cifarSet, batch_size= 10, shuffle= False, num_workers= 2)
    >>> transforms.Compose([ 
    >>> transforms.CenterCrop(10),
    >>> transforms.PILToTensor(), >>> transforms.ConvertImageDtype(torch.float), >>> ])

    作为 Dataset类的参数传递 :

    torchvision.datasets.Caltech101(root: strtarget_type: Union[List[str], str] = ‘category’transform: Optional[Callable] = Nonetarget_transform: Optional[Callable] = Nonedownload: bool = False)

    或者自定义的类:
    (自己实现torchvision.datasets.CIFAR10的功能)

    (自己实现torchvision.datasets.CIFAR10的功能)
    import os
    import torch
    import torch.utils.data as data
    from PIL import Image
    
    def default_loader(path):
    return Image.open(path).convert('RGB')
    
    class myImageFloder(data.Dataset):
    def __init__(self, root, label, transform = None, target_transform=None, loader=default_loader):
    fh = open(label)
    c=0
    imgs=[]
    class_names=[]
    for line in fh.readlines():
    if c==0:
    class_names=[n.strip() for n in line.rstrip().split('    ')]
    else:
    cls = line.split()
    fn = cls.pop(0)
    if os.path.isfile(os.path.join(root, fn)):
    imgs.append((fn, tuple([float(v) for v in cls])))
    c=c+1
    self.root = root
    self.imgs = imgs
    self.classes = class_names
    self.transform = transform
    self.target_transform = target_transform
    self.loader = loader
    
    def __getitem__(self, index):
    fn, label = self.imgs[index]
    img = self.loader(os.path.join(self.root, fn))
    if self.transform is not None:
    img = self.transform(img)
    return img, torch.Tensor(label)
    
    def __len__(self):
    return len(self.imgs)
    def getName(self):
    return self.classes

    实例化torch.utils.data.DataLoader

    mytransform = transforms.Compose([
    transforms.ToTensor()
    ]
    )
    
    # torch.utils.data.DataLoader
    imgLoader = torch.utils.data.DataLoader(
    myFloder.myImageFloder(root = "../data/testImages/images", label = "../data/testImages/test_images.txt", transform = mytransform ),
    batch_size= 2, shuffle= False, num_workers= 2)
    
    for i, data in enumerate(imgLoader, 0):
    print(data[i][0])
    # opencv
    img2 = data[i][0].numpy()*255
    img2 = img2.astype('uint8')
    img2 = np.transpose(img2, (1,2,0))
    img2=img2[:,:,::-1]#RGB->BGR
    cv2.imshow('img2', img2)
    cv2.waitKey()
    break

    2 使用Python+OpenCV进行数据扩充(适用于目标检测)

    https://pythonmana.com/2021/12/202112131040182515.html

    下面内容来自

    数据扩充是一种增加数据集多样性的技术,无需收集更多真实数据,但仍有助于提高模型精度并防止模型过拟合。

    数据扩充方法包括:

    1. 随机裁剪
    2. Cutout
    3. 颜色抖动
    4. 增加噪音
    5. 过滤
    import os
    
    import cv2
    
    import numpy as np
    
    import random
    
    
    def file_lines_to_list(path):
    
        '''
    
        ### 在TXT文件里的行转换为列表 ###
    
        path: 文件路径
    
        '''
    
        with open(path) as f:
    
            content = f.readlines()
    
        content = [(x.strip()).split() for x in content]
    
        return content
    
    
    def get_file_name(path):
    
        
    '''
    
        ### 获取Filepath的文件名 ###
    
        path: 文件路径
    
        '''
    
        basename = os.path.basename(path)
    
        onlyname = os.path.splitext(basename)[0]
    
        return onlyname
    
    
    def write_anno_to_txt(boxes, filepath):
    
        
    '''
    
        ### 给TXT文件写注释 ###
    
        boxes: format [[obj x1 y1 x2 y2],...]
    
        filepath: 文件路径
        '''
    
        txt_file = open(filepath, "w")
    
        for box in boxes:
    
            print(box[0], int(box[1]), int(box[2]), int(box[3]), int(box[4]), file=txt_file)
    
        txt_file.close()

    随机裁剪

    随机裁剪随机选择一个区域并进行裁剪以生成新的数据样本,裁剪后的区域应具有与原始图像相同的宽高比,以保持对象的形状。

    def randomcrop(img, gt_boxes, scale=0.5):
    
        
    '''
    
        ### 随机裁剪 ###
    
        img: 图像
    
        gt_boxes: format [[obj x1 y1 x2 y2],...]
    
        scale: 裁剪区域百分比
        '''
    
    
        # 裁剪
    
        height, width = int(img.shape[0]*scale), int(img.shape[1]*scale)
    
        x = random.randint(0, img.shape[1] - int(width))
    
        y = random.randint(0, img.shape[0] - int(height))
    
        cropped = img[y:y+height, x:x+width]
    
        resized = cv2.resize(cropped, (img.shape[1], img.shape[0]))
    
    
        # 修改注释
    
        new_boxes=[]
    
        for box in gt_boxes:
    
            obj_name = box[0]
    
            x1 = int(box[1])
    
            y1 = int(box[2])
    
            x2 = int(box[3])
    
            y2 = int(box[4])
    
            x1, x2 = x1-x, x2-x
    
            y1, y2 = y1-y, y2-y
    
            x1, y1, x2, y2 = x1/scale, y1/scale, x2/scale, y2/scale
    
            if (x1<img.shape[1] and y1<img.shape[0]) and (x2>0 and y2>0):
    
                if x1<0: x1=0
    
                if y1<0: y1=0
    
                if x2>img.shape[1]: x2=img.shape[1]
    
                if y2>img.shape[0]: y2=img.shape[0]
    
                new_boxes.append([obj_name, x1, y1, x2, y2])
    
        return resized, new_boxes

    Cutout

    Terrance DeVries和Graham W.Taylor在2017年的论文中介绍了Cutout,它是一种简单的正则化技术,用于在训练过程中随机屏蔽输入的方块区域,可用于提高卷积神经网络的鲁棒性和整体性能。这种方法不仅非常容易实现,而且还表明它可以与现有形式的数据扩充和其他正则化工具结合使用,以进一步提高模型性能。如本文所述,剪切用于提高图像识别(分类)的准确性,因此,如果我们将相同的方案部署到对象检测数据集中,可能会导致丢失对象的问题,尤其是小对象。

    剪切输出是新生成的图像,我们不移除对象或更改图像大小,则生成图像的注释与原始图像相同。

    def cutout(img, gt_boxes, amount=0.5):
    
        
    '''
    
        ### Cutout ###
    
        img: 图像
    
        gt_boxes: format [[obj x1 y1 x2 y2],...]
    
        amount: 蒙版数量/对象数量
        '''
    
        out = img.copy()
    
        ran_select = random.sample(gt_boxes, round(amount*len(gt_boxes)))
    
    
        for box in ran_select:
    
            x1 = int(box[1])
    
            y1 = int(box[2])
    
            x2 = int(box[3])
    
            y2 = int(box[4])
    
            mask_w = int((x2 - x1)*0.5)
    
            mask_h = int((y2 - y1)*0.5)
    
            mask_x1 = random.randint(x1, x2 - mask_w)
    
            mask_y1 = random.randint(y1, y2 - mask_h)
    
            mask_x2 = mask_x1 + mask_w
    
            mask_y2 = mask_y1 + mask_h
    
            cv2.rectangle(out, (mask_x1, mask_y1), (mask_x2, mask_y2), (0, 0, 0), thickness=-1)
    
        return out

    颜色抖动

    ColorJitter是另一种简单的图像数据增强,我们可以随机改变图像的亮度、对比度和饱和度。我相信这个技术很容易被大多数读者理解。

    def colorjitter(img, cj_type="b"):
    
        
    '''
    
        ### 不同的颜色抖动 ###
    
        img: 图像
    
        cj_type: {b: brightness, s: saturation, c: constast}
        '''
    
        if cj_type == "b":
    
            # value = random.randint(-50, 50)
    
            value = np.random.choice(np.array([-50, -40, -30, 30, 40, 50]))
    
            hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    
            h, s, v = cv2.split(hsv)
    
            if value >= 0:
    
                lim = 255 - value
    
                v[v > lim] = 255
    
                v[v <= lim] += value
    
            else:
    
                lim = np.absolute(value)
    
                v[v < lim] = 0
    
                v[v >= lim] -= np.absolute(value)
    
    
            final_hsv = cv2.merge((h, s, v))
    
            img = cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
    
            return img
    
    
        elif cj_type == "s":
    
            # value = random.randint(-50, 50)
    
            value = np.random.choice(np.array([-50, -40, -30, 30, 40, 50]))
    
            hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    
            h, s, v = cv2.split(hsv)
    
            if value >= 0:
    
                lim = 255 - value
    
                s[s > lim] = 255
    
                s[s <= lim] += value
    
            else:
    
                lim = np.absolute(value)
    
                s[s < lim] = 0
    
                s[s >= lim] -= np.absolute(value)
    
    
            final_hsv = cv2.merge((h, s, v))
    
            img = cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
    
            return img
    
    
        elif cj_type == "c":
    
            brightness = 10
    
            contrast = random.randint(40, 100)
    
            dummy = np.int16(img)
    
            dummy = dummy * (contrast/127+1) - contrast + brightness
    
            dummy = np.clip(dummy, 0, 255)
    
            img = np.uint8(dummy)
    
            return img

    增加噪声

    在一般意义上,噪声被认为是图像中的一个意外因素,然而,几种类型的噪声(例如高斯噪声、椒盐噪声)可用于数据增强,在深度学习中添加噪声是一种非常简单和有益的数据增强方法。

    对于那些无法识别高斯噪声和椒盐噪声之间差异的人,高斯噪声的值范围为0到255,具体取决于配置,因此,在RGB图像中,高斯噪声像素可以是任何颜色。相比之下,椒盐噪波像素只能有两个值0或255,分别对应于黑色(PEPER)或白色(salt)。

    def noisy(img, noise_type="gauss"):
    
        
    '''
    
        ### 添加噪声 ###
    
        img: 图像
    
        cj_type: {gauss: gaussian, sp: salt & pepper}
        '''
    
        if noise_type == "gauss":
    
            image=img.copy() 
    
            mean=0
    
            st=0.7
    
            gauss = np.random.normal(mean,st,image.shape)
    
            gauss = gauss.astype('uint8')
    
            image = cv2.add(image,gauss)
    
            return image
    
    
        elif noise_type == "sp":
    
            image=img.copy() 
    
            prob = 0.05
    
            if len(image.shape) == 2:
    
                black = 0
    
                white = 255            
    
            else:
    
                colorspace = image.shape[2]
    
                if colorspace == 3:  # RGB
    
                    black = np.array([0, 0, 0], dtype='uint8')
    
                    white = np.array([255, 255, 255], dtype='uint8')
    
                else:  # RGBA
    
                    black = np.array([0, 0, 0, 255], dtype='uint8')
    
                    white = np.array([255, 255, 255, 255], dtype='uint8')
    
            probs = np.random.random(image.shape[:2])
    
            image[probs < (prob / 2)] = black
    
            image[probs > 1 - (prob / 2)] = white
    
            return image

    滤波

    本文介绍的最后一个数据扩充过程是滤波。与添加噪声类似,滤波也简单且易于实现。实现中使用的三种类型的滤波包括模糊(平均)、高斯和中值。

    def filters(img, f_type = "blur"):
    
        
    '''
    
        ### 滤波 ###
    
        img: 图像
    
        f_type: {blur: blur, gaussian: gaussian, median: median}
        '''
    
        if f_type == "blur":
    
            image=img.copy()
    
            fsize = 9
    
            return cv2.blur(image,(fsize,fsize))
    
    
        elif f_type == "gaussian":
    
            image=img.copy()
    
            fsize = 9
    
            return cv2.GaussianBlur(image, (fsize, fsize), 0)
    
    
        elif f_type == "median":
    
            image=img.copy()
    
            fsize = 9
    
            return cv2.medianBlur(image, fsize)

    上述内容可以在这里找到完整实现

    https://github.com/tranleanh/data-augmentation

    linux-11

    more命令:显示文件内容,类似cat

    Linux more 命令类似 cat ,不过会以一页一页的形式显示,更方便使用者逐页阅读,而最基本的指令就是按空白键(space)就往下一页显示,按 b 键就会往回(back)一页显示,而且还有搜寻字串的功能(与 vi 相似),使用中的说明文件,请按 h 。
    more命令和cat的功能一样都是查看文件里的内容,但有所不同的是more可以按页来查看文件的内容,还支持直接跳转行等功能。

    ps命令:任务管理器

    Linux中的ps命令是Process Status的缩写。ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信息,就可以使用top命令。

    linux上进程有5种状态:

    运行(正在运行或在运行队列中等待)  
    中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号)  
    不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生)  
    僵死(进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放)  
    停止(进程收到SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU信号后停止运行运行) 

    ps工具标识进程的5种状态码:

    D 不可中断 uninterruptible sleep (usually IO)
    R 运行 runnable (on run queue)
    S 中断 sleeping
    T 停止 traced or stopped
    Z 僵死 a defunct (”zombie”) process

    命令参数

    a 显示所有进程
    -a 显示同一终端下的所有程序
    -A 显示所有进程
    c 显示进程的真实名称
    -N 反向选择
    -e 等于“-A”
    e 显示环境变量
    f 显示程序间的关系
    -H 显示树状结构
    r 显示当前终端的进程
    T 显示当前终端的所有程序
    u 指定用户的所有进程
    -au 显示较详细的资讯
    -aux 显示所有包含其他使用者的行程
    -C<命令> 列出指定命令的状况
    –lines<行数> 每页显示的行数
    –width<字符数> 每页显示的字符数
    –help 显示帮助信息
    –version 显示版本显示

    输出列的含义

    F 代表这个程序的旗标 (flag), 4 代表使用者为 super user
    S 代表这个程序的状态 (STAT),关于各 STAT 的意义将在内文介绍
    UID 程序被该 UID 所拥有
    PID 进程的ID
    PPID 则是其上级父程序的ID
    C CPU 使用的资源百分比
    PRI 这个是 Priority (优先执行序) 的缩写,详细后面介绍
    NI 这个是 Nice 值,在下一小节我们会持续介绍
    ADDR 这个是 kernel function,指出该程序在内存的那个部分。如果是个 running的程序,一般就是 “-“
    SZ 使用掉的内存大小
    WCHAN 目前这个程序是否正在运作当中,若为 - 表示正在运作
    TTY 登入者的终端机位置
    TIME 使用掉的 CPU 时间。
    CMD 所下达的指令为何

    htop 任务管理器(apt install htop)

    命令:htop
    在按F5,树型显示进程

    杀死进程 kill

    kill -9 进程id
    
    注意 pid 为进程id
        ppid为父一级的id

    查看进程号 lsof -i:

    sudo netstat -antup
    
    1、lsof -i:端口号
    
    2、netstat -tunlp | grep 端口号

    模式扩展

    ? 匹配单个字符,包括空字符(不会匹配隐藏文件)
    * 匹配任意个字符,不会匹配隐藏文件
    [1-9]匹配【】中的字符
    [!1234] 匹配【】之外的符号
    {1,2,3,4,5}  会分别输出1,2,3,4,5,
    echo 1{2,3,4,5}1
    输出:121 131 141 151

    单引号

    单引号用于保留字符的字面含义,各种特殊字符在''单引号里面都会变成普通的字符,比如* $ \

    双引号

    双引号作用同单引号,但特别的是:不会转义 $ ` \ 这三个符号

    总之:可以认为双引号可以包含变量($符号仍然起作用)

    输入输出重定向

    将原始键盘输入、屏幕输出 转变为文件输入、输出到文件

    输入重定向 < 和<<

    <
    <<
    例如:cat <file1
    将file1的内容输入到cat中

    输出重定向 >> 和 >

    命令>1.txt 2>error.txt
    将错误信息输出到error中,正确信息输出到1.txt中

    总结 命令>1.txt 2>error.txt &

    命令 & 可以后台执行命令

    过滤器

    wc 命令

    Linux系统中的wc(Word Count)命令的功能为统计指定文件中的字节数、字数、行数,并将统计结果显示输出。
    统计指定文件中的字节数、字数、行数,并将统计结果显示输出。该命令统计指定文件中的字节数、字数、行数。如果没有给出文件名,则从标准输入读取。wc同时也给出所指定文件的总统计数。

    命令参数:

    -c 统计字节数。
    
    -l 统计行数。
    
    -m 统计字符数。这个标志不能与 -c 标志一起使用。
    
    -w 统计字数。一个字被定义为由空白、跳格或换行字符分隔的字符串。
    
    -L 打印最长行的长度。
    
    -help 显示帮助信息
    
    --version 显示版本信息

    grep 搜索命令

    Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。

    grep的工作方式是这样的,它在一个或多个文件中搜索字符串模板。如果模板包括空格,则必须被引用,模板后的所有字符串被看作文件名。搜索的结果被送到标准输出,不影响原文件内容。

    grep可用于shell脚本,因为grep通过返回一个状态值来说明搜索的状态,如果模板搜索成功,则返回0,如果搜索不成功,则返回1,如果搜索的文件不存在,则返回2。我们利用这些返回值就可进行一些自动化的文本处理工作。

    grep [option] pattern file

    grep rnn test.txt  #在test.txt中搜索rnn

    命令参数:

    -a   --text   #不要忽略二进制的数据。   
    
    -A<显示行数>   --after-context=<显示行数>   #除了显示符合范本样式的那一列之外,并显示该行之后的内容。   
    
    -b   --byte-offset   #在显示符合样式的那一行之前,标示出该行第一个字符的编号。   
    
    -B<显示行数>   --before-context=<显示行数>   #除了显示符合样式的那一行之外,并显示该行之前的内容。   
    
    -c    --count   #计算符合样式的列数。   
    
    -C<显示行数>    --context=<显示行数>或-<显示行数>   #除了显示符合样式的那一行之外,并显示该行之前后的内容。   
    
    -d <动作>      --directories=<动作>   #当指定要查找的是目录而非文件时,必须使用这项参数,否则grep指令将回报信息并停止动作。   
    
    -e<范本样式>  --regexp=<范本样式>   #指定字符串做为查找文件内容的样式。   
    
    -E      --extended-regexp   #将样式为延伸的普通表示法来使用。   
    
    -f<规则文件>  --file=<规则文件>   #指定规则文件,其内容含有一个或多个规则样式,让grep查找符合规则条件的文件内容,格式为每行一个规则样式。   
    
    -F   --fixed-regexp   #将样式视为固定字符串的列表。   
    
    -G   --basic-regexp   #将样式视为普通的表示法来使用。   
    
    -h   --no-filename   #在显示符合样式的那一行之前,不标示该行所属的文件名称。   
    
    -H   --with-filename   #在显示符合样式的那一行之前,表示该行所属的文件名称。   
    
    -i    --ignore-case   #忽略字符大小写的差别。   
    
    -l    --file-with-matches   #列出文件内容符合指定的样式的文件名称。   
    
    -L   --files-without-match   #列出文件内容不符合指定的样式的文件名称。   
    
    -n   --line-number   #在显示符合样式的那一行之前,标示出该行的列数编号。   
    
    -q   --quiet或--silent   #不显示任何信息。   
    
    -r   --recursive   #此参数的效果和指定“-d recurse”参数相同。   
    
    -s   --no-messages   #不显示错误信息。   
    
    -v   --revert-match   #显示不包含匹配文本的所有行。   
    
    -V   --version   #显示版本信息。   
    
    -w   --word-regexp   #只显示全字符合的列。   
    
    -x    --line-regexp   #只显示全列符合的列。   
    
    -y   #此参数的效果和指定“-i”参数相同。
    ps命令常用用法(方便查看系统进程)
    
    1)ps a 显示现行终端机下的所有程序,包括其他用户的程序。
    
    2)ps -A 显示所有进程。
    
    3)ps c 列出程序时,显示每个程序真正的指令名称,而不包含路径,参数或常驻服务的标示。
    
    4)ps -e 此参数的效果和指定"A"参数相同。
    
    5)ps e 列出程序时,显示每个程序所使用的环境变量。
    
    6)ps f 用ASCII字符显示树状结构,表达程序间的相互关系。
    
    7)ps -H 显示树状结构,表示程序间的相互关系。
    
    8)ps -N 显示所有的程序,除了执行ps指令终端机下的程序之外。
    
    9)ps s 采用程序信号的格式显示程序状况。
    
    10)ps S 列出程序时,包括已中断的子程序资料。
    
    11)ps -t<终端机编号>  指定终端机编号,并列出属于该终端机的程序的状况。
    
    12)ps u  以用户为主的格式来显示程序状况。
    
    13)ps x  显示所有程序,不以终端机来区分。
    
    最常用的方法是ps -aux,然后再利用一个管道符号导向到grep去查找特定的进程,然后再对特定的进程进行操作。

    nohup 不挂断地运行命令

    用途:不挂断地运行命令。

    语法:nohup Command [ Arg … ] [ & ]

      无论是否将 nohup 命令的输出重定向到终端,输出都将附加到当前目录的 nohup.out 文件中。

      如果当前目录的 nohup.out 文件不可写,输出重定向到 $HOME/nohup.out 文件中。

      如果没有文件能创建或打开以用于追加,那么 Command 参数指定的命令不可调用。

    退出状态:该命令返回下列出口值:   
      126 可以查找但不能调用 Command 参数指定的命令。   
      127 nohup 命令发生错误或不能查找由 Command 参数指定的命令。   
      否则,nohup 命令的退出状态是 Command 参数指定命令的退出状态。

    2.&

    用途:在后台运行

    一般两个一起用

    nohup command &

    重点记忆

    nohup 命令>1.txt 2>error.txt &

    反引号 “ ==$()

    这个东西的用法,我百度了一下,和$()是一样的。在执行一条命令时,会先将其中的 “,或者是$() 中的语句当作命令执行一遍,再将结果加入到原命令中重新执行,例如:

    echo `ls`

    会先执行 ls 得到xx.sh等,再替换原命令为:

    echo xx.sh

    最后执行结果为

    xx.sh

    新建子 shell :bash命令

    $ bash

    子 Shell 是由 Shell 或 Shell 脚本运行的子进程。当我们在 Shell 命令行提示符下,运行一个 Shell 脚本时,它会创建一个叫做子 Shell 的新进程,我们的脚本将会使用这个子 Shell 来运行。

    子 Shell 是命令处理程序(提供给我们命令行提示符的 Shell 或是一个 xterm 窗口)的一个单独实例。就像我们的命令在命令行提示符下被解释,类似地,脚本批处理一连串命令。实际上,每个运行的 Shell 脚本都是父 Shell 的子进程。

    退出子shell : exit命令

    exit

    产生子Shell
    执行 bash命令本身
    显然它会进入子shell环境,它的绝大多数环境都是新配置的,因为会加载一些环境配置文件。事实上fork出来的bash子进程内容完全继承父shell,但因重新加载了环境配置项,所以子shell没有继承普通变量,更准确的说是覆盖了从父shell中继承的变量
    当前 Shell 及随后新建的子 Shell,都可以读取变量$NAME。

    子 Shell 如果修改继承的变量,不会影响父 Shell。

    # 输出变量 $foo
    $ export foo=bar
    
    # 新建子 Shell
    $ bash
    
    # 读取 $foo
    $ echo $foo
    bar
    
    # 修改继承的变量
    $ foo=baz
    
    # 退出子 Shell
    $ exit
    
    # 读取 $foo
    $ echo $foo
    bar