对高维向量分类的一些见解

对高维度向量进行分类,首先我们会考虑使用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)

 

posted @ 2025-02-24 15:10  liujunxi  阅读(63)  评论(0)    收藏  举报