planning is don

This commit is contained in:
StarLee 2016-07-07 02:30:19 +08:00
parent a525611a18
commit 5c30e1b81b
10 changed files with 1417 additions and 229 deletions

BIN
csv.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

10
main/bottom_nodes.csv Normal file
View File

@ -0,0 +1,10 @@
17栋,18-3,1
601食堂,9-3,100
轻食,16-1,100
超市,14-1,10000000000
102教学楼,9-6,1
101教学楼,9-6,1
103教学楼,9-6,1
PDL,16-8,1
银河楼,13-8,1
航天楼,14-6,1
1 17栋 18-3 1
2 601食堂 9-3 100
3 轻食 16-1 100
4 超市 14-1 10000000000
5 102教学楼 9-6 1
6 101教学楼 9-6 1
7 103教学楼 9-6 1
8 PDL 16-8 1
9 银河楼 13-8 1
10 航天楼 14-6 1

View File

@ -15,6 +15,8 @@ class Point:
self.x,self.y = x,y self.x,self.y = x,y
def line_dis(self,point): def line_dis(self,point):
return math.sqrt(math.pow(self.x-point.x,2) + math.pow(self.y-point.y,2)) return math.sqrt(math.pow(self.x-point.x,2) + math.pow(self.y-point.y,2))
def mh_dis(self,point):
return abs(self.x-point.x) + abs(self.y-point.y)
def equals(self,point): def equals(self,point):
return self.x==point.x and self.y == point.y return self.x==point.x and self.y == point.y
def next_to(self,point): def next_to(self,point):
@ -44,7 +46,7 @@ class AIPath:
self.frame_bottom.pack() self.frame_bottom.pack()
self.width_canvas,self.height_canvas= 800,400 self.width_canvas,self.height_canvas= 700,500
self.cvs = tk.Canvas(self.frame_top,width=self.width_canvas,height=self.height_canvas) self.cvs = tk.Canvas(self.frame_top,width=self.width_canvas,height=self.height_canvas)
self.cvs.pack() self.cvs.pack()
#加载地图并绘制 #加载地图并绘制
@ -53,11 +55,6 @@ class AIPath:
self.draw_map() self.draw_map()
#画个坐标
for i in range(0,self.column_matrix):
self.cvs.create_text((i+0.5)* self.width_m,self.height_m/2,text="%d"%i,fill = "white")
for i in range(0,self.row_matrix):
self.cvs.create_text(self.width_m/2,(i+0.5)*self.height_m,text="%d"%i,fill = "white")
#初始化控件 #初始化控件
self.init_widget() self.init_widget()
top.mainloop() top.mainloop()
@ -91,6 +88,12 @@ class AIPath:
for key,value in self.locations.items(): for key,value in self.locations.items():
self.cvs.create_text((value[0] + 0.5)* self.width_m ,(value[1] + 0.5)* self.height_m,text =key,fill="black") self.cvs.create_text((value[0] + 0.5)* self.width_m ,(value[1] + 0.5)* self.height_m,text =key,fill="black")
# #画个坐标
# for i in range(0,self.column_matrix):
# self.cvs.create_text((i+0.5)* self.width_m,self.height_m/2,text="%d"%i,fill = "white")
# for i in range(0,self.row_matrix):
# self.cvs.create_text(self.width_m/2,(i+0.5)*self.height_m,text="%d"%i,fill = "white")
#A*算法 #A*算法
def a_star(self,start,end): def a_star(self,start,end):
@ -212,7 +215,82 @@ class AIPath:
start = self.text_start.get() start = self.text_start.get()
end = self.text_end.get() end = self.text_end.get()
self.a_star(start,end) self.a_star(start,end)
def find_planning(self):
self.find_planning_with_nodes("top_nodes.csv")
self.find_planning_with_nodes("bottom_nodes.csv")
def load_sub_nodes(self,file):
locations = dict()
with open(file,encoding='utf-8') as fp:
for line in fp.readlines():
items = line.strip().split(",")
locations[items[0]] = ([int(num) for num in items[1].split("-")],float(items[2]))
return locations
def find_planning_with_nodes(self,file):
sub_nodes =[(Point(value[0][0],value[0][1]),value[1])for value in self.load_sub_nodes(file).values()]
point_start = Point(2,8)
point_end = Point(19,11)
point_start.gn = 0
point_start.father = None
frontier = list()
frontier.append(point_start)
expanded = list()
while True:
print("------------new round-----------------")
if len(frontier) == 0:
print("fail")
return
point_to_ep = self.pop_frontier4planning(frontier, sub_nodes)
print("现在判读单节点: %d-%d" %(point_to_ep.x,point_to_ep.y))
if point_to_ep.equals(point_end) or point_to_ep.next_to(point_end):
print("succesuss")
self.get_solution(point_to_ep)
return
expanded.append(point_to_ep)
print("现在的expanded:")
print(expanded)
neighbors = self.get_neighbors(point_to_ep)
print("邻接点:")
print(neighbors)
for p in neighbors:
if self.contained(expanded, p) is False and self.contained(frontier,p) is False:
frontier.append(p)
p.father= point_to_ep
p.gn = point_to_ep.gn + 1
print("------------round ends----------------")
print("\n")
def pop_frontier4planning(self,frontier,sub_nodes):
min = sys.maxsize
poped = ''
pos = -1
for i in range(len(frontier)):
p = frontier[i]
sum = 0
for sb in sub_nodes:
sum += sb[0].mh_dis(p)*sb[1]
fn = sum + p.gn
if min > fn:
min = fn
poped = p
pos = i
print("挑选之前的frontier:")
print (frontier)
del p
del frontier[pos]
print("挑选之后的frontier:")
print(frontier)
return poped
def init_widget(self): def init_widget(self):
#------------------fro frame center-------------------- #------------------fro frame center--------------------
@ -237,13 +315,15 @@ class AIPath:
self.text_end = Entry(self.frame_bottom) self.text_end = Entry(self.frame_bottom)
button_yes = Button(self.frame_bottom,width=10, text='确定',command=self.find_path) button_yes = Button(self.frame_bottom,width=10, text='确定',command=self.find_path)
button_planing = Button(self.frame_bottom,width=10, text='道路规划',command=self.find_planning)
#位置 #位置
label_start.grid(row = 0, column=0) label_start.grid(row = 0, column=0)
self.text_start.grid(row = 0, column=1) self.text_start.grid(row = 0, column=1)
label_end.grid(row = 0, column=2) label_end.grid(row = 0, column=2)
self.text_end.grid(row = 0, column=3) self.text_end.grid(row = 0, column=3)
button_yes.grid(row=0,column=4,padx=10) button_yes.grid(row=0,column=4,padx=10)
button_planing.grid(row=0,column=5,padx=10)
#测试数据 #测试数据
self.text_start.insert(0,'图书馆') self.text_start.insert(0,'图书馆')
self.text_end.insert(0,'17栋') self.text_end.insert(0,'17栋')

