Форум программистов, компьютерный форум CyberForum.ru

Многослойный перспетрон - C++

Восстановить пароль Регистрация
 
Рейтинг: Рейтинг темы: голосов - 20, средняя оценка - 4.70
Duha666
50 / 50 / 5
Регистрация: 10.03.2012
Сообщений: 138
23.03.2012, 14:12     Многослойный перспетрон #1
Пытаюсь написать простейший персептрон для решения хотя бы XOR задачи, но не успешно. К сожалению, в сети кода не на пэхапэ не нашел, пришлось импровизировать. Но теперь не могу найти ошибку. Снабдил код комментариями, да бы не было проблем взаимопонимания:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <stdio.h>
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <cmath>
 
using namespace std;
 
const int input_size = 2, hidden_size = 3 , output_size = 1; // размеры соответственных слоев
const int sz = input_size + hidden_size + output_size; // итоговый размер матриц
const double a = 0.5; // коэффициент инерциальности
 
int x[input_size], y[output_size]; // входной вектор и ответ учителя
 
double o[sz], d[sz], w[sz][sz], delta_w[sz][sz];
double v = 1;
 
double f(double x) // активационная функция
{
    return x / (abs(x) + 0.2);
}
 
void Initialize()
{
    for (int i = 0; i < input_size; i++) // инициализация весов между входным и скрытым слоями
        for (int j = input_size; j < input_size + hidden_size; j++)
            w[i][j] = 1.0 * (rand() % 2 * 2 - 1) / (rand() % 10 + 1); // 1* (1 или -1) * (1..10);
    for (int i = input_size; i < input_size + hidden_size; i++) // инициализация весом между скрытым и выходным слоями
        for (int j = input_size + hidden_size; j < input_size + hidden_size + output_size; j++)
            w[i][j] = 1.0 * (rand() % 2 * 2 - 1) / (rand() % 10 + 1);
}
 
void GetAnswer() // текущий ответ сети
{
    double tmp;
    for (int i = 0; i < input_size; i++)
        o[i] = x[i]; // получаем ответы входного слоя
    for (int i = input_size; i < input_size + hidden_size; i++) // получаем ответ для каждого нейрона из скрытого слоя
    {
        tmp = 0;
        for (int j = 0; j < input_size; j++)
            tmp += o[j] * w[j][i];
        o[i] = f(tmp);
    }
    for (int i = input_size + hidden_size; i < input_size + hidden_size + output_size; i++) // получаем ответ для каждого нейрона выходного слоя
    {
        tmp = 0;
        for (int j = input_size; j < input_size + hidden_size; j++)
            tmp += o[j] * w[j][i];
        o[i] = f(tmp);
    }
}
 
void BackPropagation() // обратное распределение ошибки
{
    double tmp;
    for (int i = input_size + hidden_size; i < input_size + hidden_size + output_size; i++)
        d[i] = o[i] * (1 - o[i]) * (y[i - input_size - hidden_size] - o[i]);
    for (int i = input_size; i < input_size + hidden_size; i++)
    {
        tmp = 0;
        for (int j = input_size + hidden_size; j < input_size + hidden_size + output_size; j++)
            tmp += d[j] * w[i][j];
        d[i] = o[i] * (1 - o[i]) * tmp;
    }
    for (int i = 0; i < input_size; i++)
    {
        tmp = 0;
        for (int j = input_size; j < input_size + hidden_size; j++)
            tmp += d[j] * w[i][j];
        d[i] = o[i] * (1 - o[i]) * tmp;
    }
    for (int i = 0; i < input_size; i++) // пересчет дельты ребер между входным и скрытым слоями
        for (int j = input_size; j < input_size + hidden_size; j++)
        {
            delta_w[i][j] = a * delta_w[i][j] + (1 - a) * v * d[j] * o[i];
            w[i][j] += delta_w[i][j];
        }
    for (int i = input_size; i < input_size + hidden_size; i++) // пересчет дельты ребер между скрытым и выходным слоями
        for (int j = input_size + hidden_size; j < input_size + hidden_size + output_size; j++)
        {
            delta_w[i][j] = a * delta_w[i][j] + (1 - a) * v * d[j] * o[i];
            w[i][j] += delta_w[i][j];
        }
    v *= 0.99; // уменьшаем коффициент обучения
}
 
