├── Matplotlib 学习笔记.md ├── NumPy 高维数组降维方法详细分析.md ├── Python 科学计算.md ├── Python 进阶笔记.md ├── README.md ├── SVM 原理详解.md ├── TensorFlow 安装总结.md ├── sources └── TOC.png └── 数据预处理之One-Hot(独热编码)编码.md /NumPy 高维数组降维方法详细分析.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NumPy 降维方法深度解析 3 | date: 2019-03-13 12:23:56 4 | tags: 5 | - python 6 | - NumPy 7 | toc: true 8 | --- 9 | 10 | 11 | 12 | ## numpy的flat、flatten、ravel、reshape 13 | 14 | 四个函数都是对多维数组进行降维(降至一维) 15 | 16 | 17 | 18 | 使用方法: 19 | 20 | ```python 21 | import numpy as np 22 | 23 | a = np.arange(64).reshape([4,4,4]) 24 | print(a) 25 | 26 | #对三维数组a进行降维打击 27 | b = a.reshape(-1) 28 | print('reshape方法:\n',b) 29 | c = [] 30 | for x in a.flat: 31 | c.append(x) 32 | print('flat迭代器:\n',c) 33 | d = a.flatten() 34 | print('flatten方法:\n',d) 35 | e = a.ravel() 36 | print('ravel方法:\n',e) 37 | a.resize(64) 38 | print('resize方法:\n',a) 39 | 40 | # 41 | [[[ 0 1 2 3] 42 | [ 4 5 6 7] 43 | [ 8 9 10 11] 44 | [12 13 14 15]] 45 | 46 | [[16 17 18 19] 47 | [20 21 22 23] 48 | [24 25 26 27] 49 | [28 29 30 31]] 50 | 51 | [[32 33 34 35] 52 | [36 37 38 39] 53 | [40 41 42 43] 54 | [44 45 46 47]] 55 | 56 | [[48 49 50 51] 57 | [52 53 54 55] 58 | [56 57 58 59] 59 | [60 61 62 63]]] 60 | reshape方法: 61 | [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 62 | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 63 | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63] 64 | flat迭代器: 65 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63] 66 | flatten方法: 67 | [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 68 | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 69 | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63] 70 | ravel方法: 71 | [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 72 | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 73 | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63] 74 | resize方法: 75 | [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 76 | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 77 | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63] 78 | [Finished in 0.3s] 79 | 80 | ``` 81 | 82 | 83 | 84 | 可见这几个降维操作区别还是蛮大的,其中最明显的是flat方法其返回的是一个iterator不能直接输出,需要使用for 来遍历整个iterator。 85 | 86 | 其中resize 和reshape类似,不同的是resize没有返回值。不能把结果直接赋值给另一个变量。 87 | 88 | shape是ndarray中的一个字段,可以直接赋值改变shape形状`a.shape = (3,4)` 89 | 90 | flatten 返回的是一个coper,其ndarray本身不变,修改返回值不影响原本的ndarray。 91 | 92 | reshape,类似ravel 返回的是一个原数组的一个视图,实质还是引用,虽然ndarray本身的维度不变,但是对降维后的数组进行操作,原数组仍然发生改变。 93 | 94 | `ravel()`相当于`reshape(-1)`相当于`reshape(ndarray.size())` 95 | 96 | ```python 97 | import numpy as np 98 | 99 | a = np.arange(64).reshape([4,4,4]) 100 | print(a) 101 | 102 | #对三维数组a进行降维打击 103 | b = a.reshape(-1) 104 | b[0] = 111 105 | print('reshape方法:\n',b,'\n原数组\n',a,sep = '') 106 | # 107 | [[[ 0 1 2 3] 108 | [ 4 5 6 7] 109 | [ 8 9 10 11] 110 | [12 13 14 15]] 111 | 112 | [[16 17 18 19] 113 | [20 21 22 23] 114 | [24 25 26 27] 115 | [28 29 30 31]] 116 | 117 | [[32 33 34 35] 118 | [36 37 38 39] 119 | [40 41 42 43] 120 | [44 45 46 47]] 121 | 122 | [[48 49 50 51] 123 | [52 53 54 55] 124 | [56 57 58 59] 125 | [60 61 62 63]]] 126 | reshape方法: 127 | [111 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 128 | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 129 | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 130 | 54 55 56 57 58 59 60 61 62 63] 131 | 原数组 132 | [[[111 1 2 3] 133 | [ 4 5 6 7] 134 | [ 8 9 10 11] 135 | [ 12 13 14 15]] 136 | 137 | [[ 16 17 18 19] 138 | [ 20 21 22 23] 139 | [ 24 25 26 27] 140 | [ 28 29 30 31]] 141 | 142 | [[ 32 33 34 35] 143 | [ 36 37 38 39] 144 | [ 40 41 42 43] 145 | [ 44 45 46 47]] 146 | 147 | [[ 48 49 50 51] 148 | [ 52 53 54 55] 149 | [ 56 57 58 59] 150 | [ 60 61 62 63]]] 151 | [Finished in 0.3s] 152 | ``` 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /Python 科学计算.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Python科学计算 3 | date: 2019-03-09 21:23:56 4 | tags: 5 | 6 | - python 7 | - science conputing 8 | toc: true 9 | --- 10 | # Python 科学计算 11 | 12 | --- 13 | [TOC] 14 | --- 15 | 16 | ## NumPy(MatLab 替代品之一) 17 | 18 | >- 数组的算数和逻辑运算 19 | >- 傅立叶变换和用于图形操作的例程 20 | >- 与线性代数有关的操作。 NumPy 拥有线性代数和随机数生成的内置函数 21 | frmemeta 22 | 23 | ## SciPy(科学计算) 24 | 25 | >SciPy是一个开源的算法库和数学工具包。 26 | > 27 | >其包含最优化、线性代数、积分、插值、特殊函数、快速傅里叶变换、信号处理和图像处理、常微分方程求解和其他科学与工程常用的计算。 28 | 29 | ## Matplotlib(科学绘图) 30 | 31 | >Matplotlib是Numpy的数值可视化包,它利用通用的图形用户界面工具包向应用程序嵌入式绘图提供接口。 32 | 33 | 34 | 35 | ## NumPy 36 | 37 | ### NumPy Ndarray 38 | 39 | NumPy 是用来处理数组的,在其内部可生成 Ndarray 对象表示数组。 40 | 41 | ndarray 对象用于存放同类型元素的多维数组。 42 | 43 | ndarray 中的每个元素在内存中都有相同存储大小区域。 44 | 45 | ndarrary 内部构成 46 | 47 | > 一个指向数据的指针 48 | > 49 | > 数据类型 dtype(描述数据在内存中固定大小的格子) 50 | > 51 | > 一个表示数组形状 (shape) 的元组 (表示各维度大小的元组) 52 | > 53 | > 一个跨度元组 (stride) (可为负数意义大致和切片类似) 54 | > 55 | > ![ndarray内部结构](http://www.runoob.com/wp-content/uploads/2018/10/ndarray.png) 56 | 57 | numpy 创建 array 58 | > `numpy.array(object, dtype = None,copy = True, order = None, subok = False, ndmin = 0)` 59 | > 参数说明: 60 | > 61 | > | 名称 | 描述 | 62 | > | ------ | --------------------------------------------- | 63 | > | object | 数组或嵌套数列 | 64 | > | dtype | 数组元素的类型 | 65 | > | copy | 深拷贝 | 66 | > | order | 创建数组样式(C 为行,F为列,A任意(default)) | 67 | > | subok | 默认返回一个与基类类型一致的数组 | 68 | > | ndmin | 指定生成数组的最小维度 | 69 | 70 | eg: 71 | >创建一维数组 72 | > ```python 73 | > import numpy as np 74 | > a = np.array([1,2,3]) 75 | > print(a) 76 | >``` 77 | >创建二维数组 78 | >```python 79 | >import numpy as np 80 | > a = np.array([[1,2,3],[3,4,5]]) 81 | > print(a) 82 | >``` 83 | >创建二维数组 84 | >```python 85 | >import numpy as np 86 | > a = np.array([[1,2,3],[3,4,5]],order = F) 87 | > print(a) 88 | >``` 89 | >创建最小维度为二维的数组 90 | >```python 91 | > import numpy as np 92 | > a = np.array([1,2,3],ndmin = 2) 93 | > print(a) 94 | > 【output】[[1,2,3]] 95 | > ``` 96 | >创建数据类型为复数的数组 97 | >```python 98 | >import numpy as np 99 | > a = np.array([1,2,3],dtype = complex,dmin = 2) 100 | > print(a) 101 | > 【output】[[1.+0.j,2.+0.j,3.+0.j]] 102 | > ``` 103 | ### NumPy数据类型 104 | numpy 支持大量的数据类型,可与C语言的数据类型做参照 105 | 106 | | 名称 | 描述 | 字符代码 | 107 | | ------ | ------------------------------------------------------ | ------------------------------------------------------ | 108 | | bool_ | 布尔类型(True, False) | b | 109 | | int_ | 默认整数类型(类比C语言的long->int32,int64) | int | 110 | | intc | 类比C语言的int->int32,int64 | int | 111 | | intp | 用于索引的正数类型类比C语言的size_t->int32.int64 | int | 112 | | int8 | 8bit,1字节,类比于char(-128 to 127) | i1 | 113 | | int16 | 16bit整数(-32768 to 32767) | i2 | 114 | | int32 | 32bit整数(-2147483648 to 2147483647) | i3 | 115 | | int64 | 64bit整数(-9223372036854775808 to 9223372036854775807) | i4 | 116 | | uint8 | 无符号8bit整数(0 to 255) | u1 | 117 | | uint16 | 无符号16bit整数(0 to 65535) | u2 | 118 | | uint32 | 无符号32bit整数(0 to 4294967295) | u3 | 119 | | uint64 | 无符号64bit整数(0 to 18446744073709551615) | u4 | 120 | | float_ | float64类型简写 | f | 121 | | float16 | 半精度浮点型 ->1 个符号位,5 个指数位,10 个尾数位 | f2 | 122 | | float32 | 单精度浮点型 ->1 个符号位,8 个指数位,23 个尾数位 | f4 | 123 | | float64 | 双精度浮点型 ->1 个符号位,11 个指数位,52 个尾数位 | f8 | 124 | | complex_ | cpmplex128简写,128bit复数 | c16 | 125 | | complex64 | 64bit复数 | c8 | 126 | | complex128 | 128bit复数 | c16 | 127 | 128 | | 符号 | 描述 | 129 | | ------------ | --------------------------------------- | 130 | | `'?'` | boolean | 131 | | `'b'` | (signed) byte | 132 | | `'B'` | unsigned byte | 133 | | `'i'` | (signed) integer | 134 | | `'u'` | unsigned integer | 135 | | `'f'` | floating-point | 136 | | `'c'` | complex-floating point | 137 | | `'m'` | timedelta | 138 | | `'M'` | datetime | 139 | | `'O'` | (Python) objects | 140 | | `'S'`, `'a'` | zero-terminated bytes (not recommended) | 141 | | `'U'` | Unicode string | 142 | | `'V'` | raw data (`void`) | 143 | 144 | | Build_In | 等价 | 145 | | ------------------------------------------------------------ | ------------------------------------------ | 146 | | `int` | `int_` | 147 | | `bool` | `bool_` | 148 | | `float` | `float_` | 149 | | `complex` | `cfloat` | 150 | | `bytes` | `bytes_` | 151 | | `str` | `bytes_` (Python2) or `unicode_` (Python3) | 152 | | `unicode` | `unicode_` | 153 | | `buffer` | `void` | 154 | | (all others) | `object_` | 155 | 156 | dtype 可取类型 np.bool_,np.int32,np.float_,np.complex128 157 | 158 | *** 数据类型对象dtype *** 159 | 160 | 数据类型对象是用来描述与数组对应的内存区域如何使用 161 | 162 | - 数据的类型 163 | - 数据的大小 164 | - 数据的字节顺序 (`‘<’:小端法(最小的存储在最小的地址,低位存前面,低位先存),’>‘:大端法`) 165 | 166 | 构造dtype `np.dtype(object, align, copy)` 167 | 168 | align(True/False)如果为 true,填充字段使其类似 C 的结构体 169 | 170 | copy(True/False)复制 dtype 对象 ,如果为 False,则是对内置数据类型对象的引用 171 | 172 | eg: 173 | 174 | > ```python 175 | > import numpy as np 176 | > student = np.dtype([('name','S20'), ('age', 'i1'), ('marks', 'f4')]) 177 | > a = np.array([('abc', 21, 50),('xyz', 18, 75)], dtype = student) 178 | > print(student) 179 | > print(a['name']) 180 | > print(a['age']) 181 | > print(a['marks']) 182 | > ``` 183 | > 输出 184 | > ```python 185 | > [('name', 'S20'), ('age', 'i1'), ('marks', ' [b'abc' b'xyz'] 187 | > [21 18] 188 | > [50. 75.] 189 | > ``` 190 | > 191 | 192 | ### NumPy数组属性 193 | 194 | 数组的维度成为秩(rank), 一维数组 r=1,二维数组r=2... 195 | 196 | 每一个线性数组称为一个轴(axis),也称为维度(dimensions)。秩是轴或维度的数量 197 | 198 | axis表示轴,对于二维数组来说,axis = 0对列操作,axis = 1是对行操作。 199 | 200 | NumPy比较重要的属性有: 201 | 202 | | 属性 | 描述 | 203 | | ----------------- | ------------------------------------------------------------ | 204 | | ndarray.ndim | 秩,即轴的数量或维度的数量 | 205 | | ndarray.shape | 数组的维度,对于矩阵(二维数组)来说即行列数(n, m) | 206 | | ndarray.size | 数组元素的总个数(n*m) | 207 | | ndarray.dtype | ndarray 对象的类型 | 208 | | ndarray.itemsizes | ndarray 每个元素的大小 | 209 | | ndarray.flag | ndarray 对象的信息 | 210 | | ndarray.real | ndarray 元素的实部 | 211 | | ndarray.imag | ndarray 元素的虚部 | 212 | | ndarray.data | 包含实际数组元素的缓冲区,一般通过数组的索引获得元素(可忽略) | 213 | 214 | eg: 215 | 216 | np.arange[^arange] 217 | 218 | ```python 219 | import numpy as np 220 | a = np.arange 221 | ``` 222 | 223 | 224 | [^arange]: arange和range 225 | 226 | range返回list,arange返回numpy.ndarray。 227 | 228 | 都是以三点切片方式控制生成的数列。 229 | 230 | 231 | ```python 232 | arange(start, end, step) 233 | range(start, end, step) 234 | ``` 235 | 但是arange的step可以是小数,其返回值数列元素类型也为float。 236 | 237 | 但是有一点类似,返回结果都不包括end。 238 | 239 | ndarray.ndim 240 | 241 | ```python 242 | import numpy as np 243 | a = np.arange(24) 244 | print(a.ndim) #1 245 | b = np.array(a,shape(3,4,2)) 246 | print(b) 247 | print(b.ndim) #3 248 | ``` 249 | 250 | 251 | 252 | ndarray.shape 253 | 254 | ```python 255 | import numpy as np 256 | a = n.array([[1,2,3],[4,5,6]]) 257 | print(a.shape) #(2, 3) 258 | ``` 259 | 260 | ndarray.reshape 261 | 262 | 注意:修改a.shape = shape或a.reshape()是返回一个视图,若返回的视图被修改,源数组也会发生改变。 263 | 264 | ```python 265 | import numpy as np 266 | a = np.array([[1,2,3],[4,5,6]]) 267 | b = a.reshape(3, 2) 268 | print(b) 269 | [[1, 2],[3, 4],[5, 6]] 270 | a.shape = (3,2) 271 | print(a) 272 | [[1, 2],[3, 4],[5, 6]] 273 | ``` 274 | 275 | ndarray.itemsize 276 | 277 | ```python 278 | import numpy as np 279 | x = np.array([1,2,3,4,5,6],dtype = np.int8) 280 | print(x.itemsize) #1 281 | y = np.array([1,2,3,4,5],dtype = np.float64) 282 | print(y.itemsize) #8 283 | ``` 284 | 285 | ndarray.flags 286 | 287 | 返回ndarray的对象信息 288 | 289 | | 属性 | 描述 | 290 | | ---------------- | ------------------------------------------------------------ | 291 | | C_CONTIGUOUS (C) | 数据是在一个单一的C风格的连续段中 | 292 | | F_CONTIGUOUS (F) | 数据是在一个单一的Fortran风格的连续段中 | 293 | | OWNDATA (O) | 数组拥有它所使用的内存或从另一个对象中借用它 | 294 | | WRITEABLE (W) | 数据区域可以被写入,将该值设置为 False,则数据为只读 | 295 | | ALIGNED (A) | 数据和所有元素都适当地对齐到硬件上 | 296 | | UPDATEIFCOPY (U) | 这个数组是其它数组的一个副本,当这个数组被释放时,原数组的内容将被更新 | 297 | 298 | ```python 299 | import numpy as np 300 | x = np.array([1, 2, 3, 4, 5]) 301 | print(x.flags) 302 | # 303 | C_CONTIGUOUS : True 304 | F_CONTIGUOUS : True 305 | OWNDATA : True 306 | WRITEABLE : True 307 | ALIGNED : True 308 | WRITEBACKIFCOPY : False 309 | UPDATEIFCOPY : False 310 | ``` 311 | 312 | ### NumPy创建数组 313 | 314 | 1. b = np.array([...] ,dtype = ...) 315 | 316 | 2. numpy.empty(shape,dtype = float, order = 'C') 317 | 318 | | 参数 | 描述 | 319 | | ----- | ------------------------------------------------------------ | 320 | | shape | 数组形状 | 321 | | dtype | 数据类型,可选 | 322 | | order | 有"C"和"F"两个选项,分别代表,行优先和列优先,在计算机内存中的存储元素的顺序。 | 323 | 324 | ```python 325 | import numpy as np 326 | x = np.empty([1,2],dtype = int) 327 | print(x) 328 | #未初始化所以是随机值 329 | #未初始化所以是随机值 330 | [[-5968 18343] 331 | [32766 0] 332 | [-1344 18343]] 333 | [Finished in 0.3s] 334 | ``` 335 | 336 | 3. numpy.zeros(shape, dtype = np.float, order = 'C') 337 | 338 | ```python 339 | import numpy as np 340 | x = np.zeros([3,2], dtype = np.int) 341 | print(x) 342 | y = np.zeros([2,3], dtype = [('x','i4'),('y','i4'),('z', 'i4')]) 343 | print(y) 344 | # 345 | [[0 0] 346 | [0 0] 347 | [0 0]] 348 | [[(0, 0) (0, 0) (0, 0)] 349 | [(0, 0) (0, 0) (0, 0)]] 350 | ``` 351 | 352 | 4. numpy.ones(shape, dtype = np.int4) 353 | 354 | ```python 355 | import numpy as np 356 | x = np.ones([3,2], dtype = np.int4) 357 | print(x) 358 | # 359 | [[1 1] 360 | [1 1] 361 | [1 1]] 362 | ``` 363 | 364 | ### NumPy从已有的数组创建数组 365 | 366 | **numpy.asarray方法** 367 | 368 | numpy.asarray 类似numpy.array,但是numpy.asarray的参数只有三个。 369 | 370 | `numpy.asarray(a, dtype = None, order = None)` 371 | 372 | | 参数 | 描述 | 373 | | ----- | ------------------------------------------------------------ | 374 | | a | 任意形式的输入参数,可以是,列表, 列表的元组, 元组, 元组的元组, 元组的列表,多维数组 | 375 | | dtype | 数据类型,可选 | 376 | | order | 可选,有"C"和"F"两个选项,分别代表,行优先和列优先,在计算机内存中的存储元素的顺序。 | 377 | 378 | 将数列转换成 ndarray 379 | 380 | ```python 381 | import numpy as np 382 | x = [1, 2, 3] 383 | a = np.asarray(x) 384 | print(a) 385 | # 386 | [1, 2, 3] 387 | ``` 388 | 389 | 将元组转化为ndarray 390 | 391 | ```python 392 | import numpy as np 393 | x = (1, 2, 3) 394 | a = np.asarray(x) 395 | print(a) 396 | # 397 | [1, 2, 3] 398 | ``` 399 | 400 | 将元组列表转换成ndarray 401 | 402 | ```python 403 | import numpy as np 404 | x = [(1, 2, 3), (4, 5, 6)] 405 | a = np.asarray(x) 406 | print(a) 407 | # 408 | [[1,2,3], 409 | [4,5,6]] 410 | ``` 411 | 412 | **numpy.frombuffer方法** 413 | 414 | numpy.frombuffer可以实现动态数组,其接受buffer输入参数,以流的形式读入转化成ndarray对象 415 | 416 | ```python 417 | numpy.frombuffer(buffer, dtype = float, count = -1, offset = 0) 418 | ``` 419 | 420 | | 参数 | 描述 | 421 | | ------ | ---------------------------------------- | 422 | | buffer | 可以是任意对象,会以流的形式读入。 | 423 | | dtype | 返回数组的数据类型,可选 | 424 | | count | 读取的数据数量,默认为-1,读取所有数据。 | 425 | | offset | 读取的起始位置,默认为0。 | 426 | 427 | ```python 428 | import numpy as np 429 | s = b'Hellow,world!' 430 | a = np.frombuffer(s,dtype = 'S1') 431 | print(a) 432 | # 433 | [b'H' b'e' b'l' b'l' b'o' b'w' b',' b'w' b'o' b'r' b'l' b'd' b'!'] 434 | ``` 435 | 436 | **NumPy.fromiter方法** 437 | 438 | numpy.fromiter方法可从可迭代对象中建立ndarray 对象,返回一维数组 439 | 440 | `np.fromer(iterable, dtype, count = -1)` 441 | 442 | | 参数 | 描述 | 443 | | -------- | -------------------------------------- | 444 | | iterable | 可迭代对象 | 445 | | dtype | 返回数组的数据类型 | 446 | | count | 读取的数据数量,默认为-1,读取所有数据 | 447 | 448 | ```python 449 | import numpy as np 450 | l = range(9) 451 | i = iter(l) 452 | x = np,fromiter(i, dtype = np.int4) 453 | print(x) 454 | # 455 | [0 1 2 3 4 5 6 7 8] 456 | ``` 457 | 458 | ### NumPy从数值范围创建数组 459 | 460 | 使用**numpy.arange**方法生成ndarray对象。 461 | 462 | `numpy.arange(start, stop, step, dtype)` 463 | 464 | | 参数 | 描述 | 465 | | ------- | ------------------------------------------------------------ | 466 | | `start` | 起始值,默认为`0` | 467 | | `stop` | 终止值(不包含) | 468 | | `step` | 步长,默认为`1`(step 可以为浮点小数) | 469 | | `dtype` | 返回`ndarray`的数据类型,如果没有提供,则会使用输入数据的类型 | 470 | 471 | eg: 472 | 473 | x = np.arange(5) 474 | 475 | #[0,1,2,3,4] 476 | 477 | x = np.arange(5, dtype = float) 478 | 479 | print(x) 480 | 481 | # 482 | [0. 1. 2. 3. 4.] 483 | 484 | ```python 485 | x =np.arange(10, 20, 2) 486 | print(x) 487 | # 488 | [10 12 14 16 18] 489 | ``` 490 | 491 | **numpy.linspace**函数用于创建一个一维数组,数组是一个等差数列构成的。 492 | 493 | `np.linspace(start, stop, num = 50, endpoint = True, retstep = False, dtype = None)` 494 | 495 | | 参数 | 描述 | 496 | | -------- | ---------------------------------------- | 497 | | start | 序列起始值 | 498 | | stop | 序列终止值 | 499 | | num | 要生成等步长样本的数量,默认值为50 | 500 | | endpoint | Ture代表包含stop,反之不包含。默认为True | 501 | | retstep | Ture代表生成的数组会显示间距,反之不显示 | 502 | | dtype | ndarray的数据类型 | 503 | 504 | ```python 505 | import numpy as np 506 | a = np.linsapce(1, 10, 50,retstep = True) 507 | print(a) 508 | # 509 | (array([ 1. , 1.59183673, 2.18367347, 2.7755102 , 3.36734694, 510 | 3.95918367, 4.55102041, 5.14285714, 5.73469388, 6.32653061, 511 | 6.91836735, 7.51020408, 8.10204082, 8.69387755, 9.28571429, 512 | 9.87755102, 10.46938776, 11.06122449, 11.65306122, 12.24489796, 513 | 12.83673469, 13.42857143, 14.02040816, 14.6122449 , 15.20408163, 514 | 15.79591837, 16.3877551 , 16.97959184, 17.57142857, 18.16326531, 515 | 18.75510204, 19.34693878, 19.93877551, 20.53061224, 21.12244898, 516 | 21.71428571, 22.30612245, 22.89795918, 23.48979592, 24.08163265, 517 | 24.67346939, 25.26530612, 25.85714286, 26.44897959, 27.04081633, 518 | 27.63265306, 28.2244898 , 28.81632653, 29.40816327, 30. ]), 0.5918367346938775) 519 | ``` 520 | 521 | **NumPy.logspace**函数用于创建一个一维数组,数组是一个等比数列构成的。 522 | 523 | `numpy.logspace(star, stop, num = 50, endpoint, base = '10.0', dtype)` 524 | 525 | | `start` | 序列的起始值为:base ** start | 526 | | ---------- | ------------------------------------------------------------ | 527 | | `stop` | 序列的终止值为:base ** stop。如果`endpoint`为`true`,该值包含于数列中 | 528 | | `num` | 要生成的等步长的样本数量,默认为`50` | 529 | | `endpoint` | 该值为 `ture` 时,数列中中包含`stop`值,反之不包含,默认是True。 | 530 | | `base` | 对数 log 的底数。 | 531 | | `dtype` | `ndarray` 的数据类型,默认float | 532 | 533 | ```python 534 | import numpy as np 535 | a = np.logspace(1,10,dtype = np.int64) 536 | print(a) 537 | # 538 | [ 10 15 23 35 54 82 539 | 126 193 294 449 686 1048 540 | 1599 2442 3727 5689 8685 13257 541 | 20235 30888 47148 71968 109854 167683 542 | 255954 390693 596362 910298 1389495 2120950 543 | 3237457 4941713 7543120 11513953 17575106 26826957 544 | 40949150 62505519 95409547 145634847 222299648 339322177 545 | 517947467 790604321 1206792640 1842069969 2811768697 4291934260 546 | 6551285568 10000000000] 547 | ``` 548 | 549 | ```python 550 | import numpy as np 551 | a = np.logspace(1,10,num = 10,base = 2) 552 | print(a) 553 | # 554 | [ 2. 4. 8. 16. 32. 64. 128. 256. 512. 1024.] 555 | ``` 556 | 557 | ### NumPy 切片和索引 558 | 559 | numpy的ndarray 可以像list那样通过切片和索引操作或访问 560 | 561 | ```python 562 | import numpy as np 563 | a = np.arange(8) 564 | s = slice(0,5,2) 565 | b = a[s] 566 | c = a[:5:2] 567 | print(a,b,c,sep = '\n') 568 | # 569 | [0 1 2 3 4 5 6 7] 570 | [0 2 4] 571 | [0 2 4] 572 | ``` 573 | 574 | python list多维数组下标访问 575 | 576 | ```python 577 | a = [ 578 | [[1,2,4],[4,5,6]], 579 | [[7,8,9],[10,11,12]], 580 | [[13,14,15],[16,17,18]] 581 | ] 582 | print(a[2][1][2]) 583 | # 584 | 18 585 | ``` 586 | 587 | numpy ndarray多维数组下标访问 588 | 589 | ```python 590 | import numpy as np 591 | a = np.arange(64).shape(4,4,4) 592 | b = a[2,2,2] 593 | print(b) 594 | ``` 595 | 596 | 597 | 598 | 多维数组操作 599 | 600 | ```python 601 | import numpy as np 602 | a = np.array([[1,2,3],[4,5,6],[7,8,9]]) 603 | print(a[:2,:2]) 604 | # 605 | [[1 2] 606 | [4 5]] 607 | ``` 608 | 609 | 用`...`代替全维度`::` 610 | 611 | ```python 612 | import numpy as np 613 | a = np.arange(64) 614 | b = a.reshape(4,4,4) 615 | print(b[...,2,2]) 616 | # 617 | [[[ 0 1 2 3] 618 | [ 4 5 6 7] 619 | [ 8 9 10 11] 620 | [12 13 14 15]] 621 | 622 | [[16 17 18 19] 623 | [20 21 22 23] 624 | [24 25 26 27] 625 | [28 29 30 31]] 626 | 627 | [[32 33 34 35] 628 | [36 37 38 39] 629 | [40 41 42 43] 630 | [44 45 46 47]] 631 | 632 | [[48 49 50 51] 633 | [52 53 54 55] 634 | [56 57 58 59] 635 | [60 61 62 63]]] 636 | 637 | [10 26 42 58] 638 | ``` 639 | 640 | ### NumPy 高级索引 641 | 642 | - 整数数组索引 643 | 644 | 使用二维数组分别表示行列值 645 | 646 | 输出矩阵四角元素的值。 647 | 648 | ```python 649 | import numpy as np 650 | x = np.arange(12).reshape(4,3) 651 | print(x) 652 | print(x[[0,0,3,3],[0,2,0,2]]) 653 | # 654 | [[ 0 1 2] 655 | [ 3 4 5] 656 | [ 6 7 8] 657 | [ 9 10 11]] 658 | [0 2 9 11] 659 | 660 | y = x[0:-2:-1,[0:-2:-1]] 661 | 662 | rows = np.array([[0,0],[3,3]]) 663 | cols = np.array([[0,2],[0,2]]) 664 | y = x[rows,cols] 665 | print(y) 666 | # 667 | [[0 2] 668 | [9 11] 669 | ``` 670 | 671 | ```python 672 | import numpy as np 673 | 674 | a = np.array([[1,2,3], [4,5,6],[7,8,9]]) 675 | b = a[1:3, 1:3] 676 | c = a[1:3,[1,2]] 677 | d = a[...,1:] 678 | print(b) 679 | print(c) 680 | print(d) 681 | 682 | # 683 | b = [5 6 8 9] 684 | c = [[5 6][8 9]] 685 | d = [[2 3][5 6][8 9]] 686 | ``` 687 | 688 | - 布尔索引 689 | 690 | 使用布尔切片过滤掉ndarray小于等于5的值 691 | 692 | ```python 693 | import numpy as np 694 | x = np.array([[0,1,2],[3,4,5],[6,7,8],[9,10,11]]) 695 | print(x[x>5]) 696 | # 697 | [ 6 7 8 9 10 11] 698 | ``` 699 | 使用布尔切片过滤掉ndarray等于NaN的值 700 | ```python 701 | import numpy as np 702 | a = np.array(np.nan,1,2,np.nan,4) 703 | print(a[~isnan(a)]) 704 | [ 1 2 4] 705 | ``` 706 | 使用布尔切片保留复数 707 | 708 | ```python 709 | import numpy as np 710 | a = np.array([1, 2+6j, 5, 3.5+5j]) 711 | print(a[np.iscomplex(a)]) 712 | # 713 | [2. +6.j 3.5+5.j] 714 | ``` 715 | 716 | - 花式索引 717 | 718 | 使用整数数组进行索引 719 | 720 | 一维数组为第一维度 721 | 722 | ```python 723 | import numpy as np 724 | x = np.arange(32).reshape(8,4) 725 | print(x[[4,2,1,7]]) 726 | # 727 | [[16 17 18 19] 728 | [ 8 9 10 11] 729 | [ 4 5 6 7] 730 | [28 29 30 31]] 731 | ``` 732 | 733 | 传入倒序一维数组 734 | 735 | ```python 736 | import numpy as np 737 | x = np.arange(32).reshape(8,4) 738 | print(x) 739 | print(x[[-4,-2,-1,-7]]) 740 | # 741 | [[16 17 18 19] 742 | [24 25 26 27] 743 | [28 29 30 31] 744 | [ 4 5 6 7]] 745 | ``` 746 | 747 | 传入两个索引数组 748 | 749 | ```python 750 | import numpy as np 751 | x = np.arange(32).reshape(8,4) 752 | print(x) 753 | print(x[np.ix_([1,5,3,2],[0,3,2])]) 754 | # 755 | [[ 4 7 6] 756 | [20 23 22] 757 | [12 15 14] 758 | [ 8 11 10]] 759 | ``` 760 | 761 | ### 广播数组(Broadcast) 762 | 763 | ndarray 做算术运算时要求两个ndarray同型即a.shape=b,shape 764 | 765 | 但是遇到在末尾维度相同但是前面不相同时即会自动触发广播机制。 766 | 767 | ```python 768 | import numpy as np 769 | a = np.arange(12).reshape(4,3) 770 | b = np.arange(12,24).reshape(4,3) 771 | c = np.asarray([10,20,30]) 772 | print(a,b,c,sep = '\n') 773 | print(f'a,b同型直接运算:"\n"{a+b}') 774 | print(f'a,c不同维度,a自动出发广播然后运算:"\n"{a+c}') 775 | # 776 | [[ 0 1 2] 777 | [ 3 4 5] 778 | [ 6 7 8] 779 | [ 9 10 11]] 780 | [[12 13 14] 781 | [15 16 17] 782 | [18 19 20] 783 | [21 22 23]] 784 | [10 20 30] 785 | a,b同型直接运算: 786 | [[12 14 16] 787 | [18 20 22] 788 | [24 26 28] 789 | [30 32 34]] 790 | a,c不同维度,a自动出发广播然后运算: 791 | [[10 21 32] 792 | [13 24 35] 793 | [16 27 38] 794 | [19 30 41]] 795 | ``` 796 | 797 | 广播原理: 798 | 799 | ![broadcase](http://www.runoob.com/wp-content/uploads/2018/10/image0020619.gif) 800 | 801 | **np.tile(x,y,z,...)方法**:tile即瓷砖,即将该数组进行横向纵向复制 802 | 803 | ```python 804 | import numpy as np 805 | mat = np.tile(np.array([[1,2,3],[4,5,6]]),(2,3)) 806 | print(mat) 807 | # 808 | [[1 2 3 1 2 3 1 2 3] 809 | [4 5 6 4 5 6 4 5 6] 810 | [1 2 3 1 2 3 1 2 3] 811 | [4 5 6 4 5 6 4 5 6]] 812 | ``` 813 | 814 | 即broadcast 执行过程: 815 | 816 | ```python 817 | import numpy as np 818 | a = np.array([[0,0,0], 819 | [10,10,10], 820 | [20,20,20], 821 | [30,30,30] 822 | ]) 823 | b = np.array([1,2,3]) 824 | c = a+b 825 | d = a+np.tile(b,(4,1)) 826 | print(c,d,sep = '\n') 827 | # 828 | [[ 1 2 3] 829 | [11 12 13] 830 | [21 22 23] 831 | [31 32 33]] 832 | [[ 1 2 3] 833 | [11 12 13] 834 | [21 22 23] 835 | [31 32 33]] 836 | ``` 837 | 838 | **广播的规则:** 839 | 840 | - 让所有输入数组都向其中形状最长的数组看齐,形状中不足的部分都通过在前面加 1 补齐。 841 | 842 | - 输出数组的形状是输入数组形状的各个维度上的最大值。 843 | 844 | - 如果输入数组的某个维度和输出数组的对应维度的长度相同或者其长度为 1 时,这个数组能够用来计算,否则出错。 845 | 846 | - 当输入数组的某个维度的长度为 1 时,沿着此维度运算时都用此维度上的第一组值。 847 | 848 | **简单理解:**对两个数组,分别比较他们的每一个维度(若其中一个数组没有当前维度则忽略),满足: 849 | 850 | - 数组拥有相同形状。 851 | 852 | - 当前维度的值相等。 853 | 854 | - 当前维度的值有一个是 1。 855 | 856 | 若条件不满足,抛出 **"ValueError: frames are not aligned**或者 857 | 858 | **ValueError: operands could not be broadcast together with shapes"** 异常。 859 | 860 | ### NumPy迭代数组 861 | 862 | Numpy提供了numpt.nditer用来灵活访问数组元素 863 | 864 | 865 | ```python 866 | import numpy as np 867 | 868 | a = np.arange(6).reshape(3,2) 869 | 870 | print(a) 871 | 872 | for x in np.nditer(a): 873 | print(x,end = ',') 874 | print('\n') 875 | for x in np.nditer(a.T): 876 | print(x,end = ',') 877 | print('\n') 878 | for x in np.nditer(a.T.copy(order = 'C')): 879 | print(x,end = ',') 880 | print('\n') 881 | for x in np.nditer(a.T,order = 'C'): 882 | print(x,end = ',') 883 | print('\n') 884 | for x in np.nditer(a.T,order = 'F'): 885 | print(x,end = ',') 886 | print('\n') 887 | # 888 | [[0 1] 889 | [2 3] 890 | [4 5]] 891 | 0,1,2,3,4,5, 892 | 893 | 0,1,2,3,4,5, 894 | 895 | 0,2,4,1,3,5, 896 | 897 | 0,2,4,1,3,5, 898 | 899 | 0,1,2,3,4,5, #反反得反 900 | ``` 901 | **ndarray.T**表示ndarray的转置 902 | 903 | ```python 904 | import numpy as np 905 | 906 | a = np.arange(0,60,5) 907 | a = a.reshape(3,4) 908 | print ('原始数组是:') 909 | print (a) 910 | print ('\n') 911 | print ('原始数组的转置是:') 912 | b = a.T #b是a的转置 913 | print (b) 914 | print ('\n') 915 | print ('以 C 风格顺序排序:') 916 | c = b.copy(order='C') 917 | print (c) 918 | for x in np.nditer(c): 919 | print (x, end=", " ) 920 | print ('\n') 921 | print ('以 F 风格顺序排序:') 922 | c = b.copy(order='F') 923 | print (c) 924 | for x in np.nditer(c): 925 | print (x, end=", " ) 926 | # 927 | 原始数组是: 928 | [[ 0 5 10 15] 929 | [20 25 30 35] 930 | [40 45 50 55]] 931 | 932 | 933 | 原始数组的转置是: 934 | [[ 0 20 40] 935 | [ 5 25 45] 936 | [10 30 50] 937 | [15 35 55]] 938 | 939 | 940 | 以 C 风格顺序排序: 941 | [[ 0 20 40] 942 | [ 5 25 45] 943 | [10 30 50] 944 | [15 35 55]] 945 | 0, 20, 40, 5, 25, 45, 10, 30, 50, 15, 35, 55, 946 | 947 | 以 F 风格顺序排序: 948 | [[ 0 20 40] 949 | [ 5 25 45] 950 | [10 30 50] 951 | [15 35 55]] 952 | 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 953 | ``` 954 | **修改数组中元素的值** 955 | 956 | op_flags = ['readwrite'] 957 | 958 | ```python 959 | import numpy as np 960 | 961 | a = np.arange(0,60,5).reshape(3,4) 962 | print('原始数据:\n',a) 963 | for x in np.nditer(a, op_flags = ['readwrite']): 964 | x[...]*=2 965 | print('现在数据:\n',a) 966 | # 967 | 原始数据: 968 | [[ 0 5 10 15] 969 | [20 25 30 35] 970 | [40 45 50 55]] 971 | 现在数据: 972 | [[ 0 10 20 30] 973 | [ 40 50 60 70] 974 | [ 80 90 100 110]] 975 | ``` 976 | 977 | **使用外部循环**(不重要) 978 | 979 | np.nditer()有很多参数其中flags参数可取下列值 980 | 981 | | 参数 | 描述 | 982 | | --------------- | ---------------------------------------------- | 983 | | `c_index` | 可以跟踪 C 顺序的索引 | 984 | | `f_index` | 可以跟踪 Fortran 顺序的索引 | 985 | | `multi-index` | 每次迭代可以跟踪一种索引类型 | 986 | | `external_loop` | 给出的值是具有多个值的一维数组,而不是零维数组 | 987 | 988 | ```python 989 | import numpy as np 990 | a = np.arange(0,60,5) 991 | a = a.reshape(3,4) 992 | print ('原始数组是:') 993 | print (a) 994 | print ('\n') 995 | print ('修改后的数组是:') 996 | for x in np.nditer(a, flags = ['external_loop'], order = 'F'): 997 | print (x, end=", " ) 998 | ``` 999 | 1000 | 1001 | 1002 | **使用广播循环** 1003 | 1004 | 如果两个数组可以广播,nditer组合对象能够同时迭代它们。(广播条件,见上广播规则) 1005 | 1006 | ```python 1007 | import numpy as np 1008 | a = np.arange(0,60,5).reshape(3,4) 1009 | b = np.array([11,22,33,44],dtype = 'int') 1010 | for x,y in np.nditer([a,b],order = 'C'): 1011 | print(f'{x}:{y}',end = ',') 1012 | # 1013 | 0:11,5:22,10:33,15:44,20:11,25:22,30:33,35:44,40:11,45:22,50:33,55:44, 1014 | ``` 1015 | 1016 | 1017 | 1018 | 1019 | ### NumPy数组操作 1020 | 1021 | - 修改数组形状(reshape) 1022 | 1023 | `ndarray.reshape`可以重定义数组行列 1024 | 1025 | `ndarray.flat`是一个数组元素迭代器,可用for in 访问迭代器内的值 1026 | 1027 | `ndarray.flatten`返回一份数组拷贝,对拷贝进行修改并不会改变原始数组 1028 | 1029 | > ndarray.flatten(order = 'C')#按照C(即按行)展开 1030 | > 1031 | > ndarray.flatten(order = 'F')#按照F(即按列)展开 1032 | 1033 | `numpy.ravel(ndarray, order = 'C')`展平数组元素,返回引用修改会引起原来的ndarray发生改变 1034 | 1035 | > order:'C' -- 按行,'F' -- 按列,'A' -- 原顺序,'K' -- 元素在内存中的出现顺序 1036 | 1037 | - 翻转数组(transpose) 1038 | `numpy.transpose(ndarray, axes)`翻转数组(相当于ndarray.T) 1039 | 1040 | axes:维度 1041 | 1042 | `numpy.rollaxis(ndarray,axis,start)`函数向后滚动一个特定的轴到特定的位置 1043 | 1044 | axis:要向后滚动的轴 1045 | 1046 | start = 0:默认为0表示完整的滚动 1047 | 1048 | 1049 | ```python 1050 | import numpy as np 1051 | 1052 | a = np.arange(27).reshape([3,3,3]) 1053 | print(a) 1054 | 1055 | print(np.rollaxis(a,2)) 1056 | 1057 | print(np.rollaxis(a,2,1)) 1058 | print(np.rollaxis(a,1,0)) 1059 | # 1060 | [[[ 0 1 2] 1061 | [ 3 4 5] 1062 | [ 6 7 8]] 1063 | 1064 | [[ 9 10 11] 1065 | [12 13 14] 1066 | [15 16 17]] 1067 | 1068 | [[18 19 20] 1069 | [21 22 23] 1070 | [24 25 26]]] 1071 | yzx 1072 | [[[ 0 3 6] 1073 | [ 9 12 15] 1074 | [18 21 24]] 1075 | 1076 | [[ 1 4 7] 1077 | [10 13 16] 1078 | [19 22 25]] 1079 | 1080 | [[ 2 5 8] 1081 | [11 14 17] 1082 | [20 23 26]]] 1083 | xzy 1084 | [[[ 0 3 6] 1085 | [ 1 4 7] 1086 | [ 2 5 8]] 1087 | 1088 | [[ 9 12 15] 1089 | [10 13 16] 1090 | [11 14 17]] 1091 | 1092 | [[18 21 24] 1093 | [19 22 25] 1094 | [20 23 26]]] 1095 | yxz 1096 | [[[ 0 1 2] 1097 | [ 9 10 11] 1098 | [18 19 20]] 1099 | 1100 | [[ 3 4 5] 1101 | [12 13 14] 1102 | [21 22 23]] 1103 | 1104 | [[ 6 7 8] 1105 | [15 16 17] 1106 | [24 25 26]]] 1107 | ``` 1108 | 1109 | ​ `numpy.swapaxes(ndarray,axis1,axis2)`交换数组的两个轴 1110 | 1111 | ```python 1112 | import numpy as np 1113 | 1114 | a = np.arange(27).reshape([3,3,3]) 1115 | print(a) 1116 | print(np.swapaxes(a,2,1)) 1117 | print(np.swapaxes(a,1,0)) 1118 | print(np.swapaxes(a,2,0)) 1119 | # 1120 | [[[ 0 1 2] 1121 | [ 3 4 5] 1122 | [ 6 7 8]] 1123 | 1124 | [[ 9 10 11] 1125 | [12 13 14] 1126 | [15 16 17]] 1127 | 1128 | [[18 19 20] 1129 | [21 22 23] 1130 | [24 25 26]]] 1131 | xzy 1132 | [[[ 0 3 6] 1133 | [ 1 4 7] 1134 | [ 2 5 8]] 1135 | 1136 | [[ 9 12 15] 1137 | [10 13 16] 1138 | [11 14 17]] 1139 | 1140 | [[18 21 24] 1141 | [19 22 25] 1142 | [20 23 26]]] 1143 | yxz 1144 | [[[ 0 1 2] 1145 | [ 9 10 11] 1146 | [18 19 20]] 1147 | 1148 | [[ 3 4 5] 1149 | [12 13 14] 1150 | [21 22 23]] 1151 | 1152 | [[ 6 7 8] 1153 | [15 16 17] 1154 | [24 25 26]]] 1155 | zyx 1156 | [[[ 0 9 18] 1157 | [ 3 12 21] 1158 | [ 6 15 24]] 1159 | 1160 | [[ 1 10 19] 1161 | [ 4 13 22] 1162 | [ 7 16 25]] 1163 | 1164 | [[ 2 11 20] 1165 | [ 5 14 23] 1166 | [ 8 17 26]]] 1167 | ``` 1168 | 1169 | - 修改数组维度(broadcast,broadcast_to,expand_dims,squeeze) 1170 | 1171 | | 函数 | 描述 | 1172 | | -------------- | -------------------------- | 1173 | | `broadcast` | 产生模仿广播的对象 | 1174 | | `broadcast_to` | 将数组广播到新形状 | 1175 | | `expand_dims` | 扩展数组的形状 | 1176 | | `squeeze` | 从数组的形状中删除一维条目 | 1177 | 1178 | `numpy.broadcast `用于模仿广播对象,它返回一个对象(numpy.broadcast object),该对象封装了将一个数组广播到另一个数组的结果。 1179 | 1180 | 1181 | 1182 | `numpy.broadcast_to(array, shape, subok)` 函数将数组广播到新形状。它在原始数组上返回只读视图。 它通常不连续。 如果新形状不符合 NumPy 的广播规则,该函数可能会抛出ValueError。 1183 | 1184 | 1185 | 1186 | ``numpy.expand_dims(arr, axis)`` 函数通过在指定位置插入新的轴来扩展数组形状。 1187 | 1188 | `numpy.squeeze(arr,axis = 0)`函数从给定数组的形状中删除一维的条目 1189 | 1190 | 1191 | 1192 | - 连接数组 1193 | 1194 | | 函数 | 描述 | 1195 | | ------------- | ------------------------------ | 1196 | | `concatenate` | 连接沿现有轴的数组序列 | 1197 | | `stack` | 沿着新的轴加入一系列数组。 | 1198 | | `hstack` | 水平堆叠序列中的数组(列方向) | 1199 | | `vstack` | 竖直堆叠序列中的数组(行方向) | 1200 | 1201 | `numpy.concatenate((a1, a2, ...), axis = 0)` 1202 | 1203 | ```python 1204 | import numpy as np 1205 | 1206 | a = np.array([[1,2],[3,4]]) 1207 | 1208 | print ('第一个数组:') 1209 | print (a) 1210 | print ('\n') 1211 | b = np.array([[5,6],[7,8]]) 1212 | 1213 | print ('第二个数组:') 1214 | print (b) 1215 | print ('\n') 1216 | # 两个数组的维度相同 1217 | 1218 | print ('沿轴 0 连接两个数组:') 1219 | print (np.concatenate((a,b))) 1220 | print ('\n') 1221 | 1222 | print ('沿轴 1 连接两个数组:') 1223 | print (np.concatenate((a,b),axis = 1)) 1224 | # 1225 | 第一个数组: 1226 | [[1 2] 1227 | [3 4]] 1228 | 第二个数组: 1229 | [[5 6] 1230 | [7 8]] 1231 | 沿轴 0 连接两个数组: 1232 | [[1 2] 1233 | [3 4] 1234 | [5 6] 1235 | [7 8]] 1236 | 沿轴 1 连接两个数组: 1237 | [[1 2 5 6] 1238 | [3 4 7 8]] 1239 | [Finished in 0.3s] 1240 | ``` 1241 | 1242 | `numpy.stack(arrays, axis)`沿新的轴连接数组,新建一个维度 1243 | 1244 | `numpy.hstack(ndarray1,ndarray2)`水平堆叠生成数组 1245 | 1246 | `numpy.vstack(ndarray1,ndarray2)`垂直堆叠生成数组 1247 | 1248 | - 分割数组 1249 | 1250 | | 函数 | 数组及操作 | 1251 | | -------- | -------------------------------------- | 1252 | | `split` | 将一个数组分割为多个子数组 | 1253 | | `hsplit` | 将一个数组水平分割为多个子数组(按列) | 1254 | | `vsplit` | 将一个数组垂直分割为多个子数组(按行) | 1255 | 1256 | `numpy.split(ary, indices_or_sections, axis)`沿特定轴将数组分割为子数组 1257 | >`ary`:被分割的数组 1258 | >`indices_or_sections`:果是一个整数,就用该数平均切分,如果是一个数组,为沿轴切分的位置(左开右闭) 1259 | >`axis`:沿着哪个维度进行切向,默认为0,横向切分。为1时,纵向切分 1260 | 1261 | ```python 1262 | import numpy as np 1263 | 1264 | a = np.arange(9) 1265 | 1266 | print ('第一个数组:') 1267 | print (a) 1268 | print ('\n') 1269 | 1270 | print ('将数组分为三个大小相等的子数组:') 1271 | b = np.split(a,3) 1272 | print (b) 1273 | print ('\n') 1274 | #[array([0,1,2]),array([3,4,5]),array([6,7,8])] 1275 | 1276 | print ('将数组在一维数组中表明的位置分割:') 1277 | b = np.split(a,[4,7]) 1278 | print (b) 1279 | #[array([0,1,2,3],array([4,5,6]),array([7,8])] 1280 | ``` 1281 | 1282 | `numpy.hsplit `函数用于水平分割数组,通过指定要返回的相同形状的数组数量来拆分原数组,用法和split类似。 1283 | 1284 | `numpy.vsplit` 沿着垂直轴分割,其分割方式与split用法相同 1285 | 1286 | - 数组元素的添加和删除 1287 | 1288 | | 函数 | 元素及描述 | 1289 | | -------- | ---------------------------------------- | 1290 | | `resize` | 返回指定形状的新数组 | 1291 | | `append` | 将值添加到数组末尾 | 1292 | | `insert` | 沿指定轴将值插入到指定下标之前 | 1293 | | `delete` | 删掉某个轴的子数组,并返回删除后的新数组 | 1294 | | `unique` | 查找数组内的唯一元素 | 1295 | 1296 | `numpy.resize(arr, shape)` np.resize(arr,shape)等价于arr.resize(shape) 1297 | 1298 | `numpy.append(arr, values, axis=None)`函数在数组的末尾添加值。 追加操作会分配整个数组,并把原来的数组复制到新数组中。 此外,输入数组的维度必须匹配否则将生成ValueError。 1299 | 1300 | ```python 1301 | import numpy as np 1302 | 1303 | a = np.array([[1,2,3],[4,5,6]]) 1304 | print (a) 1305 | # 1306 | [[1 2 3] 1307 | [4 5 6]] 1308 | print ('向数组添加元素:') 1309 | print (np.append(a, [7,8,9])) 1310 | #未传递axis数组会展开 1311 | [1 2 3 4 5 6 7 8 9] 1312 | 1313 | print ('沿轴 0 添加元素:') 1314 | print (np.append(a, [[7,8,9]],axis = 0)) 1315 | # 1316 | [[1 2 3] 1317 | [4 5 6] 1318 | [7 8 9]] 1319 | print ('沿轴 1 添加元素:') 1320 | print (np.append(a, [[5,5,5],[7,8,9]],axis = 1)) 1321 | # 1322 | [[1 2 3 5 5 5] 1323 | [4 5 6 7 8 9]] 1324 | ``` 1325 | 1326 | `numpy.insert(arr, obj, values, axis)`函数在给定索引之前,沿给定轴在输入数组中插入值。 1327 | 1328 | ```python 1329 | import numpy as np 1330 | 1331 | a = np.array([[1,2],[3,4],[5,6]]) 1332 | print (a) 1333 | # 1334 | [[1 2] 1335 | [3 4] 1336 | [5 6]] 1337 | print ('未传递 Axis 参数。 在插入之前输入数组会被展开。') 1338 | print (np.insert(a,3,[11,12])) 1339 | # 1340 | [1 2 3 11 12 4 5 6] 1341 | print ('传递了 Axis 参数。 会广播值数组来配输入数组。') 1342 | print ('沿轴 0 广播:') 1343 | print (np.insert(a,1,[11],axis = 0)) 1344 | #自动广播 1345 | [[1 2] 1346 | [11 11] 1347 | [3 4] 1348 | [5 6]] 1349 | print ('沿轴 1 广播:') 1350 | print (np.insert(a,1,11,axis = 1)) 1351 | [[1 11 2] 1352 | [3 11 4] 1353 | [5 11 6]] 1354 | ``` 1355 | 1356 | `Numpy.delete(arr, obj, axis)`函数返回从输入数组中删除指定子数组的新数组。 与 insert() 函数的情况一样,如果未提供轴参数,则输入数组将展开。 1357 | > `arr`:输入数组 1358 | > `obj`:可以被切片,整数或者整数数组,表明要从输入数组删除的子数组 1359 | > `axis`:沿着它删除给定子数组的轴,如果未提供,则输入数组会被展开 1360 | 1361 | ```python 1362 | a = np.array([1,2,3,4,5,6,7,8,9,10]) 1363 | print (np.delete(a, np.s_[::2])) 1364 | #np.s_[::]切片 1365 | ``` 1366 | 1367 | `numpy.unique(arr, return_index, return_inverse, return_counts)`函数用于去除数组中的重复元素。 1368 | 1369 | >arr:输入数组,如果不是一维数组则会展开 1370 | >return_index:如果为true,返回新列表元素在旧列表中的位置(下标),并以列表形式储 1371 | >return_inverse:如果为true,返回旧列表元素在新列表中的位置(下标),并以列表形式储 1372 | >return_counts:如果为true,返回去重数组中的元素在原数组中的出现次数 1373 | 1374 | ### 位运算(bitwise按位) 1375 | 1376 | `numpy.bitwise_and(a,b)`与运算 1377 | 1378 | `numpy.bitwise_or(a,b)`或运算 1379 | 1380 | `numpy.invert()`非 1381 | 1382 | `left_shift(a,width = 8)`按位左移 1383 | 1384 | `right_shift(a,width = 8)`按位右移 1385 | 1386 | ### 字节转换函数 1387 | 1388 | `ndarray.byteswap(True/False)`对ndarray中的元素进行大小端字节转换 1389 | 1390 | ### 字符串函数 1391 | 1392 | | 函数 | 描述 | 1393 | | -------------- | ------------------------------------------ | 1394 | | `numpy.char.add(['s1'],['s2'])` | 对两个数组的逐个字符串元素进行连接 | 1395 | | `numpy.char.multiply(['s'],mult)` | 返回按元素多重连接后的字符串 | 1396 | | `numpy.char.center('str',width = Num,fillchar = '*')` | 居中字符串 | 1397 | | `numpy.char.capitalize('str')` | 将字符串第一个字母转换为大写 | 1398 | | `numpy.char.title('str')` | 将字符串的每个单词的第一个字母转换为大写 | 1399 | | `numpy.char.lower('str')` | 数组元素转换为小写 | 1400 | | `numpy.char.upper('str')` | 数组元素转换为大写 | 1401 | | `numpy.char.split('str',sep = '*')` | 指定分隔符对字符串进行分割,并返回数组列表 | 1402 | | `numpy.char.splitlines('str')` | 返回元素中的行列表,以换行符分割 | 1403 | | `numpy.char.strip('str','char')` | 移除元素开头或者结尾处的特定字符 | 1404 | | `numpy.char.join(['char1','char2'],['str1',str2])` | 通过指定分隔符来连接数组中的元素 | 1405 | | `numpy.char.replace('str','s1',s2)` | 使用新字符串s2替换字符串中的所有子字符串s1 | 1406 | | `numpy.char.decode()` | 数组元素依次调用`str.decode` | 1407 | | `numpy.char.encode()` | 数组元素依次调用`str.encode` | 1408 | 1409 | ### 数学函数 1410 | 1411 | **三角函数** 1412 | 1413 | ​ NumPy 提供了标准的三角函数:numpy.sin()、numpy.cos()、numpy.tan() 1414 | 1415 | ```python 1416 | import numpy as np 1417 | 1418 | a = np.array([0,30,45,60,90]) 1419 | print ('不同角度的正弦值:') 1420 | # 通过乘 pi/180 转化为弧度 1421 | print (np.sin(a*np.pi/180)) 1422 | print ('\n') 1423 | print ('数组中角度的余弦值:') 1424 | print (np.cos(a*np.pi/180)) 1425 | print ('\n') 1426 | print ('数组中角度的正切值:') 1427 | print (np.tan(a*np.pi/180)) 1428 | ``` 1429 | 1430 | numpy.arcsin,numpy.arccos,和 numpy.arctan 函数返回给定角度的 sin,cos 和 tan 的反三角函数 1431 | 1432 | **舍入函数** 1433 | 1434 | ​ numpy.around(a,decimals)四舍五入 1435 | 1436 | >a: 数组 1437 | > 1438 | >decimals: 舍入的小数位数。 默认值为0。 如果为负,整数将四舍五入到小数点左侧的位置(即 numpy.around(192.586,decimals = -1) 190) 1439 | 1440 | ​ numpy.floor()向下舍入 1441 | 1442 | ​ numpy.ceil()向上舍入 1443 | 1444 | ### 运算函数 1445 | 1446 | NumPy 算术函数包含简单的加减乘除: `numpy.add(),numpy.subtract(),numpy.multiply() 和 numpy.divide()` 1447 | 1448 | `numpy.reciprocal() `函数返回参数逐元素的倒数 1449 | 1450 | ```python 1451 | import numpy as np 1452 | 1453 | a = np.array([0.25, 1.33, 1, 100]) 1454 | print ('调用 reciprocal 函数:') 1455 | print (np.reciprocal(a)) 1456 | # 1457 | 调用 reciprocal 函数: 1458 | [4. 0.7518797 1. 0.01 ] 1459 | ``` 1460 | 1461 | `numpy.power() `函数将第一个输入数组中的元素作为底数,计算它与第二个输入数组中相应元素的幂。 1462 | 1463 | `numpy.mod()` 计算输入数组中相应元素的相除后的余数。 函数 numpy.remainder() 也产生相同的结果。 1464 | 1465 | ### 统计函数 1466 | 1467 | `numpy.amin(arr,axis = None)`用于计算数组中的元素沿指定轴的最小值。axis 默认为None,即将arr展开。 1468 | 1469 | `numpy.amax(arr,axis = None) `用于计算数组中的元素沿指定轴的最大值。用法与numpy.amin相似。 1470 | 1471 | numpy.ptp(arr,axis = None)函数计算数组中元素最大值与最小值的差(最大值 - 最小值)。 1472 | 1473 | `numpy.percentile(a,q,axis = None,keepdims = False)`百分位数是统计中使用的度量,表示小于这个值的观察值的百分比。 函数numpy.percentile()接受以下参数。 1474 | 1475 | > a:数组 1476 | > 1477 | > q:要计算的百分位数[0-100] 1478 | > 1479 | > axis:维度 1480 | > 1481 | > keepdims:保持原数组的dims 1482 | 1483 | `numpy.median(arr, axis = None)` 函数用于计算数组 a 中元素的中位数(中值) 1484 | 1485 | `numpy.mean(arr, axis = None) `函数返回数组中元素的算术平均值。 (算术平均值是沿轴的元素的总和除以元素的数量。) 1486 | 1487 | `numpy.average(arr, axis = None,weights = wts)` 函数根据在另一个数组中给出的各自的权重计算数组中元素的加权平均值。不指定权重默认为1,此时和mean结果相同 1488 | 1489 | ```python 1490 | import numpy as np 1491 | 1492 | a = np.array([1,2,3,4]) 1493 | print ('我们的数组是:') 1494 | print (a) 1495 | print ('\n') 1496 | print ('调用 average() 函数:') 1497 | print (np.average(a)) 1498 | print ('\n') 1499 | # 不指定权重时相当于 mean 函数 1500 | wts = np.array([4,3,2,1]) 1501 | print ('再次调用 average() 函数:') 1502 | print (np.average(a,weights = wts)) 1503 | print ('\n') 1504 | # 如果 returned 参数设为 true,则返回权重的和 1505 | print ('权重的和:') 1506 | print (np.average([1,2,3, 4],weights = [4,3,2,1], returned = True)) 1507 | ``` 1508 | 1509 | `numpy.std(arr,axis = None)`标准差,等价于 `sqrt(numpy.mean(arr-numpy.mean(arr,axis = None)**2))` 1510 | 1511 | `numpy.var(arr,axis = None)`方差,等价于`numpy.mean(arr-numpy.mean(arr,axis = None)**2)` 1512 | 1513 | ### NumPy排序 1514 | 1515 | `numpy.sort(ndarray,axis,kind,order) ` 1516 | 1517 | axis 默认为None这时数组会展开;kind 可以选quicksort(defult),mergesort,heapsort 1518 | 1519 | order如果数组包含字段则是要排序的字段 1520 | 1521 | ```python 1522 | import numpy as np 1523 | 1524 | dtp = [('name','S10'),('age',np.int8)] 1525 | a = np.array([("bob",12),('alis',13),('john',11)],dtype = dtp) 1526 | print(a) 1527 | print('sort by name:',np.sort(a,order = 'name')) 1528 | print('sort by age:',np.sort(a,kind = 'quicksort',order = 'age')) 1529 | # 1530 | [(b'bob', 12) (b'alis', 13) (b'john', 11)] 1531 | sort by name: [(b'alis', 13) (b'bob', 12) (b'john', 11)] 1532 | sort by age: [(b'john', 11) (b'bob', 12) (b'alis', 13)] 1533 | ``` 1534 | 1535 | `numpy.argsort(ndarray,axis = -1,kind = 'quicksort',order = None)`函数返回排序后的索引值。 1536 | 1537 | `numpy.lexsort(tuple)` 按照多个序列排序。返回排序后的索引值,可使用 for 遍历。 1538 | 1539 | `numpy.msort(ndarray)` = `numpy.sort(a,axis = 0)`按照第一个轴排序 1540 | 1541 | `sort_complex(ndarray)` 对复数数列按照先实部后虚部进行排序。 1542 | 1543 | `partition(ndarray, kth[, axis, kind, order])` 指定一个数对数组进行分区(即快速排序的步骤) 1544 | 1545 | kth接收tuple表示的区间,np.partition(arr,(1,3))即小于1的放最前面,[1,3)放中间,大于3的放最后。 1546 | 1547 | `argpartition(ndarray,kth[, axis, kind, order])` 返回partition结果中元素的索引 1548 | 1549 | `numpy.argmax()`返回给定轴最大元素的索引 1550 | 1551 | `numpy.argmin()`返回给定轴最小元素的索引 1552 | 1553 | `numpy.nonzero()`返回数组中非零元素的索引 1554 | 1555 | `numpy.where()`返回满足条件的元素的索引(参照布尔切片) 1556 | 1557 | `numpy.extract()`根据条件返回满足条件的元素 1558 | 1559 | ```python 1560 | import numpy as np 1561 | x = np.arange(9).reshape(3,3) 1562 | condition = np.mod(x,2) == 0#返回一个bool_numpy.ndarray 1563 | print(condition) 1564 | print(np.extract(condition,x))#print(x[x%2==0]) 1565 | # 1566 | [[ True False True] 1567 | [False True False] 1568 | [ True False True]] 1569 | [0 2 4 6 8] 1570 | ``` 1571 | 1572 | 1573 | 1574 | ### 矩阵库 1575 | 1576 | NumPy 中包含一个矩阵库 numpy.matlib,操作对象和返回对象都是矩阵,而不是ndarray。 1577 | 1578 | `numpy.matlib.empty(shape, dtype = np.float, order)`返回一个新的矩阵 1579 | 1580 | `numpy.matlib.zeros(shape, dtype = np.float, order)`返回一个初始值为0的新矩阵 1581 | 1582 | `numpy.matlib.ones(shape, dtype = np.float, order)`返回一个初始值为1的新矩阵 1583 | 1584 | `numpy.matlib.eye(n, M = n, k, dtype = np.float)`返回一个主对角线为1的对角矩阵。(k为对角线偏置,为0则为主对角线,小于0向下偏,大于0向上偏) 1585 | 1586 | `numpy.matlib.identity()`返回给定大小的单位阵。 1587 | 1588 | >`np.matlib.identity(5, dtype = np.float)`等价于`numpy.matlib.eye(5,k=0,dtype = np.float)` 1589 | 1590 | `numpy.matlib.rand(n,M,dtype = np.float)`返回一个n维矩阵,数值随机(值小于1大于0) 1591 | 1592 | matlib总是一个二维数组,其本质是ndarray。二者之间可以直接赋值或深拷贝。 1593 | 1594 | 1595 | 1596 | ### 线性代数 1597 | 1598 | NumPy内置了线性代数库linalg。 1599 | 1600 | | 函数 | 描述 | 1601 | | ------------- | -------------------------------- | 1602 | | `dot` | 两个数组的点积,即元素对应相乘。 | 1603 | | `vdot` | 两个向量的点积,高维数组自动降维 | 1604 | | `inner` | 两个数组的内积 | 1605 | | `matmul` | 两个数组的矩阵积 | 1606 | | `determinant` | 数组的行列式 | 1607 | | `solve` | 求解线性矩阵方程 | 1608 | | `inv` | 计算矩阵的乘法逆矩阵 | 1609 | 1610 | `numpy.dot(a, b, out = None)`两个向量的矢量积 1611 | 1612 | >a : ndarray 数组 1613 | > 1614 | >b : ndarray 数组 1615 | > 1616 | >out : ndarray, 可选,用来保存dot()的计算结果 1617 | > 1618 | >```python 1619 | >import numpy as np 1620 | >import numpy.linalg 1621 | >import numpy.matlib 1622 | >a = np.arange(1,7).reshape(3,2,order = 'F') 1623 | >b = np.arange(1,7).reshape(2,3,order = 'C') 1624 | >print(a,b,sep = '\n') 1625 | >n = np.dot(a,b) 1626 | >print(n) 1627 | >m = np.dot(b,a) 1628 | >print(m) 1629 | ># 1630 | >[[1 4] 1631 | > [2 5] 1632 | > [3 6]] 1633 | >[[1 2 3] 1634 | > [4 5 6]] 1635 | >[[17 22 27] 1636 | > [22 29 36] 1637 | > [27 36 45]] 1638 | >[[14 32] 1639 | > [32 77]] 1640 | >``` 1641 | 1642 | `numpy.vdot(a, b)`两个向量的数量积在计算高维数量积时,先把高维降到一维,然后进行数量积运算。 1643 | 1644 | `numpy.inner(ndarray)`一维ndarray内积运算,高维数组为对应维度上的内积运算。 1645 | 1646 | 对于多维数组相当: 1647 | 1648 | ```python 1649 | import numpy as np 1650 | a = np.arange(1,5).reshape(2,2) 1651 | b = np.arange(5,9).reshape(2,2) 1652 | print (a,b,np.inner(a,b),sep = '\n') 1653 | print(np.dot(a,b)) 1654 | # 1655 | [[1 2] 1656 | [3 4]] 1657 | [[5 6] 1658 | [7 8]] 1659 | inner() 1660 | [[17 23] 1661 | [39 53]] 1662 | dot() 1663 | [[19 22] 1664 | [43 50]] 1665 | inner运算类似于 1666 | [a11*b11+a12*b12,a11*b21+a12*b22] 1667 | [a21*b11+a22*b12,a21*b21+a22*b22] 1668 | ``` 1669 | 1670 | `numpy.matmul(a, b, out = None)`返回矩阵乘积,高于二维视为存在最后两个索引的矩阵的栈,若有一个为一维,则横向广播,然后再进行乘积,最后反横向广播。 1671 | 1672 | ```python 1673 | import numpy as np 1674 | a = np.array([[1,2],[3,4]]) 1675 | b = np.array([[2,2],[3,3]]) 1676 | c = np.array([2,3]) 1677 | print(np.matmul(a,c)) 1678 | print(np.matmul(a,b)) 1679 | # 1680 | [ 8 18] 1681 | [[ 8 8] 1682 | [18 18]] 1683 | ``` 1684 | 1685 | `numpy.linalg.det(ndarray)`计算输入矩阵的行列式的值。 1686 | 1687 | `numpy.linalg.solve(ndarray1,ndarray2)`计算增广矩阵的解。 1688 | 1689 | ```python 1690 | import numpy as np 1691 | import numpy.linalg 1692 | 1693 | a = np.array([2,2,-1,1,-2,4,5,8,-1]).reshape(3,3) 1694 | b = np.array([6,3,27]) 1695 | print(np.linalg.solve(a,b)) 1696 | # 1697 | [1. 3. 2.] 1698 | ``` 1699 | 1700 | 1701 | 1702 | `numpy.linalg.inv(ndarray)`计算可逆矩阵的逆矩阵。 1703 | 1704 | ```python 1705 | import numpy as np 1706 | import numpy.linalg 1707 | 1708 | print(np.linalg.inv(np.array([1,2,-1,-3]).reshape(2,2))) 1709 | # 1710 | [[ 3. 2.] 1711 | [-1. -1.]] 1712 | ``` 1713 | 1714 | ### 副本和视图 1715 | 1716 | 副本即拷贝,在内存中重新划出一块区域把源数据拷贝一份,修改副本内的内容源数据不会同时修改。 1717 | 1718 | 视图本身上还是源数据,只是改变了形状(shape),修改视图会导致源数据发生变化。reshape,ravel,切片返回的都是视图, 1719 | 1720 | **视图一般发生在:** 1721 | 1722 | - 1、numpy 的切片操作返回原数据的视图。 1723 | - 2、调用 ndarray 的 view() 函数产生一个视图。 1724 | 1725 | **副本一般发生在:** 1726 | 1727 | - Python 序列的切片操作,调用deepCopy()函数。 1728 | - 调用 ndarray 的 copy() 函数产生一个副本。 1729 | 1730 | 注意:对于numpy.ndarray对象来说,简单的赋值(a = b)不会引起产生副本。 1731 | 1732 | 修改视图的形状不会改变原数组的形状 1733 | -------------------------------------------------------------------------------- /Python 进阶笔记.md: -------------------------------------------------------------------------------- 1 | ```php 2 | title: Python进阶笔记 3 | date: 2019-03-06 21:23:56 4 | tags: 5 | - Python 6 | - advancement 7 | - noteBook 8 | toc: true 9 | form: https://github.com/LiuQixuan/PythonLearningNote 10 | info: Copyright©2019 AIUSoft.All Rights Reserved. 11 | ``` 12 | - [函数式编程](#函数式编程) 13 | - [面向对象](#面向对象) 14 | - [变量](#变量) 15 | - [方法(存储在内存中)](#方法存储在内存中) 16 | - [属性(@property def func(self))](#属性property-def-funcself) 17 | - [创建类的两种方法](#创建类的两种方法) 18 | - [使用枚举类(枚举类不可比较大小,但是同一枚举类的成员可以做等值is / not is判断)](#使用枚举类枚举类不可比较大小但是同一枚举类的成员可以做等值is--not-is判断) 19 | - [定义枚举类](#定义枚举类) 20 | - [高级面向对象](#高级面向对象) 21 | - [元类types](#元类types) 22 | - [元类type构造对象](#元类type构造对象) 23 | - [使用Slot限制添加属性](#使用slot限制添加属性) 24 | - [装饰器](#装饰器) 25 | - [python垃圾回收机制](#python垃圾回收机制) 26 | - [python 异常(Exception)、调试(Debug)、回溯(Traceback)](#python-异常exception调试debug回溯traceback) 27 | - [异常(Exception)](#异常exception) 28 | - [调试(Debug)](#调试debug) 29 | - [回溯(Traceback)](#回溯traceback) 30 | - [with 用法](#with-用法) 31 | - [f_string 处理](#f_string-处理) 32 | - [数组复制的5种方法](#数组复制的5种方法) 33 | 34 | 35 | Python 进阶学习笔记 当年学了点Python2 ,那时候听说什么Python3 失去了Python的灵魂,就没管Python3 。 最近因为项目需要使用Python3,又把当年的那份热血花在了Python3的学习上。 本以为Python3只是Python2 的升级会很好上手,没想到Python3相对于Python2而言有太多的新语法、新概念。 从小养成学习记笔记的好习惯,把学习遇到的重点写下来,第一日后复习用,其次如果有其他同学看到我的笔记,并感觉有用,也是算是我劳动成果的附加收益吧 36 | 37 | ## 函数式编程 38 | 39 | - 高阶函数(参数里有函数类型) 40 | >map/reduce(functools.reduce()) 41 | >filter 42 | >sorted 43 | - 返回函数(返回函数类型) 44 | - 匿名函数(`lambda`) 45 | - 装饰器(`@:有参装饰器、无参装饰器,functools.wraps(function)`) 46 | - 偏函数(`functools.partor(function[,args,**kwords]`) 47 | 48 | 49 | 50 | ## 面向对象 51 | ### 变量 52 | >1.静态字段 53 | >2.普通字段(self.value) 54 | 55 | ### 方法(存储在内存中) 56 | 57 | >1.普通方法def func(self[,args]) 58 | >2.类方法(@classmethod def func(cls[,args])) 59 | >3.静态方法(@staticmethod def func()) 60 | 61 | ### 属性(@property def func(self)) 62 | 63 | >1.静态字段 64 | >2.装饰类 65 | > 66 | >>经典装饰器 67 | >>新装饰器(使用方法模拟成员变量以及对成员变量的操作) 68 | >> 69 | >>方法一 70 | >>继承object 71 | >> 72 | >>```python 73 | >>@property object.funcName //user funcName to get the function return value like member value 74 | >>@funcName.setter object.func = value 75 | >>@funcName.deleter del object.funcName 76 | >>``` 77 | >>方法二 78 | >>创建property对象的静态字段 79 | >> 80 | >>```python 81 | >>pro = property(get_value(),set_value(),del_value(),__doc__) 82 | >>obj = Object() 83 | >>ov = obj.pro 84 | >>obj.pro = value 85 | >>del obj.pro 86 | >>obj.pro.__doc__ 87 | >>``` 88 | 89 | >类的特殊成员 90 | > 91 | >- `__doc__` 表示类的描述信息 92 | > 93 | >- `__name__`类名 94 | > 95 | >- `__bases__`类的所有父类构成的元组:tuple,不包含该类的type (类名即type类型使用`(my_class,)`可获得该类名的元组) 96 | > 97 | >- `__mro__`类的继承顺序包含类本身, 98 | > 99 | >- `__module__` 表示当前操作的对象在那个模块 packages.module 100 | > 101 | >- `__class__` 表示当前操作的对象的类 packages.module.class 102 | > 103 | >- `__init__()` 类的构造方法 104 | > 105 | >- `__del__()` 类的析构方法 106 | > 107 | >- `__call__()` 对象的方法 obj() self() 108 | > 109 | >- `__getattr__()`当访问obj不存在的属性时会调用该方法 110 | > 111 | > getattr(obj, name, value) ==> obj.name 不存在该属性返回value 112 | > 113 | > name是函数name()需要添加@property 描述符 114 | > 115 | >- `__dict__` 类或对象中的所有成员 116 | > 117 | >- `__str__` 将类格式化成字符串 print(obj) 时调用,即toString()方法 118 | > 119 | >- `__repr__` 将类格式化成字符串 print(obj) 时调用,和 `__str__` 的区别:更适合从编程语言上理解 120 | >> ```python 121 | >>print(obj) 122 | >> 123 | >>[output:] class_name.method(self, arg1, arg2, ...) 124 | >> ``` 125 | > 126 | >- `__getitem__` 用于索引操作[num],获取数据 127 | > 128 | >- `__setitem__` 用于索引操作[num],设置数据 129 | > 130 | >- `__delitem__` 用于索引操作[num],删除数据 131 | >>```python 132 | >> class mydict(object): 133 | >> def __getitem__(self, key): 134 | >> return self.key 135 | >> def __setitem__(self, key, value): 136 | >> self.key = value 137 | >> def __delitem__(self, key): 138 | >> del self.key 139 | >> obj = mydict() 140 | >> result = obj[key] 141 | >> obj['key'] = value 142 | >> del obj['key'] 143 | >>``` 144 | 145 | > **实现切片操作** 146 | > 147 | > >`__getitem__(self, n)`传入的参数n 可能是int也可能是slice 148 | > > 149 | > > ```python 150 | > > class Fib(object): 151 | > > def __getiter__(self, n): 152 | > > if isinstance(n, int): 153 | > > a, b = 1, 1 154 | > > for x in range(n): 155 | > > a, b = b, a + b 156 | > > return a 157 | > > if isinstanece(n, slice): 158 | > > l = [] 159 | > > slice.start = 0 if (slice.start is None) 160 | > > a, b = 1, 1 161 | > > for x in range(n.stop): 162 | > > if (x >= n.start and (x - n.start)%n.step == 0): 163 | > > l.append(a) 164 | > > a, b = b, a+b 165 | > > return l 166 | > > 167 | > > 168 | > > ``` 169 | > - `__getslice__` (self, i, j) obj[-1:1] 170 | > 171 | > - `__setslice__` (self, i, j, sequence) obj[0:1] = [11,22,33,44] 172 | > 173 | > - `__delslice__` (self, i, j) del obj[0:2] 174 | > 175 | > - `__iter__` 迭代器 176 | > 177 | > - `__next__` 配合`__iter__`实现类的interable属性 178 | > 179 | > - `__new__`方法`__new__(cls, name, bases, attrs)` 类准备将自身实例化时调用,在`__init__()`调用之前调用`__new__()`,该方法是一个类方法@classmethod 180 | > 181 | > cls:当前准备创建的类的对象:type 182 | > 183 | > name:类的名字:str 184 | > 185 | > bases:类继承的父类集合:class 186 | > 187 | > attrs:类的方法集合:dict 188 | > 189 | > - `__metaclass__` 该属性定义一个类的元类,即表示类该有哪个类(元类)来实例化 190 | ### 创建类的两种方法 191 | >1.普通方法 192 | >```python 193 | >class Object(object): 194 | > def func(self): 195 | > print('hello world!') 196 | >object = Object() 197 | >``` 198 | >2.特殊方法(元类type构造对象) 199 | > 200 | >```python 201 | >def func(self): 202 | > print('Hello world!') 203 | > object = type('Object', (object,), {'func':func}) 204 | >#arg1:str 类名 ;arg2:tuple 基类; arg3:dict 成员; 205 | >``` 206 | >***类的创建过程*** 207 | >![类的创建过程](file:///C:/Users/LiuQixuan/Desktop/pypro/type_class.png) 208 | >【CODE】 209 | > 210 | >```python 211 | >class MyType(type): 212 | > def __init__(self, what, bases=None, dict=None): 213 | > super(MyType, self).__init__(what, bases, dict) 214 | > 215 | > def __call__(self, *args, **kwargs): 216 | > obj = self.__new__(self, *args, **kwargs) 217 | > self.__init__(obj) 218 | >class Foo(object): 219 | > __metaclass__ = MyType 220 | > def __init__(self, name): 221 | > self.name = name 222 | > def __new__(cls, *args, **kwargs): 223 | > return object.__new__(cls, *args, **kwargs) 224 | ># 第一阶段:解释器从上到下执行代码创建Foo类 225 | ># 第二阶段:通过Foo类创建obj对象 226 | >obj = Foo() 227 | > 228 | >``` 229 | ### 使用枚举类(枚举类不可比较大小,但是同一枚举类的成员可以做等值is / not is判断) 230 | >```python 231 | >from enum import Enum 232 | >month = Enum('Mouth',('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')) 233 | >for name ,member in month.__members__.items(): 234 | > print(f'{name}=>{member}=>{member.value}') 235 | >``` 236 | ### 定义枚举类 237 | >```python 238 | >from enum import Enum, unique 239 | >@unique#unique装饰器帮助检查Enum内部字段,保证字段不重复 240 | >class My_weekday(Enum): 241 | > Sun = 0 242 | > Mon = 1 243 | > Tue = 2 244 | > Wed = 3 245 | > Thu = 4 246 | > Fri = 5 247 | > Sat = 6 248 | >my_weekday = My_weekday()#my_weekday.Sun 249 | >print(my_weekday.Sun)#my_weekday.Sun 250 | >print(my_weekday['Sun'])#my_weekday.Sun 251 | >print(my_weekday(0))#my_weekday.Sun 252 | >print(my_weekday.Sun.value)#0 253 | >for name, member in my_weekday.__members__.item(): 254 | > print(f'{name} {member} {member.value}') 255 | >【output】 256 | >""" 257 | >Sun My_weekday.Sun 0 258 | >Mon My_weekday.Mon 1 259 | >Tue My_weekday.Tue 2 260 | >Wed My_weekday.Wed 3 261 | >Thu My_weekday.Thu 4 262 | >Fri My_weekday.Fri 5 263 | >Sat My_weekday.Sat 6 264 | >""" 265 | >``` 266 | 267 | 268 | ## 高级面向对象 269 | 270 | ### 元类types 271 | >动态地给对象添加方法(MethodType(func[, object/None], Object)) 272 | >```python 273 | >from types import MethodType 274 | > 275 | >def func(self, value): 276 | > self.age = value 277 | >class Student(object): 278 | > def __init__(self,name): 279 | > self.name = name 280 | >student = Student('Bob') 281 | >student.age_setter = MethodType(age_setter,func) 282 | >student.age_setter(21) 283 | >print(f'{student.name}is {student.age} years old.') 284 | >``` 285 | >动态的给类添加方法 286 | >```python 287 | >def func(self, value): 288 | > self.age = value 289 | >class Student(object): 290 | > def __init__(self,name): 291 | > self.name = name 292 | >Student.age_setter = func 293 | >Student.age_setter = MethodType(func, None, Student) 294 | >student = Student('Bob') 295 | >student.age_setter(21) 296 | >print(f'{student.name}is {student.age} years old.') 297 | >``` 298 | ### 元类type构造对象 299 | >```python 300 | >def func(self): 301 | > print('Hello world!') 302 | >object = type('Object', (object,), {'func':func}) 303 | >#arg1:str 类名 ;arg2:tuple 基类; arg3:dict('a' = a, 'b' = b...) 类变量/静态字段; 304 | >``` 305 | ### 使用Slot限制添加属性 306 | >slot是一个特殊静态字段,tuple类型.slot只对当前类起作用,对其子类并不起作用 307 | >```python 308 | >class Student(object): 309 | > __slot__ = ('name', 'age') 310 | > def __init__(self, name, age): 311 | > super(Student,self).__init__() 312 | > self.name = name 313 | > self.age = age 314 | > @property 315 | > def name(self): 316 | > return self.name 317 | > 318 | > @name.setter 319 | > def name(self,name): 320 | > self.name = name 321 | > 322 | > @property 323 | > def age(self): 324 | > return self.age 325 | > 326 | > @age.setter 327 | > def age(self,name): 328 | > self.age = age 329 | >s1 = Student('Bill',21) 330 | >s1.score = 89 [ERROR]:不能添加score字段 331 | >``` 332 | 333 | ### 装饰器 334 | >一般装饰器(@decorate) 335 | 336 | >描述符装饰器 337 | > 338 | >>实现了```__get__() __set__() __del__()@property @func.setter @func.deleter``` 339 | >> 340 | >>数据描述符(实现了get set) 341 | >>非数据描述符(只实现get 没有实现set) 342 | 343 | ### python垃圾回收机制 344 | >- 引用计数器(当一个对象被引用机会增加其引用计数,当不被引用时减少引用计数,减少至0时在合适的时机下内存被回收) 345 | >- 循环垃圾收集器(针对循环引用) 346 | >- 手动内存回收 del obj.obj (将引用计数置零,只有最后引用该内存的对象释放后才执行析构函数__del__()) 347 | 348 | 349 | 350 | 351 | ## python 异常(Exception)、调试(Debug)、回溯(Traceback) 352 | ### 异常(Exception) 353 | > 354 | >```python 355 | > 356 | >try: 357 | > pass 358 | >except Exception as e: 359 | > raise 360 | >else: 361 | > pass 362 | >finally: 363 | > pass 364 | > 365 | >``` 366 | > 367 | >#### 常见的异常: 368 | > 369 | >>| 异常名称 |描述 | 370 | >>| ------------- | --------------- | 371 | >>| BaseException | 所有异常K的基类 | 372 | >>| SystemExit | 解释器请求退出 | 373 | >>| KeyboardInterrupt | 用户自行中断执行^C | 374 | >>| Exception | 常规错误的基类 | 375 | >>| StopIteration | 迭代器溢出 | 376 | >>| GeneratorExit | 生成器发生异常后通知退出 | 377 | >>| StandardError | 所有标准异常类的基类 | 378 | >>| ArithmeticError | 所有数值计算错误的基类 | 379 | >>| FloattingPointError | 浮点计算错误 | 380 | >>| OverflowError | 数值运算溢出 | 381 | >>| ZeroDivisionError | 除[,取模]by0 | 382 | >>| AssertionError | 断言语句失败 | 383 | >>| AttributeError | 对象缺失该属性 | 384 | >>| EOFError | 没有内建输入,到达EOF标记 | 385 | >>| EnvironmentError | 操作系统错误的基类 | 386 | >>| IOError | 输入/输出操作失败 | 387 | >>| OSError | 操作系统错误 | 388 | >>| WindowsError | 系统调用失败 | 389 | >>| ImportError | 导入模块/对象失败 | 390 | >>| LookupError | 无效数据查询的基类 | 391 | >>| IndexError | 序列中没有此索引 | 392 | >>| KeyError | 映射中没有此键 | 393 | >>| MemoryError | 内存溢出(对于Python解释起来说非致命) | 394 | >>| NameError | 未声明/初始化对象 | 395 | >>| UnboundLocalError | 访问未初始化的本地变量 | 396 | >>| ReferenceError | 试图访问已被回收器回收的对象(弱引用) | 397 | >>| RuntimeError | 一般运行时错误 | 398 | >>| NotImplementedError | 尚未实现的方法 | 399 | >>| SyntaxError | Python语法错误 | 400 | >>| IndentationError | 缩进错误 | 401 | >>| TabError | Tab和Space混用 | 402 | >>| SystemError | 一般的解释器系统错误 | 403 | >>| TypeError | 对类型无效的操作 | 404 | >>| ValueError | 传入无效的参数 | 405 | >>| UnicodeError | Unicode相关错误 | 406 | >>| UnicodeDecodeError | Unicode解码时的错误 | 407 | >>| UnicodeEncodeError | Unicode编码时的错误 | 408 | >>| UnicodeTranslateError | Unicode转码时的错误 | 409 | >>| Warning | 警告的基类 | 410 | >>| DeprecationWarning | 关于被弃用的特性的警告 | 411 | >>| FutureWarning | 关于构造将来语义会有改变的警告 | 412 | >>| OverflowWarning | 旧的关于自动提升为长整型(long)的警告 | 413 | >>| pendingDeprecationWarning | 关于特性将会被废弃的警告 | 414 | >>| RuntimeWarning | 可疑的运行时行为的警告 | 415 | >>| SysntaxWarning | 可疑语法的警告 | 416 | >>| UserWarning | 用户代码生成的警告 | 417 | >> 418 | > 419 | ### 调试(Debug) 420 | > 421 | >#### 单元测试 422 | > 423 | >>单元测试的类在unittest包中使用时直接导入包`import unittest`. 424 | >> 425 | >>```python 426 | >>import unittest 427 | >>class TestClass(unitest.TestCase): 428 | >> def setUp(self): 429 | >> pass 430 | >> def tearDown(self): 431 | >> pass 432 | >> def test_init(self): 433 | >> self.assertEqual() 434 | >> self.assertTrue() 435 | >> self.assertRaises() 436 | >> def test_func1(self): 437 | >> pass 438 | >> def test_func2(self): 439 | >> pass 440 | >>``` 441 | >#### 文档测试(doctest) 442 | >> 443 | >> Python的文档测试模块可以直接提取注释中的代码并执行 444 | >> 445 | >>```python 446 | >>class Dict(dict): 447 | >> ''' 448 | >> Simple dict but also support access as x.y style. 449 | >> 450 | >> >>> d1 = Dict() 451 | >> >>> d1['x'] = 100 452 | >> >>> d1.x 453 | >> 100 454 | >> >>> d1.y = 200 455 | >> >>> d1['y'] 456 | >> 200 457 | >> >>> d2 = Dict(a=1, b=2, c='3') 458 | >> >>> d2.c 459 | >> '3' 460 | >> >>> d2['empty'] 461 | >> Traceback (most recent call last): 462 | >> ... 463 | >> KeyError: 'empty' 464 | >> >>> d2.empty 465 | >> Traceback (most recent call last): 466 | >> ... 467 | >> AttributeError: 'Dict' object has no attribute 'empty' 468 | >> ''' 469 | >> def __init__(self, **kw): 470 | >> super(Dict, self).__init__(**kw) 471 | >> 472 | >> def __getattr__(self, key): 473 | >> try: 474 | >> return self[key] 475 | >> except KeyError: 476 | >> raise AttributeError(r"'Dict' object has no attribute '%s'" % key) 477 | >> 478 | >> def __setattr__(self, key, value): 479 | >> self[key] = value 480 | >> 481 | >>if __name__=='__main__': 482 | >> import doctest 483 | >> doctest.testmod() 484 | >>``` 485 | > 486 | > 487 | > 488 | ### 回溯(Traceback) 489 | >代码 490 | >以下代码功能相同 491 | >```python 492 | >import traceback 493 | > 494 | >traceback.print_exc() 495 | >traceback.format_exc() 496 | >traceback.print_exception(*ss.exc_info()) 497 | > 498 | >``` 499 | ## with 用法 500 | >事前事后用with(前后文管理器) 501 | >```python 502 | >【version 1.0】 503 | >file = open("/src/tmp.txt") 504 | >data = file.read() 505 | >file.close() 506 | >#可能会忘记关闭句柄 507 | >#可能会出现异常 508 | >【version 2.0】 509 | >file = open("/src/tmp.txt") 510 | >try: 511 | > data = file.read() 512 | >finally: 513 | > file.close() 514 | >#总体结构安全性都有提升 515 | >【version 3.0 with】 516 | >with open("/src/tmp.txt") as file: 517 | > data = file.read() 518 | >``` 519 | > 520 | >with 对处理对象需要自定义`__enter__() __exit__()`方法 521 | > 522 | >```python 523 | >class Sample: 524 | >def __enter__(self): 525 | > print('function:enter') 526 | > return "SAMPLE" 527 | > def __exit__(self, type, value, trace): 528 | > print('function:exit') 529 | >def get_sample(): 530 | > return Sample() 531 | > 532 | >with get_sample() as sample: 533 | > print(f'{sample}do somethings') 534 | > 535 | > 536 | >[result:] 537 | >function:enter 538 | >SAMPLE do somethings 539 | >function:exit 540 | > 541 | >class Sample: 542 | >def __init__(self, value): 543 | > self.num = value 544 | >def __enter__(self): 545 | > print('function:enter') 546 | > return self 547 | > def __exit__(self, type, value, trace): 548 | > print('function:exit') 549 | > print('m_type:{type}\tm_value:{value}\ttrace:{trace}) 550 | > def func(self, value): 551 | > return self.num/value 552 | >def get_sample(num): 553 | >return Sample(num) 554 | > 555 | >with get_sample() as sample: 556 | > print(f'{sample}do somethings') 557 | > num = input("input a number:") 558 | > sample.func(num) 559 | > 560 | > 561 | >#执行顺序: 562 | >#with 后面的函数/或type对象使用类的__init__()创建出一个对象,然后调用__enter__()方法将返回值赋给as 后的变量。在with执行完毕后调用类中的__exit__()方法,清理或关闭句柄。 563 | >``` 564 | > 565 | >- `open`(*file*, *mode='r'*, *buffering=-1*, *encoding=None*, *errors=None*, *newline=None*, *closefd=True*, *opener=None*) 566 | > 567 | ## f_string 处理 568 | `__str__,__repr__` 569 | > 570 | >- `__str __()`和`__repr __()`方法处理对象如何呈现为字符串,因此您需要确保在类定义中包含至少一个这些方法。如果必须选择一个,请使用`__repr __()`,因为它可以代替`__str __()`。 571 | >- `__str __()`返回的字符串是对象的非正式字符串表示,应该可读。`__repr __()`返回的字符串是官方表示,应该是明确的。调用`str()`和`repr()`比直接使用`__str __()`和`__repr __()`更好。 572 | >- 默认情况下,f字符串将使用`__str __()`,但如果包含转换标志`!r`,则可以确保它们使用`__repr __()`。 573 | > 574 | >```python 575 | >f"{new_comedian}" 576 | >'This __str__' 577 | >f"{new_comedian!r}" 578 | >'This __repr__' 579 | >``` 580 | > 581 | 582 | ## 数组复制的5种方法 583 | 584 | python中简单将一个数组赋值给另一个数组,实际上是两个引用指向同一块内存,可能无法达到预期目的.因此这里整理了几种数组拷贝复制的方法. 585 | 586 | >1. 切片 587 | >``` python 588 | >newArr = oldArr[:] 589 | >``` 590 | > 591 | >2. list() 592 | >``` python 593 | >newArr = list(oldArr) 594 | >``` 595 | >3. Arr*1 596 | >```python 597 | >newArr = oldArr*1 598 | >``` 599 | >4. copy.copy()浅拷贝方法 600 | >```python 601 | >newArr = copy.copy(oldArr) 602 | >newArr = oldArr.copy() 603 | >``` 604 | >5. copy.deepcopy()深拷贝方法 605 | >```python 606 | >newArr = copy.deepcopy(oldArr) 607 | >``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PythonLearningNote 2 | #Python 进阶学习笔记 3 | 当年学了点Python2 ,那时候听说什么Python3 失去了Python的灵魂,就没管Python3 。 4 | 最近因为项目需要使用Python3,又把当年的那份热血花在了Python3的学习上。 本以为Python3只是Python2 的升级会很好上手,没想到Python3相对于Python2而言有太多的新语法、新概念。 5 | 从小养成学习记笔记的好习惯,把学习遇到的重点写下来,第一日后复习用,其次如果有其他同学看到我的笔记,并感觉有用,也是算是我劳动成果的附加收益吧 6 | 7 | **推荐大家看<<流畅的Python>>一书,对于一些让人疑惑不解的地方讲解很透彻.** 8 | 9 | ## 同步更新到[Gitpage](https://liuqixuan.github.io/) 10 | -------------------------------------------------------------------------------- /SVM 原理详解.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SVM原理详解 3 | date: 2019-3-19 17:57:14 4 | tags: 5 | - python 6 | - MachineLearning 7 | toc: 8 | ture 9 | --- 10 | 11 | 12 | 13 | 14 | 15 | # SVM 原理详解 16 | 17 |
转自:http://www.blogjava.net/zhenandaci/category/31868.html 
18 | 19 | **(一)SVM的简介** 20 | 21 | 支持向量机(Support Vector Machine)是Cortes和Vapnik于1995年首先提出的,它在解决小样本、非线性及高维模式识别中表现出许多特有的优势,并能够推广应用到函数拟合等其他机器学习问题中[10]。 22 | 支持向量机方法是建立在统计学习理论的VC 维理论和结构风险最小原理基础上的,根据有限的样本信息在模型的复杂性(即对特定训练样本的学习精度,Accuracy)和学习能力(即无错误地识别任意样本的能力)之间寻求最佳折衷,以期获得最好的推广能力[14](或称泛化能力)。 23 | 24 | 以上是经常被有关SVM 的学术文献引用的介绍,我来逐一分解并解释一下。 25 | 26 | 27 | 28 | Vapnik是统计机器学习的大牛,这想必都不用说,他出版的《Statistical Learning Theory》是一本完整阐述统计机器学习思想的名著。在该书中详细的论证了统计机器学习之所以区别于传统机器学习的本质,就在于统计机器学习能够精确的给出学习效果,能够解答需要的样本数等等一系列问题。与统计机器学习的精密思维相比,传统的机器学习基本上属于摸着石头过河,用传统的机器学习方法构造分类系统完全成了一种技巧,一个人做的结果可能很好,另一个人差不多的方法做出来却很差,缺乏指导和原则。 29 | 30 | 所谓VC维是对函数类的一种度量,可以简单的理解为问题的复杂程度,VC维越高,一个问题就越复杂。正是因为SVM关注的是VC维,后面我们可以看到,SVM解决问题的时候,和样本的维数是无关的(甚至样本是上万维的都可以,这使得SVM很适合用来解决文本分类的问题,当然,有这样的能力也因为引入了核函数)。 31 | 32 | 结构风险最小听上去文绉绉,其实说的也无非是下面这回事。 33 | 34 | 机器学习本质上就是一种对问题真实模型的逼近(我们选择一个我们认为比较好的近似模型,这个近似模型就叫做一个假设),但毫无疑问,真实模型一定是不知道的(如果知道了,我们干吗还要机器学习?直接用真实模型解决问题不就可以了?对吧,哈哈)既然真实模型不知道,那么我们选择的假设与问题真实解之间究竟有多大差距,我们就没法得知。比如说我们认为宇宙诞生于150亿年前的一场大爆炸,这个假设能够描述很多我们观察到的现象,但它与真实的宇宙模型之间还相差多少?谁也说不清,因为我们压根就不知道真实的宇宙模型到底是什么。 35 | 36 | 这个与问题真实解之间的误差,就叫做风险(更严格的说,误差的累积叫做风险)。我们选择了一个假设之后(更直观点说,我们得到了一个分类器以后),真实误差无从得知,但我们可以用某些可以掌握的量来逼近它。最直观的想法就是使用分类器在样本数据上的分类的结果与真实结果(因为样本是已经标注过的数据,是准确的数据)之间的差值来表示。这个差值叫做经验风险Remp(w)。以前的机器学习方法都把经验风险最小化作为努力的目标,但后来发现很多分类函数能够在样本集上轻易达到100%的正确率,在真实分类时却一塌糊涂(即所谓的推广能力差,或泛化能力差)。此时的情况便是选择了一个足够复杂的分类函数(它的VC维很高),能够精确的记住每一个样本,但对样本之外的数据一律分类错误。回头看看经验风险最小化原则我们就会发现,此原则适用的大前提是经验风险要确实能够逼近真实风险才行(行话叫一致),但实际上能逼近么?答案是不能,因为样本数相对于现实世界要分类的文本数来说简直九牛一毛,经验风险最小化原则只在这占很小比例的样本上做到没有误差,当然不能保证在更大比例的真实文本上也没有误差。 37 | 38 | 统计学习因此而引入了泛化误差界的概念,就是指真实风险应该由两部分内容刻画,一是经验风险,代表了分类器在给定样本上的误差;二是置信风险,代表了我们在多大程度上可以信任分类器在未知文本上分类的结果。很显然,第二部分是没有办法精确计算的,因此只能给出一个估计的区间,也使得整个误差只能计算上界,而无法计算准确的值(所以叫做泛化误差界,而不叫泛化误差)。 39 | 40 | 置信风险与两个量有关,一是样本数量,显然给定的样本数量越大,我们的学习结果越有可能正确,此时置信风险越小;二是分类函数的VC维,显然VC维越大,推广能力越差,置信风险会变大。 41 | 42 | 泛化误差界的公式为: 43 | 44 | R(w)≤Remp(w)+Ф(n/h) 45 | 46 | 公式中R(w)就是真实风险,Remp(w)就是经验风险,Ф(n/h)就是置信风险。统计学习的目标从经验风险最小化变为了寻求经验风险与置信风险的和最小,即结构风险最小。 47 | 48 | SVM正是这样一种努力最小化结构风险的算法。 49 | 50 | SVM其他的特点就比较容易理解了。 51 | 52 | 小样本,并不是说样本的绝对数量少(实际上,对任何算法来说,更多的样本几乎总是能带来更好的效果),而是说与问题的复杂度比起来,SVM算法要求的样本数是相对比较少的。 53 | 54 | 非线性,是指SVM擅长应付样本数据线性不可分的情况,主要通过松弛变量(也有人叫惩罚变量)和核函数技术来实现,这一部分是SVM的精髓,以后会详细讨论。多说一句,关于文本分类这个问题究竟是不是线性可分的,尚没有定论,因此不能简单的认为它是线性可分的而作简化处理,在水落石出之前,只好先当它是线性不可分的(反正线性可分也不过是线性不可分的一种特例而已,我们向来不怕方法过于通用)。 55 | 56 | 高维模式识别是指样本维数很高,例如文本的向量表示,如果没有经过另一系列文章([《文本分类入门》](https://www.baidu.com/s?wd=%E3%80%8A%E6%96%87%E6%9C%AC%E5%88%86%E7%B1%BB%E5%85%A5%E9%97%A8%E3%80%8B&tn=24004469_oem_dg&rsv_dl=gh_pl_sl_csd))中提到过的降维处理,出现几万维的情况很正常,其他算法基本就没有能力应付了,SVM却可以,主要是因为SVM 产生的分类器很简洁,用到的样本信息很少(仅仅用到那些称之为“支持向量”的样本,此为后话),使得即使样本维数很高,也不会给存储和计算带来大麻烦(相对照而言,kNN算法在分类时就要用到所有样本,样本数巨大,每个样本维数再一高,这日子就没法过了……)。 57 | 58 | 下一节开始正式讨论SVM。别嫌我说得太详细哦。 59 | 60 | **SVM入门(二)线性分类器Part 1** 61 | 62 | 线性分类器(一定意义上,也可以叫做感知机) 是最简单也很有效的分类器形式.在一个线性分类器中,可以看到SVM形成的思路,并接触很多SVM的核心概念. 63 | 64 | 用一个二维空间里仅有两类样本的分类问题来举个小例子。如图所示 65 | 66 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/hi_8CA8/clip_image002_2.gif) 67 | 68 | C1和C2是要区分的两个类别,在二维平面中它们的样本如上图所示。中间的直线就是一个分类函数,它可以将两类样本完全分开。一般的,如果一个线性函数能够将样本完全正确的分开,就称这些数据是线性可分的,否则称为非线性可分的。 69 | 70 | 什么叫线性函数呢?在一维空间里就是一个点,在二维空间里就是一条直线,三维空间里就是一个平面,可以如此想象下去,如果不关注空间的维数,这种线性函数还有一个统一的名称——超平面(Hyper Plane)! 71 | 72 | 实际上,一个线性函数是一个实值函数(即函数的值是连续的实数),而我们的分类问题(例如这里的二元分类问题——回答一个样本属于还是不属于一个类别的问题)需要离散的输出值,例如用1表示某个样本属于类别C1,而用0表示不属于(不属于C1也就意味着属于C2),这时候只需要简单的在实值函数的基础上附加一个阈值即可,通过分类函数执行时得到的值大于还是小于这个阈值来确定类别归属。 例如我们有一个线性函数 73 | 74 | g(x)=wx+b 75 | 76 | 【看到好多人都在问g(x)=0 和 g(x)的问题,我在这里帮楼主补充一下:g(x)实际是以w为法向量的一簇超平面,在二维空间表示为一簇直线(就是一簇平行线,他们的法向量都是w),而g(x)=0只是这么多平行线中的一条。】 77 | 78 | 我们可以取阈值为0,这样当有一个样本xi需要判别的时候,我们就看g(xi)的值。若g(xi)>0,就判别为类别C1,若g(xi)<0,则判别为类别C2(等于的时候我们就拒绝判断,呵呵)。此时也等价于给函数g(x)附加一个符号函数sgn(),即f(x)=sgn [g(x)]是我们真正的判别函数。 79 | 80 | 关于g(x)=wx+b这个表达式要注意三点:一,式中的x不是二维坐标系中的横轴,而是样本的向量表示,例如一个样本点的坐标是(3,8),则xT=(3,8) ,而不是x=3(一般说向量都是说列向量,因此以行向量形式来表示时,就加上转置)。二,这个形式并不局限于二维的情况,在n维空间中仍然可以使用这个表达式,只是式中的w成为了n维向量(在二维的这个例子中,w是二维向量,为了表示起来方便简洁,以下均不区别列向量和它的转置,聪明的读者一看便知);三,g(x)不是中间那条直线的表达式,中间那条直线的表达式是g(x)=0,即wx+b=0,我们也把这个函数叫做分类面。 81 | 82 | 实际上很容易看出来,中间那条分界线并不是唯一的,我们把它稍微旋转一下,只要不把两类数据分错,仍然可以达到上面说的效果,稍微平移一下,也可以。此时就牵涉到一个问题,对同一个问题存在多个分类函数的时候,哪一个函数更好呢?显然必须要先找一个指标来量化“好”的程度,通常使用的都是叫做“分类间隔”的指标。下一节我们就仔细说说分类间隔,也补一补相关的数学知识。 83 | 84 | **SVM入门(三)线性分类器Part 2** 85 | 86 | 上回说到对于文本分类这样的不适定问题(有一个以上解的问题称为不适定问题),需要有一个指标来衡量解决方案(即我们通过训练建立的分类模型)的好坏,而分类间隔是一个比较好的指标。 87 | 88 | 在进行文本分类的时候,我们可以让计算机这样来看待我们提供给它的训练样本,每一个样本由一个向量(就是那些文本特征所组成的向量)和一个标记(标示出这个样本属于哪个类别)组成。如下: 89 | 90 | Di=(xi,yi) 91 | 92 | xi就是文本向量(维数很高),yi就是分类标记。 93 | 94 | 在二元的线性分类中,这个表示分类的标记只有两个值,1和-1(用来表示属于还是不属于这个类)。有了这种表示法,我们就可以定义一个样本点到某个超平面的间隔: 95 | 96 | δi=yi(wxi+b) 97 | 98 | 这个公式乍一看没什么神秘的,也说不出什么道理,只是个定义而已,但我们做做变换,就能看出一些有意思的东西。 99 | 100 | 首先注意到如果某个样本属于该类别的话,那么wxi+b>0(记得么?这是因为我们所选的g(x)=wx+b就通过大于0还是小于0来判断分类),而yi也大于0;若不属于该类别的话,那么wxi+b<0,而yi也小于0,这意味着yi(wxi+b)总是大于0的,而且它的值就等于|wxi+b|!(也就是|g(xi)|) 101 | 102 | 现在把w和b进行一下归一化,即用w/||w||和b/||w||分别代替原来的w和b,那么间隔就可以写成 103 | 104 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMPart2_C019/clip_image002%5B28%5D.gif) 105 | 106 | 【点到直线的距离,做解析几何中为: 107 | D = (Ax + By + c) /sqrt(A^2+B^2) 108 | sqrt(A^2+B^2)就相当于||W||, 其中向量W=[A, B]; 109 | (Ax + By + c)就相当于g(X), 其中向量X=[x,y]。】 110 | 111 | 这个公式是不是看上去有点眼熟?没错,这不就是解析几何中点xi到直线g(x)=0的距离公式嘛!(推广一下,是到超平面g(x)=0的距离, g(x)=0就是上节中提到的分类超平面) 112 | 113 | 小Tips:||w||是什么符号?||w||叫做向量w的范数,范数是对向量长度的一种度量。我们常说的向量长度其实指的是它的2-范数,范数最一般的表示形式为p-范数,可以写成如下表达式 114 | 115 | ​ 向量w=(w1, w2, w3,…… wn) 116 | 117 | 它的p-范数为 118 | 119 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMPart2_C019/clip_image004%5B10%5D.gif) 120 | 121 | 122 | 123 | 看看把p换成2的时候,不就是传统的向量长度么?当我们不指明p的时候,就像||w||这样使用时,就意味着我们不关心p的值,用几范数都可以;或者上文已经提到了p的值,为了叙述方便不再重复指明。 124 | 125 | 当用归一化的w和b代替原值之后的间隔有一个专门的名称,叫做几何间隔,几何间隔所表示的正是点到超平面的欧氏距离,我们下面就简称几何间隔为“距离”。以上是单个点到某个超平面的距离(就是间隔,后面不再区别这两个词)定义,同样可以定义一个点的集合(就是一组样本)到某个超平面的距离为此集合中离超平面最近的点的距离。下面这张图更加直观的展示出了几何间隔的现实含义: 126 | 127 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMRefresh_9B92/image_2.png) 128 | 129 | H是分类面,而H1和H2是平行于H,且过离H最近的两类样本的直线,H1与H,H2与H之间的距离就是几何间隔。 130 | 131 | 之所以如此关心几何间隔这个东西,是因为几何间隔与样本的误分次数间存在关系: 132 | 133 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMPart2_C019/clip_image012_2.gif) 134 | 135 | 其中的δ是样本集合到分类面的间隔,R=max ||xi|| i=1,...,n,即R是所有样本中(xi是以向量表示的第i个样本)向量长度最长的值(也就是说代表样本的分布有多么广)。先不必追究误分次数的具体定义和推导过程,只要记得这个误分次数一定程度上代表分类器的误差。而从上式可以看出,误分次数的上界由几何间隔决定!(当然,是样本已知的时候) 136 | 137 | 至此我们就明白为何要选择几何间隔来作为评价一个解优劣的指标了,原来几何间隔越大的解,它的误差上界越小。因此最大化几何间隔成了我们训练阶段的目标,而且,与二把刀作者所写的不同,最大化分类间隔并不是SVM的专利,而是早在线性分类时期就已有的思想。 138 | 139 | **SVM入门(四)线性分类器的求解——问题的描述Part1** 140 | 141 | 上节说到我们有了一个线性分类函数,也有了判断解优劣的标准——即有了优化的目标,这个目标就是最大化几何间隔,但是看过一些关于SVM的论文的人一定记得什么优化的目标是要最小化||w||这样的说法,这是怎么回事呢?回头再看看我们对间隔和几何间隔的定义: 142 | 143 | 间隔:δ=y(wx+b)=|g(x)| 144 | 145 | 几何间隔:![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMPart1_EEC8/clip_image002_2.gif) 146 | 147 | 可以看出δ=||w||δ几何。注意到几何间隔与||w||是成反比的,因此最大化几何间隔与最小化||w||完全是一回事。而我们常用的方法并不是固定||w||的大小而寻求最大几何间隔,而是固定间隔(例如固定为1),寻找最小的||w||。 148 | 149 | 而凡是求一个函数的最小值(或最大值)的问题都可以称为寻优问题(也叫作一个规划问题),又由于找最大值的问题总可以通过加一个负号变为找最小值的问题,因此我们下面讨论的时候都针对找最小值的过程来进行。一个寻优问题最重要的部分是目标函数,顾名思义,就是指寻优的目标。例如我们想寻找最小的||w||这件事,就可以用下面的式子表示: 150 | 151 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMPart1_EEC8/clip_image002%5B4%5D.gif) 152 | 153 | 但实际上对于这个目标,我们常常使用另一个完全等价的目标函数来代替,那就是: 154 | 155 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMPart1_EEC8/clip_image002%5B6%5D.gif)(式1) 156 | 157 | 不难看出当||w||2达到最小时,||w||也达到最小,反之亦然(前提当然是||w||描述的是向量的长度,因而是非负的)。之所以采用这种形式,是因为后面的求解过程会对目标函数作一系列变换,而式(1)的形式会使变换后的形式更为简洁(正如聪明的读者所料,添加的系数二分之一和平方,皆是为求导数所需)。 158 | 159 | 接下来我们自然会问的就是,这个式子是否就描述了我们的问题呢?(回想一下,我们的问题是有一堆点,可以被分成两类,我们要找出最好的分类面) 160 | 161 | 如果直接来解这个求最小值问题,很容易看出当||w||=0的时候就得到了目标函数的最小值。但是你也会发现,无论你给什么样的数据,都是这个解!反映在图中,就是H1与H2两条直线间的距离无限大,这个时候,所有的样本点(无论正样本还是负样本)都跑到了H1和H2中间,而我们原本的意图是,H1右侧的被分为正类,H2 左侧的被分为负类,位于两类中间的样本则拒绝分类(拒绝分类的另一种理解是分给哪一类都有道理,因而分给哪一类也都没有道理)。这下可好,所有样本点都进入了无法分类的灰色地带。 162 | 163 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMPart1_EEC8/clip_image002%5B8%5D.gif) 164 | 165 | 造成这种结果的原因是在描述问题的时候只考虑了目标,而没有加入约束条件,约束条件就是在求解过程中必须满足的条件,体现在我们的问题中就是样本点必须在H1或H2的某一侧(或者至少在H1和H2上),而不能跑到两者中间。我们前文提到过把间隔固定为1,这是指把所有样本点中间隔最小的那一点的间隔定为1(这也是集合的间隔的定义,有点绕嘴),也就意味着集合中的其他点间隔都不会小于1,按照间隔的定义,满足这些条件就相当于让下面的式子总是成立: 166 | 167 | ​ yi[(w·xi)+b]≥1 (i=1,2,…,l) (l是总的样本数) 168 | 169 | 但我们常常习惯让式子的值和0比较,因而经常用变换过的形式: 170 | 171 | ​ yi[(w·xi)+b]-1≥0 (i=1,2,…,l) (l是总的样本数) 172 | 173 | 因此我们的两类分类问题也被我们转化成了它的数学形式,一个带约束的最小值的问题: 174 | 175 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMPart1_EEC8/clip_image002%5B10%5D.gif) 176 | 177 | 下一节我们从最一般的意义上看看一个求最小值的问题有何特征,以及如何来解。 178 | 179 | **SVM入门(五)线性分类器的求解——问题的描述Part2** 180 | 181 | 从最一般的定义上说,一个求最小值的问题就是一个优化问题(也叫寻优问题,更文绉绉的叫法是规划——Programming),它同样由两部分组成,目标函数和约束条件,可以用下面的式子表示: 182 | 183 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMPart2_1603/clip_image002_2.gif)(式1) 184 | 185 | 约束条件用函数c来表示,就是constrain的意思啦。你可以看出一共有p+q个约束条件,其中p个是不等式约束,q个等式约束。 186 | 187 | 关于这个式子可以这样来理解:式中的x是自变量,但不限定它的维数必须为1(视乎你解决的问题空间维数,对我们的文本分类来说,那可是成千上万啊)。要求f(x)在哪一点上取得最小值(反倒不太关心这个最小值到底是多少,关键是哪一点),但不是在整个空间里找,而是在约束条件所划定的一个有限的空间里找,这个有限的空间就是优化理论里所说的可行域。注意可行域中的每一个点都要求满足所有p+q个条件,而不是满足其中一条或几条就可以(切记,要满足每个约束),同时可行域边界上的点有一个额外好的特性,它们可以使不等式约束取得等号!而边界内的点不行。 188 | 189 | 关于可行域还有个概念不得不提,那就是凸集,凸集是指有这么一个点的集合,其中任取两个点连一条直线,这条线上的点仍然在这个集合内部,因此说“凸”是很形象的(一个反例是,二维平面上,一个月牙形的区域就不是凸集,你随便就可以找到两个点违反了刚才的规定)。 190 | 191 | 回头再来看我们线性分类器问题的描述,可以看出更多的东西。 192 | 193 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMPart2_1603/clip_image002%5B5%5D.gif)(式2) 194 | 195 | 在这个问题中,自变量就是w,而目标函数是w的二次函数,所有的约束条件都是w的线性函数(哎,千万不要把xi当成变量,它代表样本,是已知的),这种规划问题有个很有名气的称呼——二次规划(Quadratic Programming,QP),而且可以更进一步的说,由于它的可行域是一个凸集,因此它是一个凸二次规划。 196 | 197 | 一下子提了这么多术语,实在不是为了让大家以后能向别人炫耀学识的渊博,这其实是我们继续下去的一个重要前提,因为在动手求一个问题的解之前(好吧,我承认,是动计算机求……),我们必须先问自己:这个问题是不是有解?如果有解,是否能找到? 198 | 199 | 对于一般意义上的规划问题,两个问题的答案都是不一定,但凸二次规划让人喜欢的地方就在于,它有解(教科书里面为了严谨,常常加限定成分,说它有全局最优解,由于我们想找的本来就是全局最优的解,所以不加也罢),而且可以找到!(当然,依据你使用的算法不同,找到这个解的速度,行话叫收敛速度,会有所不同) 200 | 201 | 对比(式2)和(式1)还可以发现,我们的线性分类器问题只有不等式约束,因此形式上看似乎比一般意义上的规划问题要简单,但解起来却并非如此。 202 | 203 | 因为我们实际上并不知道该怎么解一个带约束的优化问题。如果你仔细回忆一下高等数学的知识,会记得我们可以轻松的解一个不带任何约束的优化问题(实际上就是当年背得烂熟的函数求极值嘛,求导再找0点呗,谁不会啊?笑),我们甚至还会解一个只带等式约束的优化问题,也是背得烂熟的,求条件极值,记得么,通过添加拉格朗日乘子,构造拉格朗日函数,来把这个问题转化为无约束的优化问题云云(如果你一时没想通,我提醒一下,构造出的拉格朗日函数就是转化之后的问题形式,它显然没有带任何条件)。 204 | 205 | 读者问:如果只带等式约束的问题可以转化为无约束的问题而得以求解,那么可不可以把带不等式约束的问题向只带等式约束的问题转化一下而得以求解呢? 206 | 207 | 聪明,可以,实际上我们也正是这么做的。下一节就来说说如何做这个转化,一旦转化完成,求解对任何学过高等数学的人来说,都是小菜一碟啦。 208 | 209 | **SVM入门(六)线性分类器的求解——问题的转化,直观角度** 210 | 211 | 让我再一次比较完整的重复一下我们要解决的问题:我们有属于两个类别的样本点(并不限定这些点在二维空间中)若干,如图, 212 | 213 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_1244A/image_2.png) 214 | 215 | 圆形的样本点定为正样本(连带着,我们可以把正样本所属的类叫做正类),方形的点定为负例。我们想求得这样一个线性函数(在n维空间中的线性函数): 216 | 217 | g(x)=wx+b 218 | 219 | 使得所有属于正类的点+代入以后有g(x+)≥1,而所有属于负类的点x-代入后有g(x-)≤-1(之所以总跟1比较,无论正一还是负一,都是因为我们固定了间隔为1,注意间隔和几何间隔的区别)。代入g(x)后的值如果在1和-1之间,我们就拒绝判断。 220 | 221 | 求这样的g(x)的过程就是求w(一个n维向量)和b(一个实数)两个参数的过程(但实际上只需要求w,求得以后找某些样本点代入就可以求得b)。因此在求g(x)的时候,w才是变量。 222 | 223 | 你肯定能看出来,一旦求出了w(也就求出了b),那么中间的直线H就知道了(因为它就是wx+b=0嘛,哈哈),那么H1和H2也就知道了(因为三者是平行的,而且相隔的距离还是||w||决定的)。那么w是谁决定的?显然是你给的样本决定的,一旦你在空间中给出了那些个样本点,三条直线的位置实际上就唯一确定了(因为我们求的是最优的那三条,当然是唯一的),我们解优化问题的过程也只不过是把这个确定了的东西算出来而已。 224 | 225 | 样本确定了w,用数学的语言描述,就是w可以表示为样本的某种组合: 226 | 227 | w=α1x1+α2x2+…+αnxn 228 | 229 | 式子中的αi是一个一个的数(在严格的证明过程中,这些α被称为拉格朗日乘子),而xi是样本点,因而是向量,n就是总样本点的个数。为了方便描述,以下开始严格区别数字与向量的乘积和向量间的乘积,我会用α1x1表示数字和向量的乘积,而用表示向量x1,x2的内积(也叫点积,注意与向量叉积的区别)。因此g(x)的表达式严格的形式应该是: 230 | 231 | g(x)=+b 232 | 233 | 但是上面的式子还不够好,你回头看看图中正样本和负样本的位置,想像一下,我不动所有点的位置,而只是把其中一个正样本点定为负样本点(也就是把一个点的形状从圆形变为方形),结果怎么样?三条直线都必须移动(因为对这三条直线的要求是必须把方形和圆形的点正确分开)!这说明w不仅跟样本点的位置有关,还跟样本的类别有关(也就是和样本的“标签”有关)。因此用下面这个式子表示才算完整: 234 | 235 | w=α1y1x1+α2y2x2+…+αnynxn (式1) 236 | 237 | 其中的yi就是第i个样本的标签,它等于1或者-1。其实以上式子的那一堆拉格朗日乘子中,只有很少的一部分不等于0(不等于0才对w起决定作用),这部分不等于0的拉格朗日乘子后面所乘的样本点,其实都落在H1和H2上,也正是这部分样本(而不需要全部样本)唯一的确定了分类函数,当然,更严格的说,这些样本的一部分就可以确定,因为例如确定一条直线,只需要两个点就可以,即便有三五个都落在上面,我们也不是全都需要。这部分我们真正需要的样本点,就叫做支持(撑)向量!(名字还挺形象吧,他们“撑”起了分界线) 238 | 239 | 式子也可以用求和符号简写一下: 240 | 241 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_1244A/clip_image002_2.gif) 242 | 243 | 因此原来的g(x)表达式可以写为: 244 | 245 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_1244A/clip_image002%5B4%5D.gif) 246 | 247 | 注意式子中x才是变量,也就是你要分类哪篇文档,就把该文档的向量表示代入到 x的位置,而所有的xi统统都是已知的样本。还注意到式子中只有xi和x是向量,因此一部分可以从内积符号中拿出来,得到g(x)的式子为: 248 | 249 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_1244A/clip_image002%5B6%5D.gif) 250 | 251 | 发现了什么?w不见啦!从求w变成了求α。 252 | 253 | 但肯定有人会说,这并没有把原问题简化呀。嘿嘿,其实简化了,只不过在你看不见的地方,以这样的形式描述问题以后,我们的优化问题少了很大一部分不等式约束(记得这是我们解不了极值问题的万恶之源)。但是接下来先跳过线性分类器求解的部分,来看看 SVM在线性分类器上所做的重大改进——核函数。 254 | 255 | **SVM入门(七)为何需要核函数** 256 | 257 | 生存?还是毁灭?——哈姆雷特 258 | 259 | 可分?还是不可分?——支持向量机 260 | 261 | 之前一直在讨论的线性分类器,器如其名(汗,这是什么说法啊),只能对线性可分的样本做处理。如果提供的样本线性不可分,结果很简单,线性分类器的求解程序会无限循环,永远也解不出来。这必然使得它的适用范围大大缩小,而它的很多优点我们实在不原意放弃,怎么办呢?是否有某种方法,让线性不可分的数据变得线性可分呢? 262 | 263 | 有!其思想说来也简单,来用一个二维平面中的分类问题作例子,你一看就会明白。事先声明,下面这个例子是网络早就有的,我一时找不到原作者的正确信息,在此借用,并加进了我自己的解说而已。 264 | 265 | 例子是下面这张图: 266 | 267 | /![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_10595/clip_image001_2.jpg) 268 | 269 | 我们把横轴上端点a和b之间红色部分里的所有点定为正类,两边的黑色部分里的点定为负类。试问能找到一个线性函数把两类正确分开么?不能,因为二维空间里的线性函数就是指直线,显然找不到符合条件的直线。 270 | 271 | 但我们可以找到一条曲线,例如下面这一条: 272 | 273 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_10595/clip_image002_2.gif) 274 | 275 | 显然通过点在这条曲线的上方还是下方就可以判断点所属的类别(你在横轴上随便找一点,算算这一点的函数值,会发现负类的点函数值一定比0大,而正类的一定比0小)。这条曲线就是我们熟知的二次曲线,它的函数表达式可以写为: 276 | 277 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_10595/clip_image002%5B5%5D.gif) 278 | 279 | 问题只是它不是一个线性函数,但是,下面要注意看了,新建一个向量y和a: 280 | 281 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_10595/clip_image002%5B7%5D.gif) 282 | 283 | 这样g(x)就可以转化为f(y)=,你可以把y和a分别回带一下,看看等不等于原来的g(x)。用内积的形式写你可能看不太清楚,实际上f(y)的形式就是: 284 | 285 | g(x)=f(y)=ay 286 | 287 | 在任意维度的空间中,这种形式的函数都是一个线性函数(只不过其中的a和y都是多维向量罢了),因为自变量y的次数不大于1。 288 | 289 | 看出妙在哪了么?原来在二维空间中一个线性不可分的问题,映射到四维空间后,变成了线性可分的!因此这也形成了我们最初想解决线性不可分问题的基本思路——向高维空间转化,使其变得线性可分。 290 | 291 | 而转化最关键的部分就在于找到x到y的映射方法。遗憾的是,如何找到这个映射,没有系统性的方法(也就是说,纯靠猜和凑)。具体到我们的文本分类问题,文本被表示为上千维的向量,即使维数已经如此之高,也常常是线性不可分的,还要向更高的空间转化。其中的难度可想而知。 292 | 293 | 小Tips:为什么说f(y)=ay是四维空间里的函数? 294 | 295 | 大家可能一时没看明白。回想一下我们二维空间里的函数定义 296 | g(x)=ax+b 297 | 变量x是一维的,为什么说它是二维空间里的函数呢?因为还有一个变量我们没写出来,它的完整形式其实是 298 | y=g(x)=ax+b 299 | 即 300 | y=ax+b 301 | 看看,有几个变量?两个。那是几维空间的函数?(作者五岁的弟弟答:五维的。作者:……) 302 | 再看看 303 | f(y)=ay 304 | 里面的y是三维的变量,那f(y)是几维空间里的函数?(作者五岁的弟弟答:还是五维的。作者:……) 305 | 306 | 用一个具体文本分类的例子来看看这种向高维空间映射从而分类的方法如何运作,想象一下,我们文本分类问题的原始空间是1000维的(即每个要被分类的文档被表示为一个1000维的向量),在这个维度上问题是线性不可分的。现在我们有一个2000维空间里的线性函数 307 | 308 | f(x’)=+b 309 | 310 | 注意向量的右上角有个 ’哦。它能够将原问题变得可分。式中的 w’和x’都是2000维的向量,只不过w’是定值,而x’是变量(好吧,严格说来这个函数是2001维的,哈哈),现在我们的输入呢,是一个1000维的向量x,分类的过程是先把x变换为2000维的向量x’,然后求这个变换后的向量x’与向量w’的内积,再把这个内积的值和b相加,就得到了结果,看结果大于阈值还是小于阈值就得到了分类结果。 311 | 312 | 你发现了什么?我们其实只关心那个高维空间里内积的值,那个值算出来了,分类结果就算出来了。而从理论上说, x’是经由x变换来的,因此广义上可以把它叫做x的函数(有一个x,就确定了一个x’,对吧,确定不出第二个),而w’是常量,它是一个低维空间里的常量w经过变换得到的,所以给了一个w 和x的值,就有一个确定的f(x’)值与其对应。这让我们幻想,是否能有这样一种函数K(w,x),他接受低维空间的输入值,却能算出高维空间的内积值? 313 | 314 | 如果有这样的函数,那么当给了一个低维空间的输入x以后, 315 | 316 | g(x)=K(w,x)+b 317 | 318 | f(x’)=+b 319 | 320 | 这两个函数的计算结果就完全一样,我们也就用不着费力找那个映射关系,直接拿低维的输入往g(x)里面代就可以了(再次提醒,这回的g(x)就不是线性函数啦,因为你不能保证K(w,x)这个表达式里的x次数不高于1哦)。 321 | 322 | 万幸的是,这样的K(w,x)确实存在(发现凡是我们人类能解决的问题,大都是巧得不能再巧,特殊得不能再特殊的问题,总是恰好有些能投机取巧的地方才能解决,由此感到人类的渺小),它被称作核函数(核,kernel),而且还不止一个,事实上,只要是满足了Mercer条件的函数,都可以作为核函数。核函数的基本作用就是接受两个低维空间里的向量,能够计算出经过某个变换后在高维空间里的向量内积值。几个比较常用的核函数,俄,教课书里都列过,我就不敲了(懒!)。 323 | 324 | 回想我们上节说的求一个线性分类器,它的形式应该是: 325 | 326 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_10595/clip_image002%5B9%5D.gif) 327 | 328 | 现在这个就是高维空间里的线性函数(为了区别低维和高维空间里的函数和向量,我改了函数的名字,并且给w和x都加上了 ’),我们就可以用一个低维空间里的函数(再一次的,这个低维空间里的函数就不再是线性的啦)来代替, 329 | 330 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_10595/clip_image002%5B11%5D.gif) 331 | 332 | 又发现什么了?f(x’) 和g(x)里的α,y,b全都是一样一样的!这就是说,尽管给的问题是线性不可分的,但是我们就硬当它是线性问题来求解,只不过求解过程中,凡是要求内积的时候就用你选定的核函数来算。这样求出来的α再和你选定的核函数一组合,就得到分类器啦! 333 | 334 | 明白了以上这些,会自然的问接下来两个问题: 335 | 336 | 1. 既然有很多的核函数,针对具体问题该怎么选择? 337 | 338 | 2. 如果使用核函数向高维空间映射后,问题仍然是线性不可分的,那怎么办? 339 | 340 | 第一个问题现在就可以回答你:对核函数的选择,现在还缺乏指导原则!各种实验的观察结果(不光是文本分类)的确表明,某些问题用某些核函数效果很好,用另一些就很差,但是一般来讲,径向基核函数是不会出太大偏差的一种,首选。(我做文本分类系统的时候,使用径向基核函数,没有参数调优的情况下,绝大部分类别的准确和召回都在85%以上,可见。虽然libSVM的作者林智仁认为文本分类用线性核函数效果更佳,待考证) 341 | 342 | 对第二个问题的解决则引出了我们下一节的主题:松弛变量。 343 | 344 | **SVM入门(八)松弛变量** 345 | 346 | 现在我们已经把一个本来线性不可分的文本分类问题,通过映射到高维空间而变成了线性可分的。就像下图这样: 347 | 348 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_D32/image_2.png) 349 | 350 | 圆形和方形的点各有成千上万个(毕竟,这就是我们训练集中文档的数量嘛,当然很大了)。现在想象我们有另一个训练集,只比原先这个训练集多了一篇文章,映射到高维空间以后(当然,也使用了相同的核函数),也就多了一个样本点,但是这个样本的位置是这样的: 351 | 352 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_D32/image_4.png) 353 | 354 | 就是图中黄色那个点,它是方形的,因而它是负类的一个样本,这单独的一个样本,使得原本线性可分的问题变成了线性不可分的。这样类似的问题(仅有少数点线性不可分)叫做“近似线性可分”的问题。 355 | 356 | 以我们人类的常识来判断,说有一万个点都符合某种规律(因而线性可分),有一个点不符合,那这一个点是否就代表了分类规则中我们没有考虑到的方面呢(因而规则应该为它而做出修改)? 357 | 358 | 其实我们会觉得,更有可能的是,这个样本点压根就是错误,是噪声,是提供训练集的同学人工分类时一打瞌睡错放进去的。所以我们会简单的忽略这个样本点,仍然使用原来的分类器,其效果丝毫不受影响。 359 | 360 | 但这种对噪声的容错性是人的思维带来的,我们的程序可没有。由于我们原本的优化问题的表达式中,确实要考虑所有的样本点(不能忽略某一个,因为程序它怎么知道该忽略哪一个呢?),在此基础上寻找正负类之间的最大几何间隔,而几何间隔本身代表的是距离,是非负的,像上面这种有噪声的情况会使得整个问题无解。这种解法其实也叫做“硬间隔”分类法,因为他硬性的要求所有样本点都满足和分类平面间的距离必须大于某个值。 361 | 362 | 因此由上面的例子中也可以看出,硬间隔的分类法其结果容易受少数点的控制,这是很危险的(尽管有句话说真理总是掌握在少数人手中,但那不过是那一小撮人聊以自慰的词句罢了,咱还是得民主)。 363 | 364 | 但解决方法也很明显,就是仿照人的思路,允许一些点到分类平面的距离不满足原先的要求。由于不同的训练集各点的间距尺度不太一样,因此用间隔(而不是几何间隔)来衡量有利于我们表达形式的简洁。我们原先对样本点的要求是: 365 | 366 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_D32/clip_image002_2.gif) 367 | 368 | 意思是说离分类面最近的样本点函数间隔也要比1大。如果要引入容错性,就给1这个硬性的阈值加一个松弛变量,即允许 369 | 370 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_D32/clip_image002%5B5%5D.gif) 371 | 372 | 因为松弛变量是非负的,因此最终的结果是要求间隔可以比1小。但是当某些点出现这种间隔比1小的情况时(这些点也叫离群点),意味着我们放弃了对这些点的精确分类,而这对我们的分类器来说是种损失。但是放弃这些点也带来了好处,那就是使分类面不必向这些点的方向移动,因而可以得到更大的几何间隔(在低维空间看来,分类边界也更平滑)。显然我们必须权衡这种损失和好处。好处很明显,我们得到的分类间隔越大,好处就越多。回顾我们原始的硬间隔分类对应的优化问题: 373 | 374 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_D32/clip_image002%5B7%5D.gif) 375 | 376 | ||w||2就是我们的目标函数(当然系数可有可无),希望它越小越好,因而损失就必然是一个能使之变大的量(能使它变小就不叫损失了,我们本来就希望目标函数值越小越好)。那如何来衡量损失,有两种常用的方式,有人喜欢用 377 | 378 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_D32/clip_image002%5B9%5D.gif) 379 | 380 | 而有人喜欢用 381 | 382 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_D32/clip_image002%5B11%5D.gif) 383 | 384 | 其中l都是样本的数目。两种方法没有大的区别。如果选择了第一种,得到的方法的就叫做二阶软间隔分类器,第二种就叫做一阶软间隔分类器。把损失加入到目标函数里的时候,就需要一个惩罚因子(cost,也就是libSVM的诸多参数中的C),原来的优化问题就变成了下面这样: 385 | 386 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_D32/clip_image002%5B13%5D.gif) 387 | 388 | 这个式子有这么几点要注意: 389 | 390 | 一是并非所有的样本点都有一个松弛变量与其对应。实际上只有“离群点”才有,或者也可以这么看,所有没离群的点松弛变量都等于0(对负类来说,离群点就是在前面图中,跑到H2右侧的那些负样本点,对正类来说,就是跑到H1左侧的那些正样本点)。 391 | 392 | 【在迭代求w的时候如何样本点非离群点,即分类正确,那么就设它的松弛变量为0了。。。】 393 | 394 | 二是松弛变量的值实际上标示出了对应的点到底离群有多远,值越大,点就越远。 395 | 396 | 三是惩罚因子C决定了你有多重视离群点带来的损失,显然当所有离群点的松弛变量的和一定时,你定的C越大,对目标函数的损失也越大,此时就暗示着你非常不愿意放弃这些离群点,最极端的情况是你把C定为无限大,这样只要稍有一个点离群,目标函数的值马上变成无限大,马上让问题变成无解,这就退化成了硬间隔问题。 397 | 398 | 四是惩罚因子C不是一个变量,整个优化问题在解的时候,C是一个你必须事先指定的值,指定这个值以后,解一下,得到一个分类器,然后用测试数据看看结果怎么样,如果不够好,换一个C的值,再解一次优化问题,得到另一个分类器,再看看效果,如此就是一个参数寻优的过程,但这和优化问题本身决不是一回事,优化问题在解的过程中,C一直是定值,要记住。 399 | 400 | 五是尽管加了松弛变量这么一说,但这个优化问题仍然是一个优化问题(汗,这不废话么),解它的过程比起原始的硬间隔问题来说,没有任何更加特殊的地方。 401 | 402 | 从大的方面说优化问题解的过程,就是先试着确定一下w,也就是确定了前面图中的三条直线,这时看看间隔有多大,又有多少点离群,把目标函数的值算一算,再换一组三条直线(你可以看到,分类的直线位置如果移动了,有些原来离群的点会变得不再离群,而有的本来不离群的点会变成离群点),再把目标函数的值算一算,如此往复(迭代),直到最终找到目标函数最小时的w。 403 | 404 | 啰嗦了这么多,读者一定可以马上自己总结出来,松弛变量也就是个解决线性不可分问题的方法罢了,但是回想一下,核函数的引入不也是为了解决线性不可分的问题么?为什么要为了一个问题使用两种方法呢? 405 | 406 | 其实两者还有微妙的不同。一般的过程应该是这样,还以文本分类为例。在原始的低维空间中,样本相当的不可分,无论你怎么找分类平面,总会有大量的离群点,此时用核函数向高维空间映射一下,虽然结果仍然是不可分的,但比原始空间里的要更加接近线性可分的状态(就是达到了近似线性可分的状态),此时再用松弛变量处理那些少数“冥顽不化”的离群点,就简单有效得多啦。 407 | 408 | 本节中的(式1)也确实是支持向量机最最常用的形式。至此一个比较完整的支持向量机框架就有了,简单说来,支持向量机就是使用了核函数的软间隔线性分类法。 409 | 410 | 下一节会说说松弛变量剩下的一点点东西,顺便搞个读者调查,看看大家还想侃侃SVM的哪些方面。 411 | 412 | **SVM入门(九)松弛变量(续)** 413 | 414 | 接下来要说的东西其实不是松弛变量本身,但由于是为了使用松弛变量才引入的,因此放在这里也算合适,那就是惩罚因子C。回头看一眼引入了松弛变量以后的优化问题: 415 | 416 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_11A27/clip_image002_2.gif) 417 | 418 | 注意其中C的位置,也可以回想一下C所起的作用(表征你有多么重视离群点,C越大越重视,越不想丢掉它们)。这个式子是以前做SVM的人写的,大家也就这么用,但没有任何规定说必须对所有的松弛变量都使用同一个惩罚因子,我们完全可以给每一个离群点都使用不同的C,这时就意味着你对每个样本的重视程度都不一样,有些样本丢了也就丢了,错了也就错了,这些就给一个比较小的C;而有些样本很重要,决不能分类错误(比如中央下达的文件啥的,笑),就给一个很大的C。 419 | 420 | 当然实际使用的时候并没有这么极端,但一种很常用的变形可以用来解决分类问题中样本的“偏斜”问题。 421 | 422 | 先来说说样本的偏斜问题,也叫数据集偏斜(unbalanced),它指的是参与分类的两个类别(也可以指多个类别)样本数量差异很大。比如说正类有10,000个样本,而负类只给了100个,这会引起的问题显而易见,可以看看下面的图: 423 | 424 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_11A27/image_2.png) 425 | 426 | 方形的点是负类。H,H1,H2是根据给的样本算出来的分类面,由于负类的样本很少很少,所以有一些本来是负类的样本点没有提供,比如图中两个灰色的方形点,如果这两个点有提供的话,那算出来的分类面应该是H’,H2’和H1,他们显然和之前的结果有出入,实际上负类给的样本点越多,就越容易出现在灰色点附近的点,我们算出的结果也就越接近于真实的分类面。但现在由于偏斜的现象存在,使得数量多的正类可以把分类面向负类的方向“推”,因而影响了结果的准确性。 427 | 428 | 对付数据集偏斜问题的方法之一就是在惩罚因子上作文章,想必大家也猜到了,那就是给样本数量少的负类更大的惩罚因子,表示我们重视这部分样本(本来数量就少,再抛弃一些,那人家负类还活不活了),因此我们的目标函数中因松弛变量而损失的部分就变成了: 429 | 430 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVM_11A27/clip_image002%5B5%5D.gif) 431 | 432 | 其中i=1…p都是正样本,j=p+1…p+q都是负样本。libSVM这个算法包在解决偏斜问题的时候用的就是这种方法。 433 | 434 | 那C+和C-怎么确定呢?它们的大小是试出来的(参数调优),但是他们的比例可以有些方法来确定。咱们先假定说C+是5这么大,那确定C-的一个很直观的方法就是使用两类样本数的比来算,对应到刚才举的例子,C-就可以定为500这么大(因为10,000:100=100:1嘛)。 435 | 436 | 但是这样并不够好,回看刚才的图,你会发现正类之所以可以“欺负”负类,其实并不是因为负类样本少,真实的原因是负类的样本分布的不够广(没扩充到负类本应该有的区域)。说一个具体点的例子,现在想给政治类和体育类的文章做分类,政治类文章很多,而体育类只提供了几篇关于篮球的文章,这时分类会明显偏向于政治类,如果要给体育类文章增加样本,但增加的样本仍然全都是关于篮球的(也就是说,没有足球,排球,赛车,游泳等等),那结果会怎样呢?虽然体育类文章在数量上可以达到与政治类一样多,但过于集中了,结果仍会偏向于政治类!所以给C+和C-确定比例更好的方法应该是衡量他们分布的程度。比如可以算算他们在空间中占据了多大的体积,例如给负类找一个超球——就是高维空间里的球啦——它可以包含所有负类的样本,再给正类找一个,比比两个球的半径,就可以大致确定分布的情况。显然半径大的分布就比较广,就给小一点的惩罚因子。 437 | 438 | 但是这样还不够好,因为有的类别样本确实很集中,这不是提供的样本数量多少的问题,这是类别本身的特征(就是某些话题涉及的面很窄,例如计算机类的文章就明显不如文化类的文章那么“天马行空”),这个时候即便超球的半径差异很大,也不应该赋予两个类别不同的惩罚因子。 439 | 440 | 看到这里读者一定疯了,因为说来说去,这岂不成了一个解决不了的问题?然而事实如此,完全的方法是没有的,根据需要,选择实现简单又合用的就好(例如libSVM就直接使用样本数量的比)。 441 | 442 | **SVM入门(十)将SVM用于多类分类** 443 | 444 | 从 SVM的那几张图可以看出来,SVM是一种典型的两类分类器,即它只回答属于正类还是负类的问题。而现实中要解决的问题,往往是多类的问题(少部分例外,例如垃圾邮件过滤,就只需要确定“是”还是“不是”垃圾邮件),比如文本分类,比如数字识别。如何由两类分类器得到多类分类器,就是一个值得研究的问题。 445 | 446 | 还以文本分类为例,现成的方法有很多,其中一种一劳永逸的方法,就是真的一次性考虑所有样本,并求解一个多目标函数的优化问题,一次性得到多个分类面,就像下图这样: 447 | 448 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMSVM_CBFA/clip_image001_2.jpg) 449 | 450 | 多个超平面把空间划分为多个区域,每个区域对应一个类别,给一篇文章,看它落在哪个区域就知道了它的分类。 451 | 452 | 看起来很美对不对?只可惜这种算法还基本停留在纸面上,因为一次性求解的方法计算量实在太大,大到无法实用的地步。 453 | 454 | 稍稍退一步,我们就会想到所谓“一类对其余”的方法,就是每次仍然解一个两类分类的问题。比如我们有5个类别,第一次就把类别1的样本定为正样本,其余2,3,4,5的样本合起来定为负样本,这样得到一个两类分类器,它能够指出一篇文章是还是不是第1类的;第二次我们把类别2 的样本定为正样本,把1,3,4,5的样本合起来定为负样本,得到一个分类器,如此下去,我们可以得到5个这样的两类分类器(总是和类别的数目一致)。到了有文章需要分类的时候,我们就拿着这篇文章挨个分类器的问:是属于你的么?是属于你的么?哪个分类器点头说是了,文章的类别就确定了。这种方法的好处是每个优化问题的规模比较小,而且分类的时候速度很快(只需要调用5个分类器就知道了结果)。但有时也会出现两种很尴尬的情况,例如拿一篇文章问了一圈,每一个分类器都说它是属于它那一类的,或者每一个分类器都说它不是它那一类的,前者叫分类重叠现象,后者叫不可分类现象。分类重叠倒还好办,随便选一个结果都不至于太离谱,或者看看这篇文章到各个超平面的距离,哪个远就判给哪个。不可分类现象就着实难办了,只能把它分给第6个类别了……更要命的是,本来各个类别的样本数目是差不多的,但“其余”的那一类样本数总是要数倍于正类(因为它是除正类以外其他类别的样本之和嘛),这就人为的造成了上一节所说的“数据集偏斜”问题。 455 | 456 | 因此我们还得再退一步,还是解两类分类问题,还是每次选一个类的样本作正类样本,而负类样本则变成只选一个类(称为“一对一单挑”的方法,哦,不对,没有单挑,就是“一对一”的方法,呵呵),这就避免了偏斜。因此过程就是算出这样一些分类器,第一个只回答“是第1类还是第2类”,第二个只回答“是第1类还是第3类”,第三个只回答“是第1类还是第4类”,如此下去,你也可以马上得出,这样的分类器应该有5 X 4/2=10个(通式是,如果有k个类别,则总的两类分类器数目为k(k-1)/2)。虽然分类器的数目多了,但是在训练阶段(也就是算出这些分类器的分类平面时)所用的总时间却比“一类对其余”方法少很多,在真正用来分类的时候,把一篇文章扔给所有分类器,第一个分类器会投票说它是“1”或者“2”,第二个会说它是“1”或者“3”,让每一个都投上自己的一票,最后统计票数,如果类别“1”得票最多,就判这篇文章属于第1类。这种方法显然也会有分类重叠的现象,但不会有不可分类现象,因为总不可能所有类别的票数都是0。看起来够好么?其实不然,想想分类一篇文章,我们调用了多少个分类器?10个,这还是类别数为5的时候,类别数如果是1000,要调用的分类器数目会上升至约500,000个(类别数的平方量级)。这如何是好? 457 | 458 | 看来我们必须再退一步,在分类的时候下功夫,我们还是像一对一方法那样来训练,只是在对一篇文章进行分类之前,我们先按照下面图的样子来组织分类器(如你所见,这是一个有向无环图,因此这种方法也叫做DAG SVM) 459 | 460 | ![img](http://www.blogjava.net/images/blogjava_net/zhenandaci/WindowsLiveWriter/SVMSVM_CBFA/clip_image002_2.gif) 461 | 462 | 这样在分类时,我们就可以先问分类器“1对5”(意思是它能够回答“是第1类还是第5类”),如果它回答5,我们就往左走,再问“2对5”这个分类器,如果它还说是“5”,我们就继续往左走,这样一直问下去,就可以得到分类结果。好处在哪?我们其实只调用了4个分类器(如果类别数是k,则只调用k-1个),分类速度飞快,且没有分类重叠和不可分类现象!缺点在哪?假如最一开始的分类器回答错误(明明是类别1的文章,它说成了5),那么后面的分类器是无论如何也无法纠正它的错误的(因为后面的分类器压根没有出现“1”这个类别标签),其实对下面每一层的分类器都存在这种错误向下累积的现象。。 463 | 464 | 不过不要被DAG方法的错误累积吓倒,错误累积在一对其余和一对一方法中也都存在,DAG方法好于它们的地方就在于,累积的上限,不管是大是小,总是有定论的,有理论证明。而一对其余和一对一方法中,尽管每一个两类分类器的泛化误差限是知道的,但是合起来做多类分类的时候,误差上界是多少,没人知道,这意味着准确率低到0也是有可能的,这多让人郁闷。 465 | 466 | 而且现在DAG方法根节点的选取(也就是如何选第一个参与分类的分类器),也有一些方法可以改善整体效果,我们总希望根节点少犯错误为好,因此参与第一次分类的两个类别,最好是差别特别特别大,大到以至于不太可能把他们分错;或者我们就总取在两类分类中正确率最高的那个分类器作根节点,或者我们让两类分类器在分类的时候,不光输出类别的标签,还输出一个类似“置信度”的东东,当它对自己的结果不太自信的时候,我们就不光按照它的输出走,把它旁边的那条路也走一走,等等。 467 | 468 | **大Tips:SVM的计算复杂度** 469 | 470 | 使用SVM进行分类的时候,实际上是训练和分类两个完全不同的过程,因而讨论复杂度就不能[一概而论](https://www.baidu.com/s?wd=%E4%B8%80%E6%A6%82%E8%80%8C%E8%AE%BA&tn=24004469_oem_dg&rsv_dl=gh_pl_sl_csd),我们这里所说的主要是训练阶段的复杂度,即解那个二次规划问题的复杂度。对这个问题的解,基本上要划分为两大块,解析解和数值解。 471 | 472 | 解析解就是理论上的解,它的形式是表达式,因此它是精确的,一个问题只要有解(无解的问题还跟着掺和什么呀,哈哈),那它的解析解是一定存在的。当然存在是一回事,能够解出来,或者可以在可以承受的时间范围内解出来,就是另一回事了。对SVM来说,求得解析解的时间复杂度最坏可以达到O(Nsv3),其中Nsv是支持向量的个数,而虽然没有固定的比例,但支持向量的个数多少也和训练集的大小有关。 473 | 474 | 数值解就是可以使用的解,是一个一个的数,往往都是近似解。求数值解的过程非常像穷举法,从一个数开始,试一试它当解效果怎样,不满足一定条件(叫做停机条件,就是满足这个以后就认为解足够精确了,不需要继续算下去了)就试下一个,当然下一个数不是乱选的,也有一定章法可循。有的算法,每次只尝试一个数,有的就尝试多个,而且找下一个数字(或下一组数)的方法也各不相同,停机条件也各不相同,最终得到的解精度也各不相同,可见对求数值解的复杂度的讨论不能脱开具体的算法。 475 | 476 | 一个具体的算法,Bunch-Kaufman训练算法,典型的时间复杂度在O(Nsv3+LNsv2+dLNsv)和O(dL2)之间,其中Nsv是支持向量的个数,L是训练集样本的个数,d是每个样本的维数(原始的维数,没有经过向高维空间映射之前的维数)。复杂度会有变化,是因为它不光跟输入问题的规模有关(不光和样本的数量,维数有关),也和问题最终的解有关(即支持向量有关),如果支持向量比较少,过程会快很多,如果支持向量很多,接近于样本的数量,就会产生O(dL2)这个十分糟糕的结果(给10,000个样本,每个样本1000维,基本就不用算了,算不出来,呵呵,而这种输入规模对文本分类来说太正常了)。 477 | 478 | 这样再回头看就会明白为什么一对一方法尽管要训练的两类分类器数量多,但总时间实际上比一对其余方法要少了,因为一对其余方法每次训练都考虑了所有样本(只是每次把不同的部分划分为正类或者负类而已),自然慢上很多。 -------------------------------------------------------------------------------- /TensorFlow 安装总结.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TensorFlow 安装总结 3 | date: 2019-3-22 12:30:24 4 | tags: 5 | - python 6 | - TensorFlow 7 | - MachineLearning 8 | toc: 9 | ture 10 | --- 11 | 12 | 13 | # TensorFlow 安装总结 14 | 15 | ![TensorFlow 2.0 Logo](http://poatdt87z.bkt.clouddn.com/tensorflow2.0.gif) 16 | 17 | ## TensorFlow 简介: 18 | 19 | TensorFlow 是 Google 公司人工智能团队谷歌大脑(Google Brain)研发和维护的开源深度学习框架,世界上由成千上万个开发者为该项目贡献代码,框架能够随着对深度学习的研究进展及时更新。TensorFlow 核心组件包括:最底层的设备层、内核应用、执行器、分发中心。主要是对张量进行运算,有适用于高阶神经网络的Estimators接口。而且 TensorFlow 可轻松移植到网页端和移动设备上。目前最新版是TensorFlow 1.13.1。 20 | 21 | TensorFlow-GPU是TensorFlow的GPU版,能够使用支持NVIDIA CUDA 并行计算技术的显卡进行矢量运算,其训练模型的效率比CPU高几十到上百倍。 22 | 23 | 24 | 25 | 26 | ## 并行计算框架安装: 27 | 28 | 我们打开TensorFlow的官网可以明显的在导航栏找到 Install 页面的入口。 29 | 30 | 然而相信我,安装TensorFlow并没有官方页面上写的那么简单(特别是喜欢挑战难度安装GPU版本),输入几行代码,坐到旁边喝杯茶刷刷微博,过一会就安装完成了。 31 | 32 | 因为TensorFlow支持众多平台,首先我们要搞懂TensorFlow到底支持哪些操作系统。 33 | 34 | - Ubuntu 16.04 或更高版本(这里可代表Linux家族) 35 | - Windows 7 或更高版本 36 | - macOS 10.12.6 (Sierra) 或更高版本(不支持 GPU) 37 | - Raspbian 9.0 或更高版本 38 | 39 | 可以看出来常用的操作系统都是支持的。 40 | 41 | ### 安装Cuda 42 | 43 | #### Linux 44 | 45 | 首先我们看一下linux版的安装步骤 46 | 47 | 1. 由于GPU版本计算速度更快,所以优先考虑安装GPU版本。我们要查看我们的电脑是否支持GPU版本的硬件。 48 | 49 | > 这里有两个问题: 50 | > 51 | > 首先,你的电脑需要配置有NVIDIA的显卡 52 | > 53 | > 其次,你的显卡需要在CUDA支持名单里 [GPUSUPPORT](https://developer.nvidia.com/cuda-gpus) 54 | 55 | 2. 确认了你的GPU在NVIDIA CUDA技术支持名单里之后就可以安装驱动了。 56 | 57 | > 我们可以直接从NVIDIA官方网站找到CUDA[下载链接](https://developer.nvidia.com/cuda-downloads) 58 | > 59 | > 根据自己的操作系统版本下载合适的安装包。 60 | > 61 | > 另外要注意CUDA并不是版本越高越好,一般情况下CUDA发布版本要低于市面上相关机器学习框架的应用版本,也就是说你下载了一个过高的版本可能无法使用,具体如何选择CUDA版本,下文会有介绍。 62 | 63 | 3. 64 | 65 | 安装显卡驱动和 Cuda: 66 | 指定到对应的文件所在位置: 67 | `# cd /usr/tmp/tensorflow-gpu` 68 | (这是我的文件所在的路径) 69 | 70 | cd到驱动所在目录: 71 | 72 | ``` 73 | # sh ./NVIDIA-Linux-x86_64-375.66.run 74 | # sh cuda_8.0.61_375.26_linux.run 75 | ``` 76 | 77 | 修改配置文件: 78 | 79 | `# vi ~/.bashrc` 80 | (vi 命令为在terminal终端中进入文本编辑的方法,有的是vim,有界面还可以gedit ~/.bashrc方法进入) 81 | 按Insert键,进入插入模式: 82 | 83 | ``` 84 | #gpu driver 85 | 86 | export CUDA_HOME=/usr/local/cuda-8.0 87 | 88 | exportPATH=/usr/local/cuda-8.0/bin:$PATH 89 | 90 | exportLD_LIBRARY_PATH=/usr/local/cuda-8.0/lib64:$LD_LIBRARY_PATH 91 | 92 | exportLD_LIBRARY_PATH="/usr/local/cuda-8.0/lib:${LD_LIBRARY_PATH}" 93 | ``` 94 | 95 | 96 | 97 | 按 Esc 键, 并输入 98 | `# wq` 99 | 表示写入并退出。 100 | 101 | 修改之后需要激活所修改的配置文件: 102 | 103 | `# source ~/.bashrc` 104 | 然后重启服务器 105 | `# reboot` 106 | 接下来就可以通过查看显卡信息测试驱动是否安装成功: 107 | 108 | `# nvidia-smi` 109 | ![mem](http://poatdt87z.bkt.clouddn.com/mem.png) 110 | 有显示,说明显卡驱动安装成功,继续闯关! 111 | 112 | 测试CUDA是否安装成功: 113 | `# cd /usr/local/cuda/samples/` 114 | 如果安装成功了的话,上面这个文件夹是可以进去的 115 | `# make(这步运行时间比较长,不要以为存在问题)` 116 | `# ./bin/x86_64/linux/release/deviceQuery` 117 | 118 | \#./bin/x86_64/linux/release/bandwidthTest 119 | 显示 Result = PASS 则测试通过 120 | 安装CUDNN 121 | 指定到对应的文件所在位置: 122 | `# cd /usr/tmp/tensorflow-gpu` 123 | (这是我的文件所在的路径) 124 | 125 | ``` 126 | $ tar -zxvf cudnn-8.0-linux-x64-v6.0.tgz 127 | 128 | $ cd cuda 129 | 130 | $ cp include/* /usr/local/cuda-8.0/inlcude/ 131 | 132 | $ cp lib64/lib* /usr/local/cuda-8.0/lib64/ 133 | ``` 134 | 135 | 136 | 137 | 注意驱动和cuda的安装顺序,先安装驱动的话,安装cuda时x-configtion选择N 138 | 139 | #### Mac OS和Raspbian 140 | 141 | 因为该硬件没有NVIDIA的显卡所以压根就不支持CUDA,这里跳过 142 | 143 | #### Windows 144 | 145 | Windows安装CUDA和安装NVIDIA显卡驱动步骤一致。 146 | 147 | 下载CUDA安装包,打开解压安装。选择精简版安装,安装程序会自动检测不兼容的软件,然会自己安装,自己注册环境变量,十分简单。 148 | 149 | 由于国产部分杀毒防护软件敌友不分,在安装完成之后,一定要人工检查一下系统变量,看一下CUDA路径是否被添加到Windows环境变量里。 150 | 151 | ### 安装cuDNN 152 | 153 | 安装cuDNN也很简单,首先从 [官方网站](https://developer.nvidia.com/rdp/cudnn-download) 下载cuDNN,这里需要注册NVIDIA账户。 154 | 155 | #### Linux 156 | 157 | ``` 158 | 安装cudnn: 159 | $ tar -xvzf cudnn-8.0-linux-x64-v6.0.tgz 160 | $ cp -P cuda/include/cudnn.h /usr/local/cuda-8.0/include 161 | $ cp -P cuda/lib64/libcudnn* /usr/local/cuda-8.0/lib64 162 | $ chmod a+r /usr/local/cuda-8.0/include/cudnn.h /usr/local/cuda-8.0/lib64/libcudnn* 163 | ``` 164 | 165 | #### Windows 166 | 167 | 解压`cudnn-xxx-windowsxx-x64-vx.x.x.xx.zip`文件到cuda安装目录,合并到同名文件夹即可。 168 | 169 | ## TensorFlow安装 170 | 171 | 这里通常有两种安装方法: 172 | 173 | > 1. 从源代码构建 174 | > 2. 从发行版安装 175 | 176 | 前者适合追求最新版本,或者需要深度制定TensorFlow的用户,当然你想体验一下一个大型软件从源码到可执行文件的编译过程也可以尝试。 177 | 178 | 后者适合普通用户,可以直接使用Python 的pip工具安装。 179 | 180 | #### Linux 从源码构建 181 | 182 | Linux 和 macOS 设置 183 | 184 | 安装以下构建工具以配置开发环境。 185 | 186 | 安装 Python 和 TensorFlow 软件包依赖项 187 | 188 | [Ubuntu](https://tensorflow.google.cn/install/source#ubuntu)[mac OS](https://tensorflow.google.cn/install/source#mac-os) 189 | 190 | ``` 191 | sudo apt install python-dev python-pip # or python3-dev python3-pip 192 | ``` 193 | 194 | 安装 TensorFlow pip 软件包依赖项(如果使用虚拟环境,请省略 `--user` 参数): 195 | 196 | ``` 197 | pip install -U --user pip six numpy wheel mock 198 | pip install -U --user keras_applications==1.0.5 --no-deps 199 | pip install -U --user keras_preprocessing==1.0.3 --no-deps 200 | ``` 201 | 202 | 这些依赖项列在 `REQUIRED_PACKAGES` 下的 [`setup.py`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tools/pip_package/setup.py) 文件中。 203 | 204 | 安装 Bazel 205 | 206 | [安装 Bazel](https://docs.bazel.build/versions/master/install.html),它是用于编译 TensorFlow 的构建工具。 207 | 208 | 将 Bazel 可执行文件的位置添加到 `PATH` 环境变量中。 209 | 210 | 安装支持 GPU 的版本(可选,仅限 Linux) 211 | 212 | 没有针对 macOS 的 GPU 支持版本。 213 | 214 | 要安装在 GPU 上运行 TensorFlow 所需的驱动程序和其他软件,请参阅 [GPU 支持](https://tensorflow.google.cn/install/gpu)指南。 215 | 216 | **注意**:您可以轻松地设置 TensorFlow 的某个支持 GPU 的 [Docker 映像](https://tensorflow.google.cn/install/source#docker_linux_builds)。 217 | 218 | 下载 TensorFlow 源代码 219 | 220 | 使用 [Git](https://git-scm.com/) 克隆 [TensorFlow 代码库](https://github.com/tensorflow/tensorflow): 221 | 222 | ``` 223 | git clone https://github.com/tensorflow/tensorflow.git 224 | cd tensorflow 225 | ``` 226 | 227 | 代码库默认为 `master` 开发分支。您也可以检出要构建的[版本分支](https://github.com/tensorflow/tensorflow/releases): 228 | 229 | ``` 230 | git checkout branch_name # r1.9, r1.10, etc. 231 | ``` 232 | 233 | 要测试源代码树的副本,请对 r1.12 及更早版本运行以下测试(这可能需要一段时间): 234 | 235 | ``` 236 | bazel test -c opt -- //tensorflow/... -//tensorflow/compiler/... -//tensorflow/contrib/lite/... 237 | ``` 238 | 239 | 对于 r1.12 之后的版本(如 `master`),请运行以下命令: 240 | 241 | ``` 242 | bazel test -c opt -- //tensorflow/... -//tensorflow/compiler/... -//tensorflow/lite/... 243 | ``` 244 | 245 | **要点**:如果您在使用最新的开发分支时遇到构建问题,请尝试已知可用的版本分支。 246 | 247 | 配置构建 248 | 249 | 通过在 TensorFlow 源代码树的根目录下运行以下命令来配置系统构建: 250 | 251 | ``` 252 | ./configure 253 | ``` 254 | 255 | 此脚本会提示您指定 TensorFlow 依赖项的位置,并要求指定其他构建配置选项(例如,编译器标记)。以下代码展示了 `./configure` 的示例运行会话(您的会话可能会有所不同): 256 | 257 | 查看示例配置会话 258 | 259 | 配置选项 260 | 261 | 对于 [GPU 支持](https://tensorflow.google.cn/install/gpu),请指定 CUDA 和 cuDNN 的版本。如果您的系统安装了多个 CUDA 或 cuDNN 版本,请明确设置版本而不是依赖于默认版本。`./configure` 会创建指向系统 CUDA 库的符号链接,因此,如果您更新 CUDA 库路径,则必须在构建之前再次运行此配置步骤。 262 | 263 | 对于编译优化标记,默认值 (`-march=native`) 会优化针对计算机的 CPU 类型生成的代码。但是,如果要针对不同类型的 CPU 构建 TensorFlow,请考虑指定一个更加具体的优化标记。要查看相关示例,请参阅 [GCC 手册](https://gcc.gnu.org/onlinedocs/gcc-4.5.3/gcc/i386-and-x86_002d64-Options.html)。 264 | 265 | 您可以将一些预先配置好的构建配置添加到 `bazel build` 命令中,例如: 266 | 267 | - `--config=mkl` - 支持 [Intel® MKL-DNN](https://github.com/intel/mkl-dnn)。 268 | - `--config=monolithic` - 此配置适用于基本保持静态的单体构建。 269 | 270 | **注意**:从 TensorFlow 1.6 开始,二进制文件使用 AVX 指令,这些指令可能无法在旧版 CPU 上运行。 271 | 272 | 构建 pip 软件包 273 | 274 | Bazel 构建 275 | 276 | 仅支持 CPU 277 | 278 | 使用 `bazel` 构建仅支持 CPU 的 TensorFlow 软件包构建器: 279 | 280 | ``` 281 | bazel build --config=opt //tensorflow/tools/pip_package:build_pip_package 282 | ``` 283 | 284 | GPU 支持 285 | 286 | 要构建支持 GPU 的 TensorFlow 软件包构建器,请运行以下命令: 287 | 288 | ``` 289 | bazel build --config=opt --config=cuda //tensorflow/tools/pip_package:build_pip_package 290 | ``` 291 | 292 | Bazel 构建选项 293 | 294 | 从源代码构建 TensorFlow 可能会消耗大量内存。如果系统内存有限,请使用以下命令限制 Bazel 的内存消耗量:`--local_resources 2048,.5,1.0`。 295 | 296 | [官方 TensorFlow 软件包](https://tensorflow.google.cn/install/pip)是使用 GCC 4 构建的,并使用旧版 ABI。对于 GCC 5 及更高版本,为了使您的构建与旧版 ABI 兼容,请使用 `--cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0"`。兼容 ABI 可确保针对官方 TensorFlow pip 软件包构建的自定义操作继续支持使用 GCC 5 构建的软件包。 297 | 298 | 构建软件包 299 | 300 | `bazel build` 命令会创建一个名为 `build_pip_package` 的可执行文件,此文件是用于构建 `pip` 软件包的程序。例如,以下命令会在 `/tmp/tensorflow_pkg` 目录中构建 `.whl` 软件包: 301 | 302 | ``` 303 | ./bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg 304 | ``` 305 | 306 | 尽管可以在同一个源代码树下构建 CUDA 和非 CUDA 配置,但建议您在同一个源代码树中的这两种配置之间切换时运行 `bazel clean`。 307 | 308 | 安装软件包 309 | 310 | 生成的 `.whl` 文件的文件名取决于 TensorFlow 版本和您的平台。例如,使用 `pip install` 安装软件包: 311 | 312 | ``` 313 | pip install /tmp/tensorflow_pkg/tensorflow-version-tags.whl 314 | ``` 315 | 316 | **成功**:TensorFlow 现已安装完毕。 317 | 318 | Docker Linux 构建 319 | 320 | 借助 TensorFlow 的 Docker 开发映像,您可以轻松设置环境,以从源代码构建 Linux 软件包。这些映像已包含构建 TensorFlow 所需的源代码和依赖项。要了解安装说明和[可用映像标记的列表](https://hub.docker.com/r/tensorflow/tensorflow/tags/),请参阅 TensorFlow [Docker 指南](https://tensorflow.google.cn/install/docker)。 321 | 322 | #### Windows 从源码构建 323 | 324 | Windows 设置 325 | 326 | 安装以下构建工具以配置 Windows 开发环境。 327 | 328 | 安装 Python 和 TensorFlow 软件包依赖项 329 | 330 | 安装[适用于 Windows 的 Python 3.5.x 或 Python 3.6.x 64 位版本](https://www.python.org/downloads/windows/)。选择 pip 作为可选功能,并将其添加到 `%PATH%` 环境变量中。 331 | 332 | 安装 TensorFlow pip 软件包依赖项: 333 | 334 | ``` 335 | pip3 install six numpy wheel 336 | pip3 install keras_applications==1.0.5 --no-deps 337 | pip3 install keras_preprocessing==1.0.3 --no-deps 338 | ``` 339 | 340 | 这些依赖项列在 `REQUIRED_PACKAGES` 下的 [`setup.py`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tools/pip_package/setup.py) 文件中。 341 | 342 | 安装 Bazel 343 | 344 | [安装 Bazel](https://docs.bazel.build/versions/master/install-windows.html),它是用于编译 TensorFlow 的构建工具。 345 | 346 | 将 Bazel 可执行文件的位置添加到 `%PATH%` 环境变量中。 347 | 348 | 安装 MSYS2 349 | 350 | 为构建 TensorFlow 所需的 bin 工具[安装 MSYS2](https://www.msys2.org/)。如果 MSYS2 已安装到 `C:\msys64` 下,请将 `C:\msys64\usr\bin`添加到 `%PATH%` 环境变量中。然后,使用 `cmd.exe` 运行以下命令: 351 | 352 | ``` 353 | pacman -S git patch unzip 354 | ``` 355 | 356 | 安装 Visual C++ 生成工具 2015 357 | 358 | 安装 Visual C++ 生成工具 2015。此软件包随附在 Visual Studio 2015 中,但可以单独安装: 359 | 360 | 1. 转到 [Visual Studio 下载页面](https://visualstudio.microsoft.com/vs/older-downloads/), 361 | 2. 选择“可再发行组件和生成工具”, 362 | 3. 下载并安装: 363 | - *Microsoft Visual C++ 2015 Redistributable 更新 3* 364 | - *Microsoft 生成工具 2015 更新 3* 365 | 366 | **注意**:TensorFlow 针对 Visual Studio 2015 更新 3 进行了测试。 367 | 368 | 安装 GPU 支持(可选) 369 | 370 | 要安装在 GPU 上运行 TensorFlow 所需的驱动程序和其他软件,请参阅 Windows [GPU 支持](https://tensorflow.google.cn/install/gpu)指南。 371 | 372 | 下载 TensorFlow 源代码 373 | 374 | 使用 [Git](https://git-scm.com/) 克隆 [TensorFlow 代码库](https://github.com/tensorflow/tensorflow)(`git` 随 MSYS2 一起安装): 375 | 376 | ``` 377 | git clone https://github.com/tensorflow/tensorflow.git 378 | cd tensorflow 379 | ``` 380 | 381 | 代码库默认为 `master` 开发分支。您也可以检出要构建的[版本分支](https://github.com/tensorflow/tensorflow/releases): 382 | 383 | ``` 384 | git checkout branch_name # r1.9, r1.10, etc. 385 | ``` 386 | 387 | **要点**:如果您在使用最新的开发分支时遇到构建问题,请尝试已知可用的版本分支。 388 | 389 | 配置构建 390 | 391 | 通过在 TensorFlow 源代码树的根目录下运行以下命令来配置系统构建: 392 | 393 | ``` 394 | python ./configure.py 395 | ``` 396 | 397 | 此脚本会提示您指定 TensorFlow 依赖项的位置,并要求指定其他构建配置选项(例如,编译器标记)。以下代码展示了 `python ./configure.py` 的示例运行会话(您的会话可能会有所不同): 398 | 399 | 查看示例配置会话 400 | 401 | 配置选项 402 | 403 | 对于 [GPU 支持](https://tensorflow.google.cn/install/gpu),请指定 CUDA 和 cuDNN 的版本。如果您的系统安装了多个 CUDA 或 cuDNN 版本,请明确设置版本而不是依赖于默认版本。`./configure.py` 会创建指向系统 CUDA 库的符号链接,因此,如果您更新 CUDA 库路径,则必须在构建之前再次运行此配置步骤。 404 | 405 | **注意**:从 TensorFlow 1.6 开始,二进制文件使用 AVX 指令,这些指令可能无法在旧版 CPU 上运行。 406 | 407 | 构建 pip 软件包 408 | 409 | Bazel 构建 410 | 411 | 仅支持 CPU 412 | 413 | 使用 `bazel` 构建仅支持 CPU 的 TensorFlow 软件包构建器: 414 | 415 | ``` 416 | bazel build --config=opt //tensorflow/tools/pip_package:build_pip_package 417 | ``` 418 | 419 | GPU 支持 420 | 421 | 要构建支持 GPU 的 TensorFlow 软件包构建器,请运行以下命令: 422 | 423 | ``` 424 | bazel build --config=opt --config=cuda //tensorflow/tools/pip_package:build_pip_package 425 | ``` 426 | 427 | Bazel 构建选项 428 | 429 | 从源代码构建 TensorFlow 可能会消耗大量内存。如果系统内存有限,请使用以下命令限制 Bazel 的内存消耗量:`--local_resources 2048,.5,1.0`。 430 | 431 | 如果构建支持 GPU 的 TensorFlow,请添加 `--copt=-nvcc_options=disable-warnings` 以禁止显示 nvcc 警告消息。 432 | 433 | 构建软件包 434 | 435 | `bazel build` 命令会创建一个名为 `build_pip_package` 的可执行文件,此文件是用于构建 `pip` 软件包的程序。例如,以下命令会在 `C:/tmp/tensorflow_pkg` 目录中构建 `.whl` 软件包: 436 | 437 | ``` 438 | bazel-bin\tensorflow\tools\pip_package\build_pip_package C:/tmp/tensorflow_pkg 439 | ``` 440 | 441 | 尽管可以在同一个源代码树下构建 CUDA 和非 CUDA 配置,但建议您在同一个源代码树中的这两种配置之间切换时运行 `bazel clean`。 442 | 443 | 安装软件包 444 | 445 | 生成的 `.whl` 文件的文件名取决于 TensorFlow 版本和您的平台。例如,使用 `pip3 install` 安装软件包: 446 | 447 | ``` 448 | pip3 install C:/tmp/tensorflow_pkg/tensorflow-version-cp36-cp36m-win_amd64.whl 449 | ``` 450 | 451 | **成功**:TensorFlow 现已安装完毕。 452 | 453 | 使用 MSYS shell 构建 454 | 455 | 也可以使用 MSYS shell 构建 TensorFlow。做出下面列出的更改,然后按照之前的 Windows 原生命令行 (`cmd.exe`) 说明进行操作。 456 | 457 | 停用 MSYS 路径转换 458 | 459 | MSYS 会自动将类似 Unix 路径的参数转换为 Windows 路径,此转换不适用于 `bazel`。(标签 `//foo/bar:bin` 被视为 Unix 绝对路径,因为它以斜杠开头。) 460 | 461 | ``` 462 | export MSYS_NO_PATHCONV=1 463 | export MSYS2_ARG_CONV_EXCL="*" 464 | ``` 465 | 466 | 设置 PATH 467 | 468 | 将 Bazel 和 Python 安装目录添加到 `$PATH` 环境变量中。如果 Bazel 安装到了 `C:\tools\bazel.exe`,并且 Python 安装到了 `C:\Python36\python.exe`,请使用以下命令设置 `PATH`: 469 | 470 | ``` 471 | # Use Unix-style with ':' as separator 472 | export PATH="/c/tools:$PATH" 473 | export PATH="/c/Python36:$PATH" 474 | ``` 475 | 476 | 要启用 GPU 支持,请将 CUDA 和 cuDNN bin 目录添加到 `$PATH` 中: 477 | 478 | ``` 479 | export PATH="/c/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v9.0/bin:$PATH"export PATH="/c/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v9.0/extras/CUPTI/libx64:$PATH"export PATH="/c/tools/cuda/bin:$PATH" 480 | ``` 481 | 482 | #### 使用Python自带的pip包管理工具安装 483 | 484 | 执行`pip install tensorflow =='x.xx'``pip install tensorflow-gpu ==’x.xx’` 485 | 486 | linux 执行`sudo pip install tensorflow =='x.xx'``sudo pip install tensorflow-gpu ==’x.xx’` 487 | 488 | ## 重点TensorFlow-GPU 与CUDA 及cudnn 版本参照表 489 | 490 | | 版本 | Python 版本 | cuDNN | CUDA | 491 | | :-------------------- | :---------- | :---- | :--- | 492 | | tensorflow_gpu-1.12.0 | 3.5-3.6 | 7 | 9 | 493 | | tensorflow_gpu-1.11.0 | 3.5-3.6 | 7 | 9 | 494 | | tensorflow_gpu-1.10.0 | 3.5-3.6 | 7 | 9 | 495 | | tensorflow_gpu-1.9.0 | 3.5-3.6 | 7 | 9 | 496 | | tensorflow_gpu-1.8.0 | 3.5-3.6 | 7 | 9 | 497 | | tensorflow_gpu-1.7.0 | 3.5-3.6 | 7 | 9 | 498 | | tensorflow_gpu-1.6.0 | 3.5-3.6 | 7 | 9 | 499 | | tensorflow_gpu-1.5.0 | 3.5-3.6 | 7 | 9 | 500 | | tensorflow_gpu-1.4.0 | 3.5-3.6 | 6 | 8 | 501 | | tensorflow_gpu-1.3.0 | 3.5-3.6 | 6 | 8 | 502 | | tensorflow_gpu-1.2.0 | 3.5-3.6 | 5.1 | 8 | 503 | | tensorflow_gpu-1.1.0 | 3.5 | 5.1 | 8 | 504 | | tensorflow_gpu-1.0.0 | 3.5 | 5.1 | 8 | 505 | 506 | 这里经过测试 507 | 508 | | TensorFlow - GPU | CUDA | cuDNN | 509 | | --------------------- | ------------- | --------------------------- | 510 | | tensorflow_gpu-1.12.0 | CUDA 9.0.176 | cuDNNv7.4.2.24 for cuda 9.0 | 511 | | tensorflow_gpu-1.13.0 | CUDA 10.0.130 | cuDNNv7.4.2.24 for cuda10.0 | -------------------------------------------------------------------------------- /sources/TOC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuQixuan/PythonLearningNote/0277bbe833131f71f74a1b87353bb487f4ed9e51/sources/TOC.png -------------------------------------------------------------------------------- /数据预处理之One-Hot(独热编码)编码.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数据预处理之One-Hot(独热编码)编码 3 | date: 2019-03-18 17:36:48 4 | tags: 5 | - python 6 | - MachineLearning 7 | toc: 8 | true 9 | --- 10 | 11 | # 数据预处理之One-Hot(独热编码)编码 12 | 13 | ## 为什么使用One-Hot编码 14 | 15 | 对于机器学习任务中,特征并不总是连续值,很多是分类值。这些分类值本身没有大小的意义。为了将数据集中一个分类变量替换为一个或多个新特征,我们使用One-Hot编码对数据进行预处理。 16 | 17 | 独热编码(哑变量 dummy variable)是因为大部分算法是**基于向量空间**中的度量来进行计算的,为了使非偏序关系的变量取值不具有偏序性,并且到圆点是等距的。使用one-hot编码,将离散特征的取值扩展到了欧式空间,离散特征的某个取值就对应欧式空间的某个点。将离散型特征使用one-hot编码,会让特征之间的**距离计算更加合理**。离散特征进行one-hot编码后,编码后的特征,其实每一维度的特征都可以看做是**连续的特征**。就可以跟对连续型特征的**归一化**方法一样,对每一维特征进行归一化。比如归一化到[-1,1]或归一化到均值为0,方差为1。 18 | 19 | ​ 为什么特征向量要映射到欧式空间? 20 | 21 | ​ 将离散特征通过one-hot编码映射到欧式空间,是因为,在回归,分类,聚类等机器学习算法中,特征之间距离的计算或相似度的计算是非常重要的,而我们常用的距离或相似度的计算都是在欧式空间的相似度计算,计算余弦相似性,基于的就是欧式空间。 22 | 23 | ## 什么是One-Hot编码 24 | 25 | 独热编码即 One-Hot 编码,又称一位有效编码,其方法是使用N位状态寄存器来对N个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候,其中只有一位有效(即各种属性之间相互排斥)。 26 | 27 | 简而言之,即把离散的特征的每一种取值都看成一种状态。 28 | 29 | 这样做的好处主要有: 30 | 31 | 1. 解决了分类器不好处理属性数据的问题 32 | 33 | 2. 在一定程度上也起到了扩充特征的作用 34 | 35 | eg: 36 | 37 | ```java 38 | 自然状态码为:000,001,010,011,100,101 39 | 40 | 独热编码为:000001,000010,000100,001000,010000,100000 41 | ``` 42 | 43 | ## One-Hot编码特征 44 | 45 | 1. 经过独热编码后,就变成了m个二元特征。 46 | 2. 这些特征互斥,每次只有一个激活。 47 | 3. 数据会变的稀疏 48 | 49 | ## One-Hot编码的优缺点 50 | 51 | - 优点:独热编码解决了分类器不好处理属性数据的问题,在一定程度上也起到了**扩充特征**的作用。它的值只有0和1,不同的类型存储在垂直的空间。 52 | - 缺点:当类别的数量很多时,**特征空间**会变得非常大。在这种情况下,一般可以用PCA*(principal Component Analysis,主成分分析方法)来**减少维度**。而且one hot encoding+*PCA这种组合在实际中也非常有用。 53 | 54 | ## 使用One-Hot编码的场合 55 | 56 | 首先要明确,独热编码用来解决**类别型数据**的离散值问题。 57 | 58 | 将离散型特征进行one-hot编码的作用,是为了**让距离计算更合理**,但如果特征是离散的,并且不用one-hot编码就可以很合理的计算出距离,那么就没必要进行one-hot编码。 有些基于**树的算法**在处理变量时,并不是基于**向量空间**度量,数值只是个类别符号,即没有偏序关系,所以不用进行独热编码。 Tree Model不太需要one-hot编码: 对于决策树来说,one-hot的本质是**增加树的深度**。 59 | 60 | ## One-Hot 和 哑变量(虚拟变量) 61 | 62 | 首先One-Hot编码主要解决数据的向量化 表示,而虚拟变量则是一个统计学概念。 63 | 64 | 对于哑变量余独热编码的相异点,最直观的理解就是哑变量将任意一个One-Hot编码状态去除。即哑变量将定性特征转化为n-1个特征,而One-hot则是转化为n个特征。意思就是哑变量在编码时会去除第一个状态,而One-hot则对所有的状态都会进行编码。 65 | 66 | 这样可以使One-Hot编码减少一个冗余位,这种去除是随机的。但是对于一个没有权重的数据集而言随机取出有点过于随意。 --------------------------------------------------------------------------------