{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d7054c11",
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings(\"ignore\")\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "import sys\n",
    "sys.path += ['utils/']  \n",
    "\n",
    "from node_embedding import *\n",
    "from dcsbm import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "645aa56b",
   "metadata": {},
   "outputs": [],
   "source": [
    "def CreateEmbedding_exact(Pv, dim = 128, p0 = None, n_epochs = 30, sum_partials = False, η = .8, verbose = True):\n",
    "\n",
    "    # sample size\n",
    "    n = Pv[0].shape[0]\n",
    "\n",
    "    eps = η/n_epochs\n",
    "    \n",
    "    # initialize the weights\n",
    "    X = np.random.uniform(-1,1, (n, dim))\n",
    "    X = normalize(X, norm = 'l2', axis = 1)\n",
    "\n",
    "    for epoch in range(n_epochs):\n",
    "        \n",
    "        # print update\n",
    "        if verbose:\n",
    "            print(\"[%-25s] %d%%\" % ('='*(int((epoch+1)  /n_epochs*25)-1) + '>', (epoch+1)/(n_epochs)*100), end = '\\r')\n",
    "\n",
    "        # compute the gradient\n",
    "        GRAD = _computeGrad_exact(Pv, X, np.ones(n)/n, sum_partials)\n",
    "        \n",
    "        # update the weights\n",
    "        D = diags((GRAD * X).sum(axis = 1))\n",
    "        GRAD = GRAD - D@X\n",
    "        GRAD = normalize(GRAD, norm = 'l2', axis = 1)\n",
    "        X = np.sqrt(1-η**2)*X - η*GRAD\n",
    "\n",
    "        η = η - eps\n",
    "            \n",
    "    return X\n",
    "\n",
    "\n",
    "def _computeGrad_exact(Pv, X, p0, sum_partials):\n",
    "\n",
    "    n, dim = np.shape(X)\n",
    "\n",
    "    # \"energy\" part of the gradient\n",
    "    if sum_partials:\n",
    "        U, Ut = _computeUsum(Pv, X, X)\n",
    "\n",
    "    else:\n",
    "        U, Ut = _computeUprod(Pv, X, X)\n",
    "\n",
    "\n",
    "    M = np.exp(X@X.T)\n",
    "    z = M@np.ones(n)\n",
    "    Z_1 = np.diag(z**(-1))\n",
    "#     Zgrad = M@(Z_1@X) #+ Z_1@(M@X)\n",
    "    Zgrad = Z_1@(M@X)\n",
    "\n",
    "    P0 = np.reshape(p0, (n,1))\n",
    "    u = np.reshape(np.ones(n), (n,1))\n",
    "    E = np.sum(p0)\n",
    "\n",
    "    return -(U + Ut) + (u@(P0.T@X))/E + P0@(u.T@X)/E + Zgrad\n",
    "\n",
    "\n",
    "\n",
    "def _computeUsum(Pv, X, Y):\n",
    "    '''\n",
    "    This function computes the \"energetic\" contribution of the gradient in the case in which P is written as a sum of products of matrices\n",
    "\n",
    "    Use: U, Ut = _computeUsum(Pv, X, Y)\n",
    "\n",
    "    Inputs:\n",
    "        * Pv (list sparse array): The matrix P is given by a sum of the powers of the only elements contained in Pv.\n",
    "        * X (array): input weights with respect to which the gradient is computed\n",
    "        * Y (array): output weights with respect to which the gradient is computed\n",
    "       \n",
    "    Output:\n",
    "        * U (array): first contribution to the gradient\n",
    "        * Ut (array): second contribution to the gradient (coming from the transpose)\n",
    "\n",
    "    '''\n",
    "\n",
    "    # \"standard\" contribution U\n",
    "    U = [Pv[0]@Y]\n",
    "\n",
    "    for i in range(1, len(Pv)):\n",
    "        U.append(Pv[i]@U[-1])\n",
    "\n",
    "\n",
    "    # transpose contribution Ut\n",
    "    Pv = Pv[::-1]\n",
    "    Ut = [X.T@Pv[0]]\n",
    "\n",
    "    for i in range(1, len(Pv)):\n",
    "        Ut.append(Ut[-1]@Pv[i])\n",
    "\n",
    "    U = np.sum(U, axis = 0)/len(Pv)\n",
    "    Ut = np.sum(Ut, axis = 0)/len(Pv)\n",
    "\n",
    "    return U, Ut.T\n",
    "\n",
    "\n",
    "def _computeUprod(Pv, X, Y):\n",
    "    '''\n",
    "    This function computes the \"energetic\" contribution of the gradient in the case in which P is written as a product of matrices\n",
    "\n",
    "    Use: U, Ut = _computeUprod(Pv, X, Y)\n",
    "\n",
    "    Inputs:\n",
    "        * Pv (list sparse array): The matrix P is given by a sum of the powers of the only elements contained in Pv.\n",
    "        * X (array): input weights with respect to which the gradient is computed\n",
    "        * Y (array): output weights with respect to which the gradient is computed\n",
    "\n",
    "    Output:\n",
    "        * U (array): first contribution to the gradient\n",
    "        * Ut (array): second contribution to the gradient (coming from the transpose)\n",
    "\n",
    "    '''\n",
    "\n",
    "    # \"standard\" contribution U\n",
    "    U = Pv[0]@Y\n",
    "\n",
    "    for i in range(1, len(Pv)):\n",
    "        U = Pv[i]@U\n",
    "\n",
    "\n",
    "    # transpose contribution Ut\n",
    "    Pv = Pv[::-1]\n",
    "    Ut = X.T@Pv[0]\n",
    "\n",
    "    for i in range(1, len(Pv)):\n",
    "        Ut = Ut@Pv[i]\n",
    "\n",
    "    return U, Ut.T\n",
    "\n",
    "\n",
    "def NodeEmbedding_exact(A, dim, n_epochs = 30, walk_length = 5, verbose = True, η = 0.5):\n",
    "   \n",
    "    \n",
    "    # check that all entries of A are positive\n",
    "    if not (A[A.nonzero()] > 0).all():\n",
    "        raise DeprecationWarning(\"The weighted adjacency matrix has negative entries\")\n",
    "    \n",
    "    # create the probability matrix\n",
    "    n = A.shape[0]\n",
    "    d = A@np.ones(n)\n",
    "    D_1 = diags(d**(-1))\n",
    "    P = D_1.dot(A)\n",
    " \n",
    "    # Eder\n",
    "    embedding = CreateEmbedding_exact([P for i in range(walk_length)], dim = dim, n_epochs = n_epochs, \n",
    "                        sum_partials = True, verbose = verbose, η = η)\n",
    "    \n",
    "    return embedding\n",
    "\n",
    "\n",
    "def _computeGrad(Pv, X, Y, ℓ, p0, sum_partials, sym):\n",
    "    '''\n",
    "    This function computes the gradient of the loss function\n",
    "\n",
    "    Use: GRADX, GRADY = _computeGrad(Pv, X, Y, ℓ, p0, sum_partials)\n",
    "\n",
    "    Inputs:\n",
    "        * Pv (list sparse array): the P matrix is provided by the product of all the elements appearing in Pv. Note the use of the `walk_length` parameter in the case in which P is the sum of powers of a given matrix.\n",
    "        * X (array): input weights with respect to which the gradient is computed\n",
    "        * Y (array): output wieghts with respect to which the gradient is computed\n",
    "        * ℓ (array): label assignment to create the mixture of Gaussians\n",
    "        * p0 (array): array of size n that specifies the \"null model\" probability\n",
    "        * sum_partials (bool): if True, P is written as the sum of powers\n",
    "        \n",
    "    Output:\n",
    "        * GRADX, GRADY (array): gradient of the cost function computed in X and Y\n",
    "\n",
    "    '''\n",
    "\n",
    "    n, dim = np.shape(X)\n",
    "    m, _ = np.shape(Y)\n",
    "    k = len(np.unique(ℓ))\n",
    "\n",
    "    # compute the parameters\n",
    "    π = np.array([np.sum(ℓ == a)/n for a in range(k)])\n",
    "    μ = np.stack([np.mean(Y[ℓ == a], axis = 0) for a in range(k)])\n",
    "\n",
    "    # Z part of the gradient\n",
    "    Ω = [np.cov((Y[ℓ == a]).T) for a in range(k)]\n",
    "    Z = np.exp(np.array([X@μ[a] + 0.5*(X * X@Ω[a])@np.ones(dim) for a in range(k)]).T)@np.diag(π)\n",
    "    Zgrad = diags(1/(Z@np.ones(k)))@(Z@μ + np.sum([diags(Z[:,a])@X@Ω[a] for a in range(k)], axis = 0))\n",
    "\n",
    "\n",
    "    # \"energy\" part of the gradient\n",
    "    if sum_partials:\n",
    "        U, Ut = _computeUsum(Pv, X, Y)\n",
    "\n",
    "    else:\n",
    "        U, Ut = _computeUprod(Pv, X, Y)\n",
    "\n",
    "    P0 = np.reshape(p0, (m,1))\n",
    "    u = np.reshape(np.ones(n), (n,1))\n",
    "    E = np.sum(p0)\n",
    "\n",
    "    return -U + Zgrad + (u@(P0.T@Y))/E + u@(u.T@X)/n*(1-sym), -Ut + P0@(u.T@X)/E + P0@(P0.T@Y)/E*(1-sym)\n",
    "    \n",
    "\n",
    "def _Clustering(X, k):\n",
    "    '''This function generates the label assignment for the Gaussian approximation given the embedding\n",
    "\n",
    "    Use: ℓ = _Clustering(X, k)\n",
    "\n",
    "    Inputs:\n",
    "        * X (array): embedding matrix of size (n x dim)\n",
    "        * k (int): number of clusters to look for\n",
    "\n",
    "    Output:\n",
    "        * ℓ (array): entry-wise label assignment into one of the k clasess.\n",
    "\n",
    "    '''\n",
    "\n",
    "    n, dim = np.shape(X)\n",
    "\n",
    "    kmeans = faiss.Kmeans(dim, k, verbose = False, spherical = True)\n",
    "    kmeans.train(np.ascontiguousarray(X).astype('float32'))\n",
    "    _, ℓ = kmeans.assign(np.ascontiguousarray(X).astype('float32'))\n",
    "\n",
    "    return ℓ\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "d3543d39",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i: 10/10; n: 10000\r"
     ]
    }
   ],
   "source": [
    "from time import time\n",
    "\n",
    "nv = np.linspace(500, 10000, 20).astype(int)\n",
    "n_epochs = 25\n",
    "n_iter = 10\n",
    "\n",
    "tv, tev = [[] for i in range(n_iter)], [[] for i in range(n_iter)]\n",
    "\n",
    "\n",
    "for i in range(n_iter):\n",
    "    for n in nv:\n",
    "        print(f'i: {i+1}/{n_iter}; n: {n}', end = '\\r')\n",
    "        \n",
    "#         θ = np.random.negative_binomial(2, 0.4, size = (n,1))\n",
    "        θ = np.random.negative_binomial(3, 0.3, size = (n,1))\n",
    "        P_ = np.minimum(θ@θ.T/n, 1)\n",
    "\n",
    "        dim = 32\n",
    "        A = np.random.binomial(1, P_)\n",
    "        A = np.triu(A)\n",
    "        A = A + A.T\n",
    "        A = csr_matrix(A)\n",
    "\n",
    "        t0 = time()\n",
    "        res = NodeEmbedding(A, dim, n_epochs = n_epochs, walk_length = 3, k = 1, verbose = False, η = 1., sym = True)\n",
    "        t = time() - t0\n",
    "\n",
    "        t0 = time()\n",
    "        res_exact = NodeEmbedding_exact(A, dim, n_epochs = n_epochs, walk_length = 3, verbose = False, η = 1.)\n",
    "        te = time() - t0\n",
    "\n",
    "        tv[i].append(t)\n",
    "        tev[i].append(te)\n",
    "        \n",
    "        del A\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "6133cf82",
   "metadata": {},
   "outputs": [],
   "source": [
    "tv, tev, nv = np.array(tv), np.array(tev), np.array(nv)\n",
    "np.savetxt('saved_files/test_against_full/tv.dat', tv)\n",
    "np.savetxt('saved_files/test_against_full/tev.dat', tev)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "96c02023",
   "metadata": {},
   "outputs": [],
   "source": [
    "tv = np.loadtxt('saved_files/test_against_full/tv.dat')\n",
    "tev = np.loadtxt('saved_files/test_against_full/tev.dat')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "962fb44e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import networkx as nx\n",
    "\n",
    "n = 3000\n",
    "θ = np.random.negative_binomial(3, 0.3, size = (n,1))\n",
    "P_ = np.minimum(θ@θ.T/n, 1)\n",
    "A = np.random.binomial(1, P_)\n",
    "A = np.triu(A)\n",
    "A = (A + A.T)\n",
    "A = csr_matrix(A).sign()\n",
    "G = nx.from_scipy_sparse_matrix(A)\n",
    "\n",
    "\n",
    "d = A@np.ones(n)\n",
    "D_1 = diags(d**(-1))\n",
    "P = D_1@A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "23153f67",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[========================>] 100%\r"
     ]
    }
   ],
   "source": [
    "# sample size\n",
    "\n",
    "dim = 32\n",
    "η0 = .7\n",
    "η = copy(η0)\n",
    "n_epochs = 150\n",
    "eps = η/n_epochs\n",
    "\n",
    "# initialize the weights\n",
    "X0 = np.random.uniform(-1,1, (n, dim))\n",
    "X0 = normalize(X0, norm = 'l2', axis = 1)\n",
    "\n",
    "X = copy(X0)\n",
    "X_exact = []\n",
    "\n",
    "for epoch in range(n_epochs):\n",
    "\n",
    "    # print update\n",
    "    print(\"[%-25s] %d%%\" % ('='*(int((epoch+1)  /n_epochs*25)-1) + '>', (epoch+1)/(n_epochs)*100), end = '\\r')\n",
    "\n",
    "    # compute the gradient\n",
    "    GRAD = _computeGrad_exact([P], X, np.ones(n)/n, False)\n",
    "\n",
    "    # update the weights\n",
    "    D = diags((GRAD * X).sum(axis = 1))\n",
    "    GRAD = GRAD - D@X\n",
    "    GRAD = normalize(GRAD, norm = 'l2', axis = 1)\n",
    "    X = np.sqrt(1-η**2)*X - η*GRAD\n",
    "\n",
    "    η = η - eps\n",
    "    \n",
    "    X_exact.append(X)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "028edfec",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[========================>] 100%\r"
     ]
    }
   ],
   "source": [
    "X = copy(X0)\n",
    "η = copy(η0)\n",
    "\n",
    "for epoch in range(n_epochs):\n",
    "\n",
    "    # print update\n",
    "    print(\"[%-25s] %d%%\" % ('='*(int((epoch+1)  /n_epochs*25)-1) + '>', (epoch+1)/(n_epochs)*100), end = '\\r')\n",
    "\n",
    "    # compute the gradient\n",
    "    GRADX, GRADY = _computeGrad([P], X, X, np.zeros(n).astype(int), np.ones(n)/n, False, True)\n",
    "    GRAD = GRADX + GRADY\n",
    "    \n",
    "    # update the weights\n",
    "    D = diags((GRAD * X).sum(axis = 1))\n",
    "    GRAD = GRAD - D@X\n",
    "    GRAD = normalize(GRAD, norm = 'l2', axis = 1)\n",
    "    X = np.sqrt(1-η**2)*X - η*GRAD\n",
    "\n",
    "    η = η - eps\n",
    "    \n",
    "X_fin = copy(X)\n",
    "\n",
    "max_kappa = 9\n",
    "X_approx =  [[] for i in range(1,max_kappa)]\n",
    "    \n",
    "for k in range(1,max_kappa):\n",
    "    η = copy(η0)\n",
    "    ℓ = _Clustering(X_fin, k)\n",
    "    X = copy(X0)\n",
    "\n",
    "    for epoch in range(n_epochs):\n",
    "\n",
    "        # print update\n",
    "        print(\"[%-25s] %d%%\" % ('='*(int((epoch+1)  /n_epochs*25)-1) + '>', (epoch+1)/(n_epochs)*100), end = '\\r')\n",
    "\n",
    "        # compute the gradient\n",
    "        GRADX, GRADY = _computeGrad([P], X, X, ℓ, np.ones(n)/n, False, True)\n",
    "        GRAD = GRADX + GRADY\n",
    "\n",
    "        # update the weights\n",
    "        D = diags((GRAD * X).sum(axis = 1))\n",
    "        GRAD = GRAD - D@X\n",
    "        GRAD = normalize(GRAD, norm = 'l2', axis = 1)\n",
    "        X = np.sqrt(1-η**2)*X - η*GRAD\n",
    "\n",
    "        η = η - eps\n",
    "\n",
    "        X_approx[k-1].append(X)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "0acd7af6",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 62432 characters]\n",
      "text/plain": [
       "<Figure size 1008x288 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots(1, 3, figsize = (14, 4))\n",
    "\n",
    "\n",
    "nv = np.linspace(500, 10000, 20).astype(int)\n",
    "m = np.mean(tv, axis = 0)\n",
    "s = np.std(tv, axis = 0)\n",
    "\n",
    "α = np.mean(m*nv)/np.mean(nv**2)\n",
    "ax[1].plot(nv, α*nv, color = '#e63946', linewidth = 5, linestyle = '-', label = r'$y \\propto x$', alpha = 0.7)\n",
    "\n",
    "ax[1].plot(nv, m, color = '#264653', linewidth = 0, marker = 'o')\n",
    "ax[1].fill_between(nv, m-s, m+s, alpha = 0.3, color = '#2a9d8f')\n",
    "\n",
    "\n",
    "ax[1].tick_params(axis='both', which='major', labelsize = 16)\n",
    "ax[1].tick_params(axis = 'x', rotation = 30)\n",
    "ax[1].set_xlabel('n', fontsize = 24)\n",
    "ax[1].set_ylabel('comp. time (s)', fontsize = 24)\n",
    "ax[1].legend(fontsize = 20, loc = 2)\n",
    "ax[1].set_title('EDRep', fontsize = 26)\n",
    "\n",
    "###############################\n",
    "m = np.mean(tev, axis = 0)\n",
    "s = np.std(tev, axis = 0)\n",
    "\n",
    "α = np.mean(m*nv**2)/np.mean(nv**4)\n",
    "ax[0].plot(nv, α*nv**2, color = '#e63946', linewidth = 5, linestyle = '-', label = r'$y \\propto x^2$', alpha = 0.7)\n",
    "ax[0].plot(nv, m, color = '#264653', linewidth = 0, marker = 'o')\n",
    "ax[0].fill_between(nv, m-s, m+s, alpha = 0.3, color = '#2a9d8f')\n",
    "\n",
    "ax[0].tick_params(axis='both', which='major', labelsize = 16)\n",
    "ax[0].tick_params(axis = 'x', rotation = 30)\n",
    "ax[0].set_xlabel('n', fontsize = 24)\n",
    "ax[0].set_ylabel('comp. time (s)', fontsize = 24)\n",
    "ax[0].legend(fontsize = 20, loc = 2)\n",
    "ax[0].set_title('Full softmax', fontsize = 26)\n",
    "\n",
    "\n",
    "#################################\n",
    "cmap = plt.cm.get_cmap('rainbow', len(X_approx))\n",
    "\n",
    "for i in range(len(X_approx)):\n",
    "#     dist = np.array([(np.linalg.norm(K.T@K)**2 + np.linalg.norm(Q.T@Q)**2 - 2*np.linalg.norm(K.T@Q)**2) for K, Q in zip(X_exact, X_approx[i])])\n",
    "    dist = np.array([np.linalg.norm(np.abs(K - Q)) for K, Q in zip(X_exact, X_approx[i])])\n",
    "#     dist_rel = np.array([np.linalg.norm(K) for K in X_exact])\n",
    "    ax[2].plot(np.log10(dist/np.sqrt(n)), label = r'$\\kappa = $' + str(i+1), color = cmap(i))\n",
    "    \n",
    "# ax[2].legend(ncols = 2, fontsize = 12)\n",
    "ax[2].tick_params(axis='both', which='major', labelsize = 16)\n",
    "ax[2].tick_params(axis = 'x', rotation = 30)\n",
    "ax[2].set_xlabel('epoch', fontsize = 24)\n",
    "ax[2].set_ylabel(r'${\\rm log}_{10}(C_t)$', fontsize = 24)\n",
    "# ax[2].set_title('Embedding difference', fontsize = 26)\n",
    "\n",
    "\n",
    "plt.tight_layout()\n",
    "# plt.savefig('../../paper/v6_NIPS_rebuttal/Figures/new.pdf', dpi = 400, bbox_inches = 'tight')\n",
    "plt.show();"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "1bd9d133",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0.12135379,  0.31618733,  0.60007651,  1.43094766,  2.01860771,\n",
       "        3.67249351,  5.21826408,  6.78284695,  8.13547094,  9.23936315,\n",
       "       11.36215243, 13.26816249, 15.16753402, 17.8631747 , 22.52997992,\n",
       "       25.43393734, 25.31032104, 29.23176932, 32.61956761, 37.94481111])"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.mean(tev, axis = 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "b2cfd5e3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.102266  , 0.16223524, 0.2078249 , 0.24746287, 0.31322761,\n",
       "       0.38385673, 0.43934555, 0.50462906, 0.55580013, 0.61813321,\n",
       "       0.69411914, 0.76600177, 0.82527821, 0.90136158, 0.96489785,\n",
       "       1.04344513, 1.0615016 , 1.1229104 , 1.21275012, 1.29365532])"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.mean(tv, axis = 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "110c211e",
   "metadata": {},
   "outputs": [],
   "source": [
    "X =  X_approx[-1][-1]\n",
    "d = A@np.ones(n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "496c56fd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0.026, 49.58506944444438)"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "θ = np.random.negative_binomial(3, 0.3, size = (n,1))\n",
    "P_ = np.minimum(θ@θ.T/n, 1)\n",
    "d = P_@np.ones(n)\n",
    "np.mean(d == 0), np.mean(d)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "id": "27f113fd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[-0.02696915, -0.0216431 , -0.08146465, ...,  0.10544149,\n",
       "        -0.08084426, -0.13540457],\n",
       "       [-0.13843913, -0.05072544, -0.18872852, ...,  0.08259439,\n",
       "         0.19926817,  0.07674501],\n",
       "       [-0.13253289,  0.06865681,  0.18012397, ...,  0.12422462,\n",
       "        -0.17671402, -0.04926537],\n",
       "       ...,\n",
       "       [ 0.24485395,  0.0179893 ,  0.09294438, ...,  0.50140216,\n",
       "         0.0380602 , -0.22205437],\n",
       "       [ 0.14087105, -0.07705725,  0.2024218 , ...,  0.06511348,\n",
       "         0.0236356 , -0.00841525],\n",
       "       [-0.26490694, -0.06473813,  0.35884704, ..., -0.15652091,\n",
       "        -0.04595899,  0.22315687]])"
      ]
     },
     "execution_count": 88,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dea7f358",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