int main()
{
    srand(time(NULL));
    Initialize(); // инициализируем начальные веса
    while (true)
    {
        printf("Input:\n");
        for (int i = 0; i < input_size; i++) // получаем входной вектор
            scanf("%d", &x[i]);
        GetAnswer(); // подсчитываем ответ
        printf("Maybe: ");
        for (int i = input_size + hidden_size; i < input_size + hidden_size + output_size; i++)
            printf("%d ", (int)(o[i] + 0.5)); // выводим ответ уже по пороговой функции
        printf("\nWhat is the right answer?\n");
        for (int i = 0; i < output_size; i++) // получаем ответ учителя и
            scanf("%d", &y[i]);
        for (int i = 0; i < 100; i++) // корректируем веса
        {
            GetAnswer();
            BackPropagation();
        }
        printf("\n\n");
    }
}
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Xind
275 / 148 / 7
Регистрация: 05.11.2011
Сообщений: 425
Записей в блоге: 1
23.03.2012, 19:28     Многослойный перспетрон #2
Duha666, сейчас сложно разобраться в коде, но заметил у вас
C++
1
2
3
4
double f(double x) // активационная функция
{
    return x / (abs(x) + 0.2);
}
почему бы не воспользоваться сигмоидальной функцией?

и посмотрите в каком-нибудь источники про проблемы алгоритма обратного распределения ошибки. Возможно в коде упущен момент с преждевременной сходимостью. По каким критериям выбирается начальное значение? Осуществляется ли перезапуск? и т.п.
Duha666
50 / 50 / 5
Регистрация: 10.03.2012
Сообщений: 138
24.03.2012, 03:48  [ТС]     Многослойный перспетрон #3
Это тоже сигмоидальная функция. Правда, потом вернул на 1/(1+ exp(-x)). Проблема в том, что веса после нескольких нескольких итераций уменьшаются на крайне малое значение.

Начальные веса беру случайно у нуля. Во время работы алгоритма больше не меняю их
Nekto
342 / 287 / 10
Регистрация: 23.03.2012
Сообщений: 838
24.03.2012, 03:55     Многослойный перспетрон #4
Цитата Сообщение от Duha666 Посмотреть сообщение
Это тоже сигмоидальная функция. Правда, потом вернул на 1/(1+ exp(-x)). Проблема в том, что веса после нескольких нескольких итераций уменьшаются на крайне малое значение.

