Використання набору інструкцій Intel SSSE3 для прискорення реалізації алгоритму DNN в задачах розпізнавання мови, виконуваних на мобільних пристроях

За останні тридцять років технології розпізнавання мови серйозно просунулися вперед, почавши свій шлях у дослідних лабораторіях і дійшовши до широкого кола споживачів. Ці технології починають відігравати важливу роль в нашому житті. Їх можна зустріти на робочому місці, вдома, у машині. Їх використовують у медичних цілях і в інших сферах діяльності. Розпізнавання мови входить в топ-10 перспективних технологій світового рівня.



Огляд
В результаті досліджень останніх років відбулася зміна основних алгоритмів розпізнавання мови. Так, колись це були алгоритми GMM (Gaussian Mixture Model) і HMM-GMM (Hidden Markov Model – Gaussian Mixture Model). Від них стався перехід до алгоритму DNN (Deep Neural Network). Робота цього алгоритму нагадує діяльність людського мозку. Тут використовуються складні обчислення і величезну кількість даних.

Завдяки Інтернету скористатися сучасними технологіями розпізнавання мовлення може будь-який власник смартфона. До його послуг – незліченна безліч серверів. А ось без Інтернету служби розпізнавання мови в мобільних пристроях майже марні. Вони рідко здатні правильно розуміти тих, хто намагається з ними розмовляти.

Чи можна перенести реалізацію алгоритму DNN з сервера на смартфон або планшет? Відповідь на це питання – так. Завдяки підтримці процесорами від Intel набору інструкцій SSSE3, на мобільних пристроях можна користуватися програмами для розпізнавання мовлення, заснованими на алгоритмі DNN. При цьому підключення до Інтернету не потрібно. В результаті наших випробувань точність розпізнавання мовлення таким додатком становила понад 80%. Це дуже близько до того, що можливо при використанні серверних систем. У цьому матеріалі ми розповімо про алгоритм DNN і про те, як набір інструкцій Intel SSSE3 здатний допомогти у прискоренні розрахунків, необхідних для реалізації цього алгоритму.

Попередні відомості
DNN (ДПС) – це скорочення від Deep Neural Network (Глибока Нейронна Мережа). Це – мережа прямого поширення, що містить безліч прихованих шарів. DNN знаходиться на передньому краї сучасних технологій машинного навчання. Для цього алгоритму знайшлося безліч варіантів практичного застосування.

Глибокі нейронні мережі мають велику кількість прихованих шарів. При їх навчанні потрібно модифікувати десятки мільйонів параметрів. Як результат, навчання таких мереж вимагає значних витрат часу.

Розпізнавання мовлення – типовий приклад застосування DNN. Спрощено, програми для розпізнавання мови можна представити складаються з акустичної моделі (acoustic model), мовної модель (model language) і підсистеми декодування (decoding). Акустична модель використовується для моделювання розподілу ймовірностей варіантів вимови. Мовна модель застосовується для моделювання зв'язків між словами. На етапі декодування використовуються дві вищеописані моделі, мова перетворюється на текст. Нейронна мережа вміє моделювати будь-які словесні конструкції. У той час як глибока нейронна мережа має більш сильну здатність до виділення суттєвих ознак даних, ніж дрібна (shallow) мережа, вона моделює структуру людського мозку, і, таким чином, здатна більш точно «зрозуміти» характеристики речей. В результаті, в порівнянні з іншими методами, в такий нейронної мережі можна більш точно змоделювати акустичні і мовні моделі.


Області застосування алгоритму DNN

Схема типової глибокої нейронної мережі
Зазвичай типова глибока нейронна мережа містить безліч лінійних і нелінійних прошарків, що накладаються один на одного.


Чотири прихованих шару в акустичної моделі, побудованої на базі DNN

Мережу, схема якої наведена, складається з набору лінійних шарів. Кожен нейрон з попереднього шару пов'язаний з кожним нейроном з наступного. Зв'язок входу мережі з її виходом можна описати наступною формулою:

YT = XTWT + B

