# Introduction

weihang.chen's blog

> 华南理工大学本硕毕业，目前关注计算机视觉相关研究。曾经在腾讯QQ邮箱、腾讯AI Lab实习，现就职于深圳大疆创新。

* 我的Github：[ahangchen](https://github.com/ahangchen)
* 知乎：[梦里风林](https://www.zhihu.com/people/meng-li-feng-lin/activities)
* 邮箱： <cweihang@foxmail.com>

> 博客记录工作学习生活中的点滴，既是积累，也是备忘。不定期更新，欢迎[Star](https://github.com/ahangchen/windy-afternoon)

## 推荐阅读

* [2019秋招面经](https://zhuanlan.zhihu.com/p/42936891)
* [我的研究生这三年](https://zhuanlan.zhihu.com/p/54161673)
* [读论文系列](/ml/papers)
* [谷歌深度学习教程笔记](https://github.com/ahangchen/GDLnotes)
* [CVPR 2018 TFusion](/ml/papers/reid/tfusion)
* [Sqlite在Android上的一个Bug](/android/sqlite/sqlite-zai-android-shang-de-yi-ge-bug)
* [Pytorch实验代码的亿些小细节](/ml/ncs/torch_best_practice)

## Paper List

* Jianming Lv, **Weihang Chen**, Qing Li, Can Yang. Unsupervised Cross-dataset person Re-identification by Transfer Learning of Spatial-temporal Patterns, CVPR 2018. (CCF A类会议) [\[paper\]](http://openaccess.thecvf.com/content_cvpr_2018/papers/Lv_Unsupervised_Cross-Dataset_Person_CVPR_2018_paper.pdf) [\[code\]](https://github.com/ahangchen/TFusion)
* Jianming Lv, Jiajie Zhong, **Weihang Chen**, Qinzhe Xiao, Zhenguo Yang and Qing Li. Homepage Augmentation by Predicting Links in Heterogenous Networks. CIKM 2018. (CCF B类会议)


# Android

* [**activity**](/android/activity)
  * [Activity四种启动模式](/android/activity/activity-si-zhong-qi-dong-mo-shi)
  * [Intent Flag](/android/activity/intent_flag)
  * [多task的应用](/android/activity/duo-task-de-ying-yong)
  * [Task和回退栈](/android/activity/task-he-hui-tui-zhan)
* [**sqlite**](/android/sqlite)
  * [【源码】CursorWindow读DB](/android/sqlite/cong-yuan-ma-kan-android-zhong-sqlite-shi-zen-mo-tong-guo-cursorwindow-du-db-de)
  * [Sqlite在Android上的一个Bug](/android/sqlite/sqlite-zai-android-shang-de-yi-ge-bug)
* [**Chromium**](https://github.com/ahangchen/windy-afternoon/tree/0ed76feaed07a46585e67528211ae55b520e232d/android/chromnium/README.md)
  * [Chromium中文文档(writing)](https://www.gitbook.com/book/ahangchen/chromium_doc_zh)
* [ListView读取DB数据最佳实践](/android/listview_db)
* [Android Project结构](/android/androidproject-jie-gou)
* [一个由Proguard与FastJson引起的血案](/android/yi-ge-you-proguard-yu-fastjson-yin-qi-de-xie-an)
* [琐碎的一些tips](/android/note)


# activity

* [Activity四种启动模式](/android/activity/activity-si-zhong-qi-dong-mo-shi)
* [Intent Flag](/android/activity/intent_flag)
* [多task的应用](/android/activity/duo-task-de-ying-yong)
* [Task和回退栈](/android/activity/task-he-hui-tui-zhan)


# Activity四种启动模式

## **1.Standard**

默认模式，多次实例化，按序入栈出栈。

## **2.SingleTop**

栈顶Activity不会被重复创建，会触发onNewIntent()事件，不在栈顶时可以多次实例化。

## **3.SingleTask**

Developer.android.com的说法：

> （1）新建一个task，创建一个Activity；
>
> （2）如果存在一个不同的task包含这个Activity的实例，会切换到这个task，将这个Activity以上的Activity弹出，并且触发这个Activity的onNewIntent事件；

（2）描述了不会新建task的情况，但是：

实验发现（2）的前提条件，有一点问题：

**（1）“包含这个Activity的实例”不是必要条件：**

如果没有一个task包含这个Activity的实例，但是，存在一个task包含与这个Activity属于同一个App的其他Activity的实例，这时不会新建task，而是切换到这个task中，在这个task中新建这个Activity。

**（2） “不同的task”不是必要条件：**

如果不存在一个不同的task包含这个Activity的实例，但是在当前的task中本身就存在这个Activity实例，不会新建task，而会在当前task中，将这个Activity以上的Activity弹出，并且触发这个Activity的onNewIntent事件；

## **4.SingleInstance**

（1）如果没有一个task包含这个Activity的实例，会新建一个task，创建一个Activity；

（2）如果存在一个task包含这个Activity的实例，会切换到这个task，并且触发这个Activity的onNewIntent事件，Activity独占task。没有描述弹出其他Activity的必要。

（3）在包含这个Activity的task中，仅包含这个一个Activity，如果需要打开新的Activity，必须在其他task中打开，如果新建了task，task的根activity为新打开的这个Activity，之后打开这个Activity时，都不会新建Activity，只会将这个task带到前台。


# Intent Flag

FLAG\_与LaunchMode相比最大的不同是临时性

**1.FLAG\_ACTIVITY\_NEW\_TASK：**

Developer.android.com的说法：

> （1）在新的task中启动这个Activity，
>
> （2）如果已有一个task包含这个Activity，则这个task被带到前台。
>
> （3）效果与SingleTask相同。

## **实验发现**

* 只有从外部（另外的App）启动Activity，并添加FLAG\_ACTIVITY\_NEW\_TASK标识时，表现才与上述相符；
* 但不具有singleTask的clear\_top效果。
* 而在同一个APP内启动Activity并添加FLAG\_ACTIVITY\_NEW\_TASK时，与不添加标识效果一样，不仅不会新建task，而且新建的Activity不具有唯一性，与singleTask不完全相同。
* 在同一App内启动Activity时，如果将new\_task和multi\_task组合使用，就会达到打开新task的效果。

**2.FLAG\_ACTIVITY\_SINGLE\_TOP**

打开的Activity如果在栈顶，则不创建新的实例，并且会触发onNewIntent事件。

与启动launchMode为SINGLE\_TOP的Activity一致。

**3.FLAG\_ACTIVITY\_CLEAR\_TOP**

Developer.android.com的说法：

> （1）如果当前task包含这个Activity，这个Activity以上的Activity出栈，这个Activity到达栈顶。
>
> （2）如果这个Activity是standard模式，这个Activity也出栈，并且重新实例化到达栈顶。

默认行为应该是清除包括这个Activity及其以上Activity的所有Activity，但如果为要启动的Activity设置了特殊的launchMode，则launchMode会影响这个Activity的销毁与否，即：

如果这个Activity是singleTop或singleTask模式，这个Activity不出栈。

singleInstance模式没有CLEAR\_TOP的意义，因为它的task中只有自己一个Activity。

**4.FLAG\_ACTIVITY\_REORDER\_TO\_FRONT**

如果当前task中包含这个Activity，这个Activity被拉到栈顶，其他Activity的顺序不变，仍在task中。如果这个Activity被设置为SingleTask或者打开这个Activity的时候，还添加了CLEAR\_TOP的标签，则会将这个Activity上面的Activity出栈。


# 多task的应用

这部分的想法都是基于以下两点：

> 1.Activity可能被复用，可能是复用Activity的功能，还可能是复用Activity的状态；
>
> 2.Task的作用：target，同一个task中的Activity服务于相同的或者接近的目标（target）。

（一个task的目标往往由task的root Activity决定，因为是root Activity造就了这个task）。

**Activity复用情景1：**

> 在当前App中，通过Intent，打开了当前App或其他App的一个Activity（standard或singleTop），则这个Activity变成当前task的一部分。
>
> 即：在当前task中打开了一个activity。

**使用理由：**

为了完成task的目标，需要新的Activity的完全参与进来，需要它成为task的一部分，可以这样子复用；

**Activity复用情景2：**

> 在当前App中，通过Intent，使用FLAG\_ACTIVITY\_NEW\_TASK打开了当前App（新Activity的task\_affinity与当前app中其他Activity不同）或其他App的一个Activity（非singleInstance），

* 假如这个Activity没有被打开过，且没有一个task的affinity与这个Activity相同，则这个Activity变为新的task的root Activity，创建了一个新的task。
* 如果有其他的task的affinity与这个Activity相同，则会将旧的task调起，将这个Activity在这个task中打开，
* 假如这个Activity已经被打开过，则会将旧的task调起，如果配合FLAG\_ACTIVITY\_CLEAR\_TOP标签，新的Activity以上的Activity会被销毁，也就是打开了一个全新的Activity以供复用。
* 如果要打开的Activity为singleTask，不论有没有加FLAG\_ACTIVITY\_CLEAR\_TOP标签，都有上层Activity出栈的效果。

以上四种情况都可以归纳为在新的task中打开了要复用的Activity。

**使用理由：**

为了完成task的目标，需要用到新的Activity，但是这个Activity的功能，与原来task的目标有一定差距，体验上是一个新的功能，则需要创建一个独立的task，在这个task完成它的任务后，旧的task可能就不关心这个task了（比如新的task中的activity只是显示一个通知，让用户看一眼，看完就可以不管），或者，新的Activity不应该过度参与到旧的task中，（比如通知看完了就不应该再存在在task中），这种情况下就可以这样复用。

与第一种复用情形还有一个区别，这个Task中的Activity在被销毁前是可以被其他task重用的。

**Activity复用情景3：**

在当前App中，通过Intent，打开了一个SingleInstance的Activity，会创建一个新的task，且新的task中永远只有一个Activity。

**使用理由：**

与复用情形2一样，因为新的Activity的功能与原来的task的目标有一定差距，所以不能视为同一个task，所以要在新的task中打开这个Activity。

但与情形2不同的是，情形2中，旧的task不关心打开的新Activity，但打开的新Activity所在的task，可以继续创建Activity为新task的目标服务（比如添加附件功能）。

而在情形3中，新的task只有一个目标，就是发挥当前Activity的功能。不愿过多地执行更多功能，就需要使用singleInstance的模式。（比如打电话就是纯粹的打电话，打完电话该做什么不是打电话所在的这个task该关心的）

另一方面，新的task在被复用的时候，不会增加Activity，也可以保证其他task重用这个task的时候，不会受到其他task复用时新增Activity的影响。

情形2和情形3使得创建后的Activity可以被复用，节省了创建时的开销。


# Task和回退栈

对Task和backStack的认识过程

1.由demo测试得到的关系图：

![task1](/files/-L_G1DZCRKKevdiwnZT9)

* 一个task中可以有多个app的Activity，
* 由于一个app可以对应一个或多个process，
* 所以一个task也可以对应多个process。

**2.由adb shell dumpsys activity测试得到的关系图：** ![task2](/files/-L_G1DZEa_CGdy5oEGfA)

* Android将所有的task维护在Main stack中，
* 每个task由TaskId唯一标识，在Hist中包含了处于同一个task内的Activity，
* 不同的process（拥有不同的PID）中，可以包含同一个app的不同Activity，
* 一个package标识一个Application。

**3.通过学习源码得到的关系图：**&#x20;

![task3](/files/-L_G1DZGzZ1-ocL47DvR)

* 在ActivityManagerService中，由一个ActivityStack对象维护回退栈，而具体到ActivityStack中，由mHistory来维护回退栈（是一个ArrayList，而非Stack）。
* 每个Task即为一个TaskRecord对象，
* TaskRecord没有为每个Task维护一个自己的stack或list，
* mHistory中，不是直接管理Activity，而是维护ActivityRecord对象，
* ActivityRecord对象通过TaskRecord与自己所属的Task关联起来，
* ActivityRecord对象通过Activity info对象与对应的Activity关联起来，
* 分析ActivityStack中的moveTaskToFrontLocked()等对Task进行操作的代码，得到，同一个task的ActivityRecord在mHistory中是放在一起的（按照先入先出的顺序），当一个task被调到前台时，这个mHistory中，属于同一个task（taskId一致）的ActivityRecord被一起移到mHistory的顶端（在这个ArrayList中，以最后一个元素所在的位置为top，top=size()-1）;


# sqlite

* [【源码】CursorWindow读DB](/android/sqlite/cong-yuan-ma-kan-android-zhong-sqlite-shi-zen-mo-tong-guo-cursorwindow-du-db-de)
* [Sqlite在Android上的一个Bug](/android/sqlite/cong-yuan-ma-kan-android-zhong-sqlite-shi-zen-mo-tong-guo-cursorwindow-du-db-de)


# 【源码】CursorWindow读DB

**执行QUERY**

执行SQLiteDatabase类中query系列函数时，只会构造查询信息，不会执行查询。

![](/files/-L_G1D_QmXRClogfnpB2)

（query的源码追踪路径）

**执行MOVE**（里面的FILLWINDOW是真正打开文件句柄并分配内存的地方）

当执行Cursor的move系列函数时，第一次执行，会为查询结果集创建一块共享内存，即cursorwindow

![](/files/-L_G1D_S0kSnbNiaxSC7)

**moveToPosition源码路径**

**FILLWINDOW----真正耗时的地方**

![](/files/-L_G1D_ULO8ezF10Z6Q-)

然后会执行sql语句，向共享内存中填入数据，

**fillWindow源码路径**

在SQLiteCursor.java中可以看到

```java
@Override
public boolean onMove(int oldPosition, int newPosition) {
    // Make sure the row at newPosition is present in the window
    if (mWindow == null || newPosition < mWindow.getStartPosition() ||
            newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
        fillWindow(newPosition);
    }

    return true;
}
```

如果请求查询的位置在cursorWindow的范围内，不会执行fillWindow，

而超出cursorwindow的范围，会调用fillWindow，

而在nativeExecuteForCursorWindow中，

获取记录时，如果要请求的位置超出窗口范围，会发生CursorWindow的清空：

```c
CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);  
if (cpr == CPR_FULL && addedRows && startPos + addedRows < requiredPos) {  
// We filled the window before we got to the one row that we really wanted. 
// Clear the window and start filling it again from here.  
// TODO: Would be nicer if we could progressively replace earlier rows.  
window->clear();  
window->setNumColumns(numColumns);  
startPos += addedRows;  
addedRows = 0;  
cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);  
}
```

CursorWindow的清空机制会影响到多线程读（通常认为不可以并发读写，sqlite的并发实际上是串行执行的，但可以并发读，这里要强调的是多线程读也可能有问题），具体见稍后一篇文章“listview并发读写数据库”。

上面说的这些直观的感受是什么样的呢？大概是这样，

执行query，读10000条数据，很快就拿到了cursor，这里不会卡，

执行moveToFirst，卡一下（fillwindow(0)）

moveToPosition(7500)，卡一下，因为已经超了cursorwindow的区域，又去fillwindow(7500)，

关于fillwindow还有一些奇特的细节，比如4.0以后，fillwindow会填充position前后各一段数据，防止读旧数据的时候又需要fill，感兴趣的同学可以看看各个版本fillwidow的源码。

这里还可以延伸一下，因为高版本的android sqlite对旧版有许多改进，

所以实际开发里我们有时候会把sqlite的源码带在自己的工程里，使得低版本的android也可以使用高版本的特性，并且避开一部分兼容性问题。

**CURSOR关闭**（显式调用CLOSE()的理由）

追踪源码看关闭

```java
 //SQLiteCursor

super.close();
synchronized (this) {
    mQuery.close();
    mDriver.cursorClosed();
}


//AbstractCursor

public void close() {
    mClosed = true;
    mContentObservable.unregisterAll();
    onDeactivateOrClose();
}

protected void onDeactivateOrClose() {
    if (mSelfObserver != null) {
        mContentResolver.unregisterContentObserver(mSelfObserver);
        mSelfObserverRegistered = false;
    }
    mDataSetObservable.notifyInvalidated();
}


//AbstractWindowedCursor

/** @hide */
@Override
protected void onDeactivateOrClose() {
    super.onDeactivateOrClose();
    closeWindow();
}

protected void closeWindow() {
    if (mWindow != null) {
        mWindow.close();
        mWindow = null;
    }
}



//SQLiteClosable

public void close() {
    releaseReference();
}

public void releaseReference() {
    boolean refCountIsZero = false;
    synchronized(this) {
        refCountIsZero = --mReferenceCount == 0;
    }
    if (refCountIsZero) {
        onAllReferencesReleased();
    }
}

//CursorWindow

@Override
protected void onAllReferencesReleased() {
    dispose();
}

private void dispose() {
    if (mCloseGuard != null) {
        mCloseGuard.close();
    }
    if (mWindowPtr != 0) {
        recordClosingOfWindow(mWindowPtr);
        nativeDispose(mWindowPtr);
        mWindowPtr = 0;
    }
}
```

跟CursorWindow有关的路径里，最终调用nativeDispose()清空cursorWindow;

当Cursor被GC回收时，会调用finalize：

```java
@Override
protected void finalize() {
    try {
        // if the cursor hasn't been closed yet, close it first
        if (mWindow != null) {
            if (mStackTrace != null) {
                String sql = mQuery.getSql();
                int len = sql.length();
                StrictMode.onSqliteObjectLeaked(
                    "Finalizing a Cursor that has not been deactivated or closed. " +
                    "database = " + mQuery.getDatabase().getLabel() +
                    ", table = " + mEditTable +
                    ", query = " + sql.substring(0, (len > 1000) ? 1000 : len),
                    mStackTrace);
            }
            close();
        }
    } finally {
        super.finalize();
    }
}
```

然而finalize()并没有释放CursorWindow，而super.finalize();里**也只是解绑了观察者，没有去释放cursorwindow**

**所以不调用cursor.close(),最终会导致cursorWindow所在的共享内存(1M或2M)泄露。**


# Sqlite在Android上的一个Bug

> SQLiteCantOpenDatabaseException: unable to open database file

先上log

```
12-14 19:51:30.346 17770-18098/com.company.product W/System.err: com.company.product.database.sqlite.SQLiteCantOpenDatabaseException: unable to open database file (code 14)

12-14 19:51:30.346 17770-18098/com.company.product W/System.err:     at com.company.product.database.sqlite.SQLiteConnection.nativeExecuteForCursorWindow(Native Method)

12-14 19:51:30.346 17770-18098/com.company.product W/System.err:     at com.company.product.database.sqlite.SQLiteConnection.executeForCursorWindow(SQLiteConnection.java:913)

12-14 19:51:30.346 17770-18098/com.company.product W/System.err:     at com.company.product.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:819)

12-14 19:51:30.346 17770-18098/com.company.product W/System.err:     at com.company.product.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)

12-14 19:51:30.346 17770-18098/com.company.product W/System.err:     at com.company.product.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:159)

12-14 19:51:30.346 17770-18098/com.company.product W/System.err:     at com.company.product.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:147)

12-14 19:51:30.346 17770-18098/com.company.product W/System.err:     at com.company.product.database.sqlite.AbstractCursor.moveToPosition(AbstractCursor.java:218)

12-14 19:51:30.346 17770-18098/com.company.product W/System.err:     at com.company.product.database.sqlite.AbstractCursor.moveToFirst(AbstractCursor.java:258)
```

先给出结论，

* **这是sqlite在Android系统上的一个bug，在需要建立索引的sql语句频繁执行时，会发生这个异常。**
* **（如果你是在SQLiteDatabase执行open()时看到的这个exception，那应该是线程冲突的问题，跟这篇文章讲的不是同一个）**
* **根本原因是sqlite临时文件目录不可用。**
* **解决方案是第一次建立连接时设置临时文件目录。**

在项目里遇到了这样一个奇怪的crash，长期占据各个版本crash上报榜首，但在开发中一直不能重现。

在许多查DB的代码路径里，都会在moveToFirst()，getCount()等需要执行fillWindow的地方出现这个crash。

&#x20;**网络上的解决方案：**

谷歌搜索SQLiteCantOpenDatabaseException，多是一些执行SQLiteDatabase open()时线程冲突的问题，与我们这个问题不同。

跟这个问题相关的回答屈指可数，一直没找到解决方案，最相关的两种回答来自github：

<https://github.com/Raizlabs/DBFlow/issues/380>

<https://github.com/dxopt/OpenFilesLeakTest/blob/master/bugs-show/AbstractCursor.moveToFirst.md>

第一个链接与我们的情况相符，但是没有根本的解决方案，只有try – catch

第二个链接讲的是FD泄露导致打不开文件，于是我排查了app中各种泄露的地方，并且写了一个计算文件句柄数的上报工具，发现用户发生此类crash时，FD都不超过256，低于系统对单个进程默认FD数量1024的限制。排除这个可能。

（但有些时候也有可能是由这个问题引发的，可以用StrictMode detectLeak去排查）

于是先尝试在一些可能触发这个Exception的地方try-catch

再分析用户日志，发现try – catch住这个Exception后是可以继续执行一些DB查询的，

于是全都上了try – catch

&#x20;**重现路径**

分析用户日志，发现用户的一些共性，由于业务保密限制这里总结一下，共性是DB中数据量很大，并且查询中有大量的子查询。

于是尝试重现这个问题：

在数据量很大的情况下，多次查询就会重现。

可以重现的话就可以开始打log了。

为了在sqlite native层打log，编译sqlite，使用sqlite3\_log来输出自己想观察的信息。

首先我们可以看到sqlite的log

```
12-14 19:51:30.346 17770-18098/com.company.package E/SQLiteLog: (14) cannot open file at line 32440 of [bda77dda96]

12-14 19:51:30.346 17770-18098/com.company.package E/SQLiteLog: (14) os_unix.c:32440: (30) open(./etilqs_3P2SKRP0Ge6cj3T) -

12-14 19:51:30.346 17770-18098/com.company.package E/SQLiteLog: (14) statement aborts at 180: [SELECT M.*,…………………
```

可以看到是打开一个”./etilqs\_3P2SKRP0Ge6cj3T”的文件时打开失败。

先查查这个临时文件是什么鬼，

在sqlite3.c搜索前缀etilqs\_里可以看到这样的注释：

```c
/*
** Temporary files are named starting with this prefix followed by 16 random
** alphanumeric characters, and no file extension. They are stored in the
** OS's standard temporary file directory, and are deleted prior to exit.
** If sqlite is being embedded in another program, you may wish to change the
** prefix to reflect your program's name, so that if your program exits
** prematurely, old temporary files can be easily identified. This can be done
** using -DSQLITE_TEMP_FILE_PREFIX=myprefix_ on the compiler command line.
**
** 2006-10-31:  The default prefix used to be "sqlite_".  But then
** Mcafee started using SQLite in their anti-virus product and it
** started putting files with the "sqlite" name in the c:/temp folder.
** This annoyed many windows users.  Those users would then do a 
** Google search for "sqlite", find the telephone numbers of the
** developers and call to wake them up at night and complain.
** For this reason, the default name prefix is changed to be "sqlite" 
** spelled backwards.  So the temp files are still identified, but
** anybody smart enough to figure out the code is also likely smart
** enough to know that calling the developer will not help get rid
** of the file.
*/
#ifndef SQLITE_TEMP_FILE_PREFIX
# define SQLITE_TEMP_FILE_PREFIX "etilqs_"
#endif
```

总之就是临时文件就对了。

**临时文件源码追踪**

然后找找这个东西在哪里用的，

```c
/*
** Create a temporary file name in zBuf.  zBuf must be allocated
** by the calling process and must be big enough to hold at least
** pVfs->mxPathname bytes.
*/
static int unixGetTempname(int nBuf, char *zBuf){
  static const unsigned char zChars[] =
    "abcdefghijklmnopqrstuvwxyz"
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "0123456789";
  unsigned int i, j;
  const char *zDir;

  /* It's odd to simulate an io-error here, but really this is just
  ** using the io-error infrastructure to test that SQLite handles this
  ** function failing. 
  */
  SimulateIOError( return SQLITE_IOERR );

  zDir = unixTempFileDir();
  if( zDir==0 ) zDir = ".";

  /* Check that the output buffer is large enough for the temporary file 
  ** name. If it is not, return SQLITE_ERROR.
  */
  if( (strlen(zDir) + strlen(SQLITE_TEMP_FILE_PREFIX) + 18) >= (size_t)nBuf ){
    return SQLITE_ERROR;
  }

  do{
    sqlite3_snprintf(nBuf-18, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX, zDir);
    j = (int)strlen(zBuf);
    sqlite3_randomness(15, &zBuf[j]);
    for(i=0; i<15; i++, j++){
      zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ];
    }
    zBuf[j] = 0;
    zBuf[j+1] = 0;
  }while( osAccess(zBuf,0)==0 );
  return SQLITE_OK;
}
```

这里可以留意到一个神奇的东西

**zDir = unixTempFileDir();**

**if( zDir==0 ) zDir = ".";** 我们的文件是 **./etilqs\_3P2SKRP0Ge6cj3T**

所以**unixTempFileDir()**&#x786E;实是返回了0

那再看下**unixTempFileDir();**

```c
/*
** Return the name of a directory in which to put temporary files.
** If no suitable temporary file directory can be found, return NULL.
*/
static const char *unixTempFileDir(void){
  static const char *azDirs[] = {
     0,
     0,
     0,
     "/var/tmp",
     "/usr/tmp",
     "/tmp",
       /* List terminator */
  };
  unsigned int i;
  struct stat buf;
  const char *zDir = 0;

  azDirs[0] = sqlite3_temp_directory;
  if( !azDirs[1] ) azDirs[1] = getenv("SQLITE_TMPDIR");
  if( !azDirs[2] ) azDirs[2] = getenv("TMPDIR");
  for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); zDir=azDirs[i++]){
    if( zDir==0 ) continue;
    if( osStat(zDir, &buf) ) continue;
    if( !S_ISDIR(buf.st_mode) ) continue;
    if( osAccess(zDir, 07) ) continue;
    break;
  }
  return zDir;
}
```

azDirs\[0]是sqlite3\_temp\_directory，我们没有设置过，

azDirs\[1]和\[2]是环境变量，用sqlite3\_log打出来是

![](/files/-L_G1Dzeq8Fae4UjFLN2)

即环境变量里没有设置这两个值，

而另外三个目录/var/tmp，/usr/tmp，/tmp在Android系统里都是应用不可写的，

所以会返回0给unixGetTemp，

于是unixGetTemp使用了”.”作为临时文件的目录，

那”.”是哪个目录呢？

使用

```c
system(“ls . >  /sdcard/0.txt”);
```

结果是：

```
acct
adb_keys
cache
config
d
data
default.prop
dev
etc
firmware
fstab.qcom
init
init.goldfish.rc
init.qcom.class_core.sh
init.qcom.class_main.sh
init.qcom.rc
init.qcom.sh
init.qcom.usb.rc
init.qcom.usb.sh
init.rc
init.target.rc
init.trace.rc
init.usb.rc
mnt
persist
proc
root
sbin
sdcard
storage
storage_int
sys
system
tombstones
ueventd.goldfish.rc
ueventd.qcom.rc
ueventd.rc
vendor
```

这特么是根目录！当前工作目录是根目录我也是醉了。。。

所以在根目录创建临时文件一定会失败！

**etilqs临时文件创建时机**

那为什么平时使用都是正常的呢？

找一找这个临时文件的创建时机：

在unixGetTempname函数里，人为地造一个crash，通过crash堆栈配合addr2line来查看调用栈：

```
12-19 21:00:45.633 13680-14105/com.company.package E/SQLiteLog: (14) pagerstress;/data/data/com.company.package/databases/push
12-19 21:00:45.633 13680-14105/com.company.package E/SQLiteLog: (14) pager_write_pagelist
12-19 21:00:46.083 3727-3727/? I/DEBUG:     #00  pc 00037202  /data/app-lib/com.company.package-1/libqmsqlite.so unixGetTempname 32107
12-19 21:00:46.083 3727-3727/? I/DEBUG:     #01  pc 000376a7  /data/app-lib/com.company.package-1/libqmsqlite.so unixOpen 32396
12-19 21:00:46.083 3727-3727/? I/DEBUG:     #02  pc 00015ec5  /data/app-lib/com.company.package-1/libqmsqlite.so sqlite3OsOpen 17420
12-19 21:00:46.083 3727-3727/? I/DEBUG:     #03  pc 0003a16b  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.093 3727-3727/? I/DEBUG:     #04  pc 0003e0c7  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.093 3727-3727/? I/DEBUG:     #05  pc 00038e75  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.093 3727-3727/? I/DEBUG:     #06  pc 00038f55  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.093 3727-3727/? I/DEBUG:     #07  pc 00039445  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.093 3727-3727/? I/DEBUG:     #08  pc 0003add1  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.093 3727-3727/? I/DEBUG:     #09  pc 0003c1f1  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.093 3727-3727/? I/DEBUG:     #10  pc 0003d8df  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.093 3727-3727/? I/DEBUG:     #11  pc 0004c2e7  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.093 3727-3727/? I/DEBUG:     #12  pc 0004e317  /data/app-lib/com.company.package-1/libqmsqlite.so (sqlite3_step+334)
12-19 21:00:46.093 3727-3727/? I/DEBUG:     #13  pc 00063ebd  /data/app-lib/com.company.package-1/libqmsqlite.so (sqlite3_blocking_step+6)
12-19 21:00:46.093 3727-3727/? I/DEBUG:     #14  pc 00012279  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.103 3727-3727/? I/DEBUG:          61e75c04  61ced1f7  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.103 3727-3727/? I/DEBUG:          61e75c24  61ced6ab  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.103 3727-3727/? I/DEBUG:          61e75c50  61d71f4c  /data/app-lib/com.company.package-1/libqmsqlite.so
12-19 21:00:46.113 3727-3727/? I/DEBUG:          61e7610c  61cf016f  /data/app-lib/com.company.package-1/libqmsqlite.so
```

使用addr2line –C –f –e 加上面14个pc地址，结果：

```
pagerOpentemp
/media/Software/company/qmsqlite/jni/sqlite/sqlite3.c:46566
pagerStress
/media/Software/company/qmsqlite/jni/sqlite/sqlite3.c:47482
sqlite3PcacheFetchStress
/media/Software/company/qmsqlite/jni/sqlite/sqlite3.c:40751
btreeGetPage
/media/Software/company/qmsqlite/jni/sqlite/sqlite3.c:56428
btreeGetUnusedPage
/media/Software/company/qmsqlite/jni/sqlite/sqlite3.c:56556
allocateBtreePage
/media/Software/company/qmsqlite/jni/sqlite/sqlite3.c:60283
balance_nonroot
/media/Software/company/qmsqlite/jni/sqlite/sqlite3.c:61869
sqlite3BtreeInsert
/media/Software/company/qmsqlite/jni/sqlite/sqlite3.c:62554
sqlite3VdbeExec
/media/Software/company/qmsqlite/jni/sqlite/sqlite3.c:77746 (discriminator 3)
sqlite3Step
/media/Software/company/qmsqlite/jni/sqlite/sqlite3.c:71550
sqlite3_blocking_step
/media/Software/company/qmsqlite/jni/sqlite/sqlite3_unlock_notify.c:85 (discriminator 1)
nativeExecuteForCursorWindow
/media/Software/company/qmsqlite/jni/sqlite/SQLiteConnection.cpp:994
```

![](/files/-L_G1DziF3dzMgTDix_M)

**懒得看图的童鞋还是听我说吧，**

先看sqlite的architecture

![](/files/-L_G1Dzkqh11_joCBzh3)

因为我们crash的地方是查DB的地方，所以拿query操作来解释这个architecture是怎么运行的

先用SQL Command Processor解析sql语句，变成类似汇编的命令给Virtual Machine执行，

我们可以用explain plan select …. 这样的语句来查看virtual machine要执行的命令，比如

```sql
explain plan select * from A where A.a in (select b from B)
```

对应的命令是：

```
 0| Trace| 0| 0| 0| | 00
 1| Goto| 0| 56| 0| | 00
 2| OpenRead| 0| 4| 0| 13| 00
 3| Rewind| 0| 54| 0| | 00
 4| null| 0| 1| 0| | 00
 5| Once| 0| 17| 0| | 00
 6| null| 0| 1| 0| | 00
 7| OpenEphemeral| 4| 1| 0| keyinfo(1,BINARY)| 00
 8| Integer| 10000| 2| 0| | 00
 9| OpenRead| 1| 5| 0| 1| 00
 10| Rewind| 1| 16| 0| | 00
 11| Column| 1| 0| 3| | 00
 12| MakeRecord| 3| 1| 4| b| 00
 13| IdxInsert| 4| 4| 0| | 00
 14| IfZero| 2| 16| -1| | 00
 15| Next| 1| 11| 0| | 01
 16| Close| 1| 0| 0| | 00
 17| Column| 0| 0| 4| | 00
 18| IsNull| 4| 22| 0| | 00
 19| Affinity| 4| 1| 0| b| 00
 20| NotFound| 4| 22| 4| 1| 00
 21| Goto| 0| 39| 0| | 00
 22| null| 0| 5| 0| | 00
 23| Once| 1| 35| 0| | 00
 24| null| 0| 5| 0| | 00
 25| OpenEphemeral| 6| 1| 0| keyinfo(1,BINARY)| 00
 26| Integer| 10000| 6| 0| | 00
 27| OpenRead| 2| 5| 0| 12| 00
 28| Rewind| 2| 34| 0| | 00
 29| Column| 2| 11| 7| | 00
 30| MakeRecord| 7| 1| 4| b| 00
 31| IdxInsert| 6| 4| 0| | 00
 32| IfZero| 6| 34| -1| | 00
 33| Next| 2| 29| 0| | 01
 34| Close| 2| 0| 0| | 00
 35| Column| 0| 1| 4| | 00
 36| IsNull| 4| 53| 0| | 00
 37| Affinity| 4| 1| 0| b| 00
 38| NotFound| 6| 53| 4| 1| 00
 39| Column| 0| 0| 8| | 00
 40| Column| 0| 1| 9| | 00
 41| Column| 0| 2| 10| | 00
 42| Column| 0| 3| 11| | 00
 43| Column| 0| 4| 12| | 00
 44| Column| 0| 5| 13| | 00
 45| Column| 0| 6| 14| | 00
 46| Column| 0| 7| 15| | 00
 47| Column| 0| 8| 16| | 00
 48| Column| 0| 9| 17| | 00
 49| Column| 0| 10| 18| | 00
 50| Column| 0| 11| 19| | 00
 51| Column| 0| 12| 20| | 00
 52| ResultRow| 8| 13| 0| | 00
 53| Next| 0| 4| 0| | 01
 54| Close| 0| 0| 0| | 00
 55| Halt| 0| 0| 0| | 00
 56| Transaction| 0| 0| 0| | 00
 57| VerifyCookie| 0| 3| 0| | 00
 58| TableLock| 0| 4| 0| labels| 00
 59| TableLock| 0| 5| 0| Items| 00
 60| Goto| 0| 2| 0| | 00
```

可以看到其中需要建立索引，IdxInsert，于是在sqlite3VdbeExec中会进入

**OP\_IdxInsert分支**，然后

**会调用sqlite3BtreeInsert**，向B树中插入一个节点，

此时如果pPage满了，会执行**balance**平衡B树，

在这里面就会**btreeGetPage**去获取可用的page，

获取page的过程最终会执行**sqlite3\_malloc**，为page分配空间，一旦分配失败，就会在**fetch**处触发pBase == 0的条件，

于是执行**sqlite3PcacheFetchStress**，在其中调用**pager\_write\_pagelist**时触发**pPager->fd == 0**的条件（因为page在前面没有分配到空间），

于是触发**pagerOpenTemp**，往下执行调用**unixGetTempname**，得到上面所说的那个不正确的文件路径，

执行**sqlite3Osopen**时就会失败。

从上面的分析看出，触发这个路径需要几个条件：

* 执行的sql语句需要建立索引，
* B树不平衡
* 没有设置过环境变量
* 分配的内存不足以新建新的page

  所以触发条件还是比较严格的。

在**unixOpenTempname**执行时用一个变量计算临时文件的打开次数，也可以发现确实是一打开这样的文件就会失败（在打开第一个的时候就失败）。

**解决方案(Solution)**

那么最重要的事情来了，怎么修复呢？

既然是临时文件的目录没有写权限，那就改目录吧！

翻了翻sqlite的一些资料，找到了这样一个programa

<http://www.sqlite.org/c3ref/temp_directory.html>

```sql
PRAGMA temp_store_directory = 'your dir'
```

这个东西仅对当前SqliteConncetion有效，

在第一次建立sqlite连接的时候（我是重写了getReadabelDatabase()方法），设置一下临时文件目录，like this:

```java
private static boolean mainTmpDirSet = false;
@Override
    public SQLiteDatabase getReadableDatabase() {
        if (!mainTmpDirSet) {
            boolean rs = new File("/data/data/com.cmp.pkg/databases/main").mkdir();
            Log.d("ahang", rs + "");
            super.getReadableDatabase().execSQL("PRAGMA temp_store_directory = '/data/data/com.cmp.pkg/databases/main'");
            mainTmpDirSet = true;
            return super.getReadableDatabase();
        } 
        return super.getReadableDatabase();
    }
```

然后再去执行那些繁重的查询，你会发现问题消失了，

并且sqlite3会在不需要这个临时文件时自动删除它，所以你不需要做一套清理逻辑。

于是问题解决!


# Chromium

* [**Chromium**](https://github.com/ahangchen/windy-afternoon/tree/0ed76feaed07a46585e67528211ae55b520e232d/android/chromium/chromnium/README.md)
  * [Chromium中文文档(writing)](https://www.gitbook.com/book/ahangchen/chromium_doc_zh)


# ListView读取DB数据最佳实践

> 开发中我们常常用listview来呈现DB中的数据，但常见的许多做法，都没能全面考虑读写并发，内存优化。这里希望由浅入深讨论得到一种ListView读DB的最佳实践。

* 需求

> 从服务器获取 头像 + 名字，放到ListView中显示，本地存储 头像url + 名字 底部加载更多，加载服务器数据 无数据时显示空白页

这里只做逻辑层讨论，UI层，则不做不必要讨论。

* 分析
  * 首先，从服务器获取，就涉及异步写DB，以及在写DB时的并发读
  * 用户随时可能发起写DB操作
  * 是否能够加载更多，是需要从server获取的另一个数据，listview的item数量与这个值相关（影响getCount）
  * 考虑listview数据源清空的正确姿势
* 架构
  * 至少三个线程，UI主线程，读DB线程，网络请求写DB线程
  * M层：头像url + 名字
  * V层：listview，footer作为加载更多
  * C层：adapter
* Adapter的设计

  * 方案一 Cursor读取DB，遍历Cursor把数据都放到一个list放在内存里，关掉cursor，listview访问list作为数据源

    优点：没有cursor关闭问题，以及cursorwindow被置换的风险

  缺点： 一次读出DB数据会占用大量内存空间，采取游标窗口的设计，需要实现java层的cursorwindow，成本太高

  * 方案二 Cursor读取DB，listview访问cursor作为数据源

    优点：cursor机制自动达到所取即所需的机制，不会占用太多内存。

  缺点：必须谨慎考虑关闭Cursor的时机。

对方案二做一些讨论：

* Q 什么时候关闭cursor？
* A 在cursor不与listview相关联时才能关闭，否则listview滑动中，getView调用cursor.getXXX()方法会报IllegalStateException: attemp to open a database or cursor which is already closed
* Q 怎样读取新数据？
* A 点击加载更多时，重新查询DB，得到新的cursor（读DB线程），并将新的cursor交给listview的adapter（adapter.setCursor），然后adapter.notifyDataSetChanged()（UI主线程），这就引出另一个问题。
* Q adapter.setCursor应该在哪个线程做？
* A 假设放在读DB线程，setCursor后，通知主线程adapter notify，那么在主线程执行notify前，DB线程setCursor后，这段时间内，如果listview的adapter执行getView，就会在onLayhout时产生IllegalStateException，item num of listview changed, but not notify。如果一定要放DB线程做setCursor也行，不过要在onLayout时，与setCursor线程竞争锁，一来影响体验，二来影响性能。所以应该放在主线程做。
* Q 数据库数据变动后,应该如何改变adapter?
* A 首先,cursor在读DB线程中必须执行moveToPosition()来达到预读的效果,否则会在主线程读DB造成延迟。所以要在子线程中读DB，而setCursor又要在主线程做，这就需要用handler之类的线程通信来实现，因为子线程不能直接更改listview的adapter使用的cursor，所以，DB中要有一个新的cursor来承载新数据，再通知主线程换掉adapter中的cursor并notifyDataSetChanged。换掉adapter中cursor后，应该在notify后，马上关闭旧cursor。当然这里可以做一个优化，即对cursor做一个引用计数，在activity生命周期结束时清理所有cursor。
* Q 加载更多的实现？
* A 一个footer，会计入listview的adapter的getCount中，adapter.count = localData.count + hasFooter?1:0;为了正确显示加载更多，实际上请求网络数据时，会请求服务器数据量svrCnt，hasFooter = svrCnt > localData.count；而localData.count又是一次异步查DB的过程，所以，为了得到新的数据，我们需要先查DB得到当前position相关的DB数据（一个cursor），同时做一次cursor.getCount()得到hasFooter（一个boolean），这里就涉及到一个刷新策略的问题：
* Q 新数据与footer应该分别刷新listview还是等两个数据都拿到再刷新listview？
* A 分别刷新有两个问题，1，性能问题，2，数据不一致问题，这两个不是致命的，只是影响体验


# Android Project结构

* src
  * 各个功能模块的package（业务代码，包括activity）
  * utils（工具类）
  * view（自定义可重用view）
  * model（各个功能模块的公用基础代码，业务相关，不包括activity，主要是各种manager）
* res
  * layout（以前缀区分）
  * activity\_xxx
  * include\_xxx
  * item\_xxx
  * fragment\_xxx


# 一个由Proguard与FastJson引起的血案

更新微信sdk导致ComposeData中的内部类ComposeDataSender方法被混淆

根本原因，fastjson使用姿势不对。

**问题描述：**

一个发件人列表里，应当呈现的数据（这里命名为ComposeData）为空

（1）业务逻辑层

获取发件人列表时，如果Composedata超时，会从网络重新拉取Json格式的ComposeData，拉取后利用FastJson的toString()方法，将ComposeData写入DB。

混淆情况下，toString()生成的json字符串会缺少alias字段。

**定位问题到FastJson的toString()。**

（2）FastJson层

FastJson执行JSON类的toString()方法时，

对普通的Object对象（这里是ComposeDataSender），会将这个对象当做JavaBean对象处理，

在JavaBeanSerializer中，找到这个对象的getter方法，

来确定要生成的Json格式数据中，K-V的值。

在JavaBeanSerializer中打log，发现不混淆时，getter有4个（getNick(),getAlias(),getType(),getSignvalid()），混淆后，只找到一个getter（getType()）。

**定位问题到getter方法。**

（3）混淆后的ComposeDataSender

反编译没问题的旧包和有问题的新包，

**发现， 旧包的ComposeDataSender有getAlias()和getType()方法和一个被混淆后的return this.XXX方法（混淆前为getNick()方法）**

**新包的ComposeDataSender有getType()方法和两个被混淆后的return this.XXX方法（混淆前为getNick(),getAlias()方法）**&#x20;

导致发件人列表为空的原因：

获取发件人列表时，依赖于需要读取DB中的各个别名帐号的alias和type ，

旧包中，getAlias()和getType()方法没有混淆，toString()时存入DB的数据是可用的（实际上，nick字段在4.1.1也丢失了，但由于没有使用到这个字段，不会引起问题）

新包中，getType()方法没有混淆，其他getter被混淆，toString()存入DB的数据只有type（丢失了nick，alias），所以在获取发件人列表时，alias为空

（4）新包丢失alias分析

在反编译后的旧包所有代码中中查找getAlias()

可以看到mm.sdk.contact中有RContact这个类，包含了getAlias方法，因为是第三方库，其中public的getAlias方法没有被混淆，

因为proguard混淆时，同名的方法（不论是否在同一个类中）是被替换为相同的名字，（可以查看\~\build\outputs\mapping\debug\mapping.txt查看混淆时变量和方法的替换规则）

所以，代码中所有getAlias方法都没有被混淆，（相同的情况还可以在ComposeDataSender里看到，accountId属性虽然是私有的，但也没有被混淆）

而新包中，更新了mm.sdk，去掉了RContact这个类，没有getAlias方法，所以ComposeDataSender里的getAlias被混淆

而getType()没有丢失：

查找getType()方法，发现在新包或旧包中的很多第三方库中仍然被保留，所以没有被混淆，toString()时仍然可以将type字段正确存入DB

（经实验，把ComposeDataSender里的type名字改为senderType，并相应地改变get方法和set方法，就会被混淆）

（5）解决方案

修改ComposeData toString()方法，原有toString()方法，在处理items这一array对象时，直接往JSONArray中存入了ComposeDataSender对象，

所以toString生成K-V时会依赖于ComposeDataSender的getter方法。

修改为：

**往JSONArray中存入JSONObject对象，将K-V信息存入JSONObject，解析时走MapSerializer流程，不需要依赖于ComposeDataSender的getter方法。不会受混淆影响。**&#x20;


# 琐碎的一些tips

## MultiDex打包时zip错误

我遇到的是

Execution failed for task ':excelSior:packageAllDebugClassesForMultiDex'.java.util.zip.ZipException: duplicate entry: android\\/support\\/v4\\/util\\/TimeUtils.class

在gradle里，把v4的依赖换成这样子就好了：

```
dependencies {
    compile fileTree(include: '*.jar', dir: 'libs')
    compile 'com.android.support:multidex:1.0.1@aar'
}
```

## 多语言

默认的values文件夹里的strings.xml作为英语的字符串资源文件，新建一个values-zh文件夹，里面放一个strings.xml文件，只不过value都是中文，这样就会自动根据系统语言调用字符串了。

## Preference

android.support.v7.preference可以实现material design效果的设置页面，但我们要如何用getSharedPreference那套方法来操作其中的数据呢？关键在于配置xml文件的名称，查询android.support.v7.preference.PreferenceManager的构造方法，看到：

```java
public PreferenceManager(Context context) {
        this.mContext = context;
        this.setSharedPreferencesName(getDefaultSharedPreferencesName(context));
    }
```

所以可以用getDefaultSharedPreferencesName来获得xml文件的名字，另一种方法：

```java
SharedPreferences sps = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
String userName = sps.getString(getString(R.string.pref_user_name_key), getString(R.string.pref_default_user_name));
```

用getDefaultSharedPreferences来获取。


# Computer Vision


# 特征提取

## 特征点提取

* Harris角点检测：<https://senitco.github.io/2017/06/18/image-feature-harris/>

  > 计算图像周围的差异，如果两个方向的差异都很大，说明是一个具有两个主方向的点，这种差异可以用自相关矩阵的特征值描述，特征值计算麻烦，可以用矩阵的行列式和迹来间接表示。


# 三维视觉

Rodrigues vector, 旋转矩阵与四元数相互转换

```cpp
#include <opencv2/core/core.hpp>
#include <opencv2/calib3d/calib3d.hpp>
void mat2quat(cv::Mat R, double Q[])
{
    double trace = R.at<double>(0,0) + R.at<double>(1,1) + R.at<double>(2,2);
 
    if (trace > 0.0) 
    {
        double s = sqrt(trace + 1.0);
        Q[3] = (s * 0.5);
        s = 0.5 / s;
        Q[0] = ((R.at<double>(2,1) - R.at<double>(1,2)) * s);
        Q[1] = ((R.at<double>(0,2) - R.at<double>(2,0)) * s);
        Q[2] = ((R.at<double>(1,0) - R.at<double>(0,1)) * s);
    } 
    
    else 
    {
        int i = R.at<double>(0,0) < R.at<double>(1,1) ? (R.at<double>(1,1) < R.at<double>(2,2) ? 2 : 1) : (R.at<double>(0,0) < R.at<double>(2,2) ? 2 : 0); 
        int j = (i + 1) % 3;  
        int k = (i + 2) % 3;

        double s = sqrt(R.at<double>(i, i) - R.at<double>(j,j) - R.at<double>(k,k) + 1.0);
        Q[i] = s * 0.5;
        s = 0.5 / s;

        Q[3] = (R.at<double>(k,j) - R.at<double>(j,k)) * s;
        Q[j] = (R.at<double>(j,i) + R.at<double>(i,j)) * s;
        Q[k] = (R.at<double>(k,i) + R.at<double>(i,k)) * s;
    }
}

void quat2mat(double *q2, cv::Mat &rot_mat) {
    double q[4];
    q[0] = q2[3];
    q[1] = q2[0];
    q[2] = q2[1];
    q[3] = q2[2];
    // double *q = q2;
    double x[3][3];
    x[0][0] = 1 - 2 * (q[2]*q[2] + q[3] * q[3]);
    x[0][1] = 2 * (q[1] * q[2] - q[0]*q[3]);
    x[0][2] = 2 * (q[1] * q[3] + q[0] * q[2]);
    x[1][0] = 2 * (q[1] * q[2] + q[0] * q[3]);
    x[1][1] = 1 - 2 *(q[1] * q[1] + q[3] * q[3]);
    x[1][2] = 2 * (q[2] * q[3] - q[0] * q[1]);
    x[2][0] = 2 * (q[1] * q[3] - q[0] * q[2]);
    x[2][1] = 2 * (q[2] * q[3] + q[0] * q[1]);
    x[2][2] = 1 - 2 * (q[1] * q[1] + q[2] * q[2]);
    rot_mat = cv::Mat(3, 3, CV_64F, x);
    std::cout << rot_mat << std::endl;
}

int rotation_main() {
    double r_vec[3] = {0.35710906, -2.29245728, -0.60095994};
    cv::Mat rot_vec(3, 1, CV_64F, r_vec);
    cv::Mat rot_mat;
    cv::Rodrigues(rot_vec, rot_mat);
    std::cout<<rot_mat<<std::endl;
    double q[4];
    mat2quat(rot_mat, q);
    cv::Mat new_mat;
    quat2mat(q, new_mat);
    
    return 0;
}

int main() {
    rotation_main();
    return 0;
}
```


# 计算机视觉常用工具

## Eigen

* [将Eigen中的vector4f变成vector3f](https://stackoverflow.com/questions/25104665/best-way-to-convert-an-eigen-vector4-type-to-vector3)

## OpenCV

* [OpenCV逐元素访问时使用的类型](https://stackoverflow.com/questions/30596158/how-to-find-out-what-type-to-use-for-opencv-at-function-in-c)

```
        C1      C2     C3     C4     C6
CV_8U   uchar   Vec2b  Vec3b  Vec4b
CV_8S   char    -       
CV_16U  ushort  -
CV_16S  short   Vec2s  Vec3s  Vec4s
CV_32S  int     Vec2i  Vec3i  Vec4i
CV_32F  float   Vec2f  Vec3f  Vec4f  Vec6f
CV_64F  double  Vec2d  Vec3d  Vec4d  Vec6d
```


# 浅谈深度学习数据集设计

最近在和一些其他模块的同事沟通的时候，发现大家对于网络训练所需要的数据认识有很大差异，并且对于数据训练后的网络泛化性也没有感性的认识。本文尽量以与业务无关的语言，梳理一些网络训练中通用的逻辑，以形成日后数据采集和模型测试的一些准则。

TLDR:

* 模型通过训练数据，学习输入输出的映射关系；
* 数据应当尽可能全面覆盖用户场景；
* 数据清洗和数据扩增非常必要;
* 数据集测试和用户场景实测都应当重视起来；

下面开始讨论深度学习数据集设计的亿些小细节。

## Section 1. 为什么模型预测会出错

以最简单的二维曲线拟合为例，给定一堆数据点(x, y)，求一个映射y=f(x)，使得这个映射能够描述这些数据点x和y的对应关系。首先我们必须承认，由于数据的不完整，不可能存在一个模型，能够完美预测所有未知数据点的分布，面对未知，请保持敬畏。网络在设计和训练的时候，一方面是调整节点之间的连接，构造不同的搜索空间，另一方面是优化权重，在搜索空间中找到最能描述当前数据点的一个映射。以下面这条曲线对应的数据点及其拟合为例，我们来分类讨论项目中常见的一些问题：

![](/files/IwdwR0KlLgf74cNPvU0H)

$$y=-2x^2+80+20x+x^3/25$$

### 1.1 小数据到大数据

> 情景1：某个模型，在早期的一组数据上做实验，效果看起来挺不错的，但运用到大规模测试中的时候，效果拉跨。

我们用曲线中一部分数据点模拟“早期的实验数据”：

```python
# 生成代码
import matplotlib.pyplot as plt

def func(x):
    return -2 * (x ** 2) + 80 + 20 * x + (x**3)/25

def draw_left():
    xs = []
    ys = []
    for i in range(-10, 20):
        xs.append(i)
        ys.append(func(xs[-1]))
    return xs, ys

if __name__ == '__main__':
    xs, ys = draw_left()
    plt.plot(xs, ys)
    plt.scatter(xs, ys)
    plt.show()
```

![](/files/rwnGlKylgK9tZZMoCJZr)

假如我们的训练集只有\[-10, 20]区间的数据点，模型可能会认为，这些数据的分布是一条抛物线，峰值在x=6附近，越往右，y越小；然而，真实的数据分布并非如此，当测试集位于\[30, ∞]，模型的预测就会显著出错。

模型在早期的一组数据上做实验，效果看起来挺不错的，但运用到大规模测试中的时候，效果拉跨，就是陷入了上文描述的困境中。数据越相似，分布越接近，训练出来的模型也就越容易学习到局部的特征，而不能在更广阔的场景上有好的泛化，这也是我们常说的过拟合。

从上文的分析可以引申出两个小结论：

* 结论1. 场景过于单一，学习到的是局部的数据分布；
* 结论2. 相似场景的大量数据没有意义，仍然只能学到局部的数据分布；

### 1.2 数据扩增

> 情景2： 做了一堆数据扩增，但模型有了一些改善，但在大规模测试中仍然失效。

在情景1的基础上，假如我们对原始数据做扩增，可以在原始数据附近得到一些新的数据点：

```python
def draw_aug_left():
    xs = []
    ys = []
    import random
    for i in range(-10, 20):
        xs.append(i + random.randint(-15, 15))
        ys.append(func(xs[-1]))

    xs, ys = zip(*sorted(zip(xs, ys)))
    return xs, ys
```

为了模拟数据扩增的随机性，我对x做了随机的偏移，为了提高数据扩增的威力，我为每个扩增点赋予了正确的结果func(x)，尽管实际情况中某些数据扩增可能会得到错误的结果。最终随机出来的数据分布可能长下面这样：

![](/files/2OFkd4GJrMEV4eJHlBLN)

![](/files/3kiGiqfl9uuYg2EuGnMw)

由于数据扩增是围绕原始数据做的变化，这些新的数据点很难离原始数据太远，在第一张图中，跟原始数据比差别不大，看不到拐点，在第二张图中，由于随机到了30附近的点，可以看到一点拐点，但仍然看不到后边上升的尾巴，模型在拐点处的表现可能改善，但仍然很难学习到30以后的数据分布趋势。

从上文的分析可以引申处两个结论：

* 结论3：数据扩增能一定程度上避免过拟合；
* 结论4：数据扩增围绕少量数据进行，仍然可能过拟合局部数据分布；

### 1.3 样本平衡

> 情景3：采集了很多数据都是正样本，没有多少负样本，测试的时候遇到负样本预测出错。

假设我们的数据集有了较广的分布，但只有x轴以上的点：

```python
def draw_pos_sample():
    xs = []
    ys = []
    for i in range(-10, 40):
        y = func(i)
        if y > 20 or random.random() > 0.99:
            xs.append(i)
            ys.append(y)
    return xs, ys
```

![](/files/UIoXD5B8X8Nal0nUFlkx)

模型没见过多少负样本，没法知道y<0的样本变化趋势，只能根据x轴以上的数据点做大致的外推，给定一个\[20, 30]区间的输入，预测值大概率低于f(20)，但具体值应该是多少就很难保证了。

从上文分析可以引申处一个结论：

* 结论5：样本失衡时，数据量少的样本类型预测容易出错；

### 1.4 脏数据

情景4：数据标注的时候，某两类数据很像，有些样本标错了，测试时这两类数据总是预测失败，连带其他数据表现也变差了。

```python
def draw_dirty_sample():
    xs = []
    ys = []
    for i in range(0, 35):
        xs.append(i)
        y = func(xs[-1])
        if random.random() > 0.9:
            ys.append(random.randint(-100, 100))
        else:
            ys.append(func(xs[-1]))
    return xs, ys
```

![](/files/4DT8WPUNQSEJ2A4AJXZG)

我们在曲线数据里随机加一些脏数据，曲线的样子就大不相同，遇上这样的数据，模型肯定学不出\[30, ∞]曲线上升的趋势了。情景4中标错的数据也与我们的随机数据相似，会影响整个模型的参数，导致标错的数据类型及相似的数据类型表现不佳。

从上文分析可以引申出一个结论：

* 结论6：脏数据（尤其是关键拐点处）会严重影响模型识别效果。

测试场景是无限的，训练集无法覆盖所有的目标场景，从上面的分析我们可以看出，如果要让有限的数据训练出的模型，在尽可能多的场景上达到最大的泛化能力，关键就在于要让训练集的数据分布尽可能覆盖目标测试场景数据分布。所以问题变成了怎么确定目标测试场景数据分布

## Section 2. 我们需要什么样的数据

### 2.1 目标测试场景数据分布

2.1.1 模型可能有哪些输入？

这其实是一个需求分析问题，当我们拿到一个NN相关需求的时候，就需要问，我的输入是什么样的：用户可能在哪些场景下触发这个模型的识别？室内还是室外？是否所有天气状况都需要工作？识别目标的尺寸是怎样的？变化范围有多大？输入的图像是灰度图还是彩色图？标定是否正常，图像是否有畸变？成像信噪比如何？各种场景中是否有明显的Corner case是人也区分不开的？如果确实有区分不开的数据，是否能够增加更多的输入信息？考虑清楚这一系列问题之后，需要制定一个完备的数据采集场景list，覆盖采集对应的数据。采集时还需要注意，相似场景不需要过多采集，优先提高场景的丰富性而非数量。

2.1.2 模型需要有怎样的输出？

这也是一个需求分析问题，但更侧重于上下游的联调：下游希望我们有什么样的输出？输出的变化范围有多大？下游希望输出有多高的准确率？如果给出置信度和输出，是否能容忍一部分预测出错？什么样的错误预测是下游可以通过其他渠道识别并排除的？什么样的错误预测是绝对不允许的？召回率更重要还是准确率更重要？讨论清楚这一系列问题之后，才能调整优化方向，让有限的模型在下游更关切的方向上有更好的预测效果。

### 2.2 数据后处理

当我们根据最终业务需求，与上下游一起，确认模型的工作范围，并采集分布尽可能广的数据之后，往往还需要针对Section 1提到的几种模型容易出错的场景，对数据进行处理。

相似样本不需要太多，可以适当降频采样，从而实现样本的平衡性，提高数据丰富性。

由于采集精力有限，我们为了覆盖尽可能广的数据分布，单个场景的数据往往就没有那么多，因此数据增强就非常重要了。亮度变化，随机噪声，遮挡，翻转平移缩放，仿射变换，生成数据，很多时候都能提高泛化性。

脏数据一定要挑出来修正或去除。脏数据不仅是指标签不对的数据或者标注精度不够的数据，更重要的是那些不利于模型形成正确分布的数据，例如分类问题，图中同时有多个类别，模型就不知道自己该学成什么样。标注时一定程度上需要根据模型的拟合能力来定好标注原则，使得标注更容易学习。

## Section 3. 模型测试

先讲数据集上的测试，即测试集的设计。原则上测试集的分布要与训练集相同，因此测试集的采集一般要与训练集同步进行，沿用相同的采集list；但是，测试集必须与训练集有差异，否则就只能称之为验证集了。实际操作中，可以在不同的城市采集相同场景的数据，或者同个地点，这周采训练集，下周采测试集，以保证训练测试是有一定差异的，保证模型过拟合能被发现。

模型测试不仅包含数据集上的测试，还包含与上下游联调起来之后的实际场景测试。很多时候，测试集上99%的准确率，不如一次成功的demo更能让人信服。实测成功意味着你的模型确实正常工作起来了，准确率和速度都达到了下游的需求，要达到这一点，往往需要克服各种模型部署中的困难，可能需要替换掉某些无法实现的算子，或者压缩模型以达到推理速度的需求，可能会导致泛化性下降，具体部署相关的问题可以再另写一篇文章讨论，这里先讲实测中的数据问题。

实测的场景一方面要包含训练list中的各种场景，另一方面更要像用户一样测试。数据集设计时往往我们会为了数据平衡，导致普通场景数据比例没有实测中那么多；但像用户一样测试，往往普通场景会测得更多，更多发现普通场景中不work的数据，对于这类数据，出于优先保证用户最常见的场景体验，需要将其补充进训练集中。

测试应当尽早开展，一边测试，一边采集。测试得到大量的新数据，如果全部加入训练，就会打乱原来的数据分布。更好的策略是，用初版模型筛出困难场景，能够预测成功的场景，就没必要加太多了，预测失败的数据更有价值，从而更有针对性地优化模型。但如果是一些模型能力范围之外的数据（比如行人识别任务遇到了穿玩偶服的人），就不能加入训练数据之中了，也许下一版本设计新的模型（比如增加一个类用来识别熊本熊？）来解决。

## 写在最后

深度学习是一个数据驱动算法，模型越小，对数据的质量要求越高；采集设备搭建，数据集设计，采集，分析，清洗，增强，测试往往要占到算法一半以上的工作量。如果下次你的模型预测出错了，不如花点时间，把数据集再整整，说不定会有惊喜发生。


# 随笔

* 在ORB SLAM中获取各种3D点，同时根据语义分割的结果为点赋予其类别，用于更准确的点匹配。比如[这篇](https://paperswithcode.com/paper/visual-semantic-slam-with-landmarks-for-large)


# Machine Learning

* [基础](https://github.com/ahangchen/windy-afternoon/tree/f8fddea761a9c19a823405bf8ce7b345e8b2468e/ml/base/README.md)
  * [SGD](https://github.com/ahangchen/windy-afternoon/tree/f8fddea761a9c19a823405bf8ce7b345e8b2468e/ml/base/SGD.pdf)
  * [《机器学习（周志华）》笔记](/ml/zzh_ml_notes/melon)
* [工具包](/ml/kit)
  * [TensorFlow学习笔记](https://github.com/ahangchen/GDLNotes)
* [技巧](/ml/trick)
* [实践](/ml/ncs)
* [读论文系列](/ml/papers)
  * [MLA 2016 南京 笔记](/ml/papers/overview/mla_2016)
  * [CNCC2017中的深度学习与跨媒体智能](/ml/papers/overview/cncc_cv)
  * [Person Re-id](/ml/papers/reid)
    * [Person Re-identification](/ml/papers/reid/reid)
    * [CVPR2016 Re-id](/ml/papers/reid/cvpr-reid)
    * [Camera topology and Person Re-id](/ml/papers/reid/reid-topo)
    * [Deep transfer learning Person Re-id](/ml/papers/reid/deep_transfer_learning_person_reid)
    * [CVPR 2018 TFusion完全解读](/ml/papers/reid/tfusion)
  * [Object Detection](/ml/papers/detection)
    * [RCNN](/ml/papers/detection/rcnn)
    * [SPPNet](/ml/papers/detection/sppnet)
    * [Fast RCNN](/ml/papers/detection/fast_rcnn)
    * [Faster RCNN](/ml/papers/detection/faster_rcnn)
    * [YOLO](/ml/papers/detection/yolo)
    * [SSD](/ml/papers/detection/ssd)
    * [YOLOv2\&YOLOv3](/ml/papers/detection/yolo23)
    * [Other](/ml/papers/detection/other)
  * [集成学习](https://github.com/ahangchen/windy-afternoon/tree/f8fddea761a9c19a823405bf8ce7b345e8b2468e/ml/essemble/README.md)
    * [多模态数据融合](https://github.com/ahangchen/windy-afternoon/tree/f8fddea761a9c19a823405bf8ce7b345e8b2468e/ml/Methodologies_for_Cross-Domain_Data_Fusion_An_Overview.pptx)


# 技巧

## 反卷积的棋盘格效应

使用反卷积时，由于卷积区域的overlap，会形成棋盘格效应，在kernel size不能被stride整除时尤为明显，比较好的替代方案是upsample+conv


# FaceBook: 1 hour training ImageNet

## Accurate, Large Minibatch SGD

## Training ImageNet in 1 Hour

### Main Idea

* Higher training speed requires larger mini-batch size.

> 8192 images one batch, 256 GPUs

* Larger mini-batch size leads to lower accuracy
* Linear scaling rule for adjusting learning rates as a function of minibatch size
* Warmup scheme overcomes optimization challenges early in training

### Background

* mini-batch SGD
* Larger mini-batch size lead to lower accuracy.

### mini-batch SGD

![](/files/-L_G1E3StizpGkSaNAWN)

### mini-batch SGD

* Iteration(in FaceBook Paper):

![](/files/-L_G1E3UjXQoAbmTfxnr)

* Convergence:

  * Learning Rate: ![](https://www.zhihu.com/equation?tex=\gamma+%3D+1%2F\sqrt{MK\sigma^2})

  * Converge Speed: ![](https://www.zhihu.com/equation?tex=1%2F\sqrt{MK})

  > M: batch size, K: iteration number, σ²: stochastic gradient variance

### Goal

* Use large minibatches
  * scale to multiple workers
* Maintaining training and generalization accuracy

### Solution

* Linear Scaling Rule: When the minibatch size is multiplied by k, multiply the learning rate by k.

### Analysis

* k iteration, minibatch size of n:

  ![](/files/-L_G1E3W7rZDkqdudm24)
* 1 iteration, minibatch size of kn:

  ![](/files/-L_G1E3YR-hXB10qkQ4d)
* Assume gradients of the above fomulas are equal
  * Two updates can be similar only if we set the second learning rate to k times the first learning rate.

### Conditions that assumption not hold

* Initial training epochs when the network is changing rapidly.
* Results are stable for a large range of sizes, beyond a certain point

  ![](/files/-L_G1E3_8fBIoVrKI5Y5)

### Warm Up

* Low learning rate to solve rapid change of the initial network.
* Constant Warmup: Sudden change of learning rate causes the training error to spike.
* Gradual warmup: Ramping up the learning rate from a small to a large value.
* start from a learning rate of η and increment it by a constant amount at each iteration such that it reaches η̂ = kη after 5 epochs.

### Reference

* [Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour](https://research.fb.com/wp-content/uploads/2017/06/imagenet1kin1h3.pdf?)
* [机器之心提问：如何评价Facebook Training ImageNet in 1 Hour这篇论文?](https://www.zhihu.com/question/60874090)
* [Asynchronous Parallel Stochastic Gradient for Nonconvex Optimization](https://arxiv.org/abs/1506.08272)
* [ENTROPY-SGD: BIASING GRADIENT DESCENT INTO WIDE VALLEYS](https://arxiv.org/pdf/1611.01838.pdf)


# L2 Norm与L2 normalize

## L2 Norm

L2 Norm是向量一个L2模，是一个实数，也称L2范数，二范数

$$norm(x) = \sqrt{x\_1^2+x\_2^2+...+x\_n^2 }$$

## L2 Normalize

L2归一化，是对单个向量的各个元素做归一化的手段，使得向量x变换后的结果x'的L2 norm为1

$$1 = norm(x')=\frac{\sqrt{x\_1^2+x\_2^2+...+x\_n^2 }}{norm(x)}$$ $$=\sqrt{\frac{x\_1^2+x\_2^2+...+x\_n^2}{norm(x)^2}}$$ $$=\sqrt{(\frac{x\_1}{norm(x)})\_2+(\frac{x\_2}{norm(x)})\_2+...+(\frac{x\_n}{norm(x)})\_2}$$ $$=\sqrt{x\_1^2'+x\_2^2'+...+x\_n^2'}$$

即： $$x'\_i =\frac{x\_i}{norm(x)}$$


# 实践

> 转载请注明作者[梦里茶](https://github.com/ahangchen)

![](/files/-MkuKw3XRQiMs8VaiwOc) 代码: 训练数据预处理： <https://gist.github.com/ahangchen/ae1b7562c1f93fdad1de58020e94fbdf> 测试：<https://github.com/ahangchen/ncs_detection>

> Star是一种美德。

## Background

最近在做一个项目，要在树莓派上分析视频中的图片，检测目标，统计目标个数，这是一张样例图片：

![](/files/-MkuKw3ZuSRrziJh4oag)

## Motivation

当下效果最好的目标检测都是基于神经网络来做的，包括faster rcnn， ssd, yolo2等等，要在树莓派这种资源紧张的设备上运行检测模型，首先想到的就是用最轻量的MobileNet SSD，使用Tensorflow object detection api实现的MobileNet SSD虽然已经非常轻，但在树莓派上推导一张1280x720的图仍然需要2秒，有兴趣的同学可以参考这两个项目：

* armv7版Tensorflow（必须是1.4及以上）:<https://github.com/lhelontra/tensorflow-on-arm/releases>
* Tensorflow Object detection API: <https://github.com/tensorflow/models/tree/master/research/object_detection>

具体的操作在Tensorflow文档里都说的很清楚了，在树莓派上的操作也是一样的，有问题可以评论区讨论

## Hardware

极限的模型仍然不能满足性能需求，就需要请出我们今天的主角了，[Intel Movidius Neural Computing Stick](https://developer.movidius.com/) ![Intel Movidius Neural Computing Stick](https://upload-images.jianshu.io/upload_images/1828517-91b7cdc17798b7ef.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)

|  处理器 |        Intel Movidius VPU        |
| :--: | :------------------------------: |
| 支持框架 |         TensorFlow, Caffe        |
| 连接方式 |          USB 3.0 Type-A          |
|  尺寸  | USB stick (72.5mm X 27mm X 14mm) |
| 工作温度 |            0° - 40° C            |
|      |                                  |
|      |      x86\_64 Ubuntu 16.04主机      |
|      | Raspberry Pi 3B  Stretch desktop |
|      |         Ubuntu 16.04 虚拟机         |
| 系统要求 |      USB 2.0 以上 (推荐 USB 3.0)     |
|      |              1GB 内存              |
|      |              4GB 存储              |
|      |                                  |

实际上这不是一个GPU，而是一个专用计算芯片，但能起到类似GPU对神经网络运算的加速作用。

京东上搜名字可以买到，只要500元左右，想想一块GPU都要几千块钱，就会觉得很值了。

SDK是开源的：<https://github.com/movidius/ncsdk>

提问不在GitHub issue里，而是在一个专门的论坛：<https://ncsforum.movidius.com/>

虽然目前NCSDK支持的框架包含Tensorflow和Caffe，但并不是支持所有的模型，目前已支持的模型列表可以在这里查到：<https://github.com/movidius/ncsdk/releases>

截止到2018年3月15日，NCSDK还没有支持Tensorflow版的MobileNet SSD（比如`tf.cast`这个操作还未被支持），所以我们需要用Caffe来训练模型，部署到树莓派上。

## Environment

ncsdk的环境分为两部分，训练端和测试端。

* 训练端通常是一个Ubuntu 带GPU主机，训练Caffe或TensorFlow模型，编译成NCS可以执行的graph；
* 测试端则面向ncs python mvnc api编程，可以运行在树莓派上raspbian stretch版本，也可以运行在训练端这种机器上。

### 训练端

#### 安装

安装这个过程，说难不难，也就几行命令的事情，但也有很多坑

在训练端主机上，插入神经计算棒，然后：

```
git clone https://github.com/movidius/ncsdk
cd ncsdk
make install
```

其中，make install干的是这些事情：

* 检查安装Tensorflow
* 检查安装Caffe([SSD-caffe](https://github.com/weiliu89/caffe))
* 编译安装ncsdk（不包含inference模块，只包含mvNCCompile相关模块，用来将Caffe或Tensorflow模型转成NCS graph的）

注意，

* 这些库都是安装到`/opt/movidius/`这个目录下，并关联到系统python3里边的（`/usr/bin/python3`），如果你电脑里原来有tf或caffe，也不会被关联上去
* NCSDK mvNCCompile模块目前只兼容python3，我尝试过将安装完的SDK改成兼容python2的版本，可以将模型编译出来，但是在运行时会报错，所以暂时放弃兼容python2了，也建议大家用默认的python3版本
* 这个步骤主要的坑来自万恶的Caffe，如果你装过python3版的caffe，大概会有经验一些，这里有几个小坑提示一下：
  * 最好在ncsdk目录中的ncsdk.conf中，开启caffe的cuda支持，即设置`CAFFE_USE_CUDA=yes`，这样你之后也能用这个caffe来训练模型
  * caffe的依赖会在脚本中安装，但有些Debian兼容问题要解决
  * 开启CUDA支持后，编译caffe会找不到libboost-python3，因为在Ubuntu16.04里，它叫libboost-python3.5，所以要软链接一下：

```bash
cd /usr/lib/x86_64-linux-gnu/
sudo ln -s libboost_python-py35.so libboost_python3.so
```

* 其他可能出现的caffe的坑，可以在我[博客](https://github.com/ahangchen/windy-afternoon/blob/master/linux/note.md#caffe官网安装教程没告诉你的东西)找找答案，如果没有的话，就去caffe的GitHub issue搜吧

#### 测试

一波操作之后，我们装好了ncsdk编译模块，可以下载我训练的caffe模型，尝试编译成ncs graph

```bash
git clone https://github.com/ahangchen/MobileNetSSD
mvNCCompile example/MobileNetSSD_deploy.prototxt -w MobileNetSSD_deploy.caffemodel -s 12 -is 300 300 -o ncs_mobilenet_ssd_graph
```

这里其实是调用python3去执行/usr/local/bin/ncsdk/mvNCCompile.py这个文件， 不出意外在当前版本（1.12.00）你会遇到这个错误：

```bash
[Error 17] Toolkit Error: Internal Error: Could not build graph. Missing link: conv11_mbox_conf
```

这是因为NCSDK在处理caffe模型的时候，会把conv11\_mbox\_conf\_new节点叫做conv11\_mbox\_conf，所以build graph的时候就会找不着。因此需要为这种节点起一个别名，即，将conv11\_mbox\_conf\_new起别名为conv11\_mbox\_conf，修改SDK代码中的/usr/local/bin/ncsdk/Models/NetworkStage.py，在第85行后面添加：

```python
if ''_new' in name:
    self.alias.append(name[:-4])
```

于是就能编译生成graph了，你会看到一个名为ncs\_mobilenet\_ssd\_graph的文件。

> 这个解决方案在新版本的ncsdk中不被支持，建议修改prototxt，详情见这个[issue](https://github.com/ahangchen/windy-afternoon/issues/17#issuecomment-410917202)

上边这个bug我已经跟NCSDK的工程师讲了，他们在跟进修这个bug： ![NCS bug](https://upload-images.jianshu.io/upload_images/1828517-0339d113ef259dbb.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)

### 测试端

#### NCSDK

测试端要安装ncsdk python api，用于inference，实际上测试端能做的操作，训练端也都能做

```
git clone https://github.com/movidius/ncsdk
cd api/src
make install
```

从输出日志可以发现，将ncsdk的lib和include文件分别和系统的python2（/usr/bin/python2）和python3(/usr/bin/python3)做了关联。

然后你可以下一个GitHub工程来跑一些测试：

```bash
git clone https://github.com/movidius/ncappzoo
cd ncappzoo/apps/hello_ncs_py
python3 hello_ncs.py
python2 hello_ncs.py
```

没报错就是装好了，测试端很简单。

#### OpenCV

看pyimagesearch这个[教程](https://www.pyimagesearch.com/2017/09/04/raspbian-stretch-install-opencv-3-python-on-your-raspberry-pi/)

## Caffe模型训练

就是正常的用caffe训练MobileNet-SSD，主要参考这个仓库：

* MobileNet-SSD: <https://github.com/chuanqi305/MobileNet-SSD>

README里将步骤讲得很清楚了

1. 下载SSD-caffe（这个我们已经在NCSDK里装了）
2. 下载chuanqi在VOC0712上预训练的[模型](https://drive.google.com/open?id=0B3gersZ2cHIxVFI1Rjd5aDgwOG8)
3. 把MobileNet-SSD这个项目放到SSD-Caffe的examples目录下，这一步可以不做，但是要对应修改train.sh里的caffe目录位置
4. 创建你自己的`labelmap.prototxt`，放到MobileNet-SSD目录下，比如说，你是在coco预训练模型上训练的话，可以把[coco的标签文件](https://github.com/weiliu89/caffe/blob/ssd/data/coco/labelmap_coco.prototxt)复制过来，将其中与你的目标类（比如我的目标类是Cattle）相近的类（比如Coco中是Cow）改成对应的名字，并用它的label作为你的目标类的label。（比如我用21这个类代表Cattle）
5. 用你自己的数据训练MobileNet-SSD，参考SSD-caffe的[wiki](https://github.com/weiliu89/caffe/wiki/Train-SSD-on-custom-dataset)，主要思路还是把你的数据转换成类似VOC或者COCO的格式，然后生成lmdb，坑也挺多的：
6. 假设你的打的标签是这样一个文件`raw_label.txt`，假装我们数据集只有两张图片：

```
data/strange_animal/1017.jpg 0.487500    0.320675    0.670000    0.433193
data/strange_animal/1018.jpg 0.215000    0.293952    0.617500    0.481013
```

* 我们的目标是将标签中涉及的`图片和位置信息`转成这样一个目录（在ssd-caffe/data/coco目录基础上生成的）：

  ```
  coco_cattle
  ├── all # 存放全部图片和xml标签文件
  │   ├── 1017.jpg
  │   ├── 1017.xml
  │   ├── 1018.jpg
  │   └── 1018.xml
  ├── Annotations # 存放全部标签xml
  │   ├── 1017.xml
  │   └── 1018.xml
  ├── create_data.sh # 将图片转为lmdb的脚本
  ├── create_list.py # 根据ImageSets里的数据集划分文件，生成jpg和xml的对应关系文件到coco_cattle目录下，但我发现这个对应关系文件用不上
  ├── images  # 存放全部图片
  │   ├── 1017.jpg
  │   └── 1018.jpg
  ├── ImageSets # 划分训练集，验证集和测试集等，如果只想分训练和验证的话，可以把minival.txt,testdev.txt,test.txt内容改成一样的
  │   ├── minival.txt 
  │   ├── testdev.txt
  │   ├── test.txt
  │   └── train.txt
  ├── labelmap_coco.prototxt # 如前所述的标签文件，改一下可以放到MobileNet-SSD目录下
  ├── labels.txt
  ├── lmdb # 手动创建这个目录
  │   ├── coco_cattle_minival_lmdb # 自动创建的，由图片和标签转换来的LMDB文件
  │   ├── coco_cattle_testdev_lmdb
  │   ├── coco_cattle_test_lmdb
  │   └── coco_cattle_train_lmdb
  ├── minival.log
  ├── README.md
  ├── testdev.log
  ├── test.log
  └── train.log
  ```
* 其中，标签xml的格式如下：

```markup
<annotation>
  <folder>train</folder>
  <filename>86</filename>
  <source>
    <database>coco_cattle</database>
  </source>
  <size>
    <width>720</width>
    <height>1280</height>
    <depth>3</depth>
  </size>
  <segmented>0</segmented>
  <object>
    <name>21</name>
    <pose>Unspecified</pose>
    <truncated>0</truncated>
    <difficult>0</difficult>
    <bndbox>
      <xmin>169</xmin>
      <ymin>388</ymin>
      <xmax>372</xmax>
      <ymax>559</ymax>
    </bndbox>
  </object>
  <object>
    <name>21</name>
    <pose>Unspecified</pose>
    <truncated>0</truncated>
    <difficult>0</difficult>
    <bndbox>
      <xmin>169</xmin>
      <ymin>388</ymin>
      <xmax>372</xmax>
      <ymax>559</ymax>
    </bndbox>
  </object>
</annotation>
```

代表一张图中多个对象所在位置（bndbox节点表示），以及类别（name）。

* 一开始，`all`, `Annotations`, `images`, `ImageSets`,`lmdb`四个目录都是空的，你可以把自己的图片放到随便哪个地方，只要在raw\_label.txt里写好图片路径就行
* 读取`raw_label.txt`，利用`lxml`构造一棵dom tree，然后写到`Annotations`对应的xml里，并将对应的图片移动到`image`目录里，可以参考[这份代码](https://gist.github.com/ahangchen/ae1b7562c1f93fdad1de58020e94fbdf)。并根据我们设置的train or not标志符将当前这张图片分配到训练集或测试集中（也就是往ImageSet/train.txt中写对应的图片名）
* 这样一波操作之后，我们的`images`和`Annotations`目录里都会有数据了，接下来我们需要把它们一块复制到`all`目录下

```
cp images/* all/
cp Annotations/* all/
```

* 然后用create\_data.sh将`all`中的数据，根据`ImageSet`中的数据集划分，创建训练集和测试集的lmdb，这里对coco的create\_data.sh做了一点修改：

```bash
cur_dir=$(cd $( dirname ${BASH_SOURCE[0]} ) && pwd )
root_dir=$cur_dir/../..

cd $root_dir

redo=true
# 这里改成all目录
data_root_dir="$cur_dir/all"
# 这里改成自己的数据集名，也是我们这个目录的名字
dataset_name="coco_cattle"
# 指定标签文件
mapfile="$root_dir/data/$dataset_name/labelmap_coco.prototxt"
anno_type="detection"
label_type="xml"
db="lmdb"
min_dim=0
max_dim=0
width=0
height=0

extra_cmd="--encode-type=jpg --encoded"
if $redo
then
  extra_cmd="$extra_cmd --redo"
fi
for subset in minival testdev train test
do
  python3 $root_dir/scripts/create_annoset.py --anno-type=$anno_type --label-type=$label_type --label-map-file=$mapfile --min-dim=$min_dim --max-dim=$max_dim --resize-width=$width --resize-height=$height --check-label $extra_cmd $data_root_dir $root_dir/data/$dataset_name/ImageSets/$subset.txt $data_root_dir/../$db/$dataset_name"_"$subset"_"$db examples/$dataset_name 2>&1 | tee $root_dir/data/$dataset_name/$subset.log
done
```

于是会lmdb目录下会为每个划分集合创建一个目录，存放数据

```
├── lmdb
│   ├── coco_cattle_minival_lmdb
│   │   ├── data.mdb
│   │   └── lock.mdb
│   ├── coco_cattle_testdev_lmdb
│   │   ├── data.mdb
│   │   └── lock.mdb
│   ├── coco_cattle_test_lmdb
│   │   ├── data.mdb
│   │   └── lock.mdb
│   └── coco_cattle_train_lmdb
│       ├── data.mdb
│       └── lock.mdb
```

1. 将5生成的lmdb链接到MobileNet-SSD的目录下：

```bash
cd MobileNet-SSD
ln -s PATH_TO_YOUR_TRAIN_LMDB trainval_lmdb
ln -s PATH_TO_YOUR_TEST_LMDB test_lmdb
```

1. 运行`gen_model.sh`生成三个prototxt（train, test, deploy）

```
# 默认clone下来的目录是没有example这个目录的，而gen_model.sh又会把文件生成到example目录
mkdir example
./gen_model.sh
```

1. 训练

   ```
   ./train.sh
   ```

   这里如果爆显存了，可以到`example/MobileNetSSD_train.prototxt`修改batch size，假如你batch size改到20，刚好可以吃满GTX1060的6G显存，但是跑到一定步数（设置在`solver_test.prototxt`里的test\_interval变量），会执行另一个小batch的test（这个batch size定义在`example/MobileNetSSD_test.prototxt`里），这样就会再爆显存，所以如果你的`train_batch_size + test_batch_size <= 20`的话才可以保证你在6G显存上能顺利完成训练，我的设置是`train_batch_size=16, test_batch_size=4`

一开始的training loss可能比较大，30左右，等到loss下降到2.x一段时间就可以ctrl+c退出训练了，模型权重会自动保存在snapshot目录下

1. 运行merge\_bn.py将训练得到的模型去除bn层，得到可部署的Caffe模型，这样你就能得到一个名为`MobileNetSSD_deploy.caffemodel`的权重文件，对应的prototxt为`example/MobileNetSSD_deploy.prototxt`
2. 离题那么久，终于来到主题，我们要把这个caffemodel编译成NCS可运行的graph，这个操作之前在搭环境的部分也提过：

```
mvNCCompile example/MobileNetSSD_deploy.prototxt -w MobileNetSSD_deploy.caffemodel -s 12 -is 300 300 -o ncs_mobilenet_ssd_graph
```

参数格式：

```
mvNCCompile prototxt路径 -w 权重文件路径 -s 最大支持的NCS数目 -is 输入图片宽度 输入图片高度 -o 输出graph路径
```

其实训练端相对于chuanqi的MobileNet-SSD没啥改动，甚至训练参数也不用怎么改动，主要工作还是在数据预处理上，可以参考我的[预处理代码](https://gist.github.com/ahangchen/ae1b7562c1f93fdad1de58020e94fbdf)

## 树莓派NCS模型测试

现在我们要用ncs版的ssd模型在树莓派上进行对图片做检测，这个目标一旦达成我们自然也能对视频或摄像头数据进行检测了。

### [仓库](http://github.com/ahangchen/ncs_detection)结构

```
ncs_detection
├── data # 标签文件
│   └── mscoco_label_map.pbtxt
├── file_helper.py # 文件操作辅助函数
├── model # 训练好的模型放在这里
│   ├── ncs_mobilenet_ssd_graph
│   └── README.md
├── ncs_detection.py # 主入口
├── object_detection # 改了一下TF的Object detection包中的工具类来用
│   ├── __init__.py
│   ├── protos
│   │   ├── __init__.py
│   │   ├── string_int_label_map_pb2.py
│   │   └── string_int_label_map.proto
│   └── utils
│       ├── __init__.py
│       ├── label_map_util.py
│       └── visualization_utils.py
├── r10 # 图片数据
│   ├── 00000120.jpg
│   ├── 00000133.jpg
│   ├── 00000160.jpg
│   ├── 00000172.jpg
│   ├── 00000192.jpg
│   ├── 00000204.jpg
│   ├── 00000220.jpg
│   └── 00000236.jpg
├── README.md
└── total_cnt.txt
```

* 由于这个工程一开始是用Tensorflow Object Detection API做的，所以改了其中的几个文件来读标签和画检测框，将其中跟tf相关的代码去掉。
* TF的图片IO是用pillow做的，在树莓派上速度奇慢，对一张1280x720的图使用Image的get\_data这个函数获取数据需要7秒，所以我改成了OpenCV来做IO。

### 任务目标

检测`r10`目录中的图片中的对象，标记出来，存到`r10_tmp`目录里

### 流程

* 准备目标目录

```python
def config_init(dataset_pref):
    os.system('mkdir %s_tmp' % dataset_pref)
    os.system('rm %s_tmp/*' % dataset_pref)
```

* 指定模型路径，标签位置，类别总数，测试图片路径

```python
PATH_TO_CKPT = 'model/ncs_mobilenet_ssd_graph'
PATH_TO_LABELS = os.path.join('data', 'mscoco_label_map.pbtxt')
NUM_CLASSES = 81
TEST_IMAGE_PATHS = [os.path.join(img_dir, '%08d.jpg' % i) for i in range(start_index, end_index)]
```

* 发现并尝试打开神经计算棒

```python
def ncs_prepare():
    print("[INFO] finding NCS devices...")
    devices = mvnc.EnumerateDevices()

    if len(devices) == 0:
        print("[INFO] No devices found. Please plug in a NCS")
        quit()

    print("[INFO] found {} devices. device0 will be used. "
          "opening device0...".format(len(devices)))
    device = mvnc.Device(devices[0])
    device.OpenDevice()
    return device
```

* 将NCS模型加载到NCS中

```python
def graph_prepare(PATH_TO_CKPT, device):
    print("[INFO] loading the graph file into RPi memory...")
    with open(PATH_TO_CKPT, mode="rb") as f:
        graph_in_memory = f.read()

    # load the graph into the NCS
    print("[INFO] allocating the graph on the NCS...")
    detection_graph = device.AllocateGraph(graph_in_memory)
    return detection_graph
```

* 准备好标签与类名对应关系

```python
category_index = label_prepare(PATH_TO_LABELS, NUM_CLASSES)
```

* 读取图片，由于Caffe训练图片采用的通道顺序是RGB，而OpenCV模型通道顺序是BGR，需要转换一下

```python
image_np = cv2.imread(image_path)
image_np = cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB)
```

* 使用NCS模型为输入图片推断目标位置

```python
def predict(image, graph):
    image = preprocess_image(image)
    graph.LoadTensor(image, None)
    (output, _) = graph.GetResult()
    num_valid_boxes = output[0]
    predictions = []
    for box_index in range(num_valid_boxes):
        base_index = 7 + box_index * 7

        if (not np.isfinite(output[base_index]) or
                not np.isfinite(output[base_index + 1]) or
                not np.isfinite(output[base_index + 2]) or
                not np.isfinite(output[base_index + 3]) or
                not np.isfinite(output[base_index + 4]) or
                not np.isfinite(output[base_index + 5]) or
                not np.isfinite(output[base_index + 6])):
            continue

        (h, w) = image.shape[:2]
        x1 = max(0, output[base_index + 3])
        y1 = max(0, output[base_index + 4])
        x2 = min(w, output[base_index + 5])
        y2 = min(h, output[base_index + 6])
        pred_class = int(output[base_index + 1]) + 1
        pred_conf = output[base_index + 2]
        pred_boxpts = (y1, x1, y2, x2)

        prediction = (pred_class, pred_conf, pred_boxpts)
        predictions.append(prediction)

    return predictions
```

其中，首先将图片处理为Caffe输入格式，缩放到300x300，减均值，缩放到0-1范围，转浮点数

```python
def preprocess_image(input_image):
    PREPROCESS_DIMS = (300, 300)
    preprocessed = cv2.resize(input_image, PREPROCESS_DIMS)
    preprocessed = preprocessed - 127.5
    preprocessed = preprocessed * 0.007843
    preprocessed = preprocessed.astype(np.float16)
    return preprocessed
```

graph推断得到目标位置，类别，分数

```python
graph.LoadTensor(image, None)
(output, _) = graph.GetResult()
```

其中的output格式为，

```
[
    目标数量，
    class，score，xmin, ymin, xmax, ymax,
    class，score，xmin, ymin, xmax, ymax,
    ...
]
```

* 根据我们感兴趣的类别和分数进行过滤

```python
def predict_filter(predictions, score_thresh):
    num = 0
    boxes = list()
    scores = list()
    classes = list()
    for (i, pred) in enumerate(predictions):
        (cl, score, box) = pred
        if cl == 21 or cl == 45 or cl == 19 or cl == 76 or cl == 546 or cl == 32:
            if score > score_thresh:
                boxes.append(box)
                scores.append(score)
                classes.append(cl)
                num += 1
    return num, boxes, classes, scores
```

* 用OpenCV将当前图片的对象数量写到图片右上角，用pillow（tf库中的实现）将当前图片的对象位置和类别在图中标出

```python
def add_str_on_img(image, total_cnt):
    cv2.putText(image, '%d' % total_cnt, (image.shape[1] - 100, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
```

```python
result = vis_util.visualize_boxes_and_labels_on_image_array(
                image_np,
                np.squeeze(valid_boxes).reshape(num, 4),
                np.squeeze(valid_classes).astype(np.int32).reshape(num, ),
                np.squeeze(valid_scores).reshape(num, ),
                category_index,
                use_normalized_coordinates=True,
                min_score_thresh=score_thresh,
                line_thickness=8)
```

* 保存图片

```python
 cv2.imwrite('%s_tmp/%s' % (dataset_pref, image_path.split('/')[-1]),
                        cv2.cvtColor(result, cv2.COLOR_RGB2BGR))
```

* 释放神经计算棒

```python
def ncs_clean(detection_graph, device):
    detection_graph.DeallocateGraph()
    device.CloseDevice()
```

### 运行

python2 ncs\_detection.py

### 结果

|     框架     | 图片数量/张 |   耗时   |
| :--------: | :----: | :----: |
| TensorFlow |  1800  |  60min |
|     NCS    |  1800  |  10min |
| TensorFlow |    1   |  2sec  |
|     NCS    |    1   | 0.3sec |

性能提升6倍！单张图300毫秒，可以说是毫秒级检测了。在论坛上有霓虹国的同行尝试后，甚至评价其为“超爆速”。

## 扩展

单根NCS一次只能运行一个模型，但是我们可以用多根NCS，多线程做检测，达到更高的速度，具体可以看Reference第二条。

## Reference

* <https://www.pyimagesearch.com/2018/02/19/real-time-object-detection-on-the-raspberry-pi-with-the-movidius-ncs/>
* <https://qiita.com/PINTO/items/b97b3334ed452cb555e2>

看了这么久，还不快去给[我的GitHub](https://github.com/ahangchen/ncs_detection)点star!


# Pytorch实验代码的亿些小细节

## 序

你是否有过这样的经历：炼了一大堆的丹，但过了一周回来看结果，忘记了每个模型对应的配置；改了模型中的一个组件，跑起来一个新的训练，这时候测试旧模型却发现结果跟原来不一样了；把所有的训练测试代码写在一个文件里，加入各种if else，最后一个文件上千行，一个周末没看，回来改一个逻辑要找半天……其实这些情况除了深度学习相关的开发，在别的软件开发中也是很常见的，为了解决这些问题，软件行业的开发者形成了很多套路，比如设计模式，提高代码复用性，或者各种最佳实践，比如谷歌、阿里都有一套Java开发最佳实践，各种框架比如客户端的Android，后端的spring，也有各种最佳实践，让开发者的代码更加简洁，更专注于核心的业务实现。在炼丹领域，从2016年至今，各大训练框架互相竞争，互相学习，学术界基于这些框架产出了很多论文，质量越高的论文，往往代码写得也越有条理，最新的论文代码也渐渐形成了一些固定的范式。笔者在学校和工业界阅读过许多论文的开源代码，也基于别人的代码做过不少的改进，有些代码也在工业界落地，自己的炼丹代码也渐渐形成了一些风格，今天就讲讲自己炼丹代码中的一些能让实验更有条理的小习惯。

> 先上代码，欢迎star：<https://github.com/ahangchen/torch_base>

## 代码结构

```
torch_base
├── checkpoints # 存放模型的地方
├── data        # 定义各种用于训练测试的dataset
├── eval.py     # 测试代码
├── loss.py     # 定义各种花里胡哨的loss
├── metrics.py  # 定义各种约定俗成的评估指标
├── model       # 定义各种实验中的模型
├── options.py  # 定义各种实验参数，以命令行形式传入
├── README.md   # 介绍一下自己的repo
├── scripts     # 各种训练，测试脚本
├── train.py    # 训练代码
└── utils       # 各种工具代码
```

checkpoints比较简单，每次训练的模型各自放在一个目录里，scripts目录可以放每次训练或测试用的命令脚本，README.md往往是这个repo的门面，可以放一些介绍性的内容；其他都是代码目录，下面会逐一讲解。

## options

首先要介绍的是options.py这个文件，因为这里定义了各种实验参数，其他模块多多少少都会与它有关，受它控制；通常我们需要把各种参数通过某种方式传给程序，比如命令行参数，或者yaml配置文件，我比较习惯用命令行参数，配合pycharm的configuration使用，或者写在scripts目录的脚本里边，都很方便清晰。命令行传参用到了`argparse`这个lib，这里lib的详细介绍可以看[官网教程](https://docs.python.org/3/library/argparse.html)，这里只挑重点来讲一下：

```python
def parse_common_args(parser):
    parser.add_argument('--model_type', type=str, default='base_model', help='used in model_entry.py')
    parser.add_argument('--data_type', type=str, default='base_dataset', help='used in data_entry.py')
    parser.add_argument('--save_prefix', type=str, default='pref', help='some comment for model or test result dir')
    parser.add_argument('--load_model_path', type=str, default='checkpoints/base_model_pref/0.pth',
                        help='model path for pretrain or test')
    parser.add_argument('--load_not_strict', action='store_true', help='allow to load only common state dicts')
    parser.add_argument('--val_list', type=str, default='/data/dataset1/list/base/val.txt',
                        help='val list in train, test list path in test')
    parser.add_argument('--gpus', nargs='+', type=int)
    return parser

def parse_train_args(parser):
    parser = parse_common_args(parser)
    ...
    return parser

def parse_test_args(parser):
    parser = parse_common_args(parser)
    ...
    return parser
```

我会在外面初始化一个parser，先用parse\_common\_args添加训练测试共用的一些参数，在parse\_train\_args和parse\_test\_args中调用这个公共的函数，这样可以避免有些参数在训练时写了，测试时忘了写，一跑就报错。parse\_train\_args解析训练相关的参数，parse\_test\_args解析测试相关的参数；具体参数和用途如下：

* parse\_common\_args
  * `model_type`: 模型的名字，配合model目录和model\_entry.py使用；
  * `data_type`：数据集的名字，配合data目录和data\_entry.py使用；
  * `save_prefix`：训练时：实验的名字，可以备注自己改了那些重要组件，具体的参数，会用于创建保存模型的目录；测试时：测试的名字，可以备注测试时做了哪些配置，会用于创建保存测试结果的目录；
  * `load_model_path`：模型加载路径，训练时，作为预训练模型路径，测试时，作为待测模型路径，有的人喜欢传入一个模型名字，再传入一个epoch，但其实没啥必要，就算要循环测多个目录，我们也可以写shell生成对应的load\_model\_path，而且通常只需要测最后一个epoch的模型；
  * `load_not_strict`：我写了一个`load_match_dict`函数（utils/torch\_utils.py），允许加载的模型和当前模型的参数不完全匹配，可多可少，如果打开这个选项，就会调用此函数，这样我们就可以修改模型的某个组件，然后用之前的模型来做预训练啦！如果关闭，就会用torch原本的加载逻辑，要求比较严格的参数匹配；
  * `val_list`: 训练时可以传入验证集list，测试时可以传入测试集list；
  * `gpus`：可以配置训练或测试时使用的显卡编号，在多卡训练时需要用到，测试时也可以指定显卡编号，绕开其他正在用的显卡，当然你也可以在命令行里export CUDA\_VISIBLE\_DEVICES这个环境变量来控制
* parse\_train\_args
  * `lr`，`momentum`, `beta`, `weight-decay`: optmizer相关参数，在train.py中初始化optimizer
  * `model_dir`：模型的存储目录，留空，不用传入，会在`get_train_model_dir`函数中确定这个字段的值，创建对应的目录，填充到args中，方便其他模块获得模型路径
  * `train_list`：训练集list路径
  * `batch_size`：训练时的batch size，有人可能会问，为啥测试时不用设置batch size？主要是出于测试时的可视化需求，往往测试需要一张一张forward，所以我习惯将测试batch size为1
  * `epochs`：模型训练epoch数
  * parse\_test\_args
    * `save_viz`：控制是否保存可视化结果的开关
    * `result_dir`：可视化结果和测试结果的存储目录，留空，不用传入，会在`get_test_result_dir`中自动生成，自动创建目录，这个目录通常位于模型路径下，形如checkpoints/model\_name/checkpoint\_num/val\_info\_save\_prefix

使用时，调用`prepare_train_args`，就会创建一个包含所有公共参数和训练参数的parser，然后创建一个模型目录，并调用`save_args`函数保存所有参数，返回对应的args。保存参数这一步十分重要，能够避免模型训练完成之后，脚本或命令找不到，忘记自己训练的模型配置这种尴尬局面。

测试时也类似，调用`prepare_test_args`，创建parser，创建目录，保存参数，并返回对应的args。

## data

接下来是data package，在这里，可以为每种数据集定义一个dataset，最好是每个dataset各自形成一个文件，比如[list\_dataset.py](https://github.com/ahangchen/torch_base/blob/main/data/list_dataset.py), [mem\_list\_dataset.py](https://github.com/ahangchen/torch_base/blob/main/data/mem_list_dataset.py)，如果多个dataset都写到一个文件里，随着实验进行，各种修修补补下来，代码就会很长，很难查阅。

这里我们还有一个[data\_entry.py](https://github.com/ahangchen/torch_base/blob/main/data/data_entry.py)，可以根据命令行参数，以字典的形式，快捷地选择要构造的dataset，如果你有更多的dataset，可以继续扩展这个字典，字典访问是O(1)的，也可以避免一堆if-else的判断。有了dataset，再用pytorch的dataloader接口包一下，可以支持shuffle，多线程加载数据，非常方便。

通常我们还会在data package里放一个augment.py，可以把数据扩增操作都放进去，因为往往多个dataset都需要调用相同的augmentor，所以最好独立出来，在dataset文件中分别调用。

## model

这里放的就是各种花里胡哨的模型啦，也是炼丹工作最主要的部分。建议每个模型创建一个package，比如[base](https://github.com/ahangchen/torch_base/tree/main/model/base)，[better](https://github.com/ahangchen/torch_base/tree/main/model/better)， [best](https://github.com/ahangchen/torch_base/tree/main/model/best)，甚至[sota](https://github.com/ahangchen/torch_base/tree/main/model/sota)，现代的神经网络结构有一些常用的小组件，比如conv-bn-relu这样的结构，我习惯把它们都放在一个单独的文件[submodules.py](https://github.com/ahangchen/torch_base/blob/main/model/submodules.py)中，可以在各种任务中复用。

与data\_entry类似，我们有一个[model\_entry.py](https://github.com/ahangchen/torch_base/blob/main/model/model_entry.py)，在`select_model`函数中也是通过字典实现参数名和模型的对应，在`equip_multi_gpu`函数中，可以方便的实现单机多卡，至于多机多卡，我自己用得不多，因为我大多是训练面向无人机上的模型，参数量和计算量要求很小，我们的单机服务器足够train绝大多数模型了，如果是为了更大的batch size加速训练，不如在另一台机器上多跑一组别的实验，总体效率更高。如果大家想看这方面教程，可以留言，我可以补一下对应的代码。

## utils

存放各种可复用的util函数或者类，比如一些通用的可视化代码放到[viz.py](https://github.com/ahangchen/torch_base/blob/main/utils/viz.py)，一些pytorch魔改函数放到[torch\_utils.py](https://github.com/ahangchen/torch_base/blob/main/utils/torch_utils.py)，还有基于tensorboard的存图存曲线的[logger.py](https://github.com/ahangchen/torch_base/blob/main/utils/logger.py)，这里主要介绍一下这个日志组件：

### Recoder

一个数据统计工具，在循环里record每次迭代的数据（比如各种评价指标`metrics`），在每个epoch训练完成后，调用summary，得到之前统计的指标各自的均值。这个工具在训练时嵌入到Logger中使用，在测试时由于不需要调用tensorboard，所以直接被eval.py调用。

### Logger

将tensorboard的SummaryWritter包了一层，包含一个recorder，还有一个SummaryWritter；在训练或验证的每个step以name-value的形式record一下对应的曲线数据，name最好用`train/xxx`，`val/xxx`这种形式，这样训练和测试的曲线会显示在两个图中，在每个epoch的最后一个step在每次训练或验证的epoch循环结束时，调用一次save\_curves保存曲线，调用一次save\_checkpoint保存模型参数；这些操作都在下面的train.py中体现。

## train.py

终于来到核心的训练代码环节，这里我整了一个trainer，将训练中固定的操作封装成一些函数，需要按实际情况修改的操作封装成另外的函数，这样有新任务来了，只需要修改这些函数就行。现在依次介绍这些函数：

* `__init__`：构造函数，初始化了命令行参数`args`，日志工具（[Logger](https://github.com/ahangchen/torch_base/blob/main/utils/logger.py)对象）`logger`，训练验证的两个dataloader，参数优化器`optimizer`，以及模型本身`model`，这里我们有三种方式初始化模型：1. 根据模型的构造函数初始化模型参数，2. 使用torch.load加载模型参数，这种方式要求模型参数和我们的模型定义完全匹配，3. 使用[load\_match\_dict](https://github.com/ahangchen/torch_base/blob/main/utils/torch_utils.py#L4)加载模型参数，可以找到模型参数和模型定义中，参数量和名字相同的部分进行初始化，适合只改了部分网络结构的模型初始化，作为一种局部pretrain。
* `train`：训练入口，迭代epochs次，每次调用train\_per\_epoch, val\_per\_epoch执行训练和测试，再调用logger存储曲线和图像。
* `train_per_epoch`：训练核心代码，将模型切换到训练模式，遍历整个train\_loader，调用step进行数据拆包，不同loader返回的数据不同，拆包方式也有差异，还需要用Variable对数据再打包一下，这些操作都独立到step函数里，方便单独修改；再执行模型forward，获取结果，调用compute\_metrics计算metrics（训练中也需要观察各种指标，这些指标的计算推荐放在[metrics.py](https://github.com/ahangchen/torch_base/blob/main/metrics.py)），计算loss（各种花里胡哨的loss请放到[loss.py](https://github.com/ahangchen/torch_base/blob/main/loss.py)），反向传播，在每次迭代中都调用logger的record函数，记录metrics，在最后一个step，调用gen\_imgs\_to\_write，将torch的数据转成图像可视化，各种可视化可以写在[viz.py](https://github.com/ahangchen/torch_base/blob/main/utils/viz.py)再调用图像的存储（曲线的存储可以放到外面，每个epoch存一次，但图像不行，除非把图传出去，比较蛋疼）。最后根据print\_freq，每隔一段时间打印日志方便观察。
* `val_per_epoch`：与训练类似，差别就是模型在eval模式下，不用计算loss和反向传播；

## eval.py

最后介绍的是测试代码，我把测试的过程包成了一个Evaluator，和trainer也比较类似：

* `__init__`：构造函数，初始化命令行参数`args`，加载模型`model`并切换到eval模式，初始化测试集的data\_loader，设置一个recorder用于统计各种评估指标；
* `eval`：测试核心代码，遍历整个测试集，执行forward，得到输入，输出，真值，调用compute\_metrics，调用recorder做记录，根据viz\_freq，决定这个step是否调用`viz_per_epoch`可视化并保存结果（与训练不同，往往测试集可视化的内容是要向领导/导师/甲方汇报的，不能存到tensorboard里），循环结束时，调用recorder得到所有的评估指标，并将所有metrics写到`result.txt`里，避免测试窗口一关就找不到测试结果了。

## 总结

至此，[torch\_base](https://github.com/ahangchen/torch_base)这个工程就基本介绍完了，主要还是实践中遇到的各种大坑小坑，逼着自己给工程加上了亿点点小细节，如果基于这个工程去开发新的任务，可以省去一些的脚手架开发工作，专注于model&\&data&\&metric&\&loss&\&viz相关的一些内容，让炼丹bring up更快，效率更高，对我来说还是挺有用的，不知道有没有给你一些启发？如果有什么建议也欢迎在[issue](https://github.com/ahangchen/torch_base/issues)或者[知乎评论区](https://zhuanlan.zhihu.com/p/409662511)告诉我，感谢你的阅读\~


# 工具


# MXNet踩坑手记


# PyTorch踩坑手记

* 多GPU模式下，不能和其他进程共享同一个GPU，否则会出现ConnectionError，应该用环境变量将自己的进程使用的GPU和其他进程使用的GPU分开
* 循环中定义的变量，如果在循环结束后不需要使用，需要del，避免一直占用显存

## torch.unfold

给定一个NCHW的tensor，构造一个k1\*k2的滑动窗口，按照一定的padding, dilation, stride在这个tensor上滑动L次，

$$L = \prod\_d \left\lfloor\frac{\text{spatial\_size}\[d] + 2 \times \text{padding}\[d] % - \text{dilation}\[d] \times (\text{kernel\_size}\[d] - 1) - 1}{\text{stride}\[d]} + 1\right\rfloor$$

滑动时，将窗口内$$C*H*W$$个元素flatten，就能得到一个$$N\* (C*H*W) \* L$$的tensor；

unfold有什么用呢？可以看到它跟卷积操作很像，如果我们给窗口内flatten后的元素乘上一些权重再求和，再把L个元素变成二维的，那它就跟卷积一样了；

与卷积不同的地方在于，unfold之后，我们可以乘上任意的权重， 比如根据feature预测出一个权重出来，而卷积对于每个窗口里的元素，乘的权重是一样的；

因此unfold可以实现动态权重的卷积。


# PyTorch模型剪枝

pytorch官方的剪枝工具分为非结构化和结构化剪枝两种，非结构化剪枝会随机地把一些权重参数变为0，结构化剪枝则将某个维度某些通道随机变成0，但这套工具不会真正输出剪枝后的模型，只是将模型变稀疏了，只有用某些特殊前向库，才能加快模型运行速度。最近找到一个库，能根据一定策略找到权重中作用较小的部分，用index表示，并保留对应的模型。

## 原理

首先构造输入，前向运行一次模型，得到模型对应的计算图

```python
DG = tp.DependencyGraph()
DG.build_dependency(model, example_inputs=torch.randn(1,3,224,224))
```

对于计算图中的各种带权重的层，根据指定策略（目前支持ln,l1,l2等）比较权重中各个数值，找到第k小的数对应的index，记为将要删除的部分，

```python
strategy = tp.strategy.L1Strategy() 
# 3. get a pruning plan from the dependency graph.
pruning_idxs = strategy(model.conv1.weight, amount=0.4) # or manually selected pruning_idxs=[2, 6, 9, ...]
```

得到index后，对依赖该层的其他层，递归使用对应的prune函数进行剪枝，得到每一层的剪枝计划。

```python
pruning_plan = DG.get_pruning_plan( model.conv1, tp.prune_conv, idxs=pruning_idxs )
```

在剪枝计划执行时，根据index改变model中对应层的定义，使得model中的channel数变少。

```python
# plune plane exec source code
def exec(self, dry_run=False):
    num_pruned = 0
    for dep, idxs in self._plans: # idxs were computed by specified strategy
        _, n = dep(idxs, dry_run=dry_run)
        num_pruned += n
    return num_pruned
```

以卷积层为例，执行剪枝计划时，根据strategy提供的idx，对weight和bias进行修剪：

```python
class ConvPruning(BasePruningFunction):
    @staticmethod
    def prune_params(layer: nn.Module, idxs: Sequence[int]) -> nn.Module: 
        keep_idxs = list(set(range(layer.out_channels)) - set(idxs))
        layer.out_channels = layer.out_channels-len(idxs)
        if not layer.transposed:
            layer.weight = torch.nn.Parameter(layer.weight.data.clone()[keep_idxs])
        else:
            layer.weight = torch.nn.Parameter(layer.weight.data.clone()[:, keep_idxs])
        if layer.bias is not None:
            layer.bias = torch.nn.Parameter(layer.bias.data.clone()[keep_idxs])
        return layer
    
    @staticmethod
    def calc_nparams_to_prune(layer: nn.Module, idxs: Sequence[int]) -> int: 
        nparams_to_prune = len(idxs) * reduce(mul, layer.weight.shape[1:]) + (len(idxs) if layer.bias is not None else 0)
        return nparams_to_prune
```

如此，剪枝后model.forward时，运行的卷积层就是剪枝过的版本啦。

## 样例

* [CIFAR10上ResNet18剪枝](https://github.com/VainF/Torch-Pruning/blob/master/examples/prune_resnet18_cifar10.py)
* 注意事项：
  * 剪枝是对遍历网络的每一个单独的层进行剪枝
  * 剪枝时虽然有一定的策略，但不能保证每个剪掉这些层之后损失就是最小的
  * 每层剪枝的比例可以不同，可以考虑人工将网络划分为几个部分，每个部分可以设置不同的剪枝比例，但具体应该设置多少，可以从剪枝后模型的执行结果进行评估，可以考虑写一个遍历算法，或者写个启发式搜索来找到最佳比例；
  * 剪枝后应当在新的模型上继续fine tune一定epoch，以得到最适合此网络结构的权重


# Keras踩坑手记

* 方法一：<https://github.com/keras-team/keras/issues/5920> ，具体看zc813的回答，这是目前比较可行的做法，
* 方法二：使用社区实现的keras分支：<https://github.com/nagash91/keras/tree/keras-lrmult-implementation>


# mscnn

* [Source Code](https://github.com/zhaoweicai/mscnn)
* 别人写的[教程](https://gist.github.com/arundasan91/b432cb011d1c45b65222d0fac5f9232c)

## Install

* 从github下载mscnn工程
* 如果git clone出现

  ```
    正克隆到 'mscnn'...
    remote: Counting objects: 1120, done.
    error: RPC failed; curl 56 GnuTLS recv error (-110): The TLS connection was non-properly terminated.
    fatal: The remote end hung up unexpectedly
    fatal: 过早的文件结束符（EOF）
    fatal: index-pack failed
  ```
* 用这种方法避开：

  ```
    mkdir mscnn
    cd mscnn
    git init
    git remote add origin https://github.com/zhaoweicai/mscnn.git
    git pull
  ```
* 我只在ubuntu上安装，所以只讲ubuntu上的依赖:
  * [CUDA和CUDNN](https://www.youtube.com/watch?v=cVWVRA8XXxs)
  * 其他依赖

    ```
    sudo apt-get install libprotobuf-dev libleveldb-dev libsnappy-dev libopencv-dev libhdf5-serial-dev protobuf-compiler
    sudo apt-get install --no-install-recommends libboost-all-dev
    sudo apt-get install libatlas-base-dev
    sudo apt-get install libgflags-dev libgoogle-glog-dev liblmdb-dev
    ```
  * Caffe在debian系列系统上对hdf5的支持导致的[一个坑>>](https://github.com/BVLC/caffe/issues/2347#issuecomment-118508564)

## 编译

```
make all
make pycaffe
make matcaffe
make test
make runtest
```

## 测试（Python版）

* [源代码](https://github.com/GBJim/mscnn/blob/master/examples/caltech/run_mscnn_detection.py)
* 前面是用apt安装的opencv和protobuf，没有安装Python包，所以直接运行会报cv2和protobuf找不到
* 安装opencv python包：

```
sudo apt-get install python-opencv
```

* 这样会安装到系统默认的python解释器中，即`/usr/lib/python2.7/dist-packages`，我们把这里面跟opencv有关的复制到前面编译caffe指定的python解释器目录中，比如`~/anaconda2/lib/python2.7/site-packages`，同时注意改cv2的名字

```
sudo ln -s cv2.x86_64-linux-gnu.so cv2.so
```

搞定opencv的依赖

* 安装protobuf python包: `pip install protobuf`（注意pip要和caffe对应的python解释器绑定）
* 由于前面的那份python代码用了nms来做GPU调用，这个东西是来自py-faster-rcnn的，也是caffe的一个变种，复制[这个目录](https://github.com/rbgirshick/py-faster-rcnn/tree/master/lib/)，然后make，按上面的复制opencv的方法把nms目录复制到caffe对应的python解释器就好了


# Matlab

* [Matlab Remote IPC实现自动化数据处理](/ml/kit/matlab/ripc_auto)


# Matlab Remote IPC自动化数据处理

> 转载请注明作者[梦里茶](https://github.com/ahangchen)

## 需求

在研究中遇到这样一种需求，

* 在Matlab端做GPU运算；
* 在Python端做数据清洗和数据分析；
* 两端分属两个服务器；
* M端需要等待P端完成数据清洗才能开始做训练和预测
* P端需要等待M端完成训练和预测才能做数据分析

## 问题

* 数据很多，不适合将两端合并到一个服务器上
* 存在增量训练，对同一份数据，可能要结合多份额外数据进行N次传输
* 由于采用10折交叉验证，数据划分为10份，总共要进行10×N次传输
* M端和P端都需要各自运行一段时间才能出结果，需要等待，说快不快，说慢不慢

> 上面的几个点使得训练的过程非常繁琐耗时

## 探索

作为不耐心而且懒的程序员，超过15秒的事情就想要自动化，于是开始想能不能把上面这个过程自动化

### 方案一 用一种语言重写另一方的代码

* Python端逻辑复杂，与数据关联密切，重构为Matlab代码工程量大（而且我还是比较熟悉Python）
* Matlab端是某篇论文的代码，使用了MatConvNet这个库做CNN，用Python实现工程量也很大，而且因为是别人的代码，还可能有很多未知的坑

### 方案二 使用[第三方库](http://mlabwrap.sourceforge.net/)作为Lib相互调用

* 由于Matlab端涉及GPU运算，配置比较复杂
* 作为库相互调用使得两个模块的耦合增加，不符合解耦原则
* 需要编写接口代码

### 方案三 Matlab和Python进行进程间通信

* 进程间通信要求两边各有一个可执行程序，但是Matlab打包麻烦（需要下另一个收费的打包工具）具体看[这里](https://www.youtube.com/watch?v=6b_NmBEW9ak)，在GUI界面中操作相对方便，并且打包后不好调试
* 远程进程间通信有许多方案可以选择，例如socket，这个工作量比上面的方案小，但也需要额外编写一部分功能代码

### 方案四 (Final)使用Matlab进行执行shell命令进行scp，以文件的方式进行数据传输

* 改动的代码最少
* 依旧是两个模块，依旧低耦合
* 通信性能没有库调用或者Socket那么好，但程序主要性能瓶颈不在数据传输，而在于训练和预测，所以没关系
* 执行ssh的时候可以同时执行另一个服务器上的命令，从而调用Python端脚本进行Python端工作

下面具体讲方案四的实现

## 实现

### Matlab执行shell命令

```
setenv('d_name', dataset_name);
setenv('std_idx', num2str(i));
% transport raw image predict data
!env LD_LIBRARY_PATH='' scp ${d_name}_train.log cwh@192.168.231.171:/home/cwh/coding/Project/data/${std_idx}-train/renew_pid.log
!env LD_LIBRARY_PATH='' ssh cwh@192.168.231.171 "cd /home/cwh/coding/Project; python data_analysis.py data/${std_idx}-train"
```

其中

* 通过setenv来设置环境变量，从而动态决定要执行的脚本的内容
* 在matlab代码中，使用`!`开头，代表这是一行shell命令
* 需要把LD\_LIBRARY\_PATH这个环境变量置为空，防止使用matlab自带的一些lib，导致ssh等命令执行失败

### SSH

* Matlab是有一个[库](https://cn.mathworks.com/matlabcentral/fileexchange/27999-ssh-from-matlab--updated--+-sftp-scp)可以做ssh和scp的
* 但是，不想把密码写在代码里，并且懒得去用第三方的东西
* 于是想直接用shell调用
* shell调用有一个老大难的问题，就是需要输密码
* 受Hadoop分布式配置的启发，发现可以做ssh免密码配置，避开输密码的问题
* 免密码具体参考这个[教程](https://my.oschina.net/aiguozhe/blog/33994)
* ssh配置免密之后，scp也是免密的，另外还有一个好处，ssh可以通过`""`来嵌入ssh之后要执行的代码，从而做Python调用

### Python

* 如果之前Python端是用IDE开发的，转为shell调用就要注意两个问题
  * 路径，cd到正确的目录，Python端的文件操作和lib import的相对路径是相对于要执行的py文件
  * 库

    ```
    # 加载对应的库
    env LD_LIBRARY_PATH='/your/lib/path/sth.so'
    ```
  * 命令行参数，参考这个[教程](http://lingxiankong.github.io/blog/2014/01/14/command-line-parser/)

### 同步控制

* 由于matlab调用shell命令时会陷入等待，会等待命令完成再执行下一步，不像socket发完消息后不知道什么时候另一边运行结束，所以不需要自己实现排队等待同步互斥的东西
* 如果需要在等待的时候做些并发，可以考虑[matlab 并行工具箱](http://blog.sina.com.cn/s/blog_45eac6860100lzlk.html)，结合Python端的并发几乎就是一个分布式框架了，有空再深入去搞一搞

## SUMMARY

* matlab调shell
* shell中ssh做远程进程调用
* scp做数据传输
* 效果：尽可能轻量地解决自动化的问题


# Papers

* [Person Re-id](/ml/papers/reid)
  * [Person Re-identification Overview](/ml/papers/reid/reid)
  * [CVPR2016 Re-id](/ml/papers/reid/cvpr-reid)
  * [Camera topology and Person Re-id](/ml/papers/reid/reid-topo)
  * [Deep transfer learning person re-id](/ml/papers/reid/deep_transfer_learning_person_reid)
  * [CVPR2018:TFusion](/ml/papers/reid/tfusion)
  * [ECCV2018:TAUDL](/ml/papers/reid/eccv2018_taudl)
  * [CVPR2018:Graph+reid](/ml/papers/reid/cuhk_sentimes)
* [Object Detection](/ml/papers/detection)
  * [RCNN](/ml/papers/detection/rcnn)
  * [SPP-net](/ml/papers/detection/sppnet)
  * [Fast RCNN](/ml/papers/detection/fast_rcnn)
  * [Faster RCNN](/ml/papers/detection/faster_rcnn)
  * [YOLO](/ml/papers/detection/yolo)
  * [SSD](/ml/papers/detection/ssd)
* Object Counting
  * [ECCV 2016, Hydra CCNN](/ml/papers/overview/eccv2016_hydra_ccnn)
* 大杂烩
  * [CNCC 2017琐记](/ml/papers/overview/cncc2017)
  * [CNCC2017中的深度学习与跨媒体智能](/ml/papers/overview/cncc_cv)
  * [MLA2016](/ml/papers/overview/mla_2016)


# Classification

## Self-training with Noisy Student improves ImageNet classification

> CVPR2020

### Method

* 在有标签数据上训练Teacher,在无标签数据上预测伪标签
* 用有标签数据和伪标签数据从头训练一个Student模型，Student模型
* 把Student当成Teacher，迭代，效果最好的是迭代三次

特别的地方在于：

* model noise: Student比Teacher大，多了Dropout和stochastic depth(训练时，重复的block中，某些会被设置成identity)
* data noise:训练数据加Data augumentation（）
* 一些trick：
  * Teacher网络置信度低的，不会用于训练；
  * ImageNet每个类的图像数量差不多，因此带伪标签的unlabel data也在类别上做了平衡，数量多的类只保存一部分，数量少的类多复制几份

### 效果

感觉其实这些操作平平无奇，效果比有监督的EfficientNet-L2（480M，85.5%）提升了一点：（480M，88.7%）

## Meta Pseudo Labels

### Motivation

使用伪标签无监督学习的方法，因为伪标签可能是错的，所以Student不能超越Teacher，如果能让Teacher在过程中也得到训练，输出更好的标签，Student就有希望输出更好的结果。

### Method

* 在有标签数据上先训练一个Teacher，在无标签数据上预测伪标签
* 用伪标签数据训练Student
* Student在有标签数据上测试，得到Loss
* Teacher根据Student在有标签数据上的Loss，对自身权重进行修正（关键就在于如何修正）

### Detail

假如用伪标签方案训练Student，那么训练目标可以这样表示： $$\theta\_{S}^PL = argmin\_{\theta\_S} L\_u(\theta\_T, \theta\_S)$$

其中，$$L\_u(\theta\_T, \theta\_S) = E\_{x\_u}\[CE(T(x\_u; \theta\_T), S(x\_u; \theta\_S))]$$

如果根据Student在有标签数据集上的表现，优化Teacher，那这个目标可以表示为：$$min\_{\theta\_T} L\_l (\theta\_{S}^{PL}(\theta\_T))$$

其中，$$\theta\_{S}^{PL}(\theta\_T)$$就是伪标签方案下，在Teacher网络的参数$$\theta\_T$$下，优化出的最优Student参数，即$$argmin\_{\theta\_S} L\_u(\theta\_T, \theta\_S)$$

所以问题就在于如何解这个优化问题：$$min\_{\theta\_T} L\_l (\theta\_{S}^{PL}(\theta\_T))$$，

首先$$\theta\_{S}^{PL}(\theta\_T)$$可以通过梯度下降法迭代得到，但它是一个多步的优化过程才能得到的结果，我们用单步优化的结果来近似：$$\theta\_{S}^{PL}(\theta\_T)=\theta\_S - \eta\_S \* \triangledown\_{\theta\_S}L\_u(\theta\_T, \theta\_S)$$，那么优化目标就变成了：$$min\_{\theta\_T} L\_l (\theta\_S - \eta\_S \* \triangledown\_{\theta\_S}L\_u(\theta\_T, \theta\_S))$$;

那么每在伪标签数据上更新一次Student，就可以在有标签数据上算出Teacher的权重更新量，对Teacher进行更新：$$\theta\_T=\theta\_T-\eta\_T\triangledown\_{\theta\_T} L\_l (\theta\_S - \eta\_S \* \triangledown\_{\theta\_S}L\_u(\theta\_T, \theta\_S))$$，其中两个梯度都是对CE求导，因此这个更新量可以很容易地求出来；

## 辅助Loss

单独用上面的方法已经可以得到不错的结果了，但如果在训练的时候，配合其他Loss训练，效果更佳；文中提及了unsupervised domain adaption的一些方法，可以在论文附录查询到。

### 效果

在ImageNet上首次干到了90.2%的top1 ACC

## EfficientNet: Rethinking model scaling for convolutional neural networks

### Motivation

这篇论文我更愿意称其为实验报告，研究了如何扩展网络的input resolution, width(channel数)， depth(layer数)，才能得到性价比最高的网络

### Observation

这篇文章基于两个observation来调整网络规模：

* 增加输入/宽度/深度，都能提高模型效果，但存在边际效应，加的太多提升不明显；
* 因此，与其把多出来的算力堆在单个维度上，不如同时扩张三个维度；

### Method

具体的扩张策略如下：

* 假设depth变成$$\alpha$$倍，那么FLOPS会变为$$\alpha$$倍，假设width(卷积的输入和输出通道数)变为$$\beta$$倍，那么FLOPS会变为$$\beta^2$$倍，假设输入宽度变为$$\gamma$$，那么FLOPS变为$$\gamma^2$$；
* 我们希望调整参数后，FLOPS变为$$2^{\phi}$$，那么就要求$$\alpha \* \beta^2 \* \gamma^2=2$$，因此具体的调整策略就变成了：
* depth: $$d=\alpha^\phi$$
* width: $$w=\beta^\phi$$
* resolution: $$r = \gamma^\phi$$
* st: $$\alpha \* \beta^2 \* \gamma^2=2$$, $$\alpha>1, \beta>1, \gamma>1$$

### Network

在各种基础网络上试了这个策略，发现Mnas上改出来的最好，网络结构大概长这样，但是size会有所不同，比如EfficientNet-B0就是

![](/files/-MaOrvpAmM72OC7LM-MG)

### Result

![](/files/-MaOrvpBf1C-08SxQ_xx)

## MLP-Mixer: An all-MLP Architecture for Vision

使用MLP代替卷积，保留skip connection和normalization，在ImageNet上最高达到87.94%

### Method

* 将图像分成S个PxP大小的patch，每个patch（n c p p） reshape成(n c p\*p)的tensor
* 用一个矩阵乘法先将每个patch处理一下reproject到n *C,所有 patch合起来就是n* S \* C的tensor X
* 接下来开始mlp：
  * $$U=X+W\_2\sigma(W\_1 Layer Norm(X))$$
  * 对X做layer norm
  * 先对每个patch做乘法，$$WX$$，变成$$n*S1*C$$
  * 做一个非线性变换$$\sigma$$，这里用的是GELU
  * 再做一个乘法，从$$n*S1*C$$变成$$n*S*C$$
  * 再做一个残差加上X
  * 这里不同channel之间的权重是共享的，用来减少参数数量
  * 由于中间token数量变了，patch数目没变，起一个花里胡哨的名字，把patch叫做token，乘法叫做token-mixing，也就是将同个通道不同token先混合一下
  * 再做一个MLP：$$Y=U+W\_4\sigma(W\_3 Layer Norm(U))$$
  * 也是一样的操作，不同之处在于中间是channel数量变了，后面又变回来，所以叫channel-mixing
* 每个mlp-mixer block都做四次乘法，把$$n*S*C$$的tensor变成$$n*S*C$$
* 网络最后接一个Global Average Pooling做分类
* 训练的时候也是一样预训练+finetune

### MLP-Mixer的卷积本质

* 一开始分不同patch，其实就是一个$$P*P$$，stride为$$P*P$$的卷积
* 不同位置相同通道的混合，可以用深度可分离卷积代替

  ·- 同个位置不同通道的混合，可以用一个1x1的卷积代替


# Re-identification

* Person Re-identification
  * [Person Re-identification](/ml/papers/reid/reid)
  * [CVPR2016 Re-id](/ml/papers/reid/cvpr-reid)
  * [Camera topology and Person Re-id](/ml/papers/reid/reid-topo)
  * [Deep transfer learning person re-id](/ml/papers/reid/deep_transfer_learning_person_reid)


# CVPR2018:TFusion完全解读

## Citation

Please cite this paper in your publications if it helps your research:

```
@article{
  title={Unsupervised Cross-dataset Person Re-identification by Transfer Learning of Spatial-Temporal Patterns},
  author={Jianming, Lv and Weihang, Chen and Qing, Li and Can, Yang},
  journal={CVPR},
  year={2018}
}
```

论文可以在[arxiv下载](https://arxiv.org/abs/1803.07293)，老板一作，本人二作，也是我们实验室第一篇CCF A类论文，这个方法我们称为TFusion。

> 转载请注明作者[梦里茶](https://github.com/ahangchen)

代码：<https://github.com/ahangchen/TFusion>

![](/files/-MkuKu7Id8wvp5dgOCAG)

* 解决的目标是跨数据集的Person Reid
* 属于无监督学习
* 方法是多模态数据融合 + 迁移学习
* 实验效果上，超越了所有无监督Person reid方法，逼近有监督方法，在部分数据集上甚至超越有监督方法

本文为你解读CVPR2018 TFusion

## Task

行人重识别(Person Re-identification)是一个图像检索问题，给定一组图片集(probe)，对于probe中的每张图片，从候选图片集（gallery）中找到最可能属于同一个行人的图片。

![](/files/-MkuKu7J9A7J5go1TiMS) 行人重识别数据集是由一系列监控摄像头拍摄得到，并用检测算法将行人抠出，做行人的匹配。在这些数据集中，人脸是十分模糊的，无法作为匹配特征，而且由于多个摄像头拍摄视角不同，同个人可能被拍到正面，侧面，背面，具有不同的视觉特征，因此是一个比较难的图像匹配问题。常用数据集有很多，可以在[这个网站](http://robustsystems.coe.neu.edu/sites/robustsystems.coe.neu.edu/files/systems/projectpages/reiddataset.html)查到。

## Related Work

行人重识别问题有以下几种常见的解决方案：

### 基于视觉的行人重识别

这类方法通常提取行人图像特征，对特征进行距离度量，从而判断是否是同一个人。

#### 有监督学习

![](/files/-MkuKu7K5q5oqs70LG9v) 这类方法通常需要提供行人图片和行人id标签（person1,person2等），训练模型，提取图像特征，根据两张图特征的距离大小（可以用余弦距离，欧氏距离之类的计算），为probe中的每张图和gallery中的每张图计算其相似度，根据相似度将gallery中的图片排序，排序越高越可能为同一个人。

这方面的论文代表有TOMM2017: A Discriminatively Learned CNN Embedding for Person Re-identification，我们采用的基础图像分类器就是基于这篇论文用Keras实现的，后面细讲。

#### 无监督学习

在CVPR2018之前，Person Reid领域正式发表的无监督工作只有CVPR2016的UMDL：Unsupervised Cross-Dataset Transfer Learning for Person Re-identification，基于字典学习方法，在多个源数据集上学习跨数据集不变性字典，迁移到目标数据集上。然而准确率依然很低。

### 结合摄像头拓扑的行人重识别

行人图片是摄像头拍到的，摄像头之间有一定的距离，行人的移动有一定的速度限制，因此行人在摄像头间的移动时间就会呈现出一定规律，比如，AB摄像头间有10米，人行走速度2m/s，如果AB摄像头在1s内捕捉到了两张图片，则这两张图片不可能是同一个人的，因此我们可以利用摄像头拓扑约束来提升行人重识别的准确率。

然而，这类方法往往有以下缺陷：

* 有些方法需要预先知道摄像头拓扑（AB摄像头之间的距离）
* 有些方法可以根据拍摄到的图像数据推断出摄像头拓扑，但是需要图像有标注（是否是同一个人）
* 即使推断出摄像头拓扑，与图像的融合结果依然很差

### 迁移学习

迁移学习现在是深度学习领域很常用的一个套路了，在源数据集上预训练，在目标数据集上微调，从而使得源数据集上的模型能够适应目标场景。这方面的论文代表有前面讲的UMDL，和[Deep transfer learning person re-identification](http://cweihang.cn/ml/reid/deep_transfer_learning_person_reid.html)，然而，目前的迁移学习大多需要标签，而无监督迁移学习效果又很差，仍然有很大提升空间。

更多关于Person Reid的内容可以看一下我在博客写的几篇[调研](http://cweihang.cn/ml/reid/)

## Motivation

* 现有的行人重识别数据集中是否包含时空信息？包含的话是否存在时空规律？
* 缺乏两个时空点是否属于同一行人这种标签时，如何挖掘时空信息，构建时空模型？
* 如何融合两个弱分类器？有监督的融合有boosting算法可以用，无监督呢？
* 在缺乏标签的条件下，如何进行有效的迁移学习？

对应有三个创新点

* 无监督的时空模型构建
* 基于贝叶斯推断的时空图像模型融合
* 基于Learning to Rank的迁移学习

接下来详细解析我们的方法。

## 时空模型

### 数据集中的时空规律

所谓时空模型，即一个摄像头网络中，行人在给定两个摄像头间迁移时间的分布。

我们看遍所有Reid数据集，发现有三个数据集有时空信息，Market1501, GRID, DukeMTMC-ReID，其中，DukeMTMC-ReID是2017年后半年才出来的，时间比较仓促在论文中就没有包含跟它相关的实验。Market1501是一个比较大的Person Reid数据集，GRID是一个比较小的Person Reid数据集，并且都有六个摄像头（GRID中虽然介绍了8个摄像头，实际上只有6个摄像头的数据）。

例如，Marke1501中一张图片的时空信息是写在图片名字中的：

![](/files/-MkuKu7Li2yn2YQEbyzb) 0007\_c3s3\_077419\_03.jpg：

* 0007代表person id，
* c3代表是在3号摄像头拍到的，也就是空间信息，
* s3代表属于第3个时间序列（GRID和DukeMTMC中没有这个序列的信息，在Market1501中，不同序列的属于不同起始时间的视频，同一系列不同摄像头的视频起始时间相近）,
* 077419为帧号，也就是时间信息。

我想吐槽的是，其实时空信息是非常容易保存的，只要知道图片是在什么时候，哪台摄像机上拍摄，就能够将时空信息记录并有效利用起来，希望多模态数据融合得到更多重视之后，做数据集的人能够更加重视可保存的信息吧。

我们首先通过Market1501中的真实行人标签，计算训练集中所有`图片对`对应的`时空点对`对应的迁移时间，这里可视化了从摄像头1出发的行人，到达其他摄像头需要的时间的分布。

![](/files/-MkuKu7MMLPcRX9kyz5t) 可以看到，到达不同目标摄像头的峰值位置不同，其中从摄像头1到摄像头1，意味着被单个摄像头拍到连续多帧，所以峰值集中在0附近，从摄像头1到摄像头2，峰值集中在-600附近，意味着大部分人是单向从摄像头2运动到摄像头1，等等，并且，说明这个数据集中存在显著可利用的时空规律。

### 无监督的时空模型构造

我们将迁移时间差命名为delta，这样说起来方便\~\~(装逼)\~\~一点。

如果我们能够统计一个数据集中的所有delta，给定一个新的delta（两个新的图片对应的两个时空点算出来的），我们能够用极大似然估计，用在这个delta前后一定范围(比如100帧)的delta的出现频率(=目标范围delta数量/总的delta数量)，作为新时间差出现的概率，也就是两个时空点是同一人产生的概率。

> 但是！问题是我们在目标场景上往往是没有行人标记数据的！

于是我们就*思考*，

* 我们能不能根据两个时空点对应的两张图是否属于同一个人，来决定两个时空点是否属于同一个人？
* 而两张图是否属于同一个人，其实是一个图像匹配的二分类问题，我们可以用一些视觉模型来做，
* 但是这种视觉模型往往是需要有标签训练的，无标签的视觉模型往往比较弱
* 视觉模型弱没关系！我们相信跟时空模型结合就能变成一个强大的分类器！要有信仰！
* 只要我们能无监督地把时空模型构造出来，结合弱的图像分类器，因为加了时空信息，一定能吊打其他无监督模型！

思路有了，实现就很自然了，

* 我们先在其他数据集上（于是我们就可以说这是一个跨数据集的任务了）预训练一个卷积神经网络，
* 然后用这个卷积神经网络去目标数据集上提特征，
* 用余弦距离算特征相似度
* 将相似度排在前十的当做同一个人
* 用这种“同一个人”的信息+极大似然估计构造时空模型

图像分类器上，我们这里用的是LiangZheng的Siamese网络，他们的源码是用MATLAB实现的，我用Keras[复现](https://github.com/ahangchen/rank-reid/blob/master/pretrain/pair_train.py#L139)了一把：

![](/files/-MkuKu7NPSoOJyH3e4uC) 时空模型的极大似然估计可以看[这里](https://github.com/ahangchen/TrackViz/blob/simfus/train/st_estim.py#L30)

聪明的读者应该会注意到，这个图像分类器是在其他数据及上预训练的，由于特征空间中数据分布不同，这个图像分类器太弱了，对于目标数据集来说，前十里会有许多错的样本，导致构造出来的时空模型和真实的时空模型有偏差

![](/files/-MkuKu7OiSHDqm5TCTFb) 可以看到，构造的模型跟真实的模型还是有些差别的，但是峰值位置还是差不多，一定程度上应该还能用，但我们还是希望构造的模型尽量接近真实模型的。

于是我们开始*思考*

* 导致模型出现偏差的因素是什么？是错误的样本对
* 如何去掉错误样本对的影响？我们能不能把错误的样本对分离出来？没有标签咋办？
* （灵光一闪）错误的样本不就跟我瞎选的差不多？那我是不是可以随机地选样本对，算一个随机的delta分布出来
* 将估算的delta分布去掉随机的delta分布，剩下的多出来的部分，就是由于正确的行人迁移产生的，不就得到真实的delta分布了？

于是我们可视化了一下随机的delta分布

![](/files/-MkuKu7PzDKIvEQQ88L5) 可以发现，

* 确实与估计模型和真实模型不同
* 存在较多抖动

这种随机的时间差分布也呈现出一定的集中趋势，其实体现的是采样的时间差分布，如，在1号摄像头采的图片大多在某个时间段，2号摄像头也大多在这个时间段采，但3号摄像头的图片大多是在其他时间段采到的。

考虑到时间差的频率图有这么多的抖动，我们在计算某个区域的时间差时，加上了均值滤波，并且做了一定区域的截断，包括概率极小值重置为一个最小概率值，时间差极大值重置为一个最大时间差。

接下来，应该怎么把错误的模型从估计的模型滤掉呢？又怎么将时空模型和图像模型结合呢？

## 基于贝叶斯推断的模型融合

首先看时空模型和图像模型的融合， 我们有一个视觉相似度$$P\_{v}$$，一个时空概率$$P\_{st}$$，一个直观的想法是，联合评分可以是$$P\_{v} \* P\_{st}$$，如果要再抑制随机的评分$$P\_{random}$$，可以做个除法，就是$$P\_{v} \* P\_{st} / P\_{random}$$

这样一看，像不像条件概率公式？于是我们开始推导（大量公式预警）：

先看看我们手上的资源：现在我们有一个弱的图像分类器，可以为两张图片提取两个视觉特征$$v\_{i}, v\_{j}$$, 有两个时空点，空间特征为两个摄像头编号$$c\_{i}, c\_{j}$$，时间特征为两张图片拍摄的时间差$$∆*{ij}$$，假定两张图对应的person id分别为$$P*{i}, P\_{j}$$，那么我们的目标就是求，在给定这些特征的条件下，两张图属于同一个人的概率

$$Pr(P\_{i}=P\_{j}|v\_{i},v\_{j},c\_{i},c\_{j},∆\_{ij})$$（论文公式6）

由条件概率公式P(A|B) = P(B|A)\*P(A)/P(B)，可得

$$Pr(P\_{i}=P\_{j}|v\_{i},v\_{j},c\_{i},c\_{j},∆*{ij})$$ $$= Pr(v*{i},v\_{j},c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j}) \*Pr(P\_{i}=P\_{j})/ Pr(v\_{i},v\_{j},c\_{i},c\_{j},∆\_{ij})$$

由时空分布和图像分布的独立性假设（长得像的人运动规律不一定像），我们可以拆解第一项，得到 $$= Pr(v\_{i},v\_{j}|P\_{i}=P\_{j})\*Pr(c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j}) \*Pr(P\_{i}=P\_{j})/ Pr(v\_{i},v\_{j},c\_{i},c\_{j},∆\_{ij})$$

其中$$Pr(P\_{i}=P\_{j})$$是一个不好求的项，我们试着把它换掉，

先交换顺序（乘法交换律）

$$= Pr(v\_{i},v\_{j}|P\_{i}=P\_{j}) \* Pr(P\_{i}=P\_{j})\*Pr(c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j}) / Pr(v\_{i},v\_{j},c\_{i},c\_{j},∆\_{ij})$$

由条件概率公式$$P(A|B)\*P(B) = P(B|A) \* P(A)$$可得

$$= Pr(P\_{i}=P\_{j}|v\_{i},v\_{j}) \* Pr(v\_{i}=v\_{j})\*Pr(c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j}) / Pr(v\_{i},v\_{j},c\_{i},c\_{j},∆\_{ij})$$

可以看到

* $$Pr(P\_{i}=P\_{j}|v\_{i},v\_{j})$$可理解为两张图从视觉特征相似度上判定为同一人的概率
* $$Pr(c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j})$$就是两个时空点是同一个人移动产生的概率

再次利用时空分布和图像分布的独立性假设，拆解分母

$$= Pr(P\_{i}=P\_{j}|v\_{i},v\_{j}) \* Pr(v\_{i}=v\_{j})\*Pr(c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j}) / Pr(v\_{i},v\_{j}) \* P(c\_{i},c\_{j},∆\_{ij})$$

约掉$$Pr(v\_{i}=v\_{j})$$，

$$= Pr(P\_{i}=P\_{j}|v\_{i},v\_{j}) \* Pr(c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j}) /P(c\_{i},c\_{j},∆\_{ij})$$

也就是

> \= 视觉相似度\*同一人产生这种移动的概率/任意两个时空点组成这种移动的概率

这也就是论文公式(7)，也就是我们一开始的猜想：$$P\_{v} \* P\_{st} / P\_{random}$$

看着好像很接近我们手头掌握的资源了，但是，

* 我们并不知道理想的两张图的视觉相似度$$Pr(P\_{i}=P\_{j}|v\_{i},v\_{j})$$ ，只有我们的图像分类器判定的两张图的视觉相似度$$Pr(S\_{i}=S\_{j}|v\_{i},v\_{j})$$ ，
* 我们并不能计算同一人产生这种移动的真实概率$$Pr(c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j})$$ ，我们只有依据视觉分类器估算的时空概率$$Pr(c\_{i},c\_{j},∆*{ij}|S*{i}=S\_{j})$$ ，
* 我们倒是确实有数据集中任意两个时空点产生这种移动的概率$$P(c\_{i},c\_{j},∆\_{ij})$$

于是我们想用$$Pr(c\_{i},c\_{j},∆*{ij}|S*{i}=S\_{j}) ，P(c\_{i},c\_{j},∆\_{ij})$$去近似，得到

$$= Pr(S\_{i}=S\_{j}|v\_{i},v\_{j}) \* Pr(c\_{i},c\_{j},∆*{ij}|S*{i}=S\_{j}) /P(c\_{i},c\_{j},∆\_{ij})$$

看到这里其实就大致理解我们的融合原理了，实际上我们大部分实验也是用的这个近似公式算的。

实现上，先模拟两个时空模型，计算图像相似度，然后代入公式求融合评分，具体可以实现看[我GitHub](https://github.com/ahangchen/TrackViz/blob/simfus/train/st_filter.py)

> 但这个近似能不能做呢？我们来做一下误差分析（大量推导，不感兴趣可以跳到接下来出现的第二张图，不影响后面的理解，只是分析一波会更加严谨）。

实际上，误差是由图像分类器引入的，假设图像分类器判定两张图是同一个人的错判率为$$E\_{p}$$，图像分类器判定两张图不是同一人的错判率为$$E\_{n}$$，

则有，

$$E\_{p} = Pr(P\_{i}≠P\_{j}|S\_{i}=S\_{j})$$（论文公式1）

$$E\_{n} = Pr(P\_{i}=P\_{j}|S\_{i}≠S\_{j})$$（论文公式2）

则$$Pr(P\_{i}=P\_{j}|v\_{i},v\_{j})$$ 与 $$Pr(S\_{i}=S\_{j}|v\_{i},v\_{j})$$ 的关系可以表示为：

$$Pr(P\_{i}=P\_{j}|v\_{i},v\_{j})$$ $$= Pr(P\_{i}=P\_{j}|S\_{i}=S\_{j}) \* Pr(S\_{i}=S\_{j}|v\_{i},v\_{j}) + Pr(P\_{i}=P\_{j}|S\_{i}≠S\_{j}) \* Pr(S\_{i}≠S\_{j}|v\_{i},v\_{j})$$ $$= (1-E\_{p}) \* Pr(S\_{i}=S\_{j}|v\_{i},v\_{j}) + E\_{n}\* (1-Pr(S\_{i}=S\_{j}|v\_{i},v\_{j}) )$$ $$= (1-E\_{p}-E\_{n}) \* Pr(S\_{i}=S\_{j}|v\_{i},v\_{j}) + E\_{n}$$ （论文公式8）

推导，$$Pr(c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j})$$ 和$$Pr(c\_{i},c\_{j},∆*{ij}|S*{i}=S\_{j})$$ 的关系（这个没法像视觉相似度那样直接推导，因为因果关系不同）

$$Pr(c\_{i},c\_{j},∆*{ij}|S*{i}=S\_{j})$$ $$= Pr(c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j}) \* (Pr(P\_{i}=P\_{j})|S\_{i}=S\_{j}) + Pr(c\_{i},c\_{j},∆*{ij}|P*{i}≠P\_{j}) \* (Pr(P\_{i}=P\_{j})|S\_{i}≠S\_{j})$$ $$= Pr(c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j}) \* (1- E\_{p}) + Pr(c\_{i},c\_{j},∆*{ij}|P*{i}≠P\_{j}) \* E\_{p}$$

同样可以得到

$$Pr(c\_{i},c\_{j},∆*{ij}|S*{i}≠S\_{j})$$ $$= Pr(c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j}) \* E\_{n} + Pr(c\_{i},c\_{j},∆*{ij}|P*{i}≠P\_{j}) \* (1 - E\_{p})$$

联立上面两个式子解方程，消掉$$Pr(c\_{i},c\_{j},∆*{ij}|S*{i}≠S\_{j})$$可以得到

$$Pr(c\_{i},c\_{j},∆*{ij}|P*{i}=P\_{j})$$ $$= (1 - E\_{p} - E\_{n})^{-1}(1-E\_{n}) \* Pr(c\_{i},c\_{j},∆*{ij}|S*{i}=S\_{j}) - E\_{p} \* Pr(c\_{i},c\_{j},∆*{ij}|S*{i}≠S\_{j})$$ （论文公式5）

其中有个新概念$$Pr(c\_{i},c\_{j},∆*{ij}|S*{i}≠S\_{j})$$ ，意味着图像分类器认为不是同一个人的时候，这种时空点出现的概率，实现上也不难，统计视觉相似度top10以后的点对应的时间差，作为反时空概率模型即可。

我们把两个近似（公式5和公式8）代进公式7，

可以得到

$$Pr(P\_{i}=P\_{j} | v\_{i}, v\_{j}, ∆*{ij}, c*{i}, c\_{j})$$ $$= (M\_{1} + E\_{n}/(1 - E\_{n} - E\_{p}))((1-E\_{n})M\_{2} - E\_{p}M\_{3})/Pr(∆*{ij}, c*{i}, c\_{j}))$$（论文公式9）

其中，

$$M\_{1} = Pr(S\_{i}=S\_{j}|v\_{i},v\_{j})$$，视觉相似度

$$M\_{2} = Pr(∆*{ij},c*{i},c\_{j}|S\_{i}=S\_{j})$$，正时空概率模型

$$M\_{3} = Pr(∆*{ij},c*{i},c\_{j}|S\_{i}≠S\_{j})$$，反时空概率模型

分母$$Pr(∆*{ij}, c*{i}, c\_{j}))$$为随机概率模型

以上四项都是可以从无标签目标数据集中结合图像分类器求解到的，并且，当En=Ep=0时（意味着图像分类器完全准确），这个公式可以退化为近似解：

$$Pr(S\_{i}=S\_{j}|v\_{i},v\_{j}) \* Pr(c\_{i},c\_{j},∆*{ij}|S*{i}=S\_{j}) /P(c\_{i},c\_{j},∆\_{ij})$$

到这里，你是不是以为我们就可以用公式9算融合评分了？非也，公式9中，还有个问题：$$E\_{p}，E\_{n}$$是未知的！

如果想要正儿八经地算$$E\_{p}，E\_{n}$$，要求目标数据集有标签，然后我们用图像分类器先算一遍，数数哪些算错了，才能把$$E\_{p}，E\_{n}$$算出来。因此我们用两个常数α和β分别替代$$E\_{p}，E\_{n}$$，整个模型的近似就都集中在了这两个常数上。

在论文Table1,2,3,4,Fig6相关的实验中，α=β=0，并且，在Fig5中，我们设置了其他常数来检查模型对于这种近似的敏感性

![](/files/-MkuKu7TqJ8STTlCfk_1) 可以看到，虽然α和β较大时，准确率会有所下降，但是仍然能保持一定的水准，当你看到纯图像分类器的准确率之后，还会发现融合模型的准确率一直高于纯图像分类器。

你可能注意到了，图中α+β都是小于1的，这是因为，只有当$$E\_{p}+E\_{n}<1$$且$$α+β<1$$时，融合模型的$$E\_{p}+E\_{n}$$才会小于图像模型的$$E\_{p}+E\_{n}$$，说人话就是，只有图像模型不是特别糟糕，且近似的参数也比较正常的时候，融合模型才会比单个的图像模型要准，融合才有意义。这个定理的具体的证明放到论文附录里了，有兴趣的可以邮件私信我拿附录去看，这里摆出来就太多了。

> 于是我们得到了一个由条件概率推断支撑的多模态数据融合方法，称为贝叶斯融合

看一眼融合得到的时空分布图：

![](/files/-MkuKu7UMXYylAFQLHGr) 再从数据上看一眼融合的模型有多强：

|    源数据集    |    目标数据集   |    纯   |   图像   |    结果   |     |   融合   |   时空   |    结果   |
| :--------: | :--------: | :----: | :----: | :-----: | :-: | :----: | :----: | :-----: |
|            |            | rank-1 | rank-5 | rank-10 |     | rank-1 | rank-5 | rank-10 |
|   CUHK01   |    GRID    |  10.70 |  20.20 |  23.80  |     |  30.90 |  63.70 |  79.10  |
|    VIPeR   |    GRID    |  9.70  |  17.40 |  21.50  |     |  28.40 |  65.60 |  80.40  |
| Market1501 |    GRID    |  17.80 |  31.20 |  36.80  |     |  49.60 |  81.40 |  88.70  |
|            |            |        |        |         |     |        |        |         |
|    GRID    | Market1501 |  20.72 |  35.39 |  42.99  |     |  51.16 |  65.08 |  70.04  |
|    VIPeR   | Market1501 |  24.70 |  40.91 |  49.52  |     |  56.18 |  71.50 |  76.48  |
|   CUHK01   | Market1501 |  29.39 |  45.46 |  52.55  |     |  56.53 |  70.22 |  74.64  |
|            |            |        |        |         |     |        |        |         |
|    GRID    |  DukeMTMC  |  9.74  |  18.31 |  22.53  |     |  20.20 |  27.42 |  28.82  |
|    VIPeR   |  DukeMTMC  |  18.80 |  32.22 |  39.67  |     |  43.99 |  56.15 |  60.41  |
|   CUHK01   |  DukeMTMC  |  22.35 |  34.16 |  39.81  |     |  31.15 |  40.84 |  43.90  |

可以看到，

* 跨数据集直接迁移效果确实很差
* 融合之后的准确率Rank1准确率变成2-4倍

说明这种融合方式是确实行之有效的。

## 基于Learning to Rank的迁移学习

前面讲到图像分类器太弱了，虽然融合后效果挺好的（这个时候我们其实想着要不就这样投个NIPS算了），但是如果能提升图像分类器，融合的效果理论上会更好。而现在我们有了一个强大的融合分类器，我们能不能用这个融合分类器为目标数据集的图片打标签，反过来训练图像分类器呢？

一个常用的无监督学习套路就是，根据融合评分的高低，将图片对分为正样本对和负样本对（打伪标签），然后喂给图像分类器学习。

![](/files/-MkuKu7WYfjUWlaEbRTp) 我们也尝试了这种做法，但是发现，数据集中负样本远远多于正样本，融合分类器分对的负样本是挺多的，但是分对的正样本超级少，分错的正样本很多，错样本太多，训练出来效果极差，用上一些hard ming的技巧也不行。

于是我们*思考*，

* 我们无法提供正确的01标签，分类器就只能学到许多错的01标签
* 我们是否可以提供一些软标签，让分类器去学习回归两个样本之间的评分，而不是直接学习二分类的标签？
* 这是一个图像检索问题，我们能不能用信息检索中的一些学习方法来完成这个任务？

于是自然而然地想到了Learning to Rank

### Ranking

* 问题定义：给定一个对象，寻找与其最相关的结果，按相关程度排序
* 常用方法：
  * Point-wise：每一个结果算一个绝对得分，然后按得分排序
  * Pair-wise：每两个结果算一下谁的得分高，然后按这个相对得分排序
  * List-wise：枚举所有排列情况，计算综合得分最高的一种作为排序结果

综合得分往往需要许多复杂的条件来计算，不一定适用于我们的场景，所以排除List-wise，Point-wise和Pair-wise都可以采用，得分可以直接用融合评分表示，Pair-wise可以用一组正序样本，一组逆序样本，计算两个得分，算相对得分来学习，有点Triplet loss的意味，于是在实验中采用了Pair-wise方法。

### Pair-wise Ranking

* 给定样本$$x\_{i}$$，其排序得分为$$o\_{i}$$，
  * 给定样本$$x\_{j}$$，其排序得分为$$o\_{j}$$，
* 定义$$o\_{ij}=o\_{i} - o\_{j}$$，如果$$o\_{ij}>0$$说明$$x\_{i}$$的排名高于$$x\_{j}$$，
* 将这个排名概率化，定义$$P\_{ij} = e^{o\_{ij}}/(1+e^{o\_{ij}})$$，为$$x\_{i}$$排名高于$$x\_{j}$$的概率。
* 对于任何一个长度为n的排列，只要知道n-1个相邻item的概率$$P\_{i,i+1}$$，就可以推断出来任何两个item的排序概率
* 例如，已知$$P\_{ik}和P\_{kj}$$，$$P\_{ij} = P\_{ik} \* P\_{kj} = e^{o\_{ik}+o\_{kj}}/(1 + e^{o\_{ik}+o\_{kj}})$$，其中$$o\_{ik}=ln(P\_{ik}/(1 - P\_{ik}))$$

### RankNet: Pair-wise Learning to Rank

RankNet是Pair-wise Learning to Rank的一种方法，用一个神经网络去学习输入的两个样本（还有一个query样本）与其排序概率（上面定义的）的映射关系。

具体到我们这个问题里

* 给定查询图片A，给定待匹配图片B和C
* 用神经网络预测AB之间的相似度$$S\_{ab}$$为B的绝对排序得分，计算AC之间的相似度$$S\_{ac}$$为C的绝对排序得分

> 具体的神经网络用[Keras实现](https://github.com/ahangchen/rank-reid/blob/master/transfer/simple_rank_transfer.py)并可视化出来长这样：

![](/files/-MkuKu7_vmmP3m0j6bg9)

> * 输入是三张图片，分别用Resnet52提取特征并flatten
> * flatten之后写一个Lambda层+全连接层算特征向量带权重的几何距离，得到score1和score2
> * 用score1和score2和真实分数算交叉熵Loss（下面讲）

* 则B排序高于C的概率为：

$$P\_{bc}= e^{o\_{bc}}/(1+ e^{o\_{bc}}) = e^{S\_{ab}- S\_{ac}} / (1 + e^{S\_{ab}- S\_{ac}})$$

* 用预测概率P\_{bc}去拟合真实的排序概率，回归损失用预测概率和真实概率的交叉熵表达

$$C(o\_{bc}) = -P'*{bc}ln P*{bc} - (1-P'*{bc})ln (1 - P*{bc})$$

网络实现超级简单，主要麻烦在样本三元组构造

### Transfer Learning to rank

> 整个Learning to rank过程如图

![](/files/-MkuKu7arltf8iDLfGp4) 我们用融合分类器为目标数据集中的图片对评分，构造三元组输入RankNet，其中&#x53;*{i}是查询图，S*{j}是在与&#x53;*{i}融合相似度top1 - top25中抽取的图片，S*{k}是在与S\_{i}融合相似度top25 - top50中抽取的图片，喂给RankNet学习，使得resnet52部分卷积层能充分学习到目标场景上的视觉特征。

### Learning to Rank效果

|    源数据集    |    目标数据集   |    纯   |   图像   |    结果   |     |   融合   |   时空   |    结果   |
| :--------: | :--------: | :----: | :----: | :-----: | :-: | :----: | :----: | :-----: |
|            |            | rank-1 | rank-5 | rank-10 |     | rank-1 | rank-5 | rank-10 |
|   CUHK01   |    GRID    |  17.40 |  33.90 |  41.10  |     |  50.90 |  78.60 |  88.30  |
|    VIPeR   |    GRID    |  18.50 |  31.40 |  40.50  |     |  52.70 |  81.70 |  89.20  |
| Market1501 |    GRID    |  22.30 |  38.10 |  47.20  |     |  60.40 |  87.30 |  93.40  |
|            |            |        |        |         |     |        |        |         |
|    GRID    | Market1501 |  22.38 |  39.25 |  48.07  |     |  58.22 |  72.33 |  76.84  |
|    VIPeR   | Market1501 |  25.23 |  41.98 |  50.33  |     |  59.17 |  73.49 |  78.62  |
|   CUHK01   | Market1501 |  30.58 |  47.09 |  54.60  |     |  60.75 |  74.44 |  79.25  |
|            |            |        |        |         |     |        |        |         |
|    GRID    |  DukeMTMC  |  13.06 |  23.29 |  29.04  |     |  29.94 |  43.00 |  47.80  |
|    VIPeR   |  DukeMTMC  |  19.39 |  33.21 |  39.77  |     |  42.64 |  56.51 |  59.78  |
|   CUHK01   |  DukeMTMC  |  27.96 |  42.01 |  48.97  |     |  45.47 |  60.82 |  66.16  |

对比Learning to Rank前的效果，准确率都提升了，GRID数据集上提升尤为明显。

#### 对比SOA有监督方法

一方面，我们将上面的跨数据集无监督算法应用在GRID和Market1501两个数据集上，与当前最好的方法进行对比，另一方面，我们还测试了有监督版本的效果，有监督即源数据集与目标数据集一致，如GRID预训练->GRID融合时空，效果如下：

* GRID

|   Method   | Rank 1 |
| :--------: | :----: |
|    JLML    |  37.5  |
| TFusion无监督 |  60.4  |
| TFusion有监督 |  64.1  |

由于在这个数据集上时空规律十分明显（正确时间差都集中在一个很小的范围内），可以过滤掉大量错误分类结果，所以准确率甚至碾压了全部有监督方法。

* Market1501

|   Method   | Rank 1 |
| :--------: | :----: |
|    S-CNN   |  65.88 |
|    DLCE    |  79.5  |
|   SVDNet   |  82.3  |
|    JLML    |  88.8  |
| TFusion无监督 |  60.75 |
| TFusion有监督 |  73.13 |

在Market1501这个数据集上，无监督的方法逼近2016年的有监督方法（我们的图像分类器只是一个ResNet52)，有监督的方法超越2016年的有监督方法，虽然比不上2017年的有监督方法，但是如果结合其他更好的图像分类器，应该能有更好的效果。

#### 对比SOA无监督方法

我们向UMDL的作者要到了代码，并复现了如下几组跨数据集迁移实验

|  Method |   Source   |   Target   | Rank1 |
| :-----: | :--------: | :--------: | :---: |
|   UMDL  | Market1501 |    GRID    |  3.77 |
|   UMDL  |   CUHK01   |    GRID    |  3.58 |
|   UMDL  |    VIPeR   |    GRID    |  3.97 |
|   UMDL  |    GRID    | Market1501 | 30.46 |
|   UMDL  |   CUHK01   | Market1501 | 29.69 |
|   UMDL  |    VIPeR   | Market1501 | 30.34 |
|         |            |            |       |
| TFusion | Market1501 |    GRID    |  60.4 |
| TFusion |   CUHK01   |    GRID    |  50.9 |
| TFusion |    VIPeR   |    GRID    |  52.7 |
| TFusion |    GRID    | Market1501 | 58.22 |
| TFusion |   CUHK01   | Market1501 | 59.17 |
| TFusion |    VIPeR   | Market1501 | 60.75 |

其中，UMDL迁移到Market1501的结果与悉尼科技大学hehefan与LiangZheng[复现](https://github.com/hehefan/Unsupervised-Person-Re-identification-Clustering-and-Fine-tuning/tree/master/dataset/Duke)出来的效果差不多，所以我们的复现是靠谱的。

可以看到，无监督的TFusion全面碾压UMDL。

> 更多详细实验结果可以到论文中细看。

#### 多次迭代迁移学习

![](/files/-MkuKu7nW35bn3_D-prO) 回顾一下整个架构，我们用图像分类器估算时空模型，得到融合模型，用融合模型反过来提升图像分类器模型，图像分类器又能继续增强融合模型，形成一个`闭环`，理论上这个闭环循环多次，能让图像分类器无限逼近融合分类器，从而得到一个目标场景中也很强大的图像分类器，因此我们做了多次迭代的尝试：

![](/files/-MkuKu7pCEZ-FmzBhEOX) 在从目前的实验效果看，第一次迁移学习提升比较大，后面提升就比较小了，这个现象往好了说可以是收敛快，但往坏了说，没有出现图像分类器接近融合分类器的现象，所以这里边应该还有东西可挖。

## 后记

![](/files/-MkuKu7qSNzAFFEy27tv) 调研，可视化，找思路，找数据集，做实验，Debug，调参，写论文，九个月写一篇CVPR，这也是我们实验室第一篇CCF A类论文，算是来之不易的开山之作了。现在我们在Person Reid领域继续探索，正在搭建一个基于树莓派的摄像头网络，构造自己的数据集，并在这个基础上开展行人检测，多模态数据融合，轻量级深度模型，分布式协同终端，视频哈希，图像索引等一系列研究，欢迎follow我的[Github](https://github.com/ahangchen)，也欢迎持续关注我们[实验室的博客](http://blog.so-link.org)

看了人家这么久，还不给我[Github](https://github.com/ahangchen/TFusion)点star！


# ECCV2018:TAUDL

## ECCV 2018 TAUDL

> 转载请注明作者[梦里茶](https://github.com/ahangchen)

## Overview

关注Person reid这个领域的同学应该知道，有监督模型基本已经被刷爆了，2018年开始出现一些无监督/半监督/多模态的方法，包括我们之前做的结合时空模型的[TFusion](https://zhuanlan.zhihu.com/p/34778414)， 今天要介绍的也是结合时空数据的一个工作，由南京大学和QMUL的G.Shaogang老师合作发表在ECCV2018上的一篇论文：

> Unsupervised Person Re-identification by Deep Learning Tracklet Association

![](/files/-MkuKsqOTlKUGKa-5806) TAUDL是Tracklet Association Unsupervised Deep Learning的缩写，通过利用无监督的单摄像头轨迹信息（比如用detection和tracking的方法来提取监控视频中的行人轨迹）来训练端到端的神经网络，然后用这个图像模型对跨摄像头的图像进行自动标注和学习。具体如下：

## 无监督单摄像头轨迹标注

Person Re-id数据集通常用监控摄像头采集得到，所以有很多视频格式的reid数据集，对于这种数据集，我们可以直接应用detection+tracking模型来提取单个人的轨迹，但是由于tracking的模型也存在误差，单人的轨迹可能会被切成多段，我们要避免切成多段的这种轨迹被当做不同的人，所以需要对这样的轨迹数据做进一步的筛选才能用。这个筛选方法就是上图中的SSTT，Sparse Space-Time Tracklet sampling。具体而言，SSTT依赖于reid数据集的三个特点：

* 由于同个人不会在短时间内回到同个摄像头的视野内，因此同个人出现多段轨迹大多来源于tracking的误差
* 同时出现在不同位置的人应该是不同的人
* 同个人在单个摄像头中的迁移时间比较短，所以经过一个比较长的时间间隔P之后，出现的轨迹就不会是之前出现过的人的了

针对上面三个特点，提出了三个样本筛选规则（没错，这是手工规则）：

* 在同一个时间窗口内，为每个轨迹分配不同的id
* 同个时间内出现的不同轨迹，只选那些空间上离得比较远的（稀疏的空间采样）
* 在一个时间窗口采集轨迹后，要过一个time Gap P才采集新的轨迹（稀疏的时间采样）

用这三个规则过一遍所有的数据，我们就可以得到为每个摄像头得到一个轨迹和id的集合，这样我们就能用这些轨迹和id数据来训练图像模型了。

## 无监督跨摄像头轨迹关联学习

![](/files/-MkuKsqQhnf1f5WRV_ey) 由于我们之前是为每个摄像头的数据独立分配轨迹id的，比较直接的思路是把不同摄像头的id都当做不一样的，但这样训练出来可能效果不好，因为同个人可能出现在不同摄像头里，将这种跨摄像头的正样本当成负样本去训练会出问题，所以我们希望能同时学习同摄像头内不同人的差异性以及不同摄像头内的同个人的关联性。

PCTD（单摄像头）：具体而言，假设有T个摄像头，我们有T个轨迹标记集，我们用T个集合训练出一个多任务（T个任务）图像模型，这T个任务共享用于特征表达的卷积层，但各自拥有自己的分类层，因为它们的ID是独立的，这种策略其实也平平无奇，是迁移学习常用的套路，而且计算量有点大，通过这种方式更多的是对单摄像头的数据做特征表达，还没将不同摄像头的数据关联起来。

CCTA（跨摄像头）：由于不同摄像头之间是有相同的人的，所以需要对不同摄像头的ID进行对齐，具体做法是，

* 在每个Batch做K近邻聚类（coding技能点满）：为每个轨迹在不同摄像头轨迹中寻找K个在特征空间中最相似的轨迹，将这些轨迹认为是相同人的轨迹，剩下的轨迹认为是不同人的轨迹
* 用聚类的结果训练图像模型，希望同人轨迹特征更接近， 不同人的轨迹特征更远离（用L\_{ccta}约束）。

PCTD和CCTA同时作为约束训练模型。

## 实验

由于TAUDL有了单摄像头的轨迹数据，比其他纯图像无监督方法要更鲁棒一点，CCTA的训练也一定程度上保证了跨摄像头的预测质量，因此效果也很不错。 ![Exp](https://upload-images.jianshu.io/upload_images/1828517-0d91ab76522a23de.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)

然而TAUDL这个方法依赖于视频数据集中的帧间关联性，对于图片数据集，如CUHK03这种连帧号都不保留的数据集，TAUDL是没办法提取出轨迹的，邮件问过作者，他们是用person ID来提取的轨迹信息，所以不算是严格无监督的方法，算是这个方法的一个美中不足吧。


# CVPR2018:Graph+reid

## Graph + Reid

今天解读港中文商汤联合实验室沈岩涛老师在CVPR2018和ECCV2018上发表的两篇关于Graph+Person reid的文章：

CVPR2018：Deep Group-shuffling Random Walk for Person Re-identification ECCV2018：Person Re-identification with Deep Similarity-Guided Graph Neural Network

## Motivation and Related Work

person reid这个任务是为probe图像在gallery图片集中寻找属于同一个人的图片，所以通常的做法都是考虑probe和gallery之间的关系，但有时候probe和gallery的差别太大，比如一个正面的人和一个背面的人，假如我有一个侧面的人的图片，跟两者都很像，就有可能用这个中间图片将两者关联起来。在这之前其实也有人提出过一些方法，比如CVPR2017:Re-ranking person re-identification with k-reciprocal encoding. 先计算出gallery图片之间的相似度，将较相似的图片的rank拉近。

但这些方法大多是将这种关系抽取和利用作为一个后处理的过程，对图像模型的训练起不到帮助。并且对gallery之间的相似度提取大多是基于已经训练好的图像模型，加上无监督的聚类方法，提取gallery图片之间的相似度关系，这种无监督方法较弱，不能充分利用数据集中的监督信息。

因此这两篇文章开始将gallery之间的关系（下面简称G2G）融入到图像模型的训练里。Graph由许多节点和边构成，在Reid问题里，节点就是一个个的人的图像，边就是图像之间的相似度。利用一个batch中的所有图像在关系图上的关联，提供更多的监督信号，将模型训练的更好。之所以说更多的监督信号，是因为以往我们一个batch的label只有batch\_size对样本之间的相似关系，但将batch中的所有图片看成batch size个图之后，每个图之间有batch gallery size x batch gallery size个相似度关系可以学习，就引入了更多的监督信号。

### Overview

![](/files/-MkuKtnqcgHioYnCrv1N) 这两篇文章是同一班人马写的，所以其实整体结构上都很像，我们结合起来分析：

* DGRW是用Random Walk的方式将G2G的相似度信息用来更新P2G的**相似度关系**，在反向传播时通过G2G这个很多相似度关联的图对原来的图像模型提供更多的训练。
* SGGNN是用G2G的相似度关系对P2G的**相似度特征**进行修正，从而得到更好的相似度特征，并且也通过G2G之间的大量关联提供了更多的监督信号。

### Deep Group-shuffling Random Walk

与其他Reid方法一样，首先训练一个图像模型提取图像特征，对特征计算相似度可以得到probe中所有图片与gallery中所有图片之间的相似度，与其他Reid方法不同，但与reranking相同的是，我们也可以计算出所有gallery图片之间的相似度。给定一张probe图片，它和所有gallery之间的相似度向量为y，所有gallery之间的相似度矩阵为W，我们将一张probe图片和所有gallery图片合起来看成一个图，根据random walk的思想，probe图片这个节点walk到第j张gallery图片的概率是yj，接着，从probe节点出发，经过其他节点k，再到达gallery图片j的概率是yk \* Wkj，那么，从probe节点出发，走两步（包含了所有中间节点的可能）到达图片j的概率就是：

$$∑*{k=1}^n W*{kj}\*y^k$$

到达所有图片j的概率拼成一个新的向量，我们就可以得到：

$$y^{(t+1)} = Wy^{(t)}$$

论文里讲t拓展到了无穷大，并加入了权重因子λ对walk前后的概率进行了平衡，最终化简为了这样的形式：

$$y^(\infty)=(1-\lambda )(I-\lambda W)^{-1}y^{(0)}$$

但根本的道理就是上面所述的Random walk思想。

这样我们就得到了一组更好的P2G相似度，并且我们反向传播的时候，这组相似度的梯度会传到W矩阵上，W矩阵是由前边图像模型得出的，也就会传到前面的模型上，从而对模型提供更多的监督信号。

论文里还有一个创新点是，将视觉特征分成了K个group，每个group的特征都可以单独拿出来用，这样我们就可以做K个上边提到的random walk，于是监督信号更多了，同时因为视觉特征被拆成了K份，每一份都只是原特征的一小部分，相当于dropout了一部分信息，也能很大程度上防止过拟合。但其实这个方法并不见得比dropout优雅，实验效果也差不多。

![](/files/-MkuKtnrCpVTLPHpWSMq) 实现上还有一个小的细节，把所有图片构成一个graph的话，开销太大了，所以训练时graph其实只是所有图片的一个子图，

* 首先每个batch有64个人，每个人有4张图，这样batch size就是256（流下了贫穷的泪水），
* 训练时，首先有256个softmax loss，
* 然后对于64个人，每个人的4张图中取1张做probe，3张做gallery，galley是所有人一起用的（这样就有正样本也有负样本）
* 这样W矩阵就是64\_3x64\_3，对角线为0，于是就有192\*192个g2g相似度，同时有64个p2g的相似度。这些相似度可以用真实标签约束，也可以直接用feature求出来，论文里是用feature求的；
* 为每个人得到更新后的p2g向量，对这64个长度为192的p2g向量加二分类loss就完成训练模型的搭建了
* 测试时先算所有p2g相似度，然后对每个probe，选择top75个gallery组成G2G图，用这个G2G更新P2G相似度，从而获得更准的ranking结果

### Deep Similarity-Guided Graph Neural Network

这篇文章跟上一篇很多地方是一样的，我们来讲讲不同的地方。

#### Graph and node

这篇文章声称每个节点是一个P2G的图像对，但在讨论中，图里的P都是相同的，所以我觉得可以认为这其实也是一个G2G的图，只不过每个节点的value变成了SiameseCNN算出来的P2G的相似度向量di，我们用通常的Siamese二分类loss和softmax多分类loss约束di，同时我们希望能用G2G的相似度矩阵W来进一步修正di，

#### feature update

考虑一下用类似random walk的模式来更新相似度向量：：

$$d\_i^{t+1}=(1-α)d\_i^{t+1}+αWd\_i^t$$

但dit毕竟不是相似度的结果向量，而是相似度特征向量，所以上面这个式子其实是不成立的，于是进入拼凑模式，文中定义了一个消息向量:

$$t\_i=F(d\_i)$$

这个F就是d到t的映射，

![](/files/-MkuKtnsB9q-IWKAIxfi) 如图，通过两层FC+BN+ReLU，将相似度特征d映射为一个能够根据相似度矩阵W修正特征向量的消息向量t，由于权重是可训练的，所以下面这个式子就成立了：

$$d\_i^{t+1}=(1-α)d\_i^{t+1}+αWt\_i^t$$

文中说W\*t得到的结果是fusion feature，其实我觉得这种G2G相似度和特征的fusion没有Random Walk来得优雅，然后用fusion feature和原来的feature做加权和，其实这种加权和的方式来修正原来的feature也值得商榷。不过这种update形式也是借鉴自其他的Graph Neural Network方法：

$$d\_i^{t+1}=(1-α)d\_i^{t+1}+α\sum\_{j=1}^N h(d\_i,d\_j)t\_i^t$$

这里边的h往往是无监督的，而SGGNN的W是可以有监督训练得到的，就比其他GNN的方法要好一点。

实现细节上，与DGRW类似

* 选择48个人，每个人有4个图，共192张图
* 每个人的4张图里，一张做probe，其他做gallery，gallery是所有人共用的，于是有Mx(K-1)=144个gallery图片（论文里的顺序应该是写反了）
* 用siamese CNN算出48\_144个P2G相似度，144\_144个G2G相似度
* 为每个probe取top100个gallery图片构成G2G图，于是W矩阵大小为100x100
* 用W矩阵更新p2g相似度特征，对相似度特征加dense层再做二分类loss约束即可
* 测试与DGRW基本相同，区别只在于更新的是相似度feature而非相似度结果。

### Experiment

跟别的方法对比就不用看了，我们来看Ablation Study

DGRW

![](/files/-MkuKtntqDycZe8q-EhU) baseline rank1就91%了我还能说什么，流下了不会调参的泪水，不过看baseline+triplet居然还掉了，说明他们的hack泛化能力其实不强，另外针对两个主要的创新点，group shuffle也没有比dropout效果要好多少，random walk也没有比reranking好多少。不过有一个现象，mAP比top1提高的多，说明这种基于g2g关系的方法，通常是用得分较高的gallery图像把gallery中得分比较低的对象拉上来了。

SGGNN

![](/files/-MkuKtnuVB79b4nSUNgk) 结果也跟random walk类似。

### 总结

将graph之间的关联融合到神经网络训练中提供了更丰富的监督信号，基于Graph的方法虽然结果上提升不明显（可能是baseline太强了吧），但创新性是有的。


# Person Re-identification

## 发展

* Multi-camera tracking
* Multi-camera tracking with explicit “re-identification”
* 独立的re-id(image-based)
* video-based re-id（对单个人采样多个帧）
* deep-learning re-id
* end to end image-based re-id: 结合detection，做尽可能少的中间人工处理

## 目标

* 在视频数据中找到人物（稀疏）
* 关联大量不同摄像头捕捉到的人物
* 自动匹配和追踪人物

  > Detect -> Track -> Retrieval

  通常认为的Re-id只是指Retrieval这个过程
* Re-id当做identification任务时，目标类似聚类或分类
* Re-id当做recognization任务时,目标类似Ranking

## 数据

* video-based
* image-based
* 个体的动作行为
* 长期的活动模式

![](/files/-L_G1DnR9nFhFEdPrmlr)

![](/files/-L_G1DnTu8-WSWXJEUv-)

## Person Reid数据集收集

<http://www.liangzheng.org/Datasets.html>

<https://robustsystems.coe.neu.edu/sites/robustsystems.coe.neu.edu/files/systems/projectpages/reiddataset.html>

* 数据越来越多
* 边界倾向于由行人检测器（DPM，ACF）生成，但这种检测器可能会错标，导致re-id准确率下降
* 摄像头越多需要越大的泛化能力
* 训练性能逐年提升
* 深度学习方法取得优势
* 大规模数据集上还有提升空间(mAP准确率极低，性能有很大提升空间)，泛化能力弱

## 特点与困难

* 数据
  * 摄像头采集到的图像数据的视角，环境，时间等都不同
  * 摄像头中出现的不同的人会相互干扰
  * 一个人会出现在不同的摄像头中
  * 摄像距离不确定，人数不确定
  * 训练数据与真实环境不一致，搜索空间大小不确定

## Detect

* object detection圈出人物的精度不能完全满足re-id的需求
* 人工识别代价高，不准确，经验不可迁移，需要自动re-id

## Feature

* 人脸识别和图像细节不可靠，太模糊
* 一般基于视觉特征，如衣着，持有物，但这些特征辨识度很低，且很容易受光线视角影响，而且在衣着变化大的场合直接失效
* 特征表达很受摄像头影响
* 特征类内差异大于类间差异：同一个人在不同地方的样子的差异大于不同人的差异

## 建模和系统设计

* 类间差异有时候小于类内差异
* 样本少，因此往往不当多分类问题来做，而当做二分类问题做，给定一个人，判断是不是同一个人
* 需要数据标记，因此需要少的训练数据的算法往往更受青睐
* 不同摄像头间泛化能力弱
* 性能
* 长时间的re-id，人物可能换衣服，拿不同的东西，短时的特征会变得不可靠

## Video-based

* multi-shot：计算代价更高，问题更多
* pooling-based：聚合多帧特征为一个vector，可扩展性更高
* 建立索引
* stable region的stable可以加上时间的概念
* 在时间上做聚类
* 条件随机场（CRF）联合时空限制
* 在最后一层注入时间信息
* 喂入RNN记忆帧的时间流

## 评价标准：

* Rank-1 accuracy：匹配百分比，
* CMC curve：匹配的出现在rank的前x的百分比
* mAP: 不仅衡量是否匹配，还衡量匹配的图是否完整（即可能有多个匹配的图）

## 研究热点

* 寻找受环境影响少的feature representation
* ML优化Re-id模型

## 步骤

* 输入轨迹或包含行人的矩形（可能由视频监控系统生成）
* 提取图像特征，而不仅是像素点
* 构建一个可视化的feature representation，比如feature的柱状图
* 比较特征的相似度来匹配人物
* 匹配策略可能影响特征和超参数

## 特征表达

> 提取颜色，纹理，空间结构，容易可靠测量，不同人不同摄像头间这些特征都有一些区分度

* 通常结合多种视觉特征，做成特征直方图，给不同特征加权重，但特征越多越可能出现图像匹配出错
* 更进一步，会提取这样一些特征：
  * WH: weighted color histogram（RGB, YUV, HSV）
  * MSCR：maximally stable color regions
  * RHSP: recurrent high-structured patches
* re-id首先要行人检测，但行人检测的准确度不太能满足re-id的需求，如果没把行人圈出来，re-id的特征提取很容易受背景影响，所以很多工作会先尝试把行人抠出来
* 检测不同肢体部位来判断姿态，利用对称特点；
  * 将行人图像分解成多个身体部位，比较不同部位之间的相似度
* 捕捉人物身体3D特征，减小对衣着的依赖
* 拥挤场所精确行人检测很困难，捕捉行人之间的关系，re-id一群人
* `从视觉特征中提取语义用于re-id，比如发型，衣服风格`: mid-level feature represention

## 建模学习

* 在相关的摄像头之间做迁移，亮度迁移，姿态迁移，背景迁移等
* `距离测量`:找到一个量度差异的量，使得类中距离小，类外距离大：最近邻，信息理论，逻辑精度，概率相关，RankSVM
  * Match：最近邻算法，Mahalanobis距离函数(距离中加上权重)，KISSME（权重基础上引入概率）， ITML（引入信息理论，牺牲部分一致性使得距离度量与原始距离更接近），SVM，boosting, adaboost.
* 减少数据标记需求：半监督稀疏标记，迁移学习
* 把re-id当做推断问题来做，填充稀疏数据；条件随机场；
* 上下文：合并同一轨迹上的多个帧，集合分析， 考虑外部上下文比如人群，学习摄像机网络的拓扑， 减少匹配搜索空间减少出错率。
* 图像搜索：索引与哈希

## 实验环境与真实环境

* 当前数据集的局限
* 实际搜索空间巨大，会产生许多误判，需要结合环境中其他知识来筛选，或者摄像头拓扑推断人物出现在某一帧的可能性：同一个人在不同摄像头出现的时间表达了摄像头之间的距离，寻找人群活动模式的时空关系，
* `人群re-id`
* 基于服饰属性的特征描述
* 工程上应用re-id成果的比较少，关注：相关性，容量，可用性。使用GPU，结合轨迹

## Other Idea

* 多模态：结合红外信号，或者其他人的能量信号：运动时，能量的转移和消耗因人而异；利用声音信号
* Deep learning
  * 缺乏训练数据：大多数re-id数据集中，一个人只有两张图，因此大多数研究采用多张图的组合
  * 将一张图分成若干个部分，传给CNN
  * LSTM记忆身体组件的空间连接关系
  * 在不同层都做compare（性能不好）
  * 比较前先resize
  * identification比recognization更能充分利用标签

## 展望

* 性能评估
* 大数据在特征上建立索引
* re-ranking匹配结果
* 真实环境数据集上的准确率提高


# CVPR2016 Re-id

## Part-based-CNN

* DataSet: i-LIDS, PRID2011, VIPeR and CUHK01
* 同时调整特征提取和距离测量公式的参数
* 提取身体部分的特征和全身特征作为一个特征

![](/files/-L_G1DnCLq7qP0hhIhe_)

* 使用[triplet loss](http://blog.csdn.net/tangwei2014/article/details/46788025)，同时缩小类内差异，增大类间差异

## Spatial Constraints

* 提取身体部分特征和全身特征共同计算相似度

![](/files/-L_G1DnExnfgWGDUWPXR)

![](/files/-L_G1DnGuEHEdOlbZzoZ)

* dataset:VIPeR, GRID, 3DPES, Market-1501

## Pose-aware

![](/files/-L_G1DnIf8tnHqz6Gboi)

* 根据移动路径判断姿态，对其他姿态图像做推断，并用推断结果做match
* CUHK02 \[12] and VIPeR \[8], iLIDS-Vid \[19], PRID 2011 \[9] and 3DPeS \[3]

## Hierarchical Gaussian Descriptor

![](/files/-L_G1DnK7NosUJ_a7rDg)

* 对图片做切片，找一个能描述切片像素数据分布的高斯分布
* 逐级整合各个切片的高斯分布
* VIPeR \[12], CUHK01 \[20], GRID \[26], PRID450S \[33] and CUHK03 \[21].

## RNN-CNN

![](/files/-L_G1DnMI0MnaY6OD_vA)

* 两组图片，分别通过CNN+RNN得到特征
* 特征分别用identity目标做优化，用recognize目标做优化
* iLIDS-VID and PRID-2011

## Cross-Dataset Transfer Learning

* 用一个有标签数据集训练特征表达模型，分解出数据集共用的模式和数据集独有的模式
* 字典学习，找到各个数据集共用的字典
* 将模型应用在无标签无监督聚类上
* VIPeR PRID CUHK01 CAVIAR iLIDS

## domain-based-cnn

![](/files/-L_G1DnOfqeorz7Ocuke)

* 先将所有数据集混合在一起，训练CNN做分类
* 逐个去掉神经元，观察在不同数据集中损失函数的变化，变化少的说明这个神经元对这个domain没用
* 对每个神经元，根据神经元对loss的影响，设置一个因数据集不同而不同的dropout值
* 实现对不同数据集有不同的预测能力
* CUHK03，CUHK01，PRID，VIPeR (I cannot find the link to the original dataset. This is my previous backup version.)，3DPeS，i-LIDS (I cannot find the link to the original dataset. This is my previous backup version.)，Shinpuhkan (need to send an email to the authors)

## Video-Based-Top-Push-Multi-shot

* 多匹配
* 按匹配程度排序，避免因顺序或者帧刚好错开导致的低效匹配和误判
* PRID 2011 \[8] and iLIDS-VID

## Distance-metric:null-Foley-Sammon-transfer

* 将同个人的所有图的特征投射为一个点（同个类中的特征是可以被投射到一个点中的）
* 使得不同人的点的距离尽量远
* 将这个方法应用在re-id上
* VIPeR \[11], PRID2011 \[14], CUHK01 \[19], CUHK03 \[19] and Market1501

## 根据样本调节距离函数-SVM

* 学习一个SVM模型做分类，从中学到两个字典和一个映射矩阵，通过这三个东西，关联特征空间和权重空间
* 权重即为距离测量时用到的参数
* VIPER, QMUL GRID, PRID, CUHK01, CUHK03，OpeRID


# Camera topology and Person Re-id

## Our Method

* retrain the image classifier
* deep learning model
* 不依赖场景

## Monitoring Activities from Multiple Video Streams: Establishing a Common Coordinate Frame（LSCTM-19-18）

* 只比较同一时刻不同空间的图片
* 通过拼合地面来决定摄像头之间的重叠与关联

## Tracking Across Multiple Cameras With Disjoint Views（LSCTM-19-14）

* 融合space-time和appearance的概率公式做最终的预测
* 用appearance的相似来建立关联，从而学习出space-time模型
  * 找到appearance最相近的两个object进行时空迁移概率的学习
* 没有对图像分类器做进一步的优化
* 场景中有多个人物时，只有图像分类器生效
* 比较不同时间空间的图片

## A Stochastic Approach to Tracking Objects Across Multiple Cameras(LSCTM-19-10)

* 训练Markov时空模型
* 训练是通过识别一个人携带的红色球的运动来进行的（用球产生轨迹训练集）
* 预测时结合时空和图像分类器预测
* 没有重新训练图像分类器

## LEARNING A MULTI-CAMERA TOPOLOGY （LSCTM-19-11）

* Detect and Track object
* 场景中找到入口和出口
* 建立所有入口和出口结点的迁移拓扑结构
* Markov-train和HMM建立概率模型
* 没有重新训练图像分类器

## 总结 根据摄像头拓扑辅助Re-id，

* 在知道拓扑的情况下，可以用于对结果直接进行剪枝减少搜索空间，
* 在不知道拓扑的情况下，需要学习拓扑，
* 学习拓扑往往将时空模型表达为概率模型，用概率密度函数表示，常用Markov建模。
* 学习拓扑通常需要知道轨迹之间的关联，这种关联基本上也是用图像分类器来做，具体体现为tracking等。
* 概率模型的建模和最后辅助图像分类器预测都有许多概率的文章可以做。


# Deep transfer learning Person Re-id

[arxiv 2016](https://arxiv.org/abs/1611.05244) by Mengyue Geng, Yaowei Wang, Tao Xiang, Yonghong Tian

## Transfer Learning

旧数据训练得到的分类器，在新的数据上重新训练，从而在新数据上取得比较好的表现，新数据与旧数据有相似的地方，但具有不同的分布。

![](/files/-L_G1DcUPLsHVKRVobu9)

## Fine tuning一般步骤

这是InceptionV4的图示

![](/files/-L_G1DcWugj0_tunUQJ2)

* 移除Softmax分类层
* 换成与目标数据集输出维数相同的Softmax层
* 冻结靠近输入的卷积层
* 以较高的学习率训练分类层
* 以很低的学习率微调剩下的卷积层

## 论文核心模型

![](/files/-L_G1DcYIIwQXzuQzuUN)

几个创新点：

* 对于CNN输出的两张图的特征，使用了相同的dropout而非各自独立的随机dropout
* 使用了二分类加多分类两种loss，二分类用于判断两张图中的人是否相同，多分类用于描述两张图中各自的人物ID
* 分两阶段进行Fine tune，先微调多分类，再联合二分类和多分类进行微调，避免多分类网络不稳定对二分类的影响

## Unsupervised Transfer Learning

![](/files/-L_G1Dc_Hjo-JqB7aTI9)

### Self-training

* 将图片均分为两组（论文中是按摄像头划分的）
* 将B组中的每张图片，与A组中CNN输出相似度最高的图片归为一类，从而构造出多分类标签
* 喂入CNN训练
* 迭代多次

### Co-training

* 由于CNN输出的图片相似度不一定真的可靠，存在噪音，因此Self-training效果没有特别好
* 寻找一个互补的模型，将特征映射到另一个子空间中
* 将B组中的每张图片，与A组中子空间相似度最高的图片归为一类，构造多分类标签
* 喂入CNN训练
* 迭代多次

### Co-Model

* CNN计算得到深度特征： $$𝑦=\theta(𝑥)$$
* Learn a subspace defined by a dictionary D and a new representation Z in the subspace.
* $$(D^*, Z^*) = min\_{D,Z} ||Y-DZ||\_F^2 + \lambda\Omega(Z) s.t. ||d\_i||\_2^2 \leq 1$$
* 其中$$||Y-DZ||\_F^2$$是reconstruction error
* $$\Omega(Z) = \sum\_{i,j} W\_{ij}||z\_i - z\_j||\_2^2$$
  * 当$$y\_i$$和$$y\_j$$是最近邻时，$W\_{ij}$为1，否则为0
  * 从而最小化最近邻的representation z的差异

## Trick Result

* Softmax loss VS Multi loss： 76.6% -> 83.7%（Market1501）
* 一致的Dropout VS 随机的Dropout： 80.8%-> 83.7% （Market1501）
* Two-stepped VS one-stepped: 47.6%->56.3%(VIPeR)

## Supervised Transfer Learning Result

|   DataSet  | State of the art | Transfer |
| :--------: | :--------------: | :------: |
|   CUHK03   |       75.3       |   85.4   |
| Market1501 |       82.21      |   83.7   |
|    VIPeR   |       53.5       |   56.3   |
|    PRID    |       40.9       |   43.6   |
|   CUHK01   |       86.6       |   93.2   |

(表中都是top1准确率)

## Unsupervised Transfer Learning Result

| DataSet | State of the art | Transfer |
| :-----: | :--------------: | :------: |
|  VIPeR  |       33.5       |   45.1   |
|   PRID  |       25.0       |   36.2   |
|  CUHK01 |       41.0       |   68.8   |

## Compare with other unsupervised method

使用其他无监督方法进行实验对比

|      Method     | Top1 acc |
| :-------------: | :------: |
|  Self-training  |   42.8   |
|     SubSpace    |   42.3   |
|     Transfer    |   45.1   |
| CNN+AutoEncoder |   36.4   |
|   Adversarial   |   22.8   |

其中SubSpace为只使用Co-Model，不使用CNN模型，Self-training为只使用CNN模型，Transfer是两者结合的Co-training。

总体来说这种无监督的方法取得了比较好的效果，在小数据集上甚至超过了有监督的效果。

如果觉得我的文章对你有帮助，可以前往github点个[star](https://github.com/ahangchen/windy-afternoon)


# Evaluate

* Rank Accuracy：

  将gallery中的图片，按照与probe图片的相似度排序，如果在第X个之前就命中，则rankX命中数+1, rankX命中数/probe图片总数则为rankX acc，rankX有时又名为topK，当gallery中存在junk image时，命中junk image时跳过。
* mAP:

  信息检索中的mAP：

  对probe中的每个图片，计算一个AP，取平均为mAP，AP为P的均值，P的含义为，probe对应的目标中，第k个命中时，前k个结果中的命中率。

  举例：假设有两个主题，主题1有4个相关网页，主题2有5个相关网页。某系统对于主题1检索出4个相关网页，其rank分别为1, 2, 4, 7；对于主题2检索出3个相关网页，其rank分别为1,3,5。对于主题1，平均准确率为(1/1+2/2+3/4+4/7)/4=0.83。对于主题2，平均准确率为(1/1+2/3+3/5+0+0)/5=0.45。则MAP= (0.83+0.45)/2=0.64。

  在market1501的评估代码里有另一种计算mAP的方法（可见于[Liang Zheng的综述](https://arxiv.org/pdf/1610.02984v1.pdf)），是求Precision-Recall曲线的面积。这是用[Pascal VOC 2007(The PASCAL Visual Object Classes (VOC) Challenge)的算法](https://link.springer.com/content/pdf/10.1007%2Fs11263-009-0275-4.pdf)，计算的是Precision-Recal曲线下的面积，与信息检索领域计算每次命中时准确率的平均值不同


# Object Detection

* [RCNN](/ml/papers/detection/rcnn)
* [SPPNet](/ml/papers/detection/sppnet)
* [Fast RCNN](/ml/papers/detection/fast_rcnn)
* [Faster RCNN](/ml/papers/detection/faster_rcnn)
* [YOLO](/ml/papers/detection/yolo)
* [SSD](/ml/papers/detection/ssd)
* [YOLO2\&YOLO3](/ml/papers/detection/yolo23)
* [Other](/ml/papers/detection/other)


# 读论文系列·干货满满的RCNN

> 转载请注明作者：[梦里茶](https://github.com/ahangchen)

Object Detection，顾名思义就是从图像中检测出目标对象，具体而言是找到对象的位置，常见的数据集是PASCAL VOC系列。2010年-2012年，Object Detection进展缓慢，在DPM之后没有大的进展，直到CVPR2014，RBG大神（Ross Girshick）把当时爆火的CNN结合到Detection中，将PASCAL VOC上的准确率提高到53.7%，本文为你解读RBG的CVPR2014 paper：

> Rich feature hierarchies for accurate object detection and semantic segmentation

## Key insights

* 可以用CNN对图片局部区域做识别，从而判断这个局部是不是目标对象
* 在标记数据稀缺的情况下，可以用其他数据集预训练，再对模型进行fine tune

## RCNN Overview

![](/files/-MkuKuJ8Rgdj580fHAyw) 1. 输入图片 2. 通过selective search给出2k个推荐区域（region proposal）

> 检测问题的一个特点是，我们不仅需要知道一张图片中是否包含目标对象，而且需要知道目标对象所处位置，有几种方式，一种是回归图中检测框的位置\[38]，但是准确率很低，一种是用滑动窗口的方法将图片切割成很多小块，再对小块做分析，但是对于CNN来说，每经过一层pooling，感受野就会变小，RCNN采用了一个五层卷积的结构，要求输入至少是195x195的尺寸，用滑窗不能保证这个输入大小。

![](/files/-MkuKuJ9Qifg3vrJqo76)

> Selective search是一种比较好的数据筛选方式，首先对图像进行过分割切成很多很多小块，然后根据小块之间的颜色直方图、梯度直方图、面积和位置等基本特征，把相近的相邻对象进行拼接，从而选出画面中有一定语义的区域。关于Selective Search的更多信息可以查阅这篇论文：Recognition using Regions（CVPR2009）

1. 将每个推荐区域传入CNN提取特征
2. 为每个类训练一个SVM，用SVM判断推荐区域属于哪个类
3. 用NMS对同个类的region proposals进行合并
4. 用bounding box regressor对预测位置进行精细的修正，进一步提高精度

> 非极大值抑制（NMS）顾名思义就是抑制不是极大值的元素，搜索局部的极大值。这个局部代表的是一个邻域，邻域有两个参数可变，一是邻域的维数，二是邻域的大小。这里不讨论通用的NMS算法，而是用于在目标检测中用于提取分数最高的窗口的。例如在行人检测中，滑动窗口经提取特征，经分类器分类识别后，每个窗口都会得到一个分数。但是滑动窗口会导致很多窗口与其他窗口存在包含或者大部分交叉的情况。这时就需要用到NMS来选取那些邻域里分数最高（是行人的概率最大），并且抑制那些分数低的窗口。（转自知乎专栏：晓雷的机器学习笔记）

## 训练

从上面的Overview可以看出，需要训练的主要有两个部分，各个类共用的CNN和各个类单独的SVM。

### Network Structure

RCNN试了两种CNN框架，一种是Hinton他们在NIPS2012上发表的AlexNet：ImageNet Classification with Deep Convolutional Neural Networks

![](/files/-MkuKuJA4JXA1-K2zbRB) 这是一个五层卷积+三层全连接的结构，输入是224x224的图片，输出是1000维one-hot的类别，

一种是VGG16(Very Deep Convolu- tional Networks for Large-Scale Image Recognition)

![](/files/-MkuKuJBOo8avr95xuYZ) 这是两个网络的检测结果：

![](/files/-MkuKuJCNnbzkTSYBGgc) VGG16精度更高一些，但是计算量比较大，实时性不如AlexNet，方便起见我们下面都以AlexNet为基础进行分析。

### Supervised Pretraining

首先用ImageNet预训练，输入图片，输出为这张图片包含的目标对象的类别，而不涉及具体位置，因为ImageNet中没有bounding box信息。训练到AlexNet能够在分类任务上达到Hinton他们的精度之后，开始用检测数据做Fine tune。

### Domain Specific Fine Tuning

直接用ImageNet预训练得到的CNN在PASCAL VOC上表现肯定是不如人意的，接下来，用PASCAL VOC 的检测数据进行fine tune。 因为VOC有20个分类，在ILSVR2013的检测任务中，最后有200个分类，而ImageNet有1000个分类，首先要把最后的全连接分类层替换成目标任务输出个数+1（加一个背景类）的全连接层。输入数据用的是Selective Search得到的Region Proposals对应的bounding box，

在这里的Fine tune中，需要判定Region Proposal属于哪种目标分类，在VOC的训练集中，有bounding box和对应的分类标注，RBG他们是检查每个Region Proposal与训练集中bounding box的重叠率，如果Region Proposal和bounding box重叠率大于阈值（经过实验，选了0.5）,则认为这个Region Proposal的分类为bounding box对应的分类，并且用这个对应的bounding box作为Fine tune的输入。

但是这些输入大小不一，需要调整到目标输入尺寸224x224，在附录A中讨论了很多的预处理方法，

![](/files/-MkuKuJDUqnMufOSIyiS) A. 原图 B. 等比例缩放，空缺部分用原图填充 C. 等比例缩放，空缺部分填充bounding box均值 D. 不等比例缩放到224x224 实验结果表明B的效果最好，但实际上还有很多的预处理方法可以用，比如空缺部分用区域重复。

训练时，采用0.001的初始学习率（是上一步预训练的1/10），采用mini-batch SGD，每个batch有32个正样本（各种类混在一起），96个负样本进行训练。

### Object category classifiers

每个类对应一个Linear SVM二分类器(恩，很简单的一个SVM，没有复杂的kernel)，输入是CNN倒数第二层的输出，是一个长度为4096的向量，SVM根据这个特征向量和标签进行学习，调整权重，学习到特征向量中哪些变量对当前这个类的区分最为有效。

训练SVM的数据和Fine tuning训练CNN的数据有所不同，直接使用将PASCAL VOC训练集中的正样本，将与bounding box重叠率小于0.3的Region Proposals作为背景（负样本），这个重叠率也是调参比较出来的；另一方面，由于负样本极多，论文采用了hard mining技术筛选出了难分类负样本进行训练。不过这样的话，SVM和CNN的正负样本定义就不同了，SVM的正样本会少很多（那些重叠率大于0.5的bounding box就没用上了）。

> 附录B中解释，其实一开始RBG他们是用SVM的正负样本定义来Fine tune CNN的，发现效果很差。SVM可以在小样本上就达到比较好的效果，但CNN不行，所以需要用上更多的数据来Fine tune，重叠率大于0.5的Region Proposals的数据作为正样本，可以带来30倍的数据，但是加入这些不精准的数据的代价是，检测时位置不够准确了（因为位置有些偏差的样本也被当做了正样本）。
>
> 于是会有一个很自然的想法，如果有很多的精确数据，是不是可以直接用CNN加softmax输出21个分类，不用SVM做分类？RBG他们直接在这个分类方式上fine tune，发现这样做的准确率也很高（50.9%），但是不如用SVM做分类的结果（54.2%），一方面是因为正样本不够精确，另一方面是因为负样本没有经过hard mining，但至少证明，是有可能直接通过训练CNN来达到比较好的检测效果的，可以加快训练速度，并且也更加简洁优雅。

### Bounding-box regression

这部分是在附录C展开阐述的（CVPR篇幅限制）。首先，为每个类训练一个bounding box regressor，类似DPM中的bounding box regression，每个类的regressor可以为每个图输出一个响应图，代表图中各个部分对这个类的响应度。DPM中的Regressor则是用图像的几何特征（HOG）计算的；不同于DPM，RCNN-BB中这种响应度(activation)是用CNN来计算的，输入也有所不同，DPM输入是原图，输出是响应图（从而得到bbox的位置），RCNN-BB的Regressor输入是Region Proposals的位置和原图，输出是bounding box的位置。

定义一个region proposal的位置为$$P=(P\_x, P\_y, P\_w, P\_h)$$，x,y为region prosal的中心点，w,h为region proposal的宽高，对应的bounding box的位置为$$G=(G\_x,G\_y,G\_w,G\_h)$$，Regressor的训练目标就是学习一个P->G的映射，将这个映射拆解为四个部分：

$$\hat{G}\_x=P\_wd\_x(P)+P\_x$$

$$\hat{G}\_y=P\_hd\_x(P)+P\_x$$

$$\hat{G}\_w=P\_wexp(d\_w(P))$$

$$\hat{G}\_h=P\_hexp(d\_h(P))$$

其中，$$d\_*(P)$$是四个线性函数，输入为P经过前面说的fine tune过的CNN后得到的pool5特征，输出为一个实数，即$$d\_*(P) = w^T\_\*\phi\_5(P)$$

训练就是解一个最优化问题，求出四个w向量，使得预测的G和真实的G相差最小，用差平方之和代表距离，化简后的形式为：

$$w\_x = argmin\_{\hat{w}*\*} \sum\_i^N(t**^i-\hat{w}\_*^T\phi\_5(P^i))^2 + \lambda||\hat{w}\_\*||^2$$

其中，

$$t\_x = (G\_x - P\_x)/P\_w$$

$$t\_y = (G\_y - P\_y)/P\_h$$

$$t\_w = log(G\_w/P\_w)$$

$$t\_h = log(G\_h/P\_h)$$

跟前边的四个映射是对应的， 同时加上了$$\lambda||\hat{w}\_\*||^2$$，对w的l2正则约束，抑制过拟合

训练得到四个映射关系后，测试时用这四个映射就能够对预测的Region Proposals位置做精细的修正，提升检测框的位置准确率了。

> 至此，整个训练和测试过程就介绍完毕了。

### 玄学时间

在论文中还打开RCNN中卷积层分析它们的功能，在AlexNet的论文中，Hinton已经用可视化的方式为我们展示了第一层卷积描述的是对象的轮廓和颜色，但后面的层因为已经不能表示成图像，所以不能直接可视化，RBG的方法是，输入一张图片的各个区域，看pool5（最后一层卷积层的max pooling输出）中每个单元的响应度，将响应程度高的区域框出来：

![](/files/-MkuKuJEcDKE6rHs47Tc) pool5的feature map大小为6x6x256，图中每行的16张图代表一个unit响应度最高的16张图，将每张图响应度较高的区域用白色框框出来了，这里只挑了6个unit进行展示（所以只有6行）。一个unit是6x6x256的张量中的一个实数，这个数越大，意味着对输入的响应越高。

可以看到不同的unit有不同的分工，第一行的unit对person响应度比较高，第二行的unit对dog和dot array（点阵）的响应度比较高，可以从这个角度出发，用每个unit充当单独的一种object detector。

附录D中还有更多的可视化结果

![](/files/-MkuKuJF_RHpX5x29hJR) 之所以说是玄学是因为，虽然这种可视化一定程度上体现了CNN学习到的东西，但是仍然没有说明白为什么是这个单元学习到这种信息。

## Summary

RCNN第一次把CNN结合Region proposal用到了detection任务中，取得了很好的效果，在这篇论文里，还体现了很多视觉深度学习的流行技巧，比如Pretrain，Fine tune，传统方法与深度学习结合（分割+检测，CNN+SVM，Bounding box regression），可以说是相当值得一读的好paper了。


# 读论文系列·SPP-net

本文为您解读SPP-net:

> Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition

## Motivation

神经网络在计算机视觉方面的成功得益于卷积神经网络，然而，现有的许多成功的神经网络结构都要求输入为一个固定的尺寸（比如224x224,299x299），传入一张图像，需要对它做拉伸或者裁剪，再输入到网络中进行运算。

![](/files/-MkuKt4TevvyF3NY5RUN) 然而，裁剪可能会丢失信息，拉伸会使得图像变形，这些因素都提高了视觉任务的门槛，因此，如果能有一种模型能够接收各种尺度的输入，应当能够让视觉任务更加容易完成。

## 什么限制了输入的尺寸

深度卷积神经网络中的核心组件有两个，一个是CNN，一个是全连接层，卷积是用filter在图像上平移与图像的局部进行逐位乘法，多个filter则产生多个feature map（特征/特征图），然后可以用pooling操作进一步采样，得到更小的feature map；实际上，我们并不在意feature map有多大，不同图像的feature map完全可以有不同的尺寸；但是在后边的具体任务中，比如分类任务，为了输出softmax对应的one-hot层，需要输出固定的尺寸，为了让不同的输入能共用一套权重参数，要求全连接层的输入尺寸是一致的，逆推回去也就限制了feature map的大小必须一致；而不同尺寸的输入图片在使用同一套卷积核（filter）的时候，会产生不同尺寸的feature map，因此才需要将不同尺寸的输入图片通过裁剪、拉伸调整为相同的尺寸。

## Solution

因此突破口有两个，

* 让卷积层能为不同尺寸的输入产生相同尺寸的输出（SPP）
* 让全连接层能为不同尺寸的输入产生相同尺寸的输出（全卷积）

全卷积和卷积的区别在于最后不是用全连接层进行分类， 而是用卷积层，假设我们要将一个16x16的feature map转为10x1的one-hot分类，则可以使用10个1x1卷积核，每个卷积核对应一个分类，参数数量少了很多，但是…实验结果表明还挺有效的，并且，全卷积+反卷积开辟了图像分割的新思路，可以说是一个开创新的工作了，感兴趣的同学可以看[这篇博客](http://blog.csdn.net/taigw/article/details/51401448)

这里我们详细讲一下SPP

![](/files/-MkuKt4UU8atvyp19A69) SPP中SP（Spatial Pyramid）的思想来源于SPM（Spatial Pyramid Matching），可以参考[这篇文章](http://blog.csdn.net/jwh_bupt/article/details/9625469)，正如论文Conclusion中说的， Our studies also show that many time-proven techniques/insights in computer vision can still play important roles in deep-networks-based recognition.

SPM是在不同的分辨率（尺度）下，对图片进行分割，然后对每个局部提取特征，将这些特征整合成一个最终的特征，这个特征有宏观有微观（多尺度金字塔），保留了区域特性（不同的区域特征不同），然后用特征之间的相似度进行图片间的匹配（matching）。先前我们提到过，每个filter会得到一个feature map，SPP的输入则是卷积后的这些feature map，每次将一个feature map在不同尺度下进行分割，尺度L将图片分割为$$2^L$$个小格子（其实格子数也可以自己定，不一定要分成$$2^L$$个），L为0代表全图；对每个小格子的做pooling，论文中是max pooling, 实际中也可以用其他，这里不像SPM需要做SIFT之类的特征提取，因为feature map已经是卷积层提取过的特征了，将pooling得到的结果拼接起来，就可以得到固定尺寸的feature map。

![](/files/-MkuKt4W8yJk3a9Ms_i9) 举个例子，一个具有256个filter的卷积层，输出了256个feature map，对于一个640x320的图片，输出的feature map可能是32x16的，对于一个640x640的图片，输出的feature map可能是32x32的，对256个feature map中的每个feature map，我们在4个尺度下对它们做切割，在最粗糙的尺度下切为1个图，次之切为2个子图，接下来是4个子图，8个, 对每个子图做max pooling，得到其中最大的数，放到最终的特征里，可以得到一个1+2+4+8=15这么长的特征，256个feature则可以得到最终256\*15这么长的特征，可以看到，最终的特征尺寸只跟卷积层结构和SP尺度L有关，跟输入图片无关，从而保证了对不同尺寸的图片都输出一样大小的特征。

其实看到这里，你可能发现了，对不同尺寸输出相同尺寸特征这个特性，是由pooling操作决定的，像max pooling，sum pooling这些，就是将多个输入聚合为一个值的运算；而Spatial Pyramid只是让特征有更好的组织形式而已。当然，能找到这种有效的特征组织形式也是很值得肯定的。但这里有东西仍然值得商榷，max pooling实际上还是丢了一些信息，虽然通过多层的特征可以将这些信息弥补回来。

## 实验

然后作者就将这个结构应用到各种网络结构和各种任务里了，并且都取得了很好的效果（说的轻巧，复现一堆论文，改源码，跑大量实验，一定超级累）；特别是在检测任务对RCNN的改进上，这个地方比较有意思。在RCNN中，需要将每个Region Proposal输入卷积层判断属于哪个分类，而region proposal是方形的，这就导致有很多区域做了重复的卷积运算。

在SPP-net的实验中，

* 整张图只过一遍卷积层，从conv5得到整张图对应的feature map；
* 然后将feature map中每个region proposal对应的部分提取出来，这个位置计算量也不小，但比算卷积本身还是要快很多，原图中的一个区域唯一对应于feature map中的一个区域，不过feature map中的一个区域实际上对应原图的范围（所谓感受野）要大于region proposal所在区域，从这个意义上来讲，依然是接收了更多不相关信息，但是好在没有裁剪或变形；
* 由于region proposal形状不一，对应的feature map尺寸也不一致，这时SPP就能充分发挥其特性，将不同尺寸的feature map转为尺寸一致的feature，传给全连接层进行分类
* 原图实际上可以保持原图的宽高比缩放到多种尺度（文中将宽或高缩放到{480, 576, 688, 864, 1200}这五个尺寸，），分别算一个特征，将不同尺度的特征拼接起来进行分类，这种combination的方式能一定程度上提高精度
* 这里还有一个小trick，可以将原图缩放到面积接近的范围（文中是224x224），再输入到网络中，进一步提升精度，至于原因…文中没有提，玄学解释是，输入的尺度更接近，模型训练更容易吧。

由于整张图只过了一遍卷积，所以比原来的RCNN快了很多，准确率也不差

![](/files/-MkuKt4X5sXTRveRGH8v)

## Summary

严格来讲SPP-net不是为detection而生的模型，但是SPP-net为RCNN进化到Fast-RCNN起了很大的借鉴作用，值得一读。SPP-net的想法很有意思，SPP（Spatial Pyramid Pooling）是对网络结构的一种改进，可能因为是华人写的论文，感觉很好读，含金量个人感觉没有RCNN或者DPM的论文高，但是实验很丰富，从分类任务和检测任务上的各种网络结构证明SPP的有效性


# 读论文系列·Fast RCNN

Fast RCNN是对RCNN的性能优化版本，在VGG16上，Fast R-CNN训练速度是RCNN的9倍, 测试速度是RCNN213倍；训练速度是SPP-net的3倍，测试速度是SPP-net的3倍，并且达到了更高的准确率，本文为您解读Fast RCNN。

## Overview

Fast rcnn直接从单张图的feature map中提取RoI对应的feature map，用卷积神经网络做分类，做bounding box regressor，不需要额外磁盘空间，避免重复计算，速度更快，准确率也更高。

## Related work

### RCNN缺点

* Multi-stage training

需要先预训练卷积层，然后做region proposal, 然后用SVM对卷积层抽取的特征做分类，最后训练bounding-box回归器。

* 训练时间和空间代价很大 训练过程中需要把CNN提取的特征写到磁盘，占用百G级的磁盘空间，GPU训练时间为2.5GPU\*天（用的是K40，壕，友乎？）
* 目标检测很慢 Region proposal很慢，VGG16每张图需要花47秒的时间

总结：RCNN就是慢！最主要的原因在于不同的Region Proposal有着大量的重复区域，导致大量的feature map重复计算。

### SPP-net

SPP-net中则提出只过一遍图，从最后的feature map裁剪出需要的特征，然后由Spatial pyramid pooling层将其转为固定尺寸特征，由于没有重复计算feature map，训练速度提升3倍，测试速度提升了10-100倍。

但是SPP-net也有缺点，SPP-net的fine-tune不能越过SPP层，因为pyramid BP开销太大了，只能fine-tune全连接层，tune不到卷积层，所以在一些较深的网络上准确率上不去。

## Fast RCNN architecture

![](/files/-MkuKxWyjGezejo1RJ25)

* 首先将整个图片输入到一个基础卷积网络，经过max pooling得到整张图的feature map，
* 然后用一个RoI pooling层为region proposal从feature map中提取一个**固定长度**的特征向量，
* 每个特征会输入到一系列全连接层，得到一个RoI特征向量，
* 再分叉传入两个全连接层输出，
  * 其中一个是传统softmax层进行分类，
  * 另一个是bounding box regressor，
* 根据RoI特征向量（其中包含了原图中的空间信息）和原来的Region Proposal位置回归真实的bounding box位置（如RCNN做的那样，但是是用神经网络来实现）。

其他都是一目了然的，接下来主要讲RoI pooling

## RoI pooling

我们可以根据卷积运算的规则，推算出原图的region对应feature map中的哪一部分。但是因为原图region的大小不一，这些region对应的feature map尺寸是不固定的，RoI pooling就是为了得到固定大小的feature map。

* RoI pooling层使用max pooling将不同的RoI对应的feature map转为固定大小的feature map。
  * 首先将h  *w的feature map划分为H*  W个格子
  * 对每个格子中的元素做max pooling

由于多个RoI会有重复区域，所以max pooling时，feature map里同一个值可能对应pooling output的多个值。所以BP算梯度的时候，从RoI pooling层output y到input x的梯度是这样求的

![](/files/-MkuKxX1qDh2ZmYr4plE) 其中

* $$i ^{\*}(r, j) = argmax\_{i'∈R(r,j)  }x\_{i'}$$，也就是在$$R(r, j)$$这个区域中做max pooling得到的结果，
* $$i =  i ^{ \* }(r, j)$$ 是一个条件表达式，就是判断input的$$x\_{i}$$是否是max pooling的结果，如果不是，输出的梯度就不传到这个值上面
* r是RoI数量，j是在一个region中，与x对应的输出个数
* $$y\_{rj}$$是第j个跟x对应的输出

举例：

![](/files/-MkuKxX4q2xjl2ka-4Za)

> 也就是说，将Loss对输出的梯度，传回到max pooling对应的那个feature unit上，再往回传

其实这是SPPnet的一个特例，是Spatial pooling，没有pyramid，也因此计算量大大减少，能够实现FC到CNN的梯度反向传播，并且，实验发现其实feature pyramid对准确率提升不大。倒是原图层面的Pyramid作用大些：

* 训练时，为每张训练图片随机选一个尺度，缩放后扔进网络学
* 测试时，用image pyramid为每张测试图片中的region proposal做尺度归一化（将它们缩放到224x224）

但是这种Pyramid方法计算代价比较大，所以Fast RCNN中只在小模型上有这样做

## Multi-task loss

* $$L\_{cls}$$:  SoftMax多分类Loss，没啥好说的
* $$L\_{loc}$$：bounding box regression loss

定义真实的bounding box为($$v\_{x}, v\_{y}, v\_{w}, v\_{h}$$)，预测的bounding box位置（由第二个fc层输出，有K个类，每个类分别有4个值，分别为）$$t\_{x}^{k}，t\_{y}^{k}，t\_{w}^{k}, t\_{h}^{k}$$

$$L\_{loc}(t^{k}, v) = ∑*{i∈{x,y,w,h}} smooth*{L1}(t\_{i}^{k} - v\_{i})$$

即预测位置和真实位置四个值的差值求和，其中

$$smooth\_{L1}(x) = 0.5x^{2} if |x|<1 otherwise |x|-0.5$$

是一个软化的L1（画一下图像可以看出来，在(-1,1)的范围内是抛物线，没L1那么尖锐），如果采用L2 loss，需要仔细调节学习率防止梯度爆炸。

![](/files/-MkuKxX6E9aLxr19whNu) 整个模型的Loss就是：

$$L(p, k, t^{k}, v) = L\_{cls}(p, k) + λ|k ≥ 1| L\_{Ioc}(t^{u}, v)$$

* p代表预测类别，k代表真实类别
* k≥1意味着不算0类（也就是背景类）的bounding box loss，因为背景的bounding box没啥意义
* λ是超参数，在论文的实验中设为1

## 训练

* ImageNet预训练
* 最后一层换成RoI pooling层
* FC+sofmax分类层换成两个FC，分别求softmax分类和bounding box回归loss，每个类有自己的bounding box regressor
* 用Detection数据BP微调整个神经网络

> 这就比较厉害了，谁不喜欢end to end，之前RCNN是需要分开微调SVM分类层和bounding box regressor的

前面讲了RoI pooling使得梯度反向传播到卷积层成为可能，但是这种BP训练仍然很耗显存和时间，尤其是在输入的ROI属于不同图片时，因为单张图的feature map是不存到磁盘的，当一张图的几个RoI和其他图的几个RoI混在一起交替输入时，需要反复前向传播计算feature map，再pooling，实际上也就反复计算了feature map。

在Fast RCNN的训练中，每次输入两张图（这么小的batch size），每张图取64个ROI，单张图的多个ROI在前向计算和后向传播过程中是共享feature map的的，这样就加快了训练速度。

然而，这犯了训练中的一个忌讳，实际上，相当于训练数据（ROI）没有充分shuffle，但在Fast RCNN的实验中效果还行，就先这样搞了。

### 样本筛选

* 正样本：与bounding box有超过50%重叠率的
* 负样本（背景）：与bounding box重叠率位于0.1到0.5之间的。

### Truncated SVD加速全连接层运算

* Truncated SVD

$$W ≈ U∑\_{t}V^{T}$$

将$$u×v$$大小的矩阵W分解为三个矩阵相乘，其中，$$U$$是一个$$u×t$$的矩阵，包含$$W$$的前$$t$$个左奇异向量，$$∑\_{t}$$是一个$$t×t$$的对角矩阵，包含$$W$$的前$$t$$个上奇异向量，$$V^{T}$$是一个$$v\*t$$的矩阵，包含$$W$$的前$$t$$个右奇异向量，参数数量从$$uv$$变成$$t(u+v)$$，当$$t$$远小于$$min(u,v)$$时，参数数量就显著少于$$W$$。

具体实现上，将一个权重为W的全连接层拆成两个，第一层的权重矩阵为∑\_{t}V^{T}（并且没有bias），第二层的权重矩阵为U（带上W原来的bias）。

在Fast RCNN中，Truncated SVD减少了30%的训练时间。

## 实验结果

### PK现有方法

* 在VOC12上取得65.7%的mAP，是当时的SOA
* 在VGG16上，Fast R-CNN训练速度是RCNN的9倍, 测试速度是RCNN213倍；
* 训练速度是SPP-net的3倍，测试速度是SPP-net的3倍

### 创新点必要性验证

* RoI pooling是否比SPP更优？（是否有fine tune卷积层的必要？）
  * 使用VGG16，在Fast RCNN中冻结卷积层，只fine tune全连接层：61.4%
  * 使用VGG16，在Fast RCNN中fine tune整个网络：66.9%
* Multi Loss(Softmax + bb regressor)是否比Single task（只用Softmax loss）更优？stage-wise和Multi Task同时进行(end2end)哪个更优？

在VOC07上，end2end + bb regressor > stage-wise+ bb regressor > end2end

* Image Pyramid是否必须？ 实际上，使用Pyramid在Fast RCNN上只提升了1%左右，所以这个也没被列为正式的创新点
* 如果用SVM来分类会不会更好？

![](/files/-MkuKxXBlYxUYtfJ0wen) S M L是由浅到深的三个网络，可以看到，只用Softmax分类也能达到不错的效果，在网络比较深的情况下，也有超越SVM的可能。

* 模型泛化能力

  一个模型如果能够在更多训练数据的条件下学到更好的特征分布，这个模型效果越好，RBG用VOC12去训练Fast RCNN，然后在VOC07上测试，准确率从66.9%提升到70.0%。在其他组合上也取得了提升。

注意，在训练更大的数据的时候，需要更多的iteration，更慢的learning rate decay。

* 使用更多的Region Proposal效果会不会更好？（不是很想讲这方面，太玄学）

![](/files/-MkuKxXCNqT_O5lxWzEZ) 图中红色线表示Average Recall，通常人们用Average Recall来评价Region Proposal的效果，然而，proposals越多，AR这个指标一直往上涨，但实际上的mAP并没有上升，所以使用AR这个指标比较各种方法的时候要小心，控制proposal的数量这个变量不变。

图中蓝色线表示mAP(= AVG(AP for each object class))，可以看到，

* 太多的Region Proposal反而会损害Fast RCNN的准确度
* DPM使用滑动窗口+层次金字塔这种方法提供密集的候选区域，用在Fast RCNN上略有下降。


# 读论文系列·Faster RCNN

> 转载请注明作者：[梦里茶](https://github.com/ahangchen)

Faster RCNN在Fast RCNN上更进一步，将Region Proposal也用神经网络来做，如果说Fast RCNN的最大贡献是ROI pooling layer和Multi task，那么RPN（Region Proposal Networks）就是Faster RCNN的最大亮点了。使用RPN产生的proposals比selective search要少很多（300vs2000）,因此也一定程度上减少了后面detection的计算量。

## Introduction

Fast RCNN之后，detection的计算瓶颈就卡在了Region Proposal上。一个重要原因就是，Region Proposal是用CPU算的，但是直接将其用GPU实现一遍也有问题，许多提取规则其实是可以重用的，因此有必要找一种能够共享算力的GPU版Region Proposal。

Faster RCNN则是专门训练了一个卷积神经网络来回归bounding box，从而代替region proposal。这个网络完全由卷积操作实现，并且引入anchor以应对对象形状尺寸各异的问题，测试速度与Fast RCNN相比速度极快。

这个网络叫做region proposal layer.

## RPN

![](/files/-MkuKwkCFMDRRUb81n4k) 训练数据就是图片和bounding box

* 输入任意尺寸的图片，缩放到1000×600
* 输入到一个基础卷积神经网络，比如ZF或者VGG，以ZF为例，得到一个51×39的feature map
* 用一个小的网络在feature map上滑窗，算每个3x3窗口的feature，输出一个长度为256的向量，这个操作很自然就是用3×3卷积来实现，于是可以得到一个51×39×256的feature map
* 每个256向量跟feature map上一个3×3窗口对应，也跟800×600的原图上9个区域相对应，具体讲一下这个9个区域：

  * 卷积后feature map上的每个3x3的区域对应原图上一个比较大的感受野，用ZF做前面的卷积层，感受野为171×171，用VGG感受野为228×228

  * 我们想用feature map来判断它的感受野是否是前景，从而将感受野作为proposal，但是对象并不总是正方形的，于是我们需要对感受野做一个替换，得到多种形状的proposal

  * 我们让每个3x3的区域（图中橙色方格）和原图上九个区域相对应，这九个区域的中心（灰色方格）就是感受野的中心

  * 九个区域有九种尺寸分别是

  > 128x128 128x64 64x128
  >
  > 256x256 256x128 128x256
  >
  > 512x512 512x256 256x512

  * 这九个区域我们也成为9个anchor，或者9个reference box
  * 如此，每个特征就能和原图上形状和尺寸各异的区域对应起来了
* 回到刚刚的256向量，将这个向量输入一个FC，得到2x9个输出，代表9个anchor为前景还是背景的概率
  * 学习用的标签设置：如果anchor与真实bounding box重叠率大于0.7，就当做是前景，如果小于0.3，就当做背景
* 将256向量输入另一个FC，得到4x9个输出，代表9个anchor的修正后的位置信息(x,y,w,h)
  * 学习用的标签就是真实的bounding box，用的还是之s前Faster RCNN的bounding box regression

> 两个FC在实现的时候是分别用两个1x1卷积实现的 ![FC](https://upload-images.jianshu.io/upload_images/1828517-c23f035a21b10c24.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240) 以橙色为例，256向量和W1矩阵相乘，得到长度为18的向量，这样的操作在51x39个feature都要做一遍，实现起来就很自然变成了用一个1x1的卷积核在feature map上做卷积啦，这样也暗含了一个假设，不同位置的slide window对于anchor的偏好是相同的，是一个参数数量与精度的权衡问题。

* 于是我们会得到图片上51x39x9≈20K个anchor为前景的概率，以及修正后的位置

上面这个过程可以完全独立地训练，得到一个很好的Region Proposal Network

理论上我们可以用上面这个流程去训练RPN，但训练RPN的时候，一个batch会直接跑20K个anchor开销太大了。

* 因此每个batch是采一张图里的256个anchor来训练全连接层和卷积层；
* 这256个anchor里正负样本比例为1:1，正样本128个，负样本128个，
* 如果正样本不足128个，用负样本填充，这也意味着并非所有的背景anchor都会拿来训练RPN，因为前景的anchor会远少于背景的anchor，丢掉一些背景anchor才能保证样本平衡，丢背景anchor的时候是以slide window为单位丢的，下面会说明。
* 具体实现上，先算所有anchor，再算所有anchor与bounding box的重叠率，按重叠率区分正负样本，然后选择batch中的256个anchor，参与训练。同一张图会多次参与训练，直到图中的正anchor用完。

因此最终的一个mini batch的训练损失函数为：

![](/files/-MkuKwkDiU5UW7gsZE8y) 其中，

* $$p\_{i}$$是一个batch中的多个anchor属于前景/后景的预测概率向量，$$t\_{i}$$是一个batch中正anchor对应的bounding box位置向量
* $$L\_{cls}$$是softmax二分类损失
* $$L\_{reg}$$跟Fast RCNN中的bounding box regression loss一样，乘一个$$p\_{i}\*$$ ，意味着只有前景计算bounding box regression loss
* 论文中说$$N\_{cls}$$为256，也就是mini-batch size，$$N\_{reg}$$约为256 \* 9=2304（论文中说约等于2400）,这意味着一对p对应9个t，这种对应关系也体现在全连接层的输出个数上，由于两个task输出数量差别比较大，所以要做一下归一化。

> 但这就意味着loss中的mini-batch size是以3x3的slide window为单位的，因为只有slide window和anchor的个数才有这种1:9的关系，而挑选训练样本讲的mini-batch size却是以anchor为单位的，所以我猜实际操作是这样的：
>
> * 先选256个anchor，
> * 然后找它们对应的256个slide window，
> * 然后再算这256个slide window对应的256×9个anchor的loss，每个slide window对应一个256特征，有一个$$L\_{cls}$$，同时对应9个anchor，有9个$$L\_{reg}$$

论文这里讲得超级混乱，可以感受下：

![](/files/-MkuKwkEU0Y7u2EuUYoO)

## Proposal layer

其实这也可以算是RPN的一部分，不过这部分不需要训练，所以单独拉出来讲

* 接下来我们会进入一个proposal layer，根据前面得到的这些信息，挑选region给后面的fast rcnn训练
  * 图片输入RPN后，我们手头的信息：anchor，anchor score，anchor location to fix
  * 用全连接层的位置修正结果修正anchor位置
  * 将修正后的anchor按照前景概率从高到底排序，取前6000个
  * 边缘的anchor可能超出原图的范围，将严重超出边缘的anchor过滤掉

![](/files/-MkuKwkFwdtP9zd14AyW)

* 对anchor做非极大抑制，跟RCNN一样的操作
* 再次将剩下的anchor按照anchor score从高到低排序（仍然可能有背景anchor的），取前300个作为proposals输出，如果不足300个就…也没啥关系，比如只有100个就100个来用，其实不足300个的情况很少的，你想Selective Search都有2000个。

## Fast RCNN

接下来就是按照Fast RCNN的模式来训练了，我们可以为每张图前向传播从proposal\_layer出来得到最多300个proposals，然后

* 取一张图的128个proposal作为样本（有正有负），一张图可以取多次，直到proposal用完

![](/files/-MkuKwkGKfrSS29YywJV)

* 喂给Fast RCNN做分类和bounding box回归，这里跟RPN很像，但又有所不同，
  * BB regressor：拟合proposal和bounding box，而非拟合anchor和bounding box
  * Classifier：Object多分类，而非前景背景二分类

## 迭代训练

RPN和Fast RCNN其实是很像的，因此可以一定程度上共享初始权重，实际训练顺序如下（MATLAB版）： 1. 先用ImageNet pretrain ZF或VGG 2. 训练RPN 3. 用RPN得到的proposal去训练Fast RCNN 4. 用Fast RCNN训练得到的网络去初始化RPN 5. 冻结RPN与Fast RCNN共享的卷积层，Fine tune RPN 6. 冻结RPN与Fast RCNN共享的卷积层，Fine tune Fast RCNN

论文中还简单讲了一下另外两种方法：

* 将整个网络合起来一块训练，而不分步，但由于一开始训练时RPN还不稳定，所以训练Fast RCNN用的proposal是固定的anchor，最后效果差不多，训练速度也快。

![](/files/-MkuKwkHlP66aaBGz_vw)

* 整个网络合起来一起训练，不分步，训练Fast RCNN用的proposals是RPN修正后的anchor，但这种动态的proposal数量不好处理，用的是一种RoI warping layer来解决，这又是另一篇论文的东西了。

## SUMMARY

网络结构和训练过程都介绍完了，实验效果也是依样画葫芦，就不再介绍了，整体来说，Faster RCNN这篇论文写得很乱，很多重要的细节都要去看代码才能知道是怎么回事，得亏是效果好才能中NIPS。。


# 读论文系列·YOLO

> CVPR2016： You Only Look Once:Unified, Real-Time Object Detection

转载请注明作者：[梦里茶](https://github.com/ahangchen)

![](/files/-MkuKwIRNLkWrByZBejA) YOLO，You Only Look Once，摒弃了RCNN系列方法中的region proposal步骤，将detection问题转为一个回归问题

## 网络结构

* 输入图片：resize到448x448
* 整张图片输入卷积神经网络（24层卷积+2层全连接，下面这张示意图是Fast YOLO的）

![](/files/-MkuKwISZKlVtEhHHyx-)

* 将图片划分为$$S\*S$$个格子，$$S=7$$
* 输出一个$$S\*S$$大小的class probability map，为图片上每个格子所属的分类

![](/files/-MkuKwIUOy6c9DE8yTYW)

* 输出为每个格子输出B个bounding box，每个bounding box由x,y,w,h表示，为每个bounding box输出一个confidence，即属于前景的置信度

> 于是输出可以表示为一个$$S*S*(B\*(4+1)+C)$$的tensor，训练只需要根据数据集准备好这样的tensor进行regression就行

* 对所有bounding box按照confidence做非极大抑制，得到检测结果

## 训练

### Loss

![](/files/-MkuKwIV2VpwUBIaXIQY)

* 前两行为定位loss，$$\lambda\_{coord}$$为定位loss的权重，论文中取5
* 第三行为一个bounding box属于前景时的置信度回归loss，
  * 当格子中有对象出现时，真实$$C\_{i}$$为1，
  * $$1\_{ij}^{obj}$$是一个条件表达式，当bounding box“负责(is responsible for)”图中一个真实对象时为1，否则为0，
  * 所谓“负责”，指的是在当前这个格子[前向传播（论文里没讲，有代码依据）](https://github.com/pjreddie/darknet/blob/master/src/detection_layer.c#L120)预测出的所有bounding box中，这个bounding box与真实的bounding box重叠率最大
* 第四行为一个bounding box属于背景时的置信度回归loss，
  * 为了避免负样本过多导致模型跑偏， $$\lambda\_{noobj}=0.5$$，
  * $$1\_{ij}^{noobj}$$是一个条件表达式，为$$1\_{ij}^{obj}$$取反
  * 于是我们可以发现一个格子的两个bounding box的分工：一个贡献前景loss，一个贡献背景loss ，不论是前景背景box，我们都希望它们的confidence接近真实confidence，实际上，如果 $$\lambda\_{noobj}=1$$， 第四五行可以合并为一项求和，但由于背景box太多，所以才单独拆开加了权重约束
* 第五行为分类loss，$$1\_{i}^{obj}$$是一个条件表达式，当有对象出现在这个格子中，取1，否则取0

YOLO里最核心的东西就讲完了，其实可以把YOLO看作固定region proposal的Faster RCNN，于是可以省掉Faster RCNN里region proposal部分，分类和bounding box regression跟Faster RCNN是差不多的

## 细节

### Leaky Relu

网络中只有最后的全连接层用了线性的激活函数，其他层用了leaky Relu：$$f(x)=max(x, 0.1x)$$

对比Relu和leaky Relu

![](/files/-MkuKwIXorhs8_acKLkl) ![](/files/-MkuKwIY2OCvYaQLsp8x) 在x小于0的时候，用了0.1x，避免使用relu的时候有些单元永远得不到激活（Dead ReLU Problem）

### Fast YOLO

卷积层更少，只有9层卷积+2层全连接，每层filters也更少，于是速度更快

## 实验效果

* 对比当前最好方法：

![](/files/-MkuKwI_4ek0Ez1S3upg) Fast YOLO速度最快，准确率不太高，但还是比传统方法好，YOLO则比较中庸，速度不慢，准确率也不太高，但也还行。

* 再看看具体是在哪些类型的图片上出错的：

![](/files/-MkuKwIbxSHl51k47evv) 主要是定位不准（毕竟没有精细的region proposal），但是在背景上出错较少（不容易把背景当成对象）

## 缺点

* 固定的格子是一种很强的空间限制，7x7的格子决定了整张图片最多预测98个对象，对于对象数量很多的图片（比如鸟群）无能为力
* 难以泛化到其他形状或角度的物体上
* 损失函数没有考虑不同尺寸物体的error权重，大box权重和小box权重一样

## Summary

Anyway，YOLO结构还是挺优雅的，比Faster RCNN黑科技少多了，更重要的是，它是当时最快的深度学习检测模型，也是很值得肯定的。


# 读论文系列·SSD

转载请注明作者：[梦里茶](https://github.com/ahangchen)

> Single Shot MultiBox Detector

## Introduction

一句话概括：SSD就是关于类别的多尺度RPN网络

基本思路：

* 基础网络后接多层feature map
* 多层feature map分别对应不同尺度的固定anchor
* 回归所有anchor对应的class和bounding box

## Model

![](/files/-MkuKyEbbfe4OIH5BDY5)

* 输入：300x300
* 经过VGG-16（只到conv4\_3这一层）
* 经过几层卷积，得到多层尺寸逐渐减小的feature map
* 每层feature map分别做3x3卷积，每个feature map cell(又称slide window)对应k个类别和4个bounding box offset，同时对应原图中6（或4）个anchor(又称default box)
  * 38x38, 最后3x3, 1x1三个feature map的每个feature map cell只对应4个anchor，分别为宽高比: 1:1两种，1:2, 2:1两种，因此总共有 38 x 38 x 4 + 19 x 19 x 6 + 10 x 10 x 6 + 5 x 5 x 6 + 3 x 3 x 4 + 1 x 1 x 4 = 8732 个anchor
  * 其他feature map的feature map cell对应6个anchor，分别为宽高比: 1:1两种，1:2, 2:1两种，1:3， 3:1两种
  * 每层的feature map cell对应的anchor计算方法如下

    * 位置：假设当前feature map cell是位于第i行，第j列，则anchor的中心为 $$\frac{i+0.5}{|f\_{k}|},\frac{j+0.5}{|f\_{k}|}$$, $$f\_{k}$$是第k层feature map的size（比如38）
    * 缩放因子:

    ![Scale](https://upload-images.jianshu.io/upload_images/1828517-91ef6530e5dce4b2.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)

    其中$$s\_{min}$$为0.2，$$s\_{max}$$为0.9，m为添加的feature map的层数，缩放因子就是为不同feature map选择不同的大小的anchor，要求小的feature map对应的anchor尽量大，因为越小的feature map，其feature map cell的感受野就越大
  * anchor宽高：

    ![width](https://upload-images.jianshu.io/upload_images/1828517-ba128e30ed7637e3.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)

    ![height](https://upload-images.jianshu.io/upload_images/1828517-4898e977cc483570.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)

    其中，$$a\_{r}∈{1,2,3,1/2,1/3}$$，可以理解为在缩放因子选择好anchor尺寸后，用$$a\_{r}$$来控制anchor形状，从而得到多尺度的各种anchor，当$$a\_{r}=1$$时，增加一种$$s\_{k}=sqrt(s\_{k-1}s\_{k+1})$$，于是每个feature map cell通常对应6种anchor。
* 网络的训练目标就是，回归各个anchor对应的类别和位置

## Training

### 样本

* 正样本 选择与bounding box jaccard overlap（两张图的交集/并集）大于0.5的anchor作为正样本
* 样本比例 Hard negative mining：由于负样本很多，需要去掉一部分负样本，先整图经过网络，根据每个anchor的最高类置信度进行排序，选择置信度靠前的样本，这样筛选出来的负样本也会更难识别，并且最终正负样本比例大概是1:3

### Loss

还是一如既往的location loss + classification loss，并为location loss添加了系数α（然而实际上α=1）进行平衡，并在batch维度进行平均

![](/files/-MkuKyEe6XxkDBMWC7nV)

* $$x$$是$$x\_{ij}^{p}$$的集合$$x\_{ij}^{p}={1,0}$$，用于判断第i个anchor是否是第j个bounding box上的p类样本
* $$c$$是$$c\_{i}^{p}$$的集合，$$c\_{i}^{p}$$是第i个anchor预测为第p类的概率
* l是预测的bounding box集合
* g是ground true bounding box集合

其中定位loss与faster rcnn相同

![](/files/-MkuKyEiNoZ1kBwId-Sh) 这个式子里的k不是很明确，其实想表达不算背景0类的意思，且前景类只为match的类算location loss

分类loss就是很常用的softmax交叉熵了

![](/files/-MkuKyEjIwS5NjrEuo1R)

> 核心的内容到这里就讲完了，其实跟YOLO和faster rcnn也很像，是一个用anchor box充当固定的proposal的rpn，并且用多尺度的anchor来适应多种尺度和形状的目标对象。

## Detail

在训练中还用到了data augmentation（数据增强/扩充），每张图片多是由下列三种方法之一随机采样而来

* 使用整图
* crop图片上的一部分，crop出来的min面积为0.1,0.3,0.5,0.7,0.9
* 完全随机地crop

然后这些图片会被resize到固定的大小，随机水平翻转，加入一些图像上的噪声，详情可以参考另一篇论文：

Some improvements on deep convolutional neural network based image classification

从切除实验中，可以看到data augmentaion是很重要的（从65.6到71.6）

![](/files/-MkuKyEkTyP-DeGnHfa2) 这个表中还提到了atrous，其实是指空洞卷积，是图像分割（deeplab）领域首先提出的一个卷积层改进，主要是能让测试速度更快。具体可以参考 [ICLR2015 Deeplab](https://arxiv.org/pdf/1412.7062.pdf)

从这个表中也可以看出多种形状的anchor可以提升准确率

## Result

输入尺寸为300x300，batch size为8的SSD300可以做到实时(59FPS)且准确(74.3% mAP)的测试

## Summary

SSD算是一个改进性的东西，站在Faster RCNN的肩膀上达到了实时且准确的检测


# 读论文系列·YOLOv2 & YOLOv3

## YOLOv2/YOLO9000

> YOLO9000: Better, Faster, Stronger

YOLOv2 是一个单纯的改进型工作，在YOLO上集成了很多已有的trick（比如加了BN，anchor），因为是trick文章，这里就不做完整解读了，可以参考[这篇解读](https://zhuanlan.zhihu.com/p/25167153)，我觉得其中比较有新意的地方有两个：

* Dimension Clusters得到更好的anchor
* YOLO9000：用WordTree整合ImageNet和COCO数据集联合训练Darknet（有种知识图谱和DL结合的感觉），用多个softmax loss分别做不同层次的分类

改进点list:

* Batch Normalization
* 用448x448的图片训练分类器
* 使用anchor
* 聚类得到更好的初始anchor位置（使用IOU进行box距离判断）
* 直接预测box位置
* 类似ResNet的passthrough feature叠加
* 由于是全卷积网络，可以接收不同尺度输入进行训练
* Darknet-19：大量使用3x3和1x1卷积，BN
* 用wordtree整合ImageNet和COCO数据集，多标签联合训练Darknet

## YOLOv3

YOLOv3也是一个单纯的改进性工作。。没啥创新性，但是效果好\_(:з」∠)\_

作者也觉得这不算是一篇正式paper，只是一个工作报告，所以论文写得跟玩儿似的

![](/files/-MkuKvlqljgpL6G0WC3k) 改进点list

* 使用更多的shotcut，构造更深的darknet-53（ResNet提出来的）

![](/files/-MkuKvlrGia58lK1UgTV)

* 使用三层feature map分别对应不同尺度的anchor（SSD，FPN已经这样做过了）
* 由于softmax分类loss前提假设是每个对象只属于一个分类，对于有包含关系的类别，softmax没那么适用，于是yolov3使用了逻辑回归的方法做分类，同时回归一个anchor属于多个类的概率，ground truth值是0或1，分别代表一个anchor是否与ground truth box相匹配。


# 读论文系列·detection其他文章推荐

* CVPR2017 [FPN](https://www.jiqizhixin.com/articles/2017-07-25-2)：特征金字塔
* [ICCV2017 best paper Mask RCNN](https://zhuanlan.zhihu.com/p/31983610)：分割+检测+分类多任务学习
* [Focal loss](https://xmfbit.github.io/2017/08/14/focal-loss-paper/)：自适应调节样本权重的loss，结合ResNet和FPN变成RetinaNet（不过被YOLOv3的实验效果打脸了）


# Depth

单目深度可以分为两个小领域，一个是Mono depth estimation，是真单目，从图像语义出depth，输出的depth scale通常和真实世界是不对齐的，另一个是Multi view stereo，利用前后帧图像和pose（pose也可能真实模型估出来的），通过对极几何出深度。

## Mono Depth Estimation

* Before 2020: 可以参考知乎上这篇综述，挺完整的：<https://zhuanlan.zhihu.com/p/111759578>
* Predicting Sharp and Accurate Occlusion Boundaries in Monocular Depth Estimation Using Displacement Fields[\[code(pytorch)\]](https://github.com/dulucas/Displacement_Field)[\[paper\]](https://arxiv.org/abs/2002.12730)

> CVPR2020,通过计算displacement，对depth做refine，所谓displacement，即将周围一定范围内的某个像素的depth挪过来作为自己的depth，网络为每个像素输出要挪的目标像素坐标，从而削弱CNN depth的边缘模糊现象。

* Self-supervised Monocular Trained Depth Estimation using Self-attention and Discrete Disparity Volume

> CVPR2020, 将回归的目标从inverse depth换成了disparity，用softmax probability乘disp求和的方式回归disparity，同时加了一块attention模块对disparity做修正，和其他单目文章类似，多次decode得到更大尺寸的disparity。这篇论文也是做自监督，文章中提到一个操作比较实用：计算warp loss时，如果warp的右图的cost比原始右图还大，就认为这个地方是移动物体之类的无效区域。

## Multi view stereo

* MVDepthNet: Real-time Multiview Depth Estimation Neural Network [\[code(pytorch)\]](https://github.com/HKUST-Aerial-Robotics/MVDepthNet)[\[paper\]](https://arxiv.org/abs/1807.08563)

> 3DV2018，大概是第一篇CNN做mvs的论文，基本思路就是把measure frame 利用pose和多个depth warp到ref frame，找到cost最小的depth，找最小的过程用一个unet实现，多个multi view时，对cost volume求平均。

* MVSNet: Depth Inference for Unstructured Multi-view Stereo [code(tensorflow)](https://github.com/YoYo000/MVSNet)[paper](https://arxiv.org/abs/1804.02505)

> ECCV2018，在MVDepthNet基础上添加了对图像的feature提取，并对encode-decode出来的depth加了图像上的refine

* DPSNet: End-to-end Deep Plane Sweep Stereo [\[code(pytorch)\]](https://github.com/sunghoonim/DPSNet) [\[paper\]](https://arxiv.org/abs/1905.00538)

> ICLR2019，思路和mvsnet基本一致，同时发表，但是代码是pytorch的，看得出是在MVDepthNet上改的，代码质量比较高

* Depth Estimation by Learning Triangulation and Densification of Sparse Points for Multi-view Stereo

arxiv 2020.5月的一篇，用superpoint提描述子，做point match，利用稀疏的匹配点和一些随机点做三角测量（也可以理解为稀疏的cost volume），然后用unet encode-decode把稀疏的深度图变成dense的，相同效果下，计算量小于cost volume的方法，不过只做了室内的实验。

* Fast-MVSNet: Sparse-to-Dense Multi-View Stereo With Learned Propagation and Gauss-Newton Refinement [\[code(pytorch)\]](https://github.com/svip-lab/FastMVSNet) [\[paper\]](https://arxiv.org/abs/2003.13017)

> CVPR2020，特征提取层天然会缩小feature map，在小的feature map上做plane sweep，得到winner takes all depth，nearest upsample得到大分辨率depth map，再用原图输出一个kxk的卷积核，根据这个卷积核，用周围的信息丰富depth map，再用warp loss refine depth map，少了encode-decode层，计算量少了很多，plane sweep部分在小分辨率上做的，计算量也小。

* Cost Volume Pyramid Based Depth Inference for Multi-View Stereo [\[code(pytorch)\]](https://github.com/JiayuYANG/CVP-MVSNet) [\[paper\]](https://arxiv.org/abs/1912.08329)

> CVPR2020, 提取feature pyrammid，现在最小scale的feature上，对所有depth range做plane sweep，接3d卷积出depth，再对更大scale的feature，在已经估计出来的depth附近几个channel做plane sweep，接3d卷积出原depth的残差，加在原depth上作为refine，从而得到multi scale，且计算量还不会太大（因为后面只在周围的depth搜索）的cost volume。

* Upgrading Optical Flow to 3D Scene Flow Through Optical Expansion-Supplementary Material[\[code(pytorch)\]](https://github.com/gengshan-y/expansion)[\[paper\]](https://openaccess.thecvf.com/content_CVPR_2020/papers/Yang_Upgrading_Optical_Flow_to_3D_Scene_Flow_Through_Optical_Expansion_CVPR_2020_paper.pdf)

> CVPR2020，推导出optical expansion(物体长度在像素坐标系上的变化)和motion in depth的反比关系（只在没有旋转只有平移时成立），用一个encoder-decoder输出光流，通过一个local affine layer得到初始的expansion，再通过一个encoder-decoder得到refine的expansion，再用一个encoder-decoder得到motion-in-depth，为了得到真正的motion in depth，还需要用一个单目网络出frame1的depth，再用motion in depth换算出frame2的depth，motion in depth的思路比较新奇，但并不怎么实用。

## 其他Depth相关的论文

* Depth Sensing Beyond LiDAR Range

> cvpr2020，构造了一个三目系统，讲了怎么用三目出深度，声称解决超远距离（超过lidar范围）深度估计，但是论文里看不出为啥三目能解这个问题。


# 3D vision

包含光流，motion stereo，双目深度等。


# 数据集相关

* MineNav: An Expandable Synthetic Dataset Based on Minecraft for Aircraft Visual Navigation

> 使用minecraft生成场景，导出三维模型 <https://www.bilibili.com/video/BV1Bs411H7Ke/>


# 光流相关

Flow基本套路

* multi scale feature pyramid
* warp and correlation，得到cost
* 对不同scale的feature pyramid计算residual flow，实现refine

## RAFT

* 先把不同scale所有可能flow的cost算好，在计算出flow后look up得到cost
* 使用GRU实现不同scale coarse to fine的refine

## FastFlowNet

> ICRA 2021, 在TX2上用TensorRt可以跑到5Hz的模型

* 在feature提取层上先用卷积+stride降采样，再用pooling降采样，得到比较好的pyramid feature，
* 在correlation层，搜索半径为3的区域全部用来计算cost，在搜索半径大于3的部分，只采样计算其中一半的元素的cost，从而减少计算量
* cost到flow的过程用的是ShuffleNet中的SBD模块
* 从小scale开始做cost2flow，warp 大scale feature后再做一次cost2flow得到residual flow，总计六次，五次是refine


# Hashing

> 转载请注明作者：[梦里茶](https://github.com/ahangchen)

这是腾讯AI Lab与西电合作的一篇CVPR2018的paper，在多模态检索任务中加入对抗网络组件，为跨模态对象生成更好的语义特征，从而提高了跨模态检索的效果。

## 问题描述

跨模态检索：

* 利用一种模态的数据去检索另一种模态中的数据，比如文字搜图片

![](/files/-MkuKuYu49i5uC5JMF8_)

* 寻找多种模态的数据对应的关键字

![](/files/-MkuKuYvsJns1Bj2OorA)

* 常用的数据集：MSCOCO, NUS-WIDE, MIRFLICKR-25K

如果我们在检索的时候再去做特征提取，检索速度会很慢，因此通常需要预先将特征提取出来，根据相似度建立索引，从而加快检索速度，为了节省存储空间，并加快计算效率，通常会要求特征尽量短，并且是二进制表示，这样的特征我们称为Hash。

## 常用方法

我们要根据多模态的内容生成一个hash，希望不同模态的同个对象hash尽量相近，不同对象的hash尽量不同。由于跨模态的内容具有语义上的联系，通常的做法是将不同模态的内容映射到公共的语义空间，已经有很多这方面的工作，有监督/无监督的，Shallow的手工特征/Deep特征。得到特征之后，可以用sign操作将连续的feature向量变成离散值，从而得到更轻量的特征。

![](/files/-MkuKuYwfOt7MhS9AMo4)

## SSAH

![](/files/-MkuKuYxqO-gCV0f5U_O) 这篇论文提出了一个结合对抗学习的深度神经网络：

* 利用深度提取图像和文本特征，转为hash（I/T->F->L+H->B）
* 利用标签生成特征，再转为hash，并希望特征能够还原回label（（L->F->L+H->B）
* 有监督地最小化不同模态特征和hash的差异
* 加入能够区分不同来源的特征的判别器进行对抗训练，进一步减小不同模态特征的差异

接下来具体讲其中几个部分：

## Self supervised semantic Generation(L->F->L+H->B)

* 输入：某个图文对应的label，每个对象会对应多个label，one hot成01向量
* 经过四层神经网络（L->4096->512->N）
* 输出长度为N的向量，N=K+c，K为哈希码长度，c为label的类别个数
* 训练目标：让生成的hash保留语义上的相关性，并能还原回原来的label

![](/files/-MkuKuYy1SPjqlf63Vvm) 训练目标由这个Loss约束完成：

![](/files/-MkuKuYzbpColxz_EMyn) 首先解释一下符号（以下数学符号用LaTeX格式显示，简书不支持公式编辑，更好的阅读体验请查看[cweihang.io](http://cweihang.io)），

* $$H^l$$ 是根据label生成的hash，
* $$B^l$$是由hash执行sign操作得到的二进制码
* $$\hat{L}$$ 是由特征还原回来的label
* $$L$$ 是原本的label
* $$\Delta\_{ij}^l=\frac{1}{2}(F\_i^l)^\top(F\_j^l)$$ ，即样本i和样本j的label生成的特征的余弦相似度
* $$\Gamma\_{ij}^l=\frac{1}{2}(H\_i^l)^\top(H\_j^l)$$，即样本i和样本j的label生成的特征余弦相似度
* $$S\_{ij}$$ 表示样本i和j是否包含至少一个相同的label，
  * 包含为1，表示样本i和j在语义上相似
  * 不包含为0，表示样本i和j在语义上不相似
* 对于 $$\mathcal{J}\_1$$ ,

$$-\sum\_{i,j=1}^{n}{S\_{ij}\Delta\_{ij}^{l}-log(1+e^{\Delta\_{ij}^l})}$$

$$= -\sum\_{i,j=1}^{n}{log(\frac{e^{S\_{ij}\Delta\_{ij}^{l}}}{1+e^{\Delta\_{ij}^l}})}$$

$$= -\sum\_{i,j=1}^{n}{log(\frac{e^{\Delta\_{ij}^{l}}}{1+e^{\Delta\_{ij}^l}})} if S\_{ij}=1$$

$$= -\sum\_{i,j=1}^{n}{log(\frac{1}{1+e^{\Delta\_{ij}^l}})} if S\_{ij}=0$$

$$= \sum\_{i,j=1}^{n}-{S\_{ij}log(\frac{e^{\Delta\_{ij}^{l}}}{1+e^{\Delta\_{ij}^l}})-(1-S\_{ij})log(1-\frac{e^{\Delta\_{ij}^{l}}}{1+e^{\Delta\_{ij}^l}})}$$

所以，实际上这个loss和交叉熵loss是等效的

即$$S\_{ij}=1$$时，

$$min -\sum\_{i,j=1}^{n}{log(\frac{e^{\Delta\_{ij}^{l}}}{1+e^{\Delta\_{ij}^l}})}=max\sum\_{i,j=1}^{n}{log(\frac{1}{1+e^{-\Delta\_{ij}^l}})}=max \Delta\_{ij}^l$$

最大化两个向量的余弦相似度

$$S\_{ij}=0$$ 时，

$$min -\sum\_{i,j=1}^{n}{log(\frac{1}{1+e^{\Delta\_{ij}^l}})}=max\sum\_{i,j=1}^{n}{log(\frac{1}{1+e^{\Delta\_{ij}^l}})}=min\Delta\_{ij}^l$$

最小化两个向量的余弦相似度

* 对于$$\mathcal{J}\_2$$ 同理，从而约束了相似的label具有相似的hash
* 对于 $$\mathcal{J}\_3$$, 使 $$H^l$$ 和 $$B^l$$ 尽可能接近，从而使得Hash向量中的元素尽量接近-1,1，减少了 H -> B 时的损失
* 对于 $$\mathcal{J}\_4$$ , 使得还原的标签与原来的标签尽可能相同

> 这个部分跟自编码器很像，是自监督的过程，由label生成特征，再由特征还原回label

## Feature Learning(I/T->F->L+H->B)

* 输入：图像/文本，
* 经过神经网络提取特征（图像和文本的网络不同）
* 输出长度为N的向量，N=K+c，K为哈希码长度，c为label的类别个数
* 训练目标：
  * 在特征中保留语义信息，因此希望预测label与真实label相近
  * hash尽量接近binary code
  * 让特征提取得到的feature和hash与Semantic Generation得到的特征和hash尽量相同，
    * 因此监督信号做feature learning的时候还对提取feature和生成feature的相似性做约束，
    * 对提取hash和生成hash的相似性做约束

其中，图像的特征提取网络作者试用了CNN-F和VGG16（VGG16更优），文本特征提取则是一个新的多尺度融合模型：

![](/files/-MkuKuZ-fCs0gc4WqrZb)

* 输入：文本，转为一个词袋向量，由于词袋向量非常稀疏，需要转化为一个相对稠密的向量
* 网络：T->Multi-scale Fusion->4096->512->N
* 输出长度为N的向量，N=K+c，K为哈希码长度，c为label的类别个数
* Multi-scale Fusion：
  * 5个average pooling layer(1x1,2x2,3x3,5x5,10x10)+1个1x1conv

训练Loss与前面的Semantic Generation很像

![](/files/-MkuKuZ0FwJcbxeLL98R) 但又与之前的模型不同，这里的监督信号有标签和标签生成的特征，而之前的监督信号就是输入本身。

其中

* $$\Delta\_{ij}^l=\frac{1}{2}(F\_i^{l})^\top(F\_j^{v,t})$$，即样本i的标签label生成的特征和样本j的输入（图/文）提取的特征的余弦相似度，目标是使提取的特征和生成的特征尽量相近
* $$\Gamma\_{ij}^l=\frac{1}{2}(H\_i^l)^\top(H\_j^{v,t})$$，即样本i的标签label生成的hash和样本j的输入（图/文）提取的hash的余弦相似度，目标是使提取的hash和生成的hash尽量相近

## Adversarial learning

* Motivation:不同模态提取的特征会有不同的分布，希望相同语义的对象在不同模态里的特征表达尽量接近
* Solution:加入判别器D，希望D能区分特征是来自Feature Learning还是Semantic Generation，D越强大，越能区分两种特征，要欺骗D，就迫使Feature Learning和Semantic Generation得到的特征尽量相近

![](/files/-MkuKuZ1THB1PwYU-4XY)

* 判别器D的网络结构：F->4096->4096->1
* 每个样本(图+文+label)产生3个特征 $$(𝑥\_𝑖^𝑣,𝑥\_𝑖^𝑡,𝑥\_𝑖^𝑙)$$
* 输入：图/文特征+生成特征 $$(𝑥\_𝑖^𝑣, 𝑥\_𝑖^𝑙 )$$ 或$$(𝑥\_𝑖^𝑡,𝑥\_𝑖^𝑙)$$
* 输出： $$(𝑦\_𝑖^𝑣, 𝑦\_𝑖^𝑙 )$$ 或 $$(𝑦\_𝑖^𝑡, 𝑦\_𝑖^𝑙 )$$ ，即输入向量是否来自生成特征
* 监督信号： $$𝑦\_𝑖^𝑣$$ 或 $$𝑦\_𝑖^𝑡=0$$，$$𝑦\_𝑖^𝑙=1$$
* 判别器的损失函数：

![](/files/-MkuKuZ2YcP_M8gOtdL1) 即最小化判别器的预测误差

## Training

* 于是我们有了特征生成Loss:

  $$\mathcal{L}\_{gen}=\mathcal{L}^v+\mathcal{L}^t+\mathcal{L}^l$$

图像特征提取loss+文本特征提取loss+标签生成loss

* 以及对抗loss $$\mathcal{L}*{adv}=\mathcal{L}^v*{adv}+\mathcal{L}^t\_{adv}$$
* 我们的优化目标是： $$(B,\theta^{v,t,l})=argmin\_{B,\theta^{v,t,l}}\mathcal{L}*{gen}(B,\theta^{v,t,l})-\mathcal{L}*{adv}(\hat{\theta}\_{adv})$$

在最优的判别器参数 $$\hat{\theta}\_{adv}$$ 下，最小化特征的生成Loss

以及

$$\theta\_{adv}=argmax\_{\theta\_{adv}} L\_{gen}(\hat{B}, \hat{\theta}^{v,t,l})-L\_{adv}(\theta\_{adv})$$

在最优生成器参数 $$\hat{B}, \hat{\theta}^{v,t,l}$$下，最小化判别器的识别误差 $$L\_{adv}$$

* 具体实现上，分为四步迭代进行优化：
  * Label自监督生成特征
  * 图像分类器feature learning
  * 文本分类器feature learning
  * 判别器训练

于是SSAH的工作机制就梳理完毕了

## 方法评估

生成的Hash效果是否足够好，通常由Hamming Ranking和Hash Lookup来评估，在论文中，作者还对模型本身做了Training efficiency，Sensitivity analysis，Ablation study的实验评估。

* Hamming Ranking
  * 按照哈希码海明距离进行Ranking，计算mAP

![](/files/-MkuKuZ3p_4tkD56p4aZ)

> 可以看到使用VGG作为图像基础网络时，SSAH准确率领先其他方法很多。

* Hash Lookup
  * 海明距离小于某个值认为是正样本，这个值称为Hamming Radius，改变Radius可以改变Precision-Recall的值，于是可以得到P-R曲线，P-R曲线与坐标轴围成的面积越大，说明效果越好

![](/files/-MkuKuZ4Gy36BTasSpmD)

> SSAH的PR曲线基本都是在其他模型的曲线之上

* 对Ranking的结果计算TopN的命中率（不过这个文中好像没讲）
* Training efficiency
  * 达到相同的效果所需训练时间

![](/files/-MkuKuZ5xycvEC2KfIiJ)

> 相对于另一种深度学习方法DCMH，SSAH只要比较短的时间就能得到比较好的效果

* Sensitivity analysis
  * 超参数改变时的结果变化

![](/files/-MkuKuZ6YneZHk8FnDN7)

> 可以看到，超参数变化时，准确率依然能维持在比较高的水平

* Ablation study
  * 去除不同组件对效果的影响

![](/files/-MkuKuZ72-f_8g2NYFzp)

> 其中， SSAH-1: remove LabNet SSAH-2: TxtNet改成三层全连接 SSAH-3: 去掉对抗网络 可以看到在I2T任务中，标签生成网络是很重要的，在T2I任务中对抗网络的效果更明显。

## Summary

SSAH中最妙的两点是，用Label生成特征和哈希来监督feature learning，加入对抗学习来拉近不同模态特征的相似性，模型的思路足够清晰，容易复现，有很多值得学习的东西。


# CVPR2018: SSAH

> 转载请注明作者：[梦里茶](https://github.com/ahangchen)

这是腾讯AI Lab与西电合作的一篇CVPR2018的paper，在多模态检索任务中加入对抗网络组件，为跨模态对象生成更好的语义特征，从而提高了跨模态检索的效果。

## 问题描述

跨模态检索：

* 利用一种模态的数据去检索另一种模态中的数据，比如文字搜图片

![](/files/-MkuKx2NzrrKMJmtO1Bw)

* 寻找多种模态的数据对应的关键字

![](/files/-MkuKx2O1jKeTyrbH4DW)

* 常用的数据集：MSCOCO, NUS-WIDE, MIRFLICKR-25K

如果我们在检索的时候再去做特征提取，检索速度会很慢，因此通常需要预先将特征提取出来，根据相似度建立索引，从而加快检索速度，为了节省存储空间，并加快计算效率，通常会要求特征尽量短，并且是二进制表示，这样的特征我们称为Hash。

## 常用方法

我们要根据多模态的内容生成一个hash，希望不同模态的同个对象hash尽量相近，不同对象的hash尽量不同。由于跨模态的内容具有语义上的联系，通常的做法是将不同模态的内容映射到公共的语义空间，已经有很多这方面的工作，有监督/无监督的，Shallow的手工特征/Deep特征。得到特征之后，可以用sign操作将连续的feature向量变成离散值，从而得到更轻量的特征。

![](/files/-MkuKx2PNpUOL0CKoijC)

## SSAH

![](/files/-MkuKx2QmjTNmXqWp3zy) 这篇论文提出了一个结合对抗学习的深度神经网络：

* 利用深度提取图像和文本特征，转为hash（I/T->F->L+H->B）
* 利用标签生成特征，再转为hash，并希望特征能够还原回label（（L->F->L+H->B）
* 有监督地最小化不同模态特征和hash的差异
* 加入能够区分不同来源的特征的判别器进行对抗训练，进一步减小不同模态特征的差异

接下来具体讲其中几个部分：

## Self supervised semantic Generation(L->F->L+H->B)

* 输入：某个图文对应的label，每个对象会对应多个label，one hot成01向量
* 经过四层神经网络（L->4096->512->N）
* 输出长度为N的向量，N=K+c，K为哈希码长度，c为label的类别个数
* 训练目标：让生成的hash保留语义上的相关性，并能还原回原来的label

![](/files/-MkuKx2RpFqSndKznfM_) 训练目标由这个Loss约束完成：

![](/files/-MkuKx2S363G7ooZgKfa) 首先解释一下符号（以下数学符号用LaTeX格式显示，简书不支持公式编辑，更好的阅读体验请查看[cweihang.io](http://cweihang.io)），

* $$H^l$$ 是根据label生成的hash，
* $$B^l$$是由hash执行sign操作得到的二进制码
* $$\hat{L}$$ 是由特征还原回来的label
* $$L$$ 是原本的label
* $$\Delta\_{ij}^l=\frac{1}{2}(F\_i^l)^\top(F\_j^l)$$ ，即样本i和样本j的label生成的特征的余弦相似度
* $$\Gamma\_{ij}^l=\frac{1}{2}(H\_i^l)^\top(H\_j^l)$$，即样本i和样本j的label生成的特征余弦相似度
* $$S\_{ij}$$ 表示样本i和j是否包含至少一个相同的label，
  * 包含为1，表示样本i和j在语义上相似
  * 不包含为0，表示样本i和j在语义上不相似
* 对于 $$\mathcal{J}\_1$$ ,

$$-\sum\_{i,j=1}^{n}{S\_{ij}\Delta\_{ij}^{l}-log(1+e^{\Delta\_{ij}^l})}$$

$$= -\sum\_{i,j=1}^{n}{log(\frac{e^{S\_{ij}\Delta\_{ij}^{l}}}{1+e^{\Delta\_{ij}^l}})}$$

$$= -\sum\_{i,j=1}^{n}{log(\frac{e^{\Delta\_{ij}^{l}}}{1+e^{\Delta\_{ij}^l}})} if S\_{ij}=1$$

$$= -\sum\_{i,j=1}^{n}{log(\frac{1}{1+e^{\Delta\_{ij}^l}})} if S\_{ij}=0$$

$$= \sum\_{i,j=1}^{n}-{S\_{ij}log(\frac{e^{\Delta\_{ij}^{l}}}{1+e^{\Delta\_{ij}^l}})-(1-S\_{ij})log(1-\frac{e^{\Delta\_{ij}^{l}}}{1+e^{\Delta\_{ij}^l}})}$$

所以，实际上这个loss和交叉熵loss是等效的

即$$S\_{ij}=1$$时，

$$min -\sum\_{i,j=1}^{n}{log(\frac{e^{\Delta\_{ij}^{l}}}{1+e^{\Delta\_{ij}^l}})}=max\sum\_{i,j=1}^{n}{log(\frac{1}{1+e^{-\Delta\_{ij}^l}})}=max \Delta\_{ij}^l$$

最大化两个向量的余弦相似度

$$S\_{ij}=0$$ 时，

$$min -\sum\_{i,j=1}^{n}{log(\frac{1}{1+e^{\Delta\_{ij}^l}})}=max\sum\_{i,j=1}^{n}{log(\frac{1}{1+e^{\Delta\_{ij}^l}})}=min\Delta\_{ij}^l$$

最小化两个向量的余弦相似度

* 对于$$\mathcal{J}\_2$$ 同理，从而约束了相似的label具有相似的hash
* 对于 $$\mathcal{J}\_3$$, 使 $$H^l$$ 和 $$B^l$$ 尽可能接近，从而使得Hash向量中的元素尽量接近-1,1，减少了 H -> B 时的损失
* 对于 $$\mathcal{J}\_4$$ , 使得还原的标签与原来的标签尽可能相同

> 这个部分跟自编码器很像，是自监督的过程，由label生成特征，再由特征还原回label

## Feature Learning(I/T->F->L+H->B)

* 输入：图像/文本，
* 经过神经网络提取特征（图像和文本的网络不同）
* 输出长度为N的向量，N=K+c，K为哈希码长度，c为label的类别个数
* 训练目标：
  * 在特征中保留语义信息，因此希望预测label与真实label相近
  * hash尽量接近binary code
  * 让特征提取得到的feature和hash与Semantic Generation得到的特征和hash尽量相同，
    * 因此监督信号做feature learning的时候还对提取feature和生成feature的相似性做约束，
    * 对提取hash和生成hash的相似性做约束

其中，图像的特征提取网络作者试用了CNN-F和VGG16（VGG16更优），文本特征提取则是一个新的多尺度融合模型：

![](/files/-MkuKx2U4u8Wqy6Yi2nc)

* 输入：文本，转为一个词袋向量，由于词袋向量非常稀疏，需要转化为一个相对稠密的向量
* 网络：T->Multi-scale Fusion->4096->512->N
* 输出长度为N的向量，N=K+c，K为哈希码长度，c为label的类别个数
* Multi-scale Fusion：
  * 5个average pooling layer(1x1,2x2,3x3,5x5,10x10)+1个1x1conv

训练Loss与前面的Semantic Generation很像

![](/files/-MkuKx2VDwffUWloZh79) 但又与之前的模型不同，这里的监督信号有标签和标签生成的特征，而之前的监督信号就是输入本身。

其中

* $$\Delta\_{ij}^l=\frac{1}{2}(F\_i^{l})^\top(F\_j^{v,t})$$，即样本i的标签label生成的特征和样本j的输入（图/文）提取的特征的余弦相似度，目标是使提取的特征和生成的特征尽量相近
* $$\Gamma\_{ij}^l=\frac{1}{2}(H\_i^l)^\top(H\_j^{v,t})$$，即样本i的标签label生成的hash和样本j的输入（图/文）提取的hash的余弦相似度，目标是使提取的hash和生成的hash尽量相近

## Adversarial learning

* Motivation:不同模态提取的特征会有不同的分布，希望相同语义的对象在不同模态里的特征表达尽量接近
* Solution:加入判别器D，希望D能区分特征是来自Feature Learning还是Semantic Generation，D越强大，越能区分两种特征，要欺骗D，就迫使Feature Learning和Semantic Generation得到的特征尽量相近

![](/files/-MkuKx2WzHBzIRIFNUIg)

* 判别器D的网络结构：F->4096->4096->1
* 每个样本(图+文+label)产生3个特征 $$(𝑥\_𝑖^𝑣,𝑥\_𝑖^𝑡,𝑥\_𝑖^𝑙)$$
* 输入：图/文特征+生成特征 $$(𝑥\_𝑖^𝑣, 𝑥\_𝑖^𝑙 )$$ 或$$(𝑥\_𝑖^𝑡,𝑥\_𝑖^𝑙)$$
* 输出： $$(𝑦\_𝑖^𝑣, 𝑦\_𝑖^𝑙 )$$ 或 $$(𝑦\_𝑖^𝑡, 𝑦\_𝑖^𝑙 )$$ ，即输入向量是否来自生成特征
* 监督信号： $$𝑦\_𝑖^𝑣$$ 或 $$𝑦\_𝑖^𝑡=0$$，$$𝑦\_𝑖^𝑙=1$$
* 判别器的损失函数：

![](/files/-MkuKx335DQB6NZvAXaQ) 即最小化判别器的预测误差

## Training

* 于是我们有了特征生成Loss:

  $$\mathcal{L}\_{gen}=\mathcal{L}^v+\mathcal{L}^t+\mathcal{L}^l$$

图像特征提取loss+文本特征提取loss+标签生成loss

* 以及对抗loss $$\mathcal{L}*{adv}=\mathcal{L}^v*{adv}+\mathcal{L}^t\_{adv}$$
* 我们的优化目标是： $$(B,\theta^{v,t,l})=argmin\_{B,\theta^{v,t,l}}\mathcal{L}*{gen}(B,\theta^{v,t,l})-\mathcal{L}*{adv}(\hat{\theta}\_{adv})$$

在最优的判别器参数 $$\hat{\theta}\_{adv}$$ 下，最小化特征的生成Loss

以及

$$\theta\_{adv}=argmax\_{\theta\_{adv}} L\_{gen}(\hat{B}, \hat{\theta}^{v,t,l})-L\_{adv}(\theta\_{adv})$$

在最优生成器参数 $$\hat{B}, \hat{\theta}^{v,t,l}$$下，最小化判别器的识别误差 $$L\_{adv}$$

* 具体实现上，分为四步迭代进行优化：
  * Label自监督生成特征
  * 图像分类器feature learning
  * 文本分类器feature learning
  * 判别器训练

于是SSAH的工作机制就梳理完毕了

## 方法评估

生成的Hash效果是否足够好，通常由Hamming Ranking和Hash Lookup来评估，在论文中，作者还对模型本身做了Training efficiency，Sensitivity analysis，Ablation study的实验评估。

* Hamming Ranking
  * 按照哈希码海明距离进行Ranking，计算mAP

![](/files/-MkuKx35aCyVTH82nUl_)

> 可以看到使用VGG作为图像基础网络时，SSAH准确率领先其他方法很多。

* Hash Lookup
  * 海明距离小于某个值认为是正样本，这个值称为Hamming Radius，改变Radius可以改变Precision-Recall的值，于是可以得到P-R曲线，P-R曲线与坐标轴围成的面积越大，说明效果越好

![](/files/-MkuKx36tlyiOlC5nwgh)

> SSAH的PR曲线基本都是在其他模型的曲线之上

* 对Ranking的结果计算TopN的命中率（不过这个文中好像没讲）
* Training efficiency
  * 达到相同的效果所需训练时间

![](/files/-MkuKx37aQVeXlxTLC4o)

> 相对于另一种深度学习方法DCMH，SSAH只要比较短的时间就能得到比较好的效果

* Sensitivity analysis
  * 超参数改变时的结果变化

![](/files/-MkuKx38J7PVAjfCSIhP)

> 可以看到，超参数变化时，准确率依然能维持在比较高的水平

* Ablation study
  * 去除不同组件对效果的影响

![](/files/-MkuKx39aX94-8skWO0k)

> 其中， SSAH-1: remove LabNet SSAH-2: TxtNet改成三层全连接 SSAH-3: 去掉对抗网络 可以看到在I2T任务中，标签生成网络是很重要的，在T2I任务中对抗网络的效果更明显。

## Summary

SSAH中最妙的两点是，用Label生成特征和哈希来监督feature learning，加入对抗学习来拉近不同模态特征的相似性，模型的思路足够清晰，容易复现，有很多值得学习的东西。


# 大杂烩

* [CNCC2017 琐记](/ml/papers/overview/cncc2017)
* [ECCV 2016 Hydra CCNN](/ml/papers/overview/eccv2016_hydra_ccnn)
* [CNCC2017深度学习与跨媒体智能](/ml/papers/overview/cncc_cv)
* [MLA2016笔记](/ml/papers/overview/mla_2016)


# CNCC2017 琐记

大会日程表：<http://cncc.ccf.org.cn/cn/news/schedule_empty>

> 早上的论坛可以在[爱奇艺](http://www.iqiyi.com/a_19rrh1kcs5.html)下载视频
>
> 下午的分论坛是多个同时进行的，我也只去了一部分，这里先按时间顺序写自己的一些收获，之后会从另外的角度做一个总结。
>
> 如果觉得我的整理对你有帮助，欢迎[star](https://github.com/ahangchen/windy-afternoon/blob/master/ml/papers/cncc2017.md)这个项目

## 10-26 am

### 丘成桐 现代几何在计算机科学中的应用

* 从几何学的角度找到优化问题（如GAN）的等价形式，通过解决等价问题加速优化过程

### 沈向洋 理解自然语言 概述，对话和理解

* 自然语言：机器学习（表述）->机器智能（对话）->机器意识（意境）
* 图像表述：微软有一个Image Caption的api可以用
* 检测，分割，识别只是基础任务，对图像进行理解是以后的热点
* 小冰：LSTM端到端，话题引导，有意识的脑补

### 李飞飞 A Quest for Visual Intelligence: Exploration Beyond Objects

* 在图像识别，分类，分割，检测之外，还有更多的东西可以做
* 图像理解，场景理解，问答，场景检索，思维导图生成
* 上一点也适用于视频

### 汤道生 让AI服务于人

* 腾讯的AI产品
  * 微信语音转文字
  * QQ视频挂件，QQ扫码转文字
  * 天天P图：美颜美妆
  * QQ音乐：个性化推荐
  * 企鹅FM：文字转语音
  * 全民K歌：伴奏分离
* 腾讯在方面已经有不错的工具，可以集成到我们想要做的东西中
  * 腾讯云小微三大开放平台
  * 腾讯云智慧交通
  * 腾讯觅影
* 可以做的问题：
  * 手机性能挖掘，模型压缩，内存共享

### 马维英 人工智能和新一代信息与内容平台

* 今日头条：智能内容分发（推荐系统）

## 10-26 pm 深度学习与医疗影像分论坛

### 医疗图像与疾病预警

* 医疗数据结构化，为每个病人提供个性化服务
* 通过对图像进行分析，加上医生判断，实现半自动阅片
* 对图像做异常检测，对异常数据进行预警

### 图像分割

> 图像分割是医疗图像中一个很重要的任务，通常分为分割，配准，可视化几个子任务
>
> * 分割面临的困难：不同目标区域亮度一致，区分度小，不同目标区域边界模糊，图像采集存在噪声
> * 两种分割任务： 区域分割（二维）、曲面分割（三维）
> * 分割步骤：检测识别（定位），边界寻优
> * 当前的一些分割方法：
>   * 按照图像中区域之间的联系，或者图像中的能量，将图像转为图，用图割，图搜索的方法，对图像进行分割
>   * 外观模型：特定的目标区域往往具有特殊的外观，包括轮廓，形状，可以用外观模型进行匹配，做粗粒度的分割，或者对细粒度处理后的图像进行校正，
>   * 多模态图像处理
>     * 融合结构信息和功能信息合起来进行分割
>     * 对准两个模型（结构和功能）的图像，对两个模型的预测结果进行约束（比如希望两个模型的输出相近）
>     * 双模型交互迭代优化
>   * 曲面分割
>     * 对曲面做分层
>     * 建模成三维的图结构，对边权和点权做最大流最小割
>   * 异常区域分割
>     * 先对区域进行矫正，再用原来的方法进行分割
>   * 数据后处理：欠分割，过分割的解决
>     * 添加位置约束
>     * 边缘匹配
>     * 多边形近似
>       * 对于某种目标区域，有着固定的多边形外观，可通过多边形近似的方法，标记出图像中近似的特征点
>       * 实际图像和多边形往往不完全相似，可以用聚类的方法对瓶颈进行校正
>       * 添加像素与边缘的平均几何距离约束，可以使得标记出的边缘更加接近真实边缘
>   * 特征用深度学习的方法提取，距离度量用传统方法

### 基于贝叶斯的视觉信息编解码

* 任务
  * 视觉信息编码：视觉信息->人脑->神经活动（编码）（反之就是解码，解码也可能解码为语义信息）
  * 神经活动可以用仪器测得的脑电波变化表达
* 模型
  * 对图像进行卷积（这是推理网络），得到中间特征，用这种中间特征与神经活动进行关联，从而得到神经活动的编码
  * 将神经活动进行反卷积（这是生成网络），得到图像，
  * 目标信号的生成模型融入相似度分析，即，用两个信号是否属于对同一对象的概率作为学习目标，建立起一个贝叶斯线性模型
* 除了视觉数据之外，还有其他模态的数据，可以根据其他模态的数据构建多视图生成式自编码器

### DL

* 小数据集下的深度学习
  * 数据增广
  * pretrain
  * 传统方法加深度学习做检测
    * faster rcnn提取特征（可能漏选，可以用传统方法预筛选更简单有效）
    * 多尺度卷积分类（LUNA2016第四名）
* 分类
  * 领域知识在特征提取中的作用
    * 领域知识进行预处理，对于不同的输入图片，提取不同的特征，对多种特征融合预测
* 分割
  * 多网络提取特征融合（ensemble）
  * 不同网络提取不同部分或者不同结构的局部的特征，将特征拼接起来
* 多模型投票
* 多模型相互学习（深度协同）
  * identification loss and classification loss
* 贝叶斯推理
* 深度学习影响分析
* 将先验知识设计到网络中
* 模拟数据去除隐私问题
* 脉冲神经网络
* 领域知识最大的作用在于不是直接端到端，而是对问题做分割，对子问题做端到端
* 移动GPU

### 异常检测

* 只有正常数据，如何发现异常数据
* 高斯模型，低概率区域为异常数据
* 高斯过程学习（非参数模型）
* 生成式↑
* 判别式（基于分类）↓
* 单类SVM：将原点作为第二类，让超平面离原点尽可能远
* 分类结果差越多（？？），说明越异常
* GAN做异常检测（如果还原出现异常（异常的局部会还原失败，从而自动完成标注），说明是异常图像）

## 10-27 am

### 物体识别到场景理解

* Face Recognition, Car Recognition
* 单类识别，多类识别
* 可扩展方向：性能，稳定性，可解释性，推广性，与人感知的一致性
* 视觉：什么东西在哪里
* 场景理解-知识图谱
* 属性组合挖掘

## 10-27 pm

### 语音前沿技术

* 港中文
  * Man-Computer Symbiosis:人机共生
  * Microsoft speech-recognition
  * 人机共生三种场景
    * AI competencies
      * Challenge: 语音加情感识别，场景丰富，non-native, dysarthric, personal speaker
    * 人机协作解决困难问题
      * RFID加在电车上，可视化，动态规划车次
    * 人机合作发明新的知识
      * AI进行search,retrieve，cluster,categorize,compare....
  * Challenge: 语音加情感识别，场景丰富，non-native dysarthric speaker
* 李锦辉 ECE
  * 语音识别（ASR），实际错词率比声称的高
  * 语音总是备选项，需要solution，speech app（在用户hands,eye-busy scenarios里）
  * more than WERs
  * 频谱转换（paradigm shift，旧方法应用在新的场景）
  * 降噪，增强，杂音分离，消除回响
  * 信号处理->识别
  * DNN黑箱
    * 属性分析，专业知识，不能盲目分析，不能说只有标签就行，knowledge-driven
    * 例如发音中识别摩擦音，爆破音
    * 将传统模型中里程碑式的东西拿过来用        &#x20;
  * 自动化语音属性抓取
* 搜狗 陈伟
  * 自然交互
  * 知识计算
  * 语音←(asr tts)→语言←(ocr 图像生成)→图像
  * 语音听写（字幕，演讲，采访），语音翻译，语音同传
  * 可穿戴设备，车载，智能家居
  * SeqSequence CNN LSTM
  * 运算平台：单卡3TFlops->1PFlops
  * 基于容器对GPU运算做调度
  * GPU -> FPGA -> RDMA
  * 移动端：模型压缩，轻量化

### 人工智能与机器学习前沿技术论坛

* 朱军：半监督深度学习模型
  * 贝叶斯深度学习
  * 基于贝叶斯推断的深度生成模型
  * 对GAN加中间约束的生成模型
  * ZhuSuan（珠算）：概率编程模型，开源可用
* 演化算法
  * 适用于：解空间不规则，需求不好精确建模的情况
* 视频检索的哈希学习
  * 图像检索
    * 通常的特征太大，检索太慢
    * 用二进制编码的一个哈希值来表达特征
    * 设计一个损失，约束正负样本的相似度误差，用变量绝对值与1的差的一范数等价为二进制约束
  * 视频检索
    * 对两段视频分别切割为帧图片做图像检索的哈希学习

## 10-28 am

* 下一个互联网引爆点
* 十年后的智能机器人

## 10-28 pm 多媒体计算

### 多媒体计算

* AI2.0
  * 大数据智能
  * 群体智能
  * 跨媒体智能
  * 混合增强智能
  * 自主无人系统
* 应用：制造，农业，医疗

### 朱文武 TMM趋势

* TMM介绍
  * IEEE of Transactions on Multimedia
  * 多媒体计算
  * 多媒体社交
  * 多媒体信号处理
  * 多媒体应用和系统
* 趋势：
  * 2.5->3.5,CCF A, 长文
  * 一年900篇提交，接受30%-35%
  * 中了之后半年内出
  * 超过60%是机器学习+图像视频分析，多模态，跨媒体
* 跨媒体智能
  * 文本图像语音视频及其交互属性混合
  * 多源融合+知识演化+系统演化
  * 解决语义鸿沟（机器认识世界是什么）意图鸿沟（机器理解人要达到什么目标）
  * 机器学习助力多媒体目前效果好，多媒体知识助力AI不成熟
  * 跨媒体深度分析和综合推理
    * 浅层到深度
    * 知识图谱指导多媒体分析，属性补全，知识表达理解是以后的趋势，多媒体理解，视频QA之类
    * 难点：跨媒体知识学习推理，多媒体情感分析
    * 知识离散，特征连续，如何转化
    * 知识和数据如何融合
    * 媒体到机器学习近期套路：
      * 深度学习+反馈（知识、规则进行反馈/强化学习）（黑箱）
      * 统计推理，贝叶斯推理（白盒）
    * 数据驱动 && 知识驱动
  * Cross-media analysis and reasoning: advances and directions

### 图像与视频生成的规则约束学习(GAN)

* 已有工作
  * 人脸姿态转换，年龄转换，表情转换
  * 超分辨率，画风转换，字体转换，图片转视频
  * 应用：
    * 动画自动制作（补间），手语生成
    * 视频自动编辑（天气变化）
* WGAN，PPGAN
* 无监督新框架
* 创意：随机性
* 难点
  * 解空间巨大：需要找出解所在的低维子空间
  * 宏观结构的一致性（视频生成需要的像素感受野（pooling）很大，难以预测长期运动变化）
  * 微观结构的清晰度，要同时逼近多模分布，避免单模生成的结果不够精确
* 用领域中的规则去约束GAN，加入破坏规则的代价
* 缩小预测空间，保证宏观结构，加快细节生成
* 工作介绍：
  * 景深风景生成
    * 难点：要求空间结构合理，不能有严重的模糊
    * 约束：从现有风景图像中对景深关系建模（对区域进行标注， 不同区域有不同的远近限制）
    * 建立位置和对象的关系，得到某个位置有某个对象的概率分布
    * Hawkes过程模型
    * 根据对象对图层做分解，由概率约束建立图层约束
    * 层内DCGAN，层间LSTM聚合出整图
  * 骨架约束的人体视频生成
    * 骨架运动有约束
    * 骨架提取很鲁棒，可以得到很多有标签知识（传统方法用来提取知识）
    * 静图+动作序列变动图
    * CNN编码解码，孪生网络双输入进行生成
    * 判别器：对生成和实际帧做Triplet loss优化
    * gan loss和视频相似度loss相加
    * 交互运动视频生成
* 创意+规则约束+复杂场景+复杂交互

### 基于锚图的视觉数据分析

* 图学习
  * 相似度矩阵 -> 图的邻接矩阵 -> 用图的方法对邻接矩阵进行优化
  * 标号建模 标号平滑 标号学习
* 锚图学习（速度+）
  * coarse to fine
  * 利用数据点图，生成锚点图，先采一部分有代表性的数据（例如聚类中心）生成一个图模型，然后推理出其他图
  * 图模型：表示矩阵，邻接矩阵，如何建立，加快相似度计算
* 高效锚图（性能速度+）
  * 数学上优化约束条件
* 层次化锚图（速度++）
  * 锚点是线性增加的，也会增加得很快
  * 对第一层采样的点做再采样，多层采样减少了锚点数目，从最少的锚点的层逐层推理
* 标号预测器（速度+++）
  * 优化对锚点的标号
  * 对最小的锚点层接一个优化器进行标号预测
* 主动学习（样本选择）
  * 减小标号的误差损失
* Google Expander Graph Learning平台：经典方法，并行运算

### 彭宇新 跨媒体智能

* 形式上多源异构，语义上相互关联
* 聚焦于跨媒体统一空间学习，获得多个媒体间共享的潜在子空间
* 跨媒体实体关系，跨媒体知识图谱，跨媒体知识演化和推理
  * 讲了好多篇论文的Motivation和Solution，我会具体整理另一篇文章
* 跨媒体智能描述与生成
* 用于信息检索
* 应用：内容自动监管，舆情分析，智能医疗

### 层次记忆网络：视频问答 跨媒体推理

* 视频转语言
  * 视频帧 + 时序依赖 -> 动态动作信息
  * 选择ROI帧，时序结构编码（Dual Memory Recurrent Model--LSTM扩充来的），生成句子
* 层次记忆网络
  * Image QA: 检查是否真的理解了图片
* 视频时序推理


# ECCV 2016 Hydra CCNN

Towards perspective-free object counting with deep learning，这是一篇发在ECCV 2016上的论文，提出一种多输入的CNN模型来解决Object Counting的问题。

## Object Counting

### 定义

给定一张图片，输出图片中目标对象的个数，比如下面两张图，左图有36个车，右图有8个猪

![](/files/-L_G1DPlZxr4pScUmxK5)

### 常用方法

* Counting by detection

  用检测器去检测图中有多少个对象，检测到多少个就认为是多少个

![](/files/-MkuN6LSO2TUAAXVDX5l)

检测有三种方法，整体的检测（图a，检测整个人），部分的检测（图b，检测头），形状的匹配（图c，人的形状抽象成几何图形）

* Counting by Clustering

![](/files/-L_G1DPpXQdIWOBLe0CT)

* 在摄像头捕捉到的连续视频帧里，目标移动时，在多个帧之间的位置比较相近，将这些运动物体在多帧图片之间做聚类，聚类中心的个数就是目标的个数
* 不适用于静止物体
* 优点：无监督

更多细节可看Related work部分的参考文献

* Counting by Regression

![](/files/-MkuN6LTtuX8SpO6iApn)

* 给定输入图片，ROI，透视图（场景中由远及近的几何关系，用于缩放对象）
* 提取特征（背景分离，边缘检测，纹理识别）
* 学习一种映射，将特征回归到对象数量上

回归又分两种，

* 回归对象数量
* 回归对象密度图

具体讲一下对象密度图：

* 先给出原图和目标对象的坐标（可以根据坐标画出质心图）

![](/files/-MkuN6LUMNMEwZ5idEYy)

* 对质心图做一个高斯滤波，可以得到密度图，作为回归的目标，对质心图进行求和，反过来可以得到目标的数量

![](/files/-MkuN6LVeKSB1LLdyKBW)

> 进入正题，Hydra CCNN

* 方法： 回归密度图，多尺度输入组合
* 优势：不需要透视图，多尺度鲁棒性，训练简单，误差小

![](/files/-MkuN6LWtEs84U9xkzJN)

## CCNN(Counting CNN)

* Regression: 用一个CNN将原始图像映射为对象密度图

  ![](/files/-MkuN6LXFjfRBxauaHpz)

$$D\_{pred}^{(P)} = R(P|\omega)$$

其中P是image patch，$$\omega$$是CNN参数

* Conv1, Conv2, Conv3后面都跟着一个max pooling层
* Conv4和Conv5都是卷积层（比全连接层更快，参数更少）
* 对比论文中提到的另一种方法：Zhang et al：卷积后接全连接层，损失为密度图和数量的回归误差，两个损失交替进行优化，CCNN更快，并且训练更简单。
* Loss： $$l(\omega) = \frac{1}{2N}\sum\_{n=1}^{N}||D\_{pred}^{(P\_n)}-D\_{gt}^{(P\_n)}||\_2^2$$，gt是ground truth的意思，这个损失就是求每张图的密度图的预测插值的l2模，然后求平均，N为图片数量
* 训练：滑动窗口将图片划分为多个网格，对每个网格做回归，将回归的密度图拼接成一个完整尺寸的密度图

## Hydra CCNN

Hydra: 海德拉是希腊神话中的九头蛇

* Motivation: 多尺度范围内的对象大小不同，会导致计数出错
* Solution: 用多个CCNN各自回归多个尺度的密度图，组合成最终密度图

![](/files/-MkuN6LY2V-Au0xOGPnV)

举个例子：n=3时，也就是回归三个尺度的密度图，

* S0: 整个patch（不是整张图，是一个patch，前面讲了会做滑窗划分）
* S1：在中间抠出面积为2/3的图
* S2：在中间抠出面积为1/3的图

> 私以为这个地方还有改进空间

## 实验结果

### 数据集

* TRANCOS（汽车），UCSD（行人），UCF\_CC\_50（行人）
* 评价指标：
  * MAE（Mean Absolute Error）
  * GAME(Grid Average Mean Absolute Error)：GAME(L) = \frac{1}{N}\su&#x6D;*{n=1}^{N}\sum*{l=1}^{4^L}|&#x44;*{I\_n}^{l}-D*{I\_n^{gt}}^{l}|
    * N是图片总数
    * L: 对于每张图，划分成$$4^L$$个小格，计算每个小格的误差
    * L为0时就是MAE


# CNCC2017深度学习与跨媒体智能

转载请注明作者：[梦里茶](https://github.com/ahangchen)

## 目录

* 机器学习与跨媒体智能
  * 传统方法与深度学习
    * 图像分割
    * 小数据集下的深度学习
    * 语音前沿技术
  * 生成模型
    * 基于贝叶斯的视觉信息编解码
    * 珠算：基于别噎死推断的深度生成模型库
    * 图像与视频生成的规则约束
      * 景深风景生成
      * 骨架约束的人体视频生成
  * 跨媒体智能
    * 视频检索的哈希学习
    * 多媒体与知识图谱
    * 基于锚图的视觉数据分析
    * 视频问答
    * 细粒度分类
    * 跨媒体关联与检索（待补充）

正片开始

## 传统方法与深度学习

### 图像分割

图像分割是医疗图像中一个很重要的任务，通常分为分割，配准，可视化几个子任务。这里贴一张广义的图像分割的图：

![](/files/-MkuKtdlQMQg0feSK9wB)

#### 存在的困难：

* 不同目标区域亮度一致，区分度小，
* 不同目标区域边界模糊，
* 图像采集存在噪声

#### 常用分割步骤

检测（定位）-> 边界寻优

#### 常用分割方法

* 按照图像中区域的能量与联系，建立`图模型`，用图割，图搜索的方法对图像进行分割
* `外观模型`：特定的目标区域往往具有特殊的外观，包括轮廓，形状，可以用外观模型进行匹配，做粗粒度的分割，或者对细粒度处理后的图像进行校正
* 多模态图像处理：融合`结构信息`和`功能信息`进行分割
  * 对准两个模型（结构和功能）的图像，对两个模型的预测结果进行约束（比如希望两个模型的输出相近）
  * `双模型交互迭代优化`
* 多边形近似
  * 对于某种目标区域，有着固定的多边形外观，可通过多边形近似的方法，标记出图像中近似的特征点&#x20;

### 语音前沿技术

#### 任务

降噪，增强，杂音分离，消除回响

#### 结合领域知识和DNN

* 数据标注：结合领域知识提出需要标注哪些数据
* 不直接学习目标，而是根据领域知识将目标任务进行分解
  * 比如识别字母，分解为识别摩擦音，爆破音
* 将传统模型中里程碑式的东西拿过来用

#### 移动端语音挑战

模型压缩，轻量化

## 生成模型

### 基于贝叶斯的视觉信息编解码

#### 任务

* 视觉信息编码：视觉信息通过人脑转为神经活动的过程
* 视觉信息解码：神经活动新号转为视觉信息的过程

#### 模型（基于卷积和反卷积的自编码器）

* 推理网络：卷积神经网络，得到中间特征，建立中间特征与神经活动信号之间的关联，从而得到神经活动得到编码
* 生成网络：将神经活动进行反卷积，得到图像
* 对于两个信号，学习两个信号产生于同一对象的概率（相似度分析），建立起一个贝叶斯推断模型

#### 多视图生成式自编码器

除了视觉数据之外，还有其他模态的数据，可以根据多个模态的数据构建多视图的生成时自编码器

### 珠算：基于贝叶斯推断的深度生成模型库

#### 任务

大数据中有许多不确定因素，需要学习对不确定性建模

#### 模型

![](/files/-MkuKtdpcgBFmQsYmTOG)

给定一个输入z，用神经网络学习变量x的分布的参数（均值和方差），约束生成样本与真实样本的相似性

#### 有约束的GAN

![](/files/-MkuKtdrLBSzVNG5lESS)

在GAN的基础上，加一个分类器C，对生成器G生成的对象加中间约束，使得生成的对象更符合实际需求，比如生成不同姿态的人脸，要求不同人的人脸尽量不同，同个人的人脸尽量相同。

#### 珠算

* 基于Tensorflow的python库，无监督生成模型
* 贝叶斯推断
* 适合传统多层贝叶斯推断模型以及深度生成模型
* 可用于
  * 多变量回归
  * 变分自编码器实现
* <http://zhusuan.readthedocs.io>

### 图像与视频生成的规则约束学习

* GAN成为无监督领域的新框架
  * WGAN，DCGAN
  * 在生成中，往往通过随机性引入创意
* 已有工作
  * 人脸姿态转换，人脸年龄转换，人脸表情转换
  * 图像超分辨率生成，画风转换，字体转换，图像转视频
* 应用
  * 动画自动制作，手语生成
  * 视频自动编辑（如生成不同天气情况下的风景）
  * 创意+规则约束+复杂场景+复杂交互
* 难点
  * 解空间巨大：需要找出解所在的低维子空间
  * 宏观结构的一致性（视频生成需要的像素感受野（pooling）很大，难以预测长期运动变化）
  * 微观结构的清晰度，要同时逼近多模分布，避免单模生成的结果不够精确
* 解决方法
  * 用领域中的规则去约束GAN，加入破坏规则的代价
  * 缩小预测空间，保证宏观结构，加快细节生成

#### 景深风景生成

* 难点：要求空间结构合理，不能有严重的模糊
* 约束：从现有风景图像中对景深关系建模（对区域进行标注， 不同区域，即图层，有不同的远近限制）
* 建立位置和对象的关系，得到某个位置有某个对象的概率分布
* Hawkes过程模型
* 根据对象对图层做分解，由概率约束建立图层约束（树在人之前的概率有多大）
* 层内DCGAN，层间LSTM聚合出整图

#### 骨架约束的人体视频生成

* 骨架运动有约束
* 骨架提取很鲁棒，可以得到很多有标签知识（传统方法用来提取知识），作为约束条件
* 静图+动作序列变动图
* CNN编码解码，孪生网络双输入进行生成
* 判别器：对生成和实际帧做Triplet loss优化
* gan loss和视频相似度loss相加
* 交互运动视频生成

#### 视频检索的哈希学习

> Learning Multifunctional Binary Codes for Both Category and Attribute Oriented Retrieval Tasks

视频检索基于图像检索，大规模图像检索对性能要求较高

* 图像检索
  * 任务：通常图像特征很大，直接检索特征太慢
  * 方法：
    * 用二进制编码出一个哈希值来表达特征
    * 对哈希值做高效的异或运算求相似度
    * 模型（添加了对二进制编码的约束，希望绝对值与1尽量相近）：

![](/files/-L_G1Db5lVlSPraWhsvV)

#### 多媒体与知识图谱

> Cross-media analysis and reasoning: advances and directions

* 任务：
  * 将文本，图像，语音，视频及其交互属性进行混合
  * 多源融合+知识演化+系统演化
* 难点：
  * 解决语义鸿沟（机器认识世界是什么）
  * 意图鸿沟（机器理解人要达到什么目标）
  * 离散的知识和连续的特征如何转化如何关联
* 典型问题：
  * 跨媒体知识学习推理，多媒体情感分析
* 现状：
  * 机器学习助力多媒体效果很好
  * 多媒体助力机器学习还不成熟
* 任务：
  * 跨媒体深度分析和综合推理
* 方法：
  * 从浅层到深度
  * 知识图谱指导多媒体分析，属性补全
  * 深度学习+反馈（知识和规则进行反馈/强化学习）（黑箱方法）
  * 统计推理，贝叶斯推理（白盒方法）
* 趋势：
  * 知识表达理解，多媒体理解

#### 基于锚图的视觉数据分析

* 图学习
  * 对视觉数据可以计算相似度，对于整个数据集就可以得到一个相似度矩阵，学过图论的同学都知道，矩阵就是图
  * 相似度矩阵 -> 图的邻接矩阵 -> 用图的方法对邻接矩阵进行优化
  * 标号建模 标号平滑 标号学习
* 锚图学习（速度+）
  * 这是一种coarse to fine的思路
  * 利用数据点图，生成锚点图，先采一部分有代表性的数据（例如聚类中心）生成一个图模型，然后推理出其他图
  * 图模型中需要建立表示矩阵（特征工程），邻接矩阵（度量学习），并加快相似度计算
* 高效锚图（性能速度+）
  * 从数学上优化锚图的约束条件，使得优化问题的复杂度大大降低
* 层次化锚图（速度++）
  * 建立多层的锚图，也就是对采样点再采样
  * 锚点是线性增加的，也会增加得很快
  * 对第一层采样的点做再采样，多层采样减少了锚点数目，从最少的锚点的层逐层推理
* 标号预测器（速度+++）
  * 优化对锚点的标号（打伪标签进行半监督学习）
  * 对最小的锚点层接一个优化器进行标号预测
* 主动学习（样本选择）
  * 是一种hard mining的思路，选择更有用的样本作为锚点
  * 减小标号的误差损失
* 对比Google Expander Graph Learning平台：经典方法，并行运算，而锚图可以通过并行进一步提升速度

#### 视频问答

* 任务：
  * 输入视频，问题，输出答案
* 模型（层次记忆网络+视频时序推理）：
  * 对图像进行分层
  * 对问题进行记忆
  * 用文本和图像特征一同训练生成答案
  * 用LSTM做时序推理

### 细粒度分类

* 任务：
  * 识别图像同一大类中的子类
* 挑战：
  * 姿态视角不同导致类内差异大，外形颜色相似导致类间差异小

#### 基于模型动态扩容的增量深度学习方法

论文：Error-Driven Incremental Learning in Deep Convolutional Neural Network for Large-Scale Image Classification

* 将目标的多个类别按相似度划分为几个大类，
* 增加一个新的类别时，将其归入最相近的大类中，重用大类的参数，扩展小类分类层参数
* 利用类别子集合划分实现模型动态扩容，利用特征迁移学习实现训练加速（对类别做聚类）

  ![](/files/-L_G1DiwaZLdjozSSKnA)

#### 局部两级注意力深度模型

> The Application of Two-level Attention Models in Deep Convolutional Neural Network for Fine-grained Image Classification

给定图片-类别，不给出对象位置（bounding box）和局部的位置(part location)，用Attention学习对象位置和局部特征

* Object level: 首先用公开的数据集预训练模型，top-down地作用在整图上，选出跟目标相关的区域（响应度最高的区域），相当于抠图，对抠过的区域再加上类别标签进行迁移学习。

![](/files/-L_G1Diy-9nbxzpDG5AA)

* Part level:&#x20;
  * 对于Object level得到的模型，对卷积层的filter做相似度聚类，同一类的卷积层合为一个part detector，用来为具体的对象局部做识别

![](/files/-MkuKtdyTSecfxY1JXkq)

* 结合总体评分和局部评分来对对象做细粒度分类

#### 空间约束的显著性部件选择模型

> Weakly Supervised Learning of Part Selection Model with Spatial Constraints for Fine-grained Image Classification

* 显著性提取和协同分割定位对象
* 先通过显著性聚类提出备选局部，
* 再对局部位置关系提出两个空间约束：局部和整体必须有尽可能多的重叠，局部之间有尽可能少的重叠。

![](/files/-L_G1DbEiWnMnZjvGtVn)

上面两篇都是不需要局部组件的标注，就学到了局部的特征和约束

#### 显著性引导的细粒度辨识性定位方法

> Fine-grained Discriminative Localization via Saliency-guided Faster R-CNN

结合分类模型和检测模型做更高精度的细粒度分类

* 显著性模型提供弱标记的图片训练faster r-cnn检测模型
* 检测模型提供更精确的备选区域进行分类

![](/files/-MkuKtdzdL7iMNTcKrpx)

#### 视觉文本联合建模的图像细粒度表示

> Fine-grained Image Classification via Combining Vision and Language

* 在图片数据集的基础上，增加对图片的描述文本，利用这两个模态的数据提供更高精度的细粒度分类
* 卷积做图像分类，CNN+LSTM做文本分类，两个分类结果合起来

![](/files/-L_G1Dj53ixdYQdu27c9)

### 跨媒体关联与检索

* 跨媒体统一表征学习：使用相同的特征类型表征不同媒体的数据
* 跨媒体相似度计算：通过分析跨媒体关联关系，计算不同媒体数据的语义相似性

这里的六篇论文我还没读完，读完之后补具体的理解

#### 跨媒体关联传递方法

> IJCV2013： Exhaustive and Efficient Constraint Propagation

#### 基于稀疏和半监督的统一表征方法

> Learning Cross-Media Joint Representation With Sparse and Semisupervised Regularization

#### 基于跨媒体语义单元的统一表征方法

> Semi-Supervised Cross-Media Feature Learning with Unified Patch Graph Regularization

#### 基于跨媒体多深度网络的统一表征方法

> Cross-media Shared Representation by Hierarchical Learning with Multiple Deep Networks

#### 基于多粒度层级网络跨媒体关联学习方法

> CCL: Cross-modal Correlation Learning with Multi-grained Fusion by Hierarchical Network

#### 跨媒体混合迁移网络方法

> Cross-modal Common Representation Learning by Hybrid Transfer Network, IJCAI2017

#### 跨媒体检索数据集PKU-XMedia

* [www.icst.pku.edu.cn/mlpl/XMedia](http://www.icst.pku.edu.cn/mlpl/XMedia)
* 五种媒体类型（图像、文本、视频、音频、3D）
* 10万标注数据，200个语义类别，基于wordNet的层次结构
* 来自Wikipedia, Flickr, Youtube, Findsounds, Freesound, Yobi3D


# MLA2016笔记

## 开幕式

* ppt会在[网站](http://lamda.nju.edu.cn/conf/mla16/program.htm)上共享
* 每年的会议也会由清华大学出版社整理为《机器学习及其应用》一书
* 公众号：MLA2016

> 以下笔记整理与会议期间，主要关注机器学习应用方向，对于一些比较小众或者高深的topic，由于个人水平有限，会跳过一部分不讲。

## Presentation（11月5日）

### 分布式学习

* 背景
  * 大数据分布式学习介绍
  * 大数据自然地分散存储
* 问题
  * 数据分为子集，子集运算，output平均
  * 分布式结果与单机运算结果理论上是否应该近似？？
* 常用方法
  * 最小二乘回归：最小化预测与实际值差异 - 过拟合 - 假设空间
  * 假设空间越大学习效果越好
  * 具体应用时假设空间会被限制
  * 目标函数要学好，需要与kernel空间有联系，具体关系由一个函数描述
  * kernel空间
  * 假设空间越大，函数光滑性越好
* 回归分布式学习
  * 函数复杂度：λ趋向无穷的速度
  * 衡量学习算法的好坏&#x20;
* 单机结果与分布式结果的误差由一个公式描述，这个公式表述了：
  * 分布式能达到单机效果
  * m一般越大越接近单机结果，分的多variance越小，但不能分到底，要有一个限制
* 数学上的问题：最小二乘回归能得出minimize rate吗？用二次分解解决
* 深度学习困境：
  * 黑箱：强加条件可能有效但不明所以
  * 浅层神经网络做不了局部近似，两层可以做到，三层与两层效果往往有很大差别，但不明所以
  * 希望从数学上说明更多层数能获得许多不同的结构层次信息
    * 内积学的是多个方向上的东西，多层时方向维度信息指数增加
  * sgd
  * 在经典结构上加些几何结构能学到数学上对应的结构
  * minor gradient
  * 深度网络，函数有结构才能学得好

### ML in NLU，CL，NLP

* NLP && DL
* 机器翻译，情感分析，自动摘要，问答系统，关系抽取
* 挑战
  * 未知现象太多
  * 歧义，双关，隐喻
  * 跨文化语义
  * 方法
    * 规则：词法，词性
    * 统计：概率，贝叶斯，语言模型，翻译模型，解码器
      * 分词-短语-翻译-调序
    * 常用方法，开源工具
    * example：
      * 由字构词，识别词的位置做为特征，进一步得到词义
      * 词义消岐，词性，位置
      * 文本分类，分类器组合
* 深度学习
  * RBM无监督做图像，HMM语音识别的突破
  * 词向量
    * one hot 相乘为0！
    * 近似词放在相近的地方
    * 词表规模， 词向量维度的确定
    * 词向量获取CBOW，etc
  * 句子语义表达：RNN，attention（在句子中的重要性，可用眼动仪测量）
  * 翻译：生词 - 概率优化 - 同义词 - 拆词 - 补词
* 《统计自然语言处理》宗成庆
  * 深度学习不等于深度理解
    * 难以全文分析，就缺乏归纳推理
    * 常识学习
  * 深度学习泛滥，应该多考虑其他机器学习方法的出路

### 忆阻

* 硬件模拟大脑计算
* 内存基于电容，易失，忆阻不易失
* BP更新权重需要用外部电路辅助实现

## Spotlight

* hulu：CF-NADE神经网络协同过滤推荐
* CNN可视化，随机权重少量训练，重构图像，验证ML也不一定需要大量训练 \*\*
* 估计光泽量，反射量，考量对数变换
* 深度网络缩小文本与图像的语义差异，再映射？？
* 迁移学习：模型的联合概率分布有差异
* 高维非凸最优化
* 函数偏移值强化学习
* 异构人脸识别
* loss function求和优化，共享内存异步优化
* 深度学习+哈希学习
* 蒙特卡罗预测状态

## Presentation 11-06

### 迁移学习 kernel embedding of distribution

* 分类聚类，增强学习
* wifi室内定位，N个路由器，N维向量，
  * 连续空间：回归问题
  * 设备移动，记录位置和路由器强度
  * 回归学习定位系统
  * WiFi对温度敏感，所以强度会发生变化，不同目标设备也会影响强度值
  * 使用迁移学习，让后来的设备适配先前的设备模型
* 情感分析
  * 用户情感词可能很多种，不同产品会有不同类型的情感词
  * 添加enable标签
* Bug predict
* 机器学习强假设：训练与测试数据需要来源相同，分布相同，特征相同
* 其实我们只需要收集一些enable数据和一点feature数据（半监督）
* 添加迁移学习方法预测target
* 异构迁移，同构迁移
* 监督迁移，半监督，无监督迁移
* 基于样本迁移学习
  * source跟target在数据集上有重叠
* 基于特征迁移学习
  * 训练集与测试集只有一部分重叠特征
  * 特征映射到一个空间，在这个空间训练集与测试集接近
* 基于参数迁移学习
  * 可能迁移目标与源在参数上相近
* 基于关系迁移学习
  * source和target在数据之间的关系可能相同
* 通用的基于特征的迁移学习
  * source和target结构相似，
  * 假设它们有潜在的相同因子
  * 去掉一些因子，使得数据的分布不变，那这些因子就是它们的共同特征
  * 同时还要尽量保持原来的数值
  * 最小化source和target分布的差异
  * RKHS
  * 找一个向量代表一组数据
  * 增加多维统计信息比如(E\[x] E\[x2] ...)
  * 无穷维，用核函数形式展示

### 搜索引擎信息检索

* 略

### 可视化特征学习与表达

* 特征的寻找，低维表达高维图像数据 - 子空间
* 稀疏特征表达 \*\*
* 人脸 - 关键点定位 - 让模拟关键点与实际关键点误差最小，先拟合再回归
* CSR
* hypergraph base：feature hyper edge： 轮廓
  * 空间大，边不能自适应非均匀自适应分布

### Bug mining

* 自然语言与程序语言存在不同
* 需要根据问题本身进行设计

### 多任务学习

* school数据集：每个学校单独预测，多个学校同时联合训练
  * 弥补训练数据的缺失
* 训练过程中多任务联合，抓住任务间联系，建模，预测
* 重点在任务之间的关联
  * 假设所有任务相似
    * 假设所有任务参数向量接近，让所有任务共享方差，简单，假设太强
  * 特征空间相似
    * 共享同一组特征：所有任务都会使用特征中的某一部分，group sparsity l1,q norm 规范化，最小化norm，使得共享稀疏特征
  * 共享低维子空间
    * rank minimization，增加一个rank项，rank最小化
  * 共享结构，聚簇，图，树
    * 聚簇：同一个族中的任务距离更相近
  * 学习外围任务
* 避免无关因素影响
* 约定
  * 每个任务有一个特征矩阵，不同任务样本数量不同，矩阵长度不同，但所有任务的特征相同，矩阵宽度相同
  * 每个任务有一个参数向量
  * 用以上符号表达几乎所有多任务模型
  * 损失：正常的预测与实际值的偏差
  * 规范化参数
* 然而上述假设都有点强，需要分开：W = P + Q，任务由相关部分和不相关部分组成
  * l2 norm，在Q中让有些列为0，表示有些列是有特异性的
* 任务之间的关系：task-level（这种假设还是比较强）
* feature-level：任务在某些特征上是否相关，有何相关

> 协同聚类

* 规范化项
  * 仍然W = P + Q模型
  * Q描述协同聚类效果
  * 模拟任务与特征之间的协同聚类
  * Q的一个行是一个特征，Q的一个列是一个任务
  * 在Z向量上聚类
  * 两个规范化项
  * 第二个规范化项是非凸的，需要CoCMTL优化
* 优化
  * Low rank MTL
  * 为求解添加核范数 nuclear norm，做一个松弛
  * 矩阵信息主要由奇异值大的特征向量表达，很受核范数影响，产生负面因素
  * 在核范数前增加权值，奇异值越大，权重越小，权值自动优化，需要设计优化算法
  * 这个权重也是非凸，但物理意义更接近矩阵的秩
  * 权值的优化：近似非凸函数，随机权值，逐步调节
  * 权值优化收敛性：一定会收敛，收敛速度还可以
* 轨迹回归
  * 一个序列，含有多个路段的数据，预测通过路线的时间
  * n 个轨迹对，每个路段的行驶距离，需要预计走过每个路段的时间
* 挑战：
  * 不同时间走过一个路段的耗时是不同的
  * 轨迹数据非常稀疏
  * 训练样本有限
  * 不能单任务
* 将序列按时间划分成多个子集，每个时间段的预测分别为一个任务（比如早高峰一个任务，晚高峰一个任务， 平常时间为一个任务）
  * 相邻时间代价变化光滑，存在全局光滑性
  * 存在局部突出变化
* 分解
  * P 模拟全局平滑性质
  * Q 抓住局部性质
  * P 时间上的平滑性，空间上的平滑性
  * Q 异常现象，l inf,1 范数，达到列稀疏效果，描述了某些任务与其他大部分任务之间的区别
    * 高峰代价由全路段最大代价决定
* 建模完成
* 优化非平滑，需要近似
* 苏州出租车行驶数据，6W轨迹信息
* Q矩阵每列的最大值画出来，几乎描述了高峰的局部现象

### 大数据

* 核心：分析处理
* 大数据分析与处理的核心基础，搭建新平台，研发新算法
* 数据预处理，算法工程化
* 处理：计算机为基础
* 分析：数学为基础
* 基于全数据中心估计
* 基于数据分解的分布估计
* ADMM
* 理论决定深度结构
* 模型族决定假设空间
* 深度学习解决模型选择与参数选择
* 解反问题的一个新思路：模型求解与范例学习

### 自适应动态规划 - 学习控制

* 略

### 人脸识别

* triplet loss不需占用额外显存
* seetaface

## Spotlight

* 社交影响驾驶行为
  * 车联网
  * 司机之间的社交分享提高司机经验
  * 启发：在同一个地方都停留超过10分钟，就可能是有社交关系
  * 2013纽约出租车行驶数据集
  * 从行为模式建模出社交关系
  * 用社交关系得到权重作用于行为模式预测
  * 行为模式得到轨迹

## Special Session

> 顶会Review(以下环节中paper部分表示很值得关注的paper)

* ML
  * ICML
    * 神经网络，深度学习，优化，再增强学习，矩阵构造，无监督学习，在线学习，学习理论，应用
    * 过去十年最有影响文章：dynamic topic models
    * rnn,采样，动态组织模型深度强化学习&#x20;
  * NIPS
    * 论文数取决于场地。。
    * learning，model, network，optimization，deep， inference，贝叶斯
    * 顶会有tutorial，tensorflow，NVIDIA gpu介绍
    * 邀请牛人分享
    * workshop：bayesian, application, deep learning, new areas, others
    * Paper:&#x20;
      * Competitive Distribution Estimation: Why is Good-Turing Gooding good
      * Fast Convergence of Regularized Learning in Games
    * startup:
      * ai startup: openai was founded
    * hot areas: new models: optimization for dl , bayesian, reinference
    * adaptive data analysis: 实验不可泛化，需要避免
    * review: 每篇论文有六个reviewer，review会公开（重要）
  * COLT
    * 计算机理论文章进入这个圈子
    * 十大机构占半
    * 一小群数学家在这里开会
    * bandit, 计算机理论，online, 限制学习， 监督学习,pac
    * 两个invited talk
    * Paper:&#x20;
      * Multi-scale exploration of convex functions and bandit convex optimization
      * Provably manipulation-resistant reputation systems (协同过滤)
    * dl：理论：
    * the power of depth for feedforward neural networks
    * benefits of depth in neural networks
    * on the expressive power of deep learning: a tensor analysis
    * 深层少节点可行，浅层多节点才行
    * Paper
      * Online Learning in Repeated Auctions: 拍卖，true value unknow the true value
      * Learning Simple Auctions：证明多项式级样本可以达到买卖平衡
* AI
  * AAAI
    * tripleAI
    * topic: machine learning method, ml app, 博弈论,
    * 计算机视觉，web，nlp, 认知模型
    * 启发式，多智能，不确定，规划调度
    * 鲁棒AI
    * paper:
      * Bidirectional Search  That  Is  Guaranteed  to  Meet  in  the Middle
      * Toward  a Taxonomy  and Computational Models  of  Abnormalities in  Images
    * What is hot: meeting and competitions
  * [IJCAI](http://ijcai-16.org/index.php/welcome/view/accepted_papers)
    * deep learning渐弱
    * 传统领域review比较多，ml review少
    * ml, ai arguement 少
    * 投稿时解释清楚问题
    * co-author list投稿后一般不可变
    * 限制author投稿数量
    * knowledge graph, knowledge base
    * paper:&#x20;
      * Hierarchical Finite State Controllers for Generalized Planning
      * Using Task Features for Zero-Shot Knowledge Transfer in Lifelong Learning
* DM
  * [KDD](http://www.kdd.org/kdd2016/program/accepted-papers)
    * sigkdd
    * talks多
    * classical ml , techniques still , pronounce for solving dm tasks
    * graph, streawm, heterogeneous
    * clustering, neural network
    * paper:
      * FRAUDAR: Bounding Graph Fraud in the Face of Camouflage
      * Ranking Causal Anomalies via Temporal and Dynamical Analysis on Vanishing Correlations
      * TRIEST: Counting Local and Global Triangles in Fully-Dynamic Streams with Fixed Memory Size
      * Predicting Matchups and Preferences in Context
    * kdd有点看author
    * graphs over time:densification laws, shinking dismeters
    * kdd china: acm 数据挖掘中国分会
  * [ICDM](http://www.cs.uvm.edu/~icdm/Awards/BestPapers.shtml)
    * 数据挖掘blabla
    * 盲审,关注可重现性
    * Paper
      * Fast Random Walk with Restart and its Applications
      * Diamond Sampling for Approximate Maximum All-pairs Dot-product (MAD) Search&#x20;
      * From Micro to Macro: Uncovering and Predicting Information Cascading Process with Behavioral Dynamics&#x20;
    * 新应用，正面的论文title，
    * 不要早于两周提交，
    * 多跨界合作
    * 神经网络，学习 ⬆️
* Other
  * ISCA
    * 处理器架构
    * 寒武纪团队
    * 深度学习处理器
  * AI statistics↑
    * ai, ml, statistics
    * 在美国受认可
    * \\<4人审核，逐层审核
    * 高斯，图模型，优化，在线学习，聚类，矩阵，推理，贝叶斯，压缩感知，稀疏编码，深度学习
    * 半监督，nonlinear embedding and manifold learning , semi-supervised learning ↓
    * Paper
      * Provable Bayesian Inference via Particle Mirror Descent
  * UAI
    * AI的不确定性
    * 图模型，贝叶斯，因果推断
    * Paper
      * Stability of Causal Inference
      * Online learning with Erdos-Renyi side-observation graphs
    * bayesian, reinforce, optimization
    * 非凸问题，凸近似
    * 深度神经网络自由度
    * 理论理解，迁移学习中协同矩阵重构
    * 因果发现，贝叶斯应用，ml on health
    * 外国比较火，DL不太火&#x20;
  * ICLR
    * 小
    * emergeing
    * dl
    * open review
  * ACML
    * 亚太
    * 长文16页
    * 4-5 review
    * 两轮投稿
    * 会议转期刊 -> MLJ
    * ML
  * SIGIR
    * 信息检索
    * ML
    * search new trend from google
    * IR
      * Matching
      * Translation
      * classification
      * structured predicction
    * Word Embedding, rnn, cnn
  * ACM multimedia
    * 多媒体
    * 多个投稿方向
    * 视觉，多媒体搜索，，
    * DL on Multimedia，图片检索，视频分析
    * 图像视频自动描述
    * 多模态社交媒体主体意见挖掘
    * CNN分析菜肴
  * CVPR
    * CV
    * 应用Dl
    * 3D
    * 紧密结合工业界
    * 主题提取
    * 视频问答
    * imagenet
  * ICCV
    * CV
    * 提前投
    * DL ↑
    * Track
  * ACL
    * 计算语言学、自然语言处理
    * 工业界应用
    * 双盲审
    * 语义，语法，ML，资源与评估
  * ACM SIGGRAPH
    * 图形学顶会
    * 工业界，艺术界
    * TOG
    * Geometry
    * Animation
    * Human Model
    * 3d print, image processing， render↓
    * VR, AR, ML
    * novelty
    * 视觉效果


# 《机器学习》（周志华）读书笔记

## 概念

[概念列表](/ml/zzh_ml_notes/melon)出自西瓜书后面的索引

## 课后题

参考这位同学的[csdn博客](http://blog.csdn.net/icefire_tyh/article/details/52064910)

## 伪代码


# 西瓜书概念整理

* 括号表示概念出现的其他页码
* 如有兴趣协同整理，请到[issue](https://github.com/ahangchen/windy-afternoon/issues/2)中认领章节
* 公式采用latex编辑，github不能直接渲染，请到[gitbook页](https://ahangchen.gitbooks.io/windy-afternoon/content/ml/melon/)浏览

整理by: [ahangchen](https://github.com/ahangchen), [luopengting](https://github.com/luopengting), [hscspring](https://github.com/hscspring)

## 概念列表

|                     o                    |   [绪论](/ml/zzh_ml_notes/melon/ch01)   |                    o                   |
| :--------------------------------------: | :-----------------------------------: | :------------------------------------: |
|  [模型评估与选择](/ml/zzh_ml_notes/melon/ch02)  |  [线性模型](/ml/zzh_ml_notes/melon/ch03)  |   [决策树](/ml/zzh_ml_notes/melon/ch04)   |
|    [神经网络](/ml/zzh_ml_notes/melon/ch05)   |  [支持向量机](/ml/zzh_ml_notes/melon/ch06) |  [贝叶斯分类器](/ml/zzh_ml_notes/melon/ch07) |
|    [集成学习](/ml/zzh_ml_notes/melon/ch08)   |   [聚类](/ml/zzh_ml_notes/melon/ch09)   | [降维与度量学习](/ml/zzh_ml_notes/melon/ch10) |
| [特征选择与稀疏学习](/ml/zzh_ml_notes/melon/ch11) | [计算学习理论](/ml/zzh_ml_notes/melon/ch12) |  [半监督学习](/ml/zzh_ml_notes/melon/ch13)  |
|   [概率图模型](/ml/zzh_ml_notes/melon/ch14)   |  [规则学习](/ml/zzh_ml_notes/melon/ch15)  |   [强化学习](/ml/zzh_ml_notes/melon/ch16)  |
|                     o                    |   [附录](/ml/zzh_ml_notes/melon/ch17)   |                    o                   |


# 绪论

## 第一章 绪论

* Page2: 标记（label)

  示例结果的信息，例如“好瓜”，称为标记
* Page2: 假设(269)(hypothesis)

  学得模型对应了数据的某种潜在的规律，因此亦称假设
* Page2: 示例(instance)

  数据集中的每条记录是关于某个事件或对象的描述，称为一个“示例”或“样本”
* Page2: 属性(attribute)

  反映事务或对象在某方面的表现或性质的事项，如“色泽”，称为属性或特征
* Page2: 属性空间(attribute space)

  属性长成的空间称为属性空间，样本空间，或输入空间
* Page2: 数据集(data set)

  数据记录的集合称为一个数据集
* Page2: 特征(247)(feature)

  同属性
* Page2: 学习(learning)

  从数据中学得模型的过程称为学习或训练
* Page2: 学习器(learner)

  学习过程就是为了找出或逼近真相，有时将模型称作学习器
* Page2: 训练(training)

  同学习
* Page2: 训练集(training data)

  训练过程中使用的数据称为“训练集”，其中每个样本称为一个“训练样本”，训练样本组成的集合称为训练集
* Page2: 训练样本(training sample)

  见训练集
* Page2: 样本(sample)

  同示例
* Page2: 样本空间(sample space)

  同属性空间
* Page2: 样例(sample)

  同示例（instance）
* Page2: 真相(ground-truth)

  潜在规律本身称为真相或真实
* Page3: 标记空间(label space)

  所有标记的集合称为标记空间或输出空间
* Page3: 测试(testing)

  学得模型后，使用其进行预测的过程称为测试，被预测的样本称为测试样本
* Page3: 测试样本(testing sample)

  见测试
* Page3: 簇(197)（cluster）

  将训练集中的西瓜分成若干组，称为聚类，每个组称为一个簇
* Page3: 独立同分布(267)（independent and identically distributed）

  我们获得的每个样本都是独立的从一个分布上采样获得的，即“独立同分布”
* Page3: 多分类（multi-class classification）

  预测值涉及多个类别时，称为“多分类”
* Page3: 二分类（binary classification)

  预测值设计两个分类的任务
* Page3: 泛化（121，350）（generalization)

  学得模型适用于新样本的能力，称为“泛化”能力
* Page3: 分类（classification)

  如果预测的是离散值，此类学习任务称为分类
* Page3: 回归（regression）

  如果预测的值是连续值，此类学习任务称为回归
* Page3: 监督学习（supervised learning）

  根据训练数据是否拥有标记信息，学习任务可以大致分为两大类：监督学习和无监督学习，分类和回归是前者的代表，聚类是后者的代表
* Page3: 聚类(197)（clustering）

  见簇
* Page3: 无导师学习

  同无监督学习
* Page3: 无监督学习(197)（unsupervised learning）

  见有监督学习
* Page3: 有导师学习

  同有监督学习
* Page4: 概念学习(17)（concept learning）

  广义的归纳学习大体相当于从样例中学习，而狭义的归纳学习则要求从训练数据中学得概念，因此亦称为概念学习或概念形成
* Page4: 归纳学习(11)（inductive learning）

  从样例中学习
* Page5: 版本空间（version space）

  存在着一个与训练集一致的假设集合，称之为“版本空间”
* Page6: 归纳偏好（inductive bias）

  机器学习算法在学习过程中对某种类型假设的偏好，称为归纳偏好
* Page6: 偏好

  同归纳偏好
* Page7: 奥卡姆剃刀(17)（Occam's razor）

  若有多个假设与观察一致，则选最简单的那个
* Page10: 符号主义(363)（symbolism）

  基于逻辑表示
* Page10: 连接主义（connectionism）

  基于神经网络
* Page10: 人工智能

  有很多种说法。。见仁见智
* Page11: 机械学习

  信息存储与检索
* Page11: 类比学习

  通过观察和发现学习
* Page11: 示教学习

  从指令中学习
* Page12: 统计学习(139)

  如SVM，核方法
* Page14: 数据挖掘

  从海量数据中发掘知识
* Page16: WEKA
* Page17: 迁移学习

  类比学习升级版


# 模型评估与选择

## 第二章 模型评估与选择

* Page23: 错误率(error rate)

  分类错误的样本数占样本总数的比例称为错误率，即如果在m个样本中有a个样本分类错误，则错误率E = a/m；相应的，1-a/m称为精度。
* Page23: 泛化误差（generalization error)

  在新样本上的误差称为泛化误差
* Page23: 过拟合(104,191,352)（overfitting）

  当学习器把训练样本学得太好了的时候，很可能已经把训练样本自身的一些特点当作了所有潜在样本都会具有的一般性质，这样就会导致泛化性能下降，这种现象称为过拟合
* Page23: 过配

  同过拟合
* Page23: 精度(29)(accuracy)

  精度=1-错误率
* Page23: 经验误差(267)(empirical error)

  学习器在训练集上的误差称为“训练误差”
* Page23: 欠配（underfitting）

  欠拟合，对训练样本的一般性质尚未学好
* Page23: 误差(error)

  学习器的实际预测输出与样本的真实输出之间的差异称为误差
* Page23: 训练误差（trainning error)

  同经验误差
* Page24: 模型选择(model selection)

  选择学习算法与参数配置
* Page25: 分层采样(stratified sampling)

  如果从采样的角度看待数据集的划分过程，则保留类别比例的采样方式通常称为“分层采样”
* Page25: 留出法（hold-out）

  直接将数据集D划分为两个互斥的集合，其中一个集合作为训练集S，另一个作为测试集T，在S上训练出模型后，用T来评估其测试误差，作为对泛化误差的估计。
* Page26: k折交叉验证（k-fold cross validation）

  交叉验证先将数据集D划分为k个大小相似的互斥子集，每个自己都尽可能保持数据分布的一致性，即从数据集中分层采样得到，然后，每次用k-1个子集的并集作为训练集，余下的那个子集作为测试集，这样就可以获得k组训练/测试集，最终返回k个测试结果的均值，交叉验证评估结果的稳定性和保真性很大程度上取决于k的取值，通常称之为k折交叉验证，最常用的k是10
* Page26: 交叉验证法（cross validation）

  同k折交叉验证
* Page27: 包外估计(179)（out of bag estimate）

  用于测试的样本没在训练集中出现，这样的测试结果称为包外估计
* Page27: 自助法(bootstrapping)

  以自主采样法为基础，给定包含m个样本的数据集D，对它采样产生数据集D’：每次随机从D中挑选一个样本，将其考本放入D’， 然后再将该样本放回D中，下次可能再被采到，这个过程执行m次后，得到包含m个样本的数据集D’,m足够大时，有36.8%的样本不会被采到，于是可以用没采到的部分做测试集。
* Page28: 参数调节（parameter tuning）

  大多数学习算法有些参数需要设定，参数配置不同，学得模型的性能往往有显著差别，因此，在进行模型评估与选择时，除了要对适用学习算法进行选择，还需要对算法参数进行设定，这就是参数调节或者调参。
* Page28: 验证集(105)（validation set）

  通常把学得模型在实际使用中遇到的数据称为测试数据，为了加以区分，为了加以区分，模型评估与选择中用于评估测试的数据集常称为“验证集”。
* Page29: 均方误差(54)（mean squared error）

  回归任务最常用的性能度量是均方误差（几何距离）
* Page30: 查全率（recall）

  预测为真且正确的结果占所有预测正确的结果的比例。
* Page30: 查准率（precision）

  预测为真且正确的结果占所有预测结果的比例。
* Page30: 混淆矩阵（confusion matrix）

| 真实情况 |  预测为正例  |  预测为反例  |
| :--: | :-----: | :-----: |
|  正例  | TP（真正例） | FN（假反例） |
|  反例  | FP（假正例） | TN（真反例） |

* Page30: 召回率

  同查全率
* Page30: 准确率

  同查准率
* Page31: P-R曲线

  查准率（纵轴）与查全率（横轴）的关系曲线
* Page31: 平衡点（break-even point，bep）

  查准率=查全率时的取值。平衡点大的学习模型可以认为综合性能更好
* Page32: F1

  查准率和查全率的调和平均，比算术平均（求和除以2）和几何平均（平方相乘开方）更重视较小值。

  $$\frac{1}{F1} = \frac{1}{2} \cdot (\frac{1}{P} + \frac{1}{R})$$

  $$\frac{1}{F\_\beta} = \frac{1}{1+\beta^2} \cdot (\frac{1}{P} + \frac{\beta^2}{R})$$
* Page32: 宏F1(macro-F1)

  如果进行多次训练/测试，每次得到一个混淆矩阵，或是在多个数据集上进行训练/测试，可以在n个混淆矩阵上综合考察查准率和查全率

  $$\text{macro-}P=\frac{1}{n}\sum\_{i=1}^{n}P\_i$$

  $$\text{macro-}R=\frac{1}{n}\sum\_{i=1}^{n}R\_i$$

  $$\text{macro-}F1=\frac{2\times\text{macro-}P\times\text{macro-}R}{\text{macro-}P+\text{macro-}R}$$
* Page32: 宏查全率

  见宏F1之($$\text{macro-}R$$)
* Page32: 宏查准率

  见宏F1之($$\text{macro-}P$$)
* Page32: 微F1($$\text{macro-}F1$$)

  将各混淆矩阵的对应元素进行平均，再去计算，可以得到$$\text{macro-}F1$$
* Page32: 微查准率

  将各混淆矩阵的对应元素进行平均，再去计算
* Page32: 微查全率

  将各混淆矩阵的对应元素进行平均，再去计算
* Page33: ROC曲线(46)

  真正例率（True Positive Rate，TPR）和假正例率（FPR）的关系曲线

  $$TPR=\frac{TP}{TP+FN}$$

  $$FPR=\frac{FP}{TN+FP}$$
* Page35: 代价(47)(cost)

  为权衡不同类型错误所造成的不同损失，可为错误赋予“非均等代价”
* Page35: 代价矩阵

| 真实情况 |  预测为0类 |  预测为1类 |
| :--: | :----: | :----: |
|  0类  |    0   | cost01 |
|  1类  | cost10 |    0   |

* Page36: 代价敏感(67)(cost-sensitive)

  在损失函数中考虑了非均等代价
* Page36: 代价曲线

  正例概率代价（横轴）和归一化代价（纵轴）的曲线

  正例概率代价： $$P(+)cost = \frac{p\times cost\_{01}}{p \times cost\_{01} + (1 - p) \times cost\_{10}}$$，p是样例为正例的概率

  归一化代价： $$cost\_{norm} = \frac{FNR \times p \times cost\_{01} + FPR \times (1-p) \times cost\_{10}}{(p \times cost\_{01}+ (1-p) \times cost\_{10}}$$
* Page36: 规范化(183)(normalization)

  将不同变化范围的值映射到相同的固定范围中，常见的是\[0,1]，此时亦称归一化
* Page36: 归一化（regular）

  同规范化
* Page36: 总体代价

  错误率是直接计算错误次数，并没有考虑不同错误会造成不同的后果，在非均等代价下，我们所希望的不再是简单的最小化错误次数，而是希望最小化总体代价
* Page37: 假设检验(hypothesis test)

  假设是对学习器泛化错误率分布的某种判断或猜想，用测试错误率估计泛化错误率，以检查学习器性能。
* Page38: 二项检验（binomial test）

  二项分布检验，根据收集到的样本数据，推断总体分布是否服从某个指定的二项分布。泛化错误率为e的学习器被测得测试错误率为e’的概率是服从二项分布的。
* Page38: 置信度(confidence)

  估计总体参数落在某一区间时，可能不犯错误的概率，一般用符号1-α表示。
* Page40: 交叉验证成对t校验（paired t-tests）

  对两个学习器A和B，使用k折交叉验证法分别得到k个测试错误率，如果两个学习器性能相同，则使用相同训练/测试集时测试错误率应该相同，求两个学习器的k个测试错误率的差，若$$\left | \frac{\sqrt{k}\mu}{\sigma}\right|$$＜临界值则认为两个学习器性能相同。
* Page41: 5x2交叉验证

  由于交叉验证中，不同轮次的训练集之间有一定程度的重复，会过高估计假设成立的概率，因此做5次2折交叉验证，每次验证前将数据打乱，对5次2对2个学习器的测试错误率求差值，对所有差值求方差，对前两次差值求均值，再进行临界值判断。
* Page41: McNemar检验

  两个学习器分类差别列联表

| 算法B\A |    正确   |    错误   |
| :---: | :-----: | :-----: |
|   正确  | e\_{00} | e\_{01} |
|   错误  | e\_{10} | e\_{11} |

检验变量$$|e\_{01}-e\_{10}|$$是否服从正态分布，服从则认为两学习器性能相同等同于检查$$τx^2 = (|e\_{01}-e\_{10}|-1)^2/(e\_{01}+e\_{10})$$ 是否服从自由度为1的卡方分布（标准正态分布变量的平方）

* Page41: 列联表(187)

  见McNemar检验
* Page42: Friedman检验

  有多个数据集多个学习器进行比较时使用，对各个算法在各个数据集上对测试性能排序，对平均序值计算τx²和τF,并进行临界值检验。
* Page43: Nemenyi后续检验(Nemenyi post-hoc test)

  学习器性能性能显著不同时，进行后续检验来进一步区分各算法，临界值域：$$CD=q\alpha \times \sqrt{\frac{k(k+1)}{6N}}$$
* Page44: 偏差-方差分解(177)

  对学习算法的期望泛化错误率进行拆解，学习算法在不同训练集上学得的结果很可能不同，真实输出与期望输出的差别称为偏差(bias)，使用样本数相同的不同训练集产生的输出的方差为var(x)，有：$$E(f;D)=bias^2(x)+var(x)+\varepsilon^2$$


# 线性模型

## 第3章 线性模型

* Page53: 线性回归(252)(linear regression)

  给定数据集$$D={(x\_i,y\_i)...}$$，线性回归试图学得一个线性模型以尽可能准确地预测实值输出标记
* Page53: 线性模型(linear model)

  给定由d个属性描述的示例$$x=(x\_1;x\_2;...x\_d)$$，$$x\_i$$是x在第i个属性上的取值，线性模型试图学得一个通过属性的线性组合来进行预测的函数，即

  $$f(x) = w\_1x\_1+w\_2x\_2+...+w\_dx\_d+b$$
* Page54: 参数学习

  线性回归试图学得$$f(x\_i) = wx\_i + b$$, 使得$$f(x\_i) \approx y\_i$$
* Page54: 平方损失

  欧氏距离算得的均方误差
* Page54: 最小二乘法(72)(least square method)

  基于均方误差最小化来进行模型求解的方法称为最小二乘法
* Page55: 多元线性回归(multivariate linear regression)

  样本由多个属性描述的线性回归
* Page56: 对数线性回归(log-linear regression)

  线性回归的一种变化，$$ln y=wx+b$$
* Page56: 正则化(105,133)（regularization）

  往往可以解出多个w都能使均方误差最小化，选择哪一个解将由学习算法的归纳偏好决定，常见的做法是引入正则化项。
* Page57: 对数几率回归（log-linear regression）

  对于分类问题，需要找一个单调可微函数将数据和线性模型关联起来，单位阶跃函数不连续，对数几率函数则连续且单调可微，并近似单位阶跃函数

  $$y = 1/(1+e^{-z})$$

  用线性回归模型的预测结果去逼近真实标记的对数几率，因此对应的模型称为“对数几率回归”。
* Page57: 广义线性模型（generalized linear model）

  考虑单调可微函数g(.)，令 y=g(wx+b)，这样得到的模型称为广义线性模型，其中函数g称为联系函数。
* Page57: 阶跃函数(98)（unit-step function）

  考虑二分类任务，我们需要将实值z转为0/1值，最理想的是单位阶跃函数： $$\begin{eqnarray}y= \begin{cases} 0, \&z<0\cr 0.5, \&z=0 \cr 1, &1\end{cases} \end{eqnarray}$$
* Page57: 联系函数(link function)

  见广义线性模型
* Page58: Sigmoid函数(98,102)

  Sigmoid函数即形似S的函数
* Page58: 对率函数

  同对数几率函数
* Page58: 对率回归(132,325)(logistic regression)

  用线性回归模型的预测结果去逼近真实标记的对数几率
* Page58: 对数几率函数(98)（logistic function）

  见对数几率回归
* Page58: 几率(odds)

  若将模型输出y视为样本x作为正例的可能性，则1-y是其反例可能性，两者的比值y/1-y称为几率
* Page58: 替代函数（surrogate function）

  功能相似的函数，例如用对数几率函数替代单位阶跃函数
* Page59: 对数似然(149)（log-likelihood）

  $$l(w,b) = \sum\_i^n ln p(y\_i|x\_i;w,b)$$

  对率回归模型最大化对数似然，即令每个样本属于其真实标记的概率越大越好
* Page59: 极大似然法(149,297)(maximum likelihood method)

  估计类条件概率的一种常用策略是先假定其具有某种确定的概率分布形式，再基于训练样本对概率分布的参数进行估计。
* Page60: Fisher判别分析(Fisher Discriminant Analysis)

  同线性判别分析
* Page60: 线性判别分析(139)（Linear Discriminant Analysis）

  给定训练样例集，设法将样例投影到一条直线上，使得同类样例的投影尽可能接近，异类样例的投影点尽可能远离，在对新样本进行分类时，将其投影到同样的这条直线上，再根据投影点的位置来确定样本的类别。
* Page61: 广义瑞利商（generalized Rayleigh quotient）

  LDA欲最大化的目标，即Sb(类间散度矩阵)，Sw(类内散度矩阵)的广义瑞利商
* Page61: 类间散度矩阵(138)(within-class scatter matrix)

  各类样本内部求差平方，再求和
* Page61: 类内散度矩阵(138)(between-class scatter matrix)

  各类样本之间的均值之差的平方和
* Page62: 全局散度矩阵（global scatter matrix）

  样本与全局均值之差的平方和
* Page63: MvM（Many vs. Many）

  多分类转二分类的一种解决方法：每次将若干个类作为正类，若干个其他类作为反类，通常用纠错输出码ECOC来拆分。
* Page63: OVO（one vs. one）

  一对一，对于多个分类，两两配对产生N(N-1)/2个二分类任务
* Page63: OvR(one vs. Rest)

  一对多，每次将一个类的样例作为正例，所有其他类的样例作为反例来训练N个分类器。
* Page63: 多分类器学习

  顾名思义。。
* Page64: ECOC

  同纠错输出码
* Page64: 纠错输出码（Error Correcting Output Codes）

  将编码思想引入类别拆分，分编码和解码两部：

  * 编码：对N个分类器做M次划分，每次划分将一部分类别划为正类，其余为反类，一共产生M个二分类训练集，训练出M个分类器
  * 解码：M个分类器分别对测试样本做预测，所有预测标记组成一个编码，将这个预测编码与每个类别各自的编码做比较，返回其中距离最小的类别作为最终预测结果。
* Page65: 编码矩阵(coding Matrix)

  ECOC中，类别划分通过编码矩阵（编码组成的矩阵。。）指定。
* Page66: 类别不平衡(209)（class-imbalance）

  分类任务中不同类别的训练样例数目差别很大的情况，往往通过再缩放处理。
* Page67: 过采样（oversampling）

  对训练集里的某类样例增加采样次数减小类别不平衡。
* Page67: 欠采样(undersampling)

  对训练集里的某类样例减少采样次数减小类别不平衡。
* Page67: 上采样(upsampling)

  同过采样
* Page67: 稀疏表示(255)（sparse representation）

  见11章。
* Page67: 稀疏性(sparsity)

  本质上对应L0范数的优化。
* Page67: 下采样(downsampling)

  同欠采样
* Page67: 阈值移动(threshold-moving)

  欠采样和过采样改变了观测几率，在执行在缩放时，根据采样比例调整阈值。
* Page67: 再平衡(rebalance)

  同再缩放
* Page67: 再缩放(rescaling)

  原本决策规则是$$y/(1-y)>1$$预测为正，即预测为正的可能性大于预测为负的可能性，但正例少的时候，我们可以修改为，预测为正的可能性高于观测到的几率，即$$y/(1-y) > m^+/m^-$$
* Page68: 多标记学习(multi-label learning)

  例如一幅画可同时标注蓝天，白云，羊群等，每个样本属于多个类别，这就是多标记学习。


# 决策树

## 第四章 决策树

* Page73: 决策树(363)（decision tree）

  以二分类任务为例，我们希望从给定训练数据集学得一个模型用以对新示例进行分类，这儿把样本分类的任务，可以看做为当前样本是否属于正类这个问题的决策或判定过程，决策树是基于树结构进行决策的，决策时通常会进行一系列判断或子决策，决策的过程形成一个树结构。
* Page73: 判定树

  同决策树
* Page74: 分而治之（divide-and-conquer）

  分解问题分别进行处理的策略。
* Page75: ID3决策树（Iterative Dichotomiser decision tree）

  以信息增益为准则来划分属性的迭代二分器决策树。
* Page75: 划分选择

  决策树学习算法最重要的地方就是选择最优划分属性。
* Page75: 信息增益（information gain）

  属性划分减少的信息熵，信息熵是度量样本集合纯度的一种指标，假设第k类样本所占比例为pk，则数据集D的信息熵为：$$Ent(D)=-\sum pklogpk$$，$$Ent(D)$$越小，D的纯度越高。

  $$Gain(D,a)=Ent(D)-\sum \frac{Dv}{D\*Ent(Dv)}$$，Dv是某个属性a的某个可能取值的样本集合
* Page77: 增益率（gain ratio）

  信息增益准则对可取值数目较多的属性有偏好，为减少这种偏好的不利影响，使用增益率选择最优划分属性，增益率定义为:$$Gain\_ratio(D,a)=\frac{Gain(D,a)}{IV(a)}$$, $$IV(a)=-\sum \frac{Dv}{D\*log(Dv/D)}$$，IV(a)称为为a的固有值。属性可能取值数目越多，IV(a)的值越大，增益率即增益/固有值
* Page78: C4.5决策树(page83)

  基于增益率和二分法，可处理连续值的决策树
* Page79: CART决策树(Classfication and Regression Tree)

  使用基尼指数划分属性的决策树。
* Page79: 后剪枝（postpruning）

  先从训练集生成一颗完整的决策树，然后自底向上地对非叶节点进行考察，若将该结点子树替换成叶节点能提升泛化性能，则进行替换，后剪枝训练时间开销大。
* Page79: 基尼指数（Gini index）

  $$Gini(D) = \sum\sum p(x=k)\*p(x \neq k)$$,反映了从数据集D中随机抽取两个样本，其类别标记不一致的概率。
* Page79: 剪枝(352)（pruning） 决策树学习算法对付过拟合的主要手段，为了尽可能正确分类训练样本，结点划分过程将不断重复，有时会造成决策树分支过多，因训练样本过度学习导致将训练集自身的特点当做所有数据都具有的一般性质而导致过拟合，因此可通过主动去掉一些分支来降低过拟合的风险。
* Page79: 预剪枝(352)（prepruning）

  在决策树生成过程中，对每个结点在划分前先进行估计，若当前结点的划分不能带来决策树泛化性能的提升，则停止划分并将当前结点标记为叶节点，预剪枝基于贪心存在欠拟合的风险。
* Page82: 决策树桩(decison stump)

  仅有一层划分的决策树。
* Page83: 离散化

  连续属性转为离散值，可用二分法。
* Page85: 缺失值

  样本在某些属性上的取值未知。
* Page88: 多变量决策树(92)（multivariate decision tree）

  每个结点结合多个变量学习一个线性分类器，比如-0.8\*密度-0.044\*含糖率<=-0.313，这样的多个结点构成的决策树。
* Page90: 斜决策树（oblique descision tree）

  同多变量决策树
* Page92: 增量学习(109)（incremental learning）

  在接收到新样本后对已学得的模型进行调整，不用完全重新学习，主要机制是通过调整分支路径上的划分属性次序来对树进行部分重构。


# 神经网络

## 第五章 神经网络

* Page97: M-P神经元模型

  神经元接收来自n个其他神经元传递过来的输入信号，这些输入信号通过带权重的连接进行传递，神经元接收到的总输入值将与神经元的阈值进行比较，然后通过激活函数处理以产生神经元的输出。
* Page97: 人工神经网络

  非生物学意义的神经网络。
* Page97: 神经网络（neural networks）

  神经网络是由具有适应性的简单单元组成的广泛并行互联的网络，它的组织能够模拟生物神经系统对真实世界物体所作出的交互反应。
* Page97: 神经元（neuron）

  神经网络中具有适应新的简单单元，与其他神经元相连。
* Page97: 阈值(104)（bias/threshold）

  临界值。
* Page98: 感知机（perceptron）

  由两层神经元组成，输入层接收外界输入信号后传递给输出层，输出层是M-P神经元，亦称“阈值逻辑单元”。
* Page98: 激活函数（activation function）

  将输入值映射为输出值0或1，理想的激活函数是阶跃函数，实际常用Sigmoid函数作为激活函数。
* Page98: 挤压函数（squashing function）

  Sigmoid函数把可能在较大范围内变化的输入值挤压到(0,1)输出值范围内，因此有时也称为“挤压函数”。
* Page98: 阈值逻辑单元(threshold logic unit)

  经激活函数处理以产生输出的神经元。
* Page99: 非线性可分（linearly unseparable）

  用线性超平面无法划分
* Page99: 功能神经元（functional neuron）

  进行运算或其他处理的神经元。
* Page99: 收敛(converge)

  感知机的学习过程使得权重向量趋于稳定。
* Page99: 线性超平面(linear meta-surface)

  在空间内可以用线性模型表达的平面。
* Page99: 线性可分(126)(linearly seprarable)

  存在一个线性超平面可以划分两种模式，则认为这两种模式线性可分，比如与、或、非问题。
* Page99: 学习率（learning rate）

  对权重每次的调整量。
* Page99: 哑结点(dummy node)

  输入固定的结点，不对输入做出响应，但会影响输出。
* Page99: 振荡（fluctuation）

  权重持续往不同方向波动。
* Page100: 多层前馈神经网络(multi-layer feedforward neural network)

  多个感知机相连，每层神经元与下一层神经元全互联，神经元之间不存在同层连接，也不存在跨层连接，这样的神经网络结构被称为“多层前馈神经网络”。
* Page101: BP算法(BackPropagation algorithm)

  神经网络学习过程就是根据训练数据来调整神经元之间的连接权以及每个功能神经元的阈值。 （误差）逆向传播，从最后一层开始，利用广义的感知机学习规则，基于梯度下降策略，以目标的负梯度方向对参数进行调整，

  * 对每个训练样例，先将输入示例提供给输入层神经元，
  * 然后逐层将信号前传
  * 知道产生输出层结果
  * 计算输出层误差
  * 将误差逆向传播至隐层
  * 根据隐层神经元的误差来对连接权和阈值进行调整
  * 迭代循环进行，直到某些条件（如训练误差已经很小）停止。
* Page101: BP网络

  利用BP算法训练的神经网络
* Page101: 单隐层网络

  输入层神经元仅接受输入，不进行函数处理，隐层与输出层包含功能神经元，只包含一个隐层的神经网络称为“单隐层网络”，也称为两层网络（虽然有点歧义，实际上有输入，隐，输出三层）。
* Page101: 反向传播算法

  同BP算法。
* Page101: 连接权(104)（connection weight）

  神经元之间的联系的权重。
* Page101: 误差逆传播（error BackPropagaton）

  见BP算法。
* Page102: 梯度下降（254，389，407）

  以目标的负梯度方向对参数进行调整。
* Page103: 链式法则(402)

  计算梯度时，按影响顺序依次计算导数然后相乘。
* Page105: 累积误差逆传播（accumulated eror backpropagation）

  针对多个训练样例进行误差逆传播计算。直接针对累积误差（同时考虑多个训练样例的误差）最小化，参数更新频率比标准误差逆传播低很多，但训练到一定程度下降会非常缓慢，这时标准BP往往会更快获得较好的解。
* Page105: 早停(early stopping)

  将数据分成训练集和验证集，训练集计算梯度，更新连接权和阈值，验证集用来估计误差，若训练集误差降低但验证集误差升高，则停止训练，同时返回具有最小验证集误差的连接权和阈值。以此防止过拟合。
* Page106: 正则化(regularization)

  在误差目标函数中增加一个用于描述网络复杂度的部分，例如连接权与阈值的平方和，并对这个部分加一个λ做折中。以此防止过拟合。
* Page106: 参数空间（parameter space）

  参数为坐标轴的空间，在这个空间中可以画出误差函数组成的屏幕。
* Page106: 局部极小

  在该点导数为0
* Page106: 全局最小

  最小的极小值。
* Page107: 模拟退火（simulated annealing）

  模拟退火在每一步都以一定概率接受比当前解更差的结果，从而有助于跳出局部极小，在每部迭代过程中，接受次优解的概率随着时间的推移而逐渐降低，从而保证算法稳定。
* Page107: 遗传算法(genetic algorithms)

  多个极小值竞争。
* Page108: ART网络（Adaptive Resonance Theory network）

  自适应谐振理论网络，竞争型无监督学习，由比较层接收输入，识别层进行距离度量学习，对一个输入距离近的识别层神经元抑制其他神经元的激活，距离大于识别阈值则将输入归入识别层神经元的模式，同时更新网络连接权。识别过程中动态调整神经元个数以适应输入模式的变化。
* Page108: RBF网络(Radial Basis Function network)

  径向基函数网络，是一种单隐层前馈神经网络，使用径向基函数作为激活函数，输出是对隐层神经元输出的线性组合。分两步，确定神经元中心（聚类，随机采样），BP确定参数w和β。
* Page108: 径向基函数(Radial Basis Function)

  $$\rho(x, c\_i) = e^{-β\_i|x-c\_i|^2}$$

  样本与样本中心的欧氏距离的单调函数。
* Page108: 竞争型学习（competitive learning）

  网络中的输出神经元相互竞争，每一时刻仅有一个竞争获胜的神经元被激活，其他神经元的状态被抑制，这种机制亦称胜者通吃。
* Page108: 胜者通吃(winner-take-all)

  见竞争型学习。
* Page108: 自适应谐振理论（Adaptive Resonance Theory）

  见ART网络。
* Page109: Kohonen网络

  同SOM网络
* Page109: SOM网络（Self-Organizing Map network）

  自组织映射网络，将高维输入数据映射到低维空间（通常为二维），同时保持输入数据在高维空间的拓扑结构，即将高位空间中相似的样本点映射到网络输出层的临近神经元。训练时，接收一个训练样本，每个输出层神经元计算该样本与自身携带的权向量之间的距离，距离最近的神经元成为竞争获胜者，称为最佳匹配单元，
* Page109: 可塑性-稳定性窘境(stability-plasticity dilemma)

  可塑性是指神经网络要有学习新知识的能力，而稳定性则是指神经网络在学习新知识时要保持对旧知识的记忆。往往两者不可兼得。
* Page109: 在线学习(241,393)(online learning)

  ART网络具有兼顾可塑性和稳定性的优点，适合在线学习。
* Page109: 自组织映射（Self-Organizing Map）

  将高维输入数据映射到低维空间，同时保持输入数据在高维空间的拓扑结构，即将高维空间中的相似的样本点映射到网络输出层中的邻近神经元。
* Page110: 级联相关(Cascade-Correlation)

  级联相关网络有两个主要成分：级联和相关，级联是指建立层次连接的层级结构，在开始训练时，网络只有输入层和输出层，处于最小拓扑结构，随着训练的进行，新的因曾神经元逐渐加入，从而创建起层级结构，当新的隐层神经元加入时，其输入端连接权值是冻结固定的。相关是指通过最大化新神经元的输出和网络误差之间的相关性来训练相关的参数，训练速度较快，但容易过拟合。
* Page111: Boltzmann分布(Boltzmann distribution)

  若网络中的神经元以任意不依赖于输入值的顺序进行更新，则网络最终将达到Boltzmann分布，此时状态向量出现的概率将仅由其能量与所有可能状态向量的能量确定：$$P(s) = \frac{e^{-E(s)}}{\sum e^{-E(t)}}$$
* Page111: Boltzmann机(Boltzmann machine)

  通常分两层：显层与隐层，显层用于表示数据的输入与输出，隐层则被理解为数据的内在表达，神经元是布尔型的，只能取0、1两种状态，状态1表示激活，状态0表示抑制，状态向量出现的概率将仅由其能量与所有可能状态向量的能量确定。训练过程就是将每个训练样本视为一个状态向量，使其出现的概率尽可能大。
* Page111: Elman网络(Elman Network)

  最常用的递归神经网络之一，结构与多层前馈网络相似，但隐层神经元的输出被反馈回来，与下一时刻输入层神经元提供的信号一起作为隐层神经元在下一时刻的输入。
* Page111: 递归神经网络(Recurrent neural networks)

  允许网络中出现环形结构，从而可让一些神经元的输出反馈回来作为输入信号，这样的结构与信息反馈过程，是的网络在t时刻的输出状态不仅与t时刻的输入有关，还与t-1时刻的网络状态有关，从而能处理与实际有关的动态变化。
* Page111: 基于能量的模型(energy-based model)

  为网络状态定义一个能量，能量最小化时网络达到理想状态，而网络的训练就是在最小化这个能量函数。
* Page112: 对比散度(Constrastive Divergence)

  假定网络中有d个显层神经元和q个隐层神经元，令v和h分别表示显层与隐层的状态向量，同一层内不存在连接，采用对比散度算法对网络进行训练，计算隐层神经元状态的概率分布，然后根据这个概率分布采样得到h，并更新连接权w。
* Page112: 受限Boltzmann机(Restricted Boltzmann Machine, RBM)

  标准Boltzmann机是一个全连接图，训练网络的复杂度很高，这使其难以用于解决现实任务，现实中常用受限Boltzmann机仅保留显层与隐层之间的连接，从而将Boltzmann机结构由完全图简化为二部图。
* Page113: 发散(diverge)

  不能达到稳定（收敛）状态
* Page113: 卷积神经网络(Convoutional Neural Network，CNN)

  用权共享来节省训练开销，通常包含多个“卷积层”和“采样层(pooling)”对输入信号进行加工，然后在连接层实现与输出目标之间的映射，每个卷积层包含多个特征映射(feature map)，每个特征映射是一个特征。详细内容可以参考这个[教程](https://github.com/ahangchen/GDLnotes/blob/master/note/lesson-3/README.md)
* Page113: 权共享(weight sharing)

  让一组神经元使用相同的连接权。
* Page113: 深度学习(deep learning)

  很多层的神经网络，可以增加隐层数目或增加隐层神经元数目来实现。
* Page113: 无监督逐层训练(unsupervised layer-wise training)

  多隐层网络训练的手段，基本思想是每次训练一层隐节点，训练时将上一层隐节点的输出做为输入，本层隐节点的输出作为下一层隐节点的输入，这称为“预训练”，预训练完成后，对整个网络做“微调(fine-tuning)”训练，如在DBN中，每层都是一个RBM，整个网络可视为若干个RBM堆叠而得，可按标准的RBM训练；然后将第一层训练好的隐节点视为第二层的输入结点，对第二层进行预训练，各层预训练完成后，再利用BP算法等对整个网络进行训练。，预训练+微调可视为将大量参数分组，对每组先找到局部看来比较好的设置，然后基于这些局部较优的结果联合起来进行全局寻优，利用了模型大量参数所提供的自由度的同时，有效地节省了训练开销。
* Page114: ReLU(Rectified Linear Unit)

  将Sigmoid激活函数替换为修正线性函数$$\begin{eqnarray}f(x)= \begin{cases} 0, \&x<0\cr x, \&otherwise \end{cases} \end{eqnarray}$$ ，这样的神经元被称为RELU。
* Page114: 表示学习(Representation learning)

  对输入信号进行逐层加工，从而把初始的、与输出目标之间的联系不太密切的输入表示，转化成与输出目标联系更加密切的表示，换言之，通过多层处理，逐渐将初始的“低层”特征表示成“高层”特征表示后，用“简单模型”即可完成复杂的分类等学习任务。
* Page114: 汇合(pooling)

  基于局部相关性原理进行亚采样，从而在减少数据量的同时保留有用信息。
* Page114: 特征学习(feature learning)

  同表示学习。
* Page115: 广义δ规则

  最小化网络均方误差，BP算法亦称广义δ规则。
* Page115: 可解释性(191)

  神经网络是一种难解释的“黑箱模型”，难以解释其工作机理。


# 支持向量机

## 第6章 支持向量机

* Page121: 划分超平面

  超平面是将N维空间分成两个闭半空间的N-1维的仿射空间。划分超平面则是把两类数据集样本划分开来的超平面。
* Page122: 支持向量

  支持平面上把两类划分开来的超平面的向量点，即距离超平面最近的几个训练样本点。
* Page122: 间隔

  两个异类支持向量到超平面的距离之和为间隔，$$\gamma = \frac{2}{\parallel \omega \parallel }$$.
* Page123: SVM

  $$\min\_{\omega,b} \frac{1}{2}\parallel \omega \parallel ^{2}$$ s.t. $$y\_i(\omega ^{T}x\_i+b)\geqslant 1, i=1,2,...,m$$
* Page123: 对偶问题(405)(dual problem)

  在求最小值的原问题里，其对偶问题提供了一个下界(lower bound)。
* Page124: KKT条件(124,132,135)(Karush-Kuhn-Tucker)
  * $$\alpha \_i\geqslant 1$$
  * $$y\_i f(x\_i)-1\geqslant 0$$
  * $$\alpha \_i(y\_if(x\_i)-1)=0$$ - Page126: 核函数

    可以看成是一种映射，不仅可以是点对点的映射，还可以是一个分布对点的映射。
* Page127: 核技巧(kernel trick)

  $$x\_i$$与$$x\_j$$在特征空间的内积等于它们在原始样本空间中通过函数κ(·,·)计算的结果。
* Page127: 支持向量展式

  模型最优解课通过训练样本的核函数展开。
* Page128: 核矩阵(138,233)

  $$K=\begin{bmatrix}\kappa(x\_1,y\_1) & \cdots\ & \kappa(x\_1,y\_j) & \cdots\ &\kappa(x\_1,y\_m)\ \vdots & \ddots & \vdots & \ddots & \vdots \ \kappa(x\_i,y\_1) & \cdots\ & \kappa(x\_i,y\_j) & \cdots\ & \kappa(x\_i,y\_m)\ \vdots & \ddots & \vdots & \ddots & \vdots \ \kappa(x\_m,y\_1) & \cdots\ & \kappa(x\_m,y\_j) & \cdots\ & \kappa(x\_m,y\_m)\ \end{bmatrix}$$ 形为这样的核函数，可以将低维空间映射为高维空间，将原来线性不可分的分布转化为线性可分的分布。核函数可以表示高维空间的内积。 此外，核函数必须满足对称性和矩阵半正定。
* Page128: RKHS

  非线性映射ϕ，它将原始特征空间中的数据点映射到另一个高维空间中，这个高维空间称为再生核希尔伯特空间(Reproducing Kernel Hilbert Space)，简称RKHS。
* Page128: 高斯核

  高斯核函数是最常用的径向基函数。
* Page128: 线性核

  线性核函数是$$\kappa(x\_i,y\_i)=x\_i^Tx\_j$$；多项式核是$$\kappa(x\_i,y\_i)=(x\_i^Tx\_j)^d$$，即d为1时退化为线性核。
* Page129: 软间隔

  允许支持向量机在一些样本上出错，即允许某些样本不满足约束。
* Page129: 硬间隔

  要求所有样本均满足约束，即所有样本都划分正确。
* Page130: 0/1损失函数(page147)

  $$ l\_{0/1}(z) = \left{ \begin{array}{rl} 1, &\mbox{ if $z<0$} \ 0, &\mbox{ otherwise} \end{array} \right. $$
* Page130: 替代损失

  0/1损失函数不易直接求解（非连续，不是convex），所以通常会用其他函数来替代求解，也叫替代损失。
* Page130: hinge损失

  $$l\_{hinge}(z)=\max(0,1-z)$$，采用hinge损失函数，则优化目标变成$$\min\_{w,b}\frac{1}{2}||w||^2+C\sum\_{i=1}^{m}max(0,1-y\_i(w^Tx\_i+b))$$
* Page130: 指数损失(173)

  $$l\_{exp}(z)=\exp(-z)$$
* Page130: 对率损失

  $$l\_{log}(z)=\log(1+\exp(-z))$$
* Page130: 松弛变量

  松弛变量，即slack variables$$\xi\_i$$
* Page131: 软间隔支持向量机

  引入了松弛变量$$\xi\_i$$，式子可重写为：

  $$\begin{align\*}& \min\_{w,b}\frac{1}{2}||w||^2+C\sum\_{i=1}^{m}max(0,1-y\_i(w^Tx\_i+b))\\& \begin{array}{r@{\quad}r@{}l@{\quad}l}s.t.\&yi(w^Tx\_i+b)\geq1-\xi\_i\ &\xi\_i\geq0, i=1,2\ldots,m \\\end{array} .\end{align\*}$$
* Page133: 结构风险

  描述模型f的性质，$$\Omega(f)$$.
* Page133: 经验风险

  描述模型和训练数据的契合程度，可以理解为误差。
* Page133: 罚函数法

  正则化。对不希望得到的结果施以惩罚，从而使得优化过程趋向于希望目标。
* Page133: 支持向量回归

  Support Vector Regression(SVR)其实是对误差的计算加了一个误差容忍值epsilon（即$$\epsilon$$），即落入这个$$\epsilon$$误差范围内的不计入误差和中。
* Page137: 核方法

  这一系列基于核函数的学习方法统称为核方法。最常见的是将线性学习器拓展为非线性学习器。如把线性不可分的低维分布通过方法转成线性可分的高维分布。
* Page137: Mercer定理

  任何半正定的函数都可以作为核函数。
* Page137: 表示定理

  优化问题的解总可用核函数表示。
* Page137: 核化(232)

  引入核函数来解决问题。
* Page137: 核线性判别分析

  Kernelized Linear Discriminant Analysis(KLDA)是指引入核函数进行线性判别分析。
* Page139: 割平面法

  Cutting plane algorithm是求全整数规划的一种方法，先松弛求得最优解，再逐步把不符合整数可行解的那一部分可行域通过平面（不等式分割）划分掉。
* Page140: 多核学习

  使用多个核函数，学习后获得最优凸组合。
* Page140: 一致性

  相合性。即考虑通过求解替代损失函数得到的解释是否是原问题的解。


# 贝叶斯分类器

## 第7章 贝叶斯分类器

* Page147: 贝叶斯风险(Bayes risk)

  贝叶斯最优分类器对应的条件风险
* Page147: 贝叶斯最优分类器(Bayes optimal classifier)

  为最小化总体风险，只需在每个样本上选择那个能使条件风险最小的类别标记，此时判定准侧称为贝叶斯最优分类器。
* Page147: 风险(risk)

  决策论中将“期望损失”称为风险。
* Page147: 条件风险(conditional risk)

  基于后验概率$$P(c\_{i}|x)$$可获得将样本x分类为ci所产生的期望损失，即在样本x上的条件风险。
* Page148: 贝叶斯定理

  $$P(c|x)=\frac{P(c)P(x|c)}{P(x)}$$

  其中， P(c)是类“先验”(prior)概率；P(x|c)是样本x相对于类标记c的类条件概率(class-conditional probability)，或称为“似然”(likelihood)；P(x)是用于归一化的“证据”(evidence)因子。估计P(c|x)的问题转化为如何基于训练数据D来估计先验P(c)和似然P(x|c)。
* Page148: 判别式模型(325)（discriminative models）

  给定x,可通过直接建模P(c|x)来预测c，这样得到的是判别式模型。
* Page148: 生成式模型(295,325)(generative models)

  先对联合概率分布P(x,c)建模，然后再由此获得P(c|x)，这样得到的是生成式模型。
* Page148: 似然(likelihood)

  见贝叶斯定理。
* Page148: 先验(Prior)

  见贝叶斯定理
* Page148: 证据（evidence）

  见贝叶斯定理
* Page149: 极大似然估计(maximum likelihood estimation， MLE)

  令$$D\_{c}$$表示训练集D中第c类样本组成的集合，假设这些样本是独立同分布的，则参数$$\theta\_{c}$$对于数据集$$D\_{c}$$的似然是：

  $$P(D\_{c}|\theta\_{c}) = \prod\_{x \in D\_{c}} P(x|\theta\_{c})$$
* Page150: 朴素贝叶斯分类器(naive bayes classifier)

  基于贝叶斯公式来估计后验概率P(c|x)的主要困难在于：类条件概率P(x|c)是所有属性上的联合概率，难以从有限的训练样本直接估计而得，为避开这个障碍，朴素贝叶斯分类器采用了“属性条件独立性假设”：

  $$P(c|x) = \frac{P(c)P(x|c)}{P(x)}=\frac{P(c)}{P(x)} \* \prod\_{i=1}^{d} P(x\_{i}|c)$$

  即P(x|c)等于在c的条件下，所有属性的概率的乘积。
* Page150: 条件独立性假设(305)

  对已知类别，假设所有属性相互独立，换言之，假设每个属性独立的对分类结果发生影响。
* Page153: 拉普拉斯修正(Laplacian correction)

  为了避免其他属性携带的信息被训练集中未出现的属性值抹去，在估计概率值时通常要进行“平滑”，常用“拉普拉斯修正”，具体来说，另N表示训练集D中可能的类别数，$$N\_{i}$$表示第i个属性可能的取值书，则类先验概率

  $$\hat{P}(c)=\frac{|D\_{c}|+1}{|D|+N}$$

  条件概率：

  $$\widehat{P}(x\_{i}|c)=\frac{D\_{c,x\_{i}}+1}{D\_{c}+N\_{i}}$$
* Page154: 半监督贝叶斯分类器(semi-naive Bayes classifiers)

  半朴素贝叶斯分类器的基本想法是适当考虑一部分属性间的相互依赖信息，从而既不需进行完全联合概率计算，又不至于彻底忽略了比较强的属性依赖关系。
* Page154: 独依赖估计(One-Dependent Estimator)

  独依赖估计是半朴素贝叶斯分类器最常用的一种策略，假设每个属性在类别之外最多仅依赖于一个其他属性，即

  $$P(c|x)\propto P(c)\prod\_{i=1}^{d}P(x\_{i}|c,pa\_{i})$$

  其中$$pa\_{i}$$为属性$$x\_{i}$$所依赖的属性，称为$$x\_{i}$$的父属性，对每个属性$$x\_{i}$$，若其父属性$$pa\_{i}$$已知，可以估计概率值$$P(x\_{i}|c,pa\_{i})$$，于是问题的关键就转化为如何确定每个属性的父属性，不同的做法产生不同的独依赖分类器。
* Page154: 懒惰学习(225,240)（lazy learning）

  若任务数据更替频繁，则可采用“懒惰学习”方式，先不进行任何训练，待收到预测请求时再根据当前数据集进行概率估值；若数据不断增加，则可在现有估值基础上，仅对新增样本的属性值所涉及的概率估值进行计数修正即可实现增量学习。
* Page155: 超父(super-parent)

  假设所有属性都依赖于同一个属性，称为超父，然后通过交叉验证等模型选择方法来确定超父属性，由此形成了超父独依赖估计(Super-Parent ODE，SPODE).
* Page156: 贝叶斯网(319,339)（Bayesian network）

  借助有向无环图来刻画属性之间的依赖关系，并使用条件概率表来描述属性的联合概率分布。具体来说，一个贝叶斯网有结构G和参数$$\theta$$两部分构成，即$$B=\<G,\theta>$$。网络结构G是一个有向无环图，每个结点对应于一个属性，若两个属性有直接依赖关系，则它们由一条边连接起来；参数$$\theta$$定量描述这种依赖关系，假设属性$$x\_{i}$$在G中的父结点集为$$\pi\_{i}$$，则$$\theta$$包含了每个属性的条件概率表$$\theta\_{x\_{i}|\pi\_{i}}=P\_{B}(x\_{i}|\pi\_{i})$$。
* Page156: 概率图模型(319)

  是一种用图来表达变量相关关系的概率模型，它以图为表示工具，最常见的是用一个结点表示一个或一组随机变量，结点之间的边表示变量间的概率相关关系，即“变量关系图”。根据边的性质不同，概率图模型可大致分为两类：第一类是用有向无环图表示变量间的以来关系，称为有向图模型或贝叶斯网；第二类是使用无向图表示变量间的相关关系，称为无向图模型或马尔科夫网。
* Page156: 信念网(belief network)

  即贝叶斯网。
* Page158: V型结构（V-structure）

```
graph TD;
  A[x1]-->B[x4];
  C[x2]-->B[x4];
```

```
也称冲撞结构，给定父节点$$x_{4}$$的取值，则$$x_{1}$$与$$x_{2}$$不独立，若x_{4}未知，则V型结构下$$x_{1}$$与$$x\_{2}$$却是相互独立的，这样的独立性称为边际独立性。
```

* Page158: 边际独立性（marginal independence）

  见V型结构。
* Page158: 边际化(328)(marginalization)

  对变量做积分或求和亦称边际化。
* Page158: 道德图（moral graph）

  为了分析又想吐中变量间的条件独立性，可使用“有向分离”，把有向图转为一个无向图：

  * 找出有向图中的所有V型结构，在V型结构的两个父节点之间加上一条无箱变；
  * 将所有有向边改为无向边。

    由此产生的无向图称为“道德图”，令父结点相连的过程称为“道德化”（moralization）。
* Page158: 端正图

  即道德图。
* Page158: 有向分离（D-seperation）

  见道德图。
* Page159: 最小描述长度（Minimal Description Length）

  常用评分函数通常基于信息论准则，此类准则将学习问题看做一个数据压缩任务，学习的目标是找到一个能以最短编码长度描述训练数据的模型，此时编码的长度包括了描述模型自身所需的字节长度和使用该模型描述数据所需的字节长度。对贝叶斯网学习而言，模型就是一个贝叶斯网，同时，每个贝叶斯网描述了一个在训练数据上的概率分布，自有一套编码机制能使那些经常出现的样本有更短的编码，选择综合编码长度（包括描述网络和编码数据）最短的贝叶斯网，这就是最小描述长度。
* Page161: 吉布斯采样(334)

  吉布斯采样先随机产生一个与证据E=e一致的样本$$q^{0}$$作为初始点，然后每步从当前样本出发产生下一个样本。具体来说，在第t次采样中，算法先假设$$q^{t}=q^{t-1}$$，然后对非证据变量逐个进行采样改变其取值，采样概率根据贝叶斯网B和其他变量的当前取值（即Z=z）计算获得。假定经过T次采样得到的与q一致的样本共有$$n\_{q}$$个，则可近似估算出后验概率

  $$P(Q=q|E=e) \backsimeq \frac{n\_{q}}{T}$$
* Page161: 近似推断(161)

  当网络结点较多，连接稠密时，难以进行精确推断，此时需借助“近似推断”，通过降低精度要求，在有限时间内求得近似解。
* Page161: 精确推断(328,331)

  最理想的是直接根据贝叶斯网定义的联合概率分布来精确计算后验概率，精确推断是NP难的。
* Page161: 马尔科夫链(Markov chain)

  吉布斯采样是在贝叶斯网所有变量的联合状态空间与证据E=e一致的子空间中进行“随机漫步”，每一步仅依赖于前一步的状态，这是一个马尔可夫链。在一定条件下，无论从什么初始状态开始，马尔可夫链第t步的状态分布在$$t\rightarrow\infty$$时必收敛于一个平稳分布，对吉布斯采样来说，这个分布恰好是$$P(Q|E=e)$$。在T很大的时候，吉布斯采样相当于根据$$P(Q|E=e)$$采样，从而保证了后验概率收敛于$$P(Q=q|E=e)$$。
* Page161: 平稳分布（stationary distribution）

  见马尔科夫链。
* Page162: EM算法(208,295,335)(Expectation-Maximization Algorithm)

  常用的估计参数隐变量的利器，它是一种迭代式的方法，基本想法是：若参数$$\theta$$，则可根据训练数据推断出最优隐变量Z的值（E步）；反之，若Z的值已知，则可方便地对参数$$\theta$$做极大似然估计。
* Page162: 隐变量(319)（latent variable）

  现实应用中往往会遇到不完整的训练样本，存在未观测的变量，未观测变量的学名是隐变量。
* Page163: 边际似然（marginal likehood）

  令X表示已观测变量集，Z表示隐变量集，$$\theta$$表示模型参数。若欲对$$\theta$$做极大似然估计，则应最大化对数似然

  $$LL(\theta|X,Z)=ln P(X,Z|\theta)$$

  然而由于Z是隐变量，上式无法直接秋季，此时我们可以通过对Z计算期望，来最大化已观测的对数“边际似然”

  $$LL(\theta|X) = ln P(X|\theta) = ln \sum\_Z P(X,Z|\theta)$$
* Page163: 坐标下降(408)（coordinate descent）

  非梯度优化方法，在每步迭代中沿着一个坐标方向进行搜索，通过循环使用不同的坐标方向来达到目标函数的局部极小值，不妨假设目标是求解函数$$f(x)$$的极小值，其中$$x=(x\_1,x\_2,...,x\_d)^T \in R^d$$是一个d维向量。从初始点$$x^0$$开始，坐标下降法通过迭代地构造序列$$x^0,x1,x2,...$$来解决问题，$$x^(t+1)$$的第i个分量$$x\_i^{t+1}$$构造为

  $$x\_i^(t+1) = argmin\_{y \in \mathbb{R}}$$

  迭代执行该过程，序列$$x^0,x1,x2,...$$能收敛到所期望的局部极小点或驻点。若目标函数不光滑，可能陷入非驻点。
* Page164: 贝叶斯分类器（Bayes Classifier）

  通过最大后验概率进行单点估计。
* Page164: 贝叶斯学习（Bayes learning）

  进行分布估计。


# 集成学习

## 第8章 集成学习

* Page171: 多分类器系统(multi-classifier system)

  即集成学习。
* Page171: 个体学习器（individual learner）

  集成学习的一般结构是：先产生一组“个体学习器”，再用某种策略将它们结合起来，个体学习器通常由一个现有的学习算法从训练数据产生。
* Page171: 基学习器(base learner)

  集成中只包含同种类型的个体学习器，这样的集成是同质的。同质集成中的个体学习器亦称“基学习器”，相应的学习算法称为“基学习算法”。
* Page171: 基学习算法(base learning algorithm)

  见基学习器。
* Page171: 集成学习(311)(ensemble learning)

  集成学习通过构建并结合多个学习器来完成学习任务，有时也被称为多分类器系统(multi-classifier system)，基于委员会的学习(committee-based learning)。
* Page171: 弱学习器（weak learner）

  集成学习通过将多个学习器进行结合，常可获得比单一学习器显著优越的泛化性能，这对弱学习器尤为明显，基学习器有时也被直接称为弱学习器。
* Page172: AdaBoost

  AdaBoost算法有多种推导方式，比较容易理解的是基于“加性模型”，即基学习器的线性组合

  $$H(x) = \sum\_{t=1}^T \alpha\_t h\_t (x)$$

  来最小化指数损失函数（exponential loss function）

  $$l\_{exp}(H|D) = \mathbb{E}\_{x\~D}\[e^{-f(x)H(x)}]$$
* Page172: 多样性(diversity)

  学习器之间具有差异。
* Page172: 投票法(225)(voting)

  少数服从多数。
* Page173: Boosting(page139)

  Boosting是一族可将弱学习器提升为强学习器的算法，这族算法的工作机制类似：先从初始训练集训练出一个基学习器，再根据基学习器的表现对训练样本分布进行调整，使得先前基学习器做错的样本在后续收到更多关注，然后基于调整后的样本分布来训练下一个基学习器，如此重复进行，直至基学习器数目达到事先指定的值T，最终将这T个基学习器进行加权结合。
* Page173: 加性模型

  见AdaBoost
* Page177: 重采样（re-sampling）

  在每一轮学习中，根据样本分布对训练集重新进行采样，再用重采样而得的样本集对基学习器进行训练。
* Page177: 重赋权（re-weighting）

  在训练过程的每一轮中，根据样本分布为每个训练样本重新赋予一个权重，对无法接受带权样本的基学习算法，则可通过重采样法处理，两种做法没有显著的优劣差别。
* Page178: Bagging（Boostrap AGGregatING）

  Bagging是并行式集成学习方法最著名的代表，基于自助采样法，给定包含m个样本的数据集，先随机取出一个样本放入采样集中，再把该样本放回初始数据集，使得下次采样时该样本仍有可能被选中，这样经过m次随机采样操作，我们得到含m个样本的采样集，初始训练集中有的样本在采样集里多次出现，有的从未出现。采样出T个含m个训练样本的采样集，然后基于每个采样集训练出一个基学习器，再将这些基学习器进行结合，这就是Bagging的基本流程。
* Page178: 自助采样法(Boostrap sampling)

  见Bagging。
* Page179: 随机森林（Random Forest，RF）

  是Bagging的一个扩展变体，RF在以决策树为基学习器构建Bagging集成的基础上，进一步在决策树的训练过程中引入随机属性选择。具体来说，传统决策树在选择划分属性时是在当前结点的属性集合（假定有d个属性）中选择一个最优属性，，而在RF中，对基决策树的每个结点，先从该结点的属性集合中随机选择一个包含k个属性的子集，然后再从这个子集中选择一个最优属性用于划分，这里的参数k控制了随机性的引入程度：若令k=d，则基决策树的构建与传统决策树相同；若令k=1，则是随机选择一个属性用于划分；一般情况下，推荐$$k=log\_2d$$。
* Page182: 加权平均(225)(weighted averaging)

  假定集成包含T个基学习器$${h\_1,h\_2,...h\_T}$$，其中$$h\_i$$在示例$$x$$上的输出为$$h\_i(x)$$，加权平均结合$$h\_i$$： $$H(x)=\sum\_{i=1}^Tw\_ih\_i(x)$$

  其中$$w\_i$$是个体学习器$$h\_i$$的权重，通常要求$$w\_i\geqq0, \sum\_{i=1}^T=1$$
* Page182: 简单平均(simple averaging)

  $$H(x)=\frac{1}{T}\sum\_{i=1}^Th\_i(x).$$

  符号含义见加权平均。
* Page182: 绝对多数投票(majority voting) 对分类任务来说，学习器$$h\_i$$将从类别标记集合$${c\_1,c\_2,...,c\_N}$$中预测出一个标记，最常见的结合策略是使用投票法，将$$h\_i$$在样本$$x$$上的预测输出表示为一个N维向量$$(h\_i^1(x);h\_i^2(x);...;h\_i^N(x))$$，其中$$h\_i^j(x)$$是$$h\_i$$在类别标记$$c\_j$$上的输出。

  绝对多数投票法: $$\begin{eqnarray}H(x)= \begin{cases} c\_j, \&if \sum\_{i=1}^Th\_i^j(x)>0.5\sum\_{k=1}^N\sum\_{i=1}^Th\_i^k(x)\cr reject, otherwise\end{cases} \end{eqnarray}$$

  即若某标记得票过半数，则预测为该标记；否则拒绝预测。
* Page183: 加权投票(225)（weighted voting）

  $$H(x)=c\_{argmax\_j\sum\_{i=1}^Tw\_ih\_i^j(x)}$$

  与加权平均法类似，$$wi$$是$$h\_i$$的权重，通常$$wi\geq0, \sum\_{i=1}^Tw\_i=1$$.
* Page183: 相对多数投票（plurality votiing）

  $$H(x) = c\_{argmax\_j\sum\_{i=1}^Th\_i^j(x)}$$

  即预测为得票最多的标记，若同时又多个标记获得最高表，则从中随机选取一个，绝对多数投票和相对多数投票统称为多数投票法。
* Page184: Stacking

  一种集成学习方法，先从初始数据集训练出初级学习器，然后生成一个新数据集用于训练次级学习器，在新数据集中，初级学习器的输出被当作样例输入特征，而初始样本的标记仍被当做样例标记。
* Page185: 贝叶斯模型平均（Bayes Model Averaging）

  基于后验概率来为不同模型赋予权重，可视为加权平均法的一种特殊实现，理论上，若数据生成模型恰在当前考虑的模型中，且数据噪声少，则BMA不差于Stacking；然而，在现实应用中无法确保数据生成模型一定在当前考虑的模型中，甚至可能难以用当前考虑的模型来进行近似，因此，Stacking通常优于BMA，更鲁棒，BMA对模型近似误差更敏感。
* Page185: 分歧(304)（ambiguity）

  假定我们用个体学习器$$h\_1,h\_2,...,h\_T$$通过加权平均法结合产生的集成来完成回归学习任务$$f:\mathbb{R}^d\mapsto\mathbb{R}$$，对示例$$x$$，定义学习器$$h\_i$$的“分歧”为：

  $$A(h\_i|x)=(h\_i(x)-H(x))^2$$

  则集成的“分歧”是 $$\overline{A}(h|x) = \sum\_{i=1}^Tw\_iA(h\_i|x) = \sum\_{i=1}^Tw\_i(h\_i(x)-H(x))^2$$

  这里的分歧表征了个体学习器在样本x上的不一致性，在一定程度上反映了个体学习器的多样性。
* Page185: 误差-分歧分解（error-ambiguity decomposition）

  $$E=\overline{E}-\overline{A}$$

  $$E$$: 集成泛化误差，$$\overline{E}$$: 个体学习器泛化误差的加权均值，$$\overline{A}$$表示个体学习器的加权分歧值。这个分解明确提出：个体学习器准确性越高，多样性越大，集成越好。
* Page187: 差异性度量

  同多样性度量。
* Page187: 多样性度量（diversity measure）

  度量集成中个体分类器的多样性，估算个体学习器的多样化程度，典型做法是考虑个体分类器的两两相似/不相似性，常用度量有不合度量，相关系数，Q-统计量，K-统计量
* Page189: 属性子集

  训练样本通常由一组属性描述，不同的子空间（即属性子集）提供了观察数据的不同视角。
* Page189: 随机子空间（random subspace）

  依赖输入属性扰动产生随机的属性子集。
* Page189: 稳定基学习器(stable base learner)

  对数据样本扰动不敏感的学习器，例如线性学习器、支持向量机、朴素贝叶斯，k近邻学习器。
* Page189: 子空间(227)(subspace)

子空间一般指从初始的高维属性空间投影产生的低维属性空间，描述低维空间的属性是通过初始属性投影变换而得，未必是初始属性。

* Page191: 集成修剪(ensemble pruning)

  集成产生之后再视图通过去除一些个体学习器来获得较小的集成，称为集成修剪，有助于减小模型的存储开销和预测时间开销，减小集成规模常导致泛化性能下降，并行化集成进行修剪能在减小规模的同时提升泛化性能，并催生了基于优化的集成修剪技术。
* Page191: 选择性集成(selective emsemble)

  对并行化集成的修剪亦称“选择性集成”，但现在一般将选择性集成用作集成修剪的同义语，亦称集成选择(ensemble selection)。


# 聚类

## 第9章 聚类

* Page197: 有效性指标

  聚类性能度量，即评估其好坏的性能度量。聚类性能度量分为两类，外部指标和内部指标。
* Page199: 距离度量

  用于衡量点和点之间的距离，常用的有欧氏、曼哈顿、切比雪夫。最常用的是闵可夫斯基距离：\
  $$dist\_{mk}(x\_i,x\_j)={\left( \sum\_{u=1}^{n}\left|x\_{iu}-x\_{ju} \right|^p\right)}^{\frac{1}{p}}$$
* Page200: 街区距离

  闵可夫斯基距离$$p=1$$时即曼哈顿距离，又称街区距离: $$dist\_{man}(x\_i,x\_j)={\parallel x\_i-x\_j\parallel }*{1} =\sum*{u=1}^{n}\left|x\_{iu}-x\_{ju} \right|$$
* Page200: 离散属性

  定义域上有限个取值，比如{西瓜，哈密瓜，木瓜}。
* Page200: 连续属性

  定义域上可以取无限个取值，如实数。
* Page200: 列名属性

  “离散属性”也称“列名属性”，见“离散属性”。
* Page200: 曼哈顿距离

  同“街区距离”。
* Page200: 闵可夫斯基距离(220)

  见“距离度量”。
* Page200: 欧氏距离

  闵可夫斯基距离$$p=2$$时即欧氏距离：\
  $$dist\_{ed}(x\_i,x\_j)={\parallel x\_i-x\_j\parallel }*{2} =\sqrt{\sum*{u=1}^{n}\left|x\_{iu}-x\_{ju} \right|^2}$$
* Page200: 切比雪夫距离

  闵可夫斯基距离当$$p\rightarrow \infty$$时即切比雪夫距离。
* Page200: 数值属性

  见“连续属性”。
* Page200: 无序属性

  不能直接在属性值上计算距离。
* Page200: 有序属性

  可以直接在属性值上计算距离。
* Page201: 非度量距离

  不满足直递性的距离。
* Page201: 混合属性

  存在有序属性和无序属性。
* Page201: 加权距离

  即给每个距离加权重。
* Page201: 相似度度量

  对两个事物之间相似程度的综合性度量。
* Page201: 距离度量学习(237)

  度量学习（Metric Learning）是常说的相似度学习，距离度量学习则是选定距离计算式来进行度量。
* Page202: 原型聚类

  基于原型的聚类，著名的几种方法包括k均值算法、学习向量量化和高斯混合聚类等。
* Page202: k均值算法(218)

  针对聚类所得的簇，同类样本的最小均方误差。
* Page204: LVQ(218)

  见下
* Page204: 学习向量化(Learning Vector Quantization, LVQ)

  指通过找到一组原型向量来刻画聚类结构，每个原型向量代表一个聚类簇。
* Page206: 概率模型(319)

  概率模型是将学习归结为计算变量的概率分布的一种描述。
* Page206: 高斯混合(296)

  高斯混合是用高斯概率密度函数去量化，将变量/事物分解为若干个高斯密度函数的模型。
* Page211: 密度聚类

  基于密度的聚类，即是通过事物的紧密程度去聚类。如DBSCAN算法。
* Page214: 层次聚类

  通过不同层次对数据集进行划分，得到的是树形聚类结构。可采用“自顶向下”的分拆策略，也可用“自底向上”的聚合策略。著名的AGNES是自底向上聚合的层次聚类算法。
* Page219: 聚类集成

  对原始数据集的多个聚类器进行集成。可有效降低聚类过程中的随机性和聚类假设不符等因素的影响。
* Page219: 异常检测

  异常检测常借助聚类或距离计算，如将离心或密度极低的点作为异常点。
* Page220: 豪斯多夫距离

  Hausdorff distance： $$dist\_H(X, Z) = \max (dist\_h(X, Z), dist\_h(Z, X))$$ 其中 $$dist\_h(X, Z) = \max \limits\_{x\in X} \min \limits\_{z\in Z}\left | x-z \right |\_2$$


# 降维与度量学习

## 第10章 降维与度量学习

* Page225: k近邻

  k近邻是常用的监督学习方法，主要是用某种距离度量方法来找出与测试样本最靠近的k个点，根据这k位邻居的信息来预测其分类。是“懒惰学习”。
* Page225: 急切学习

  这种学习方法在训练阶段就对样本进行学习处理。
* Page225: 平均法

  将这k个样本的实值的平均值作为预测的输出。
* Page225: 最近邻分类器

  k=1，即是最近邻分类器。
* Page226: 密采样

  训练样本的采样密度足够大，保证任意小的距离内都能找到一个训练样本，即为“密采样”。
* Page227: 多维缩放

  多维缩放是指多维空间的样本转换到低维空间上，能够继续保持其距离。
* Page227: 降维

  通过数学变换将高维空间投射到低维子空间。
* Page227: 维数约简

  即降维。
* Page227: 维数灾难(247)

  即高维情况下带来的距离计算量大、样本稀疏等问题，比如随着维度增加，计算量会呈指数增长的趋势。
* Page229: PCA

  PCA(Principal Component Analysis，即主成分分析)，可从最近重构性和最大可分性来思考PCA。

  最近重构性则是希望样本点到超平面的距离足够小，优化目标是$$\min\_W -tr(W^TXX^TW)$$ $$s.t. W^TW=I$$

  最大可分性则是希望样本点在该超平面的投影尽可能分开，优化目标是$$\max\_W tr(W^TXX^TW)$$ $$s.t. W^TW=I$$

  PCA的步骤是对所有样本进行中心化，然后计算协方差矩阵，再对协方差矩阵做特征值分解，然后取最大的d'个特征值所对应的特征向量。
* Page229: 线性降维

  基于线性变换来进行降维的方法。
* Page229: 主成分分析

  同PCA。
* Page231: 奇异值分解(402)

  任意的实矩阵都可以进行分解，如$$A\in \mathbb{R}^{m\times n}$$可以分解为$$A=U\Sigma V^T$$,其中U是m×m阶酉矩阵；Σ是半正定m×n阶对角矩阵；而$$V^T$$，即V的共轭转置，是n×n阶酉矩阵。$u\_i$称为A的左奇异值，$$v\_i$$称为A的右奇异值。Σ对角线上的元素为A的奇异值。矩阵A的秩是非零奇异值的个数。
* Page232: 本真低维空间

  对原始低维空间和降维后的低维空间进行区分，称原始采样的低维空间为本真低维空间。
* Page232: 非线性降维

  非线性降维即是采用非线性变换的方法对数据进行降维，常用的是基于核技巧对线性降维方法进行核化。
* Page232: 核化线性降维

  对线性降维方法进行核化，以保持其原本的低维结构。
* Page232: 核主成分分析

  Kernelized PCA，在高维特征空间将数据投影到由d维的W确定的超平面上，z是x在高维空间上的像，假设$$z\_i=\phi (x\_i)$$，引入核函数$$\kappa (x\_i,x\_j)=\phi(x\_i)^T\phi(x\_j)$$，进一步用矩阵K替代，进而计算出投影矩阵。主要作用是将线性不可分的数据，映射到高维后进行划分。
* Page234: 本真距离

  即为在原始空间上的距离。
* Page234: 测地线距离

  测地线距离是两点之间的本真距离。
* Page234: 等度量映射

  等度量映射认为高维空间的直线距离不能很好地衡量其距离，所以等度量映射试图让“流形”距离在降维后仍能很好保持。
* Page234: 流形学习

  流形是在局部与欧式空间同胚的空间，所以局部可以利用欧氏距离来计算。
* Page235: 局部线性嵌入

  保持邻域内样本的线性关系的一种方法。
* Page237: 度量学习

  通过学习，得到合适的距离度量方法。
* Page238: 近邻成分分析

  NCA(Neighbourhood Component Analysis，即近邻成分分析)是和KNN关联的距离度量方法，在原数据集上进行NCA距离测量，并且完成降维，然后使用KNN在低维空间上对数据进行分类。NCA主要是随机选择近邻，然后通过LOO(Leave one out)的交换检验结果来求马氏距离的变换矩阵。通过优化目标可以得到最大化正确率的距离度量矩阵。
* Page239: 必连约束(307)

  样本必属于一个簇。
* Page239: 勿连约束

  样本必不属于同一个簇。
* Page240: 半监督聚类(307)

  半监督聚类的先验知识主要是样本相似度约束条件，将必连关系和勿连关系作为学习任务优化目标的约束。约束条件主要是基于约束和基于距离。前者主要是依靠用户提供的约束来实现监督指导作用，后者主要是自适应距离度量。
* Page240: 多视图学习

  多视图学习可以看成是从多个角度去学习，比如对同一个事物用多种方法去提取其特征，就能得到其多模态的特征，然后再对多模态特征进行学习。
* Page240: 流形假设(294)

  流行假设是指在很小的一个领域内的样本具有相似的特性，则其标签也相似。
* Page240: 流形正则化

  在正则化项加入与流形相关的项。


# 特征选择与稀疏学习

## 第11章 特征选择与稀疏学习

* Page247: 冗余特征(redundant feature)

  在特征选择过程中，有一类特征所包含的信息能从其他特征中推演出来，这类特征成为『冗余特征』。\
  例如，考虑立方体对象，若已有特征『底面长』、『底面宽』，则『底面积』是冗余特征，因为它能从『底面长』和『底面宽』得到。\
  冗余特征很多时候不起作用，去除它们会减轻学习过程的负担。但有时又会降低学习任务的难度。例如若学习目标是估算立方体的体积，则『底面积』这个冗余特征的存在将使得体积的估算更加容易；确切地说，若某个冗余特征恰好对应了完成学习任务所需的『中心概念』，则该冗余特征是有益的。
* Page247: 数据预处理(data preprocessing)

  现实世界中数据大体上都是不完整、不一致的脏数据，无法直接进行数据挖掘，或挖掘结果差强人意。为了提高数据挖掘的质量产生了数据预处理技术。它是指在主要的处理以前对数据进行的一些处理。\
  数据预处理有多种方法：数据清理，数据集成，数据变换，数据归约等。在现实机器学习任务中，特征选择也是一个重要的数据预处理过程。
* Page247: 特征选择 & 相关特征(feature selection & relevant feature)

  对一个学习任务，给定的属性集称为特征。对当前学习任务有用的属性称为『相关特征』，没什么用的属性称为『无关特征』。从给定的特征中选出相关特征子集的过程，称为特征选择。
* Page247: 相关特征(relevant feature)

  见特征选择。
* Page248: 子集搜索(subset search)

  从特征集合中选取包含所有重要信息的特征子集，若没有任何领域知识作为先验，就只能遍历所有可能的子集。而这会遭遇组合爆炸问题导致计算不可行。可行的办法是产生一个『候选子集』，评价出它的好坏，基于评价结果产生下一个候选子集，再评价，……直到无法找到更好的候选子集为止。而产生候选子集的过程就是子集搜索。\
  具体而言，给定特征集合 $${a\_1, a\_2, ...., a\_d}$$, 可以将每个特征看作一个候选子集，对这 $$d$$ 个候选单特征子集进行评价，假定 $${a\_2}$$ 最优，于是将 $${a\_2}$$ 作为第一轮的选定集；然后，加入一个特征，构成包含两个特征的候选子集，假定在这 $$d-1$$ 个候选两特征子集中 $${a\_2, a\_4}$$ 最优，且优于 $${a\_2}$$，于是将 $${a\_2, a\_4}$$ 作为本轮选定集；直至最优的候选特征子集不如上一轮的选定集，则停止生成候选子集，并将上一轮的选定集作为特征选择结果。\
  逐渐增加相关特征的策略称为『前向』搜索；类似的，逐渐减少特征的策略称为『后向』搜索；前向与后向搜索结合起来的策略称为『双向』搜索。
* Page248: 子集评价(subset evaluation)

  由于子集搜索过程仅考虑了本轮选定集最优，无法解决这样的问题：例如在第三轮假定选择 $${a\_5}$$ 优于 $${a\_6}$$，于是选定集为 $${a\_2, a\_4, a\_5}$$，然而在第四轮却可能是 $${a\_2, a\_4, a\_6, a\_8}$$ 比所有的 $${a\_2, a\_4, a\_5, a\_i}$$ 都更优。\
  通过对每个候选特征子集，基于训练数据集计算其信息增益，以此作为评价准则。这一过程称为子集评价。\
  具体而言，给定数据集 $$D$$，假定 $$D$$ 中第 $$i$$ 类样本（假定样本属于离散型）所占的比例为 $$p\_i(i = 1,2,...,|y|)$$。对属性子集 $$A$$，假定根据其取值将 $$D$$ 分成了 $$V$$ 个子集 $${D^1, D^2, ..., D^V}$$，每个子集中的样本在 $$A$$ 上取值相同，于是属性子集 $$A$$ 的信息增益为：\
  $$Gain(A) = Ent(D) - \sum\_{v=1}^{V} \frac {|D^v|}{|D|} Ent(D^v)$$，\
  其中信息熵定义为：\
  $$Ent(D) = -\sum\_{i=1}^{|y|} p\_k log\_2 p\_k$$，\
  信息增益 $$Gain(A)$$ 越大，意味着特征子集 $$A$$ 包含的有助于分类的信息越多。\
  更一般的，特征子集 $$A$$ 实际上确定了对数据集 $$D$$ 的一个划分，每个划分区域对应着 $$A$$ 上的一个取值，而样本标记信息 $$Y$$ 则对应着对 $$D$$ 的真实划分，通过估算这两个划分的差异，就能对 $$A$$ 进行评价。与 $$Y$$ 对应的划分的差异越小，则说明 $$A$$ 越好。
* Page249: 过滤式(filter)特征选择

  常见的特征选择方法之一。先对数据集进行特征选择，然后再训练学习器，特征选择过程与后续学习器无关。相当于先用特征选择过程对初始特征进行『过滤』，再用过滤后的特征来训练模型。\
  Relif 是一种著名的过滤式特征选择方法，该方法设计了一个『相关统计量』来度量特征的重要性。该统计量是一个向量，其每个分量分别对应于一个初始特征，而特征子集的重要性则是由子集中每个特征所对应的相关统计量分量之和来决定。最终只需指定一个阈值，然后选择比阈值大的相关统计量分量即可。也可指定欲选取的特征个数 $$k$$，然后选择相关统计量分量最大的 $$k$$ 个特征。时间开销岁采样次数以及原始特征数线性增长，是一个运行效率很高的过滤式特征选择算法。
* Page250: 包裹式(wrapper)特征选择

  常见的特征选择方法之一。直接把最终将要使用的学习器的性能作为特征子集的评价准则。它的目的就是为给定学习器选择有利于其性能、『量身定做』的特征子集。\
  LVW（Las Vegas Wrapper）是一个典型的包裹式特征选择方法。它在拉斯维加斯方法框架下使用随机策略来进行子集搜索，并以最终分类器的误差为特征子集评价准则。计算开销很大，且有可能运行很长时间达不到停止条件。
* Page251: 拉斯维加斯方法(Las Vegas method)

  是一种在电脑运算中永远给出正确解的随机化算法；也就是说，它总是给出正确结果，或是返回失败。 换言之，拉斯维加斯算法不赌结果的正确性，而是赌运算所用资源。它的一个显著特征是它所作的随机性决策有可能导致算法找不到所需的解。一个简单的例子是随机快速排序，他的中心点虽然是随机选择的，但排序结果永远一致。
* Page251: 蒙特卡洛方法(340,384)(Monte Carlo method)

  也称统计模拟方法，是二十世纪四十年代由于科学技术的发展和电子计算机的发明，而提出的一种以概率统计理论为指导、使用随机数来解决问题的数值计算方法。
* Page252: LASSO(261)

  全称 Least Absolute Shrinkage and Selection Operator，对目标损失函数引入 $$L\_1$$ 正则化项，即采用 $$L\_1$$ 范数时，目标损失函数称为 LASSO，如式所示：\
  $$min\_w \sum\_{i=1}^m (y\_i - w^Tx\_i)^2 + \lambda ||w||\_1$$
* Page252: Tikhonov 正则化（L2 正则化）

  当样本特征很多，而样本数相对较少时，要优化的目标损失函数很容易陷入过拟合。为了缓解过拟合问题，对目标损失函数引入正则化项。Tikhonov 正则化（L2 正则化）就是使用 $$L\_2$$ 范数正则化，即：$$||w||\_2^2$$。
* Page252: 岭回归(ridge regression)

  引入 Tikhonov 正则化项（L2 正则化项）的目标损失函数称为岭回归，如式所示：\
  $$min\_w \sum\_{i=1}^m (y\_i - w^Tx\_i)^2 + \lambda ||w||^2\_2$$
* Page252: 嵌入式(embedding)特征选择

  将特征选择过程与学习器训练过程融为一体，两者在同一个优化过程中完成，即在学习器训练过程中自动地进行了特征选择。
* Page253: L1 正则化

  为了缓解过拟合问题，对目标损失函数引入正则化项。L1 正则化就是使用 $$L\_1$$ 范数正则化，即：$$||w||\_1$$。
* Page253: L2正则化

  见 Tikhonov 正则化。
* Page253: Lipschitz 条件

  在使用近端梯度下降对 L1 正则化问题求解时，对优化目标：$$min\_x f(x) + \lambda \lVert x \rVert\_1$$，若 $$f(x)$$ 可导，微分算子 $$\nabla f$$ 满足 L-Lipschitz 条件，即存在常数 $$L>0$$ 使得：\
  $$\lVert \nabla f(x^{'})-\nabla f(x) \rVert\_2^2 \leqslant L \lVert x^{'} - x \rVert\_2^2 \quad (\forall x,x^{'})$$ 在 $$x\_k$$ 附近可将 $$f(x)$$ 通过二阶泰勒展开式近似为：\
  $$f(x)^{\*} \backsimeq f(x\_k) + \langle \nabla f(x\_k), x-x\_k \rangle + \frac {L}{2}\lVert x - x\_k \rVert^2$$\
  L-Lipschitz 条件是指：对于在实数集的子集的函数 $$f: D \subseteq \mathbb{R} \to \mathbb{R}$$，若存在常数 $$K$$，使得 $$\lvert f(a)-f(b)\rvert \leqslant K\lvert a-b\rvert \quad \forall a,b \in D$$，则称 $$f$$ 符合 L-Lipschitz 条件，对于 $$f$$ 最小的常数 $$K$$ 称为 $$f$$ 的 L-Lipschitz 常数。
* Page253: 近端梯度下降(259)(Proximal Gradient Descent)

  在引入 L1 正则项的目标损失函数：\
  $$min \quad f(x) + \lambda \lVert x \rVert\_1$$\
  会遇到求导问题（L1 范数在 $$x=0$$ 处不可导），若 $$f(x)$$ 可导，微分算子 $$\nabla f$$ 满足 L-Lipschitz 条件，在 $$x\_k$$ 附近可将 $$f(x)$$ 通过二阶泰勒展开式近似为：\
  $$f(x)^{*} \backsimeq f(x\_k) + \langle \nabla f(x\_k), x-x\_k \rangle + \frac {L}{2}\lVert x - x\_k \rVert^2$$\
  $$= \frac {L}{2} \lVert x - \lgroup x\_k - \frac {1}{L} \nabla f(x\_k) \rgroup \rVert\_2^2 + const$$，\
  其中 const 是与 $$x$$ 无关的常数，$$\langle .,. \rangle$$ 表示内积，上式最小值在如下 $$x\_{k+1}$$ 获得：\
  $$x\_{k+1} = x\_k - \frac {1}{L} \nabla f(x\_k)$$，\
  若通过梯度下降法对 $$f(x)$$ 最小化，则每一步梯度下降迭代实际上等价于最小化 $$f(x)^{*}$$，类似得到每一步迭代应为：\
  $$x\_{k+1} = arg\ min\_x \frac {L}{2} \lVert x - \lgroup x\_k - \frac {1}{L} \nabla f(x\_k) \rgroup \rVert\_2^2 + \lambda \lVert x \rVert\_1$$，\
  即在每一步对 $$f(x)$$ 进行梯度下降迭代的同时考虑 L1 范数最小化。\
  这种近似求解的方法称为近端梯度下降（Proximal Gradient Descent）。
* Page255: 码书学习 & 字典学习(codebook & dictionary learning)

  为普通稠密表达的样本找到合适的字典，将样本转化为合适的稀疏表示形式，从而使学习任务得以简化，模型复杂度得以降低，通常称为字典学习，亦称为码书学习。字典学习侧重于学得字典的过程。给定数据集 $${x\_1,x\_2,..,x\_m}$$，字典学习最简单的形式为：\
  $$min\_{\bf{B, \alpha\_i}} \sum\_{i=1}^m \lVert x\_i - \bf{B \alpha\_i} \rVert\_2^2 + \lambda \sum\_{i=1}^m \lVert \bf{\alpha\_i} \rVert\_1$$，\
  其中 $$\bf{B} \in \mathbb{R}^{d \times k}$$ 为字典矩阵，$$k$$ 称为字典的词汇量，通常由用户指定，$$\bf{\alpha\_i} \in \mathbb{R}^k$$ 则是样本 $$\bf{x\_i} \in \mathbb{R}^d$$ 的稀疏表示。
* Page255: 稀疏编码(sparse coding)

  字典学习亦称为稀疏编码，后者更侧重于对样本进行稀疏表达的过程。两者通常在同一个优化求解过程中完成。
* Page255: 字典学习(dictionary learning)

  同码书学习。
* Page257: 压缩感知(compressed sensing)

  压缩感知（Compressed sensing），也被称为压缩采样（Compressive sampling）或稀疏采样（Sparse sampling），是一种寻找欠定线性系统的稀疏解的技术。在现实任务中，我们常希望根据部分信息来恢复全部信息，压缩感知就是为处理此类问题提供了方法。\
  压缩感知关注如何利用信号本身所具有的稀疏性，从部分观测样本中恢复信号。通常认为，压缩感知分为感知测量和重构恢复两个阶段。
* Page259: 局部线性嵌入(Locally Linear Embedding) 无。\
  局部线性嵌入（Locally Linear Embedding）是一种非常重要的降维方法。和传统的 PCA，LDA 等关注样本方差的降维方法相比，LLE 关注于降维时保持样本局部的线性特征，由于 LLE 在降维时保持了样本的局部特征，它广泛的用于图像图像识别，高维数据可视化等领域。
* Page259: 协同过滤(collaborative filtering)

  利用某兴趣相投、拥有共同经验之群体的喜好来推荐使用者感兴趣的资讯，个人通过合作的机制给予资讯相当程度的回应（如评分）并记录下来以达到过滤的目的进而帮助别人筛选资讯，回应不一定仅限于特别感兴趣的，特别不感兴趣资讯的记录也相当重要。分为以使用者为基础的协同过滤、以项目为基础的协同过滤和以模型为基础的协同过滤。
* Page260: 核范数 & 迹范数(nuclear norm & trace norm)

  矩阵奇异值之和。
* Page260: 迹范数(trace norm)

  同核范数。


# 计算学习理论

## 第12章 计算学习理论

* Page267: 计算学习理论（computational learning theory）

  计算学习理论研究的是关于通过“计算”来进行“学习”的理论，即关于机器学习的理论基础，其目的是分析学习任务的困难本质，为学习算法提供理论保证，并根据分析结果指导算法设计。
* Page268: Jensen不等式

  $$f(\mathbb{E}(x)) \leqq \mathbb{E}(f(x))$$

  期望的函数小于函数的期望
* Page268: Hoeffding不等式

  若$$x\_1,x\_2,...,x\_m$$为$$m$$个独立随机变量，且满足$$0\leqq x\_i\leqq 1$$，则对于任意$$\epsilon\geqq 0$$有： $$P(\frac{1}{m}\sum\_{i=1}^{m}x\_i-\frac{1}{m}\sum\_{i=1}^m\mathbb{E}(x\_i)\geqq \epsilon)\leqq exp(-2m\epsilon^2)$$ $$P(\mid \frac{1}{m}\sum\_{i=1}^{m}x\_i-\frac{1}{m}\sum\_{i=1}^m\mathbb{E}(x\_i)\mid \geqq \epsilon)\leqq 2exp(-2m\epsilon^2)$$
* Page268: McDiarmid 不等式

  若$$x\_1,x\_2,...,x\_m$$为$$m$$个独立随机变量，且对任意$$1\leqq i\leqq m$$，函数$$f$$满足

  $$\sum\_{x\_1,x\_2,...,x\_m, x^{'}*i } |f(x\_1,...,x\_m) - f(x\_1, ... , x*{i-1}, x^{'}*i, x*{i+1}, ..., x\_m)| \leqq c\_i$$，

  则对任意$$\epsilon\geqq 0$$，有 $$P(f(x\_1,...,x\_m)-E(f(x\_1,...,x\_m))\geqq \epsilon)\leqq exp(\frac{-2\epsilon^2}{\sum\_i c\_i^2})$$ $$P(\mid f(x\_1,...,x\_m)-E(f(x\_1,...,x\_m))\mid \geqq \epsilon)\leqq exp(\frac{-2\epsilon^2}{\sum\_i c\_i^2})$$
* Page268: 概率近似正确
* Page268: 概念类(concept class)

  令c表示概念，这是从样本空间$$\mathcal{X}$$到标记空间$$\mathcal{Y}$$的映射，它决定示例x的真实标记y，若对任何样例(x,y)有c(x)=y成立，则称c为目标概念，所有我们希望学得的目标概念所构成的集合称为概念类，用符号$$\mathcal{C}$$表示。
* Page268: 假设空间（hypothesis space）

给定学习算法$$\mathfrak{L}$$，它所考虑的所有可能概念的集合称为“假设空间”，用符号$$\mathcal{H}$$表示。由于学习算法事先并不知道概念类的真实存在，因此假设空间和概念类往往不同。

* Page269: PAC辨识(PAC Identify)

  对于$$0<\epsilon, \delta<1$$，所有$$c\in\mathcal{C}$$和分布$$\mathcal{D}$$，若存在学习算法$$\mathfrak{L}$$，其输出假设$$h\in\mathcal{H}$$满足

  $$P(E(h)\leqq \epsilon)\geqq 1-\delta$$

  则称学习算法$$\mathfrak{L}$$能从假设空间$$\mathcal{H}$$中PAC辨识概念类$$\mathcal{C}$$，这样的学习算法$$\mathfrak{L}$$能从假设空间$$\mathcal{H}$$中PAC辨识概念类$$\mathcal{C}$$.
* Page269: PAC可学习(PAC Learnable)

  令$$m$$表示从分布$$\mathcal{D}$$中独立同分布采样得到的样例数目，$$0<\epsilon,\delta<1$$，对所有分布$$\mathcal{D}$$，若存在学习算法$$\mathfrak{L}$$和多项式函数$$poly(.,.,.,.)$$，使得对于任何$$m\geqq poly(1/\epsilon,1/\delta,size(x),size(c))$$, $$\mathfrak{L}$$能从假设空间$$\mathcal{H}$$中PAC辨识概念类$$\mathcal{C}$$，则称概念类$$\mathcal{C}$$对假设空间$$\mathcal{H}$$而言是PAC可学习的，也简称概念类$$\mathcal{C}$$是PAC可学习的。对于计算机算法来说，必须考虑时间复杂度，于是：
* Page270: PAC学习算法（PAC Learning Algorithm）

  若学习算法$$\mathfrak{L}$$使概念类$$\mathcal{C}$$为PAC可学习的，且$$\mathfrak{L}$$的运行时间也是多项式函数$$poly(1/\epsilon,1/\delta,size(x),size(c))$$，则称概念类$$\mathcal{C}$$是高效PAC可学习的，称$$\mathfrak{L}$$为概念类$$\mathcal{C}$$的PAC学习算法。
* Page269: 时间复杂度

  假定学习算法$$\mathfrak{L}$$处理每个样本的时间为常数，则$$\mathfrak{L}$$的时间复杂度等价于样本复杂度。
* Page270: 样本复杂度

  满足PAC学习算法$$\mathfrak{L}$$所需的$$m\geqq poly(1/\epsilon,1/\delta,size(x),size(c))$$中最小的m，称为学习算法$$\mathfrak{L}$$的样本复杂度。
* Page269: 不可分(272)(non-seperable)

  对于较为困难的学习问题，目标概念$$c$$往往不存在于假设空间$$\mathcal{H}$$中，假定对于任何$$h\in\mathcal{H}, \hat{E(h)}\neq0$$，也就是说，$$\mathcal{H}$$中的任意一个假设都会在训练集上出现或多或少的错误。$$\mathcal{H}$$中不存在任何假设能将所有示例完全正确分开，则称该问题对于学习算法$$\mathfrak{L}$$是不可分的，亦称不一致的。
* Page269: 不一致(non-consistent)

  即不可分。
* Page269: 可分(270)(seperable)

  可分情形意味着目标概念$$c$$属于假设空间$$\mathcal{H}$$， 即 $$c\in\mathcal{H}$$，$$\mathcal{H}$$中存在假设能将所有示例按与真实标记一致的方式完全分开，我们将该问题对学习算法$$\mathfrak{L}$$是可分的，亦称一致的。
* Page270: 恰PAC可学习(properly PAC learnable)

  若学习算法$$\mathfrak{L}$$使概念类$$\mathcal{C}$$为PAC可学习的，且$$\mathfrak{L}$$的运行时间也是多项式函数$$poly(1/\epsilon, 1/\delta, size(x), size(c))$$，则称概念类$$\mathcal{C}$$是高效PAC可学习的，称$$\mathfrak{L}$$为概念类$$\mathcal{C}$$的PAC学习算法。若在PAC学习中假设空间与概念类完全相同，称为恰PAC可学习，这意味着学习算法的能力与学习任务恰好匹配。
* Page270: 有限假设空间

  一般而言，$$\mathcal{H}$$越大，其包含任意目标概念的可能性越大，但从中找到某个具体概念的难度也越大，$$|\mathcal{H}|$$有限时，我们称$$\mathcal{H}$$为有限假设空间，否则称为“无限假设空间”。
* Page273: 不可知PAC可学习(agnostic PAC learnable)

  令$$m$$表示从分布$$\mathcal{D}$$中独立同分布采样得到的样例数目，$$1<\epsilon, \delta < 1$$，对所有分布$$\mathcal{D}$$，若存在学习算法$$\mathcal{L}$$和多项式函数$$poly(.,.,.,.)$$，使得对于任何$$m\leqq poly(1/\epsilon, 1/\delta, size(x), size(c))$$，$$\mathfrak{L}$$能从假设空间$$\mathcal{H}$$中输出满足$$P(E(h)-min\_{h^{'} \in \mathcal{H}})\leqq\epsilon\geqq 1 - \delta$$的假设h,则称假设空间$$\mathcal{H}$$是不可知PAC可学习的。
* Page273: 增长函数

  给定假设空间$$\mathcal{H}$$和示例集$$D={x\_1, x\_2, ..., x\_m}$$，$$\mathcal{H}$$中每个假设h都能对$$D$$中示例赋予标记，标记结果可表示为

  $$h|\_D = {h(x\_1), h(x\_2), ... , h(x\_m)}$$

  随着m的增大，$$\mathcal{H}$$中所有假设对D中的示例所能赋予标记的可能结果数也会增大。

  对所有$$m\in\mathbb{N}$$，假设空间$$\mathcal{H}$$的增长函数

  $$\prod\_{\mathcal{H}}(m)= max\_{{x\_1, ..., x\_m\subseteq \mathcal{H}}}|{h(x\_1), h(x\_2),..., h(x\_m)|h\subseteq \mathcal{H}}|$$，

  表示假设空间$$\mathcal{H}$$对m个示例所能赋予标记的最大可能结果数，可能结果数越大，假设空间的表示能力越强，对学习任务的适应能力也越强，可以利用增长函数来估计经验误差与泛化误差之间的关系。
* Page273: 对分

  假设空间$$\mathcal{H}$$中不同的假设对于$$\mathcal{D}$$中示例赋予标记的结果可能相同，也可能不同；尽管$$\mathcal{H}$$可能包含无穷多个假设，但其对$$\mathcal{D}$$中示例赋予标记的可能结果是有限的。对m个示例，最多有$$2^m$$个可能结果，对二分类问题来说，$$\mathcal{H}$$中的假设对$$\mathcal{D}$$中示例赋予标记的每种可能结果称为对$$\mathcal{D}$$的一种对分。
* Page273: 打散

  若假设空间$$\mathcal{H}$$能实现示例集$$\mathcal{D}$$上的所有对分，即$$\prod\_{\mathcal{H}}(m)=2^m$$，则称示例集$$\mathcal{D}$$能被假设空间$$\mathcal{H}$$打散。
* Page273: VC维(274)

  假设空间的VC维是能被$$\mathcal{H}$$打散的最大示例集的大小，即$$VC(\mathcal{H})=max{m: \prod\_{\mathcal{H}}(m)=2^m}$$，可用于度量假设空间的复杂度。
* Page278: 经验风险最小化(Empirical Risk Minimization, ERM)

  令h表示学习算法$$\mathfrak{L}$$输出的假设，若$$h$$满足$$\hat{E}(h)=min\_{h'}$$，则称$$\mathfrak{L}$$为满足经验风险最小化原则的算法。
* Page279: Rademacher复杂度

  经验误差最小的假设是

  $$argmax\_{h\in\mathcal{H}}\frac{1}{m}\sum\_{i=1}^m y\_i h(x\_i)$$，

  然而现实任务中的标记y有时候会收到噪声影响，不再是真实标记，再此情形下，选择假设空间$$\mathcal{H}$$中在训练集上表现最好的假设，有时还不如选择$$\mathcal{H}$$中事先考虑了随机噪声影响的假设。考虑随机变量$$\sigma\_i$$，它以0.5的概率取值-1,0.5的概率取值+1，称为Rademacher随机变量，基于$$\sigma\_i$$可将上式改写为

  $$sup\_{h\in\mathcal{H}}\frac{1}{m}\sum^m\_{i=1}\sigma\_i h(x\_i)$$,

  期望为

  $$\mathbb{E}*{\sigma}\[sup*{h\in\mathcal{H}}\sum\_{i=1}^m \sigma\_ih(x\_i)]$$，

  考虑实值函数空间 $$\mathcal{F}:\mathcal{Z}\rightarrow\mathcal{R}$$， 令$$Z={z\_1,z\_2,...,z\_m}$$，

  其中$$z\_i\in\mathcal{Z}$$, 将$$\mathcal{X}$$和$$\mathcal{H}$$替换成$$Z$$和$$F$$可得，函数空间$$\mathcal{Z}$$g关于$$\mathcal{F}$$的经验Rademacher复杂度

  $$\hat{R}*Z(\mathcal{F})=\mathbb{E}*{\sigma}\[sup\_{f\in\mathcal{F}}\frac{1}{m}\sum\_{i=1}^m\sigma\_i f(z\_i)]$$，

  其衡量了函数空间$$\mathcal{F}$$与随机噪声在集合$$\mathcal{Z}$$中的相关性。对所有从$$\mathcal{D}$$独立同分布采样而得的大小为m的集合Z求期望可得函数空间$$\mathcal{F}$$关于上$$\mathcal{Z}$$分布的$$\mathcal{D}$$Rademacher复杂度。
* Page284: 稳定性

  算法在输入发生变化时，输出是否会随之发生较大的变化。
* Page285: 均匀稳定性

  对任何$$x\in\mathcal{X},z=(x,y)$$，若学习算法$$\mathcal{L}$$满足

  $$|l(\mathcal{L}*D, z) - l(\mathcal{L}*{D\_i},z)|\leqq\beta$$，

  则称$$\mathcal{L}$$关于损失函数$$l$$满足$$\beta$$均匀稳定性。

  其中l为泛化损失，$$D\_i$$为移除$$D$$中第i个样例得到的集合。


# 半监督学习

## 第13章 半监督学习

* Page293: 半监督学习(294)(semi-supervised learning)

  使用未标记样本进行学习
* Page293: 查询(query)

  向专家寻求标记的过程。
* Page293: 未标记样本(unlabeled sample)

  类别标记未知的样本
* Page293: 有标记样本(labeled sample)

  类别标记已知的样本
* Page293: 主动学习(active learning)

  先用已标记样本训练一个模型，拿这个模型进行预测，与专家交互进行标记，然后把这个新获得的有标记样本加入已标记样本集中重新训练一个模型，再去预测和标记，若每次都挑出对改善模型性能帮助大的样本，则只需进行较少的标记就能构建出比较强的模型，从而大幅降低标记成本，这样的学习方式成为“主动学习”，其目标是使用尽量少的“查询”来获得尽量好的性能。

  主动学习引入了额外的专家知识，通过与外界的交互来将部分未标记样本转变为有标记样本。
* Page294: 聚类假设（cluster assumption）

  假设数据存在簇结构，同一个簇的样本属于同一个类别，这是一种将未标记样本所揭示的数据分布信息与类别标记相联系的假设。
* Page295: 直推学习(transductive learning)

  假定学习估计差中所考虑的未标记样本恰是带预测数据，学习的目的就是在这些未标记样本上获得最优泛化性能。不同于纯半监督学习的基于开放世界假设，希望学得模型能适用于训练过程中未观察到的数据，直推学习是基于封闭世界假设，仅试图对学习过程中观察到的未标记数据进行预测。
* Page298: S3VM(Semi-Supervised Support Vector Machine)

  支持向量机在半监督学习上的推广，在不考虑未标记样本时，支持向量机试图找到最大间隔划分超平面，在考虑未标记样本后，S3VM试图找到能将两类有标记样本分开，且穿过数据低密度区域的划分超平面，，这里的基本假设是“低密度分割”，显然，这是聚类假设在考虑了线性超平面划分后的推广。
* Page298: 半监督SVM

  同S3VM
* Page300: 图半监督学习

  给定一个数据集，我们可将其映射为一个图，数据集中每个样本对应于图中一个节点，若两个样本之间的相似度很高（或相关性很强），则对应的结点之间存在一条边，边的“强度”正比于样本之间的相似度（或相关性），将有标记样本所对应的节点想象为染过色，而未标记样本所对应的节点尚未染色，于是半监督学习就对应于“颜色”在图上扩散或传播的过程。由于一个图对应了一个矩阵，这就使得我们能基于矩阵运算来进行半监督学习算法的推导与分析。
* Page301: 亲和矩阵（affinity matrix）

  表达数据集中样本间相关性的矩阵。
* Page302: 标记传播（label propagation）

  根据已有标记，对未标记样本进行标记。
* Page304: 基于分歧的方法

  使用多学习器，学习器之间的分歧对未标记数据的利用至关重要。
* Page304: 协同训练（co-training）

  利用多视图的相容互补性，假设数据拥有两个充分且条件独立视图，“充分”是指每个视图都包含足以产生最优学习器的信息，“条件独立”是指在给定类别标记条件下两个视图独立，在此情形下，首先在每个视图上基于有标记样本分别训练处一个分类器，然后让每个分类器分别去挑选自己“最有把握的”未标记样本赋予伪标记，并将 样本提供给另一个分类器作为新增的有标记样本用于训练更新，这个过程不断迭代进行，直到两个分类器都不再发生变化，或达到预先设定的迭代轮数为止。


# 概率图模型

## 第14章 概率图模型(probabilistic model)

* Page319: 马尔科夫网(Markov network)

  使用无向图表示变量间的相关关系的概率图模型。 概率图模型是一类用图来表达变量相关关系的概率模型。它以图为表示工具，最常见的是用一个结点表示一个或一组随机变量，结点之间的边表示变量间的概率相关关系，即『变量关系图』。根据边的性质不同，概率图模型可大致分为两类：第一类是使用有向无环图表示变量间的依赖关系，称为有向图模型或贝叶斯网；第二类是使用无向图表示变量间的相关关系，称为无向图模型或马尔科夫网。
* Page319: 推断

  概率模型提供了一种描述框架，将学习任务归结于计算变量的概率分布。在概率模型中，利用已知变量推测未知变量的分布称为『推断』，其核心是如何基于可观测变量推测出未知变量的条件分布。
* Page319: 隐马尔科夫模型(Hidden Markov Model)

  隐马尔科夫（HMM）模型是结构最简单的动态贝叶斯网，主要用于时序数据建模，在语音识别、自然语言处理等领域有广泛应用。
* Page322: 马尔科夫随机场(Markov Random Field)

  马尔科夫随机场（MRF）是典型的马尔科夫网，一种著名的无向图模型。图中每个结点表示一个或一组变量，结点之间的边表示两个变量之间的依赖关系。
* Page322: 势函数(potential functions)

  同因子。
* Page322: 因子(factor)

  马尔科夫随机场有一组势函数，亦称『因子』，这是定义在变量子集上的非负实函数，主要用于定义概率分布函数。\
  对结点的一个子集，若任意两结点间都有边连接，则称该结点子集为一个『团』。在马尔科夫随机场中，多个变量之间的联合概率分布能基于团分解为多个因子的乘积，每个因子仅与一个团相关。
* Page323: 全局马尔科夫性(global Markov property)

  在马尔科夫随机场中得到『条件独立性』需要借助『分离』的概念。若从结点集 A 中的结点到 B 中的结点都必须经过结点集 C 中的结点，则称结点集 A 和 B 被结点集 C 分离，C 称为『分离集』。\
  全局马尔科夫性就是指给定两个变量子集的分离集，则这两个变量子集条件独立。\
  也就是说，若令 A，B 和 C 对应的变量集分别为 $$X\_A$$，$$X\_B$$，$$X\_C$$，则 $$X\_A$$ 和 $$X\_B$$ 在给定 $$X\_C$$ 的条件下独立，记为：$$X\_A \perp X\_B \mid X\_C$$。
* Page324: 局部马尔科夫性(local Markov property)

  由全局马尔科夫性得出的推论之一。给定某变量的邻接变量，则该变量条件独立于其他变量。\
  形式化地说，令 $$V$$ 为图的结点集，$$n(v)$$ 为结点 $$v$$ 在图上的邻接结点，$$n^*(v) = n(v) \cup {v}$$，有 $$X\_v \perp X\_{V \setminus{n}^*(v)} \mid X\_n(v)$$.\
  注：$$\setminus$$ 表示『非』
* Page325: 成对马尔科夫性(pairwise Markov property)

  由全局马尔科夫性得出的推论之一。给定所有其他变量，两个非邻接变量条件独立。

  形式化地说，令图的结点集和边集分别为 $$V$$ 和 $$E$$，对图中的两个结点 $$u$$ 和 $$v$$，若 $$\langle u,v \rangle \notin E$$，则 $$X\_u \perp X\_v \mid X\_{V\setminus \langle u,v \rangle}$$.\
  注：$$\setminus$$ 表示『非』
* Page325: 马尔科夫毯(Markov blanket)

  某变量的所有邻接变量组成的集合称为该变量的马尔科夫毯。
* Page325: 条件随机场(Conditional Random Field)

  一种判别式无向图模型。判别式模型对条件分布进行建模，可看作给定观测值的马尔科夫随机场，也可看作对率回归（常说的逻辑回归）的扩展。\
  条件随机场试图对多个变量在给定观测值后的条件概率进行建模。具体来说，若令 $$X={x\_1,x\_2,...,x\_n}$$ 为观测序列，$$\mathrm{y} = {y\_1,y\_2,...,y\_n}$$ 为与之相应的标记序列，则条件随机场的目标是构建条件概率模型 $$P(\mathrm{y}|X)$$。

  标记变量 $$y$$ 可以是结构型变量，即其分量之间具有某种相关性。

  令 $$G=\langle V,E \rangle$$ 表示结点与标记变量 $$\mathrm{y}$$ 中元素一一对应的无向图，$$\mathit{y}\_v$$ 表示与结点 $$v$$ 对应的标记变量，$$n(v)$$ 表示结点 $$v$$ 的邻接结点，若图 $$G$$ 的每个变量 $$\mathit{y}*v$$ 都满足马尔科夫性，即：\
  $$P(\mathit{y} \mid \mathrm{x}, \mathrm{y}*{V\setminus{v}}) = P(\mathit{y}*v \mid \mathrm{x}, \mathrm{y}*{n(v)})$$ 则 $$(\mathrm{y}, \mathrm{x})$$ 构成一个条件随机场。
* Page326: 链式条件随机场(chain-structured CRF)

  构成条件随机场的图 $$G$$，理论上来说可具有任意结构，只要能表示标记变量之间的条件独立性关系即可。\
  但在现实应用中，尤其是对标记序列建模时，最常用的仍是链式结构，即：链式条件随机场（chain-structured CRF）
* Page328: 边际分布(marginal distribution)

  边际分布是指对无关变量求和或积分后得到结果。\
  例如在马尔科夫网中，变量的联合分布被表示成极大团的势函数乘积，于是，给定参数 $$\Theta$$ 求解某个变量 $$x$$ 的分布，就变成对联合分布中其他变量进行积分的过程，这称为『边际化』（marginalization）。
* Page328: 变量消去

  概率图模型的推断方法大致可以分为：精确推断方法和近似推断方法。\
  精确推断方法实质是一种动态规划算法，它利用图模型所描述的条件独立性来削减计算目标概率值所需的计算量。变量消去法是最直观的精确推断算法，也是构建其他精确推断算法的基础。\
  变量消去法通过利用乘法对加法的分配率，把多个变量的积的求和问题，转化为对部分变量交替进行求积与求和的问题。这种转化使得每次的求和与求积运算限制在局部，仅与部分变量有关，从而简化了运算。\
  它的一个明显的缺点是：若需计算多个边际分布，重复使用变量消去法将会造成大量的冗余计算。
* Page330: 信念传播(340)(Belief Propagation)

  亦称 Sum-Product 算法，将变量消去法中的求和操作看作一个消息传递过程，较好地解决了求解多个边际分布时的重复计算问题。\
  信念传播算法最早由 Pearl 作为精确推断技术提出，后来衍生出多种近似推断算法。对一般的带环图，信念传播算法需在初始化、消息传递等环节进行调整，由此形成了迭代信念传播算法（Loopy Belief Propagation）。
* Page331: MCMC(Markov Chain Monte Carlo)

  马尔科夫链蒙特卡洛方法，概率图模型中最常用的采样技术。\
  MCMC 方法先设法构造一条马尔科夫链，使其收敛至平稳分布恰为待估计参数的后验分布，然后通过这条马尔科夫链来产生符合后验分布的样本，并基于这些样本来进行估计。这里马尔科夫链转移概率的构造至关重要，不同的构造方法将产生不同的 MCMC 算法。
* Page333: MH 算法(Metropolis-Hastings)

  MH（Metropolis-Hastings）算法是 MCMC 的重要代表。它基于『拒绝采样』来逼近平稳分布。算法每次根据上一轮采样结果获得候选样本，但这个候选样本会以一定概率被『拒绝』掉。
* Page334: 变分推断(variational inference)

  变分推断通过使用已知简单分布来逼近需推断的复杂分布，并通过限制近似分布的类型，从而得到一种局部最优、但具有确定解的近似后验分布。\
  变分推断使用的近似分布须具有良好的数值性质，通常是基于连续型变量的概率密度函数来刻画的。
* Page334: 盘式记法(plate notation)

  概率图模型一种简洁的表示方法。相互独立的、由相同机制生成的多个变量被放在一个方框（盘）内，并在方框中标出类似变量重复出现的个数 $$N$$，方框可以嵌套。通常用阴影标注出已知的、能观察到的变量。在很多学习任务中，对属性变量使用盘式记法将使得图表示非常简洁。
* Page335: KL 散度(414)(Kullback-Leibler divergence)

  亦称相对熵或信息散度，可用于度量两个概率分部之间的差异。给定两个概率分布 $$P$$ 和 $$Q$$，二者之间的 KL 散度定义为：\
  $$KL(P \parallel Q) = \int\_{-\infty}^{\infty} p(x) \log \frac {p(x)}{q(x)}dx$$，\
  其中 $$p(x)$$ 和 $$q(x)$$ 分别为 $$P$$ 和 $$Q$$ 的概率密度函数。
* Page337: 平均场(mean field)

使用变分法对隐变量进行推断，对隐变量 $$z\_j$$ 分布进行估计时，融合了 $$z\_j$$ 之外的其他 $$z\_{i \ne j}$$ 的信息，这是通过联合似然函数 $$\ln p(x,z)$$ 在 $$z\_j$$ 之外的隐变量分布上求期望的到的，称为平均场方法。

* Page337: 话题模型(topic model)

  一族生成式有向图模型，主要用于处理离散型的数据（如文本集合），在信息检索、自然语言处理等领域有广泛应用。\
  话题表示一个概念，具体表示为一系列相关的词，以及它们在该概念下出现的概率。
* Page337: 隐狄利克雷分配模型(Latent Dirichlet Allocation)

  隐狄利克雷分配模型（Latent Dirichlet Allocation，简称 LDA）是话题模型的典型代表。\
  现实任务中可通过统计文档中出现的词来获得词频向量，但通常并不知道这组文档谈论了哪些话题。LDA 从生成式模型的角度看待文档和话题。\
  具体来说，LDA 认为每篇文档包含多个话题，不妨用向量 $$\Theta\_t \in \mathbb{R}^N$$ 表示文档 $$t$$ 中所包含的每个话题的比例，$$\Theta\_{t,k}$$ 即表示文档 $$t$$ 中包含话题 $$k$$ 的比例，进而通过下面的步骤由话题生成文档 $$t$$：
* 根据参数为 $$\alpha$$ 的狄利克雷分布随机采样一个话题分布 $$\Theta\_t$$；
* 按如下步骤生成文档中的 $$N$$ 个词：
  * 根据 $$\Theta\_t$$ 进行话题指派，得到文档 $$t$$ 中的词 $$n$$ 的话题 $$z\_t,n$$；
  * 根据指派的话题所对应的词频分布 $$\mathcal{\beta\_k}$$ 随机采样生成词。
* Page340: 非参数化(non-parametric)法

  一般认为在一个统计推断问题中，如给定或者假定了总体分布的具体形式，只是其中含有若干个参数，要基于来自总体的样本对这些参数做出估计或者进行某种形式的假设检验，这类推断方法称为非参数化方法。非参数化是指参数的数目无须事先指定，是贝叶斯学习方法的重要发展。


# 规则学习

## 第15章 规则学习

* Page347: 规则（rule）

  机器学习中的 “规则” 通常是指语义明确、能描述数据分布所隐含的客观规律或领域的概念、可写成 “若……，则……” 形式的逻辑规则。
* Page347: 规则学习（rule learning）

  规则学习是从训练数据中学习出一组能用于对未见示例进行判别的规则。
* Page347: 逻辑文字（literal）

  在数理逻辑中，“文字” 专指原子公式（atom）及其否定。
* Page348: 冲突消解（conflict resolution）

  规则集合是每条规则的的集成，当同一个示例被判别结果不同的多条规则覆盖时，称发生了 “冲突”（conflict），解决冲突的办法称为 “冲突消解”（conflict resolution）。常用的策略有投票法、排序法、元规则法等。
* Page348: 带序规则（ordered rule）

  排序法消解冲突时会在规则集合上定义一个顺序，在发生冲突时使用排序最前的规则；相应的规则学习过程称为 “带序规则”（ordered rule）学习或 “优先级规则”（priority rule）学习。
* Page348: 优先级规则（priority rule）

  同 “带序规则”
* Page348: 元规则（meta-rule）

  关于规则的规则。
* Page348: 默认规则（default rule）

  规则学习算法通常会设置一条 “默认规则”（default rule）来处理规则集合未覆盖的样本。
* Page348: 缺省规则

  同 “默认规则”，可认为是一种特殊的元规则。
* Page348: 命题规则（propositional rule）

  由 “原子命题”（propositional atom）和逻辑连接词 “与”（∧）、“或”（∨）、“非”（￢）和 “蕴含”（←）构成的简单陈述句。
* Page348: 原子命题

  最基本的命题，不含逻辑连接词的逻辑文字。
* Page348: 一阶规则

  也被称为 “关系型规则”（relational rule），基本成分是能描述事物的属性或关系的 “原子公式”（atomic formula），例如表达父子关系的谓词（predicate）“父亲（X,Y）” 就是原子公式。 如果进一步用谓词 “自然数（X)” 表示 X 是自然数，那么 “所有自然数加 1 都是自然数” 就可写作 “∀X∃Y（自然数(Y) ← 自然数(X)∧(Y=δ(X)）”，或更简洁的 “∀X(自然数(δ(X)) ← 自然数(X))”。这样的规则就是一阶规则。其中 X 和 Y 称为逻辑变量，“∀” “∃” 分别表示 “任意” 和 “存在”，用于限定变量的取值范围，称为 “量词”（quantifier）。 从形式语言系统的角度看，命题规则是一阶规则的特例。
* Page349: 序贯覆盖（sequential covering）

  规则学习的目标是产生一个能覆盖尽可能多的样例的规则集。最直接的做法是 “序贯覆盖”（sequential covering），即逐条归纳：在训练集上每学到一条规则，就将该规则覆盖的训练样例去除，然后以剩下的训练样例组成训练集重复上述过程。 由于每次只处理一部分数据，因此也被称为 “分治”（separate-and-conquer）策略。
* Page350: 特化（specialization）

  使用序贯覆盖法时，在属性和候选值较多时会由于组合爆炸而不可行。现实任务中一般有两种策略来产生规则。其中一种是 “自顶向下”（top-down），即从比较一般的规则开始，逐渐添加新文字以缩小规则覆盖范围，直到满足预定条件为止；亦称为 “生成-测试”（generate-then-test）法，这个过程就是规则逐渐 “特化”（specialization）的过程。这种方法通常更容易产生泛化性能较好的规则，也是通常使用的一种策略。
* Page350: 泛化（generalization）

  另一种产生策略的规则是 “自底向上”（bottom-up），即从比较特殊的规则开始，逐渐删除文字以扩大规则覆盖范围，直到满足条件为止；亦称为 “数据驱动”（data-driven）法，这个过程就是规则逐渐 “泛化”（generalization）的过程。适用于训练样本较少的情形；通常在一阶规则学习这类假设空间非常复杂的任务上使用较多。
* Page352: 似然率（Likelihood Ratio Statistics，LRS）

  用于在已知某些观测数据时，对其参数进行估计，是关于模型参数的函数。在规则学习中，它衡量了规则（集）覆盖样例的分布与训练集经验分布的差别。LRS 越大，说明采用规则（集）进行预测与直接使用训练集正、反例比率进行猜测的差别越大；LRS 越小，说明规则（集）的效果越可能仅是偶然现象。
* Page353: RIPPER（Repeated Incremental Pruning to Produce Error Reduction）

  一种著名的规则学习算法，它首先使用 IREP\* 剪枝机制生成规则集 $$\mathcal{R}$$，对 $$\mathcal{R}$$ 中的每条规则 $$r\_i$$ 产生两个变体：

  * $$r'\_i$$: 基于 $$r\_i$$ 覆盖的样例，用 IREP\* 重新生成一条规则 $$r'\_i$$，该规则称为替换规则（replacement rule）；
  * $$r''\_i$$: 基于 $$r\_i$$ 增加文字进行特化，然后再用 IREP\* 剪枝生成一条规则 $$r''\_i$$，该规则称为修订规则（revised rule）。

  接下来，把 $$r'\_i$$ 和 $$r''\_i$$ 分别与 $$\mathcal{R}$$ 中除 $$r\_i$$ 之外的规则放在一起，组成规则集 $$\mathcal{R'}$$ 和 $$\mathcal{R''}$$，将它们与 $$\mathcal{R}$$ 一起进行比较，选择最优的规则集保留下来。 RIPPER 将 $$\mathcal{R}$$ 中的所有规则放在一起重新加以优化，避免了最初按序生成时，每条规则没有对其后产生的规则加以考虑常常导致算法陷入局部最优的问题。
* Page357: ILP(364)（Inductive Logic Programming，归纳逻辑程序设计）

  归纳逻辑程序设计在一阶规则学习中引入了函数和逻辑表达式嵌套。一方面，这使得机器学习系统具备了更为强大的表达能力；另一方面 ILP 可看作用机器学习技术来解决基于背景知识的逻辑程序（logic program）归纳，其学得的 “规则” 可被 PROLOG 等逻辑程序设计语言直接使用。
* Page357: 归纳逻辑程序设计(364)

  同 ILP。
* Page358: 最小一般泛化（Least General Generalization）

  自底向上的规则生成策略中，将 “特殊” 规则转变为更 “一般” 规则的技术叫 “最小一般泛化”（Least General Generalization，简称 LGG）。 给定一阶公式 r1 和 r2，LGG 先找出涉及相同谓词的文字，然后对文字中每个位置的常量逐一进行考察，若常量在两个文字中相同则保持不变，记为 LGG(t,t)=t；否则将他们替换为同一个新变量，并将该替换应用于公式的所有其他位置：假定这两个不同的常量分别为 s,t，新变量为 V，则记为 LGG(s,t) = V，并在以后所有出现 LGG(s,t) 的位置用 V 来代替。
* Page359: 归纳（induction）

  归纳是从个别事物出发概括出一般规律性。机器学习属于归纳的范畴。
* Page359: 逆归结

  假设两个逻辑表达式 C1 和 C2 成立，且分别包含了互补项 L1 与 L2；不失一般性，令 L = L1 = ￢L2，C1 = A ∨ L，C2 = B ∨ ￢L。归结原理是通过演绎推理消去 L 而得到 “归结项” C = A ∨ B。 而逆归结与上面过程相反，它研究的是在已知 C 和某个 Ci 的情况下如何得到 Cj。 基于逆归结，我们可基于背景知识来发明新的概念和关系。 在逻辑推理实践中，有四种完备的逆归结操作：吸收（absorption）、辨识（identification）、内构（intra-construction）、互构（inter-construction）。
* Page359: 演绎（deduction）

  演绎是从一般性规律出发来探讨具体事物。
* Page361: 置换（substitution）

  置换（substitution）是用某些项来替换逻辑表达式中的变量。
* Page361: 合一（unification）

  合一（unification）是用一种变量置换令两个或多个逻辑表达式相等。
* Page361: 最一般合一置换（most general unifer，简称 MGU)

  若 δ 是一组一阶逻辑表达式 W 的合一化子，且对 W 的任意合一化子 θ 均存在相应的置换 λ 使 θ = δ o λ，则称 δ 为 W 的 “最一般合一置换” 或 “最一般合一化子”。
* Page362: 归结商（resolution quotient）

  逆归结中，已知 C 和某个 Ci，则 Cj = C/Ci 称为 “归结商”。
* Page363: 关系学习

  关系学习是指学习对概念的网络式描述。
* Page364: 统计关系学习（statistical relational learning）

  将关系学习与统计学习相结合，如概率归纳逻辑程序设计、概率关系模型、贝叶斯逻辑程序、马尔科夫逻辑网等统称为 “统计关系学习”。


# 强化学习

## 第16章 强化学习(reinforcement learning)

* Page371: MDP

  在概率论和统计学中，马可夫决策过程（英语：Markov Decision Processes，缩写为 MDPs）提供了一个数学架构模型，用于面对部份随机，部份可由决策者控制的状态下，如何进行决策，以俄罗斯数学家安德雷·马尔可夫的名字命名。在经由动态规划与强化学习以解决最佳化问题的研究领域中，马可夫决策过程是一个有用的工具。
* Page371: 奖赏(reward)

  奖励函数定义了强化学习 Agent 的目标，它将环境的状态映射为一个数字（奖励），表现了该状态的内在愿望。Agent 的目标是最大限度地提高长期收益。
* Page371: 马尔科夫决策过程(Markov Decision Process)

  Markov Decision Process，通常用来描述强化学习任务：机器处于环境 $$E$$ 中，状态空间为 $$X$$，其中每个状态 $$x \in X$$ 是机器感知到的环境的描述；机器能采取的动作构成了动作空间 $$A$$；若某个动作 $$a \in A$$ 作用在当前状态 $$x$$ 上，则潜在的转移函数 $$P$$ 将使得环境从当前状态按某种概率转移到另一个状态；在转移到另一个状态的同时，环境会根据潜在的『奖赏』函数 $$R$$ 反馈给机器一个奖赏。
* Page371: 强化学习(reinforcement learning)

  强化学习是机器学习中的一个领域，强调如何基于环境而行动，以取得最大化的预期利益。强化学习任务对应了四元组 $$E = \langle \mathit{X,A,P,R} \rangle$$，其中 $$P: X \times A \times X \to \mathbb{R}$$ 指定了状态转移概率，$$R: X \times A \times X \to \mathbb{R}$$ 指定了奖赏；在有的应用中，奖赏函数可能仅与状态转移有关，即 $$R: X \times X \to \mathbb{R}$$。
* Page371: 再励学习

  强化学习，亦称再励学习。
* Page372: 策略(policy)

  在环境中状态的转移、奖赏的返回是不受机器控制的，机器只能通过选择要执行的动作来影响环境，也只能通过观察转移后的状态和返回的奖赏来感知环境。\
  机器要做的是通过在环境中不断地尝试而学得一个『策略』（policy）$$\pi$$，根据这个策略，在状态 $$x$$ 下就能得知要执行的动作 $$a = \pi(x)$$。\
  简单来说，policy 是 Agent 的决策功能，规定了在 Agent 可能遇到的任何情况下应采取的行动。这是 Agent 的核心。\
  策略有两种表示方法：一种是将策略表示为函数 $$\pi: X \to A$$，确定性策略常用这种表示；另一种是概率表示 $$\pi: X \times A \to \mathbb{R}$$，随机性策略常用这种表示，$$\pi(x,a)$$ 为状态 $$x$$ 下选择动作 $$a$$ 的概率，这里必须有 $$\sum\_a \pi(x,a) = 1$$。\
  在强化学习任务中，学习的目的就是要找到能使长期累积奖赏最大化的策略。
* Page373: K-摇臂赌博机(K-armed bandit)

  单步强化学习对应的理论模型，K-摇臂赌博机（K-armed bandit）有 K 个摇臂，赌徒在投入一个硬币后可选择按下其中一个摇臂，每个摇臂以一定的概率吐出硬币，但这个概率赌徒并不知道。赌徒的目标是通过一定的策略最大化自己的奖赏，即获得最多的硬币。
* Page374: ϵ-贪心

  强化学习面临「探索-利用窘境」，$$\epsilon$$-贪心法基于一个概率来对探索和利用进行折中：每次尝试时，以 $$\epsilon$$ 的概率进行探索，即以均匀概率随机选取一个摇臂；以 $$1 - \epsilon$$ 的概率进行利用，即选择当前平均奖赏最高的摇臂（若有多个，则最随机选择一个）。
* Page374: 探索-利用窘境(Exploration-Exploitation dilemma)

  若获知每个摇臂的期望奖赏，可采用「仅探索」法：将所有的尝试机会平均分配给每个摇臂（即轮流按下每个摇臂），最后以每个摇臂各自的平均吐币概率作为其奖赏期望的近似估计。若执行奖赏最大的动作，则可采用「仅利用」法：按下目前最优的（即到目前为止平均奖赏最大的）摇臂，若有多个摇臂同为最优，则从中随机选取一个。\
  「探索」（即估计摇臂的优劣）和「利用」（即选择当前最优摇臂）这两者是矛盾的，因为尝试次数（即总投币数）有限，加强了一方则会自然削弱另一方，这就是强化学习所面临的「探索-利用窘境」（Exploration-Exploitation dilemma）。
* Page375: Softmax

  Softmax 算法基于当前已知的摇臂平均奖赏来对探索和利用进行折中。若个摇臂的平均奖赏想当，则选取个摇臂的概率也相当；若某些摇臂的平均奖赏高于其他摇臂，则它们被选取的概率也明显更高。\
  Softmax 算法中摇臂概率的分配是基于 Boltzmann 分布：\
  $$P(k) = \frac {e^{\frac {Q(k)}{\tau}}}{\sum\_{i=1}^K e^{\frac {Q(i)}{\tau}}}$$，\
  其中，$$Q(i)$$ 记录当前摇臂的平均奖赏；$$\tau > 0$$ 称为「温度」，$$\tau$$ 越小则平均奖赏高的摇臂被选取的概率越高。$$\tau$$ 趋于 0 时 Softmax 将趋于「仅利用」，$$\tau$$ 趋于无穷大时 Softmax 则将趋于「仅探索」。
* Page377: 有模型学习(model-based learning)

  在已知模型的环境中学习称为「有模型学习」，即机器已对环境进行了建模，能在机器内部模拟出与环境相同或近似的情况。
* Page377: 状态-动作值函数(state-action value function)

  在模型已知时，对任意策略 $$\pi$$ 能估计出该策略带来的期望累积奖赏。令函数 $$V^{\pi}(x)$$ 表示从状态 $$x$$ 出发，使用策略 $$\pi$$ 所带来的累积奖赏；函数 $$Q^{\pi}(x,a)$$ 表示从状态 $$x$$ 出发，执行动作 $$a$$ 后再使用策略 $$\pi$$ 带来的累积奖赏。这里的 $$V(\cdot)$$ 称为「状态值函数」（state value function），$$Q(\cdot)$$ 称为「状态-动作值函数」（state-action value function），分别表示指定「状态」上以及指定「状态-动作」上的累积奖赏。
* Page377: 状态值函数(state value function)

  见「状态-动作值函数」。
* Page380: Bellman 等式

  对于状态值函数，由于 MDP 具有马尔科夫性质，即系统下一时刻的状态仅由当前时刻的状态决定，不依赖于以往任何状态，于是值函数有很简单的递归形式。对于 $$T$$ 步累积奖赏有：\
  $$V\_T^{\pi}(x) = \sum\_{a \in A} \pi (x, a) \sum\_{x' \in X} P\_{x \to x'}^a \lgroup \frac {1}{T} R\_{x \to x'}^a + \frac {T-1}{T} V\_{T-1}^{\pi} (x') \rgroup$$，\
  这样的递归等式称为 Bellman 等式。
* Page381: 策略迭代(policy iteration)

  一种求解最优解的方法。从一个初始策略（通常是随机策略）出发，先进性策略评估，然后改进策略，评估改进的策略，再进一步改进策略，……不断迭代进行策略评估和改进，直到策略收敛、不再改进为止。这样的做法称为「策略迭代」（policy iteration）。
* Page382: 值迭代(value iteration)

  策略迭代算法在每次改进策略后都需重新进行策略评估，这通常比较耗时。由于策略改进和值函数的改进是一致的，因此可将策略改进视为值函数的改善。这种改善值函数的算法就称为值迭代（value iteration）算法。
* Page382: 免模型学习(model-free learning)

  在现实的强化学习任务中，环境的转移概率、奖赏函数往往很难得知，甚至很难知道环境中一共有多少状态。若学习算法不依赖于环境建模，则称为「免模型学习」（model-free learning）。
* Page386: TD(Temporal Difference) 学习(393)

  由于蒙特卡洛强化学习算法没有充分利用强化学习任务的 MDP 结构，因此效率要低很多。时序差分（Temporal Difference，简称 TD）学习则结合了动态规划与蒙特卡洛方法的思想，能做到更高效的免模型学习。
* Page386: 时序差分学习(393)

  同 TD 学习。
* Page387: Sarsa 算法(390)

  该算法每次更新值函数需前一步的状态（state）、前一步的动作（action）、奖赏值（reward）、当前状态（state）、将要执行的动作（action），因此得名 Sarsa 算法。Sarsa 让系统按照策略指引进行探索，在探索每一步都进行状态价值的更新，更新公式如下：\
  $$Q^\pi\_{t+1} (x,a) = Q^\pi\_t (x,a) + \alpha \lgroup R^\alpha\_{x \to x'} + \gamma Q^\pi\_t(x',a') - Q^\pi\_t(x,a) \rgroup$$，\
  其中，$$x'$$ 是前一次在状态 $$x$$ 执行动作 $$a$$ 后转移到的状态，$$a'$$ 是策略 $$\pi$$ 在 $$x'$$ 上选择的动作。\
  Sarsa 是一个同策略（on-policy）算法，算法中的评估（上式）和执行（$$a' = \pi^\epsilon(x')$$）的均为 $$\epsilon$$-贪心策略。
* Page387: Q-学习(393)(Q-learning)

  将 Sarsa 修改为异策略（off-policy）算法，即动作值函数更新（评估）不同于选取动作（执行）时遵循的策略，就得到 Q-学习算法，Q-学习的动作值函数更新公式如下：\
  $$Q^\pi\_{t+1} (x,a) = Q^\pi\_t (x,a) + \alpha \lgroup R^\alpha\_{x \to x'} + \gamma max\_{a} Q^\pi\_t(x',a) - Q^\pi\_t(x,a) \rgroup$$
* Page388: 表格值函数(tabular value function)

  如果我们假定强化学习任务是在有限状态空间上进行，每个状态可以用一个编号来指代；值函数就是关于有限状态的「表格值函数」（tabular value），也就是说值函数能表示为一个数组，输入 $$i$$ 对应的函数值就是数组元素 $$i$$ 的值，且更改一个状态上的值不影响其他状态上的值。
* Page388: 值函数近似(value function approximation)

  实际强化学习任务所面临的状态空间往往是连续的，有无穷多个状态。我们假定状态空间为 $$n$$ 维实数空间 $$X = \mathbb{R}^n$$，此时显然无法用表格值函数来记录状态值。但考虑简单情形，即值函数能表达为状态的线性函数：\
  $$V\_\theta(x) = \theta^Tx$$，\
  其中 $$x$$ 为状态向量，$$\theta$$ 为参数向量。由于此时的值函数难以像有限状态那样精确记录每个状态的值，因此这样的值函数求解被称为值函数近似（value function approximation）。
* Page390: 模仿学习(imitation learning)

  在强化学习的经典任务设置中，机器所能获得的反馈信息仅有多步决策后的累计奖赏，但在现实任务中，往往能得到人类专家的决策过程范例。从这样的范例中学习，称为「模仿学习」（imitation learning）。
* Page391: 逆强化学习(inverse reinforcement learning)

  在很多任务中，设计奖赏函数往往相当困难，从人类专家提供的范例数据中反推出奖赏函数有助于解决该问题，这就是「逆强化学习」（inverse reinforcement learning）。\
  逆强化学习的基本思想是：欲使机器做出与范例一致的行为，等价于在某个奖赏函数的环境中求解最优策略，该最优策略所产生的轨迹与范例数据一致。换言之，我们要寻找某种奖赏函数使得范例数据是最优的，然后即可使用这个奖赏函数来训练强化学习策略。
* Page393: 近似动态规划(approximate dynamic programming)

  强化学习在运筹学与控制论领域的研究被称为「近似动态规划」（approximate dynamic programming）。


# 附录

## 附录

* Page399: 行列式（determinant）

  n 阶方阵 A 的行列式（determinant）定义为：\
  $$det(A) = \sum\_{\sigma \in S\_n} par(\sigma) A\_{1\sigma\_1}A\_{2\sigma\_2}...A\_{n\sigma\_n}$$\
  其中，Sn 为所有 n 阶排列（permutation）的集合，par(σ) 的值为 -1 或 +1 取决于 σ = (σ1,σ2,...σn) 为奇排列或偶排列，即其中出现降序的次数为奇数或偶数，例如 (1,3,2) 中降序次数为 1，(3,1,2) 中降序次数为 2。对于单位阵，有 det(I) = 1。

  直观理解：

  * 是什么：以二维为例，表示一个区域的面积，负数则是将区域翻转，或者说定向改变。如果矩阵所代表的变换将空间压缩到更小的维度（不满秩），则行列式为 0（比如二维到一维，面积就变成了零）。列代表基向量，行代表坐标，一个 m×n 的矩阵表示 n 个基向量表示的空间映射在 m 维的坐标上。行列式是面积（二维）或体积（三维）缩放的比例。&#x20;
  * 怎么算：以二维为例，主对角线元素代表两个维度缩放的比例，其余两个元素代表两个维度的坐标区域对角线的缩放。 &#x20;
* Page399: 迹（trace）

  对于 n 阶方阵 A，它的迹（trace）是主对角线上的元素之和，即：\
  $$tr(A) = \sum\_{i=1}^n A\_ii$$
* Page400: Frobenius 范数

  矩阵 A(m×n) 的 Frobenius 范数定义为：\
  $$\Arrowvert A \Arrowvert\_F = (tr(A^TA))^{1/2} = \lgroup \sum\_{i=1}^m \sum\_{j=1}^n A\_{ij}^2 \rgroup ^{1/2}$$\
  矩阵的 Frobenius 范数就是将矩阵张成向量后的 L2 范数，其实就是所有元素的平方和再开方。
* Page402: 低秩矩阵近似问题

  给定一个秩为 r 的矩阵 A，欲求其最优 k 秩近似矩阵 A'（k ≤ r），这样的问题称为低秩矩阵近似问题。\
  该问题可以形式化为：\
  $$min\_{A' \in R^{m\*n}} \ \ \Arrowvert A - A' \Arrowvert\_F, \ \ \ s.t. rank(A') = k$$\
  该问题可以使用奇异值分解：对矩阵 A 进行奇异值分解后，将 Σ 矩阵（见奇异值分解）的 r-k 个最小的奇异值置零获得矩阵 Σ\_k，A\_k = U\_k Σ\_k V\_k^T 就是最优解，其中 U\_k 和 V\_k 分别是 U 和 V 前 k 列组成的矩阵。这个结果也称为 Eckart-Young-Mirsky 定理。
* Page402: 奇异值分解（Singular Value Decomposition，简称 SVD）

  对任意矩阵 $$A \in \mathbb{R}^{m\times n}$$ 都可分解为：$$A = U\sum V^T$$，其中，$$U \in \mathbb{R}^{m\times m}$$ 是满足 $$U^TU=I$$ 的 m 阶酉矩阵（unitary matrix）；$$V \in \mathbb{R}^{n \times n}$$ 是满足 $$V^TV=I$$ 的 n 阶酉矩阵；$$\sum \in \mathbb{R}^{m \times n}$$ 是 m×n 的矩阵，其中 $$(\sum)\_{ii} = \sigma\_i$$ 且其他位置的元素均为 0，$$\sigma\_i$$ 为非负实数且满足 $$\sigma\_1 \ge \sigma\_2 \ge ... \ge 0$$。
* Page403: 拉格朗日乘子法（Lagrange multipliers）

  拉格朗日乘子法是一种寻找多元函数在一组约束下的极值的方法。通过引入拉格朗日乘子，可将有 d 个变量与 k 个约束条件的最优化问题转化为具有 d+k 个变量的无约束优化问题求解。有等式约束和不等式约束两种。\
  以等式约束的优化问题为例。假定 x 为 d 维向量，要求 x 的某个取值 x\* 使目标函数 f(x) 最小且同时满足 g(x)=0 的约束。从几何角度看该问题的目标是在由方程 g(x)=0 确定的 d-1 维曲面上寻找能使目标函数 f(x) 最小化的点。此时很容易得出在最优点目标函数与约束函数相切（即目标函数在该点的梯度正交于约束曲面）。由此可知，在最优点，梯度 $$\nabla g(x), \nabla f(x)$$ 方向相同或相反：，即存在 λ ≠ 0 使得 $$\nabla f(x^*) + \lambda \nabla g(x^*) = 0$$，λ 称为拉格朗日乘子，定义拉格朗日函数为：$$L(x, \lambda) = f(x) + \lambda g(x)$$。
* Page405: 对偶函数（dual function）

  将优化问题的约束推广到多个：具有 m 个等式约束和 n 个不等式约束，且可行域 $$\mathbb{D} \subset \mathbb{R}^d$$ 非空的优化问题：\
  $$min\_x f(x) \ \ s.t.\ \ h\_i(x) = 0\ (i=1,...m);\ g\_j(x) \le 0\ (j=1,...n)$$\
  该问题为优化问题的主问题（primal problem），相应的拉格朗日函数为：\
  $$L(x,\lambda,\mu) = f(x) + \sum\_{i=1}^m \lambda\_i h\_i(x) + \sum\_{j=1}^n \mu\_j g\_j(x)$$，\
  其对偶函数定义为：\
  $$\Gamma(\lambda, \mu) = \inf\_{x\in D} L (x, \lambda, \mu) = \inf\_{x\in D} \lgroup f(x) + \sum\_{i=1}^m \lambda\_i h\_i(x) + \sum\_{j=1}^n \mu\_j g\_j(x)\rgroup$$。\
  对偶函数给出了主问题的最优值下界，因为若 x\* 为主问题可行域的点，对任意 $$\mu \succeq 0, \lambda$$，都有 $$\sum\_{i=1}^m \lambda\_i h\_i(x) + \sum\_{j=1}^n \mu\_j g\_j(x) \le 0$$，进而有 $$\Gamma(\lambda,\mu) \le L(x^*, \lambda, \mu) \le f(x^*)$$。
* Page406: 二次规划（Quadratic Programming，简称 QP）

  一类典型的优化问题，包括凸二次优化和非凸二次优化。目标函数是变量的二次函数，约束条件是变量的线性不等式。假定变量个数为 d，约束条件个数为 m，标准的二次规划问题形如：\
  $$\min\_x \ \ \frac{1}{2} x^TQx + c^Tx, \ \ s.t. Ax \le b$$\
  其中，x 为 d 维向量， Q ∈ R 为实对称矩阵，A ∈ R 为实矩阵，b ∈ R 和 c ∈ R 为实向量，Ax ≤ b 的每一行对应一个约束。
* Page407: 半正定规划（Seme-Definite Programming，简称 SDP）

  是一类凸优化问题，其中的变量可组织成半正定对称矩阵形式，且优化问题的目标函数和约束都是这些变量的线性函数。\
  给定 d×d 的对称矩阵 **X, C**，$$C·X = \sum\_{i=1}^d\sum\_{j=1}^dC\_{ij}X\_{ij}$$，\
  若 Ai(i=1,...,m) 也是 d×d 的对称矩阵，bi(i=1,2,...,m) 为 m 个实数，则半正定规划问题形如：\
  $$min\_X C · X; \ \ s.t. \ A\_i \cdot X = b\_i; \ \ i = 1,2,...,m, X \succeq 0$$
* Page409: 伯努利分布（Bernoulli distribution）

  关于布尔变量 x ∈ {0,1} 的概率分布，其连续参数 μ ∈ \[0,1] 表示变量 x=1 的概率。\
  $$P(x|\mu) = Bern(x|\mu) = \mu^x(1-\mu)^{(1-x)}$$\
  $$\mathbb{E}\[x] = \mu; var\[x] = \mu(1-\mu)$$
* Page409: 均匀分布（uniform distribution）

  关于定义在区间 `[a,b](a<b)` 上连续变量的简单概率分布。\
  $$p(x|a,b) = U(x|a,b) = \frac{1}{b-a}$$\
  $$\mathbb{E}\[x] = \frac{a+b}{2}; var\[x] = \frac{(b-a)^2}{12}$$
* Page410: 多项分布（multinominal distribution）

  将伯努利分布由单变量扩展为 d 维，并在此基础上扩展二项分布就得到多项分布，它描述了在 N 次独立实验中有 mi 次 xi=1 的概率。\
  $$P(m\_1,m\_2,...,m\_d|N,\mu) = Mult(m\_1,m\_2,...,m\_d|N,\mu) = \frac{N!}{m\_1!m\_2!...m\_d!} \prod\_{i=1}^d \mu\_i^{m\_i}$$\
  $$\mathbb{E}\[m\_i] = N\mu\_i; \ var\[m\_i] = N\mu\_i(1-\mu\_i); \ cov\[m\_j,m\_i] = -N\mu\_j\mu\_i$$
* Page410: 二项分布（binomial distribution）

  描述 N 次是独立的伯努利实验中有 m 次成功（x=1）的概率。\
  $$P(m|N,\mu) = Bin(m|N,\mu) = {N \choose m} \mu^m (1-\mu)^{N-m}$$
* Page411: 贝塔分布（Beta distribution）

  关于连续变量 μ ∈ \[0,1] 的概率分布，由两个参数 a>0, b>0 确定：\
  $$p(\mu|a,b) = Beta(\mu|a,b) = \frac{\Gamma(a+b)}{\Gamma(a)\Gamma(b)} \mu^{a-1} (1-\mu)^{b-1} = \frac{1}{B(a,b)} \mu^{a-1}(1-\mu)^{b-1}$$\
  $$\mathbb{E}\[\mu] = \frac{a}{a+b}; \ var\[\mu] = \frac{ab}{(a+b)^2(a+b+a)}; \ \Gamma(a) = \int\_{0}^{+\infty}t^{a-1}e^{-t}dt$$\
  当 a=b=1 时，贝塔分布退化为均匀分布。
* Page412: 狄利克雷分布（Dirichlet distribution）

  关于一组 d 个连续变量 μi ∈ \[0,1] 的概率分布，$$\sum\_{i=1}^d \mu\_i = 1$$。令 $$\mu = (\mu\_1,...,\mu\_d)$$，参数 $$\alpha = (\alpha\_1,...,\alpha\_d), \ \alpha\_i>0, \hat{\alpha} = \sum\_{i=1}^d \alpha\_i$$\
  $$p(\mu|\alpha) = Dir(\mu|\alpha) = \frac{\Gamma(\hat{\alpha})}{\Gamma(\alpha\_1)...\Gamma(\alpha\_i）} \prod\_{i=1}^d \mu\_i^{(\alpha\_i-1)}$$\
  $$\mathbb{E}\[\mu\_i] = \frac{\alpha\_i}{\hat{\alpha}}, \ var\[\mu\_i] = \frac{\alpha\_i(\hat{\alpha}-\alpha\_i)}{\hat{\alpha}^2(\hat{\alpha}+1)}, \ cov\[\mu\_j,\mu\_i] = \frac{\alpha\_j\alpha\_i}{\hat{\alpha}^2(\hat{\alpha}+1)}$$\
  当 d=2 时，狄利克雷分布退化为贝塔分布。
* Page412: 高斯分布（Gaussian distribution）

  亦称正态分布（normal distribution），是应用最广泛的连续概率分布。\
  对于单变量 x ∈ (-∞, +∞)，高斯分布的参数为均值 μ ∈ (-∞, +∞) 和 方差 σ^2 > 0。\
  $$p(x|\mu,\sigma^2) = \mathcal{N}(x|\mu,\sigma^2) = \frac{1}{\sqrt{2\pi\sigma^2}} \exp { -\frac{(x-\mu)^2}{2\sigma^2} }$$\
  $$\mathbb{E}=\mu, \ var\[x]=\sigma^2$$\
  对于 d 维向量 **x**，多元高斯分布的参数为 d 维均值向量 **μ** 和 d×d 的对称正定协方差矩阵 **Σ**。\
  $$p(x|\mu,\sum) = \mathcal{N}(x|\mu,\sum) = \frac{1}{\sqrt{2\pi^d \det(\sum)}} \exp { -\frac{1}{2}(x-\mu)^T {\sum}^{-1}(x-\mu) }$$\
  $$\mathbb{E}=\mu, \ var\[x]=\sum$$
* Page412: 正态分布（normal distribution）

  同高斯分布。
* Page413: 共轭分布（conjugate distribution）

  假设变量 x 服从分布 P(x|Θ)，其中 Θ 为参数，X={x1,x2,...,xm} 为变量 x 的观测样本，假设参数 Θ 服从先验分布 ∏(Θ)。\
  若由先验分布 ∏(Θ) 和抽样分布 P(X|Θ) 决定的后验分布 F(Θ|X) 与 ∏(Θ) 是同种类型的分布，则称先验分布 ∏(Θ) 为分布 P(X|Θ) 或 P(x|Θ) 的共轭分布。
* Page414: 相对熵（relative entropy）

  亦称 KL 散度或信息散度，可用于度量两个概率分布之间的差异。给定两个概率分布 P 和 Q，二者之间的相对熵定义为：\
  $$KL(P||Q) = \int\_{-\infty}^{+\infty} p(x)\log\frac{p(x)}{q(x)}dx$$\
  其中 p(x) 和 q(x) 分别为 P 和 Q 的概率密度函数。\
  通俗地说，用分布 Q 的最佳信息传递方式来传达分布 P，比用分布 P 自己的最佳信息方式传达平均多耗费的信息长度为 KL 散度。
* Page414: 信息散度（information divergence）

  同相对熵。
* Page415: 交叉熵（cross entropy）

  KL 散度展开可得：\
  $$KL(P||Q) = \int\_{-\infty}^{+\infty} p(x)\log p(x)dx - \int\_{-\infty}^{+\infty} p(x)\log q(x)dx = -H(P) + H(P,Q)$$\
  其中 H(P) 为熵，H(P,Q) 为 P 和 Q 的交叉熵。 通俗地说，用分布 Q 的最佳信息传递方式传达分布 P 中随机抽选的一个事件，所需的平均信息长度为交叉熵。
* Page415: 熵（entropy）

  熵是对整个事件信息量的量化，传达信息所需的最优平均信息长度为香农熵。\
  $$H(P) = \sum\_xP(x)\log\frac{1}{P(x)}$$

附：一些不错的学习资料

* 奇异值分解
  * [奇异值分解 SVD 的数学解释 - CSDN 博客](https://blog.csdn.net/u010099080/article/details/68060274)
  * [(3 条消息) 奇异值的物理意义是什么？ - 知乎](https://www.zhihu.com/question/22237507)
  * [机器学习中的数学 (5)- 强大的矩阵奇异值分解 (SVD) 及其应用 - LeftNotEasy - 博客园](http://www.cnblogs.com/LeftNotEasy/archive/2011/01/19/1939687.html)
  * [奇异值分解 (SVD) 原理详解及推导 - CSDN 博客](https://blog.csdn.net/zhongkejingwang/article/details/43053513)
* 拉格朗日乘子法
  * [(3 条消息) 拉格朗日乘子法如何理解？ - 知乎](https://www.zhihu.com/question/38586401)
  * [【整理】深入理解拉格朗日乘子法（Lagrange Multiplier) 和 KKT 条件 - mo\_wang - 博客园](http://www.cnblogs.com/mo-wang/p/4775548.html)
  * [An Introduction to Lagrange Multipliers](http://www.slimy.com/~steuard/teaching/tutorials/Lagrange.html)
* 梯度
  * [文章](https://www.matongxue.com/madocs/222.html#/madoc)
  * [为什么梯度反方向是函数值局部下降最快的方向？](https://zhuanlan.zhihu.com/p/24913912)
  * [梯度 - YouTube](https://www.youtube.com/watch?v=npkl19rcpdY)
* 正定矩阵和半正定矩阵
  * [正定矩阵与半正定矩阵定义性质与理解 - CSDN 博客](https://blog.csdn.net/asd136912/article/details/79146151)
* 贝塔分布
  * [带你理解 beta 分布 - CSDN 博客](https://blog.csdn.net/a358463121/article/details/52562940)
* 狄利克雷分布
  * [(2 条消息) 什么是狄利克雷分布？狄利克雷过程又是什么？ - 知乎](https://www.zhihu.com/question/26751755)
  * [机器学习的数学基础（1）--Dirichlet 分布 - CSDN 博客](https://blog.csdn.net/jwh_bupt/article/details/8841644)
  * [科学网—再谈分布之分布（dirichlet 分布）- 贝叶斯分析之 2 - 张天蓉的博文](http://blog.sciencenet.cn/blog-677221-1051014.html)
  * [通俗理解 Dirichlet 分布及其实践 | A Notebook](https://xijunlee.github.io/2017/09/09/Dirichlet分布与Beta分布/)
  * [Dirichlet Distribution（狄利克雷分布）与 Dirichlet Process（狄利克雷过程） | 数据学习者官方网站 (Datalearner)](https://www.datalearner.com/blog/1051459673766843)
  * [LDA数学八卦](http://emma.memect.com/t/9756da9a47744de993d8df13a26e04e38286c9bc1c5a0d2b259c4564c6613298/LDA)
* 熵、相对熵、交叉熵
  * [Shannon entropy in the context of machine learning and AI](https://medium.com/swlh/shannon-entropy-in-the-context-of-machine-learning-and-ai-24aee2709e32)
  * [如何理解KL散度的不对称性 | 机器之心](https://www.jiqizhixin.com/articles/0224)
  * [【 深度学习 】熵，交叉熵，KL 散度 Entropy, Cross-Entropy and KL-Divergence](https://www.bilibili.com/video/av19193502?from=search\&seid=9145887951572377038)


# Java

* [java web](https://github.com/ahangchen/windy-afternoon/tree/0ed76feaed07a46585e67528211ae55b520e232d/java/java-web/README.md)
  * [Servlet部署](broken://pages/-L_G1ChFt1hj47mWbw5o)
  * [琐碎的tips](broken://pages/-L_G1ChGisENfSKaPIjD)
* [JNI](/java/note)
* [Note](/java/note-1)
* [Effective Java笔记](https://github.com/ahangchen/windy-afternoon/tree/0ed76feaed07a46585e67528211ae55b520e232d/java/ej/README.md)


# java web

* [Servlet部署](broken://pages/-L_KsuGpkIw5MYdeNbIr)
* [琐碎的tips](broken://pages/-L_KsuGqXExs6wtx61Dl)


# Servlet部署

网上搜到的很多利用eclipse结合tomcat开发servlet的教程都要修改server.xml

感觉这种方式太粗暴了，server.xml最好是与应用无关的，

这里比较推荐export war的方式进行部署

先记录一下环境配置过程

我的系统是ubuntu 14.04；

* 下载eclipse for javaee，解压
* 下载tomcat插件：
* 在<http://www.eclipsetotale.com/tomcatPlugin.html上下载tomcatPluginV33.zip> ，将里面的jar放到eclipse的plugin目录下，

> 我并没有将jar放到了plugin目录下，而是建立了一个软链接，这样就可以防止冗余，节省一点磁盘空间

* 重启eclipse，会发现多了菜单栏多了tomcat，新建项目的时候，新建project-java-tomcat project，就可以建一个简单的服务器程序了
* 在WEB-INF/src下建自己的包，建servlet的类，在WEB-INF下建立web.xml，配置servlet及映射关系，注意class里要带包名；
* 挺重要的一点，在项目的properties-tomcat里，设置export war file路径（以.war结尾）
* 然后右击项目，tomcat project-export to war file……
* 导出到tomcat的webapp目录下，启动tomcat，就可以通过url之类的途径访问了

> 本文主要推荐war方式部署，具体servlet编写和站点访问请参考其他资料）
>
> 如果需要使用第三方库，记得设置build path后，手动复制jar文件到lib目录下，再导出war


# 琐碎的tips

## **IDEA servlet-api.jar**(2015-1-6)

idea从14升级到15后，发现

```java
import javax.servlet.AsyncContext;
```

找不到， 右击工程，open module setting，找到图中的位置，在classPath里添加tomcat的servlet-api.jar即可。 ![](/files/-Ml5Np_mfqrO6nDZOyYy)

## **tomcat允许跨域**(2016-1-12)

参考 ： <https://tomcat.apache.org/tomcat-7.0-doc/config/filter.html>

1. 全局方式

在/tomcat/conf/web.xml中，添加这样一个filter

```markup
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
```

其中async-supported不是必须的，如果你用到了AsyncContext，这里又是全局的filter，就需要设置允许async。

全局方式对虚拟目录也能生效。

2.非全局方式

在web app的web.xml里加上面这个filter就好了，记得要把url-pattern这一项改成对应的格式。

## tomcat虚拟目录

最简单的非侵入式的方式是：在tomcat/conf/Catalina/localhost目录下添加一个xml文件，内容如下：

```markup
<?xml version="1.0" encoding="UTF-8"?>
<Context  reloadable="true" docBase="/home/cwh/Software/learn_software" crossContext="true">
</Context>
```

其中，

* reloadable表示在启动tomcat后，目录内容改变时，是否允许不重启tomcat，仅刷新就得到新的内容，可以则为true
* docBase即虚拟目录代表的本地目录
* crossContext表示是否允许跨域，

然后把xml命名成虚拟目录名，比如test，则可以通过url：<http://localhost:8888/test> 访问docBase下的文件

如果需要以目录的形式访问文件，需要在conf/web.xml中配置[listing为true](http://blog.csdn.net/istend/article/details/52892208)

如果需要支持中文文件，需要[在server.xml中配置](http://blog.csdn.net/istend/article/details/52892208)Context的URLEncoding为UTF-8


# JNI

## **javah**(2016-1-15)

使用javah可以自动从java文件生成jni头文件，

> 用法：javah \[选项] <类>
>
> 其中 \[选项] 包括：
>
> -help 输出此帮助消息并退出
>
> -classpath <路径> 用于装入类的路径
>
> -bootclasspath <路径> 用于装入引导类的路径
>
> -d <目录> 输出目录
>
> -o <文件> 输出文件（只能使用 -d 或 -o 中的一个）
>
> -jni 生成 JNI样式的头文件（默认）
>
> -version 输出版本信息
>
> -verbose 启用详细输出
>
> -force 始终写入输出文件

## Example：

工程结构如下： ![](/files/-L_G1DpACUD3XOUXC4bW)

执行：

```
javah -d lib -classpath out/production/VideoSvr -jni cwh.NVR.NVRNative
```

关键在于找到正确的classpath，注意-jni 类名要放在最后面写，否则会把-jni后面的东西都当做类名解析的。


# Note

## java执行系统调用

```java
package scut.cs.cwh;

import java.io.InputStreamReader;
import java.io.LineNumberReader;

public class ExecLinuxCMD {

    public static Object exec(String cmd) {
        try {
            String[] cmdA = { "/bin/sh", "-c", cmd };
            Process process = Runtime.getRuntime().exec(cmdA);
            LineNumberReader br = new LineNumberReader(
                    new InputStreamReader(process
                            .getInputStream()));
            StringBuffer sb = new StringBuffer();
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
                sb.append(line).append("\n");
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String rst = exec("ls").toString();

        System.out.println(rst);
    }

}
```


# Effective Java笔记

## 静态工厂方法代替构造器

* 有名称，接口比构造器更易懂

> BigInteger.probablePrime

* 可以实现单例、对象池、内存重用

> 单例模式

* 可以返回return类型的子类，面向接口编程

  * 可以让返回的类非public

  * 可以通过不同的工厂方法返回不同子类

  > Java Collections Framework
* 利用类型推导，简化模板参数

  ```java
  Map<String, List<String>> m = HashMap.newInstance();
  ```
* 由于返回的子类型是private，就无法直接实例化这些子类型
* 静态工厂方法在Javadoc中没有辨识度
* 常用名称：

  > valueOf, of, getInstance, newInstance, getType, newType

## 构造参数多的时候，使用Builder

* M1: 多参数重载构造函数

  > 代码可读性差
* M2：Java Bean（setter，getter）
  * 简单
  * set过程中对象属性不完整
  * 不能单例化
* M3:Builder

  ```java
  public class Line {
    private final int length;
    private final int id;
    public static class Builder {
        private final int id;
        private final int length = 0;
        public Builder(int id) {
            this.id = id;
        }

        public Builder length(int len) {
            length = len;
            return this;
        }
        public Line build() {
            return new Line(this);
        }
    }
    private Line(Builder builder) {
        id = builder.id;
        length = builder.length;
    }
    public static void main(String[]args) {
        Line line = new Line.Builder(0).length(1).build();
    }
  }
  ```

  * 传递构造参数时像setter一样明确
  * build()调用构造器时，可以执行参数检查进行约束，在build之后才使用返回类，就能保证属性的完整性。
  * 可以向Builder传递模板参数，让它的build()方法返回任意类型
  * Class.newInstance调用无参构造函数，但无参构造函数不存在时，编译不会报错，而Builder的检查则弥补了这一点。

## 私有构造器或枚举类型实现单例

* M1：

  ```java
  public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() {}
    public void leaveTheBuilding(){}
  }
  ```

> 反射可以调用到私有的构造器

* M2:

  ```java
  public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() {}
    public static Elvis getInstance() {return INSTANCE;}
    public void leaveTheBuilding() {}
  }
  ```

> 灵活，可以通过修改getInstance()方法，决定是否返回单例对象

* 防止反序列化出错

  ```java
  private Object readResolve() {
    return INSTANCE;
  }
  ```
* 枚举单例（Java 1.5）

  ```java
  public enum Elvis {
    INSTANCE;
    public void leaveTheBuilding() {}
  }
  ```

> 简洁，序列化，防止多实例化，最佳方法

## 私有构造器强化不可实例化

只包含static method和static field的对象不希望被实例化，但实际上它有默认的无参构造器，我们仍然可能实例化它。

因此通过将构造器变为私有防止实例化。

```java
public class UtilityClass {
    private UtilityClass() {
        throw new AssertionError();
    }
}
```

这种方法的一个缺点是，这种工具类无法被子类化。

## 避免创建不必要的对象

* String

  ```java
  String s = new String("test");
  ```

  上面这种写法每次都会重新创建两个String对象

  ```java
  String s = "test";
  ```

  JTS 3.10.5保证了相同内容的字符串重用同一个对象，而且只创建一次。
* 延迟初始化
* 单例
* 对象池
* 多态层连接单例层，不需要为每个多态层创建多个单例层
* static执行开销大的代码块，存储执行结果重用
* 对于一个类中，创建开销大，创建后不修改，但会多次读取的对象，适合
* 但如果static创建的对象很少使用，可以考虑延迟初始化，但延迟初始化实现复杂，也会影响性能
* 有些对象初始化后可能改变，但改变后其功能是不变的，应当保持它为一个确定的引用，然后改变引用所指对象的内容，如Map中的keySet
* 优先使用基本类型（int）而非装箱基本类型(Integer)，性能优化
* 重对象才有必要尽可能避免创建，小对象可以由JVM很容易地构造和销毁，比自己维护对象池要好得多

## 引用泄露

java中没有内存泄露，只有引用泄露。比如一个Stack的实现：

```java
public class Stack {
    private Object[] elements;
    private int size = 0;
    // ...
    public Object pop() {
        return elements[--size];
    }

}
```

这里的pop操作只改变了size，而没有将elements\[size-1]的引用移除，Stack对象一直持有element的引用，应该改为：

```java
public Object pop() {
    Object result = elements[--size];
    elemets[size] = null;
    return result;
}
```

通过置null去掉stack中elements数组对element对象的引用

> 在这个例子中，由于Stack是自己在管理内存，存储池包含了对象引用单元（即elements数组）

需要警惕引用泄露的情形：

* 类中有对象引用单元
* 缓存
* 监听器与回调：bind而没有unbind，好的做法是只保存回调的弱引用。

## Avoid finalize

* Note
  * finalize不保证执行，尽量不要用
  * System.gc和System.runFinalization只是增加finalize执行的机会
  * finalize有严重的性能损失
  * 通过try - catch - finally来显式释放资源
* 合理用法
  * 作为显式释放资源的backup，或者check
  * 回收native peer
* 父类finalize
  * 显式调用super.finalize()
  * 内部类强制子类执行

    \`\`\`java

    public class Foo {

    private final Object finalizeGuardian = new Object() {

    ```
    @Override protected void finalize() throws Throwable {
        Foo.finalize();
    }
    ```

    };

    }


# 后端开发

我用过的后端框架有python的Django，用Java裸写Servlet做过毕设，现在准备入坑Spring boot，配合Spring Cloud。

后端开发主要有四个点要学习，是架构设计（+接口设计），数据库（+缓存），分布式（并发），各种框架，Django足够轻量，但不够强大，Servlet之上配合ssh可以做成很复杂强大的框架，但写起来超级麻烦，spring boot简化了ssh中的spring开发，功能也十分强大，就是有时候出了一些问题不好定位，需要从框架反查，不过我做过一段时间安卓开发，从源码反查是家常便饭了，没在怕的。

* [架构设计](/backend/design)
* [数据库](/backend/db)
* [Spring boot](/backend/spring_boot)
* [分布式](/backend/concurrent)


# 架构设计


# 数据库

## Oracle

### 记录oracle sql plus 执行时所有的输入输出：

```sql
spool d:\xx.txt
SQL statement
spool off (关闭)
```

## MySQL

### MySQL err 150

* 十个可能引起mysql 外键错误的原因：

> <http://verysimple.com/2006/10/22/mysql-error-number-1005-cant-create-table-mydbsql-328_45frm-errno-150/>

* MySql默认charset是瑞典的，应该这样修改：

  ```sql
  ALTER DATABASE db_name DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
  ALTER TABLE db_table CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
  ```

## MongoDB

* [教程](http://www.runoob.com/mongodb/mongodb-connections.html)
* 常用命令

```
# 启动mongodb后端
mongod
# 连接mongodb
mongo
mongodb://user:psw@ip[:port]/dbname
# 查看与切换数据库（不存在会创建），展示所有的文档
show dbs
use reid
db.getCollectionNames()
# 查询文档（传统意义的表）
db.col_name.find()
```


# java web

## **IDEA servlet-api.jar**(2015-1-6)

idea从14升级到15后，发现

```java
import javax.servlet.AsyncContext;
```

找不到， 右击工程，open module setting，找到图中的位置，在classPath里添加tomcat的servlet-api.jar即可。 ![](/files/-L_G1DOX9A2gPzQsaR8E)

## **tomcat允许跨域**(2016-1-12)

参考 ： <https://tomcat.apache.org/tomcat-7.0-doc/config/filter.html>

1. 全局方式

在/tomcat/conf/web.xml中，添加这样一个filter

```markup
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
```

其中async-supported不是必须的，如果你用到了AsyncContext，这里又是全局的filter，就需要设置允许async。

全局方式对虚拟目录也能生效。

2.非全局方式

在web app的web.xml里加上面这个filter就好了，记得要把url-pattern这一项改成对应的格式。

## tomcat虚拟目录

最简单的非侵入式的方式是：在tomcat/conf/Catalina/localhost目录下添加一个xml文件，内容如下：

```markup
<?xml version="1.0" encoding="UTF-8"?>
<Context  reloadable="true" docBase="/home/cwh/Software/learn_software" crossContext="true">
</Context>
```

其中，

* reloadable表示在启动tomcat后，目录内容改变时，是否允许不重启tomcat，仅刷新就得到新的内容，可以则为true
* docBase即虚拟目录代表的本地目录
* crossContext表示是否允许跨域，

然后把xml命名成虚拟目录名，比如test，则可以通过url：<http://localhost:8888/test> 访问docBase下的文件

如果需要以目录的形式访问文件，需要在conf/web.xml中配置[listing为true](http://blog.csdn.net/istend/article/details/52892208)

如果需要支持中文文件，需要[在server.xml中配置](http://blog.csdn.net/istend/article/details/52892208)Context的URLEncoding为UTF-8


# Servlet部署

网上搜到的很多利用eclipse结合tomcat开发servlet的教程都要修改server.xml

感觉这种方式太粗暴了，server.xml最好是与应用无关的，

这里比较推荐export war的方式进行部署

先记录一下环境配置过程

我的系统是ubuntu 14.04；

* 下载eclipse for javaee，解压
* 下载tomcat插件：
* 在<http://www.eclipsetotale.com/tomcatPlugin.html上下载tomcatPluginV33.zip> ，将里面的jar放到eclipse的plugin目录下，

> 我并没有将jar放到了plugin目录下，而是建立了一个软链接，这样就可以防止冗余，节省一点磁盘空间

* 重启eclipse，会发现多了菜单栏多了tomcat，新建项目的时候，新建project-java-tomcat project，就可以建一个简单的服务器程序了
* 在WEB-INF/src下建自己的包，建servlet的类，在WEB-INF下建立web.xml，配置servlet及映射关系，注意class里要带包名；
* 挺重要的一点，在项目的properties-tomcat里，设置export war file路径（以.war结尾）
* 然后右击项目，tomcat project-export to war file……
* 导出到tomcat的webapp目录下，启动tomcat，就可以通过url之类的途径访问了

> 本文主要推荐war方式部署，具体servlet编写和站点访问请参考其他资料）
>
> 如果需要使用第三方库，记得设置build path后，手动复制jar文件到lib目录下，再导出war


# 琐碎的tips

## **IDEA servlet-api.jar**(2015-1-6)

idea从14升级到15后，发现

```java
import javax.servlet.AsyncContext;
```

找不到， 右击工程，open module setting，找到图中的位置，在classPath里添加tomcat的servlet-api.jar即可。 ![](/files/-L_G1DOX9A2gPzQsaR8E)

## **tomcat允许跨域**(2016-1-12)

参考 ： <https://tomcat.apache.org/tomcat-7.0-doc/config/filter.html>

1. 全局方式

在/tomcat/conf/web.xml中，添加这样一个filter

```markup
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
```

其中async-supported不是必须的，如果你用到了AsyncContext，这里又是全局的filter，就需要设置允许async。

全局方式对虚拟目录也能生效。

2.非全局方式

在web app的web.xml里加上面这个filter就好了，记得要把url-pattern这一项改成对应的格式。

## tomcat虚拟目录

最简单的非侵入式的方式是：在tomcat/conf/Catalina/localhost目录下添加一个xml文件，内容如下：

```markup
<?xml version="1.0" encoding="UTF-8"?>
<Context  reloadable="true" docBase="/home/cwh/Software/learn_software" crossContext="true">
</Context>
```

其中，

* reloadable表示在启动tomcat后，目录内容改变时，是否允许不重启tomcat，仅刷新就得到新的内容，可以则为true
* docBase即虚拟目录代表的本地目录
* crossContext表示是否允许跨域，

然后把xml命名成虚拟目录名，比如test，则可以通过url：<http://localhost:8888/test> 访问docBase下的文件

如果需要以目录的形式访问文件，需要在conf/web.xml中配置[listing为true](http://blog.csdn.net/istend/article/details/52892208)

如果需要支持中文文件，需要[在server.xml中配置](http://blog.csdn.net/istend/article/details/52892208)Context的URLEncoding为UTF-8


# Spring boot

* 跨域

  只用在Controller类上添加一个“@CrossOrigin“注解就可以实现对当前controller 的跨域 访问了，当然这个标签也可以加到方法上。


# django

## Unknown command: 'syncdb'  solution：(2016-2-9)

syncdb command is deprecated in django 1.7. Use the python manage.py migrate instead.

## 推荐一个较新版本的django中文文档

<http://python.usyiyi.cn/django/intro/tutorial01.html>

## install django for python3

sudo pip3 install Django

if for python2

sudo pip install Django

## 1.9中的deprecated接口

<https://docs.djangoproject.com/en/1.9/internals/deprecation/>

## django runserver, 停在performing System checking

检查引用的py模块中，是否有自动执行的语句。

比如views.py引用a.py，a.py中执行了一个死循环执行一些监听操作，views.py引用a.py时，就会去执行那段死循环，导致runserver卡住

## 在bat中运行python脚本不显示黑框

不用python，用pythonw

## 修改DB

```
python manage.py makemigrations module_name
python manage.py migrate
```

## Syncdb

Django1.9中，不能使用syncdb，因为有了migration系统，使用makemigrations, migrate，代替之。

## Session

Django的Session依赖于本地的数据库，使用Session前，需要执行makemigrations，migrate，Session才能正常工作。

## Django migrate

migrate时，会去检查数据库里，django\_migration表里执行的操作名，从而决定需要执行哪些migration，因此可以删掉这个表里的操作进行回退。

## 发邮件

* 因为是服务端的问题所以也归在这里好了
* 阿里云服务器上发邮件的时候，由于安全策略，必须使用SSL发邮件
* 代码

  ```python
  def send_163_mail(user, pwd, from_addr, to_addr, subject, content):
    msg = MIMEText(content)
    # 加邮件头
    msg['to'] = to_addr
    msg['from'] = from_addr
    msg['subject'] = subject
    # 发送邮件
    try:
        server = smtplib.SMTP_SSL('smtp.163.com', 465)
        server.starttls()
        server.set_debuglevel(1)
        server.login(user, pwd)  # XXX为用户名，XXXXX为密码
        server.sendmail(msg['from'], msg['to'], msg.as_string())
        server.quit()
        print('发送成功')
    except smtplib.SMTPConnectError:
        print('SMTPConnectError')
  ```

## 多APP外键双向依赖

* makemigrations和migrate时不指定app名，可以自动处理




---

[Next Page](/llms-full.txt/1)

