0%

Part of the article is generated by [ChatGPT]

collate_fn

This post records about the collate_fn from torch.util.data.DataLoader and the python built-in function zip.

Each batch, the dataloader collects batch_size number of items. They are picked from the dataset one by one. So currently, the batch data is [(data1, target1), (data2, target2), ..., (dataN, targetN)].

The default collate_fn would change it into [torch.tensor([data1, data2, ..., dataN]), torch.tensor([target1, target2, ..., targetN])].

However, in some NLP tasks, the data is not in the same length. So we need to apply torch.nn.utils.rnn.pad_sequence to make each data same length (usually the maximum length in this batch). A typical implementation is:

1
2
3
4
5
def collate_fn_train(batch):
x, y = zip(*batch)
x_pad = pad_sequence(x, batch_first=True)
# y = torch.Tensor(y) # optional
return x_pad, y

zip and *

  • What does the zip do in the above function?

The zip() function in Python is a built-in function that takes one or more iterables (such as lists, tuples, or strings) and “zips” them together, returning an iterator of tuples where the i-th tuple contains the i-th element from each of the input iterables.

1
2
3
4
5
6
7
8
9
a = [1, 2, 3]
b = ['a', 'b', 'c']
c = [True, False, True]

zipped = zip(a, b, c)

print(list(zipped))
# OUTPUT
[(1, 'a', True), (2, 'b', False), (3, 'c', True)]
  • What is the *?

In Python, the asterisk (*) symbol can be used to unpack iterables like lists or tuples. When used in this way, the asterisk is sometimes called the “splat” operator or the “unpacking” operator. The unpacking operator is used to extract the individual elements from an iterable and assign them to separate variables.

1
2
3
4
5
6
my_list = [1, 2, 3]
print(*my_list)
# equals
print(1, 2, 3)
# OUTPUT
1 2 3
  • So, what is the x, y = zip(*batch)?

First, batch is [(data1, target1), (data2, target2), ..., (dataN, targetN)].

*batch would unpack the outmost list, to be zip((data1, target1), (data2, target2), ..., (dataN, targetN)). The result would be two tuples, [data1, data2, ..., dataN] and [target1, target2, ..., targetN]. The former one is assigned to x and the other is assigned to y.

In this way, we get separate data structure, that contains all data and target respectively.

The code is generated by [ChatGPT].

Here are two common functions, that can convert the python built-in dictionary into the format of latex/markdown unnumbered list, so that we can copy it into the latex/markdown. Example can be:

The input python dictionary is:

1
2
3
4
5
6
7
8
9
10
11
12
13
my_dict = {
"fruits": {
"apples": "red",
"bananas": "yellow",
"grapes": "purple"
},
"vegetables": {
"carrots": "orange",
"spinach": "green"
},
"meat": "beef",
"dairy": "milk"
}

The result for markdown is:

1
2
3
4
5
6
7
8
9
10
11
- fruits
- apples
- bananas
- grapes
- vegetables
- carrots
- spinach
- meat
- beef
- dairy
- milk

and result for LaTeX is:

1
2
3
4
5
6
7
8
9
10
11
12
13
\item fruits
\begin{itemize}
\item apples: red
\item bananas: yellow
\item grapes: purple
\end{itemize}
\item vegetables
\begin{itemize}
\item carrots: orange
\item spinach: green
\end{itemize}
\item meat: beef
\item dairy: milk

Function explanation: it is as simple as it shows. We use the recursion strategy here. When we are going to print a sub-dictionary, we recursively call the function, with more (two) spaces indented.

Dict to latex unnumbered list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def dict_to_latex(d, level=0):
"""
Converts a dictionary to a string in LaTeX format for an unnumbered list.
Nested dictionaries are considered as second level items.

Args:
- d (dict): The dictionary to be converted.
- level (int): The current nesting level (defaults to 0).

Returns:
- A string in LaTeX format for an unnumbered list.
"""

latex_str = ""

