cs231n

cs231n

it's never too late

Python Keywords

assert

in dictionary

verb

  1. to say that something is certainly true断言
  2. to do something to show that you have power主张
    • assert your authority/right

in python

definition

The assert keyword is used when debugging code.
用于调试代码。
The assert keyword lets you test if a condition in your code returns True, if not, the program will raise an AssertionError.
如果assert后的条件判断为真,则无事发生继续运行;为假则报错AssertionError。

usage

>>> x = 9527
>>> assert x == 9527
>>> assert x == 1551
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError

yield

in dictionary

verb

  1. produce产生
    • The process yields oil for industrial use.
    • Early radio equipment yielded poor sound quality.
    • The experiments yielded some surprising results.
  2. give up放弃
    • Despite renewed pressure to give up the occupied territory, they will not yield.
  3. bend/break弯折
    • His legs began to yield under the sheer weight of his body.
  4. stop让路
    • If you’re going downhill, you need to yield to bikers going uphill.
      • Phrasal verb: yield to something

in python

definition

The yield keyword is used to return a list of values from a function.
用来返回函数值。
Unlike the return keyword which stops further execution of the function, the yield keyword continues to the end of the function.
跟return相比,yield返回值后,函数会继续运行。
When you call a function with yield keyword(s), the return value will be a list of values, one for each yield.
无论yield几次(哪怕只有一次),都会以generator的形式返回。

usage

>>> def test1():
... yield 9527
...
>>> print(type(test1()))
<class 'generator'>
>>> print(test1()) # 如果是return 9527這裡當然是直接打印9527
<generator object test1 at 0x1047d7350>
>>> for i in test1():
... print(i)
...
9527

可以用for遍历,或以迭代器的方式遍历

>>> def test2():
... yield 1
... yield 2
... yield 3
...
>>> x = test2() #a generator object
>>> print(next(x))
1
>>> print(next(x))
2
>>> print(next(x))
3
>>> for i in test2():
... print(i)
...
1
2
3

generator? what?

iterator first

https://docs.python.org/3/glossary.html#term-iterator
iterator is an object representing a stream of data.
迭代器是个数据流对象。
Repeated calls to the iterator’s __next__() method (or passing it to the built-in function next()) return successive items in the stream. When no more data are available a StopIteration exception is raised instead.
迭代器是這樣一種數據流對象:调用它的“__next__()”方法或用python内置函数“next(iterator)”可以得到它这个流的后继数据,没数据了就抛出StopIteration异常。
根据這裡迭代器的定义,我們就可以自造迭代器类。

>>> class MyNumbers:
... def __iter__(self):
... self.a = 1
... return self
... def __next__(self):
... x = self.a
... self.a += 1
... return x
...
>>> myclass = MyNumbers()
>>> myiter = iter(myclass)
>>> print(next(myiter))
1
>>> print(next(myiter))
2
>>> print(next(myiter))
3

这里的iter()也是个内置函数,iter(object)或iter(object, sentinel),接收一個對象返回一個iterator對象。
Without a second argument, object must be a collection object which supports the iterable protocol (the __iter__() method), or it must support the sequence protocol (the __getitem__() method with integer arguments starting at 0).
如果没有sentinel哨兵参数,這裡的object就必须实现__iter__或__getitem__方法。

now generator

https://docs.python.org/3/glossary.html#term-generator
A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.
“generator function”是个包含yield的函数,返回结果是个可以被for循环或python内置函数next()遍历的iterator。

Usually refers to a generator function, but may refer to a generator iterator in some contexts. In cases where the intended meaning isn’t clear, using the full terms avoids ambiguity.
“generator”可能指代函数或迭代器,所以最好说全“生成器函数”或“生成器迭代器”而不只是说“生成器”。

Generator iterator is an object created by a generator function.
Each yield temporarily suspends processing, remembering the location execution state (including local variables and pending try-statements). When the generator iterator resumes, it picks up where it left off (in contrast to functions which start fresh on every invocation).
每个yield都会暂时中止处理,记住位置执行状态(包括局部变量和待处理的try语句)。当生成器迭代器恢复时,它会从上次中断的地方继续执行(与每次调用时都从头开始的函数不同)。

即,调用包含yield的函数会返回一个iterator,每次调用next(iterator)的时候函数就会运行到下一个yield处,python会记录这个yield的位置状态,下次next(iterator)从这里开始运行函数

>>> def test():
... yield
...
>>> print(type(test()))
<class 'generator'>
>>> print(type(test))
<class 'function'>
>>> def test():
... return
...
>>> print(type(test()))
<class 'NoneType'>
>>>

根据上述官方文档,这里的<class 'generator'>实际上是个”generator iterator”。这里的<class 'function'>实际上是个”generator function”。

generator expression

以前常用的下面这种写法也是个generator,()括号里面写一个循环(必须有括号)。

>>> x = (i*i for i in range(10))
>>> print(type(x))
<class 'generator'>
>>> for i in x:
... print(i)
...
0
1
4
9
16
25
36
49
64
81

实际上相当于下面这种写法。
>>> def genEx(num):
... i = 0
... while i < num:
... yield i * i
... i = i + 1
...
>>> x = genEx(10)
>>> for i in x:
... print(i)

在上面这种只需要print结果的情况里,其实yield可以直接替换成print。
但很多时候不是需要打印而是需要取用这个值,如果把(i * i)存入其他容器也可实现相同功能。yield为这种情况提供了方便。

yield写斐波那契数列

The Fibonacci sequence is the series of numbers where each number is the sum of the two preceding numbers. For example, 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, …

>>> def fib(limit): # a generator function
... a, b = 0, 1
... while (b < limit):
... yield b
... a, b = b, a + b
...
>>> x = fib(200) # a generator iterator
>>> for i in x:
... print(i)
...
1
1
2
3
5
8
13
21
34
55
89
144
>>>

numpy dot

矩陣點乘,但向量其實是特殊處理的:
在數學上,點乘需要一個行向量和一個列向量:

但 NumPy 的 1D 陣列已隱含了這個行為。只要兩個 1D 陣列長度相等,它會自動將一個看作「行」,另一個看作「列」,執行點乘運算。因此,不需要手動轉置。

範例

import numpy as np

# 定義兩個向量
v1 = np.array([1, 2, 3]) # 一維向量
v2 = np.array([4, 5, 6]) # 一維向量

# 點乘
dot_product = np.dot(v1, v2)
print("點乘結果:", dot_product) # 結果: 32

如果明確定義行或列向量

如果需要明確區分行向量或列向量,可以使用 2D 陣列來表示:

列向量:v_col = np.array([[1], [2], [3]])(形狀是 3×1)
行向量:v_row = np.array([[1, 2, 3]])(形狀是 1×3)
這時候計算點乘就需要遵守矩陣乘法的規則。

範例:行與列向量操作

v_row = np.array([[1, 2, 3]])  # 行向量
v_col = np.array([[4], [5], [6]]) # 列向量

# 行向量乘列向量 (結果是標量)
result = np.dot(v_row, v_col)
print("行向量 * 列向量的結果:")
print(result) # 結果是 [[32]]

# 列向量乘行向量 (結果是矩陣)
result2 = np.dot(v_col, v_row)
print("列向量 * 行向量的結果:")
print(result2) # 結果是 3x3 矩陣

輸出:
行向量 * 列向量的結果:
[[32]]
列向量 * 行向量的結果:
[[ 4 8 12]
[ 5 10 15]
[ 6 12 18]]

numpy裡想取一行是array[i],想取一列是array[:,j]。
chatgpt輔助寫代碼實在是太好用了,語法這方面絕對的百科全書。

lambda

lambda 是 Python 中用来创建匿名函数(没有名字的函数)的一种表达式。它非常简洁,但功能强大,适合用在一些简单的场景。

基本语法

lambda 参数1, 参数2, ...: 表达式
  • 参数:可以有多个参数,类似于常规函数定义。
  • 表达式:只能包含一个表达式,表达式的计算结果即为返回值。

    示例

  1. 简单的用法
    # 普通函数定义
    def add(x, y):
    return x + y

    # lambda表达式
    add_lambda = lambda x, y: x + y

    print(add(3, 5)) # 输出 8
    print(add_lambda(3, 5)) # 输出 8
  2. 作为内联函数
    常用在需要短小函数的场景,例如 map()、filter()、sorted() 等。
    • 跟map()配合
      nums = [1, 2, 3, 4]
      squared = map(lambda x: x ** 2, nums)
      print(list(squared)) # 输出 [1, 4, 9, 16]
    • 与filter()配合
      nums = [1, 2, 3, 4, 5]
      even = filter(lambda x: x % 2 == 0, nums)
      print(list(even)) # 输出 [2, 4]
    • 跟sorted()配合
      words = ["apple", "banana", "cherry"]
      sorted_words = sorted(words, key=lambda x: len(x))
      print(sorted_words) # 输出 ['apple', 'cherry', 'banana']
  3. 作为函数参数
    可以直接将 lambda 函数传递给其他函数作为参数。
    def apply_function(func, value):
    return func(value)

    result = apply_function(lambda x: x * 2, 10)
    print(result) # 输出 20

    优缺点

    优点:
  • 简洁,适合定义简单逻辑。可以轻松传递小函数。

