向量
真实世界里的数字是有方向的。
我印象最深刻的是神作《天蝎》的里剧情。
主人公和匪徒在天台上追逐,可匪徒失足了,幸好还有一只手扣住了墙。
主人公并没有救,因为主人公:
- 重 72 公斤,俩臂伸展为 1.7 米,1.8米的个子,弯腰到 90 度;
而匪徒:
- 90 公斤。
那,主人公能拉起匪徒吗?
因为俩个人的用力的方向呈 90 度角,不是简单的合力。
所以不仅不能,而且可能会被拖下去。
真实世界是有方向的,数学要研究真实世界,那绝对不能只是看数值大小,还得关心方向。
描述带方向的数字的工具,叫【向量】,如果只关心数值,不关心方向的数量叫【标量】。
因为我们的世界是 3 维的,所以用 3 个向量就可以研究我们的世界了。
不过在一些虚拟的世界里不止,比如游戏的血量、蓝量、攻击、防御等等。
这时候就需要向量的形式化定义了:
代码实现向量的初始化:
#include<stdio.h>
#include<malloc.h>
typedef int T;
typedef struct _vector{ // 向量建模
T *arr; // 向量是一组数字,用数组描述很方便
int len;
}vector;
void init( vector * v ) // 初始化为0
{
printf("向量维度:> ");
scanf("%d", &v->len);
v->arr = (T*)malloc(sizeof(T) * v->len);
for( int i = 0; i < v->len; i++ )
v->arr[i] = 0;
}
void print(vector *v){ // 打印向量
for( int p=0; p<v->len; p++ )
printf("%d ", v->arr[p]);
putchar(10);
}
T get_index_val(vector *v, int index) // 获取第 n 个向量
{
return v->arr[index];
}
int get_len( vector *v ) // 获取向量的个数
{
return v->len;
}
int main(){
vector v;
init(&v);
print(&v);
}
向量的运算
向量加法
int add(int *arr_1, int *arr_2)
{
int len = sizeof(arr_1)/sizeof(arr_1[0]); // 获取向量长度,默认俩个向量同维
static int sum = 0;
for( int i=0; i<len; i++ )
sum += arr_1[i] + arr_2[i];
return sum;
}
数量乘法
static
int mul(int *arr_1, int *arr_2)
{
int len = sizeof(arr_1)/sizeof(arr_1[0]); // 获取向量长度,默认俩个向量同维
static int mul = 0;
for( int i=0; i<len; i++ )
mul *= arr_1[i] + arr_2[i];
return mul;
}
向量的模
根据向量坐标点求长度/模:
二维平面可以通过【勾股定理】就求出长度为 5。
- 二维向量的模:
三维平面可以拆分为一个二维平面和一个维平面,比如先计算二维平面的
- 三维向量的模:
一般化后,n维向量的模:
- n维向量的模:
double norm( T *arr, int n ) // 向量模
{
double norm_result = 0.0f;
for( int i=0; i<n; i++ )
norm_result += sqrt(pow(arr[i], 2));
return norm_result;
}
单位向量
单位向量:长度为 1 的向量,
每个向量都有一个单位向量,那怎么求呢?
根据 求 的过程叫【归一化】、【规范化】(normalize)。
单位向量其实有无数多个,但在二维平面中有俩个单位向量比较特殊,就是 坐标正方向上的俩个。
只由 0、1 组成,因此也叫标准单位向量。
同理,三维空间中就有 3 个标准单位向量。
double normalize( T *arr, int n )
{
double normalize_result[n];
for( int i=0; i<n; i++ )
normalize_result[i] = arr[i]/norm(arr, sizeof(arr)/sizeof(arr[0]));
}
零向量
零向量:起点和终点为同一个点的向量,在几何上是原点。
T *zero( int N ) // 传入维度n,返回 n 维零向量
{
T *p = (T*)malloc(sizeof(T) * N);
for( int i=0; i<N; i++ )
p[i] = 0;
return p;
}
反证法证明:存在零向量。
对于任意一个向量 ,都存在一个向量 , 满足 ,上述 唯一。
假设存在另一向量 ,也满足
可以建立一个等式:
点乘
点乘有俩种实现方法,这是上述公式的前一个:
long long dot( T * arr_1, T * arr_2, int len )
{
long long sum = 0;
for( int i=0; i<len; i++ )
sum += arr_1[i] + arr_2[i];
return sum;
}
Python版完整代码:
class Vector:
def __init__(self, lst):
self._values = list(lst)
@classmethod
def zero(cls, dim):
"""返回一个dim维的零向量"""
return cls([0] * dim)
def __add__(self, another):
"""向量加法,返回结果向量"""
assert len(self) == len(another), \
"Error in adding. Length of vectors must be same."
return Vector([a + b for a, b in zip(self, another)])
def __sub__(self, another):
"""向量减法,返回结果向量"""
assert len(self) == len(another), \
"Error in subtracting. Length of vectors must be same."
return Vector([a - b for a, b in zip(self, another)])
def __mul__(self, k):
"""返回数量乘法的结果向量:self * k"""
return Vector([k * e for e in self])
def __rmul__(self, k):
"""返回数量乘法的结果向量:k * self"""
return self * k
def __pos__(self):
"""返回向量取正的结果向量"""
return 1 * self
def __neg__(self):
"""返回向量取负的结果向量"""
return -1 * self
def __iter__(self):
"""返回向量的迭代器"""
return self._values.__iter__()
def __getitem__(self, index):
"""取向量的第index个元素"""
return self._values[index]
def __len__(self):
"""返回向量长度(有多少个元素)"""
return len(self._values)
def __repr__(self):
return "Vector({})".format(self._values)
def __str__(self):
return "({})".format(", ".join(str(e) for e in self._values))
Numpy 向量的基本使用
Python的 list 元素可以是任意练习,设计理念主要用于存储,而 numpy 的作用却不是存储,而是计算。
Numpy 元素就只能是一种。
import numpy as np
# 导入 numpy 并取 np 这个别名,方便调用
vec1 = np.array([1, 2, 3])
# 传一个列表初始化,形成向量,也可以 np.zeros(n)
# 基本属性
print(vec1.size)
# 或print(len(vec1))
# 基本运算
vec2 = np.array([4, 5, 6])
print("{} + {} = {}".format(vec1, vec2, vec1+vec2))
print("{}".format(vec1.dot(vec1)))
# 点乘
print(np.linalg.norm(vec1))
# 模, linalg 是线代包
print(vec1/np.linalg.norm(vec1))
# 归一化
向量应用:新闻分类自动化
现在浏览器上的新闻,都是计算机自动分类的。
计算机分类的原理是三角函数的余弦定理 + 向量。
原理:余弦定理可以只靠俩个三角形的俩个边的向量,计算出这俩个边的夹角。
一篇新闻里会有很多词,像 “之乎者也的” 这种虚词,对判断新闻的分类没有太大的意义。而像 “股票”、“利息” 这种实词,是判断新闻分类的重点词。
科学家精选了一个词汇表,这里面收录着 64000 个词,每个词都对应一个编号。他们先把大量文字数据输入计算机,算出每个词出现的次数。
一般出现次数越少的词越有搜索价值,比如 “爱因斯坦”、“某个人名”;而出现次数越多的词,越没有搜索价值,比如“一个”、“这里” 等等。
根据这个标准,把词汇表里的64000个词都算出各自的权重,越特殊的词权重越大。
然后,再往计算机里输入要分类的新闻,计算出这64000个词在这篇新闻里的分布,如果某些词没有在这篇新闻里出现,对应的值就是零,如果出现,对应的值就是这个词的权重。
这样,这64000个数,就构成了一个64000维的向量,我们就用这个向量来代表这篇新闻,把它叫做这篇新闻的特征向量。
不同类型的新闻,用词上有不同的特点,比如金融类新闻就经常出现 “股票”、“银行” 这些词,所以不难判断,同类新闻的特征向量会有相似性。
只要算出不同新闻特征向量之间夹角的大小,就可以判断出是不是同一类新闻。
这时就要用到余弦定理,来把两则新闻的特征向量之间的夹角算出来。
科学家可以人工设定一个值,只要两个向量之间的夹角小于这个值,这两则新闻就可以判定成同一类新闻。
在向量中公式转换为:
把公式翻译为代码:
double CosSimilarity(double *va, double *vb, int vn) // vn 是多少维,也就是词典中有多少个词
{
double cossu = 0.0;
double cossda = 0.0;
double cossdb = 0.0;
for (int i = 0; i < vn; i++)
{
cossu += va[i] * vb[i];
cossda += va[i] * va[i];
cossdb += vb[i] * vb[i];
}
return cossu / (sqrt(cossda) * sqrt(cossdb));
}
完整代码:
#include <stdio.h>
#include <math.h>
double CosSimilarity(double *va, double *vb, int vn)
{
double cossu = 0.0;
double cossda = 0.0;
double cossdb = 0.0;
for (int i = 0; i < vn; i++)
{
cossu += va[i] * vb[i];
cossda += va[i] * va[i];
cossdb += vb[i] * vb[i];
}
return cossu / (sqrt(cossda) * sqrt(cossdb));
}
// 建立的词典
const int VN = 11; // 11 个词即 11维
const char *base_words[] =
{
"进攻", "炮弹", "射程", "榴弹炮", "发射", "口径", "迫击炮", "瞄准", "后坐力", "弹道", "目标"
};
/* 原文 */
//第一行: 口径为155毫米的榴弹炮,炮弹的射程超过40公里,炮弹发射后击中目标的弹道是一条抛物线
//第二行: 大口径榴弹炮射程很远且弹道弯曲,炮弹通常都不是直接对着目标瞄准,而是计算好抛物线弹道,以一定的仰角和方向发射炮弹
//第三行: 我们必须统一口径,抵挡敌人发射的糖衣炮弹的进攻
int main()
{
// v1代表原文第一行的 11 个关键字出现的权重(一一对应, 出现n次权重为n)
double v1[] = { 0, 2, 1, 1, 1, 1, 0, 0, 0, 1, 1 };
double v2[] = { 0, 2, 1, 1, 1, 1, 0, 1, 0, 2, 1 };
double v3[] = { 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 };
/* 检查相似度 */
printf("第一行 和 第二行 的相似度: %.2lf\n", CosSimilarity(v1, v2, VN));
printf("第一行 和 第三行 的相似度: %.2lf\n", CosSimilarity(v1, v3, VN));
printf("第二行 和 第三行 的相似度: %.2lf\n", CosSimilarity(v2, v3, VN));
// 代码还可补充,当相似度大于设定值时,归为一类新闻......
return 0;
}
向量应用:筛选简历自动化
向量不仅可以对新闻分类,对人也可以分类。
现在大公司在招聘伙伴时,由于简历特别多,会先用计算机筛选简历。
原理:先把简历向量化,而后计算夹角。
把各种技能和素质列在一张表里,这个表就有 N 个维度啦。
而后不同岗位因为评比的方式不同,某些向量的权值就很高,一些就很低甚至是零。
比如,开发人员的权值:
- 编程能力:4
- 工程经验:2
- 沟通能力:1
- 学历:1
- 领导力:1
- 企业文化融合度:1
接下来计算机会对每份简历进行分析,把每份简历变成一个 N 维的向量,假设是 P。
计算 P、V 的夹角,如果夹角非常小说明某一份简历和某一个岗位比较匹配,这时简历才会递给HR。
所以,写给大公司的简历,一定要突出重点。别把自己描述为全能的,不然直接被计算机卡死啦,最好是先打探内部消息,知道这家公司看重什么维度,您再往上面写。
余弦定理
新闻分类、筛选简历自动化,关键在于算出俩个向量的夹角。
而算出俩个向量的夹角,只需要中学学过的余弦定理即可。
我们可以简单的推导一下:【余弦定理】。
余弦定理是从毕达哥拉斯定理推过来的,就是 。
数学家就是喜欢研究,他们发现:
- 如果 a 和 b 的夹角大于 90度,
- 如果 a 和 b 的夹角小于 90度,
对比一下 就知道夹角是什么样的角了。
把 移到等式左边, 。
将等式左边作为判定因子 ,用 和 比较大小,可以判定夹角:
- ,锐角;
- , 直角;
- ,钝角。
为了消除边长的影响,将 除以 夹角的俩个边长(a、b)的积,使得 的动态范围是在 。
当 时,夹角最大,是 180度。
当 时,是 90度。
如果再除以 2, 的动态范围是在 ,这个数值就等于夹角的余弦。
这种从毕达哥拉斯定理出发,建立角度判定因子 和具体角度的关系,就是我们所学的余弦定理。