for k, v in d.items():
if isinstance(k, str):
k = k.replace('_', '\_')
if isinstance(v, str):
v = v.replace('_', '\_')
if isinstance(v, dict):
latex_str += f"{' '*level}\\item {k}\n\\begin{{itemize}}\n{dict_to_latex(v, level+1)}\\end{{itemize}}\n"
else:
latex_str += f"{' '*level}\\item {k}: {v}\n"

return latex_str

Note that we should avoid the _ in our string. So we need first convert it into escape character, \_.

The result of the string should be wrapped by a \begin{itemize} \end{itemize}.

Dict to markdown unnumbered list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def dict_to_markdown(d, level=0):
"""
Converts a dictionary to a string in Markdown format for an unnumbered list.
Nested dictionaries are considered as second level items.

Args:
- d (dict): The dictionary to be converted.
- level (int): The current nesting level (defaults to 0).

Returns:
- A string in Markdown format for an unnumbered list.
"""

md_str = ""

for k, v in d.items():
if isinstance(v, dict):
md_str += f"{' '*level}- {k}\n{dict_to_markdown(v, level+1)}"
else:
md_str += f"{' '*level}- {k}: {v}\n"

return md_str

“C/C++ 复杂类型声明规则” 将分为两节。第一节介绍螺旋法则声明规范,第二节介绍const的修饰关系。

本节主要解决以下问题:

1
2
3
const int *a;  // a可变嘛? *a可变嘛?
int * const a; // a可变嘛? *a可变嘛?
const int * const a; // a可变嘛? *a可变嘛?

这两节内容,我都尝试以一种不用死记硬背,而是理解的方式去掌握。

本文参考自【初中生也能看懂的C/C++类型声明规则教学,很简单的!】 https://www.bilibili.com/video/BV1mB4y1L7HB/ 。里面通过一些例子介绍了螺旋法则和对const关键词的判读。我将在此基础上加上我对其的解释,使其变得更加通俗易懂。

基本解决思路:const 只修饰右边最近的东西。

例如const r, 说明r这个变量是const的,不能动。const *r,意为const (*r),说明*r,r指向的东西,是const的。r自身可以变化。

再例如,const int r, 说明这个int是const的。这个整数是常量。r是一个整型常量。

举一些例子:

例子1

1
2
int const a1 = 5;
const int a2 = 6;

在这里,两个其实是等价的。对于第一个,a1被const修饰了,所以a1不能变。a1是一个整型变量。所以连起来,a1是一个常量整型变量。对于第二个,可以理解成a2是一个const int,a2是一个常量整型变量。

所以,不同的声明形式,意义可能相同。

例子2

1
2
int const *p1 = &a1;
const int *p2 = &a1;

在这里,两个其实也是等价的。对于第一个,*p1被const修饰了,所以*p1不能变。即p1指向的是一个整型常量。对于第二个,可以理解成p2指向一个const int,p2指向的是一个常量整型变量。对于这两个来说,p1 p2自己可以变,但是他们指向的不能变。

一个声明里的多个const,意义可能相同。例如:

1
const int const *p3

这个例子里虽然有两个const,但其实还是只修饰了一个*p3p3自身没有被const修饰,可以变。

例子3

1
int *const r2;

在这里,r2直接被const修饰,所以r2不能变。r2指向一个int,那个int没有额外修饰,所以那个int是可以变的。总之,r2是不能变的,但是r2指向的int是可以变的。

例子4

1
const int *const r3;

在这里,r3直接被const修饰,所以r3不能变。r3指向一个const int,那个int没有额外修饰,所以那个int是可以变的。总之,r3是不能变的,r3指向的int也不可以变的。

例子5

1
const int **r5;

r5指向一个指向int的指针。这个被指向的指针指向的int是被const所修饰的。所以r5可以变,r5指向的那个(指向int的指针)也是可以变的,但是r5指向的指针指向的int没得变。

例子6

1
int const * const * const r6;

