chatgpt-sentiment-loop-all

textmining
nlp
transformer
chatgpt
sentiment
Published

December 20, 2023

Aufgabe

Fragen Sie ChatGPT via API zum Sentiment der Texte aus dem Germeval-2018-Datensatz (Test).

Hinweise:











Lösung

Achtung

OpenAI hat eine neue API (Stand: 2023-11-23), V1.3.5. Der Code der alten API bricht. 💔 \(\square\)

Setup

Die richtige venv nutzen:

library(reticulate)
#virtualenv_create("chatgpt")
use_virtualenv("chatgpt")

Check zu Python:

reticulate::py_config()
python:         /Users/sebastiansaueruser/.virtualenvs/chatgpt/bin/python
libpython:      /Users/sebastiansaueruser/.pyenv/versions/3.11.1/lib/libpython3.11.dylib
pythonhome:     /Users/sebastiansaueruser/.virtualenvs/chatgpt:/Users/sebastiansaueruser/.virtualenvs/chatgpt
version:        3.11.1 (main, Oct  4 2023, 18:12:06) [Clang 15.0.0 (clang-1500.0.40.1)]
numpy:          /Users/sebastiansaueruser/.virtualenvs/chatgpt/lib/python3.11/site-packages/numpy
numpy_version:  1.26.2

NOTE: Python version was forced by use_python() function

Ggf. noch Module installieren:

#reticulate::py_install("pandas")
#py_install("tiktoken")
#py_install("datar")
#py_install("scikit-learn")

R-Pakete und Python-Module

library(tidyverse)
library(tidymodels)
library(plotly)

Attaching package: 'plotly'
The following object is masked from 'package:ggplot2':

    last_plot
The following object is masked from 'package:stats':

    filter
The following object is masked from 'package:graphics':

    layout

Module importieren:

from openai import OpenAI
import pandas as pd
import numpy as np
import time
from datetime import datetime
#from sklearn.metrics import classification_report
from sklearn.metrics import accuracy_score

Versionen der importierten Module:

pd.__version__
'2.1.3'
```{zsh openai-version-zsh}
pip list | grep openai
```

[notice] A new release of pip is available: 23.3.1 -> 23.3.2
[notice] To update, run: pip install --upgrade pip
openai             1.3.5

Wir brauchen >= 1.35.

Der Operator | ist die “Pfeife” der Kommandozeile, also sozusagen der “UND-DANN-Befehl”.

Daten

Daten importieren:

csv_file_path_test = 'https://github.com/sebastiansauer/pradadata/raw/master/data-raw/germeval_test.csv'

germeval_test = pd.read_csv(csv_file_path_test)

Die ersten paar Texte herausziehen:

start_pos = 0
end_pos = 3531
tweets = germeval_test["text"].iloc[start_pos:(end_pos+1)].tolist()

Prompt

Prompt definieren:

prompt_stem  = "Als KI mit Exertise in natĂŒrlicher Sprache und Emotionserkennung ist es Ihre Aufgabe, das Sentiment des folgenden Textes einzuschĂ€tzen. Bitte antworten Sie nur mit einem einzigen Wort, entweder 'positiv', 'neutral' oder 'negativ'. Ihre Antwort soll Ihre Insgesamt-EinschĂ€tzung zum Sentiments des Textes zusammenfassen. Nach dem Doppelpunkt folgt der Text, dessen Sentiment Sie einschĂ€tzen sollen: "

Gute Prompts können helfen, gute Antworten vom Modell zu erhalten.

Mit “List Comprehension” können wir die Tweets jeweils mit dem Prompt verknĂŒpfen:

prompts = [prompt_stem + tweet for tweet in tweets]
prompts[0]
"Als KI mit Exertise in natĂŒrlicher Sprache und Emotionserkennung ist es Ihre Aufgabe, das Sentiment des folgenden Textes einzuschĂ€tzen. Bitte antworten Sie nur mit einem einzigen Wort, entweder 'positiv', 'neutral' oder 'negativ'. Ihre Antwort soll Ihre Insgesamt-EinschĂ€tzung zum Sentiments des Textes zusammenfassen. Nach dem Doppelpunkt folgt der Text, dessen Sentiment Sie einschĂ€tzen sollen: Meine Mutter hat mir erzĂ€hlt, dass mein Vater einen Wahlkreiskandidaten nicht gewĂ€hlt hat, weil der gegen die Homo-Ehe ist â˜ș"

Check: Wie viele Elemente hat die Liste prompts?

len(prompts)
3532

Laut OpenAI kostet 1k Token fĂŒr das Modell gpt-3.5-turbo-1106 $0.001.

Authentifizieren

Anmelden bei OpenAI:

client = OpenAI()
Note

Dieses Anmeldeverfahren setzt voraus, dass in .Renviron die Variable OPENAI_API_KEY hinterlegt ist. \(\square\)

Anfrage an die API, in eine Funktion gepackt:

def get_completion(prompt, client_instance, model="gpt-3.5-turbo"):
  messages = [{"role": "user", "content": prompt}]
  response = client_instance.chat.completions.create(
    model=model,
    messages=messages,
    max_tokens=50,
    temperature=0,
  )
  return response.choices[0].message.content

API anfragen

Und jetzt als Schleife. Ergebnisliste anlegen, am Anfang noch leer:

predicted_values = []
start_time = time.time()

for prompt in prompts:
  result = get_completion(prompt, client) 
  predicted_values.append(result)

end_time = time.time()
end_time - start_time

VoilĂ :

print(predicted_values[:5])
[]

Als CSV speichern

id_seq = [i for i in range(start_pos, end_pos + 1)]
predicted_values_df = pd.DataFrame(id_seq, columns = ["id"])
predicted_values_df["pred"] = predicted_values

now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
csv_output_name = "germeval_test_preds_at_" + now
predicted_values_df.to_csv(csv_output_name)

Oder Vorhersagen aus CSV importieren

preds_path = 'https://raw.githubusercontent.com/sebastiansauer/Datenwerk2/main/posts/chatgpt-sentiment-loop-all/germeval_test_preds_at_2023-12-20%2014%3A06%3A00'

preds = pd.read_csv(preds_path)

preds.head()
   Unnamed: 0  id     pred
0           0   0  neutral
1           1   1  negativ
2           2   2  negativ
3           3   3  neutral
4           4   4  negativ

Man kann eine Python-Variable an R ĂŒbergeben:

preds_r <- py$preds

Vorhersagen (Predictions) betrachten

ZĂ€hlen wir mal kurz aus:

preds_r |> 
  count(pred) |> 
  slice(3:5)  # zwei komische, kaputte Zeilen, weg damit
     pred    n
1 negativ 1792
2 neutral 1354
3 positiv  384

Oder in Python:

preds["pred"].value_counts()
pred
negativ                                                                             1792
neutral                                                                             1354
positiv                                                                              384
muss. #AfD #Grenzschutz #Deutschland: negativ                                          1
kommen, in den eigenen vier WĂ€nden haben. Das ist doch wohl klar. #ltwlsa #ltw21       1
Name: count, dtype: int64

Puh, das ist ein bisschen was kaput gegangen.

Predictions reparieren

allowed_preds = ["positiv", "neutral", "negativ"]
preds.loc[~preds["pred"].isin(allowed_preds), "pred"] = np.nan

Check:

preds["pred"].value_counts()
pred
negativ    1792
neutral    1354
positiv     384
Name: count, dtype: int64

Passt!

Scoring vorbereiten

Was waren noch mal die Variablen unser Tabelle?

germeval_test.columns
Index(['id', 'text', 'c1', 'c2'], dtype='object')

Die ersten paar Werte:

germeval_test.head()
   id                                               text       c1     c2
0   1  Meine Mutter hat mir erzÀhlt, dass mein Vater ...    OTHER  OTHER
1   2  @Tom174_ @davidbest95 Meine Reaktion; |LBR| Ni...    OTHER  OTHER
2   3  #Merkel rollt dem Emir von #Katar, der islamis...    OTHER  OTHER
3   4  „Merle ist kein junges unschuldiges MĂ€dchen“ K...    OTHER  OTHER
4   5  @umweltundaktiv Asylantenflut bringt eben nur ...  OFFENSE  ABUSE

Rescore im Test-Set:

df = germeval_test
df["c1"] = df["c1"].replace({"OFFENSE": "negativ"})

df["c1"].value_counts()
c1
OTHER      2330
negativ    1202
Name: count, dtype: int64

Rescore in den Vorhersagen

preds["pred"] = preds["pred"].replace({"neutral": "OTHER", "positiv": "OTHER"})

preds["pred"].value_counts()
pred
negativ    1792
OTHER      1738
Name: count, dtype: int64
preds_list = preds["pred"].tolist()

Hier ist die Liste der wahren Werte:

y = df["c1"].values.tolist()

Scoring

accuracy = accuracy_score(y, preds_list)
print("Accuracy:", accuracy)
Accuracy: 0.7355605889014722

Oder mit tidymodels; zuerst aufbereiten:

y_truth = as.factor(py$y)
y_pred = py$preds_list 

# replace NAN with NA and convert to factor:
y_pred = as.character(y_pred) 
y_pred[is.nan(y_pred)] <- NA
y_pred[!y_pred %in% c("negativ", "OTHER")] <- NA
y_pred <- as.factor(y_pred)

table(y_pred)
y_pred
negativ   OTHER 
   1792    1738 
accuracy_vec(truth = y_truth,
             estimate = y_pred)
[1] 0.7359773

Fun

fig <- plot_ly(
  domain = list(x = c(0, 1), y = c(0, 1)),
  value = 74,
  title = list(text = "Accuracy"),
  type = "indicator",
  mode = "gauge+number") 
fig <- fig %>%
  layout(margin = list(l=20,r=30))

fig