XT – це вектор-рядок, вхід нейронної мережі. У застосуванні до розпізнавання мови ми зазвичай вкладаємо 4 фрагмента даних для одночасної роботи над ними, таким чином, створюючи вхідні матрицю 4xM. WT і B, відповідно, лінійна матриця перетворення нейронної мережі і вектор зміщення. Зазвичай розмірність такої мережі дуже велика, у всіх шарах є однакова кількість нейронів, тобто, мережа має квадратну форму.

Набір інструкцій Intel SSSE3
Intel називає набір команд Supplemental Streaming SIMD Extensions 3, або, для стислості, просто SSSE3, розширенням набору команд SSE3. Це – частина технології SIMD, інтегрованої в мікропроцесори Intel. Дана технологія розрахована на поліпшення можливостей з обробки мультимедійних даних. Вона призначена для прискорення виконання завдань кодування і декодування інформації та для прискорення проведення різних розрахунків. Використовуючи набір інструкцій SSSE3, ми можемо обробляти декілька потоків даних за допомогою однієї інструкції за один тактовий цикл. Це дозволяє значно підвищити ефективність додатків. Зокрема, команди SSSE3 застосовні до матричних обчислень.

Для використання набору інструкцій SSSE3 потрібно підключити відповідні відмінності файли SIMD:

#include <mmintrin.h> //MMX
#include <xmmintrin.h> //SSE
#include <emmintrin.h> //SSE2
#include <pmmintrin.h> //SSE3
#include <tmmintrin.h> //SSSE3
#include <smmintrin.h> //SSSE4.1
#include <nmmintrin.h> //SSSE4.2
#include <wmmintrin.h> //AES
#include <immintrin.h>//AVX

Заголовковий файл tmmintrin.h забезпечує роботу з SSSE3, нижче наведено опис функцій, які у ньому визначені.

/*Горизонтальне складання [з насиченням] упакованих слів, подвійних слів,
{X}MM2/m{128,64} (b) to {X}MM1 (a).*/
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//then r0=a0+a1,r1=a2+a3,r2=a4+a5,r3=a6+a7,r4=b0+b1,r5=b2+b3,r6=b4+b5, r7=b6+b7
extern __m128i _mm_hadd_epi16 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=a0+a1,r1=a2+a3,r2=b0+b1,r3=b2+b3
extern __m128i _mm_hadd_epi32 (__m128i a, __m128i b);
//SATURATE_16(x) is ((x > 32767) ? 32767 : ((x < -32768) ? -32768 : x))
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//then r0=SATURATE_16(a0+a1), ..., r3=SATURATE_16(a6+a7),
//r4=SATURATE_16(b0+b1), ..., r7=SATURATE_16(b6+b7)
extern __m128i _mm_hadds_epi16 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=a0+a1, r1=a2+a3, r2=b0+b1, r3=b2+b3
extern __m64 _mm_hadd_pi16 (__m64 a, __m64 b);
//a=(a0, a1), b=(b0, b1), 则r0=a0+a1, r1=b0+b1
extern __m64 _mm_hadd_pi32 (__m64 a, __m64 b);
//SATURATE_16(x) is ((x > 32767) ? 32767 : ((x < -32768) ? -32768 : x))
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=SATURATE_16(a0+a1), r1=SATURATE_16(a2+a3),
//r2=SATURATE_16(b0+b1)r3=SATURATE_16(b2+b3)
extern __m64 _mm_hadds_pi16 (__m64 a, __m64 b);

/*Горизонтальне віднімання [з насиченням] упакованих слів, подвійних слів,
{X}MM2/m{128,64} (b) from {X}MM1 (a).*/
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//потім r0=a0-a1, r1=a2-a3, r2=a4-a5, r3=a6-a7, r4=b0-b1, r5=b2-b3, r6=b4-b5, r7=b6-b7
extern __m128i _mm_hsub_epi16 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=a0-a1, r1=a2-a3, r2=b0-b1, r3=b2-b3
extern __m128i _mm_hsub_epi32 (__m128i a, __m128i b);
//SATURATE_16(x) is ((x > 32767) ? 32767 : ((x < -32768) ? -32768 : x))
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//then r0=SATURATE_16(a0-a1), ..., r3=SATURATE_16(a6-a7),
//r4=SATURATE_16(b0-b1), ..., r7=SATURATE_16(b6-b7)
extern __m128i _mm_hsubs_epi16 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=a0-a1, r1=a2-a3, r2=b0-b1, r3=b2-b3
extern __m64 _mm_hsub_pi16 (__m64 a, __m64 b);
//a=(a0, a1), b=(b0, b1), 则r0=a0-a1, r1=b0-b1
extern __m64 _mm_hsub_pi32 (__m64 a, __m64 b);
//SATURATE_16(x) is ((x > 32767) ? 32767 : ((x < -32768) ? -32768 : x))
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=SATURATE_16(a0-a1), r1=SATURATE_16(a2-a3),
//r2=SATURATE_16(b0-b1)r3=SATURATE_16(b2-b3)
extern __m64 _mm_hsubs_pi16 (__m64 a, __m64 b);