7
main/top_nodes.csv Normal file
View File

@ -0,0 +1,7 @@
天河楼,15-12,1
体育馆,11-11,1
博餐,6-16,100000
游泳馆,8-14,1
八队,20-16,1
科技楼,2-10,1
博宿舍,5-14,1
1 天河楼 15-12 1
2 体育馆 11-11 1
3 博餐 6-16 100000
4 游泳馆 8-14 1
5 八队 20-16 1
6 科技楼 2-10 1
7 博宿舍 5-14 1

BIN
map.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

14
report.aux Normal file
View File

@ -0,0 +1,14 @@
\relax
\@writefile{toc}{\contentsline {section}{\numberline {1}实验内容}{1}}
\@writefile{toc}{\contentsline {subsection}{\numberline {1.1}实验要求}{1}}
\@writefile{toc}{\contentsline {subsection}{\numberline {1.2}实验环境}{1}}
\@writefile{toc}{\contentsline {section}{\numberline {2}实验过程}{1}}
\@writefile{toc}{\contentsline {subsection}{\numberline {2.1}建模}{1}}
\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces 地形数据}}{1}}
\newlabel{knn}{{1}{1}}
\@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces 地图模型}}{2}}
\newlabel{knn}{{2}{2}}
\@writefile{toc}{\contentsline {subsection}{\numberline {2.2}两点间最短路径}{2}}
\@writefile{toc}{\contentsline {subsection}{\numberline {2.3}求解}{3}}
\@writefile{lot}{\contentsline {table}{\numberline {1}{\ignorespaces 利用欧几里得距离得到的结果}}{3}}
\@writefile{toc}{\contentsline {section}{\numberline {3}实验总结}{3}}

