問題描述
這是我曾經做過的最笨拙的編碼。它是我的字符串庫在 C. 它檢測用户是否生氣的不同程度,即 str_isHeated()。
為什麼?
曾經玩過 text-based 遊戲,你通過打字,打印多個!!! 在計算機上發誓,電腦響應很愚蠢?我認為對於 AI 來説,NPC(non-playable 角色) 可以判斷你的心情並做出適當的反應,這可能是有用的。也許甚至用於在線客户服務。
它有效,但我有興趣看看有沒有人有任何想法如何改進它。我一直在玩樂。
/*
Function: str_getHeat()
Software usually gets user information, but it hardly
detects the user's emotion when entering in the information.
This may be useful for checking a customer's or player's
typing behavior, which may generate better responses with AI.
Calculated as follows:
All Caps
One or more words in caps
Exclamation Point Count
If 'please' or 'sorry' is found, take off heat points.
Swearing words
Returns: EHeat_Cold, EHeat_Warm, EHeat_Heated, EHeat_VeryHeated
*/
EHeat str_isHeated(STRING *objString)
{
int i;
int intHeatScore = 0; /* 0% cold; 100% very heated */
STRINGCOLLECTION tokens;
STRING temp_a;
/* Count how many exclamations there are */
for (i = 0; i < objString->length; i++)
{
if (objString->str[i] == '!')
intHeatScore += 10;
}
/* tokenize user's input */
sc_init(&tokens);
str_tokenize(objString, &tokens);
/* Check if all caps. That can be taken as impatient. */
if (str_isUpper(objString))
{
intHeatScore += 10;
}
else
{
/* check if one or more words are all in caps. That is
demanding behavior, and that is not nice. */
for (i = 0; i < tokens.count; i++)
{
if (str_isUpperCString(tokens.items[i]))
{
intHeatScore += 10;
/* 'I' is excused. */
if (!strcmp(tokens.items[i], "I"))
intHeatScore -= 10;
}
}
}
/* Check if the user said please. That's always nice.
Take off a few heat points. */
if (str_findStringIgnoreCaps(objString, "please"))
intHeatScore -= 6;
/* Check if the user said he's sorry. That's also nice. */
if (str_findStringIgnoreCaps(objString, "sorry"))
intHeatScore -= 6;
/* Almost forgot... swearing. That is never nice. */
for (i = 0; i < tokens.count; i++)
{
str_setText(&temp_a, tokens.items[i]);
str_toLower(&temp_a);
/* don't say these words out loud (censored for your innocence*/
if (str_findString(&temp_a, "$#@#") ||
str_findString(&temp_a, "@#$@") ||
str_findString(&temp_a, "@$$") ||
str_findString(&temp_a, "@$$#@") ||
str_findString(&temp_a, "%#@") ||
str_findString(&temp_a, "@#$")
)
{
/* big no-no */
intHeatScore += 20;
}
}
/* Check the final heat score */
if (intHeatScore >= 50)
return EHeat_VeryHeated;
else if (intHeatScore >= 30)
return EHeat_Heated;
else if (intHeatScore > 10)
return EHeat_Warm;
else if (intHeatScore >= 0)
return EHeat_Cold;
return EHeat_Cold;
}
最佳解決方案
算法:
你在做法中假設有一些謬誤。沒有錯,但可以改進的事情:
-
您可以使用此評論在您的”heat score” 上承擔界限
/* 0% cold; 100% very heated */
但是沒有代碼實現這些界限。你可以走向負面,也可以超過 100. 我建議觀察這些界限,情緒應該被看作只能在 0 和 1 之間的概率。
為了匹配這個概率,您可能會更好地將您的情緒評分存儲為
double而不是int,但這種選擇取決於您。 -
現在你使用的是 bag-of-words model 。這是首次從情緒分析開始時的典型方法,因為它更容易,但通常會給出較低的準確度,表示文本的實際情緒。
正如我所説,這是一個相當直接和實用的方式,但有很多情況下會犯錯誤。
-
曖昧的情緒詞 – 「這個產品工作非常糟糕」,而 「這個產品是非常好的」
-
錯過了否定 – 「我永遠不會在數百萬年前説這個產品值得購買」
-
引用/間接文本 – 「我爸爸説這個產品是可怕的,但我不同意」
-
比較 – 「這個產品和頭上的孔有關」
-
任何細微的東西 – 「這個產品是醜陋,緩慢而不吸引人的,但它是唯一在市場上做這份工作」
就 NLP 幫助您而言,word sense disambiguation(或甚至只是 part-of-speech tagging) 可能有助於 (1),syntactic parsing 可能有助於 (2) 中的長距離依賴,某種 chunking 可能有助於 (3) 。這是所有的研究水平的工作,但沒有什麼我知道你可以直接使用。問題 (4) 和 (5) 更加困難,我扔了手,放棄了這一點。
-
-
你不會在實際生活中得到體面的句子數量。例如,在這篇文章中看看很多句子。他們不包含發誓的話,”please” 或”sorry”,感嘆號或字母上限。你需要一個更一般的正面,負面和中性詞彙的詞彙,然後是系統來衡量這些詞語對你的分數的影響。
-
你有一些奇怪的分數修改。為什麼當我用”I” 提到自己時,得分被認為更積極?我不認為應該,我會説,重新考慮你的想法。以感嘆號結尾的句子也不是必需的 (更高的熱量) 。感嘆號通常用於表示強烈的感覺 (如興奮) 或高音量。我看過的大多數情緒分析系統根本不考慮標點符號。 “A” 應與”I” 相同。一個句子很可能從”A” 開始,並且是積極的或中性的,但你的程序認為它有負面的含義。
對於基本情緒分析,這是很好的,但請注意,它確實有它的缺陷。如果您希望提高算法的準確性,建議您閲讀 this research paper,其分類準確率達到 90%(高於任何其他已發表的結果) 。
碼:
-
現在你有方法
str_findString()。我猜這是strstr()的一個變體。我也猜測,<string.h>的這種方法的實現將更加高效和更快,基於它是一個標準庫。if (strstr(temp_a, "$#@#")) { ... } -
在
for循環內聲明i(C99)for (int i = 0; i < objString->length; i++) -
我將添加另一個選項卡到功能體。
EHeat str_isHeated(STRING *objString) { int intHeatScore = 0; /* 0% cold; 100% very heated */ STRINGCOLLECTION tokens; -
我會把你最後的兩個回報條件合併成一個。
else if (intHeatScore >= 0)
return EHeat_Cold;return EHeat_Cold;
我發現最後一個
else-if比較沒有用,總體來説,如果沒有包含EHeat_Cold,就會返回EHeat_Cold。return EHeat_Cold; -
我不太確定我所假設的是
#define:STRING和STRINGCOLLECTION。我想是可以保留他們,但有沒有一個具體的原因,你不只是放在他們實際上:char*和char*數組分別?
次佳解決方案
我會嘗試更普遍的方法。
定義規則界面,通過規則運行字符串的每個部分,並添加結果。
然後,規則可以單獨檢查所有帽子,感嘆號等,而不是將其硬編碼為您的中心方法。
(即,為每個感興趣的標記返回 1 點的規則或某事) 。
可能值得擁有兩種類型的規則 – 「完整字符串規則」 和”word rule” 。完整的字符串規則可以處理諸如感嘆號和內容的總數,然後將字符串拆分為空格,並通過單詞規則運行所有找到的單詞。
對於像好的/討厭的話,我會有一個配置文件在某個地方列出單詞和一個正面或負面的分數在他們旁邊 – 例如,請-10,發誓+10 等。
您的字詞規則可以掃描您的字詞與該字典,並將結果應用於您的分數。
第三種解決方案
使用散列表!這樣的事情會奏效:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
#include <sys/resource.h>
#include <sys/time.h>
// default dictionary (LIST OF BAD WORDS)
#define DICTIONARY "/PATH_TO_BAD_WORDS"
// prototype
double calculate(const struct rusage* b, const struct rusage* a);
int main(int argc, char* argv[])
{
// check for correct number of args
if (argc != 2 && argc != 3)
{
printf("Usage: speller [dictionary] textn");
return 1;
}
// structs for timing data
struct rusage before, after;
// benchmarks
double ti_load = 0.0, ti_check = 0.0, ti_size = 0.0, ti_unload = 0.0;
// determine dictionary to use
char* dictionary = (argc == 3) ? argv[1] : DICTIONARY;
// load dictionary
getrusage(RUSAGE_SELF, &before);
bool loaded = load(dictionary);
getrusage(RUSAGE_SELF, &after);
// abort if dictionary not loaded
if (!loaded)
{
printf("Could not load %s.n", dictionary);
return 1;
}
// calculate time to load dictionary
ti_load = calculate(&before, &after);
// try to open text
char* text = (argc == 3) ? argv[2] : argv[1];
FILE* fp = fopen(text, "r");
if (fp == NULL)
{
printf("Could not open %s.n", text);
unload();
return 1;
}
// prepare to report misspellings
printf("nMISSPELLED WORDSnn");
// prepare to spell-check
int index = 0, misspellings = 0, words = 0;
char word[LENGTH+1];
// spell-check each word in text
for (int c = fgetc(fp); c != EOF; c = fgetc(fp))
{
// allow only alphabetical characters and apostrophes
if (isalpha(c) || (c == ''' && index > 0))
{
// append character to word
word[index] = c;
index++;
// ignore alphabetical strings too long to be words
if (index > LENGTH)
{
// consume remainder of alphabetical string
while ((c = fgetc(fp)) != EOF && isalpha(c));
// prepare for new word
index = 0;
}
}
// ignore words with numbers (like MS Word can)
else if (isdigit(c))
{
// consume remainder of alphanumeric string
while ((c = fgetc(fp)) != EOF && isalnum(c));
// prepare for new word
index = 0;
}
// we must have found a whole word
else if (index > 0)
{
// terminate current word
word[index] = '';
// update counter
words++;
// check word's spelling
getrusage(RUSAGE_SELF, &before);
bool misspelled = !check(word);
getrusage(RUSAGE_SELF, &after);
// update benchmark
ti_check += calculate(&before, &after);
// print word if misspelled
if (misspelled)
{
printf("%sn", word);
misspellings++;
}
// prepare for next word
index = 0;
}
}
// check whether there was an error
if (ferror(fp))
{
fclose(fp);
printf("Error reading %s.n", text);
unload();
return 1;
}
// close text
fclose(fp);
// determine dictionary's size
getrusage(RUSAGE_SELF, &before);
unsigned int n = size();
getrusage(RUSAGE_SELF, &after);
// calculate time to determine dictionary's size
ti_size = calculate(&before, &after);
// unload dictionary
getrusage(RUSAGE_SELF, &before);
bool unloaded = unload();
getrusage(RUSAGE_SELF, &after);
// abort if dictionary not unloaded
if (!unloaded)
{
printf("Could not unload %s.n", dictionary);
return 1;
}
// calculate time to unload dictionary
ti_unload = calculate(&before, &after);
// report benchmarks
printf("nWORDS MISSPELLED: %dn", misspellings);
printf("WORDS IN DICTIONARY: %dn", n);
printf("WORDS IN TEXT: %dn", words);
printf("TIME IN load: %.2fn", ti_load);
printf("TIME IN check: %.2fn", ti_check);
printf("TIME IN size: %.2fn", ti_size);
printf("TIME IN unload: %.2fn", ti_unload);
printf("TIME IN TOTAL: %.2fnn",
ti_load + ti_check + ti_size + ti_unload);
// that's all folks
return 0;
}
/**
* Returns number of seconds between b and a.
*/
double calculate(const struct rusage* b, const struct rusage* a)
{
if (b == NULL || a == NULL)
{
return 0.0;
}
else
{
return ((((a->ru_utime.tv_sec * 1000000 + a->ru_utime.tv_usec) -
(b->ru_utime.tv_sec * 1000000 + b->ru_utime.tv_usec)) +
((a->ru_stime.tv_sec * 1000000 + a->ru_stime.tv_usec) -
(b->ru_stime.tv_sec * 1000000 + b->ru_stime.tv_usec)))
/ 1000000.0);
}
}
// maximum length for a word
// (e.g., pneumonoultramicroscopicsilicovolcanoconiosis)
#define LENGTH 45
/**
* Returns true if word is in dictionary else false.
*/
bool check(const char* word);
/**
* Loads dictionary into memory. Returns true if successful else false.
*/
bool load(const char* dictionary);
/**
* Returns number of words in dictionary if loaded else 0 if not yet loaded.
*/
unsigned int size(void);
/**
* Unloads dictionary from memory. Returns true if successful else false.
*/
bool unload(void);
// size of hashtable
#define SIZE 1000000
// create nodes for linked list
typedef struct node
{
char word[LENGTH+1];
struct node* next;
}
node;
// create hashtable
node* hashtable[SIZE] = {NULL};
// create hash function
int hash (const char* word)
{
int hash = 0;
int n;
for (int i = 0; word[i] != ''; i++)
{
// alphabet case
if(isalpha(word[i]))
n = word [i] - 'a' + 1;
// comma case
else
n = 27;
hash = ((hash << 3) + n) % SIZE;
}
return hash;
}
// create global variable to count size
int dictionarySize = 0;
/**
* Loads dictionary into memory. Returns true if successful else false.
*/
bool load(const char* dictionary)
{
// TODO
// opens dictionary
FILE* file = fopen(dictionary, "r");
if (file == NULL)
return false;
// create an array for word to be stored in
char word[LENGTH+1];
// scan through the file, loading each word into the hash table
while (fscanf(file, "%sn", word)!= EOF)
{
// increment dictionary size
dictionarySize++;
// allocate memory for new word
node* newWord = malloc(sizeof(node));
// put word in the new node
strcpy(newWord->word, word);
// find what index of the array the word should go in
int index = hash(word);
// if hashtable is empty at index, insert
if (hashtable[index] == NULL)
{
hashtable[index] = newWord;
newWord->next = NULL;
}
// if hashtable is not empty at index, append
else
{
newWord->next = hashtable[index];
hashtable[index] = newWord;
}
}
// close file
fclose(file);
// return true if successful
return true;
}
/**
* Returns true if word is in dictionary else false.
*/
bool check(const char* word)
{
// TODO
// creates a temp variable that stores a lower-cased version of the word
char temp[LENGTH + 1];
int len = strlen(word);
for(int i = 0; i < len; i++)
temp[i] = tolower(word[i]);
temp[len] = '';
// find what index of the array the word should be in
int index = hash(temp);
// if hashtable is empty at index, return false
if (hashtable[index] == NULL)
{
return false;
}
// create cursor to compare to word
node* cursor = hashtable[index];
// if hashtable is not empty at index, iterate through words and compare
while (cursor != NULL)
{
if (strcmp(temp, cursor->word) == 0)
{
return true;
}
cursor = cursor->next;
}
// if you don't find the word, return false
return false;
}
/**
* Returns number of words in dictionary if loaded else 0 if not yet loaded.
*/
unsigned int size(void)
{
// TODO
// if dictionary is loaded, return number of words
if (dictionarySize > 0)
{
return dictionarySize;
}
// if dictionary hasn't been loaded, return 0
else
return 0;
}
/**
* Unloads dictionary from memory. Returns true if successful else false.
*/
bool unload(void)
{
// TODO
// create a variable to go through index
int index = 0;
// iterate through entire hashtable array
while (index < SIZE)
{
// if hashtable is empty at index, go to next index
if (hashtable[index] == NULL)
{
index++;
}
// if hashtable is not empty, iterate through nodes and start freeing
else
{
while(hashtable[index] != NULL)
{
node* cursor = hashtable[index];
hashtable[index] = cursor->next;
free(cursor);
}
// once hashtable is empty at index, go to next index
index++;
}
}
// return true if successful
return true;
}
#ifndef DICTIONARY_H
參考文獻
注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。