JavaScript的作用

  • 表单动态校验(密码强度检测) ( JS 产生最初的目的 )
  • 网页特效
  • 服务端开发(Node.js)
  • 桌面程序(Electron)
  • App(Cordova)
  • 控制硬件-物联网(Ruff)
  • 游戏开发(cocos2d-js)

  • Electron
  • https://www.electronjs.org/zh/docs/latest/tutorial/quick-start

    https://wizardforcel.gitbooks.io/electron-doc/content/tutorial/quick-start.html

  • Cordova
  • https://cordova.axuer.com/docs/zh-cn/latest/

    https://cordova.axuer.com/

    http://www.dba.cn/book/cordova/

  • cocos2d-js
  • https://docs.cocos.com/creator/manual/zh/

    https://zhuanlan.zhihu.com/p/101240692

  • Ruff
  • https://chain.ruffcorp.com/

    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