缺点:

  • 只能写单一表达式,功能有限。
  • 可读性较差,复杂逻辑建议使用 def 定义常规函数。

使用建议:

  • 当逻辑非常简单且不会重复使用时,用 lambda。
  • 复杂逻辑或需要调试时,使用 def 明确定义函数。

广播boardcast

A.shape B.shape Resulting Shape 说明
(3, 4) (3, 4) (3, 4) 相同形状,逐元素操作
(3, 4) (1, 4) (3, 4) B 的第 0 维度广播为 3,变成(3, 4)
(3, 4) (4,) (3, 4) B 增加维度后广播为 (1, 4),再把第 0 维广播为(3, 4)
(3, 1, 4) (1, 2, 4) (3, 2, 4) 高维度逐维广播
(3, 4) () (3, 4) 标量广播为 (3, 4)
(3, 4) (2, 4) Error 形状不兼容,无法广播

广播其实就是把两个矩阵补成一样大小。

注意⚠️:

  • 广播的结果只是虚拟扩展,并不真的复制数据,因此它的内存和计算效率都很高。
  • 如果不确定广播的结果,可以使用 np.broadcast_shapes 或 np.broadcast_to 来检查或预览。

    # 检查广播后形状
    np.broadcast_shapes((3, 4), (1, 4)) # 输出 (3, 4)
  • 标量:

    A = np.ones((3, 4))
    B = 2 # 标量
    C = A + B # B 会广播为 (3, 4) 进行相加
  • 高维度例如(3, 1, 4)和(1, 2, 4)
    • 第 0 维:3 和 1 -> 广播为 3。
    • 第 1 维:1 和 2 -> 广播为 2。
    • 第 2 维:4 和 4 -> 保持不变。
    • 最终广播结果为 (3, 2, 4)
      A = np.ones((3, 1, 4))
      B = np.ones((1, 2, 4))
      C = A + B # 广播为 (3, 2, 4)

數學

矩阵计算加速

反直觉的简化公式。
这些公式的反直觉点在于:

  • 避免直接操作整个矩阵或高维数据,通过预处理(如点积、模长)简化计算。
  • 利用代数性质减少重复计算,如平方展开、trace 操作。
  • 通过广播机制和维度扩展,实现高效并行计算。

1. L2距离矩阵

L2 中的 “L” 是 “norm” 的缩写来源,表示范数(norm)的符号约定。“L” 最早来源于法语数学文献中用于 Lebesgue 空间($L^p$-spaces)的符号,它现在广泛用于表示函数和向量范数。

问题:给定两个矩阵 $A \in \mathbb{R}^{m \times n}$ 和 $B \in \mathbb{R}^{p \times n}$,计算两组向量之间的欧几里得距离矩阵 $D$,形状为 $(m, p)$。
公式:

简化:

解释

  • 利用 $A \cdot B^T$ 点积高效计算向量间的关系。

KNN计算(500,3072)和(5000,3072)得到(500,5000)的两两图片距离矩阵时,有两种做法:

第一种:

I. 直接通过广播计算一个 $(500, 3072) - (5000, 3072)$ 得到 $(500, 5000)$ 的矩阵?

我们需要计算一个矩阵 $\mathbf{C} \in \mathbb{R}^{500 \times 5000}$,其中每个元素为:

或展开为:


直接广播计算的步骤

  1. 输入矩阵形状

    • $\mathbf{A} \in \mathbb{R}^{500 \times 3072}$
    • $\mathbf{B} \in \mathbb{R}^{5000 \times 3072}$
  2. 扩展维度

    • 将 $\mathbf{A}$ 扩展为 $(500, 1, 3072)$:
    • 将 $\mathbf{B}$ 扩展为 $(1, 5000, 3072)$:
  3. 广播相减

    • 利用广播机制直接相减,结果是形状为 $(500, 5000, 3072)$ 的 3D 矩阵:
  4. 计算平方差

    • 对最后一个维度(即 3072)计算平方和,得到形状为 $(500, 5000)$ 的矩阵:

完整代码实现

import numpy as np

# 示例输入
A = np.random.rand(500, 3072) # (500, 3072)
B = np.random.rand(5000, 3072) # (5000, 3072)

# 扩展维度
A_expanded = A[:, np.newaxis, :] # (500, 1, 3072)
B_expanded = B[np.newaxis, :, :] # (1, 5000, 3072)

# 广播减法并计算平方差
C = np.sum((A_expanded - B_expanded) ** 2, axis=2) # (500, 5000)

print(C.shape) # 输出: (500, 5000)

问题

“直接广播可能导致内存不足”的原因是 广播机制会在内存中创建一个扩展后的高维临时数组

对于形状为 $ (500, 3072) $ 的矩阵 $ A $ 和 $ (5000, 3072) $ 的矩阵 $ B $:

  1. 扩展后它们会形成 $ (500, 1, 3072) $ 和 $ (1, 5000, 3072) $。
  2. 相减后生成一个 $ (500, 5000, 3072) $ 的 3D 数组

这个 3D 数组需要存储 $ 500 \times 5000 \times 3072 $ 个浮点数,占用约 76 GB(若每个浮点数占 8 字节)。
对于大规模数据,内存无法承受,因此直接广播操作会报错或导致性能问题。

使用范数分解公式避免构造高维中间数组,可以极大节省内存。


第二种:

II. 优化方法:使用范数分解公式

由于直接广播会创建一个巨大的 $(500, 5000, 3072)$ 的临时数组,可能导致内存不足,我们可以利用范数分解公式优化计算。

公式展开为:


优化后的步骤

  1. 预先计算每行向量的平方和:

    • 对于 $\mathbf{A}$,计算 $|\mathbf{A}[i, :]|^2$:
    • 对于 $\mathbf{B}$,计算 $|\mathbf{B}[j, :]|^2$:
  2. 计算点积:

  3. 合并结果:


优化代码实现

# 计算每行向量的平方和
A_norm = np.sum(A ** 2, axis=1).reshape(-1, 1) # (500, 1)
B_norm = np.sum(B ** 2, axis=1).reshape(1, -1) # (1, 5000)

# 计算点积
dot_product = A @ B.T # (500, 5000)

# 利用范数分解公式
C = A_norm + B_norm - 2 * dot_product

print(C.shape) # 输出: (500, 5000)

两种方法对比

方法 内存占用 计算速度 适用场景
直接广播相减 高(需要创建 3D 数组) 简单实现 小规模矩阵
范数分解公式 低(避免创建 3D 数组) 更高效 大规模矩阵

2. 余弦相似度矩阵

问题:给定矩阵 $A$ 和 $B$,计算两组向量的余弦相似度矩阵:

简化:

解释

  • 先计算点积 $A \cdot B^T$。
  • 再将每行的模长预计算为一个列向量,最后进行广播除法。

3. 矩阵 Frobenius 范数的平方

Frobenius 范数:

如果需要计算两个矩阵 $A$ 和 $B$ 之间的差的 Frobenius 范数:

解释

  • 通过展开 $| A - B |_F^2$,简化计算,避免构造 $A - B$。

4. 交叉熵损失的高效计算

交叉熵损失公式:

如果 $y$ 是 one-hot 编码的标签,交叉熵可以简化为直接取预测的正确类别的概率对数:

解释

  • 不需要计算 $y$ 和 $\hat{y}$ 的逐元素乘积,只需取索引值。

5. 矩阵的平方和(双循环的优化)

直接计算矩阵 $A \in \mathbb{R}^{m \times n}$ 中每对行向量的平方和:

可以化简为:

解释

  • 避免显式计算所有对的组合,通过提前计算模长和点积直接构造。

6. 矩阵乘法优化(维度分解)

如果需要计算 $C = A^T A$,其中 $A \in \mathbb{R}^{m \times n}$,可通过分块减少乘法次数:

  • 普通算法时间复杂度为 $O(m^2 n)$。
  • 利用分块或对称性质,可以优化为 $O(m^2)$。

矩陣求導,要轉置

矩阵C=AB那C对A的导数是B的转置,C对B的导数是A的转置。