Начальные веса беру случайно у нуля. Во время работы алгоритма больше не меняю их
Чтобы сеть не вырождалась, надо постоянно чередовать обучающие примеры.
Duha666
50 / 50 / 5
Регистрация: 10.03.2012
Сообщений: 138
24.03.2012, 04:09  [ТС]     Многослойный перспетрон #5
Nekto, естественно. Так и происходит. Но добиться правильного ответа невозможно даже по единственному примеру.
Цитата Сообщение от Duha666 Посмотреть сообщение
веса после нескольких нескольких итераций уменьшаются на крайне малое значение.
Nekto
342 / 287 / 10
Регистрация: 23.03.2012
Сообщений: 838
24.03.2012, 04:21     Многослойный перспетрон #6
Цитата Сообщение от Duha666 Посмотреть сообщение
Nekto, естественно. Так и происходит. Но добиться правильного ответа невозможно даже по единственному примеру.
https://sites.google.com/site/nekto1...edirects=0&d=1 вот писал как-то (полтора года назад) методом тыка, вроде работало. Уже детали не особо помню
Duha666
50 / 50 / 5
Регистрация: 10.03.2012
Сообщений: 138
24.03.2012, 15:55  [ТС]     Многослойный перспетрон #7
Честно говоря, не увидел различий в обучении/получении ответа.
VTsaregorodtsev
297 / 277 / 35
Регистрация: 19.02.2010
Сообщений: 1,210
24.03.2012, 22:31     Многослойный перспетрон #8
Duha666, веса в интервале [-10,10] - это перебор (насыщение нейронов).
Сигмоида 1/(1+ exp(-x)) тоже мастдай.
Шаг обучения скорее всего на порядки завышен.
В код внимательно не вглядывался - поэтому не все проблемные места мог назвать.
Duha666
50 / 50 / 5
Регистрация: 10.03.2012
Сообщений: 138
26.03.2012, 04:38  [ТС]     Многослойный перспетрон #9
Веса в интервале [-1, 1]. Было много вариантов а-ля [-0.1..0.1] и так далее. Но дело не в этом, проблема в необучаемости сети.
По каким причинам сигмоида 1/(1+ exp(-x)) плоха? В таком случае, какую лучше использовать? Шаг обучения изначально 1, далее умножается на 0.99.
Xind
275 / 148 / 7
Регистрация: 05.11.2011
Сообщений: 425
Записей в блоге: 1
28.03.2012, 13:57     Многослойный перспетрон #10
Цитата Сообщение от VTsaregorodtsev Посмотреть сообщение
Сигмоида 1/(1+ exp(-x)) тоже мастдай.
А что в ней не так?

Цитата Сообщение от Duha666 Посмотреть сообщение
По каким причинам сигмоида 1/(1+ exp(-x)) плоха?
Тоже стало интересно. Помню когда учили, считали чуть ли не самой крутой активационной функцией?!
Duha666
50 / 50 / 5
Регистрация: 10.03.2012
Сообщений: 138
29.03.2012, 15:30  [ТС]     Многослойный перспетрон #11
Вообщем, всяко развлекался с начальными весами, активационными функциями и ничего. Очевидно, где-то баг. Но найти не могу. Может быть все-таки есть у кого идеи?
Nekto
342 / 287 / 10
Регистрация: 23.03.2012
Сообщений: 838
29.03.2012, 16:29     Многослойный перспетрон #12
смог добиться от твоей системы такого
Код
Input:
0
0
Maybe: 0.024326 v=1.000000

Input:
1
1
Maybe: 0.027209 v=1.000000

Input:
0
1
Maybe: 0.979380 v=1.000000

Input:
1
0
Maybe: 0.982667 v=1.000000
Добавлено через 1 минуту
hidden_size поставил 6, функцию активации 1 / (1 + exp(-x));
const double a = 0.1;
v поставил, чтоб не менялось
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        for (int i = 0; i < 1000; i++) // êîððåêòèðóåì ГўГҐГ±Г*
        {
            x[0]=0;
            x[1]=0;
            y[0]=0;
            GetAnswer();
            BackPropagation();
            x[0]=0;
            x[1]=1;
            y[0]=1;
            GetAnswer();
            BackPropagation();
            x[0]=1;
            x[1]=0;
            y[0]=1;
            GetAnswer();
            BackPropagation();
            x[0]=1;
            x[1]=1;
            y[0]=0;
            GetAnswer();
            BackPropagation();
        }
Добавлено через 1 минуту
и +убрал отрицательные веса связей.
Duha666
50 / 50 / 5
Регистрация: 10.03.2012
Сообщений: 138
29.03.2012, 16:38  [ТС]     Многослойный перспетрон #13
Оу, каким образом? Сделал ровно то же самое, но результат все-ещё не меняется.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <stdio.h>
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <cmath>
 
using namespace std;
 
