Sherry's emmm... Unnamed Blog

Recording while Learning

0%

用例图

  1. 用例名称使用动词开头
  2. 系统边界系统名称
  3. 一定要有include或者extend,搞不清全都include虚线箭头
  4. 所有用例和Actor关联,关联线实线没有箭头
  5. 支持性参与者最右边,GPS, 传感器, 外部设计, 短信, 邮件, 银行卡
  6. 画有用的用例,Login不要
  7. 判断是否要写:这个功能是否有一个单独的界面
  8. 不能太多层
  9. 外部设备<<>>
    1. <<system>> - 系统
    2. <<service>> - 服务
    3. <<device>> - 设备

活动图

  1. 起点只能一个,终点可以多个
  2. 有箭头的线,有循环一定有汇聚节点
  3. 判定/有条件一定写guard
  4. 注意分支循环的菱形【这里错过】
  5. 活动图的基本动作对应用例的子用例

领域建模

概念和属性

  1. 通过名词找概念类和属性

    不要和UI, database的名词,业务流程没有关系的名词不要,任何计算出的结果,不参与业务运算,模糊术语

    每个类写一两个代表属性就好

  2. 区分概念类和属性:属性为现实世界的数字文本,否则不是属性而是概念类;当需要记录信息时引入属性

  3. 一定有计算属性【没有则扣分】

描述类

  1. 描述类,命名xxxDescription【没有则扣分】:
    1. 需要商品/服务的描述,独立于实例
    2. 删除所有实例,导致信息丢失
    3. 减少冗余重复

关联

  1. 角色
  2. 多重性,可以出现多重关联,黑色三角【没有则扣分】
  3. 没有箭头!!

书写

  1. 类元首字母大写

  2. 关联名称首字母大写(e.g. Uses, Has)

    1. 关联不是动作
    2. 数据之间的约束

不要出现

  1. database
  2. 方法
  3. 小票
  4. selection

状态图

  1. 看清题目要什么东西的状态图
    1. 系统和用例:过程
    2. 对象:生命周期
  2. 寻找主要状态,名词/名词+动词
  3. 确定转换边:触发事件[监护条件]/动作
    1. 事件被动动词:e.g. onKeyPressed
    2. 监护条件:”如果…”, “在…条件下”
    3. 动作别写了,写错扣分。是系统的动作
  4. 一定有起点
  5. 横着画!!!!!!