假设 $C = A \cdot B$,则矩阵求导结果如下:

  1. $C$ 对 $A$ 的导数

    因为 $A$ 的每个元素对 $C$ 的每个元素的贡献是通过 $B$ 的列影响的。

  2. $C$ 对 $B$ 的导数

    因为 $B$ 的每个元素对 $C$ 的每个元素的贡献是通过 $A$ 的行影响的。

例子验证

假设:

计算 $C = A \cdot B$:

  1. 对 $A$ 求导(结果为 $B^T$):

  2. 对 $B$ 求导(结果为 $A^T$):(在矩陣乘法裡,A的1和2,只會受到B的5和7影響;對A的3和4來說只有B的6和8會影響結果,所以正好轉置過來)

商的求导法则

如果函数为 $\frac{u(x)}{v(x)}$,其导数公式是:

例子 1: $ \frac{x}{x+1} $ 的导数

设 $u = x$,$v = x+1$:

  • $u’ = 1$,$v’ = 1$。

代入公式:

例子 2: $ \frac{x^2}{x+2} $ 的导数

设 $u = x^2$,$v = x+2$:

  • $u’ = 2x$,$v’ = 1$。

代入公式:

化简:

Notes

https://cs231n.stanford.edu/slides/2017/

https://cs231n.stanford.edu/slides/2024/ 今年的也大差不差,lecture6都是講Batch Normalization,甚至很多圖都一樣。

其實用slides裡的圖片這篇筆記的圖片會很小,但懶得挨個扒,截圖堆成超大圖片屎山好了。

Lecture 1

From PASCAL Visual Object Challenge(20 object categories) to ImageNet (Deng, Dong, Socher, Li, Li, & Fei-Fei 2009). 李飞飞他们花了3年, 从互联网上下billions of images,然后用WorldNet这个词典把图像组织起来(which has tens of thousands of classes),然后用Amazon Mechanical Turk平台排序、清洗、标注图像。然后开放了Large Scale Visual Recognition Challenge,“输入是image,输出是label,用前五个labels是否正确来作为指标”作为benchmark评价进展。2012年CNN(from 1998 LeCun to recognize handwriting digits and addresses for the post office)把error rate从26降到了16,从此一发不可收。

WordNet是一个由普林斯顿大学认识科学实验室在心理学教授乔治·A·米勒的指导下建立和维护的英语字典。开发工作从1985年开始,从此以后该项目接受了超过300万美元的资助(主要来源于对机器翻译有兴趣的政府机构)。
WordNet根据词条的意义将它们分组,每一个具有相同意义的字条组称为一个synset(同义词集合)。WordNet为每一个synset提供了简短,概要的定义,并记录不同synset之间的语义关系。

http://wordnetweb.princeton.edu/perl/webwn

world net
视觉处理信息的阶段,对 解构vision数据是很有启发的

The primary focus of this class is Image Classification (A core task in Computer Vision ), the setup is that your algorithm looks at an image, and then picks from among some fixed set of categories to classify that image. This might seem like somewhat of restrictive or artificial setup, but it’s actually quite general. This problem can be applied in many different settings both in industry and academia and many different places. But in this course we are also gonna talk about several other visual recognition problems that build upon many of the tools that we develop for the purpose of image classification, such as, object detection and image captioning.
这也是我后来一直存在的疑问,当时没仔细看错过了这个关键问题。AI的最终目标比如图像分类这个任务从来不是能识别一切图像,因为视觉信息这个二维量本身就是局限的,通用AI(AGI)的目标是能做出人类所有智能行为的AI,所以超越人类水平已经足够了。
这个技术的价值是能应用到所有场景里,比如在饭店判断菜品的任务里,能超过人的准确率就已经可以接受了。
回顾在商汤做的visual grounding任务,输入“这个垃圾桶是满的”,视觉定位模型找到图像中的垃圾桶并输出“是”“否”满。其实完全能分割成目标检测+图像分类任务,只是因为对这些基础技术太不熟练导致思路模糊,只能跟着团队用OFA人云亦云亦步亦趋。随便搭一个两步的模型对从事这项工作的人来说应该是最基础的事,基础不牢地动山摇。(不过当时团队是侧重于做一个能快速适应新场景的模型,经过几十张甚至0张图片的训练就能检测新任务比如“电动车有没有超载”,但一样也该入职时迅速搭一个两步模型上手任务。)
(关于Justin Johnson这句话的语法,from among的among强调范围,是对的;some set的some表示不确定,set是对的,sets大概也可以)
另外,AI的飞速发展一直让过去的我焦虑,这种焦虑让我不愿意学习当前的sota技术,因为未来某一天就突然会被革新。可现在回头看,雖然沒有新的課堂錄像,但通過課程官網可以看出stanford的cs231n内容几乎从未改变,这足以说明这些老玩意还是新积木的基石。

Lecture 2

Numpy efficiently implements vectorized operations. It’s should be practiced a lot in assignment 1.
就识别猫这个问题来说,通过找到所有edges然后根据猫的特征逐一匹配(耳朵眼睛尾巴,大致间距和位置)大概也可以做到,而且这样的代码完全可解释。而现在neural nets的data driven,根本不可能讲清楚哪个部分识别了哪个特征,能把握的只有梯度变化规律。

knn也是data-driven的,只不过不从training data里获得知识,只是简单记住train set,来一个新数据就跟所有train set数据比较,跟哪个类别的某k个图最相似那就属于哪个类别。如何计算两个图(向量)的距离(Distance Metric)四年前已经总结过了,可以用L1曼哈顿也可以用L2欧式,对于新问题用什么距离试试哪个好就行了。训练O(1),推理O(N),正好反了。
只要记住KNN的full form——K Nearest Neighbors就能想起来这个dumb算法了。

metric不是metrix,metric is a system for measuring something是指标的意思;metrix才是矩阵。

模型在train set上训练完后,在validation set上找最优hyperparameters(不靠训练得到的都算,包括距离计算L1还是L2),最后临论文deadline,只在test set上跑一次only once,得到的数就是最终写在报告、论文里的数据,以保证没有作弊。严格分离验证集和测试集对确保做研究没有不诚实、不公平很重要。

validation set for tuning hyperparameters

另外idea4是交叉验证,神经网络实验的验证集validation set一般不用cross validation,在不同分割的训练集上训练好几遍开销太大了,只适用于较小的数据集。

linear classification

linear classification是最簡單的parametric model(即上面的f(x,W)也有時weights/paramters會寫成theta θ而不是W),用Xtrain训练出W参数,inference过程就可以抛弃训练数据只用x和W计算结果。
最简单的x和W组合方式就是二者相乘。
其中對bias的簡單理解是,数据集里的猫比狗多,那猫类别的bias值可能就比狗高。可以通过增加一个常数项1特征,把b算作W的一部分。
模型做的通常是在给定幾個类别里打分的工作,而不是凭空产生一个相关类别,人脑大概也是这么工作的——看到一张猫的图片,在语言系统的无数个词条里关联到“猫”。
在很多模型裡linear classification就作為最後一層,承擔把所有的計算匯總到一起給上面10個類別打分的工作。
可以把linear classification的Wx这个計算过程看成是template matching,這在我之前的筆記也寫過一次,weights的每一行可以看作是一個被strech成一維的模板,圖片跟模板越像的話,在這一類的分數就會越高。
這是因為這兩個vector的點積就是點積相似度,點積越大可以認為就越相似,因為圖片的每個像素都是個xyz-axis都在0~255內的向量,所以可以把這個點積相似度放在r=255的球裡來想,或許有幫助。

unit sphere in 3d coordinate

template matching viewpoint linear classification

在neural network等其他複雜模型裡,不會只限制模型每個類別只學一個模板,可能因此才變得更準確。
这种可视化方式可以学来,用在神经网络里可视化一下参数。不过我之前也可視化過比如capsule,能看出參數對應圖像的一些特徵,但不像linear这么明显。

站在比數據高一點兒維度的角度看問題 單純linear decision boundaries顯然解決不了的問題

之後的內容可以理解成:

  • Loss function (quantifying what it means to have a “good” W)
  • Optimization (start with random W and find a W that minimizes the loss)
  • ConvNets! (tweak the functional form of f)

Problems:

  • KNN:(never used)
    1. Very slow at test time.
    2. Distance metrics on pixels are not informative.
    3. Curse of dimensionality.

knn前幾名label出現次數同樣多怎麼辦?怎麼決定分到哪個類?

Lecture 3

multiclass svm loss

