十几年前的降维可视化算法有这么好的效果?还得是Hinton。带你不使用任何现成库手敲t-SNE。

本文主要是介绍十几年前的降维可视化算法有这么好的效果?还得是Hinton。带你不使用任何现成库手敲t-SNE。,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

问题描述

依据Visualizing Data using t-SNE实现t-SNE算法,并对MNIST或者Olivetti数据集进行可视化训练。

有以下几点要求:

  • 不能使用现成的t-SNE库,例如sklearn等;
  • 可以使用支持矩阵、向量操作的库实现,例如numpy
  • 将数据降低至二维,同一类型的数据使用同一种颜色绘制散点图。

符号介绍

  • x i x_i xi:第 i i i个原始数据;
  • y i y_i yi:第 i i i个输出数据;
  • p j ∣ i p_{j\vert i} pji:输入的条件概率;
  • p i j p_{ij} pij:输入的联合概率;
  • q j ∣ i q_{j\vert i} qji:输出的条件概率;
  • q i j q_{ij} qij:输出的联合概率;

SNE算法介绍

SNE算法中使用高斯分布作为输入的条件分布,其条件概率定义如下:
p j ∣ i = e x p ( − ∥ x j − x i ∥ 2 / ( 2 σ i 2 ) ) ∑ k e x p ( − ∥ x k − x i ∥ 2 / ( 2 σ i 2 ) ) i ≠ j \begin{equation} p_{j\vert i} = \frac{exp(-\parallel x_j - x_i\parallel^2 / (2\sigma_i^2))}{\sum_{k} exp(-\parallel x_k - x_i\parallel^2 / (2\sigma_i^2))}\quad i\ne j \tag{1} \end{equation} pji=kexp(xkxi2/(2σi2))exp(xjxi2/(2σi2))i=j(1)
p i ∣ i p_{i\vert i} pii定义成0,在 ( 1 ) (1) (1)的公式中,两个向量越相似(欧式距离越近)则条件概率值越大。

SNE算法中同样对低维分布进行了定义,同样选择高斯分布作为其分布,不同的是由于低维分布是经过训练得出的,我们可以事先规定其方差为1使训练过程中拟合到方差为1的结果,这样能够一定程度上简化计算,其条件概率定义如下:
q j ∣ i = e x p ( − ∥ y j − y i ∥ 2 ) ∑ k e x p ( − ∥ y k − y i ∥ 2 ) i ≠ j q_{j\vert i} = \frac{exp(-\parallel y_j - y_i\parallel^2)}{\sum_{k} exp(-\parallel y_k - y_i\parallel^2)}\quad i\ne j qji=kexp(ykyi2)exp(yjyi2)i=j
同样地,我们将 q i ∣ i q_{i\vert i} qii定义成0

SNE需要做的就是尽可能是这两个分布相似以达到相似的数据映射到低维时依然相似,而K-L散度可以描述两个分布的相似程度,因此SNE通过K-L散度作为其损失函数进行训练:
C = ∑ i ∑ j p j ∣ i l o g 2 p j ∣ i q j ∣ i C=\sum_i\sum_j p_{j\vert i} log_2 \frac{p_{j\vert i}}{q_{j\vert i}} C=ijpjilog2qjipji

K-L散度描述了两个分布之间的相似程度,当其值越小时两个分布则越相似。

K-L散度的一大特点是对于 p j ∣ i p_{j\vert i} pji大的而 q j ∣ i q_{j\vert i} qji小的点特别敏感,其表现为在训练过程中会尽可能让输入距离近的点在降维后距离也尽可能的近。

非对称性的影响

SNE算法中的条件概率并不具有对称性,也就是 p i ∣ j = p j ∣ i p_{i\vert j} = p_{j\vert i} pij=pji q i ∣ j = q j ∣ i q_{i\vert j} = q_{j\vert i} qij=qji并不总是成立。

