我的作业是使用K-最近邻算法,通过NumPy来根据花的各种特征(例如,茎长、花瓣长度等)判断它是什么种类的花。(顺便说一下,我以前用过Python,尽管这不是我最擅长的语言;然而,我对NumPy完全是新手)。
我的训练数据和测试数据都放在这样的CSV文件中:
4.6,3.6,1.0,0.2,Iris-setosa5.1,3.3,1.7,0.5,Iris-setosa4.8,3.4,1.9,0.2,Iris-setosa7.0,3.2,4.7,1.4,Iris-versicolor6.4,3.2,4.5,1.5,Iris-versicolor6.9,3.1,4.9,1.5,Iris-versicolor5.5,2.3,4.0,1.3,Iris-versicolor
我知道如何实现基本算法。以下是我用C#编写的代码:
namespace Project_3_Prototype{ public class FourD { public double f1, f2, f3, f4; public string name; public static double Distance(FourD a, FourD b) { double squared = Math.Pow(a.f1 - b.f1, 2) + Math.Pow(a.f2 - b.f2, 2) + Math.Pow(a.f3 - b.f3, 2) + Math.Pow(a.f4 - b.f4, 2); return Math.Sqrt(squared); } } class Program { static void Main(string[] args) { List<FourD> distances = new List<FourD>(); using (var parser = new TextFieldParser("iris-training-data.csv")) { parser.SetDelimiters(","); while (!parser.EndOfData) { string[] fields = parser.ReadFields(); var curr = new FourD { f1 = double.Parse(fields[0]), f2 = double.Parse(fields[1]), f3 = double.Parse(fields[2]), f4 = double.Parse(fields[3]), name = fields[4] }; distances.Add(curr); } } double correct = 0, total = 0; using (var parser = new TextFieldParser("iris-testing-data.csv")) { parser.SetDelimiters(","); int i = 1; while (!parser.EndOfData) { total++; string[] fields = parser.ReadFields(); var curr = new FourD { f1 = double.Parse(fields[0]), f2 = double.Parse(fields[1]), f3 = double.Parse(fields[2]), f4 = double.Parse(fields[3]), name = fields[4] }; FourD min = distances[0]; foreach (FourD comp in distances) { if (FourD.Distance(comp, curr) < FourD.Distance(min, curr)) { min = comp; } } if (min.name == curr.name) { correct++; } Console.WriteLine(string.Format("{0},{1},{2}", i, curr.name, min.name)); i++; } } Console.WriteLine("Accuracy: " + correct / total); Console.ReadLine(); } }}
这个程序运行得非常好,输出如下:
# 格式为编号,正确标签,预测标签1,Iris-setosa,Iris-setosa2,Iris-setosa,Iris-setosa3,Iris-setosa,Iris-setosa4,Iris-setosa,Iris-setosa5,Iris-setosa,Iris-setosa6,Iris-setosa,Iris-setosa7,Iris-setosa,Iris-setosa8,Iris-setosa,Iris-setosa9,Iris-setosa,Iris-setosa10,Iris-setosa,Iris-setosa11,Iris-setosa,Iris-setosa12,Iris-setosa,Iris-setosa...Accuracy: 0.946666666666667
我正在尝试在NumPy中做同样的事情。然而,作业不允许我使用for
循环,只能使用矢量化函数。
所以,基本上我想做的是:对于测试数据中的每一行,获取训练数据中与之最接近的行的索引(即具有最小欧几里得距离的行)。
以下是我在Python中尝试的代码:
import numpy as npdef main(): # 将CSV的每一行分割成属性和标签的列表 data = [x.split(',') for x in open("iris-training-data.csv")] # 最后一项是标签 labels = np.array([x[-1].rstrip() for x in data]) # 将前三项转换为二维浮点数数组 floats = np.array([x[0:3] for x in data]).astype(float) classifyTrainingExamples(labels, floats)def classifyTrainingExamples(labels, floats): # 我们对测试数据做与训练数据相同的事情 testingData = [x.split(',') for x in open("iris-testing-data.csv")] testingLabels = np.array([x[-1].rstrip() for x in testingData]) testingFloats = np.array([x[0:3] for x in testingData]).astype(float) res = np.apply_along_axis(lambda x: closest(floats, x), 1, testingFloats) correct = 0 for number, index in enumerate(res): if labels[index] == testingLabels[number]: correct += 1 print("{},{},{}".format(number + 1, testingLabels[number], labels[index])) number += 1 print(correct / len(list(res)))def closest(otherArray, item): res = np.apply_along_axis(lambda x: distance(x, item), 1, otherArray) i = np.argmin(res) return i# 获取两个“平面”列表之间的欧几里得距离(即某一特定行)def distance(a, b): # 逐元素相减,然后每个元素都平方 lst = (a - b) ** 2 # 将所有元素相加,然后取平方根 result = np.sqrt(lst.sum()) return resultmain()
不幸的是,输出看起来像这样
1,Iris-setosa,Iris-setosa2,Iris-setosa,Iris-setosa3,Iris-setosa,Iris-setosa4,Iris-setosa,Iris-setosa....74,Iris-setosa,Iris-setosa75,Iris-setosa,Iris-setosa0.93333333
每行都只有Iris-setosa
作为标签,准确率为0.93333333。
我尝试使用调试器逐步检查,发现if
语句认为每个项目都是正确的(但正确率仍然显示为0.93333333)。
所以,基本上:
- 它显示每个结果都是“正确”的(显然不是这样的)。
- 它对每个值都显示
Iris-setosa
- 我的百分比显示为93%。正确值实际上大约是94%,但考虑到每个结果都被认为是“正确”的,我期望这个显示为100%。
有人能帮我看看我遗漏了什么吗?
在任何人问之前,顺便说一下,是的,我确实尝试使用调试器逐步检查了:) 顺便说一下,是的,这是作业。
回答:
如果你真的想在一行中完成,这里是你可以做的(我从scikit-learn下载了数据集):
import numpy as npfrom sklearn import datasetsfrom sklearn.model_selection import train_test_split# 加载数据集iris = datasets.load_iris()X = iris.datay = iris.target# 分割训练和测试集Xtrain, Xtest, ytrain, ytest = train_test_split(X, y, test_size=0.2)# 1-最近邻居 ypred = np.array([ytrain[np.argmin(np.sum((x-Xtrain)**2,axis=1))] for x in Xtest])# 计算分类错误sum(ypred != ytest)/ len(ytest)
现在,这是1-最近邻居,它只查看训练集中最接近的点。对于k-最近邻居,你需要将其更改为这样:
# k-最近邻居 k = 3ypredk = np.array([np.argmax(np.bincount(ytrain[np.argsort(np.sum((x-Xtrain)**2,axis=1))[0:k]])) for x in Xtest])sum(ypredk != ytest)/ len(ytest)
用语言来说,你对距离进行排序,找到k个最低值的索引(这就是np.argsort
部分),然后找到对应的标签,然后在k个标签中查找最常见的标签(这就是np.argmax(np.bincount(x))
部分)。
最后,如果你想确认,可以与scikit-learn
进行比较:
# scikit-learn NNfrom sklearn import neighborsknn = neighbors.KNeighborsClassifier(n_neighbors=k, algorithm='ball_tree')knn.fit(Xtrain,ytrain)ypred_sklearn = knn.predict(Xtest)sum(ypred_sklearn != ytest)/ len(ytest)