Введение и исходный код
Я пытаюсь вычислить косинусное сходство между двумя разреженными векторами размерности 169647. В качестве входных данных два вектора представлены в виде строки формы <index, value>
. Только ненулевые элементы вектора получают индекс.
x = "1:0.1 43:0.4 100:0.43 10000:0.9"
y = "200:0.5 500:0.34 501:0.34"
Сначала мы преобразуем каждый из x и y в два vectors<float>.
с помощью функции splitVector
. Затем мы вычисляем расстояние с помощью функции cosine_similarity
. Неважно, split
функция. Я использую его на тот случай, если вы захотите запустить код.
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
void split(const string& s, char c,vector<string>& v) {
string::size_type i = 0;
string::size_type j = s.find(c);
while (j != string::npos) {
v.push_back(s.substr(i, j-i));
i = ++j;
j = s.find(c, j);
if (j == string::npos)
v.push_back(s.substr(i, s.length()));
}
}
float cosine_similarity(const std::vector<float> & A,const std::vector<float> & B)
{
float dot = 0.0, denom_a = 0.0, denom_b = 0.0 ;
for(unsigned int i = 0; i < A.size(); ++i)
{
dot += A[i] * B[i] ;
denom_a += A[i] * A[i] ;
denom_b += B[i] * B[i] ;
}
return dot / (sqrt(denom_a) * sqrt(denom_b)) ;
}
void splitVector(const vector<string> & v, vector<float> & values)
{
vector<string> tmpv;
string parsed;
for(unsigned int i = 0; i < v.size(); i++)
{
split(v[i], ':', tmpv);
int idx = atoi(tmpv[0].c_str());
float val = atof(tmpv[1].c_str());
tmpv.clear();
values[idx] = val;
}//end for;
}//end function
int main()
{
//INPUT VECTORS.
vector<string> x {"1:0.1","43:0.4","50:0.43","90:0.9"};
vector<string> y {"20:0.5","40:0.34","50:0.34"};
//STEP 1: Initialize vectors
int dimension = 169647;
vector<float> X;
X.resize(dimension, 0.0);
vector<float> Y;
Y.resize(dimension, 0.0);
//STEP 2: CREATE FLOAT VECTORS
splitVector(x, X);
splitVector(y, Y);
//STEP 3: COMPUTE COSINE SIMILARITY
cout << cosine_similarity(X,Y) << endl;
}
Проблема и предлагаемое решение
Инициализация и заполнение vector<float>
является проблемой. Это действительно занимает так много времени на выполнение. Я думал об использовании структуры std::map<int,float>
в С++. где X и Y будут представлены:
std::map<int,float> x_m{ make_pair(1,0.1), make_pair(43,0.4), make_pair(50,0.43), make_pair(90,0.9)};
std::map<int,float> y_m{ make_pair(20,0.5), make_pair(40,0.34), make_pair(50,0.34)};
Для этого я использовал следующую функцию:
float cosine_similarity(const std::map<int,float> & A,const std::map<int,float> & B)
{
float dot = 0.0, denom_a = 0.0, denom_b = 0.0 ;
for(auto &a:A)
{
denom_a += a.second * a.second ;
}
for(auto &b:B)
{
denom_b += b.second * b.second ;
}
for(auto &a:A)
{
if(B.find(a.first) != B.end())
{
dot += a.second * B.find(a.first)->second ;
}
}
return dot / (sqrt(denom_a) * sqrt(denom_b)) ;
}
Вопрос
- Можете ли вы помочь мне с математикой сложности?
- Снизит ли сложность вторая предложенная функция, использующая карты?
- Что вы думаете о решении?
merge
): в вашем случае у вас будет четыре массива, два с (отсортированными) индексами и два со значениями по этим индексам. выполнить итерацию по обоим массивам индексов, одновременно сравнивая значения в одинаковых элементах индекса и заменяя все несовпадающие индексы в одном с нулевым значением в другом. - person BeyelerStudios   schedule 20.01.2016map
имеет ненужное время построенияO(nlogn)
, если ваши индексы уже в порядке (как в большинстве разреженных векторных представлений), поэтому он будет доминировать в сравнении расстояний, которое равноO(n)
. Тогда есть накладные расходы на итерацию по двум картам вместо увеличения двух индексов (или четырех указателей). - person BeyelerStudios   schedule 20.01.2016split
очень неэффективна, потому что она создает много динамически выделяемых строк. Вместо этого попробуйте использовать std::istringstream. - person gudok   schedule 20.01.2016std::vector<float>
с фиксированной длиной 169647. Если есть только несколько ненулевых элементов (как в вашем примере), используйтеstd::vector<std::pair<int, float> >
, заполните егоsplit
, отсортируйте по индексу измерения, а затем реализуйте косинусное сходство, одновременно сканируя оба вектора слева направо (аналогично этапу слияния сортировки слиянием). - person gudok   schedule 20.01.2016std::unordered_map
вместоstd::map
(в идеале, с правильно угаданной начальной емкостью); 2) создать только одну карту для двух векторов, например.std::unordered_map<int, std::pair<float, float> >
-- тогда в алгоритме косинусного сходства потребуется только один проход по этой карте. - person gudok   schedule 20.01.2016