这样是会存在潜在问题的:对于 i i i而言,其认为 j j j与其很接近( p j ∣ i p_{j\vert i} pji比较大),在训练过程中更新 y i y_i yi时会让其与 y j y_j yj的距离更近,但是此时对于 j j j而言,由于对称性并不一定成立,其完全可能认为 i i i与其并不是很接近( p i ∣ j p_{i\vert j} pij比较小),在训练过程中更新 y j y_j yj时会让其与 y i y_i yi的距离更远,这就需要更多轮数的训练才能收敛。

因此使用对称的概率密度能够获得更好的效果。

对称的概率也就类似于联合概率密度。

t-SNE中对于高纬数据使用以下的联合分布:
p i j = p j ∣ i + p i ∣ j 2 \begin{equation} p_{ij} = \frac {p_{j\vert i} + p_{i\vert j}} {2} \tag{3} \end{equation} pij=2pji+pij(3)
这样即解决了对称性的问题,同时其能保证 ∑ j p i j > 1 2 n \sum_j p_{ij} > \frac {1} {2n} jpij>2n1,以免出现梯度消失的问题。

拥挤问题及解决方案

拥挤问题

对于一个高纬度,如果我们认为距离在 r r r内即为相似点的话,那么对于低纬度若仍认为距离在 r r r内为相似点的话,就很容易出现拥挤问题。

例如,将一个三维空间里一个半径为1的球中的点映射到二维空间里一个半径为1的圆上就很可能出现拥挤。

解决方案

根据拥挤问题产生的原因,我们只需要在衡量低维空间时,放宽距离限制。

例如,如果将一个三维空间里一个半径为1的球中的点映射到二维空间里一个半径为1的圆上很可能出现拥挤,我们不如尝试将其映射到二维空间里一个半径为2(甚至可以选择更大的半径)的圆上。

因为我们使用的是概率去衡量距离,上面的表述等价于当距离大于某个值时,若高维概率与低维概率相等,则低维概率对应的距离应该大于高维概率对应的距离。

在图像上的体现则为:从某个点开始低维概率密度函数图像一直在高维概率密度函数图像之上。

这样的概率密度函数也被称为重尾分布(这样的定义并不严谨),下图是不同自由度的t分布和高斯分布的概率密度函数:

t-SNE选择自由度为1t分布作为低维分布,其联合分布概率定义如下:
q i j = ( 1 + ∥ y i − y j ∥ 2 ) − 1 ∑ k ∑ l ≠ k ( 1 + ∥ y k − y l ∥ 2 ) − 1 i ≠ j \begin{equation} q_{ij} = \frac{(1 + \parallel y_i - y_j \parallel^2)^{-1}}{\sum_k \sum_{l \ne k}(1 + \parallel y_k - y_l \parallel^2)^{-1}}\quad i\ne j \tag{2} \end{equation} qij=kl=k(1+ykyl2)1(1+yiyj2)1i=j(2)
q i ∣ i q_{i\vert i} qii定义成0,在(2)中依然是两个向量越相似(欧式距离越近)则联合概率值越大,同时不难发现该函数关于 i , j i, j i,j对称。

K-L散度则改写成如下形式:
C = ∑ i ∑ j p i j l o g 2 p i j q i j C=\sum_i\sum_j p_{ij} log_2 \frac{p_{ij}}{q_{ij}} C=ijpijlog2qijpij

梯度的计算:
δ C δ y i = 4 ∑ j ( p i j − q i j ) ( y i − y j ) ( 1 + ∥ y i − y j ∥ 2 ) − 1 \begin{equation} \frac{\delta C}{\delta y_i} =4\sum_j (p_{ij} - q_{ij}) (y_i - y_j) (1 + \parallel y_i - y_j \parallel^2)^{-1} \tag{4} \end{equation} δyiδC=4j(pijqij)(yiyj)(1+yiyj2)1(4)

方差的确定

下面考虑如何计算 σ i \sigma_i σi