const int input_size = 2, hidden_size = 6 , output_size = 1; // размеры соответственных слоев
const int sz = input_size + hidden_size + output_size; // итоговый размер матриц
const double a = 0.1; // коэффициент инерциальности
 
int x[input_size], y[output_size]; // входной вектор и ответ учителя
 
double o[sz], d[sz], w[sz][sz], delta_w[sz][sz];
double v = 1;
 
double f(double x) // активационная функция
{
    return 1 / (1 + exp(-x));
}
 
void Initialize()
{
    for (int i = 0; i < input_size; i++) // инициализация весов между входным и скрытым слоями
        for (int j = input_size; j < input_size + hidden_size; j++)
            w[i][j] = 1.0 / (rand() % 10000 + 1);
    for (int i = input_size; i < input_size + hidden_size; i++) // инициализация весом между скрытым и выходным слоями
        for (int j = input_size + hidden_size; j < input_size + hidden_size + output_size; j++)
            w[i][j] = 1.0 / (rand() % 10000 + 1);
}
 
void GetAnswer() // текущий ответ сети
{
    double tmp;
    for (int i = 0; i < input_size; i++)
        o[i] = f(x[i]); // получаем ответы входного слоя
    for (int i = input_size; i < input_size + hidden_size; i++) // получаем ответ для каждого нейрона из скрытого слоя
    {
        tmp = 0;
        for (int j = 0; j < input_size; j++)
            tmp += o[j] * w[j][i]; // получаем входное значения нейрона
        o[i] = f(tmp); // возвращаем ответ по пороговой функции
    }
    for (int i = input_size + hidden_size; i < input_size + hidden_size + output_size; i++) // получаем ответ для каждого нейрона выходного слоя
    {
        tmp = 0;
        for (int j = input_size; j < input_size + hidden_size; j++)
            tmp += o[j] * w[j][i];
        o[i] = f(tmp);
    }
}
 
void BackPropagation() // обратное распределение ошибки
{
    double tmp;
    for (int i = input_size + hidden_size; i < input_size + hidden_size + output_size; i++)
        d[i] = o[i] * (1 - o[i]) * (y[i - input_size - hidden_size] - o[i]);
    for (int i = input_size; i < input_size + hidden_size; i++)
    {
        tmp = 0;
        for (int j = input_size + hidden_size; j < input_size + hidden_size + output_size; j++)
            tmp += d[j] * w[i][j];
        d[i] = o[i] * (1 - o[i]) * tmp;
    }
    for (int i = 0; i < input_size; i++) // пересчет дельты ребер между входным и скрытым слоями
        for (int j = input_size; j < input_size + hidden_size; j++)
        {
            //delta_w[i][j] = a * delta_w[i][j] + (1 - a) * v * d[j] * o[i];
            //w[i][j] += delta_w[i][j];
            w[i][j] += v * d[j] * o[i];
        }
    for (int i = input_size; i < input_size + hidden_size; i++) // пересчет дельты ребер между скрытым и выходным слоями
        for (int j = input_size + hidden_size; j < input_size + hidden_size + output_size; j++)
        {
            //delta_w[i][j] = a * delta_w[i][j] + (1 - a) * v * d[j] * o[i];
            //w[i][j] += delta_w[i][j];
            w[i][j] += v * d[j] * o[i];
        }
    //v *= 0.99; // уменьшаем коффициент обучения
}
 
 
void AutoLearn()
{
    x[0] = rand() % 2;
    x[1] = rand() % 2;
    y[0] = (x[0] ^ x[1]);
    GetAnswer();
    printf("%d %d - %0.2lf\n", x[0], x[1], o[input_size + hidden_size]);
    BackPropagation();
}
 
