我的朴素贝叶斯分类器适用于我的模型,但不接受用户对我的应用程序的输入

My Naive Bayes classifier works for my model but will not accept user input on my application

我正在尝试将我的机器学习朴素贝叶斯情绪分析模型部署到 Web 应用程序中。这个想法是用户应该键入一些文本,应用程序对其执行情感分析,然后将具有指定情感的文本存储在数据库中的另一列中,稍后通过 html 作为列表调用。

虽然模型和矢量化器在 Google Colab 上工作正常,但当我将模型加载到我的应用程序并尝试 运行 用户通过它输入时,它不起作用。根据我尝试过的不同解决方案,我收到了很多错误代码。

最近的是:

ValueError: DataFrame constructor not properly called!

但是当我尝试解决这个问题时,我收到了其他错误消息,例如:

'numpy.ndarray' object has no attribute 'lower'

或:

ValueError: X has 1 features, but MultinomialNB is expecting 26150 features as input.

或:

sklearn.exceptions.NotFittedError: Vocabulary not fitted or provided

基本上我不知道自己在做什么,几周来我一直在努力弄清楚。我的倾向是,问题要么是模型无法读取来自用户的格式,要么是矢量化器无法处理输入。

或者我的整个方法可能是错误的,并且我缺少一些步骤。对此的任何帮助将不胜感激。

我的模型代码如下所示(预处理后):

#Split into training and testing data
x = df['text']
y = df['sentiment']

df1 = df[df["text"].notnull()]
x1 = df1['text']
y1 = df1['sentiment']

x1_train, x1_test, y1_train, y1_test = train_test_split(x1, y1, test_size=0.2, random_state=30)

# Vectorize text
vec = CountVectorizer(stop_words='english')
x1 = vec.fit_transform(x1).toarray()
x1_test = vec.transform(x1_test).toarray()

df1 = df1.replace(r'^\s*$', np.nan, regex=True)

from sklearn.naive_bayes import MultinomialNB

sentiment_model = MultinomialNB()
sentiment_model.fit(x1, y1)
sentiment_model.score(x1_test, y1_test)

# Save model to disk
pickle.dump(sentiment_model, open('sentiment_model.pkl','wb'))

我的应用程序代码如下所示:

@app.route('/journal', methods=['GET', 'POST'])
def entry():
    if request.method == 'POST':
        journals = request.form
        
        entry_date = journals['entry_date']
        journal_entry = journals['journal_entry']

        vec = CountVectorizer(stop_words='english')
        sdf = pd.DataFrame('journal_entry')
        sdf = vec.fit_transform(sdf).toarray()
        sdf = vec.transform(sdf).toarray()

        sentiment = sentiment_model.predict(sdf)
        journals['sentiment'] = sentiment

        cur = mysql.connection.cursor()
        #insert the values with sentiment attribute into database
        cur.execute("INSERT INTO journals(entry_date, journal_entry, sentiment) VALUES(%s, %s, %s)",(entry_date, journal_entry, sentiment))
        mysql.connection.commit()
   
    return render_template('journal.html')

对于开始这一旅程的人来说,数据科学管道可能会很棘手。目前的代码只有一个主要问题。您正在使用每个新的传入数据重新创建预处理步骤。您训练模型的矢量器不再使用,因此功能不匹配。请记住,我们在 fitting/training 期间只 .fit_tranform。我们在使用的时候,只.transform.

您可以像处理模型一样,通过保存经过训练的向量化器来修复它。然后模型和向量化器都将用于 API 个端点 model.predict(vec.transform(data)).

更好的方法是将您的预处理(向量化器)和分类器合并为一个。


from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB

sentiment_model = Pipeline(
    steps=[
        (
            "count_verctorizer",CountVectorizer(stop_words='english')
        ),
        (
            "naive_bayes", MultinomialNB()
       )
])

# now, it performs transformation in a pipeline 

sentiment_model.fit(X1, y1)

# now we can use it/save it to use in our API we’re you will only need the model as the model will do `vectorizer tranform` for you

在API上,可以确保如果没有值和正则表达式也执行过滤的其他步骤。如果需要,您可以将它们添加到管道中。


...

journal_entry = journals['journal_entry']
X = pd.DataFrame(journal_entry) # this should be like your X1
X = X.replace(r'^\s*$', np.nan, regex=True)
X = X[X ["text"].notnull()]

# assuming we have loaded our model

sentiment = sentiment_model.predict(X)
...

所以在我看来,这里有多个问题在起作用。

首先,sdf = pd.DataFrame('journal_entry') 没有意义——您从文字字符串 'journal_entry' 创建数据框,而不是它的实际内容?我建议您完全摆脱 entry 函数中的 DataFrame,因为它不是 sklearn 对象必需的输入结构。

其次,您通过调用 fit_transform 复制功能,然后在 entry 函数中再次调用 transform。调用 fit_transform 就足够了,因为它正在做两件事:1) 它学习字典 2) 它转换为文档术语矩阵。

第三,您使用特定的 CountVectorizer 模型训练了您的模型。该模型将使用学习的文档术语矩阵将每个文档转换为向量,该矩阵在您调用 fitfit_transform 函数时获得固定大小。然后使用这个固定大小的向量训练你的朴素贝叶斯模型。因此,当它在推理时获得不同大小的向量时它会抱怨——这是因为您在每次 entry 调用时再次重新初始化 CountVectorizer 。如果要保留特征大小,还需要保存 CountVectorizer

此外,我建议检查您的 entry 函数,确保您在 POST 请求中获得算法的有效字符串。


# load both CountVectorizer and the model 
vec = pickle.load(open("my_count_vec.pkl", "rb"))
sentiment_model = pickle.load(open("my_sentiment_model", "rb"))

@app.route('/journal', methods=['GET', 'POST'])
def entry():
    if request.method == 'POST':
        journals = request.form
        
        entry_date = journals['entry_date']
        journal_entry = journals['journal_entry']
        sdf = vec.transform([journal_entry]).reshape(1, -1)
        sentiment = sentiment_model.predict(sdf)
        ...

sdf = vec.transform([journal_entry]).reshape(1, -1) 假定日记条目是单个字符串,因此需要重塑以进行进一步处理。