系统顺序图与操作契约

  1. 系统
    1. System
    2. 前面冒号
    3. 直角矩形框
    4. Actor右边
    5. 下划线
  2. 参与者
    1. Actor
    2. 前面冒号
    3. 最左边
    4. 下划线
  3. 外部实体
    1. 最右边
  4. 消息不应该超过5个
    1. 命名为动作
    2. 实心三角实线!!!!
    3. 返回虚线箭头
    4. 控制焦点
  5. 后置条件【pml: 至少写一个,表示会,不要都写】【不要忘记】:
    1. 使用注释写在后面
    2. 类型
      1. 创建/删除xxx对象
      2. 修改xxx属性
      3. 生成xxx关联(一般创建对象伴随着生成关联
  6. 看是否需要图框
  7. pml: 有可能有循环
  8. 场景名:xxxx Scenario

逻辑建模(包图)

  1. 三个包M, V, C
    1. M都来自领域建模
    2. 理论你是变量都在C,动作命名规则:xxxAction或xxxController, 一个用例一个控制器
    3. 界面都是V
  2. 外部资源写在foundation
  3. 依赖关系,都是向下指,UI, controller, domain, foundation,使用带箭头的虚线

部署建模

  1. 操作系统表示为OS=XXX
  2. 数据库与其他东西的协议为JDBC

img

  1. 节点之间没有箭头实线
  2. 节点是软件还是物理设备需要标记
  3. 写清连接介质

对象动态建模(顺序图)

  1. 创建实例的create使用虚线实心箭头
  2. 遵循BCE
  3. 冒号
  4. 下划线表示的静态对象
  5. 最左边的方法是copy SSD的,不能多不能少
  6. 控制器来源于包图,控制器左侧为UI,方法和顺序图和交互图保持一致

对象静态建模(类图)

和顺序图对应

  • 各种关系的连线
    • 依赖:虚线箭头
    • 泛化:实线空心箭头(指向父类)
    • 实线:虚线空心箭头(指向接口)
  • 关联先表示属性:实线箭头 + 多重性(放在目标一段)+ 角色名(目标一端)+ 不需要关联名称
    • 对数据对象使用属性文本表示法,其他对象使用关联线
  • 控制器一定无状态
  • 不要冒号

步骤

  1. 抄顺序图的类,抄领域模型的属性和关联,补充类的方法(和顺序图一致
  2. 领域建模的has, contains, own改为导航箭头(没有关联名)
  3. 多重性保持
  4. 不写get,set
  5. Domain写所有entity
  6. Boundary的方法都不考虑

Summary

  • 顺序图和系统顺序图中,一个页面内修改的内容,直接传参,不需要一个个set方法

Gram矩阵在风格迁移中的应用

https://blog.csdn.net/dcrmg/article/details/81231044

Gram矩阵本身定义

  • n维欧式空间中任意k个向量之间两两的内积所组成的矩阵

  • 通过网络提取图像局部细节纹理特征向量,组合起来计算Gram矩阵,得到图像特征之间的隐藏联系

  • 数值意义:

    • Gram计算的是两两特征之间的相关性,哪两个特征是同时出现的,哪两个是此消彼长的等等

    • Gram的对角线元素,还体现了每个特征在图像中出现的量

    • Gram矩阵可以度量各个维度自己的特性以及各个维度之间的关系,所以可以反映整个图像的大体风格。只需要比较Gram矩阵就可以比较两个图像的风格差异了。


可以修改的参数

Loss的计算方法:当前为MSE,探究各种方法的区别

更新参数的方法~

调整内容和风格的weight之比


Gatys论文中算法的知乎实现

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

  • CNN卷积过后提取了图像的特征图,每个数字是原图像的特征大小,Gram矩阵内积运算后,特征图中越大的数字数字会变得更大,相当于对图像的特性进行缩放,使得特征突出

  • 迁移vgg16模型并剔除全连接部分,加入计算内容和风格loss的部分

  • 优化:使用LBFGS -> 对于多个loss的优化可以取得较好效果

用例建模

用例图(考点1)

课件地址:https://sysu-swsad.github.io/swad-guide/06-usecase-modeling

需求识别

系统

  • 使用system框并命名,避免命名空泛

参与者

  • 系统左边

依赖的逮捕系统

  • Neighboursystem框表示,构造型识别
    • <<system>> - 系统
    • <<service>> - 服务
    • <<device>> - 设备

用例

用户级用例

  • 参与者驱动
  • manage 用例

子用例

  • 业务复用
  • 复杂业务分解

关系

  • <<include>> - 子用例必须
  • <<extend>> - 子用例可选
  • <<include>>箭头指向子用例,<<extend>>箭头指向父用例

用例和Actor

  • 无方向线

活动图(考点2)

课件地址:https://sysu-swsad.github.io/swad-guide/07-usecase-modeling

以下来自:

UML活动图参考

UML活动图指南

简单的控制流

分支和循环
  1. 操作:圆角矩形,文本 - 指定的操作

  2. 控制流:箭头

  3. 初始节点:第一个操作

  4. 最终结点:活动结束

  5. 决策节点:条件分支 - 单输入多输出,输入令牌只会在一个输出上显示

  6. 防护:指定令牌是否可以沿连接线流动

  7. 合并节点:多输入单输出

  8. 注释

  9. 调用行为的操作:在另一个活动图中进行了更详细定义的操作【大概不考】

并发流
  1. 分叉节点:单个流划分为并发流,每个传入令牌在每个传出连线上生成一个令牌

  2. 结点加入:并发流合并为单个流,每个输入流有等待令牌,输出生成一个令牌

  3. 发送信号:将消息/信号发送给另一个活动/同一活动并发线程

  1. 接受事件:等待消息/信号后才执行
数据流

一个操作到另一个操作

  1. 对象节点:传递的数据
  1. 输入插针:操作执行可以接受的数据
  1. 输出插针:操作执行时生成的数据

参数节点:通过节点活动接受/生成数据

控制流

  • 操作 + 连接器
  • 每个操作都在控制流的下一个操作开始之前结束
  • 操作重叠的情况下使用并发流

决策和循环

  • 决策节点 - 多个传出路径

  • 防护条件 - 知道何时采用每条路径

  • 合并节点 - 在分叉的两个或多个替代流组合在一起

    • 不是汇集操作组合,一个操作中组合并发流

结束活动

所有并发操作和子活动终止

使用多个活动最终节点减少其他连接线混乱程度

多泳道图

  • 描述2个以上组织、角色或系统之间的交互业务流程

领域建模(考点3)

https://sysu-swsad.github.io/swad-guide/08-domain-modeling

  • 描述问题域中事物及其之间的关系与量化的越是
  • 按照用例构建领域模型
    • 识别实体(entity)和中介实体(Model)

以下内容来自课本

如何创建领域建模(9.4)

  1. 寻找概念类(9.5)
  2. 绘制为UML类图的类
  3. 添加关联和属性

如何寻找概念类(9.5)

  1. 重用/修改现有模型
  2. 使用分类列表
  3. 确定名词短语

使用分类列表

  • P104:业务交易、交易项目、与交易或交易项目相关的产品或服务、交易记录的地方、与交易相关的人或组织的角色+用例的参与者、交易/服务地点、重要事件(需要记录的时间地点)、物理对象、事物的描述、类别、事物的容器、容器中的事物、其他协作系统、金融工作合约法律材料记录、金融手段、执行工作所需的进度表、手册、文档

使用名词短语

  • 名词/名词短语来自用例/其他文档/专家想法

  • 名词短语为候选的概念类或概念类的属性

  • 事件中直接将概念类绘制为UML类图

报表对象的处理(9.9)

  • 一般不现实其他信息的报表
  • 作为凭证/必要属性时需要,e.g.退货需要小票

使用领域术语(9.10)

  • 不要凭空增加事物

属性与类的常见错误(9.12)

  • 如果概念类XX不是现实中的数字或文本,XX八成为概念类

描述类(9.13)

e.g. 价格独立于汉堡,航线独立于航班

  • 需要有关商品或服务的描述,独立于任何商品或服务的现有实例
  • 删除所描述事物的实例后,导致信息丢失,而这些信息是需要维护的,但错误地与所删除事物关联起来
  • 减少冗余或重复信息

关联(9.14)

  • 对象之间需要持续一段时间的关系需要关联表示

避免加入大量关联

关联表示法

  • 类之间连线,首字母大写的关联名称
  • 末端多重性表达式 -> 类的实例之间的数量关系
  • ”阅读导向箭头“(实心三角)阅读关联名称的方向

关联的命名

  • 类名 - 动词短语 - 类名(动词短语构成可读的和有意义的顺序)

角色

  • 定义:关联的每一端
  • 可选项:
    • 多重性表达式
    • 名称
    • 导航

多重性

  • 类A有多少个实例可以和类B的一个实例关联
  • 对于一个关联在不同时间段/条件/场景下有不同多重性时,根据关注点自行选择

两个类之间多个关联

  • e.g. Flight - Flies from - Airport, Flight - Flies to - Airport,

如何在常见关联列表中找到关联

  • A是B的相关交易(paid-by)、一个项目(contains)、产品(records-sale-of)或服务、相关的角色、物理或逻辑部分、被物理或逻辑地包含在B中、B地描述、在B中被感知(is-on)/记日志/记录/生成报表/捕获、B的成员(member of)、B的组织化子单元、使用/管理/拥有B(contains)、与B相邻

属性(9.16)

  • 对象的逻辑数值

准则:何时展示属性

  • 当需求(用例)建议或暗示需要记住信息时,引入属性

属性表示法

  • 类框图的第二格表示,类型和其他信息可选

  • visibility name: type multiplicity = default {property-string}
    
  • visibility属性可见性一般为私有,一般不显示标出可见性符号

  • multiplicity可能出现的值或者这填充到集合属性中的对象数量,e.g. [0..1]表示可选值

  • **{property-string}**最常用的值

导出属性

  • 表示方法:在属性名称前加以/符号 - 从多重性值导出的属性

准则:什么样的属性类型是适当的

数据类型属性

  • 大部分属性应该是“简单”数据类型:Boolean, Date, Number, Character, String(Text)和Time; 以及Address, color, geometrics, phone number, social security number, universal product code, SKU, ZIP以及邮政编码。
  • 把复杂领域概念建模为属性是错误的 - 通过关联描述概念类的关系

数据类型

  • 对数据类型等价性检验不是基于标识,而是根据值判断

准则:何时加入数据类型

  • 由不同小节组成:电话、人名
  • 具有与之相关的操作:社会安全号
  • 其他属性:e.g. 促销价格可能有开始日期和结束日期
  • 单位的数量:支付总额具有货币单位

加入的数据类型可以表示为属性也可以表示为单独的类

准则:任何属性都不表示外键

  • 属性不应该用于表示概念类的关系,应采用关联

准则:对数量和单位建模

  • 数量:Quantity类/类型
  • 单位:Unit

状态建模(考点4)

https://sysu-swsad.github.io/swad-guide/09-domain-modeling

解决问题

  1. 从实例角度识别业务实践,完善、优化业务过程细节,细化业务过程与领域模型
  2. 给出业务过程合理性与完备性验证
  3. 为程序开发提供业务规范细节

符号体系

  • 描述一个事物或对象事件或消息刺激产生 可见的状态(属性/属性组合) 的数据变化。
  • 基础
    • 起始 - 黑点
    • 终止 - 圆圈内黑点
    • 状态 - 圆角矩形
  • 变迁 - event[guard]/动作
扩展符号(应该不考)
  • 复合状态
  • 信号

步骤

研究对象

识别状态集合

识别事件和变迁条件

合理性、完整性检查与逻辑分析


扣分点:

  1. 必须有起始,通常有终止和取消
  2. 状态命名:名词短语动词过去时正在进行时 - 延续性词汇
  3. 需求分析不涉及动作

架构设计

逻辑模型 & 包图(考点5)

https://sysu-swsad.github.io/swad-guide/11-architecture-design-methods

  • 三层模型(表示层、业务层、持久化层)
    • 表示层:按用户角色划分分区
    • 业务层:按业务功能服务划分分区
    • 持久化层:按核心交易实体管理

以下内容来自课本chapter13

  • 逻辑架构是软件类的宏观组织结构,将软件类组织为包、子系统和层
  • - 对类、包、子系统的粗粒度分组
  • 宽松分层架构 - 可以调用其下任何一层的服务

包图(13.5)

  • 描述系统的逻辑结构 - 层、子系统、包

UML包

  • UML能够组织任何事物:类、其他包、用例
    • 包内部显示了成员,则在标签上标识包名;否则可以在包体内标识包名

依赖线

  • 带有箭头的虚线,箭头指向被依赖的包

准则:使用层进行设计(13.6)

  • 较低层是低级别和一般性服务,较高层与应用相关

概念区分

  • :对系统在垂直方向的划分
  • 分区:对系统在水平方向的划分

准则:不要将外部资源表示为最底层

  • 这是物理,而非逻辑

准则:模型 - 视图分离原则

  • 不要将非UI对象直接与UI对象连接或耦合

  • 不要在UI对象方法中加入应用逻辑

  • 从UI层发送到领域层的消息是SSD所描述的消息

部署模型(考点6)

基本元素

  • artifacts:项目编译后生成的程序包
  • components: 具有特定接口的功能部件,一个artifacts可以包含多个components,一个component也可以涉及多个artifacts
  • device, host, execute environment(EEN): 容器/节点
    • <<container catalog>> 分类
    • {key: value, ...}表示属性描述
  • 关联:通讯模式或协议、网络连接

以下内容来自课本chapter38

  • 部署图表示如何将具体软件制品分配到计算节点(具有处理服务的某种事物)上。
  • 表示软件元素在物理架构上的部署
  • 使用构造型标记节点类型
  • 设备节点或EEN可以包含其他EEN
  • 具体实例名称带有下划线,没有下划线表示类而非实例;交互图实例(顺序图)中以生命线框图表示的实例名称没有下划线

构件图(考点7)

以下内容来自课本chapter38

  • 构件表示封装了其内容的系统模块
  • UML是设计级别的试图,并不存在于工具软件试图

系统顺序图(课本Chapter10)

  • 系统相关的输入和输出事件
  • 展示直接与系统交互的外部参与者、系统以及由参与者发起的系统事件
  • 时间顺序自上而下,遵循场景顺序
  • 系统被视为黑盒,强调从参与者到系统的跨越系统边界的事件(做什么,而非如何做)
  • 通常不在SSD中显示用例文本
  • 系统事件以动词开始

详细设计

对象动态建模(考点8)

顺序图(课本Chapter 15)

  • 顺序图中没有发送者的起始消息要使用黑点标记起点

常用的UML交互图表示法(15.3)

使用生命线框图表示参与者
  • 生命线框图表示交互的参与者
    • 未命名实例 - :类名
    • 命名实例 - 实例名:类名
    • 元类 - <<metaclass>> 类名
    • 数组 - 实例名:ArrayList<类名>
    • 数组元素 - 实例名[i]:类名
    • 接口/抽象类 - 实例名:接口/抽象类名
单实例对象
  • 在生命线框图右上角标识”1”

顺序图的基本表示法(15.4)

消息
  • 带实心箭头的实线 - 同步消息
  • 最开始的消息在UML中称为创始消息,实心圆表示
表示应答或返回
  • returnVar = message(parameter)
发送给“自身”的消息
  • 书上和课上不一样
  • 1561899453704
实例的创建
  • 虚线create
    • 开放箭头 - 暗示调用构造器
    • 实心箭头 - 调用操作符new并调用构造器
对象生命线和对象的销毁
  • 显示销毁对象,大X和短生命线(P168)
UML顺序图中图框
  • 操作符 & 保护信息
操作符 含义
alt 选择,互斥条件逻辑
loop 保护信息为真的循环片段;loop(n)指明循环次数
opt 保护信息为真时执行的可选片段
par [] 并行执行的并行片段
region [] 只能执行一个线程的临界片段

注意alt和opt的区别在于opt里面只有一个条件,没有else if, else的关系,条件为真就执行;alt使用虚线隔开if, else if, else…

有条件消息[opt]
  • 保护信息置于相关的生命线之上
互斥条件[alt]
  • 不同的分支之间在图框内采用横虚线隔开
对集合的迭代[loop]
  • 在保护信息设置循环条件,并在表示消息的横线下方设置动作图框(圆角矩形),写i++

  • 保护信息和动作框图在同一条生命线上

  • 右侧框图为选择器表达式实例名[i]:类名

  • 也可以同时不写保护信息和动作图框

关联交互图
  • 整个顺序图周围放置图框,命名为sd 名称
  • 在主顺序图加入标记为ref的图框(引用),内容写需要引用的周围图框的名称(即sd后面的部分)
对类调用静态方法的消息
  • 使用元类的生命线框图表示接受消息的对象是类
多态消息和案例
  • 消息的接受者直接写:抽象类名{abstract},并且停止在该消息,不要再展示更多信息

  • 也可以选择为多态的每个具体情况做单独的图

通信图的基本表示方法(15.5)

  • 连接两个对象,多个消息沿着一条链流转
消息
  • 使用消息表达式和消息方向的小箭头表示
  • 增加顺序编号表示当前控制线程中消息的次序
自身传递的消息
  • 使用到自身的链表示
实例的创建
  • create
消息的顺序编号
  • 不为第一个消息编号
  • 使用合法编号方案表示后续消息的顺序和嵌套,嵌套消息使用附加数字
有条件消息
  • 顺序编号后使用带有方括号的条件子句表示有条件消息[]
互斥的有条件路径
  • 使用条件字母修改顺序编号,后续的嵌套消息仍然沿用并附加其外部消息的顺序编号
迭代或循环
  • 在消息编号后使用[i=1...n]表示迭代,如果迭代子句不重要,使用*简化
集合的迭代
  • 和迭代循环类似,消息的接受者变为实例名[i]:类名
调用静态方法的消息 & 多态消息
  • 和顺序图类似,修改框图内容以及具体化多态的每个情况

对象静态建模(考点9)

  • 符号
    • 虚线空箭头:接口实现
    • 实线空箭头:继承
    • 虚线箭头:依赖
    • 实线箭头:关联(多重性)
      • 多重性和角色名放在箭头一端

Abstract

  • 前人的缺陷:缺乏对图像语义的代表,难以将图像内容风格区分开来
    • 本文使用CNN的图像表示提取图像信息
    • A Neural Algorithm of Artistic Style架构图象的内容和风格分离并重新组合

Introduction

  • 探究高性能神经网络学习的特征表示是如何独立处理和操作自然图像的内容和风格的
  • A Neural Algorithm of Artistic Style - 通过CNN得到的特征表示限制的纹理迁移算法

2. Deep image representations

  • 使用VGG生成结果
    • 标准化网络-将每一个卷积层的激活函数的平均值设为1
    • 不用全连接层
  • 发现使用平均池化最大池化效果更好一点

2.1 Content representation

  • 输入图像在CNN的每一层编码

  • 有$N_l个$不同模板的层有$N_l$个feature map

    • 每个feature map的size为feature map的长乘宽
  • 层$l$的结果可以被存储到矩阵中$F^l\in R^{N_l\times M_l}$

    ​ 其中$F^l_{ij}$是第i个模板在位置j在$l$层的activation

  • 将编码在每个不同层的图像信息可视化

    • 可以在白噪声图像使用梯度下降在白噪声图像来找到一个与原图特征相应匹配的图像
  • $\vec p$和$\vec x$是原图和生成图像,$P^l$和$F^l分别是他们的.在l层相应的特征表示

  • 两个特征表示的损失函数

    • $$
      L_{content}(\vec p, \vec x, l) = \frac{1}{2}\sum_{i,j}(F^l_{ij}-P^l_{ij})^2
      $$
  • 损失函数相对于$l$层激活的导数为:

    • $$
      \frac{\partial L_{content}}{\partial F^l_{ij}} = \begin{cases}
      (F^l - P^l){ij} & F^l{ij} >0\
      0 &F_{ij}^l<0
      \end{cases}
      $$

    • 据此对于图像$\vec x$的梯度可以通过标准误差反向传播计算出来 => 因此我们可以改变最初随机图像$\vec x$直到生成对于CNN的某一层产生和原图$\vec p$一样的响应

  • CNN是在图像识别上训练的,图像的表示使得目标信息在处理层上面愈加明显=> 在网络的处理层,输入图像转变为representations对真正的图像内容越来越敏感,但是对于图像的外观相对保持不变了

  • 网络的月高层获得目标的更高层内容以及在输入图像中的排列,但是不限制重构过程中的像素值

  • 因此参考网络高层的特征响应作为内容表示

2.2 Style Representation

  • 特征空间可以在网络的任何一层上建立特征空间,包括不同模板相应之间的关系 <= 由Gram矩阵给出,$G^l_{ij}$为向量化的feature maps i和j在l层的内积:
    $$
    G^l_{ij} = \sum_k F^l_{ik}F^l_{jk}
    $$

  • 通过涵盖不同层的特征关联,可以获得输入图片的稳定多维表示,获得其纹理信息,但不是global arrangement

  • 通过在网络的不同层生成的风格特征空间可视化风格特征空间获得的信息

  • 使用梯度下降从一个白噪声图像,最小化和原图的Gram矩阵的mean-squared

计算

$\vec a$和$\vec x$是原图和生成图像,$A^l$和$G^l$是相应的层l风格表示:

  • l层loss: $E_l = \frac{1}{4N^2_lM^2_l}\sum_{i,j}(G^l_{ij} - A^l_{ij})^2$

  • 总风格损失:$L_{style}(\vec a, \vec x) = \sum^L_{l=0}w_lE_l$

    • $w_l$是每层对于总损失贡献的权重因子
  • $E_l$对于$l$层的activation的导数为:

    • $$
      \frac{\partial E_l}{\partial F^l_{ij}} = \begin{cases}\frac{1}{N^2_lM^2_l}((F^l)^T(G^l-A^l)){ji} & if F^l{ij} > 0 \
      0 & if F^l_{ij} < 0
      \end{cases}
      $$

    • $E_l$对于$\vec x$的梯度可以通过标准差反向传播直接计算

2.3 Style transfer

1561799638893

  • 风格图像:过左侧网络,得到风格特征$A^L$

  • 内容图像:过右侧网络,得到内容图像$F^l$

  • 目标图像:白噪声$\vec x$过网络

    • 风格损失:计算和风格网络每一层的误差,并求和
    • 内容损失:计算和特定层的损失
    • 两种损失线性组合得到总损失
  • 最小化的损失函数:

    • $$
      L_{total}(\vec p, \vec a, \vec x) = \alpha L_{conent}(\vec p, \vec x) + \beta L_{style}(\vec a, \vec x)
      $$
  • 损失函数对于输入像素$\vec x$的梯度可以被用作一些数值优化策略的输入??

  • 在计算风格图像的特征之前,缩放风格图像为和内容图像相同的大小

3 Results

  • CNN中对于图像的内容和风格表示是完全可分的
    • => 可以独立处理二者之一产生新的图像

3.1 Trade-off between content and style matching

  • 合成图像无法很好地同时满足风格和内容的相似性,但是可以通过调节$L_{total}$的参数控制生成图片的侧重

3.2 Effect of different layers of the Convolutional Neural Network

Style

  • 另一个合成图像中的重要因子为对于进行内容和风格表示匹配的layers的选择
  • 风格表示是多维的,包含网络的很多层
  • 层级的数目和位置决定了那种维度的风格匹配,导致了不同的视觉体验
  • 发现在较高层匹配风格表示保留了较多局部图片结构 => 平滑连续的视觉体验
  • => 较好的图片通常在网络高层匹配风格表示

Content

  • 网络中高层层级提取内容,会使得风格化更好。像素级信息不是很多,一些内容高尚的细节会被弱化以更好地风格化

3.3 Initialization of gradient descent

  • 生成图像的输入无论设置为风格图像还是内容图像,对于输出的效果影响不大;反而初始化为白噪声可以使得产生的图片效果更加多样

3.4 Photorealistic style transfer

  • 可以应用到任何图像之间的风格迁移

!!!可以为不同种风格迁移调参

  • 如果是两幅照片,主要迁移了色彩和光影

4 Discussion

limiting factor

  • 合成图像的分辨率,CNN的优化随着像素个数线性增长 => 处理速度与图像分辨率十分相关

low-level noise

  • 主要在风格图像和内容图像都为照片的时候会表现得比较明显
  • 可以选择去噪的后处理

参考:https://zhuanlan.zhihu.com/p/29024978

文件组织架构

  • checkpoints/: 保存训练好模型
  • data/: 数据相关
  • models/: 模型定义
  • utils/: 工具函数
  • config.py: 可配置变量
  • main.py: 程序主入口,通过不同命令指定不同参数和操作

__init__.py

  • 每个文件夹包含 => 其他程序从目录导入函数/模块from FOLDERNAME.PACKAGENAME import MODULENAME
  • 在其中加入:from .PACKAGENAME import MODULENAME -> 从外界访问:from FOLDERNAME import MODULENAME

数据加载

  • 划出验证集,将图像随机排列,前70%作为训练集,后30%作为验证集
训练集 验证集/测试集
图像增强
返回Label 分类 图片id

模型定义

对于nn.Module进行简易封装

  • 封装nn.ModuleBasicModule类,主要提供saveload两个方法

load

  • 将模型路径作为参数

  • ```python
    self.load_state_dict(t.load(path))

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    #### save

    - 使用模型 + 时间作为命名,传入参数,并放入`checkpoints/`文件夹

    - ```python
    if name is None:
    prefix = 'checkpoints/' + self.model_name + '_'
    name = time.strftime(prefix + '%m%d_%H:%M:%S.pth')
    t.save(self.state_dict(), name)

实际使用:

1
2
module.save()
module.load(opt.load_path)
  • 一般模型继承BasicModule并加以实现

数据集导入

  • 在models/__init__.py实现:

    • ```python
      from .DATASETNAME import DATASETNAME
      … #多个模型的添加以此类推

      1
      2
      3
      4
      5
      6

      - 使得主函数中可以写为

      - ```python
      import models
      model = getattr(models, 'DATASETNAME')()
    • 直接修改字符串可以选择具体模型

工具函数

Visualizer的封装

  • 构造函数visdom.Visdom(...),传入env和其他参数
属性
  • index - 字典,记录plot的名称和是第几个绘制的图片
    • key - name, value - 下标
    • 用于确认是需要在已有串口上进行绘制还是新窗口
  • log_text - 输出的日志

绘图

  • ```python
    x = self.index.get(name, 0)

    在窗口上绘制多个点

    self.vis.line(Y=np.array([y]), X=np.array([x]),
              win=unicode(name),
              opts=dict(title=name),
              update=None if x == 0 else 'append',
              **kwargs
             )
    
    self.index[name] = x + 1
    1
    2
    3
    4
    5
    6
    7
    8
    9

    #### 输出图像

    - ```python
    self.vis.images(img_.cpu().numpy(),
    win=unicode(name),
    opts=dict(title=name),
    **kwargs
    )

输出文本

  • ```python
    self.log_text += (‘[{time}] {info}
    ‘.format(

    time=time.strftime('%m%d_%H%M%S'),\
    info=info)) 
    

    self.vis.text(self.log_text, win)

    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

    -

    ## 配置文件

    - 将所有可配置项放入`config.py`,包括模型定义、数据处理和训练的变量默认值 => 方便调试修改代码

    - 主要用到的变量如下:

    - ```python
    class DefaultConfig(object):
    # visdom参数
    env = 'default' # visdom 环境
    model = 'AlexNet' # 使用的模型,名字必须与models/__init__.py中的名字一致

    # 数据集参数
    train_data_root = './data/train/' # 训练集存放路径
    test_data_root = './data/test1' # 测试集存放路径
    load_model_path = 'checkpoints/model.pth' # 加载预训练的模型的路径,为None代表不加载

    # 模型参数
    batch_size = 128 # batch size
    use_gpu = True # use GPU or not
    num_workers = 4 # how many workers for loading data
    print_freq = 20 # print info every N batch

    debug_file = '/tmp/debug' # if os.path.exists(debug_file): enter ipdb
    result_file = 'result.csv'

    # 训练参数
    max_epoch = 10
    lr = 0.1 # initial learning rate
    lr_decay = 0.95 # when val_loss increase, lr = lr*lr_decay
    weight_decay = 1e-4 # 损失函数
  • 在程序中使用config.py中的参数

  • ```python
    import models
    from config import DefaultConfig

    opt = DefaultConfig()
    lr = opt.lr
    model = getattr(models, opt.model)
    dataset = DogCat(opt.train_data_root)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    - 通过命令行传入需要参数并覆盖默认配置

    ```python
    def parse(self, kwargs):
    '''
    根据字典kwargs 更新 config参数
    '''
    # 更新配置参数
    for k, v in kwargs.iteritems():
    if not hasattr(self, k):
    # 警告还是报错,取决于你个人的喜好
    warnings.warn("Warning: opt has not attribut %s" %k)
    setattr(self, k, v)

    # 打印配置信息
    print('user config:')
    for k, v in self.__class__.__dict__.iteritems():
    if not k.startswith('__'):
    print(k, getattr(self, k))
    • 使用方法:

    • ```python
      opt = DefaultConfig()
      new_config = {‘lr’:0.1,’use_gpu’:False}
      opt.parse(new_config)
      opt.lr == 0.1

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      ## main.py

      ### fire

      - 假设main.py如下:

      - ```python
      import fire
      # def FUNC(para1, ...)
      # ... 各种定义函数

      if __name__ == '__main__':
      fire.Fire()
  • 在命令行可以直接调用文件内的函数

    • ```shell
      python main.py FUNC –para1=VALUE1 –para2=VALUE2
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16

      ### 主程序函数分析

      - 四个函数:

      - `train` - 训练
      - `val` - 辅助训练
      - `test` - 测试
      - `help` - 打印帮助信息

      - `__main__`部分如下:

      - ```python
      if __name__=='__main__':
      import fire
      fire.Fire()
  • 因此可以通过:*python main.py <function> --args=xx执行训练测试等等

训练

步骤:

  1. 定义网络
  2. 定义数据
  3. 定义criterion(loss func)和optimizer
  4. 计算重要指标
  5. 开始训练
    1. 训练网络
    2. 可视化指标
    3. 计算验证集上的指标

训练函数

根据命令行更新参数配置

1
2
opt.parse(kwargs)
vis = Visualizer(opt.env)

处理模型

  1. 从Models导入模型包
  2. (optional)从本地加载模型文件
  3. (optional)使用GPU

处理数据

  1. 加载训练集和测试集
  2. 设置两个dataloader

目标函数和优化器

统计指标

  1. 平滑处理之后的损失

    1
    loss_meter = meter.AverageValueMeter()
  2. 混淆矩阵

    1
    confusion_matrix = meter.ConfusionMeter(2)
  3. previous_loss用于存储上次的损失

训练

  1. 每个epoch清空上次的平滑损失和混淆矩阵

    1
    2
    loss_meter.reset()
    confusion_matrix.reset()
  2. 遍历训练集

    1
    for ii,(data,label) in enumerate(train_dataloader):
    1. 加载Batch的input和label

      1
      2
      input = Variable(data)
      target = Variable(label)
    2. 优化器清零

      1
      optimizer.zero_grad()
    3. 过模型,计算结果

      1
      score = model(input)
    4. 计算损失函数

      1
      loss = criterion(score,target)
    5. 反向传播

      1
      loss.backward()
    6. 更新

      1
      optimizer.step()
    7. 可视化结果保存

      1. 将loss[0]加入loss_meter

        1
        loss_meter.add(loss.data[0])
      2. 混淆矩阵加入(score.data, target.data)

        1
        confusion_matrix.add(score.data, target.data)
  3. 训练到一定程度进行绘图

    1
    2
    if ii%opt.print_freq==opt.print_freq-1:
    vis.plot('loss', loss_meter.value()[0])
  4. 每个epoch保存模型

    1
    model.save()
  5. 计算验证集的表现

    1
    val_cm,val_accuracy = val(model,val_dataloader)
    • 并绘图

      1
      2
      3
      4
      5
      6
      7
      8
      vis.plot('val_accuracy',val_accuracy)
      vis.log("epoch:{epoch},lr:{lr},loss:{loss},train_cm:{train_cm},val_cm:{val_cm}"
      .format(
      epoch = epoch,
      loss = loss_meter.value()[0],
      val_cm = str(val_cm.value()),
      train_cm=str(confusion_matrix.value()),
      lr=lr))
  6. 调整学习率

    如果损失不下降,降低学习率

    1
    2
    3
    4
    5
    6
    if loss_meter.value()[0] > previous_loss:          
    lr = lr * opt.lr_decay
    for param_group in optimizer.param_groups:
    param_group['lr'] = lr
    # 这里记录上一步的损失
    previous_loss = loss_meter.value()[0]

meter工具

  • 快速统计训练工程中的指标
  • AverageValueMeter计算所有数的平均值和标准差
    • 统计epoch损失的平均值
  • confusionmeter - 统计分类情况

验证

注意将模型置于验证模式,验证完后调整回训练模式

  1. 将模型设置为验证模式

    1
    model.eval()
  2. 初始化混淆矩阵

    1
    confusion_matrix = meter.ConfusionMeter(2)
  3. 进行遍历数据集并存储相关指标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    for ii, data in enumerate(dataloader):
    input, label = data
    val_input = Variable(input, volatile=True)
    val_label = Variable(label.long(), volatile=True)
    if opt.use_gpu:
    val_input = val_input.cuda()
    val_label = val_label.cuda()
    score = model(val_input)
    confusion_matrix.add(score.data.squeeze(), label.long())
  4. 恢复模型为训练模式

    1
    model.train()
  5. 计算整个验证集上的表现

    1
    2
    3
    cm_value = confusion_matrix.value()
    accuracy = 100. * (cm_value[0][0] + cm_value[1][1]) /\
    (cm_value.sum())

测试

  • 这里计算每个样本属于狗的概率,将结果保存为csv
  1. 处理输入参数

    1
    opt.parse(kwargs)
  2. 加载模型

    1
    model = getattr(models, opt.model)().eval()
  3. 构建数据集和数据集加载器

    1
    2
    3
    4
    5
    train_data = DogCat(opt.test_data_root,test=True)
    test_dataloader = DataLoader(train_data,\
    batch_size=opt.batch_size,\
    shuffle=False,\
    num_workers=opt.num_workers)
  4. 编辑测试集计算结果并保存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    results = [] # 结果数组
    for ii,(data,path) in enumerate(test_dataloader):
    input = t.autograd.Variable(data,volatile = True)
    if opt.use_gpu: input = input.cuda()
    score = model(input)
    # 计算概率
    probability = t.nn.functional.softmax(score)[:,1].data.tolist()
    batch_results = [(path_,probability_) for path_,probability_ in zip(path,probability) ]
    results += batch_results
  5. 写文件

    1
    write_csv(results,opt.result_file)

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 训练模型
python main.py train
--train-data-root=data/train/
--load-model-path='checkpoints/resnet34_16:53:00.pth'
--lr=0.005
--batch-size=32
--model='ResNet34'
--max-epoch = 20

# 测试模型
python main.py test
--test-data-root=data/test1
--load-model-path='checkpoints/resnet34_00:23:05.pth'
--batch-size=128
--model='ResNet34'
--num-workers=12

# 打印帮助信息
python main.py help

数据处理

数据加载

  • 自定义数据集
    1. 继承Dataset,实现python方法:
      • __getitem__: 返回一条数据/一个样本
      • __len__: 返回样本数量

以猫狗识别为例:

  • 文件结构

    所有文件放在一个文件夹,根据前缀判断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    data/dogcat/
    |-- cat.12484.jpg
    |-- cat.12485.jpg
    |-- cat.12486.jpg
    |-- cat.12487.jpg
    |-- dog.12496.jpg
    |-- dog.12497.jpg
    |-- dog.12498.jpg
    `-- dog.12499.jpg
  • 在构造函数中获取图片路径

    1
    2
    3
    4
    5
    def __init__(self, root):
    # 图片文件夹路径
    imgs = os.listdir(root)
    # 遍历文件夹下所有文件 + 文件姐路径=> 构成绝对路径
    self.imgs = [os.path.join(root, img) for img in imgs]
  • 定义__getitem__,这一步中真正读取图片,并根据文件名生成label

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def __getitem__(self, index):
    img_path = self.imgs[index]
    # 根据文件名生成Label
    label = 1 if 'dog' in img_path.split('/')[-1] else 0
    # 加载图片
    pil_img = Image.open(img_path)
    # 图片转为numpy
    array = np.asarray(pil_img)
    # numpy转为tensor
    data = t.from_numpy(array)
    return data, bale
  • 还需要定义__len__,返回imgs数组的长度

  • 调用数据加载类

    • ```python
      dataset = DogCat(FILEPATH)

      访问某个元素

      dataset[INDEX]

      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

      ### transforms

      - 常见操作

      - | 操作 | 说明 |
      | ------------------------------------------------ | ------------------------------------------------ |
      | `Scale` | 调整图片尺寸,长宽比 |
      | `CenterCrop`, ``RandomCrop`, `RandomResizedCrop` | 裁剪 |
      | `pad` | 填充 |
      | `ToTensor` | 将PIL Image对象转成Tensor,将[0, 255]归一化[0,1] |

      - 对Tensor的操作

      - Normalize: 标准化
      - `ToPILImage`: Tensor转PIL Image
      - `Compose`: 拼接

      - 实现

      - ```python
      transform = T.Compose([
      T.Resize(224), # 缩放,保持长宽比,短边224
      T.CenterCrop(224), # 中间切224*224
      T.ToTensor(), # 归一化到[0, 1]
      T.Normalize(mean = [.5, .5, .5], std = [.5, .5, .5]) # 表转化到[-1, 1]
      ])
    • 在数据集的类中定义transform,并在__getitem__方法中返回transforms处理过的data(图像数据).

  • 自定义转换

    • e.g. trans=T.Lambda(lambda img: img.rotate(random()*360))

ImageFolder

  • 假设文件按文件夹保存,每个文件夹存储同类别,文件夹名为类名。

    1
    ImageFolder(root, transform=None, target_transform=None, loader=default_loader)
    • root: 路径
    • transform: 对图片的转换操作
    • target_transform: 对label转换
    • loader: 读取格式
  • 属性

    • class_to_idx - 类名和类下标的对应关系

图片保存:Channel x Height x Width

DataLoader

  • ```python
    DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0, collate_fn=default_collate, pin_memory=False, drop_last=False)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    - `dataset` - Dataset对象
    - `shuffle` - 打乱
    - `sampler`
    - `num_workers` - 多进程加载的进程数
    - `collate_fn` - 多个样本数据拼成一个batch的方法,一般使用默认
    - `pin_memory` - 是否将数据保存在`pin_memory`区,转到GPU快
    - `drop_last` -师傅 将最后不足`batch_size`个数据丢弃

    #### 可迭代

    - 可以使用for循环

    - ```python
    for batch_datas, batch_labels in dataloader:
  • 可以使用迭代器

    • ```python
      dataiter = iter(dataloader)
      batch_datas, batch_labels = next(dataiter)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      ### 剔除出错样本

      #### 返回None,并在拼合为batch的时候筛选掉

      - 使用`try`, `except`

      - 异常情况:`return None, None`

      > 这里需要配合实现`Dataloader`的`collate_fn`,将None过滤

      ```python
      from torch.utils.data.dataloader import default_collate # 导入默认的拼接方式
      def my_collate_fn(batch):
      # 过滤为None的数据
      batch = list(filter(lambda x:x[0] is not None, batch))
      if len(batch) == 0: return t.Tensor()
      return default_collate(batch) # 用默认方式拼接过滤后的batch数据

随机选取图片代替

  • 优点:保证每个batch的数目仍是batch_size

注意事项

  • 高负载的操作放在__getitem__,如加载图片 => 为实现并行加速
  • dataset包含只读,避免修改 => 使用多进程加载,修改可能产生冲突

sampler

  • 对数据采样,loader里面shuffle为True,调用随机采样器RandomSampler,打乱数据;默认使用SequentialSampler,顺序采样
WeightedRandomSampler
  • 根据每个样本的权重选取数据,比例不均衡问题中进行重采样
  • 构建时提供每个样本的权重weights和选取样本总数num_samples,可选replacement
    • 权重越大被选中概率越大
    • replacement决定是否可以重复选取某一样本

在指定了sampler的情况下,shuffle不再生效;一个epoch返回的图片总数取决于sampler.num_samples

计算机视觉工具包 torchvision

  • models:提供网络结构以及预训练好的模型
  • datasets:提供常用的数据集加载
  • transforms:提供常用的数据预处理

Visdom

  • env: 不同环境的可视化结果相互隔离,默认main
  • pane: 窗格

安装和使用

  • ```shell
    $ pip install visdom
    $ python -m visdom.server # 启动visdom服务
    $ nohup python -m visdom.server & # 将服务放至后台运行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    > **注意:**
    >
    > 手动指定保存`env`,在web上或者程序里save,否则visdom服务重启后,信息会丢失。

    #### 常用操作

    - 新建连接客户端

    - ```python
    vis = visdom.Visdom(env=u'test1',use_incoming_socket=False)
    • 可以指定host, port..
  • 画图函数: line, image, text, histogram, scatter, bar, pie…

支持tensor和narray的数据结构

常见参数
  • win: 指定Pane的名字,两次操作指定的win相同,则覆盖

    需要更新数值且不覆盖,指定参数update='append'

    也可以使用vis.updateTrace更新图:

    1. 增加新trace

      1
      2
      3
      x = t.arange(0, 9, 0.1)
      y = (x ** 2) / 9
      vis.line(X=x, Y=y, win='polynomial', name='this is a new Trace',update='new')

      在原trace上追加

      1
      2
      3
      4
      5
      for ii in range(0, 10):
      # y = x
      x = t.Tensor([ii])
      y = x
      vis.line(X=x, Y=y, win='polynomial', update='append' if ii>0 else None)
  • opts: 选项,接收字典,用于pane的显示格式

text
  • 支持html

使用GPU加速 cuda

注意事项

  • 损失函数定义后也应该调用criterion.cuda转移到GPU

  • 推荐方法设置环境变量CUDA_VISIBLE_DEVICES

    • 在运行py的命令行实现

    • 在程序中:

      • ```python
        import os
        os.environ[“CUDA_VISIBLE_DEVICES”] = “2”
        1
        2
        3
        4
        5

        - 在Jupyter notebook中:

        - ```shell
        %env CUDA_VISIBLE_DEVICES=1,2

持久化

  • 可持久化的对象:
    • Tensor
    • Variable
    • nn.Module
    • Optimizer
  • 都保存成Tensor,使用t.save(OBJ, FILENAME)t.load(FILENAME)可完成
    • load的时候可以指定加载的存储位置,设置map_location参数
  • 建议module和optimizer保存为state_dict
module
  • ```python

    保存

    t.save(model.state_dict(), FILENAME)

    加载

    model.load_state_dict(t.load(FILENAME))
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    - optimizer同样

    #### 一起保存加载

    - ```python
    all_data = dict(
    optimizer = optimizer.state_dict(),
    model = model.state_dict(),
    info = u'模型和优化器的所有参数'
    )
    t.save(all_data, 'all.pth')

    all_data = t.load('all.pth')

参考:https://github.com/chenyuntc/pytorch-book

基本介绍

Tensor

  • t.Tensor
    • 传入维度 - 分配空间不初始化
    • 传入具体数据 - 舒适化
  • t.rand
    • 传入维度 - 指定维度的随机初始化数据,复合[0,1]分布
  • Add
    • x+y
    • t.add(x,y)
    • t.add(x,y,out=result) - 指定输出目标
    • x.add(y)- x不变
    • x.add_(y) - x改变 = x+=y

函数名后面带下划线_会修改Tensor本身

  • 与Numpy转换:
    • Tensor -> numpy: a.numpy()
    • Numpy -> tensor: t.from_numpy(a)

一个对象的tensor和numpy共享内存,一个改变,另一个随之而变

  • 获得元素值:

    • 下标访问tensor - tensor[index...]获得0-dim tensor
    • 获得值 - scalar.item()
  • scalar和tensor的区别:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    tensor = t.tensor([2])
    scalar = tensor[0]
    scalar0 = t.tensor(2)
    # tensor 1-dim
    tensor.size() # torch.Size([1])
    # scalar 0-dim
    scalar.size() # torch.Size([])
    # scalar0 0-dim
    scalar0.size() # torch.Size([])
  • 数据拷贝和共享内存

    • t.tensor() - 会进行数据拷贝
    • t.from_numpy() / tensor.detach()新建的tensor与原tensor共享内存
  • GPU加速

    • t.cuda

Autograd: 自动微分

  • 使用autograd功能

    • ```python
      x = t.ones(2, 2, requires_grad=True)
      result = doSomething(x)

      1
      2
      3
      4
      5

      - 反向传播

      - ```python
      result.backward()

      反向传播会累加之前的梯度,反向传播前把梯度清零

    • 梯度清零

      • ```python
        x.grad.data.zero_()
        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
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59

        ### 神经网络

        定义网络

        - 继承`nn.Module`,实现`forward`,把网络中具有**可学习参数**的层放在`__init__`中。不具有可学习参数使用`forward`的`nn.functional`代替

        - `__init__`函数先执行父类构造函数`super(CLASSNAME, self).__init__()`

        ##### 卷积层

        - `nn.Conv2d(inputC, outputC, k)`
        - 输入图片通道数
        - 输出通道数
        - 卷积核

        ##### 全连接层

        - `nn.Linear(input, output)`

        - 输入样本数
        - 输出样本数

        > y = Wx + b

        ##### forward

        - `F.relu(input)` - 激活
        - 上一层的结果
        - `F.max_pool2d(input, kernel size)`
        - kernel size

        - `x.view(ONEDIMENSION,-1`
        - -1 自适应维度

        #### 训练网络

        - 定义了forward函数,backward就会自动被实现
        - `net.parameters()`返回网络的**可学习参数**
        - `·net.named_parameters()`返回可学习的参数及名称

        > torch.nn不支持一次只输入一个样本:
        >
        > 1. 用 `input.unsqueeze(0)`将batch_size设为1
        > 2. Conv2d输入时将nSample设为1

        #### 损失函数

        - `nn.MSELoss(output, target)`计算均方误差
        - `nn.CrossEntropyLoss(output target)`计算交叉熵损失

        #### 优化器

        - 反向传播计算参数的梯度,使用优化方法**更新网络的权重和参数**

        - e.g. SGD:

        - ```python
        weight = weight - learning_rate * gradient
    • ```python
      learning_rate = 0.01
      for f in net.parameters():

      f.data.sub_(f.grad.data * learning_rate)# inplace 减法
      
      1
      2
      3
      4
      5
      6
      7

      ##### 构造优化器

      > optim <= torch.optim

      - ```
      optimizer = optim.SGD(net.parameters(), lr = 0.01)
    • 要调整的参数为网络中的可学习参数

    • lr为学习率

  • 训练步骤

    1. 清零优化器梯度

      1
      optimizer.zero_grad()
    2. 过网络

      1
      output = net(input)
    3. 计算损失

      1
      loss = criterion(output, target)
    4. 反向传播

      1
      loss.backward()
    5. 更新参数

      1
      optimizer.step()

Tensor 和 Autograd

Tensor

基础操作

创建
  • 函数 功能
    arange(s,e,step) 从s到e,步长为step
    linspace(s,e,steps) 从s到e,均匀切分成steps份
    rand/randn(*sizes) 均匀/标准分布
    normal(mean,std)/uniform(from,to) 正态分布/均匀分布
    randperm(m) 随机排列
  • 创建的时候可以指定数据类型(e.g. dtype = t.int)和存放device(e.g. device = t.device('cpu')

  • tensor转化为list - tensor.tolist()

    tensor的数据类型为tensor([[1,2],[3,4]]); list数据类型为[[1,2],[3,4]]

  • 获得tensor的维度 - tensor.size()tensor.shape返回torch.Size对象 - torch.Size([第一维, 第二维, ..])

  • 获得tensor元素的总数 - tensor.numel()

  • 传参为数据和维度的区别:

    1
    2
    t.Tensor(2,3) # tensor([[x, x, x],[x, x, x]])
    t.Tensor((2,3)) # tensor([2,3])

使用维度作为参数构造tensor的时候不会马上分配空间,使用到tensor才分配;其他的构造函数都是马上分配

  • 使用eye构造对角矩阵,不要求行列数一致
常用操作
  • 调整tensor形状,调整前后元素总数一致。view返回的新tensor与原tensor共享内存。

  • view的参数为修改后的每个维度,如果有一个为-1则该维度根据元素总数不变的原则自动计算

  • unsqueeze(index) - 第index维的维度+1

    • 即之前维度为[x, y, z, …]
      • index = 0 -> 维度变为 [1, x, y, z, …]
      • index = 1 -> 维度变为 [x, 1, y, z, …]
      • index = 2 -> 维度变为 [x, y, 1, z, …]
    • 参数为负数的时候表示倒数
  • squeeze(index) - 压缩第index维的1,如果不输入index,则把所有维度为1的压缩

  • resize可以修改tensor的大小,如果新大小超过了原大小,自动分配新空间,大小小于原大小,之前数据仍然保留

索引操作
  • 索引出来的结果与原tensor共享内存

  • None - 新增轴

    • ```pythob
      a[:,None,:,None,None].shape
      torch.Size([3, 1, 4, 1, 1])

      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
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138

      - 筛选tensor元素 - `tensor[CONDITION]`

      > 返回结果与原tensor不共享内存空间

      - 常用选择函数

      | 函数 | 功能 |
      | ------------------------------: | ----------------------------------------------------: |
      | index_select(input, dim, index) | 在指定维度dim上选取,比如选取某些行、某些列 |
      | masked_select(input, mask) | 例子如上,a[a>0],使用ByteTensor进行选取 |
      | non_zero(input) | 非0元素的下标 |
      | gather(input, dim, index) | 根据index,在dim维度上选取数据,输出的size与index一样 |

      > 对tensor的任何索引操作仍是一个**tensor**,想要获取标准的python对象数值,需要调用`tensor.item()`, 这个方法**只对包含一个元素的tensor适用**

      - 索引访问:
      - 维度为3:[[a,b], [c,d], [e,f]] => [[a,c,e], [b,d,f]]
      - [[a,b,c], [d], [e]] => [[a,d,e], [b,d,e], [c,d,e]]

      ##### 逐元素操作

      - `clamp` - 控制元素范围,用于比较大小

      ![1561187765542](C:\Users\Sherry\AppData\Roaming\Typora\typora-user-images\1561187765542.png)

      ##### 归并操作

      - 使得输出形状小于输入形状,e.g. 均值、和...
      - 指定对第几维度的元素操作,`keepdim = True`保存被操作的维度为1,否则不保留这一维度

      ##### 线性代数

      | 函数 | 功能 |
      | -------------------------------- | --------------------------------- |
      | trace | 对角线元素之和(矩阵的迹) |
      | diag | 对角线元素 |
      | triu/tril | 矩阵的上三角/下三角,可指定偏移量 |
      | mm/bmm | 矩阵乘法,batch的矩阵乘法 |
      | addmm/addbmm/addmv/addr/badbmm.. | 矩阵运算 |
      | t | 转置 |
      | dot/cross | 内积/外积 |
      | inverse | 求逆矩阵 |
      | svd | 奇异值分解 |

      #### Tensor和Numpy

      > 当numpy的数据类型和Tensor的类型不一样时,数据会被复制,不共享内存

      - 调用`t.tensor`进行构建的时候都会进行数据拷贝

      ##### 广播法则

      ###### N

      - 所有输入数组像其中shape最长的看齐,shape不足通过前面加1
      - 两个数组要么在某一个维度长度一致,要么其中一个为1,否则不能计算
      - 输入某个长度为1,计算时沿此维度扩充成和另一个输入一样的维度

      ###### Pytorch中的使用

      - `unsqueeze`或者`view`或者`tensor[NONE]`,实现补齐1

      - `expand`或者`expand_as`重复数组,不会占用空间

      > repeat和expand相似,repeat占用额外空间,expand不占用



      #### 内部结构

      - Tensor分为头信息区tensor和存储区storage
      - tensor保存size, stride, type
      - 共享内存的情况下,即共享storage
      - 下标访问只是对于原storage地址增加了偏移量进行映射的

      > 大部分操作不修改tensor,只修改tensor的头
      >
      > contiguous方法将离散的tensor变成连续数据
      >
      > 高级索引一般不共享storage,普通索引共享storage

      #### 其他问题

      ##### GP

      - 推荐使用`tensor.to(device)`使得程序同时兼容CPU和GPU,避免频繁在内存和显存中传输数据

      ##### 持久化

      - `t.save`,

      ##### 向量化

      - python的for循环十分低效
      - `t.set_num_threads`可以设置PyTorch进行CPU多线程并行计算时候所占用的线程数,这个可以用来限制PyTorch所占用的CPU数目

      ### AutoGrad

      - 自动求导引擎

      #### 计算图

      - 有向无环图

      - 有向无环图的叶子节点由用户自己创建,不依赖其他变量,根节点为计算图的最终目标

      - 链式的中间导数在前向传播过程中保存为buffer,在计算完梯度后会自动清空

      > 需要多次反向传播则指定`retain_graph`保存buffer

      - 变量的`requires_grad`属性默认为False,如果某一个节点requires_grad被设置为True,那么所有依赖它的节点`requires_grad`都是True。

      - 修改tensor数值,不希望被autograd记录,则修改tensor.data或者tensor.detach()
      - 反向传播非叶子结点的导数会在计算完后被清空,查看梯度可以使用autograd.grad函数或者hood

      - hook需要register和remove

      ##### 总结

      - `autograd`根究用户对variable的操作构建计算图。对变量的操作抽象为Function
      - 某个变量不是Function的输出,且由用户创建则为叶子节点(用户输入),其`grad_fn`为None. 叶子节点中需要求导的Variable,具有`AccumulateGrad`标识,因为梯度累加
      - Variable默认不求导,如果一个节点的`require_grad`设置为True,所有依赖它的节点`require_grad`为True
      - Variable的`volatile`默认为True,volatile为True的节点不会求导,volatile优先级比`require_grad`高
      - 多次反向传播梯度累加,若需要保存反向传播中间变量的值,需要设置`retain_graph = True`

      - 非叶子节点梯度计算完之后被清空,使用`autogtad.grad`或`hook`获取

      #### 扩展autograd

      - 自己定义函数继承`Function`,并且实现`forward`, `backward`静态方法(def之前写`@staticmethod`)

      - 主函数中调用时:

      - 前向传播:

      - ```
      z = MultiplyAdd.apply(w, x, b)
    • 反向传播:

      • ```
        z.backward()
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        ## 神经网络工具箱nn

        - Module - 神经网络中的某层/包含很多层的神经网络

        ##### 全连接层

        - 继承`nn.Module`

        - 构造函数`__init__`自己定义可学习参数

        - ```python
        # infeatures和outfeatures为输入输出特征的维度
        def __init__(self, infeatures, outfeatures):
        super...
        # 自己定义可学习参数如下:
        self.para1 = nn.Parameter..
        self.para2 = ...
        ...

    nn.Parameter - tensor,默认requires_grad = True

  • 不需要写反向传播函数

  • 调用:

    1
    2
    layer = Linear(INFEATURES, OUTFEATURES)
    y = layer(x)
  • 返回可学习参数 - named_parameters()

子模块sub module
  • named_parameters()返回可学习参数为:模块名.参数名

常用神经网络层

图像相关层

卷积层
  • nn.Conv2d
池化层
  • 没有可学习参数,权重固定
其他
  • Linear全连接

  • BatchNorm - 批量规范化,风格迁移instanceNorm

    • 初始化通道数
    • 权重初始化为单位阵
    • 偏差初始化为0
  • Dropout - 防止过拟合

    • 输出每个元素的舍弃概率

激活函数

Relu
  • $ReLU(x) = max(0, x)$
    • 其inplace参数为True会将输出覆盖到输入,节省内存,一般不使用Inplace

前馈传播网络 feedfoward neural network: 将每一层的输出直接作为下一层的输入。简化forward -> ModuleList & Sequential

Sequential
  • 包含几个sub module - 前向传播一层一层传递
  1. 声明,添加module
  • ```python
    net1 = nn.Sequential()
    net1.add_module(‘conv’, nn.Conv2d(3,3,3))
    net1.add_module(‘batchnorm’, nn.BatchNorm2d(3))
    net1.add_module(‘activation_layer’, nn.ReLU())
    1
    2
    3
    4
    5
    6
    7
    8
    9

    2. 将模块作为参数传递

    - ```python
    net2 = nn.Sequential(
    nn.Conv2d(3, 3, 3),
    nn.BatchNorm2d(3),
    nn.ReLU()
    )
  1. 将模块作为有序字典传入
  • ```python
    from collections import OrderedDict
    net3= nn.Sequential(OrderedDict([
          ('conv1', nn.Conv2d(3, 3, 3)),
          ('bn1', nn.BatchNorm2d(3)),
          ('relu1', nn.ReLU())
        ]))
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    - 其中方法1和方法3可以按照模块名称访问子模块,而方法2按照下标获取子模块(从0)



    ---

    - 模块数组`nn.ModuleList`,其中的元素可以被主(父)module识别,即其参数可以自动加入到主module的参数中

    - ```python
    modellist = nn.ModuleList([nn.Linear(3,4), nn.ReLU(), nn.Linear(4,2)])

损失函数

  • ```
    nn.LOSS_FUNCTION_NAME()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    ### 优化器

    - 优化方法来自包`torch.optim`,所有的优化方法继承基类`optim.Optimizer`

    - 优化器可以对于不同的子网络(如在主网络/主Module中定义了多个子模块或者Sequential)

    - ```python
    optimizer =optim.SGD([
    {'params': net.features.parameters()}, # 学习率为1e-5
    {'params': net.classifier.parameters(), 'lr': 1e-2}
    ], lr=1e-5)

    如果某个参数显示执行学习率,则使用最外层的默认学习率

  • 指定自网络中的不同层有不同的学习率的例子:

    • ```python

      只为两个全连接层设置较大的学习率,其余层的学习率较小

      special_layers = nn.ModuleList([net.classifier[0], net.classifier[3]])
      special_layers_params = list(map(id, special_layers.parameters()))
      base_params = filter(lambda p: id(p) not in special_layers_params,
                       net.parameters())
      
      optimizer = t.optim.SGD([
              {'params': base_params},
              {'params': special_layers.parameters(), 'lr': 0.01}
          ], lr=0.001 )
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15

      ##### 调整学习率

      ###### 新建优化器

      - 优点:简单
      - 缺点:如果使用动量的优化器,会丢失动量等状态信息

      ###### 修改学习率

      - 手动decay保存动量

      - ```python
      for param_group in optimizer.param_groups:
      param_group['lr'] *= 0.1 # 学习率为之前的0.1倍

nn.functional

  • 大部分layer在functional有与之对应的函数
nn.functional和nn.Module的区别
  • module: 自动提取可学习参数
  • function: 类似纯函数,不需要可学习参数时可使用. e.g. 激活函数,池化层

  • 不具备可学习参数的层不放在构造函数__init__中。

初始化策略

  • 随机初始化可能导致后期训练中梯度爆炸或梯度消失
  • nn.init模块

nn.Module深入分析

属性

变量名 类型 说明 备注
_parameters 字典 用户设置 子模块的参数不会存放于此
_modules 字典 子模块
_buffers 字段 缓存
_backward_hooks_forward_hooks 钩子,提取中间变量
training 区分训练阶段于测试阶段,据此确定前向传播策略
_parameters, _modules, _buffers
  • 使用named_paramaters()的时候将parameters和modules的parameter全部返回

  • module层层嵌套,常用方法:

    • named_children- 查看直接子module
    • named_modules - 查看所有子module

training
  • 一些layer在训练和测试阶段差距较大,为了使得每个training都要被设置好 => 定义了模型的的train和eval模式
    • model.train() - 将当前Module及其子module所有training属性设置为True
    • model.eval() - 将training属性都设为False
_backward_hooks_forward_hooks
  • 在module前向传播或反向传播时注册,传播执行结束执行钩子,使用完及时删除
  • 钩子用于获取某些中间结果
使用方法
1
2
3
4
5
6
7
8
9
10
model = VGG()
features = t.Tensor()
def hook(module, input, output):
'''把这层的输出拷贝到features中'''
features.copy_(output.data)

handle = model.layer8.register_forward_hook(hook)
_ = model(input)
# 用完hook后删除
handle.remove()

Python的原始实现:

  • result = obj.name会调用buildin函数getattr(obj, 'name'),如果该属性找不到,会调用obj.__getattr__('name')
  • obj.name = value会调用buildin函数setattr(obj, 'name', value),如果obj对象实现了__setattr__方法,setattr会直接调用obj.__setattr__('name', value')

nn.Module中的实现:

  • nn.Module中实现了__setattr__函数,执行module.name = value时,在__setattr__中判断value是否为Parameternn.Module对象。如果是则加入_parameters_modules字典。如果是其他的对象,则保存在__dict__

_modules_parameters的item未保存在__dict__中,默认getattr无法获取,nn.Module实现__getattr__:如果getattr无法处理,则调用自定义的__getattr___modules, _parameters_buffers三个字典中获取

保存模型
  • 使用Module的state_dict()函数,返回当前Module所有状态数据。下次使用,module.load_state_dict()

  • 优化器的实现:

    • # 保存模型
      t.save(net.state_dict(), 'net.pth')
      
      # 加载已保存的模型
      net2 = Net()
      net2.load_state_dict(t.load('net.pth'))
      
  • 将Module放在GPU上运行

    • model = model.cuda() 将所有参数转存到GPU
    • input.cuda() 将输入放入GPU
在多个GPU计算
  • nn.parallel.data_parallel(module, inputs, device_ids=None, output_device=None, dim=0, module_kwargs=None)

  • class torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)
    • 将一个输入batch分成多分,送到对应GPU计算,各个GPU得到的梯度累加

通过device_ids参数指定在哪些GPU上优化,output_devices指定输出到哪个GPU

reference: http://cs231n.github.io/python-numpy-tutorial/

Basic Grammar

Basic data types

  • X unary increment (x++) or decrement (x--) operators.
  • and instead of &&, or instead of ||, not instead of ! and != for XOR

String

  • length - len(str)

  • formatting - '%s %s %d' % (str1, str2, 1)

  • capitalize - str.capitalize()

  • uppercase - str.upper()

  • Right-justify (右对齐) - str.rjust(totallength)

    totallength = len(str) + space in left

  • Center(居中) - str.center(totallength)

  • Replace - str.replace(str1, str2), all str1 in str will be replaced by str2

  • Strip leading and trailing whitespace - str.strip()

Containers

Lists

  • Negative index count from the end - list[-1]
  • can contain elements of different types
  • add new ele - list.append(newEle)
  • remove and return the last ele - list.pop()
Slicing
  • get sublist from index x to index y - nums[x:y]
    • no x: from the start
    • no y: to the end
    • x and y can be negative
Loops
  • get elements in the list - for item in list
  • get both indices and elements in the list - for index, item in list
List comprehensions
  • transform a list of elements to another: newList = [(do something to item) for item in oldList]

  • With conditions: newList = [(do something to item) for item in oldList if (in some condition)]

Dictionaries

  • key-value set
  • access with a default value - dic.get(key, defaultValue)
  • delete - del d[key]
Loops
  • get keys in the list - for key in dic
  • get both keys and value in the list - for key, value in dic.items()
Dictionary comprehensions
  • same with list, and change the value part of the dictionary
  • use {}, X []

Sets

  • an unordered collection of distinct elements

  • judge whether an element is in the set - ele in set

  • add

  • remove

  • len

Loops
  • when access both indices and elements, the order is not as assumptions
Set comprehensions
  • same, use {}

Tuples

  • ordered list of values
  • similar to list, but tuples can be used as keys in dictionaries and as element of sets

Functions

  • ```
    def functionName(para1, para2, …):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    ### Classes

    - ```python
    class className():
    # constructor
    def __init__(self, para1, para2, ...):
    # do sth

    # methods, definition of functions

Numpy

Arrays

  • all elements are of the same type

  • indexed by a tuple of nonnegative integers

  • rank - number of dimensions of the array

  • shape - a tuple of integers giving the size of the array along each dimension

  • initialize -

    1
    2
    3
    np.array([1,2,3])
    np.array([[1,2,3],[4,5,6]])
    # more ranks
    • all zeros - np.zeros(shape)
    • all ones - np.ones(shape)
    • all specific number - np.ones(shape, specificNumber)
    • idenity matrix - np.eye(dimension)
      • the shape of this matrix is dimension * dimension

Array indexing

Slicing

  • slice indexing - specify the range for each dimension - array[range in the first dimension, range in the second dimention, ...]

Accessing elements

  • integer indexing - array[index in the first dimension, index in the second dimension]

mix of slicing by index and range

  • if use only slice indexing, the returning array will be of the same rank
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np

# Create the following rank 2 array with shape (3, 4)
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]]
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])