上面那个例子的升级版。r6指向一个指向int的指针。r6被const修饰,r6不能变。r6指向的(指向int的指针)被const修饰,即*r6不能变。r6指向的指针指向的int,**r6也被const修饰,也不能变。

“C/C++ 复杂类型声明规则” 将分为两节。第一节介绍螺旋法则声明规范,第二节介绍const的修饰关系。这两节内容,我都尝试以一种不用死记硬背,而是理解的方式去掌握。

本文参考自【初中生也能看懂的C/C++类型声明规则教学,很简单的!】 https://www.bilibili.com/video/BV1mB4y1L7HB/ 。里面通过一些例子介绍了螺旋法则。我将在此基础上加上我对其的解释,使其变得更加通俗易懂。

螺旋法则:

  • 第一步,找到变量名,如果没有变置名,找到最里面的结构

  • 第二步,向右看,读出你看到的东西但是不要跳过括号

  • 第三步,再向左看,读出你看到的东西,但是也不要跳过括号

  • 第四步,如果有括号的话,跳出一层括号

  • 第五步,重复上述过程,直到你读出最终的类型

举例1:

1
int *v[5];
  • 第一步:
1
2
int *v[5];
^

如上,我们找到了v是变量名。读作:v是……

  • 第二步,向右看,读出你看到的东西但是不要跳过括号。
1
2
int *v[5];
^^^

[5]意为是一个5个元素的数组。读作:v是一个五个元素的数组。

  • 第三步,再向左看,读出你看到的东西,但是也不要跳过括号。
1
2
int *v[5];
^^^^^

int *意为每个东西是一个指向int的指针。读作:v是一个五个元素的数组,数组每个东西指向一个int型指针。

  • 第四步,如果有括号的话,跳出一层括号。
1
int *v[5];

对于上面这个例子,无这一步。我们已经读完了:v是一个五个元素的数组,数组每个东西指向一个int型指针。

我们可以验证一下我们理解正不正确。我们可以用下列的代码给v数组赋值:

1
2
3
int a = 2023;
v[0] = &a;
printf("%p %p", &a, v[0]);

g++编译通过且运行正确,可见我们的理解没有问题。

举例2

1
int (*func)()
  • 第一步:
1
2
int (*func)()
^^^^

如上,我们找到了func是变量名。读作:func是……

  • 第二步:向右看,读出你看到的东西但是不要跳过括号。

向右边遇到括号了,不跳出括号,skip。

  • 第三步,再向左看,读出你看到的东西,但是也不要跳过括号。
1
2
int (*func)()
^

单独一个*意为“指向”。读作:func指向……

  • 第四步,如果有括号的话,跳出一层括号。向右看。
1
2
int (*func)()
^^

这样一组括号是函数的意思。括号内为空,说明这个函数没有参数。整个(*func)的左边是int,它是这个函数的返回值,是int。读作:func指向一个接收空参且返回值为int的函数。

我们可以验证一下我们理解正不正确。我们可以用下列的代码用匿名函数给func赋值:

1
2
3
4
5
int (*func1)();
func1 = []()->int{
return 2023;
};
printf("%d", func1());

g++编译通过且运行正确,可见我们的理解没有问题。

总结&一个网站推荐

螺旋法则五步骤;

当读的时候,看到[]意为一个数组, ()意为是一个函数,那么在主体的左侧肯定配套还会有一个返回值(当然,这个返回值也可能是一个复杂复合类型)。 *如果单独出现,意为指向,如果和类型出现,如int*,那就可以理解为指向int。

这里顺便推荐一个可以辅助阅读的网站:https://cdecl.org/ 。这个网站可以将表达式翻译成人话(英语),帮助大家的理解。但是总体来说只要会了螺旋法则,啥声明都能理解确切含义。

本文是一个Python强大的计算库,NumPy(以下简称np)的入门介绍。在本文中,你会了解到:

  1. 什么是numpy?
  2. 为什么要用numpy?
  3. 从python内置list到numpy,有什么不同?
  4. 怎么安装与学习numpy?

