# "그래프 합성곱 신경망"
> "미분 방정식을 개념을 확장·추상화하여 그래프 신경망을 이해한다."

- toc: true
- badges: true
- author: 단호진
- categories: [graph]

## 미분 방정식이 추상화된 그래프

그래프에서 라플라스 연산자를 만들고 이용하는 것을 처음 보면 상당히 추상적으로 여겨진다. 그 개념을 이해하기 위하여 라플라스 연산자를 살펴보고 그것으로 무엇을 할 수 있는지 알아보자. 그리고 이를 그래프에 확장하고 그래프 푸리에 변환 및 그래프 합성곱 신경망을 설명한다. 마지막으로 spektral 패키지의 예제를 통해 논문 인용 문제에 접근해 보겠다.

### 유한 차분

1차원 연속 공간에서 정의된 함수 $u(x)$에 대해 작용하는 라플라스 연산자 $\Delta$는 공간을 등간격 h로 이산화 한 후 다음과 같이 근사할 수 있다[1].

$\Delta u(x) = u''(x) \approx \frac{\frac{u(x+h) - u(x)}{h} - \frac{u(x) - u(x-h)}{h}}{h} = \frac{u(x - h) - 2u(x) + u(x+h)}{h^2}$

2차원인 경우,

$\Delta u(x, y) \approx \frac{u(x-h, y) + u(x+h, y) + u(x, y-h) + u(x, y+h) - 4 u(x, y)}{h^2}$

식과 같이 된다. $(x, y)$에 인접한 혹은 연결된 점들에서 함수 값을 더하고 그 점들의 수만큼 $u(x, y)$ 값을 제한 것에 비례하여 각 점에서의 라플라스 값을 근사할 수 있다. 그 개념을 확장하여 그래프에서 사용하는 $L = D - A$ 행렬을 얻을 수 있고, 자연스럽게 라플라스 행렬이라는 이름을 붙였다[3].

라플라스 연산자가 있는 문제에 대하여 어떤 접근을 할 수 있는지 물리적인 문제를 살펴보자.

### 푸리에 변환을 이용한 열확산 방정식 풀이

$x \in [0, 1]$, $t \ge 0$에서 1D 열확산에 대한 열전달 모형으로 시간에 따른 온도 분포는 $u(x, t)$는 $k \ge 0$ 계수에 대하여 지배방정식 $u_t - k u_{xx} = 0$을 따른다. 문제를 간단하게 만들기 위하여 $u(0, t) = u(1, t) = 0$인 디리클레 경계치 문제이며 $u(x, 0) = \phi(x)$ 초기 분포가 주어진다고 하자. 변수 분리 $(x, t) = X(x)T(t)$ 관계를 적용하면 2개의 상미분 방정식으로 바꿀 수 있다[4].

$\frac{\dot T}{k T} = \frac{X''}{X} = \text{const}$

경계 조건에 의하여 상수는 $-(n\pi)^2$이고, $X_n(x) = D_n \sin(n\pi x)$이다. 단, $n=1, 2, ...$ 변수 $t$에 대하여 지수 함수로 일반 해를 구할 수 있으므로, $u(x, t)$는 다음과 같다.

$u(x, t) = \sum_n A_n \sin(n\pi x) \exp(-k (n\pi)^2 t)$

$u(x, 0) = \phi(x)$ 조건으로부터 $A_n$을 구할 수 있다.

$\int \phi(x) \sin (m \pi x) dx = \sum_n A_n \int \sin (n \pi x) \sin(m\pi x) dx = \sum_n A_n \frac{\delta_{nm}}{2} = \frac{A_m}{2}$

이 문제를 푸는 과정에서 발견한 다음 사항에 주목하자.

* $\sin (n \pi x)$는 라플라스 연산자의 고유 벡터이다.
* 고유 벡터 사이에 직교성이 존재하면 계산이 간편하다.

고유 벡터로부터 푸리에 급수를 연결해보자[5].

* 프리에 급수에서 사용하는 사인함수는 라플라스 연산자의 고유 벡터이다.
* 연속 정의역의 이산 표본은 이산 푸리에 변환으로 연결된다.

이를 다음 절에서 그래프에 확장해보겠다.

이 절을 마무리하면서 실제 값을 대입하여 열확산 문제를 풀어보고 해를 음미해 보겠다.

```python
from sympy import *
from sympy.abc import x, t, n
from sympy.plotting import plot

phi = Piecewise((0, x < 0.4), (1, x < 0.6), (0, True))
```

```python
k = 1
A = 2 * integrate(phi * sin(n * pi * x), (x, 0, 1))
u = Sum(A * sin(n * pi * x) * exp(-k * (n * pi) ** 2 * t), (n, 1, 30))
```

```python
p = plot(
    phi,
    u.doit().subs({t: 0}),
    u.doit().subs({t: 0.0005}),
    u.doit().subs({t: 0.005}),
    u.doit().subs({t: 0.02}),
    (x, 0, 1), show=False
)
p[2].line_color = 'orange'
p[3].line_color = 'red'
p[4].line_color = 'black'
p.show();
```

$t=0$에서 불연속 점에서 깁스 현상이 목격된다. 시간이 흐름에 따라 깁스 물결은 확산에 의해 사라지고 온도가 평형 $u(x, \infty) = 0$을 향하여 변화되는 것을 잘 모사하고 있다.

### 그래프 푸리에 변환

이산 시스템에서 이산 푸리에 변환은 다음과 같다[5].

$A_k = \sum_{r = 0}^{n - 1} a_m e^{-i (2\pi mk/n)}$

$a_m = \frac{1}{n} \sum_{k = 0}^{n - 1} A_k e^{i (2\pi mk/n)}$

지수항은 라플라스 연산자의 고유 벡터이다. 그래프 라플라스 연산자 $\mathcal{L}$는 고유치 $\lambda_{l}$과 고유 벡터 $x_{l}$를 가진다.

$\mathcal{L} x_{l} = \lambda_{l} x_{l}$

N개의 꼭지점으로 구성된 그래프 $\mathcal{G} (\mathcal{V, E})$에서 고유치는 음이 아닌 실수이다.

$0 = \lambda_0 \lt \lambda_1 \le \lambda_1 \cdots \le \lambda_{N - 1}$