σ i \sigma_i σi可以通过困惑度来计算,困惑的表示如下:
P e r p ( i ) = 2 H ( i ) H ( i ) = − ∑ j p j ∣ i l o g 2 p j ∣ i \begin{aligned} &Perp(i) = 2^{H(i)} \\ &H(i) = -\sum_j p_{j\vert i}log_2 p_{j\vert i} \\ \end{aligned} Perp(i)=2H(i)H(i)=jpjilog2pji
对于某个给定的perplexity可以通过二分来确定 σ \sigma σ(二分 σ i \sigma_i σi使其计算的 P e r p ( i ) Perp(i) Perp(i)与给定的perplexity接近),论文中指出perplexity一般选取5-50,而且对于perplexity的选择并不敏感。需要注意的是perplexity的选择需要小于样本个数。

perplexity实际上表明了每个点会把自己最近的多少个点看成一组,如果perplexity越大,则其会倾向于将更多的点看成一组;如果perplexity越小,则会倾向于将更少的点看成一组。因此,perplexity的选择往往会随着数据量增大而增大。

t-SNE算法流程

根据上面的分析,我们只需要对 y i y_i yi进行初始化并进行梯度下降计算即可。
算法流程大致描述如下:

  • 计算 p i j p_{ij} pij(依据公式(3));
  • 初始化 y i y_i yi
  • 每轮迭代如下:
    • 根据当前的 y i y_i yi计算 q i j q_{ij} qij(依据公式(2));
    • 计算梯度(依据公式(4));
    • 进行梯度下降;

注意:原文中的伪代码有笔误,其中梯度下降部分的梯度前应该是减号,而不是加号,即: y i t + 1 = y i t − η δ C δ y i + α ( t ) ( y i t − y i t − 1 ) y_i^{t+1} = y_i^t - \eta \frac{\delta C}{\delta y_i} + \alpha(t) (y_i^t - y_i^{t - 1}) yit+1=yitηδyiδC+α(t)(yityit1)

一些优化

  1. 学习率的优化可以使用Learning an Adaptive Learning Rate Schedule中提到的自适应调整方法;
  2. 使用动量梯度下降算法替代梯度下降算法;
  3. eraly_exaggeration:在前50轮迭代中将p扩大4倍;
  4. 训练初期增加 L 2 L2 L2惩罚项;

使用随机游走改进t-SNE

使用t-SNE时,其每轮训练的复杂度是 O ( n 2 d ) O(n^2d) O(n2d),其中 n n n是样本数量, d d d是输入数据的维度,这对于训练样本数量很多的情况下便很难处理了。一种简单的方式是随机选择总样本的一部分进行计算,但是这样不能使用到未被选择的数据。

随机游走优化的t-SNE只选择一部分数据(这一部分数据也被称为地标)进行降维可视化,但是其会利用到未被选择到的数据。

首先给出算法流程:

  • 设输入样本个数为 N N N,确定最终降维可视化的样本个数 n n n,随机选择 n n n个输入样本作为地标,确定邻居个数 k k k
  • 对于 N N N个输入的每一个点 i i i,计算其 k k k个最近的邻居节点 j j j,建立 i − > j i->j i>j的单向边,边权为 e x p ( − ∥ x i − x j ∥ ) exp(-\parallel x_i-x_j\parallel) exp(xixj)
  • n n n个地标的每一个点作为起点进行随机游走(随机游走过程中边被选择的概率正比于边权,也就是两个点越近走这条边的概率则越大),当且仅当其达到另一个地标点时停止随机游走,我们定义 p j ∣ i p_{j\vert i} pji表示从地标 i i i开始在地标 j j j停止的概率;
  • 其余计算过程与原始t-SNE一致(将地标作为训练集);

从上述过程中我们不难发现我们值更改了 p j ∣ i p_{j\vert i} pji的计算步骤,下面解释为什么这么做。

在上面过程的随机游走过程中,如果 p j ∣ i > p k ∣ i p_{j\vert i}>p_{k\vert i} pji>pki说明 j , i j, i j,i之间有着更多的点或者 j , i j, i j,i之间的点距离更近。即使 j , k j, k j,k之间的距离与 j , i j, i j,i之间距离相等,我们依然会认为 j , i j, i j,i更有可能是同一类点。例如下图中的例子(选择 k = 3 k=3 k=3):

图中A, B, C距离几乎相等(甚至A, C距离更近),但是A, B中有着更多的近邻点连接,我们认为A, B是一类的点可能性更大。

