diff --git a/function.py b/function.py index 34a7735..f2b4e23 100644 --- a/function.py +++ b/function.py @@ -1,170 +1,134 @@ -# function.py import numpy as np import os +from sklearn.preprocessing import StandardScaler # 导入标准化库 -""" -实验说明: -- 自行编写实现数据加载与 kNN 的手写数字分类算法。 -- 函数输入: 待分类的样本 input_vector, 训练数据集 dataSet, 训练集对应的标签 labels。 -- 函数返回: 预测的分类标签。 - -注意事项: -- 在 function.py 中实现 kNN 分类算法。 -- 可以自行导入其他库,需要在 requirements.txt 添加依赖,若未在 requirements.txt 中添加使用到的依赖,则无法自动评测,成绩为零。 -- 自动评测脚本仅会加载 classify 和 load_data 函数,请勿修改函数名。 -- 训练集和测试集需手动下载解压,并放入当前目录下。 - -测评环境: -- CPU: Intel Xeon(R) CPU E3-1231 v3 @ 3.40GHz -- OS: Ubuntu 22.04.4 LTS -- Python: 3.10.12 -- numpy: 2.1.1 -""" - - -""" -请在这里填写学号和姓名,不填写即成绩无效。 -""" +# 学号和姓名 id = "202210423119" name = "肖锐" +def sigmoid(z): + """计算sigmoid函数""" + return 1.0 / (1 + np.exp(-z)) -""" -参数说明: -- input_vector (numpy array): 输入待分类的样本。 -- dataSet (numpy array): 训练数据集。 -- labels (list or numpy array): 训练集对应的标签。 -- k (int): kNN 算法参数,请**自行设置默认值**,评测时仅会使用其默认值,不修改默认值将**无法通过**评测,**成绩为零**。 -返回值: -- int: 预测的分类标签。 -""" +def compute_gradient(X_batch, y_batch, weights, lambda_reg): + """计算梯度,包含L2正则化""" + z = np.dot(X_batch, weights) + h = sigmoid(z) + gradient = np.dot(X_batch.T, (h - y_batch)) / len(y_batch) + gradient += (lambda_reg / len(y_batch)) * weights # L2正则化 + return gradient - -# 这里写自己的代码实现 -def classify(input_vector, dataSet, labels, k=4): - """ - KNN 分类算法。 - - Args: - - input_vector (numpy array): 输入待分类的样本。 - - dataSet (numpy array): 训练数据集。 - - labels (list or numpy array): 训练集对应的标签。 - - k (int): kNN 算法参数,请**自行设置默认值**,评测时仅会使用其默认值,不修改默认值将**无法通过**评测,**成绩为零**。 - - Returns: - - int: 预测的分类标签。 - """ - """ - 使用欧式距离的 kNN 分类算法。 - """ - # 计算欧式距离 - distances = np.linalg.norm(dataSet - input_vector, axis=1) +def train_logreg(X, y, learning_rate=0.001, iterations=10000, batch_size=64, lambda_reg=0.1): + """对率回归训练函数。""" + m, n = X.shape + weights = np.random.randn(n) * np.sqrt(2. / n) # He初始化 + print("开始训练对率回归模型") - # 获取距离最近的 k 个样本的索引 - k_indices = np.argsort(distances)[:k] - - # 获取最近邻样本的标签 - k_nearest_labels = [labels[i] for i in k_indices] - - # 找出出现次数最多的标签 - most_common = max(set(k_nearest_labels), key=k_nearest_labels.count) - - return most_common + for i in range(iterations): + indices = np.random.permutation(m) + X_shuffled = X[indices] + y_shuffled = y[indices] - -# 这里也写自己的代码实现 - -def load_dataset(directory): - """ - 从文件夹中加载数据,若不自行正确实现,则无法通过评测,**不记录成绩**。 - - Args: - - directory (str): 数据文件夹路径。 - - Returns: - - np.array: 特征数据。 - - np.array: 标签数据。 - """ - features = [] - labels = [] - - # 遍历文件夹中的所有文件 - for filename in os.listdir(directory): - if filename.endswith(".txt"): # 假设文件是 .txt 格式 - filepath = os.path.join(directory, filename) + for j in range(0, m, batch_size): + X_batch = X_shuffled[j:j + batch_size] + y_batch = y_shuffled[j:j + batch_size] + gradient = compute_gradient(X_batch, y_batch, weights, lambda_reg) + weights -= learning_rate * gradient - # 假设文件名格式为 "数字_编号.txt" - label = int(filename.split('_')[0]) - - # 加载文件中的二进制数据并转换为 1D 向量 - image_vector = img_to_vector(filepath) - - features.append(image_vector) - labels.append(label) + if i % 1000 == 0: # 每1000次迭代打印一次信息 + print(f"迭代 {i}: 当前权重形状: {weights.shape}") + + return weights + +def predict_logreg(inX, weights): + """对率回归预测函数。""" + z = np.dot(inX, weights) + h = sigmoid(z) + return 1 if h >= 0.5 else 0 + +def load_data(filepath): + """从文件中加载数据。""" + if not os.path.exists(filepath): + raise FileNotFoundError(f"文件 {filepath} 不存在!") - return np.array(features), np.array(labels) + data, labels = [], [] + print(f"加载数据中: {filepath}") + with open(filepath, 'r') as f: + for line in f: + line = line.strip().split() + if len(line) >= 1 and line[-1].isdigit(): + line_data = [0 if x == '?' else float(x) for x in line[:-1]] + data.append(line_data) + labels.append(int(line[-1])) - -# 这里还写自己的代码实现 -def img_to_vector(filename): - """ - 将二值图像转换为向量。 - 该部分应为 load_dataset 函数的辅助子函数,可单独在这里实现,也可直接集成至 load_dataset 函数中。 - - Args: - - filename (str): 图像文件路径。 - - Returns: - - np.array: 二值图像向量。 - """ - with open(filename) as file: - lines = file.readlines() - image_vector = np.array([int(char) for line in lines for char in line.strip()]) + data = np.array(data) + labels = np.array(labels) - return image_vector + num_features = data.shape[1] + indices_to_remove = [2] # 去掉特征3(索引2) + + if num_features > 23: + indices_to_remove.extend(range(23, min(28, num_features))) + + data = np.delete(data, indices_to_remove, axis=1) + target_variable = np.where((labels == 2) | (labels == 3), 0, 1) + + print(f"数据加载成功,数据形状: {data.shape}, 标签形状: {target_variable.shape}") + return data, target_variable + +def k_fold_cross_validation(X, y, k=5, learning_rate=0.001, iterations=10000, batch_size=64, lambda_reg=0.1): + """K折交叉验证。""" + m = X.shape[0] + fold_size = m // k + accuracies = [] + + for i in range(k): + val_start = i * fold_size + val_end = (i + 1) * fold_size if i != k - 1 else m + + X_val = X[val_start:val_end] + y_val = y[val_start:val_end] + X_train = np.concatenate((X[:val_start], X[val_end:])) + y_train = np.concatenate((y[:val_start], y[val_end:])) + + weights = train_logreg(X_train, y_train, learning_rate, iterations, batch_size, lambda_reg) + + val_accuracy = np.mean([predict_logreg(X_val[j], weights) == y_val[j] for j in range(len(y_val))]) + accuracies.append(val_accuracy) + + print(f"Fold {i + 1}/{k} - 验证准确率: {val_accuracy:.4f}") + + return np.mean(accuracies) if __name__ == "__main__": - # 以下代码可用于测试,可自行随意修改 - train_data, train_labels = load_dataset("trainingDigits") + # 加载训练集 + try: + X_train, y_train = load_data("./data/horse-colic.data") + except FileNotFoundError as e: + print(e) + exit() - test_data = ( - "00000000000000110000000000000000" - "00000000000011111000000000000000" - "00000000000011111100000000000000" - "00000000001111111110000000000000" - "00000000001111111110000000000000" - "00000000001111111100000000000000" - "00000000011111111000000000000000" - "00000000111111111000000000000000" - "00000000111111110000000000000000" - "00000000111111110000000000000000" - "00000000111111110000000000000000" - "00000000111111110000000000000000" - "00000000111111100000000000000000" - "00000001111111100000000000000000" - "00000001111111100000000000000000" - "00000001111111100000000000000000" - "00000001111111111111000000000000" - "00000001111111111111111000000000" - "00000001111111111111111100000000" - "00000011111111111111111110000000" - "00000011111111111111111110000000" - "00000001111111111111111110000000" - "00000001111111111111111110000000" - "00000000111111111100111111100000" - "00000000111111111000111111100000" - "00000000011111111001111111100000" - "00000000011111111111111111000000" - "00000000001111111111111111000000" - "00000000000111111111111111000000" - "00000000000011111111111100000000" - "00000000000001111111110000000000" - "00000000000000000111100000000000" - ) - predicted_label = classify( - np.array([int(char) for char in test_data]).reshape(1, 1024), - train_data, - train_labels, - ) + # 数据归一化处理 + scaler = StandardScaler() + X_train = scaler.fit_transform(X_train) # 使用训练集进行标准化 - print(f"预测的标签: {predicted_label}") + # k折交叉验证 + avg_accuracy = k_fold_cross_validation(X_train, y_train, k=5, learning_rate=0.01, iterations=1000) + print(f"K折交叉验证的平均准确率: {avg_accuracy:.4f}") + + # 加载测试集 + try: + X_test, y_test = load_data("./data/horse-colic.test") + except FileNotFoundError as e: + print(e) + exit() + + # 测试集也需要进行相同的归一化 + X_test = scaler.transform(X_test) # 使用相同的Scaler进行测试集的标准化 + + # 训练对率回归模型 + weights = train_logreg(X_train, y_train, learning_rate=0.01, iterations=1000) + + # 测试单个数据 + y_pred = predict_logreg(X_test[11], weights) + print(f"预测结果: {y_pred}, 实际结果: {y_test[11]}") \ No newline at end of file