int main()
{
    srand(time(NULL));
    Initialize(); // инициализируем начальные веса
    while (true)
        AutoLearn();
}
Nekto
342 / 287 / 10
Регистрация: 23.03.2012
Сообщений: 838
29.03.2012, 17:50     Многослойный перспетрон #14
Цитата Сообщение от Duha666 Посмотреть сообщение
Оу, каким образом? Сделал ровно то же самое, но результат все-ещё не меняется.
Запусти пару тысяч раз Autolearn без вывода, а потом проверь.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include <stdio.h>
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <cmath>
 
using namespace std;
 
const int input_size = 2, hidden_size = 6 , output_size = 1; // Г°Г*çìåðû ñîîòâåòñòâåГ*Г*ûõ ñëîåâ
const int sz = input_size + hidden_size + output_size; // èòîãîâûé Г°Г*çìåð Г¬Г*òðèö
const double a = 0.1; // êîýôôèöèåГ*ГІ ГЁГ*åðöèГ*ëüГ*îñòè
 
int x[input_size], y[output_size]; // âõîäГ*îé âåêòîð ГЁ îòâåò ó÷èòåëÿ
 
double o[sz], d[sz], w[sz][sz], delta_w[sz][sz];
double v = 1;
 
double f(double x) // Г*ГЄГІГЁГўГ*öèîГ*Г*Г*Гї ГґГіГ*êöèÿ
{
    return 1 / (1 + exp(-x));
}
 
void Initialize()
{
    for (int i = 0; i < input_size; i++) // ГЁГ*èöèГ*ëèçГ*öèÿ âåñîâ ìåæäó âõîäГ*ûì ГЁ ñêðûòûì ñëîÿìè
        for (int j = input_size; j < input_size + hidden_size; j++)
            w[i][j] = 1.0 * (rand() % 2) / (rand() % 10 + 1); // 1* (1 èëè -1) * (1..10);
    for (int i = input_size; i < input_size + hidden_size; i++) // ГЁГ*èöèГ*ëèçГ*öèÿ âåñîì ìåæäó ñêðûòûì ГЁ âûõîäГ*ûì ñëîÿìè
        for (int j = input_size + hidden_size; j < input_size + hidden_size + output_size; j++)
            w[i][j] = 1.0 * (rand() % 2) / (rand() % 10 + 1);
}
 
void GetAnswer() // òåêóùèé îòâåò ñåòè
{
    double tmp;
    for (int i = 0; i < input_size; i++)
        o[i] = x[i]; // ïîëó÷Г*ГҐГ¬ îòâåòû âõîäГ*îãî ñëîÿ
    for (int i = input_size; i < input_size + hidden_size; i++) // ïîëó÷Г*ГҐГ¬ îòâåò äëÿ ГЄГ*æäîãî Г*åéðîГ*Г* ГЁГ§ ñêðûòîãî ñëîÿ
    {
        tmp = 0;
        for (int j = 0; j < input_size; j++)
            tmp += o[j] * w[j][i];
        o[i] = f(tmp);
    }
    for (int i = input_size + hidden_size; i < input_size + hidden_size + output_size; i++) // ïîëó÷Г*ГҐГ¬ îòâåò äëÿ ГЄГ*æäîãî Г*åéðîГ*Г* âûõîäГ*îãî ñëîÿ
    {
        tmp = 0;
        for (int j = input_size; j < input_size + hidden_size; j++)
            tmp += o[j] * w[j][i];
        o[i] = f(tmp);
    }
}
 