이산 푸리에 변환과 마찬가지로 그래프 푸리에 연산을 다음과 같이 정의하여 활용할 수 있다.

$\hat f (l) = \sum_{n=1}^{N} x_{l}^* (n) f(n)$

$f(n) = \sum_{l=0}^{N-1}  \hat f (l) x_{l} (n)$

### 그래프 합성곱 신경망

앞서 라플라스 방정식으로 표현된 내부 열원이 없는 열확산 방정식을 살펴보았다. 이를 확장한 방정식을 푸와송 방정식이라고 한다[7].

$\Delta \phi = f$

전산 유체 역학의 유한 체적으로 위 푸와송 방정식의 단위 체적에서 의미는 주변과 주고 받는 정보와 내부에서 생성되는 정보는 서로 상쇄된다는 것이다. 그래프의 각 꼭짓점에서 스칼라 값이 정의되면 주변 꼭짓점과 정보를 주고 받아서 새로운 스칼라 값으로 투사한다. 차수 행렬 D와 인접 행렬 A로 라플라스 연산자 $L = D - A$ 혹은 정규화 하여 $L = I - D^{-1/2} A D^{-1/2}$로 그래프를 나타낼 수 있다. 그래프 내에 여러 섬이 있을 수도 있으므로 수치적인 안정성을 위하여 자기 연결항을 추가하여 $\tilde A = A + I$로 하여 새로 $L = I - \tilde D^{-1/2} \tilde A \tilde D^{-1/2}$로 표현하여 보자. $L x = x + \delta x$일 때 새로운 정보에 해당하는 $\tilde D^{-1/2} \tilde A \tilde D^{-1/2}$을 이용하여 그래프 합성곱 신경망을 다음과 같이 정의하였다[8].

$H^{(l + 1)} = \sigma \left( \tilde D^{-1/2} \tilde A \tilde D^{-1/2} H^{(l)} W^{(l)} \right) $, $H^{(0)} = X$

이 식은 각 꼭짓점의 정보와 인접 정보를 모아서 정보를 생성하고 이에 대하여 학습 층을 쌓겠다는 뜻이다. 추가 항 뿐만 아니라 $I$ 행렬까지 이용한다면 Resnet스러운 접근이 될 것이다. 이 부분에 대해서는 추가적인 숙제로 남겨두겠다.