# Two ways of accessing the data in the middle row of the array.
# Mixing integer indexing with slices yields an array of lower rank,
# while using only slices yields an array of the same rank as the
# original array:
row_r1 = a[1, :] # Rank 1 view of the second row of a
row_r2 = a[1:2, :] # Rank 2 view of the second row of a
print(row_r1, row_r1.shape) # Prints "[5 6 7 8] (4,)"
print(row_r2, row_r2.shape) # Prints "[[5 6 7 8]] (1, 4)"

# We can make the same distinction when accessing columns of an array:
col_r1 = a[:, 1]
col_r2 = a[:, 1:2]
print(col_r1, col_r1.shape) # Prints "[ 2 6 10] (3,)"
print(col_r2, col_r2.shape) # Prints "[[ 2]
# [ 6]
# [10]] (3, 1)"
  • if use integer indexing, arbitrary arrays can be construct.

  • a[[0,1,2]] => index 0, 1 and 2 in first dimension

  • Select one element from each row of a using the indices in b(an array of indices, [0, 2, 0, 1])

    1
    a[np.arange(4), b]

    The output is a[0, 0], a[1, 2], a[2, 0], a[2, 1].

Boolean array indexing
1
2
3
4
5
a = np.array([[1,2], [3, 4], [5, 6]])
bool_idx = (a > 2)
# [[False False]
# [ True True]
# [ True True]]

