机器学习:K近邻算法

本文内容介绍机器学习的K近邻算法,用它处理分类问题。分类问题的目标是利用采集到的已经经过分类处理的数据来预测新数据属于何种类别。

K近邻算法

K近邻算法对给定的某个新数据,让它与采集到的样本数据点分别进行比较,从中选择最相似的K个点,然后统计这K个点中出现的各个类别的频数,并判定频数最高的类别作为新数据所属的类别。

这里有个问题是如何判定样本数据与新数据是否相似。常用的一种计算方法叫欧几里得距离。

欧几里得距离(Euclidean Distance)

假设有两个数据分别是:$- X = (x_1,x_2,...,x_n) -$和$- Y = (y_1,y_2,...,y_n) -$。则$-X-$与$-Y-$的距离为:

$$ D = \sqrt{\sum_{i=1}^{n}(x_i-y_i)^2} $$

Python实现这个式子代码如下:

def euclidean_distance(x, y):  
    if len(x) != len(y):
        warnings.warn('Input error')
    return sqrt( sum( [(x[i] - y[i])**2 for i in range(0, len(x))] ) )

print(euclidean_distance([1,2,3], [2,4,5]))  

NumPy提供则可以用下面方法计算两个ndarray的距离:

np.linalg.norm(np.array([1,2,3]) - np.array([2,4,5]))  

实现K近邻算法

接下来依照上面描述的算法原理实现K近邻算法。先定义一下输入数据的格式:

#二维测试数据的格式
dataset = {'k': [[1,2],[2,3],[3,1]], 'r':[[6,5],[7,7],[8,6]]}  
new_features = [5,7]  

我们假定样本数据的结构如dataset为一个字典类型的数据,字典的元素的关键字为类型名称,元素值为一个包含该类型所有样本点的列表。新数据为一个数据点。

所以K近邻算法的实现如下:

# KNN实现
def k_nearest_neighbors(data, predict, k=3):  
    if len(data) >= k:
        warnings.warn('K less than total voting groups')
    # 计算距离
    distances = []
    for group in data:
        for features in data[group]:
            #distance = euclidean_distance(features, predict)
            distance = np.linalg.norm(np.array(features)-np.array(predict))
            distances.append([distance, group])
    # 排序后取前k项数据类别构成新数组
    votes = [i[1] for i in sorted(distances)[:k]]
    # 统计数组中频数最高的类别
    vote_result = Counter(votes).most_common(1)[0][0]
    return vote_result

# 调用KNN
result = k_nearest_neighbors(dataset, new_features, k=3)  
print(result)  

使用真实数据测试

UCI网站的机器学习数据库中可以找到Breast Cancer Wisconsin(Original)的真实统计数据。数据可以从这个链接下载,数据的描述可以看这个链接

看到数据描述中提到了数据每一列的定义如下: 乳癌数据描述

这份数据的预测目标是判断给定特征数据对应的乳癌情况,2表示良性、4表示恶性。

根据描述我们先处理一下下载到的数据,给它的每一列加上个列名。这样在Python里面就可以把它当成一个CSV文件处理。我这里把它保存到了一个名为breast-cancer-wisconsin.data的文件里。形状如下: 数据格式

数据里面还有一些统计不到的数据,用英文问号?表示。还有一个很奇怪的现象是数据读取进来后有些数字会被处理成字符串类型,如'1'这样的数据。这些都需要我们提前处理一下。

df = pd.read_csv('./dataset/breast-cancer-wisconsin.data')  
# 处理问号
df.replace('?', -99999, inplace=True)  
# id字段不应该当成一个统计特征字段,因此去除该列的内容
df.drop(['id'], 1, inplace=True)  
# 源数据有部分数据是字符串,如'1',这对我们的模型有影响,所以整理一下类型
# 用列表存放数据
full_data = df.astype(float).values.tolist()  
random.shuffle(full_data) # 洗乱数据  

接下来生成训练数据集和统计数据集,代码如下:

# 生成训练数据集和统计数据集
test_size = 0.2  
train_set = {2:[], 4:[]} # 训练集,占80%  
test_set = {2:[], 4:[]} # 统计集,占20%  
train_data = full_data[:-int(test_size*len(full_data))]  
test_data = full_data[-int(test_size*len(full_data)):]  
for i in train_data:  
    train_set[i[-1]].append(i[:-1])
for i in test_data:  
    test_set[i[-1]].append(i[:-1])

最后,利用上述KNN函数统计测试数据的准确性。

correct = 0  
total = 0  
for group in test_set:  
    for data in test_set[group]:
        vote = k_nearest_neighbors(train_set, data, k=5)
        if group == vote:
            correct += 1
        total += 1
# 打印结果
print('correct: ', correct)  
print('total: ', total)  
print('Accuracy: ', correct/total)  

完整代码请查看github链接

sklearn的K近邻算法

同样需要先处理一下数据并生成符合sklearn的输入格式的数据集。

from sklearn import model_selection  
# 读取乳癌统计数据
df = pd.read_csv('./dataset/breast-cancer-wisconsin.data')  
# 处理问号
df.replace('?', -99999, inplace=True)  
# 因为ID字段与分类无关,所以去除他先,稍后我们看一下它的影响
df.drop(['id'], 1, inplace=True)  
df = df.astype(float)

# 生成数据集
X = np.array(df.drop(['class'], 1))  
y = np.array(df['class'])  
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y, test_size=0.2)  

然后生成KNN模型对象,用数据训练模型,评估模型准确性。

from sklearn import neighbors  
# 构建模型与训练
clf = neighbors.KNeighborsClassifier()  
clf.fit(X_train, y_train)

# 计算精确度
accuracy = clf.score(X_test, y_test)  
print('Accuracy: ', accuracy)

# 预测我们自己构造的数据属于哪个类型
example_measures = np.array([[4,2,1,1,1,2,3,2,1],[2,3,4,4,1,2,3,4,1]])  
prediction = clf.predict(example_measures)  
print('Predict resuct: ', prediction)  

完整代码请查看github链接

ChardLau

继续阅读此作者的更多文章