이 신경망의 이용을 다음 CORA 인용 데이터를 가지고 그래프 학습을 수행하여 보겠다. Spektral [튜토리얼](https://github.com/danielegrattarola/spektral/blob/master/examples/node_prediction/citation_gcn.py) 코드와 동일하며 설명을 추가하여 정리한다.

## 참고

1. 위키피디아, https://en.wikipedia.org/wiki/Finite_difference_method
1. 위키피디아, https://en.wikipedia.org/wiki/Discrete_Laplace_operator
1. 위키피디아, https://en.wikipedia.org/wiki/Laplacian_matrix
1. Standford, 편미분 방정식 2003년 여름 강의 노트, Math 220B, https://web.stanford.edu/class/math220b/handouts/heateqn.pdf, 2003
1. 단호진 블로그, 푸리에 급수, https://danhojin.github.io/jupyter-blog/general/2021/01/30/dft.html
1. D.k. Hammond, P. Vandergheynst, and R. Gribonval, Wavelets on graphs via spectral graph theory, arXiv:0912.3848v1, 2009
1. 위키피디아, https://en.wikipedia.org/wiki/Poisson's_equation
1. T.N. Kipf, M. Welling, Semi-supervised classification with graph convolutional networks,  arXiv:1609.02907v4, 2017

* spektral 패키지는 cpu로 학습한다. 잦은 메시지 전달로 cuda의 이점이 없는 듯 하다.

```python
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'  # tensorflow device: cpu
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.losses import CategoricalCrossentropy
from tensorflow.keras.optimizers import Adam

%matplotlib inline

tf.__version__
```

    '2.4.0'

## CORA 인용 데이터

https://relational.fit.cvut.cz/dataset/CORA

2708개의 과학 논문에서 인용 관계를 가지고 온 데이터이다. 각 논문은 7개로 카테고리가 중 하나로 분류되어 있으며 1433개의 어휘집에서 논문에 포함된 어휘가 각 논문의 특징 변수이다.

내부 코드를 살펴보지 못한 관계로 추정 사항만 정리해본다.

* GCNConv 층은 위에서 보인 그래프 합성곱 신경망 층이다. 활성 함수가 없는 것이 기본으로 $X' = \hat D^{-1/2} \hat A \hat D^{-1/2} X W + b$ 항을 계산한다. $\hat D^{-1/2} \hat A \hat D^{-1/2}$ 부분은 한번만 계산하여 저장해 두면 되므로 LayerPreprocess를 통하여 처리하고, 결과를 희소 행렬에 저장한다.
* 다른 종류의 학습 층도 GCNConv 층과 비슷한 transforms 구조로 정의한다. spektral의 다른 예제를 살펴보자.

```python
from spektral.data.loaders import SingleLoader
from spektral.datasets.citation import Citation
from spektral.transforms import AdjToSpTensor, LayerPreprocess
from spektral.layers import GCNConv
from spektral.models.gcn import GCN

learning_rate = 1e-2
epochs = 200
patience = 10
data = 'cora'
seed = 0

tf.random.set_seed(seed=seed)

dataset = Citation(
    data, normalize_x=True, transforms=[LayerPreprocess(GCNConv), AdjToSpTensor()]
)
```

    Pre-processing node features

    /home/danhojin/miniconda3/envs/ml8/lib/python3.8/site-packages/scipy/sparse/_index.py:125: SparseEfficiencyWarning: Changing the sparsity structure of a csr_matrix is expensive. lil_matrix is more efficient.
      self._set_arrayXarray(i, j, x)

* 인용 데이터셋에는 하나의 그래프가 들어있다.
* 꼭짓점은 2708개, 각 꼭짓점은 1433개의 특징 변수의 데이터가 들어있다. 단어 사전의 어휘가 1433개 이므로 각 단어가 특징 변수가 되었다.
* 간선(edge)에는 특징 변수 설정이 되어 있지 않다.
* 각 꼭지점은 7개의 클래스 중 하나이다.

```python
print(len(dataset))
g = dataset[0]
g
```

    1

    Graph(n_nodes=2708, n_node_features=1433, n_edge_features=None, n_labels=7)

* a: 인접 행렬
* x: 꼭짓점 특징
* e: 간선 특징
* y: 레이블

```python
print(g.a.shape, g.x.shape)  # 1433-word dictionary
assert(g.e == None)  # edges have not features
```

    (2708, 2708) (2708, 1433)

```python
g.y.shape
```

    (2708, 7)

```python
g.y[0]  # one-hot
```

    array([0., 0., 0., 1., 0., 0., 0.], dtype=float32)

### 모델

```python
def mask_to_weights(mask):
    return mask.astype(np.float32) / np.count_nonzero(mask)

weights_tr, weights_va, weights_te = (
    mask_to_weights(mask)
    for mask in (dataset.mask_tr, dataset.mask_va, dataset.mask_te)
)
```

* GCN에서 channels은 GCNConv의 은닉층의 크기를 결정한다.

```python
mod_1 = GCN(n_labels=dataset.n_labels, channels=16, n_input_channels=dataset.n_node_features)
mod_1.compile(
    optimizer=Adam(learning_rate),
    loss=CategoricalCrossentropy(reduction='sum'),
    weighted_metrics=['acc']
)
```

### 학습

```python
loader_tr = SingleLoader(dataset, sample_weights=weights_tr)
loader_va = SingleLoader(dataset, sample_weights=weights_va)

logs = mod_1.fit(
    loader_tr.load(),
    steps_per_epoch=loader_tr.steps_per_epoch,
    validation_data=loader_va.load(),
    validation_steps=loader_va.steps_per_epoch,
    epochs=epochs,
    callbacks=[EarlyStopping(patience=patience, restore_best_weights=True)],
    verbose=0,
)
```

```python
list([k for k in logs.history])
```

    ['loss', 'acc', 'val_loss', 'val_acc']

```python
fig, axes = plt.subplots(2, 1, figsize=(6, 8))
axes[0].plot(logs.history['loss'], label='tr loss')
axes[0].plot(logs.history['val_loss'], label='va loss')
axes[0].legend()
axes[1].plot(logs.history['acc'], label='tr acc')
axes[1].plot(logs.history['val_acc'], label='va acc')
axes[1].legend();
```

### 평가

```python
loader_te = SingleLoader(dataset, sample_weights=weights_te)
eval_results = mod_1.evaluate(loader_te.load(), steps=loader_te.steps_per_epoch)
eval_results
```

    1/1 [==============================] - 0s 11ms/step - loss: 1.0158 - acc: 0.8230

    [1.0157898664474487, 0.8230001330375671]

## 맺으며

* 그래프 학습의 기본 개념을 이해하게 되었다.
* 그래프를 다룰 때 물리적인 의미를 가지는 미분 방정식에서 나오는 아이디어를 확장하여 적용하면 좋은 결과를 얻을 수도 있다.
* 그래프 합성곱 신경망을 Resnet 스럽게 구성해 볼 수 있을 듯 하다.


# Компанейские числа. Исследование

### #Занимательная Математика

#### Весь код на Github, ссылка в конце статьи!

Импорт библиотек

```python
from IPython.display import Image
from IPython.core.display import HTML
from IPython.core.interactiveshell import InteractiveShell
from scipy.ndimage.filters import gaussian_filter1d
from scipy.signal import savgol_filter
import numpy as np
import sympy as sp
import pandas as pd
import random as r
import time
import matplotlib.pyplot as plt
import ipyturtle as turtle
InteractiveShell.ast_node_interactivity = "all"

def drawPlot(ss,title="Скорости",y="Секунд",x="Номер итерации"):
    fig,ax=plt.subplots(figsize=(6,6))
    ax.set_facecolor("#F2F2F2")
    ax.grid()
    ax.set_title(title)
    ax.set_ylabel(y)
    ax.set_xlabel(x)
    ax.plot(ss)
```

```python
Image(url="https://sun9-46.userapi.com/c858036/v858036072/1e3bba/s6JwXFoOgLM.jpg", width=400)
```

Компанейские числа?
Прошлая статья была про дружественные числа. Помним что это?

Если число разложить на его делители, а потом просуммировать их, то выйдет такое число, которое при той же операции вернёт первое число.
Например:
Возьмём число 8. Его собственные делители = 4,2,1
Сумма: 4+2+1 = 7
Делители семи: 1 и всё. Но 1 + 0, даст 1, а не 8. Поэтому числа 8 и 7 не являются дружественными.

Первый представитель дружественной пары это - (220,284)
Проверьте сами.

Однако должен отметить очень интересный момент по теме. Любой дружественное число ещё является и компанейским. Почему?
Компанейские числа - это такие числа, которые могут проходить несколько стадий таких вот операций, как описано выше из примера.
Каждое компанейское число обладает своим порядком, так сказать.
У дружественных чисел это порядок равен 2. Как понять?

Создам функцию для нахождения собственных делителей.

```python
def Divisors(num):
    from math import sqrt as mmsq
    s=set([1])
    i=1
    a=int(mmsq(num)+1)
    while i<=a:
        if(num//i==num):
            i+=1
            continue
        if (num%i==0):
            if (num//i!=i):
                s.add(num//i)
            s.add(i)
        i+=1
    return s
```

```python
Divisors(220)
```

    {1, 2, 4, 5, 10, 11, 20, 22, 44, 55, 110}

Просуммируем

```python
sum(Divisors(220))
```

    284

Операция проделана всего-то 1 раз.
из 220 мы получили число 284.

Повторим алгоритм ещё раз.

```python
Divisors(284)
```

    {1, 2, 4, 71, 142}

```python
sum(Divisors(284))
```

    220

И вот мы вернулись снова к числу 220, которое и было изначально.
Для этого мы провели нашу операцию два раза! Поэтому порядок - два.
В википедии это называют длиной последовательности.

Сколько чисел в данной цепи операций кроме изначального

Есть и другие длины. Например если длина будет равна 1, то получается, что у нас сумма собс.делителей числа вернёт сразу его же.
Такие числа называются **совершенные**, но о них будет статья позже 😁

В целом длины последовательностей могут быть совсем разные.
От 1 до бесконечности.

Например, возьмём число 1264460

```python
sum(Divisors(1264460))
```

    1547860

```python
sum(Divisors(1547860))
```

    1727636

```python
sum(Divisors(1727636))
```

    1305184

```python
sum(Divisors(1305184))
```

    1264460

И вот мы получили в конце тоже самое число, проделав наш алгоритм 4 раза! Поэтому длина последовательности(порядок, либо период) равен 4

В целом, узнать является ли число компанейским не так уж и быстро. Если конечно мы со временем во всей последовательности получаем единицу, то число ТОЧНО не компанейское. А вдруг вам попадётся число с длиной последовательности, например 999. Это замучаться надо, чтобы дойти до такого.

Короче говоря, компанейские числа образуют бесконечный цикл при проделывании операции с делителями

Классификация всех известных компанейских чисел 2015 год по длине:

```python
stat=np.array([(1,49),(2,1007478796),(4,1581),(5,1),(6,5),(8,4),(9,1),(28,1)])
pd.DataFrame(stat).rename(columns={0:"Длина последовательности",1:"Количество комп.чисел"})
```

      Длина последовательности
      Количество комп.чисел

      0
      1
      49

      1
      2
      1007478796

      2
      4
      1581

      3
      5
      1

      4
      6
      5

      5
      8
      4

      6
      9
      1

      7
      28
      1

Алгортмы нахождения компанейских чисел.

Первый. Мой.

```python
def SociableNumber(num,minIt=1,maxIt=20,sequence=False):
    num1=num
    iterat=0
    if(sequence):
        seq=[]
    while iterat<maxIt:
        iterat+=1
        if(sequence):
            seq.append(num)

        num = sum(Divisors(num))
        if(num==1):
            return False

        if(num1==num):
            if(iterat!=minIt):
                return False
            if(sequence):
                return (num,iterat,seq)
            else:
                return (num,iterat)
```

Расскажу про него. На вход функции мы подаём число, которое хотим проверить, затем указываем интервал длины последовательности в двух параметрах, и в конце указываем хотим ли вывести все "числа-посредники".

Проверим на числе 4 периода

```python
SociableNumber(1264460,1,sequence=True)
```

    False

```python
SociableNumber(1264460,2,sequence=True)
```

    False

```python
SociableNumber(1264460,3,sequence=True)
```

    False

```python
SociableNumber(1264460,4,sequence=True)
```

    (1264460, 4, [1264460, 1547860, 1727636, 1305184])

И мы видим, что число 1264460 является компанейским числом с периодом 4

Также, дополнительно я решил посмотреть на ряд чисел-посредников. Всё совершенно верно!

Теперь посмотрим на алгоритм, когда мы не знаем длину последовательности компанейского числа.

```python
def _SociableNumber(num,sequence=False):
    num1=num
    iterat=0
    if(sequence):
        seq=[]
    while True:
        iterat+=1
        if(sequence):
            seq.append(num)

        num = sum(Divisors(num))
        if(num==1):
            return False

        if(num1==num):
            if(sequence):
                return (num,iterat,seq)
            else:
                return (num,iterat)
```

```python
_SociableNumber(1264460)
```

    (1264460, 4)

Алгоритм сработал верно и быстро.
Однако почему вначале я показал не этот алгоритм, и почему текущий не основной?
Дело в том, что чисто гипотетически можно подать такое число, что оно будет бесконечно искать эти числа. То есть период равен бесконечности. Правда, есть ли такое число? Неизвестно, но пока мы до дойдём до единицы, чтобы доказать всего-то, что число НЕ компанейское пройдёт много времени.

Хотя мы ещё сравним по скорости все алгоритмы.
Точнее уже, потому что на этот случай в библиотеке **sympy** нет возможности найти или хотя-бы проверить компанейское число помимо дружественного.

```python
%timeit SociableNumber(1264460,4,sequence=True)
```

    680 µs ± 2.78 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

```python
%timeit _SociableNumber(1264460)
```

    679 µs ± 2.43 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

В принципе, разницы совсем нет, но немного быстрее выходит метод, когда мы уже знаем период компанейского числа.

```python
InteractiveShell.ast_node_interactivity = "last_expr"
```

```python
s = np.array([])
for i in range(1,10):
    start_time = time.time()
    _SociableNumber(i)
    end_time = (time.time() - start_time)
    s=np.append(s,end_time)
```

```python
ss = np.array([])
for i in range(1,10):
    start_time = time.time()
    SociableNumber(i)
    end_time = (time.time() - start_time)
    ss=np.append(ss,end_time)
```

Можно получить примерные графики скорости выполнения данных программ

```python
fig,ax=plt.subplots(1,2,figsize=(12,6))
ax[0].set_facecolor("#F2F2F2")
ax[0].grid()
ax[0].set_title("Алгоритм 1")
ax[0].set_ylabel("Скорость, сек")
ax[0].set_xlabel("Итерация")
ax[0].plot(s)
ax[1].set_facecolor("#F2F2F2")
ax[1].grid()
ax[1].set_title("Алгоритм 2")
ax[1].set_ylabel("Скорость, сек")
ax[1].set_xlabel("Итерация")
ax[1].plot(ss)
```

Визуально я доказал почему алгоритм проверки работает быстрее алгоритма конкретно нахождения компанейского числа

А давайте сравним скорости выполнения следующих программ:
    1. Поиск компанейских чисел с периодом 2
    2. Поиск дружественного числа

Суть в том, что оба варианта - это дружественные числа, но первый - это новый текущий алгоритм для компанейских чисел в целом, а следующий это алгоритм из предыдущей статьи про дружественные числа

```python
def AmicableNumber(k,returni=False):
    allDels = dict()
    from itertools import chain
    concatenated = chain( range(k, int(k*1.6)+1 ),range(k, int(k/1.6)+1 ,-1) )
    for i in concatenated:
        if(str(i) not in allDels):
            allDels[str(i)] = Divisors(i)
        if(i != k and sum(allDels[str(i)]) == k and sum(allDels[str(k)]) == i):
            if(returni):
                return (k,i)
            else:
                print(k,"->",i)
```

```python
ss = np.array([])
for i in range(200,500):
    start_time = time.time()
    SociableNumber(i,2)
    end_time = (time.time() - start_time)
    ss=np.append(ss,end_time)
```

```python
s = np.array([])
for i in range(200,500):
    start_time = time.time()
    AmicableNumber(i)
    end_time = (time.time() - start_time)
    s=np.append(s,end_time)
```

    220 -> 284
    284 -> 220

```python
fig,ax=plt.subplots(1,2,figsize=(12,6))
ax[0].set_facecolor("#F2F2F2")
ax[0].grid()
ax[0].set_title("Компанейский подход")
ax[0].set_ylabel("Скорость, сек")
ax[0].set_xlabel("Итерация")
ax[0].plot(s)
ax[1].set_facecolor("#F2F2F2")
ax[1].grid()
ax[1].set_title("Дружественный подход")
ax[1].set_ylabel("Скорость, сек")
ax[1].set_xlabel("Итерация")
ax[1].plot(ss)
```

Ожидаемый результат. Если мы ищем компанейские числа, то чем больше число, тем логично нужно больше времени. Но если мы ищем дружественные, то лучше использовать подход изначально сделанный для дружественных чисел, а не абстрактно компанейскийх с 2 периодом.

Теперь попробуем сделать вот что. Попробуем взять первые 26 000 чисел. Из них оставить только компанейские порядка от 1 до 5.
Мы получим своего рода компанейскую последовательность)

