问题描述
这是我曾经做过的最笨拙的编码。它是我的字符串库在 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 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。