上面算法的只是增加了一次全图的计算,其后续单轮训练的复杂度依然为 O ( n 2 d ) O(n^2d) O(n2d)

新的 p j ∣ i p_{j\vert i} pji的计算:只需要实际模拟多次随机游走计算即可。

问题分析及相关说明

问题分析

尽可能的贴近原文进行实现。其中的优化部分只有4没有实现(因为原文并没有指出前多少轮增加L2正则,而调参是一件麻烦的事情)。

因为C\C++更加贴近底层,可以使用C\C++编写t-SNE算法部分以获得更快的执行速度。

对于数据读取与处理部分可以使用torch直接实现。

对于数据可视化部分可以使用matplot绘制散点图,并使用imageio生成动态图。

超参数说明

1,2,3的实现全部依照原文,超参数的选择也尽可能的依照原文,下面是超参数说明:

  • learningRate:学习率,与原文一致,初始100后续按照论文中采用的优化方法进行更新;
  • epoch:迭代次数,与原文一致,选择1000
  • perp/perplexity:困惑度,与原文一致,选择40
  • alpha/momentum:动量系数,与原文设置一致,前250轮选择0.5,后续选择0.8
  • exaggeration:夸大值,与原文一致,前50轮设置为4,后续设置为1
  • eps:二分时的精度,原文并未给出,这里设置为1e-7

环境说明

  • 编译t-SNE部分需要使用cmake 3.0.0及以上的版本;
  • 编译C++代码的编译器需要支持C++17
  • 数据下载、解析以及可视化部分通过Python实现;
  • Python 3.8及以上被要求,对应版本的numpy, imageio, matplotlib, torch, sklearn被需要;
    • numpy一些基础的数据输出;
    • imageio用于绘制GIF图片;
    • matplotlib用于绘制每轮迭代的散点图;
    • torch用于下载、解析数据。
    • sklearn用于压缩原始数据;

数据集说明

与原论文一致选择用MNIST作为数据集,随机从其中抽取6000张作为数据集。

注意1:由于每轮训练的复杂度为 O ( n 2 d ) O(n^2d) O(n2d)其中 n n n是样本个数, d d d是输入数据的维度,因此在论文中训练之前使用PCA将原始数据压缩为30维。由于没有GPU的支持,我们依然选择在训练开始之前使用sklearn提供的PCA将数据压缩成30维。压缩后整个算法对于数据集需要做约1e12次计算,按照现在计算机单核每秒做1e9次运算计算的话整个计算部分的完成大约需要15分钟。实测确系15分钟左右。

注意2:在相关矩阵运算部分,容易使用多线程解决,或者使用相关的多线程矩阵计算库实现。这样对于一个八核心的计算机,理论能将运算时间降低到2~3分钟左右。但是由于C++并没有官方的包管理系统,为了保证代码的可复现性,此处不使用多线程加速。15分钟与使用GPU都需要几小时的深度网络训练时长相比实在是可以接受。

代码运行说明

如果平台是UNIK-like,在保证安装上述的环境后,直接通过bash start.sh即可执行,start.sh的流程描述如下:

  • 首先在代码根目录创建build目录,并在build目录下通过cmake构建tsne可执行文件。
  • 接着通过prepareData.py在代码根目录下将MNIST数据下载至data目录下,并随机读取6000个数据通过PCA进行压缩,将压缩后的数据写入data/data.in(特征)与data/label.txt(标签)中。
  • 构建完成后,会在代码根目录下运行tsne(即在code目录下通过build/tsne运行)以确保能从相对路径中读取到数据(该过程时间较长且没有提示请耐心等待,可以通过查看data/data.out文件的大小是否发生变化来判断程序是否正常执行,程序完成时data/data.out会有六百万行的数据,可以通过查看当前的行数确定程序执行进度)。
  • tsne执行完成后,会继续执行createFig.py读取tsnedata/data.out中写入的1000轮低维特征y,并绘制1000张散点图放置于代码根目录下的fig目录中。散点图绘制完成后,将1000张图片组成一张GIF图片(result.gif)放置在代码根目录下。