Datatypes

1
np.array(ARRAYDATA, dtype=ARRAYTYPE) 
  • * and / is elementwise

  • matrix multiplication or vector multiplication - dot

  • As a function: np.dot(x, y)

  • As an instance method: x.dot(y)

  • Transpose - v.T

  • `

SciPy

Image operations

  • read from file into a numpy arrays - imread(FILEPATH)

  • Computing distances between sets of points - scipy.spatial.distance.pdist

Matplotlib

  • plot, subplot and imshow

环境:win10子系统,

基本步骤

1
2
python3 -m pip install --upgrade pip
python3 -m pip install jupyter

参考:https://jupyter.org/install.html

报错解决

allow_root

运行notebook:

1
jupyter notebook

输出信息如下:

1
2
[I 19:33:39.307 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
[C 19:33:43.147 NotebookApp] Running as root is not recommended. Use --allow-root to bypass.

需要修改配置文件(我这里用的是vim编辑器):

1
vim /root/.jupyter/jupyter_notebook_config.py

找到下面的内容:

1
#c.NotebookApp.allow_root = False

并修改为:

1
c.NotebookApp.allow_root =True

参考:https://www.cnblogs.com/Annian/p/9103892.html

set password

运行jupyter notebook后打开网页(localhost:8888),显示输入密码,但是我本身没有设置密码。于是使用命令行修改密码:

1
jupyter notebook password

就可以看到当前目录下的文件了。

参考:https://jupyter-notebook.readthedocs.io/en/stable/public_server.html