/*Множення і додавання упакованих слів,
{X}MM2/m{128,64} (b) to {X}MM1 (a).*/
//SATURATE_16(x) is ((x > 32767) ? 32767 : ((x < -32768) ? -32768 : x))
//a=(a0, a1, a2, ..., a13, a14, a15), b=(b0, b1, b2, ..., b13, b14, b15)
//then r0=SATURATE_16((a0*b0)+(a1*b1)), ..., r7=SATURATE_16((a14*b14)+(a15*b15))
//Параметр a містить байти без знака. Параметр b містить байти зі знаком.
extern __m128i _mm_maddubs_epi16 (__m128i a, __m128i b);
//SATURATE_16(x) is ((x > 32767) ? 32767 : ((x < -32768) ? -32768 : x))
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//then r0=SATURATE_16((a0*b0)+(a1*b1)), ..., r3=SATURATE_16((a6*b6)+(a7*b7))
//Параметр a містить байти без знака. Параметр b містить байти зі знаком.
extern __m64 _mm_maddubs_pi16 (__m64 a, __m64 b);

/*Упаковане множення старших елементів цілих чисел з округленням і масштабуванням,
{X}MM2/m{128,64} (b) to {X}MM1 (a).*/
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//then r0=INT16(((a0*b0)+0x4000) >> 15), ..., r7=INT16(((a7*b7)+0x4000) >> 15)
extern __m128i _mm_mulhrs_epi16 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=INT16(((a0*b0)+0x4000) >> 15), ..., r3=INT16(((a3*b3)+0x4000) >> 15)
extern __m64 _mm_mulhrs_pi16 (__m64 a, __m64 b);

/*Упакована перестановка байтів
{X}MM2/m{128,64} (b) by {X}MM1 (a).*/
//SELECT(a, n) extracts the nth 8-bit parameter from a. The 0th 8-bit parameter
//is the least significant 8-bits, b=(b0, b1, b2, ..., b13, b14, b15), b is mask
//then r0 = (b0 & 0x80) ? 0 : SELECT(a, b0 & 0x0f), ...,
//r15 = (b15 & 0x80) ? 0 : SELECT(a, b15 & 0x0f)
extern __m128i _mm_shuffle_epi8 (__m128i a, __m128i b);
//SELECT(a, n) extracts the nth 8-bit parameter from a. The 0th 8-bit parameter
//is the least significant 8-bits, b=(b0, b1, ..., b7), b is mask
//then r0= (b0 & 0x80) ? 0 : SELECT(a, b0 & 0x07),...,
//r7=(b7 & 0x80) ? 0 : SELECT(a, b7 & 0x07)
extern __m64 _mm_shuffle_pi8 (__m64 a, __m64 b);