```python
komp=[]
per=[]
for i in range(26000):
    for j in range(1,6):
        if(SociableNumber(i,j)):
            komp.append(i)
            per.append(j)
            break
```

```python
len(komp)
```

    25

Только 25 компанейских числа из 26 000

```python
fig,ax=plt.subplots(1,2,figsize=(12,6))
ax[0].set_facecolor("#F2F2F2")
ax[0].grid()
ax[0].set_title("Компанейские числа")
ax[0].set_ylabel("Число")
ax[0].set_xlabel("Номер числа")
ax[0].plot(komp)
ax[1].set_facecolor("#F2F2F2")
ax[1].grid()
ax[1].set_title("Периоды")
ax[1].set_ylabel("Период")
ax[1].set_xlabel("Номер компанейского числа")
ax[1].plot(per)
```

Если пренебречь всеми 26 000 чисел и оставить только компанейские то такие графики получаются выше.
Первое показывает как именно растут компанейские числа. А на втором графике у нас отображаются период соответственно числам первого графика.

Попробуем узнать частоту компанейских чисел, а также сделать количественную статистику периодов и компанейских чисел среди множества обычных 26 000 чисел.
Для этого я могу просто взять все эти 26 000 чисел. И отметить точками только компанейские

```python
komp1=[]
komp2=[]
komp3=[]
komp4=[]
komp5=[]
kompa=[]
for i in range(26000):
    if(i in komp):
        kompa.append(1)

        if(per[komp.index(i)]==1):
            komp1.append(0.6)
        elif(per[komp.index(i)]==2):
            komp2.append(0.8)
        elif(per[komp.index(i)]==3):
            komp3.append(1)
        elif(per[komp.index(i)]==4):
            komp4.append(1.2)
        elif(per[komp.index(i)]==5):
            komp5.append(1.4)

        if(per[komp.index(i)]!=1):
            komp1.append(0)
        if(per[komp.index(i)]!=2):
            komp2.append(0)
        if(per[komp.index(i)]!=3):
            komp3.append(0)
        if(per[komp.index(i)]!=4):
            komp4.append(0)
        if(per[komp.index(i)]!=5):
            komp5.append(0)
    else:
        komp1.append(0)
        komp2.append(0)
        komp3.append(0)
        komp4.append(0)
        komp5.append(0)
        kompa.append(0)
```

```python
fig,ax=plt.subplots(1,2,figsize=(12,6))
ax[0].set_ylim([0.5, 1.5])
ax[0].set_xlim([0, 20000])
ax[0].set_facecolor("#F2F2F2")
ax[0].grid()
ax[0].set_title("Частота компанейский чисел")
ax[0].set_xlabel("Число")
ax[0].set_ylabel("Компанейское true,false")
ax[0].scatter(range(26000),kompa)
ax[1].set_ylim([0.5, 1.5])
ax[1].set_xlim([0, 20000])
ax[1].set_facecolor("#F2F2F2")
ax[1].grid()
ax[1].set_title("Частота компанейский чисел с классификацией")
ax[1].set_xlabel("Число")
ax[1].set_ylabel("Компанейское true,false")
ax[1].scatter(range(26000),komp1)
ax[1].scatter(range(26000),komp2)
ax[1].scatter(range(26000),komp3)
ax[1].scatter(range(26000),komp4)
ax[1].scatter(range(26000),komp5)
ax[1].legend(["Период 1","Период 2","Период 3","Период 4","Период 5"])
```

На первом графике мы видим точки - это компанейские числа. Найти закономерность в частоте не будет легкой задачей, но мы можем их проклассифицировать и узнать к каким периодам они относятся.
Что и было сделано на втором графике. И мы видим, что точек зелёного и красного цвета нет, а значит в первых 26 000 числах нет компанейских чисел 3 и 4 периода.
Довольно много дружественных чисел.

```python
import matplotlib.ticker as ticker
fig,ax=plt.subplots(1,2,figsize=(12,6))
for axis in [ax[0].xaxis, ax[0].yaxis]:
    axis.set_major_locator(ticker.MaxNLocator(integer=True))
ax[0].set_facecolor("#F2F2F2")
ax[0].set_title("Периоды в первых 26 000 числах")
ax[0].set_xlabel("Период")
ax[0].set_ylabel("Количество")
ax[0].hist(per,bins=5)
plt.pie([per.count(1),per.count(2),per.count(3),per.count(4),per.count(5)],
        colors=["red","yellow","blue","#E7AFFF","#A7FF5B"],
        labels=["1 период","2 период","3 период","4 период","5 период"],
        autopct='%1.1f%%',
        shadow=True,
        textprops={'color':"black"})
plt.legend([per.count(1),per.count(2),per.count(3),per.count(4),per.count(5)])
;
```

Ну, а это классификация компанейских чисел по периоду более наглядно.

А я кстати так и не показал сам ряд компанейских чисел. Первые 25 чисел.

```python
for i in komp:
    print(i,end=", ")
```

    6, 28, 220, 284, 496, 1184, 1210, 2620, 2924, 5020, 5564, 6232, 6368, 8128, 10744, 10856, 12285, 12496, 14264, 14288, 14536, 14595, 15472, 17296, 18416,

### Анализ

Попробуем проанализировать ряд. Найдём скорость изменения ряда, ускорение. Затем проделаем тоже самое с отношениями между числами и плотностью.

```python
dkomp=[komp[i]-komp[i-1] for i in range(1,len(komp))]
ddkomp=[dkomp[i]-dkomp[i-1] for i in range(1,len(dkomp))]
```

```python
fig,ax=plt.subplots(1,2,figsize=(12,6))
ax[0].set_facecolor("#F2F2F2")
ax[0].grid()
ax[0].set_title("Скорость ряда")
ax[0].plot(dkomp)
ax[1].set_facecolor("#F2F2F2")
ax[1].grid()
ax[1].set_title("Ускорение ряда")
ax[1].plot(ddkomp)
```

Да, можно сделать вывод, что вероятность встречи компанейского числа растёт с его ростом, но про ускорение что-либо сказать сложно. Видно, что есть неопределенность. К тому же, компанейские числа включают в себя разные периоды, но первые 25 чисел только 1,2 и 5 периоды.

#### Отношения

```python
rkomp=[komp[i]/komp[i-1] for i in range(1,len(komp))]
```

```python
np.mean(rkomp) # Среднее соотношение
```

    1.6826428895408647

```python
drawPlot(rkomp,title="Отношения между компанейскими числами",y="Отношение",x="Число")
```

Вау! Я этого не ожидал! Похоже, что ряд отношений сходится к какому-то значению. Могу спрогнозировать, что это происходит уже начиная с 30-ого числа, (но это не точно).
Для этого было бы полезно узнать скорость и ускорение данного ряда.

```python
drkomp=[rkomp[i]-rkomp[i-1] for i in range(1,len(rkomp))]
ddrkomp=[drkomp[i]-drkomp[i-1] for i in range(1,len(drkomp))]
```

```python
fig,ax=plt.subplots(1,2,figsize=(12,6))
ax[0].set_facecolor("#F2F2F2")
ax[0].grid()
ax[0].set_title("Скорость ряда")
ax[0].plot(drkomp)
ax[1].set_facecolor("#F2F2F2")
ax[1].grid()
ax[1].set_title("Ускорение ряда")
ax[1].plot(ddrkomp)
```

😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍😍

Замечательно!!!

Скорость и ускорения схожи, а так же оба графика сходятся. Этим можно сделать вывод, что мы сможем генерировать компанейские числа по формуле, если узнаем точное отношение чисел.

#### Плотность

Как и в прошлых статьях, мы исследуем компанейские числа на свойство плотности. Узнаем также скорость и ускорение плотностей.

```python
densities=[(len(list(filter(lambda x: x < i, sorted(komp))))-1)/i for i in range(200,10000)]
drawPlot(densities,"Изменение плотности","Плотность","Дружественное число")
```

Плотность компанейских чисел значительно уменьшается со временем. Можно даже выявить тренд, и понять что за функция.

Функция $ {f(x)}={{1}\over{x}} , x \in (0,\inf) $

```python
ddensities=[densities[i]-densities[i-1] for i in range(1,len(densities))]
ddensities=[ddensities[i]-ddensities[i-1] for i in range(1,len(ddensities))]
```

```python
fig,ax=plt.subplots(1,2,figsize=(12,6))
ax[0].set_facecolor("#F2F2F2")
ax[0].grid()
ax[0].set_title("Скорость плотности")
ax[0].plot(densities)
ax[1].set_facecolor("#F2F2F2")
ax[1].grid()
ax[1].set_title("Ускорение плотности")
ax[1].plot(ddensities)
```

Супер зрелище! Я ещё раз убедился, что можно попробовать генерировать компанейские числа.
А давайте попробуем?

```python
def findSociableNumbers(countt):
    komp=[]
    per=[]
    i=0
    while len(komp)<countt:
        i+=1
        for j in range(1,6):
            if(SociableNumber(i,j)):
                komp.append(i)
                per.append(j)
                break
    return (komp,per)

komp=findSociableNumbers(30)[0]
```

```python
rkomp=[komp[i]/komp[i-1] for i in range(1,len(komp))]
```

```python
drawPlot(rkomp,title="Отношения между компанейскими числами",y="Отношение",x="Число")
```

Упс. А вот вроде всё сходилось, в процессе резко поднялось значение.  План провален, однако...
Найдём отношение компанейских чисел

```python
ratio=rkomp[-1]
ratio
```

    1.0375586854460095

А теперь при помощи итерации попробуем сгенерировать потенциальные компанейские числа