想一些相關問題有助於理解:

  1. 選margin為1還是其他數字無所謂,只是一個arbitrary choice;
  2. 車的分數稍微改變一下不影響loss仍然為0;
  3. 就像hinge曲線一樣,loss最小值為1,最大值為infinity;
  4. 讓W為全0,即s≈0,那麼loss應該是num of classes - 1,這個可以用於debug
  5. 讓j可以=y_i的話,結果是loss-1,其實都一樣但是讓loss最小值為0而不是“1”比較make sense;
  6. 計算loss不用sum,用mean也沒區別,因為不關心loss絕對值;
  7. 如果用上式loss的平方,可以代表我們更不能容忍大的錯誤,因為錯誤較大的類別會直接指數倍放大;
  8. 如果loss=0,這時的W是唯一的嗎?當然不是,2W的loss也是0。
def L_i_vectorized(x, y, W) :
scores = W.dot(x)
margins = np.maximum(0, scores - scores[y] + 1)
margins[y] = 0
loss_i = np. sum(margins)
return loss_i

相比想矩陣,用一條數據vector的例子分析example code比較簡單。

Occam’s Razor: “Among competing hypotheses, the simplest is the best” William of Ockham, 1285 - 1347 發現regularization是“W”的函數,是讓W越簡單越好,比如讓高次冪特徵係數為0

之前學cs231n就是卡在batch normalization附近了,這一曠就是四年。總之還是沒理解清防止過擬合加的正則項到底是個什麼。需要找點最簡單的例子,具體分析。

想一些相關問題有助於理解:

  1. min:-log(1)=0 max:-log(0)=infinity,但這兩個值都是取不到的,因為如果softmax函數=1,錯誤類別都需要是負無窮才能e^sj為0,如果softmax=0,正確類別分數需要是負無窮e^sy才能為0;
  2. 如果W=0,s全≈0,L_i就=-log(1/C(num of classes))=log(C);這個也可以用來debug,跟上面SVM一樣!
  3. 對比一下再來想問題3:

如果是右圖這種情況,修改一點已經分類正確的分數,svm loss仍然是0不會變,而softmax會改變,因為svm的目標是只要正確分類的score跟錯誤分類差距大過margin就可以了,而softmax會盡可能地把錯誤分數拉向-inf,把正確分數拉向inf。

  • svm和softmax都只是為了最優化f(x)=Wx+b的損失函數而已,他們的函數形狀不重要,f(x)=Wx+B仍是線性函數