void BackPropagation() // îáðГ*ГІГ*îå Г°Г*ñïðåäåëåГ*ГЁГҐ îøèáêè
{
    double tmp;
    for (int i = input_size + hidden_size; i < input_size + hidden_size + output_size; i++)
        d[i] = o[i] * (1 - o[i]) * (y[i - input_size - hidden_size] - o[i]);
    for (int i = input_size; i < input_size + hidden_size; i++)
    {
        tmp = 0;
        for (int j = input_size + hidden_size; j < input_size + hidden_size + output_size; j++)
            tmp += d[j] * w[i][j];
        d[i] = o[i] * (1 - o[i]) * tmp;
    }
    for (int i = 0; i < input_size; i++)
    {
        tmp = 0;
        for (int j = input_size; j < input_size + hidden_size; j++)
            tmp += d[j] * w[i][j];
        d[i] = o[i] * (1 - o[i]) * tmp;
    }
    for (int i = 0; i < input_size; i++) // ïåðåñ÷åò äåëüòû ðåáåð ìåæäó âõîäГ*ûì ГЁ ñêðûòûì ñëîÿìè
        for (int j = input_size; j < input_size + hidden_size; j++)
        {
            delta_w[i][j] = a * delta_w[i][j] + (1 - a) * v * d[j] * o[i];
            w[i][j] += delta_w[i][j];
        }
    for (int i = input_size; i < input_size + hidden_size; i++) // ïåðåñ÷åò äåëüòû ðåáåð ìåæäó ñêðûòûì ГЁ âûõîäГ*ûì ñëîÿìè
        for (int j = input_size + hidden_size; j < input_size + hidden_size + output_size; j++)
        {
            delta_w[i][j] = a * delta_w[i][j] + (1 - a) * v * d[j] * o[i];
            w[i][j] += delta_w[i][j];
        }
    //v *= 0.99; // óìåГ*ГјГёГ*ГҐГ¬ êîôôèöèåГ*ГІ îáó÷åГ*ГЁГї
}
 
int main()
{
    srand(time(NULL));
    Initialize(); // ГЁГ*èöèГ*ëèçèðóåì Г*Г*Г·Г*ëüГ*ûå ГўГҐГ±Г*
    while (true)
    {
        printf("Input:\n");
        for (int i = 0; i < input_size; i++) // ïîëó÷Г*ГҐГ¬ âõîäГ*îé âåêòîð
            scanf("%d", &x[i]);
        GetAnswer(); // ïîäñ÷èòûâГ*ГҐГ¬ îòâåò
        printf("Maybe: ");
        for (int i = input_size + hidden_size; i < input_size + hidden_size + output_size; i++)
            printf("%f ", o[i]); // âûâîäèì îòâåò óæå ГЇГ® ïîðîãîâîé ГґГіГ*êöèè
        for (int i = 0; i < 1000; i++) // êîððåêòèðóåì ГўГҐГ±Г*
        {
            x[0]=0;
            x[1]=0;
            y[0]=0;
            GetAnswer();
            BackPropagation();
            x[0]=0;
            x[1]=1;
            y[0]=1;
            GetAnswer();
            BackPropagation();
            x[0]=1;
            x[1]=0;
            y[0]=1;
            GetAnswer();
            BackPropagation();
            x[0]=1;
            x[1]=1;
            y[0]=0;
            GetAnswer();
            BackPropagation();
        }
        printf("v=%f\n\n",v);
    }
}
Добавлено через 1 минуту
получается где-то после 4 тысяч уже близкие результаты
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
29.03.2012, 19:26     Многослойный перспетрон
Еще ссылки по теме:

Многослойный персептрон Matlab
Java Многослойный персептрон. Функция активации

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
Duha666
50 / 50 / 5
Регистрация: 10.03.2012
Сообщений: 138
29.03.2012, 19:26  [ТС]     Многослойный перспетрон #15
Ох ты, уже невероятный прогресс. Но вот что интересно. Почему-то обучение очень и очень скачкообразное. После 50к обучений они едва сдвинулись, но на 60 сразу получилось (0 0 - 0) (0 1 - 0.5) (1 0 - 0.5) (1 1 - 0). То есть всегда одно из значений (1, 0) (0, 1) оказывается в состоянии 0.5, а другое 1. На 100к то же самое, но на границе в 200к все становится идеально. Из-за чего так может происходить?
Yandex
Объявления
29.03.2012, 19:26     Многослойный перспетрон
Ответ Создать тему
Опции темы

Текущее время: 22:54. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru