机器学习KNN算法(K近邻算法)的总体理论很简单不在这里赘述了。
这里使用比较古老的数据集,是房屋预测的数据集 下载地址 https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data
有14列,分别是13个特征,1个房屋价格值
一般对于txt类似的数据集读入都是差不多的步骤
path = 'XXX.txt'
fr = open(path) #打开文件
lines = fr.readlines() #读入所有行
dataset = []
for line in lines: #遍历每一行(这里的每一行都是string)
# 将整个string左右两边的空格和换行符去掉
# 可以理解为只保留最外面的两层
line = line.strip()
# 用空格将string分割成list
line = line.split('t')
# 将list每个元素的string转换为float
line = [float(i) for i in line]
dataset.append(line)
但是这里为.data,其实和txt文件处理相似,由于数据集的每列之间的空格个数不相同,有的是一个空格,有个两个,有的是三个,这里string的split需要用正则化re.split来代替具体代码如下:
path = 'housing.data'
fr = open(path)
lines = fr.readlines()
dataset = []
for line in lines:
line = line.strip()
line = re.split(r' +', line) # re处理
line = [float(i) for i in line]
dataset.append(line)
这里的feature我们不全都用,只用一部分,因为一些feature与最后的预测没有很密切的关系 这里我们采用pandas来进行操作作为容易,还有一点很关键作为特征,有的数大小差距很大,如下图所示:
所标三列就与其他差别很大,这里只是举例,所以需要做归一化处理,利用(data-min)/(max-min)
housing_header = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV']
# 需要用到的column
cols_used = ['CRIM', 'INDUS', 'NOX', 'RM', 'AGE', 'DIS', 'TAX', 'PTRATIO', 'B', 'LSTAT']
dataset_array = np.array(dataset)
# 转换为pandas
dataset_pd = pd.DataFrame(dataset_array, columns=housing_header)
# 取出特征数据
housing_data = dataset_pd.get(cols_used)
# 取出房价数据
labels = dataset_pd.get('MEDV')
housing_data = np.array(housing_data)
# 对特征数据进行归一化处理
# ptp(0)为每列求出每列数据中的range = max-min
housing_data = (housing_data - housing_data.min(0))/housing_data.ptp(0)
# [:, np.newaxis]为行向量转换为列向量
labels = np.array(labels)[:, np.newaxis]
print(housing_data)
print(labels)
最终得到的数据为 housing_data:
labels:
## 四、分为训练集与测试集 一般来说 训练集和测试集需要随机分配,如果没有验证集的部分,一般为8:2 其实这里可以用sklearn来进行分配操作,但这里我选择用numpy来操作,更为基础写法
np.random.seed(22)
# 生成随机的index
train_ratio = 0.8
data_size = len(housing_data)
train_index = np.random.choice(a=data_size, size=round(data_size*train_ratio), replace=False)
test_index = np.array(list(set(range(data_size))-set(train_index)))
# 生成训练集和测试集
x_train = housing_data[train_index]
y_train = labels[train_index]
x_test = housing_data[test_index]
y_test = labels[test_index]
## 五、Tensorflow实现KNN ### 1. 计算图的输入 这里不必过多解释, tensorflow的输入 x的每一个输入为 上面处理之后保留num个features的数据 y的每一个输入为 加格,即一个数据
x_train_placeholder = tf.placeholder(tf.float32, [None, num_features])
x_test_placeholder = tf.placeholder(tf.float32, [None, num_features])
y_train_placeholder = tf.placeholder(tf.float32, [None, 1])
y_test_placeholder = tf.placeholder(tf.float32, [None, 1])
故名思议: KNN-K近邻算法既 取前k个distance进行判断,如果是classifications问题其实很好判断,只需要选一种计算距离的方式即可,如L2(欧式距离)
也可以是L1
因为此处需要用到tensorflow来编写,需要注意的是矩阵运算,正常来说应该是一个testdata与全部的traindata进行计算,但这里采用bathsize大小的testdata与traindata进行继续,这里集中于矩阵和维度的变换上,先上code再解释 distance计算
distance = tf.sqrt(tf.reduce_sum(tf.square(tf.subtract(x_train_placeholder, tf.expand_dims(x_test_placeholder, 1))), reduction_indices=[1]))
这句话其实也可以分解为
x_test_placeholder_expansion = tf.expand_dims(x_test_placeholder, 1) #增加一个维度
x_substract = x_train_placeholder - x_test_placeholder_expansion #相减
x_square = tf.square(x_substract) #平方
x_square_sum = tf.reduce_sum(x_square, reduction_indices=[1]) # 每个相加
x_sqrt = tf.sqrt(x_square_sum) # 开方
这几句话主要是做了一个distance计算的过程, 就是我们最为熟悉的欧式距离 大概都能看懂,这里主要是讲解两句话
x_test_placeholder_expansion = tf.expand_dims(x_test_placeholder, 1)
这句话主要是因为 这里我们所要做的是每个testdata都与所有的traindata做计算,所以这里需要把testdata扩展一个维度 这样一来就相当于x_train_placeholder - x_test_placeholder_expansion时直接相当于batchsize大小的testdata每一个自己为一个维度的数据都与所有的traindata进行相减。
x_square_sum = tf.reduce_sum(x_square, reduction_indices=[1]) # 每个相加
这句话做的是将你之前计算出来的每个testdata与所有的traindata计算出来的每个feature之间的相减平方进行相加。
如果以上两句大家实在是无法明白,请自行 玩转以下tf的这两个函数即可明白用意。
以上计算距离的方式再预测问题中效果并不好,而是用这里介绍的另一种计算方式L1,代码如下:
distance = tf.reduce_sum(tf.abs(x_train_placeholder - tf.expand_dims(x_test_placeholder , 1)), axis=2)
因为这里不是分类问题,而是做预测问题,那么问题来了, 预测用这个算法怎么做。 主要有两种做法。
每个testdata的预测选择前k个最小的distance,每个distance对应traindata的房价值,做平均值即为预测值,如下图
如果k为3则prediction = (7+6+2)/3 = 5
按权重来, 即每个选中的distance作为总共distance总和的比例再乘以此distance对应的value最后求和即可,如下图
这里也有一种编程思想,即 如果不乘以7、6、2 剩下那部分其实就有点像前向推理里面的所谓的weights,后面的编程也会如此处理。
top_k_value, top_k_index = tf.nn.top_k(tf.negative(distance), k=K)
top_k_value = tf.truediv(1.0, top_k_value)
top_k_value_sum = tf.reduce_sum(top_k_value, axis=1)
top_k_value_sum = tf.expand_dims(top_k_value_sum, 1)
top_k_value_sum_again = tf.matmul(top_k_value_sum, tf.ones([1, K], dtype=tf.float32))
top_k_weights = tf.div(top_k_value, top_k_value_sum_again)
weights = tf.expand_dims(top_k_weights, 1)
top_k_y = tf.gather(y_train_placeholder, top_k_index)
predictions = tf.squeeze(tf.matmul(weights, top_k_y), axis=[1])
这里的代码稍微有点绕,请随着我来一个个看 因为tf.topk是返回的前k个的最大的,这里我们要的distance是最小的,所以这里需要对distance来个negative操作也就是取负值
top_k_value, top_k_index = tf.nn.top_k(tf.negative(distance), k=K)
因为top_k_value变为负数了,所以这里用一个倒数就又可以恢复到以前的大小比较关系了,这里相当于一个数变为负的变小了,再倒数一下,如果所有数都这么干,那最后的大小关系还是之前正数的大小关系,因为我们不需要他的真正大小只需要 比例就可以了。倒数代码如下:
top_k_value = tf.truediv(1.0, top_k_value)
之后开始算整个总和,就可以和前面的公式比对来看,此过程相当于算5+8+3=16的过程
top_k_value_sum = tf.reduce_sum(top_k_value, axis=1)
top_k_value_sum = tf.expand_dims(top_k_value_sum, 1)
因为这里我们采用的都是矩阵方式,也就是很多操作需要矩阵化,前面步骤结束之后接下来应该是用每个distace的负数倒数来除以总和sum,但是在相除之前有一个步骤需要进行,相当于为之后的计算做铺垫,,每个sum扩展到k个在一个维度里。
top_k_value_sum_again = tf.matmul(top_k_value_sum, tf.ones([1, K], dtype=tf.float32))
最后相除
top_k_weights = tf.div(top_k_value, top_k_value_sum_again)
weights = tf.expand_dims(top_k_weights, 1)
之后取出前k个value,与整个权重weights进行矩阵相乘计算,前一步的expand_dims和之前仔细讲到的差不多功能,这里不赘述了。
top_k_y = tf.gather(y_train_placeholder, top_k_index)
predictions = tf.squeeze(tf.matmul(weights, top_k_y), axis=[1])
loss = tf.reduce_mean(tf.square(tf.subtract(predictions, y_test_placeholder)))
loop_nums = int(np.ceil(len(x_test)/batchsize))
with tf.Session() as sess:
for i in range(loop_nums):
min_index = i*batchsize
max_index = min((i+1)*batchsize, len(x_test))
x_test_batch = x_test[min_index: max_index]
y_test_batch = y_test[min_index: max_index]
result, los = sess.run([predictions, loss], feed_dict={
x_train_placeholder: x_train, y_train_placeholder: y_train,
x_test_placeholder: x_test_batch, y_test_placeholder: y_test_batch
})
print("No.%d batch, loss is %f"%(i+1, los))
loss就为比较常见的计算loss的方式, 其他都是tf常用格式,问题不大。
利用plt可视化柱状图hist
pins = np.linspace(5, 50, 45)
print(pins)
plt.hist(result, pins, alpha=0.5, label='prediction')
plt.hist(y_test_batch, pins, alpha=0.5, label='actual')
plt.legend(loc='best')
plt.show()
import numpy as np
import re
import pandas as pd
import tensorflow as tf
from matplotlib import pyplot as plt
path = 'housing.data'
fr = open(path)
lines = fr.readlines()
dataset = []
for line in lines:
line = line.strip()
line = re.split(r' +', line)
line = [float(i) for i in line]
dataset.append(line)
housing_header = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV']
cols_used = ['CRIM', 'INDUS', 'NOX', 'RM', 'AGE', 'DIS', 'TAX', 'PTRATIO', 'B', 'LSTAT']
dataset_array = np.array(dataset)
dataset_pd = pd.DataFrame(dataset_array, columns=housing_header)
housing_data = dataset_pd.get(cols_used)
labels = dataset_pd.get('MEDV')
housing_data = np.array(housing_data)
housing_data = (housing_data - housing_data.min(0))/housing_data.ptp(0)
labels = np.array(labels)[:, np.newaxis]
np.random.seed(13)
train_ratio = 0.8
data_size = len(housing_data)
train_index = np.random.choice(a=data_size, size=round(data_size*train_ratio), replace=False)
test_index = np.array(list(set(range(data_size))-set(train_index)))
x_train = housing_data[train_index]
y_train = labels[train_index]
x_test = housing_data[test_index]
y_test = labels[test_index]
num_features = len(cols_used)
batchsize = len(x_test)
K = 4
x_train_placeholder = tf.placeholder(tf.float32, [None, num_features])
x_test_placeholder = tf.placeholder(tf.float32, [None, num_features])
y_train_placeholder = tf.placeholder(tf.float32, [None, 1])
y_test_placeholder = tf.placeholder(tf.float32, [None, 1])
distance = tf.reduce_sum(tf.abs(tf.subtract(x_train_placeholder, tf.expand_dims(x_test_placeholder, 1))), axis=2)
# distance = tf.sqrt(tf.reduce_sum(tf.square(tf.subtract(x_train_placeholder, tf.expand_dims(x_test_placeholder, 1))), reduction_indices=[1]))
# x_test_placeholder_expansion = tf.expand_dims(x_test_placeholder, 1)
# x_substract = x_train_placeholder - x_test_placeholder_expansion
# x_square = tf.square(x_substract)
# x_square_sum = tf.reduce_sum(x_square, reduction_indices=[1])
# x_sqrt = tf.sqrt(x_square_sum)
top_k_value, top_k_index = tf.nn.top_k(tf.negative(distance), k=K)
top_k_value = tf.truediv(1.0, top_k_value)
top_k_value_sum = tf.reduce_sum(top_k_value, axis=1)
top_k_value_sum = tf.expand_dims(top_k_value_sum, 1)
top_k_value_sum_again = tf.matmul(top_k_value_sum, tf.ones([1, K], dtype=tf.float32))
top_k_weights = tf.div(top_k_value, top_k_value_sum_again)
weights = tf.expand_dims(top_k_weights, 1)
top_k_y = tf.gather(y_train_placeholder, top_k_index)
predictions = tf.squeeze(tf.matmul(weights, top_k_y), axis=[1])
loss = tf.reduce_mean(tf.square(tf.subtract(predictions, y_test_placeholder)))
loop_nums = int(np.ceil(len(x_test)/batchsize))
with tf.Session() as sess:
for i in range(loop_nums):
min_index = i*batchsize
max_index = min((i+1)*batchsize, len(x_test))
x_test_batch = x_test[min_index: max_index]
y_test_batch = y_test[min_index: max_index]
result, los = sess.run([predictions, loss], feed_dict={
x_train_placeholder: x_train, y_train_placeholder: y_train,
x_test_placeholder: x_test_batch, y_test_placeholder: y_test_batch
})
print("No.%d batch, loss is %f"%(i+1, los))
pins = np.linspace(5, 50, 45)
print(pins)
plt.hist(result, pins, alpha=0.5, label='prediction')
plt.hist(y_test_batch, pins, alpha=0.5, label='actual')
plt.legend(loc='best')
plt.show()
当batchsize为 测试数据大小的时候并且k=4