对高维向量分类的一些见解
对高维度向量进行分类,首先我们会考虑使用knn分类
建立一个knn分类器,我们目的分类数为2,设置k=3,创建分类模型,进行预测
代码示例如下
import numpy as np from collections import Counter class KNNClassifier: def __init__(self, k=3): self.k = k self.X_train = None self.y_train = None def fit(self, X, y): self.X_train = np.array(X) self.y_train = np.array(y) def euclidean_distance(self, x1, x2): return np.sqrt(np.sum((x1 - x2) ** 2)) def predict(self, X): X = np.array(X) predictions = [self._predict(x) for x in X] return np.array(predictions) def _predict(self, x): distances = [self.euclidean_distance(x, x_train) for x_train in self.X_train] k_indices = np.argsort(distances)[:self.k] k_nearest_labels = self.y_train[k_indices] return Counter(k_nearest_labels).most_common(1)[0][0] # 756维测试示例 if __name__ == "__main__": # 生成756维训练数据 np.random.seed(42) X_train = np.random.rand(10, 756) # 10个样本,每个样本756维 y_train = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) # 二分类标签 # 生成测试数据 X_test = np.random.rand(2, 756) # 2个测试样本,756维 # 训练和预测 knn = KNNClassifier(k=3) knn.fit(X_train, y_train) predictions = knn.predict(X_test) print("KNN 756维预测结果:", predictions)
knn分类器可以处理高维度向量,但事实上在实际处理中可能会出现维度灾难,即随着维度增加,在高维度空间中,所有点的距离会趋于相似,可能会影响分类效果,在本项目中我们要处理756维pt向量,所以很有可能会遇到维度灾难问题,knn分类方法被我们排除
现在可以考虑kd树,或者用pca降维,t-SNE等方法降低维度
pca降维
from sklearn.decomposition import PCA pca = PCA(n_components=50) # 降到50维 X_train_pca = pca.fit_transform(X_train) X_test_pca = pca.transform(X_test)
attention:通过pca降维可能会丢失信息,如果降维后不可分,原数据可能是可分的;如果降维后可分,原数据一定是可分的。
如果想处理非常高维度的向量,我们可以优先考虑支持向量机SVM
支持向量机通过寻找最大超平面间隔来分离不同类别的数据,对于线性可分数据,目标是最优化最大化间隔和最小化分类误差(或者引入松弛变量来解决不可分情况)
下面是示例代码
import numpy as np class SVMClassifier: def __init__(self, learning_rate=0.001, lambda_param=0.01, n_iters=1000): self.lr = learning_rate self.lambda_param = lambda_param self.n_iters = n_iters self.weights = None self.bias = None def fit(self, X, y): n_samples, n_features = X.shape self.weights = np.zeros(n_features) self.bias = 0 y_ = np.where(y <= 0, -1, 1) for _ in range(self.n_iters): for idx, x_i in enumerate(X): condition = y_[idx] * (np.dot(x_i, self.weights) + self.bias) >= 1 if condition: self.weights -= self.lr * (2 * self.lambda_param * self.weights) else: self.weights -= self.lr * (2 * self.lambda_param * self.weights - np.dot(x_i, y_[idx])) self.bias -= self.lr * y_[idx] def predict(self, X): linear_output = np.dot(X, self.weights) + self.bias return np.where(linear_output >= 0, 1, 0) # 756维测试示例 if __name__ == "__main__": # 生成756维训练数据 np.random.seed(42) X_train = np.random.rand(10, 756) # 10个样本,每个样本756维 y_train = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) # 二分类标签 # 生成测试数据 X_test = np.random.rand(2, 756) # 2个测试样本,756维 # 训练和预测 svm = SVMClassifier(learning_rate=0.001, lambda_param=0.01, n_iters=1000) svm.fit(X_train, y_train) predictions = svm.predict(X_test) print("SVM 756维预测结果:", predictions)
svm可以通过寻找最大间隔超平面,在高维度空间具有更优的鲁棒性,预测的时候只要做一次矩阵运算
但是关键是,我们不知道我们的数据是否是可以被一个最大超平面进行分割,也就是是否线性可分
现在有两个选择,一个是使用RBF核,但是需要借助SMO算法,可以直接对非线性可分的数据进行分类
还有一个就是检验一下我们数据的线性可分性
我选择第二个,先检验一下数据的线性可分性
这里我们采用scipy.spatial模块,里面有一个Qhull库,采用基于凸包(convex hull)的方法
核心思想是,如果两类的凸包不相交,则数据是线性可分的
这里的凸包不相交指的可以想象通过分别旋转两条直线,可以包围住两个不同的簇,那么这两个簇就是可以被一个二维超平面分割的
示例代码如下
import numpy as np from scipy.spatial import ConvexHull import matplotlib.pyplot as plt # 生成二维数据 np.random.seed(42) X_class0 = np.random.randn(10, 2) + [2, 2] # 类 0,中心在 (2, 2) X_class1 = np.random.randn(10, 2) + [-2, -2] # 类 1,中心在 (-2, -2) X = np.vstack([X_class0, X_class1]) y = np.array([0]*10 + [1]*10) # 计算两类的凸包 hull0 = ConvexHull(X_class0) hull1 = ConvexHull(X_class1) # 获取凸包的顶点 vertices0 = X_class0[hull0.vertices] vertices1 = X_class1[hull1.vertices] # 可视化 plt.scatter(X_class0[:, 0], X_class0[:, 1], c='blue', label='Class 0') plt.scatter(X_class1[:, 0], X_class1[:, 1], c='red', label='Class 1') plt.plot(np.append(vertices0[:, 0], vertices0[0, 0]), np.append(vertices0[:, 1], vertices0[0, 1]), 'b-') plt.plot(np.append(vertices1[:, 0], vertices1[0, 0]), np.append(vertices1[:, 1], vertices1[0, 1]), 'r-') plt.legend() plt.title("Convex Hulls of Two Classes") plt.show() # 检查凸包是否相交(二维情况下可以使用多边形相交算法) def is_convex_hull_separated(hull0, hull1, X0, X1): # 获取凸包的顶点坐标 poly0 = X0[hull0.vertices] poly1 = X1[hull1.vertices] # 检查点是否在另一个凸包内部(二维简单方法) for p in poly0: if point_in_hull(p, hull1, X1): return False for p in poly1: if point_in_hull(p, hull0, X0): return False return True def point_in_hull(point, hull, points): # 使用 Qhull 的 Delaunay 三角剖分判断点是否在凸包内 from scipy.spatial import Delaunay tri = Delaunay(points[hull.vertices]) return tri.find_simplex(point) >= 0 # 判断是否线性可分 separated = is_convex_hull_separated(hull0, hull1, X_class0, X_class1) print("数据是否线性可分:", separated)
可分输出True,否则输出False
经过检测我们的数据并不能被线性分割
那么我们还需要寻求别的方法,在我们的示例代码里,它是使用了一个叫线性探针(Linear Probe)的分类模型,对预训练的embeddings进行分类,它采用里一种基于监督学习的线性分类方法
输入的embeddings通常用预训练模型(如 BERT、ResNet 等)生成
代码中定义了一个很简单的神经网络LinearProbe 只有一层线性层
self.fc = nn.Linear(1536, 2)

浙公网安备 33010602011771号