/*Знак упакованих байтів, слів, подвійних слів, {X}MM2/m{128,64} (b) to {X}MM1 (a).*/
//a=(a0, a1, a2, ..., a13, a14, a15), b=(b0, b1, b2, ..., b13, b14, b15)
//then r0=(b0 < 0) ? -a0 : ((b0 = 0) ? 0 : a0), ...,
//r15= (b15 < 0) ? -a15 : ((b15 == 0) ? 0 : a15)
extern __m128i _mm_sign_epi8 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//r0=(b0 < 0) ? -a0 : ((b0 = 0) ? 0 : a0), ...,
//r7= (b7 < 0) ? -a7 : ((b7 == 0) ? 0 : a7)
extern __m128i _mm_sign_epi16 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3)
//then r0=(b0 < 0) ? -a0 : ((b0 = 0) ? 0 : a0), ...,
//r3= (b3 < 0) ? -a3 : ((b3 == 0) ? 0 : a3)
extern __m128i _mm_sign_epi32 (__m128i a, __m128i b);
//a=(a0, a1, a2, a3, a4, a5, a6, a7), b=(b0, b1, b2, b3, b4, b5, b6, b7)
//then r0=(b0 < 0) ? -a0 : ((b0 = 0) ? 0 : a0), ...,
//r7= (b7 < 0) ? -a7 : ((b7 == 0) ? 0 : a7) 
extern __m64 _mm_sign_pi8 (__m64 a, __m64 b); 
//a=(a0, a1, a2, a3), b=(b0, b1, b2, b3) 
//r0=(b0 < 0) ? -a0 : ((b0 = 0) ? 0 : a0), ..., 
//r3= (b3 < 0) ? -a3 : ((b3 == 0) ? 0 : a3) 
extern __m64 _mm_sign_pi16 (__m64 a, __m64 b); 
//a=(a0, a1), b=(b0, b1), 则r0=(b0 < 0) ? -a0 : ((b0 = 0) ? 0 : a0), 
//r1= (b1 < 0) ? -a1 : ((b1 == 0) ? 0 : a1) 
extern __m64 _mm_sign_pi32 (__m64 a, __m64 b); 

/*Упаковане вирівнювання і зсув вправо на n*8 біт,
{X}MM2/m{128,64} (b) to {X}MM1 (a).*/
//n: константа, яка визначає, на скільки байтів 
//вправо буде зрушений проміжний результат, 
//якщо n > 32, підсумкове значення буде нулем.
//CONCAT(a, b) це 256-бітне беззнакове проміжне значення,
//яке являє собою об'єднання параметрів a і b. 
//Результат – це проміжне значення, зміщена вправо на n байт.
//then r= (CONCAT(a, b) >> (n * 8)) & 0xffffffffffffffff
extern __m128i _mm_alignr_epi8 (__m128i a, __m128i b, int n);
//n: цілочисельна константа, що вказує, на скільки байтів вправо
//потрібно зрушити проміжний результат.
//Якщо n > 16, в результаті вийде нуль.
//CONCAT(a, b) це 128-бітне беззнакове проміжне значення,
//яке являє собою об'єднання параметрів a і b.
//Результуючі значення - праві 64 біта, отримані
//після зсуву цього проміжного результату вправо на n байтів. 
//then r = (CONCAT(a, b) >> (n * 8)) & 0xffffffff
extern __m64 _mm_alignr_pi8 (__m64 a, __m64 b, int n);

/*Абсолютне значення упакованих байтів, слів, подвійних слів,
{X}MM2/m{128,64} (b) to {X}MM1 (a).*/
//a=(a0, a1, a2, ..., a13, a14, a15)
//then r0 = (a0 < 0) ? -a0 : a0, ..., r15 = (a15 < 0) ? -a15 : a15
extern __m128i _mm_abs_epi8 (__m128i a);
//a=(a0, a1, a2, a3, a4, a5, a6, a7)
//then r0 = (a0 < 0) ? -a0 : a0, ..., r7 = (a7 < 0) ? -a7 : a7
extern __m128i _mm_abs_epi16 (__m128i a);
//a=(a0, a1, a2, a3)
//then r0 = (a0 < 0) ? -a0 : a0, ..., r3 = (a3 < 0) ? -a3 : a3
extern __m128i _mm_abs_epi32 (__m128i a);
//a=(a0, a1, a2, a3, a4, a5, a6, a7)
//then r0 = (a0 < 0) ? -a0 : a0, ..., r7 = (a7 < 0) ? -a7 : a7
extern __m64 _mm_abs_pi8 (__m64 a);
//a=(a0, a1, a2, a3)
//then r0 = (a0 < 0) ? -a0 : a0, ..., r3 = (a3 < 0) ? -a3 : a3
extern __m64 _mm_abs_pi16 (__m64 a);
//a=(a0, a1), then r0 = (a0 < 0) ? -a0 : a0, r1 = (a1 < 0) ? -a1 : a1
extern __m64 _mm_abs_pi32 (__m64 a);

