此系列博客依据Peter的《机器学习实战》一书所写。
kNN模块
# 导入所需的模块
from numpy import * # 引入numpy库,简化常用数学函数的调用
import operator # 引入operator模块,用于字典排序
# 定义K近邻算法的分类函数
def classify(input, data_set, labels, k):
# 获取数据集的行数
data_size = data_set.shape[0]
# 将输入向量扩展为与数据集相同行数的矩阵,以便计算每行的差值
diff_mat = tile(input, (data_size, 1)) - data_set
# 计算差值矩阵的平方
sq_diff_mat = diff_mat ** 2
# 对平方差矩阵求和并开方,得到欧几里得距离
distances = sq_diff_mat.sum(axis=1) ** 0.5
# 对距离进行排序,返回索引
sorted_distances = distances.argsort()
# 初始化一个字典来记录每个类别的票数
class_count = {}
# 遍历最近的k个样本
for i in range(k):
# 获取最近样本的标签
vote_label = labels[sorted_distances[i]]
# 在字典中增加对应标签的计数
class_count[vote_label] = class_count.get(vote_label, 0) + 1
# 根据类别出现的次数降序排序
# 使用itemgetter(1)获取字典中的值作为排序依据
sorted_class_counts = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
# 返回票数最多的类别
return sorted_class_counts[0][0]
任务1数据集文件读取
# 导入所需的模块
from numpy import * # 引入numpy库,简化常用数学函数和数组操作的调用
# 定义一个函数,用于加载数据文件
def file_load(filename):
# 打开指定的文件
file = open(filename)
# 读取文件的所有行到列表中
lines = file.readlines()
# 关闭文件(虽然这里没有显示关闭,但在with语句或使用完后应关闭文件以释放资源)
file.close() # 这一行代码是假设的,因为原代码中没有包含它,但通常在open之后需要有close来释放文件资源
# 计算文件中的行数,即数据点的数量
num_of_lines = len(lines)
# 初始化一个全零的二维数组,大小为行数 x 3,用于存储数据
data_mat = zeros((num_of_lines, 3))
# 初始化一个字典用于存储类别标签,但实际上这里应该是一个列表
class_label = []
# 初始化索引变量
index = 0
# 遍历文件中的每一行
for line in lines:
# 去掉行尾的空白字符
line = line.strip()
# 按制表符分割行,得到一个列表
line_list = line.split('\t')
# 将前三列数据(特征)存入data_mat的当前行
data_mat[index, :] = line_list[0: 3]
# 将最后一列数据(类别标签)转换为整数并添加到class_label列表中
class_label.append(int(line_list[-1]))
# 更新索引变量,准备处理下一行
index += 1
# 返回数据矩阵和类别标签列表
return data_mat, class_label
归一化数值
# 导入所需的模块
from numpy import * # 引入numpy库,简化常用数学函数和数组操作的调用
# 定义自动归一化函数
def auto_norm(data_set):
# 计算每列的最小值,axis=0表示按列计算
min_values = data_set.min(0)
# 计算每列的最大值,同上
max_values = data_set.max(0)
# 计算数据的范围,即最大值减去最小值
ranges = max_values - min_values
# 获取数据集中样本的数量
m = data_set.shape[0]
# 使用tile函数重复min_values和(max_values - min_values),以便与data_set进行广播运算
# tile(A, reps)函数会根据reps参数重复A中的元素,reps=(m, 1)意味着将min_values或(max_values - min_values)复制成m行1列的形式
# 进行向量化操作,计算归一化后的数据
norm_data = (data_set - tile(min_values, (m, 1))) / tile(ranges, (m, 1))
# 返回归一化后的数据集、数据的范围以及每列的最小值
return norm_data, ranges, min_values
任务1主体函数
import numpy as np # 导入numpy库,提供高效数组操作和数学函数
# 假设file_load和auto_norm函数已被定义,且能正常工作
dating_data, dating_labels = file_load("datingTestSet2.txt") # 加载数据集
norm_data, ranges, min_values = auto_norm(dating_data) # 对数据集进行归一化处理
m = norm_data.shape[0] # 获取归一化数据集的行数,即样本数量
ho_ratio = 0.25 # 设置hold-out比例,这里是25%,即25%的数据用于测试,75%的数据用于训练
num_testdata = int(m * ho_ratio) # 计算测试集样本数量
# 注意:这里的变量名ho_ratio和num_testdata可以更清晰一些,比如使用test_ratio和num_test_samples
error_count = 0.0 # 初始化分类错误计数器
# 开始测试,遍历前num_testdata个样本作为测试集
for i in range(num_testdata):
# 调用classify函数对第i个样本进行分类
# 注意:classify函数需要你自己定义,它应该接收四个参数:待分类样本、训练数据集、训练数据集对应的标签以及k值
classify_result = classify(norm_data[i, :], norm_data[num_testdata : m, :], dating_labels[num_testdata : m], 3)
# 输出分类结果和真实标签
print("the classifier came back with: %d, the real answer is: %d" % (classify_result, dating_labels[i]))
# 如果分类结果与真实标签不符,则增加错误计数
if (classify_result != dating_labels[i]):
error_count += 1.0
# 计算并输出总的错误率
print("the total error rate is: %f" % (error_count / float(num_testdata)))
任务1绘图模块
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
dating_data, dating_labels = file_load("datingTestSet2.txt")
# 创建一个三维的图形
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# 转换 labels 到数值型数组
labels_num = np.array(dating_labels, dtype=float)
# 使用 scatter 函数在三维空间中创建散点图
sc = ax.scatter(dating_data[:, 0], dating_data[:, 1], dating_data[:, 2],
s=15.0 * labels_num, # 点的大小
c=labels_num, # 点的颜色
cmap='viridis', # 颜色映射
alpha=0.6) # 点的透明度
# 添加颜色条
plt.colorbar(sc)
# 设置坐标轴标签
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
# 显示图形
plt.show()
任务2字符图转向量
import numpy as np # 导入numpy库,提供高效数组操作和数学函数
# 定义一个函数,将图像文件转换为一维向量
def img2vector(filename):
# 初始化一个1x1024的零向量,1024是32x32像素图像拉平后的长度
vect = np.zeros((1, 1024))
# 打开图像文件
file = open(filename)
# 遍历图像的每一行,共32行
for i in range(32):
# 读取当前行的内容
line = file.readline()
# 遍历行中的每一个字符,共32个字符
for j in range(32):
# 将当前字符转换为整数,并存储到向量中相应的位置
# 计算位置的方式是32*i + j,这确保了图像的二维结构被正确地转换为一维向量
vect[0, 32 * i + j] = int(line[j])
# 关闭文件
file.close() # 这一行代码在原代码中省略了,但在实际应用中,文件使用完毕后应当关闭
# 返回转换后的向量
return vect
任务2主体函数
import numpy as np # 导入numpy库,提供高效数组操作和数学函数
import os
# 初始化手写数字的标签列表
hw_labels = []
# 从'data/trainingDigits'目录中获取所有文件名
training_data = os.listdir('E:/code/python/machinelearn/ch2/trainingDigits')
# 获取训练数据文件的总数
m = len(training_data)
# 创建一个m行1024列的零矩阵,用于存储训练数据
training_mat = np.zeros((m, 1024))
# 循环遍历所有训练数据文件
for i in range(m):
# 获取文件名,假设文件名格式为"1_17.txt",其中"1"是数字类别,"17"是随机编号
file_name = training_data[i]
# 分割文件名,去除扩展名'.txt'
file = file_name.split('.')[0]
# 再次分割文件名,获取类别数字
class_num = int(file.split('_')[0])
# 将类别数字添加到标签列表
hw_labels.append(class_num)
# 读取图像文件,将其转换为1024维的向量,并存储到训练矩阵中
training_mat[i, :] = img2vector('E:/code/python/machinelearn/ch2/trainingDigits/%s' % file_name)
# 从'data/testDigits'目录中获取所有文件名
test_data = os.listdir('E:/code/python/machinelearn/ch2/testDigits')
# 初始化错误计数器
error = 0.0
# 获取测试数据文件的总数
m_test = len(test_data)
# 循环遍历所有测试数据文件
for i in range(m_test):
# 获取文件名
file_name = test_data[i]
# 分割文件名,去除扩展名'.txt'
file = file_name.split('.')[0]
# 再次分割文件名,获取类别数字
class_num = int(file.split('_')[0])
# 读取图像文件,将其转换为1024维的向量,并存储到测试矩阵中
# 注意:test_mat未在代码中定义,应该改为使用img2vector的返回值直接进行分类
test_vector = img2vector('E:/code/python/machinelearn/ch2/testDigits/%s' % file_name)
# 调用classify函数对测试样本进行knn分类,接收四个参数:待分类样本、训练数据集、训练数据集对应的标签以及k值
classify_result = classify(test_vector, training_mat, hw_labels, 3)
# 打印分类结果与真实标签
print("the classifier came back with: %d, the real answer is: %d" % (classify_result, class_num))
# 如果分类结果与真实标签不符,增加错误计数
if(classify_result != class_num):
error += 1.0
# 打印总错误数
print("\n the total number of errors is: %d" % error)
# 打印错误率
print("\n the total error rate is: %f" % (error / float(m_test)))