若是Windows平台,只需要按照上述流程通过cmake构建tsne,手动执行prepareData.py,接着保证工作目录在代码根目录的情况下执行tsne二进制文件,等待执行完成后执行createDate.py即可。

代码优化说明

  1. 在公式中不乏具有对称性的公式,因此可以只计算矩阵中一半的元素,例如下面是计算输入联合概率p的代码:
for (int i = 0; i < n; i++) {for (int j = i + 1; j < n; j++) {p[i][j] = p[j][i] = max(1e-100, (output[i][j] + output[j][i]) / (2 * n)) * EXAGGERATION;}
}
  1. 对矩阵进行操作是尽可能的避免拷贝矩阵,例如下面是重载+=符号的代码,其直接在原矩阵上进行操作:
template<class T>
void operator+=(vector<T> &a, const vector<T> &b)
{for (int i = 0; i < b.size(); i++) { a[i] += b[i]; }
}
  1. 所有需要用到 σ i \sigma_i σi的地方都是以 1 2 σ i 2 \frac{1}{2\sigma_i^2} 2σi21形式出现,因此我们可以不计算 σ i \sigma_i σi,而是在二分时直接计算 1 2 σ i 2 \frac{1}{2\sigma_i^2} 2σi21,后续所有涉及 σ i \sigma_i σi第地方均进行替代,例如下面是计算输入条件概率的部分代码(其中(*doubleSigmaSquareInverse)[i]表示 1 2 σ i 2 \frac{1}{2\sigma_i^2} 2σi21):
double piSum = 0;
for (int i = 0; i < x.size(); i++) {piSum = 0;for (int j = 0; j < x.size(); j++) {if (j == i) { continue; }output[i][j] = exp(-disSquare[i][j] * (*doubleSigmaSquareInverse)[i]);piSum += output[i][j];}for (int j = 0; j < x.size(); j++) { output[i][j] /= piSum; }
}
  1. 对于多次使用到的数据只计算一次,例如计算输入的条件概率中会使用到两个数据点之间的二范数的平方:
if (disSquare.empty()) {disSquare.resize(x.size(), vector<double>(x.size()));double normSquare = 0;for (int i = 0; i < x.size(); i++) {for (int j = i + 1; j < x.size(); j++) {normSquare = 0;assert(x[i].size() == x[j].size());for (int k = 0; k < x[i].size(); k++) { normSquare += pow(x[i][k] - x[j][k], 2); }disSquare[i][j] = disSquare[j][i] = normSquare;}}
}

代码分析

我们将t-SNE的实现放置在命名空间TSNE中,使用之前需要对以下数据进行初始化:

// n : the number of samples
// m : the dimension of every sample
// outputDimension : the dimension of result y
// epoch : the num of iterations
// perp : perplexity, usually chosen in [5, 50], and it must be less than n
// x : samples, x[i] means the i-th sample
int n, m, outputDimension, epoch;
double perp;
vector<vector<double>> x

初始化完成后即可执行run

// run t-SNE
void run()
{// initialize matrixs and generate initial yinit();// compute 1 / (2 sigma ^ 2)getDoubleSigmaSquareInverse();// compute joint probability pijauto &&output = perpNet.p(x);for (int i = 0; i < n; i++) {for (int j = i + 1; j < n; j++) {p[i][j] = p[j][i] = max(1e-100, (output[i][j] + output[j][i]) / (2 * n)) * EXAGGERATION;}}// gradient descent with epoch and momentum scalegradientDescent(50, 0.5); p /= EXAGGERATION;gradientDescent(200, 0.5);gradientDescent(epoch - 250, 0.8);
}

getDoubleSigmaSquareInverse通过二分确定 1 2 σ 2 \frac {1} {2 \sigma^2} 2σ21

while (r[0] - l[0] >= eps) {for (int i = 0; i < n; i++) { doubleSigmaSquareInverse[i] = (l[i] + r[i]) / 2; }// get Perp with now 1 / (2 sigma^2)auto &&perp = perpNet(x);for (int i = 0; i < n; i++) {if (perp[i] < TSNE::perp) { r[i] = doubleSigmaSquareInverse[i]; }else { l[i] = doubleSigmaSquareInverse[i]; }}
}