1271
report.log Normal file

File diff suppressed because it is too large Load Diff

BIN
report.pdf Normal file

Binary file not shown.

BIN
report.synctex.gz Normal file

Binary file not shown.

View File

@ -28,7 +28,7 @@
%文档信息/同时也用于生成报告封面 %文档信息/同时也用于生成报告封面
\author{李志星\\ 15060025} \author{李志星\\ 15060025}
\title{\Huge 人工智能实验报告\newline \large科大校园交通规划} \title{\Huge 人工智能实验报告\\ \Large 科大校园交通规划}
\usepackage{graphicx} \usepackage{graphicx}
@ -44,205 +44,49 @@
\section{实验内容} \section{实验内容}
\subsection{实验要求} \subsection{实验要求}
运用人工智能课程涉及到的算法,求解交通规划问题。 运用人工智能课程涉及到的算法,求解交通规划问题。给定一个新的地理区域,能够以图形化方式展示该区域的地形和各种建筑物等分布;其次,能够利用相关算法求解两个地点之间的最短路径,同时以图形化方式展示出来。最后,能够对该区域进行道路规划。
\subsection{实验环境} \subsection{实验环境}
利用Python实现规划算法通过Python自带图形工具包Tkinter对实验数据构建的模型以及解决方案进行可视化。 利用Python实现规划算法通过Python自带图形工具包Tkinter对实验数据构建的模型以及解决方案进行可视化。
\section{实验过程} \section{实验过程}
\subsection{建模} \subsection{建模}
模型 本次实验以科大校园的地理环境为基础地形数据我是以CSV的形式输入的规模是23*18的矩阵如下图所示涉及的元素有0 - 围墙1 - 道路2 - 建筑物3 - 训练场4 - 大门5 - 草坪/操场6 - 其他非道路区域。在这些元素中,只有道路是可以通过的,其他都可以看做"障碍物"。
文件: \begin{figure}[h!]
\centering
\includegraphics[width=10cm]{csv.jpg}
\caption{地形数据}
\label{knn}
\end{figure}
地形分布:各种类型 数据加载过程为:
\begin{python}
建筑物标记
excel ->csv ->juzhen \end{python}
另外为了更好的显示的显示部分建筑物以及更直观的可视化我还对大部分的建筑物做了标记这些数据也是存放在csv文件中格式是<名称,坐标>。其中为了简化处理过程,还做了如下的假设:
\begin{itemize}
\item 为便于定位和识别建筑物,我标记了一些建筑物用来做测试,这些建筑物假设只占一个格子,这样方便判断和确定其位置。如果一个建筑物占据了多个格子,那在确认它的时候需要做一些额外的繁琐的判断,但是对于基本的核心算法是没有影响的,因此在这里做了个一个简化性的假设。
\item 建筑物只要挨着道路,那任何方向都是通的,不再设置哪个方向有门哪个方向没有门。理由和上面的是一样的,也是为了简化一些不必要的计算和判断。
\end{itemize}
\begin{figure}[h!]
\centering
\includegraphics[width=18cm]{map.jpg}
\caption{地图模型}
\label{knn}
\end{figure}
\subsection{两点间最短路径}
本次试验支持搜寻两点之间的最短路径具体求解过程用到了A*算法。
\subsection{求解} \subsection{求解}
加载数据
绘制地形
\section{总结}
Spark MLlib实现了常用的机器学习算法包括Logistic回归、决策树、随机森林、K-menas等。和我了解到的机器学习算法相比分类算法中的KNN算法Saprk MLlib并没有实现因此本次实验我在Spark平台实现的是KNN算法。由于首次接触Spark编程而网上教程又比较少所以大部分思路都是参考官方提供的实例结合自己的理解完成的。
\subsection{KNN}
KNN(K-Nearest Neighbors)算法是一个理论上比较成熟的方法也是最简单的机器学习算法之一。该方法就是找出与未知样本x距离最近的k个训练样本看这k个样本中多数属于哪一类就把x归为那一类。KNN方法是一种懒惰学习方法它存放样本直到需要分类时才进行分类。
KNN算法流程大致如下
\begin{itemize}
\item 选择一种距离计算方式, 通过数据所有的特征计算新数据与已知类别数据集中的数据点的距离
\item 按照距离递增次序进行排序选取与当前距离最小的k个点
\item 返回k个点出现频率最多的类别作预测分类
\end{itemize}
\begin{figure}[h!]
\centering
\includegraphics[width=8cm]{knn.png}
\caption{KNN示意图}
\label{knn}
\end{figure}
以上图为例在这个图中我假设有三类数据五角星、三角形和多边形。中间的圆形是未知类型的数据点现在需要判断这个数据是属于五角星、三角形和多边形中的哪一类。按照NKK的思想先把离这个圆圈最近的几个点找到因为离圆圈最近的点对它的类别有判断的帮助。那到底要用多少个来判断呢这个个数就是k了。如果k=3就表示要选择离圆圈最近的3个点来判断由于三角形所占比例为2/3所以可以认为圆形表示的未知类是和三角形是一类的。如果k=6由于多边形多占比例最大为1/2为因此圆圈被认为是属于多边形一类的。从这个例子也可以看出k值对最后结果的影响还是挺大的。
在使用KNN算法的时候有些地方需要注意。数据的所有特征都要做可比较的量化若是数据特征中存在非数值的类型必须采取手段将其量化为数值。举个例子若样本特征中包含颜色红黑蓝一项颜色之间是没有距离可言的可通过将颜色转换为灰度值来实现距离计算另外样本有多个参数每一个参数都有自己的定义域和取值范围他们对distance计算的影响也就不一样如取值较大的影响力会盖过取值较小的参数。为了公平样本参数必须做一些scale处理最简单的方式就是所有特征的数值都采取归一化处置 需要一个distance函数以计算两个样本之间的距离。距离的定义有很多如欧氏距离、余弦距离、汉明距离、曼哈顿距离等等。 一般情况下选欧氏距离作为距离度量但是这是只适用于连续变量。通常情况下如果运用一些特殊的算法来计算度量的话K近邻分类精度可显著提高如运用大边缘最近邻法或者近邻成分分析法K值的确定K是一个自定义的常数K的值也直接影响最后的估计一种选择K值得方法是使用 cross-validate交叉验证误差统计选择法。交叉验证就是数据样本的一部分作为训练样本一部分作为测试样本比如选择95\%作为训练样本,剩下的用作测试样本。通过训练数据训练一个机器学习模型,然后利用测试数据测试其误差率。 cross-validate交叉验证误差统计选择法就是比较不同K值时的交叉验证平均误差率选择误差率最小的那个K值。例如选择K=1,2,3,… 对每个K=i做100次交叉验证计算出平均误差然后比较、选出最小的那个。
总的来说KNN优点有
\begin{itemize}
\item 简单,易于理解,易于实现,无需估计参数,无需训练。
\item 适合对稀有事件进行分类。
\item 特别适合于多分类问题KNN比SVM的表现要好。
\end{itemize}
NKK缺点有
\begin{itemize}
\item 当样本不平衡时如一个类的样本容量很大而其他类样本容量很小时有可能导致当输入一个新样本时该样本的K个邻居中大容量类的样本占多数。 该算法只计算“最近的”邻居样本,某一类的样本数量很大,那么或者这类样本并不接近目标样本,或者这类样本很靠近目标样本。无论怎样,数量并不能影响运行结果。
\item 该方法的另一个不足之处是计算量较大因为对每一个待分类的文本都要计算它到全体已知样本的距离才能求得它的K个最近邻点。
\end{itemize}
\subsection{安装实验环境}
\begin{itemize}
\item Spark1.6.1Spark官网提供多个下载形式可以下载源代码也可以下载预编译好的。我下载的是编译好了的Pre-built for Hadoop 2.6。下载地址为:\newline http://www.apache.org/dyn/closer.lua/spark/spark-1.6.1/spark-1.6.1-bin-hadoop2.6.tgz
\item 依赖JDK因为Spark运行在JVM之上所以首先需要安装JDK并配置好系统环境变量。
\item 操作系统Ubuntu 14.04Spark声称是可以支持Windows和类Unix系统Linux和Mac OS不过我在我笔记本Windows系统试了下会出现一些小问题所以我干脆在虚拟机里的Ubuntu系统上跑的运行状态良好未出现问题。
\item 机器配置虚拟机我使用的是Spark的单节点模式2G内存单CPU4核
\item 编程语言Python如报告2中所述Spark本身使用scala语言编写的但是它还提供了Java、Python和R的接口综合考虑开发效率和编程语言的易用性我最后选用的Python。相对Java来说实现同样的功能Python写出的代码更简洁些因此Python很适合来做这种科学研究性质的程序。
\item 其他配置conf目录下的log4j.properties用于配置Spark的日志输出Spark默认是把INFO级别及以上的日志信息都进行输出虽然这对了解它干了哪些工作很有帮助但是我觉得过多的信息输出到控制台上会影响正常的输出。因此在这里可以把输出日志级别提高些比如设置为WARN或者ERROR。
\end{itemize}
\subsection{数据集}
和报告2中的一样我们还是采用MNIST的手写识别数据集只不过这次用的数据更多我想把0到9所有的数字都利用起来对其进行一个多分类每个数字都对应着差不多两百个左右的样本其中训练样本有1934个测试样本936个。并且每个样本都已经解析到单独的txt文件中文件内容是个32*32的矩阵用0、1串来表示数字。具体细节和报告2中是一样的没什么大的变化。
\section{实验过程}
\subsection{算法模型设计}
本节中简要介绍我实现的KNN涉及到的主要类和算法的大致模型下一节详细介绍具体实现细节。
\begin{table}[!h]
\renewcommand\arraystretch{2}
\centering
\begin{tabular}{c|c}
\hline
类名 & 功能 \\
\hline
KNN & 实现KNN主要逻辑功能距离算法由子类实现\\
\hline
KNNWithEuclid & 利用欧几里得距离计算差异性的KNN \\
\hline
KNNWithCos & 利用余弦相似度计算差异性的KNN \\
\hline
DataHelper & 数据操作的辅助类 \\
\hline
Statistics & 用于统计数据特性的辅助类 \\
\hline
\end{tabular}
\caption{主要相关类及其功能}
\end{table}
其大致模型如类图所示。
\begin{figure}[h!]
\centering
\includegraphics[width=16cm]{classfigure.jpg}
\caption{算法模型示意图}
\label{knnmodel}
\end{figure}
为更加直观的展示算法的大致运行过程,下图简要给出使用该算法解决实际问题时通常流程所对应的时序图。
\begin{figure}[h!]
\centering
\includegraphics[width=16cm]{seqfigure.jpg}
\caption{算法运行时序图}
\label{knnmodel}
\end{figure}
\subsection{实现KNN算法}
在实现KNN算法的时候我尽量按照Spark库提供的API设计的因此传递给KNN的数据都是DataFrame类型的。在新建KNN对象进行分类的时候需要提供DataFrame的列信息目前用到的有特征向量列和类标签列所以初始化函数可疑传递测试数据DataFrame的信息默认值分别是“features”和“label”。其代码如下所示
\begin{python}
def __init__(self,featuresCol="features", labelCol="label"):
self.featuresCol,self.labelCol = featuresCol,labelCol
\end{python}
由于KNN不需要训练因此就没有给他设计用于“训练”的接口而是直接用classify函数对数据进行分类。本来设计的函数接受四个参数inXsdataSetK和disFuncinXs表示要预测的样本该样本的类标签未知只包含它的特征向量该参数类型是DenseVector在Spark中用来表示本地向量dataSet表示易知类标签的样本集合每个样本既包含特征向量也包含类别标签该参数类型为DataFrameDataFrame在报告2中以进行过信息介绍这里就不再赘述了k是用于指定租后选出k距离最短的近邻的类型就是int数值类型即可。disFun用于指定计算两个样本点之间“差异性”的距离函数默认使用计算欧几里得距离的computedDist关于距离计算后文会有详细介绍。但是后来感觉距离计算应该由子类负责这样可以更加灵活实现不同的距离函数的KNN因此该函数现在只接受前三个参数disFunc由不同的子类根据具体的距离函数实现相应的功能。
其主要代码如下:
\begin{python}
def classify(self,inXs,dataSet,k=10):
if len(inXs) != len(dataSet.first()[self.featuresCol].values):
print "length of features of inXs is not corresponding with dataset's"
return
dataSet = dataSet.select(self.featuresCol,self.labelCol)
dis = dataSet.map(lambda row: (row[1],computeDist(row[0].toArray(),inXs.toArray())))
orderedDis = dis.takeOrdered(k, key=lambda x: x[1])
groupLabel = sc.parallelize(orderedDis).map(lambda row:(row[0],1)).reduceByKey(add).takeOrdered(1,key=lambda row:-row[1])[0][0]
return groupLabel
\end{python}
上面的代码中程序逻辑的开始是进行一些常规的数据有效性判断。我代码里写的是判断inXs的维度和dataSet中样本的维度是否一样也就是特征属性数是否一样。其实还可以进行许多其他的判断就像Spark库中所做的对测试数据在这里指已经知道类标签的哪些数据样本后文姑且用训练数据集指代做的一些统计在这里也可以进行一些类似的统计。比如我在后期添加的统计一下所有已知的类标签是否都是属于一个类别如果是的话那么对于要预测的样本就不用进行距离的计算了直接输出该类别即可。不过其它的一些非核心业务逻辑我没有全都写到代码中毕竟这次实验做得不是一个对外提供服务的库主要目的是为了体验在Spark上做机器学习算法的开发因此我在这里用这些个测试代表一系列的有效性判断和数据集特点统计。
下面这段代码用于从dataSet这个dataFrame中选择出以self.featuresCol和self.labelCol命名的两列这两个属性值如前所述有默认值也可以经过用户设置。这个函数生成的是一个新的DataFrame这个新的DataFrame就包含指定的这两个列这个操作有点类似SQL查询语句。关于DataFram的一些操作细节报告1和2中都没有展开所以在这里进行一个简单的介绍。DataFrame是基于RDD的RDD就是一个不可变的分布式对象集合。每个RDD都被分为多个分区这些分区运行在集群的不同节点上。RDD操作分为两种类型转换和动作其中转换是惰性计算的Spark只会记录下这些转换操作动作是触发Spark启动计算的动因。
\begin{python}
dataSet = dataSet.select(self.featuresCol,self.labelCol)
\end{python}
接着就是计算inXs与训练数据集每个样本之间的距离计算距离函数可以有多种选择常用的有欧几里得距离和余弦相似度距离计算函数后面会进行详细介绍。这里对dataSet进行了一次map操作Spark 中的map操作会把dataSet中的每一个元素进行映射在得到的新的RDD中有且仅有一个新的元素与其对应也就是说map操作是一一映射的。map函数接受一个函数作为参数该函数的要求是接收一个参数这个参数由map传递给它表示的是dataSet中的一个元素该函数执行具体的对该元素的映射操作然后把该结果返回返回的结果会放到新的RDD中也就是map函数的返回值。
\begin{python}
dis = dataSet.map(lambda row: (row[1],distFun(row[0].toArray(),inXs.toArray())))
\end{python}
上面函数参数我用的python的lambda语法lambda是一个表达式而不是一个语句它能够出现在Python语法不允许def出现的地方用来编写简单的函数。返回的是该样本的类标签以及该样本和要预测的样本之间的距离在计算距离的时候会调用vector的toArray方法把其转换为numpy中的array便于后面的计算过程。
这一行函数用于对表示距离的dis这个rdd进行排序dis有两个列类标号和距离值。排序是按照距离值排的并且顺序是按照从小到大排的k用于指定返回排序后的前k个数值。这个函数返回的就是k个和要预测的样本距离最近的数据点返回的数据类型是list。
\begin{python}
orderedDis = dis.takeOrdered(k, key=lambda x: x[1])
\end{python}
算法的最后一步就是从k个距离最短的样本点中选择所占比例最多的类别。由于上一步返回的结果是普通的list类型的数据所以首先用paralelize函数把list转换为RDD数据便于后续进行并行化操作这一步要对k个样本点进行归类技术这是一个典型的MapReduce模型的问题首先把数据格式转换为类标号1的形式这样的形式便于后面reduce利用add函数对其进行累加。计数完成后再用排序函数对其按从大到小的顺序进行排列最后返回所占比例最大的的类别就是最终要预测的分类结果。
\begin{python}
label = sc.parallelize(orderedDis).map(lambda row:(row[0],1)).reduceByKey(add).takeOrdered(1,key=lambda row:-row[1])[0][0]
\end{python}
距离函数或者说为差异性函数的计算有多种形式一种是衡量空间各点间的绝对距离的“距离度量”一种是衡量空间向量的夹角的“相似度度量”更加的是体现在方向上的差异而不是位置。“距离衡量”中常用的有欧几里得距离、明可夫斯基距离、曼哈顿距离和切比雪夫距离“相似度度量”中常用的有向量空间余弦相似度、皮尔森相关系数和Jaccard相似系数等。其实很多的距离度量和相似度度量都是基于欧几里得距离和余弦相似度的变形和衍生所以本报告重点比较了此两者对KNN在手写手别问题的影响不过关于这两个度量标准的计算公式本报告就不再展开了他们的计算过程还是比较简单的。在python中使用numpy进行向量的运算是很简便的两个向量a和b之间的欧几里得距离和余弦相似度的计算过程如下
\begin{python}
#Euclidean Distance
np.sqrt(np.sum((a-b)**2))
#Cosine Similarity
a.dot(b) / (np.sqrt(a.dot(a)) * np.sqrt(b.dot(b)))
\end{python}
\subsection{测试算法效果}
首先是加载数据利用的还是报告2中编写的loadData函数。测试样本在testData中我在程序中用的的迭代使用knn对每个样本进行预测然后统计预测失败的个数最后看其错误率。在这里我曾经做过一个尝试把整个testData作为一个DataFrame传给knn的classify函数这和库提供的API是一致的然后利用map函数对每一个样本计算其预测结果。但是在具体运行的时候就出错了因为在利用map函数对每一个样本预测的时候预测过程会在每一个worker节点进行工作但是预测过程又会对dataSet进行一系列的操作这不符合Spark闭包函数的要求。这里不像报告2中使用的Spark库它们先把模型训练出来然后在预测的时候直接用模型来对其进行计算在函数体内不会访问外部变量。因此我就设计classify每次针对一个样本进行预测在程序外对所有样本的预测结果进行统计。
\begin{python}
for x in testData:
prediction = knn.classify(x[0],datasetDF,4)
if prediction != x[1]:
errorCount += 1
count += 1
print "error rate is %f(%d/%d)" % (1.0 * errorCount / count,errorCount,count)
\end{python}
\subsection{运行算法}
我把KNN算法实现文件KNN.py放到了和Spark同级的目录中因此要把该文件提交给Spark执行只需执行以下命令
\begin{figure}[h!]
\centering
\includegraphics[width=12cm]{launch_knn.jpg}
\caption{运行knn.py命令}
\label{launch_knn}
\end{figure}
\section{实验结果}
本次报告分别从K值和距离算法两个方面对实现的KNN算法进行测试下面两个表分别是利用欧几里得距离和余弦相似度对946个样本利用KNN预测得到的结果为便于显示K的取值范围我设定在了1到10。
\begin{table}[!h] \begin{table}[!h]
\renewcommand\arraystretch{1.1} \renewcommand\arraystretch{1.1}
@ -276,48 +120,10 @@ K值 & 预测错误数 & 预测错误率 \\
\caption{利用欧几里得距离得到的结果} \caption{利用欧几里得距离得到的结果}
\end{table} \end{table}
\begin{table}[!h]
\renewcommand\arraystretch{1.1}
\centering
\begin{tabular}{c c c}
\hline
K值 & 预测错误数 & 预测错误率 \\
\hline
1 & 11 & 0.011628 \\
2 & 17 & 0.017970 \\
3 & 13 & 0.013742 \\
4 & 14 & 0.014799 \\
5 & 14 & 0.014799 \\
6 & 15 & 0.015856 \\
7 & 18 & 0.019027 \\
8 & 20 & 0.021142 \\
9 & 25 & 0.026427 \\
10 & 26 & 0.027484 \\
\hline
\end{tabular}
\caption{利用余弦相似度得到的结果}
\end{table}
从上面两个表中可以看出K的取值对于距离函数取欧几里得距离还是余弦相似度都有很大的影响。在K值取1到10这个范围内欧几里得算法随着K值的增加错误率先是下降然后再增高并且K=3的时候效果最好错误率是最低的并且在K=8的时候又出现了一个波动。对于余弦相似度来说在K=1的时候效果最好随着K值的增加错误率是先增高后下降然后再增高。在取相同的K值时两个距离函数的错误率不好比较都有表现好的情况也都有表现差的情况很少有一样的情况。但是在K取值在1到10这个区间内他们两个取到的最好效果是一样的都是错判11个样本。当然如果K的取值范围更大一些的话错误率肯定还会变化。通过上面的对比可以发现KNN算法对参数K和距离函数的选择还是很敏感的。
\section{实验总结} \section{实验总结}
通过本次实验我基于Spark实现了KNN算法Spark提供了非常丰富的API来支持分布式计算同时KNN算法的计算过程又很简洁所以本次的实验过程没有遇到太大的问题。在实现KNN算法的过程中为了搞懂Spark的原理需要翻阅很多的资料虽然在完成报告2的过程中已经对Spark有了一定的了解但是那还都是停留在基本的使用层次大部分工作是学习官网提供的教程和网络相关介绍只是对一些基本的概念和MLlib的API的使用有了个大概的认识。但是对于如何使用Spark 进行分布式编程还是了解的太少这个时候我就找了些论文和书籍来看看由于时间比较紧虽然只看了部分内容但是对于编程实现KNN我感觉还是起到了很大的帮助。这次实验让我感受到了Spark编程的强大以及简洁往往几行代码就能够实现很复杂的功能。不过在实验过程中也还是遇到了很多的问题 通过本次实验我基于Spark实现了KNN算法Spark提供了非常丰富的API来支持分布式计算同时KNN算法的计算过程又很简洁所以本次的实验过程没有遇到太大的问题。在实现KNN算法的过程中为了搞懂Spark的原理需要翻阅很多的资料虽然在完成报告2的过程中已经对Spark有了一定的了解但是那还都是停留在基本的使用层次大部分工作是学习官网提供的教程和网络相关介绍只是对一些基本的概念和MLlib的API的使用有了个大概的认识。但是对于如何使用Spark 进行分布式编程还是了解的太少这个时候我就找了些论文和书籍来看看由于时间比较紧虽然只看了部分内容但是对于编程实现KNN我感觉还是起到了很大的帮助。这次实验让我感受到了Spark编程的强大以及简洁往往几行代码就能够实现很复杂的功能。不过在实验过程中也还是遇到了很多的问题
\begin{itemize}
\item Spark提供了丰富的API但是因为是第一次接触所以用起来不太熟悉比如一个函数产生的dataframe得利用输出查看才晓得具体包含了哪些信息。rdd的操作函数map reduce等函数具体有什么效果或者返回的数据类型是什么需要经常查看API以及结合具体的调试来体会。
\item 对Spark的编程模型理解不深一开始我曾经试图在worker program中访问外部变量但是这是不允许的Spark要求函数必须是闭包函数因此在运行的时候报了错。目前算法在预测大量测试数据的时候运行时间有点长这是在后续工作中需要改进的地方。
\end{itemize}
在前段时间召开的2016Spark峰会上Spark2.0已经被发布了据称新一代的Spark会更简单、快速和智能。Spark的迅猛发展已经收到业内各大公司的广泛关注包括IBM、Microsoft在内的许多企业都在积极推广Spark。对于机器学习模块
基于DataFrame的机器学习API也就是ml包将作为主要的ML API尽管原本的mllib包仍然保留但以后的开发重点会集中在基于DataFrame的API上。此外任何编程语言的用户都将可以保留与载入机器学习的管道与模型了。还是如报告2中所总结通过谭老师的作业我算是开始接触了Spark这门快速发展的技术对于我以后的研究工作提供了更多思路目前我正打算把Spark应用到我们正在开发的一个系统中来处理大量数据的匹配问题希望能在处理效率方面有所提升。
\end{spacing} \end{spacing}
\end{document} \end{document}