```python
gkomp=komp.copy()
i=0
while i<570:
    gkomp.append(int(ratio*gkomp[-1]))
    i+=1
gkomp
```

    [6,
     28,
     220,
     284,
     496,
     1184,
     1210,
     2620,
     2924,
     5020,
     5564,
     6232,
     6368,
     8128,
     10744,
     10856,
     12285,
     12496,
     14264,
     14288,
     14536,
     14595,
     15472,
     17296,
     18416,
     63020,
     66928,
     66992,
     67095,
     69615,
     72229,
     74941,
     77755,
     80675,
     83705,
     86848,
     90109,
     93493,
     97004,
     100647,
     104427,
     108349,
     112418,
     116640,
     121020,
     125565,
     130281,
     135174,
     140250,
     145517,
     150982,
     156652,
     162535,
     168639,
     174972,
     181543,
     188361,
     195435,
     202775,
     210390,
     218291,
     226489,
     234995,
     243821,
     252978,
     262479,
     272337,
     282565,
     293177,
     304188,
     315612,
     327465,
     339764,
     352525,
     365765,
     379502,
     393755,
     408543,
     423887,
     439807,
     456325,
     473463,
     491245,
     509695,
     528838,
     548700,
     569308,
     590690,
     612875,
     635893,
     659776,
     684556,
     710267,
     736943,
     764621,
     793339,
     823135,
     854050,
     886126,
     919407,
     953938,
     989766,
     1026940,
     1065510,
     1105529,
     1147051,
     1190132,
     1234831,
     1281209,
     1329329,
     1379256,
     1431059,
     1484807,
     1540574,
     1598435,
     1658470,
     1720759,
     1785388,
     1852444,
     1922019,
     1994207,
     2069106,
     2146818,
     2227449,
     2311109,
     2397911,
     2487973,
     2581417,
     2678371,
     2778967,
     2883341,
     2991635,
     3103996,
     3220578,
     3341538,
     3467041,
     3597258,
     3732366,
     3872548,
     4017995,
     4168905,
     4325483,
     4487942,
     4656503,
     4831395,
     5012855,
     5201131,
     5396478,
     5599162,
     5809459,
     6027654,
     6254044,
     6488937,
     6732652,
     6985521,
     7247887,
     7520108,
     7802553,
     8095606,
     8399666,
     8715146,
     9042475,
     9382098,
     9734477,
     10100091,
     10479437,
     10873030,
     11281406,
     11705120,
     12144748,
     12600888,
     13074160,
     13565208,
     14074699,
     14603326,
     15151807,
     15720888,
     16311343,
     16923975,
     17559617,
     18219133,
     18903419,
     19613406,
     20350059,
     21114380,
     21907408,
     22730221,
     23583938,
     24469719,
     25388769,
     26342337,
     27331720,
     28358263,
     29423362,
     30528464,
     31675072,
     32864746,
     34099102,
     35379819,
     36708638,
     38087366,
     39517877,
     41002116,
     42542101,
     44139926,
     45797763,
     47517866,
     49302574,
     51154313,
     53075601,
     55069050,
     57137371,
     59283375,
     61509980,
     63820213,
     66217216,
     68704247,
     71284688,
     73962047,
     76739964,
     79622216,
     82612721,
     85715546,
     88934909,
     92275187,
     95740921,
     99336824,
     103067784,
     106938874,
     110955357,
     115122694,
     119446551,
     123932806,
     128587559,
     133417138,
     138428110,
     143627287,
     149021739,
     154618799,
     160426077,
     166451469,
     172703167,
     179189670,
     185919798,
     192902701,
     200147872,
     207665162,
     215464792,
     223557366,
     231953886,
     240665769,
     249704858,
     259083444,
     268814277,
     278910587,
     289386102,
     300255063,
     311532248,
     323232989,
     335373195,
     347969371,
     361038643,
     374598779,
     388668216,
     403266083,
     418412226,
     434127239,
     450432487,
     467350139,
     484903195,
     503115521,
     522011878,
     541617957,
     561960415,
     583066909,
     604966135,
     627687867,
     651262998,
     675723580,
     701102869,
     727435371,
     754756887,
     783104563,
     812516940,
     843034008,
     874697257,
     907549736,
     941636111,
     977002725,
     1013697663,
     1051770814,
     1091273943,
     1132260757,
     1174786982,
     1218910436,
     1264691109,
     1312191244,
     1361475422,
     1412610649,
     1465666448,
     1520714953,
     1577831007,
     1637092265,
     1698579298,
     1762375703,
     1828568217,
     1897246835,
     1968504932,
     2042439389,
     2119150727,
     2198743242,
     2281325147,
     2367008720,
     2455910455,
     2548151223,
     2643856433,
     2743156205,
     2846185546,
     2953084533,
     3063998506,
     3179078262,
     3298480262,
     3422366844,
     3550906443,
     3684273821,
     3822650302,
     3966224022,
     4115190182,
     4269751315,
     4430117561,
     4596506952,
     4769145710,
     4948268553,
     5134119015,
     5326949776,
     5527023007,
     5734610725,
     5949995165,
     6173469161,
     6405336547,
     6645912567,
     6895524306,
     7154511134,
     7423225167,
     7702031746,
     7991309933,
     8291453029,
     8602869105,
     8925981559,
     9261229692,
     9609069304,
     9969973315,
     10344432406,
     10732955688,
     11136071394,
     11554327596,
     11988292951,
     12438557474,
     12905733341,
     13390455720,
     13893383634,
     14415200859,
     14956616853,
     15518367720,
     16101217211,
     16705957763,
     17333411575,
     17984431728,
     18659903342,
     19360744782,
     20087908905,
     20842384356,
     21625196913,
     22437410881,
     23280130538,
     24154501638,
     25061712967,
     26002997961,
     26979636382,
     27992956058,
     29044334689,
     30135201719,
     31267040281,
     32441389211,
     33659845143,
     34924064678,
     36235766637,
     37596734398,
     39008818319,
     40473938255,
     41994086170,
     43571328843,
     45207810677,
     46905756617,
     48667475175,
     50495361566,
     52391900967,
     54359671895,
     56401349712,
     58519710264,
     60717633654,
     62998108157,
     65364234284,
     67819228998,
     70366430087,
     73009300700,
     75751434059,
     78596558342,
     81548541753,
     84611397781,
     87789290655,
     91086541008,
     94507631750,
     98057214163,
     101740114225,
     105561339172,
     109526084305,
     113639740053,
     117907899303,
     122336365004,
     126931158055,
     131698525493,
     136644948985,
     141777153641,
     147102117158,
     152627079304,
     158359551766,
     164307328358,
     170478495620,
     176881443812,
     183524878321,
     190417831497,
     197569674933,
     204990132207,
     212689292102,
     220677622321,
     228965983722,
     237565645082,
     246488298418,
     255746074884,
     265351561264,
     275317817086,
     285658392375,
     296387346079,
     307519265180,
     319069284529,
     331053107422,
     343487026949,
     356387948148,
     369773410989,
     383661614218,
     398071440104,
     413022480107,
     428535061519,
     444630275097,
     461330003739,
     478656952236,
     496634678141,
     515287623798,
     534641149574,
     554721568337,
     575556181232,
     597173314799,
     619602359486,
     642873809607,
     667019304803,
     692071673058,
     718064975332,
     745034551870,
     773017070250,
     802050575235,
     832174540502,
     863429922304,
     895859215160,
     929506509626,
     964417552241,
     1000639807724,
     1038222523507,
     1077216796690,
     1117675643514,
     1159654071439,
     1203209153934,
     1248400108072,
     1295288375041,
     1343937703681,
     1394414237152,
     1446786602866,
     1501126005790,
     1557506325256,
     1616004215406,
     1676699209411,
     1739673827604,
     1805013689673,
     1872807631069,
     1943147823785,
     2016129901673,
     2091853090468,
     2170420342692,
     2251938477628,
     2336518326552,
     2424274883417,
     2515327461197,
     2609799854105,
     2707820505902,
     2809522684527,
     2915044663288,
     3024529908857,
     3138127276325,
     3255991211586,
     3378281961316,
     3505165790848,
     3636815210222,
     3773409208727,
     3915133498256,
     4062180765796,
     4214750935403,
     4373051440019,
     4537297503493,
     4707712433201,
     4884527923649,
     5067984371485,
     5258331202338,
     5455827209937,
     5660740907962,
     5873350895115,
     6093946233898,
     6322826843621,
     6560303908170,
     6806700299087,
     7062351014545,
     7327603634809,
     7602818794801,
     7888370674417,
     8184647507258,
     8492052108469,
     8811002422402,
     9141932090849,
     9485291042617,
     9841546105250,
     10211181639719,
     10594700198957,
     10992623211124,
     11405491688537,
     11833866963223,
     12278331450104,
     12739489438840,
     13217967915416,
     13714417414586,
     14229512904335,
     14763954703558,
     15318469434208,
     15893811009201,
     16490761657433,
     17110132987289,
     17752767090097,
     18419537685030,
     19111351307002,
     19829148539189,
     20573905291834,
     21346634129085,
     22148385645670,
     22980249895272,
     23843357872559,
     24738883050871,
     25668042977664,
     26632100929876,
     27632367631467,
     28670203035465,
     29747018172947,
     30864277071461,
     32023498745506,
     33226259261769,
     34474193881929,
     35768999285945,
     37112435878844,
     38506330184152,
     39952577327218,
     41453143611808,
     43010069193472,
     44625470853320,
     46301544875979,
     48040570035640,
     49844910694255,
     51717020016104,
     53659443303093,
     55674821455321,
     57765894561624,
     59935505624971,
     62186604427786,
     64522251542444,
     66945622492394,
     69460012069573,
     72068838813970,
     74775649661443,
     77584124766098,
     80498082503791,
     83521484663557,
     86658441834019,
     89913218992104,
     93290241301666]