perpNet是一个PerpNet类的一个对象,其内部拥有PLayer, HLayer, PerpLayer三个内部类的对象p, h, perp,分别用于计算对应的公式,通过重载()实现函数式调用,以PerpLayeroperator()代码为例:

vector<double> & operator()(const vector<double> x)
{if (output.empty()) { output.resize(x.size()); }for (int i = 0; i < x.size(); i++) { output[i] = pow(2, x[i]); }return output;
}

下面给出gradientDescent部分的部分代码:

// calculate joint probability of current result y
q = &qLayer(y);
// calculate gradient
getGradient();
fill(ymean.begin(), ymean.end(), 0);
for (int i = 0; i < n; i++) {for (int d = 0; d < outputDimension; d++) {// update learning rate adaptively// we don't update learningRate variable; we use learningRate * gain[i][d] as the new learning rategain[i][d] = max(sign(g[i][d]) == sign(lastY[i][d]) ? gain[i][d] * 0.8 : gain[i][d] + 0.2, 0.01);// get stepstep = momentum * (y[i][d] - lastY[i][d]) - learningRate * gain[i][d] * g[i][d];lastY[i][d] = y[i][d];y[i][d] += step;// for centralizationymean[d] += y[i][d];}
}
for (int d = 0; d < outputDimension; d++) { ymean[d] /= n; }
// centralization
for (int i = 0; i < n; i++) {for (int d = 0; d < outputDimension; d++) {y[i][d] -= ymean[d];}
}
// output y after every iteration
cout << y;

梯度计算部分的代码:

for (int i = 0; i < n; i++) {fill(g[i].begin(), g[i].end(), 0);for (int j = 0; j < n; j++) {// this scale is to prevent re-calculationscale = 4 * (p[i][j] - (*q)[i][j]) * qLayer.disSquarePlusInverse[i][j];for (int d = 0; d < outputDimension; d++) { g[i][d] += scale * (y[i][d] - y[j][d]); }}
}

详细的代码可以查看code/tsne.cpp

注意:由于写报告时,没有增加随机游走的实现,所以上述的代码分析是按照没有随机游走代码时的版本进行分析,而后续实现随机游走后合并了一些代码以方便复用,可能结构上会有所不同,但是非随机游走部分执行与上述分析过程一致。

实验结果

我同样实现了随机游走版本的t-SNE由于不是作业的要求没有给出代码分析部分,这里只给出某次实验的结果。

某次随机实验的结果如下(可通过右键在新标签页打开以重新播放):

  • 普通版本:由于图片不能超过5MB,所以你只能通过连接自行查看:result.gif
  • 随机游走版本:由于图片不能超过5MB,所以你只能通过连接自行查看:result_random_walk.gif

提供最终结果的静态图(左侧普通版本,右侧随机游走版本):

补充一点运行时间(单核运行):
普通版本:

time of getDisSquare: 0.406387 (s)
time of randomSelection: 0.0014 (s)
time of getDoubleSigmaSquareInverse: 34.677 (s)
time of calculating joint probability: 35.357 (s)
time of run t-SNE: 736.599 (s)

随机游走:

time of getDisSquare: 71.0614 (s)
time of randomSelection: 0.007075 (s)
time of getKNearestNeighbor for all nodes: 7.71446 (s)
time of calculating joint probability with random walk: 100.894 (s)
time of run t-SNE: 903.163 (s)

引用

Learning an Adaptive Learning Rate Schedule

Visualizing Data using t-SNE

最后的话

代码仓库:Kaiser-Yang/t-SNE。

后续如果有时间,我可能会自己去实现多线程的矩阵乘法(别问我为什么要自己实现多线程矩阵乘法,Just for fun)试着对比一下多线程与单线程的运行时间。

这篇关于十几年前的降维可视化算法有这么好的效果?还得是Hinton。带你不使用任何现成库手敲t-SNE。的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/930556

相关文章

使用动画切换Activity

在startActivity()方法调用之后调用overridePendingTransition(int enterAnim, int exitAnim)方法 enterAnim 进入的动画资源id exitAnim 退出的动画资源id enterAnim .xml:透明度渐变动画 <?xml version="1.0" encoding="utf-8"?> <set xmlns:an

ThinkPHP5在PHP7以上使用QueryList4, ThinkCMF在PHP5中使用QueryList3教程

QueryList 是一款用于网页采集爬虫的框架,官方最新版本为QueryList4,QueryList4版本只能在PHP7以上使用; 在PHP7以上环境中,如何在ThinkPHP5中使用QueryList4 ,开发者也给出了教程。对于PHP5环境,只能使用QueryList3,官网给出的ThinkPHP中使用QueryList3的教程,是基于ThinkPHP3.2.3。目前我们用的基本为

PHP使用imap读取邮件内容,2018可用

本文以QQ邮箱为例,已验证可以成功读取邮件内容,这里用到一个类,如下。 <?php/**** @package Application* @author songhui@diaojia.com.cn* @version Email.php 2017-07-16 09:52:16Z*/namespace Email;class Email {/*** @var re

R语言数据探索和分析7-使用随机森林模型对中国GDP及其影响因素分析

一、研究背景和意义 国内生产总值(GDP)是宏观经济领域中最为关注的经济统计数据之一,它反映了一个国家或地区在一定时期内所创造的所有最终商品和服务的总价值。GDP的增长率不仅仅是一个国家经济健康状况的关键指标,还直接关系到国家的社会稳定和人民生活水平。因此,对GDP增长的因素进行深入研究具有极其重要的经济和政策意义。..... 二、理论部分 随机森林(Random Forest)是一种经典的

flink sql使用注意事项

可以通过create 语句控制字段个数和顺序 , 决定后面insert into的select语句中的字段顺序受影响,所以insert into的select只能跟create定义的顺序一样,不能改别名 例如create table mysink (id varchar ,name varchar) with(...)​不可以通过insert 或select语句控制输出个数 insert into

jenkins的使用,部署javaweb项目

添加部署的服务器 创建项目 进入配置 构建 构建后的操作   #!/bin/sh server_home="/home/mop/tomcat/apache-tomcat-8.0.47" sh $server_home/bin/shutdown.sh # Kill all remaining processes pidList=$(ps aux | grep $

使用 ORPO 微调 Llama 3

原文地址:https://towardsdatascience.com/fine-tune-llama-3-with-orpo-56cfab2f9ada 更便宜、更快的统一微调技术 2024 年 4 月 19 日 ORPO 是一种新的令人兴奋的微调技术,它将传统的监督微调和偏好校准阶段合并为一个过程。这减少了训练所需的计算资源和时间。此外,经验结果表明,在各种模型大小和基准上,ORPO 都优

tarjan算法介绍与分析

tarjan算法是一个求取有向图的所有强连通分量的算法,它是以算法提出者Robert Tarjan的名字来命名的。提出此算法的Robert Tarjan是普林斯顿大学的教授,同时他也是1986年的图灵奖获得者(图灵奖是计算机领域的最高荣誉,和物理、化学领域的诺贝尔奖是一个层次的奖项)。   在详细讲述该算法之前,我们需要明确几个概念(注意该算法的研究对象是有向图,无向图没有强连通等概念)。

手摇算法及其应用

在技术类面试中,若能用手摇算法解决面试官提出的问题,那么我们在面试官的眼中将会提升一个档次,因此学习这种简单算法的性价比是相当高的。首先,我们简单介绍一下手摇算法。         手摇算法也叫三次反转算法,我们可以通过一个简单例子引入该算法。若要将一个字符串abcdef变成defabc,简单的方法是用一个辅助数组来做,但是这种方式有O(n)的空间开销,因此在实际面试中,这种方

AIGC笔记--Diffuser的基本使用

目录 1--加载模型 2--半精度推理 3--固定随机种子 4--更改扩散步数 5--设置negative_prompt 1--加载模型         以下代码使用 from_pretrained() 来加载预训练模型,使用参数cache_dir来指定下载模型的存储地址; from diffusers import DiffusionPipeline, EulerD