Визначення структур даних __m64 і __m128 знаходяться в заголовочном файлі для MMX (mmintrin.h) і SSE (xmmintrin.h).

__m64:

typedef union __declspec(intrin_type) _CRT_ALIGN(8) __m64 
{ 
unsigned __int64 m64_u64; 
float m64_f32[2]; 
__int8 m64_i8[8]; 
__int16 m64_i16[4]; 
__int32 m64_i32[2]; 
__int64 m64_i64; 
unsigned __int8 m64_u8[8]; 
unsigned __int16 m64_u16[4]; 
unsigned __int32 m64_u32[2]; 
} __m64; 

__m128:

typedef union __declspec(intrin_type) _CRT_ALIGN(16) __m128 { 
float m128_f32[4]; 
unsigned __int64 m128_u64[2]; 
__int8 m128_i8[16]; 
__int16 m128_i16[8]; 
__int32 m128_i32[4]; 
__int64 m128_i64[2]; 
unsigned __int8 m128_u8[16]; 
unsigned __int16 m128_u16[8]; 
unsigned __int32 m128_u32[4]; 
} __m128; 

Приклад: використання функцій SSSE3 для прискорення обчислень, примеряющихся в алгоритмі DNN
Тут ми розглянемо кілька функцій. На їх прикладі показано, як SSSE3 використовується для прискорення розрахунків при реалізації алгоритму DNN.

__m128i _mm_maddubs_epi16 (__m128i a, __m128i b) Складання з насиченням

Ця функція дуже важлива при виконанні матричних обчислень в алгоритмі DNN. Параметр – це 128-бітний регістр (register), який використовується для зберігання 16-ти цілих чисел без знаку (8-ми бітних). Параметр b – це ціле зі знаком, теж 8-ми бітовий. Зворотний результат – це 8 16-бітних цілих чисел зі знаком. Ця функція відмінно підходить для виконання матричних обчислень:

r0 := SATURATE_16((a0*b0) + (a1*b1))
r1 := SATURATE_16((a2*b2) + (a3*b3))
...
r7 := SATURATE_16((a14*b14) + (a15*b15))

__m128i _mm_hadd_epi32 (__m128i a, __m128i b) Складання суміжних елементів

Цю функцію можна назвати функцією, яка виконує попарне додавання. Параметри a і b – це 128-бітові регістри, які зберігають за 4 цілих 32-бітних числа зі знаком. Згідно зі звичайною операцією складання відповідних елементів двох векторах, команда виконує додавання суміжних елементів вхідного вектора:

r0 := a0 + a1
r1 := a2 + a3
r2 := b0 + b1
r3 := b2 + b3

Припустимо, у нас є завдання обчислень на векторах, типова для реалізації DNN.

Є п'ять векторів: a1, b1, b2, b3, b4. Вектор a1 – це одновимірний масив з 16-ти цілих чисел типу signed char. Вектори b1, b2, b3, b4 – масиви цілих чисел з 16 елементів кожного типу unsigned char. Нам потрібно отримати скалярні добутки a1*b1, a1*b2 a1*b3, a1*b4 результат треба зберегти у вигляді 32-бітного цілого числа зі знаком.

Якщо ми скористаємося звичайним для програмування на C підходом, то код для вирішення цієї задачі буде виглядати так:

unsigned char b1[16],b2[16],b3[16],b4[16];
signed char a1[16];
int c[4],i;
//
//Ініціалізація b1,b2,b3,b4 і a1, c ініціалізується нулями
// 
for(i=0;i<16;i++){
c[0] + = (short)a1[i]*(short)b1[i];
c[1] + = (short)a1[i]*(short)b2[i];
c[2] + = (short)a1[i]*(short)b3[i];
c[3] + = (short)a1[i]*(short)b4[i];
}

Припустимо, що за один тактовий цикл можна виконати одну операцію множення і одну операцію додавання. Отримуємо – 64 тактових циклу на виконання розрахунків.

Тепер скористаємося набором інструкцій SSSE3 для рішення тієї ж задачі.