因为网上numpy基础教程非常多,而且基本都覆盖了核心内容。因此,我不会再重复介绍numpy的一些基本操作,我只会留一些链接在第四部分。相反,我会从一些high-level的角度,介绍numpy,给读者构建一个大体印象。

1. 什么是numpy?

根据numpy官网( https://numpy.org/doc/stable/user/whatisnumpy.html )的介绍,numpy是一个python的科学计算库(如其名,numpy=numeric python)。他提供了多维数组等实用对象,和大量对数组的快速操作与算法。

NumPy is the fundamental package for scientific computing in Python. It is a Python library that provides a multidimensional array object, various derived objects (such as masked arrays and matrices), and an assortment of routines for fast operations on arrays.

2. 为什么要用numpy?

2.1 方便

python的内置list仅提供了一些简单操作,如append增加元素,sort排序元素等。如果要做一些复杂运算,就会略显吃力。numpy集成了大量的数学、数组操作函数,不需要自己造轮子了。例如直接使用np.corr()就可以计算相关系数,直接使用np.linalg.inv()就可以计算矩阵的逆矩阵,直接调用np.fft模块可以进行一些傅立叶变换相关的操作。

2.2 快速高效

高效是numpy的核心亮点之一。我想可以分为程序运行高效和程序员编程高效。

运行高效:因为python是一种解释型语言,其运行速度比c/c++语言会慢几十倍左右。numpy的底层代码(见 https://github.com/numpy/numpy )都是使用c语言写的,并被高度优化过。因此其相同任务运行速度远快于python的list,和c++水平大抵相同。举个例子,两个2,000,000长的向量相加,纯用python list和for循环需要0.173s,而使用numpy则只需要0.008s。

编程高效:numpy里的许多运算符也被重载过,进行一般四则运算也很方便。如两个np.ndarray a, b, 可以直接通过加号a+b实现向量的逐位相加,而若是两个list a,b, a+b只是将两个列表拼接起来。另外,numpy也实现了-, *, &进行逐位相减、逐位向乘、逐位与的操作。下面是例子展示。

1
2
3
4
5
6
7
8
9
10
11
# a, b are list
a = [1, 2, 3]
b = [3, 2, 1]
a+b
#OUTPUT: [1, 2, 3, 3, 2, 1]

# a, b are ndarray
a = np.array([1, 2, 3])
b = np.array([3, 2, 1])
a+b
#OUTPUT: np.array([4, 4, 4])

2.3 Python数据科学之基础

许多python的科学计算、数据科学库都是以numpy为数据容器的基础根基的。例如sklearn(机器学习库)中的算法使用numpy来实现,用户传入的均为numpy.ndarray数据;matplotlib(画图库)中接收numpy.ndarray数组绘图,等等。因此numpy是要用python玩数据科学等领域的基础。学会numpy,可以做以下事情:

  1. 复现论文中的算法

  2. 研究一些数据科学库的源码,实现细节

  3. 掌握一个以python为接口的高性能计算的核心工具,做任何想做的事情

  4. ……

3. 从python list到numpy

从python的内置list列表到numpy的ndarray有一些“同”也有一些“不同”。这里简单进行一个介绍,以使读者可以对numpy数组有一个更清晰的大体影响。

numpy.ndarray对象更像是其他编程语言(如C++/Java)里的数组(这样的数组结构才可以做到快速操作)。一旦构建,就要声明数组大小形状和数据类型。例如在C++中声明一个二维10*10的整型数组可以是:

1
int array[10][10];

而与之对应,在numpy里声明这样一个数组是:

1
array = np.ndarray(shape=(10, 10), dtype=int)

而python中的list实现本质是一个动态数组,其长度会根据运行时来决定。

接下来,我便由此列几点numpy数组和list的不同:

  • 改变大小。

因为list是动态数组,其长度会根据运行时来决定,因此list有append、pop等方法改变列表长短。而numpy.ndarray数组则“一旦构建,就有固定的大小”。在运行时不能改变大小(有几个元素),只能改变形状(如每行每列有几个元素)。np.append()方法虽说叫append,但是其本质也是要构建一个新的数组,而不是在原来的上做添加。

  • 统一数据类型。

一个python list中可以存放不同类型,例如[1, 1.2f, 'str']

一个numpy数组具有统一的数据类型,这使得其indexing索引、存储效率远高于list。一个数组内只能是一个类型,例如np.int32, int, np.float32, float等。如果需要存储不同类型,则需要将类型设为object。但是这样许多numpy函数便不支持使用了。

但是作为python语言的模块,numpy的设计还是非常清晰易懂的。大体上就是创建数组对象,然后对其进行运算或函数操作。语法上也非常相近。例如,numpy也支持list的切片索引(如a[::2],且其功能玩法更多)。

4. 怎么安装与学习numpy?

4.1 安装

网上的教程非常多,涵盖Windows,macOS和Linux系统。只需在网上找到相关教程即可。使用anaconda、miniconda、pip安装都可以。

4.2 学习

以个人经验来看,numpy的学习胜在多用。只要用得多,自然而然就熟练、学会了。numpy已经包含了大部分需要用的函数,如有对应需求,一定要先去网上搜索看看有无相关函数(现在还有chatGPT之类的可以问了)。

对于新手朋友我在这里推荐几个学习资源链接:

这里会摆放我在其他平台上写的文章的链接(因为我懒得搬过来了),欢迎点击阅读。

Cheatsheet

来自mpl官网的 https://matplotlib.org/cheatsheets/cheatsheets.pdf 的官方cheatsheet,但是pdf不方便即时查看,遂导出成图片,可以存到相册。图片可以单击后放大查看。

page1:

Cheatsheet

page2:

Cheatsheet

这里面覆盖了大部分使用mpl时会用到的函数,详细使用方法可见官网(https://matplotlib.org/)。

本文后续将会挑一些进行额外解释。

colormap class

colormap是把图变好看的灵魂。属于matplotlib.colors.Colormap()类. 这个类实现了__call__()方法,因此可以传入一个介于0-1之间的数,其返回一个四元组,表示一个RGBA颜色。这个元组可以被mpl的各类画图函数接收。

基础用法:

1
2
3
4
5
6
7
8
9
10
11
12
import matplotlib.pyplot as plt

cmap = plt.get_cmap('magma')

print(cmap(0))
print(cmap(0.5))
print(cmap(1))

# OUTPUT:
(0.001462, 0.000466, 0.013866, 1.0)
(0.716387, 0.214982, 0.47529, 1.0)
(0.002258, 0.001295, 0.018331, 1.0)

实践用法:画一条渐变的线。官网有例子 https://matplotlib.org/stable/gallery/lines_bars_and_markers/multicolored_line.html#sphx-glr-gallery-lines-bars-and-markers-multicolored-line-py, 我这是另一种实现方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
import matplotlib.pyplot as plt
import numpy as np

cmap = plt.get_cmap('magma')

t = np.linspace(0, 10, 100)
xt = np.sin(t)
normalized_t = t / t.max() # make it into [0, 1]

for i in range(len(t)-1): # draw piece by piece
plt.plot(t[i:i+2], xt[i:i+2], color=cmap(normalized_t[i]))

plt.show()

效果:

Cheatsheet

This blog is built with Hexo! This is the originally first post, automatically generated by Hexo. I modified it to a Quick Reference Handbook (QRH) for using this.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Clean cache

1
2
3
$ hexo clean
# shortcut:
$ hexo c

Generate static files

1
2
3
$ hexo generate
# shortcut:
$ hexo g

More info: Generating

Run server (local testing)

1
2
3
$ hexo server
# shortcut:
$ hexo s

More info: Server

Deploy to remote sites

1
2
3
$ hexo deploy
# shortcut:
$ hexo d

More info: Deployment