master d74bbe402666 cached
98 files
739.1 KB
389.2k tokens
32 symbols
1 requests
Download .txt
Showing preview only (1,289K chars total). Download the full file or copy to clipboard to get everything.
Repository: HarleysZhang/2020_algorithm_intern_information
Branch: master
Commit: d74bbe402666
Files: 98
Total size: 739.1 KB

Directory structure:
gitextract_g7w5_kbg/

├── .gitignore
├── 1-computer_basics/
│   ├── Linux系统/
│   │   ├── Linux基础-学会使用命令帮助.md
│   │   ├── Linux基础-常用命令总结.md
│   │   ├── Linux基础-文件及目录管理.md
│   │   ├── Linux基础-文件权限与属性.md
│   │   ├── Linux基础-文本处理命令.md
│   │   ├── Linux基础-新手必备命令.md
│   │   ├── Linux基础-查看cpu、内存和环境等信息.md
│   │   ├── Linux基础-查看和设置环境变量.md
│   │   └── Linux基础-查看进程命令ps和top.md
│   ├── README.md
│   ├── 操作系统/
│   │   ├── 深入理解计算机系统-第1章计算机系统漫游笔记.md
│   │   ├── 深入理解计算机系统-第2章信息的表示和处理笔记.md
│   │   ├── 深入理解计算机系统-第3章程序的机器级表示笔记.md
│   │   └── 计算机基础知识.md
│   └── 效率工具/
│       ├── Docker基础和常用命令.md
│       ├── git工业界实战总结.md
│       ├── ubuntu16.04安装mmdetection库.md
│       └── 程序编译工具基础.md
├── 2-programming_language/
│   ├── README.md
│   ├── cpp/
│   │   ├── C++日期和时间编程总结.md
│   │   └── c++基本编程题汇总.md
│   ├── python3/
│   │   ├── Python数据分析-pandas库入门.md
│   │   ├── python3编程总结.md
│   │   ├── python图像处理-读取图像方式总结.md
│   │   └── python数据分析-堆叠数组函数总结.md
│   └── shell/
│       └── Shell语法基础.md
├── 3-machine_learning/
│   ├── README.md
│   ├── k-means.py
│   ├── 《机器学习》学习笔记.md
│   ├── 机器学习入门总结.md
│   ├── 机器学习基本原理.md
│   ├── 机器学习基本概念总结.md
│   └── 机器学习经典算法总结.md
├── 4-deep_learning/
│   ├── README.md
│   ├── ml-dl-框架笔记/
│   │   ├── Pytorch基础-tensor数据结构.md
│   │   └── Pytorch基础-张量基本操作.md
│   ├── 深度学习基础/
│   │   ├── 反向传播与梯度下降详解.md
│   │   ├── 深度学习基础-优化算法详解.md
│   │   ├── 深度学习基础-参数初始化详解.md
│   │   ├── 深度学习基础-损失函数详解.md
│   │   ├── 神经网络基础部件-BN层详解.md
│   │   ├── 神经网络基础部件-卷积层详解.md
│   │   └── 神经网络基础部件-激活函数详解.md
│   └── 深度学习基础总结.md
├── 5-computer_vision/
│   ├── 2D目标检测/
│   │   ├── 0-目标检测模型的基础.md
│   │   ├── 1-目标检测模型的评价标准-AP与mAP.md
│   │   ├── 2-Faster-RCNN网络详解.md
│   │   ├── 3-FPN网络详解.md
│   │   ├── 4-Mask-RCNN详解.md
│   │   ├── 5-Cascade-RCNN论文解读.md
│   │   ├── 6-RetinaNet网络详解.md
│   │   ├── 7-YOLOv1-v5论文解读.md
│   │   ├── 8-Scaled-YOLOv4论文解读.md
│   │   ├── simple_yolov2/
│   │   │   ├── encoder.py
│   │   │   └── model.py
│   │   └── yolov3_pytorch/
│   │       └── darknet.py
│   ├── 3D视觉算法/
│   │   ├── 3D视觉算法初学概述.md
│   │   └── 单目3D目标检测综述.md
│   ├── README.md
│   ├── 工业视觉/
│   │   └── Halcon快速入门.md
│   ├── 数字图像处理/
│   │   ├── OpenCV3基本函数总结.md
│   │   ├── 《数字图像处理》学习笔记.md
│   │   ├── 数字图像处理基础总结.md
│   │   └── 视频编解码基础.md
│   └── 项目实践/
│       ├── GitHub车牌检测识别项目调研.md
│       └── draw_heart.py
├── 6-model_compression/
│   ├── README.md
│   ├── 卷积网络压缩方法总结.md
│   └── 神经网络量化基础.md
├── 7-high-performance_computing/
│   ├── 0-处理器基础知识.md
│   ├── 1-卷积算法的优化.md
│   ├── 2-模型编译优化.md
│   ├── README.md
│   └── 通用矩阵乘算法从入门到实践.md
├── 8-model_deploy/
│   ├── README.md
│   ├── 推理框架/
│   │   ├── ONNX模型分析与使用.md
│   │   └── TensorRT基础笔记.md
│   ├── 模型压缩部署概述.md
│   ├── 模型板端推理.md
│   ├── 模型转换总结.md
│   └── 神经网络模型复杂度分析.md
├── LICENSE
├── README.md
├── SUMMARY.md
├── book.json
├── cv算法工程师成长路线.md
├── data/
│   ├── code/
│   │   └── pytorch_note.py
│   ├── images/
│   │   ├── README.md
│   │   ├── dl/
│   │   │   └── courgette.log
│   │   └── yolov2/
│   │       └── 损失函数计算.jfif
│   └── intern_info.md
└── interview_summary/
    ├── 1-计算机视觉岗2019届实习面经.md
    ├── 2-计算机视觉岗2019届暑期实习应聘总结.md
    ├── 3-2019届地平线机器人实习总结.md
    ├── 4-计算机视觉岗2020届秋招面经.md
    ├── 5-视觉算法岗2021年社招面经.md
    └── README.md

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Node rules:
## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

## Dependency directory
## Commenting this out is preferred by some people, see
## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git
node_modules

# Book build output
_book

# eBook build output
*.epub
*.mobi
*.pdf

.history/
.DS_Store
.book.json
.package-lock.json
.SUMMARY.md

================================================
FILE: 1-computer_basics/Linux系统/Linux基础-学会使用命令帮助.md
================================================
- [概述](#概述)
- [使用 whatis](#使用-whatis)
- [使用 man](#使用-man)
- [查看命令程序路径 which](#查看命令程序路径-which)
- [总结](#总结)
- [参考资料](#参考资料)

### 概述

`Linux` 命令及其参数繁多,大多数人都是无法记住全部功能和具体参数意思的。在 `linux` 终端,面对命令不知道怎么用,或不记得命令的拼写及参数时,我们需要求助于系统的帮助文档; `linux` 系统内置的帮助文档很详细,通常能解决我们的问题,我们需要掌握如何正确的去使用它们。

* 需要知道某个命令的**简要说明**,可以使用 `whatis`;而更详细的介绍,则可用 `info` 命令;
* 在只记得**部分命令关键字**的场合,我们可通过 `man -k` 来搜索;
* 查看命令在哪个**位置**,我们需要使用 `which`;
* 而对于命令的**具体参数**及使用方法,我们需要用到强大的 `man` ;

### 使用 whatis

使用方法如下:

```bash
$ whatis ls # 查看 ls 命令的简要说明
ls (1)               - list directory contents
$ info ls  # 查看 ls 命令的详细说明,会进入一个窗口内,按 q 退出
File: coreutils.info,  Node: ls invocation,  Next: dir invocation,  Up: Directory listing
10.1 'ls': List directory contents
The 'ls' program lists information about files (of any type, including
directories).  Options and file arguments can be intermixed arbitrarily,
as usual.
... 省略
```

### 使用 man

查看命令 `cp` 的说明文档。

```bash
$ man cp  # 查看 cp 命令的说明文档,主要是命令的使用方法及具体参数意思
CP(1)      User Commands      CP(1)

NAME
       cp - copy files and directories
... 省略
```

在 `man` 的帮助手册中,将帮助文档分为了 `9` 个类别,对于有的关键字可能存在多个类别中, 我们就需要指定特定的类别来查看;(一般我们查询的 bash 命令,归类在1类中);如我们常用的 `printf` 命令在分类 `1` 和分类 `3` 中都有(CentOS 系统例外);分类 `1` 中的页面是命令操作及可执行文件的帮助;而3是常用函数库说明;如果我们想看的是 `C` 语言中 `printf` 的用法,可以指定查看分类 `3` 的帮助:

```bash
$man 3 printf
```

`man` 页面所属的分类标识(常用的是分类 `1` 和分类 `3` )

```bash
(1)、用户可以操作的命令或者是可执行文件
(2)、系统核心可调用的函数与工具等
(3)、一些常用的函数与数据库
(4)、设备文件的说明
(5)、设置文件或者某些文件的格式
(6)、游戏
(7)、惯例与协议等。例如Linux标准文件系统、网络协议、ASCⅡ,码等说明内容
(8)、系统管理员可用的管理条令
(9)、与内核有关的文件
```

### 查看命令程序路径 which

查看程序的 `binary` 文件所在路径,可用 `which` 命令。

```bash
$ which ls  # 查看 ping 程序(命令)的 binary 文件所在路径
/bin/ls
$ cd /bin;ls 
```

![image](./images/feaa2c13-1cdf-46e1-948c-535f4c51a9bd.png)

查看程序的搜索路径:

```bash
$ whereis ls
ls: /bin/ls /usr/share/man/man1/ls.1.gz
```
当系统中安装了同一软件的多个版本时,不确定使用的是哪个版本时,这个命令就能派上用场。

### 总结

本文总共讲解了 `whatis info man which whereis` 五个帮助命令的使用,`Linux` 命令的熟练使用需要我们在项目中多加实践、思考和总结。

### 参考资料

[《Linux基础》](https://linuxtools-rst.readthedocs.io/zh_CN/latest/base/index.html)

================================================
FILE: 1-computer_basics/Linux系统/Linux基础-常用命令总结.md
================================================
- [1,scp 命令复制远程文件](#1scp-命令复制远程文件)
- [2,ubuntu 系统使用使用 dpkg 命令安装和卸载 .deb 包](#2ubuntu-系统使用使用-dpkg-命令安装和卸载-deb-包)
- [3,vim 查找字符串](#3vim-查找字符串)
- [4,df 和 du 命令使用](#4df-和-du-命令使用)
- [5,ls -lh 查看指定文件大小](#5ls--lh-查看指定文件大小)
- [6,ctrl + r,反向查找历史命令](#6ctrl--r反向查找历史命令)
- [7,find 查找文件和文件夹](#7find-查找文件和文件夹)
- [8,hdfs 命令详解](#8hdfs-命令详解)
- [9,top 命令进行程序性能分析](#9top-命令进行程序性能分析)
- [10,tar 压缩、解压命令](#10tar-压缩解压命令)
- [11,linux 系统特殊符号作用](#11linux-系统特殊符号作用)
- [12, linxu 中 shell 变量含义](#12-linxu-中-shell-变量含义)
- [13,vim 跳转到行尾和行首命令](#13vim-跳转到行尾和行首命令)
- [14,vscode 列选择快捷键](#14vscode-列选择快捷键)
- [15,查看 cpu 信息](#15查看-cpu-信息)
- [16,mkdir -p 创建多层目录](#16mkdir--p-创建多层目录)
- [17,查看 gcc 所有安装的版本](#17查看-gcc-所有安装的版本)
- [18,查看系统版本命令](#18查看系统版本命令)
- [19,shell 读取文件每一行内容并输出](#19shell-读取文件每一行内容并输出)
- [20,实时查看 GPU 显存信息](#20实时查看-gpu-显存信息)
- [21,update-alternatives 管理软件版本](#21update-alternatives-管理软件版本)
- [22,管道和重定向命令](#22管道和重定向命令)
- [23,Bash快捷输入或删除命令](#23bash快捷输入或删除命令)
- [24,srun 命令行参数用法](#24srun-命令行参数用法)
- [参考资料](#参考资料)


### 1,scp 命令复制远程文件

```bash
# 从本地复制到远程:
scp local_file remote_username@remote_ip:remote_file 
# 从远程复制到本地
scp root@www.runoob.com:/home/root/others/music /home/space/music/1.mp3 
```

### 2,ubuntu 系统使用使用 dpkg 命令安装和卸载 .deb 包

```bash
sudo dpkg -i package_name.deb  # 安装deb包
sudo dpkg -r package_name # 卸载 deb 包
sudo apt install path_to_deb_file # 安装deb包
sudo apt remove program_name # 卸载deb 包
```

### 3,vim 查找字符串

在 `normal` 模式下按下 `/` 进入查找模式,输入要查找的字符串并按下回车。`Vim` 会跳转到第一个匹配,按下 `n` 查找下一个,按下 `N` 查找上一个,`vim` 还支持正则表达式查找。

### 4,df 和 du 命令使用

* `df` :查看**磁盘空间**占用情况
* `du` :查看**目录**占用的空间大小。

```bash
df -hT #查看硬盘使用情况。
du -h --max-depth=1 floder_name # 查看当前目录下所有文件/文件夹的空间大小
du -h -d 0 . # 查看当前目录空间大小
du -sh foldername # 查看指定目录空间大小
```
![image](images/c62f2296-e82d-4d3c-bb6f-07c1d581fe6b.png)

### 5,ls -lh 查看指定文件大小

```bash
$ ls -lh .bashrc  # 能查看文件空间大小,不能查看目录大小
$ find . -type l -delete # 删除当前目录下的符号链接
```

### 6,ctrl + r,反向查找历史命令

终端中按下 `ctrl + r`,可弹出搜索历史命令行,输入你要查找你输入过命令的关键信息,即可弹出完整历史命令。

### 7,find 查找文件和文件夹

`find` 支持基于正则表达式查找指定名字的文件,也支持根据文件类型、基于目录深度和文件时间戳进行搜索。

1. 查找目录:find /(查找范围) -name '查找关键字' -type d
2. 查找文件:find /(查找范围) -name 查找关键字 -print

![image](images/285954fe-e25e-4957-baee-29c1cacff6d9.png)

### 8,hdfs 命令详解

[HDFS 常用命令](http://blog.sanyuehua.net/2017/11/01/Hadoop-HDFS/)

### 9,top 命令进行程序性能分析

`top` 命令是 `Linux` 下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于 `Windows` 的任务管理器。

![image](images/78316e49-97f8-41cf-a9d9-b8f7e2ce024d.png)

`load average` 后面分别是 1分钟、5分钟、15分钟的负载情况。数据是每隔 5 秒钟检查一次活跃的进程数,然后根据这个数值算出来的。如果这个数除以 CPU  的数目,**结果高于 5 的时候就表明系统在超负荷运转了**。

### 10,tar 压缩、解压命令

* `tar -zxvf` 解压 `.tar.gz` 和 `.tgz`。
* `tar -xvf file.tar`  解压 tar 包
* `tar –cvf jpg.tar ./*.jpg`:将当前目录下所有 `jpg` 文件仅打包成 `jpg.tar` 后。
* `tar –zcvf xxx.tar.gz ./*.jpg`:打包后以 `gzip` 压缩,命名为 `xxx.tar.gz`。

在参数 `f` 之后的文件档名是自己取的,我们习惯上都用 `.tar` 来作为辨识。 如果加 `z` 参数,则以  `.tar.gz` 或  `.tgz` 来代表 `gzip` 压缩过的 `tar` 包; 如果加 `j` 参数,则以  `.tar.bz2` 来作为 `tar` 包名。

### 11,linux 系统特殊符号作用

1. `$`: 作为变量的前导符,引用一个变量的内容,比如:`echo $PATH`;在正则表达式中被定义为行末(`End of line`)。
2. `>>` : 表示将符号左侧的内容,以追加的方式输入到右侧文件的末尾行中。
3. `|`\*\* : 管道命令。\*\*管道命令 "|" 仅能处理前面一个命令传来的正确信息。

### 12, linxu 中 shell 变量含义

* `$1~$n`:添加到 `Shell` 的各参数值。`$1` 是第 `1` 参数、`$2` 是第 `2` 参数…。
* `$$`:`shell` 脚本本身的 `PID`。
* `$!`:`shell` 脚本最后运行的后台 `process` 的 `PID`。
* `$?`:最后运行的命令结束代码(返回值)。
* `$*`:所有参数列表。如 `"$*"` 用`「"」`括起来的情况、以 `"$1 $2 … $n"` 的形式输出所有参数。
* `$#`:添加到 `shell` 的参数个数。
* `$0`:`shell` 本身的文件名。

### 13,vim 跳转到行尾和行首命令

![image](images/20c9770d-41d5-4880-9b43-335934bebccd.png)

1. 跳到文本的最后一行行首:按`“G”`,即`“shift+g”`;
2. 跳到最后一行的最后一个字符 : 先重复 1 的操作即按“G”,之后按“\$”键,即`“shift+4”`;
3. 跳到第一行的第一个字符:先按两次`“g”`;
4. `^` 跳转行首,`$` 跳转行尾;

### 14,vscode 列选择快捷键

* VSCode 列选择快捷键:Alt+Shift+左键

### 15,查看 cpu 信息
> 总核数 = 物理 cpu 个数 \* 每颗物理 cpu 的核数
总逻辑 cpu 数 = 物理 cpu 个数 \* 每颗物理 cpu 的核数 \* 超线程数

1. 查看物理 cpu 个数:`cat /proc/cpuinfo | grep "physical id"| sort| uniq| wc -l`
2. 查看每个物理 cpu 中的 core 个数(核数):`cat /proc/cpuinfo| grep "cpu cores"| uniq`
3. 查看逻辑 cpu 的个数: `cat /proc/cpuinfo| grep "processor"| wc -l` 

```bash
# lscpu | grep -E '^Thread|^Core|^Socket|^CPU\(' 查看 cpu 核数和线程数
```
![image](images/0ac7fde7-15c9-44e4-9bbf-dc22f48a0360.png)

### 16,mkdir -p 创建多层目录

`mkdir -p /xxx/xxx/` 创建多层目录

### 17,查看 gcc 所有安装的版本

```bash
# centos 系统
rpm -qa | grep gcc | awk '{print $0}'
# ubuntu 系统
dpkg -l | grep gcc | awk '{print $2}'
# 查看系统当前使用 gcc 版本
gcc -v
```
![image](images/f53e407f-3f8b-4e93-b0de-f84a6f4c9882.png)

### 18,查看系统版本命令

* `lsb_release -a`:  适用于大部分 Linux 系统,会显示出完整的版本信息,`centos` 系统无法直接使用该命令,需要安装 yum install -y redhat-lsb。
* `cat /etc/os-release`: 适用于所有 Linux 系统。能显示较为全面的系统信息。
* `cat /proc/version`:  该文件记录了 Linux 内核的版本、用于编译内核的 `gcc` 的版本、内核编译的时间,以及内核编译者的用户名。

![image](images/a1561fd2-f491-4aaf-9a09-c1aa71b1e148.png)
![image](images/4ab2b6b3-a1cd-4e9e-8f76-fc6d59b1a557.png)

> `release` 文件通常被视为操作系统的标识。在 `/etc` 目录下放置了很多记录着发行版各种信息的文件,每个发行版都各自有一套这样记录着相关信息的文件。`LSB`(Linux 标准库Linux Standard Base)能够打印发行版的具体信息,包括发行版名称、版本号、代号等。

### 19,shell 读取文件每一行内容并输出

```bash
for line in `cat  test.txt`
do
    echo $line
done
```
### 20,实时查看 GPU 显存信息

`watch -n 1 nvidia-smi # 每1s显示一次显存信息`

### 21,update-alternatives 管理软件版本

`update-alternatives` 命令用于处理 Linux 系统中软件版本的切换,使其多版本共存。alternatives 程序所在目录 /etc/alternatives 。语法:

```bash
update-alternatives --help
用法:update-alternatives [<选项> ...] <命令>

命令:
  --install <链接> <名称> <路径> <优先级>
    [--slave <链接> <名称> <路径>] ...
                           在系统中加入一组候选项。
  --remove <名称> <路径>   从 <名称> 替换组中去除 <路径> 项。
  --remove-all <名称>      从替换系统中删除 <名称> 替换组。
  --auto <名称>            将 <名称> 的主链接切换到自动模式。
  --display <名称>         显示关于 <名称> 替换组的信息。
  --query <名称>           机器可读版的 --display <名称>.
  --list <名称>            列出 <名称> 替换组中所有的可用候选项。
  --get-selections         列出主要候选项名称以及它们的状态。
  --set-selections         从标准输入中读入候选项的状态。
  --config <名称>          列出 <名称> 替换组中的可选项,并就使用其中哪一个,征询用户的意见。
  --set <名称> <路径>      将 <路径> 设置为 <名称> 的候选项。
  --all                    对所有可选项一一调用 --config 命令。

<链接> 是指向 /etc/alternatives/<名称> 的符号链接。(如 /usr/bin/pager)
<名称> 是该链接替换组的主控名。(如 pager)
<路径> 是候选项目标文件的位置。(如 /usr/bin/less)
<优先级> 是一个整数,在自动模式下,这个数字越高的选项,其优先级也就越高。
..........
```

安装命令

```bash
sudo update-alternatives --install link name path priority [ --slave slink sname spath]
# 该命令完成 /usr/bin/gcc 到 /etc/alternatives/gcc 再到 /usr/local/${gcc_version}/bin/gcc 符号链接到建立。
update-alternatives --install /usr/bin/gcc gcc /usr/local/${gcc_version}/bin/gcc 100
```
选项注释:

* `link` 是在 /usr/bin/, /usr/local/bin/ 等默认PATH搜索目录
* `name` 是在 /etc/alternatives 目录中的链接名
* `path` 是真正的可执行程序的位置,可以在任何位置
* `priority` 是优先级,数字越大优先级越高

### 22,管道和重定向命令

* 批处理命令连接执行,使用 `|`
* 串联: 使用分号 `;`
* 前面成功,则执行后面一条,否则,不执行:`&&`
* 前面失败,则后一条执行: `||`

实例1:判断 /proc 目录是否存在,存在输出success,不存在输出 failed。

```bash
$ ls /proc > log.txt && echo  success! || echo failed.
success!
$ if ls /proc > log.txt;then echo success!;else echo failed.;fi  # 与前面脚本效果相同
success!
$ :> log.txt  # 清空文件
```

### 23,Bash快捷输入或删除命令

常用快捷键:

```bash
Ctl-U   删除光标到行首的所有字符,在某些设置下,删除全行
Ctl-W   删除当前光标到前边的最近一个空格之间的字符
Ctl-H   backspace,删除光标前边的字符
Ctl-R   匹配最相近的一个文件,然后输出
```

### 24,srun 命令行参数用法

srun 命令常见的参数用法解释:
+ -n:指定启动的 MPI 进程数。
+ --ntasks-per-node:指定每个节点上的 MPI 进程数。
+ -c:指定每个 MPI 进程使用的 CPU 核心数量。
+ -p:指定要使用的分区或队列。
+ --mem:指定每个 MPI 进程可以使用的内存量。
+ -t:指定最长的运行时间,格式为 HH:MM:SS。
+ --gres:指定要请求的通用资源,如 GPU、InfiniBand 等。
+ -J:指定作业名称。
+ -o:指定作业输出文件名。
+ -e:指定作业错误文件名。


### 参考资料
1. [linux find 命令查找文件和文件夹](https://www.cnblogs.com/jiftle/p/9707518.html)
2. [每天一个linux命令(44):top命令](https://www.cnblogs.com/peida/archive/2012/12/24/2831353.html)
3. [Shell中截取字符串的用法小结](https://www.cnblogs.com/kevingrace/p/8868262.html)
4. [查看 Linux 发行版名称和版本号的 8 种方法](https://linux.cn/article-9586-1.html)
5. [Linux Commands - Complete Guide](https://linoxide.com/linux-commands-brief-outline-examples/)

================================================
FILE: 1-computer_basics/Linux系统/Linux基础-文件及目录管理.md
================================================
- [一,概述](#一概述)
- [二,文件及目录常见操作](#二文件及目录常见操作)
  - [2.1,创建、删除、移动和复制](#21创建删除移动和复制)
  - [2.2,目录切换](#22目录切换)
  - [2.3,列出目录内容](#23列出目录内容)
  - [2.4,查找目录或者文件 find/locate](#24查找目录或者文件-findlocate)
  - [2.5,查看及搜索文件内容](#25查看及搜索文件内容)
- [三,总结](#三总结)
- [四,参考资料](#四参考资料)

> 本文大部分内容参看 《Linux基础》一书,根据自己的工程经验和理解加以修改、拓展和优化形成了本篇博客,不适合 Linux 纯小白,适合有一定基础的开发者阅读。

## 一,概述
**在 Linux 中一切皆文件**。文件管理主要是涉及文件/目录的创建、删除、移动、复制和查询,有`mkdir/rm/mv/cp/find` 等命令。其中 `find` 文件查询命令较为复杂,参数丰富,功能十分强大;查看文件内容是一个比较大的话题,文本处理也有很多工具供我们使用,本文涉及到这两部分的内容只是点到为止,没有详细讲解。另外给文件创建一个别名,我们需要用到 `ln`,使用这个别名和使用原文件是相同的效果。

## 二,文件及目录常见操作
### 2.1,创建、删除、移动和复制
创建和删除命令的常用用法如下:

* 创建目录:`mkdir`
* 删除文件:`rm file(删除目录 rm -r)`
* 移动指定文件到目标目录中:`mv source_file(文件) dest_directory(目录)` 
* 复制:`cp(复制目录 cp -r)`

这些命令的常用和复杂例子程序如下

```bash
$ find ./ | wc -l  # 查看当前目录下所有文件个数(包括子目录)
14995
$ cp –r test/ newtest   # 使用指令 cp 将当前目录 test/ 下的所有文件复制到新目录 newtest 下
$ mv test.txt demo.txt  # 将文件 test.txt 改名为 demo.txt
```
### 2.2,目录切换
* 切换到上一个工作目录: `cd -`
* 切换到 home 目录: `cd or cd ~`
* 显示当前路径: `pwd`
* 更改当前工作路径为 path: `$ cd path`

### 2.3,列出目录内容
* **显示当前目录下的文件及文件属性**:`ls`
* 按时间排序,以列表的方式显示目录项:`ls -lrt`

`ls` 命令部分参数解释如下:

* `-a`:显示所有文件及目录 (. 开头的隐藏文件也会列出)
* `-l`:除文件名称外,亦将文件型态、权限、拥有者、文件大小等资讯详细列出
* `-r`:将文件以相反次序显示(原定依英文字母次序)
* `-t`: 将文件依建立时间之先后次序列出

常用例子如下:

```bash
$ pwd
/
$ ls -al  # 列出根目录下所有的文件及文件类型、大小等资讯
total 104
drwxr-xr-x   1 root root 4096 Dec 24 01:24 .
drwxr-xr-x   1 root root 4096 Dec 24 01:24 ..
drwxrwxrwx  11 1019 1002 4096 Jan 13 09:34 data
drwxr-xr-x  15 root root 4600 Dec 24 01:24 dev
drwxr-xr-x   1 root root 4096 Jan  8 03:15 etc
drwxr-xr-x   1 root root 4096 Jan 11 05:49 home
drwxr-xr-x   1 root root 4096 Dec 23 01:15 lib
drwxr-xr-x   2 root root 4096 Dec 23 01:15 lib32
... 省略
```
### 2.4,查找目录或者文件 find/locate
1,查找文件或目录

```bash
$ find ./ -name "cali_bin*" | xargs file  # 查找当前目录下文件名含有 cali_bin 字符串的文件
./classifynet_calib_set/cali_bin.txt: ASCII text
./calib_set/cali_bin.txt:             ASCII text
./cali_bin.txt:                       ASCII text
```
2,查找目标文件夹中是否含有 `obj` 文件:

```bash
$ find ./ -name '*.o'
```
`find` 是实时查找,如果需要更快的查询,可试试 `locate`;locate 会为文件系统建立索引数据库,如果有文件更新,需要定期执行更新命令来更新索引库。

```bash
$ locate string  # 寻找包含有 string 的路径
```
### 2.5,查看及搜索文件内容
1,查看文件内容命令:`cat` `vi` `head` `tail more`。

```bash
$ cat -n  # 显示时同时显示行号 
$ ls -al | more  # 按页显示列表内容
$ head -1 filename  # 显示文件内容第一行
$ diff file1 file1  # 比较两个文件间的差别
```
2,使用 `egrep` 查询文件内容:

```bash
$ egrep "ls" log.txt  # 查找 log.txt 文件中包含 ls 字符串的行内容
-rw-r--r--   1 root root       2009 Jan 13 06:56 ls.txt
```

## 三,查看硬盘或指定目录空间大小

* `df` :查看**磁盘空间**占用情况
* `du` :查看**目录**占用的空间大小。

```bash
df -hT #查看硬盘使用情况。
du -h --max-depth=1 floder_name # 查看当前目录下所有文件/文件夹的空间大小
du -h -d 0 . # 查看当前目录空间大小
du -sh foldername # 查看指定目录空间大小
```

![image](images/c62f2296-e82d-4d3c-bb6f-07c1d581fe6b.png)

## 三,总结
利用 `ls -al` 命令查看文件属性及权限,已知了 `Linux` 系统内文件的三种身份(文件拥有者、文件所属群组与其他用户),每种身份都有四种权限(`rwxs`)。可以使用 `chown`, `chgrp`, `chmod` 去修改这些权限与属性。文件是实际含有数据的地方,包括一般文本文件、数据库内容文件、二进制可执行文件(binary program)等等。

* 文件管理,目录的创建、删除、查询、管理: `mkdir` `rm` `mv` `cp`
* 文件的查询和检索命令: `find` `locate`
* 查看文件内容命令:`cat` `vi` `tail more`
* 管道和重定向命令: `;` `|` `&&` `>`

## 四,参考资料
[《Linux基础》](https://linuxtools-rst.readthedocs.io/zh_CN/latest/base/index.html)

================================================
FILE: 1-computer_basics/Linux系统/Linux基础-文件权限与属性.md
================================================
- [一,文件类型](#一文件类型)
  - [1.1,概述](#11概述)
  - [1.2,正规文件(regular file)](#12正规文件regular-file)
  - [1.3,目录(directory)](#13目录directory)
  - [1.4,链接文件(link)](#14链接文件link)
  - [1.5,设备与装置文件(device)](#15设备与装置文件device)
  - [1.6,资料接口文件(sockets):](#16资料接口文件sockets)
  - [1.7,数据输送文件(FIFO, pipe):](#17数据输送文件fifo-pipe)
  - [1.8,文件拓展名](#18文件拓展名)
- [二,文件属性与权限](#二文件属性与权限)
  - [2.1,Linux 文件属性](#21linux-文件属性)
  - [2.2,Linux 文件权限](#22linux-文件权限)
  - [2.3,如何改变文件属性和权限](#23如何改变文件属性和权限)
  - [2.4,文件与目录的权限意义](#24文件与目录的权限意义)
- [三,Linux 文件属性与权限总结](#三linux-文件属性与权限总结)
- [四,参考资料](#四参考资料)

> Linux 系统由 Linux 内核、shell、文件系统和第三方应用软件组成。Linux 文件权限与属性是学习 Linux 系统的一个重要关卡,必须理解这个部分内容的概念。

## 一,文件类型
### 1.1,概述

一个基本概念:任何装置在 Linux 下都是文件,数据沟通的接口也有专属的文件在负责,Linux 的文件种类繁多,常用的是一般文件(`-`)与目录文件(`d`)。

注意:`Linux` 文件类型和文件的文件名所代表的意义是两个不同的概念,在 `linux` 中文件类型与文件扩展名没有关系。它不像 `Windows` 那样是依靠文件后缀名来区分文件类型的,在 `linux` 中文件名只是为了方便操作而的取得名字。

`Linux` 文件类型常见的有:**普通文件、目录、字符设备文件、块设备文件、符号链接文件**等。查看文件类型方法: 可以使用 `ls -al` 命令列出的信息中第一栏十个字符中,第一个字符为文件的类型。

![image](images/2c00f3a1-eb00-4665-b0ed-c8dd415e227a.png)

### 1.2,正规文件(regular file)

就是一般我们在进行存取的类型的文件,在由 `ls -al` 所显示出来的属性方面,第一个字符为 `-`,例如 `-rwxrwxrwx`。另外,依照文件的内容,又大略可以分为:

* 纯文本档(`ASCII`):Linux 系统中最为常见的一种文件类型,称为纯文本是因为文件内容为人类可以直接读取到的数据,例如数字、字母等。
* 二进制文件(`binary`):Linux 系统仅认识且可以执行的二进制文件 binary file,Linux 系统中可执行的文件就是这种类型,例如 cat 就是一个 binary file。
* 数据格式文件(`data`): 有些程序在运作的过程当中会读取某些特定格式的文件,那些特定格式的文件可以被称为数据文件 (`data file`)。举例来说,我们的 Linux 在使用者登入时,都会将登录的数据记录在 `/var/log/wtmp` 文件内,该文件是一个 `data file`,它能够通过 `last` 这个指令读出来,但是使用 `cat` 命令读取时会读出乱码,因为他是属于一种特殊格式的文件。

### 1.3,目录(directory)
第一个属性为 `d`,例如 `drwx------`。

![image](images/6c34e412-890f-4565-8424-998517fb44b2.png)

### 1.4,链接文件(link)
类似 Windows 系统下的快捷键,第一个属性为 `l`,例如 `lrwxrwxrwx`。

### 1.5,设备与装置文件(device)
与系统周边设备及存储相关的一些文件,通常集中在 `/dev` 目录下,一般分为两种:

* 区块(block)设备类型:就是一些储存数据, 以提供系统随机存取的接口设备,比如硬盘设备,第一个属性为 `b`。
* 字符(character)设备文件:一些串行端口的接口设备,例如键盘、鼠标、摄像头等。这些设备的特性是\*\*一次性读取",不能够截断输出,第一个属性为 `c`。

### 1.6,资料接口文件(sockets):
被用于网络上的数据连接了。我们可以启动一个程序来监听客户端的要求, 而客户端就可以透过这个 `socket` 来进行数据的沟通了。第一个属性为 `s`,最常在 `/run` 或 `/tmp` 这些目录中可以看到这种文件类型。

![image](images/cfce666e-0390-441b-bf87-baffb21aa628.png)

### 1.7,数据输送文件(FIFO, pipe):
`FIFO` 也是一种特殊的文件类型,他主要的目的在解决多个程序同时存取一个文件所造成的错误问题。`FIFO` 是 `first-in-first-out` 的缩写。第一个属性为 `p`。

### 1.8,文件拓展名
**注意 Linux 系统的文件是没有所谓的拓展名的**。 一个 Linux 文件能不能被执行,与他的 `ls -al` 展示的文件信息的**第一栏的十个属性**有关, 与文件名根本一点关系也没有,这与 Windows 不同,在Linux 系统下,只要用户的权限有 `x`,文件就可以被执行。拥有了 `x` 权限,表示文件可以被执行,但是只有具有可执行的程序代码文件才能被执行,文本等文件即使有权限也是不能执行成功的。

## 二,文件属性与权限
### 2.1,Linux 文件属性
`ls -al` 命令:列出所有的文件详细的权限与属性 (包含隐藏文件-文件名第一个字符为『 `.` 』的文件)。 `ls -al` 展示的文件属性信息如下:

![image](images/eeb259ca-1f6b-4066-a59e-dec2d8c3f41b.png)

* 第一列代表这个文件的类型与权限(permission);第一列的第一个字符代表这个文件是『目录、 文件或链接文件等等文件类型』:
   * 当为 `d` 则是目录,例如上图文件名为『.config』的那一行;
   * 当为 `-` 则是文件,例如上图文件名为『initial-setup-ks.cfg』那一行;
   * 若是 `l` 则表示为链接文件(link file);
   * 若是 `b` 则表示为装置文件里面的可供储存的接口设备(可随机存取装置);
   * 若是 `c` 则表示为装置文件里面的串行端口设备,例如键盘、鼠标(一次性读取装置)。
* 第二列表示有多少文件名连结到此节点(i-node);
* 第三列表示这个文件(或目录)的『**拥有者账号**』;
* 第四列表示这个文件的**所属群组**;
* 第五列为这个文件的**容量大小**,默认单位为 `bytes`;
* 第六列为这个文件的建档日期或者是最近的修改日期;
* 第七列为这个文件的文件名。

`ls -al` 命令展示的文件属性的七个字段的意义很重要,必须理解和熟记,这是掌握 `Linux` 文件权限与目录管理的基础知识。

### 2.2,Linux 文件权限
Linux 文件的基本权限就有九个,分别是 `owner/group/others` 三种身份各有自己的 `read/write/execute` 权限。在 Linux 中,对于文件的权限(`rwx`),分为三部分,**第一部分是该文件的拥有者所拥有的权限,第二部分是该文件所在用户组的用户所拥有的权限,最后一部分是其他用户所拥有的权限**。
每个文件/文件夹的属性都用 `10` 个字符表示,第一个字符如果是 `d`:表示文件夹,如果是 `-`:表示文件。用(`rwx`)表示文件权限,其中`r`: 可读`(4)`,`w`: 可写`(2)`,`x`: 可执行(`1`)。举例,`drwxr-xrw-` 表示文件夹拥有者拥有可读可写可执行的权限,**用户所在用户组和其他用户无任何权限**,命令的详细解释如下。

* 从第二到第四位 `(rwx) ` 是文件所有者的权限:可读、可写、可执行。
* 从第五到第七位 `(r-x)` 文件夹用户拥有者所在组的权限:可读、可执行。
* 从第八位到第十位 `(rw-)` 其他人对这个文件夹操作的权限.:可读、可写。

`Linux` 系统查看文件/目录权限示例,如下:

```bash
root@5b84c8f05303:/data/project# ls -al
total 16
drwx------ 4 1018 1002 4096 Jul 20 02:59 .
drwx------ 8 1018 1002 4096 Jul 20 02:57 ..
drwx------ 6 1018 1002 4096 Jul 20 02:57 DeepPruner
-rw-r--r-- 1 root root    0 Jul 20 02:59 demo.py
drwx------ 8 1018 1002 4096 Jul 20 02:57 nn_tools-master
```
![image](images/b7d1fdca-5ae0-4d88-8e0d-00dedeb439b6.png)

### 2.3,如何改变文件属性和权限
`Linux/Unix` 是多人多工操作系统,所有的文件皆有拥有者。利用 `chown` 命令可以改变文件的拥有者(用户)和群组,用户可以是用户名或者`用户 ID`,组可以是组名或者`组 ID`。**注意,普通用户不能将自己的文件改变成其他的拥有者,其操作权限一般为管理员(****root**** 用户)**;同时用户必须是已经存在系统中的账号,也就是在 `/etc/passwd` 这个文件中有纪录的用户名称才能改变。

三个常用于群组、拥有者、各种身份的权限之修改的命令,如下所示:

* `chown` : 改变文件的拥有者。如递归子目录修改命令: `chown -R user_name folder/`
* `chgrp` : 改变文件所属群组。
* `chmod` : 改变文件读、写、执行权限(权限分数 `r:4 w:2 x:1`)属性。如增加脚本可执行权限命令: `chmod a+x myscript` 。

添加用户和用户组的命令:`adduser` `groupadd`,其简单用法如下所示:

* `adduser harley`:新建 harley 用户
* `passwd harley`:给 harley 用户设置密码
* `groupadd harley`:新建 harley工作组
* `useradd -g harley harley`:新建 harley 用户并增加到 harley 工作组

**改变所属群组 chgrp/chown/chmod 命令的用法如下**:

```bash
$ chgrp [-R] group dirname/filename  # -R : 进行递归(recursive)的持续变更,亦即连同子目录下的所有文件、目录都更新成为这个群组之意。
$ chown [-R] 账号名称 文件或目录
$ chown [-R] 账号名称:组名 文件或目录
$ chmod [-R] xyz 文件或目录  # xyz : 数字类型的权限属性分数值, 为 rwx 属性数值的相加。
```
`chgrp` 范例:将 `test` 的工作组从 `harley` 改为 `root`(前提是当前用户切换为 `root` 了,否则会提示权限不足的错误):

![image](images/375869b7-c94c-4893-b291-9abe6b22356a.png)

`chmod` 范例:将 `.bashrc` 这个文件所有的权限都设定启用。

```bash
root@17c30d837aba:~# ls -al .bashrc
-rw-r--r-- 1 root root 3479 Jan  8 03:14 .bashrc
root@17c30d837aba:~# chmod 777 .bashrc
root@17c30d837aba:~# ls -al .bashrc
-rwxrwxrwx 1 root root 3479 Jan  8 03:14 .bashrc
```
### 2.4,文件与目录的权限意义
我们可以利用 `ls -al` 命令查看文件属性及权限,已知了 `Linux` 系统内文件的三种身份(拥有者、群组与其他人),每种身份都有三种权限(`rwx`),再使用 `chown`, `chgrp`, `chmod` 去修改这些权限与属性。
**文件是实际含有数据的地方**,包括一般文本文件、数据库内容文件、二进制可执行文件(binary program)等等。因此,权限对于文件来说,他的意义是这样的:

* `r (read)`:可读取此一文件的实际内容,如读取文本文件的文字内容等;
* `w (write)`:可以编辑、新增或者是修改该文件的内容(但不含删除该文件);
* `x (eXecute)`:该文件具有可以被系统执行的权限。Linux 底下, 文件是否能被执行,是由 `x` 这个权限来决定的!跟文件名是没有绝对的关系,即使是能够执行成功的 `.sh` 脚本文件,如果没有 `x` 可执行权限,文件也不能被执行。

**目录主要的内容是记录文件名列表**,文件名与目录有强烈的关连。对一般文件来说,` rwx` 主要是针对『文件的内容』来设计权限,对目录来说, `rwx` 则是针对『目录内的文件名列表』来设计权限。权限对文件和目录重要性的理解汇总成如下表格:

![image](images/06b2a08b-152d-4553-b17a-7c0e3bb9ccea.png)

## 三,Linux 文件属性与权限总结
利用 `ls -al` 命令查看文件属性及权限,已知了 `Linux` 系统内**文件的三种身份**(文件拥有者、文件所属群组与其他用户),**每种身份都有四种权限**(`rwxs`)。可以使用 `chown`, `chgrp`, `chmod` 去修改这些权限与属性。文件是实际含有数据的地方,包括一般文本文件、数据库内容文件、二进制可执行文件(binary program)等等。

## 四,参考资料
《鸟哥的Linux私房菜 基础篇 第四版》

================================================
FILE: 1-computer_basics/Linux系统/Linux基础-文本处理命令.md
================================================
- [概述](#概述)
- [find 文件查找](#find-文件查找)
- [grep 文本搜索](#grep-文本搜索)
- [参考资料](#参考资料)

## 概述

`Linux` 下使用 Shell 处理文本时最常用的工具有: `find、grep、xargs、sort、uniq、tr、cut、paste、wc、sed、awk`。

## find 文件查找

`man` 文档给出的 `find` 命令的一般形式为:
```shell 
find [-H] [-L] [-P] [-D debugopts] [-Olevel] [starting-point...] [expression]
```
这对于大部分人来说都太复杂了,[-H] [-L] [-P] [-D debugopts] [-Olevel] 这几个选项并不常用,`find` 命令的常用形式可以简化为:
```shell
$ find [PATH] [option] [action]
```
**1,根据文件或者正则表达式进行匹配**

```shell
$ find .  # 查找当前目录及子目录下所有文件及文件夹
$ find /data -name "*.txt"  # 在 /data 目录及子目录下查找以 .txt 结尾的文件名
$ find . \( -name "*.txt" -o -name "*.pdf" \)  # 当前目录及子目录下查找所有以 .txt 和 .pdf 结尾的文件
$ find . -maxdepth 1 -type d  # 查找当前目录下所有的子目录
$ find . -maxdepth 1 -regex ".*\.txt$"  # 基于正则表达式匹配当前目录下的所有以 .txt 结尾的文件
./multi_classifynet_infer_ret.txt
./cali_left_img.txt
... 省略
```
**2,根据文件类型进行搜索**

```shell
find . -type 类型参数,f 普通文件,l 符号连接,d 目录,c 字符设备,b 块设备,s 套接字,p Fifo
$ find . -maxdepth 1 -type d  # 查找当前目录下的所有子目录
```

**3,基于目录深度搜索**

```shell
$ find . maxdepth 3 -type f  # 目录向下最大深度限制 3
```

**4,根据文件时间戳进行搜索**

`find . -type -f 时间戳参数`。与时间有关的选项:共有 `-atime`, `-ctime` 与 `-mtime`,以 `-mtime` 说明
+ -mtime n : n 为数字,意义为在 n 天之前的『一天之内』被更改过内容的文件;
+ -mtime +n :列出在 n 天之前(不含 n 天本身)被更改过内容的文件名;
+ -mtime -n :列出在 n 天之内(含 n 天本身)被更改过内容的文件名。
+ -newer file : file 为一个存在的文件,列出比 file 还要新的文件名

```shell
$ find /etc -newer /etc/passwd  # 寻找 /etc 底下的文件,如果文件日期比 /etc/passwd 新就列出
```

**5,与文件权限及名称有关的参数:**

+ `-name filename`:搜寻文件名为 `filename` 的文件。
+ `-size [+-]SIZE`:搜寻比 SIZE 还要大(+)或小(-)的文件。 这个 SIZE 的规格有:`c`: 代表 byte, `k`: 代表 1024 bytes。所以,要找比 50KB还要大的文件,就是 `-size +50k`。
+ `-type TYPE`:搜寻文件的类型为 TYPE 的, 类型主要有:一般正规文件 (f), 装置文件 (b, c), 目录 (d), 连结档 (l), socket (s), 及 FIFO (p) 等属性。
+ `-perm mode`:搜寻文件权限『刚好等于』 `mode` 的文件, 这个 mode 为类似 chmod 的属性值, 举例来说, `-rwxr-xr-x` 的属性为 `755`。
+ `-perm -mode`:搜寻文件权限『必须要全部囊括 mode 的权限』的文件, 举例来说,我们要搜寻 `-rwxr--r--`,亦即 `744` 的文件,使用 `-perm -744`,但是当一个文件的权限为 `-rwxr-xr-x` ,亦即 `755` 时,也会被列出来,因为 `-rwxr-xr-x` 的属性已经包括了` -rwxr--r--` 的属性了。
+ `-perm /mode`:搜寻文件权限『包含任一 `mode` 的权限』的文件, 举例来说,我们搜寻 -rwxr-xr-x ,亦即 -perm /755 时,但一个文件属性为 -rw-------也会被列出来,因为他有 `-rw....` 的属性存在。

范例:

```shell
root@17c30d837aba:/data# find . -maxdepth 1 -perm 777  # 查找当前目录下文件权限刚好等于777 的文件
.
./honggaozhang
./demo.sh
```

## grep 文本搜索

`grep` 支持使用正则表达式搜索文本,并把匹配的行打印出来。`grep` 命令常见用法,在文件中搜索一个单词,命令会返回一个包含 `“match_pattern”` 的文本行:

```shell
grep match_pattern file_name
grep "match_pattern" file_name
```

常用参数:

+ `-o`:只输出匹配的文本行,`-v` 只输出没有匹配的文本行
+ `-c`:统计文件中包含文本的次数: `grep -c “text” filename
+ `-n`:打印匹配的行号
+ `-i`:搜索时忽略大小写
+ `-l`:只打印文件名

```shell
$ grep "class" . -R -n  # 在多级目录中对文本递归搜索(程序员搜代码的最爱)
$ grep -e "class" -e "vitural" file  #  匹配多个模式
```

## 参考资料

+ [【日常小记】linux中强大且常用命令:find、grep](cnblogs.com/skynet/archive/2010/12/25/1916873.html)
+ 鸟哥的Linux私房菜 基础篇 第四版


================================================
FILE: 1-computer_basics/Linux系统/Linux基础-新手必备命令.md
================================================
- [概述](#概述)
- [系统工作](#系统工作)
- [系统状态检测](#系统状态检测)
- [文件与目录管理](#文件与目录管理)
- [文件内容查阅与编辑](#文件内容查阅与编辑)
- [打包压缩与搜索](#打包压缩与搜索)
- [常见命令图解](#常见命令图解)
- [参考资料](#参考资料)

### 概述
常见执行 `Linux` 命令的格式是这样的:

```bash
命令名称 [命令参数] [命令对象]
```
注意,命令名称、命令参数、命令对象之间请用**空格键**分隔。
命令对象一般是指要处理的文件、目录、用户等资源,而命令参数可以用长格式(完整的选项名称),也可以用短格式(单个字母的缩写),两者分别用 `--` 与 `-` 作为前缀。

### 系统工作
1. `echo`:用于在 `shell` 编程中打印 `shell` 变量的值,或者直接输出指定的字符串。
2. `date`:显示或设置系统时间与日期。
3. `reboot`:重新启动正在运行的 Linux 操作系统。
4. `poweroff`:关闭计算机操作系统并且切断系统电源。
5. `wget`:用来从指定的 `URL`下载文件。wget 非常稳定,它在带宽很窄的情况下和不稳定网络中有很强的适应性,如果是由于网络的原因下载失败,wget 会不断的尝试,直到整个文件下载完毕。
6. `ps`:将某个时间点的进程运作情况撷取下来,可以搭配 `kill` 指令随时中断、删除不必要的程序。`ps` 命令可以查看进程运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等情况。使用 `ps -l` 则仅列出与你的操作环境 ( `bash`) 有关的进程而已;使用 `ps aux` 观察系统所有进程。
7. `top`:动态观察进程的变化。
8. `pstree`:`pstree -A` 列出目前系统上面所有的进程树的相关性。
9. `pidof`:查找指定名称的进程的进程号 `id` 号。
10. `kill`:删除执行中的程序或工作,后面必须要加上 `PID` (或者是 `job number`),用法:`killall -signal 指令名称/PID`。`kill` 可将指定的信息送至程序,预设的信息为 `SIGTERM(15)`,可将指定程序终止,若仍无法终止该程序,可使用 `SIGKILL(9)` 信息尝试强制删除程序。程序或工作的编号可利用 `ps` 指令或 `job` 指令查看。 

### 系统状态检测

1. `ifconfig`:于配置和显示 Linux 内核中网络接口的网络参数。
2. `uname`:打印当前系统相关信息(内核版本号、硬件架构、主机名称和操作系统类型等),`-a` 或 `--all`:显示全部的信息。
3. `uptime`:打印系统总共运行了多长时间和系统的平均负载。uptime 命令可以显示的信息显示依次为:现在时间、系统已经运行了多长时间、目前有多少登陆用户、系统在过去的1分钟、5分钟和15分钟内的平均负载。
4. `free`:显示当前系统未使用的和已使用的内存数目,还可以显示被内核使用的内存缓冲区,`-m`:以MB为单位显示内存使用情况。
5. `who`:显示**目前**登录系统的用户信息。执行 `who` 命令可得知目前有那些用户登入系统,单独执行 who命令会列出登入帐号,使用的终端机,登入时间以及从何处登入或正在使用哪个 X 显示器。
6. `last`:显示用户**最近**登录信息。单独执行 last 命令,它会读取 `/var/log/wtmp` 的文件,并把该给文件的内容记录的登入系统的用户名单全部显示出来。
7. `history`:显示指定数目的指令命令,读取历史命令文件中的目录到历史命令缓冲区和将历史命令缓冲区中的目录写入命令文件。
8. `sosreport` 命令:收集并打包诊断和支持数据

### 文件与目录管理

1. `pwd` 命令:以绝对路径的方式显示用户当前工作目录。
2. `cd` 命令:切换工作目录至 `dirname`。 其中 dirName 表示法可为绝对路径或相对路径。`~` 也表示为 `home directory` 的意思,`.`则是表示目前所在的目录,`..` 则表示目前目录位置的上一层目录。
3. `cp, rm, mv`:复制、删除与移动文件或目录 。
4. `ls`:显示文件的文件/目录的名字与相关属性。`-l` 参数:长数据串行出,包含文件的属性与权限等等数据 (常用)。
5. `touch`:有两个功能:一是用于把已存在文件的时间标签更新为系统当前的时间(默认方式),它们的数据将原封不动地保留下来;二是用来创建新的空文件。
6. `file`:用来探测给定文件的类型。file 命令对文件的检查分为文件系统、魔法幻数检查和语言检查 3 个过程

### 文件内容查阅与编辑
文件内容查阅命令如下:

* `cat`:由第一行开始显示文件内容
* `tac`:从最后一行开始显示,可以看出 tac 是 cat 的倒着写!
* `nl`:显示的时候,顺道输出行号!
* `more`:一页一页的显示文件内容
* `less`:与 more 类似,但是比 more 更好的是,他可以往前翻页!
* `head`:只看头几行
* `tail`:只看尾巴几行
* `od`:以二进制的方式读取文件内容!

文件内容查阅命令总结:

* 直接查阅一个文件的内容可以使用 `cat/tac/nl` 这几个命令;
* 需要翻页检视文件内容使用 `more/less` 命令;
* 取出文件前面几行 (`head`) 或取出后面几行 (`tail`)文字的功能使用 `head` 和 `tail` 命令,注意 `head` 与 `tail` 都是以『行』为单位来进行数据撷取的;

文本内容编辑命令如下:

1. `tr`:可以用来删除一段讯息当中的文字,或者是进行文字讯息的替换。
2. `wc`:可以帮我们计算输出的讯息的整体数据。
3. `stat`:用于显示文件的状态信息。`stat` 命令的输出信息比 `ls` 命令的输出信息要更详细
4. `cut`:可以将一段讯息的某一段给他『切』出来,处理的讯息是以『行』为单位。
5. `diff`:在最简单的情况下,比较给定的两个文件的不同。如果使用 `“-”` 代替“文件”参数,则要比较的内容将来自标准输入。`diff` 命令是以逐行的方式,比较文本文件的异同处。如果该命令指定进行目录的比较,则将会比较该目录中具有相同文件名的文件,而不会对其子目录文件进行任何比较操作。

### 打包压缩与搜索
6. `tar`:利用 `tar` 命令可以把一大堆的文件和目录全部打包成一个文件,这对于备份文件或将几个文件组合成为一个文件以便于网络传输是非常有用的。注意打包是指将一大堆文件或目录变成一个总的文件;压缩则是将一个大的文件通过一些压缩算法变成一个小文件。为什么要区分这两个概念呢?这源于 Linux 中很多压缩程序只能针对一个文件进行压缩,这样当你想要压缩一大堆文件时,你得先将这一大堆文件先打成一个包(`tar` 命令),然后再用压缩程序进行压缩(`gzip bzip2` 命令)。
7. `grep`:(global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)一种强大的**文本搜索工具**,能够使用正则表达式搜索文本,并把匹配的行打印出来。`grep` 它是分析一行信息, 若当中有我们所需要的信息,就将该行拿出来。用法:`grep [-acinv] [--color=auto] '搜寻字符串' filename`。
8. `which`:查找命令的完整文件名。用法:`which [-a] command`,`a` : 将所有由 `PATH` 目录中可以找到的指令均列出,而不止第一个被找到的指令名称。`find` 命令是根据`『PATH』`这个环境变量所规范的路径,去搜寻命令的完整文件名。
9. `find`:用来在指定目录下查找文件。任何位于参数之前的字符串都将被视为欲查找的目录名。如果使用该命令时,不设置任何参数,则 `find` 命令将在当前目录下查找子目录与文件。并且将查找到的子目录和文件全部进行显示。用法举例:在 `/home` 目录及其子目录下查找以 `.txt` 结尾的文件名 `find /home -name "*.txt"`。
10. `whereis/locate`:`whereis` 只找系统中某些特定目录底下的文件而已, locate则是利用数据库来搜寻文件名,两者速度更快, 但没有实际搜寻硬盘内的文件系统状态。

### 常见命令图解
这个思维导图记录了常见命令,有利于索引,来源[Linux基础命令(01)【Linux基础命令、ip查看、目录结构、网络映射配置】](https://blog.csdn.net/zkk1973/article/details/80606832)

![image](images/0.3605952030126039.png)

### 参考资料
* [新手linux命令必须掌握命令](https://man.linuxde.net/xinshoumingling)
* 鸟哥的Linux私房菜 基础篇 第四版
* [Linux基础命令(01)【Linux基础命令、ip查看、目录结构、网络映射配置】](https://blog.csdn.net/zkk1973/article/details/80606832)

================================================
FILE: 1-computer_basics/Linux系统/Linux基础-查看cpu、内存和环境等信息.md
================================================
使用 `Linux` 系统的过程中,我们经常需要查看**系统、资源、网络、进程、用户**等方面的信息,查看这些信息的常用命令值得了解和熟悉。

1,**系统**信息查看常用命令如下:

```bash
uname -m && cat /etc/*release # 查询 linux 服务器当前运行环境的操作系统架构及版本
lsb_release -a         # 查看操作系统版本(裁剪版不一定支持) 
cat /etc/os-release    # 查看操作系统版本 (适用于所有的linux,包括Redhat、SuSE、Debian等发行版,但是在debian下要安装lsb)   
cat /proc/cpuinfo      # 查看CPU信息
hostname               # 查看计算机名
lsusb -tv              # 列出所有USB设备
env                    # 查看环境变量
```
2,**资源**信息查看常用命令如下:

```bash
free -h                # 将内存大小以人类可读的形式显示出来(单位GB)
df -h                  # 查看各分区使用情况
df -hT                # 查看硬盘使用情况
du -sh <目录名>        # 查看指定目录的大小
uptime                 # 查看系统运行时间、用户数、负载
```
3,**网络**信息查看常用命令如下

```bash
ifconfig               # 查看所有网络接口的属性
route -n               # 查看路由表
```
4,**进程**信息查看常用命令如下

```bash
ps -ef                 # 查看所有进程
top                    # 实时显示进程状态
```
5,**用户**信息查看常用命令如下

```bash
w                      # 查看活动用户
id <用户名>            # 查看指定用户信息
last                   # 查看用户登录日志
cut -d: -f1 /etc/passwd   # 查看系统所有用户
cut -d: -f1 /etc/group    # 查看系统所有组
crontab -l             # 查看当前用户的计划任务
```
![image](images/b3e99fdb-ebac-467e-861f-5c034fc7881c.png)

更多命令及理解,参考此[链接](https://blog.csdn.net/bluishglc/article/details/41390589)。

6,**查看操作系统、框架、库以及工具版本命令汇总**:

```bash
cat /etc/os-release # 适合所有linux系统,查看操作系统版本,显示信息比较全
cat /etc/issue # 该命令适用于所有Linux系统,显示的版本信息较为简略,只有系统名称和对应版本号。
uname -a # 显示系统详细信息,包括内核版本号、硬件架构等
cat /proc/version # 查看linux 内核
nvcc -V # 查看 cuda 版本
cat /usr/local/cuda/version.txt # 没有安装 nvcc 条件用
cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A 2 # 查看cudnn版本
find / -name NvInferVersion.h && cat /usr/local/cuda-11.0/targets/x86_64-linux/include/NvInferVersion.h | grep NV_TENSORRT # 查看cudnn版本通用
gcc -v # 查看 gcc 版本
cmake -version # 查看 cmake 版本
pkg-config --modversion opencv # 查看 opencv 版本
ffmpeg -version # 查看 ffmpeg 版本
```

`uname` 命令是用于显示系统信息的命令。它通常用于获取操作系统的一些基本信息。`uname -a` 这将显示包括内核名称、版本、硬件架构等在内的详细信息。uname 命令在 Linux 和类 Unix 系统上都是可用的。

### 参考资料
[怎么查看Linux服务器硬件信息,这些命令告诉你](https://zhuanlan.zhihu.com/p/144368206)

================================================
FILE: 1-computer_basics/Linux系统/Linux基础-查看和设置环境变量.md
================================================
- [一,查看环境变量](#一查看环境变量)
- [二,环境变量类型](#二环境变量类型)
- [三,设置环境变量](#三设置环境变量)
- [四,参考资料](#四参考资料)

## 一,查看环境变量

在 Linux中,**环境变量**是一个很重要的概念。**环境变量可以由系统、用户、`Shell` 以及其他程序来设定**,其是保存在变量 `PATH` 中。环境变量是一个可以被赋值的字符串,赋值范围包括数字、文本、文件名、设备以及其他类型的数据。
> 值得一提的是,Linux 系统中环境变量的名称一般都是大写的,这是一种约定俗成的规范。

1,使用 `echo` 命令查看单个环境变量,例如:`echo $PATH`;使用 `env` 查看当前系统定义的所有环境变量;使用 `set` 查看所有本地定义的环境变量。查看 `PATH` 环境的实例如下:

![PATH环境](../../data/images/PATH环境.png)

**使用 `unset` 删除指定的环境变量**,`set` 也可以设置某个环境变量的值。清除环境变量的值用 unset 命令。如果未指定值,则该变量值将被设为 NULL。示例如下:

```shell
$ export TEST="Test..."  # 增加一个环境变量 TEST
$ env|grep TEST  # 此命令有输入,证明环境变量 TEST 已经存在了
TEST=Test...
unset  TEST  # 删除环境变量 TEST
$ env|grep TEST  # 此命令没有输出,证明环境变量 TEST 已经删除
```

2,`C` 程序调用环境变量函数

+ `getenv()`: 返回一个环境变量。
+ `setenv()`:设置一个环境变量。
+ `unsetenv()`: 清除一个环境变量。

## 二,环境变量类型

1,按照变量的生存周期划分,Linux 变量可分为两类:

+ 永久的:需要修改配置文件,变量永久生效。
+ 临时的:使用 `export` 命令声明即可,变量在关闭 `shell` 时失效。

2,按作用的范围分,在 Linux 中的变量,可以分为环境变量和本地变量:

+ 环境变量:相当于全局变量,存在于所有的 Shell 中,具有继承性;
+ 本地变量:相当于局部变量只存在当前 Shell 中,本地变量包含环境变量,非环境变量不具有继承性。

环境变量名称都是**大写**,常用的环境变量意义如下所示。

+ `PATH`:决定了 `shell` 将到哪些目录中寻找命令或程序
+ `HOME`:当前用户主目录
+ `HISTSIZE`:历史记录数
+ `LOGNAME`:当前用户的登录名
+ `HOSTNAME`:指主机的名称
+ `SHELL`:当前用户 Shell 类型
+ `LANGUGE`:语言相关的环境变量,多语言可以修改此环境变量
+ `MAIL`:当前用户的邮件存放目录
+ `PS1`:基本提示符,对于 root 用户是 `#`,对于普通用户是 `$`

## 三,设置环境变量

设置环境有多种用途,比如**配置交叉编译工具链的时候一般需要指定编译工具的路径**,此时就需要设置环境变量。

在 `Linux` 中**设置环境变量**有三种方法:

**1,所有用户永久添加环境变量**: `vi /etc/profile`,在 `/etc/profile` 文件中添加变量。

- `vi /etc/profile` # 通过这种方式,在关闭 xshell后,添加的环境变量不生效
- 文件末尾添加:`export PATH="/usr/local/cuda/lib64:$PATH"`
- `source /etc/profile` # 激活后,环境变量才可永久生效

**2,当前用户永久添加环境变量**: `vi ~/.bash_profile`,在用户目录下的 `~/.bash_profile` 文件中添加变量。

- `vim ~/.bashrc` # 编辑 `.bashrc` 文件,在关闭 `xshell` 后,添加的环境变量仍然生效
- 文件末尾添加: `export PATH="/usr/local/cuda/lib64:$PATH"`
- `source ~/.bashrc` 

**3,临时添加环境变量 `PATH`**: 可通过 `export` 命令,如运行命令 `export PATH=/usr/local/cuda/lib64:$PATH`,将 `/usr/local/cuda/lib64` 目录临时添加到环境变量中。查看是否已经设置好,可用命令 `export` 查看。

前面两种方法可以通过 `echo $PATH` 命令查看终端打印结果是否有添加的路径,来确认已经设置好环境变量。

## 四,参考资料

1. [Linux环境变量总结](https://www.jianshu.com/p/ac2bc0ad3d74)
2. [在Linux里设置环境变量的方法(export PATH)](https://www.cnblogs.com/amboyna/archive/2008/03/08/1096024.html)

================================================
FILE: 1-computer_basics/Linux系统/Linux基础-查看进程命令ps和top.md
================================================
- [1,使用 ps 命令找出 CPU 占用高的进程](#1使用-ps-命令找出-cpu-占用高的进程)
- [2,通过 top 命令定位占用 cpu 高的进程](#2通过-top-命令定位占用-cpu-高的进程)
- [3,htop 系统监控与进程管理软件](#3htop-系统监控与进程管理软件)
- [4,参考资料](#4参考资料)

## 1,使用 ps 命令找出 CPU 占用高的进程

`ps` 是 进程状态 `(process status)` 的缩写,它能显示系统中活跃的/运行中的进程的信息。它提供了当前进程及其详细信息,诸如用户名、用户 `ID`、`CPU` 使用率、内存使用、进程启动日期时间、命令名等等的快照。只打印命令名字而不是命令的绝对路径,以运行下面的格式 ps 命令:

```bash
~$ ps -eo pid,ppid,%mem,%cpu,comm --sort=-%cpu | head
```
运行结果如下:

![image](images/fe9c61ae-8f3a-4e13-9285-1a7c84927fa1.png)

上面命令语句的各部分参数解释:

* `ps`:命令名字
* `-e`:选择所有进程
* `-o`:自定义输出格式
* `–sort=-%cpu`:基于 CPU 使用率对输出结果排序
* `head`:显示结果的前 10 行
* `PID`:进程的 ID
* `PPID`:父进程的 ID
* `%MEM`:进程使用的 RAM 比例
* `%CPU`:进程占用的 CPU 比例
* `Command`:进程名字

## 2,通过 top 命令定位占用 cpu 高的进程
* 查看 `cpu` 占用最高进程(查看前3位):`top`,然后按下 `M`(大写 `M`)。
* 查看内存占用最高进程:`top`,然后按下 `P`(大写 `P` )。
* 可视化查看所有用户所有进程使用情况:`ps axf`。

在所有监控 `Linux` 系统性能的工具中,`Linux` 的 `top` 命令是最好的也是最知名的一个(`htop` 是其升级版)。`top` 命令提供了 `Linux` 系统运行中的进程的动态实时视图。它能显示系统的概览信息和 `Linux` 内核当前管理的进程列表。它显示了大量的系统信息,如 `CPU` 使用、内存使用、交换内存、运行的进程数、目前系统开机时间、系统负载、缓冲区大小、缓存大小、进程 `PID` 等等。默认情况下,`top` 命令的输出结果按 `CPU` 占用进行排序,每 `5` 秒中更新一次结果。

```bash
ps -ef                 # 查看所有进程
top                    # 实时显示进程状态
```
`Linux` 系统下执行 `top` 命令得到以下结果(第一列为进程的 `PID`,第二列为进程所属用户):

![image](images/60434a88-d703-43b8-88c5-ea61dfc4f793.png)

**上图各个参数的意义:**

* `PID`:进程的ID
* `USER`:进程所有者
* `PR`:进程的优先级别,越小越优先被执行
* `NInice`:值
* `VIRT`:进程占用的虚拟内存
* `RES`:进程占用的物理内存
* `SHR`:进程使用的共享内存
* `S`:进程的状态。S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值为负数
* `%CPU`:进程占用CPU的使用率
* `%MEM`:进程使用的物理内存和总内存的百分比
* `TIME+`:该进程启动后占用的总的CPU时间,即占用CPU使用时间的累加值。
* `COMMAND`:进程启动命令名称

通过上图可以看出排在一行的进程 `PID` 2438占用 `cpu` 过高,定位到了进程 `id`。如果只想观察 进程`PID` 2438的 `CPU`和内存以及负载情况,可以使用以下命令:

```bash
top -p 2438
```
结果如下:

![image](images/74dede33-9491-4a06-8bee-aa789daed0df.png)

还可以通过 `top` 命令定位问题进程中每个`线程`占用 `cpu` 情况,如查看进程 `PID` 2438 的**每一个线程**占用 cpu 的情况,使用如下命令:

```bash
top -p 2438 -H
```
结果如下(单线程,所以只显示一行):

![image](images/0f1d4c73-962e-4f86-ac84-83d629539f00.png)

## 3,htop 系统监控与进程管理软件
与 `top` 只提供最消耗资源的进程列表不同,`htop` 提供所有进程的列表,并且使用彩色标识出处理器、`swap` 和内存状态。可以通过 `htop` 查看单个进程的线程,然后按 `<F2>` 来进入 `htop` 的设置菜单。选择“设置”栏下面的“显示选项”,然后开启“树状视图”和“显示自定义线程名”选项。按 `<F10>` 退出设置。

![image](images/f1d7359b-92b4-4074-84d4-0a381aaf4d1b.png)

![image](images/22ce3092-92be-4333-ac84-38f7ab7f2730.png)

## 4,参考资料
[线上linux系统故障排查之一:CPU使用率过高](https://www.jianshu.com/p/6d573e42310a)

================================================
FILE: 1-computer_basics/README.md
================================================
## 前言

本目录内容旨在分享常用编程开发效率工具、操作系统、`Linux` 系统等方向知识总结和笔记。

## 效率工具

- [git命令学习笔记](./效率工具/git常用命令总结.md)
- [ubuntu16.04安装mmdetection库](./效率工具/ubuntu16.04安装mmdetection库.md)
- [Docker基础和常用命令](./效率工具/Docker基础和常用命令.md)

## 计算机系统

* [深入理解计算机系统-第1章计算机系统漫游笔记](操作系统/深入理解计算机系统-第1章计算机系统漫游笔记.md)
* [深入理解计算机系统-第2章信息的表示和处理](操作系统/深入理解计算机系统-第2章信息的表示和处理.md)
* [深入理解计算机系统-第3章程序的机器级表示](操作系统/深入理解计算机系统-第3章程序的机器级表示.md)
  
## Linux 系统

### Linux 基础

* [Linux 基础-学会使用命令帮助](Linux系统/Linux基础-学会使用命令帮助.md)
* [Linux 基础-新手必备命令](Linux系统/Linux基础-新手必备命令.md)
* [Linux 基础-文件权限与属性](Linux系统/Linux基础-文件权限与属性.md)
* [Linux 基础-文件及目录管理](Linux系统/Linux基础-文件及目录管理.md)
* [Linux 基础-文本处理命令](Linux系统/Linux基础-文本处理命令.md)
* [Linux 基础-查看cpu、内存和环境等信息](Linux系统/Linux基础-查看cpu、内存和环境等信息.md)
* [Linux 基础-查看进程命令ps和top](Linux系统/Linux基础-查看进程命令ps和top.md)
* [Linux 基础-查看和设置环境变量](Linux系统/Linux基础-查看和设置环境变量.md)

### Linux 进阶

- [ ] 正则表达式与文件格式处理
- [ ] shell scripts 学习

## 参考资料

- 《鸟哥私房菜-基础篇》
- 《深入理解计算机系统第三版》
- [Docker-从入门到实践](https://yeasy.gitbook.io/docker_practice/ "Docker-从入门到实践")
- [Docker教程](https://haicoder.net/docker/docker-course.html "Docker教程")

================================================
FILE: 1-computer_basics/操作系统/深入理解计算机系统-第1章计算机系统漫游笔记.md
================================================
- [1,信息就是位+上下文](#1信息就是位上下文)
- [2,程序被其他程序翻译成不同格式](#2程序被其他程序翻译成不同格式)
- [3,了解编译器如何工作是有大有益处的](#3了解编译器如何工作是有大有益处的)
- [4,处理器读取存在内存中的指令](#4处理器读取存在内存中的指令)
- [5,高速缓冲至关重要](#5高速缓冲至关重要)
- [6,存储设备形成层次结构](#6存储设备形成层次结构)
- [7,操作系统管理硬件](#7操作系统管理硬件)
  - [7.1,进程](#71进程)
  - [7.2,线程](#72线程)
  - [7.3,虚拟内存](#73虚拟内存)
  - [7.4,文件](#74文件)
- [8,系统之间利用网络通信](#8系统之间利用网络通信)
- [9,重要主题](#9重要主题)
  - [9.1,Amdahl 定律](#91amdahl-定律)
  - [9.2,并发与并行](#92并发与并行)
  - [9.4,计算机系统中抽象的重要性](#94计算机系统中抽象的重要性)
- [参考资料](#参考资料)

## 1,信息就是位+上下文
* 计算机系统是由硬件和系统软件组成,它们共同工作来运行应用程序。
* C 语言是系统级编程的首选,同时它也非常实用于应用级程序的编写。

## 2,程序被其他程序翻译成不同格式
以`hello` 程序为例来解释程序的生命周期,`hello.c` 文件代码如下:

```cpp
#include <stdio.h>

int main()
{
    printf("hello world\n");
    return 0;
}
```

为了在系统上运行 `hello.c` 程序,文件中的每条 C 语言都必须被其他程序转化为一系列的**低级机器语言指令**。然后这些指令按照一种称为**可执行目标程序**的格式打包好,并以二进制磁盘文件的形式保存。目标程序也称为**可执行目标文件**。

在 Linux 系统中,`GCC` 编译器将源程序文件 hello.c 翻译(编译)为目标文件 hello 的过程分为四个阶段,如下图所示。执行这四个阶段(预处理器、编译器、汇编器和链接器)的程序一起构成了编译器系统(compilation system)。

![image](images/4q85EDFxENMJTUnhcQydhZR73dgCwmC-YCm8qgzRTiw.png)

## 3,了解编译器如何工作是有大有益处的
1. **优化程序性能**。了解一些机器代码(汇编代码)以及编译器将不同的 C/C++语句转化为机器代码的方式。比如一个函数调用的开销有多大?while 循环比 for 循环更有效吗?指针引用比数组索引更有效吗等?
2. **理解链接时出现的错误**。比如静态库和动态库的区别,静态变量和全局变量的区别等。
3. **避免安全漏洞**。

## 4,处理器读取存在内存中的指令
典型系统的硬件组织构成: 总线、`I/O` 设备、主存(动态随机存取存取器 `DRAM`)、处理器。

> 主存是临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。从逻辑上说,存储器是一个线性的字节数组,每个字节都有其唯一的地址(数组索引),这些地址是从零开始的。

将 hello 程序输出的字符串从存储器写到显示器的过程如下图所示。

![image](images/jNMMhByUJX6mgnQXa06ieKCNof94Uh4cExDepVFSI3Q.png)

## 5,高速缓冲至关重要
针对处理器与主存之间读取数据的差异,处理器系统设计者采用了更小更快的存储设备,称为高速缓冲器(cache memory,简称 cache 或高速缓冲),作为暂时的集结区域,存放处理器近期可能会需要的信息。

![image](images/ImONa4Ylt2sGRON7xzuR4GPLsH0eNnPp2DdynYWZJwY.png)

## 6,存储设备形成层次结构
在处理器和一个较大较慢的设备(例如主存)之间插入一个更小更快的存储设备(例如高速缓冲)的想法已经成为一个普遍的观念。实际上,每个计算机系统中的存储设备都被组织成了一个**存储器层次结构**,如图1-9所示。在这个层次结构中,从上至下,设备的访问速度越来越慢、容量越来越大,并且每字节的成本也越来越低。

![image](images/5kUNCag6pjUpDjf8l64j0yLLtWY8y06CCLQoVlVs7s0.png)

## 7,操作系统管理硬件
所有应用程序对硬件的操作尝试都必须通过操作系统,操作系统有两个基本功能:

1. 防止硬件被失控的应用程序滥用;
2. 向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备。

操作系统通过几个基本的抽象概念(**进程、虚拟内存和文件**)来实现这两个功能。文件是对 I/O 设备的抽象表示,虚拟内存是对主存和磁盘 I/O 设备的抽象表示,进程则是对处理器、主存和 I/O 设备的抽象表示。

### 7.1,进程
**进程**是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,每个进程都好像在独占地使用硬件。而**并发运行**,则是说一个进程的指令和另一个进程的指令是交错之行的。在大多数系统中,需要运行的进程数是可以多于它们的 `CPU` 个数的。无论在单核还是多核系统中,一个 `CPU` 看上去都像是在**并发**地执行多个进程,这实际是通过处理器在进程间切换来实现的,操作系统实现这种交错执行的机制成为**上下文切换**。

操作系统会保持跟踪进程运行所需的所有状态信息,这种状态信息称为上下文,其包括多种信息,比如 `PC` 和寄存器文件的当前值,以及主存的内容。在任何一个时刻,但处理器系统都只能执行一个进程的代码。当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进行**上下文切换,**即保存当前进程的上下文、恢复进程的上下文,然后将控制权传递到新进程。图1-12展示了示例 hello 程序运行场景的基本理念。

![image](images/PMxiwceVcvQ8THubjichY3PkaA4WqwpGK4yh-jL24n8.png)

### 7.2,线程
尽管我们直观认为一个进程只有单一的控制流,但在现代计算机系统中,一个进程实际上可以由多个称为**线程**的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。由于网络服务器对并行处理的需求,线程成为越来越重要的编程模型,因为多线程之间比多进程之间更容易共享数据且更高效。

### 7.3,虚拟内存
**虚拟内存**是一个抽象概念,它为每个进程提供了一个假象,即每个进程都在独占地使用主存,每个进程看到的内存都是一致的,称为**虚拟地址空间**。图 1-13 所示的是 Linux 进程的虚拟地址空间。在 Linux 中,地址空间最上面的区域是保留给操作系统中代码和数据的,这对所有进程来说都是一样。地址空间的底部区域存放用户进程定义的代码和数据。注意,图中的地址从下往上增大的。

![image](images/CNLtOP_LRbWvOvz83RuknMO2cFOCFDQX76sHKRMIerA.png)

每个进程看到的虚拟地址空间由大量准确定义的区构成,每个区都有专门的功能,进程的虚拟地址空间意义如下。

* **程序代码和数据**。对所有进程来说,代码是从同一固定地址开始,进接着的是和全局变量和相对应的数据位置,代码和数据区是直接按照可执行目标文件的内容初始化的。
* **堆**。代码和数据区后紧随着的是运行时堆。代码和数据区在进程一开始运行时就被指定了大小,与此不同,当调用 `malloc` 和 `free` 这样的 C 标准库函数时,堆可以在运行时动态地拓展和收缩。
* **共享库**。地址空间的中间部分是一块用来存放像 C 标准库和数学库这样的共享库的代码和数据的区域。
* **栈**。位于用户虚拟地址空间顶部的是**用户栈**,编译器用它来实现函数调用。和堆类似,用户栈在程序执行期间可以动态地拓展和收缩,调用函数栈则增长,从一个函数返回,栈则收缩。
* **内核虚拟内存**。地址空间顶部的区域是为操作系统内核保留的。

虚拟内存的运作需要硬件和操作系统软件之间精密复杂的交互,包括对处理器生成的每个地址的硬件翻译,基本思想是把一个进程虚拟内存的内容存在在磁盘上,然后用主存作为磁盘的高速缓冲。

### 7.4,文件
文件就是字节序列,每个 I/O 设备包括磁盘、键盘、显示器,甚至网络都可以看成文件,系统中所有输入输出都是通过使用一小组称为 Unix I/O 的系统函数调用读写文件来实现的。

## 8,系统之间利用网络通信
现代系统通过网络将单个计算机系统连接在一起。

![image](images/TpNXGmzm8PkOUDCgf5Zyyac0QuLMa-O1tL4DQ0TuE-g.png)

## 9,重要主题
### 9.1,Amdahl 定律
该定律的主要思想就是,当我们对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。

![image](images/t18gr2bkMba5q2g5z9rRrjWcjBARJ_la_zU8Z2VX06M.png)

### 9.2,并发与并行
我们用术语并发(`concurrency`)是一个通用的概念,指一个同时具有多个活动的系统;而术语并行(`parallelism`)指的是用并发来是一个系统运行得更快。并行可以在计算机系统的多个抽象层次上运用,这里按照系统层次结构中由高到低的顺序描述三个层次。

**1,线程级并发**

超线程,有时称为**同时多线程**(simultaneous multi-threading),是一项允许一个 `CPU` 执行多个控制流的技术。它涉及 CPU 某些硬件有多个备份,比如程序计数器和寄存器文件,而其他硬件部分只有一份,比如执行浮点算术运算的单元。常规的处理器需要大约 20000 个时钟周期做不同线程间的转换,而超线程的处理器可以在单个周期的基础上决定要执行哪一个线程。

多处理器的使用可以从两方面提高系统性能,首先,它减少了在执行多个任务时模拟并发的需要;其次,它可以使应用程序运行得更快,前提是程序以多线程方式编写。

**2,指令级并行**

在较低的抽象层次上,现代处理器可以同时执行多条指令的属性称为**指令级并行。**如果处理器可以达到比一个周期一条指令更快的执行速率,就称为**超标量**(super-scalar)处理器,大多数现代处理器都支持超标量操作。

**3,单指令、多数据并行**

在最低层次中,许多现代处理器拥有特殊的硬件,允许一条指令产生多个可以并行执行的操作,这种方式称为**单指令、多数据**,即 `SIMD` 并行。

### 9.4,计算机系统中抽象的重要性
前面我们介绍了计算机系统使用的几个抽象,如图 1-18 所示,在处理器里,指令集架构提供了对实际处理器的抽象。

![image](images/mUJJs9almEPtVReVTMkTpLWDnrPPkiAGN5lcJ7ok94k.png)

操作系统中有四个抽象:**文件是对 I/O 设备的抽象,虚拟内存时对程序存储器的抽象,进程是对一个正在运行的程序的抽象,虚拟机提供对整个计算机的抽象,包括操作系统、处理器和程序**。
## 参考资料
* 《深入理解操作系统第三版-第1章》

================================================
FILE: 1-computer_basics/操作系统/深入理解计算机系统-第2章信息的表示和处理笔记.md
================================================
- [1,信息存储](#1信息存储)
  - [1.1,十六进制表示法](#11十六进制表示法)
  - [1.2,字数据大小](#12字数据大小)
  - [1.3,寻址和字节顺序](#13寻址和字节顺序)
- [2,整数表示](#2整数表示)
- [3,整数运算](#3整数运算)
- [4,浮点数](#4浮点数)
  - [4.1,二进制小数](#41二进制小数)
  - [4.2,IEEE 浮点表示](#42ieee-浮点表示)
  - [4.3,浮点数的规格化](#43浮点数的规格化)
  - [4.4,数字示例](#44数字示例)
  - [4.5,浮点数的数值范围](#45浮点数的数值范围)
- [参考资料](#参考资料)

> 关于程序的结构和执行,我们需要考虑机器指令如何操作整数和实数数据,以及编译器如何将 C 程序翻译成这样的指令。

现代计算机存储和处理信息是用二进制(二值信号)表示的,因为二值信号更容易表示、存储和传输,如导线上的高电压和低电压。

## 1,信息存储
大多数计算机使用字节(`byte`),作为最小的可寻址的内存单位,而不是访问内存中单独的位。机器级程序将内存视为一个非常大的字节数组,称为**虚拟内存**(`virtual memory`),内存中的每个字节都由一个唯一的数字来表示,称为它的地址(`address`),所有可能地址的集合称为**虚拟地址空间**(`virtual address space`)。简而言之,这个虚拟地址空间只是一个展现给机器级程序的概念性映像,实际的实现是将动态随机访问存储器(`DRAM`)、闪存、磁盘存储器、特殊硬件和操作系统软件结合起来,为程序提供一个看上去统一的字节数组。

### 1.1,十六进制表示法
一个字节由 8 位组成,在二进制表示法中,它的值域是 $00000000_2\sim 11111111_{2}$,如果看成十进制整数,它的值域则是 $0_{10}\sim 255_{10}$。在 C 语言中,以 0x 或 0X 开头的数字常量被认为是十六进制的值。

![image](images/du15XqDyxT2wKlQmZyDKTIbP51mCrm3AHOUVdkJWhkc.png)

### 1.2,字数据大小
每台计算机都有一个字长(`word size`),说明指针数据的标称大小(`nominal size`),字长决定的最重要的系统参数就是虚拟地址空间的最大大小,即对于一个字长$w$位的机器而言,虚拟地址的范围为 $0\sim 2^{w}-1$,程序最多访问 $2^{w}$个字节。

### 1.3,寻址和字节顺序
**对于跨越多个字节的程序对象,我们必须建立两个规则: 这个对象的地址是什么,以及内存中如何排列这些字节**。对象的存储有两个通用的规则:

* **小端法**(little endian): 在内存中选择按照从变量最低有效字节(更小数值的那部分)到最高有效字节的顺序存储对象,即最低有效字节在最前面的方式。
* **大端法**(big endian): 在内存中选择按照从最高有效字节到最低有效字节的顺序存储对象,即最高有效字节在最前面的方式。

如下图所示,对于变量 $x$,假设其存储地址为 0x100,其十六进制值为 0x1234567,如果存储方式是小端法,那么内存中的存储顺序为 67、45、23、01(对应内存地址是0x100, 0x101, 0x102, 0x103),如果存储方式是大端法,那么内存中的存储顺序为 01、23、45、67。

![image](images/ymZPakWiwkFaKtCt9yb5GwYbw2cl2kt66aKr1pSVoQ8.png)

## 2,整数表示

略

## 3,整数运算

略

## 4,浮点数

IEEE 754 标准用来表示计算机系统中的浮点数定义,即浮点数规则都遵从 IEEE 754 标准。

### 4.1,二进制小数
理解浮点数的第一步是考虑含有**小数值**的二进制数字,其表示方法如下:
$$b = \sum_{i=-n}^{m} 2^{i} \times b_{i}$$

符号 . 现在变成了二进制的点,**点左边的位的权是 2 的正幂,点右边的位的权是 2 的负幂**。小数的二进制幂表示如下图所示。

![image](images/t7jsbVVmMRGIsiXX6IzRg339QPyKOXbA0aQBfXzG5Q8.png)

### 4.2,IEEE 浮点表示
定点表示法不能有效地表示非常大的数字。IEEE 标准 754,浮点表示用 $V = (-1)^s \times M \times 2^E$表示一个数:

![image](images/0OjCdS_0w64dJv3wcJrcn2xHhZq6sc3xNLVqOR2BAIA.png)

在 IEEE 754 标准中浮点数由三部分组成:**符号位(sign bit),有偏指数(biased exponent),小数(fraction)**。浮点数分为两种,单精度浮点数(single precision)和双精度浮点数(double precision),它们两个所占的位数不同。

* 在单精度浮点格式(C 语言的 `float`)中,符号位,`8` 位指数,`23` 位有效数。
* 在双精度浮点格式(C 语言的 `double`)中,符号位,`11` 位指数,`52` 位有效数。

![image](images/iWjyenH48I7GpLDjQAqc2yVQhT-7F5H2W8YBbErI86c.png)

给定位表示,根据 `exp` 的值,被编码的浮点数可以分成三种不同的情况(最后一种有两个变种)。图2-33说明了对单精度格式的情况。

![image](images/899dlOzwfvOmBpzyX5VUJ2CGNJp-t115-mGKJkNwqDg.png)

**规格化的值产生的指数的取值范围,对于单精度是 -126~127,而对于双精度是 -1022\~1023**。

![image](images/KtBlfW18psEh0gDwf8E-uncsC0rDzh0Q9N-QDvp8cc8.png)

### 4.3,浮点数的规格化

若不对浮点数的表示作出明确规定,**同一个浮点数的表示就不是唯一的**。例如 $(1.75)_{10}$可以表示成 $1.11\times 2^0$,$0.111\times 2^1$,$0.0111\times 2^2$等多种形式。当尾数不为 0 时,**尾数域的最高有效位为 1**,这称为浮点数的规格化。否则,以修改阶码同时左右移动小数点位置的办法,使其成为规格化数的形式。

**1,单精度浮点数真值**

> **对于浮点数的规格化的值,其指数的偏差 Bias 是其可能值的一半**: $2^{k-1}-1$(单精度是127,双精度是 1023)。 也就是说,用存储的指数减去此偏差就得到了实际指数。 如果存储的指数小于此偏差,则实际为负指数。

IEEE754 标准中,一个规格化的 32 位浮点数 $x$ 的真值表示为:

$$x = (-1)^{S}\times (1.M)\times 2^{e}, e = E-127, e\in [-126, 127]$$

其中尾数域值是 1.M。因为规格化的浮点数的尾数域最左位总是 1,所以这一位不予存储,默认其隐藏在小数点的左边。在计算指数 e 时,对阶码 E 的计算采用原码的计算方式,**因此 32 位浮点数的 8 bits 的阶码 E 的取值范围是 0 到 255**。其中当 E 为全 0 或者全 1 时,是 IEEE754 规定的特殊情况。去除 0 和 255 这两种特殊情况,那么指数 $e$ 的取值范围就是 $1-127=-126$ 到 $254-127=127$。

因为 $2^{127}$ 大约等于 $10^{38}$,所以单精度的实际极限为 $10^{38}$ ($e^{38}$)。

**2,双精度浮点数真值**

64 位的浮点数中符号为 1 位,阶码域为 11 位,尾数域为 52 位,**指数偏移值是 1023(指数偏差)**。因此规格化的 64 位浮点数 x 的真值是:

$$x = (-1)^{S}\times (1.M)\times 2^{e}, e = E-1023, e\in [-1022,1023]$$

因为 $2^{1023}$ 大约等于 $10^{308}$,所以单精度的实际极限为 $10^{308}$($e^{308}$)。
### 4.4,数字示例
图 2-34 展示了一组数值,它们可以用假定的 6 位格式来表示,有 $k=3$的阶码位和 $n=2$的尾数位,偏置位是 $2^{3-1}-1 = 3$。

![image](images/F8ISuUEs_e2MjSGeLoI0MMJ_MkF1-l97H9dFGHPp2Js.png)

![image](images/M8pGEmsRv5XkQscrjour5-1xZnOag3Ar9Jav9wMTdW0.png)

### 4.5,浮点数的数值范围

下图 2-36 展示了单精度和双精度的浮点数取值范围。

![image](images/gFzfKRqy4vYGCPctO0JWKh4W0c-PxkEz0aJUYsg0gws.png)
## 参考资料
*  《深入理解操作系统第三版-第2章》
* [IEEE 浮点表示形式](https://learn.microsoft.com/zh-cn/cpp/build/ieee-floating-point-representation?view=msvc-170)
* [IEEE Standard 754 Floating Point Numbers](https://steve.hollasch.net/cgindex/coding/ieeefloat.html)



================================================
FILE: 1-computer_basics/操作系统/深入理解计算机系统-第3章程序的机器级表示笔记.md
================================================

- [前言](#前言)
- [1,历史观点](#1历史观点)
- [2,程序编码](#2程序编码)
- [3,数据格式](#3数据格式)
- [4,访问信息指令](#4访问信息指令)
  - [4.1,操作数指示符](#41操作数指示符)
  - [4.2,数据传送指令](#42数据传送指令)
  - [4.3,数据传送示例](#43数据传送示例)
  - [4.4,压入和弹出数据](#44压入和弹出数据)
- [5,算术和逻辑操作指令](#5算术和逻辑操作指令)
  - [5.1,加载有效地址](#51加载有效地址)
  - [5.2,一元和二元操作](#52一元和二元操作)
  - [5.3,移位操作](#53移位操作)
  - [5.4,总结](#54总结)
- [6,控制指令](#6控制指令)
  - [6.1,条件码](#61条件码)
  - [6.2,访问条件码](#62访问条件码)
  - [6.3,跳转指令](#63跳转指令)
  - [6.4,跳转指令的编码](#64跳转指令的编码)
  - [6.5,用条件控制来实现条件分支](#65用条件控制来实现条件分支)
  - [6.6,用条件传送来实现条件分支](#66用条件传送来实现条件分支)
  - [6.7,循环](#67循环)
- [7,过程](#7过程)
- [8,数组的分配和访问](#8数组的分配和访问)
  - [8.1,基本原则](#81基本原则)
  - [8.2,指针运算](#82指针运算)
  - [8.3,嵌套到数组(多维数组)](#83嵌套到数组多维数组)
- [9,异数的数据结构](#9异数的数据结构)
- [10,在机器级程序中将控制与数据结合起来](#10在机器级程序中将控制与数据结合起来)
- [11,浮点代码](#11浮点代码)
- [参考资料](#参考资料)

## 前言

计算机执行机器代码,用字节序列编码低级的操作,包括处理数据、管理内存、读写存储设备上的数据,以及利用网络通信。编译器基于编程语言的规则、目标机器的指令集和操作系统遵循的惯例,经过一系列阶段生成机器代码。

在本章中,我们将详细学习一种特别的汇编语言,了解如何将 C 程序编译成这种形式的机器代码。阅读编译器产生的汇编代码,需要具备的技能不同于手工编写汇编代码,我们必须了解典型的编译器在将 C 程序结构变换成机器代码时所做的转换。相对于 C 代码表示的计算操作,优化编译器能够重新排列执行顺序,消除不必要的计算,用快速操作替换慢速操作,甚至将递归计算变换成迭代计算。但是源代码与对应的汇编代码的对应关系通常不太容易理解,因为这是一种逆向工程(reverse engineering)-通过研究系统和逆向工作。

本章内容会涉及到 x86-64 汇编级指令代码。

## 1,历史观点
`Intel` 处理器系列俗称 `x86`,经历了一个长期的、不断进化的发展过程。

## 2,程序编码
`Linux` 系统默认的编译器时 `GCC C` 编译器。编译器选项 `-Og` 会指示编译器使用会生成符合原始 `C` 代码整体结构的机器代码的优化等级,通常使用 `-O1` 或 `-O2` 选项。

`x86-64` 的机器代码和原始的 C 代码差别非常大,一些通常对 C 语言程序员隐藏的处理器状态都是可见的:

* **程序计数器**(PC,在 x86-64 中用 `%rip` 表示)给出将要执行的下一条指令在内存中的地址。
* **整数寄存器文件**包含 16 个命名的位置,分别存储 64 位的值。这些寄存器可以存储地址(对应于C 语言的指针)或整数数据。有的寄存器被用来记录默写重要的程序状态,有的寄存器保存临时数据,如过程的参数和局部变量,以及函数的返回值。
* **条件码寄存器**保存着最近执行的算术或逻辑指令的状态信息。它们用来实现控制或数据流中的条件变化,如用来实现 if 和 else 语句。
* 一组向量寄存器可以存放一个或多个整数或浮点数值。

## 3,数据格式
`C` 语言数据类型在 `x86-64` 中的大小。

![image](images/QSQYzFHHj7WPK5uGgWAQdgAaelr3AbiRG3hxWMKHrJs.png)

浮点数主要有两种形式:单精度(4 字节)值,对应于 C 语言数据类型 float,双精度(8 字节)值,对应于 C 语言数据类型 double。如上图所示,大多数 GCC 生成的汇编代码指令都有一个字符的后缀,表明操作数的大小,例如,数据传送指令有四个变种: `movb`(传送字节)、`movw`(传送字)、`movl`(传送双字)和`movq`(传送四字)。

## 4,访问信息指令
一个 `x86-64` 的中央处理单元(`CPU`)包含一组 `16` 个存储 `64` 位值的**通用目的寄存器,**这些寄存器用来**存储整数数据和指针**。下图显示了这 `16` 个寄存器,它们的名字都以 `%r` 开头,后面还跟着一些不同的命名规则的名字。最初的 8086 中有 8 个 16 位的寄存器,即图 3-2 中的 %ax 到 %bp,每个寄存器都有特殊的用途,它们的名字就反映了这些不同的用途。拓展到 IA32 架构时,这些寄存器也拓展成 32 位寄存器,标号从 %eax 到 %ebp。**拓展到 x86-64 后,用来的 8 个寄存器拓展成 64 位,标号从 ****%rax**** 到 ****%rbp****,除此之外,还增加了 8 个寄存器,标号从 ****%r8**** 到 ****%r15****。**

![image](images/qnsVUQQ249XWv70KXYUBmTdLYPGd6iQub56NJ-d9fUM.png)

### 4.1,操作数指示符
大多数指令有一个或多个操作数(operand),指示出执行一个操作中要使用的源数据值,以及防止结果的目的位置。不同操作数被分为三种类型。

* 立即数(`immediate`),用来表示常数值。
* 寄存器(`register`)
* 内存引用: 它会根据计算出来的地址访问某个内存位置。

如图 3-3 所示,有多重不同的**寻址模式**,允许不同形式的内存引用。

![image](images/wJpUN8D2GNjAICfQftGbUvXOux5B-KNrmz4KQi-chhE.png)

### 4.2,数据传送指令
最频繁使用的指令是**将数据从一个位置复制到另一个位置**的指令。图 3-4 列出的最简单形式的数据传送指令-MOV 类: **把数据从源位置复制到目的位置,不做任何变化**。

![image](images/yPe9VIy0F1qNvzMcBmQILriZUcc4c-fz4GrYm0hjiQE.png)

简单的数据传送指令示例代码如下。(记住,第一个是源操作数,第二个是目的操作数)

![image](images/WRpsNl_ap3Nm68Gug5LN4nByXA4yQ5hZRRuzKXGVipo.png)

### 4.3,数据传送示例
![image](images/awQ9HQwF_e_cXZkesyt85CAVtwPbALZTncgEobBiO4E.png)

### 4.4,压入和弹出数据
栈和队列都是一种"操作受限"的线性表(逻辑结构),只允许在一端插入和删除数据;**栈的特性是先进后出,队列是先进先出**。**栈**在处理函数调用过程中很重要,通过 push 指令把数据压入栈中,通过 pop 指令删除数据。

**栈可以可以通过数组实现**,总是从数组的一端插入和删除元素,这一端称为**栈顶,栈顶元素的地址是所有栈中元素地址中最低的,栈指针 ****%rsp**** 保存着站定元素的地址。**入栈和出栈汇编指令描述如下,栈操作指令都只有一个操作数-压入的数据源和弹出的栈顶数据。

![image](images/--PPoOxo2HA8iiV2CMHdheiPHqZcVdjfTNthDnsP7fA.png)

将一个四字值压入栈中,分为两步,**首先先将栈指针减 8,然后将值写到新的栈顶地址**,因此 `pushq` 指令等价于下面两条指令:

```bash
subq $8,%rsp Decrement stack pointer
moq %rbp,(%rsp)
```
![image](images/Zlw0SByXxHNCHycSo-Eg66b7nLQ1LJfjUWdkvIdnu7o.png)

## 5,算术和逻辑操作指令
图 3-10 列出了 x86-64 的一些整数和逻辑操作。和访问信息指令一样,算术和逻辑操作指令类也有各种带不同大小操作数的变种。例如,指令类 ADD 由四条加法指令组成: addb、addw、addl 和 addq,分别是字节加法、字加法、双字加法和四字加法。**算术和逻辑操作指令分为四组:加载有效地址、一元操作、二元操作和移位。二元操作数有两个操作数,而一元操作数有一个操作数。**

![image](images/AkNj9Ck9ql43MdAiTXagNpHEmRBcRBdVLaEjNou_okU.png)

### 5.1,加载有效地址
加载有效地址(load effective address)指令 leaq 实际上是 movq 指令的变形。leaq 指令可以简洁地描述普通的算术操作,如果寄存器 %rdx 的值为 x,那么指令 `leaq 7(%rdx, %rdx, 4),%rax` 将设置寄存器 `%rax` 的值为 `5x+7`。目的操作数必须是一个寄存器。

![image](images/mSOUx9Uu2iF_pkpzqSBv2B9xc_WRW5O57imc650fmyA.png)

### 5.2,一元和二元操作
对于二元操作指令,第一个操作数可以是立即数、寄存器或内存位置,第二个操作可以是寄存器或内存位置。

### 5.3,移位操作
移位操作指令,先给出移位量,第二项给出要移位的数,移位量可以是立即数,或者在单字节寄存器 `%cl` 中,移位量是由 `%c1` 寄存器的低 `m` 位决定的。例如当寄存器 `%c1` 的十六进制位 0xFF 时,指令 `salb` 会移 7 位,`salw` 会移 15 位,`sall` 会移 31 位,`salq` 会移 63 位。

### 5.4,总结
![image](images/OqeuQi8kL1BP46nwSbcKdJ0mqtEvna5Wi-55r5F-q1Y.png)

## 6,控制指令
前面的两类指令都是**直线代码**行为,也就是指令一条接着一条顺序地执行。C 语言中的某些结构,比如条件语句、循环语句和分支语句,要求有条件的执行,根据数据测试的结果来决定操作执行的顺序。

### 6.1,条件码
除了整数寄存器,CPU 还维护着一组单个位的条件码(condition code)寄存器,它们描述了最近的算术或逻辑操作的属性,可以**通过检测条件码寄存器来执行条件分支指令**。最常用的条件码有:

* `CF`: 进位标志。最近的操作使最高位产生了进位,可用来检查无符号操作的溢出。
* `ZF`: 零标志。最近的操作得出的结果为 0。
* `SF`: 符号标志位。最近的操作得到的结果为负数。
* `OF`: 溢出标志。最近的操作导致一个补码已出-正溢出或负溢出。

除了图 3-10 的整数算术操作指令会设置条件码,还有两类指令 `CMP` 和 `TEST` ,**但它们只设置条件码而不改变任何其他寄存器。**如下图 3-13 所示,`CMP` 指令根据两个操作数之差来设置条件码。

![image](images/K6PjfUTdwF9IUo79rJ-29LTz7siBHlx7hpfBPPisRbY.png)

### 6.2,访问条件码
条件码通常不会直接读取,常使用方法有三种:

1. 可以根据条件码的某种组合,将一个字节设置为 0 或者1。
2. 可以有条件跳转到程序的某个其他的部分。
3. 可以有条件地传送数据。

![image](images/yy0TyW1D5tbPw6VbHTwCtjhoRKzWeqvBA3nD9dneR6A.png)

### 6.3,跳转指令
**跳转**(jump)指令会导致执行切换到程序中一个全新的位置,示例代码如下。

![image](images/33y4SmPfYDRqPwjBUedJJXuuxhwT3Me9eJ15zEHO1LQ.png)

图 `3-15` 列举了不同的跳转指令,`jmp` 指令是无条件跳转,可以是直接跳转,即跳转目标是作为指令的一部分编码的;也可以是间接跳转,即跳转目标是从寄存器或内存位置中读出的。汇编语言中,直接跳转是会给出一个标号作为跳转目标的,例如上面示例代码中的标号“.L1";间接跳转的写法是 "*"后面跟一个操作数指示符 jmp \**%rax,用寄存器 %rax 中的值作为跳转目标。

![image](images/Mo5N1s1-WvO3ejtcXUw5ofMQTYhldNXr4IoIweyERuE.png)

### 6.4,跳转指令的编码
略

### 6.5,用条件控制来实现条件分支
![image](images/0aML0CYNT05z5GVXBUco2zBDD62MoNzr3Jj_FDly8fg.png)

### 6.6,用条件传送来实现条件分支
实现条件操作的传统方法是通过使用控制的条件转移。当条件满足时,程序沿着一条路径执行,而当条件不满足时,就走另一条路径。这种机制虽然简单通用,但是在现代处理器上,它可能会非常低效。

为了理解为什么教育条件数据传送的代码会比基于条件控制转移的代码性能要好,我们必须了解一些关于现代处理器如何运行的知识。处理器是通过流水线(pipelining)来获得高性能,在流水线中,一条指令的处理要经过一系列的阶段,每个阶段执行所需操作的一小部分(例如,从内存取指令、确定指令类型、从内存读数据、执行算术运算、向内存写数据,已经更新程序计数器)。这种方法通过重叠连续指令的步骤获得高性能,例如在取一条指令的同事,执行它前面一条指令的算术运算。要做到这一点,要求事先确定要执行的指令序列,这样才能保持流水线中充满了待执行的指令。

![image](images/lqkdYI5dn7Z3w_AmOnNU_gZDM6HjOYVQRzK_g3ae8EQ.png)

![image](images/wRKt5-V8DGMNjVgX5p_xz8TRlqncKRkVZkiYK6IUsXs.png)

![image](images/FzYI4mQOCdRMTOobIqSzNX4DG6cUhxD50092K-v1sNU.png)

同条件跳转不同,使用条件传送指令,处理器无需预测测试的结果就可以执行条件传送。处理器只是读源值(可能从内存中),检查条件码,然后要么更新目的寄存器,要么保持不变。条件传送指令类如图 3-18 所示。

![image](images/FRLfh6XYdUC9IMcIg0eqAmWP1HEZgVHtCVg5n_wyKO0.png)

![image](images/z8jUtZpnGYEQ9Yr9KYe9tdhg4bOdqhNci4cD2onpecc.png)

### 6.7,循环
`C` 语言提供了多种循环结构,如 `do-while`、`while` 和 `for`。汇编中没有直接的循环指令存在,但可以用条件测试和跳转指令组合起来实现循环的效果。GCC 和其他汇编器产生的循环代码主要基于两种基本的循环模式:`do-while` 循环和 `while` 循环。这里以给出一个函数实现 $n!$,其 C 代码、goto 代码和汇编代码如下。

![image](images/QVeSGeljjdm5GXeZgEAnborO2yK8uLFSNQxpXWkm4NY.png)

```bash
long fact_do(long n)
n in %rdi
fact_do:
    mov1 $1, %eax Set result = 1
.L2:
    imulq %rdi, %rax Compute result *= n
    subq $1, %rdi Decrement n
    cmpq $1, %rdi Copare n:1
    jg .L2 if >, goto loop
    rep; ret Return
```
## 7,过程
过程是软件中一种很重要的抽象,不同编程语言中,过程的形式多样:函数(function)、方法(method)、子例程(subroutine)、处理函数(handler)等等,但是它们都有一些共同的特性。

**编程语言过程调用机制的一个关键特性在于使用了栈数据结构提供的先进后出的内存管理原则。**

![image](images/BYrY1-ffeSExzLuOP-qeIIVMoclFpCROwtIhv6NCY-s.png)

## 8,数组的分配和访问
> 数据结构-数组的定义和使用参考这篇文章[常见数据结构-数组](https://blog.csdn.net/qq_20986663/article/details/127252593?spm=1001.2014.3001.5501)。

### 8.1,基本原则
`x86-64` 的内存引用指令可以用来简化数组访问。例如,假设 E 是一个 int 型的数组,而我们想计算 $E[i]$,在此,E 的地址存放在寄存器 %rdx 中,而 i 存放在寄存器 `%rcx` 中,指令 `movl (%rdx, %rcx, 4), %eax` 会执行地址计算 $x_{E} + 4i$,读取这个内存位置的值,并将结果存放到寄存器 `%eax` 中。

### 8.2,指针运算
单操作数操作符 & 和 \* 可以产生指针和间接引用指针。

![image](images/2VWtPSaNVKroazokJ2j_HoJ4cCBOz3Xz3Yddlv0nh_U.png)

### 8.3,嵌套到数组(多维数组)
要访问多维数组元素,编译器会以**数组起始为基地址,(可能需要经过伸缩的)偏移量为索引,产生计算期望的元素的偏移量,然后使用某种 MOV 指令**。对于一个声明如下的二维数组: 

```cpp
T D[R][C]; // T 是数据类型
```
它的数组元素 `D[i][j]` 的内存地址为 $\&D[i][j] = x_{D} + L(C\cdot i + j)$。

这里,$L$是数据类型 $T$以字节为单位的大小。以 $5\times 3$的整形数组 A 为例,假设数组起始地址、行列索引 $x_A$、$i$和$j$分别在寄存器 %rdi、%rsi 和 %rdx 中,然后可以用下面的代码将数组元素 A\[i\]\[j\] 复制到寄存器 %eax 中:

```bash
A in %rdi, i in %rsi, and j in %rdx
leaq (%rsi, %rsi, 2), %rax     Compute 3i
leaq (rdi, %rax, 4), %rax      Compute x_A + 12i
movq (rax, %rdx, 4), %eax      Read from M[x_A + 12i + 4j]
```
上面这段代码计算元素的地址为 $x_A + 12i + 4j = x_A + 4(3i + j)$,使用了 x86-64 地址运算的伸缩和加法特性。

## 9,异数的数据结构
略

## 10,在机器级程序中将控制与数据结合起来
略

## 11,浮点代码
略

## 参考资料
《深入理解操作系统第三版-第3章》



================================================
FILE: 1-computer_basics/操作系统/计算机基础知识.md
================================================

## 知识点目录

+ 操作系统
+ 计算机网络
+ 面向对象
+ 数据库
+ Linux系统开发
+ 常用工具(Cmake/Git/Docker/正则表达式)

## 操作系统

### 操作系统中堆和栈的区别

+ 操作系统中堆和栈都是指内存空间,不同的是堆为按需申请、动态分配,例如 `C++` 中的 `new` 操作(当然 C++ 的 new 不仅仅是申请内存这么简单)。**堆**可以简单理解为当前使用的空闲内存,其申请和释放需要程序员自己写代码管理。
+ 操作系统中的栈是程序运行时自动拥有的一小块内存,大小在编译时由编译器参数决定,是用于局部变量的存放或者函数调用栈的保存。在 `C` 中声明一个局部变量(如 `int a`)会存放在栈中,当其离开作用域后,所占用的内存则会被释放,栈用于保存函数调用栈时和数据结构的栈是有关系的。在函数调用过程中,常常会多层甚至递归调用。每一个函数调用都有各自的局部变量值和返回值,每一次函数调用其实是先将当前函数的状态压栈,然后在栈顶开辟新空间用于保存新的函数状态,接下来才是函数执行。当函数执行完毕之后,栈先进后出的特性使得后调用的函数先返回,这样可以保证返回值的有序传递,也保证函数现场可以按顺序恢复。操作系统的栈在内存中高地址向低地址增长,也即低地址为栈顶,高地址为栈底。这就导致了栈的空间有限制,一旦局部变量申请过多(例如开个超大数组),或者函数调用太深(例如递归太多次),那么就会导致栈溢出(Stack Overflow),操作系统这时候就会直接把你的程序杀掉。

> 参考[知乎问答-什么是堆?什么是栈?他们之间有什么区别和联系?](https://www.zhihu.com/question/19729973/answer/390903646)

### Linux查看cpu和cache信息

1,`Linux` 查看 `cpu` 信息**命令**:`cat /proc/cpuinfo`。

```shell
(base) pc:$ cat /proc/cpuinfo
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 158
model name	: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
stepping	: 10
microcode	: 0xb4
cpu MHz		: 3192.005
cache size	: 12288 KB
physical id	: 0
siblings	: 1
core id		: 0
cpu cores	: 1
apicid		: 0
initial apicid	: 0
fpu		: yes
fpu_exception	: yes
cpuid level	: 22
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon nopl xtopology tsc_reliable nonstop_tsc cpuid pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch cpuid_fault invpcid_single pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 invpcid rdseed adx smap clflushopt xsaveopt xsavec xsaves arat md_clear flush_l1d arch_capabilities
bugs		: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit srbds
bogomips	: 6384.01
clflush size	: 64
cache_alignment	: 64
address sizes	: 43 bits physical, 48 bits virtual
power management:
...
processor	: 3
...
```

2,`Linux` 查询 `L1/L2/L3 cache`大小:`cat /sys/devices/system/cpu/cpu0/cache/index*/size`(`*`为 `0/1/2/3`)

```shell
(base) harley@harley-pc:~$ cat /sys/devices/system/cpu/cpu0/cache/index0/size
32K
(base) harley@harley-pc:~$ cat /sys/devices/system/cpu/cpu0/cache/index0/type
Data
(base) harley@harley-pc:~$ cat /sys/devices/system/cpu/cpu0/cache/index1/type
Instruction
(base) harley@harley-pc:~$ cat /sys/devices/system/cpu/cpu0/cache/index1/size
32K
(base) harley@harley-pc:~$ cat /sys/devices/system/cpu/cpu0/cache/index2/size
256K
(base) harley@harley-pc:~$ cat /sys/devices/system/cpu/cpu0/cache/index3/size
12288K
```

现代 `CPU`的 `L1 cache` 是逻辑核私有的,L1 cache分指令L1 cache和数据L1 cache,大小相等都为 `32 KB`;目前,L2 cache也是片内私有,所以每个核只有`256 KB`;而对于L3 cache,一个物理核CPU的所有逻辑核共享,所以在每个逻辑核来看,L3 cache都为`12288 KB`。本机的虚拟机总共有 `1` 个物理核,而本机共有 `6` 核 `12` 线程,所以可推算得到**本机的 `Cache` 信息:

+ L1 cache:
  + L1 Data: `192 KB = 32 x 6 KB`
  + L1 Instruction: `192 KB = 32 x 6 KB`
+ L2 cache:`1536 KB = 256 X 6 KB`
+ L3 cache:`12288 KB`

### Windows查看cpu和cache信息

任务管理器--->性能,即可查看 `cpu` 和 `L1/L2/L3 Cache` 大小,如下图所示:

![windows查看cpu信息](../../data//images/cpu信息和Cache大小.png)

或者下载安装 [`cpuz`](https://www.cpuid.com/softwares/cpu-z.html) 软件,打开即可查看,如下图所示。

![windows-zpuz查看cpu信息](../../data//images/cpuz-查询cpu信息.png)

### 并发与并行区别

+ **并发**是指宏观上在一段时间内能同时运行多个程序,而**并行**则指在同一时刻能运行多个指令。
+ 并行需要硬件支持,如多流水线、多核处理器或者分布式计算系统。
+ 操作系统通过引入进程和线程,使得程序能够并发运行。

## 数据库

### 什么是事务

事务是指满足 `ACID` 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以用 `Rollback` 进行回滚。

### 并发一致性问题

+ 丢失修改
+ 读脏数据
+ 不可重复读
+ 脏影读

## 多进程与多线程区别

+ **进程是资源分配的最小单位,线程是CPU调度(独立调度)的最小单位**。
+ 多个进程之间相互独立,一个任务就是一个进程,进程内的“子任务”称为线程;线程是进程的一部分,一个进程至少有一个线程。
+ 同一个进程中的所有线程的数据是共享的(进程通讯),线程之间可以直接通信;但是进程之间的数据是独立的,进程之间的交流需要借助中间代理(`IPC`)来实现。
+ 由于创建或撤销进程时,系统要为之分配资源或回收资源,如内存空间、I/O设备等,所以进程间调用、通讯和切换开销均比多线程大,单个线程的崩溃会导致整个应用的退出。
+ 存在大量IO,网络耗时或者需要和用户交互等操作时,使用多线程(线程开销小、切换速度快)有利于提高系统的并发性和用户界面快速响应从而提高友好性。

## 进程/线程通信

### 进程间通信

进程间通信(IPC, InterProcess Communication):是指在不同进程之间传播或交换信息。`IPC` 的方式有:管道、消息队列、信号量、共享存储、Socket等。`Socket` 支持不同主机的两个进程的 `IPC`。
> python Process类参数:`target`表示调用的对象,就是子进程要执行的任务

`Python` 多线程通信可以通过导入`Process`(创建进程) 和`Queue`(创建队列):`from multiprocessing import Process, Queue`,具体通信过程如下:

1. 父进程创建 `Queue`,并传递给各个子进程:`q = Queue()` `pw = Process(target=write, args=(q,))`
2. 分别启动写入数据和读数据子进程:`pw.start()` `pr.start()`
3. 等待进程技术:`pw.join()`

### 线程通信

一个进程所含的不同线程是共享内存的,所以线程之间共享数据有一个问题是多个线程共享一个变量,导致内容改乱了。解决办法是,如果不同线程间有共享的变量,其中一个方法就是在修改前给其上一把锁`lock`,确保一次只有一个线程能修改。`Python` 创建线程方式如下:

```python
t = threading.Thread(target=loop, name='LoopThread')  # `target`表示调用的对象(自己定义的任务函数)
```

`Python` 创建锁可以使用,`threading.lock()` 方法,实现对一个共享变量的锁定,修改完后 `release` 供其它线程使用,具体加锁 `python` 代码如下:

```python
balance = 0
lock = threading.Lock()  # 创建锁

def run_thread(n):
    for i in range(100000):
        # 先要获取锁:
        lock.acquire()
        try:
            # 放心地改吧:
            change_it(n)
        finally:
            # 改完了一定要释放锁:
            lock.release()
```

## 并发与并行

> [多进程与多线程的区别](https://www.cnblogs.com/kaituorensheng/p/3603057.html)

并行指物理上同时执行,并发指能够让多个任务在逻辑上交织执行的程序设计。

要让单核 `CPU` 运行多任务,就得用到**并发技术**,实现并发技术相当复杂,最容易理解的是“时间片轮转进程调度算法”,它的思想简单介绍如下:在操作系统的管理下,所有正在运行的进程轮流使用CPU,每个进程允许占用CPU的时间非常短(比如10毫秒),这样用户根本感觉不出来CPU是在轮流为多个进程服务,就好象所有的进程都在不间断地运行一样。但实际上在任何一个时间内有且仅有一个进程占有CPU。如果一台计算机有多个CPU,情况就不同了,如果进程数小于CPU数,则不同的进程可以分配给不同的CPU来运行,这样,多个进程就是真正同时运行的,这便是并行。但如果进程数大于CPU数,则仍然需要使用并发技术。因此,我们可以得出以下结论:

+ 总线程数<= CPU数量:并行运行
+ 总线程数 >  CPU数量:并发运行

================================================
FILE: 1-computer_basics/效率工具/Docker基础和常用命令.md
================================================
- [一,Docker 简介](#一docker-简介)
  - [1.1,什么是 Docker](#11什么是-docker)
  - [1.2,Docker 与虚拟机的区别](#12docker-与虚拟机的区别)
  - [1.3,Docker 架构](#13docker-架构)
  - [1.4,为什么用 Docker](#14为什么用-docker)
- [二,Docker 基本概念](#二docker-基本概念)
  - [2.1,镜像](#21镜像)
  - [2.2,容器](#22容器)
  - [2.3,仓库](#23仓库)
- [三,Docker 使用](#三docker-使用)
  - [3.1,Docker 服务](#31docker-服务)
  - [3.2,下载与使用Docker公共镜像(Images)](#32下载与使用docker公共镜像images)
- [四,Docker 镜像命令](#四docker-镜像命令)
- [五,Docker 容器命令](#五docker-容器命令)
  - [5.1,docker run 命令](#51docker-run-命令)
  - [5.2 查看、停止、启动和删除容器](#52-查看停止启动和删除容器)
- [六,参考资料](#六参考资料)

## 一,Docker 简介
### 1.1,什么是 Docker
`Docker` 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 OverlayFS 类的 Union FS 等技术,对进程进行封装隔离,属于**操作系统层面的虚拟化技术**。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。**Docker容器**与虚拟机类似,但二者在原理上不同。容器是将[操作系统层虚拟化](https://zh.m.wikipedia.org/wiki/%E4%BD%9C%E6%A5%AD%E7%B3%BB%E7%B5%B1%E5%B1%A4%E8%99%9B%E6%93%AC%E5%8C%96%20%22%E4%BD%9C%E6%A5%AD%E7%B3%BB%E7%B5%B1%E5%B1%A4%E8%99%9B%E6%93%AC%E5%8C%96%22 "操作系统层虚拟化"),虚拟机则是虚拟化硬件,因此容器更具有便携性、能更高效地利用服务器。

专业名词 `Docker` 有两个意思:

* 代指整个 Docker 项目。
* 代指 Docker 引擎。

`Docker` 引擎(Docker Engine)是指一个服务端-客户端结构的应用,主要有这些部分:Docker 守护进程、Docker Engine API(页面存档备份,存于互联网档案馆)、Docker 客户端。

### 1.2,Docker 与虚拟机的区别
* 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程。
* Docker 容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。

|**特性**|**Docker**|**虚拟机**|
| :-----: | :-----: | :-----: |
|启动|秒级|分钟级|
|硬盘使用|一般为 MB|一般为 GB|
|性能|接近原生|弱于|
|系统支持量|单机支持上千个容器|一般几十个|

### 1.3,Docker 架构
![image](../../data/images/docker/L-xDvVjAV1tWJ6Gk6kr-hDXF-AwaxhIXYZEF2FDqzW0.png)

runc 是一个 Linux 命令行工具,用于根据 OCI 容器运行时规范 创建和运行容器。

containerd 是一个守护程序,它管理容器生命周期,提供了在一个节点上执行容器和管理镜像的最小功能集。

### 1.4,为什么用 Docker
Docker 作为一种**新的虚拟化技术**,跟传统的虚拟化技术相比具有众多的优势:

1. **更高效的利用系统资源**:不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。
2. **更快速的启动时间**:Docker 容器应用直接运行于宿主内核,不需要启动完整的操作系统,所以启动时间可做到秒级的启动时间。
3. **一致的运行环境**:Docker 镜像提供了除内核外完整的运行时环境,确保开发环境、测试环境、生产环境的一致性。
4. **持续交付和部署**:开发人员可以通过 Dockerfile 来进行镜像构建,并结合持续集成(Continuous Integration) 系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合持续部署(Continuous Delivery/Deployment) 系统进行自动部署。
5. **更轻松的迁移**:Docker 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。
6. **更轻松的维护和扩展。**

## 二,Docker 基本概念

**Docker 三个基本概念:**

* 镜像(Image)
* 容器(Container)
* 仓库(Repository)

### 2.1,镜像
**操作系统分为内核和用户空间**。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:18.04 就包含了完整的一套 Ubuntu 18.04 最小系统的 root 文件系统。

**Docker 镜像** 是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像 **不包含** 任何动态数据,其内容在构建之后也不会被改变。

**Docker** 镜像并非是像一个 `ISO` 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。**其被设计为分层存储的架构,镜像构建时,会一层层构建,前一层是后一层的基础**。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

### 2.2,容器

**容器(Container):一个运行时实例,它基于镜像(Image)创建,并提供隔离的运行环境**。

镜像(`Image`)和容器(`Container`)的关系,类似面向对象程序设计中的**类和实例**的关系。可以把 Docker容器(Container) 看做是一个简易版的 Linux 环境(包括 root 用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。它可以被启动、开始、停止、 删除。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。

容器和镜像一样都是使用分层存储,每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为**容器存储层**。

### 2.3,仓库
镜像构建完成后,可以很容器的在**当前宿主主机**上运行,但是如果需要在其他服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,即**仓库**(Repository)-集中存放镜像的地方。

`Docker` 仓库(`Registry`) 分为公开仓库(`Public`)和私有仓库(`Private`)两种形式。目前 `Docker` 官方维护了一个公共仓库 [Docker Hub](https://hub.docker.com/ "Docker Hub"),其中已经包括了数量超过 2,650,000 的镜像。大部分需求都可以通过在 `Docker Hub` 中直接下载镜像来实现。

有时候使用 `Docker Hub` 这样的公共仓库可能不方便,用户可以创建一个**本地仓库**供私人使用。[Docker Registry](https://yeasy.gitbook.io/docker_practice/repository/registry "Docker Registry") 是官方提供的工具,可以用于构建私有的镜像仓库。

一个 `Docker Registry` 中可以包含多个**仓库**(`Repository`);每个仓库可以包含多个**标签**(`Tag`);**每个标签对应一个镜像**。

通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 `<仓库名>:<标签>` 的格式来指定具体是这个软件哪个版本的镜像。如:`ubuntu: 14.04`、`ubuntu: 16.04` 等等。

```shell
$ docker image ls ubuntu
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              18.04               329ed837d508        3 days ago          63.3MB
ubuntu              bionic              329ed837d508        3 days ago          63.3MB
```

## 三,Docker 使用
### 3.1,Docker 服务
安装 Docker 这里不做介绍。以下是 Linux 系统下,一些 docker 使用命令:

1,**查看 Docker 服务状态**:使用 `systemctl status docker` 命令查看 Docker 服务的状态。其中 Active: active (running) 即表示 Docker 服务为正在运行状态。

![image](../../data/images/docker/ExVYsbQl5Uatb6Rk4h_PevAXETxUgb60O9fP1kYrsY0.png)

2,**停止 Docker 服务**:使用 `systemctl stop docker` 命令。

3,**启动 Docker 服务**:使用 `systemctl start docker` 命令。

4,**重启 Docker 服务**:使用 `systemctl restart docker` 命令。

5,**测试 Docker 是否安装正确**。

```bash
$ docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
7050e35b49f5: Pull complete
Digest: sha256:13e367d31ae85359f42d637adf6da428f76d75dc9afeb3c21faea0d976f5c651
Status: Downloaded newer image for hello-world:latest
 
Hello from Docker!
This message shows that your installation appears to be working correctly.
 
To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm64v8)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.
 
To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash
 
Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/
 
For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```
### 3.2,下载与使用Docker公共镜像(Images)
> `macos` 系统环境下操作示例,`ubuntu` 系统可能略有不同。

1,使用 **docker search** 命令从 Docker Repo 搜索 Dokcer 可用的镜像。示例命令:`docker search ubuntu18.04`。

![image](../../data/images/docker/VDaqCzSPqTxG2F5pY6Z3l7nWOUm1mcsufhAug8gNadA.png)

2,使用 **docker image pull** 命令从 Docker Repo 获取指定的 Dokcer镜像(Images)。示例命令: `docker image pull docker.io/hello-world`。拉取名为 docker.io/hello-world 的镜像。

![image](../../data/images/docker/OtA6evJeq4h1M5R-t_8pCiV41eII-K9lIsoBCa4qbBo.png)

3,使用 **docker image ls** 命令查看**本地**的 Dokcer 镜像(Images)。

4,使用 **docker run** 命令运行 Dokcer 镜像(Images)。示例命令:`docker run hello-world`。

![image](../../data/images/docker/Q2A9wZz1qY_yUxPzB7z6nVmcgAEz1T56qZt785MCIP0.png)

5,使用 **docker info** 命令,查看当前 docker容器 的所有的信息。

![image](../../data/images/docker/-rnbIRGlVItC7zPytjVm_D-ReUsB5Jf9cP9S-e2atNA.png)

6,使用 **docker version** 查看容器的版本信息。

```bash
$ docker --version # 这个命令查看 docker 版本更简单
Docker version 19.03.13, build 4484c46d9d
```
![image](../../data/images/docker/GDJbbeZyUtoedXnjm5MNMOIKgdgoa6cqcnSo-FT7fP8.png)

## 四,Docker 镜像命令
Docker 镜像(Images) 也可以理解为是一个用于创建 Docker容器(Container) 的静态模板。一个 Docker 镜像(Images) 可以创建很多 Docker容器(Container)。

**Docker 镜像常用命令如下:**

|命令|描述|
| ----- | ----- |
|**docker commit**|创建镜像。|
|**docker images**|查看镜像信息。|
|**docker load**|导入镜像。|
|**docker pull**|拉取 Docker 镜像。|
|**docker push**|上传镜像。|
|**docker rmi**|删除镜像。|
|**docker save**|导出镜像。|
|**docker search**|在 Docker Hub 搜索镜像。|
|**docker tag**|为镜像打标签。|

## 五,Docker 容器命令
### 5.1,docker run 命令
通过 **docker run** 命令可以基于镜像新建一个容器并启动,语法如下:

`docker run [OPTIONS] IMAGE [COMMAND] [ARG...]` 

**其他常用容器管理命令如下:**

```bash
# 新建容器并启动
$ docker run [镜像名/镜像ID]
# 启动已终止容器
$ docker start [容器ID]
# 列出本机运行的容器
$ docker ps
# 停止运行的容器
$ docker stop [容器ID]
# 杀死容器进程
$ docker kill [容器ID]
# 重启容器
$ docker restart [容器ID]
```
**docker run 命令语法**

1, `docker run` 命令常用选项:可通过 docker run --help 命令查看全部内容。

|**选项**|**说明**|
| :-----: | :-----: |
|\-d, --detach=false|指定容器运行于前台还是后台,默认为 false。|
|\-i, --interactive=false|打开 STDIN,用于控制台交互。|
|\-t, --tty=false|分配 tty 设备,该可以支持终端登录,默认为 false。|
|\-u, --user=""|指定容器的用户。|
|\-a, --attach=\[\]|登录容器(必须是以 docker run -d 启动的容器)。|
|\-w, --workdir=""|指定容器的工作目录。|
|\-c, --cpu-shares=0|设置容器 CPU 权重,在 CPU 共享场景使用。|
|\-e, --env=\[\]|指定环境变量,容器中可以使用该环境变量。|
|\-m, --memory=""|指定容器的内存上限。|
|\-P, --publish-all=false|指定容器暴露的端口。|
|\-p, --publish=\[\]|指定容器暴露的端口。|
|\-h, --hostname=""|指定容器的主机名。|
|\-v, --volume=\[\]|给容器挂载存储卷,挂载到容器的某个目录。|
|–volumes-from=\[\]|给容器挂载其他容器上的卷,挂载到容器的某个目录。|
|–cap-add=\[\]|添加权限。|
|–cap-drop=\[\]|删除权限。|
|–cidfile=""|运行容器后,在指定文件中写入容器 PID 值,一种典型的监控系统用法。|
|–cpuset=""|设置容器可以使用哪些 CPU,此参数可以用来容器独占 CPU。|
|–device=\[\]|添加主机设备给容器,相当于设备直通。|
|–dns=\[\]|指定容器的 dns 服务器。|
|–dns-search=\[\]|指定容器的 dns 搜索域名,写入到容器的 /etc/resolv.conf 文件。|
|–entrypoint=""|覆盖 image 的入口点。|
|–env-file=\[\]|指定环境变量文件,文件格式为每行一个环境变量。|
|–expose=\[\]|指定容器暴露的端口,即修改镜像的暴露端口。|
|–link=\[\]|指定容器间的关联,使用其他容器的 IP、env 等信息。|
|–lxc-conf=\[\]|指定容器的配置文件,只有在指定 --exec-driver=lxc 时使用。|
|–name=""|指定容器名字,后续可以通过名字进行容器管理,links 特性需要使用名字。|
|–net=“bridge”|器网络设置:<br>1. bridge 使用 docker daemon 指定的网桥。<br>2. host //容器使用主机的网络。<br>3. container:NAME\_or\_ID >//使用其他容器的网路,共享 IP 和 PORT 等网络资源。<br>4. none 容器使用自己的网络(类似–net=bridge),但是不进行配置。|
|–privileged=false|指定容器是否为特权容器,特权容器拥有所有的 capabilities。|
|–restart=“no”|指定容器停止后的重启策略:<br>1. no:容器退出时不重启。<br>2. on-failure:容器故障退出(返回值非零)时重启。<br>3. always:容器退出时总是重启。|
|–rm=false|指定容器停止后自动删除容器(不支持以 docker run -d 启动的容器)。|
|–sig-proxy=true|设置由代理接受并处理信号,但是 SIGCHLD、SIGSTOP 和 SIGKILL 不能被代理。|

2,`Docker` 交互式运行的语法为:`docker run -i -t IMAGE [COMMAND] [ARG]` 。`Docker` 交互式运行,即 `Docker` 启动直接进入 `Docker` 镜像内部。

![image](../../data/images/docker/DN6phxbTI-qJqDunkIdOgz6Gte1a--lUTVsIW5s8HZo.png)

3, **进入容器内部**: 可以使用 `docker exec` 命令在运行中的容器中启动一个交互式 shell:

```bash
docker exec -it mynginx /bin/bash
```

### 5.2 查看、停止、启动和删除容器

```bash
# 查看正在运行的容器
docker ps
# 查看所有容器(包括停止的)
docker ps -a
# 启动已存在的容器
docker start container_name
# 停止容器
docker stop container_name
# 删除容器(容器必须处于停止状态)
docker rm container_name
```

![image](../../data/images/docker/EJgGulpCOPeBpHrsbzfdNrTuBYAKCaQGoLitWluROl8.png)

## 六,参考资料
* [Docker-从入门到实践](https://yeasy.gitbook.io/docker_practice/ "Docker-从入门到实践")
* [Docker教程](https://haicoder.net/docker/docker-course.html "Docker教程")



================================================
FILE: 1-computer_basics/效率工具/git工业界实战总结.md
================================================
---
layout: post
title: git 工业界实战总结
date: 2021-09-01 20:00:00
summary: 本地仓库由 git 维护的三棵“树"组成。第一个是 工作目录,它持有实际文件;第二个是暂存区(Index),它像个缓存区域,临时保存仓库做的改动;最后是 Head,它指向我们的最后一次提交的结果。
categories: Linux
---

## 一 git 入门操作

### 1.1 git 创建代码仓库

第一步:刚下载安装的 `git` 都需要先配置用户名和邮箱:

```bash
git config --global user.name "user_name"
git config --global user.email "youremail@example.com"
```

第二步:要想从 `github` 或者 `gitlab` 上实现 `clone/pull/push` 等操作,首先就得在本地创建 `SSH Key` 文件,在用户主目录下,查看是否有 `.ssh` 目录,看这个目录下是否有 `id_rsa` 和 `id_rsa.pub` 这两个文件,如果没有,则需要打开 shell(windows 系统打开Git Bash),在命令行中输入:

```bash
ssh-keygen -t rsa -C "youremail@example.com"
```
> SSH 概述: \*\*SSH(Secure Shell) \*\* 是一种网络协议,用于计算机之间的加密登录。如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登录是安全的,即使被中途截获,密码也不会泄露,原因在于它采用了非对称加密技术(RSA)加密了所有传输的数据。

第三步:登录 `Github`,打开 `"Account settings”`,“SSH Keys”页面,然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id\_rsa.pub文件的内容,点“Add Key”,就可以看到已经添加的 `Key` 了。之后你就可以玩转 `Git`了。

> 为什么 GitHub 需要 SSH Key 呢? 
因为 GitHub 需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而 Git 支持 SSH 协议,所以,GitHub 只要知道了你的公钥,就可以确认只有你自己才能推送。

第四步:上传项目到 github 仓库。配置好用户名和密码后,接下来就是将本地项目代码上传到 `github/gitlab` 仓库了。
在前面的准备工作完成后,你首先可以在 `gitlab/github` 新建仓库后,这样会得到一个仓库地址,这时候你可以把本地的文件夹上传到这个仓库地址,具体操作步骤命令如下:

```bash
# 推送现有文件夹到远程仓库地址
cd existing_folder
git init
git remote add origin "你的仓库地址"
git add .
git commit -m "Initial commit"
git push -u origin master
```
其他上传方式命令如下图:

![image](../images/git_pratice/fc92417a-079c-4bb9-8d34-0fb3a68eb096.png)

### 1.2 git 基础命令

本地仓库由 git 维护的三棵“树"组成。

* 第一个是 `工作目录`,它持有实际文件;
* 第二个是`暂存区(Index)`,它像个缓存区域,临时保存仓库做的改动;
* 最后是 `Head`,它指向我们的最后一次提交的结果。

对于`分支`来说,在创建仓库的时候,`master` 是”默认的“分支。一般在项目中,要先在其他分支上进行开发,完成后再将它们合并到主分支上 `master`上。一般不建议使用 pull 拉取最新代码,因为 pull 拉取下来后会(配置了 `git config pull.rebase true`)自动和本地分支合并。

git 基本操作命令如下:

```bash
git init       # 创建新的 git 仓库
git status  # 查看状态
git branch # 查看分支
git branch dev  # 创建dev分支
git branch -d dev  # 删除 dev 分支
git push origin --delete dev # 删除远程分支 【git push origin --参数远程分支名称】
git branch -a # 查看远程分支
git checkout -b dev # 基于当前分支(master)创建dev分支,并切换到dev分支,dev 分支会关联到 master 分支上
git checkout -f test        # 强制切换至 test 分支,丢弃当前分支的修改
git checkout master  # 切换到master分支
git add filename  # 添加指定文件,把当前文件放入暂存区域
git add .  # 表示添加新文件和编辑过的文件不包括删除的文件
git add -A  # 表示添加所有内容
git commit  # 给暂存区域生成快照并提交
git reset -- files # 用来撤销最后一次 git add files,也可以用 git reset 撤销所有暂存区域文件
git push origin master  # 推送改动到master分支(前提是已经clone了现有仓库)
git remote add origin <server>  # 没有克隆现有仓库,想仓库连接到某个远程服务器
git pull  # 更新本地仓库到最新版本(多人合作的项目),以在我们的工作目录中 获取(fetch) 并 合并(merge) 远端的改动
git diff <source_branch> <target_branch>  # 查看两个分支差异
git diff  # 查看已修改的工作文档但是尚未写入缓冲的改动
git rm <file>  # 用于简单的从工作目录中手工删除文件
git rm -f <file>  # 删除已经修改过的并且放入暂存区域的文件,必须使用强制删除选项 -f
git mv <file>  # 用于移动或重命名一个文件、目录、软链接
git log  # 列出历史提交记录
git remote -v # 列出所有远程仓库信息, 包括网址
```

### 1.3 git 操作实例

1,**将其他分支更改的操作提交到主分支**:

```bash
git checkout master  # 切换回master分支(当前分支为dev)
git merge dev  # 合并(有合并冲突的话得手动更改文件)
```
2,**git 如何回退版本**:

```bash
git log  # 查看分支提交历史,确认要回退的历史版本
git reset --hard  [commit_id]  # 恢复到历史版本
git push -f -u origin branch  # 把修改推送到远程仓库 branch 分支
```
4,**拉取远程分支到本地**:

```bash
# 本地已经拉取了仓库代码,想拉取远程某一分支的代码到本地
git checkout -b ac_branch origin/ac_branch   # 拉取远程分支到本地(方式一)
git fetch origin ac_branch:ac_branch  # 拉取远程分支到本地(方式二)
```
5,**查看本地已有分支**

```bash
# 显示该项目的本地的全部分支,当前分支有 * 号
git branch
```
6,**查看本地分支和原称分支差异**

![image](../images/git_pratice/17e735cb-eb5d-482a-a364-2be3b3aef3c5.png)

7,**回退版本**

![image](../images/git_pratice/4d420e56-98da-4c25-a9bc-07b4fc7bbad3.png)

## 二 git 工业界实战操作

1, 合并远程 master 分支到本地分支 dev/model_compare

```bash
git fetch origin # 拉取最新远程更改
git rebase origin/master
git rebase --continue
git push origin dev/model_compare --force-with-lease
git log --oneline --graph
```

2, 关于多个 commit 注释信息需要的经验。

合并三个 commit, 第一个 commit 必须是 pick,如果想要保留后面最后一个的 commit 信息,则倒数第二个 commit 设为 f, 最后的 commit 改为 s 即可,然后进入 commit 注释修改界面,把第一个 commit 注释信息加 `#` 注释掉即可。

这样就实现了合并三个 commit,但是 commit 信息为最后一个 commit 的信息的目的。

```bash
git rebase -i HEAD~3
```

执行 `rebase` 进入编辑界面, 编辑界面操作详解

```bash
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
```

3, 本地仓库恢复到某个历史状态

```bash
git reflog # 显示本地所有对 HEAD (当前分支指针)的操作日志。
git reset --hard hash_id # 恢复到指定 hash_id 操作的位置
git reset --hard HEAD@{3} # 恢复到前面三步的操作
git reset --soft HEAD^ HEAD^ 表示上一个提交。--soft:只移动 HEAD,不 touch 暂存区和工作区
```

- git log 查看 commit 提交日志信息
- `HEAD@{0}` 总是指向当前的 HEAD。
- `HEAD@{1}` 是上一次 HEAD 移动前的位置,依此类推。

4,将远程分支 feature 的指定目录/文件的修改合并到本地分支 develop 

```bash
git checkout develop # 切换到本地目标分支
git fetch origin # 拉取最新远程更改
git checkout origin/feature -- src/utils # 把 origin/feature 上 src/utils 目录下的所有文件版本,复制到当前工作区,并标记为已修改 

git add src/utils # 提交更改
git commit -m "Merge src/utils from origin/feature into develop"
git push origin develop # 推送到远程 
```

5, git 配置用户名和邮箱

```bash

# 全局配置
git config --global user.name "harleyszhang"
git config --global user.email "ZHG5200211@outlook.com"

# 当前仓库配置
git config user.name "harleyszhang"
git config user.email "ZHG5200211@outlook.com"
```

**6, 修改过往 commit 的用户名和邮箱**

```bash
git rebase -i HEAD~1 --exec 'git commit --amend --author="harleyszhang <zhg5200211@outlook.com>" --no-edit'
```

进入 `git rebase` 界面,直接 `:wq` 保存退出。

## 参考资料

3. [Git 教程](https://www.runoob.com/git/git-tutorial.html)
4. [图解Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)
5. [git documentation](https://git-scm.com/doc)
6. [Git 使用简明手册](https://opus.konghy.cn/git-guide/)

================================================
FILE: 1-computer_basics/效率工具/ubuntu16.04安装mmdetection库.md
================================================
- [一,更新 pip 和 conda 下载源](#一更新-pip-和-conda-下载源)
  - [1.1,查看 conda 和 pip 版本](#11查看-conda-和-pip-版本)
  - [1.2,更新 pip 下载源](#12更新-pip-下载源)
  - [1.3,更新 conda 下载源](#13更新-conda-下载源)
- [二,MMDetection 简介](#二mmdetection-简介)
- [三,MMDetection 安装](#三mmdetection-安装)
  - [3.1,依赖环境](#31依赖环境)
  - [3.2,安装过程记录](#32安装过程记录)
    - [1,安装操作系统+cuda](#1安装操作系统cuda)
    - [2,安装 Anconda3](#2安装-anconda3)
    - [3,安装 pytorch-gpu](#3安装-pytorch-gpu)
    - [4,安装 mmdetection](#4安装-mmdetection)
    - [5,安装 MMOCR](#5安装-mmocr)
- [参考资料](#参考资料)
## 一,更新 pip 和 conda 下载源

默认情况下 `pip` 使用的是国外的镜像,在下载的时候速度非常慢,下面我将介绍如何更新下载源为国内的清华镜像源,其地址为:`https://pypi.tuna.tsinghua.edu.cn/simple`,阿里云镜像的更新方法一样。

### 1.1,查看 conda 和 pip 版本
```bash
root# conda --version
conda 22.9.0
root# pip --version
pip 20.2.4 from /opt/miniconda3/lib/python3.8/site-packages/pip (python 3.8)
```

如果 `pip` 版本 `<10.0.0`,建议升级 pip 到最新的版本 (>=10.0.0) 以方便后面的更新下载源配置:

```shell
# 更新 pip 版本命令
python -m pip install --upgrade pip
```
### 1.2,更新 pip 下载源

在下载安装好 `python3+pip` 或 `anconda3` 的基础上,建议更新为清华/阿里镜像源(默认的 `pip` 和 `conda`下载源速度很慢)。

1,`Linux/Mac` 系统,`pip` **全局更新下载源为清华源和和查看下载源地址的命令**如下:

```bash
# 更新 pip 下载源为清华镜像
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# 查看下载源地址
pip3 config list
```

![update_pip_download_source](./images/update_pip_download_source.png)

2,`Windows` 系统,需要当前在当前用户目录下手动创建配置文件然后修改。

- 资源管理器的地址栏输入 `%appdata%` 后回车,切换到用户路径下的 `appdata` 目录;
- 进入到 `pip` 文件夹中(没有则手动创建),并创建文件 `pip.ini`,此文件的完整路径为 `%APPDATA%/pip/pip.ini`;
- 在 `pip.ini` 文件中添加以下内容后,再次使用 `pip`,即会使用新下载源。

```shell
[global]
timeout = 8000
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
trusted-host = pypi.tuna.tsinghua.edu.cn/simple
```

`Linux/Mac` 也可通过直接修改配置文件(可能需要 `root` 权限)的方式直接更新下载源,`vim ~/.pip/pip.conf`,修改如下:
```shell
global.index-url='https://pypi.tuna.tsinghua.edu.cn/simple'
```

### 1.3,更新 conda 下载源

1,`conda` **更新源的方法**:

各系统都可以通过修改用户目录下的 `.condarc` 文件。`Windows` 用户无法直接创建名为 `.condarc` 的文件,可先执行 `conda config --set show_channel_urls yes` 生成该文件。`Linux/Mac` 系统一般自带 `.condarc` 文件,文件地址为 `~/.condarc`。

2,之后再修改`.condarc` 文件内容如下: 

```bash
channels:
  - defaults
show_channel_urls: true
default_channels:
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
custom_channels:
  conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  pytorch-lts: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
```

## 二,MMDetection 简介
`MMDetection` 是一个基于 `PyTorch` 的目标检测开源工具箱。它是 [OpenMMLab](https://openmmlab.com/) 项目的一部分。主分支代码目前支持 `PyTorch 1.5` 以上的版本。主要特性为:

* **模块化设计**
* **丰富的即插即用的算法和模型**
* **速度快**
* **性能高**

> 更多详情请参考 [MMDetection 仓库 README](https://github.com/open-mmlab/mmdetection/blob/master/README_zh-CN.md)。

## 三,MMDetection 安装
### 3.1,依赖环境
* 系统:**首选 Linux**,其次 `macOS` 和 `Windows`(理论上支持,实际安装需要踩很多坑)
* `Python 3.6+`
* 首选 CUDA 11.3+、其次推荐 CUDA 9.2+
* 首选 Pytorch 1.9+,其次推荐 PyTorch 1.3+
* `GCC 5+`
* [MMCV](https://mmcv.readthedocs.io/en/latest/#installation)

### 3.2,安装过程记录
#### 1,安装操作系统+cuda
我是在 `docker` 容器中安装和进行深度学习算法开发的,其操作系统、`cuda`、`gcc` 环境如下:

![image](images/oIxx0rBoZiuqRQc86gpaF5z8otXeCRzQe23Nabds-6E.png)

#### 2,安装 Anconda3
官网下载 [Anconda3 linux 安装脚本](https://repo.anaconda.com/archive/Anaconda3-2022.10-Linux-x86_64.sh),并安装 `Anconda3`(很好装一路 `yes` 即可),并使用 `conda` 新建虚拟环境,并激活虚拟环境进入。

```bash
conda create -n mmlab python=3.8 -y # 创建 mmlab 的虚拟环境,其中python解释器版本为3.8(python3.9版本不行, 没有pytorch_cuda11.0版本)
conda activate mmlab # 激活虚拟环境进入
```
虚拟环境安装成功后的部分过程截图如下所示:

![image](images/vwSKapCUchQN_oQ7_vdLOiZZZKB6nLe0E3wKWLEY4tc.png)

如果你激活虚拟环境出现如下所示错误。

```bash
CommandNotFoundError: Your shell has not been properly configured to use 'conda activate'.
To initialize your shell, run

    $ conda init <SHELL_NAME>

Currently supported shells are:
  - bash
  - fish
  - tcsh
  - xonsh
  - zsh
  - powershell

See 'conda init --help' for more information and options.

IMPORTANT: You may need to close and restart your shell after running 'conda init'.
```
可通过以下命令重新激活 `conda` 环境,即可解决问题,方法参考自 [stack overflow 问题](https://stackoverflow.com/questions/61915607/commandnotfounderror-your-shell-has-not-been-properly-configured-to-use-conda)。

```bash
source ~/anaconda3/etc/profile.d/conda.sh # anaconda3 的安装路径有可能不一样,自行修改
conda activate mmlab
```
#### 3,安装 pytorch-gpu
首选安装 `pytorch-gpu` 版本,使用**在线安装**命令:

```bash
conda install pytorch=1.7.1 cudatoolkit=11.0 torchvision=0.8.2 -c pytorch
```
> 官网命令的 cuda11.0 的 torchaudio==0.7.2 版本不存在,故去除。

安装过程信息(记得检查 `pytorch` 版本是 `cuda11.0` 的)截图如下:

![image](images/WGJjEZDqVJJZkZNi1MkYFXOaFrKh1UoKu8Cw-gfrl10.png)

安装成功后,进入 `python` 解释器环境,运行以下命令,判断 pytorch-gpu 版本是否安装成功。

```bash
>>> import torch
>>> torch.cuda.is_available()
True
>>> torch.cuda.device_count()
2
>>>
```
同时可通过以下命令查看 `CUDA` 和 `PyTorch` 的版本

```bash
python -c 'import torch;print(torch.__version__);print(torch.version.cuda)'
```
总的来说,`pytorch` 等各种 `python` 包有**离线和在线**两种方式安装:

* **在线**:`conda/pip` 方法安装,详细命令参考 [pytorch 官网](https://pytorch.org/),但是这种方式实际测试下来**可能**会有问题,需要自己肉眼检查安装的版本是否匹配。
* **离线**:浏览器下载安装包,然后通过 `pip` 或者 `conda` 方式离线安装。
   * `pip` **可通过**[此链接](https://download.pytorch.org/whl/torch_stable.html) 浏览器下载各种 `pytorch` 版本的二进制安装包,**到本地安装**(`pip install *.whl`)。
   * `conda` 通过[清华源链接](https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/linux-64/),浏览器下载对应版本压缩包,然后 `conda install --offline pytorch压缩包的全称(后缀都不能忘记)`

> 不通过浏览器下载 `whl` 包,而是 `pip install https://download.pytorch.org/whl/cu110/torch-1.7.1%2Bcu110-cp39-cp39-linux_x86_64.whl` 方式可能会有很多问题,比如网络问题可能会导致安装失败。
> WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0x7f74f60d6760>: Failed to establish a new connection: Errno 101 Network is unreachable')': /whl/cu110/torch-1.7.1%2Bcu110-cp39-cp39-linux_x86_64.whl

> 或者下载到一半的网络连接时常超过限制。
pip._vendor.urllib3.exceptions.ReadTimeoutError: HTTPSConnectionPool(host='download.pytorch.org', port=443): Read timed out.

#### 4,安装 mmdetection
> **不建议安装 cpu 版本**,因为很多算子不可用,其次截止到2022-11-3日,macos 系统 cpu 环境的 `mmdet.apis` 是不可用的。

建议使用 [MIM](https://github.com/open-mmlab/mim) 来自动安装 `MMDetection` 及其相关依赖包-`mmcv-full` 。

```bash
pip install openmim # 或者 pip install -U openmim
mim install mmdet
```
![image](images/g7BbKHcsuGPKLEZlVUd0GxL7zhyJzGdZ9L-nmTcJneY.png)

#### 5,安装 MMOCR

`MMOCR` 依赖 `PyTorch`, `MMCV` 和 `MMDetection`。这些依赖环境,我们在前面的步骤中已经安装好了,所以可通过下面命令直接安装 `MMOCR`。

情况 1: 若你需要直接运行 `MMOCR` 或在其基础上进行开发,则通过源码安装:
```shell
git clone https://github.com/open-mmlab/mmocr.git
cd mmocr
pip3 install -v -e .
# "-v" 会让安装过程产生更详细的输出
# "-e" 会以可编辑的方式安装该代码库,你对该代码库所作的任何更改都会立即生效
```

情况 2:如果你将 `MMOCR` 作为一个外置依赖库使用,通过 `pip` 安装即可:
```shell
pip install mmocr
```

![mmocr_install_success](./images/mmocr_install_success.png)
## 参考资料
* `mmdetection` 和 `pytorch` 官网
* https://download.pytorch.org/whl/torch\_stable.html

================================================
FILE: 1-computer_basics/效率工具/程序编译工具基础.md
================================================
- [Linux 系统下 gcc 编译生成的文件类型](#linux-系统下-gcc-编译生成的文件类型)
- [cmake/makefile/make 理解](#cmakemakefilemake-理解)
- [CMake 编译过程](#cmake-编译过程)
- [CLion 使用笔记](#clion-使用笔记)
- [Clang 是什么](#clang-是什么)
- [MinGW 是什么](#mingw-是什么)
- [GTK 是什么](#gtk-是什么)
- [GNU 是什么](#gnu-是什么)
- [GNU 工具链是什么](#gnu-工具链是什么)
- [GCC 是什么](#gcc-是什么)
- [gcc 与 g++ 的区别](#gcc-与-g-的区别)

## Linux 系统下 gcc 编译生成的文件类型

- .out 是可执行文件,相当于 win 上的 exe;
- .o 是编译中间目标文件,相当于 win 上的 .obj;
- .a 是静态库,多个 .o 练链接得到,用于静态链接;
- .so 是共享库,用于动态链接,相当于 win 上 .dll;

gcc 编译过程参考文章 [linux下gcc编译生成.out,.o,.a,.so文件](https://www.i4k.xyz/article/u011832525/105228959)。

## cmake/makefile/make 理解

> 参考知乎文章 [5分钟理解make/makefile/cmake/nmake](https://zhuanlan.zhihu.com/p/111110992)。

代码变成可执行文件,叫做编译(compile);先编译这个,还是先编译那个(即编译的安排),叫做构建(build)。

+ `CMake` 是一个跨平台的、开源的构建工具。cmake 是 `makefile` 的上层工具,它们的目的正是为了产生可移植的 `makefile`,并简化自己动手写 `makefile`时的巨大工作量;
+ 把 `make` 命令写在文件中,构成一系列构建规则的文件叫做 `Makefile` 文件。`Makefile` 文件有一套自己专门的语法,包括 `echoing`、通配符、模式匹配、变量和赋值符、自动变量等内容。
+ Linux 中 `make` 是用来编译的,它从 `Makefile中` 读取指令,然后编译。make 的作用是开始进行源代码编译,以及由  Makefile 设置文件提供的一些功能;比如 `make install` 表示进行安装(一般需要有 root 权限),`make uninstall` 是卸载,不加参数就是默认的进行源代码编译。

> `make`工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式—通过调用 `makefile` 文件中用户指定的命令来进行编译和链接的。

## CMake 编译过程

CMake 是针对跨平台编译问题,所设计的工具:它首先允许开发者编写一种平台无关的 `CMakeList.txt` 文件来定制整个编译流程,然后再根据目标用户的平台进一步**自动生成**所需的本地化 `Makefile` 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。

在 `linux` 平台下使用 `CMake` 生成 `Makefile` 并编译的流程如下:
1. 编写 `CMake` 配置文件 `CMakeLists.txt`。
2. 执行命令 `cmake PATH` 或者 `ccmake PATH` 生成 `Makefile` (ccmake 和 cmake 的区别在于前者提供了一个交互式的界面)。其中, `PATH` 是 `CMakeLists.txt` 所在的目录。
3. 使用 `make` 命令进行编译得到项目可执行文件。`make “-j []”` 指同时运行命令的个数。如果没有这个参数,make运行命令时能运行多少就运行多少。

## CLion 使用笔记

当前 `CLion` 支持五种工具链(来源这篇[博客](https://bbs.huaweicloud.com/blogs/158643)):
+ `Visual Studio`, 目前应该是不支持 2019 的, 如果安装之前版本的话, 会自动检测到;
+ `MinGW`, 是一个 GCC 的 Windows 移植版, 在一般情况下是可以代替在远程主机开发的, 但是并不推荐, 可能有兼容性问题;
+ `WSL`, 是 Windows 推出的 Linux 子系统, 目前的 WSL1.0 版本是基于底层代码翻译, 可能存在跟MinGW一样的兼容性问题;
+ `Cygwin`, 是开源界推出的在 Windows 运行 Linux 命令的工具, 跟 WSL1.0 相似;
+ `Remote Host`, 直接使用远程 Linux 主机的编译工具链。

## [Clang 是什么](https://zh.wikipedia.org/wiki/Clang)

**Clang(发音为/ˈklæŋ/类似英文单字[clang](https://zh.wiktionary.org/wiki/clang))** 是一个[C](https://zh.wikipedia.org/wiki/C%E8%AA%9E%E8%A8%80)、[C++](https://zh.wikipedia.org/wiki/C%2B%2B)、[Objective-C](https://zh.wikipedia.org/wiki/Objective-C)和[Objective-C++](https://zh.wikipedia.org/wiki/Objective-C%2B%2B)编程语言的** [编译器](https://zh.wikipedia.org/wiki/%E7%B7%A8%E8%AD%AF%E5%99%A8)前端 **。它采用了 [LLVM](https://zh.wikipedia.org/wiki/LLVM) 作为其后端,而且由 LLVM2.6 开始,一起发布新版本。它的目标是提供一个[GNU编译器套装](https://zh.wikipedia.org/wiki/GCC)(GCC)的替代品,支持了GNU编译器大多数的编译设置以及非官方语言的扩展。

## MinGW 是什么
> 参考 [MinGW 维基百科](https://zh.wikipedia.org/wiki/MinGW)

`MinGW`(Minimalist GNU for Windows),又称 `mingw32`,是将 `GCC` 编译器和 `GNU Binutils` 移植到 `Win32` 平台下的产物,包括一系列头文件(Win32API)、库和可执行文件。另有可用于产生 32 位及 64 位 Windows 可执行文件的 MinGW-w64 项目,是从原本 MinGW 产生的分支。如今已经独立发展。MinGW是从Cygwin(1.3.3版)基础上发展而来。
`GCC` 支持的语言大多在 `MinGW` 也受支持,其中涵盖 `C、C++、Objective-C、Fortran 及 Ada`。对于 C 语言之外的语言,MinGW 使用标准的 GNU 运行库,如 C++ 使用 `GNU libstdc++`。但是 MinGW 使用 Windows 中的C运行库。**因此用 MinGW 开发的程序不需要额外的第三方 DLL 支持就可以直接在 Windows 下运行**,而且也不一定必须遵从 GPL 许可证。这同时造成了 MinGW 开发的程序只能使用 Win32API 和跨平台的第三方库,而缺少 POSIX 支持[3],大多数 GNU 软件无法在不修改源代码的情况下用 MinGW 编译。

## GTK 是什么

`GTK`(原名`GTK+`)最初是 `GIMP` 的专用开发库(`GIMP Toolkit`),后来发展为 `Unix-like` 系统 (类 Unix 系统)下开发图形界面的应用程序的主流开发工具之一。`GTK` 是自由软件,并且是 `GNU` 计划的一部分。自2019年2月6日起,`GTK+` 改名为 `GTK`。
`GTK` 使用 `C` 语言开发,但是其设计者使用面向对象技术。也提供了 `C++(gtkmm)、Perl、Ruby、Java 和 Python(PyGTK)`绑定,其他的绑定有 `Ada、D、Haskell、PHP` 和所有的 `.NET` 编程语言。使用 `GTK` 的环境有 `GNOME` 等,`GNOME` 是以 `GTK` 为基础,就是说为 `GNOME` 编写的程序使用 `GTK` 做为其工具箱。

## [GNU 是什么](https://zh.wikipedia.org/wiki/GNU)

`GNU` 是一个自由的操作系统,其内容软件完全以 `GPL` 方式发布。这个操作系统是 `GNU计划` 的主要目标,名称来自 GNU's Not Unix! 的递归缩写,因为 GNU 的设计类似 Unix,但它不包含具著作权的 Unix 代码。作为操作系统,GNU 的发展仍未完成,其中最大的问题是具有完备功能的内核尚未被开发成功。GNU 的内核,称为 `Hurd`,是自由软件基金会发展的重点,但是其发展尚未成熟。在实际使用上,多半使用 `Linux 内核、FreeBSD` 等替代方案,作为系统核心,其中主要的操作系统是 Linux 的发行版。Linux 操作系统包涵了 Linux内核 与其他自由软件项目中的 GNU 组件和软件,可以被称为 `GNU/Linux`(见GNU/Linux命名争议)。
`GNU` 该系统的基本组成包括 `GNU编译器套装(GCC`)、GNU的C库( `glibc`)、以及 GNU核心工具组(`coreutils`)[14],另外也是GNU调试器(`GDB`)、GNU 二进制实用程序(`binutils`)、GNU Cash shell 和 `GNOME` 桌面环境。 GNU开发人员已经向 GNU 应用程序和工具的 Linux 移植 ,现在也广泛应用在其它操作系统中使用,如BSD变体的Solaris,和OS X作出了贡献。

## GNU 工具链是什么

**GNU 工具链**(英语:GNU toolchain)是一个包含了由 GNU 计划所产生的**各种编程工具的集合**,其组成包括我们非常熟悉的 `GCC` 编译器,由自由软件基金会负责维护工作。这些工具形成了一条工具链,用于开发应用程序和操作系统。
`GNU 工具链`在针对嵌入式系统的 `Linux内核、BSD` 及其它软件的开发中起着至关重要的作用。GNU 工具链中的部分工具也被 `Solaris, Mac OS X, Microsoft Windows (via Cygwin and MinGW/MSYS) and Sony PlayStation 3` 等其它平台直接使用或进行了移植。

## [GCC 是什么](https://zh.wikipedia.org/wiki/GCC)

**GNU 编译器套装(英语:GNU Compiler Collection,缩写为 GCC),指一套[编程语言](https://zh.wikipedia.org/wiki/%E7%B7%A8%E7%A8%8B%E8%AA%9E%E8%A8%80)[编译器](https://zh.wikipedia.org/wiki/%E7%BC%96%E8%AF%91%E5%99%A8)**,以[GPL](https://zh.wikipedia.org/wiki/GPL)及[LGPL](https://zh.wikipedia.org/wiki/LGPL)许可证所发行的[自由软件](https://zh.wikipedia.org/wiki/%E8%87%AA%E7%94%B1%E8%BB%9F%E9%AB%94),也是[GNU计划](https://zh.wikipedia.org/wiki/GNU%E8%A8%88%E5%8A%83)的关键部分,也是[GNU工具链](https://zh.wikipedia.org/wiki/GNU%E5%B7%A5%E5%85%B7%E9%93%BE)的主要组成部分之一。GCC(特别是其中的C语言编译器)也常被认为是跨平台编译器的事实标准。

它的原名为 `GNU C` 语言编译器(GNU C Compiler),因为它原本只能处理[C语言](https://zh.wikipedia.org/wiki/C%E8%AA%9E%E8%A8%80)。GCC在发布后很快地得到扩展,变得可处理[C++](https://zh.wikipedia.org/wiki/C%2B%2B)。之后也变得可处理[Fortran](https://zh.wikipedia.org/wiki/Fortran)、[Pascal](https://zh.wikipedia.org/wiki/Pascal_(%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80))、[Objective-C](https://zh.wikipedia.org/wiki/Objective-C)、[Java](https://zh.wikipedia.org/wiki/Java)、[Ada](https://zh.wikipedia.org/wiki/Ada),[Go](https://zh.wikipedia.org/wiki/Go)与其他语言。许多操作系统,包括许多[类Unix](https://zh.wikipedia.org/wiki/%E7%B1%BBUnix)系统,如[Linux](https://zh.wikipedia.org/wiki/Linux)及BSD家族都采用GCC作为标准编译器。
> GCC 原本用 C 开发,后来因为[LLVM](https://zh.wikipedia.org/wiki/LLVM)、[Clang](https://zh.wikipedia.org/wiki/Clang)的崛起,它更快地将开发语言转换为C++。许多 C 的爱好者在对 C++ 一知半解的情况下主观认定 C++ 的性能一定会输给 C,但是 Ian Lance Taylor 给出了不同的意见,并表明 C++ 不但性能不输给 C,而且能设计出更好,更容易维护的程序。

## gcc 与 g++ 的区别
> 学习了几篇博客,发现知乎的一个回答相对表达清楚和准确性,链接[在这](https://www.zhihu.com/question/20940822)。

这里的 `gcc` (小写)与前文的 `GCC` 含义是不同的。简单来说,gcc 指的是 GCC 中的 GNU C Compiler(C 编译器);`g++` 是 G++ 中的 GNU C++ Compiler(C++ 编译器),但实际上 gcc 和 g++ 都不是编译器,也不是编译器的集合,它们只是一种驱动器,根据参数中的文件类型,调用对应的 GNU  编译器,所以更准确的说法是:`gcc` 调用了 C compiler, `g++` 调用了C++ compiler。gcc 和 g++ 的主要区别如下:
+ 使用 `gcc` 编译 `cpp` 文件可能会报错,因为 `gcc` 编译文件时不会自动链接标准库 `STL`,而 `g++` 会,为了能够使用 `STL`,需要添加参数 `-lstdc++`,`gcc -lstdc++` 和 `g++` 不等价。
+ 对于 `*.c` 和 `*.cpp` 文件,`gcc` 分别当做 c 和 cpp 文件编译(c 和 cpp 的语法强度是不一样的)。
+ 对于 `*.c` 和 `*.cpp` 文件,`g++` 则统一当做 `cpp` 文件编译。
+ gcc 在编译 c 文件时,可使用的预定义宏是比较少的。


================================================
FILE: 2-programming_language/README.md
================================================
## 前言

本目录内容旨在分享cv算法工程师经常需要使用到的 `c/c++`、`python` 和 `shell` 编程语言的知识总结和学习笔记。

## cpp

* [c++基础-资源管理:堆栈与RAII](cpp/c++基础-资源管理:堆栈与RAII.md)
* [c++日期和时间编程总结](cpp/c++日期和时间编程总结.md)

## python

* [python3 编程面试题](python3/python3编程面试题.md)
* [numpy基础-堆叠数组函数总结](python3/numpy基础-堆叠数组函数总结.md)
* [python数据分析-pandas库入门](python3/python数据分析-pandas库入门.md)
* [python图像处理-读取图像方式总结](python3/python图像处理-读取图像方式总结.md)

## shell

* [shell 语法基础](shell/shell语法基础.md)

## 参考资料

- 《C++ Primer 第五版》
- https://zh.cppreference.com/w/%E9%A6%96%E9%A1%B5
- 《Python3 教程-廖雪峰》
- 《菜鸟教程-shell》



================================================
FILE: 2-programming_language/cpp/C++日期和时间编程总结.md
================================================
- [一,概述](#一概述)
- [二,C-style 日期和时间库](#二c-style-日期和时间库)
  - [2.1,数据类型](#21数据类型)
  - [2.2,函数](#22函数)
  - [2.3,数据类型与函数关系梳理](#23数据类型与函数关系梳理)
  - [2.4,时间类型](#24时间类型)
    - [2.4.1,UTC 时间](#241utc-时间)
    - [2.4.2,本地时间](#242本地时间)
    - [2.4.3,纪元时间](#243纪元时间)
  - [2.5,输出时间和日期](#25输出时间和日期)
  - [2.6,综合示例代码](#26综合示例代码)
- [三,chrono 库](#三chrono-库)
  - [3.1,时钟](#31时钟)
  - [3.2,与C-style转换](#32与c-style转换)
  - [3.3,时长 ratio](#33时长-ratio)
    - [3.3.1,时长运算](#331时长运算)
  - [3.4,时间间隔 duration](#34时间间隔-duration)
    - [3.4.1,时间间隔转换函数 duration\_cast](#341时间间隔转换函数-duration_cast)
  - [3.5,时间点 time\_point](#35时间点-time_point)
    - [3.5.1,时间点运算](#351时间点运算)
- [参考资料](#参考资料)

> `C++11` 的日期和时间编程内容在 C++ Primer(第五版)这本书并没有介绍,目前网上的文章又大多质量堪忧或者不成系统,故写下这篇文章用作自己的技术沉淀和技术分享,大部分内容来自网上资料,文末也给出了参考链接。

**日期和时间库**是每个编程语言都会提供的内部库,其可以用打印模块耗时,从而方便做性能分析,也可以用作打印运行时间点。本文的内容着重于 C++11-C++17的内容,C++20的日期和时钟库虽然使用更方便也更强大,但是考虑到版本兼容和程序移植问题,故不做深入探讨。

## 一,概述
C++ 中可以使用的日期时间 API 分为两类:

* `C-style` 日期时间库,位于 <ctime> 头文件中。这是原先 <time.h> 头文件的 C++ 版本。
* `chrono` 库:**C++ 11 中新增API**,增加了时间点,时长和时钟等相关接口(使用较为复杂)。

在 C++11 之前,C++ 编程只能使用 C-style 日期时间库,其精度只有秒级别,这对于有高精度要求的程序来说,是不够的。但这个问题在C++11 中得到了解决,C++11 中不仅扩展了对于精度的要求,也为不同系统的时间要求提供了支持。另一方面,对于只能使用 C-style 日期时间库的程序来说,C++17 中也增加了 timespec 将精度提升到了纳秒级别。

## 二,C-style 日期和时间库
`#include <ctime>`  该头文件包含了获取和操作**日期和时间**的函数和**相关数据类型**定义。

### 2.1,数据类型
|**名称**|**说明**|
| ----- | ----- |
|`time_t`|能够表示时间的基本算术类型的别名,能够表示函数 `time` 返回的时间,单位为**秒**级别。|
|`clock_t`|能够表示时钟滴答计数的基本算术类型的别名(可用作进程运行时间)|
|`size_t`|`sizeof` 运算符返回的无符号整数类型。|
|`struct tm`|包含日历日期和时间的结构体类型|
|timespec\*|以秒和纳秒表示的时间|

### 2.2,函数
`C-style` 日期时间库中包含的时间操作函数如下:

|**函数**|**说明**|
| ----- | ----- |
|`std::clock_t clock()`|返回自程序启动时起的处理器时钟时间|
|`double difftime(std::time_t time_end, std::time_t time_beg)`|计算开始和结束之间的**秒数差**|
|`std::time_t time (time_t* timer)`|返回自纪元起计的**系统当前时间**, 函数可以为空指针|
|`std::time_t mktime (struct tm * timeptr)`|将 `tm` 格式的时间转换成 `time_t` 表示的时间|

时间转换函数如下:

|函数|说明|
| ----- | ----- |
|`char* asctime(const struct tm* timeptr)`|将 `tm` 结构体对象转换为字符串的文本|
|`char* ctime(const time_t* timer)`|将 `time_t` 对象转换为 `C` 字符串,用于表示日历时间|
|`struct tm* gmtime(const time_t* time)`|将 `time_t` 转换成 `UTC` 表示的时间|
|`struct tm* localtime(const time_t* timer)`|将 `time_t` 转换成本地时间|

> `localtime` 函数使用参数 `timer` 指向的值来填充 `tm` 结构体,其中的值表示对应的时间,以本地时区表示。

`strftime` 和 `wcsftime` 函数一般不常用,故不做介绍。`tm` 结构体的一般定义如下:

```cpp
/* Used by other time functions.  */
struct tm
{
  int tm_sec;			/* Seconds.	[0-60] (1 leap second) */
  int tm_min;			/* Minutes.	[0-59] */
  int tm_hour;			/* Hours.	[0-23] */
  int tm_mday;			/* Day.		[1-31] */
  int tm_mon;			/* Month.	[0-11] */
  int tm_year;			/* Year	- 1900.  */
  int tm_wday;			/* Day of week.	[0-6] */
  int tm_yday;			/* Days in year.[0-365]	*/
  int tm_isdst;			/* DST.		[-1/0/1]*/
};
```
### 2.3,数据类型与函数关系梳理
时间和日期相关的函数及数据类型比较多,单纯看表格和代码不是很好记忆,第一个参考链接的作者给出了如下所示的思维导图,方便记忆与理解上面所有函数及数据类型之间各自的联系。

![image](../../data/images/C++日期和时间编程总结/ml59Jk5gnJije7fLRPKilgjgH-Txur8rfcHx4OWf9vA.png)

在这幅图中,以数据类型为中心,带方向的实线箭头表示该函数能返回相应类型的结果。

* `clock` 函数是相对独立的一个函数,它返回进程运行的时间,具体描述见下文。
* `time_t` 描述了纪元时间,通过 `time` 函数可以获得它,但它只能精确到秒级别。
* `timespec` 类型在 `time_t` 的基础上,增加了**纳秒**的精度,通过 `timespec_get` 获取。这是 `C++17` 上新增的特性。
* `tm` 是日历类型,因为它其中**包含了年月日等**信息。通过 gmtime,localtime 和 mktime 函数可以将 time\_t 和 tm 类型互相转换。
* 考虑到时区的差异,因此存在 gmtime 和 localtime 两个函数。
* 无论是 `time_t` 还是 `tm` 结构,都可以将其以字符串格式输出。ctime 和 asctime 输出的格式是固定的。如果需要自定义格式,需要使用 strftime 或者 wcsftime 函数。

### 2.4,时间类型
#### 2.4.1,UTC 时间
**协调世界时**(**C**oordinated **U**niversial **T**ime,简称 **UTC**)是最主要的时间标准,其以**原子时秒长**为基础,在时刻上尽量接近于格林威治标准时间。

协调世界时是世界上调节时钟和时间的主要时间标准,它与0度经线的平太阳时相差不超过 1 秒。因此UTC时间+8即可获得北京标准时间(UTC+8)。

#### 2.4.2,本地时间
**本地时间**与当地的时区相关,例如中国当地时间采用了北京标准时间(`UTC+8`)。

#### 2.4.3,纪元时间
**纪元时间**(Epoch time)又叫做 Unix 时间或者 POSIX 时间。它表示自1970 年 1 月 1 日 00:00 UTC 以来所经过的**秒数**(不考虑闰秒)。它在操作系统和文件格式中被广泛使用。**<ctime>**** 头文件中通过 ****time\_t**** 以秒级别表示纪元时间**。

纪元时间这个想法很简单:以一个时间为起点加上一个偏移量便可以表达任何一个其他的时间。

> 为什么选这个时间作为起点,可以点击这里:[Why is 1/1/1970 the “epoch time”?](https://stackoverflow.com/questions/1090869/why-is-1-1-1970-the-epoch-time)。

通过 `time` 函数获取当前时刻的纪元时间示例代码如下:

```cpp
time_t epoch_time = time(nullptr);
cout << "Epoch time: " << epoch_time << endl;
// Epoch time: 1660039180 (日历时间: Tue Aug  9 17:59:40 2022)
```
`time` 函数接受一个指针,指向要存储时间的对象,通常可以传递一个空指针,然后通过返回值来接受结果。虽然标准中没有给出定义,但`time_t` 通常使用整形值来实现。

### 2.5,输出时间和日期
使用 `ctime` 函数,可以将时间以**固定格式的字符串**的形式打印出来,格式为:Www Mmm dd hh:mm:ss yyyy\\n。代码示例如下:

```cpp
// 以字符串形式输出当前时间和日期
time_t now = time(nullptr);
cout << "Now is: " << ctime(&now);
// Now is: Tue Aug  9 18:06:38 2022
```
### 2.6,综合示例代码
`asctime()` 和 `difftime()` 函数等`sample` 代码如下(复制可直接运行):

```cpp
/* asctime example */
#include <stdio.h>      /* printf */
#include <time.h>       /* time_t, struct tm, time, localtime, asctime */
#include <vector>
#include <iostream>

using namespace std;

// 冒泡排序: 将数据从小到大排序
void bubbleSort(vector<int> &arr){
    size_t number = arr.size();
    if (number <= 1) return;
    int temp;
    for(int i = 0; i < number; i++){
        for(int j = 0; j < number-i; j++){
            if (temp > arr[j+1]){
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

// difftime() 函数: 计算时间差,单位为 s
void difftime_test()
{
    vector<int> input_array;
    for (int i = 90000; i > 0; i--) {
        input_array.emplace_back(i);
    }
    time_t time1 = time(nullptr);
    bubbleSort(input_array);
    time_t time2 = time(nullptr);
    double time_diff = difftime(time2, time1);
    cout << "input array size is " << input_array.size() << " after bubbleSort time_diff: " << time_diff << "s" << endl;
}

// astime() 函数: 将本地时间 tm 结构体对象转换为字符串文本
void astime_test()
{
    time_t raw_time = time(nullptr);  // 获取当前时刻日历时间
    struct tm* local_timeinfo = localtime(&raw_time);
    printf ( "The current date/time is: %s", asctime (local_timeinfo) );
}

int main()
{
    difftime_test();
    astime_test();
    // 3, 输出当前纪元时间
    time_t epoch_time = time(nullptr);
    cout << "Epoch time: " << epoch_time << endl;
    // 4,以字符串形式输出当前时间和日期
    time_t now = time(nullptr);
    cout << "Now is: " << ctime(&now);
}
```
`g++ time_demo.cpp -std=c++11` 编译后,运行程序 `./a.out` 后,输出结果:

![image](../../data/images/C++日期和时间编程总结/aynStdgXKbMQypwFBrSinqCBacMOMzKB4O5ZfxmRVR8.png)

## 三,chrono 库
> “chrono” 是英文 chronology 的缩写,其含义是“年表;年代学”。

`chrono` 既是头文件名字也是子命名空间的名字,`chrono` 头文件下的所有 `elements` 都是在 `std::chrono` 命名空间下定义的。

`std::chrono` 是 C++11 引入的日期时间处理库,`chrono` 库里包括三种主要类型:`Clocks`,`Time points` 和 `Durations` 。

![image](../../data/images/C++日期和时间编程总结/Bc07opCOaRwoXfaL6Q9x3FcilncZyWqh1xunhUb_NNI.png)

![image](../../data/images/C++日期和时间编程总结/b0QrXV9457SwR4CamDMD103u3Fka0b0JRO2qknTYtqo.png)

### 3.1,时钟
`C++11` `chrono` 库中包含了**三种**的时钟类:

|**名称**|**说明**|
| :-----: | :-----: |
|`chrono::system_clock`|系统时钟(可以调整)|
|`chrono::steady_clock`|单调递增时钟(不能调整)|
|`chrono::high_resolution_clock`|拥有可用的最短嘀嗒周期的时钟|

`system_clock` 是当前所在系统的时钟。因为系统时钟随时都可能被调整,所以如果想要计算两个时间点的时间差,是不推荐使用系统时钟的。

`steady_clock` 会保证时间的单调递增性,只会向前移动不会减少,所以最适合用来度量**时间间隔**。

`high_resolution_clock` 表示实现提供的拥有最小计次周期的时钟。它可以是 system\_clock 或 steady\_clock 的别名,也可能是第三个独立时钟。在不同的标准库中,high\_resolution\_clock 的实现不一致,所以官方不建议使用这个时钟。

这三个时钟类有一些共同的成员函数和数据类型,如下所示:

|**名称**|**说明**|
| ----- | ----- |
|`now()`|静态成员函数,返回当前时间,类型为 clock::time\_point|
|`time_point`|成员类型,当前时钟的时间点类型,用于表示一个具体时间,详情见下文“时间点”|
|`duration`|成员类型,时钟的时长类型,用于表示时间间隔(**一段时间**),详情见下文“时长”|
|`rep`|成员类型,时钟的 tick 类型,等同于 clock::duration::rep|
|`period`|成员类型,时钟的单位,等同于 clock::duration::period|
|`is_steady`|静态成员类型:是否是稳定时钟,对于 steady\_clock 来说该值一定是 true|

每一个时钟类都有一个 `now()` 静态函数来获取当前时间,返回的类型由 time\_*point 描述。**std::chrono::time\_point** 是模板类,模版类实例如:std::chrono::time\_point<std::chrono::steady\_*clock>,这样写比较长,庆幸的是在 C++11 中可以通过 `auto` 关键字来自动推导变量类型。

```cpp
std::chrono::time_point<std::chrono::steady_clock> now1 = std::chrono::steady_clock::now();
auto now2 = std::chrono::steady_clock::now();
```
### 3.2,与C-style转换
system\_*clock 与另外两个 clock 不一样的地方在于,它还提供了两个静态函数用来将 time\_*point 与 std::time\_t 来回转换。

|**名称**|**说明**|
| ----- | ----- |
|to\_time\_t|将系统时钟时间点转换为 `time_t`|
|from\_time\_t|将 `time_t` 转换到系统时钟时间点|

第一篇参考链接的文章给出了下面这幅图来描述 c 风格和 c++11 的几种时间类型的转换:

![image](../../data/images/C++日期和时间编程总结/f5ZI3_vc4AHIDauujjkxUdE6vhYYdwRmDp31hVZsuk0.png)

### 3.3,时长 ratio
为了支持更高精度的系统时钟,`C++11` 新增了一个新的头文件 `<ratio>` 和类型,用于自定义时间单位。`std::ratio` 是一个**模板类,提供了编译期的比例计算功能,为 std::chrono::duration 提供基础服务**。其声明如下:

```cpp
template<
    std::intmax_t Num,
    std::intmax_t Denom = 1
> class ratio;
```
第一个模板参数 `Num` (numerator) 表示分子,第二个参数 `Denom` (denominator) 表示分母。`typedef ratio<1, 1000> milli;` 表示一千分之一,因为约定了基本计算单位是秒,所以 `milli` 表示一千分之一秒。所以通过 `ratio` 可以表示**毫秒、微秒、纳秒等**。

```cpp
typedef ratio<1,1000000000> nano; // 纳秒单位
typedef ratio<1,1000000> micro; // 微秒单位
typedef ratio<1,1000> milli; // 毫秒单位
typedef ratio<1,1> s // 秒单位
```
ratio 能表达的数值不仅仅是以 10 为基底的,同时也可以表达任意的分数秒,例如:5/7秒,89/23409 秒等等对于一个具体的 ratio 来说,可以通过 den 获取分母的值,num 获取分子的值。不仅仅如此,<ratio>头文件还包含了:`ratio_add,ratio_subtract,ratio_multiply,ratio_divide` 来完成分数的加减乘除四则运算。例如,想要计算 5/7+59/1023,可以用以下代码表示:

```cpp
ratio_add<ratio<5, 7>, ratio<59, 1023>> result;
double value = ((double) result.num) / result.den;
cout << result.num << "/" << result.den << " = " << value << endl;
// 代码输出结果是 5528/7161 = 0.771959
```
> 在C++中,如果**分子和分母都是整形,则整形除法结果依然是整形**,即小数点右边部分会被抛弃,因此想要获取 `double` 类型的结果,需要先将其转换成 `double`。

#### 3.3.1,时长运算
时长对象之间可以进行相加或相减运算。`chrono` 提供了以下几个常用**时长运算的函数**:

|**函数**|**说明**|
| ----- | ----- |
|`duration_cast`|进行时长的转换|
|`floor(C++17)`|以向下取整的方式,将一个时长转换为另一个时长|
|`ceil(C++17)`|以向上取整的方式,将一个时长转换为另一个时长|
|`round(C++17)`|转换时长到另一个时长,就近取整,偶数优先|
|`abs(C++17)`|获取时长的绝对值|

### 3.4,时间间隔 duration
类模板 std::chrono::duration 表示时间间隔,其声明如下:

```cpp
template<
    class Rep,
    class Period = std::ratio<1>
> class duration;
```
类成员类型描述:

|**member type**|**definition**|**notes**|
| ----- | ----- | ----- |
|rep|The first template parameter (`Rep`)|Representation type used as the type for the internal [count](https://cplusplus.com/duration::count) object.|
|period|The second template parameter (`Period`)|The [ratio](https://cplusplus.com/ratio) type that represents a *period* in seconds.|

> `duration` 由 `Rep` 类型的**计次数**和`Period` 类型的**计次周期**组成,其中计次周期是一个编译期有理数常量,表示从一个计次到下一个的秒数。存储于 duration 的数据仅有 Rep 类型的计次数。若 Rep 是浮点数,则 duration 能表示小数的计次数。 Period 被包含为时长类型的一部分,且只在不同时长间转换时使用。

* `Rep` 表示一种数值类型,用来表示 Period 的数量,比如 int float double (count of ticks)。
* `Period` 是 std::ratio 类型,用来表示【用秒表示的时间单位】比如 second milisecond (a tick period)。
* 成员函数 `count()` 返回 `Rep` 类型的 `Period` 数量。

常用的 `duration<Rep, Period>` 已经定义好了,在 `std::chrono` 头文件中,常用时长单位的代码如下:

```cpp
/// nanoseconds
typedef duration<int64_t, nano> 	nanoseconds;
/// microseconds
typedef duration<int64_t, micro> 	microseconds;
/// milliseconds
typedef duration<int64_t, milli> 	milliseconds;
/// seconds
typedef duration<int64_t> 		seconds;
/// minutes
typedef duration<int, ratio< 60>> 	minutes;
/// hours
typedef duration<int, ratio<3600>> 	hours;
```
|**类型**|**定义**|
| ----- | ----- |
|`std::chrono::nanoseconds`|duration</\*至少 64 位的有符号整数类型\*/, std::nano>|
|`std::chrono::microseconds`|duration</\*至少 55 位的有符号整数类型\*/, std::micro>|
|`std::chrono::milliseconds`|duration</\*至少 45 位的有符号整数类型\*/, std::milli>|
|`std::chrono::seconds`|duration</\*至少 35 位的有符号整数类型\*/>|
|`std::chrono::minutes`|duration</\*至少 29 位的有符号整数类型\*/, std::ratio<60»|
|`std::chrono::hours`|duration</\*至少 23 位的有符号整数类型\*/, std::ratio<3600»|

`duration` 类的 `count()` 成员函数返回时间间隔的具体数值。

#### 3.4.1,时间间隔转换函数 duration\_cast
因为有各种 `duration` 表示不同的时长单位,所以 chrono 库提供了 `duration_cast` 函数来换 `duration` 类型,其声明如下:

```cpp
template <class ToDuration, class Rep, class Period>
constexpr ToDuration duration_cast(const duration<Rep,Period>& d);
```
其定义比较复杂,但是我们日常使用可以直接使用 `auto` 推导函数返回对象类型,示例代码如下:

```cpp
#include <iostream>
#include <chrono>
#include <ratio>
#include <thread>
 
void f()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
}
 
int main()
{
    auto t1 = std::chrono::high_resolution_clock::now();
    f();
    auto t2 = std::chrono::high_resolution_clock::now();
    // 整数时长:要求 duration_cast
    auto int_ms = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
    // 小数时长:不要求 duration_cast
    std::chrono::duration<double, std::milli> fp_ms = t2 - t1;
    std::cout << "f() took " << fp_ms.count() << " ms, "
              << "or " << int_ms.count() << " whole milliseconds\n";
    // 程序输出结果: f() took 1000.23 ms, or 1000 whole milliseconds
}
```
### 3.5,时间点 time\_point
`std::chrono::time_point` 表示时间中的一个点(**一个具体时间)**,如上个世纪80年代、你的生日、今天下午、火车出发时间等,只要它能用计算机时钟表示。其包含了**时钟和时长**两个信息。它被实现成如同存储一个 `Duration` 类型的自 `Clock` 的纪元起始开始的时间间隔的值。其声明如下:

```cpp
template<
    class Clock,
    class Duration = typename Clock::duration
> class time_point;
```
时钟的 `now()` 函数返回的值就是一个时间点。time\_point 中的 time\_since\_epoch() 返回从其时钟起点开始的时长。可以通过两个时间点相减计算一个时间间隔,下面是代码示例:

```cpp
#include <stdio.h>      /* printf */
#include <iostream>
#include <chrono>
#include <math.h>

using namespace std;

void time_point_test()
{
    auto start = chrono::steady_clock::now();
    double sum = 0;
    for(int i = 0; i < 100000000; i++) {
        sum += sqrt(i);
    }
    auto end = chrono::steady_clock::now();
    // 通过两个时间点相减计算一个时间间隔
    auto time_diff = end - start;
    // 将时间间隔单位转化为毫秒
    auto duration = chrono::duration_cast<chrono::milliseconds>(time_diff);
    cout << "Sqrt Operation cost : " << duration.count() << "ms" << endl;
}

int main()
{
    time_point_test();
    // 程序输出结果: Sqrt Operation cost : 838ms
}
```
#### 3.5.1,时间点运算
时间点有加法和减法操作,计算结果和常识一致:时间点 + 时长 = 时间点;时间点 - 时间点 = 时长。

## 参考资料
* [C++ 日期和时间编程](https://paul.pub/cpp-date-time/#id-%E6%97%B6%E9%92%9F)
* [C++日期和时间工具](https://www.apiref.com/cpp-zh/cpp/chrono/duration.html)
* [C++ <chrono> 头文件内容官方英文版资料](https://cplusplus.com/reference/chrono/)

================================================
FILE: 2-programming_language/cpp/c++基本编程题汇总.md
================================================
- [STL 库描述](#stl-库描述)
- [内存中堆和栈的区别](#内存中堆和栈的区别)
- [堆栈溢出一般是由什么原因导致的?](#堆栈溢出一般是由什么原因导致的)
- [sizeof 的作用](#sizeof-的作用)
- [C++ 面向对象特点](#c-面向对象特点)
- [多态的理解](#多态的理解)
- [虚函数的理解](#虚函数的理解)
- [动态绑定的理解](#动态绑定的理解)
- [C++构造函数初始化时什么时候只能用初始化列表?](#c构造函数初始化时什么时候只能用初始化列表)
- [C++ 构造函数和析构函数的初始化顺序](#c-构造函数和析构函数的初始化顺序)
- [全局变量和局部变量在内存中是否有区别?如果有,是什么区别?](#全局变量和局部变量在内存中是否有区别如果有是什么区别)
- [C++ 中的 new delete 和 C 语言中的 malloc free 有什么区别](#c-中的-new-delete-和-c-语言中的-malloc-free-有什么区别)
- [new、delete、malloc、free 区别](#newdeletemallocfree-区别)
- [static 关键字作用](#static-关键字作用)
- [类的 static 变量在什么时候初始化?函数的 static 变量在什么时候初始化?](#类的-static-变量在什么时候初始化函数的-static-变量在什么时候初始化)
- [C++ 变量作用域](#c-变量作用域)
- [C++ 指针和引用的区别](#c-指针和引用的区别)
- [C++ 中析构函数的作用](#c-中析构函数的作用)
- [C++ 静态函数和虚函数的区别](#c-静态函数和虚函数的区别)
- [++i 和 i++ 区别](#i-和-i-区别)
- [const 关键字作用](#const-关键字作用)
- [C++如何传递数组给函数](#c如何传递数组给函数)

## STL 库描述

`STL` 库包括:容器、算法以及融合两者的迭代器。

容器分为**顺序容器**和关联容器。顺序容器比如 `vector` 是一个动态分配存储空间的容器。区别于 `C++` 中的 `array`,`array` 分配的空间是静态的,分配之后不能被改变,而 `vector` 会自动重分配(扩展)空间。

## 内存中堆和栈的区别

- 栈内存:由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈,都是先进后出。栈使用的是一级缓存,他们通常都是被调用时处于存储空间中,调用完毕立即释放**。
- 堆内存:一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS 回收。堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。

## 堆栈溢出一般是由什么原因导致的?

没有垃圾回收资源。

## sizeof 的作用

`sizeof` 运算符返回一条表达式或一个类型名字所占的字节数,其满足右结合规律。

## C++ 面向对象特点

数据抽象、继承和动态绑定。

## 多态的理解

- 同一函数作用于不同的对象,会对应不同的实现,从而产生不同的执行结果。在运行时,可以通过**指向基类的指针或引用**,来调用实现派生类中的方法。
- `C++` 的多态性具体体现在运行和编译两个方面:在程序运行时的多态性通过继承和虚函数 virtual 来体现;在程序编译时多态性体现在函数和运算符的重载上;

## 虚函数的理解

和 `Python` 不同,在 `C++` 中,基类将**类型相关**的函数与派生类不做改变直接继承的函数区分对待。对于某些函数,基类希望它的派生类各自定义适合自身的版本,此使基类就会将这些函数声明成**虚函数**。同时,派生类内部必须在其内部对所有重新定义的虚函数进行声明。(参考 C++ Primer p526 页)

派生类必须使用类派生列表(`class derivation list`)明确指出它是从哪个(哪些)基类继承而来。派生的形式是:首先一个冒号,后面紧跟以逗号分隔的基类列表,其中每个基类前面可以有访问说明符。

```cpp
class Student: public People{
    string names;
    virtual  double get_gpa (vector<float> scores);
}
```

## 动态绑定的理解

在 C++ 语言中,,当我们使用基类的引用(或指针)调用一个虚函数时,将发生动态绑定,即函数运行的版本由实参决定,运行时自动选择函数的版本。

## C++构造函数初始化时什么时候只能用初始化列表?

如果类成员是 `const`、引用。或者属于某种未提供默认构造函数的类类型时,我们必须通过构造函数初始值列表为这些成员提供初始值。(参考 C++ Primer p259)

## C++ 构造函数和析构函数的初始化顺序

> 本回答参考[C++ 构造函数初始化顺序](https://blog.csdn.net/qq_30835655/article/details/66971183), [C++奇奇怪怪的题目之构造析构顺序](http://gaocegege.com/Blog/cpp/cppclass)

有**多个基类的派生类(多继承)** 的构造函数初始化按照如下顺序进行:

1. 先执行虚拟继承的父类的构造函数;
2. 然后从左到右执行普通继承的父类的构造函数;
3. 接着按照定义的顺序执行数据成员的初始化;
4. 最后调用类自身的构造函数;

析构函数就无脑的将构造函数顺序反转即可。多继承形式下的构造函数和单继承形式基本相同,只是要在派生类的构造函数中调用多个基类的构造函数。

实例代码如下:

```cpp

#include <iostream>
 
using namespace std;
 
class OBJ1
{
public:
    OBJ1() { cout << "OBJ1" << endl; }
    ~OBJ1() { cout << "OBJ1 destory" << endl;}
};
 
class OBJ2
{
public:
    OBJ2() { cout << "OBJ2\n"; }
    ~OBJ2(){cout << "OBJ2 destory" <<endl;}
};
 
class Base1
{
public:
    Base1() { cout << "Base1" << endl; }
    ~Base1() { cout << "Base1 destory" << endl; }
};
 
class Base2
{
public:
    Base2() { cout << "Base2" << endl; }
    ~Base2() { cout << "Base2 destory" << endl; }
};
 
class Base3
{
public:
    Base3() { cout << "Base3" << endl; }
    ~Base3() { cout << "Base3 destory" << endl; }
};
 
class Base4
{
public:
    Base4() { cout << "Base4" << endl; }
    ~Base4() { cout << "Base4 destory" << endl; }
};
 
class Derived :public Base1, virtual public Base2,
    public Base3, virtual public Base4
{
public:
    Derived() { cout << "Derived ok" << endl; }
    ~Derived() { cout << "Derived destory" << endl; }
protected:
    OBJ1 obj1;
    OBJ2 obj2;
};
 
int main()
{
    Derived aa;
    cout << "construct ok"<<endl;
    return 0;
}
```

程序输出结果如下:
> Base2
Base4
Base1
Base3
OBJ1
OBJ2
Derived ok
construct ok
Derived destory
OBJ2 destory
OBJ1 destory
Base3 destory
Base1 destory
Base4 destory
Base2 destory

## 全局变量和局部变量在内存中是否有区别?如果有,是什么区别?

全局变量储存在静态数据区,局部变量在堆栈中。

## C++ 中的 new delete 和 C 语言中的 malloc free 有什么区别

虽然这两者都分别是完成分配内存和释放内存的功能,但是 `C++` 用 `new` 分配内存时会调用构造函数,用 `delete` 释放内存时会调用析构函数。

## new、delete、malloc、free 区别

- `new` 和 `delete` 是 `C++` 的运算符,`malloc` 和 `free` 是 C++/C 语言的标准框函数,都可用于申请**动态**内存和释放内存。
- `new` 动过调用对象的构造函数来申请动态内存;`delete` 通过调用对象的析构函数来释放内存。
- 对于非内部数据类型的对象而言,只用 `maloc/free` 是无法满足动态对象的要求。我们知道**对象在创建的同时要自动执行构造函数**,对象在消亡之前要自动执行析构函数。但由于 malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于 `malloc/free`。因此 `new`/`delete` 其实比 `malloc`/`free` 更灵活。

## static 关键字作用

+ **声明全局静态变量**:在全局变量前加上关键字 `static`,全局变量就定义成一个全局静态变量,作用域在声明它的文件之外是不可见的,即**从定义之处开始到文件结尾**。
+ **局部静态变量**:在局部变量前加上关键字 `static`,作用域仍然为局部作用域,即当定义它的函数或者语句块结束的时候,作用域结束。
+ **静态函数**: 在函数返回类型前加 `static`,静态函数只在声明他的文件中可见,不能被其他文件使用。
+ **类的静态成员**:在类中,静态成员可以实现多个对象之间的数据共享,即静态成员是类的所有对象中共享的成员,而不是某个对象成员。并且使用静态数据成员不会破坏隐藏的原则,保证了数据的安全性。
+ **类的静态函数**:把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。`静态成员函数即使在类对象不存在的情况下也能被调用`,静态函数只要使用类名加范围解析运算符 `::` 就可以访问(`<类名>::<静态成员函数名>(<参数表>)`)

## 类的 static 变量在什么时候初始化?函数的 static 变量在什么时候初始化?

类的静态成员变量在类实例化之前就已经存在了,并且分配了内存。函数的`static` 变量在执行此函数时进行初始化。

## C++ 变量作用域

作用域即是程序的一个区域,在程序中变量的作用域一般有三个地方:

+ 在函数或者一个代码块内部声明的变量,称为局部变量;
+ 在函数参数中定义的变量,称为形参;
+ 在所有函数外部声明的变量,比如在程序文件开头定义的变量,称为全局变量。

## C++ 指针和引用的区别

+ 指针有自己的内存空间,而引用只是一个别名,类似于Python浅拷贝和深拷贝的区别
+ 不存在空引用, 引用必须链接到一块合法的内存地址;
+ 一旦引用被初始化为一个对象,就不能指向另一个对象。指针可以在任何时候指向任何一个对象;
+ 引用必须在创建时被初始化。指针可以在任何时间初始化。
+ 指针可以有多级,但是引用只能是一级(`int **p`;合法 而 `int &&a` 是不合法的)。

## C++ 中析构函数的作用

析构函数与构造函数对应,类的析构函数是类的一种特殊的成员函数,**它会在每次删除所创建的对象时执行**。析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

## C++ 静态函数和虚函数的区别

静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销。

## ++i 和 i++ 区别

++i 先自增1,再返回,i++,先返回 i,再自增1.

## const 关键字作用

`const`类型的对象在程序执行期间不能被修改改变。

## C++如何传递数组给函数

首先要知道的是,`C++` 传数组给一个函数,该数组类型会自动转换为指针,因此实际传递的是地址。

一维数组作为形参有以下三种方式,多维数组作为形参类似。

- 形参是一个指针;
- 形参是已定义大小的数组;
- 形参是未定义大小的数组。


================================================
FILE: 2-programming_language/python3/Python数据分析-pandas库入门.md
================================================

## pandas 库概述
pandas 提供了快速便捷处理结构化数据的大量数据结构和函数。自从2010年出现以来,它助使 Python 成为强大而高效的数据分析环境。pandas使用最多的数据结构对象是 DataFrame,它是一个面向列(column-oriented)的二维表结构,另一个是 Series,一个一维的标签化数组对象。

pandas 兼具 NumPy 高性能的数组计算功能以及电子表格和关系型数据库(如SQL)灵活的数据处理功能。它提供了复杂精细的索引功能,能更加便捷地完成重塑、切片和切块、聚合以及选取数据子集等操作。数据操作、准备、清洗是数据分析最重要的技能,pandas 是首选 python 库之一。

个人觉得,学习 pandas 还是最好在 anaconda 的 jupyter 环境下进行,方便断点调试分析,也方便一行行运行代码。

## 安装 pandas
Windows/Linux系统环境下安装

conda方式安装

```Plain Text
conda install pandas
```
pip3方式安装

```Plain Text
py -3 -m pip install --upgrade pandas    #Windows系统
python3 -m pip install --upgrade pandas    #Linux系统
```
## pandas 库使用
pandas 采用了大量的 NumPy 编码风格,但二者最大的不同是 pandas 是专门为处理表格和混杂数据设计的。而 NumPy 更适合处理统一的数值数组数据。

导入 pandas 模块,和常用的子模块 Series 和 DataFrame

```Plain Text
import pands as pd
from pandas import Series,DataFrame
```
通过传递值列表来创建 Series,让 pandas 创建一个默认的整数索引:

```Plain Text
s = pd.Series([1,3,5,np.nan,6,8])
s
```
输出

> 0    1.0
1    3.0
2    5.0
3    NaN
4    6.0
5    8.0
dtype:  float64

## pandas数据结构介绍
要使用 pandas,你首先就得熟悉它的两个主要数据结构:**Series 和 DataFrame**。虽然它们并不能解决所有问题,但它们为大多数应用提供了一种可靠的、易于使用的基础。

### Series数据结构
Series 是一种类似于一维数组的对象,它由一组数据(各种 NumPy 数据类型)以及一组与之相关的数据标签(即索引)组成。仅由一组数据即可产生最简单的 Series。代码示例:

```Plain Text
import pandas as pd
obj = pd.Series([1,4,7,8,9])
obj
```
![image](../../data/images/pandas/mehHOUu698p5GFHptdAl1TS1PZuqzwvUpJKGLHfuqEY.png)

Series 的字符串表现形式为:索引在左边,值在右边。由于我们没有为数据指定索引,于是会自动创建一个 0 到 N-1( N 为数据的长度)的整数型索引。也可以通过Series 的 values 和 index 属性获取其数组表示形式和索引对象,代码示例:

```Plain Text
obj.values
obj.index # like range(5)
```
输出:

> array(\[ 1, 4, 7, 8, 9\])
RangeIndex(start=0, stop=5, step=1)

我们也希望所创建的 Series 带有一个可以对各个数据点进行标记的索引,代码示例:

```Plain Text
obj2 = pd.Series([1, 4, 7, 8, 9],index=['a', 'b', 'c', 'd'])
obj2
obj2.index
```
输出

> a    1
b    4
c    7
d    8
e    9
dtype:  int64

> Index(\[‘a’, ‘b’, ‘c’, ‘d’, ‘e’\], dtype=’object’)

![image](../../data/images/pandas/zjIofANE2EH8FbL8t5R2ivTG3ieIx3DrA1ufwfTJKG4.png)

与普通 NumPy 数组相比,你可以通过索引的方式选取 Series 中的单个或一组值,代码示例:

```Plain Text
obj2[['a', 'b', 'c']] 
obj2['a']=2
obj2[['a', 'b', 'c']]
```
![image](../../data/images/pandas/pnppCnVHQV3YWboi44iaDHOgOzdAUpnnYFV8EwAnodU.png)

\[‘a’,’b’,’c\]是索引列表,即使它包含的是字符串而不是整数。

使用 NumPy 函数或类似 NumPy 的运算(如根据布尔型数组进行过滤、标量乘法、应用数学函数等)都会保留索引值的链接,代码示例:

```Plain Text
obj2*2
np.exp(obj2)
```
![image](../../data/images/pandas/dHcDtD_Y6NZGCUIb_zVvp_5lJLBZaTzZTm823dlaqmg.png)

还可以将 Series 看成是一个定长的有序字典,因为它是索引值到数据值的一个映射。它可以用在许多原本需要字典参数的函数中,代码示例:

```Plain Text
dict = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000,'Utah': 5000}
obj3 = pd.Series(dict)
obj3
```
输出

> Ohio 35000

> Oregon 16000

> Texas 71000

> Utah 5000

> dtype: int64

![image](../../data/images/pandas/4GjLOFJv3__gjLkru7fU5DlPrU68R6mmI0Y6daaFPuc.png)

### DataFrame数据结构
DataFrame 是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame 既有行索引也有列索引,它可以被看做由 Series 组成的字典(共用同一个索引)。DataFrame 中的数据是以一个或多个二维块存放的(而不是列表、字典或别的一维数据结构)。

> 虽然 DataFrame 是以二维结构保存数据的,但你仍然可以轻松地将其表示为更高维度的数据(层次化索引的表格型结构,这是 pandas中许多高级数据处理功能的关键要素 )

创建 DataFrame 的办法有很多,最常用的一种是直接传入一个由等长列表或 NumPy 数组组成的字典,代码示例:

```Plain Text
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada','Nevada'],
             'year': [2000, 2001, 2002, 2001, 2002, 2003],
              'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame = pd.DataFrame(data)
frame
```
结果 DataFrame 会自动加上索引(跟 Series 一样),且全部列会被有序排列,输出如下:

![image](../../data/images/pandas/yw-a9Kc_vSLq0X6TLKUBbT9dve1ihpi437Izvu_L8oM.png)

对于特别大的 DataFrame,head 方法会选取前五行:

```Plain Text
frame.head()
```
如果指定了列序列,则 DataFrame 的列就会按照指定顺序进行排列,代码示例:

```Plain Text
pd.DataFrame(data,columns=['state','year','pop'])
```
![image](../../data/images/pandas/6suW8Y3Om3nd9x6b0-GdWDCssQVsltgFion2NLZv-vM.png)

如果传入的列在数据中找不到,就会在结果中产生缺失值,代码示例:

```Plain Text
frame2 = pd.DataFrame(data,columns=['state','year','pop','debt'],
                                    index=['one','two','three','four','five','six'])
frame2
```
![image](../../data/images/pandas/GUFyyIupGf-1EizI7KuJBtuogx9O83eWMPZGMHWgNV0.png)

获取 DataFrame 的 columns 和 index,代码示例:

```Plain Text
frame2.columns
frame2.index
```
输出

> Index(\[‘state’, ‘year’, ‘pop’, ‘debt’\], dtype=’object’)

> Index(\[‘one’, ‘two’, ‘three’, ‘four’, ‘five’, ‘six’\], dtype=’object’)

![image](../../data/images/pandas/vybZ_9BZL19pm3PW54JMxn_OtrnSB_ibDYHVYiH3l98.png)

通过类似字典标记的方式或属性的方式,可以将 DataFrame 的列获取为一个 Series,代码示例:

```Plain Text
frame2['state']
frame2.state
```
![image](../../data/images/pandas/LVh77V4fUh10o4mU5AloGJg_spSi3H1cHvrGhxg3whY.png)

列可以通过赋值的方式进行修改,赋值方式类似 Series。例如,我们可以给那个空的 “debt” 列赋上一个标量值或一组值(数组或列表形式),代码示例:

```Plain Text
frame2.debt = np.arange(6.)
frame2
```
![image](../../data/images/pandas/b5g2YOTWsxoPnIZxyeSOkoV3P_8nxJpXSsf2ZuLO7co.png)

注意:将列表或数组赋值给某个列时,其长度必须跟DataFrame的长度相匹配。

如果赋值的是一个 Series,就会精确匹配 DataFrame 的索引,所有的空位都将被填上缺失值,代码示例:

```Plain Text
val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'four','five'])
frame2.debt = val
frame2
```
为不存在的列赋值会创建出一个新列。关键字 **del** 用于删除列。

作为 del 的例子,这里先添加一个新的布尔值的列,state 是否为 ‘Ohio’,代码示例:

```Plain Text
frame2['eastern'] = frame2.state=='Ohio'
frame2
```
![image](../../data/images/pandas/bVlf3zi_oCdjSzgdHHfeya1rYQUMWgLPWmYUsGMXi30.png)

DataFrame 另一种常见的数据形式是嵌套字典,如果嵌套字典传给 DataFrame,pandas 就会被解释为:外层字典的键作为列,内层键则作为行索引,代码示例:

```Plain Text
#DataFrame另一种常见的数据形式是嵌套字典
pop = {
      'Nvidia':{2001:2.4,2002:3.4},
      'Intel':{2000:3.7,2001:4.7,2002:7.8}
}
frame3 = pd.DataFrame(pop,columns=['Nvidia','Intel'])
frame3
```
![image](../../data/images/pandas/5NqSbp4XB_LEPBOnbqRv27Ou3Of3RNL1-6zib6v9l7I.png)

**表5-1列出了DataFrame构造函数所能接受的各种数据**

![image](../../data/images/pandas/_7XUyaBIMZVAO41Ih8BY10W21yMODIQz9SNWmFekhWQ.png)

### 索引对象
pandas 的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建 Series 或 DataFrame 时,所用到的任何数组或其他序列的标签都会被转换成一个 Index,代码示例:

```Plain Text
import numpy as np
import pandas as pd
obj = pd.Series(np.arange(4),index=['a','b','c','d'])
index = obj.index
#index
index[:-1]
```
![image](../../data/images/pandas/eN7Nbjg4bMr0CpiB0C44WN_TidMdM_1_kemHxwk3XIM.png)

注意:Index 对象是不可变的,因此用户不能对其进行修改。

不可变可以使 Index 对象在多个数据结构之间安全共享,代码示例:

```Plain Text
#pd.Index储存所有pandas对象的轴标签
#不可变的ndarray实现有序的可切片集
labels = pd.Index(np.arange(3))
obj2 = pd.Series([1.5, -2.5, 0], index=labels)
obj2
#print(obj2.index is labels)
```
![image](../../data/images/pandas/HdxGMf8q3VQvdnWZelz2xtTBOQYju7PxKFsJO-Azm74.png)

> 注意:虽然用户不需要经常使用 Index 的功能,但是因为一些操作会生成包含被索引化的数据,理解它们的工作原理是很重要的。

与 python 的集合不同,pandas 的 Index 可以包含重复的标签,代码示例:

```Plain Text
dup_labels = pd.Index(['foo','foo','bar','alice'])
dup_labels
```
每个索引都有一些方法和属性,它们可用于设置逻辑并回答有关该索引所包含的数据的常见问题。表5-2列出了这些函数。

![image](../../data/images/pandas/NYmjzWqZnomg7PIbJQx3KYgg3--Xb0MpCpjBKSyK4Mc.png)

## pandas 选择数据
```Plain Text
import numpy as np
import pandas as pd
# dates = pd.date_range('20190325', periods=6)
dates = pd.date_range('20190325', periods=6)
df = pd.DataFrame(np.arange(24).reshape((6,4)),index=dates, columns=['A','B','C','D'])
print(df)
'''
             A   B   C   D
2019-03-25   0   1   2   3
2019-03-26   4   5   6   7
2019-03-27   8   9  10  11
2019-03-28  12  13  14  15
2019-03-29  16  17  18  19
2019-03-30  20  21  22  23
'''
# 检索指定A列
print(df['A'])    # 等同于print(df.A)
'''
2019-03-25     0
2019-03-26     4
2019-03-27     8
2019-03-28    12
2019-03-29    16
2019-03-30    20
Freq: D, Name: A, dtype: int64
'''
## 切片选取多行或多列
print(df[0:3])    # 等同于print(df['2019-03-25':'2019-03-27'])
'''
            A  B   C   D
2019-03-25  0  1   2   3
2019-03-26  4  5   6   7
2019-03-27  8  9  10  11
'''
# 根据标签选择数据
# 获取特定行或列
# 指定行数据
print(df.loc['2019-03-25'])
bb = df.loc['2019-03-25']
print(type(bb))
'''
A    0
B    1
C    2
D    3
Name: 2019-03-25 00:00:00, dtype: int64
<class 'pandas.core.series.Series'>
'''
# 指定列, 两种方式
print(df.loc[:, ['A', 'B']])    # print(df.loc[:, 'A':'B'])
'''
             A   B
2019-03-25   0   1
2019-03-26   4   5
2019-03-27   8   9
2019-03-28  12  13
2019-03-29  16  17
2019-03-30  20  21
'''
# 行列同时检索
cc = df.loc['20190325', ['A', 'B']]
print(cc);print(type(cc.values))# numpy ndarray
'''
A    0
B    1
Name: 2019-03-25 00:00:00, dtype: int64
<class 'numpy.ndarray'>
'''
print(df.loc['20190326', 'A'])
'''
4
'''
# 根据序列iloc获取特定位置的值, iloc是根据行数与列数来索引的
print(df.iloc[1,0])     # 13, numpy ndarray
'''
4
'''
print(df.iloc[3:5,1:3]) # 不包含末尾5或3,同列表切片
'''
             B   C
2019-03-28  13  14
2019-03-29  17  18
'''
# 跨行操作
print(df.iloc[[1, 3, 5], 1:3])
'''
             B   C
2019-03-26   5   6
2019-03-28  13  14
2019-03-30  21  22
'''
# 通过判断的筛选
print(df[df.A>8])
'''
             A   B   C   D
2019-03-28  12  13  14  15
2019-03-29  16  17  18  19
2019-03-30  20  21  22  23
'''
```
## 总结
本文主要记录了 Series 和 DataFrame 作为 pandas 库的基本结构的一些特性,如何创建 pandas 对象、指定 columns 和 index 创建 Series 和 DataFrame 对象、赋值操作、属性获取、索引对象等,这章介绍操作 Series 和 DataFrame 中的数据的基本手段。

## 参考资料
* 《利用python进行数据分析》



================================================
FILE: 2-programming_language/python3/python3编程总结.md
================================================
- [Python global 语句的作用](#python-global-语句的作用)
- [lambda 匿名函数好处](#lambda-匿名函数好处)
- [Python 错误处理](#python-错误处理)
- [Python 内置错误类型](#python-内置错误类型)
- [简述 any() 和 all() 方法](#简述-any-和-all-方法)
- [Python 中什么元素为假?](#python-中什么元素为假)
- [提高 Python 运行效率的方法](#提高-python-运行效率的方法)
- [Python 单例模式](#python-单例模式)
- [为什么 Python 不提供函数重载](#为什么-python-不提供函数重载)
- [实例方法/静态方法/类方法](#实例方法静态方法类方法)
- [\_\_new\_\_和 \_\_init \_\_方法的区别](#__new__和-__init-__方法的区别)
- [Python 的函数参数传递](#python-的函数参数传递)
- [Python 实现对函参做类型检查](#python-实现对函参做类型检查)
- [为什么说 Python 是动态语言](#为什么说-python-是动态语言)
- [Python 装饰器理解](#python-装饰器理解)
- [map 与 reduce 函数用法解释](#map-与-reduce-函数用法解释)
- [Python 深拷贝、浅拷贝区别](#python-深拷贝浅拷贝区别)
- [Python 继承多态理解](#python-继承多态理解)
- [Python 面向对象的原则](#python-面向对象的原则)
- [参考资料](#参考资料)

## Python global 语句的作用

在编写程序的时候,如果想要**改变(重新赋值)**函数外部的变量,并且这个变量会作用于许多函数中,就需要告诉 Python 程序这个变量的作用域是全局变量,`global` 语句可以实现定义全局变量的作用。

## lambda 匿名函数好处

精简代码,`lambda`省去了定义函数,`map` 省去了写 `for` 循环过程:

```python
str_1 = ["中国", "美国", "法国", "", "", "英国"]
res = list(map(lambda x: "填充值" if x=="" else x, str_1))
print(res)  # ['中国', '美国', '法国', '填充值', '填充值', '英国']
```

## Python 错误处理

和其他高级语言一样,`Python` 也内置了一套`try...except...finally...` 的错误处理机制。

当我们认为某些代码可能会出错时,就可以用 `try` 来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至跳转至错误处理代码,即 `except` 语句块,执行完 `except` 后,如果有 `finally` 语句块,则执行。至此,执行完毕。跳转至错误处理代码,

## Python 内置错误类型

+ `IOError`:输入输出异常
+ `AttributeError`:试图访问一个对象没有的属性
+ `ImportError`:无法引入模块或包,基本是路径问题
+ `IndentationError`:语法错误,代码没有正确的对齐
+ `IndexError`:下标索引超出序列边界
+ `KeyError`: 试图访问你字典里不存在的键
+ `SyntaxError`: Python 代码逻辑语法出错,不能执行
+ `NameError`: 使用一个还未赋予对象的变量

## 简述 any() 和 all() 方法

+ `any()`: 只要迭代器中有一个元素为真就为真;
+ `all()`: 迭代器中所有的判断项返回都是真,结果才为真.

## Python 中什么元素为假?

答案:(0,空字符串,空列表、空字典、空元组、None, False)

## 提高 Python 运行效率的方法

1. 使用生成器,因为可以节约大量内存;
2. 循环代码优化,避免过多重复代码的执行;
3. 核心模块用 `Cython PyPy` 等,提高效率;
4. 多进程、多线程、协程;
5. 多个 `if elif` 条件判断,可以把最有可能先发生的条件放到前面写,这样可以减少程序判断的次数,提高效率。

## Python 单例模式

## 为什么 Python 不提供函数重载

> 参考知乎[为什么 Python 不支持函数重载?其他函数大部分都支持的?](https://www.zhihu.com/question/20053359)

我们知道 `函数重载` 主要是为了解决两个问题。

1. 可变参数类型。
2. 可变参数个数。

另外,一个函数重载基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。

1. 对于情况 1 ,函数功能相同,但是参数类型不同,Python 如何处理?答案是根本不需要处理,因为 `Python` 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 Python 中很可能是相同的代码,没有必要做成两个不同函数。
2. 对于情况 2 ,函数功能相同,但参数个数不同,Python 如何处理?大家知道,答案就是**缺省参数(默认参数)**。对那些缺少的参数设定为缺省参数(默认参数)即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。所以,鉴于情况 1 跟 情况 2 都有了解决方案,Python 自然就不需要函数重载了。

## 实例方法/静态方法/类方法

`Python` 类语法中有三种方法,**实例方法,静态方法,类方法**,它们的区别如下:

+ 实例方法只能被实例对象调用,静态方法(由 `@staticmethod` 装饰器来声明)、类方法(由 `@classmethod` 装饰器来声明),可以被类或类的实例对象调用;
+ `实例方法`,第一个参数必须要默认传实例对象,一般习惯用self。`静态方法`,参数没有要求。`类方法`,第一个参数必须要默认传类,一般习惯用 `cls` .

实例代码如下:

```python
class Foo(object):
    """类三种方法语法形式
    """
    def instance_method(self):
        print("是类{}的实例方法,只能被实例对象调用".format(Foo))

    @staticmethod
    def static_method():
        print("是静态方法")

    @classmethod
    def class_method(cls):
        print("是类方法")


foo = Foo()
foo.instance_method()
foo.static_method()
foo.class_method()
print('##############')
Foo.static_method()
Foo.class_method()
```

程序执行后输出如下:
> 是类 <class '__main__.Foo'> 的实例方法,只能被实例对象调用
是静态方法
是类方法
##############
是静态方法
是类方法

## \_\_new\_\_和 \_\_init \_\_方法的区别

+ `__init__` 方法并不是真正意义上的构造函数, `__new__` 方法才是(类的构造函数是类的一种特殊的成员函数,它会**在每次创建类的新对象时执行**);
+ `__new__` 方法用于创建对象并返回对象,当返回对象时会自动调用 `__init__` 方法进行初始化, `__new__` 方法比 `__init__` 方法更早执行;
+ `__new__` 方法是**静态方法**,而 `__init__` 是**实例方法**。

## Python 的函数参数传递

> 参考这两个链接,stackoverflow的最高赞那个讲得很详细
[How do I pass a variable by reference?](https://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference)
[Python 面试题](https://github.com/taizilongxu/interview_Python#Python%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7)

个人总结(有点不好):

+ 将可变对象:列表list、字典dict、NumPy数组ndarray和用户定义的类型(类),作为参数传递给函数,函数内部将其改变后,函数外部这个变量也会改变(对变量进行重新赋值除外 `rebind the reference in the method`)
+ 将不可变对象:字符串string、元组tuple、数值numbers,作为参数传递给函数,函数内部将其改变后,函数外部这个变量不会改变

## Python 实现对函参做类型检查

`Python` 自带的函数一般都会有对函数参数类型做检查,自定义的函数参数类型检查可以用函数 `isinstance()` 实现,例如:

```python
def my_abs(x):
    """
    自定义的绝对值函数
    :param x: int or float
    :return: positive number, int or float
    """
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x > 0:
        return x
    else:
        return -x
```

添加了参数检查后,如果传入错误的参数类型,函数就可以抛出一个 `TypeError` 错误。

## 为什么说 Python 是动态语言

在 `Python` 中,等号 `=` 是赋值语句,可以把`任意数据类型`赋值给变量,同样一个变量可以反复赋值,而且可以是不同类型的变量,例如:

```python
a = 100 # a是int型变量
print(a)
a = 'ABC'  # a 是str型变量
print(a)
```

Pyhon 这种变量本身类型不固定,可以反复赋值不同类型的变量称为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错,Java/C++ 都是静态语言(`int a; a = 100`)

## Python 装饰器理解

装饰器本质上是一个 Python 函数或类,**它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能**,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以**抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用**。概括的讲,**装饰器的作用就是为已经存在的对象添加额外的功能**。

## map 与 reduce 函数用法解释

1、`map()` 函数接收两个参数,一个是函数,一个是 Iterable,map 将传入的函数依次作用到序列的**每个元素**,并将结果作为新的 Iterator 返回,简单示例代码如下:

```python
# 示例1
def square(x):
    return x ** 2
r = map(square, [1, 2, 3, 4, 5, 6, 7])
squareed_list = list(r)
print(squareed_list)  # [1, 4, 9, 16, 25, 36, 49]
# 使用lambda匿名函数简化为一行代码
list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
# 示例2
list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])) # ['1', '2', '3', '4', '5', '6', '7', '8', '9']
```

**注意map函数返回的是一个Iterator(惰性序列),要通过list函数转化为常用列表结构**。map()作为高阶函数,事实上它是把运算规则抽象了。

2、`reduce()` 函数也接受两个参数,一个是函数(**两个参数**),一个是序列,与 `map` 不同的是**reduce 把结果继续和序列的下一个元素做累积计算**,效果如下:
`reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)`

示例代码如下:

```python
from functools import reduce
CHAR_TO_INT = {
    '0': 0,
    '1': 1,
    '2': 2,
    '3': 3,
    '4': 4,
    '5': 5,
    '6': 6,
    '7': 7,
    '8': 8,
    '9': 9
}
def str2int(str):
    ints = map(lambda x:CHAR_TO_INT[x], str)  # str对象是Iterable对象
    return reduce(lambda x,y:10*x + y, ints)
print(str2int('0'))
print(str2int('12300'))
print(str2int('0012345'))  # 0012345
```

## Python 深拷贝、浅拷贝区别

> Python 中的大多数对象,比如列表 `list`、字典 `dict`、集合 `set`、`numpy` 数组,和用户定义的类型(类),都是可变的。意味着这些对象或包含的值可以被修改。但也有些对象是不可变的,例如数值型 `int`、字符串型 `str` 和元组 `tuple`。

**1、复制不可变数据类型:**

复制不可变数据类型,不管 `copy` 还是 `deepcopy`, 都是同一个地址。当浅复制的值是不可变对象(数值,字符串,元组)时和=“赋值”的情况一样,对象的 `id` 值与浅复制原来的值相同。

**2、复制可变数据类型:**

1. 直接赋值:其实就是对象的引用(别名)。
2. 浅拷贝(`copy`):拷贝父对象,不会拷贝对象内部的子对象(拷贝可以理解为创建内存)。产生浅拷贝的操作有以下几种:
    + 使用切片 `[:]` 操作
    + 使用工厂函数(如 `list/dir/set` ), 工厂函数看上去像函数,实质上是类,调用时实际上是生成了该类型的一个实例,就像工厂生产货物一样.
    + 使用`copy` 模块中的 `copy()` 函数,`b = a.copy()`, `a` 和 `b` 是一个独立的对象,**但他们的子对象还是指向统一对象(是引用)**。
3. 深拷贝(`deepcopy`): copy 模块的 `deepcopy()` 方法,**完全**拷贝了父对象及其子对象,两者是完全独立的。**深拷贝,包含对象里面的子对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变**。

**注意:浅拷贝和深拷贝的不同仅仅是对组合对象来说,所谓的组合对象(容器)就是包含了其它对象的对象,如列表,类实例。而对于数字、字符串以及其它“原子”类型(没有子对象),没有拷贝一说,产生的都是原对象的引用。**更清晰易懂的理解,可以参考这篇[文章](https://www.runoob.com/w3cnote/python-understanding-dict-copy-shallow-or-deep.html)。

看一个示例程序,就能明白浅拷贝与深拷贝的区别了:

```python
#!/usr/bin/Python3
# -*-coding:utf-8 -*-

import copy
a = [1, 2, 3, ['a', 'b', 'c']]

b = a  # 赋值,传对象的引用
c = copy.copy(a)  # 浅拷贝
d = copy.deepcopy(a)  # 深拷贝

a.append(4)
a[3].append('d')

print(id(a), id(b), id(c), id(d))  # a 与 b 的内存地址相同
print('a = ', a)
print('b = ', b)
print('c = ', c)
print('d = ', d)  # [1, 2, 3, ['a', 'b', 'c']]
```

程序输出如下:
> 2061915781832 2061915781832 2061932431304 2061932811400
>
> a =  [1, 2, 3, ['a', 'b', 'c', 'd'], 4]
> b =  [1, 2, 3, ['a', 'b', 'c', 'd'], 4]
> c =  [1, 2, 3, ['a', 'b', 'c', 'd']]
> d =  [1, 2, 3, ['a', 'b', 'c']]

## Python 继承多态理解

- 多态是指对不同类型的变量进行相同的操作,它会根据对象(或类)类型的不同而表现出不同的行为。
- 继承可以拿到父类的所有数据和方法,子类可以重写父类的方法,也可以新增自己特有的方法。
- 先有继承,后有多态,不同类的对象对同一消息会作出不同的相应。

## Python 面向对象的原则

+ [Python 工匠:写好面向对象代码的原则(上)](https://www.zlovezl.cn/articles/write-solid-python-codes-part-1/)
+ [Python 工匠:写好面向对象代码的原则(中)](https://www.zlovezl.cn/articles/write-solid-python-codes-part-2/)
+ [Python 工匠:写好面向对象代码的原则(下)](https://www.zlovezl.cn/articles/write-solid-python-codes-part-3/)

## 参考资料

1. 参考[这里](https://zhuanlan.zhihu.com/p/23526961)
2. [110道Python面试题(真题)](https://zhuanlan.zhihu.com/p/54430650)
3. [关于Python的面试题](https://github.com/taizilongxu/interview_Python#15-__new__%E5%92%8C__init__%E7%9A%84%E5%8C%BA%E5%88%AB)
4. [继承和多态](http://funhacks.net/explore-python/Class/inheritance_and_polymorphism.html)
5. [Python 直接赋值、浅拷贝和深度拷贝解析](https://www.runoob.com/w3cnote/python-understanding-dict-copy-shallow-or-deep.html)


================================================
FILE: 2-programming_language/python3/python图像处理-读取图像方式总结.md
================================================
- [读取并显示图像](#读取并显示图像)
  - [opencv3库](#opencv3库)
  - [scikit-image库](#scikit-image库)
  - [PIL库](#pil库)
  - [读取图像结果分析](#读取图像结果分析)
- [打印图像信息](#打印图像信息)
  - [skimage获取图像信息](#skimage获取图像信息)
  - [PIL获取图像信息](#pil获取图像信息)
- [读取并显示图像方法总结](#读取并显示图像方法总结)
  - [PIL库读取图像](#pil库读取图像)
  - [Opencv3读取图像](#opencv3读取图像)
  - [scikit-image库读取图像](#scikit-image库读取图像)
  - [参考资料](#参考资料)

学习数字图像处理,第一步就是读取图像。这里我总结下如何使用 opencv3,scikit-image, PIL 图像处理库读取图片并显示。

## 读取并显示图像
### opencv3库
opencv 读取图像,返回的是矩阵数据,RGB 图像的 shape 是 (height, weight, channel),dtype 是 uint8。

示例代码如下:

```python
import cv2
# 读入一副彩色图像
img_cv2 = cv2.imread('test.jpg',cv2.IMREAD_COLOR)
# 打印图像尺寸,形状,图像元素数据类型
print(type(img_cv2))
print(img_cv2.shape)    # (height, width, channel)
print(img_cv2.dtype)    # uint8
# matplotlib绘制显示图像
plt.figure(1)
plt.imshow(img_PIL)
plt.show()
# cv2绘制显示图像
# cv2.imshow()
# cv2.namedWindow('image', cv2.WINDOW_NORMAL)
# cv2.imshow('image',img_cv2)
# cv2.waitKey(0)
# cv2.destroyAllWindows()
```
### scikit-image库
示例代码如下:

```python
from skimage import io
img_skimage = io.imread('test.jpg')
# 打印图像尺寸
print(img_skimage.shape)    #(height, width, channel)
# 绘制显示图像
io.imshow(img_skimage)
# import matplotlib.pyplot as plt
# plt.imshow(img_skimage)
```
注意:io.imshow(img\_skimage),这一行代码的实质是利用matplotlib包对图片进行绘制,绘制成功后,返回一个matplotlib类型的数据。也就是说scikit-image库对图像的绘制实际上是调用了matplotlib库imshow显示函数。

cv2和skimage读取图像,图像的尺寸可以通过其shape属性来获取,shape返回的是一个tuple元组,第一个元素表示图像的高度,第二个表示图像的宽度,第三个表示像素的通道数。

### PIL库
示例代码如下:

```python
# PIL库读取绘制显示图像
# plt 用于显示图片
from PIL import Image
import matplotlib.pyplot as plt

import numpy as np
img_PIL = Image.open('test.jpg')
img_PIL = np.array(img_PIL)
# 打印图像类型,尺寸和总像素个数
print(type(img_PIL)) # <class 'numpy.ndarray'>
print(img_PIL.shape) # (height, width, channel), (1200, 1793, 3)
print(img_PIL.size)  # 6454800 = 1200*1793*3
# 绘制显示图像
plt.figure(1)
plt.imshow(img_PIL)
plt.show()
```
### 读取图像结果分析
分别用Opnecv3和sckit-image读取图像,并用matplotlib库显示。示例代码如下:

```python
import cv2
from skimage import io
import matplotlib.pyplot as plt
img_cv2 = cv2.imread('test.jpg',cv2.IMREAD_COLOR)
img_skimage = io.imread('test.jpg')
# matplotlib显示cv2库读取的图像
plt.figure('imread picture',figsize=(25,25))
plt.subplot(121)
plt.title('cv2 imread picture')
plt.imshow(img_cv2)
# matplotlib显示skimage库读取的图像
plt.subplot(122)
plt.title('skimage imread picture')
plt.imshow(img_skimage)
# 打印图像尺寸,总像素个数,和图像元素数据类型
print(img_cv2.shape)
print(img_cv2.size)
print(img_cv2.dtype)
```
![image](../../data/images/python_read_images/zk5YgqAS7448RIT1Bmq40vrCPHLp3pKrCS-_ilrL6Yw.png)

通过以上输出结果对比图,我们会发现,matplotlib绘制显示的cv2库读取的图像与原图有所差别,这是因为opencv3库读取图像的通道时BGR,而正常图像读取的通道都是RGB,matplotlib库显示图像也是按照RGB顺序通道来的,解释完毕。

一点疑惑,我通过查询库函数可知plt.show()第一个参数为要显示的对象(array\_like),字面意思理解为类似数组的对象,但是很明显,PIL库返回的不是’numpy.ndarray’对象,而是’PIL.JpegImagePlugin.JpegImageFile’对象,那为什么plt.show()函数还是能显示Image.open()函数读取图像返回的结果呢?

程序如下图所示:

![image](../../data/images/python_read_images/WYCTaeuwXi9ICkRMdEn3K0jvGsddfmr415rW0Wl_UCk.png)

## 打印图像信息
图像常用信息有图像尺寸,像素个数,通道数等。

### skimage获取图像信息
**注意:scikit-image 库读取和缩放图像速度要慢 opencv 库 近 4 倍。**

```Plain Text
from skimage import io, data
# create coffee image, return (300, 451, 3) uint8 ndarray
img = data.coffee()
io.imshow(img)      # 显示图片
print(type(img))    # 显示类型
print(img.dtype)    # 显示图像元素数据类型
print(img.shape)    # 显示尺寸
print(img.shape[0]) # 图片高度
print(img.shape[1]) # 图片宽度
print(img.shape[2]) # 图片通道数
print(img.size)     # 显示总像素个数=shape[0]*shape[1]*shape[2]
print(img.max())    # 最大像素值
print(img.min())    # 最小像素值
print(img.mean())   # 像素平均值
print(img[0][0])    # 图像第一行第一列的像素值
```
输出结果如下图:

![image](../../data/images/python_read_images/En5A4Xt4naL49ocumGSh2pHXKBMxqoud1KVL7blZAmw.png)

### PIL获取图像信息
```Plain Text
# 获取PIL image图片信息
im = Image.open('test.jpg')
print (type(im))
print (im.size) #图片的尺寸
print (im.mode) #图片的模式
print (im.format) #图片的格式
print (im.getpixel((0,0)))#得到像素:
# img读出来的图片获得某点像素用getpixel((w,h))可以直接返回这个点三个通道的像素值
```
输出结果如下:

![image](../../data/images/python_read_images/cqyXYva-pIgE7adHMXrskm-r2h6ivTP5CQeoNWPiaH8.png)

plt.show函数定义如下:

> Signature: plt.imshow(X, cmap=None, norm=None, aspect=None, interpolation=None, alpha=None, vmin=None, vmax=None, origin=None, extent=None, shape=None, filternorm=1, filterrad=4.0, imlim=None, resample=None, url=None, hold=None, data=None, \*\*kwargs)
Docstring:
Display an image on the axes.

> Parameters
———-
X : array\_like, shape (n, m) or (n, m, 3) or (n, m, 4). Display the image in XX to current axes. XX may be an array or a PIL image. If XX is an array, it can have the following shapes and types:

> – MxN — values to be mapped (float or int)
– MxNx3 — RGB (float or uint8)
– MxNx4 — RGBA (float or uint8)

> The value for each component of MxNx3 and MxNx4 float arrays should be in the range 0.0 to 1.0. MxN arrays are mapped to colors based on the ∥∥∥∥ (mapping scalar to scalar) and the cmapcmap (mapping the normed scalar to a color).

## 读取并显示图像方法总结
### PIL库读取图像
> PIL.Image.open + numpy
scipy.misc.imread
scipy.ndimage.imread
这些方法都是通过调用PIL.Image.open 读取图像的信息;
**PIL.Image.open 不直接返回numpy对象**,可以用numpy提供的函数进行转换,参考Image和Ndarray互相转换;
scipy.ndimage.imread直接返回numpy.ndarray对象,通道顺序为RGB,通道值得默认范围为0-255。

### Opencv3读取图像
> cv2.imread: 使用opencv读取图像,直接返回numpy.ndarray 对象,通道顺序为BGR ,注意是**BGR**,通道值默认范围0-255。

### scikit-image库读取图像
> skimage.io.imread: 直接返回numpy.ndarray 对象,通道顺序为RGB,通道值默认范围0-255。

### 参考资料
* [https://blog.csdn.net/renelian1572/article/details/78761278](https://blog.csdn.net/renelian1572/article/details/78761278)
* [https://pillow.readthedocs.io/en/5.3.x/index.html](https://pillow.readthedocs.io/en/5.3.x/index.html)
* [http://scikit-image.org/docs/stable/user\_guide.html](http://scikit-image.org/docs/stable/user_guide.html)

================================================
FILE: 2-programming_language/python3/python数据分析-堆叠数组函数总结.md
================================================
- [numpy 堆叠数组](#numpy-堆叠数组)
- [ravel() 函数](#ravel-函数)
- [stack() 函数](#stack-函数)
- [vstack()函数](#vstack函数)
- [hstack()函数](#hstack函数)
- [concatenate() 函数](#concatenate-函数)
- [参考资料](#参考资料)

## numpy 堆叠数组

在做图像和 nlp 的数组数据处理的时候,经常需要实现两个数组堆叠或者连接的功能,这就需用到 `numpy` 库的一些函数,numpy 库中的常用**堆叠数组**函数如下:

* `stack` : Join a sequence of arrays along a new axis.
* `hstack`: Stack arrays in sequence horizontally (column wise).
* `vstack` : Stack arrays in sequence vertically (row wise).
* `dstack` : Stack arrays in sequence depth wise (along third axis).
* `concatenate` : Join a sequence of arrays along an existing axis.

## ravel() 函数

ravel() 方法可让将多维数组展平成一维数组。如果不指定任何参数,ravel() 将沿着行(第 0 维/轴)展平/拉平输入数组。

示例代码如下:

```python
std_array = np.random.normal(3, 2.5, size=(2, 4))
array1d = std_array.ravel()
print(std_array)
print(array1d)
```

程序输出结果如下:

```shell
[[5.68301857 2.09696067 2.20833423 2.83964393]
 [2.38957339 9.66254303 1.58419716 2.82531094]]
 
[5.68301857 2.09696067 2.20833423 2.83964393 2.38957339 9.66254303 1.58419716 2.82531094]
```
## stack() 函数

stack() 函数原型是 stack(*arrays*, _axis_=0, _out_=*None*),功能是沿着**给定轴**连接数组序列,轴默认为第0维,即默认沿着第 0 维 stacks 数组。

1,**参数解析:**
- arrays: 类似数组(数组、列表)的序列,这里的**每个数组必须有相同的shape。**
- axis: 默认为整形数据,axis决定了沿着哪个维度stack输入数组。

2,**返回:**
- stacked : `ndarray` 类型。The stacked array has one more dimension than the input arrays.

实例如下:

```python
import numpy as np
# 一维数组进行stack
a1 = np.array([1, 3, 4])    # shape (3,)
b1 = np.array([4, 6, 7])    # shape (3,)
c1 = np.stack((a,b))
print(c1)
print(c1.shape)    # (2,3)
# 二维数组进行堆叠
a2 = np.array([[1, 3, 5], [5, 6, 9]])    # shape (2,3)
b2 = np.array([[1, 3, 5], [5, 6, 9]])    # shape (2,3)
c2 = np.stack((a2, b2), axis=0)
print(c2)
print(c2.shape)
```
输出为:

> [[1 3 4]
[4 6 7]]

> (2, 3)

> [[[1 3 5]
[5 6 9]]
[[1 3 5]
[5 6 9]]]
(2, 2, 3)

可以看到,进行 stack 的两个数组必须**有相同的形状**,同时,输出的结果的维度是比输入的数组都要多一维的。我们拿第一个例子来举例,两个含 3 个数的一维数组在第 0 维进行堆叠,其过程等价于先给两个数组增加一个第0维,变为1*3的数组,再在第 0 维进行 `concatenate()` 操作:

```python
a = np.array([1, 3, 4])
b = np.array([4, 6, 7])
a = a[np.newaxis,:]
b = b[np.newaxis,:]
np.concatenate([a,b],axis=0)
```
输出为:

> array([[1, 2, 3],
      [2, 3, 4]])

## vstack()函数
vstack函数原型是vstack(tup),功能是垂直的(按照行顺序)堆叠序列中的数组。tup是数组序列(元组、列表、数组),数组必须在所有轴上具有相同的shape,除了第一个轴。1-D arrays must have the same length.

```python
# 一维数组
a = np.array([1, 2, 3])
b = np.array([2, 3, 4])
np.vstack((a,b))
```
> array([[1, 2, 3],
[2, 3, 4]])

```Plain Text
# 二维数组
a = np.array([[1], [2], [3]])
b = np.array([[2], [3], [4]])
np.vstack((a,b))
```
> array([[1],
[2],
[3],
[2],
[3],
[4]])

## hstack()函数
hstack()的函数原型:hstack(tup) ,参数tup可以是元组,列表,或者numpy数组,返回结果为numpy的数组。它其实就是**水平(按列顺序)**把数组给堆叠起来,与vstack()函数正好相反。举几个简单的例子:

```python
# 一维数组
a = np.array([1, 2, 3])
b = np.array([2, 3, 4])
np.hstack((a,b))
```
> array([1, 2, 3, 2, 3, 4])

```python
# 二维数组
a = np.array([[1], [2], [3]])
b = np.array([[2], [3], [4]])
np.hstack((a,b))
```
> array([[1, 2],
[2, 3],
[3, 4]])

**vstack()和hstack函数对比:**

这里的**v**是vertically的缩写,代表垂直(沿着行)堆叠数组,这里的**h**是horizontally的缩写,代表水平(沿着列)堆叠数组。
tup是数组序列(元组、列表、数组),数组必须在所有轴上具有相同的shape,除了第一个轴。  

## concatenate() 函数

concatenate()函数功能齐全,理论上可以实现上面三个函数的功能,concatenate()函数**根据指定的维度,对一个元组、列表中的list或者ndarray进行连接**,函数原型:

```python
numpy.concatenate((a1, a2, ...), axis=0)
```

```python
a = np.array([[1, 2], [3,4]])               
b = np.array([[5, 6], [7, 8]])
# a、b的shape为(2,2),连接第一维就变成(4,2),连接第二维就变成(2,4)
np.concatenate((a, b), axis=0)
```
> array([[1, 2],
[3, 4],
[5, 6],
[7, 8]])

**注意:axis指定的维度(即拼接的维度)可以是不同的,但是axis之外的维度(其他维度)的长度必须是相同的**。注意 concatenate 函数使用最广,必须在项目中熟练掌握。

## 参考资料
- [numpy中的hstack()、vstack()、stack()、concatenate()函数详解](https://mp.weixin.qq.com/s?__biz=MzI1MzY0MzE4Mg==&mid=2247484138&idx=2&sn=f1dca4b3790284371fe103b2108d92a6&chksm=e9d0122bdea79b3d832612ae41a68e120a74764d0b557ffc286da1c4163f397c4e780a77a5b9&mpshare=1&scene=1&srcid=0501xRYNtB00dNBcW8UlLzml#rd)





================================================
FILE: 2-programming_language/shell/Shell语法基础.md
================================================
- [Shell 变量](#shell-变量)
	- [使用变量](#使用变量)
	- [只读变量](#只读变量)
	- [删除变量](#删除变量)
	- [变量类型](#变量类型)
- [Shell 字符串](#shell-字符串)
	- [单引号与双引号字符串](#单引号与双引号字符串)
	- [获取字符串长度](#获取字符串长度)
	- [提取子字符串](#提取子字符串)
	- [拼接字符串](#拼接字符串)
- [Shell 数组](#shell-数组)
	- [定义数组](#定义数组)
	- [读取数组](#读取数组)
	- [获取数组的长度](#获取数组的长度)
- [Shell 传递参数](#shell-传递参数)
- [Shell 基本运算符](#shell-基本运算符)
	- [算术运算符](#算术运算符)
	- [关系运算符](#关系运算符)
	- [布尔运算符](#布尔运算符)
	- [逻辑运算符](#逻辑运算符)
	- [字符串运算符](#字符串运算符)
- [Shell 信息输出命令](#shell-信息输出命令)
	- [Shell echo 命令](#shell-echo-命令)
	- [Shell printf 命令](#shell-printf-命令)
	- [%d %s %c %f 格式替代符详解:](#d-s-c-f-格式替代符详解)
	- [printf 的转义序列](#printf-的转义序列)
- [Shell test 命令](#shell-test-命令)
	- [数值测试](#数值测试)
	- [test 检查文件属性](#test-检查文件属性)
- [Shell 流程控制](#shell-流程控制)
	- [if else](#if-else)
	- [if else-if else](#if-else-if-else)
	- [for 循环](#for-循环)
	- [while 语句](#while-语句)
- [Shell 函数](#shell-函数)
	- [局部变量与全局变量](#局部变量与全局变量)
	- [递归函数](#递归函数)
- [常用命令](#常用命令)
- [Shell 正则表达式](#shell-正则表达式)
- [参考资料](#参考资料)

## Shell 变量

在 Shell 脚本中,定义变量直接赋值即可,使用变量时需要在变量名前加美元符号`$`,注意**定义变量时变量名和等号之间不能有空格**。变量名的命名必须遵循以下规则:

+ 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
+ 中间不能有空格,可以使用下划线(_)。
+ 不能使用标点符号。
+ 不能使用 bash 里的关键字(可用 help 命令查看保留关键字)。

### 使用变量

使用一个定义过的变量,只要在变量名前面加美元符号即可(推荐给所有变量加上花括号,这是一个好的编程习惯),如:

```Shell
#!/bin/bash
my_name="hongghao.zhang"
echo $my_name
echo ${my_name}
```

> hongghao.zhang
hongghao.zhang

### 只读变量

使用 `readonly` 命令可以将变量定义为只读变量,只读变量的值不能被改变。

### 删除变量

使用 `unset` 变量可以删除变量,语法:`unset variable_name`。

### 变量类型

运行 shell 时,会同时存在三种变量:
1) 局部变量: 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
2) **环境变量**: **所有的程序,包括shell启动的程序,都能访问环境变量**,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
3) shell变量: shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行。

## Shell 字符串

字符串是 shell 编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号,也可以不用引号。单双引号的区别跟 PHP 类似。

### 单引号与双引号字符串

单引号字符串限制:

+ 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
+ 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。

双引号字符串优点:

+ 双引号里可以有变量;
+ 双引号里可以出现**转义字符**,Shell 脚本程序字符型建议都用双引号。

### 获取字符串长度

```Shell
string="honggao.zhang"
echo ${#string}  # 输出13
```

### 提取子字符串

下面实例从字符串第8个字符开始截取5个字符:

```Shell
string="honggao.zhang"
echo ${string:7:5}  # 输出zhang
```

### 拼接字符串

实际脚本中,拼接字符串可能有以下场景:灵活应用即可。

```python
your_name="qinjx"
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1
```

## Shell 数组

bash 支持一维数组,不支持多维数组,并且没有限定数组的大小。类似 C 语言,数组的元素下标也是从 0 开始。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于 0。 

### 定义数组

在 Shell 中,用括号来表示数组,数组元素用"空格"符号分割开。定义数组的一般形式为:

```Shell
数组名=(值1 值2 ... 值n)
```

### 读取数组

读取数组元素值的一般格式是:
```Shell
${数组名[下表标]}
```
使用 @ 符号可以获取数组中的所有元素,例如:
```Shell
echo ${array_name[@]}
```
### 获取数组的长度
获取数组长度的方法与获取字符串长度的方法相同,例如:
```Shell
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}
```
更多内容参考[shell脚本——字符串 数组](https://blog.csdn.net/weixin_42167759/article/details/80702517)。

## Shell 传递参数

命令行执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:`$n`。n 代表一个数字,1 为脚本的第一个参数,2 为脚本的第二个参数,以此类推。特殊字符表示的参数如下:

|参数处理 | 说明|
|--------------|-------|
|$# |传递到脚本的参数个数|
|$$	|脚本运行的当前进程ID号|
|$!	|后台运行的最后一个进程的ID号|
|$\* |以一个单字符形式显示所有向脚本传递的参数,"\$1 \$2 ... $n"的形式输出所有参数|
|$@|与 * 相同,但是使用时加引号,并在引号中返回每个参数。如 `"$@"` 用「"」括起来的情况、以 "\$1" "\$2" … "\$n" 的形式输出所有参数。|
|$-|显示 Shell 使用的当前选项,与set命令功能相同。|
|$?|显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。|

示例代码如下:

```Shell
#!/bin/bash
# author:harley
echo "=== $* 演示 ==="
for i in "$*"; do
    echo $i
done
echo "====$@ 演示==="
for i in "$@";do
    echo $i
done
```

执行脚本,`bash demo1.sh harley zhang hong`,输出结果如下:

```
=== $* 演示 ===
harley zhang hong
====$@ 演示===
harley
zhang
hong
```

## Shell 基本运算符

Shell 支持多种运算符,如下:

+ 算法运算符
+ 关系运算符
+ 布尔运算符
+ 字符串运算符
+ 文件测试运算符

**字符串判断相等用`=`,数值判断相等用`==`。原生 bash 不支持简单的数学运算**,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。`expr`是一款表达式计算工具,使用它能完成表达式的求值操作。用法如下:

```Shell
#!/bin/bash
val=`expr 3 + 6`
echo "两数之和为:" ${val}  # 两数之和为:9
```

注意:

+ **表达式和运算符之间要有空格**,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。
+ **条件表达式都要放在方括号之间,并且要有空格**,例如: [$a==$b] 是错误的,必须写成 [&emsp;\$a&emsp;==&emsp;\$b&emsp;]。
+ 完整的算数表达式要被 \` \` 包含,注意这个字符不是常用的单引号,在 Esc 键下边。
+ **bash 不支持浮点运算**,如果需要进行浮点运算,需要借助 bc,awk 处理。

### 算术运算符

|运算符|说明|举例|
|---------|-------|-----|
|+|	加法	|`expr $a + $b` 结果为 30|
|-|	减法|	`expr $a - $b` 结果为 -10|
|\*|	乘法|	`expr $a \* $b` 结果为 200|
|/|除法|	`expr $b / $a` 结果为 2|
|%|取余|	`expr $b % $a` 结果为 0|
|=|赋值|	a=$b 将把变量 b 的值赋给 a|
|==|相等|用于比较两个数字,相同则返回 true。 `[ $a == $b ]` 返回 false|
|!=|不相等|用于比较两个数字,不相同则返回 true。 `[ $a != $b ]` 返回 true|

算数运算符实例脚本如下:
```shelll
#!/bin/bash
a=10
b=20

val=`expr $a + $b`
echo "a + b : $val"
val=`expr $a - $b`
echo "a - b : $val"
val=`expr $a \* $b`
echo "a * b : $val"
```
脚本运行结果如下:
> a + b : 30
a - b : -10
a * b : 200

### 关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。Shell 的关系运算符和 C/C++/Python 不一样,它们的大于用 `>` 表示即可,但是 Shell 得用关键字表示,下表列出了常用得关系运算符,假定变量 a 为 10,变量 b 为 20:

|参数|说明|举例|
|------|-----|-------|
|-eq|等于则为真|`[ $a -eq $b]`返回false|
|-ne|不等于则为真|`[ $a -ne $b]`返回true|
|-gt|大于则为真|`[ $a -gt $b]`返回false|
|-ge|大于等于则为真|`[ $a -ge $b]`返回false|
|-lt|小于则为真|`[ $a -lt $b]`返回true|
|-le|小于等于则为真|`[ $a -le $b]`返回true|

这些关系运算符初学时不必全部记住,编写脚本用到时再来查询也可。

### 布尔运算符

|运算符|说明|举例|
|---------|------|------|
|`!`|非运算符,表达式为 true 则返回 false,否则返回 true |`[ ! false ]` 返回 true |
|`-o`|或运算,有一个表达式为 true 则返回 true |`[ $a -lt 20 -o $b -gt 100 ]` 返回 true |
|`-a`|与运算,两个表达式都为 true 才返回 true |`[ $a -lt 20 -a $b -gt 100 ]` 返回 false |

实例代码如下:
```shell
$ a=120;if [ $a != 120 ];then echo "a != 120";else echo "a == 120";fi   # ! 运算符的用法
a == 120
```

### 逻辑运算符

|运算符|说明|举例|
|---------|------|------|
| `&&` |逻辑的 AND|`[[ $a -lt 100 && $b -gt 100 ]]`|返回 false |
| `||` |逻辑的 OR| `[[ $a -lt 100 || $b -gt 100 ]]` |返回 true|

### 字符串运算符

下表列出了常用的字符串运算符,假定变量 a 为 "abc",变量 b 为 "efg":

|运算符|说明|举例|
|---------|------|------|
|=|检测两个字符串是否相等,相等返回 true|`[ $a = $b ]` 返回 false|
|!=|检测两个字符串是否相等,不相等返回 true| `[ $a != $b ]` 返回 true|
|-z|	检测字符串长度是否为0,为0返回 true| `[ -z $a ]` 返回 false|
|-n|	检测字符串长度是否为0,不为0返回 true| `[ -n "$a" ]` 返回 true|
|\$|检测字符串是否为空,不为空返回true| `[ $a ]`返回true|

字符串运算符使用示例代码如下:
```Shell
#!/bin/bash
a="abc"
b="efg"

if [ $a = $b ]
then
   echo "$a = $b : a 等于 b"
else
   echo "$a = $b: a 不等于 b"
fi
if [ $a != $b ]
then
   echo "$a != $b : a 不等于 b"
else
   echo "$a != $b: a 等于 b"
fi
if [ -z $a ]
then
   echo "-z $a : 字符串长度为 0"
else
   echo "-z $a : 字符串长度不为 0"
fi
if [ -n "$a" ]
then
   echo "-n $a : 字符串长度不为 0"
else
   echo "-n $a : 字符串长度为 0"
fi
if [ $a ]
then
   echo "$a : 字符串不为空"
else
   echo "$a : 字符串为空"
fi
```
执行脚本,输出结果如下:
> abc = efg: a 不等于 b
abc != efg : a 不等于 b
-z abc : 字符串长度不为 0
-n abc : 字符串长度不为 0
abc : 字符串不为空

## Shell 信息输出命令

### Shell echo 命令

echo 命令用于字符串的输出,`echo`打印字符串默认换行。

### Shell printf 命令

printf 命令和 echo 命令类似,都是用于信息的输出。

+ printf 命令模仿 C 程序库(library)里的 printf() 程序。
+ printf 由 POSIX 标准所定义,因此使用 printf 的脚本比使用 echo 移植性好。
+ printf 使用引用文本或空格分隔的参数,外面可以在 printf 中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认 printf 不会像 echo 自动添加换行符,我们可以手动添加 `\n`。

`printf` 命令语法如下:
```
printf format-string [arguments...]
```
**参数说明**:
+ format-string: 为格式控制字符串
+ arguments:为参数列表

示例程序如下:
```Shell
#!/bin/bash
printf "%-10s %-8s %-4s %12s\n" 姓名 性别 体重kg 学号
printf "%-10s %-8s %-4.2f %12d\n" 郭靖 男 66.1234 2017210675
printf "%-10s %-8s %-4.2f %12d\n" 杨过 男 48.6543 2017210688
printf "%-10s %-8s %-4.2f %12d\n" 郭芙 女 47.9876 2017210889
```
执行脚本,程序输出如下:
> 姓名 性别 体重kg 学号
郭靖 男 66.12 2017210675
杨过 男 48.65 2017210688
郭芙 女 47.99 2017210889

格式控制字符串解释:
+ %s %c %d %f都是格式替代符
+ %-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
+ %-4.2f 指格式化为小数,其中**.2指保留2位小数**。

### %d %s %c %f 格式替代符详解:

+ d: Decimal 十进制整数 -- 对应位置参数必须是十进制整数,否则报错!
+ s: String 字符串 -- 对应位置参数必须是字符串或者字符型,否则报错!
+ c: Char 字符 -- 对应位置参数必须是字符串或者字符型,否则报错!
+ f: Float 浮点 -- 对应位置参数必须是数字型,否则报错!

### printf 的转义序列

|序列|说明|
|-------|-----|
|`\a`| 警告字符,通常为ASCII的BEL字符|
|`\f`|换页|
|`\n`|换行|
|`\t`|水平制表符|
|`\r`|回车|

## Shell test 命令

Shell 中的 test 命令用于检查某个条件是否成立,可以进行数值、字符和文件三个方面的测试。

### 数值测试
这是关系运算符,只支持数字,不支持字符串,除非字符串的值是数字。

|参数|说明|
|------|-----|
|-eq| 等于则为真|
|-ne|	不等于则为真|
|-gt|	大于则为真|
|-ge|	大于等于则为真|
|-lt|	小于则为真|
|-le|	小于等于则为真|

符号含义:
1. eq (equal的缩写),表示等于为真
2. ne (not equal的缩写),表示不等于为真
3. gt (greater than的缩写),表示大于为真
4. ge (greater&equal的缩写),表示大于等于为真
5. lt (lower than的缩写),表示小于为真
6. le (lower&equal的缩写),表示小于等于为真

实例代码如下:
```Shell
#!/bin/bash
# 关系运算符判断
num1=100
num2=333
if test $num1 -eq $num2
then
  echo "两个数相等"
else
  echo "两个数不相等"
fi
# 算术运算符判断
str1="honggao"
str2="hong.hao"
echo "传递的参数为: $*"
if [ $1 = $2i ]
then
  echo "两个输入字符串相等"
else
  echo "输入的两个字符串不相等"
fi
```
执行脚本(`sh comm_test.sh eere wdwe2`),输出如下:
> 两个数不相等
传递的参数为: eere wdwe2
输入的两个字符串 不相等

### test 检查文件属性

检查文件属性也是 `test` 的常见用法,比如检查一个文件类型是不是普通文件,可以使用 `-f` 选项,检查路径是否是目录可以用 `-d` 选项:
```python
touch test.sh
filename="test.sh"
# 检查文件
if test -f "$filename";then
    echo "It's a regular file."
fi
# 检查目录
dirname="test_directory"
mkdir $dirname
if test -d "$dirname";then
    echo "It's a directory."
fi
```

运行脚本,输出如下:

![test命令输出](../../data/images/test命令测试.png)

`test` 命令是shell编程中非常重要的命令,一定要掌握!下面是其他一些常用的文件检查运算符:

```shell
-b file : 文件存在并且是块设备文件。
-c file : 文件存在并且是字符设备文件。
-d file : 文件存在并且是一个目录。
-e file : 文件存在。
-f file : 文件存在并且是一般文件。
-g file : 文件存在并且设置了 setgid 位。
-h file : 文件存在并且是一个链接文件。
-p file : 文件存在并且是一个命名管道(FIFO)。
-r file : 文件存在并且是可读的。
-s file : 文件存在并且有内容。
-u file : 文件存在并且设置了 setuid。
-w file : 文件存在并且是可写的。
-x file : 文件存在并且是可执行的。
-S file : 文件存在并且是一个 socket。
```
## Shell 流程控制

`Shell` 的流程控制不可为空。

### if else

if else语法格式:

```Shell
if condition
then
    command1
    command2
    command3
else
    command
fi
```

### if else-if else

`if else-if else` 语法格式如下:
```Shell
if condition1
then
    command1
elif condition2
then
    command2
else
    commandN
fi
```

根据 width、height 计算 BMI 指数脚本实例代码如下:

```Shell
echo "pleae input your weight and height"  # 无法支持输入小数
pf=`expr $2 \* $2`
bmi=`expr $1 / $pf`
echo "your bmi is: $bmi"
a=18
b=25
c=28
d=32
if [ $bmi -le $a ]
then
  echo "体重过轻"
elif [ $bmi -le $b ]
then
  echo "体重正常"
elif [ $bmi -le $c ]
then
  echo "体重过重"
elif [ $bmi -le $d ]
then
  echo "体重肥胖"
elif [ $bmi -gt $d ]
then
  echo "严重肥胖"
fi
```

执行脚本(`sh if_else.sh 64 2`),程序输出如下:
> pleae input your weight and height
your bmi is: 16
体重过轻

### for 循环

for 循环格式为:
```Shell
for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done
```

### while 语句

while 循环用于不断执行一系列命令,也可用于从输入文件中读取数据;命令通常为测试条件,其格式为:
```Shell
while condition
do
    command
done
```

## Shell 函数

shell 函数中的定义格式如下:

```python
[ function ] funname [()]
{
    action;
    [return int;]
}
```

参数说明:
+ 可以带 function fun() 定义,也可以直接 `fun()` 定义,不带任何参数。
+ 执行函数直接使用 `funname` 即可。

### 局部变量与全局变量

```python
# !/bin/bash
a="this is a" # 定义全局变量
b="this is b"
function funname() {
    local_c="this is c" # 定义局部变量
    echo $a, $b
    echo $local_c
    return 0   # shell 函数返回值是整形,并且在 0-257 之间
}
echo $d  # 打印不会生效,因为 d 是局部变量
funname  # 执行函数 funname
```
执行上诉程序 `bash fun_test.sh`,输出如下:
> this is a, this is b
this is c

### 递归函数

bash 也是支持递归函数的(能够调用自身的函数),示例程序如下:

```python
#!/bin/bash
function name() {
    echo $1
    name hello
    sleep 1
}
name
```

运行此脚本后不断打印出 hello,按 `ctrl+c` 结束。

## 常用命令

`ps、grep、awk、sed` 三剑客

## Shell 正则表达式

参考博客[Shell 正则表达式](https://man.linuxde.net/docs/shell_regex.html)。

## 参考资料

- [菜鸟教程-shell教程](https://www.runoob.com/linux/linux-shell-process-control.html)
- [Linux 命令行与 Shell 脚本教程](Linux 命令行与 Shell 脚本教程)

================================================
FILE: 3-machine_learning/README.md
================================================
## 前言

本目录内容旨在分享机器学习数学基础笔记、机器学习算法总结和机器学习经典面试题总结知识。

## 目录

1. [机器学习基本概念总结](./机器学习基本概念总结.md)
2. [机器学习基础总结](./机器学习基础总结.md)
3. [机器学习经典算法总结](./机器学习经典算法总结.md)
4. [机器学习基本原理](./机器学习基本原理.md)
5. [《机器学习》学习笔记](./《机器学习》学习笔记.md)

================================================
FILE: 3-machine_learning/k-means.py
================================================
import numpy as np
from numpy import inf
from matplotlib import pyplot as plt
import random

def distEclud(vecA, vecB):
    "两个向量的欧式距离计算公式"
    return np.sqrt(sum(np.power(vecA - vecB, 2)))

def kmeans(dataset, k):
    """K-means 聚类算法

    Args:
        dataset ([ndarray]): 数据集,二维数组
        k ([int]): 聚簇数量
    """
    m = np.shape(dataset)[0]  # 样本个数
    
    # 1, 随机初始化聚类中心点
    center_indexs = random.sample(range(m), k)
    center = dataset[center_indexs,:]
    
    cluster_assessment = np.zeros((m, 2))
    cluster_assessment[:, 0] = -1  # 将所有的类别置为 -1
    cluster_changed = True 
    while cluster_changed:
        cluster_changed = False
        # 4-8,计算样本x_i与各聚类中心的距离,根据距离最近的聚类中心确定x_j的簇标记,并将对应样本x_i划入相应的簇
        for i in range(m):
            # 初始化样本和聚类中心的距离,及样本对应簇
            min_dist = inf
            c = 0
            # 确定每一个样本离哪个中心点最近,和属于哪一簇
            for j in range(k):
                dist = distEclud(dataset[i,:], center[j,:])
                if dist < min_dist:
                    min_dist = dist
                    c = i
            # 更新样本所属簇
            if cluster_assessment[i, 0] != c:  # 仍存在数据在前后两次计算中有类别的变动,未达到迭代停止要求
                cluster_assessment[i, :] = c, min_dist
                cluster_changed = True
        # 9-15 更新簇中心点位置
        for j in range(k):
            changed_center = dataset[cluster_assessment[:,0] == j].mean(axis=0)
            center[j,:] = changed_center
            
    return cluster_assessment, center

def show_cluster(dataSet, k, centroids, clusterAssement):
    """
    针对二维数据进行聚类结果绘图
    """
    numSamples, dim = dataSet.shape
    mark = ['or', 'ob', 'og', 'ok', '^r', '+r', '<r', 'pr']
    center_mark = ['*r', '*b', '*g', '*k', '*r', '*r', '*r', '*r']

    for i in range(numSamples):
        markIndex = int(clusterAssement[i,0])
        plt.plot(dataSet[i, 0], dataSet[i, 1], mark[markIndex], markersize=2)
    for j in range(k):
        plt.plot(centroids[j, 0], centroids[j, 1], center_mark[j], markersize=12)
    plt.show()
    

if __name__ == '__main__':
    x1 = np.random.randint(0, 50, (50, 2))
    x2 = np.random.randint(40, 100, (50, 2))
    x3 = np.random.randint(90, 120, (50, 2))
    x4 = np.random.randint(110, 160, (50, 2))
    test = np.vstack((x1, x2, x3, x4))

    # 对特征进行聚类
    result, center = kmeans(test, 4, is_kmeans=False, is_random=False)
    print(center)
    # show_cluster(test, 4, center, result)  # 报错

================================================
FILE: 3-machine_learning/《机器学习》学习笔记.md
================================================
- [第 2 章 模型评估与选择](#第-2-章-模型评估与选择)
  - [2.1 经验误差与过拟合](#21-经验误差与过拟合)
  - [2.2 评估方法](#22-评估方法)
    - [2.2.1 留出法](#221-留出法)
    - [2.2.2 交叉验证法](#222-交叉验证法)
    - [2.2.3 自助法](#223-自助法)
    - [2.2.4 调参与最终模型](#224-调参与最终模型)
  - [2.3 性能度量](#23-性能度量)
    - [2.3.1 错误率与精度](#231-错误率与精度)
    - [2.3.2 查准率、查全率与F1](#232-查准率查全率与f1)
    - [2.3.3 ROC 与 AUC](#233-roc-与-auc)
  - [2.5 偏差与方差](#25-偏差与方差)
- [第 3 章 线性模型](#第-3-章-线性模型)
  - [3.5 多分类学习](#35-多分类学习)
  - [3.6 类别不平衡问题](#36-类别不平衡问题)
- [第 5 章 神经网络](#第-5-章-神经网络)
  - [5.1 神经元模型](#51-神经元模型)
  - [5.2 感知机与多层网络](#52-感知机与多层网络)
  - [5.3 误差反向传播算法](#53-误差反向传播算法)
  - [5.4 全局最小与局部最小](#54-全局最小与局部最小)
  - [5.5 其他常见神经网络](#55-其他常见神经网络)
  - [5.6 深度学习](#56-深度学习)
- [第 9 章 聚类](#第-9-章-聚类)
  - [9.1 聚类任务](#91-聚类任务)
  - [9.2 性能度量](#92-性能度量)
  - [9.3 距离计算](#93-距离计算)
  - [9.4 原型聚类](#94-原型聚类)
    - [9.4.1 k 均值算法](#941-k-均值算法)
- [参考资料](#参考资料)

## 第 2 章 模型评估与选择
### 2.1 经验误差与过拟合

+ 精度:精度=1-错误率。如果在 $m$ 个样本中有 $a$ 个样本分类错误,则错误率 $E=a/m$,精度 = $1-a/m$。
+ **误差**:一般我们把学习器的实际预测输出与样本的真实输出之间的差异称为“误差”(`error`)。学习器在训练集上的误差称为“训练误差”(`training error`),在**新样本**上的误差称为“泛化误差”(`generalization error`)。
+ **过拟合**:就是指训练误差和测试误差间距过大,即方差(`variance`)过大,表现为模型泛化性能下降即不够”稳“,正则化目的在于解决过拟合问题。
+ **欠拟合**:和过拟合相反,指模型的训练误差过大,即偏差(`bias`)过大,表现为模型不够”准“,优化算法目的在于解决欠拟合问题。

好的学习器应该尽可能学出适用于所有潜在样本的”普遍规律“。由于事先无法知道新样本是什么样子,所以无法直接获得泛化误差,同时训练误差又由于过拟合现象的存在而不适合作为标准,那么现实中如何进行模型评估与选择就是一个重要的问题了。

### 2.2 评估方法

通常使用一个测试集来评估学习器对新样本的判别能力,把测试集上的”测试误差“(`testing error`)作为泛化误差的近似。值得注意的是,测试集应该尽可能与训练集互斥,即测试样本尽量不在训练集中出现(学习器之前没有见到过)。

对于一个包含 $m$ 个样本的数据集 $D = {(x_1, y_1)}, (x_2, y_2),...,(x_m, y_m)$,将其划分为训练集 $S$ 和测试集 $T$,有两种常见的方法:**留出法和交叉验证法**。

#### 2.2.1 留出法

”留出法“(`hold-out`) 直接将数据集 $D$ 划分为两个户次互斥的集合,一个集合作为训练集 $S$,另一个作为测试集 $T$,即 $D = S \cup T$,$S \cap T = \emptyset$。值得注意的是,训练/测试集的划分应该尽可能保持数据分布的一致性,避免数据划分过程引入额外的偏差而对最终结果产生影响。从采样(`sampling`)的角度来看数据集的划分过程,则保留类别比例的采样方式称为”分层采样“(`stratified sampling`)。例如数据集 $D$ 有 `1000` 个样本,`800` 个正样本,`200` 个负样本,将 `70%` 的样本作为训练集,`30%` 的样本作为测试集。考虑分层采样,则训练 $S$ 集的构成为:正样本 $= 1000\times 70\% \times (800/1000) = 560$,负样本 $=1000\times 70\% \times (200/1000) = 140$,同理可得测试集 `T` 包含 `240` 个正样本,`60` 个负样本。

另一个问题是即使在给定训练集/测试集样本比例后,依然存在多种划分方式对样本 $D$ 进行分割,例如前 `280` 个正样本和后 `280` 个正样本构建的训练集是不一样的。因此,单次使用留出法得到的的估计结果往往不可靠,在使用留出法时,一般采用若干次随机划分、重复进行实验评估后取平均值作为留出法的评估结果。

#### 2.2.2 交叉验证法

”交叉验证法“(`cross validation`)先将数据集 $D$ 划分为 $k$ 个大小相似的**互斥子集**,即 $D = D_1\cup D_2\cup...\cup D_k,D_i \cap D_j = \emptyset$。同时每个子集都应该尽可能保持**数据分布的一致性**,即类别比例相等,从 $D$ 中通过分层采样得到。然后,每次用 $k-1$ 个子集的并集作为训练集,剩下的一个子集作为测试集,这样可获得 $k$ 组训练集/测试集,从而可进行 $k$ 组训练和测试,把 $k$ 次测试结果取平均后返回。交叉验证法评估结果的稳定性和保真性在很大程度上取决于 $k$ 的取值,为了强调这一点,通常把交叉验证法称为“$k$ 折交叉验证”(`k-fold cross validation`)。$k$ 的常用取值分别是 `10、5、20` 等。图 `2.2` 给出了 `10` 折交叉验证的示意图。

![10折交叉验证示意图](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221043341-133674568.png)

“10 次 10 折交叉验证法”与 “100 次留出法“都是进行了 `100` 次训练/测试。

交叉验证法的一个特例是留一法(`Leave-One-Out`,简称 `LDO`),留一法不受随机样本划分方式的影响,因为 $m$ 个样本只有唯一的划分方式划分为 $m$ 个子集-每个子集包含一个样本。虽然留一法的评估结果往往被认为比较准确,但是训练开销简直太大了,真实项目中很少见到有人这样用,而且留一法的估计结果未必永远比其他评估方法准确,毕竟“天下没有免费的午餐”。

#### 2.2.3 自助法

“自助法”(`bootstrapping`) :给定包含 $m$ 个样本的数据集 $D$,对它进行采样产生数据集 ${D}'$:每次随机从 $D$ 中挑选一个样本,将其拷贝放入 ${D}'$,然后再将其放回 $D$ 中,下次采样依然可能被采样到;这个过程重复 $m$ 次,就得到了包含 $m$ 个样本的数据集 ${D}'$。显然,$D$ 中有一部分样本会在 ${D}'$ 中多次出现,另外一部分样本不出现。做一个简单估计,样本在 $m$ 次采样中始终不被采到的概率是 $(1-\frac{1}{m})^m$,取极限得到如下所式:

$$
(1-\frac{1}{m})^m \rightarrow \frac{1}{e}\approx 0.368
$$

即通过自助采样,初始数据集 $D$ 中约有 `36.8%` 的样本未出现在采样数据集 ${D}'$ 中。

自助法只适用于数据集较小、难以有效划分训练/测试集的情况。值得注意的是,自助法产生的数据集改变了初始数据集的分布,这回引入估计偏差。

#### 2.2.4 调参与最终模型

需要认为设定的参数称为超参数,模型训练无法优化它的。现实当中常对每个参数选定一个范围和变化补偿,例如在 `[0,0.2]` 范围内以 `0.05` 为补偿,要评估的的候选参数有 `5` 个最终选定的参数从这 `5` 个候选值中产生,结果也许不是最佳值,但这是计算开销和性能估计之间进行折中的结果。

在模型评估与选择过程中由于需要流出一部分数据进行评估测试,事实上我们只使用了一部分数据训练模型。因此,在模型选择完成后,学习算法和参数配置已定,此使应该用数据集 $D$ 重新训练模型。这个模型在训练过程中使用了**所有 $m$ 个训练样本**,这个模型也是最终提交给用户的模型。

另外,值得注意的是,我们通常把学得模型在实际使用过程中遇到的数据集称为测试数据,为了加以区分,前面讲到的模型评估与选择中用于评估测试的数据常称为“验证集(`validation set`)”。

在研究对比不同算法的泛化性能时,我们用测试集上的判别效果来估计模型在实际使用时的泛化能力,而把训练数据另外划分为训练集和验证集,基于验证集上的性能来进行模型选择和调参。

### 2.3 性能度量

对学习器的泛化性能进行评估,不仅需要有效可行的实验估计方法,还需要衡量模型泛化能力的评价标准,这就是性能度量(`performance measure`)。

在预测任务中,给定样本集 $D={(x_1,y_1),(x_2,y_2),...,(x_m,y_m)}$,其中 $y_i$ 是示例 $x_i$ 的真实标签。要评估学习器 $f$ 的性能,需要把学习器预测结果 $f(x)$ 与真实标签 $y$ 进行比较。

回归任务最常用的性能度量是“均方误差”(`mean squared error`)。

$$E(f;D) = \frac{1}{m}\sum_{i=1}^{m}(f(x_i)-y_i)^2$$

#### 2.3.1 错误率与精度

+ 错误率:分类错误的样本数占样本总数的比例;
+ 精度:分类正确样本的样本数占样本总数的比例。

错误率和精度是分类任务中最常用的两种性能度量,适用于二分类,也适用于多分类任务。分类错误率和精度的定义如下:

$$E(f;D) = \frac{1}{m}\sum_{i=1}^{m}(f(x_i) \neq y_i)^2$$
$$acc(f;D) = \frac{1}{m}\sum_{i=1}^{m}(f(x_i) = y_i)^2 = 1 - E(f;D)$$

#### 2.3.2 查准率、查全率与F1

错误率和精度虽然常用,但是并不能满足所有任务需求。比如以西瓜问题为例,假设瓜农拉来一车西瓜,我们用训练好的模型对西瓜进行判别,现如精度只能衡量有多少比例的西瓜被我们判断类别正确(两类:好瓜、坏瓜)。但是若我们更加关心的是“挑出的西瓜中有多少比例是好瓜”,或者”所有好瓜中有多少比例被挑出来“,那么精度和错误率这个指标显然是不够用的。

对于**二分类**问题,可将样例根据真实类别与学习器预测类别的组合划分为真正例(`true positive`)、假正例(`false positive`)、真反例(`true negative`)、假反例(`false negative`)四种情况,令 $TP、FP、TN、FN$ 分别表示其对应的样例数,显然有 $TP+FP+TN+FN = $ 样例总数。分类结果的”混淆矩阵“(`confusion matrix`)如下表所示。

![混淆矩阵](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221043869-2084291202.png)

查准率(精确率) $P$ 与查全率(召回率) $R$ 分别定义为:

$$
P = \frac{TP}{TP+FP} \\
R = \frac{TP}{TP+FN}
$$

查准率和查全率是一对矛盾的的度量。一般来说,查全率高时,查准率往往偏低;而查全率高时,查准率往往偏低。通常只有在一些简单任务中,才可能使查全率和查准率都很好高。

精准率和召回率的关系可以用一个 `P-R` 图来展示,以查准率 `P` 为纵轴、查全率 `R` 为横轴作图,就得到了查准率-查全率曲线,简称 **P-R** 曲线,`PR` 曲线下的面积定义为 `AP`。

![PR曲线图](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221044321-1100755715.png)
> 为了绘图方便和美观,示意图显示出单调平滑曲线,但现实任务中的 `P-R` 曲线是非单调、不平滑的,且在很多局部有上下波动。

**如何理解 P-R 曲线呢**?

> 可以从排序型模型或者分类模型理解。以逻辑回归举例,逻辑回归的输出是一个 `0` 到 `1` 之间的概率数字,因此,如果我们想要根据这个概率判断用户好坏的话,我们就必须定义一个阈值 。通常来讲,逻辑回归的概率越大说明越接近 `1`,也就可以说他是坏用户的可能性更大。比如,我们定义了阈值为 `0.5`,即概率小于 `0.5` 的我们都认为是好用户,而大于 `0.5` 都认为是坏用户。因此,对于阈值为 `0.5` 的情况下,我们可以得到相应的**一对**查准率和查全率。
但问题是:这个阈值是我们随便定义的,我们并不知道这个阈值是否符合我们的要求。 因此,为了找到一个最合适的阈值满足我们的要求,我们就必须遍历 `0` 到 `1` 之间所有的阈值,而每个阈值下都对应着一对查准率和查全率,从而我们就得到了 `PR` 曲线。
最后如何找到最好的阈值点呢? 首先,需要说明的是我们对于这两个指标的要求:我们希望查准率和查全率同时都非常高。 但实际上这两个指标是一对矛盾体,无法做到双高。图中明显看到,如果其中一个非常高,另一个肯定会非常低。选取合适的阈值点要根据实际需求,比如我们想要高的查全率,那么我们就会牺牲一些查准率,在保证查全率最高的情况下,查准率也不那么低。

在进行性能比较时,如果一个学习器的曲线被另一个学习器的曲线完全包住,则可断言后者性能优于前者,如图 2.3,学习器 `A` 性能优于学习器 `C`。比较 `P-R` 曲线下面积的大小,也可判别学习器的优劣,它在一定程度上表征了学习器在查准率和查全率上取得”双高“的比例,但这个值不容易估算,因此人们又设计了一些综合考虑查准率、查全率的性能度量,如”平衡点(`Break-Event Point`,简称 `BEP`)“。

`BEP` 是”查准率=查全率“时的取值,基于学习器的比较,可以认为学习器 `A` 优于 `B`。但 `BEP` 过于简单了,更为常用的是 $F1$ 度量。

$$
F1 = \frac{2\times P\times R}{P+R} = \frac{2\times TP}{样例总数+TP-TN}
$$

$F1$ 度量的一般形式:$F_{\beta}$,能让我们表达出对查准率/查全率的偏见,$F_{\beta}$ 计算公式如下:

$$
F_{\beta} = \frac{1+\beta^{2}\times P\times R}{(\beta^{2}\times P)+R}
$$

其中 β > 1 对查全率有更大影响,$\beta < 1$ 对查准率有更大影响。

很多时候我们会有多个混淆矩阵,例如进行多次训练/测试,每次都能得到一个混淆矩阵;或者是在多个数据集上进行训练/测试,希望估计算法的”全局“性能;又或者是执行多分类任务,**每两两类别**的组合都对应一个混淆矩阵;....总而来说,我们希望能在 $n$ 个二分类混淆矩阵上综合考虑查准率和查全率。

一种直接的做法是先在各混淆矩阵上分别计算出查准率和查全率,记为$(P_1,R_1),(P_2,R_2),...,(P_n,R_n)$然后取平均,这样得到的是”宏查准率(`Macro-P`)“、”宏查准率(`Macro-R`)“及对应的”宏$F1$(`Macro-F1`)“:

$$\begin{aligned}
Macro\ P &= \frac{1}{n}\sum_{i=1}^{n}P_i \\
Macro\ R &= \frac{1}{n}\sum_{i=1}^{n}R_i \\
Macro\ F1 &= \frac{2 \times Macro\ P\times Macro\ R}{Macro\ P + Macro\ R}
\end{aligned}$$

另一种做法是将各混淆矩阵对应元素进行平均,得到 $TP、FP、TN、FN$ 的平均值,再基于这些平均值计算出”微查准率“(`Micro-P`)、”微查全率“(`Micro-R`)和”微$F1$“(`Mairo-F1`)

$$\begin{aligned}
Micro\ P &= \frac{\overline{TP}}{\overline{TP}+\overline{FP}} \\
Micro\ R &= \frac{\overline{TP}}{\overline{TP}+\overline{FN}} \\
Micro\ F1 &= \frac{2 \times Micro\ P\times Micro\ R}{MacroP+Micro\ R}
\end{aligned}$$

#### 2.3.3 ROC 与 AUC

`ROC` 曲线示意图如下。

![PR曲线图](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221044929-398011469.png)

### 2.5 偏差与方差

通过前面的学习,我们已经知道如何设计实验(训练集、验证集的划分)来**对学习器的泛化误差进行评估**,并且也了解了诸如**精度、查准率、查全率及 `F1`** 等性能度量指标。但这不够,我们还希望了解”为什么“具有这样的性能。”偏差-方差分解“(`bias-variance decomposition`)是解释学习算法泛化性能的一种重要工具。

假设对于测试样本 $x$,令 $y_D$ 为 $x$ 在数据集中的标记(即标签,可能会存在噪声),$y$ 才是 $x$ 的真实标记,$f(x;D)$ 为训练集 $D$ 上学得模型 $f$ 在 $x$ 上的预测输出。以回归任务为例,**算法的泛化误差**计算如下:

$$
E(f;D) = \mathbb{E}_{D} [(f(x;D) - y_{D})^2]
$$

直接计算上式是不行的,我们得进行分解,分解之前需要先知道方差、偏差、噪声的定义。学习算法的**期望预测**为

$$
\bar{f}(x) = \mathbb{E}_{D} [(f(x;D)]
$$

**方差定义**: 模型预测的期望值与预测值之差的平方的期望值,使用样本数相同的不同训练集产生的**方差**为

$$
D(x) = var(x) = \mathbb{E}_{D}[(f(x;D) - \mathbb{E}_{D}[(f(x;D)])^2] = \mathbb{E}_{D}[(f(x;D) - \bar{f}(x))^2]
$$

> 方差在统计描述和概率分布中各有不同的定义,并有不同的公式。

**噪声**为

$$
\varepsilon^2 = \mathbb{E}_{D}[(y_{D} - y)^2]
$$

模型预测的期望值与真实值的差称为**偏差**(`bias`),即

$$
bias^2(x) = (\bar{f}(x)-y)^2
$$

假定噪声为 `0` ,即 $\mathbb{E}_{D}[(y_{D}-y)]=0$,有了以上定义,通过多项式展开合并,并利用恒等变形、期望的运算性质可将期望泛化误差公式进行分解得到:
> 公式推理证明,可参考[这里](https://datawhalechina.github.io/pumpkin-book/#/chapter2/chapter2?id=_241)。

$$
E(f;D) = \mathbb{E}_{D}[(f(x;D)-\bar{f}(x))^2] + (\bar{f}(x)-y)^2 + \mathbb{E}_{D}[(y_{D}-y)^2]
$$

于是,

$$
E(f;D) = var(x) + bias^2(x) + \varepsilon^2
$$

通过上式,可知**泛化误差可分解为方差、偏差与噪声之和**。回顾偏差、方差、噪声的定义:

+ **偏差**:度量了学习算法的期望预测与真实结果的偏离程度,刻画了学习算法本身的拟合能力。
+ **方差**:度量了同样大小的训练集的变动导致的学习性能的变化,刻画了数据扰动所造成的影响,或者说刻画了模型的稳定性和泛化能力。
+ **噪声**:表达了当前任务上任何学习算法所能达到的期望泛化误差的下界,即刻画了学习问题本身的难度。

通过对泛化误差进行分解说明,模型泛化性能是由学习算法的能力、数据的充分性以及任务本身的难度所共同决定的。**模型欠拟合**表现为偏差较大, 此时偏差主导了泛化错误率;**模型过拟合**表现为偏差小但是方差很大,方差主导了泛化错误率。

一般来说,偏差与方差是有冲突的,这称为偏差-方差窘境(`bias-variane dilemma`)。

![泛化误差与偏差方差的关系示意图](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221045355-343515804.png)

## 第 3 章 线性模型

### 3.5 多分类学习

### 3.6 类别不平衡问题

$$\frac{y'}{1-y'} = \frac{y}{1-y}\times \frac{m^-}{m^+}$$

公式中,$y$ 是预测值,分类器决策规则为:若 $\frac{y}{1-y}$ 则预测为正例。令 $m^+$ 表示正例数目, $m^-$ 表示反例数目,则观测几率是 $\frac{m^-}{m^+}$。

类别不平衡学习的一个基本策略是 **“再缩放”**(rescaling)。再缩放技术主要有三种方法实现:

+ 对训练集的反类样例进行“欠采样”(undersampling),即去除一些反例使得正负样例数目接近,然后再进行学习。
+ 对训练集的正类样例进行“过采样”(oversampling),即增加一些正例使得正、负样例数目接近,然后再进行学习(或者叫模型训练)。
+ 直接对原始训练集进行学习,但是在对训练好的分类器进行预测时,将式(3.48)嵌入到分类器推理过程中,称为“阈值移动”(threshold-moving)。

值得注意的是过采样的方法不能简单地对初始正例样本进行重复采样,否则会招致严重的过拟合;过采样方法的代表性算法 SMOTE 是通过对训练集的正例进行插值来产生额外的正例(图像数据就进行数据增强)。

## 第 5 章 神经网络

### 5.1 神经元模型

神经网络有很多种定义,一种较为广泛的定义是,其是由具有适应性的简单单元组成的广泛并行互连的网络,它的组织能够模拟生物神经系统对真实世界物体所作出的交互反应。

神经网络中最基本的成分是神经元(`neuron`)模型,即上述定义中的简单单元。经典的"M-P神经元模型”如下所示,其中的 $f$ 表示激活函数,$w$ 表示神经元权重。

![M-P神经元模型](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221045786-1853234660.png)

### 5.2 感知机与多层网络

感知机(`perception`)由两层神经元组成,如下图所示,输入层接收外界输入信号后传递给输出层,输出层是 M-P 神经元,也称“阈值逻辑单元”(`threshold logic unit`)。感知机能容易地实现逻辑与、或、非运算,只需要设置合理的 $w$、$\theta$ 参数。感知机模型的公式可表示为:

$$y_j = f(\sum_{i}w_ix_i - \theta_j)$$

![M-P神经元模型](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221046121-540075924.png)

给定训练数据集,感知机的 $w$ 和 $\theta$ 参数可通过学习得到。其学习规则很简单,对于训练样本$(x,y)$,如果感知机输出为 $\hat{y}$,则感知机的参数将做如下调整:

$$
w_{i} \leftarrow w_{i} + \Delta w_i \\
\Delta w_i = \eta(y - \hat{y})x
$$

其中 $\eta$ 称为学习率,从上式可以看出当感知机预测输出 $\hat{y}$ 和样本标签 $y$ 相等时,参数不改变;否则将根据偏差的严重程度进行相应调整。

需要注意的是,感知机只能解决与、或、非这样的线性可分(即存在一个线性超平面将它们分开)问题,但是甚至不能解决异或这样简单的非线性可分问题。

要解决非线性可分问题,可以使用多功能神经元。如下图所示的就是一种常见的“多层前馈神经网络”(`multi-layer feedforward neural`),每层神经元与下一层神经元全互连,同层神经元之间不连接,也不存在跨层连接。
> “前馈” 并不意味着网络中信号不能反向传播,而单纯是指网络拓扑结构上不存在环或回路。

![多层前馈神经网络](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221046474-1186505537.png)

### 5.3 误差反向传播算法

很明显多层神经网络的学习能力比单层网络(单层感知机)强得多。感知机参数的学习规则很简单,因此其对于多层神经网络是不够用的,由此,诞生了强大的误差反向传播(`error BackPropagation`,简称 `BP`)算法,并使用至今,现阶段的所有深度神经网络的参数都是由 `BP` 算法训练得到的。

反向传播指的是计算神经⽹络参数梯度的⽅法。总的来说,反向传播依据微积分中的链式法则,沿着从输出层到输⼊层的顺序,**依次计算并存储⽬标函数有关神经⽹络各层的中间变量以及参数的梯度**。
> 前向传播:输入层-->输出层;反向传播:输出层-->输入层。

下面通过前馈神经网络的 `BP` 算法来深入理解 `BP` 过程。

给定训练集 $D = {(x_1, y_1), (x_2, y_2),...,(x_m, y_m)}, x \in \mathbb{R}^d, y_i \in \mathbb{R}^l$,即每个输入样本都由 $d$ 个属性表征,模型输出的是 $l$ 维实值向量。为了便于 `BP` 算法的推导,下图给出了一个拥有 $d$ 个输入神经元、$l$ 个输出神经元、$q$ 个隐层神经元的多层前馈神经网络,其中输出层第 $j$ 个神经元的阈值用 $\theta_j$ 表示,隐层第 $h$ 个神经元的阈值用 $\gamma_h$ 表示。输入层第 $i$ 个神经元与隐层第 $h$ 个神经元之间的连接权重参数为 $v_{ih}$,隐层第 $h$ 个神经元与输出层第 $j$ 个神经元之间的连接权为 $w_{hj}$。记隐层第 $h$ 个神经元接收到的输入为 $\alpha_h = \sum_{i=1}^{d}v_{ih}x_i$,输出层第 $j$ 个神经元接收到的输入为 $\beta_j = \sum_{h=1}^{q}w_{hj}b_h$,其中 $b_h$ 为隐层第 $h$ 个神经元的输出。这里假设隐层和输出层神经元的激活函数都为 `Sigmoid` 函数。

![BP网络](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221046857-375596699.png)

对训练样本 $(x_k,y_k)$,假设神经网络的输出为 $\hat{y}_k =(\hat{y}_1^k,\hat{y}_2^k,...,\hat{y}_l^k)$,即

$$
\hat{y}_j^k = f(\beta_j - \theta_j)\tag{5.3},
$$

则网络在 $(x_k,y_k)$ 上的均方误差损失为

$$
E_k = \frac{1}{2}\sum_{j=1}^{l}(\hat{y}_j^k - y_k)^2\tag{5.4},
$$

输入层到隐层需要 $d \times q$ 个权重参数、隐层到输出层需要需要 $q \times l$ 个权重参数,以及 $q$ 个隐层的神经元阈值、$l$ 个输出层的神经元阈值。`BP` 是一个迭代学习算法,在迭代的每一轮中采用广义的感知机学习规则对参数进行更新估计,因此任意参数 $v$ 的更新估计表达式都可为

$$
v\leftarrow v + \Delta v\tag{5.5}
$$

下面我会首先推导出隐层到输出层的权重 $w_{hj}$ 更新公式,而后类别推导 $\theta_{j}$、$v_{ih}$、$\gamma_{h}$。

`BP` 算法是基于梯度下降(`gradient desent`)策略,以目标的负梯度方向对参数进行调整,对式$(5.4)$的误差 $E_k$,给定学习率 $\eta$,可得权重参数更新公式如下:

$$
w \leftarrow w + -\eta \frac{\partial E_k}{\partial w}
$$

同时,

$$
\Delta w_{hj} = -\frac{\partial E_k}{\partial w_{hj}}\tag{5.6}
$$

注意到 $w_{hj}$ 先影响到第 $j$ 个输出层神经元的输入值 $\beta_j$,再影响到其输出值 $\hat{y}_j^k$,最后才影响到损失 $E_k$,根据梯度的链式传播法则,有

$$
\frac{\partial E_k}{\partial w_{hj}} = \frac{\partial E_k}{\partial \hat{y}_j^k}\frac{\partial \hat{y}_j^k}{\partial \beta_j}\frac{\partial \beta_j}{\partial w_{hj}} \tag{5.7}
$$ 

根据前面 $\beta_j$ 的定义,显然有

$$
\frac{\partial \beta_j}{\partial w_{hj}} = b_h \tag{5.8}
$$

再因为 `Sigmoid` 函数的一个很好的性质:

$$
{f}'(x) = f(x)(1-f(x)) \tag{5.9}
$$

再联合式$(5.3)$和$(5.4)$,有:

$$\begin{align}
-\frac{\partial E_k}{\partial \hat{y}_j^k} \frac{\partial \hat{y}_j^k}{\partial \beta_j} &= -\frac{\partial \left [\frac{1}{2} \sum_{j=1}^{l}(\hat{y}_{j}^{k} - y_{j}^{k})^{2}\right ]}{\partial \hat{y}_{j}^{k}} \frac{\partial \left [f(\beta_{j} - \theta_{j})\right ]}{\partial \beta_{j}} \nonumber \\
&= -\frac{1}{2}\times 2(\hat{y}_j^k - y_j^k) {f}'(\beta_j - \theta_j) \nonumber \\
&= -(\hat{y}_j^k - y_j^k) f(\beta_j - \theta_j)\left [1-f(\beta_j - \theta_j)\right ] \nonumber\\
&= -(\hat{y}_j^k - y_j^k) \hat{y}_j^k (1-\hat{y}_j^k) \nonumber\\
&= \hat{y}_j^k (1-\hat{y}_j^k) (y_j^k - \hat{y}_j^k ) \tag{5.10}
\end{align}$$

注意这里的 $f$ 是 `Sigmoid` 函数。令 $g_{j} = \hat{y}_j^{k}(1-\hat{y}_j^{k})(y_{j}^{k} - \hat{y}_{j}^{k})$ 。将 $g_{j}$ 和式 $(5.8)$ 带入式 $(5.6)$,就得到了 `BP` 算法中关于 $w_{hj}$ 的更新公式:

$$
\Delta w_{hj} = \eta g_{j}b_{h} \tag{5.11}
$$

类似式 $(5.10)$ 的推导过程可得其他参数的更新公式:

$$
\Delta \theta_j = -\eta g_j \tag{5.12}
$$
$$
\Delta v_{ih} = -\eta e_{h}x_{i} \tag{5.13}
$$
$$
\Delta \theta_j = -\eta e_h \tag{5.14}
$$

$v_{ih}$ 梯度的详细推导公式如下所示。因为,

$$\begin{aligned} 
\cfrac{\partial E_k}{\partial v_{ih}} &= \sum_{j=1}^{l} \cfrac{\partial E_k}{\partial \hat{y}_j^k} \cdot \cfrac{\partial \hat{y}_j^k}{\partial \beta_j} \cdot \cfrac{\partial \beta_j}{\partial b_h} \cdot \cfrac{\partial b_h}{\partial \alpha_h} \cdot \cfrac{\partial \alpha_h}{\partial v{ih}} \\
&= \sum_{j=1}^{l} \cfrac{\partial E_k}{\partial \hat{y}_j^k} \cdot \cfrac{\partial \hat{y}_j^k}{\partial \beta_j} \cdot \cfrac{\partial \beta_j}{\partial b_h} \cdot \cfrac{\partial b_h}{\partial \alpha_h} \cdot x_i \\
&= \sum_{j=1}^{l} \cfrac{\partial E_k}{\partial \hat{y}_j^k} \cdot \cfrac{\partial \hat{y}_j^k}{\partial \beta_j} \cdot \cfrac{\partial \beta_j}{\partial b_h} \cdot f^{\prime}(\alpha_h-\gamma_h) \cdot x_i \\
&= \sum_{j=1}^{l} \cfrac{\partial E_k}{\partial \hat{y}_j^k} \cdot \cfrac{\partial \hat{y}_j^k}{\partial \beta_j} \cdot w_{hj} \cdot f^{\prime}(\alpha_h-\gamma_h) \cdot x_i \\
&= \sum_{j=1}^{l} (-g_j) \cdot w_{hj} \cdot f^{\prime}(\alpha_h-\gamma_h) \cdot x_i \\
&= -f^{\prime}(\alpha_h-\gamma_h) \cdot \sum_{j=1}^{l} g_j \cdot w_{hj} \cdot x_i \\ 
&= -b_h(1-b_h) \cdot \sum_{j=1}^{l} g_j \cdot w_{hj} \cdot x_i \\
&= -e_h \cdot x_i
\end{aligned}$$

所以

$$
\Delta v_{ih} =-\eta \cfrac{\partial E_k}{\partial v_{ih}} =\eta e_h x_i
$$

![v_{ih} 梯度的详细推导公式](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221047252-538822529.png)

> 最后一层激活使用 `softmax` 激活和交叉熵损失函数的反向传播推导,可参考[这里](https://blog.csdn.net/DaVinciL/article/details/90898194)。

学习率 $\eta \in (0,1)$ 控制着算法每一轮迭代中更新的步长,若太大则容易振荡,太小则收敛速度幽会过慢。有时为了做精细调节,可令式$(5.11)$与$(5.12)$使用 $\eta_1$, 式$(5.13)$与$(5.14)$使用 $\eta_2$,两者未必相等。

下图给出了 `BP` 算法的工作流程:

![BP算法流程](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221047618-1687530698.png)

对每个训练样本,`BP` 算法操作流程如下:

1. **前向传播**:将输入样本输入给输入神经元,然后逐层将信号前向传播,直到输出层产生结果。
2. **反向传播**:计算输出层的误差,沿着输出层到输入层的顺序,依次计算并存储损失函数有关各层的中间变量及参数梯度,并对参数进行调整。
3. `1,2` 过程循环进行,直到达到某些停止条件为止,如训练误差已达到一个很小的值。

> 停止条件与缓解 `BP` 过拟合的策略有关。

值得注意的是,`BP` 算法的目标是要最小化训练集 $D$ 上的累计误差:

$$
E = \frac{1}{m}\sum_{k=1}{m}E_k,\tag{5.16}
$$

但是我们前面描述的“标准 BP 算法”的计算过程,其实每次仅针对一个训练样本更新连接权重和阈值的,因此,图5.8中算法发更新规则同样也是基于单个 $E_k$ 推导而得的。如果类似地推导出基于累计误差最小化的更新规则,就得到了累计误差反向传播(`accumulated error backpropagation`)算法。

一般来说,标准 `BP` 算法每次更新仅针对单个样本,参数更新很频繁,而且不同样本对进行更新的效果可能出现“抵消”现象。因此,为了达到同样的累计误差极小点,标准 `BP` 算法往往需要进行更多次数的迭代。而累计 `BP` 算法直接将累计误差最小化,它是在读取整个训练集 $D$ 一遍(`one epoch`)后才对神经网络各层权重参数进行更新,其参数更新频率低得多。但是在很多任务中,累计误差下降到一定程度后,进一步下降会非常缓慢,这是标准 `BP` 往往会更快获得较好的解,尤其是在训练集 $D$ 非常大时很明显。
> 标准 BP 算法和累计 BP 算法的区别类似于随机梯度下降(stochastic gradient descent,简称 SGD)与标准梯度下降之间的区别。

`[Hornik et al.,1989]` 证明,只需一个包含足够多神经元的隐层(即深度神经网络层数足够多),多层前馈神经网络就能以任意精度逼近任意复杂度的连续函数。但是,深度神经网络层数的选择依然是一个未决问题,在实际应用中通常依靠“试错法”(`trial-by-error`)调整。

因为神经网络的表示能力太过强大,因此 `BP` 神经网络在训练过程中经常遭遇过拟合,即训练误差持续降低,但验证集误差却可能上升。目前常用来缓解 `BP` 网络过拟合问题的策略,有以下两种:

第一种策略是“早停”(`early stopping`):将数据分为训练集和验证集,训练集用来更新权重参数,验证集用来估计误差,若训练集误差降低但验证集误差升高,则停止训练,同时返回具有最小验证集误差的权重参数模型。

第二种策略是“正则化”(`regulazation`):其基本思想是在误差目标函数中增加一个用于描述网络复杂度的部分,例如连接权重与阈值的平方和。仍令 $E_k$ 表示第 $k$ 个训练样本上的误差,$w_i$表示连接权重和阈值,则误差目标函数$(5.16)$更改为:

$$
E = \lambda \frac{1}{m}\sum_k^m E_k + (1- \lambda)\sum_{i} w_i^2 \tag{5.17}
$$

其中 $\lambda \in (0,1)$用于对经验误差与网络复杂度这两项进行折中,常通过交叉验证法估计。

常用的刻画模型复杂度 $R(w)$ 的函数有两种,一种是 $L1$ 正则化,即绝对值的之和;另一种是 $L2$ 正则化,即绝对值平方之和,计算公式如下:

$$
R(w) = ||w||_1 = \sum_i|w_i| \\
R(w) = ||w||_2 = \sum_i|w_i^2| 
$$

无论是哪一种正则化,其基本思想都是希望通过限制权重的大小,使得模型不能任意拟合训练数据中随机噪声。$L1$ 与 $L2$ 有很大区别,$L1$ 正则化会让参数变得更加稀疏,而 $L2$ 不会。所谓参数变得更加稀疏是指会有更多的参数变为 `0`,这样可以达到类似特征选取的功能。

> L2范数正则化(regularization)等价于权重衰减的概念。

### 5.4 全局最小与局部最小

若用 $E$ 表示神经网络在训练集上的误差,则它显然是关于连接权重 $w$ 和阈值 $\theta$ 的函数。此使,神经网络的训练过程可看作是一个参数寻优的过程,即在参数空间中,寻找一组最优参数使得 $E$ 最小。

显然,参数空间内梯度为零的点,只要其误差函数值小于零点的误差函数值,就是局部极小点;可能存在多个局部极小值,但有且仅有一个全局最小值。

基于梯度的搜索是使用最为广泛的参数寻优方法。在这类方法中,一般先从参数随机初始解出发,迭代寻找最优参数解。每次迭代我们先计算误差函数在当前点的梯度,然后根据梯度确定搜索方向。例如,由于负梯度方向是函数值下降最快的方向,因此梯度下降法就是沿着负梯度方向搜索最优解。若误差函数在当前点的梯度为零,则已达到局部极小,参数更新量也将为零,也意味着参数的迭代更新将在此停止。

![局部最小与全局最小](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221048022-233010177.png)

在现实任务中,我们常用以下启发式策略(非理论策略)来试图“跳出”局部极小,从而进一步接近全局最小:

+ 以多组不同参数值初始化多个神经网络,按标准方法训练后,取其中最小的解作为最终参数。
+ 使用”模拟退火“(`simulated annealing`)技术。模拟退火在每一步都以一定的概率接受比当前解更差的结果,从而有助于”跳出“局部极小。在每次迭代过程中,接收”次优解“的概率要随着时间的推移而逐渐降低,从而保证算法的稳定性。
+ 使用随机梯度下降算法。

### 5.5 其他常见神经网络

`2010` 年之前常见的神经网络有 `RBF` 网络、`ART` 网络、`SOM` 网络、`级联相关网络`、`Elman网络`、`Boltzmann` 机。
### 5.6 深度学习

典型的深度学习模型就是很深层(层数很多)的神经网络。值得注意的是,从增加模型复杂度的角度来看,增加隐藏层的数目显然比增加隐藏层神经元的数目更有效,即**网络的“深”比“宽”重要**。因为隐藏层层数的增加不仅增加了拥有激活函数神经元的数目,还增加了激活函数嵌套的层数,即**非线性能力进一步增强**。
## 第 9 章 聚类

### 9.1 聚类任务

在“无监督学习”中,训练样本是无标签信息的,目标是通过对无标记训练样本的学习来揭示数据的内在性质和规律,为进一步的数据分析提供基础。这类学习任务中应用最广的就是“聚类”(`clustering`)算法,其他的无监督学习任务还有密度估计(`density estimation`)、异常检测(`anomaly detection`)等。

聚类的任务是将数据集中的样本划分为若干个通常是不相交的**子集**,每个子集称为一个“簇”(`cluster`),簇所对应的概念和语义由使用者来把握。

聚类可用作其他学习任务的一个前驱过程。基于不同的学习策略,有着多种类型的聚类算法,聚类算法涉及的两个基本问题是性能度量和距离计算。

### 9.2 性能度量

聚类性能度量也叫聚类“有效性指标”(`validity index`),和监督学习的性能度量作用类似,都是用来评估算法的好坏,同时也是聚类过程中的优化目标。

聚类性能度量大致有两类。一类是将聚类结果与某个“参考模型(`reference model`)”进行比较,称为“外部指标(`external index`)”;另一类是直接考察聚类结果而不利用任何参考模型,称为“内部指标”(`inderna index`)。

外部指标包括 `Jaccard` 指数(简称 `JC`)、`FM` 指数(简称 `FMI`)和 `Rand` 指数(简称 `RI`),范围在 `[0,1]` 之间,且越大越好;内部指标包括 `DB` 指数(简称 `DBI`)和 `Dunn` 指数(简称 `DI`),`DBI` 值越小越好,`DI` 越大越好。
> 具体计算公式太多了,这里不给出,可以参考原书 `199` 页。

### 9.3 距离计算

函数 $dist(\cdot,\cdot)$ 用于计算两个样本之间的距离,如果它是一个“距离度量”(`distance measure`),则需满足一些基本性质:
+ 非负性:$dist(x_i,x_j) \geq 0$;
+ 同一性:$dist(x_i,x_j)=0$ 当前仅当 $x_i = x_j$;
+ 对称性:$dist(x_i,x_j) = dist(x_j,x_i)$;
+ 直递性:$dist(x_i,x_j \geq dist(x_i,x_k) + dist(x_k,x_j))$。

给定样本 $x_i = (x_{i1};x_{i2};...;x_{in})$ 与 $x_j = (x_{j1};x_{j2};...;x_{jn})$,最常用的是“闵可夫斯距离”(`Minkowski distance`)。

$$
dist_{mk}(x_i,x_j) = (\sum_{u=1}^{n}\left | x_{iu} - x_{ju} \right |^p)^\frac{1}{p}
$$

上式其实就是 $x_i - x_j$ 的 $L_p$ 范数 $\left \| x_i - x_j \right \|_p$。$p = 2$,上式为欧氏距离。 

属性通常划分为“连续属性”(`continuous attribute`)和“离散属性”(`categotical attribute`),前者在定义域上有无穷多个可能的取值,后者在定义域上是有限个取值。但是在涉及距离计算时,属性上定义了“序”更为重要。能直接在属性上计算距离:“1” 和 “2” 比较近、和“4”比较远,这样的属性称为“有序属性”(`ordinal attribute` );而定义域为{汽车、人、椅子}这样的离散属性则不能直接在属性上计算距离,称为“无序属性”(`non-ordinal atribute`)。显然,**闵可夫斯距离可用于有序属性**。

对无序属性采用 `VDM`(`Value Difference Metric`),同时将闵可夫斯距离和 `VDM` 集合可处理混合属性。

### 9.4 原型聚类

原型聚类算法假设聚类结构能通过一组原型刻画,这里的原型是指样本空间中具有代表性的点。一般算法先对原型进行初始化,然后对原型进行迭代更新求解。采用不同的原型表示、不同的求解方式,将产生不同的算法。

#### 9.4.1 k 均值算法

`K-Means` 算法的思想很简单,对于给定的样本集,按照样本之间的距离大小,将样本集划分为 $K$ 个簇,让簇内的点尽量紧密的连在一起,而让簇间的距离尽量的大。

给定样本集 $D = \{x_1, x_2,...,x_m\}$,假设簇划分为 $C = \{C_1,C_2,...,C_m\}$,算法目标是最小化平方误差。

$$
E = \sum_{i=1}^{k}\sum_{x \in C_{i}}\left \| x - u_i \right \|_2^2
$$

其中 $u_i=\frac{1}{C_i}\sum_{x \in C_{i}}x$ 是簇 $C_i$ 的均值向量,也称质心。上式一定程度上刻画了簇内样本围绕簇均值向量的紧密程度,$E$ 值越小簇内样本相似程度越高。

找到 $E$ 的最优解需要靠擦样本集 $D$ 所有可能的簇划分,这是一个 $NP$ 难问题。$k$ 均值算法采用了贪心策略,通过迭代优化近似求解,算法流程如图 `9.2` 所示。其中第 `1` 行对均值向量进行初始化,`4-8` 行与 `9-16` 行依次对当前簇划分即均值向量迭代更新,若更新后聚类结果保持不变,则在第 `18` 行将当前簇划分结果返回。

![k-means算法流程](https://img2023.cnblogs.com/blog/2989634/202302/2989634-20230220221048552-2059208228.png)

参考此[文章](https://www.pythonf.cn/read/116646)的代码,我修改为如下精简版的 `K-Means` 算法代码。

```python
def kmeans(dataset, k):
    """K-means 聚类算法

    Args:
        dataset ([ndarray]): 数据集,二维数组
        k ([int]): 聚簇数量
    """
    m = np.shape(dataset)[0]  # 样本个数
    
    # 1, 随机初始化聚类中心点
    center_indexs = random.sample(range(m), k)
    center = dataset[center_indexs,:]
    
    cluster_assessment = np.zeros((m, 2))
    cluster_assessment[:, 0] = -1  # 将所有的类别置为 -1
    cluster_changed = True 
    while cluster_changed:
        cluster_changed = False
        # 4-8,计算样本x_i与各聚类中心的距离,根据距离最近的聚类中心确定x_j的簇标记,并将对应样本x_i划入相应的簇
        for i in range(m):
            # 初始化样本和聚类中心的距离,及样本对应簇
            min_dist = inf
            c = 0
            # 确定每一个样本离哪个中心点最近,和属于哪一簇
            for j in range(k):
                dist = distEclud(dataset[i,:], center[j,:])
                if dist < min_dist:
                    min_dist = dist
                    c = i
            # 更新样本所属簇
            if cluster_assessment[i, 0] != c:  # 仍存在数据在前后两次计算中有类别的变动,未达到迭代停止要求
                cluster_assessment[i, :] = c, min_dist  # 更新样本所属簇
                cluster_changed = True
        # 9-16 更新簇中心点位置
        for j in range(k):
            changed_center = dataset[cluster_assessment[:,0] == j].mean(axis=0)
            center[j,:] = changed_center
            
    return cluster_assessment, center

if __name__ == '__main__':
    x1 = np.random.randint(0, 50, (50, 2))
    x2 = np.random.randint(40, 100, (50, 2))
    x3 = np.random.randint(90, 120, (50, 2))
    x4 = np.random.randint(110, 160, (50, 2))
    test = np.vstack((x1, x2, x3, x4))

    # 对特征进行聚类
    result, center = kmeans(test, 4, is_kmeans=False, is_random=False)
    print(center) # 打印簇类中心点
```

## 参考资料
《机器学习》-周志华

================================================
FILE: 3-machine_learning/机器学习入门总结.md
================================================
- [一,机器学习概述](#一机器学习概述)
  - [1.1,机器学习分类](#11机器学习分类)
  - [1.2,机器学习任务](#12机器学习任务)
- [二,线性模型](#二线性模型)
  - [2.1,基本形式](#21基本形式)
  - [2.2,线性回归](#22线性回归)
  - [2.3,多分类学习](#23多分类学习)
  - [2.4,类别不平衡问题](#24类别不平衡问题)
- [三,数据清洗与特征处理](#三数据清洗与特征处理)
  - [3.1,清洗标注数据](#31清洗标注数据)
  - [3.2,特征分类](#32特征分类)
  - [3.3,特征处理与分析](#33特征处理与分析)
- [四,数据预处理基础](#四数据预处理基础)
  - [4.1,如何处理数据中的缺失值](#41如何处理数据中的缺失值)
- [参考资料](#参考资料)

## 一,机器学习概述

### 1.1,机器学习分类

所谓机器学习,是关于在计算机上从数据中产生“模型”(model)的算法,即“学习算法”(learning algorithm)。

机器学习方法的分类,根据所处理的数据种类的不同,可以分为监督学习、无监督学习和强化学习等几种类型,如下图所示:

<img src="../data/images/ml_basic/3_machine_learing_tasks.png" alt="3 basic types of machine learing tasks" width="80%" />

- 所谓监督学习,简单理解就是,训练集样本是带标签的。
- 无监督学习,即训练集样本是不带标签的,即没有老师自导,学生(模型)自学的过程。
- 强化学习,与监督学习类似,与监督学习不同的是,强化学习不需要带标签的输入输出对,同时也无需对非最优解的精确地纠正。

### 1.2,机器学习任务

我们知道,根据**训练数据是否拥有标记信息(标签)**,学习任务可大致划分为两大类“监督学习” (supervised learning) 和“无监督学习” (unsupervised learning),其中,分类和回归任务是前者的代表,而聚类则是后者的代表。

本文以西瓜问题为例来通俗定义分类、回归和聚类问题:
1. 若我们欲预测的是离散值,例如”好瓜“、”坏瓜“,此类学习任务称为”分类“ (`classification`); 
2. 若欲预测的是连续值,例如西瓜成熟度 0.95、 0.37,此类学习任务称为”回归“ (`regression`)。
3. 若事先不知道西瓜的类别,我们还可以对西瓜做“聚类” (`clustering`),即将训练集中的西瓜分成若干组,每组称为一个“簇” (`cluster`); 这些自动形成的簇可能对应一些潜在的概念划分,例如“浅色瓜”、 “深色瓜”等。

值得注意的是,机器学习算法的最终目标是使学习到的模型能很好地适用于“新样本”(方差小),而不仅仅只是在训练集样本上表现好(偏差小)。学得模型适用于于新样本的能力,称为“泛化”(generalization)能力。

机器学习中另外两个常见的任务是异常检测和降维。
1. **异常检测**,是指寻找输入样本 $x_i$ 中所包含的异常数据的问题。在已知正常数据和异常数据标签的前提下,其和有监督分类问题是相同的。
2. **降维**,是指从高维度数据中提取关键信息,目的是将其转换为容易计算的低维度问题进而求解的方法。

如何理解**维数**?一般地,令 $D = {X_1,x_2..,x_m}$ 表示包含 $m$ 个示例的数据集,每个示例由 $d$ 个属性描述,则每个示例 $x_i = (x_1; x_2;...;x_d)$ 是 $d$ 维样本空间中的一个向量, $\mathcal X$, 其中 $x_{ij}$ 是 $x_i$ 在第 $j$ 个属性上的取值(例如上述第 3 个西瓜在第 2 个属性上的值是"硬 挺" ), 而 $d$ 称为样本的"维数" (`dimensionality`)。

## 二,线性模型

本文所涉及的各种机器学习算法大多着重于如何使特定函数与数据集相似(拟合)。

### 2.1,基本形式

假设,给定由 $d$ 个属性(即维数)描述的示例 $x = (x_1; x_2;...;x_d)$, 其中 $x_i$ 是样本 $x$ 在第 $i$ 个属性上的取值,所谓线性模型 (linear model)是指,**试图学得一个通过属性的线性组合来进预测的函数**,其数学形式如下:

$$f(x) = w_{1}x_1 + w_{2}x_2 +  ...+ w_{d}x_d + b
$$

一般用向量形式写成:

$$f(x) = w^{\top}x + b
$$

其中 $w = (w_1; w_2;...;w_d)$,模型参数 $w$ 和 $b$ 学得之后,线性模型也就确定下来了。

从数学角度理解**线性模型**,当因变量和自变量为线性关系时,它就是一种特殊的线性模型。

虽然线性模型形式简单、易于建模,但其是非线性模型的基础,非线性模型 (`nonlinear model`)可在线性模型的基础上通过引入**层级结构或高维映射**而得到。

### 2.2,线性回归

假设,给定数据集 $D = {(x_1, y_1),(x_2, y_2),...,(x_m, y_m)}$,一共有 $m$ 个样本,其中数据特征 $x_i = (x_{i1}; x_{i2}...; x_{id})$, 数据标签 $y_{i} \in \mathbb{R}$。

所谓"线性回归" (linear regression),即试图学得一个线性模型以尽可能准确地预测实值输出标记,用数学公式定义,即线性回归试图学得:
> 不同教科书中,公式形式不一样,但其意义一样。

$$f(x_i) = ωx_{i} + b, 使得 f(x_i) \simeq y_{i}$$

试图求解一个最佳模型参数的前提是,先确定最佳误差函数。**均方误差(MSE - mean squared error)函数是回归任务中最常用的性能度量**,因此我们可试图让线性回归模型的均方误差误差函数 $J$ 最小化,即:

$$
\begin{aligned}
(x^{*}, b^{*}) &= \underset{(w,b)}{\text{argmin}} \; J \\
(x^{*}, b^{*}) &= \underset{(w,b)}{\text{argmin}} \frac{1}{2m}\sum_{i = 1}^{m}(f(x_{i}) - y_i)^2 \\
&= \underset{(w,b)}{\text{argmin}}\frac{1}{2m} \sum_{i = 1}^{m}(y_i - wx_{i} - b)^2 \\
\end{aligned}
$$

均方误差有非常好的几何意义,它对应了常用的欧几里得距离(也简称“欧氏距离” Euclidean distance)。

基于均方误差最小化来进行模型求解的方法称为“最小二乘法” (`least squre method`)。在线性回归中,最小二乘法就是试图找到一条直线,使所有样本到直线上的欧氏距离之和最小。
> 最小二来法用途很广, 不仅限于线性回归。

![均方差函数的评估原理](../data/images/ml_basic/mse.png)

上图圆形点是样本点,直线是当前的拟合结果。如左图所示,计算样本点到直线的垂直距离,需要先根据直线的斜率来求垂足然后再计算距离,这样计算起来很慢;但实际上,在工程上我们通常使用的是右图的方式,即样本点到直线的**竖直距离**,因为这样计算很方便,用一个减法就可以了。

如果想让误差的值最小,通过对 $w$ 和 $b$ 求导,再令导数为 0(到达最小极值),就是 $w$ 和 $b$ 的最优解。

$$
\begin{aligned} \frac{\partial{J}}{\partial{w}} &=\frac{\partial{(\frac{1}{2m}\sum_{i=1}^m(y_i-wx_i-b)^2)}}{\partial{w}} \\\ &= \frac{1}{m}\sum_{i=1}^m(y_i-wx_i-b)(-x_i) \end{aligned}
$$

推导过程省略,最终得到最佳参数解:

$$ w = \frac{m\sum_{i=1}^m x_i y_i - \sum_{i=1}^m x_i \sum_{i=1}^m y_i}{m\sum_{i=1}^m x^2_i - (\sum_{i=1}^m x_i)^2}$$

$$ b= \frac{1}{m} \sum_{i=1}^m(y_i-wx_i)$$
> 注意,上式有很多个变种,在不同的文章可能不同版本。

根据以上公式,可用代码实现“最小二乘法”:

```python
def method3(X,Y,m):
    p = m*sum(X*Y) - sum(X)*sum(Y)
    q = m*sum(X*X) - sum(X)*sum(X)
    w = p/q
    return w

def calculate_b_1(X,Y,w,m):
    b = sum(Y-w*X)/m
    return b
```

如果模型参数形式为 $w^{\top}$,即包括两个或两个以上自变量的回归,则称为多元线性回归模型,数学公式如下:

$$f(x_i) = w^{\top}x_{i} + b, 使得 f(x_i) \simeq y_{i}$$

### 2.3,多分类学习

现实中常遇到多分类学习任务。有些二分类学习方法可直接推广到多分类, 但在更多情形下,我们是基于一些基本策略,利用二分类学习器来解决多分类问题。

### 2.4,类别不平衡问题

类别不平衡 (`class imbalance`)就是指分类任务中**不同类别的训练样本数目差别很大的情况**。类别不平衡学习的一个基本策略是“再缩放”(rescaling):

$$
\frac{y'}{1-y'} = \frac{y}{1-y} \times \frac{m^-}{m^+}
$$

其中,$m^+$ 表示正例数目, $m^-$ 表示反例数目,则观测概率是 $\frac{m^-}{m^+}$,$y$ 是样本标签值,$y'$ 是模型预测概率值。

再缩放的思想虽简单,但实际操作却很难,主要因为"训练集是真实样本总体的无偏采样"这个假设往往并不成立,也就是说我们未必能有效地基于训练集观测概率来推断出真实概率。

现有技术大体上有三类做法:
1. 第一类是直接对训练集里的反类样例进行“**欠采样**”(undersampling),即去除一些反例使得正、反样本数目接近,然后基于新的数据集进行训练。
2. 第二类是对训练集的正类样例进行“**过采样**”(oversampling),即增加一些正例使得正、反例数目接近。
3. 第三类则是直接基于原始训练集进行学习,但在用训练好的分类器进行预测时,将“再缩放策略”嵌入到其决策过程中,称为"阔值移动" (threshold-moving)。

## 三,数据清洗与特征处理

### 3.1,清洗标注数据

清洗标注数据的方法,主要是是**数据采样**和**样本过滤**。

+ **数据采样**:当模型不能使用全部的数据来训练时,需要对数据进行采样,设定一定的采样率;采样的方法包括**随机采样,固定比例采样**等。
+ **样本过滤**:包括结合业务情况进行数据的过滤和使用异常点检测算法,常用的异常点检测算法包括:偏差检测(聚类,最近邻算法)等。

### 3.2,特征分类

根据不同的分类方法,可以将特征分为:

+ `Low level` 特征和 `High level` 特征
+ 稳定特征与动态特征。
+ 二值特征、连续特征、枚举特征

`Low level` 主要指原始特征,通常不需要或者需要很少的人工处理和干预,例如文本中的词向量特征,图像特征中的像素点大小,用户 `id`,商品 `id` 等。High level 特征是经过比较复杂的处理,结合部分业务逻辑或者规则、模型得到的特征,例如人工打分,模型打分等特征,可以用于较复杂的非线性模型。Low level 比较针对性,覆盖面小。长尾样本的预测值主要受 high level 特征影响。高频样本的预测值主要受 low level 特征影响。

`稳定特征` 是变化频率较少的特征,例如评价平均分,团购单价价格等,在较长时间段内数值都不会发生变化。动态特征是更新变化比较频繁的特征,有些甚至是实时计算得到的特征,例如距离特征,2 小时销量等特征。或者叫做实时特征和非实时特征。针对两类特征的不同可以针对性地设计特征存储和更新方式,例如对于稳定特征,可以建入索引,较长时间更新一次,如果做缓存的话,缓存的时间可以较长。对于动态特征,需要实时计算或者准实时地更新数据,如果做缓存的话,缓存过期时间需要设置的较短。

`二值特征主要是 0/1 特征`,即特征只取两种值:`0 或者 1`,例如`用户 id 特征`:目前的 id 是否是某个特定的 id,`词向量特征`:某个特定的词是否在文章中出现等等。连续值特征是取值为有理数的特征,特征取值个数不定,例如距离特征,特征取值为是0~正无穷。枚举值特征主要是特征有固定个数个可能值,例如今天周几,只有 7 个可能值:周1,周2,…,周日。在实际的使用中,我们可能对不同类型的特征进行转换,例如将枚举特征或者连续特征处理为二值特征。枚举特征处理为二值特征技巧:将枚举特征映射为多个特征,每个特征对应一个特定枚举值,例如今天周几,可以把它转换成7个二元特征:今天是否是周一,今天是否是周二,…,今天是否是周日。连续值处理为二值特征方法:先将连续值离散化(后面会介绍如何离散化),再将离散化后的特征切分为N个二元特征,每个特征代表是否在这个区间内。

### 3.3,特征处理与分析

对特征进行分类后,需要对特征进行处理,`常用的特征处理方法`如下:

+ 特征归一化,离散化,缺省值处理
+ 特征降维方法
+ 特征选择方法

**特征归一化**。在有些算法中,例如线性模型或者距离相关的模型(聚类模型、knn 模型等),特征值的取值范围会对最终的结果产生较大影响,例如输入数据有两种不同的特征,其中的二元特征取值范围 `[0, 1]`,而距离特征取值可能是 [0,正无穷],两种特征取值范围不一致,导致模型可能会偏向于取值范围较大额特征,为了平衡取值范围不一致的特征,需要对特征进行归一化处理,将特征值取值归一化到 [0,1] 区间,常用的归一化方法包括:

1. `函数归一化`,通过映射函数将特征取值映射到 $[0,1]$ 区间,例如最大最小值归一化方法,是一种线性的映射。还有通过非线性函数的映射,例如 `log` 函数等。
2. `分维度归一化`,可以使用最大最小归一化方法,但是最大最小值选取的是所属类别的最大最小值,即使用的是局部最大最小值,不是全局的最大最小值。
3. `排序归一化`,不管原来的特征取值是什么样的,将特征按大小排序,根据特征所对应的序给予一个新的值。

**离散化**。在上面介绍过连续值的取值空间可能是无穷的,为了便于表示和在模型中处理,需要对连续值特征进行离散化处理。常用的离散化方法包括等值划分和等量划分。

1. `等值划分`,是将特征按照值域进行均分,每一段内的取值等同处理。例如某个特征的取值范围为 [0,10],我们可以将其划分为10段,[0,1),[1,2),…,[9,10)。
2. `等量划分`,是根据样本总数进行均分,每段等量个样本划分为 1 段。例如距离特征,取值范围[0,3000000],现在需要切分成 10 段,如果按照等比例划分的话,会发现绝大部分样本都在第 1 段中。使用等量划分就会避免这种问题,最终可能的切分是[0,100),[100,300),[300,500),..,[10000,3000000],前面的区间划分比较密,后面的比较稀疏。

**缺省值处理**。有些特征可能因为无法采样或者没有观测值而缺失,例如距离特征,用户可能禁止获取地理位置或者获取地理位置失败,此时需要对这些特征做特殊的处理,赋予一个缺省值。缺省值如何赋予,也有很多种方法。例如`单独表示,众数,平均值等`。

## 四,数据预处理基础

数据预处理的方法主要包括去除唯一属性、处理缺失值、属性编码、数据标准化正则化、特征选择、主成分分析等。

### 4.1,如何处理数据中的缺失值

可以分为以下 2 种情况:

1,**缺失值较多**:直接舍弃该列特征,否则可能会带来较大噪声,从而对结果造成不良影响。
  
2,**缺失值较少**:当缺失值较少(`< 10%`)时,可以考虑对缺失值进行填充,通常有几下几种填充策略:
  + 用一个**异常值**填充(比如 0 ),对应 `pandas` 函数是 `pd.DataFrame.fillna(0)`
  + **均值均值**填充:`pd.DataFrame.fillna(DataFrame.mean())`
  + **相邻数据**填充:`pd.DataFrame.fillna(DataFrame='pad')` 或 `data.fillna(method='bfill')`
  + **插值填充**:`pd.DataFrame.interpolate()`
  + **拟合**:简单来说,就是将缺失值也作为一个预测问题来处理:将数据分为正常数据和缺失数据,对有值的数据采用`随机森林`等方法拟合,然后对有缺失值的数据进行预测,然后用预测到的值来进行填充。

## 参考资料

- [Machine learning basics](https://medium.com/@Khuranasoils/machine-learning-basics-f58678cf9c15)
- 《机器学习》
- 《智能之门》
- [机器学习中的数据清洗与特征处理综述](https://tech.meituan.com/2015/02/10/machinelearning-data-feature-process.html)

================================================
FILE: 3-machine_learning/机器学习基本原理.md
================================================
- [前言](#前言)
- [5.1 学习算法](#51-学习算法)
  - [5.1.1 任务 $T$](#511-任务-t)
  - [5.1.2 性能度量 $P$](#512-性能度量-p)
  - [5.1.3 经验 $E$](#513-经验-e)
  - [5.1.4 示例: 线性回归](#514-示例-线性回归)
- [5.2 容量、过拟合和欠拟合](#52-容量过拟合和欠拟合)
  - [5.2.1 没有免费午餐定理](#521-没有免费午餐定理)
  - [5.2.2 正则化](#522-正则化)
- [5.3 超参数和验证集](#53-超参数和验证集)
  - [5.3.1 验证集的作用](#531-验证集的作用)
  - [5.3.2 交叉验证](#532-交叉验证)
- [5.4 估计、偏差和方差](#54-估计偏差和方差)
  - [5.4.1 点估计](#541-点估计)
  - [5.4.2 偏差](#542-偏差)
  - [5.4.3 方差和标准差](#543-方差和标准差)
  - [5.4.4 权衡偏差和方差以最小化均方误差](#544-权衡偏差和方差以最小化均方误差)
- [5.5 最大似然估计](#55-最大似然估计)
- [5.6 贝叶斯统计](#56-贝叶斯统计)
- [5.7 监督学习算法](#57-监督学习算法)
- [5.8 无监督学习算法](#58-无监督学习算法)
  - [5.8.1 PCA 降维](#581-pca-降维)
  - [5.8.2 k-均值聚类](#582-k-均值聚类)
- [5.9 随机梯度下降](#59-随机梯度下降)
- [5.10 构建机器学习算法 pipeline](#510-构建机器学习算法-pipeline)
- [参考资料](#参考资料)

> 本文大部分内容参考《深度学习》书籍,从中抽取重要的知识点,并对部分概念和原理加以自己的总结,适合当作原书的补充资料阅读,也可当作快速阅览机器学习原理基础知识的参考资料。

## 前言

**深度学习是机器学习的一个特定分支**。我们要想充分理解深度学习,必须对机器学习的基本原理有深刻的理解。

大部分机器学习算法都有**超参数**(必须在学习算法外**手动设定**)。**机器学习本质上属于应用统计学**,其更加强调使用计算机对复杂函数进行**统计估计**,而较少强调围绕这些函数证明置信区间;因此我们会探讨两种统计学的主要方法: **频率派估计和贝叶斯推断**。同时,大部分机器学习算法又可以分成**监督学习**和**无监督学习**两类;本文会介绍这两类算法定义,并给出每个类别中一些算法示例。

本章内容还会介绍如何组合不同的算法部分,例如优化算法、代价函数、模型和数据 集,来建立一个机器学习算法。最后,在 5.11 节中,我们描述了一些限制传统机器学习泛化能力的因素。正是这些挑战推动了克服这些障碍的深度学习算法的发展。
> 大部分深度学习算法都是基于随机梯度下降算法进行求解的。

## 5.1 学习算法

机器学习算法是一种能够从数据中学习的算法。这里所谓的“学习“是指:“如果计算机程序在任务 $T$ 中的性能(以 $P$ 衡量)随着经验 $E$ 而提高,则可以说计算机程序从经验 $E$ 中学习某类任务 $T$ 和性能度量 $P$。”-来自 `Mitchell` (`1997`)
> 经验 $E$,任务 $T$ 和性能度量 $P$ 的定义范围非常宽广,本文不做详细解释。

### 5.1.1 任务 $T$

从 “任务” 的相对正式的定义上说,学习过程本身不能算是任务。学习是我们所谓的获取完成任务的能力。机器学习可以解决很多类型的任务,一些非常常见的机器学习任务列举如下:
1. **分类**:在这类任务中,计算机程序需要指定某些输入属于 $k$ 类中的哪一类,例如图像分类中的二分类问题,多分类、单标签问题、多分类多标签问题。
2. **回归**:在这类任务中,计算机程序需要对给定输入预测数值。为了解决这个任务,学习算法需要输出函数 $f : \mathbb{R}^n \to \mathbb{R}$。除了返回结果的形式不一样外,这类 问题和分类问题是很像的。
3. **机器翻译**
4. **结构化输出**
5. **异常检测**
6. **合成和采样**
7. **去噪**
8. **密度估计或概率质量函数估计**
9. **输入缺失分类**
10. **转录**
11. **缺失值填补**

### 5.1.2 性能度量 $P$

为了评估机器学习算法的能力,我们必须设计其性能的**定量度量**,即算法的精度指标。通常,性能度量 $P$ 特定于系统正在执行的任务 $T$。
> 可以理解为不同的任务有不同的性能度量。

对于诸如分类、缺失输入分类和转录任务,我们通常度量模型的**准确率**(`accu- racy`)。准确率是指该模型输出正确结果的样本比率。我们也可以通过**错误率**(`error rate`)得到相同的信息。错误率是指该模型输出错误结果的样本比率。

我们使用测试集(`test set`)数据来评估系统性能,将其与训练机器学习系统的训练集数据分开。

值得注意的是,性能度量的选择或许看上去简单且客观,但是选择一个与系统理想表现能对应上的性能度量通常是很难的。

### 5.1.3 经验 $E$

根据学习过程中的不同经验,机器学习算法可以大致分类为两类

- **无监督**(`unsuper-vised`)算法
- **监督**(`supervised`)算法

**无监督学习算法**(`unsupervised learning algorithm`)训练含有很多特征的数据集,然后学习出这个数据集上有用的结构性质。在深度学习中,我们通常要学习生成数据集的整个概率分布,显式地,比如密度估计,或是隐式地,比如合成或去噪。 还有一些其他类型的无监督学习任务,例如聚类,将数据集分成相似样本的集合。

**监督学习算法**(`supervised learning algorithm`)也训练含有很多特征的数据集,但与无监督学习算法不同的是**数据集中的样本都有一个标签**(`label`)或目标(`target`)。例如,`Iris` 数据集注明了每个鸢尾花卉样本属于什么品种。监督学习算法通过研究 `Iris` 数据集,学习如何根据测量结果将样本划分为三个不同品种。

**半监督学习算法**中,一部分样本有监督目标,另外一部分样本则没有。在多实例学习中,样本的整个集合被标记为含有或者不含有该类的样本,但是集合中单独的样本是没有标记的。

大致说来,无监督学习涉及到观察随机向量 $x$ 的好几个样本,试图显式或隐式地学习出概率分布 $p(x)$,或者是该分布一些有意思的性质; 而监督学习包含观察随机向量 $x$ 及其相关联的值或向量 $y$,然后从 $x$ 预测 $y$,通常是估计 $p(y | x)$。术语监督学习(`supervised learning`)源自这样一个视角,教员或者老师提供目标 $y$ 给机器学习系统,指导其应该做什么。在无监督学习中,没有教员或者老师,算法必须学会在没有指导的情况下理解数据。

无监督学习和监督学习并不是严格定义的术语。它们之间界线通常是模糊的。很多机器学习技术可以用于这两个任务。

尽管无监督学习和监督学习并非完全没有交集的正式概念,它们确实有助于粗略分类我们研究机器学习算法时遇到的问题。传统地,人们将回归、分类或者结构化输出问题称为监督学习。支持其他任务的密度估计通常被称为无监督学习。

表示数据集的常用方法是**设计矩阵**(`design matrix`)。

### 5.1.4 示例: 线性回归

我们将机器学习算法定义为,通过经验以提高计算机程序在某些任务上性能的算法。这个定义有点抽象。为了使这个定义更具体点,我们展示一个简单的机器学习示例: **线性回归**(`linear regression`)。

顾名思义,**线性回归解决回归问题**。 换句话说,目标是构建一个系统,该系统可以将向量 $x \in \mathbb{R}$ 作为输入,并预测标量 $y \in \mathbb{R}$ 作为输出。在线性回归的情况下,输出是输入的线性函数。令 $\hat{y}$ 表示模型预测值。我们定义输出为

$$\hat{y} = w^{⊤}x \tag{5.3}$$

其中 $w \in \mathbb{R}^{n}$ 是**参数**(`parameter`)向量。

参数是控制系统行为的值。在这种情况下,$w_i$ 是系数,会和特征 $x_i$ 相乘之 后全部相加起来。我们可以将 $w$ 看作是一组决定每个特征如何影响预测的权重 (weight)。

通过上述描述,我们可以定义任务 $T$ : 通过输出 $\hat{y} = w^{⊤}x$ 从 $x$ 预测 $y$。

我们使用**测试集**(`test set`)来评估模型性能如何,将输入的设计矩 阵记作 $\textit{X}$(test),回归目标向量记作 $y$(test)。

**回归任务**常用的一种模型性能度量方法是计算模型在测试集上的 **均方误差**(`mean squared error`)。如果 $\hat{y}$(`test`) 表示模型在测试集上的预测值,那么均方误差表示为:

$$MSE_{test} = \frac{1}{m} \sum_{i}(\hat{y}^{(test)}-y^{(test)})_{i}^{2} \tag{5.4}$$

直观上,当 $\hat{y}^{(test)}$ = $y^{(test)}$ 时,我们会发现误差降为 0。

图 5.1 展示了线性回归算法的使用示例。

![图5.1-一个线性回归的例子](../data/images/ml_basc_principle/图5.1-一个线性回归的例子.png)

## 5.2 容量、过拟合和欠拟合

机器学习的挑战主要在于算法如何在测试集(先前未观测的新输入数据)上表现良好,而不只是在训练集上表现良好,即训练误差和泛化误差读比较小,也可理解为算法泛化性比较好。所谓泛化性(generalized)好指的是,算法在在测试集(以前未观察到的输入)上表现良好。

机器学习算法的两个主要挑战是: **欠拟合**(`underfitting`)和**过拟合**(`overfitting`)。
- 欠拟合是指模型不能在训练集上获得足够低的误差。
- 而过拟合是指训练误差和和测试误差之间的差距太大。

通过调整模型的**容量**(`capacity`),我们可以控制模型是否偏向于过拟合或者欠拟合。通俗地讲,**模型的容量是指其拟合各种函数的能力**。容量低的模型可能很难拟合训练集,容量高的模型可能会过拟合,因为记住了不适用于测试集的训练集性质。

一种控制训练算法容量的方法是选择**假设空间**(`hypothesis space`),即**学习算法可以选择作为解决方案的函数集**。例如,线性回归算法将其输入的所有线性函数的集合作为其假设空间。我们可以推广线性回归以在其假设空间中包含多项式,而不仅仅是线性函数。这样做就增加模型的容量。

> 注意,学习算法的效果不仅很大程度上受影响于假设空间的函数数量,也取决于这些函数的具体形式。

当机器学习算法的容量适合于所执行任务的复杂度和所提供训练数据的数量时,算法效果通常会最佳。容量不足的模型不能解决复杂任务。容量高的模型能够解决复杂的任务,但是当其容量高于任务所需时,有可能会过拟合。

图 5.2 展示了上述原理的使用情况。我们比较了线性,二次和 `9` 次预测器拟合真实二次函数的效果。

![图5-2三个模型拟合二次函数](../data/images/ml_basc_principle/图5-2三个模型拟合二次函数.png)

提高机器学习模型泛化性的早期思想是**奥卡姆剃刀**原则,即选择“最简单”的那一个模型。

统计学习理论提供了量化模型容量的不同方法。在这些中,最有名的是 **Vapnik- Chervonenkis 维度**(Vapnik-Chervonenkis dimension, VC)。`VC` 维度量二元分类 器的容量。`VC` 维定义为该分类器能够分类的训练样本的最大数目。假设存在 $m$ 个 不同 $x$ 点的训练集,分类器可以任意地标记该 $m$ 个不同的 $x$ 点,`VC` 维被定义为 $m$ 的最大可能值。

因为可以量化模型的容量,所以使得统计学习理论可以进行量化预测。统计学习理论中最重要的结论阐述了训练误差和泛化误差之间差异的上界随着模型容量增长而增长,但随着训练样本增多而下降 (`Vapnik and Chervonenkis, 1971`; `Vapnik, 1982`; `Blumer et al., 1989`; `Vapnik, 1995`)。这些边界为机器学习算法可以有效解决问题提供了理论 验证,但是它们**很少应用于实际中的深度学习算法**。一部分原因是边界太松,另一部分原因是**很难确定深度学习算法的容量**。由于有效容量受限于优化算法的能力,所以确定深度学习模型容量的问题特别困难。而且我们对深度学习中涉及的非常普遍的**非凸优化问题**的理论了解很少。

虽然更简单的函数更可能泛化(训练误差和测试误差的差距小),但我们仍然必须选择一个足够复杂的假设来实现低训练误差。通常,随着模型容量的增加,训练误差会减小,直到它逐渐接近最小可能的误差值(假设误差度量具有最小值)。通常,**泛化误差是一个关于模型容量的 U 形曲线函数**。如下图 `5.3` 所示。

![容量和误差之间的典型关系](../data/images/ml_basc_principle/图5-3model_capacity.png)

### 5.2.1 没有免费午餐定理

机器学习的**没有免费午餐定理**(`Wolpert,1996`)指出,对所有可能的数据生成分布进行平均,每个分类算法在对以前未观察到的点进行分类时具有相同的错误率。换句话说,在某种意义上,**没有任何机器学习算法普遍优于其他任何算法**。

上述这个结论听着真的让人伤感,但庆幸的是,这些结论仅在我们考虑**所有可能的数据生成分布**时才成立。如果我们对实际应用中遇到的概率分布类型做出假设,那么我们可以**设计出在这些分布上表现良好的学习算法**。

这意味着机器学习研究的目标**不是找一个通用学习算法或是绝对最好的学习算法**。反之,我们的目标是理解什么样的分布与人工智能获取经验的 “真实世界” 相关,**什么样的学习算法在我们关注的数据生成分布上效果最好**。

**总结**:没有免费午餐定理清楚地阐述了没有最优的学习算法,即暗示我们必须在特定任务上设计性能良好的机器学习算法。

### 5.2.2 正则化

所谓正则化,是指我们通过**修改学习算法,使其降低泛化误差而非训练误差**的方法。

**正则化是一种思想(策略)**,它是机器学习领域的中心问题之一,其重要性只有优化能与其相媲美。

一般地,正则化一个学习函数 $f(x;\theta)$ 的模型,我们可以给代价函数添加被称为**正则化项**(regularizer)的惩罚。如果正则化项是 $\Omega(w) = w^{\top}w$,则称为**权重衰减**(weight decay)。

例如,我们可以加入权重衰减(weight decay)来修改线性回归的目标函数,如偏好于平方 $L^2$ 范数较小权重的目标函数形式:

$$J(w) = MSE_{train} + \lambda w^{⊤}w \tag{5.18}$$

其中 $J(w)$ 是目标函数,$w$ 是权重参数。$\lambda$ 是超参数,需提前设置,**其控制我们对较小权重的偏好强度**。当 $\lambda = 0$,我们没有任何偏好。$\lambda$ 越大,则权重越小。最小化 $J(w)$ 会导致权重的选择在**拟合训练数据和较小权重之间进行权衡**。

**和上一节没有最优的学习算法一样,一样的,也没有最优的正则化形式**。反之,我们必须挑选一个非常适合于我们所要解决的任务的正则形式。

## 5.3 超参数和验证集

**超参数的值不是通过学习算法本身学习出来的,而是需要算法定义者手动指定的**。

### 5.3.1 验证集的作用

通常,`80%` 的训练数据用于训练,`20%` 用于验证。验证集是用于估计训练中或训练后的泛化误差,从而**更新超参数**。

### 5.3.2 交叉验证

一个**小规模的测试集**意味着平均测试误差估计的统计不确定性,使得很难判断算法 A 是否比算法 B 在给定的任务上做得更好。解决办法是基于在原始数据上**随机采样或分离**出的不同数据集上**重复训练和测试**,最常见的就是 $k$-折交叉验证,即将数据集分成 $k$ 个 不重合的子集。测试误差可以估计为 $k$ 次计算后的平均测试误差。在第 $i$ 次测试时, 数据的第 $i$ 个子集用于测试集,其他的数据用于训练集。算法过程如下所示。
> k 折交叉验证虽然一定程度上可以解决小数据集上测试误差的不确定性问题,但代价则是增加了计算量。

![k-折交叉验证算法](../data/images/ml_basc_principle/k-fold.png)

k-折交叉验证的训练集划分方式如下图所示:

![image](https://user-images.githubusercontent.com/37138671/114163327-a0dfe200-995c-11eb-9a0e-4ce4fe13150c.png)

+ 当 `K` 值大的时候, 我们会有更少的 `Bias`(偏差), 更多的 `Variance`。
+ 当 `K` 值小的时候, 我们会有更多的 `Bias`(偏差), 更少的 `Variance`。

`K` 折交叉验证的代码实现可以参考《Python深度学习》第三章,在模型训练好后,可通过计算所有 `Epoch` 的 `K` 折验证分数的平均值,并绘制每轮的模型验证指标变化曲线,观察哪个 `Epoch` 后模型不再收敛,从而完成模型调参工作。同时,`K` 折交叉验证方式训练模型会得到 `K`个模型,将这个 `K` 个模型在测试集上的推理结果取平均值或者投票,也是一种 `Ensemble` 方式,可以增强模型泛化性,防止过拟合。

```python
# 计算所有轮次中的 K 折验证分数平均值
average_mae_history = [np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]
```

## 5.4 估计、偏差和方差

统计领域为我们提供了很多工具来实现机器学习目标,不仅可以解决训练集上 的任务,还可以泛化。基本的概念,例如参数估计、偏差和方差,对于正式地刻画泛化、欠拟合和过拟合都非常有帮助。

### 5.4.1 点估计

点估计试图为一些感兴趣的量提供单个 ‘‘最优’’ 预测。一般地,感兴趣的量可以是单个参数也可以是一个向量参数,例如第 5.1.4 节线性回归中的权重,但是也有可能是整个函数。

为了区分参数估计和真实值,我们习惯将参数 $\theta$ 的点估计表示为 $\hat{\theta}$。

令 ${x^{(1)}, . . . , x^{(m)}}$ 是 $m$ 个独立同分布(i.i.d.)的数据点。 点估计(point esti-mator)或统计量(statistics)是这些数据的任意函数:
$$
\hat{\theta_m} =g(x^{(1)},...,x^{(m)}). \tag{5.19}
$$


### 5.4.2 偏差

估计的偏差定义如下:
$$
bias(\hat{\theta_m}) = E(\hat{\theta_m}) − \theta, \tag{5.19}
$$
其中期望作用在所有数据(看作是从随机变量采样得到的)上,$\hat{\theta}$ 是用于定义数据生成分布的 $\theta$ 的真实值。如果 $bias(\hat{\theta_m}) = 0$,那么估计量 $\hat{\theta_m}$ 则被称为是**无偏** (unbiased),同时意味着 $E(\hat{\theta_m}) = \theta$。

### 5.4.3 方差和标准差

方差记为 $Var(\hat{\theta})$ 或 $\sigma^{2}$,方差的平方根被称为标准差。

### 5.4.4 权衡偏差和方差以最小化均方误差

偏差和方差度量着估计量的两个不同误差来源。偏差度量着偏离真实函数或参数的误差期望。而方差度量着数据上任意特定采样可能导致的估计期望的偏差。

**偏差和方差的关系和机器学习容量、欠拟合和过拟合的概念紧密相联**。用 MSE 度量泛化误差(偏差和方差对于泛化误差都是有意义的)时,增加容量会增加方差,降低偏差。如图 5.6 所示,我们再次在关于容量的函数中,看到泛化误差的 U 形曲线。

![偏差方差泛化误差和模型容量的关系](../data/images/ml_basc_principle/图5-6.png)

## 5.5 最大似然估计

与其猜测某个函数可能是一个好的估计器,然后分析它的偏差和方差,我们更希望有一些原则,我们可以从中推导出特定的函数,这些函数是不同模型的良好估计器。最大似然估计就是其中最为常用的准则。

## 5.6 贝叶斯统计

到目前为止,我们已经描述了**频率统计**(frequentist statistics)和基于估计单一 $\theta$  值的方法,然后基于该估计作所有的预测。 另一种方法是在进行预测时考虑所有可能的 $\theta$ 值。 后者属于**贝叶斯统计**(Bayesian statistics)的范畴。

如 5.4.1 节中讨论的,频率派的视角是真实参数 $\theta$  是未知的定值,而点估计  $\hat{\theta}$ 是考虑数据集上函数(可以看作是随机的)的随机变量。

贝叶斯统计的视角完全不同。贝叶斯用概率反映知识状态的确定性程度。数据集能够被直接观测到,因此不是随机的。另一方面,真实参数 $\theta$ 是未知或不确定的, 因此可以表示成随机变量。

## 5.7 监督学习算法

回顾 5.1.3 节内容,简单来说,监督学习算法是给定一组输入 $x$ 和输出 $y$ 的训练 集,学习如何关联输入和输出。在许多情况下,输出 $y$ 可能难以自动收集,必须由人类“监督者”提供,但即使训练集目标是自动收集的,该术语仍然适用。

## 5.8 无监督学习算法

回顾第5.1.3节,无监督算法只处理 “特征’’,不操作监督信号。监督和无监督算法之间的区别没有规范严格的定义,因为没有客观的测试来区分一个值是特征还是监督者提供的目标。通俗地说,无监督学习的大多数尝试是指从不需要人为注释的样本的分布中提取信息。该术语通常与密度估计相关,学习从分布中采样、学习从分布中去噪、寻找数据分布的流形或是将数据中相关的样本聚类。

### 5.8.1 PCA 降维

`PCA`(Principal Component Analysis)是学习数据表示的无监督学习算法,常用于高维数据的降维,可用于提取数据的主要特征分量。

PCA 的数学推导可以从最大可分型和最近重构性两方面进行,前者的优化条件为划分后方差最大,后者的优化条件为点到划分平面距离最小。

### 5.8.2 k-均值聚类

另外一个简单的表示学习算法是 $k$-均值聚类。$k$-均值聚类算法将训练集分成 $k$ 个靠近彼此的不同样本聚类。因此我们可以认为该算法提供了 $k$-维的 one-hot 编码向量 $h$ 以表示输入 $x$。当 $x$ 属于聚类 $i$ 时,有 $h_i = 1$,$h$ 的其他项为零。

$k$-均值聚类初始化 k 个不同的中心点 ${μ^{(1)}, . . . , μ^{(k)}}$,然后迭代交换以下两个不同的步骤直到算法收敛。

1. 步骤一,每个训练样本分配到最近的中心点 $μ^{(i) }$ 所代表的聚类 $i$。 
2. 步骤二,每一个中心点 $μ^{(i) }$ 更新为聚类 $i$ 中所有训练样本 $x^{(j)}$ 的均值。

关于聚类的一个问题是**聚类问题本身是病态的**。这是说没有单一的标准去度量聚类的数据在真实世界中效果如何。我们可以度量聚类的性质,例如类中元素到类中心点的欧几里得距离的均值。这使我们可以判断从聚类分配中重建训练数据的效果如何。然而我们不知道聚类的性质是否很好地对应到真实世界的性质。此外,可能有许多不同的聚类都能很好地对应到现实世界的某些属性。我们可能希望找到和 一个特征相关的聚类,但是得到了一个和任务无关的,同样是合理的不同聚类。

例如,假设我们在包含红色卡车图片、红色汽车图片、灰色卡车图片和灰色汽车图片的数据集上运行两个聚类算法。如果每个聚类算法聚两类,那么可能一个算法将汽车和卡车各聚一类,另一个根据红色和灰色各聚一类。假设我们还运行了第三个聚类算法,用来决定类别的数目。这有可能聚成了四类,红色卡车、红色汽车、灰色卡 车和灰色汽车。现在这个新的聚类至少抓住了属性的信息,但是丢失了相似性信息。 红色汽车和灰色汽车在不同的类中,正如红色汽车和灰色卡车也在不同的类中。该聚类算法没有告诉我们灰色汽车和红色汽车的相似度比灰色卡车和红色汽车的相似度更高,我们只知道它们是不同的。

## 5.9 随机梯度下降

几乎所有的深度学习算法都用到了一个非常重要的优化算法: **随机梯度下降** (stochastic gradient descent, `SGD`)。

机器学习中反复出现的一个问题是好的泛化需要大的训练集,但大的训练集的 计算代价也更大。

机器学习算法中的代价函数通常可以分解成每个样本的代价函数的总和。

![sgd](../data/images/ml_basc_principle/sgd.png)

随机梯度下降的核心是,**梯度是期望**,而期望可使用小规模的样本近似估计。具体来说,在算法的每一步,我们从训练集中均匀抽出一小批量(`minibatch`)样本 $B={x^{(1)},...,x^{(m′)}}$。小批量的数目 $m′$ 通常是一个相对较小的数,一般为 $2^n$(取决于显卡显卡)。重要的是,当训练集大小 m 增长时,$m′$ 通常是固定的。我们可能在拟合几十亿的样本时,但每次更新计算只用到几百个样本。

![sgd2](../data/images/ml_basc_principle/sgd2.png)

梯度下降往往被认为很慢或不可靠。以前,将梯度下降应用到非凸优化问题被认为很鲁莽或没有原则。但现在,我们知道梯度下降用于深度神经网络模型的训练时效果是不错的。优化算法不一定能保证在合理的时间内达到一个局部最小值,但它通常能及时地找到代价函数一个很小的值,并且是有用的。

随机梯度下降在深度学习之外有很多重要的应用。它是在大规模数据上训练大型线性模型的主要方法。对于固定大小的模型,每一步随机梯度下降更新的计算量不取决于训练集的大小 $m$。在实践中,当训练集大小增长时,我们通常会使用一个更大的模型,但这并非是必须的。达到收敛所需的更新次数通常会随训练集规模增大而增加。然而,当 $m$ 趋向于无穷大时,该模型最终会在随机梯度下降抽样完训练 集上的所有样本之前收敛到可能的最优测试误差。继续增加 $m$ 不会延长达到模型可能的最优测试误差的时间。从这点来看,我们可以认为用 SGD 训练模型的渐近代价是关于 $m$ 的函数的 $O(1)$ 级别。

## 5.10 构建机器学习算法 pipeline

几乎所有的深度学习算法都可以被描述为一个相当简单的 `pipeline`: 

1. 特定的数据集
2. 代价函数
3. 优化过程
4. 神经网络模型。

## 参考资料

- 《深度学习》
- [【机器学习】降维——PCA(非常详细)](https://zhuanlan.zhihu.com/p/77151308)



================================================
FILE: 3-machine_learning/机器学习基本概念总结.md
================================================
- [一,余弦相似度与欧氏距离](#一余弦相似度与欧氏距离)
  - [1.1,余弦相似度](#11余弦相似度)
  - [1.2,欧式距离](#12欧式距离)
  - [1.3,余弦相似度和欧氏距离的区别](#13余弦相似度和欧氏距离的区别)
- [二,Bias(偏差)和Varience(方差)](#二bias偏差和varience方差)
- [2.1,概念定义](#21概念定义)
  - [2.2,图形定义](#22图形定义)
  - [2.3,数学定义](#23数学定义)
  - [2.4,导致偏差和方差的原因](#24导致偏差和方差的原因)
  - [2.5,深度学习中的偏差与方差](#25深度学习中的偏差与方差)
- [三,模型容量、过拟合和欠拟合](#三模型容量过拟合和欠拟合)
- [四,样本方差与总体方差](#四样本方差与总体方差)
  - [4.1,方差定义](#41方差定义)
- [五,先验概率与后验概率](#五先验概率与后验概率)
  - [5.1,条件概率](#51条件概率)
  - [5.2,先验概率](#52先验概率)
  - [5.3,后验概率](#53后验概率)
  - [5.4,贝叶斯公式](#54贝叶斯公式)
  - [5.5,后验概率实例](#55后验概率实例)
- [六,相对熵(KL散度)与交叉熵](#六相对熵kl散度与交叉熵)
  - [6.1,信息熵](#61信息熵)
  - [6.2,相对熵/KL散度](#62相对熵kl散度)
  - [6.3,交叉熵 cross-entroy](#63交叉熵-cross-entroy)
  - [6.4,为什么交叉熵可以用作代价](#64为什么交叉熵可以用作代价)
  - [6.5,KL 散度与交叉熵的关系](#65kl-散度与交叉熵的关系)
- [七,随机梯度下降算法](#七随机梯度下降算法)
  - [八,超参数和验证集](#八超参数和验证集)
- [九,正则化方法](#九正则化方法)
- [参考资料](#参考资料)

> 深度学习是机器学习的一个特定分支,要想充分理解深度学习,就必须对机器学习的基本原理有深刻的理解。机器学习的本质属于应用统计学,其更多地关注如何用计算机统计地估计复杂函数,而不太关注为这些函数提供置信区间,大部分机器学习算法可以分成监督学习和无监督学习两类;通过组合不同的算法部分,例如优化算法、代价函数、模型和数据集可以建立一个完整的机器学习算法。

## 一,余弦相似度与欧氏距离

### 1.1,余弦相似度

通过对两个文本分词,`TF-IDF` 算法向量化,利用空间中两个向量的夹角,来判断这两个向量的相似程度:(`计算夹角的余弦,取值 0-1`)

+ 当两个向量夹角越大,距离越远,最大距离就是两个向量夹角 180°;
+ 夹角越小,距离越近,最小距离就是两个向量夹角 0°,完全重合。
+ 夹角越小相似度越高,但由于有可能一个文章的特征向量词特别多导致整个向量维度很高,使得计算的代价太大不适合大数据量的计算。

**计算两个向量a、b的夹角余弦:**
我们知道,余弦定理:$cos(\theta) = \frac {a^2+b^2+c^2}{2ab}$ ,由此推得两个向量夹角余弦的计算公式如下:
$$cos(\theta) = \frac {ab}{||a|| \times ||b||} = \frac {x_{1}x_{2}+y_1y_2}{\sqrt{x^2_1+y^2_1}\sqrt{x^2_2+y^2_2}}$$
(分子就是两个向量的内积,分母是两个向量的模长乘积)

### 1.2,欧式距离
> 欧式距离和 L2 范数计算公式相同。

在欧几里得空间中,欧式距离其实就是向量空间中两点之间的距离。点 $x = (x_{1}, ..., x_{n})$ 和 $y = (y_{1}, ..., y_{n})$ 之间得欧氏距离计算公式如下:
$$d(x,y) = \sqrt {((x_{1}-y_{1})^{2} + (x_{2}-y_{2})^{2} + ... + (x_{n}-y_{n})^{2})}$$

### 1.3,余弦相似度和欧氏距离的区别

+ 欧式距离和余弦相似度都能度量 `2` 个向量之间的相似度
+ 放到向量空间中看,欧式距离衡量`两点之间`的直线距离,而余弦相似度计算的是`两个向量`之间的夹角
+ 没有归一化时,欧式距离的范围是 `[0, +∞]`,而余弦相似度的范围是 `[-1, 1]`;余弦距离是计算相似程度,而欧氏距离计算的是相同程度(对应值的相同程度)
+ 归一化的情况下,可以将空间想象成一个超球面(三维),欧氏距离就是球面上两点的直线距离,而向量余弦值等价于两点的球面距离,本质是一样。

## 二,Bias(偏差)和Varience(方差)

当我们讨论预测模型时,预测误差可以分解为我们关心的两个主要子成分:“**偏差**”引起的误差和“**方差**”引起的误差。 在模型最小化偏差和方差的能力之间存在权衡。 了解这两类错误可以帮助我们诊断模型结果,避免出现过拟合或欠拟合的错误。
 
> **有两个不同的概念都被称为“方差”**。一种是**理论概率分布的方差**。而另一种方差是一组观测值的特征(**统计意义上的方差**)。

## 2.1,概念定义

统计领域为我们提供了很多工具来实现机器学习目标,不仅可以解决训练集上的任务,还可以泛化。偏差-方差指标方法是试图对学习算法(模型)的期望泛化错误率进行拆解:

$$Error = Bias + Varience$$
> Varience(Error due to Variance), Bias(Error due to Bias)

+ `Error`: 反映的是整个模型的准确度。
+ `Bias`:偏差引起的误差被视为我们模型的预期(或平均)预测与我们试图预测的正确值之间的差异,即模型的**准确性**。当然,您只有一个模型,因此谈论预期或平均预测值可能看起来有点奇怪。但是,想象一下您可以多次重复整个模型构建过程:每次您收集新数据并运行新分析时都会创建一个新模型。由于基础数据集中的随机性,生成的模型将具有一系列预测。偏差通常衡量这些模型的预测与正确值之间的差距。
+ `Varience`:方差引起的误差被视为给定数据点的模型预测的可变性,即**即模型的稳定性。同样,假设您可以多次重复整个模型构建过程。方差是给定点的预测在模型的不同实现之间的变化程度。
+ `Bias`: 反映的是模型在**样本上的输出与真实值之间的误差**,即模型的准确性。以打靶事件为例,`low bias`,一般就得复杂化模型,表现出来就是点都打在靶心中间,但这样容易过拟合 (`overfitting`),过拟合对应下图是 `high variance`,点很分散。
+ `Varience`: 反映的是模型每一次输出的结果与模型输出期望之间的误差,即模型的稳定性,是训练集上训练出来的模型在测试集上的表现。同样以打靶事件为例,`low variance` 对应就是点都打的很集中,但不一定是靶心附近,手很稳,但是瞄的不准。

### 2.2,图形定义

`Bias` 和 `Varience` 的图形定义:

我们可以使用靶心图创建偏差和方差的图形可视化。想象一下,目标的中心是一个可以完美预测正确值的模型。当我们远离靶心时,我们的预测会变得越来越糟。

`Low Bias` 表现出来就是点都打在靶心中间,但这样容易过拟合 (`overfitting`),过拟合对应下图是 `High Variance`,表现就是点很分散,没有集中在一起,手不稳啊(对应就是模型预测结果变化性太大)。
`Low Variance` 对应就是点都打的很集中,但不一定是靶心附近,手很稳,但是瞄的不一定准。

我们可以绘制四种不同的情况,代表高低偏差和方差的组合。
![Graphical illustration of bias and variance](../data/images/ml_concept/Graphical_illustration_of_bias_variance.png)
> 图片来源 [Understanding the Bias-Variance Tradeoff](http://scott.fortmann-roe.com/docs/BiasVariance.html)。

总的来说,参数估计、偏差和方差虽然是统计领域的基本概念,但它们的关系也和机器学习的模型容量、欠拟合和过拟合的概念紧密相联。

偏差和方差度量着估计量的两个不同误差来源:
- **偏差度量着偏离真实函数或参数的误差期望**。
- **方差度量着数据上任意特定采样可能导致的估计期望的偏差**。

![模型容量和误差之间的典型关系1](../data/images/ml_concept/model_capacity_bias_varience.png)
### 2.3,数学定义

假设对测试样本 $x$, 令 $y_{D}$ 为 $x$ 在数据集中的标记,$y$ 为 $x$ 的真实标记, $f(x;D)$ 为在训练集 $D$ 上学习到的模型 $f$ 在 $x$ 上的预测输出。
+ 训练过程中期望输出与真实标记(标签)的差别称为偏差(`bias`):$bias^{2}(x) = (\bar{f} - y)^{2}$
+ (交叉验证训练模型)使用样本数相同不同训练集训练出来的模型在测试集上产生的`方差`为: $var(x) = E_{D}[(f(x;D) - \bar{f})^{2}] $

### 2.4,导致偏差和方差的原因
>  [机器学习中的Bias(偏差),Error(误差),和Variance(方差)有什么区别和联系?](https://www.zhihu.com/question/27068705)

+ 偏差通常是由于我们对学习算法做了错误的假设,或者模型的复杂度不够;
    + 比如真实模型是一个二次函数,而我们假设模型为一次函数,这就会导致偏差的增大(欠拟合);
    + 由偏差引起的误差通常在训练误差上就能体现,或者说训练误差主要是由偏差造成的
+ 方差通常是由于模型的复杂度相对于训练集过高导致的;
    + 比如真实模型是一个简单的二次函数,而我们假设模型是一个高次函数,这就会导致方差的增大(过拟合);
    + 由方差引起的误差通常体现在测试误差相对训练误差的增量上。

### 2.5,深度学习中的偏差与方差

+ 神经网络的拟合能力非常强,因此它的训练误差(偏差)通常较小;
+ 但是过强的拟合能力会导致较大的方差,使模型的测试误差(泛化误差)增大;
+ 深度学习的核心工作之一就是**研究如何降低模型的泛化误差**,这类方法统称为`正则化方法`。

## 三,模型容量、过拟合和欠拟合

+  模型容量是指模型拟合各种函数的能力,决定了模型是欠拟合还是过拟合。
+ **过拟合**:就是指训练误差和测试误差间距过大,即方差(`variance`)过大,表现为模型泛化性能下降即不够”稳“,正则化目的在于解决过拟合问题。
+ **欠拟合**:和过拟合相反,指模型的训练误差过大,即偏差(`bias`)过大,表现为模型不够”准“,优化算法目的在于解决欠拟合问题。
+  机器学习模型的目的是解决欠拟合和过拟合的问题,这也是机器学习算法的两个挑战。

> 训练误差 `train error`,泛化误差 `generalization error`(也叫测试误差 `test error`)。

模型容量与偏差、方差的关系图如下所示:

![模型容量和误差之间的典型关系2](../data/images/ml_concept/model_capacity_under_over_fitting.png)

从上图可以看出,当容量增大(x 轴)时,偏差(蓝色虚线)随之减小,而方差(绿色虚线)随之增大,使得泛 化误差(加粗曲线)产生了另一种 U 形。如果我们沿着轴改变容量,会发现**最佳容量**(optimal capacity),当容量小于最佳容量会呈现欠拟合,大于时导致过拟合。这种关系与第一章中讨论的容量、欠拟合和过拟合之间的关系类似。

## 四,样本方差与总体方差
> 本章中样本方差与总体方差概念是统计学意义上的。
### 4.1,方差定义

方差是在**概率论**和**统计学**中衡量随机变量或一组数据时离散程度的度量,在统计描述和概率分布中各有不同的定义,并有不同的公式。

概率论中,方差(variance)衡量的是当我们对 $\textrm{x}$ 依据它的概率分布进行采样时,随机变量 $\textrm{x}$ 的函数值会呈现多大的差异,简单理解就是用来**度量随机变量
Download .txt
gitextract_g7w5_kbg/

├── .gitignore
├── 1-computer_basics/
│   ├── Linux系统/
│   │   ├── Linux基础-学会使用命令帮助.md
│   │   ├── Linux基础-常用命令总结.md
│   │   ├── Linux基础-文件及目录管理.md
│   │   ├── Linux基础-文件权限与属性.md
│   │   ├── Linux基础-文本处理命令.md
│   │   ├── Linux基础-新手必备命令.md
│   │   ├── Linux基础-查看cpu、内存和环境等信息.md
│   │   ├── Linux基础-查看和设置环境变量.md
│   │   └── Linux基础-查看进程命令ps和top.md
│   ├── README.md
│   ├── 操作系统/
│   │   ├── 深入理解计算机系统-第1章计算机系统漫游笔记.md
│   │   ├── 深入理解计算机系统-第2章信息的表示和处理笔记.md
│   │   ├── 深入理解计算机系统-第3章程序的机器级表示笔记.md
│   │   └── 计算机基础知识.md
│   └── 效率工具/
│       ├── Docker基础和常用命令.md
│       ├── git工业界实战总结.md
│       ├── ubuntu16.04安装mmdetection库.md
│       └── 程序编译工具基础.md
├── 2-programming_language/
│   ├── README.md
│   ├── cpp/
│   │   ├── C++日期和时间编程总结.md
│   │   └── c++基本编程题汇总.md
│   ├── python3/
│   │   ├── Python数据分析-pandas库入门.md
│   │   ├── python3编程总结.md
│   │   ├── python图像处理-读取图像方式总结.md
│   │   └── python数据分析-堆叠数组函数总结.md
│   └── shell/
│       └── Shell语法基础.md
├── 3-machine_learning/
│   ├── README.md
│   ├── k-means.py
│   ├── 《机器学习》学习笔记.md
│   ├── 机器学习入门总结.md
│   ├── 机器学习基本原理.md
│   ├── 机器学习基本概念总结.md
│   └── 机器学习经典算法总结.md
├── 4-deep_learning/
│   ├── README.md
│   ├── ml-dl-框架笔记/
│   │   ├── Pytorch基础-tensor数据结构.md
│   │   └── Pytorch基础-张量基本操作.md
│   ├── 深度学习基础/
│   │   ├── 反向传播与梯度下降详解.md
│   │   ├── 深度学习基础-优化算法详解.md
│   │   ├── 深度学习基础-参数初始化详解.md
│   │   ├── 深度学习基础-损失函数详解.md
│   │   ├── 神经网络基础部件-BN层详解.md
│   │   ├── 神经网络基础部件-卷积层详解.md
│   │   └── 神经网络基础部件-激活函数详解.md
│   └── 深度学习基础总结.md
├── 5-computer_vision/
│   ├── 2D目标检测/
│   │   ├── 0-目标检测模型的基础.md
│   │   ├── 1-目标检测模型的评价标准-AP与mAP.md
│   │   ├── 2-Faster-RCNN网络详解.md
│   │   ├── 3-FPN网络详解.md
│   │   ├── 4-Mask-RCNN详解.md
│   │   ├── 5-Cascade-RCNN论文解读.md
│   │   ├── 6-RetinaNet网络详解.md
│   │   ├── 7-YOLOv1-v5论文解读.md
│   │   ├── 8-Scaled-YOLOv4论文解读.md
│   │   ├── simple_yolov2/
│   │   │   ├── encoder.py
│   │   │   └── model.py
│   │   └── yolov3_pytorch/
│   │       └── darknet.py
│   ├── 3D视觉算法/
│   │   ├── 3D视觉算法初学概述.md
│   │   └── 单目3D目标检测综述.md
│   ├── README.md
│   ├── 工业视觉/
│   │   └── Halcon快速入门.md
│   ├── 数字图像处理/
│   │   ├── OpenCV3基本函数总结.md
│   │   ├── 《数字图像处理》学习笔记.md
│   │   ├── 数字图像处理基础总结.md
│   │   └── 视频编解码基础.md
│   └── 项目实践/
│       ├── GitHub车牌检测识别项目调研.md
│       └── draw_heart.py
├── 6-model_compression/
│   ├── README.md
│   ├── 卷积网络压缩方法总结.md
│   └── 神经网络量化基础.md
├── 7-high-performance_computing/
│   ├── 0-处理器基础知识.md
│   ├── 1-卷积算法的优化.md
│   ├── 2-模型编译优化.md
│   ├── README.md
│   └── 通用矩阵乘算法从入门到实践.md
├── 8-model_deploy/
│   ├── README.md
│   ├── 推理框架/
│   │   ├── ONNX模型分析与使用.md
│   │   └── TensorRT基础笔记.md
│   ├── 模型压缩部署概述.md
│   ├── 模型板端推理.md
│   ├── 模型转换总结.md
│   └── 神经网络模型复杂度分析.md
├── LICENSE
├── README.md
├── SUMMARY.md
├── book.json
├── cv算法工程师成长路线.md
├── data/
│   ├── code/
│   │   └── pytorch_note.py
│   ├── images/
│   │   ├── README.md
│   │   ├── dl/
│   │   │   └── courgette.log
│   │   └── yolov2/
│   │       └── 损失函数计算.jfif
│   └── intern_info.md
└── interview_summary/
    ├── 1-计算机视觉岗2019届实习面经.md
    ├── 2-计算机视觉岗2019届暑期实习应聘总结.md
    ├── 3-2019届地平线机器人实习总结.md
    ├── 4-计算机视觉岗2020届秋招面经.md
    ├── 5-视觉算法岗2021年社招面经.md
    └── README.md
Download .txt
SYMBOL INDEX (32 symbols across 5 files)

FILE: 3-machine_learning/k-means.py
  function distEclud (line 6) | def distEclud(vecA, vecB):
  function kmeans (line 10) | def kmeans(dataset, k):
  function show_cluster (line 50) | def show_cluster(dataSet, k, centroids, clusterAssement):

FILE: 5-computer_vision/2D目标检测/simple_yolov2/encoder.py
  class DataEncoder (line 9) | class DataEncoder:
    method __init__ (line 10) | def __init__(self):
    method encode (line 13) | def encode(self, boxes, labels, input_size):
    method decode (line 63) | def decode(self, outputs, input_size):

FILE: 5-computer_vision/2D目标检测/simple_yolov2/model.py
  class Darknet (line 10) | class Darknet(nn.Module):
    method __init__ (line 15) | def __init__(self):
    method _make_layers (line 31) | def _make_layers(self, cfg, in_planes):
    method forward (line 45) | def forward(self, x):
  function test (line 55) | def test():

FILE: 5-computer_vision/2D目标检测/yolov3_pytorch/darknet.py
  class BasicBlock (line 9) | class BasicBlock(nn.Module):
    method __init__ (line 10) | def __init__(self, inplanes, planes):
    method forward (line 21) | def forward(self, x):
  class DarkNet (line 36) | class DarkNet(nn.Module):
    method __init__ (line 37) | def __init__(self, layers):
    method _make_layer (line 60) | def _make_layer(self, planes, blocks):
    method forward (line 73) | def forward(self, x):
  function darknet21 (line 86) | def darknet21(pretrained, **kwargs):
  function darknet53 (line 97) | def darknet53(pretrained, **kwargs):

FILE: 5-computer_vision/项目实践/draw_heart.py
  function heart_function (line 13) | def heart_function(t, shrink_ratio: float = IMAGE_ENLARGE):
  function scatter_inside (line 29) | def scatter_inside(x, y, beta=0.2):
  function shrink (line 39) | def shrink(x, y, ratio):
  function curve (line 46) | def curve(p):
  class Heart (line 51) | class Heart:
    method __init__ (line 56) | def __init__(self, generate_frame=30):
    method build (line 69) | def build(self, number):
    method calc_position (line 90) | def calc_position(x, y, ratio):
    method calc (line 99) | def calc(self, generate_frame):
    method render (line 140) | def render(self, render_canvas, render_frame):
  function draw (line 145) | def draw(main: Tk, render_canvas: Canvas, render_heart: Heart, render_fr...
Condensed preview — 98 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,301K chars).
[
  {
    "path": ".gitignore",
    "chars": 415,
    "preview": "# Node rules:\n## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n## Dependen"
  },
  {
    "path": "1-computer_basics/Linux系统/Linux基础-学会使用命令帮助.md",
    "chars": 2037,
    "preview": "- [概述](#概述)\n- [使用 whatis](#使用-whatis)\n- [使用 man](#使用-man)\n- [查看命令程序路径 which](#查看命令程序路径-which)\n- [总结](#总结)\n- [参考资料](#参考资料"
  },
  {
    "path": "1-computer_basics/Linux系统/Linux基础-常用命令总结.md",
    "chars": 7701,
    "preview": "- [1,scp 命令复制远程文件](#1scp-命令复制远程文件)\n- [2,ubuntu 系统使用使用 dpkg 命令安装和卸载 .deb 包](#2ubuntu-系统使用使用-dpkg-命令安装和卸载-deb-包)\n- [3,vim "
  },
  {
    "path": "1-computer_basics/Linux系统/Linux基础-文件及目录管理.md",
    "chars": 3209,
    "preview": "- [一,概述](#一概述)\n- [二,文件及目录常见操作](#二文件及目录常见操作)\n  - [2.1,创建、删除、移动和复制](#21创建删除移动和复制)\n  - [2.2,目录切换](#22目录切换)\n  - [2.3,列出目录内容]"
  },
  {
    "path": "1-computer_basics/Linux系统/Linux基础-文件权限与属性.md",
    "chars": 6223,
    "preview": "- [一,文件类型](#一文件类型)\n  - [1.1,概述](#11概述)\n  - [1.2,正规文件(regular file)](#12正规文件regular-file)\n  - [1.3,目录(directory)](#13目录di"
  },
  {
    "path": "1-computer_basics/Linux系统/Linux基础-文本处理命令.md",
    "chars": 2790,
    "preview": "- [概述](#概述)\n- [find 文件查找](#find-文件查找)\n- [grep 文本搜索](#grep-文本搜索)\n- [参考资料](#参考资料)\n\n## 概述\n\n`Linux` 下使用 Shell 处理文本时最常用的工具有: "
  },
  {
    "path": "1-computer_basics/Linux系统/Linux基础-新手必备命令.md",
    "chars": 4060,
    "preview": "- [概述](#概述)\n- [系统工作](#系统工作)\n- [系统状态检测](#系统状态检测)\n- [文件与目录管理](#文件与目录管理)\n- [文件内容查阅与编辑](#文件内容查阅与编辑)\n- [打包压缩与搜索](#打包压缩与搜索)\n- "
  },
  {
    "path": "1-computer_basics/Linux系统/Linux基础-查看cpu、内存和环境等信息.md",
    "chars": 2042,
    "preview": "使用 `Linux` 系统的过程中,我们经常需要查看**系统、资源、网络、进程、用户**等方面的信息,查看这些信息的常用命令值得了解和熟悉。\n\n1,**系统**信息查看常用命令如下:\n\n```bash\nuname -m && cat /et"
  },
  {
    "path": "1-computer_basics/Linux系统/Linux基础-查看和设置环境变量.md",
    "chars": 2204,
    "preview": "- [一,查看环境变量](#一查看环境变量)\n- [二,环境变量类型](#二环境变量类型)\n- [三,设置环境变量](#三设置环境变量)\n- [四,参考资料](#四参考资料)\n\n## 一,查看环境变量\n\n在 Linux中,**环境变量**是"
  },
  {
    "path": "1-computer_basics/Linux系统/Linux基础-查看进程命令ps和top.md",
    "chars": 2365,
    "preview": "- [1,使用 ps 命令找出 CPU 占用高的进程](#1使用-ps-命令找出-cpu-占用高的进程)\n- [2,通过 top 命令定位占用 cpu 高的进程](#2通过-top-命令定位占用-cpu-高的进程)\n- [3,htop 系统"
  },
  {
    "path": "1-computer_basics/README.md",
    "chars": 1087,
    "preview": "## 前言\n\n本目录内容旨在分享常用编程开发效率工具、操作系统、`Linux` 系统等方向知识总结和笔记。\n\n## 效率工具\n\n- [git命令学习笔记](./效率工具/git常用命令总结.md)\n- [ubuntu16.04安装mmdet"
  },
  {
    "path": "1-computer_basics/操作系统/深入理解计算机系统-第1章计算机系统漫游笔记.md",
    "chars": 4844,
    "preview": "- [1,信息就是位+上下文](#1信息就是位上下文)\n- [2,程序被其他程序翻译成不同格式](#2程序被其他程序翻译成不同格式)\n- [3,了解编译器如何工作是有大有益处的](#3了解编译器如何工作是有大有益处的)\n- [4,处理器读取"
  },
  {
    "path": "1-computer_basics/操作系统/深入理解计算机系统-第2章信息的表示和处理笔记.md",
    "chars": 4194,
    "preview": "- [1,信息存储](#1信息存储)\n  - [1.1,十六进制表示法](#11十六进制表示法)\n  - [1.2,字数据大小](#12字数据大小)\n  - [1.3,寻址和字节顺序](#13寻址和字节顺序)\n- [2,整数表示](#2整数"
  },
  {
    "path": "1-computer_basics/操作系统/深入理解计算机系统-第3章程序的机器级表示笔记.md",
    "chars": 8318,
    "preview": "\n- [前言](#前言)\n- [1,历史观点](#1历史观点)\n- [2,程序编码](#2程序编码)\n- [3,数据格式](#3数据格式)\n- [4,访问信息指令](#4访问信息指令)\n  - [4.1,操作数指示符](#41操作数指示符)"
  },
  {
    "path": "1-computer_basics/操作系统/计算机基础知识.md",
    "chars": 5424,
    "preview": "\n## 知识点目录\n\n+ 操作系统\n+ 计算机网络\n+ 面向对象\n+ 数据库\n+ Linux系统开发\n+ 常用工具(Cmake/Git/Docker/正则表达式)\n\n## 操作系统\n\n### 操作系统中堆和栈的区别\n\n+ 操作系统中堆和栈都"
  },
  {
    "path": "1-computer_basics/效率工具/Docker基础和常用命令.md",
    "chars": 10048,
    "preview": "- [一,Docker 简介](#一docker-简介)\n  - [1.1,什么是 Docker](#11什么是-docker)\n  - [1.2,Docker 与虚拟机的区别](#12docker-与虚拟机的区别)\n  - [1.3,Do"
  },
  {
    "path": "1-computer_basics/效率工具/git工业界实战总结.md",
    "chars": 5699,
    "preview": "---\nlayout: post\ntitle: git 工业界实战总结\ndate: 2021-09-01 20:00:00\nsummary: 本地仓库由 git 维护的三棵“树\"组成。第一个是 工作目录,它持有实际文件;第二个是暂存区(In"
  },
  {
    "path": "1-computer_basics/效率工具/ubuntu16.04安装mmdetection库.md",
    "chars": 7243,
    "preview": "- [一,更新 pip 和 conda 下载源](#一更新-pip-和-conda-下载源)\n  - [1.1,查看 conda 和 pip 版本](#11查看-conda-和-pip-版本)\n  - [1.2,更新 pip 下载源](#1"
  },
  {
    "path": "1-computer_basics/效率工具/程序编译工具基础.md",
    "chars": 6750,
    "preview": "- [Linux 系统下 gcc 编译生成的文件类型](#linux-系统下-gcc-编译生成的文件类型)\n- [cmake/makefile/make 理解](#cmakemakefilemake-理解)\n- [CMake 编译过程](#"
  },
  {
    "path": "2-programming_language/README.md",
    "chars": 552,
    "preview": "## 前言\n\n本目录内容旨在分享cv算法工程师经常需要使用到的 `c/c++`、`python` 和 `shell` 编程语言的知识总结和学习笔记。\n\n## cpp\n\n* [c++基础-资源管理:堆栈与RAII](cpp/c++基础-资源管"
  },
  {
    "path": "2-programming_language/cpp/C++日期和时间编程总结.md",
    "chars": 13769,
    "preview": "- [一,概述](#一概述)\n- [二,C-style 日期和时间库](#二c-style-日期和时间库)\n  - [2.1,数据类型](#21数据类型)\n  - [2.2,函数](#22函数)\n  - [2.3,数据类型与函数关系梳理]("
  },
  {
    "path": "2-programming_language/cpp/c++基本编程题汇总.md",
    "chars": 5683,
    "preview": "- [STL 库描述](#stl-库描述)\n- [内存中堆和栈的区别](#内存中堆和栈的区别)\n- [堆栈溢出一般是由什么原因导致的?](#堆栈溢出一般是由什么原因导致的)\n- [sizeof 的作用](#sizeof-的作用)\n- [C+"
  },
  {
    "path": "2-programming_language/python3/Python数据分析-pandas库入门.md",
    "chars": 8744,
    "preview": "\n## pandas 库概述\npandas 提供了快速便捷处理结构化数据的大量数据结构和函数。自从2010年出现以来,它助使 Python 成为强大而高效的数据分析环境。pandas使用最多的数据结构对象是 DataFrame,它是一个面向"
  },
  {
    "path": "2-programming_language/python3/python3编程总结.md",
    "chars": 8432,
    "preview": "- [Python global 语句的作用](#python-global-语句的作用)\n- [lambda 匿名函数好处](#lambda-匿名函数好处)\n- [Python 错误处理](#python-错误处理)\n- [Python "
  },
  {
    "path": "2-programming_language/python3/python图像处理-读取图像方式总结.md",
    "chars": 5682,
    "preview": "- [读取并显示图像](#读取并显示图像)\n  - [opencv3库](#opencv3库)\n  - [scikit-image库](#scikit-image库)\n  - [PIL库](#pil库)\n  - [读取图像结果分析](#读取"
  },
  {
    "path": "2-programming_language/python3/python数据分析-堆叠数组函数总结.md",
    "chars": 3876,
    "preview": "- [numpy 堆叠数组](#numpy-堆叠数组)\n- [ravel() 函数](#ravel-函数)\n- [stack() 函数](#stack-函数)\n- [vstack()函数](#vstack函数)\n- [hstack()函数]"
  },
  {
    "path": "2-programming_language/shell/Shell语法基础.md",
    "chars": 11309,
    "preview": "- [Shell 变量](#shell-变量)\n\t- [使用变量](#使用变量)\n\t- [只读变量](#只读变量)\n\t- [删除变量](#删除变量)\n\t- [变量类型](#变量类型)\n- [Shell 字符串](#shell-字符串)\n\t-"
  },
  {
    "path": "3-machine_learning/README.md",
    "chars": 215,
    "preview": "## 前言\n\n本目录内容旨在分享机器学习数学基础笔记、机器学习算法总结和机器学习经典面试题总结知识。\n\n## 目录\n\n1. [机器学习基本概念总结](./机器学习基本概念总结.md)\n2. [机器学习基础总结](./机器学习基础总结.md)"
  },
  {
    "path": "3-machine_learning/k-means.py",
    "chars": 2403,
    "preview": "import numpy as np\nfrom numpy import inf\nfrom matplotlib import pyplot as plt\nimport random\n\ndef distEclud(vecA, vecB):\n"
  },
  {
    "path": "3-machine_learning/《机器学习》学习笔记.md",
    "chars": 22917,
    "preview": "- [第 2 章 模型评估与选择](#第-2-章-模型评估与选择)\n  - [2.1 经验误差与过拟合](#21-经验误差与过拟合)\n  - [2.2 评估方法](#22-评估方法)\n    - [2.2.1 留出法](#221-留出法)\n"
  },
  {
    "path": "3-machine_learning/机器学习入门总结.md",
    "chars": 7684,
    "preview": "- [一,机器学习概述](#一机器学习概述)\n  - [1.1,机器学习分类](#11机器学习分类)\n  - [1.2,机器学习任务](#12机器学习任务)\n- [二,线性模型](#二线性模型)\n  - [2.1,基本形式](#21基本形式"
  },
  {
    "path": "3-machine_learning/机器学习基本原理.md",
    "chars": 11873,
    "preview": "- [前言](#前言)\n- [5.1 学习算法](#51-学习算法)\n  - [5.1.1 任务 $T$](#511-任务-t)\n  - [5.1.2 性能度量 $P$](#512-性能度量-p)\n  - [5.1.3 经验 $E$](#5"
  },
  {
    "path": "3-machine_learning/机器学习基本概念总结.md",
    "chars": 12240,
    "preview": "- [一,余弦相似度与欧氏距离](#一余弦相似度与欧氏距离)\n  - [1.1,余弦相似度](#11余弦相似度)\n  - [1.2,欧式距离](#12欧式距离)\n  - [1.3,余弦相似度和欧氏距离的区别](#13余弦相似度和欧氏距离的区"
  },
  {
    "path": "3-machine_learning/机器学习经典算法总结.md",
    "chars": 3054,
    "preview": "- [一,KNN 算法](#一knn-算法)\n  - [1.1,k 值的选取](#11k-值的选取)\n  - [1.2,KNN 算法思路](#12knn-算法思路)\n- [二,支持向量机算法](#二支持向量机算法)\n  - [2.1,支持向"
  },
  {
    "path": "4-deep_learning/README.md",
    "chars": 699,
    "preview": "## 前言\n\n本目录内容旨在分享深度学习基础、`Backbone` 网络论文以及深度学习框架使用及解析的技术笔记。\n\n## 深度学习基础\n\n1. [深度学习基础](https://github.com/HarleysZhang/deep_l"
  },
  {
    "path": "4-deep_learning/ml-dl-框架笔记/Pytorch基础-tensor数据结构.md",
    "chars": 5687,
    "preview": "- [torch.Tensor](#torchtensor)\n- [Tensor 数据类型](#tensor-数据类型)\n- [Tensor 的属性](#tensor-的属性)\n  - [view 和 reshape 的区别](#view-"
  },
  {
    "path": "4-deep_learning/ml-dl-框架笔记/Pytorch基础-张量基本操作.md",
    "chars": 11726,
    "preview": "- [一,张量的基本操作](#一张量的基本操作)\n- [二,维度变换](#二维度变换)\n  - [2.1,squeeze vs unsqueeze 维度增减](#21squeeze-vs-unsqueeze-维度增减)\n  - [2.2,t"
  },
  {
    "path": "4-deep_learning/深度学习基础/反向传播与梯度下降详解.md",
    "chars": 8056,
    "preview": "- [一,前向传播与反向传播](#一前向传播与反向传播)\n  - [1.1,神经网络训练过程](#11神经网络训练过程)\n  - [1.2,前向传播](#12前向传播)\n  - [1.3,反向传播](#13反向传播)\n  - [1.4,总结"
  },
  {
    "path": "4-deep_learning/深度学习基础/深度学习基础-优化算法详解.md",
    "chars": 10380,
    "preview": "## 前言\n\n所谓深度神经网络的优化算法,**即用来更新神经网络参数,并使损失函数最小化的算法**。优化算法对于深度学习非常重要,如果说网络参数初始化(模型迭代的初始点)能够决定模型是否收敛,那优化算法的性能则**直接**影响模型的训练效率"
  },
  {
    "path": "4-deep_learning/深度学习基础/深度学习基础-参数初始化详解.md",
    "chars": 5664,
    "preview": "- [一,参数初始化概述](#一参数初始化概述)\n  - [1.1,进行网络参数初始化的原因](#11进行网络参数初始化的原因)\n  - [1.2,网络参数初始化为什么重要](#12网络参数初始化为什么重要)\n- [二,权重初始化方式分类]"
  },
  {
    "path": "4-deep_learning/深度学习基础/深度学习基础-损失函数详解.md",
    "chars": 13354,
    "preview": "- [一,损失函数概述](#一损失函数概述)\n- [二,交叉熵函数-分类损失](#二交叉熵函数-分类损失)\n  - [2.1,交叉熵(Cross-Entropy)的由来](#21交叉熵cross-entropy的由来)\n    - [2.1"
  },
  {
    "path": "4-deep_learning/深度学习基础/神经网络基础部件-BN层详解.md",
    "chars": 9784,
    "preview": "- [一,数学基础](#一数学基础)\n  - [1.1,概率密度函数](#11概率密度函数)\n  - [1.2,正态分布](#12正态分布)\n- [二,背景](#二背景)\n  - [2.1,如何理解 Internal Covariate S"
  },
  {
    "path": "4-deep_learning/深度学习基础/神经网络基础部件-卷积层详解.md",
    "chars": 14592,
    "preview": "- [前言](#前言)\n- [一,卷积](#一卷积)\n  - [1.1,卷积运算定义](#11卷积运算定义)\n  - [1.2,卷积的意义](#12卷积的意义)\n  - [1.3,从实例理解卷积](#13从实例理解卷积)\n  - [1.4,"
  },
  {
    "path": "4-deep_learning/深度学习基础/神经网络基础部件-激活函数详解.md",
    "chars": 11443,
    "preview": "- [一,激活函数概述](#一激活函数概述)\n  - [1.1,前言](#11前言)\n  - [1.2,激活函数定义](#12激活函数定义)\n  - [1.3,激活函数性质](#13激活函数性质)\n- [二,Sigmoid 型函数(挤压型激"
  },
  {
    "path": "4-deep_learning/深度学习基础总结.md",
    "chars": 36924,
    "preview": "---\nlayout: post\ntitle: 深度学习基础总结\ndate: 2021-10-10 12:00:00\nsummary: 深度学习基础知识总结。\ncategories: DeepLearning\n---\n\n\n- [一,滤波器与"
  },
  {
    "path": "5-computer_vision/2D目标检测/0-目标检测模型的基础.md",
    "chars": 23733,
    "preview": "- [前言](#前言)\n- [一,anchor box](#一anchor-box)\n- [二,IOU](#二iou)\n- [三,Focal Loss](#三focal-loss)\n  - [3.1,Cross Entropy](#31cr"
  },
  {
    "path": "5-computer_vision/2D目标检测/1-目标检测模型的评价标准-AP与mAP.md",
    "chars": 16995,
    "preview": "- [前言](#前言)\n- [一,精确率、召回率与F1](#一精确率召回率与f1)\n  - [1.1,准确率](#11准确率)\n  - [1.2,精确率、召回率](#12精确率召回率)\n  - [1.3,F1 分数](#13f1-分数)\n "
  },
  {
    "path": "5-computer_vision/2D目标检测/2-Faster-RCNN网络详解.md",
    "chars": 10862,
    "preview": "- [Faster RCNN 网络概述](#faster-rcnn-网络概述)\n- [Conv layers](#conv-layers)\n- [RPN 网络](#rpn-网络)\n\t- [Anchors](#anchors)\n\t- [生成 "
  },
  {
    "path": "5-computer_vision/2D目标检测/3-FPN网络详解.md",
    "chars": 8998,
    "preview": "- [论文背景](#论文背景)\n- [引言(Introduction)](#引言introduction)\n- [特征金字塔网络 FPN](#特征金字塔网络-fpn)\n  - [FPN网络建立](#fpn网络建立)\n  - [Anchor锚"
  },
  {
    "path": "5-computer_vision/2D目标检测/4-Mask-RCNN详解.md",
    "chars": 5159,
    "preview": "- [ROI Pooling 和 ROI Align 的区别](#roi-pooling-和-roi-align-的区别)\n- [Mask R-CNN 网络结构](#mask-r-cnn-网络结构)\n- [骨干网络 FPN](#骨干网络-f"
  },
  {
    "path": "5-computer_vision/2D目标检测/5-Cascade-RCNN论文解读.md",
    "chars": 4137,
    "preview": "- [摘要](#摘要)\n- [1,介绍](#1介绍)\n  - [1.1,Faster RCNN 回顾](#11faster-rcnn-回顾)\n  - [1.2,mismatch 问题](#12mismatch-问题)\n- [2,实验分析]("
  },
  {
    "path": "5-computer_vision/2D目标检测/6-RetinaNet网络详解.md",
    "chars": 32081,
    "preview": "- [摘要](#摘要)\n- [1,引言](#1引言)\n- [2,相关工作](#2相关工作)\n- [3,网络架构](#3网络架构)\n- [3.1,Backbone](#31backbone)\n  - [3.2,Neck](#32neck)\n "
  },
  {
    "path": "5-computer_vision/2D目标检测/7-YOLOv1-v5论文解读.md",
    "chars": 62192,
    "preview": "- [一,YOLOv1](#一yolov1)\n  - [Abstract](#abstract)\n  - [1. Introduction](#1-introduction)\n  - [2. Unified Detectron](#2-un"
  },
  {
    "path": "5-computer_vision/2D目标检测/8-Scaled-YOLOv4论文解读.md",
    "chars": 8113,
    "preview": "- [一,Scaled YOLOv4](#一scaled-yolov4)\n- [摘要](#摘要)\n- [1,介绍](#1介绍)\n- [2,相关工作](#2相关工作)\n  - [2.1,模型缩放](#21模型缩放)\n- [3,模型缩放原则]("
  },
  {
    "path": "5-computer_vision/2D目标检测/simple_yolov2/encoder.py",
    "chars": 4363,
    "preview": "'''Encode target locations and class labels.\nReference: https://github.com/kuangliu/pytorch-yolov2/blob/master/encoder.p"
  },
  {
    "path": "5-computer_vision/2D目标检测/simple_yolov2/model.py",
    "chars": 2357,
    "preview": "'''Darknet in PyTorch.'''\nimport torch\nimport torch.nn as nn\nimport torch.nn.init as init\nimport torch.nn.functional as "
  },
  {
    "path": "5-computer_vision/2D目标检测/yolov3_pytorch/darknet.py",
    "chars": 3540,
    "preview": "import torch\nimport torch.nn as nn\nimport math\nfrom collections import OrderedDict\n\n__all__ = ['darknet21', 'darknet53']"
  },
  {
    "path": "5-computer_vision/3D视觉算法/3D视觉算法初学概述.md",
    "chars": 7281,
    "preview": "- [背景知识](#背景知识)\n  - [RGB-D相机](#rgb-d相机)\n- [一,基于3DMM的三维人脸重建技术概述](#一基于3dmm的三维人脸重建技术概述)\n  - [1.1,3D 人脸重建概述](#113d-人脸重建概述)\n "
  },
  {
    "path": "5-computer_vision/3D视觉算法/单目3D目标检测综述.md",
    "chars": 10336,
    "preview": "- [一,理论基础-相机与图像](#一理论基础-相机与图像)\n  - [1.1,单目相机介绍](#11单目相机介绍)\n  - [1.2,针孔相机模型](#12针孔相机模型)\n  - [1.3,坐标系间的欧式变换](#13坐标系间的欧式变换)"
  },
  {
    "path": "5-computer_vision/README.md",
    "chars": 1312,
    "preview": "- [一,传统数字图像处理](#一传统数字图像处理)\n- [二,计算机视觉](#二计算机视觉)\n  - [2.1,目标检测基础](#21目标检测基础)\n  - [2.2,二阶段目标检测算法](#22二阶段目标检测算法)\n  - [2.3,一"
  },
  {
    "path": "5-computer_vision/工业视觉/Halcon快速入门.md",
    "chars": 9066,
    "preview": "- [前言](#前言)\n- [一,HALCON 概述](#一halcon-概述)\n- [1.1,HALCON 安装](#11halcon-安装)\n- [二,HALCON 架构](#二halcon-架构)\n  - [2.1,算子](#21算子"
  },
  {
    "path": "5-computer_vision/数字图像处理/OpenCV3基本函数总结.md",
    "chars": 2637,
    "preview": "> 此笔记针对 Python 版本的 opencv3,c++ 版本的函数和 python 版本的函数参数几乎一样,只是矩阵格式从 ndarray 类型变成适合 c++ 的 mat 模板类型。注意,因为 python 版本的opncv只提供接"
  },
  {
    "path": "5-computer_vision/数字图像处理/《数字图像处理》学习笔记.md",
    "chars": 22299,
    "preview": "- [一,绪论](#一绪论)\n  - [1.1, 什么是数字图像处理](#11-什么是数字图像处理)\n  - [1.2,数字图像处理的起源](#12数字图像处理的起源)\n  - [1.3,数字图像处理技术应用实例](#13数字图像处理技术应"
  },
  {
    "path": "5-computer_vision/数字图像处理/数字图像处理基础总结.md",
    "chars": 3595,
    "preview": "# 数字图像处理基础\n\n## 相机成像的原理\n\n相机成像的原理:针孔相机( Pinhole Camera )通过投影变换,可以将三维相机(`Camera`)坐标转换为二维的图像坐标,这个变换矩阵是相机的内在属性,称为`相机内参(Camera"
  },
  {
    "path": "5-computer_vision/数字图像处理/视频编解码基础.md",
    "chars": 6389,
    "preview": "- [一,基本术语](#一基本术语)\n  - [1.1,颜色亮度和我们的眼睛](#11颜色亮度和我们的眼睛)\n- [二,视频编码的实现原理](#二视频编码的实现原理)\n  - [2.1,视频编码技术概述](#21视频编码技术概述)\n  - "
  },
  {
    "path": "5-computer_vision/项目实践/GitHub车牌检测识别项目调研.md",
    "chars": 7335,
    "preview": "- [一,EasyOCR](#一easyocr)\n  - [1.1,仓库介绍](#11仓库介绍)\n  - [1.2,使用记录](#12使用记录)\n- [二,HyperLPR](#二hyperlpr)\n  - [2.1,HyperLPR 概述"
  },
  {
    "path": "5-computer_vision/项目实践/draw_heart.py",
    "chars": 4794,
    "preview": "import random\nfrom math import sin, cos, pi, log\nfrom tkinter import *\n\nCANVAS_WIDTH = 640  # 画布的宽\nCANVAS_HEIGHT = 640  "
  },
  {
    "path": "6-model_compression/README.md",
    "chars": 68,
    "preview": "## 前言\n\n已经迁移到到 [dl_note](https://github.com/HarleysZhang/dl_note) 仓库。"
  },
  {
    "path": "6-model_compression/卷积网络压缩方法总结.md",
    "chars": 11803,
    "preview": "## 卷积网络的压缩方法\n\n+ [一,低秩近似](#一,低秩近似)\n+ [二,剪枝与稀疏约束](#二,剪枝与稀疏约束)\n+ [三,参数量化](#三,参数量化)\n+ [四,二值化网络](#四,二值化网络)\n+ [五,知识蒸馏](#五,知识蒸馏"
  },
  {
    "path": "6-model_compression/神经网络量化基础.md",
    "chars": 13275,
    "preview": "- [1,模型量化概述](#1模型量化概述)\r\n  - [1.1,模型量化优点](#11模型量化优点)\r\n  - [1.2,模型量化的方案](#12模型量化的方案)\r\n    - [1.2.1,PTQ 理解](#121ptq-理解)\r\n  "
  },
  {
    "path": "7-high-performance_computing/0-处理器基础知识.md",
    "chars": 4063,
    "preview": "- [一,什么是处理器](#一什么是处理器)\n- [二,指令集基础](#二指令集基础)\n  - [什么是 ISA](#什么是-isa)\n  - [ISA 功能](#isa-功能)\n- [三,CPU 设计与实现](#三cpu-设计与实现)\n "
  },
  {
    "path": "7-high-performance_computing/1-卷积算法的优化.md",
    "chars": 228,
    "preview": "## 前言\n\n等待更新。\n\n## 参考资料\n1. [im2col方法实现卷积算法](https://zhuanlan.zhihu.com/p/63974249)\n2. [通用矩阵乘(GEMM)优化算法](https://jackwish.n"
  },
  {
    "path": "7-high-performance_computing/2-模型编译优化.md",
    "chars": 12,
    "preview": "## 前言\n\n等待更新。"
  },
  {
    "path": "7-high-performance_computing/README.md",
    "chars": 1345,
    "preview": "## 一,GPU的Hello world: 矩阵相乘\n英伟达GPU架构发展史如下所示:\n\n![英伟达gpu架构发展史](../data/images/gpu_programming/英伟达gpu架构发展史.png)\n\n矩阵相乘程序是GPU编"
  },
  {
    "path": "7-high-performance_computing/通用矩阵乘算法从入门到实践.md",
    "chars": 21179,
    "preview": "- [一,背景知识](#一背景知识)\n  - [1.1,Linux 查看 CPU 和 Cache 信息](#11linux-查看-cpu-和-cache-信息)\n  - [1.2,Windows查看cpu和cache信息](#12windo"
  },
  {
    "path": "8-model_deploy/README.md",
    "chars": 383,
    "preview": "## 前言\n\n本目录旨在分享模型部署的知识,包括模型转换、模型板端推理框架、算法SDK开发等知识。\n\n## 模型推理框架\n\n1. [ONNX模型分析与使用](./推理框架/ONNX模型分析与使用.md)\n2. [TensorRT基础](./"
  },
  {
    "path": "8-model_deploy/推理框架/ONNX模型分析与使用.md",
    "chars": 11720,
    "preview": "> 本文大部分内容为对 ONNX 官方资料的总结和翻译,部分知识点参考网上质量高的博客。\n\n## 一,ONNX 概述\n\n深度学习算法大多通过计算数据流图来完成神经网络的深度学习过程。 一些框架(例如CNTK,Caffe2,Theano和Te"
  },
  {
    "path": "8-model_deploy/推理框架/TensorRT基础笔记.md",
    "chars": 2005,
    "preview": "## 一,概述\n\n`TensorRT` 是 NVIDIA 官方推出的基于 `CUDA` 和 `cudnn` 的高性能深度学习推理加速引擎,能够使深度学习模型在 `GPU` 上进行低延迟、高吞吐量的部署。采用 `C++` 开发,并提供了 `C"
  },
  {
    "path": "8-model_deploy/模型压缩部署概述.md",
    "chars": 3851,
    "preview": "# 模型压缩部署概述\n- [模型压缩部署概述](#模型压缩部署概述)\n\t- [一,模型在线部署](#一模型在线部署)\n\t\t- [1.1,深度学习项目开发流程](#11深度学习项目开发流程)\n\t\t- [1.2,模型训练和推理的不同](#12模"
  },
  {
    "path": "8-model_deploy/模型板端推理.md",
    "chars": 214,
    "preview": "## 前言\n\n> 芯片的算力不一定和模型推理速度成正比,嵌入式 `AI` 的另一个核心是 `inference` 框架。对于 `CPU` 架构来说,是否使用 `SIMD`( `ARM从v7` 开始就支持 `NEON` 指令了)、是否使用多核"
  },
  {
    "path": "8-model_deploy/模型转换总结.md",
    "chars": 12,
    "preview": "## 前言\n\n等待更新。"
  },
  {
    "path": "8-model_deploy/神经网络模型复杂度分析.md",
    "chars": 22890,
    "preview": "- [前言](#前言)\n- [一,模型计算量分析](#一模型计算量分析)\n  - [卷积层 FLOPs 计算](#卷积层-flops-计算)\n  - [全连接层的 FLOPs 计算](#全连接层的-flops-计算)\n- [二,模型参数量分"
  },
  {
    "path": "LICENSE",
    "chars": 11336,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 5862,
    "preview": "<h1 align=\"center\">\nCV 算法工程师成长之路\n</h1>\n\n<p align=\"center\">\n  <a href=\"#License\"><img src=\"./data/icons/License-Apache-2."
  },
  {
    "path": "SUMMARY.md",
    "chars": 7101,
    "preview": "# cv算法工程师成长之路\n\n* [cv算法工程师成长路线](cv算法工程师成长路线.md)\n- [1. 计算机基础](1-computer_basics/README.md)\n  - 效率工具\n    * [Docker基础和常用命令]("
  },
  {
    "path": "book.json",
    "chars": 1047,
    "preview": "{\n    \"title\": \"cv算法工程师成长之路\",\n    \"author\": \"zhanghonggao\",\n    \"theme-default\": {\n        \"showLevel\": true\n    },\n    "
  },
  {
    "path": "cv算法工程师成长路线.md",
    "chars": 22335,
    "preview": "- [前言](#前言)\n- [一,计算机系统](#一计算机系统)\n  - [1.1,计算机系统书籍](#11计算机系统书籍)\n  - [1.2,设计模式教程](#12设计模式教程)\n- [二,编程语言](#二编程语言)\n  - [2.1,C"
  },
  {
    "path": "data/code/pytorch_note.py",
    "chars": 539,
    "preview": "####################卷积神经网络计算量/参数量分析工具#####################\nimport torchvision, torch\n\nmodel = torchvision.models.resnet5"
  },
  {
    "path": "data/images/README.md",
    "chars": 35,
    "preview": "## 目录说明\n此目录存放深度学习面试题.md文件所用到的图片文件。\n"
  },
  {
    "path": "data/images/dl/courgette.log",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "data/intern_info.md",
    "chars": 3252,
    "preview": "## 春招算法实习岗位表\n\n|公司|招聘岗位|工作地点|截止时间|投递方式|内推方式|是否投递|信息来源|\n|---|-------|-------|------|-------|-------|------|-------|\n|美图影像实"
  },
  {
    "path": "interview_summary/1-计算机视觉岗2019届实习面经.md",
    "chars": 5027,
    "preview": "## 阿里算法工程师(计算机视觉方向)\n### 一面(1个小时10分钟)--->简历面\n1. 自我介绍,差不多 `10` 分钟。\n2. 简历项目和比赛介绍,中间有问一些项目和比赛细节,问了一些延伸和开放性问题:\n    + `Adam` 和"
  },
  {
    "path": "interview_summary/2-计算机视觉岗2019届暑期实习应聘总结.md",
    "chars": 4524,
    "preview": "- [找实习感想](#找实习感想)\n- [找实习建议](#找实习建议)\n- [面试过程建议](#面试过程建议)\n- [计算机视觉岗找实习心得](#计算机视觉岗找实习心得)\n- [计算机视觉面试问题分类总结](#计算机视觉面试问题分类总结)\n"
  },
  {
    "path": "interview_summary/3-2019届地平线机器人实习总结.md",
    "chars": 1575,
    "preview": "## 关于工作内容\n\n来地平线实习差不多 `3` 个月了,在这边完成的工作内容,主要有以下几个方面:\n+ 抽烟检测模型的输出,包括 `arm`、`gpu`、`j2定点化`、`mimic`模型的输出及相关训练集及模型测试分析报告的撰写;\n+ "
  },
  {
    "path": "interview_summary/4-计算机视觉岗2020届秋招面经.md",
    "chars": 3996,
    "preview": "## 字节跳动AI Lab-计算机视觉算法工程师\n\n### 一面\n>(挂)\n\n1. 钢筋数量检测项目深挖。\n2. `Roi Pooling` 和 `Roi Algin`区别?\n3. `F1 Score` 如何计算?\n4. `Siamese`"
  },
  {
    "path": "interview_summary/5-视觉算法岗2021年社招面经.md",
    "chars": 5942,
    "preview": "- [社招面经](#社招面经)\n- [一,项目](#一项目)\n- [二,深度学习、模型部署](#二深度学习模型部署)\n  - [2.1,目标检测相关](#21目标检测相关)\n  - [2.2,深度学习相关](#22深度学习相关)\n  - ["
  },
  {
    "path": "interview_summary/README.md",
    "chars": 253,
    "preview": "## 一些面经总结\n\n1. [计算机视觉岗 2019 届实习面经.md](./1-计算机视觉岗2019届实习面经.md)\n2. [计算机视觉岗 2019 届暑期实习应聘总结](./2-计算机视觉岗2019届暑期实习应聘总结.md)\n3. ["
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the HarleysZhang/2020_algorithm_intern_information GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 98 files (739.1 KB), approximately 389.2k tokens, and a symbol index with 32 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!