register __m128i a1,b1,b2,b3,b4,c,d1,d2,d3,d4;
//Ініціалізація a1, b1, b2, b3 і b4, c ініціалізується нулями //
d1 = _mm_maddubs_epi16(a1,b1);
d1 = _mm_add_epi32(_mm_srai_epi32(_mm_unpacklo_epi16(d1, d1), 16), _mm_srai_epi32(_mm_unpackhi_epi16(d1, d1), 16));
d2 = _mm_maddubs_epi16(a1,b2);
d2 = _mm_add_epi32(_mm_srai_epi32(_mm_unpacklo_epi16(d2, d2), 16), _mm_srai_epi32(_mm_unpackhi_epi16(d2, d2), 16));
d3 = _mm_hadd_epi32(d1, d2);
d1 = _mm_maddubs_epi16(a1: b3);
d1 = _mm_add_epi32(_mm_srai_epi32(_mm_unpacklo_epi16(d1, d1), 16), _mm_srai_epi32(_mm_unpackhi_epi16(d1, d1), 16));
d2 = _mm_maddubs_epi16(a1,b4);
d2 = _mm_add_epi32(_mm_srai_epi32(_mm_unpacklo_epi16(d2, d2), 16), _mm_srai_epi32(_mm_unpackhi_epi16(d2, d2), 16));
d4 = _mm_hadd_epi32(d1, d2);
c = _mm_hadd_epi32(d3, d4);

Результат ми зберігаємо в 128-бітному регістрі (з), в якому містяться 4 цілих числа. Враховуючи конвеєрну обробку даних, на обчислення піде 12 або 13 тактових циклів. Якщо порівняти ці дані, то вийде наступне:
Варіант реалізації
Тактові цикли процесора
Виграш
Звичайне програмування на C
64
Використання SSSE3
13
~500
Порівняльне тестування
Проведемо експеримент, взявши за основу вищенаведений код. Створимо дві функції, які виконують одні і ті ж обчислення різними способами. Одна з них, у підсумку, повертає суму елементів цілочисельного масиву c, друга – суму 32-бітних цілочисельних елементів 128-бітного регістру . Ініціалізація змінних проводиться при кожному виклику функцій. Все здійснюється за 10000000 викликів кожної з функцій, тест працює у фоновому потоці.


Інтерфейс програми для тестування продуктивності

Ось які результати дає випробування release-версії програми на планшеті Asus Fonepad 8 з CPU Intel Atom Z3530. На пристрої встановлена Android 5.0.

Порівняння швидкості виконання коду, написаного з використанням і без використання SSSE3

Використання SSSE3, мс.
Використання звичайного C, мс.
1
547
3781
2
507
3723
3
528
3762
4
517
3731
5
531
3755
6
517
3769
7
502
3752
8
529
3750
9
514
3745
10
510
3721
Середня
520.2
3748.9
В результаті виявляється, що код, який реалізує обчислення з використанням інструкцій SSSE3 виконується, в середньому, у 7.2 раза швидше, ніж звичайний.

Вихідний код проекту, який можна імпортувати в Android Studio, можна знайти на тут.

Підсумки
Як відомо, при розпізнаванні мови за допомогою глибокої нейронної мережі проводиться безліч матричних обчислень. Якщо ці обчислення оптимізувати, можна досягти найкращого, ніж коли-небудь, продуктивності на платформі IA. Ми працюємо спільно з компанією ISV Unisound, яка надає сервіси розпізнавання мови в Китаї. Unisound вдалося досягти приросту продуктивності в 10% при використанні, заснованого на DNN, на ARM-пристроях.

DNN в наші дні стає основним алгоритмом для розпізнавання мови. Його, зокрема, використовують такі служби, як Google Now, Baidu Voice, Tencent Wechat, iFlytek Speech Service, Unisound Speech Service і багато інших. У той же час, є набір інструкцій SSSE3, здатний допомогти в оптимізації розрахунків, на яких будується процес розпізнавання мови. Якщо скрізь, де використовується DNN, реалізують подібну оптимізацію, це підвищить якість розпізнавання мови і дозволить повніше розкрити можливості платформи IA.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.