Сколько из них настоящих компанейских чисел можно узнать только, если мы сравним с уже точно известным рядом. Здесь показано 600 чисел

## Статистика

```python
len(komp)
```

    30

За 12 минут я нашёл 30 компанейских чисел

А самое последнее, большее число - 69615

```python
EvenSociable=[i for i in komp if(i%2==0)]
len(EvenSociable)
```

    26

```python
plt.pie([len(EvenSociable),30-len(EvenSociable)],
        colors=["#A7FF5B","#E7AFFF"],
        labels=["Чётные","Нечётные"],
        autopct='%1.1f%%',
        shadow=True,
        textprops={'color':"black"})
plt.legend([len(EvenSociable),30-len(EvenSociable)])
;
```

Из 30 чисел только 4 нечётные!

Исследуем теперь на простоту!

```python
def _isPrime(n):
    if n%2==0:
        return n==2
    d=3
    while d*d<=n and n%d!=0:
        d+=2
    return d*d>n
```

```python
PrimeSociable=[i for i in komp if(_isPrime(i))]
plt.pie([len(PrimeSociable),30-len(PrimeSociable)],
        colors=["#A7FF5B","#E7AFFF"],
        labels=["Составные","Простые"],
        autopct='%1.1f%%',
        shadow=True,
        textprops={'color':"black"})
plt.legend([len(PrimeSociable),30-len(PrimeSociable)])
;
```

Как и в случае с дружественными числами, здесь тоже ни одного простого!

## Визуализация

Попробуем нарисовать спирали со средним и последним углом в ряду из 30 компанейских чисел

```python
ratio=[komp[i]/komp[i-1] for i in range(1,len(komp))]
```

```python
ratio[-1]
```

    1.0375586854460095

```python
np.mean(ratio)
```

    1.6519833851472943

```python
angle=sp.N(360/np.mean(ratio)**2)
angle
```

$\displaystyle 131.914079291008$

```python
#Создаём картинку 300х300

N=300
t = turtle.Turtle(fixed=False, width=N, height=N)
t.hideturtle()
t
```

    Turtle()

```python
for i in range(15):
    for j in range(int(angle)):
        t.forward(i/10)
        t.left(1)
        if(i>10):
            time.sleep(0.01) # Чтобы не терять рисунок
```

```python
angle=sp.N(360/ratio[-1]**2)
angle
```

$\displaystyle 334.408386396675$

```python
#Создаём картинку 300х300

N=300
t = turtle.Turtle(fixed=False, width=N, height=N)
t.hideturtle()
t
```

    Turtle()

```python
for i in range(15):
    for j in range(int(angle)):
        t.forward(i/10)
        t.left(1)
        if(i>10):
            time.sleep(0.01) # Чтобы не терять рисунок
```

# Конец

На этом всё, дорогие читатели!
Надеюсь вам было интересно узнать что-то вроде такого из занимательной математики 😍

Следующая статья скорее всего про совершенство чисел. Будет большая статья. Даже очень. К тому же, у меня есть несколько проектов, поэтому следующая статья будет совсем не скоро, но будет!

---

Здесь ссылка на датасеты компанейских чисел
[Компанейские числа](https://github.com/lonagi/pysasha/blob/master/datasets/Number%20Theory/Sociable%20Numbers/SociableeNumbers_30.csv)

Датасет состоит из двух столбиков. Компанейское число и его период. А на момент написания статьи, чисел там 30.

Если есть желание прочитать статью на другом источнике, то пожалуйста, вот ниже ссылки:

[ВКонтакте](vk.com/@lonagi-kompaneiskie-chisla-issledovanie)
[Instagram]()
[Facebook](https://www.facebook.com/%D0%97%D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F-%D0%BC%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0-%D0%BE%D1%82-Lonagi-112410007105730)
[Github](https://github.com/lonagi/pysasha/blob/master/docs/Number%20Theory/Sociable%20Numbers/SociableNumbers.md)

```python

```

```python

```

```python

```