马尔科夫模型在Gowalla数据集下的简单实践
马尔科夫模型实践第一战
这是笔者对马尔科夫模型理解的第一步,针对Gowalla模型展开第一次实战,题目是预测ID号为0的用户接下来下一步将会去哪个地点,这仅涉及一些简单的概统知识。
基础知识
题目固然简单,但是因为笔者数学基础不够牢固,所以还是从最简单的概统知识进行介绍。
数学知识
简单的概率计算
概率,亦称“或然率”,它是反映随机事件出现的可能性(likelihood)大小。随机事件是指在相同条件下,可能出现也可能不出现的事件。例如,从一批有正品和次品的商品中,随意抽取一件,“抽得的是正品”就是一个随机事件。设对某一随机现象进行了n次试验与观察,其中A事件出现了m次,即其出现的频率为m/n。经过大量反复试验,常有m/n越来越接近于某个确定的常数(此论断证明详见伯努利大数定律)。该常数即为事件A出现的概率,常用P (A) 表示。
P
(
A
)
=
m
n
(1)
P(A) = \frac{m}{n}\ \tag{1}
P(A)=nm (1)
简单的矩阵知识
矩阵是高等代数学中的常见工具,也常见于统计分析等应用数学学科中。 [2] 在物理学中,矩阵于电路学、力学、光学和量子物理中都有应用;计算机科学中,三维动画制作也需要用到矩阵。 矩阵的运算是数值分析领域的重要问题。将矩阵分解为简单矩阵的组合可以在理论和实际应用上简化矩阵的运算。对一些应用广泛而形式特殊的矩阵,例如稀疏矩阵和准对角矩阵,有特定的快速运算算法。
二维矩阵,形如
1
2
3
4
5
6
7
8
9
(2)
\begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{matrix} \tag{2}
147258369(2)
代码知识
本实现使用的是python语言,数据处理使用的是pandas库函数和numpy库函数。
数据处理
开始时从秋丽师姐那里得到的Gowalla数据集只有单纯的数据和一个单独的txt文档介绍每一行表示什么内容,数据集内容如下:
作为初学者一开始并没有想到使用pandas如此简单的库函数进行数据处理,于是使用了如下的笨办法 :
filename = 'Gowalla_NY.txt'
filename_0 = 'Gowalla_0.txt'
with open(filename) as file_object:
lines = file_object.readlines()
for line in lines:
if line[0] == '0':
with open(filename_0,'a') as file_object_0:
file_object_0.write(line)
很直白的读取数据集,并在之前先将ID为0的用户所有的地点ID数据全部单独保存在一个TXT文件中,处理起来十分麻烦,代码很冗长,而且很难达到预期效果,在师姐的推荐下使用了pandas来处理数据,并学习了一些pandas的用法,比如:
读取文件:
papa = pd.DataFrame(pd.read_csv('Gowalla_0.txt',sep = '\t'))
这里的 Gowalla_0.txt 是我预先处理过,将ID号为0的用户数据单独逐行保存后得到的细分后的数据,如下:
不添加表头会使得数据很难辨认,为了方便笔者单独剥去一列数据使用,笔者决定给它添加表头:
papa.columns=['ID','time','long','lab','geoID','sH','sW','sWH','rain','temp','wind']
表头完全按照:
1 | 用户ID |
---|---|
2 | 时间 |
3-4 | 经纬度 |
5 | 地点ID |
6 | 签到的小时 |
7 | 签到星期 |
8 | 签到周小时 |
9-11 | 分别为 降雨 温度 风速 |
但是在这里出现了一些问题,如此加入表头之后,表头会覆盖第一行的数据,笔者无奈只能复制第一行数据再做一遍表头,各位读者如果有好的方式敬请留言,笔者必定躬身请教!
两条重复数据,其实第一条被处理为表头了。。hhhh
最后建立空列表,并将所需的列数据保存进去,注意,仔细观察可知数据集中所给的数据均按照时间顺序排列,所以不需要再进行处理:
Geo_ID = []
Geo_ID = list(papa['geoID'])
单独一次转移的概率计算函数设计
这里笔者单独写了一个函数封装:
import numpy as np
def Prob(a,x_axis,y_axis):
b = a
d = 0.0
m = 0.0
for i in range(len(a)):
if a[i] is x_axis:
prob = 1
d = d + 1
if a[i] is x_axis and a[i + 1] is y_axis:
m = m + 1
prob = m / d
return prob
其中参数包括一个数组,横坐标x_axis,纵坐标y_axis
算法思想:
如果一个地点出现了一次,那么先令其转移概率prob为1,且分母d加1,表示以该地点为起点的转移共发生d次。若该数组里存在着另一个起点相同,下一跳地点也相同的地点,那么分子m加1,最后prob = m / d
返回prob。
注意这里要考虑分母不为零的情况和数据类型为浮点型,若数据类型为默认整型的话,概率计算所得的小数点部分会被直接舍弃,也就是计算得到的数值均为0.
笔者在这里因为学艺不精遇到一些问题,在此列出以后规避 :
1.python中for循环的用法,当不使用range(len())
的时候,i表示一个实例,当使用range(len())
的时候,i表示一个计数。
2.if 语句中,== 和 is 有区别,不能混用:
Python中的对象包含三要素:id、type、value。
其中id用来唯一标示一个对象,type标识对象的类型,value是对象的值。
is判断的是a对象是否就是b对象,是通过id来判断的。
==判断的是a对象的值是否和b对象的值相等,是通过value来判断的。
生成转移概率矩阵
首先,我们要理解转移矩阵的构造,在本例题中,转移函数是依照地点ID也就是Geo_ID
来生成的,但是重点问题在于,Geo_ID
中存在重复的地点数据,所以笔者先使用set()
函数来去重Geo_ID_M = list(set(Geo_ID))
,得到的列表作为转移矩阵的行列表头数据,大概举例说明:
\ | 地点1 | 地点2 | 地点3 |
---|---|---|---|
地点1 | 地点1转移到地点1的概率 | 地点1转移到地点2的概率 | 地点1转移到地点3的概率 |
地点2 | 地点2转移到地点1的概率 | 地点2转移到地点2的概率 | 地点2转移到地点3的概率 |
地点3 | 地点3转移到地点1的概率 | 地点3转移到地点2的概率 | 地点3转移到地点3的概率 |
先建造空的矩阵:
Geo_ID_P = [ [0] * num_P for i in range(num_P)]
其中num_P
是地址信息的个数,如此获取:num_P = len(Geo_ID_M)
然后使用Prob(a,x_axis,y_axis)
逐个计算概率并填充进矩阵:
for i in range(len(Geo_ID_M)):
for j in range(len(Geo_ID_M)):
Geo_ID_P[i][j] = Prob(Geo_ID,Geo_ID_M[i],Geo_ID_M[j])
如此一来便得到了转移矩阵
一点小瑕疵,Geo_ID_M
是经过set()
函数处理过的,虽然降重,但是乱序了,不过这并不影响计算。
生成初始向量
为什么需要初始向量呢?因为在预测转移的时候,需要初始向量来表示当前地点是列表中的哪一个,所以先将去重了的地点信息列表进行one-hot-encoding处理:
pi_0 = [0]*len(Geo_ID_M)
for i in range(len(Geo_ID_M)):
if Geo_ID_M[i] == now_loc:
pi_0[i] = 1
其中now_loc
表示当前地点信息,笔者以数据集中最后一个数据作为当前地点,得到一串独热码:
根据马尔科夫模型的算法:
{ π ( 1 ) = π ( 0 ) P . . . . . . . . . π ( n ) = π ( n − 1 ) P n (3) \begin{cases} \pi(1) & =\pi(0)P \\ .........\\ \pi(n) & =\pi(n-1)P^n \end{cases}\tag{3} ⎩⎪⎨⎪⎧π(1).........π(n)=π(0)P=π(n−1)Pn(3)
笔者这里只计算转移第一次的概率:
pi_1 = np.dot(pi_0,Geo_ID_P)
loc_0 = 0
for i in range(len(pi_1)):
if pi_1[i] == 1:
loc_0 = i
所以loc_0
就是下一跳的地点ID。
结论
这是笔者第一次发表文章,切实感受到了自己的写作能力的提高,同时顺利的帮助自己重新理清了一遍思路,希望对初学者有所帮助,也希望前辈们及时提出文章中的纰漏和错误,以及理解上的差错,笔者定将及时改正,感谢苏老师,秋丽学姐,宁宁学姐提供的帮助。