1. 位运算简介
1.1 位运算与二进制简介
位运算(Bit Operation):在计算机内部,数是以「二进制(Binary)」的形式来进行存储。位运算就是直接对数的二进制进行计算操作,在程序中使用位运算进行操作,会大大提高程序的性能。
在学习二进制数的位运算之前,我们先来了解一下什么叫做「二进制数」。
二进制数(Binary):由 $0$ 和 $1$ 两个数码来表示的数。二进制数中每一个 $0$ 或每一个 $1$ 都称为一个「位(Bit)」。
我们通常使用的十进制数有 $0 \sim 9$ 共 $10$ 个数字,进位规则是「满十进一」。例如:
-
$7{(10)} + 2{(10)} = 9{(10)}$:$7{(10)}$ 加上 $2{(10)}$ 等于 $9{(10)}$。
-
$9{(10)} + 2{(10)} = 11{(10)}$:$9{(10)}$ 加上 $2{(10)}$ 之后个位大于等于 $10$,符合「满十进一」,结果等于 $11{(10)}$。
而在二进制数中,我们只有 $0$ 和 $1$ 两个数码,它的进位规则是「逢二进一」。例如:
-
$1{(2)} + 0{(2)} = 1{(2)}$:$1{(2)}$ 加上 $0{(2)}$ 等于 $1{(2)}$。
-
$1{(2)} + 1{(2)} = 10{(2)}$:$1{(2)}$ 加上 $1{(2)}$,大于等于 $2$,符合「逢二进一」,结果等于 $10{(2)}$。
-
$10{(2)} + 1{(2)} = 11_{(2)}$。
1.2 二进制数的转换
1.2.1 二进制转十进制数
在十进制数中,数字 $2749{(10)}$ 可以理解为 $2 \times 1000 + 7 \times 100 + 4 \times 10 + 9 * 1$,相当于 $2 \times 10^3 + 7 \times 10^2 + 4 \times 10^1 + 9 \times 10^0$,即 $2000 + 700 + 40 + 9 = 2749{(10)}$。
同理,在二进制数中,$01101010{(2)}$ 可以看作为 $(0 \times 2^7) + (1 \times 2^6) + (1 \times 2^5) + (0 \times 2^4) + (1 \times 2^3) + (0 \times 2^2) + (1 \times 2^1) + (0 \times 2^0)$,即 $0 + 64 + 32 + 0 + 8 + 0 + 2 + 0 = 106{(10)}$。
我们可以通过这样的方式,将一个二进制数转为十进制数。
1.2.2 十进制转二进制数
十进制数转二进制数的方法是:除二取余,逆序排列法。
我们以十进制数中的 $106_{(10)}$ 为例。
$106÷2=53(余 0)53÷2=26(余 1)26÷2=13(余 0)13÷2=6(余 1)6÷2=3(余 0)3÷2=1(余 1)1÷2=0(余 1)0÷2=0(余 0)
我们反向遍历每次计算的余数,依次是 $0$,$1$,$1$,$0$,$1$,$0$,$1$,$0$,即 $01101010_{(2)}$。
2. 位运算基础操作
在二进制的基础上,我们可以对二进制数进行相应的位运算。基本的位运算共有 $6$ 种,分别是:「按位与运算」、「按位或运算」、「按位异或运算」、「取反运算」、「左移运算」、「右移运算」。
这里的「按位与运算」、「按位或运算」、「按位异或运算」、「左移运算」、「右移运算」是双目运算。
-
「按位与运算」、「按位或运算」、「按位异或运算」是将两个整数作为二进制数,对二进制数表示中的每一位(即二进位)逐一进行相应运算,即双目运算。
-
「左移运算」、「右移运算」是将左侧整数作为二进制数,将右侧整数作为移动位数,然后对左侧二进制数的全部位进行移位运算,每次移动一位,总共移动右侧整数次位,也是双目运算。
而「取反运算」是单目运算,是对一个整数的二进制数进行的位运算。
我们先来看下这 $6$ 种位运算的规则,再来进行详细讲解。
运算符 | 描述 | 规则 |
---|---|---|
& | 按位与运算符 | 只有对应的两个二进位都为 $1$ 时,结果位才为 $1$。 |
| | 按位或运算符 | 只要对应的两个二进位有一个为 $1$ 时,结果位就为 $1$。 |
^ | 按位异或运算符 | 对应的两个二进位相异时,结果位为 $1$,二进位相同时则结果位为 $0$。 |
~ | 取反运算符 | 对二进制数的每个二进位取反,使数字 $1$ 变为 $0$,$0$ 变为 $1$。 |
<< | 左移运算符 | 将二进制数的各个二进位全部左移若干位。<< 右侧数字指定了移动位数,高位丢弃,低位补 $0$。 |
>> | 右移运算符 | 对二进制数的各个二进位全部右移若干位。>> 右侧数字指定了移动位数,低位丢弃,高位补 $0$。 |
2.1 按位与运算
按位与运算(AND):按位与运算符为
&
。其功能是对两个二进制数的每一个二进位进行与运算。
-
按位与运算规则:只有对应的两个二进位都为 $1$ 时,结果位才为 $1$。
-
1 & 1 = 1
-
1 & 0 = 0
-
0 & 1 = 0
-
0 & 0 = 0
-
举个例子,对二进制数 $01111100{(2)}$ 与 $00111110{(2)}$ 进行按位与运算,结果为 $00111100_{(2)}$,如图所示:
2.2 按位或运算
按位或运算(OR):按位或运算符为
|
。其功能对两个二进制数的每一个二进位进行或运算。
-
按位或运算规则:只要对应的两个二进位有一个为 $1$ 时,结果位就为 $1$。
-
1 | 1 = 1
-
1 | 0 = 1
-
0 | 1 = 1
-
0 | 0 = 0
-
举个例子,对二进制数 $01001010{(2)}$ 与 $01011011{(2)}$ 进行按位或运算,结果为 $01011011_{(2)}$,如图所示:
2.3 按位异或运算
按位异或运算(XOR):按位异或运算符为
^
。其功能是对两个二进制数的每一个二进位进行异或运算。
-
按位异或运算规则:对应的两个二进位相异时,结果位为 $1$,二进位相同时则结果位为 $0$。
-
0 ^ 0 = 0
-
1 ^ 0 = 1
-
0 ^ 1 = 1
-
1 ^ 1 = 0
举个例子,对二进制数 $01001010{(2)}$ 与 $01000101{(2)}$ 进行按位异或运算,结果为 $00001111_{(2)}$,如图所示:
2.4 取反运算
取反运算(NOT):取反运算符为
~
。其功能是对一个二进制数的每一个二进位进行取反运算。
-
取反运算规则:使数字 $1$ 变为 $0$,$0$ 变为 $1$。
-
~0 = 1
-
~1 = 0
-
举个例子,对二进制数 $01101010_{(2)}$ 进行取反运算,结果如图所示:
2.5 左移运算和右移运算
左移运算(SHL): 左移运算符为
<<
。其功能是对一个二进制数的各个二进位全部左移若干位(高位丢弃,低位补 $0$)。
举个例子,对二进制数 $01101010{(2)}$ 进行左移 $1$ 位运算,结果为 $11010100{(2)}$,如图所示:
右移运算(SHR): 右移运算符为
>>
。其功能是对一个二进制数的各个二进位全部右移若干位(低位丢弃,高位补 $0$)。
举个例子,对二进制数 $01101010{(2)}$ 进行右移 $1$ 位运算,结果为 $00110101{(2)}$,如图所示:
3. 位运算的应用
3.1 位运算的常用操作
3.1.1 判断整数奇偶
一个整数,只要是偶数,其对应二进制数的末尾一定为 $0$;只要是奇数,其对应二进制数的末尾一定为 $1$。所以,我们通过与 $1$ 进行按位与运算,即可判断某个数是奇数还是偶数。
-
(x & 1) == 0
为偶数。 -
(x & 1) == 1
为奇数。
3.1.2 二进制数选取指定位
如果我们想要从一个二进制数 $X$ 中取出某几位,使取出位置上的二进位保留原值,其余位置为 $0$,则可以使用另一个二进制数 $Y$,使该二进制数上对应取出位置为 $1$,其余位置为 $0$。然后令两个数进行按位与运算(X & Y
),即可得到想要的数。
举个例子,比如我们要取二进制数 $X = 01101010{(2)}$ 的末尾 $4$ 位,则只需将 $X = 01101010{(2)}$ 与 $Y = 00001111{(2)}$ (末尾 $4$ 位为 $1$,其余位为 $0$) 进行按位与运算,即 01101010 & 00001111 == 00001010
。其结果 $00001010$ 就是我们想要的数(即二进制数 $01101010{(2)}$ 的末尾 $4$ 位)。
3.1.3 将指定位设置为 $1$
如果我们想要把一个二进制数 $X$ 中的某几位设置为 $1$,其余位置保留原值,则可以使用另一个二进制数 $Y$,使得该二进制上对应选取位置为 $1$,其余位置为 $0$。然后令两个数进行按位或运算(X | Y
),即可得到想要的数。
举个例子,比如我们想要将二进制数 $X = 01101010{(2)}$ 的末尾 $4$ 位设置为 $1$,其余位置保留原值,则只需将 $X = 01101010{(2)}$ 与 $Y = 00001111{(2)}$(末尾 $4$ 位为 $1$,其余位为 $0$)进行按位或运算,即 01101010 | 00001111 = 01101111
。其结果 $01101111$ 就是我们想要的数(即将二进制数 $01101010{(2)}$ 的末尾 $4$ 位设置为 $1$,其余位置保留原值)。
3.1.4 反转指定位
如果我们想要把一个二进制数 $X$ 的某几位进行反转,则可以使用另一个二进制数 $Y$,使得该二进制上对应选取位置为 $1$,其余位置为 $0$。然后令两个数进行按位异或运算(X ^ Y
),即可得到想要的数。
举个例子,比如想要将二进制数 $X = 01101010{(2)}$ 的末尾 $4$ 位进行反转,则只需将 $X = 01101010{(2)}$ 与 $Y = 00001111{(2)}$(末尾 $4$ 位为 $1$,其余位为 $0$)进行按位异或运算,即 01101010 ^ 00001111 = 01100101
。其结果 $01100101$ 就是我们想要的数(即将二进制数 $X = 01101010{(2)}$ 的末尾 $4$ 位进行反转)。
3.1.5 交换两个数
通过按位异或运算可以实现交换两个数的目的(只能用于交换两个整数)。
a, b = 10, 20 a ^= b b ^= a a ^= b print(a, b)
3.1.6 将二进制最右侧为 $1$ 的二进位改为 $0$
如果我们想要将一个二进制数 $X$ 最右侧为 $1$ 的二进制位改为 $0$,则只需通过 X & (X - 1)
的操作即可完成。
比如 $X = 01101100{(2)}$,$X - 1 = 01101011{(2)}$,则 X & (X - 1) == 01101100 & 01101011 == 01101000
,结果为 $01101000_{(2)}$(即将 $X$ 最右侧为 $1$ 的二进制为改为 $0$)。
3.1.7 计算二进制中二进位为 $1$ 的个数
从 3.1.6 中得知,通过 X & (X - 1)
我们可以将二进制 $X$ 最右侧为 $1$ 的二进制位改为 $0$,那么如果我们不断通过 X & (X - 1)
操作,最终将二进制 $X$ 变为 $0$,并统计执行次数,则可以得到二进制中二进位为 $1$ 的个数。
具体代码如下:
class Solution: def hammingWeight(self, n: int) -> int: cnt = 0 while n: n = n & (n - 1) cnt += 1 return cnt
3.1.8 判断某数是否为 $2$ 的幂次方
通过判断 X & (X - 1) == 0
是否成立,即可判断 $X$ 是否为 $2$ 的幂次方。
这是因为:
-
凡是 $2$ 的幂次方,其二进制数的某一高位为 $1$,并且仅此高位为 $1$,其余位都为 $0$。比如:$4{(10)} = 00000100{(2)}$、$8{(10)} = 00001000{(2)}$。
-
不是 $2$ 的幂次方,其二进制数存在多个值为 $1$ 的位。比如:$5{10} = 00000101{(2)}$、$6{10} = 00000110{(2)}$。
接下来我们使用 X & (X - 1)
操作,将原数对应二进制数最右侧为 $1$ 的二进位改为 $0$ 之后,得到新值:
-
如果原数是 $2$ 的幂次方,则通过
X & (X - 1)
操作之后,新值所有位都为 $0$,值为 $0$。 -
如果该数不是 $2$ 的幂次方,则通过
X & (X - 1)
操作之后,新值仍存在不为 $0$ 的位,值肯定不为 $0$。
所以我们可以通过是否为 $0$ 即可判断该数是否为 $2$ 的幂次方。
3.2 位运算的常用操作总结
功 能 | 位运算 | 示例 |
---|---|---|
去掉最后一位 | x >> 1 | 101101 -> 10110 |
在最后加一个 0 | x << 1 | 101101 -> 1011010 |
在最后加一个 1 | (x << 1) + 1 | 101101 -> 1011011 |
把最后一位变成 1 | x | 1 | 101100 -> 101101 |
把最后一位变成 0 | x | 1 - 1 | 101101 -> 101100 |
最后一位取反 | x ^ 1 | 101101 -> 101100 |
把右数第 k 位变成 1 | x | (1 << (k - 1)) | 101001 -> 101101, k = 3 |
把右数第 k 位变成 0 | x & ~(1 << (k - 1)) | 101101 -> 101001, k = 3 |
右数第 k 位取反 | x ^ (1 << (k - 1)) | 101001 -> 101101, k = 3 |
取末尾 3 位 | x & 7 | 1101101 -> 101 |
取末尾 k 位 | x & 15 | 1101101 -> 1101, k = 4 |
取右数第 k 位 | x >> (k - 1) & 1 | 1101101 -> 1, k = 4 |
把末尾 k 位变成 1 | x | (1 << k - 1) | 101001 -> 101111, k = 4 |
末尾 k 位取反 | x ^ (1 << k - 1) | 101001 -> 100110, k = 4 |
把右边连续的 1 变成 0 | x & (x + 1) | 100101111 -> 100100000 |
把右边起第一个 0 变成 1 | x | (x + 1) | 100101111 -> 100111111 |
把右边连续的 0 变成 1 | x | (x - 1) | 11011000 -> 11011111 |
只保留右边连续的 1 | (x ^ (x + 1)) >> 1 | 100101111 -> 1111 |
去掉右边起第一个 1 的左边 | x & (x ^ (x - 1)) 或 x & (-x) | 100101000 -> 1000 |
从右边开始,把最后一个 1 改写成 0 | x & (x - 1) | 100101000 -> 100100000 |
3.3 二进制枚举子集
除了上面的这些常见操作,我们经常常使用二进制数第 $1 \sim n$ 位上 $0$ 或 $1$ 的状态来表示一个由 $1 \sim n$ 组成的集合。也就是说通过二进制来枚举子集。
3.3.1 二进制枚举子集简介
先来介绍一下「子集」的概念。
-
子集:如果集合 $A$ 的任意一个元素都是集合 $S$ 的元素,则称集合 $A$ 是集合 $S$ 的子集。可以记为 $A \in S$。
有时候我们会遇到这样的问题:给定一个集合 $S$,枚举其所有可能的子集。
枚举子集的方法有很多,这里介绍一种简单有效的枚举方法:「二进制枚举子集算法」。
对于一个元素个数为 $n$ 的集合 $S$ 来说,每一个位置上的元素都有选取和未选取两种状态。我们可以用数字 $1$ 来表示选取该元素,用数字 $0$ 来表示不选取该元素。
那么我们就可以用一个长度为 $n$ 的二进制数来表示集合 $S$ 或者表示 $S$ 的子集。其中二进制的每一个二进位都对应了集合中某一个元素的选取状态。对于集合中第 $i$ 个元素来说,二进制对应位置上的 $1$ 代表该元素被选取,$0$ 代表该元素未被选取。
举个例子,比如长度为 $5$ 的集合 $S = \lbrace 5, 4, 3, 2, 1 \rbrace$,我们可以用一个长度为 $5$ 的二进制数来表示该集合。
比如二进制数 $11111_{(2)}$ 就表示选取集合的第 $1$ 位、第 $2$ 位、第 $3$ 位、第 $4$ 位、第 $5$ 位元素,也就是集合 $\lbrace 5, 4, 3, 2, 1 \rbrace$,即集合 $S$ 本身。如下表所示:
集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 |
---|---|---|---|---|---|
二进位对应值 | 1 | 1 | 1 | 1 | 1 |
对应选取状态 | 选取 | 选取 | 选取 | 选取 | 选取 |
再比如二进制数 $10101_{(2)}$ 就表示选取集合的第 $1$ 位、第 $3$ 位、第 $5$ 位元素,也就是集合 $\lbrace 5, 3, 1 \rbrace$。如下表所示:
集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 |
---|---|---|---|---|---|
二进位对应值 | 1 | 0 | 1 | 0 | 1 |
对应选取状态 | 选取 | 未选取 | 选取 | 未选取 | 选取 |
再比如二进制数 $01001_{(2)}$ 就表示选取集合的第 $1$ 位、第 $4$ 位元素,也就是集合 $\lbrace 4, 1 \rbrace$。如下标所示:
集合 S 中元素位置 | 5 | 4 | 3 | 2 | 1 |
---|---|---|---|---|---|
二进位对应值 | 0 | 1 | 0 | 0 | 1 |
对应选取状态 | 未选取 | 选取 | 未选取 | 未选取 | 选取 |
通过上面的例子我们可以得到启发:对于长度为 $5$ 的集合 $S$ 来说,我们只需要从 $00000 \sim 11111$ 枚举一次(对应十进制为 $0 \sim 2^5 - 1$)即可得到长度为 $5$ 的集合 $S$ 的所有子集。
我们将上面的例子拓展到长度为 $n$ 的集合 $S$。可以总结为:
-
对于长度为 $n$ 的集合 $S$ 来说,只需要枚举 $0 \sim 2^n - 1$(共 $2^n$ 种情况),即可得到集合 $S$ 的所有子集。
3.3.2 二进制枚举子集代码
class Solution:
def subsets(self, S): # 返回集合 S 的所有子集
n = len(S) # n 为集合 S 的元素个数
sub_sets = [] # sub_sets 用于保存所有子集
for i in range(1 << n): # 枚举 0 ~ 2^n - 1
sub_set = [] # sub_set 用于保存当前子集
for j in range(n):
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/7cf30fea542d4a3aa0c91fdd5f43d8ac.png)
<h1>一、机器学习概述</h1>
<h5>1.1、什么是机器学习?</h5>
<p>机器学习是从数据中自动分析获得规律(模型),并利用规律对未知数据进行预测</p>
<h5>1.2、为什么需要机器学习?</h5>
<ul>
<li>解放生产力,智能客服,可以不知疲倦的24小时作业</li>
<li>解决专业问题,ET医疗,帮助看病</li>
<li>提供社会便利,例如杭州的城市大脑</li>
</ul>
<h5>1.3、机器学习应用场景</h5>
<ul>
<li>自然语言处理</li>
<li>无人驾驶</li>
<li>计算机视觉</li>
<li>推荐系统</li>
</ul>
<h1>二、数据来源与类型</h1>
<h5>2.1、数据的来源</h5>
<ul>
<li>企业日益积累的大量数据(互联网公司更为显著)</li>
<li>政府掌握的各种数据</li>
<li>科研机构的实验数据</li>
</ul>
<h5>2.2、数据的类型</h5>
<p>数据的类型将是机器学习模型不同问题不同处理的依据。数据的类型包括:</p>
<p><strong>离散型数据</strong>:由记录不同类别个体的数目所得到的数据,又称计数数据,所有这些数据全部都是整数,而且不能再细分,也不能进一步提高他们的精确度。</p>
<p><strong>连续型数据</strong>:变量可以在某个范围内取任一数,即变量的取值可以是连续的,如,长度、时间、质量值等,这类整数通常是非整数,含有小数部分。</p>
<p><strong>注意</strong>:</p>
<ul>
<li>只要记住一点,离散型是区间内不可分,连续型是区间内可分</li>
</ul>
<h5>2.3、可用的数据集</h5>
<p><strong>scikit-learn</strong>:数据量较小 ,方便学习。<br>
<strong>UCI</strong>:收录了360个数据集,覆盖科学、生活、经济等领域 ,数据量几十万。<br>
<strong>Kaggle</strong>:大数据竞赛平台,80万科学家,真实数据,数据量巨大。</p>
<p>常用数据集数据的结构组成:特征值+目标值,如下图:</p>
<br>
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/e6f9f0a51a8240d8b2e65116a0e12071.png)
</div>
<h1>三、数据的特征工程</h1>
<h5>3.1、特征工程是什么?</h5>
<p>特征工程是将原始数据转换为更好地代表预测模型的潜在问题的特征的过程,从而提高了对未知数据的模型准确性。</p>
<h5>3.2、特征工程的意义</h5>
<p><strong>意义</strong>:直接影响模型的预测结果。</p>
<h5>3.3、scikit-learn</h5>
<ul>
<li>Python语言的机器学习工具</li>
<li>Scikit-learn包括许多知名的机器学习算法的实现</li>
<li>Scikit-learn文档完善,容易上手,丰富的API,使其在学术界颇受欢迎。</li>
<li>目前稳定版本0.18</li>
<li>安装:pip3 install Scikit-learn</li>
<li>引用:import sklearn</li>
</ul>
<h5>3.4、数据的特征抽取</h5>
<h6>3.4.1、特点:</h6>
<ul>
<li>特征抽取针对非连续型数据</li>
<li>特征抽取对文本等进行特征值化</li>
</ul>
<h6>3.4.2、<strong>sklearn特征抽取API</strong> :</h6>
<p>sklearn.feature_extraction</p>
<h6>3.4.3、<strong>字典特征抽取</strong> :</h6>
<p><strong>作用</strong>:对字典数据进行特征值化<br>
<strong>类</strong>:sklearn.feature_extraction.DictVectorizer<br>
<strong>DictVectorizer语法</strong>:</p>
```c
DictVectorizer(sparse=True,…)
DictVectorizer.fit_transform(X)
X:字典或者包含字典的迭代器
返回值:返回sparse矩阵
DictVectorizer.inverse_transform(X)
X:array数组或者sparse矩阵
返回值:转换之前数据格式
DictVectorizer.get_feature_names()
返回类别名称
DictVectorizer.transform(X)
按照原先的标准转换
流程:
1、实例化类DictVectorizer
2、调用fit_transform方法输入数据并转换
举一个栗子:
from sklearn.feature_extraction import DictVectorizer
dict = DictVectorizer(sparse=False)
data = dict.fit_transform([{'name': '张飞','score': 70}, {'name': '赵云','score':100}, {'name': '刘备','score': 98}])
print(dict.get_feature_names())
print(data)
运行结果:
['name=刘备', 'name=张飞', 'name=赵云', 'score']
[[ 0. 1. 0. 70.]
[ 0. 0. 1. 100.]
[ 1. 0. 0. 98.]]
从中,我们可以看出:对于字典 [{'name': '张飞','score': 70}, {'name': '赵云','score':100}, {'name': '刘备','score': 98}] ,DictVectorizer类将汉字(张飞,赵云,刘备)转成了one-hot编码(0,1,0),而数值类型的数据(70,100,98)是不做处理的。
什么是one-hot编码?
One-Hot编码,又称为一位有效编码,主要是采用N位状态寄存器来对N个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候只有一位有效。
3.4.4、文本特征抽取
作用:对文本数据进行特征值化
类:sklearn.feature_extraction.text.CountVectorizer
CountVectorizer语法:
CountVectorizer(max_df=1.0,min_df=1,…)
返回词频矩阵
CountVectorizer.fit_transform(X,y)
X:文本或者包含文本字符串的可迭代对象
返回值:返回sparse矩阵
CountVectorizer.inverse_transform(X)
X:array数组或者sparse矩阵
返回值:转换之前数据格式
CountVectorizer.get_feature_names()
返回值:单词列表
流程:
1、实例化类CountVectorizer
2、调用fit_transform方法输入数据并转换
举一个栗子:
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
data = cv.fit_transform(["我爱学习,学习使我进步", "我爱work,work 使我快乐"])
print(cv.get_feature_names())
print(data.toarray())
运行结果:
['work', '使我快乐', '学习使我进步', '我爱work', '我爱学习']
[[0 0 1 0 1]
[1 1 0 1 0]]
可以看到,API中的CountVectorizer类将中文转换成了单个词语,并给每个词语的出现个数进行了统计。有一点要注意的是,程序并不会给中文分词,所以,例子中,‘学习使我进步’程序认为是一个词语,这种情况下,可以用空格,或者逗号,将中文进行分割。还有一点要注意的是,如果是英文的话,是不会统计单个字母的,因为字母的统计是没有意义的,同理,CountVectorizer也不支持单个中文字。
我们可以验证一下栗子:
英文栗子:
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
data = cv.fit_transform(["I like study , study makes me happy", "I am a good student"])
print(cv.get_feature_names())
print(data.toarray())
运行结果:
['am', 'good', 'happy', 'like', 'makes', 'me', 'student', 'study']
[[0 0 1 1 1 1 0 2]
[1 1 0 0 0 0 1 0]]
中文栗子:
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
data = cv.fit_transform(["我 热爱 学习, 学习 使我 进步", "我 是 一个 好学生"])
print(cv.get_feature_names())
print(data.toarray())
运行结果:
['一个', '使我', '好学生', '学习', '热爱', '进步']
[[0 1 0 2 1 1]
[1 0 1 0 0 0]]
从中文栗子和英文栗子中,我们可以看到单个英文和单个中文是不会统计数量的,因为统计单个中文或者英文是没有意义的。
手动加空格,或者加逗号分隔始终是指标不治本,如果给我们一篇文章,让我们去处理的话,那要累到手瘫了。那么,有没有好的办法呢?是有的,那就是用 python 里提供的 jieba 分词类库。
我们再来举一个栗子:
import jieba
from sklearn.feature_extraction.text import CountVectorizer
con1 = jieba.cut("我热爱学习,学习使我感到进步。")
con2 = jieba.cut("我热爱工作,工作可以让我感到快乐。")
con3 = jieba.cut("如果不让我学习,也不让我工作,我会觉得浑身不舒服。")
# 转换成列表
content1 = list(con1)
content2 = list(con2)
content3 = list(con3)
# 把列表转换成字符串
c1 = ' '.join(content1)
c2 = ' '.join(content2)
c3 = ' '.join(content3)
print(c1, c2, c3)
cv = CountVectorizer()
data = cv.fit_transform([c1, c2, c3])
print(cv.get_feature_names())
print(data.toarray())
运行结果:
我 热爱 学习 , 学习 使 我 感到 进步 。 我 热爱工作 , 工作 可以 让 我 感到 快乐 。 如果 不让 我 学习 , 也 不让 我 工作 , 我会 觉得 浑身 不 舒服 。
['不让', '可以', '如果', '学习', '工作', '快乐', '感到', '我会', '浑身', '热爱', '热爱工作', '舒服', '觉得', '进步']
[[0 0 0 2 0 0 1 0 0 1 0 0 0 1]
[0 1 0 0 1 1 1 0 0 0 1 0 0 0]
[2 0 1 1 1 0 0 1 1 0 0 1 1 0]]
从栗子中可以看到,jieba分词包把句子进行了分词,然后对每个词语的个数进行了统计,但是对于 ‘我’、 ‘也’ 这样的单个中文,并没有统计个数,因为这样的单个中文统计没有意义。
至此,我们学会了统计文章中英文和中文的词语的个数,那么,单纯统计一个词语出现的个数越多就表示这个词语在文章中越重要吗?那比如,“我们”,“你们”,“他们”,“你的”,这样的指示代词出现的频率应该是最高的,能说明代词是文章的重点吗?显然不是。怎么过滤掉这种出现很多,但是并不是重点的词语呢?我们就要开始学习一种 TF-IDF 的处理方法了。
3.4.5、TF-IDF
主要思想:如果某个词或短语在一篇文章中出现的概率高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。
作用:用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。
类:sklearn.feature_extraction.text.TfidfVectorizer
TfidfVectorizer语法:
TfidfVectorizer(stop_words=None,…)
返回词的权重矩阵
TfidfVectorizer.fit_transform(X,y)
X:文本或者包含文本字符串的可迭代对象
返回值:返回sparse矩阵
TfidfVectorizer.inverse_transform(X)
X:array数组或者sparse矩阵
返回值:转换之前数据格式
TfidfVectorizer.get_feature_names()
返回值:单词列表
我们开始举个栗子:
import jieba
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
con1 = jieba.cut("我们热爱学习,学习使我们感到进步。")
con2 = jieba.cut("我们热爱工作,工作可以让我们感到快乐。")
con3 = jieba.cut("如果不让我们学习,不让我们工作,就会感到浑身不舒服。")
# 转换成列表
content1 = list(con1)
content2 = list(con2)
content3 = list(con3)
# 把列表转换成字符串
c1 = ' '.join(content1)
c2 = ' '.join(content2)
c3 = ' '.join(content3)
print(c1, c2, c3)
tf = TfidfVectorizer()
data = tf.fit_transform([c1, c2, c3])
print(tf.get_feature_names())
print(data.toarray())
运行结果:
我们 热爱 学习 , 学习 使 我们 感到 进步 。 我们 热爱工作 , 工作 可以 让 我们 感到 快乐 。 如果 不让 我们 学习 , 不让 我们 工作 , 就 会 感到 浑身 不 舒服 。
['不让', '可以', '如果', '学习', '工作', '快乐', '感到', '我们', '浑身', '热爱', '热爱工作', '舒服', '进步']
[[0. 0. 0. 0.61800047 0. 0.
0.23996625 0.4799325 0. 0.40629818 0. 0.
0.40629818]
[0. 0.43345167 0. 0. 0.32965117 0.43345167
0.25600354 0.51200708 0. 0. 0.43345167 0.
0. ]
[0.63561168 0. 0.31780584 0.24169953 0.24169953 0.
0.18770125 0.3754025 0.31780584 0. 0. 0.31780584
0. ]]
我们可以看到,通过 TF-IDF 的处理,把每句话的重点单词找出来了,第一句话 “我们” 和 “学习” 都出现了2次 ,但 “学习”是0.61800047,“我们” 是0.4799325, 重点是 “学习” , 第三句话“我们” 和 “ 不让 ” 都出现了2次 ,但 “不让”是0.63561168 ,“我们” 是0.3754025 , 第三句话重点强调 “不让” ,因为 “我们” 在三句话中都频繁出现,并不是每一句话的 “专属” , 也就不是一句话的重点了。
3.5、数据的特征处理
3.5.1、特征处理是什么?
通过特定的统计方法(数学方法)将数据转换成算法要求的数据。
3.5.2、为什么需要特征处理?
每个特征的单位不一样,比如相亲的时候,有乘坐飞机的里程数,人的身高,玩游戏的时间,里程数的数值很大,身高相对里程数值很小,那么在做分析的时候,里程数的数值就会起决定性作用。事实在,在统计分析的时候,分析人员认为每个特征同样重要。所以我们需要把不同单位的数值进行特征处理,不因为数值的相差巨大而造成特征的差别。
3.5.3、特征处理的方法
数值型数据:(标准缩放)
1、归一化
2、标准化
3、缺失值
类别型数据:one-hot编码
时间类型:时间的切分
3.5.4、归一化:
特点:通过对原始数据进行变换把数据映射到(默认为[0,1])之间。
公式: 𝑋′= (𝑥−𝑚𝑖𝑛)/(𝑚𝑎𝑥−𝑚𝑖𝑛) 𝑋′′=𝑋′∗(𝑚𝑥−𝑚𝑖)+𝑚𝑖
其中:作用于每一列,max为一列的最大值,min为一列的最小值,那么X’’为最终结果,mx,mi分别为指定区间值默认mx为1,mi为0。
sklearn归一化API : sklearn.preprocessing.MinMaxScaler
MinMaxScaler语法:
MinMaxScalar(feature_range=(0,1)…)
每个特征缩放到给定范围(默认[0,1])
MinMaxScalar.fit_transform(X)
X:numpy array格式的数据[n_samples,n_features]
返回值:转换后的形状相同的array
归一化步骤:
1、实例化MinMaxScalar
2、通过fit_transform转换
举一个栗子
from sklearn.preprocessing import MinMaxScaler
mm = MinMaxScaler(feature_range=(0, 1))
data = mm.fit_transform([[90,2,10,40],[60,4,15,45],[75,3,15,46]])
print(data)
运行结果:
[[1. 0. 0. 0. ]
[0. 1. 1. 0.83333333]
[0.5 0.5 1. 1. ]]
我们可以看到,之前的数据,特征一( 90,60,75) 是比特征二(2,4,3)在数值上大很多的,那么,如果不做特征处理,直接带入模型处理的话,特征一显然就占决定性作用了,就没有特征二什么事情了。而进行归一化转换之后,特征一和特征二在数值上就在同一量级了,他们就变得“同等重要”了。
归一化总结:注意在特定场景下最大值最小值是变化的,另外,最大值与最小值非常容易受异常点影响,所以这种方法鲁棒性较差,只适合传统精确小数据场景。
对于归一化来说:如果出现异常点,影响了最大值和最小值,那么结果显然会发生改变。那么,我们有没有好的解决办法呢?有的,那就是标准化。
3.5.5、标准化
特点:通过对原始数据进行变换把数据变换到均值为0,方差为1范围内
公式 :𝑋′= (𝑥−mean)/𝜎
其中,mean为平均值,𝜎为标准差(考量数据的稳定性)
对于标准化来说:如果出现异常点,由于具有一定数据量,少量的异常点对于平均值的影响并不大,从而方差改变较小。
sklearn特征处理API : scikit-learn.preprocessing.StandardScaler
StandardScaler(…)
处理之后每列来说所有数据都聚集在均值0附近方差为1
StandardScaler.fit_transform(X,y)
X:numpy array格式的数据[n_samples,n_features]
返回值:转换后的形状相同的array
StandardScaler.mean_
原始数据中每列特征的平均值
StandardScaler.std_
原始数据每列特征的方差
标准化步骤:
1、实例化StandardScaler
2、通过fit_transform转换
举个栗子:
from sklearn.preprocessing import StandardScaler
std = StandardScaler()
data = std.fit_transform([[ 1., -1., 3.],[ 2., 4., 2.],[ 4., 6., -1.]])
print(data)
运行结果:
[[-1.06904497 -1.35873244 0.98058068]
[-0.26726124 0.33968311 0.39223227]
[ 1.33630621 1.01904933 -1.37281295]]
标准化总结:标准化可以避免最大值,最小值发生异常值的干扰。在已有样本足够多的情况下比较稳定,适合现代嘈杂大数据场景。
缺失值处理方法
删除:如果每列或者行数据缺失值达到一定的比例,建议放弃整行或者整列。
插补:可以通过缺失值每行或者每列的平均值、中位数来填充。(主要方法)
sklearn缺失值API: sklearn.preprocessing.Imputer
Imputer语法:
Imputer(missing_values='NaN', strategy='mean', axis=0)
完成缺失值插补
Imputer.fit_transform(X,y)
X:numpy array格式的数据[n_samples,n_features]
返回值:转换后的形状相同的array
Imputer流程:
1、初始化Imputer,指定”缺失值”,指定填补策略,指定行或列
2、调用fit_transform
关于np.nan(np.NaN)
1、 numpy的数组中可以使用np.nan/np.NaN来代替缺失值,属于float类型。
2、如果是文件中的一些缺失值,可以替换成nan,通过np.array转化成float型的数组即可。
3.6、数据的特征选择
3.6.1、特征选择是什么?
特征选择就是单纯地从提取到的所有特征中选择部分特征作为训练集特征,特征在选择前和选择后可以改变值、也不改变值,但是选择后的特征维数肯定比选择前小,毕竟我们只选择了其中的一部分特征。
3.6.2、为什么要做特征选择?
冗余:部分特征的相关度高,容易消耗计算性能
噪声:部分特征对预测结果有负影响
3.6.3、特征选择主要方法:
Filter(过滤式):VarianceThreshold
Embedded(嵌入式):正则化、决策树
3.6.4、sklearn特征选择API
sklearn.feature_selection.VarianceThreshold
VarianceThreshold语法:
VarianceThreshold(threshold = 0.0)
删除所有低方差特征
Variance.fit_transform(X,y)
X:numpy array格式的数据[n_samples,n_features]
返回值:训练集差异低于threshold的特征将被删除。
默认值是保留所有非零方差特征,即删除所有样本中具有相同值的特征。
3.6.5、VarianceThreshold流程:
1、初始化VarianceThreshold,指定阀值方差
2、调用fit_transform
3.6.6、举个栗子:
from sklearn.feature_selection import VarianceThreshold
var = VarianceThreshold(threshold=1.0)
data = var.fit_transform([[0, 2, 0, 3], [0, 1, 4, 3], [0, 1, 1, 3]])
print(data)
运行结果:
[[0]
[4]
[1]]
从栗子中,可以看到,把方差是0的第一个特征值(0,0,0),第4个特征值(3,3,3),和方差小于1的第2个特征值(2,1,2)都给删除了,只剩下(0,4,1)这个方差大于1的特征值。默认情况下,threshold 等于1.0 。
3.7、降维 (PCA)
3.7.1、sklearn降维API :
sklearn. decomposition
3.7.2、本质:
PCA是一种分析、简化数据集的技术。
3.7.3、目的:
是数据维数压缩,尽可能降低原数据的维数(复杂度),损失少量信息。
3.7.4、作用:
可以削减回归分析或者聚类分析中特征的数量。
3.7.5、PCA语法:
PCA(n_components=None)
将数据分解为较低维数空间
PCA.fit_transform(X)
X:numpy array格式的数据[n_samples,n_features]
返回值:转换后指定维度的array
3.7.6、PCA流程:
1、初始化PCA,指定减少后的维度
2、调用fit_transform
3.7.7、举个栗子
from sklearn.decomposition import PCA
pca = PCA(n_components=0.9)
data = pca.fit_transform([[90,2,10,40],[60,4,15,45],[75,3,15,46]])
print(data)
运行结果:
[[ 15.77507261]
[-15.11129418]
[ -0.66377843]]
该栗子中,原本有4个特征的数据,变成了一个特征,并且该特征保留了原来90%的信息,n_components=0.9 。
四、机器学习基础
4.1、机器学习开发流程
首先要根据原始数据明确问题做什么,建立模型: 根据数据类型划分应用种类;然后做数据的基本处理:(缺失值,合并表等)和特征工程(特征进行处理) (重要);其次,找到合适的算法进行预测: 最后,对模型评估,根据模型的准确率,判定效果,如果合格 ,则上线使用,以API形式提供,如果不合格,则要换算法 或者重新提取特征工程,如此循环往复继续下去,直到得到满意的模型。
4.2、机器学习模型是什么?
定义:通过一种映射关系将输入值到输出值。
简单来讲,模型 = 算法 + 数据。
4.3、机器学习算法分类
# 枚举第 i 位元素
if i >> j & 1: # 如果第 i 为元素对应二进位删改为 1,则表示选取该元素
sub_set.append(S[j]) # 将选取的元素加入到子集 sub_set 中
sub_sets.append(sub_set) # 将子集 sub_set 加入到所有子集数组 sub_sets 中
return sub_sets # 返回所有子集
<p></p>
</div>