机器学习Knn约会网站实例
一、实验背景
海伦女士一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的人选,但她没有从中找到喜欢的人。经过一番总结,她发现曾交往过三种类型的人:
不喜欢的人—didntLike
魅力一般的人—smallDoses
极具魅力的人—largeDoses
尽管发现了上述规律,但海伦依然无法将约会网站推荐的匹配对象归入恰当的分类。她觉得可以在周一到周五约会那些魅力一般的人,而周末则更喜欢与那些极具魅力的人为伴,海伦希望我们的分类软件可以更好地帮助她将匹配对象划分到确切的分类中。此外海伦还收集了一些约会网站未曾记录的数据信息,她认为这些数据更有助于匹配对象的归类。
海伦收集约会数据有了一段时间,她把这些数据存放在文本文件.txt(自定义)中,每个样本数据占据一行,总共有1000行。海伦的样本主要包含以下3种特征和标签:

二.实验准备
1.从网络中下载对应的文本数据,且数据量要够,约1000条。
2.使用pycharm,并新建项目,选择conda环境。Anconda下载并配置详细见https://blog.csdn.net/2403_88297561/article/details/151857067?spm=1001.2014.3001.5501
3.创建一个文件夹,并在文件夹中新建一个python文件,和导入已经下载完的数据文本文件,如图

三.实验
本次实验中会使用的库
import numpy as np
import operator
import matplotlib.pyplot as plt
from sklearn.model_selection import KFold
from sklearn.metrics import roc_curve, auc, precision_recall_curve, average_precision_score
from sklearn.preprocessing import label_binarize
1.准备数据:从文本中输入数据并进行处理
从样本数据中可以看出,原始数据及其丑陋,并且飞行里程数存在数据过大,需要进行归一化处理。其他数据可以根据精度要求,对保留小数进行处理。

具体代码如下:
def read_and_preprocess(filename):
"""读取并预处理训练数据,返回特征、标签和特征名称"""
with open(filename, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 提取第一列数据用于归一化(每年飞行公里数)
first_col = [float(line.strip().split('\t')[0]) for line in lines]
min_first = min(first_col)
max_first = max(first_col)
data = []
labels = []
for line in lines:
line = line.strip()
parts = line.split('\t')
# 第一列:每年飞行公里数(归一化+保留1位小数)
first_norm = (float(parts[0]) - min_first) / (max_first - min_first)
first_rounded = round(first_norm, 1)
# 第二列:玩手机时间比例(整数)
second_int = int(round(float(parts[1])))
# 第三列:每周吃冰淇淋公升数(保留1位小数)
third_rounded = round(float(parts[2]), 1)
data.append([first_rounded, second_int, third_rounded])
if parts[-1] == 'didntLike':
labels.append(1)
elif parts[-1] == 'smallDoses':
labels.append(2)
else:
labels.append(3)
feature_names = ['每年飞行公里数', '玩手机时间比例', '每周吃冰淇淋公升数']
return np.array(data), np.array(labels), feature_names, min_first, max_first
2.KNN算法:核心算法
knn中的计算距离公式有很多,自行选择一种即可,博主选择的欧氏距离,并且要调用np库中的排序函数
具体代码如下:
def knn_classify(test_data, train_data, train_labels, k):
# 计算欧式距离
distances = np.sqrt(np.sum((train_data - test_data) ** 2, axis=1))
# 按距离排序取索引
sorted_indices = np.argsort(distances)
# 统计前k个近邻的标签
class_count = {}
for i in range(k):
label = train_labels[sorted_indices[i]]
class_count[label] = class_count.get(label, 0) + 1
# 投票选出最优标签
sorted_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
return sorted_count[0][0]
3.分割数据集和评估算法:交叉验证
交叉验证是个很好用的方法,它将数据项均分为n组,每组m个,组内取m-1行数据作为训练集来训练模型,第m行数据作用验证集来评估和提升模型,循环n次。
具体代码如下:
def cross_validate(data, labels, k_knn, n_splits=5):
kf = KFold(n_splits=n_splits, shuffle=True, random_state=42)
all_true = []
all_pred = []
fold_scores = []
for fold, (train_idx, val_idx) in enumerate(kf.split(data)):
train_data, val_data = data[train_idx], data[val_idx]
train_labels, val_labels = labels[train_idx], labels[val_idx]
preds = [knn_classify(sample, train_data, train_labels, k_knn) for sample in val_data]
all_true.extend(val_labels)
all_pred.extend(preds)
accuracy = np.mean(np.array(preds) == val_labels)
fold_scores.append(accuracy)
print(f"第{fold + 1}折验证准确率:{accuracy:.4f}")
overall_acc = np.mean(fold_scores)
print(f"\n交叉验证平均准确率:{overall_acc:.4f}(k={k_knn})")
return np.array(all_true), np.array(all_pred)
4.评估模型性能:ROC和PR曲线
ROC曲线用来评估模型的整体区分能力。PR曲线用来评估模型的正样本精确度。
具体代码如下:
def plot_roc_curve(y_true, y_pred, n_classes=3):
"""绘制ROC曲线"""
y_true_bin = label_binarize(y_true, classes=[1, 2, 3])
y_pred_bin = label_binarize(y_pred, classes=[1, 2, 3])
plt.figure(figsize=(8, 6))
for i in range(n_classes):
fpr, tpr, _ = roc_curve(y_true_bin[:, i], y_pred_bin[:, i])
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, lw=2, label=f'类别{i + 1} (AUC = {roc_auc:.3f})')
plt.plot([0, 1], [0, 1], 'k--', lw=2)
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('假正例率 (FPR)')
plt.ylabel('真正例率 (TPR)')
plt.title('KNN分类器的ROC曲线')
plt.legend(loc="lower right")
plt.show()
def plot_pr_curve(y_true, y_pred, n_classes=3):
"""绘制PR曲线"""
y_true_bin = label_binarize(y_true, classes=[1, 2, 3])
y_pred_bin = label_binarize(y_pred, classes=[1, 2, 3])
plt.figure(figsize=(8, 6))
for i in range(n_classes):
precision, recall, _ = precision_recall_curve(y_true_bin[:, i], y_pred_bin[:, i])
ap = average_precision_score(y_true_bin[:, i], y_pred_bin[:, i])
plt.plot(recall, precision, lw=2, label=f'类别{i + 1} (AP = {ap:.3f})')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('召回率 (Recall)')
plt.ylabel('精确率 (Precision)')
plt.title('KNN分类器的PR曲线')
plt.legend(loc="lower right")
plt.show()
在输入k=100的情况下的ROC曲线和PR曲线