關於optimizer他的解釋很有意思,解析解analytic solution像是你在山頂(高度就是loss值)magically teleport到了最低點,但當prediction function f,loss function包括regularizer都很複雜就只能尋求數值解(iterative methods that start some solution and gradually imporve by time,梯度下降,即求導按負導數方向找低點,高中數學,pretty easy actually)。

  1. Strategy #1: A first very bad idea solution: Random search
  2. Strategy #2: Follow the slope

    gradient偏微分
    1. 一種計算gradient的想法是——,但是這方法太蠢太慢了,Numerical gradient: approximate :(, slow :(, easy to write :)
    2. Analytic gradient: exact :), fast :), error-prone :(,直接用微積分得到dW迭代loss

In practice: Always use analytic gradient, but check implementation with numerical gradient. This is called a gradient check.(finding derivative is error-prone求導是容易出錯的)


chatgpt給的一個計算梯度的例子

示例函数

假设我们有一个二维函数:

这个函数是一个标准的二次函数,描述的是一个圆形等高线的抛物面。我们的目标是计算其梯度,并理解梯度的含义。

1. 计算梯度

首先,梯度是指函数在不同方向上变化的速率,它由偏导数组成。在二维情况下,梯度可以写成一个向量:

求偏导数:

计算 $f(x, y) = x^2 + y^2$ 对 $x$ 和 $y$ 的偏导数:

  1. 对 $x$ 的偏导数:

  2. 对 $y$ 的偏导数:

因此,梯度 $\nabla f(x, y)$ 为:

2. 梯度的意义

在几何上,梯度是函数在该点变化最快的方向。具体来说,梯度的方向就是函数值增长最快的方向,而梯度的负方向(即 $-\nabla f$)则是函数值下降最快的方向。

示例:在点 $(1, 1)$ 处的梯度

假设我们在点 $(1, 1)$ 计算梯度:

这表明,在点 $(1, 1)$ 处,函数 $f(x, y)$ 增长最快的方向是向量 $(2, 2)$ 的方向。这个方向对应于45度方向,即沿着 $x$ 轴和 $y$ 轴方向各增加1的方向。

3. 如何找到梯度最大的方向

梯度本身就表示函数变化最快的方向。它的方向指向函数值增长最快的方向,而它的大小(即梯度的模)则表示变化的速率。

在我们的例子中,点 $(1, 1)$ 的梯度是 $(2, 2)$,它的模为:

总结:

  1. 在任意点 $(x, y)$ 处,函数 $f(x, y) = x^2 + y^2$ 增长最快的方向是沿着梯度方向 $(2x, 2y)$。
  2. 梯度的大小表示函数在该方向上增长的速率。
  3. 如果我们想沿着函数值降低的方向移动,那么应当沿着梯度的负方向移动。

所以gradient descent的代碼也很簡單:

# **Vanilla Gradient Descent**
while True:
weights_grad = evaluate_gradient(loss_fun, data, weights)
weights += - step_size * weights_grad # perform parameter update

其中loss_fun就可以想像成上面的f(x,y,z)=x^2+y^2+z^2,data就是x,y,weights就是z。

learning rate(step size)是最先要確定的超參數,model size和regularization strength都是其次的。

fancier update rules than gradient descent:
momentum, adam,就是不同的利用梯度信息的方式。

比如稍微改進vanilla為stochastic(SGD隨機梯度下降是隨機選一個,這裡是變種Mini-batch Gradient Descent):

N可以是幾百萬,更新一次W算下個loss要等半輩子,主要是不一定一次到位,可能0.5N就到位了,或者要1.5個N才到位,所以用minibatch
features color histogram edge histogram like codebook(OFA啥的也有這個概念來著?) 現在跟以前先提取特徵的兩步法也沒什麼區別,包括cnn,包括transformer,只是變成隱性地提取這些特徵,做成end to end端到端的黑箱

Lecture 4 計算圖(local gradient and chain rule)

computational graph chain rule 每個node只需要關注它周圍的nodes
把幾個基本二元運算合成一個計算模塊,比如sigmoid,和它的導數 這裡算導數用的不是x,是f(x),由此可知backprop時node的forward pass的前後值都可能用到

任何時候比如做assignments時,如果求梯度遇到困難,畫一個計算圖用chain rule就都可以迎刃而解。

patterns pattern

$\frac{df}{dx} = \sum_i \frac{df}{dq_i} \cdot \frac{dq_i}{dx}.$

懶得畫圖了,如果x給多個q當了自變量,多個q又給同一個f當了自變量,那就得把導數加起來,隨便想個computational graph的例子就不抽象了。



Jacobian矩陣很簡單,就是每個輸出對應每個輸入的偏導——

Jacobian 矩阵计算示例

Jacobian 矩阵在多元函数中用于描述一个向量值函数的偏导数。假设我们有一个向量值函数 $ \mathbf{F}(\mathbf{x}) $,其中 $ \mathbf{F} : \mathbb{R}^n \to \mathbb{R}^m $,输入向量 $ \mathbf{x} \in \mathbb{R}^n $,输出向量 $ \mathbf{F}(\mathbf{x}) \in \mathbb{R}^m $。

对于每一个 $ x_i $ 和 $ F_j $,Jacobian 矩阵的元素是 $ \frac{\partial F_j}{\partial x_i} $。整个矩阵的形式是:

示例

假设我们有函数(這裡的x和y當然可以改成x1和x2):

我们希望计算 $ F(x, y) = \begin{bmatrix} F_1(x, y) \\ F_2(x, y) \end{bmatrix} $ 的 Jacobian 矩阵。

第一步:计算偏导数

  1. 对 $ F_1(x, y) = x^2 + y^2 $:

    • $ \frac{\partial F_1}{\partial x} = 2x $
    • $ \frac{\partial F_1}{\partial y} = 2y $
  2. 对 $ F_2(x, y) = e^x + \sin(y) $:

    • $ \frac{\partial F_2}{\partial x} = e^x $
    • $ \frac{\partial F_2}{\partial y} = \cos(y) $

第二步:构造 Jacobian 矩阵

将上述结果代入矩阵中:

第三步:在特定点的 Jacobian 矩阵

如果我们希望在特定点,比如 $ (x, y) = (1, 0) $ 处求得 Jacobian 矩阵,则代入此点的值:

结果

因此,函数 $ F(x, y) $ 在点 $ (1, 0) $ 处的 Jacobian 矩阵为:

这个矩阵描述了函数 $ F(x, y) $ 在点 $ (1, 0) $ 处的变化率和方向。


because each element of your gradient is quntifying how much element is contributing/affecting your final output 下面偽代碼的forward和backward

關於這個1(indicator function)
这个符号 $1_{k=i}$ 表示一个指示函数(或称为指示符号),其作用是判断条件 $k = i$ 是否成立:

  • 当 $k = i$ 时,$1_{k=i} = 1$
  • 当 $k \neq i$ 时,$1_{k=i} = 0$

因此,如果写成 $1_{k=i} x_j$,则表示仅当 $k = i$ 时,值为 $x_j$;如果 $k \neq i$,则值为 0。

所以就可以簡單寫一下計算圖的rough pseudo code:

class ComputationalGraph(object):
#...
def forward(inputs) :
# 1. [pass inputs to input gates...]
# 2. forward the computational graph:
for gate in self.graph.nodes_topologically_sorted():
gate.forward()
return loss # the final gate in the graph outputs the loss
def backward():
for gate in reversed(self.graph.nodes_topologically_sorted()):
gate.backward() # little piece of backprop (chain rule applied)
return inputs_gradients

class MultiplyGate(object):
def forward (x,y) :
z = x*y
self.x = x # must keep these around!
self.y = y
return z
def backward(dz):
dx = self.y * dz # [dz/dx * dL/dz]
dy = self.x * dz # [dz/dy * dL/dz]
return [dx, dy]

caffe layers的代碼在這節課之後就沒變過。比如下圖這個sigmoid_layer

topdiff是上游梯度 寫之前先畫computational graph
if you just stack linear layers on top of each other, they’re just gonna collapse to a single linear function (w/o max here) 這裡其實我一直有個疑問,如果X裡有x1 x1x2 x1x2x3這種高階特徵,那豈不是不再線性了?這個問題其實有點蠢,因為線性關係是指x1和f(x1)、x1x2和f(x1x2)、x1x2x3和f(x1x2x3)之間是線性/直線,而不是x1x2/x1x2x3跟f(x1) 用之前template matching的觀點可以認為W1學到了更多templates(維度變大了),而W2是模板的weights,h是tempaltes得分,h之前會做non-linear(這裡是max),最後得分就是templates的weighted sum 3層3-layer就是再套一次f = Wз max(0, W2 max(0, W1x)),代碼在下面

上面這個3-layer的代碼就像這樣:

# **forward-pass of a 3-layer neural network:**
f = lambda x: 1.0/(1.0 + np.exp(-x)) # activation function (use sigmoid)
x = np. random. randn(3, 1) # random input vector of three numbers (3x1)
h1 = f(np.dot(Wl, x) + bl) # calculate first hidden layer activations (4x1)
h2 = f(np.dot(W2, h1) + b2) # calculate second hidden layer activations (4x1)
out = np.dot(W3, h2) + b3 # output neuron (1x1)

關於polynomial regression,

多项式回归模型本身是非线性的,因为它涉及到自变量的高次方,能够拟合非线性数据关系。然而,多项式回归模型仍然可以被视为线性模型的一种,原因在于模型参数的估计和模型预测都可以通过线性回归的方法实现。

具体来说,在多项式回归模型中,自变量的高次方可以视为新的特征,将其添加到原始特征中,从而将非线性问题转化为线性问题。然后,使用线性回归模型估计模型参数(即新特征的系数),并使用线性回归模型进行预测。

因此,多项式回归模型被称为线性模型的扩展,它可以用于拟合非线性数据关系,并且可以使用线性回归的方法进行参数估计和预测。

Serena說relu是最接近實際神經的activation function

Lecture 5

1986 backprop 1998 first example backprop/gradient based cnn 2012 Alex Krizhevsky
實際寫代碼的時候是strech out這個filter和image(或activation map),因為dot product是一樣的結果

each filter is looking for a specific type of template or concept of the input volume.

有個問題問得很好,what’s the intuition for increasing the depth each time. 每一層讓filter更多activation map更深(3、6、…)是實踐裡大家發現的更好的設定而已,cnn有很多design choice,比如filter size啥的。

filter也可以說是receptive field

信號處理的卷積計算signal processing convolution

假设两个简单的离散信号 $f(t)$ 和 $g(t)$,它们的取值如下:

步骤 1:反转信号 $g(t)$

首先将 $g(t)$ 反转,得到 $g(-t)$:

步骤 2:滑动并计算乘积

接下来,我们将反转后的信号 $g(-t)$ 与信号 $f(t)$ 进行卷积。卷积的过程是:

当 t = 0 时:

当 t = 1 时:

当 t = 2 时:

当 t = 3 时:

当 t = 4 时:

最终卷积结果

因此,卷积的结果 $(f * g)(t)$ 为:

其中卷积结果的最大时间点是由 $f(t)$ 和 $g(t)$ 的最远时间点组合而成。具体来说,卷积的结果最大会到达:

例如,假设 $f(t)$ 的最后一个有效时间点是 $t = 2$,而 $g(t)$ 的最后一个有效时间点是 $t = 1$,则:

这样,卷积的最大时间点 $t_{\text{max}}$ 为 3。

  • 跟cnn的卷積一樣的是:都要想像成滑動操作,信號卷積是二維的兩條線滑動;
  • 不一樣的是:cnn不需要反轉,而(f *g)(t)裡後面這個函數要先在時間軸上反轉。

卷積、互相關和自相關的圖示比較。假定f高度是1.0,在5個不同點上的值,用在每個點下面的陰影面積來指示。


這節課原來後面講了如何可視化模型學到的特徵。(basically each element of these grids is showing what in the input would look like that basically maximizes the activation of the neuron. So in a sense, what is the neuron looking for?)


不這樣設置stride formula,但其實豎直方向上也可以有stride,ouput size又是另外的formula padding(zero,mirror,and so on)能保持activation size,input和ouput一樣size,但是stride>1的作用呢?是避免边缘信息丢失
example2 1x1 conv filter是可行的 torch的一個conv

stride越大downsampling就越多,參數越少模型越小,包括所有參數一直到FC連接的值也會變少。(tradeoff)

pooling,不影響depth stride設大就是downsampling,跟pooling效果是一樣的

右圖為什麼downsample太快不好?那為什麼還要pooling?現在不缺算力追求越多參數越好是不是不再需要太多downsample了?

這個問題可以再問chatgpt,回答得挺好,1.前幾層下採樣太快會丟失很多信息;2.pooling減少計算量即使現在不缺算力還是需要,否則計算量迅速膨脹徒增開銷,用結果導向的思路來說不影響效果的情況下downsampling總是好的;3.pooling或者說設置較大stride、現代架構其他捕捉全局關係的方式,除了减少特征图尺寸減少參數量外,也能抑制過擬合。是一種regularization。


pooling也有filter size、stride,但這裡有overlap了呀,3x3的stride2,不是說沒有overlap嗎?

Pooling 层的 stride 是否一般设置为没有 overlap?

在卷积神经网络(CNN)中,Pooling 层的 stride 是否设置为没有 overlap(即步长等于池化窗口大小)通常取决于网络设计的需求。一般来说,Pooling 层的 stride 通常设置为没有 overlap,即步长与池化窗口大小相同。具体解释如下:

  1. 没有重叠(No Overlap)

    • Stride = Pooling窗口大小:这是最常见的设置,意味着池化操作的步长与池化窗口的大小相同,从而池化窗口之间没有重叠。
    • 例如,在 2x2 的池化窗口下,stride = 2 会导致每次池化窗口滑动两个像素,池化窗口之间没有任何重叠区域。

    优点

    • 计算效率更高:没有重叠的池化可以减少特征图的尺寸,降低计算复杂度。
    • 避免冗余计算:每次池化操作处理的区域都是唯一的,因此没有重复计算的部分。
  2. 有重叠(Overlap)

    • Stride < Pooling窗口大小:在某些情况下,池化窗口之间会有重叠。例如,使用 2x2 的池化窗口和 stride = 1,池化窗口会覆盖有重叠的区域。

    优点

    • 保留更多细节:有重叠的池化可能会保留更多的特征,尤其是在需要更精细特征提取时。

    缺点

    • 计算量更大:重叠池化会增加计算开销,导致计算效率较低。
  3. 结论

    • 在大多数情况下,Pooling 层的 stride 被设置为没有重叠,尤其是在经典的卷积神经网络(如 LeNet、VGG)中。这样可以高效地减少特征图的尺寸,并降低计算量。
    • 在某些特殊任务中(例如,需要更细致特征提取的任务),也可以选择有重叠的池化。

為什麼max pooling比average pooling更commonly used?

  1. 实践效果更好。在大量的实际实验中,使用 Max Pooling 的 CNN 通常比使用 Average Pooling 的 CNN 效果更好,尤其是在分类和目标检测任务中。这也是 Max Pooling 被更广泛应用的原因之一。
  2. 更好地保留关键信息。Max Pooling 选择每个池化窗口中的最大值,从而保留了特征图中的最显著特征。这种方式在提取边缘、纹理等重要特征时非常有效。相比之下,Average Pooling 是取平均值,可能会平滑掉重要的边缘信息,导致信息丢失。
  3. 具有更强的平移不变性。Max Pooling 能更好地处理位置的微小变化(平移不变性),因为它仅保留每个窗口的最大值,对其他不显著的特征不敏感。这在处理图像时有利于使网络对物体位置的微小变动更加鲁棒。
每一個小圖都是activation maps,最後pooling層只剩一堆像素,因為已經是最高級最抽象的特徵(like 物体的局部区域、图案、场景、动作、语义信息

訓練例子:https://cs.stanford.edu/people/karpathy/convnetjs/demo/cifar10.html

通常只有第一層activation maps能被解釋,越高層越難解釋。

  • There is a trend towards smaller filters and deeper architectures
  • There is a trend towards getting rid of POOL/FC layers (just CONV)
  • Typical architectures look like:
    [(CONV-RELU)*N-POOL?]*M-(FC-RELU)*K,SOFTMAX
    where N is usually up to ~5, M is large, 0 <= K <= 2.
  • but recent(2017) advances such as ResNet/GoogLeNet challenge this paradigm

Lecture 6

  • Activation Functions(non-linearity)
  • Data Preprocessing
  • Weight Initialization
  • Batch Normalization
  • Babysitting the Learning Process
  • Hyperparameter Optimization

Activation Functions(non-linearity) 为什么输入要正负样本均衡?

gradient on w這個說法指的是$\frac{\partial L}{\partial w}\ or\ \nabla_w L$。(或者df/dw)

1. sigmoid - 3 problems: 1. Saturated neurons "kill" the gradients:(注意這裡說的是激活函數這個node的gradient,只有一個自變量) - x = -10, sigmoid(x) = 0, sigmoid'(x) = (1-0)0 = 0 - x = 0, sigmoid(x) = 1/2, sigmoid'(x) = (1-1/2)1/2 = 1/4 - x = 10, sigmoid(x) = 1, sigmoid'(x) = (1-1)1 = 0
backprop反向傳播回來的value可以光看曲線不用公式,平緩的地方導數為0(forward pass就看函數曲線本身,backward就看曲線變化,高中數學),(sigmoid gate, a node in computational graph)
2. Sigmoid outputs are not zero-centered: 如下图(這裡說的是激活函數之前score function node,有W和X倆自變量) - 如果一个神经元的输入X全是正的,gradients on w即dL/dw = dL/df \* df/dw(其中df/dw=x)就全是正的或者负的,就会导致graident只向全正和全负更新,最优化W的过程就会很低效。 - 这也是为什么x最好positive和negative均衡,因为如果输入数据X本身就是全正的,那不需要sigmoid激活函数压缩就已经会导致图里的zig zag path问题了。 - 而sigmoid激活函数就是这样一个会把输入全部拉到0~1的玩意。
講得太好了這裡
3. exp() is a bit computationally expensive 2. tanh(x)解决了zero-centered的问题,但还是存在梯度消失问题,而且计算开销比sigmoid更大因为有更多exp运算。$\tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}$ 3. ReLU(Rectified Linear Unit), f(x) = max(0, x) - pros:(上面的饱和函数、计算开销問題都解決了) - Does not saturate (in region). - Very computationally efficient - Converges much faster than sigmoid/tanh in practice (e.g. 6x faster) 收斂更快 - Actually more biologically plausible than sigmoid - 2012 Alex Krizhevsky第一次使用 - cons: - zero-centered問題還是存在 - dead relu - Dead ReLU 问题是指在使用 ReLU (Rectified Linear Unit) 激活函数时,某些神经元可能在训练过程中始终输出零,这些神经元被称为“死神经元”。这种情况通常发生在 ReLU 的输入为负值时,ReLU 会将其输出设置为零。长期处于这种状态的神经元无法进行有效的训练或更新,从而导致 死 ReLU 问题。 - 虽然 Dead ReLU 问题是存在的,但它在实际应用中通常并不严重, 1. 神经网络中往往有足够的神经元来弥补死神经元的影响。 2. 死神经元不会影响其他神经元的梯度传播。 3. 使用 改进版 ReLU 激活函数(如 Leaky ReLU 或 PReLU)可以有效避免死神经元问题。 4. Leaky ReLU / PReLU - Leaky ReLU - f(x) = max(0.01x, x)通常是0.01 - will not "die". - PReLU(Parametric ReLU) - f(x) = max(αx, x) - α是通過backprop/gradient descent訓練得到的,可是α如果學成負數,包括下面的ELU激活函數,都可能讓輸出全部變成正數,跟sigmoid一樣的問題 - α是不是每個neuron都不一樣?Serena也記不清了。chatgpt說是每個都獨立,沒有全局alpha,那這樣的話α學成負數就不是什麼問題了,大概會是偶然現象 5. ELU(Exponential Linear Units) - $$f(x) = \begin{cases} x, & \text{if } x \geq 0 \\ \alpha (e^x - 1), & \text{if } x < 0 \end{cases}$$ - pros - All benefits of RelU - Closer to zero mean outputs - Negative saturation regime compared with Leaky ReLU adds some robustness to noise - cons - exp() computation 6. maxout neuron - $f(x) = \max \left( W_1^T x + b_1, W_2^T x + b_2, \dots, W_k^T x + b_k \right)$ - Does not have the basic form of dot product -> nonlinearity - pros: - Generalizes ReLU and Leaky ReLU - Linear Regime!(線性狀態(線性區間)) Does not saturate! Does not die! - cons: - doubles the number of parameters/neuron
TLDR,現在用什麼activate?transformer用的啥來著?好像也不是很關鍵了

怎麼理解“activate”激活?這節課原來後面講了如何可視化模型學到的特徵。(basically each element of these grids is showing what in the input would look like that basically maximizes the activation of the neuron. So in a sense, what is the neuron looking for?上面這段看看

Data Preprocessing

zero-mean就是因為上面的原因,也有別的PCA(decorrelate)和Whitening 圖片說是一般不用normalize,只減去均值,減所有channels和per-channel結果上沒區別

注意,trainning set上做的preprocess,test set也要做。注意是訓練階段得到的均值,而不是测试数据本身的均值。

上面的兩種方法:

  1. 计算训练集中的所有图像的像素值的均值,得到一个 全局均值图像,然後所有图像的每个像素都减去训练集中的所有图像在该像素位置的均值;
  2. RGB三個通道分別算均值,這倆方法可以取足夠量的數據算一個值,只要能代表總體分布就行不用完全全部數據。

Weight Initialization

所有的neurons都會變一樣,都會有一樣的梯度,也會一樣地更新。
  • First idea: Small random numbers (gaussian with zero mean and 1e-2 standard deviation)
    • W = 0.01 * np. random. rand (D,H)
    • Works ~okay for small networks, but problems with deeper networks.
First Idea activation layer visualization, 結果為右 這裡的gradients跟前面一樣也是dLoss/dW=X,這裡的激活層值全為0,所以梯度完全沒有更新 不用0.01而x100倍用1.0的初始化W的話,因為用的是tanh,到了激活函數的飽和區間,tanh飽和區間斜率幾乎是0,所以梯度也會消失
xavier initialization用tanh,因為tanh在0處斜率很大,所以是梯度很有效的區域 用relu就梯度消失了,這裡雖然上面的mean也是0,但是relu在0處沒有斜率,沒有梯度,所以神經元也是deactivated 用He初始化就變成我們想看到的分布,下面分布圖一樣是因為x拉太長了,上面的mean均值在0.5,是relu很有效的斜率gradient為1的線性區域 2017年仍是個活躍研究領域,不知道現在如何?

Batch Normalization

We wants to keep activations in a gaussian range, like above, so use BN.
關於BN我的思考:

  1. 四年前就assginment卡在这没继续了,这次可好好搞明白。其实这部分跟上面找比较好的初始化W,以使每一层输入的分布都在神经元的有效区间内是一样的道理,有效区间就是tanh的0附近,relu的正数线性区间,斜率=1,即能让梯度反向传递回去。

  2. BN有幾個核心目的,(去他媽媽的减小内部协变量偏移(Internal Covariate Shift))i.加速训练过程;ii.正则化作用(因為引入了小噪聲,噪聲指讓數據分布改變了?Serena的說法是,之前activation neuron只會考慮一個input但現在的input都是被整個batch的其他特徵影響過的,但總之增強泛化不是BN的主要目的);iii.通過下面的#3.可以缓解梯度消失和梯度爆炸。

  3. 那麼只要搞懂為什麼normalization能加快收斂,就可以了——

    • https://stats.stackexchange.com/a/437848 In Machine learning, how does normalization help in convergence of gradient descent?
    • 考慮$f(w)=w_1^2 + 25w_2^2$,通過觀察可以發現有global minimum $w=[0,0]^\top$。這個函數的梯度是,假如learning rate 𝛼=0.035,$w^{(0)}=[0.5, 0.5]^\top,$ 梯度更新公式是$w^{(1)} =w^{(0)}-\alpha \nabla f\left(w^{(0)}\right),$,df/dw永遠會朝向w2的方向,可以想像w^2和25w^2這兩個曲線,變化速率相差太大(係數就是X),但經過normalization後,減去mean(12)除以σ = Σ(x-x̄)2 / N</span> = 144</span> = 12,可以標準化為$f(w)=-w_1^2 + w_2^2$,大概這麼理解下(這個分布只有兩個數據x1=1,x2=25所以感覺很怪),normalize後梯度變化會更朝向global minimum而不再zig zag。
  4. batch normalization會根據不同的激活函數學習不同的參數嗎?比如對relu來說,希望輸入都在正數區間避免dead relu,比如tanh希望輸入不要到飽和區間。還是說batch normalization總是盡可能接近mean=0,deviation=1的標準正態分佈?

    • 不一定?
    • chatgpt說BN總是希望輸入/特徵的分布盡可能接近標準正態,只不過normalization之後,1.可以避免太多負值導致太多dead relu;2.可以避免太多值在tanh的-1和1飽和區間裡。這樣來看也還是很好的。
    • 但Serena說通過學習γ和β參數,雖然BN本身是想讓分布變成標準正態分佈,但也給模型靈活度,可以主動不讓tanh飽和,或者搞出全正數分布不讓dead relu出現。
在extreme情況下才會learn identity mapping,實際上這樣效果不好,模型的特徵不會從normal distribution在收斂過程中變回去散亂的分布
  1. 激活層/隱藏層的歸一化是輸入的歸一化,不是輸出的。

  2. 數據的歸一跟激活函數的歸一化/標準化是一樣的目的和道理,實際上就是因為數據normalization/standardization效果很好,那麼為什麼不把每一層的輸入值都歸一化/標準化?

  3. BN 允许激活值的分布在训练过程中变化,并不强制要求它们是标准正态分布。尽管初始阶段会做标准化处理,但在学习过程中,BN 可以调整输出的均值和方差,导致输出分布变得更加灵活。

    • 通过学习 γ 和 β 参数,BN 可能会产生一些不完全符合标准正态分布的分布,包括可能的偏移或变化,如全正分布等。
  4. 課程進行到1小時時同學問了我想問的問題,強迫features變成gaussian分布不會丟失信息讓數據失真嗎?CNN裡希望保持空間信息所以預處理不用normalization,對特徵圖activation maps其實不會失真,說只是shifting(- mean)和scaling(/ standard deviation)。

在测试时(Testing Phase)

  • 在测试阶段,BN不再使用测试数据来计算均值和方差。相反,它会使用训练过程中通过各个mini-batch累计的全局均值和方差,每一层使用单独的全局均值和方差。
  • 这样做是因为测试集的batch大小通常较小,直接计算时可能不稳定,且我们希望测试时模型的行为尽可能和训练时一致。

Babysitting the Learning Process

增強正則的時候,期望是loss變大(加了一項),debugging策略get✔。 take the first 20 examples from CIFAR-10;turn off regularization (reg = 0.0);use simple vanilla ‘sgd’,用最簡單的設定確保模型能過擬合很少的樣本,讓loss趨近於0,debugging strategy get✔

然後用小的正則來找合適的learning rate,最重要的超參,如果loss/cost變化太慢一般來說是learning rate太小了;loss變成nan就是越界了,learning rate太大,loss變成inf?

Hyperparameter Optimization

Cross-validation strategy,
先粗定大範圍,然後仔細尋找(coarse to fine search)。

justin在2017年會盡量避免一次搜索2到4個以上超參數,複雜度畢竟指數級增長。

Tip for detecting explosions in the solver:
If the cost is ever > 3 * original cost, break out early

沒懂為什麼用10的uniform冪為範圍 隨機的更好?
之前OFA和學姐做的另一個忘了名字的模型我整天就調適這玩意 CV就是這麼枯燥,我當時做小樣本都快做瘋了,小樣本那玩意太多實驗了(找ppt
觀察曲線調參

Track the ratio of weight updates / weight magnitudes

Track the ratio of weight updates / weight magnitudes” 简单来说,指的是计算在每次训练迭代中,权重更新的大小权重的当前大小之间的比例。

计算方式:

  1. 权重更新(Weight Updates):计算在一次迭代中,权重的更新量,通常表示为:

    其中,$\eta$ 是学习率,$\nabla_w L(w)$ 是权重关于损失函数的梯度。

  2. 权重的大小(Weight Magnitudes):权重向量的模长,通常使用 L2 范数计算:

  3. 权重更新与权重大小的比率(Ratio of weight updates to weight magnitudes):

目的:

  • 衡量更新幅度与权重大小的关系:这个比率可以反映梯度更新的幅度相对于权重当前大小的比例。

    • 比率太大:可能表明学习率过高,导致梯度更新过大,容易导致训练不稳定。
    • 比率太小:可能表明学习率过低,更新幅度过小,训练过程过于缓慢。
  • 调节训练过程:通过监控这个比率,可以帮助调整学习率,以确保训练过程既不太剧烈,也不太缓慢,保持合理的收敛速度。

Track the ratio of weight updates / weight magnitudes” 通过计算每次权重更新的大小与权重大小之间的比率,帮助评估训练是否稳定、学习率是否合适。

Lecture 7

改變了一個超參後,其他超參的最優值會改變嗎?比如改變model size,learning rate的最優解不太會變,如果已經找到一個較好的範圍了,就算最壞改變也只是讓模型更慢收斂到global optimal而已,尤其是用了比較好的optimization策略。

  • Fancier optimization
  • Regularization
  • Transfer Learning(回想起2021年剛來,就瘋狂讓我學遷移學習,現在都沒人在提了)

Fancier optimization

注⚠️:公式裡的x實際上是w。

local minima versus saddle point
鞍點最好的例子,這裡梯度是0,各個方向都沒有變化率

SGD Problems:

1.跟上面zig zag例子一樣,這個問題不光希望通過BN數據/特徵解決,也希望通過優化算法解決 2.local minima和saddle point,雖然一維看起來local minima比saddle point更嚴重,但比如說在100- dimension的空間裡,每一個方向都上升造成local minima是很罕見的(任何一個方向下降就不是local minima$f(x_0) \leq f(x), \quad \forall x \in 邻域U$),而且saddle point周圍斜率過低訓練過慢也是很麻煩的問題

3.因為mini-batch SGD每次只用一小部分,反應不了真實分佈,而且有很多噪音,所以訓練過程會很曲折meander,但其實full batch vanilla gradient descent還是有這些問題。

1. Momentum SGD

一些物理術語:

  1. velocity(速度,=speed速率+方向);
  2. Kinetic Energy(動能);$KE=\frac{1}{2}mv^2$
  3. Momentum(動量);p=mv,碰撞中的動量守恆:在兩物體碰撞過程中,雖然動能可能不守恆(如在非彈性碰撞中),但動量總是守恆的。
  4. Kinetic Friction(動摩擦);
  5. Static Friction(靜摩擦)。

動量和慣性的關係:
假設有兩個物體,質量分別為 1 kg 和 10 kg,兩者都以相同的速度 5 m/s 移動。

  • 輕物體的動量:

  • 重物體的動量:

儘管兩者的速度相同,但重物體的動量要比輕物體大,這意味著在相同的外力作用下,重物體的運動狀態更難改變,這就是慣性強的表現。

用velocity vector取代gradient vector,這個intuition直覺上是想保持初始速度不那麼輕易改變(動量/慣性)

velocity一般就初始化為0。

不光是下面zigzag的情況緩解,saddle point和local minima也可以因為有原“速度”無視gradient<=0的區域衝過去

有個同學的問題問到了我的疑惑,如果因為這個動量,gradient衝過了一個narrow/sharp很窄的最優點怎麼辦?Justin Johnson的解釋是,我們還是希望要一個flat平台的最優點,更robust,就算很窄的那個是最優點,我們不一定想要。

2. Nesterov Momentum

上面稍微變體。

公式看著有點頭疼,其實就是永遠在更新完gradient的點上計算新gradient,而不是在更新前計算gradient。現在計算有問題,希望loss和gradient一起計算,於是有了下面的版本。

3. Ada Grad

類似於每次除以之前所有gradients的總和,目的是為了平均過大和過小的斜率/變化率/梯度以解決上面的zig-zag問題,但導致每次的梯度更新會越來越少。

4. RMSProp

上面的slight variation,這個分母會自己變小。

decay rate like 0.9 0.99

5. Adam

積木A加B,beta還是0.9的decay rate,因此first moment和second moment剛開始會很小

所以剛開始更新的時候底下分母很小,如果first moment不幸也很小的話可能導致更新幅度很大,讓模型更難收斂。要知道這不是因為loss函數斜率很大,只是因為adam 特性。

改進

6. Learning Rate

decay over time 一般SGD Momentum用lr decay,而且最好先選一個lr,再用cross-validation選一個好的decay

7. Second-order Optimization

上面的都是求一階導數,這裡用二階導數second derivative

好像沒見有人用過,這個倒是可以拿來當畢業論文的創新點。XD

考慮拿來水論文

8. Test Error

如果在Training Dataset已經做得足夠好了,怎麼減小train error和test error之間的gap?記得嗎?test set是最後啟用的,我們只能用val set測試 用不同的隨機初始化訓練十個模型,平均他們的預測結果,在比賽裡還是有用的 或者可以在模型訓練的不同階段,把weights拿出來幾個snapshots當不同模型,自己跟不同時間的自己ensemble,2017當周的ICLR

ensemble的模型可以用不同的超參數。

如果不訓練多個模型來提升泛化能力,單模型改善表現可以使用正則。

Regularization

1. Dropout

在 dropout 技術中,將神經網絡中的一部分神經元的輸出設為 0,而不是將其輸入設為 0。

這種看起來就是totally messing with nets的行為怎麼就好用了?

反正就是好用。

test做法

dropout當然會導致training更久,因為w更新得更少了?

有點像都是引入了噪聲,有時bn就夠了,但dropout相當於給regularization加了個係數(變0的probability)更靈活了。

2. Data Augmentation

training加入stochasticity和noise的都可以看作regularization。

pattern裡test time的marginalization是說“边缘化”,即再把噪聲給忽略掉 在網絡設計上加入這些“創新點”(別人的積木)可以當做大論文和專利的

每次用多少個正則手段?BN很多情況已經夠了(加速擬合、防止過擬合),應該每次感覺模型過擬合了才加入新的策略。

Transfer Learning

Off the shelf和off the wall到底啥意思?
这两个短语的含义是通过比喻或隐喻演变而来的,跟它们字面上的意思有些联系。让我分别解释一下为什么会有这样的意思:

  1. Off the shelf
    这个短语最初的字面意思是从货架上拿下来的物品。我们都知道商店里货架上的商品是直接可供购买的,顾客不需要等待生产或定制。所以,“off the shelf” 引申为“现成的”或“不需要定制的”。这个表达强调的是可以直接得到、无需额外处理的意思。

    例子:在一些领域,像软件、硬件、书籍等商品,有现成的商品直接从货架上取下,可以即刻使用,因此常常被形容为“off the shelf”。也可以用来描述任何“常规、标准化”的东西。

  2. Off the wall
    这个短语的起源比较有趣。它通常解释为某物“从墙上弹出来”,在字面上听起来像是某物离开了正常的轨迹,变得不受控制或不合常规。墙是一个常见的边界或框架,东西从墙上弹出来就意味着它突破了这个框架或界限。

    在这个意义上,“off the wall” 形容的是“跳脱框架、非常规、不拘一格”的事物。因此,原本它用来形容某事或某人行为怪异、出乎意料或充满创意,后来这个含义得到了广泛应用。

    例子:如果你看到一个人的行为或想法非常独特,无法用传统的方式理解,或者说他的艺术作品完全不符合常见的审美标准,就可以用“off the wall” 来形容它的独特性或非常规性。

这些表达通常是从具体的、可观察的现象逐渐引申出更抽象的意义。

當時的CNN剛開始pretrain-finetune,已經不train from scratch了。

optimization- training loss
regularization-test performance
transfer learning-less data

剛發現原來invisibility:true/false這個已經沒用了,得-BeInvisible,都給忘了。另外priority一定要有,不然推薦文章排序會bug。

Lecture 8

  • CPU vs GPU
  • Deep Learning Frameworks
    • Caffe / Caffe2
    • Theano / TensorFlow
    • Torch / PyTorch

Assignments

https://cs231n.stanford.edu/assignments.html

Colab

# **This mounts your Google Drive to the Colab VM.**
from google.colab import drive
drive.mount('/content/drive')

2022在商汤时跟着卢博用熟了jupyter发现非常方便,只是有的操作会很卡。虽然一年半过去,如今试图回忆往昔只剩脑袋空空,不过再打开colab操作起来还是很舒适自然的。本科时觉得colab跟我全权掌控的小云服务器相比有各种不便,读研实习尝试过vivo网易各个公司的infra后发现各家都是大同小异,就像去哪都要适应新的开发环境一样,colab只是把适应对象从公司的gpu clusters换成google cloud而已。
再看当时跑的cogview和OFA,其实都只是无脑地跑inference。我一直说深度学习是经验科学无迹可寻,只是因为2020、2021年这些assignments里的代码基础我没玩熟练。普通的computer science对我来说其实就像machine learning一样,计算机的底层系统是怎样运行的我也不甚清楚,一句print(“Hello, World”)背后怎么变成字节码?字节码怎么由cpython的c语言逐条执行/解释/转换成汇编指令?汇编指令又怎么变成机器码?机器码又怎么被cpu处理?
这其实跟理解反向传播的计算图一样,没必要每写一个def function():都想清楚cpython在干嘛,没必要每写一个net都想透梯度怎么七十二变,但做哪个任务比如image classification那他的pipeline一定得玩熟,网络的每一个模块是什么作用一定得玩熟。当然可以借助工具,今天我记不起“assert”、“yield”的作用了,记不起Softmax、Batch Normalization的作用了,当然可以查资料,但这都建立在能很快搭一个积木,供尝试Softmax、Batch Normalization的基础上,如果一个最简单任务的pipeline我都没法熟练随手搭建,何谈尝试陌生模块?就好像今天记不起yield的作用,我就必须会写def function():、知道怎么写type()、for-loop才能测试。

Regular Expression

写博客写代码时经常会用regex正则批量修改文本,所以在这里记录一些常用的以免重复造轮子。

加粗标题

以此为例,第一行为被替换原文本,第二行为替换后文本。
^(#+) ((?!\*)(.*))
$1 **$2**

  1. 先用(pattern)匹配markdown标题井号;
  2. 然后是个negative lookhead(?!)匹配井号后没有星号的文本,这里用转义符escape character反斜杠。

图片缩放

markdown图片语法不方便改大小位置,改成html语法的img比较方便操作,写markdown的时候每个都手敲img又太费劲不如![]()
!\[(.*)\]\((.*)\)
<img src="$2" alt="$1" width="50%" height="50%">
or
<div align=center><img src="$2" alt="$1" width="50%" height="50%"></div>

wiki圖片改assets路徑

!\[(.*)\]\(((?!/assets/).*)\)
![$1](/assets/$2)

小知识

  1. et al(and others in Latin)跟at all读音就是一样滴。
    2.“Vanilla”常被视为“基本”或“经典”的代表,因为它是最初的冰淇淋和甜品口味之一,且易于与其他味道搭配。这种简单而纯粹的特性使它成为许多食品的基准,从而赋予其“最初”或“标准”的象征意义。
  2. 他们用的也是google slide(macos)。
  3. [Deployer error] 部署异常[ 文件路径 : /Users/v2beach/code/blog/public/pics/cs231n/Screenshot 2024-11-14 at 01.48.31.png,对象键 : pics/cs231n/Screenshot 2024-11-14 at 01.48.31.png] 

    Error [ResponseTimeoutError]: Response timeout for 60000ms, please increase the timeout or use multipartDownload.
    at Client.requestError (/Users/v2beach/code/blog/node_modules/ali-oss/lib/client.js:311:11)
    at Client.request (/Users/v2beach/code/blog/node_modules/ali-oss/lib/client.js:207:22)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    解決方案,在blog/node_modules/hexo-deployer-ali-oss/index.js裡修改timeout時間。
    let result = await client.put(objectKey, localPath, {timeout: 120000});
  4. hexo d -g通過Node.js上傳到ali OSS會越傳越慢,最後超過默認60s timeout,嘗試multipartUpload還是超時,沒有嘗試流式上傳,只能修改timout到2分鐘。
    hexo在config.yml裡註冊新配置:
    hexo.extend.deployer.register('ali-oss', function(args){
    图太多不能再塞了,打开这篇文章得加载个大几分钟。
  5. chatgpt公式prompt:里面的\[\]和\(\)都换成dollar符号

常遇見bug一覽

  1. 求梯度dW寫成dw或者反之,非常難查出來;

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×