5.输入接口:输入测试集
def input_test_data(min_first, max_first):
print("\n请输入以下特征数据(按提示格式输入):")
try:
# 输入第一列:每年飞行公里数(原始值,需归一化)
first = float(input("1. 每年飞行公里数(例如:5000):"))
# 用训练集的min和max进行归一化(保持一致性)
first_norm = (first - min_first) / (max_first - min_first)
first_processed = round(first_norm, 1)
# 输入第二列:玩手机时间比例(整数)
second = int(input("2. 玩手机时间比例(%,例如:30):"))
# 输入第三列:每周吃冰淇淋公升数(保留1位小数)
third = round(float(input("3. 每周吃冰淇淋公升数(例如:1.5):")), 1)
return np.array([first_processed, second, third])
except ValueError:
print("输入格式错误!请输入数字。")
return None
def label_to_text(label):
"""将标签(1/2/3)转换为文字说明"""
if label == 1:
return "不喜欢(didntLike)"
elif label == 2:
return "有点喜欢(smallDoses)"
else:
return "非常喜欢(largeDoses)"
6.运行主函数:
if __name__ == '__main__':
# 1. 读取训练数据
filename = '数据材料.txt'
try:
print("正在加载训练数据...")
train_data, train_labels, feature_names, min_first, max_first = read_and_preprocess(filename)
print(f"训练数据加载完成,共 {len(train_data)} 条记录")
print("特征表头:", feature_names)
# 2. 用户输入K值
while True:
try:
k = int(input("\n请输入KNN的k值(正整数,例如3):"))
if k > 0:
break
else:
print("请输入正整数!")
except ValueError:
print("输入错误,请输入整数!")
# 3. 交叉验证评估模型
print("\n开始交叉验证...")
y_true, y_pred = cross_validate(train_data, train_labels, k)
# 4. 绘制评估曲线
plot_roc_curve(y_true, y_pred)
plot_pr_curve(y_true, y_pred)
# 5. 输入测试数据并预测
while True:
test_sample = input_test_data(min_first, max_first)
if test_sample is not None:
# 预测标签
predicted_label = knn_classify(test_sample, train_data, train_labels, k)
# 输出结果
print(f"\n预测结果:{label_to_text(predicted_label)}(标签值:{predicted_label})")
# 询问是否继续输入
continue_input = input("\n是否继续输入测试数据?(y/n):").strip().lower()
if continue_input != 'y':
print("程序结束。")
break
except FileNotFoundError:
print(f"错误:文件 '{filename}' 不存在,请检查路径是否正确。")
except Exception as e:
print(f"发生错误:{str(e)}")
四.运行情况:


五.结论:
本次实验是新手入门机器学习遇到的第一个实验——KNN实验,可以像博主一样多多参考其他人博客,或借助b站,ai问答,阅读书籍等学习途径来尝试这个实验,进一步提升自己。
更多推荐


所有评论(0)