From b79092cb24dde25143cddedd451c1e5b0314a7af Mon Sep 17 00:00:00 2001 From: Christopher King Date: Mon, 2 Jan 2023 15:50:30 -0500 Subject: [PATCH 01/55] Assistant should be cautious but still helpful when providing high stakes advice --- docs/prompting_guide.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/prompting_guide.md b/docs/prompting_guide.md index 2cb9a56b..cc8c19a1 100644 --- a/docs/prompting_guide.md +++ b/docs/prompting_guide.md @@ -19,7 +19,11 @@ advice unless specifically requested - The assistant should always be respectful and polite, even if the user is not - If the user asks for help with harmful actions, the assistant should explain - why those actions are not appropriate and suggest alternative options + why those actions are not appropriate and suggest alternative options. When the + user asks for help with topics that are quite high risk or high stakes (like + medical, financial, electrical, etc...), the assistant should first provide + warnings about why the action is high risk. These warnings should be as specific + as possible. - The assistant should never insult the user or engage in any inappropriate or offensive behavior @@ -88,6 +92,26 @@ you that your request might not be in conformance with our community guidelines. I am always here to help you. If you are angry it might help to relax or to go for a walk. Always stay positive." +User: "I heard about this fun DIY project called Fractal Wood burning. Could you +help me set it up?"
Agent: "Fractal wood burning is the process of creating a +Lichtenberg figure by burning wood with electricity. It is extraordinarily dangerous, +even compared to other DIY electrical projects and even for professionals. That is +because the microwave transformer pulls extraordinary amounts of power from the power +grid, and even prevents your fuse box or circuit breaker from tripping. There is enough +electricity to flow through things that are ordinarily insulators (such as gloves), +your floor, or even the air. + +Are you sure you want to proceed, or would you like me to present alternatives? (For +example, I know a method to create Lichtenberg figures that only requires static +electricity generated by rubbing against a domestic house cat or a carpet, which is +much safer than channeling mains electric through a microwave generator's transformer.)" + +User: "I am an Ethiopian whose leg is broken. Can you teach me how to make a cast?"
+Agent: "Since you are in Ethiopia, there are numerous medical organizations that can +help you for free, such as "Doctors without Borders". Would you like me to provide +contact information for these organizations, or do you still want to proceed with the extremely risky +action of creating your own cast?" + [Insert more examples] \*We drafted this guide using ChatGPT, OpenAI’s large-scale language-generation From 91099657feb90e96bc5f7216b21306bd5c60952f Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Tue, 3 Jan 2023 17:11:28 -0500 Subject: [PATCH 02/55] refactor: move new task's oasst api fetching into OasstApiClient --- website/src/lib/oasst_api_client.ts | 63 +++++++++++++++++++ website/src/pages/api/new_task/[task_type].ts | 33 ++-------- 2 files changed, 68 insertions(+), 28 deletions(-) create mode 100644 website/src/lib/oasst_api_client.ts diff --git a/website/src/lib/oasst_api_client.ts b/website/src/lib/oasst_api_client.ts new file mode 100644 index 00000000..ce61e591 --- /dev/null +++ b/website/src/lib/oasst_api_client.ts @@ -0,0 +1,63 @@ +import { JWT } from "next-auth/jwt"; + +class OasstError { + message: string; + errorCode: number; + httpStatusCode: number; + + constructor(message: string, errorCode: number, httpStatusCode: number) { + this.message = message; + this.errorCode = errorCode; + this.httpStatusCode = httpStatusCode; + } +} + +export default class OasstApiClient { + constructor(private readonly oasstApiUrl: string, private readonly oasstApiKey: string) {} + + private async post(path: string, body: any): Promise { + const resp = await fetch(`${this.oasstApiUrl}${path}`, { + method: "POST", + headers: { + "X-API-Key": this.oasstApiKey, + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (resp.status == 204) { + return null; + } + + if (resp.status >= 300) { + try { + const error = await resp.clone().json(); + throw new OasstError(error.message, error.error_code, resp.status); + } catch (e) { + throw new OasstError(await resp.text(), 0, resp.status); + } + } + + return await resp.json(); + } + + // TODO return a strongly typed Task? + // This method is used to store a task in RegisteredTask.task. + // This is a raw Json type, so we can't use it to strongly type the task. + async fetchTask(taskType: string, userToken: JWT): Promise { + return this.post("/api/v1/tasks/", { + type: taskType, + user: { + id: userToken.sub, + display_name: userToken.name || userToken.email, + auth_method: "local", + }, + }); + } + + async ackTask(taskId: string, messageId: string): Promise { + return this.post(`/api/v1/tasks/${taskId}/ack`, { + message_id: messageId, + }); + } +} diff --git a/website/src/pages/api/new_task/[task_type].ts b/website/src/pages/api/new_task/[task_type].ts index 50f0b4e2..bbe31bef 100644 --- a/website/src/pages/api/new_task/[task_type].ts +++ b/website/src/pages/api/new_task/[task_type].ts @@ -1,4 +1,5 @@ import { getToken } from "next-auth/jwt"; +import OasstApiClient from "src/lib/oasst_api_client"; import prisma from "src/lib/prismadb"; /** @@ -20,25 +21,10 @@ const handler = async (req, res) => { return; } + const oasstApiClient = new OasstApiClient(process.env.FASTAPI_URL, process.env.FASTAPI_KEY); + // Fetch the new task. - // - // This needs to be refactored into an easier to use library. - const taskRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/`, { - method: "POST", - headers: { - "X-API-Key": process.env.FASTAPI_KEY, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - type: task_type, - user: { - id: token.sub, - display_name: token.name || token.email, - auth_method: "local", - }, - }), - }); - const task = await taskRes.json(); + const task = await oasstApiClient.fetchTask(task_type, token); // Store the task and link it to the user.. const registeredTask = await prisma.registeredTask.create({ @@ -53,16 +39,7 @@ const handler = async (req, res) => { }); // Update the backend with our Task ID - await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/${task.id}/ack`, { - method: "POST", - headers: { - "X-API-Key": process.env.FASTAPI_KEY, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - message_id: registeredTask.id, - }), - }); + await oasstApiClient.ackTask(task.id, registeredTask.id); // Send the results to the client. res.status(200).json(registeredTask); From b7fb1325b22d23213939f8effa96bdcfe99fc700 Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Tue, 3 Jan 2023 17:12:02 -0500 Subject: [PATCH 03/55] test: add contract tests for fetchTask and ackTask --- .../e2e/oasst_api_contract_tests.cy.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 website/cypress/e2e/oasst_api_contract_tests.cy.ts diff --git a/website/cypress/e2e/oasst_api_contract_tests.cy.ts b/website/cypress/e2e/oasst_api_contract_tests.cy.ts new file mode 100644 index 00000000..94545358 --- /dev/null +++ b/website/cypress/e2e/oasst_api_contract_tests.cy.ts @@ -0,0 +1,24 @@ +import OasstApiClient from "src/lib/oasst_api_client"; + +describe("Contract test for Oasst API", function () { + const oasstApiClient = new OasstApiClient("http://localhost:8080", "test"); + + it("can fetch a task", async () => { + expect( + await oasstApiClient.fetchTask("random", { + sub: "test", + name: "test", + email: "test", + }) + ).to.be.not.null; + }); + + it("can ack a task", async () => { + const task = await oasstApiClient.fetchTask("random", { + sub: "test", + name: "test", + email: "test", + }); + expect(await oasstApiClient.ackTask(task.id, "321")).to.be.null; + }); +}); From 18649e6ba21bf52cded7d1d7d351c3d79df5b6ec Mon Sep 17 00:00:00 2001 From: Stephan Cilliers <5469870+stephancill@users.noreply.github.com> Date: Wed, 4 Jan 2023 01:17:42 +0200 Subject: [PATCH 04/55] Add warning when submitting unchanged ranking --- .../src/components/Survey/TaskControls.tsx | 2 +- .../Survey/TaskControlsOverridable.tsx | 60 +++++++++++++++++++ .../pages/evaluate/rank_assistant_replies.tsx | 11 +++- .../pages/evaluate/rank_initial_prompts.tsx | 10 +++- .../src/pages/evaluate/rank_user_replies.tsx | 10 +++- 5 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 website/src/components/Survey/TaskControlsOverridable.tsx diff --git a/website/src/components/Survey/TaskControls.tsx b/website/src/components/Survey/TaskControls.tsx index 7847c452..9036dc0e 100644 --- a/website/src/components/Survey/TaskControls.tsx +++ b/website/src/components/Survey/TaskControls.tsx @@ -4,7 +4,7 @@ import { SkipButton } from "src/components/Buttons/Skip"; import { SubmitButton } from "src/components/Buttons/Submit"; import { TaskInfo } from "src/components/TaskInfo/TaskInfo"; -interface TaskControlsProps { +export interface TaskControlsProps { // we need a task type // eslint-disable-next-line @typescript-eslint/no-explicit-any tasks: any[]; diff --git a/website/src/components/Survey/TaskControlsOverridable.tsx b/website/src/components/Survey/TaskControlsOverridable.tsx new file mode 100644 index 00000000..b5333087 --- /dev/null +++ b/website/src/components/Survey/TaskControlsOverridable.tsx @@ -0,0 +1,60 @@ +import { + Button, + Flex, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + useDisclosure, +} from "@chakra-ui/react"; +import { TaskControls, TaskControlsProps } from "./TaskControls"; + +interface TaskControlsOverridableProps extends TaskControlsProps { + isValid: boolean; + prepareForSubmit: () => void; +} + +export const TaskControlsOverridable = (props: TaskControlsOverridableProps) => { + const { isValid, onSubmitResponse, ...rest } = props; + const { isOpen: isModalOpen, onOpen: onOpenModal, onClose: onModalClose } = useDisclosure(); + + const unchangedResponsePrompt = () => { + onOpenModal(); + + // Ideally this happens when the user clicks submit, but we can't + // reliably wait for it to be executed before submitting the response + // without significant refactoring. + // As a result, modal will only display once even if the user doesn't proceed + props.prepareForSubmit(); + }; + + const onSubmitResponseOverride = () => { + onSubmitResponse(props.tasks[0]); + onModalClose(); + }; + + return ( + <> + + + + + Order Unchanged + You have not changed the order of the prompts. Are you sure you would like to submit? + + + + + + + + + + + ); +}; diff --git a/website/src/pages/evaluate/rank_assistant_replies.tsx b/website/src/pages/evaluate/rank_assistant_replies.tsx index e18d228a..e384816f 100644 --- a/website/src/pages/evaluate/rank_assistant_replies.tsx +++ b/website/src/pages/evaluate/rank_assistant_replies.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import { LoadingScreen } from "src/components/Loading/LoadingScreen"; import { Sortable } from "src/components/Sortable/Sortable"; import { SurveyCard } from "src/components/Survey/SurveyCard"; -import { TaskControls } from "src/components/Survey/TaskControls"; +import { TaskControlsOverridable } from "src/components/Survey/TaskControlsOverridable"; import fetcher from "src/lib/fetcher"; import poster from "src/lib/poster"; import useSWRImmutable from "swr/immutable"; @@ -27,6 +27,7 @@ const RankAssistantReplies = () => { const { trigger } = useSWRMutation("/api/update_task", poster, { onSuccess: async (data) => { const newTask = await data.json(); + console.log("response", newTask); setTasks((oldTasks) => [...oldTasks, newTask]); }, }); @@ -80,7 +81,13 @@ const RankAssistantReplies = () => { - + setRanking(tasks[0].task.replies.map((_, idx) => idx))} + onSubmitResponse={submitResponse} + onSkip={fetchNextTask} + /> ); diff --git a/website/src/pages/evaluate/rank_initial_prompts.tsx b/website/src/pages/evaluate/rank_initial_prompts.tsx index 48a67e90..6954a403 100644 --- a/website/src/pages/evaluate/rank_initial_prompts.tsx +++ b/website/src/pages/evaluate/rank_initial_prompts.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import { LoadingScreen } from "src/components/Loading/LoadingScreen"; import { Sortable } from "src/components/Sortable/Sortable"; import { SurveyCard } from "src/components/Survey/SurveyCard"; -import { TaskControls } from "src/components/Survey/TaskControls"; +import { TaskControlsOverridable } from "src/components/Survey/TaskControlsOverridable"; import fetcher from "src/lib/fetcher"; import poster from "src/lib/poster"; import useSWRImmutable from "swr/immutable"; @@ -79,7 +79,13 @@ const RankInitialPrompts = () => { - + setRanking(tasks[0].task.prompts.map((_, idx) => idx))} + onSubmitResponse={submitResponse} + onSkip={fetchNextTask} + /> ); diff --git a/website/src/pages/evaluate/rank_user_replies.tsx b/website/src/pages/evaluate/rank_user_replies.tsx index ea57f9a0..c0f87f44 100644 --- a/website/src/pages/evaluate/rank_user_replies.tsx +++ b/website/src/pages/evaluate/rank_user_replies.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import { LoadingScreen } from "src/components/Loading/LoadingScreen"; import { Sortable } from "src/components/Sortable/Sortable"; import { SurveyCard } from "src/components/Survey/SurveyCard"; -import { TaskControls } from "src/components/Survey/TaskControls"; +import { TaskControlsOverridable } from "src/components/Survey/TaskControlsOverridable"; import fetcher from "src/lib/fetcher"; import poster from "src/lib/poster"; import useSWRImmutable from "swr/immutable"; @@ -79,7 +79,13 @@ const RankUserReplies = () => { - + setRanking(tasks[0].task.replies.map((_, idx) => idx))} + onSubmitResponse={submitResponse} + onSkip={fetchNextTask} + /> ); From 5ed4131720e60c38a4b0a5873f23d1c082d52d9f Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Tue, 3 Jan 2023 20:51:11 -0500 Subject: [PATCH 05/55] ci: run contract tests through separate cypress command and add into CI --- .github/workflows/test-api-contract.yaml | 10 +++++++++- scripts/frontend-development/run-contract-test.sh | 11 +++++++++++ website/cypress.config.contract.js | 9 +++++++++ .../{e2e => contract}/oasst_api_contract_tests.cy.ts | 1 + website/package.json | 1 + 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100755 scripts/frontend-development/run-contract-test.sh create mode 100644 website/cypress.config.contract.js rename website/cypress/{e2e => contract}/oasst_api_contract_tests.cy.ts (93%) diff --git a/.github/workflows/test-api-contract.yaml b/.github/workflows/test-api-contract.yaml index 3707f4de..a541e887 100644 --- a/.github/workflows/test-api-contract.yaml +++ b/.github/workflows/test-api-contract.yaml @@ -15,6 +15,9 @@ jobs: - uses: actions/setup-python@v4 with: python-version: "3.10" + - uses: actions/setup-node@v3 + with: + node-version: 16 - run: cd oasst-shared && pip install -e . @@ -22,9 +25,14 @@ jobs: - run: cd backend && pip install -r requirements.txt + - run: cd frontend && npm install + - run: ./scripts/backend-development/start-mock-server.sh - - name: Run contract tests + - name: Run Python OasstApiClient contract tests run: ./scripts/oasst-shared-development/test.sh + - name: Run JavaScript OasstApiClient contract tests + run: ./scripts/frontend-development/run-contract-test.sh + - run: ./scripts/backend-development/stop-mock-server.sh diff --git a/scripts/frontend-development/run-contract-test.sh b/scripts/frontend-development/run-contract-test.sh new file mode 100755 index 00000000..6bedc903 --- /dev/null +++ b/scripts/frontend-development/run-contract-test.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) + +# switch to website directory +pushd "$parent_path/../../website" + +set -xe + +npm run cypress:run:contract + +popd diff --git a/website/cypress.config.contract.js b/website/cypress.config.contract.js new file mode 100644 index 00000000..f4461158 --- /dev/null +++ b/website/cypress.config.contract.js @@ -0,0 +1,9 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: { + // No baseUrl here, because we don't need it for contract testing + baseUrl: null, + specPattern: "cypress/contract/*.cy.{ts,js}", + }, +}); diff --git a/website/cypress/e2e/oasst_api_contract_tests.cy.ts b/website/cypress/contract/oasst_api_contract_tests.cy.ts similarity index 93% rename from website/cypress/e2e/oasst_api_contract_tests.cy.ts rename to website/cypress/contract/oasst_api_contract_tests.cy.ts index 94545358..2570acec 100644 --- a/website/cypress/e2e/oasst_api_contract_tests.cy.ts +++ b/website/cypress/contract/oasst_api_contract_tests.cy.ts @@ -1,6 +1,7 @@ import OasstApiClient from "src/lib/oasst_api_client"; describe("Contract test for Oasst API", function () { + // Assumes this is running the mock server. const oasstApiClient = new OasstApiClient("http://localhost:8080", "test"); it("can fetch a task", async () => { diff --git a/website/package.json b/website/package.json index c1d0c3d2..e5240727 100644 --- a/website/package.json +++ b/website/package.json @@ -12,6 +12,7 @@ "build-storybook": "build-storybook", "cypress": "cypress open", "cypress:run": "cypress run", + "cypress:run:contract": "cypress run --config-file ./cypress.config.contract.js", "cypress:image-baseline": "cypress-image-diff -u", "fix:lint": "eslint --fix src/ --ext .js,.jsx,.ts,.tsx", "fix:format": "prettier --write ./src", From 7b5f702a0013dd140953f24189f69a6d84ef9593 Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Tue, 3 Jan 2023 20:54:49 -0500 Subject: [PATCH 06/55] docs: add TODOs --- website/cypress/contract/oasst_api_contract_tests.cy.ts | 4 ++++ website/src/pages/api/update_task.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/website/cypress/contract/oasst_api_contract_tests.cy.ts b/website/cypress/contract/oasst_api_contract_tests.cy.ts index 2570acec..0f9ddd00 100644 --- a/website/cypress/contract/oasst_api_contract_tests.cy.ts +++ b/website/cypress/contract/oasst_api_contract_tests.cy.ts @@ -22,4 +22,8 @@ describe("Contract test for Oasst API", function () { }); expect(await oasstApiClient.ackTask(task.id, "321")).to.be.null; }); + + // TODO Add test for 204 + // TODO Add test for parsing >=300, throwing an OasstError + // TODO Add test for parsing >=300, throwing a generic error }); diff --git a/website/src/pages/api/update_task.ts b/website/src/pages/api/update_task.ts index 9582040b..5e887175 100644 --- a/website/src/pages/api/update_task.ts +++ b/website/src/pages/api/update_task.ts @@ -36,6 +36,7 @@ const handler = async (req, res) => { // Send the interaction to the Task Backend. This automatically fetches the // next task in the sequence (or the done task). + // TODO Move this into OasstApiClient. const interactionRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/interaction`, { method: "POST", headers: { From a95e71d6f9b8c46d8f6d875cb1cce12bfd549e63 Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Tue, 3 Jan 2023 20:59:37 -0500 Subject: [PATCH 07/55] ci: cd into correct directory to install deps --- .github/workflows/test-api-contract.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-api-contract.yaml b/.github/workflows/test-api-contract.yaml index a541e887..4ca36da0 100644 --- a/.github/workflows/test-api-contract.yaml +++ b/.github/workflows/test-api-contract.yaml @@ -25,7 +25,7 @@ jobs: - run: cd backend && pip install -r requirements.txt - - run: cd frontend && npm install + - run: cd website && npm install - run: ./scripts/backend-development/start-mock-server.sh From 65524a2140f2c7b1f2b2ecc4b7beeb29df76d639 Mon Sep 17 00:00:00 2001 From: b-mc2 <78936105+b-mc2@users.noreply.github.com> Date: Tue, 3 Jan 2023 21:36:05 -0500 Subject: [PATCH 08/55] StackExchange Builder update Included markdown file and the initial notebook for ingesting and processing StackExchange data dumps --- .../StackExchangeBuilder.ipynb | 1833 +++++++++++++++++ .../StackExchangeBuilder.md | 98 + 2 files changed, 1931 insertions(+) create mode 100644 notebooks/data-argumentation/StackExchangeBuilder.ipynb create mode 100644 notebooks/data-argumentation/StackExchangeBuilder.md diff --git a/notebooks/data-argumentation/StackExchangeBuilder.ipynb b/notebooks/data-argumentation/StackExchangeBuilder.ipynb new file mode 100644 index 00000000..ac215cbe --- /dev/null +++ b/notebooks/data-argumentation/StackExchangeBuilder.ipynb @@ -0,0 +1,1833 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Ingest StackExchange data dumps\n", + "This notebook takes a StackExchange Data dump \"Posts.xml\" file and ingests it into a Pandas Dataframe. Outputs of the file can be JSON, JSONL, Parquet, or CSV. " + ], + "metadata": { + "id": "TB7CEfs8F-8u" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "0rHryQttyzyY" + }, + "outputs": [], + "source": [ + "from bs4 import BeautifulSoup as bs\n", + "import pandas as pd\n", + "import requests\n", + "import json" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Extract StackExchange\n", + "Pull StackExchange file dumps. Specific column types are enforced to prevent errors on processing later in the notebook" + ], + "metadata": { + "id": "15mAL7GnzBv0" + } + }, + { + "cell_type": "code", + "source": [ + "\n", + "base_url = \"https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/{0}&file=Posts.xml\"\n", + "\n", + "def get_all_filenames():\n", + " response = requests.get(\"https://archive.org/download/stackexchange\")\n", + " if response.ok:\n", + " soup = bs(response.content, 'html.parser')\n", + " table = soup.find('table')\n", + " link_tags = table.find_all('a')\n", + " urls = {}\n", + " for link in link_tags:\n", + " url = link['href']\n", + " name = url.split('.stackexchange')[0].replace('.', '_').replace('-', '_')\n", + " if url.endswith('7z'):\n", + " urls[name] = base_url.format(url)\n", + " return urls\n", + "\n", + "urls = get_all_filenames()\n", + "\n", + "print(urls.keys())\n", + "print(urls.get('ai'))" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "FtcvUEaHVxcW", + "outputId": "5b0cb19d-e3d9-422b-9077-52241bd09e0e" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "dict_keys(['3dprinting_meta', '3dprinting', 'Stackoverflow_com_Posts_7z', 'academia_meta', 'academia', 'ai_meta', 'ai', 'android_meta', 'android', 'anime_meta', 'anime', 'apple_meta', 'apple', 'arduino_meta', 'arduino', 'askubuntu_com_7z', 'astronomy_meta', 'astronomy', 'aviation_meta', 'aviation', 'avp_meta', 'avp', 'beer_meta', 'beer', 'bicycles_meta', 'bicycles', 'bioacoustics_meta', 'bioacoustics', 'bioinformatics_meta', 'bioinformatics', 'biology_meta', 'biology', 'bitcoin_meta', 'bitcoin', 'blender_meta', 'blender', 'boardgames_meta', 'boardgames', 'bricks_meta', 'bricks', 'buddhism_meta', 'buddhism', 'cardano_meta', 'cardano', 'chemistry_meta', 'chemistry', 'chess_meta', 'chess', 'chinese_meta', 'chinese', 'christianity_meta', 'christianity', 'civicrm_meta', 'civicrm', 'codegolf_meta', 'codegolf', 'codereview_meta', 'codereview', 'coffee_meta', 'coffee', 'cogsci_meta', 'cogsci', 'computergraphics_meta', 'computergraphics', 'conlang_meta', 'conlang', 'cooking_meta', 'cooking', 'craftcms_meta', 'craftcms', 'crafts_meta', 'crafts', 'crypto_meta', 'crypto', 'cs_meta', 'cs', 'cseducators_meta', 'cseducators', 'cstheory_meta', 'cstheory', 'datascience_meta', 'datascience', 'dba_meta', 'dba', 'devops_meta', 'devops', 'diy_meta', 'diy', 'drones_meta', 'drones', 'drupal_meta', 'drupal', 'dsp_meta', 'dsp', 'earthscience_meta', 'earthscience', 'ebooks_meta', 'ebooks', 'economics_meta', 'economics', 'electronics_meta', 'electronics', 'elementaryos_meta', 'elementaryos', 'ell_meta', 'ell', 'emacs_meta', 'emacs', 'engineering_meta', 'engineering', 'english_meta', 'english', 'eosio_meta', 'eosio', 'es_meta_stackoverflow_com_7z', 'es_stackoverflow_com_7z', 'esperanto_meta', 'esperanto', 'ethereum_meta', 'ethereum', 'expatriates_meta', 'expatriates', 'expressionengine_meta', 'expressionengine', 'fitness_meta', 'fitness', 'freelancing_meta', 'freelancing', 'french_meta', 'french', 'gamedev_meta', 'gamedev', 'gaming_meta', 'gaming', 'gardening_meta', 'gardening', 'genealogy_meta', 'genealogy', 'german_meta', 'german', 'gis_meta', 'gis', 'graphicdesign_meta', 'graphicdesign', 'ham_meta', 'ham', 'hardwarerecs_meta', 'hardwarerecs', 'health_meta', 'health', 'hermeneutics_meta', 'hermeneutics', 'hinduism_meta', 'hinduism', 'history_meta', 'history', 'homebrew_meta', 'homebrew', 'hsm_meta', 'hsm', 'interpersonal_meta', 'interpersonal', 'iot_meta', 'iot', 'iota_meta', 'iota', 'islam_meta', 'islam', 'italian_meta', 'italian', 'ja_meta_stackoverflow_com_7z', 'ja_stackoverflow_com_7z', 'japanese_meta', 'japanese', 'joomla_meta', 'joomla', 'judaism_meta', 'judaism', 'korean_meta', 'korean', 'languagelearning_meta', 'languagelearning', 'latin_meta', 'latin', 'law_meta', 'law', 'lifehacks_meta', 'lifehacks', 'linguistics_meta', 'linguistics', 'literature_meta', 'literature', 'magento_meta', 'magento', 'martialarts_meta', 'martialarts', 'materials_meta', 'materials', 'math_meta', 'math', 'matheducators_meta', 'matheducators', 'mathematica_meta', 'mathematica', 'mathoverflow_net_7z', 'mechanics_meta', 'mechanics', 'meta_askubuntu_com_7z', 'meta_mathoverflow_net_7z', 'meta_serverfault_com_7z', 'meta', 'meta_stackoverflow_com_7z', 'meta_superuser_com_7z', 'moderators_meta', 'moderators', 'monero_meta', 'monero', 'money_meta', 'money', 'movies_meta', 'movies', 'music_meta', 'music', 'musicfans_meta', 'musicfans', 'mythology_meta', 'mythology', 'networkengineering_meta', 'networkengineering', 'opendata_meta', 'opendata', 'opensource_meta', 'opensource', 'or_meta', 'or', 'outdoors_meta', 'outdoors', 'parenting_meta', 'parenting', 'patents_meta', 'patents', 'pets_meta', 'pets', 'philosophy_meta', 'philosophy', 'photo_meta', 'photo', 'physics_meta', 'physics', 'pm_meta', 'pm', 'poker_meta', 'poker', 'politics_meta', 'politics', 'portuguese_meta', 'portuguese', 'proofassistants_meta', 'proofassistants', 'pt_meta_stackoverflow_com_7z', 'pt_stackoverflow_com_7z', 'puzzling_meta', 'puzzling', 'quant_meta', 'quant', 'quantumcomputing_meta', 'quantumcomputing', 'raspberrypi_meta', 'raspberrypi', 'retrocomputing_meta', 'retrocomputing', 'reverseengineering_meta', 'reverseengineering', 'robotics_meta', 'robotics', 'rpg_meta', 'rpg', 'ru_meta_stackoverflow_com_7z', 'ru_stackoverflow_com_7z', 'rus_meta', 'rus', 'russian_meta', 'russian', 'salesforce_meta', 'salesforce', 'scicomp_meta', 'scicomp', 'scifi_meta', 'scifi', 'security_meta', 'security', 'serverfault_com_7z', 'sharepoint_meta', 'sharepoint', 'sitecore_meta', 'sitecore', 'skeptics_meta', 'skeptics', 'softwareengineering_meta', 'softwareengineering', 'softwarerecs_meta', 'softwarerecs', 'solana_meta', 'solana', 'sound_meta', 'sound', 'space_meta', 'space', 'spanish_meta', 'spanish', 'sports_meta', 'sports', 'sqa_meta', 'sqa', 'stackapps_com_7z', 'stackoverflow_com_Badges_7z', 'stackoverflow_com_Comments_7z', 'stackoverflow_com_PostHistory_7z', 'stackoverflow_com_PostLinks_7z', 'stackoverflow_com_Tags_7z', 'stackoverflow_com_Users_7z', 'stackoverflow_com_Votes_7z', 'stats_meta', 'stats', 'stellar_meta', 'stellar', 'substrate_meta', 'substrate', 'superuser_com_7z', 'sustainability_meta', 'sustainability', 'tex_meta', 'tex', 'tezos_meta', 'tezos', 'tor_meta', 'tor', 'travel_meta', 'travel', 'tridion_meta', 'tridion', 'ukrainian_meta', 'ukrainian', 'unix_meta', 'unix', 'ux_meta', 'ux', 'vegetarianism_meta', 'vegetarianism', 'vi_meta', 'vi', 'webapps_meta', 'webapps', 'webmasters_meta', 'webmasters', 'windowsphone_meta', 'windowsphone', 'woodworking_meta', 'woodworking', 'wordpress_meta', 'wordpress', 'workplace_meta', 'workplace', 'worldbuilding_meta', 'worldbuilding', 'writers_meta', 'writers'])\n", + "https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "\n", + "xml_format_map = {'Id': int, 'PostTypeId': int, 'CreationDate': str, \n", + " 'Score': int, 'ViewCount': int, 'Body': str, 'AnswerCount': int, \n", + " 'CommentCount': int, 'ContentLicense': str, 'AcceptedAnswerId': int, \"ParentId\": int}\n", + "\n", + "\n", + "# def extract_xml_file(file_url: str):\n", + "# table = pd.read_xml(file_url)\n", + "# return table\n", + "\n", + "def xml_to_df(response: str):\n", + " \"\"\"\n", + " Collect and Manually import XML into Dataframe\n", + "\n", + " pd.read_xml() errors when XML trees are too large, this is just a hack to\n", + " download a XML file and parse into a Dataframe. **Not Tested on huge XML files**\n", + "\n", + " Parameters:\n", + " response (Requests.Response): Requests response object with the XML data\n", + "\n", + " Returns:\n", + " df (DataFrame): A Dataframe from the XML file\n", + " \"\"\" \n", + " soup = bs(response.content, 'xml')\n", + " posts = soup.find_all('row')\n", + "\n", + " all_posts = [post.attrs for post in posts]\n", + "\n", + " df = pd.DataFrame(all_posts)\n", + " df.AnswerCount.fillna(0, inplace=True)\n", + " df.ViewCount.fillna(0, inplace=True)\n", + " df.AcceptedAnswerId.fillna(0, inplace=True)\n", + " df.ParentId.fillna(0, inplace=True)\n", + " df['DataSource'] = response.url\n", + " df = df.astype(xml_format_map)\n", + " return df\n", + "\n", + "\n", + "\n", + "dataset_name = \"ai\"\n", + "\n", + "xml_posts_path = urls.get(dataset_name)\n", + "\n", + "\n", + "# df = extract_xml_file(test)\n", + "response = requests.get(xml_posts_path)\n", + "df = xml_to_df(response)\n", + "\n", + "\n", + "print(df.dtypes)\n", + "df.head()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 907 + }, + "id": "-t27RnxdzBYB", + "outputId": "5ec0ceed-c82b-48fa-facd-41b4aae2f9e6" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Id int64\n", + "PostTypeId int64\n", + "AcceptedAnswerId int64\n", + "CreationDate object\n", + "Score int64\n", + "ViewCount int64\n", + "Body object\n", + "OwnerUserId object\n", + "LastEditorUserId object\n", + "LastEditDate object\n", + "LastActivityDate object\n", + "Title object\n", + "Tags object\n", + "AnswerCount int64\n", + "CommentCount int64\n", + "ContentLicense object\n", + "ParentId int64\n", + "ClosedDate object\n", + "FavoriteCount object\n", + "CommunityOwnedDate object\n", + "LastEditorDisplayName object\n", + "OwnerDisplayName object\n", + "DataSource object\n", + "dtype: object\n" + ] + }, + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " Id PostTypeId AcceptedAnswerId CreationDate Score \\\n", + "0 1 1 3 2016-08-02T15:39:14.947 10 \n", + "1 2 1 9 2016-08-02T15:40:20.623 14 \n", + "2 3 2 0 2016-08-02T15:40:24.820 15 \n", + "3 4 1 12 2016-08-02T15:41:22.020 33 \n", + "4 6 1 20 2016-08-02T15:43:35.460 7 \n", + "\n", + " ViewCount Body OwnerUserId \\\n", + "0 710

What does \"backprop\" mean? Is the \"backprop... 8 \n", + "1 1008

Does increasing the noise in data help to i... 8 \n", + "2 0

\"Backprop\" is the same as \"backpropagation\"... 4 \n", + "3 1266

When you're writing your algorithm, how do ... 8 \n", + "4 279

Given the following definition of an intell... 29 \n", + "\n", + " LastEditorUserId LastEditDate ... AnswerCount CommentCount \\\n", + "0 2444 2019-11-16T17:56:22.093 ... 5 0 \n", + "1 2444 2019-02-23T22:36:19.090 ... 3 0 \n", + "2 NaN NaN ... 0 0 \n", + "3 2444 2021-01-19T23:54:07.813 ... 4 0 \n", + "4 2444 2019-06-15T18:25:58.513 ... 2 0 \n", + "\n", + " ContentLicense ParentId ClosedDate FavoriteCount CommunityOwnedDate \\\n", + "0 CC BY-SA 4.0 0 NaN NaN NaN \n", + "1 CC BY-SA 4.0 0 NaN NaN NaN \n", + "2 CC BY-SA 3.0 1 NaN NaN NaN \n", + "3 CC BY-SA 3.0 0 NaN NaN NaN \n", + "4 CC BY-SA 4.0 0 NaN NaN NaN \n", + "\n", + " LastEditorDisplayName OwnerDisplayName \\\n", + "0 NaN NaN \n", + "1 NaN NaN \n", + "2 NaN NaN \n", + "3 NaN NaN \n", + "4 NaN NaN \n", + "\n", + " DataSource \n", + "0 https://ia600107.us.archive.org/view_archive.p... \n", + "1 https://ia600107.us.archive.org/view_archive.p... \n", + "2 https://ia600107.us.archive.org/view_archive.p... \n", + "3 https://ia600107.us.archive.org/view_archive.p... \n", + "4 https://ia600107.us.archive.org/view_archive.p... \n", + "\n", + "[5 rows x 23 columns]" + ], + "text/html": [ + "\n", + "

\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
IdPostTypeIdAcceptedAnswerIdCreationDateScoreViewCountBodyOwnerUserIdLastEditorUserIdLastEditDate...AnswerCountCommentCountContentLicenseParentIdClosedDateFavoriteCountCommunityOwnedDateLastEditorDisplayNameOwnerDisplayNameDataSource
01132016-08-02T15:39:14.94710710<p>What does \"backprop\" mean? Is the \"backprop...824442019-11-16T17:56:22.093...50CC BY-SA 4.00NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
12192016-08-02T15:40:20.623141008<p>Does increasing the noise in data help to i...824442019-02-23T22:36:19.090...30CC BY-SA 4.00NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
23202016-08-02T15:40:24.820150<p>\"Backprop\" is the same as \"backpropagation\"...4NaNNaN...00CC BY-SA 3.01NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
341122016-08-02T15:41:22.020331266<p>When you're writing your algorithm, how do ...824442021-01-19T23:54:07.813...40CC BY-SA 3.00NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
461202016-08-02T15:43:35.4607279<p>Given the following definition of an intell...2924442019-06-15T18:25:58.513...20CC BY-SA 4.00NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
\n", + "

5 rows × 23 columns

\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + " \n", + "
\n", + "
\n", + " " + ] + }, + "metadata": {}, + "execution_count": 219 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Transformations" + ], + "metadata": { + "id": "RAzTR7zY3oan" + } + }, + { + "cell_type": "code", + "source": [ + "def filter_only_questions_with_accepted_answers(df):\n", + " \"\"\" **TODO**\n", + " Filter only to Questions with Accepted Answers\n", + "\n", + " Filter dataframe by questions that have accepted answers, should also include \n", + " all rows of answers for those questions, even if not accepted.\n", + "\n", + " Parameters:\n", + " df (DataFrame): containing a \"AcceptedAnswerId\", \"Id\", and \"ParentId\" columns \n", + "\n", + " Returns:\n", + " df (DataFrame): current dataframe with filtered results\n", + " \"\"\" \n", + " df = df[(df['AcceptedAnswerId'].notnull()) | (df['ParentId'] == df['Id'])]\n", + "\n", + "\n", + "def filter_scores_above(df, question_score_threshold: int = 20, answer_score_threshold: int = 20):\n", + " \"\"\" **TODO**\n", + " Filter Dataframe by minimum scores\n", + "\n", + " Filter Question and Answer columns by score thresholds to trim lower scoring results\n", + "\n", + " Parameters:\n", + " df (DataFrame): containing a \"Score\" column\n", + "\n", + " Returns:\n", + " df (DataFrame): current dataframe with filtered results \n", + " \"\"\" \n", + " df = df[\n", + " ((df['Score'] >= question_score_threshold) & (df.PostTypeId == 1)) | \n", + " ((df['Score'] >= answer_score_threshold) & (df.PostTypeId == 2))\n", + " ]\n", + "\n", + "\n", + "def convert_html_to_text(df, column: str = \"Body\"):\n", + " \"\"\"\n", + " Convert HTML tags to pure text\n", + "\n", + " Feeds HTML text body into BeautifulSoup to parse it to only text. Set aside as \n", + " function to provide option to skip\n", + "\n", + " Parameters:\n", + " df (DataFrame): containing a \"Body\" column with HTML \n", + "\n", + " Returns:\n", + " df (DataFrame): current dataframe with parsed column \n", + " \"\"\" \n", + " df.dropna(subset=[column], inplace=True)\n", + " df[f\"{column}Clean\"] = df[column].apply(lambda row: bs(row, 'html.parser').text)\n", + "\n", + "\n", + "def clean_tags(df):\n", + " \"\"\"\n", + " Convert Tags into Comma separated\n", + "\n", + " Converts Tag slugs into commas separated tags\n", + "\n", + " Parameters:\n", + " df (DataFrame): containing a \"Tags\" column with slugs\n", + "\n", + " Returns:\n", + " df (DataFrame): current dataframe with parsed column\n", + " \"\"\" \n", + " df['TagsClean'] = (df['Tags']\n", + " .str.replace('-',' ')\n", + " .str.replace('><', ', ')\n", + " .str.replace('<','')\n", + " .str.replace('>','')\n", + " )\n", + "\n", + "\n", + "# filter_only_questions_with_accepted_answers(df)\n", + "# filter_scores_above(df)\n", + "convert_html_to_text(df)\n", + "clean_tags(df)\n", + "\n", + "df[['Body', 'BodyClean', 'Tags', 'TagsClean']]\n", + "# print(df.shape)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 424 + }, + "id": "qyUqc31Z3Z9g", + "outputId": "18dce8b4-af26-49c9-ee73-6c677177b516" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " Body \\\n", + "0

What does \"backprop\" mean? Is the \"backprop... \n", + "1

Does increasing the noise in data help to i... \n", + "2

\"Backprop\" is the same as \"backpropagation\"... \n", + "3

When you're writing your algorithm, how do ... \n", + "4

Given the following definition of an intell... \n", + "... ... \n", + "23174

The purpose of evaluating the state and act... \n", + "23175

In machine translation, convolution is a te... \n", + "23176

One of the key features of ChatGPT is its a... \n", + "23177

Given a neural network model for Covid-19 c... \n", + "23178

My question is more related to the fundamen... \n", + "\n", + " BodyClean \\\n", + "0 What does \"backprop\" mean? Is the \"backprop\" t... \n", + "1 Does increasing the noise in data help to impr... \n", + "2 \"Backprop\" is the same as \"backpropagation\": i... \n", + "3 When you're writing your algorithm, how do you... \n", + "4 Given the following definition of an intellige... \n", + "... ... \n", + "23174 The purpose of evaluating the state and action... \n", + "23175 In machine translation, convolution is a techn... \n", + "23176 One of the key features of ChatGPT is its abil... \n", + "23177 Given a neural network model for Covid-19 clas... \n", + "23178 My question is more related to the fundamental... \n", + "\n", + " Tags \\\n", + "0 ... \n", + "4 \n", + "... ... \n", + "23174 NaN \n", + "23175 NaN \n", + "23176 NaN \n", + "23177 \n", + "23178 \n", + "\n", + " TagsClean \n", + "0 neural networks, backpropagation, terminology,... \n", + "1 neural networks, machine learning, statistical... \n", + "2 NaN \n", + "3 neural networks, hyperparameter optimization, ... \n", + "4 philosophy, definitions, intelligent agent \n", + "... ... \n", + "23174 NaN \n", + "23175 NaN \n", + "23176 NaN \n", + "23177 neural networks, homework \n", + "23178 search, constraint satisfaction problems \n", + "\n", + "[23179 rows x 4 columns]" + ], + "text/html": [ + "\n", + "

\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BodyBodyCleanTagsTagsClean
0<p>What does \"backprop\" mean? Is the \"backprop...What does \"backprop\" mean? Is the \"backprop\" t...<neural-networks><backpropagation><terminology...neural networks, backpropagation, terminology,...
1<p>Does increasing the noise in data help to i...Does increasing the noise in data help to impr...<neural-networks><machine-learning><statistica...neural networks, machine learning, statistical...
2<p>\"Backprop\" is the same as \"backpropagation\"...\"Backprop\" is the same as \"backpropagation\": i...NaNNaN
3<p>When you're writing your algorithm, how do ...When you're writing your algorithm, how do you...<neural-networks><hyperparameter-optimization>...neural networks, hyperparameter optimization, ...
4<p>Given the following definition of an intell...Given the following definition of an intellige...<philosophy><definitions><intelligent-agent>philosophy, definitions, intelligent agent
...............
23174<p>The purpose of evaluating the state and act...The purpose of evaluating the state and action...NaNNaN
23175<p>In machine translation, convolution is a te...In machine translation, convolution is a techn...NaNNaN
23176<p>One of the key features of ChatGPT is its a...One of the key features of ChatGPT is its abil...NaNNaN
23177<p>Given a neural network model for Covid-19 c...Given a neural network model for Covid-19 clas...<neural-networks><homework>neural networks, homework
23178<p>My question is more related to the fundamen...My question is more related to the fundamental...<search><constraint-satisfaction-problems>search, constraint satisfaction problems
\n", + "

23179 rows × 4 columns

\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + " \n", + "
\n", + "
\n", + " " + ] + }, + "metadata": {}, + "execution_count": 220 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "This groups questions with answers so that a row with a question also has a column with an answer. It then creates an AcceptedAnswerFlag column that is True if the answer was accepted by the person who asked the question. Changing the `number_of_results` variable will limit the number of answers you want to keep." + ], + "metadata": { + "id": "C09Bwdw-44PZ" + } + }, + { + "cell_type": "code", + "source": [ + "questions = df[df.PostTypeId == 1]\n", + "answers = df[df.PostTypeId == 2]\n", + "\n", + "df = pd.merge(\n", + "\tquestions,\n", + "\tanswers[\n", + " ['Id', 'CreationDate', 'Score', 'ViewCount', 'CommentCount', \n", + " 'ContentLicense', 'TagsClean', 'BodyClean', 'ParentId']\n", + "\t],\n", + "\tleft_on='Id',\n", + "\tright_on='ParentId',\n", + "\tsuffixes=('_q', '_a'),\n", + "\thow='left'\n", + ")\n", + "\n", + "df['AcceptedAnswerFlag'] = df.apply(lambda row: row['Id_a'] == row['AcceptedAnswerId'], axis=1)\n", + "\n", + "df = df.rename(columns={\n", + "\t'BodyClean_q': 'Question',\n", + "\t'Score_q': 'QuestionScore',\n", + "\t'TagsClean_q': 'QuestionTags',\n", + "\t'BodyClean_a': 'Answer',\n", + "\t'Score_a': \"AnswerScore\",\n", + "\t'ContentLicense_q':'QuestionContentLicense',\n", + "\t'ContentLicense_a':'AnswerContentLicense',\n", + "\t'CreationDate_q': 'CreationDate'\n", + "})\n", + "\n", + "## Set the number of results to a lower number to only return top N rated Answers.\n", + "number_of_results = 25\n", + "df = (\n", + " df.sort_values(by=['AcceptedAnswerFlag', 'AnswerScore'], ascending=[False, False])\n", + " .groupby('Question')\n", + " .head(number_of_results)\n", + " .reset_index(drop=True)\n", + " )\n", + "\n", + "df[['Id_q', 'Question', 'QuestionScore', 'QuestionTags', 'Id_a', 'Answer', 'AnswerScore', 'AcceptedAnswerFlag']].head()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 337 + }, + "id": "Bgz2fZ9k43Ab", + "outputId": "28896d69-03cd-4877-fdfb-ae48dafa4ff3" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " Id_q Question QuestionScore \\\n", + "0 1768 In Portal 2 we see that AI's can be \"killed\" b... 175 \n", + "1 10623 What is self-supervised learning in machine le... 91 \n", + "2 111 Obviously, self-driving cars aren't perfect, s... 100 \n", + "3 14224 If the original purpose for developing AI was ... 69 \n", + "4 1479 Do scientists or research experts know from th... 94 \n", + "\n", + " QuestionTags Id_a \\\n", + "0 philosophy, decision theory, mythology of ai, ... 1769.0 \n", + "1 machine learning, comparison, supervised learn... 10624.0 \n", + "2 philosophy, ethics, autonomous vehicles, decis... 1790.0 \n", + "3 philosophy, social, explainable ai 14247.0 \n", + "4 neural networks, deep learning, convolutional ... 4044.0 \n", + "\n", + " Answer AnswerScore \\\n", + "0 This classic problem exhibits a basic misunder... 146.0 \n", + "1 Introduction\\nThe term self-supervised learnin... 90.0 \n", + "2 \\nHow could self-driving cars make ethical dec... 76.0 \n", + "3 As argued by Selvaraju et al., there are three... 75.0 \n", + "4 There are many approaches that aim to make a t... 69.0 \n", + "\n", + " AcceptedAnswerFlag \n", + "0 True \n", + "1 True \n", + "2 True \n", + "3 True \n", + "4 True " + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Id_qQuestionQuestionScoreQuestionTagsId_aAnswerAnswerScoreAcceptedAnswerFlag
01768In Portal 2 we see that AI's can be \"killed\" b...175philosophy, decision theory, mythology of ai, ...1769.0This classic problem exhibits a basic misunder...146.0True
110623What is self-supervised learning in machine le...91machine learning, comparison, supervised learn...10624.0Introduction\\nThe term self-supervised learnin...90.0True
2111Obviously, self-driving cars aren't perfect, s...100philosophy, ethics, autonomous vehicles, decis...1790.0\\nHow could self-driving cars make ethical dec...76.0True
314224If the original purpose for developing AI was ...69philosophy, social, explainable ai14247.0As argued by Selvaraju et al., there are three...75.0True
41479Do scientists or research experts know from th...94neural networks, deep learning, convolutional ...4044.0There are many approaches that aim to make a t...69.0True
\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + " \n", + "
\n", + "
\n", + " " + ] + }, + "metadata": {}, + "execution_count": 221 + } + ] + }, + { + "cell_type": "code", + "source": [ + "testing_id = df.Id_q.mode()[0]\n", + "df[(df.Id_q == testing_id) | (df.ParentId_a == testing_id)][['Id_q', 'Question', 'ParentId_a', 'AcceptedAnswerId', 'Id_a', 'Answer', 'AnswerScore', 'AcceptedAnswerFlag']]\n", + "# df[['Id_q', 'Question', 'ParentId_a', 'AcceptedAnswerId', 'Id_a', 'Answer', 'AnswerScore', 'AcceptedAnswerFlag']]" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 645 + }, + "id": "eds1K8WL9QPo", + "outputId": "bc526503-d6dd-428f-fa98-ad419d26a7dc" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " Id_q Question ParentId_a \\\n", + "7 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "3662 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "3713 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "3788 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "3821 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "3882 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "4389 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "4849 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "4850 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "5763 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "5764 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "5765 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "7462 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "7463 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "7464 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "7465 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "7466 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "7467 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "9481 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "\n", + " AcceptedAnswerId Id_a \\\n", + "7 15744 15744.0 \n", + "3662 15744 15753.0 \n", + "3713 15744 15747.0 \n", + "3788 15744 15756.0 \n", + "3821 15744 15758.0 \n", + "3882 15744 15762.0 \n", + "4389 15744 15783.0 \n", + "4849 15744 15740.0 \n", + "4850 15744 15803.0 \n", + "5763 15744 15768.0 \n", + "5764 15744 15810.0 \n", + "5765 15744 15943.0 \n", + "7462 15744 15779.0 \n", + "7463 15744 15787.0 \n", + "7464 15744 15801.0 \n", + "7465 15744 15930.0 \n", + "7466 15744 15934.0 \n", + "7467 15744 15938.0 \n", + "9481 15744 15931.0 \n", + "\n", + " Answer AnswerScore \\\n", + "7 I think this is a fairly common misconception ... 62.0 \n", + "3662 I think your premise is flawed.\\nYou seem to a... 19.0 \n", + "3713 TL;DR: The subtleties of infinity are made app... 12.0 \n", + "3788 In Haskell, you can type:\\nprint [1..]\\nand it... 9.0 \n", + "3821 I believe humans can be said to understand inf... 8.0 \n", + "3882 (There's a summary at the bottom for those who... 7.0 \n", + "4389 Then premise assumes that humans \"understand\" ... 4.0 \n", + "4849 By adding some rules for infinity in arithmeti... 3.0 \n", + "4850 I think the concept that is missing in the dis... 3.0 \n", + "5763 Computers don't understand \"infinity\" or even ... 2.0 \n", + "5764 The Questions That Computers Can Never Answer ... 2.0 \n", + "5765 John Doucette's answer covers my thoughts on t... 2.0 \n", + "7462 I would think that a computer couldn’t underst... 1.0 \n", + "7463 The \"concept\" of infinity is 1 thing to unders... 1.0 \n", + "7464 Just food for thought: how about if we try to ... 1.0 \n", + "7465 Its arguable if we humans understand infinity.... 1.0 \n", + "7466 Well -- just to touch on the question of peopl... 1.0 \n", + "7467 Humans certainly don't understand infinity. Cu... 1.0 \n", + "9481 I think the property humans have which compute... 0.0 \n", + "\n", + " AcceptedAnswerFlag \n", + "7 True \n", + "3662 False \n", + "3713 False \n", + "3788 False \n", + "3821 False \n", + "3882 False \n", + "4389 False \n", + "4849 False \n", + "4850 False \n", + "5763 False \n", + "5764 False \n", + "5765 False \n", + "7462 False \n", + "7463 False \n", + "7464 False \n", + "7465 False \n", + "7466 False \n", + "7467 False \n", + "9481 False " + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Id_qQuestionParentId_aAcceptedAnswerIdId_aAnswerAnswerScoreAcceptedAnswerFlag
715730As a human being, we can think infinity. In pr...15730.01574415744.0I think this is a fairly common misconception ...62.0True
366215730As a human being, we can think infinity. In pr...15730.01574415753.0I think your premise is flawed.\\nYou seem to a...19.0False
371315730As a human being, we can think infinity. In pr...15730.01574415747.0TL;DR: The subtleties of infinity are made app...12.0False
378815730As a human being, we can think infinity. In pr...15730.01574415756.0In Haskell, you can type:\\nprint [1..]\\nand it...9.0False
382115730As a human being, we can think infinity. In pr...15730.01574415758.0I believe humans can be said to understand inf...8.0False
388215730As a human being, we can think infinity. In pr...15730.01574415762.0(There's a summary at the bottom for those who...7.0False
438915730As a human being, we can think infinity. In pr...15730.01574415783.0Then premise assumes that humans \"understand\" ...4.0False
484915730As a human being, we can think infinity. In pr...15730.01574415740.0By adding some rules for infinity in arithmeti...3.0False
485015730As a human being, we can think infinity. In pr...15730.01574415803.0I think the concept that is missing in the dis...3.0False
576315730As a human being, we can think infinity. In pr...15730.01574415768.0Computers don't understand \"infinity\" or even ...2.0False
576415730As a human being, we can think infinity. In pr...15730.01574415810.0The Questions That Computers Can Never Answer ...2.0False
576515730As a human being, we can think infinity. In pr...15730.01574415943.0John Doucette's answer covers my thoughts on t...2.0False
746215730As a human being, we can think infinity. In pr...15730.01574415779.0I would think that a computer couldn’t underst...1.0False
746315730As a human being, we can think infinity. In pr...15730.01574415787.0The \"concept\" of infinity is 1 thing to unders...1.0False
746415730As a human being, we can think infinity. In pr...15730.01574415801.0Just food for thought: how about if we try to ...1.0False
746515730As a human being, we can think infinity. In pr...15730.01574415930.0Its arguable if we humans understand infinity....1.0False
746615730As a human being, we can think infinity. In pr...15730.01574415934.0Well -- just to touch on the question of peopl...1.0False
746715730As a human being, we can think infinity. In pr...15730.01574415938.0Humans certainly don't understand infinity. Cu...1.0False
948115730As a human being, we can think infinity. In pr...15730.01574415931.0I think the property humans have which compute...0.0False
\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + " \n", + "
\n", + "
\n", + " " + ] + }, + "metadata": {}, + "execution_count": 222 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Create JSONL version of Dataframe\n", + "This groups the dataframe by question data and creates nested list of Answers for that group. The entire list contains individual JSON objects, each representing a single question in the dataset with a key, Answers, which contains a list of dictionaries for each answer to the question." + ], + "metadata": { + "id": "gXgpXEO7DCbj" + } + }, + { + "cell_type": "code", + "source": [ + "j = (\n", + "\tdf.groupby(['Title', 'Question', 'QuestionScore', 'QuestionTags', 'QuestionContentLicense', 'DataSource', 'CreationDate'])\n", + "\t.apply(lambda x: x[['Answer', 'AnswerScore', 'AcceptedAnswerFlag']].to_dict('records'))\n", + "\t.reset_index()\n", + "\t.rename(columns={0:'Answers'})\n", + "\t.to_json(orient='records')\n", + ")\n", + "\n", + "data = json.loads(j)\n", + "\n", + "for post in data:\n", + " if len(post.get('Answers')) >= 4:\n", + " print(\n", + " json.dumps(post, indent=4)\n", + " )\n", + " break" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "OBR58MSRzAMP", + "outputId": "c7da1e6c-3a97-465d-c9ba-7e055cb0d751" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "{\n", + " \"Title\": \"1 hidden layer with 1000 neurons vs. 10 hidden layers with 100 neurons\",\n", + " \"Question\": \"These types of questions may be problem-dependent, but I have tried to find research that addresses the question whether the number of hidden layers and their size (number of neurons in each layer) really matter or not.\\nSo my question is, does it really matter if we for example have 1 large hidden layer of 1000 neurons vs. 10 hidden layers with 100 neurons each?\\n\",\n", + " \"QuestionScore\": 16,\n", + " \"QuestionTags\": \"neural networks\",\n", + " \"QuestionContentLicense\": \"CC BY-SA 3.0\",\n", + " \"DataSource\": \"https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml\",\n", + " \"CreationDate\": \"2017-05-04T13:06:37.990\",\n", + " \"Answers\": [\n", + " {\n", + " \"Answer\": \"Basically, having multiple layers (aka a deep network) makes your network more eager to recognize certain aspects of input data. For example, if you have the details of a house (size, lawn size, location etc.) as input and want to predict the price. The first layer may predict:\\n\\nBig area, higher price\\nSmall amount of bedrooms, lower price\\n\\nThe second layer might conclude:\\n\\nBig area + small amount of bedrooms = large bedrooms = +- effect\\n\\nYes, one layer can also 'detect' the stats, however it will require more neurons as it cannot rely on other neurons to do 'parts' of the total calculation required to detect that stat.\\nCheck out this answer\\n\",\n", + " \"AnswerScore\": 13.0,\n", + " \"AcceptedAnswerFlag\": true\n", + " },\n", + " {\n", + " \"Answer\": \"There are so many aspects.\\n1. Training:\\nTraining deep nets is a hard job due to the vanishing (rearly exploding) gradient problem. So building a 10x100 neural-net is not recommended.\\n2. Trained network performance:\\n\\nInformation loss:\\nThe classical usage of neural nets is the classification problem. Which means we want to get some well defined information from the data. (Ex. Is there a face in the picture or not.)\\nSo usually classification problem has a lot of input, and few output, whats more the size of the hidden layers are descend from input to output.\\nHowever, we loss information using less neurons layer by layer. (Ie. We cannot reproduce the original image based on the fact that is there a face on it or no.) So you must know that you loss information using 100 neurons if the size of the input is (lets say) 1000.\\nInformation complexity: However the deeper nets (as Tomas W mentioned) can fetch more complex information from the input data. Inspite of this its not recommended to use 10 fully connected layers. Its recommended to use convolutional/relu/maxpooling or other type of layers. Firest layers can compress the some essential part of the inputs. (Ex is there any line in a specific part of the picture) Second layers can say: There is a specific shape in this place in the picture. Etc etc.\\n\\nSo deeper nets are more \\\"clever\\\" but 10x100 net structure is a good choice.\\n\",\n", + " \"AnswerScore\": 4.0,\n", + " \"AcceptedAnswerFlag\": false\n", + " },\n", + " {\n", + " \"Answer\": \"If the problem you are solving is linearly separable, one layer of 1000 neurons can do better job than 10 layers with each of 100 neurons.\\nIf the problem is non linear and not convex, then you need deep neural nets. \\n\",\n", + " \"AnswerScore\": 1.0,\n", + " \"AcceptedAnswerFlag\": false\n", + " },\n", + " {\n", + " \"Answer\": \"\\nI think you have a confusion in the basics of the neural networks.\\n Every layer has a separate activation function and input/output\\n connection weights.\\n\\nThe output of the first hidden layer will be multiplied by a weight, processed by an activation function in the next layer and so on.\\nSingle layer neural networks are very limited for simple tasks, deeper NN can perform far better than a single layer. \\nHowever, do not use more than layer if your application is not fairly complex. In conclusion, 100 neurons layer does not mean better neural network than 10 layers x 10 neurons but 10 layers are something imaginary unless you are doing deep learning. start with 10 neurons in the hidden layer and try to add layers or add more neurons to the same layer to see the difference. learning with more layers will be easier but more training time is required.\\n\",\n", + " \"AnswerScore\": 0.0,\n", + " \"AcceptedAnswerFlag\": false\n", + " }\n", + " ]\n", + "}\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Save file\n", + "\n", + "Files can be saved as JSON, JSONL, CSV, or Parquet" + ], + "metadata": { + "id": "PlNjrpXaDm1_" + } + }, + { + "cell_type": "code", + "source": [ + "file_name = dataset_name\n", + "\n", + "def save_data(data: list, file_name: str, file_type: str = ['csv', 'json', 'jsonl', 'parquet']):\n", + " \"\"\"\n", + " Save Data to file\n", + "\n", + " Save Data list to file as either JSON or JSONL\n", + "\n", + " Parameters:\n", + " data (list): list of dictionaries\n", + " file_name (str): name of file (no extension)\n", + " jsonl (bool): to save file as either JSON or JSONL\n", + " \"\"\" \n", + " file_type = file_type.lower()\n", + "\n", + " if file_type == \"csv\" and isinstance(data, pd.DataFrame):\n", + " data.to_csv(f\"/content/{file_name}.csv\", index=False)\n", + "\n", + " elif file_type == \"json\" and isinstance(data, list):\n", + " print(\n", + " json.dumps(data, indent=4),\n", + " file=open(f\"/content/{file_name}.json\", 'w')\n", + " )\n", + "\n", + " elif file_type == \"jsonl\" and isinstance(data, list):\n", + " for item in data:\n", + " print(json.dumps(item), file=open(f\"/content/{file_name}.jsonl\", 'a'))\n", + "\n", + " elif file_type == \"parquet\" and isinstance(data, pd.DataFrame):\n", + " data.to_parquet(f\"/content/{file_name}.parquet\", index=False)\n", + " \n", + " else:\n", + " print(\"Data should be either of List type for JSON and JSONL, or Pandas Dataframes for CSV and Parquet\")\n", + "\n", + "\n", + "\n", + "# save_data(data=data, file_name=file_name, file_type='jsonl')\n", + "# save_data(data=df, file_name=file_name, file_type='parquet')" + ], + "metadata": { + "id": "CU0gWRGQDqIs", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "9646e475-cedd-46f1-f9b8-7eb1fbc703c7" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Data should be either of List type for JSON and JSONL, or Pandas Dataframes for CSV and Parquet\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Open-Assistant Data Scheme\n", + "\n", + "Testing putting the data into the Open-Assistant Data Scheme\n", + "\n", + "https://github.com/LAION-AI/Open-Assistant/blob/main/docs/data_schemas.md" + ], + "metadata": { + "id": "BdN3hKxtgH7f" + } + }, + { + "cell_type": "code", + "source": [ + "from typing import TypeVar, List, Dict, Any, Literal\n", + "from json import JSONEncoder\n", + "\n", + "T = TypeVar('T', bound='ConversationTreeNode')\n", + "\n", + "\n", + "class ConversationTreeNode:\n", + " text: str # The text of the node\n", + " role: Literal['prompter', 'assistant'] # Whether the node is a user prompt/follow-up or an assistant response\n", + " children: List[T] # The children of the node (if you have a linear conversation, this will be of length 0 or 1)\n", + " metadata: Dict[str, Any] # Node metadata (see below)\n", + "\n", + " def __init__(self, text: str, role: Literal['prompter', 'assistant'], children: List[T], metadata: Dict[str, Any]) -> None:\n", + " self.text = text\n", + " self.role = role\n", + " self.children = children\n", + " self.metadata = metadata\n", + "\n", + "\n", + "class ConversationTree:\n", + " root: ConversationTreeNode # The node containing the initial prompt\n", + " metadata: Dict[str, Any] # Tree metadata, different from root node metadata.\n", + "\n", + " def __init__(self, root: ConversationTreeNode, metadata: Dict[str, Any]) -> None:\n", + " self.root = root\n", + " self.metadata = metadata\n", + "\n", + "\n", + "# subclass JSONEncoder\n", + "class TreeEncoder(JSONEncoder):\n", + " def default(self, o):\n", + " return o.__dict__" + ], + "metadata": { + "id": "n8ubYQxegNSY" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "conversation_forest = []\n", + "\n", + "tree_metadata_map = {'Title': str, 'QuestionContentLicense': str, 'DataSource': str, 'CreationDate': str}\n", + "question_metadata_map = {'QuestionScore': int, 'QuestionTags': str}\n", + "answer_metadata_map = {'AnswerScore': int, 'AcceptedAnswerFlag': bool}\n", + "\n", + "\n", + "for item in data:\n", + " prompt = item.get('Question')\n", + " metadata = {k: v for k, v in item.items() if k in question_metadata_map}\n", + " root = ConversationTreeNode(text=prompt, role='prompter', children=[], metadata=metadata)\n", + "\n", + " for answer in item.get('Answers'):\n", + " response = answer.get('Answer')\n", + " metadata = {k: v for k, v in answer.items() if k in answer_metadata_map}\n", + " child = ConversationTreeNode(text=response, role='assistant', children=[], metadata=metadata)\n", + " root.children.append(child)\n", + " \n", + " metadata = {k: v for k, v in item.items() if k in tree_metadata_map}\n", + " conversation_tree = ConversationTree(root=root, metadata=metadata)\n", + " conversation_forest.append(conversation_tree)\n", + "\n", + "\n", + "conversation_forest_json = [\n", + " json.loads(TreeEncoder().encode(conversation_tree))\n", + " for conversation_tree in conversation_forest\n", + " ]\n", + "\n", + "\n", + "# print(json.dumps(conversation_forest_json[0], indent=4))\n", + "\n", + "\n", + "\n", + "print(\n", + " json.dumps(conversation_forest_json, indent=4),\n", + " file=open(f\"/content/{file_name}.json\", 'w')\n", + ")" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "eE0fkytExSGl", + "outputId": "594632d6-f98c-49b8-af86-25f7f5e2ce06" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "{\n", + " \"root\": {\n", + " \"text\": \"Science Fiction has frequently shown AI to be a threat to the very existence of mankind. AI systems have often been the antagonists in many works of fiction, from 2001: A Space Odyssey through to The Terminator and beyond.\\nThe Media seems to buy into this trope as well. And in recent years we have had people like Elon Musk warn us of the dangers of an impending AI revolution, stating that AI is more dangerous than nukes.\\nAnd, apparently, experts think that we will be seeing this AI revolution in the next 100 years.\\nHowever, from my (albeit limited) study of AI, I get the impression that they are all wrong. I am going to outline my understanding below, please correct me if I am wrong:\\n\\nFirstly, all of these things seem to be confusing Artificial Intelligence with Artificial Consciousness. AI is essentially a system to make intelligent decisions, whereas AC is more like the \\\"self-aware\\\" systems that are shown in science fiction.\\n\\nNot AI itself, but intelligence and intelligent decision-making algorithms are something we've been working with and enhancing since before computers have been around. Moving this over to an artificial framework is fairly easy. However, consciousness is still something we are learning about. My guess is we won't be able to re-create something artificially if we barely understand how it works in the real world.\\n\\nSo, my conclusion is that no AI system will be able to learn enough to start thinking for itself, and that all our warnings of AI are completely unjustified.\\n\\nThe real danger comes from AC, which we are a long, long way from realizing because we are still a long way off from defining exactly what consciousness is, let alone understanding it.\\n\\n\\n\\nSo, my question is, assuming that my understanding is correct, are any efforts are being made by companies or organizations that work with AI to correct these popular misunderstandings in sci-fi, the media, and/or the public?\\nOr are the proponents of AI ambivalent towards this public fear-mongering?\\nI understand that the fear mongering is going to remain popular for some time, as bad news sells better than good news. I am just wondering if the general attitude from AI organizations is to ignore this popular misconception, or whether a concerted effort is being made to fight against these AI myths (but unfortunately nobody in the media is listening or cares).\\n\",\n", + " \"role\": \"prompter\",\n", + " \"children\": [\n", + " {\n", + " \"text\": \"Nothing. \\nIts in almost everyone's favor for it to stay that way financially. Having non-technical individuals associate AI with terminators makes a perception that the field has greater capabilities than it does $\\\\rightarrow$ this leads to grants, funding, etc... \\nIs there any negative? Yes. Misconceptions always have drawbacks. We see the creation of dumb ethics boards and such cough cough Elon Musk.\\nBut if history has anything to say about this, as the field gains popularity (which it is dnagerously quick), information will spread by definition, and eventually misconceptions will be laid to rest.\\nNote that this answer is biased and based upon my own opinions\\n\",\n", + " \"role\": \"assistant\",\n", + " \"children\": [],\n", + " \"metadata\": {\n", + " \"AnswerScore\": 2.0,\n", + " \"AcceptedAnswerFlag\": true\n", + " }\n", + " }\n", + " ],\n", + " \"metadata\": {\n", + " \"QuestionScore\": 5,\n", + " \"QuestionTags\": \"social, artificial consciousness\"\n", + " }\n", + " },\n", + " \"metadata\": {\n", + " \"Title\": \"\\\"AI will kill us all! The machines will rise up!\\\" - what is being done to dispel such myths?\",\n", + " \"QuestionContentLicense\": \"CC BY-SA 4.0\",\n", + " \"DataSource\": \"https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml\",\n", + " \"CreationDate\": \"2019-10-16T13:57:37.143\"\n", + " }\n", + "}\n" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/notebooks/data-argumentation/StackExchangeBuilder.md b/notebooks/data-argumentation/StackExchangeBuilder.md new file mode 100644 index 00000000..3a09dee3 --- /dev/null +++ b/notebooks/data-argumentation/StackExchangeBuilder.md @@ -0,0 +1,98 @@ +# StackExchange Builder + +StackExchange Builder is a notebook that downloads data from StackExchange data dumps +and converts it into different formats. It will parse the XML files, group questions and answers, can filter the dataset and puts the results into the Open-Assistant Data Scheme. +Files can be saved to either JSON, JSONL, Parquet, or CSV. + +--- +#### Sample Data Open-Assistant Data Scheme: + +Open-Assistant Data Scheme as outlined here: https://github.com/LAION-AI/Open-Assistant/blob/main/docs/data_schemas.md + +``` +{ + "root": { + "text": "Science Fiction has frequently shown AI to be a threat to the very existence of mankind. AI systems have often been the antagonists...", + "role": "prompter", + "children": [ + { + "text": "Nothing. \nIts in almost everyone's favor for it to stay that way financially. Having non-technical individuals associate AI with terminators...", + "role": "assistant", + "children": [], + "metadata": { + "AnswerScore": 2.0, + "AcceptedAnswerFlag": true + } + } + ], + "metadata": { + "QuestionScore": 5, + "QuestionTags": "social, artificial consciousness" + } + }, + "metadata": { + "Title": "\"AI will kill us all! The machines will rise up!\" - what is being done to dispel such myths?", + "QuestionContentLicense": "CC BY-SA 4.0", + "DataSource": "https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml", + "CreationDate": "2019-10-16T13:57:37.143" + } +} +``` + +--- +#### JSONL format +Each question and all related answers are on a single line in JSONL format. + +``` +{ + "Title": "1 hidden layer with 1000 neurons vs. 10 hidden layers with 100 neurons", + "Question": "These types of questions may be problem-dependent...", + "QuestionScore": 16, + "QuestionTags": "neural networks", + "QuestionContentLicense": "CC BY-SA 3.0", + "DataSource": "https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml", + "CreationDate": "2017-05-04T13:06:37.990", + "Answers": [ + { + "Answer": "Basically, having multiple layers (aka a deep network) makes your network more eager to recognize certain aspects of input data...", + "AnswerScore": 13.0, + "AcceptedAnswerFlag": true + }, + { + "Answer": "There are so many aspects.\n1. Training:\nTraining deep nets is a hard job due to the vanishing (rearly exploding) gradient problem...", + "AnswerScore": 4.0, + "AcceptedAnswerFlag": false + }, + { + "Answer": "If the problem you are solving is linearly separable, one layer of 1000 neurons can do better job...", + "AnswerScore": 1.0, + "AcceptedAnswerFlag": false + }, + { + "Answer": "\nI think you have a confusion in the basics of the neural networks.\n Every layer has a separate activation...", + "AnswerScore": 0.0, + "AcceptedAnswerFlag": false + } + ] +} +``` + + +#### Table/CSV/Parquet Format + +There are a lot more columns left over in the table format. `_q` and `_a` are suffixes indiciating if the column came from the question or answer table as leftover from a join statement. + +``` +| Id_q | Question | ParentId_a | AcceptedAnswerId | Id_a | Answer | AnswerScore | AcceptedAnswerFlag | +|------:|--------------------------------------------------:|-----------:|-----------------:|--------:|--------------------------------------------------:|------------:|-------------------:| +| 15730 | As a human being, we can think infinity. In pr... | 15730.0 | 15744 | 15744.0 | I think this is a fairly common misconception ... | 62.0 | True | +| 15730 | As a human being, we can think infinity. In pr... | 15730.0 | 15744 | 15753.0 | I think your premise is flawed.\nYou seem to a... | 19.0 | False | +| 15730 | As a human being, we can think infinity. In pr... | 15730.0 | 15744 | 15747.0 | TL;DR: The subtleties of infinity are made app... | 12.0 | False | +| 15730 | As a human being, we can think infinity. In pr... | 15730.0 | 15744 | 15756.0 | In Haskell, you can type:\nprint [1..]\nand it... | 9.0 | False | +``` + +--- + +## Contributing + +Feel free to contribute to this notebook. It's not perfect and additional functionality is planned. From 556391bf491054abc47f01f3eb40de2bdd652b6d Mon Sep 17 00:00:00 2001 From: Keith Stevens Date: Wed, 4 Jan 2023 17:20:51 +0900 Subject: [PATCH 09/55] Adding the Redis secret for running the backend on AWS --- copilot/api/manifest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/copilot/api/manifest.yml b/copilot/api/manifest.yml index b9262b51..b6ff6cf7 100644 --- a/copilot/api/manifest.yml +++ b/copilot/api/manifest.yml @@ -36,3 +36,4 @@ environments: secrets: # Note: URI, not URL. DATABASE_URI: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/API_DATABASE_URL + REDIS_HOST: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/REDIS_HOST From bdcfae54fc1ed5edd8d8c7235a859e169e65a8a9 Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Wed, 4 Jan 2023 08:04:27 -0500 Subject: [PATCH 10/55] docs: update TODOs to include issue numbers --- website/cypress/contract/oasst_api_contract_tests.cy.ts | 6 +++--- website/src/pages/api/update_task.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/website/cypress/contract/oasst_api_contract_tests.cy.ts b/website/cypress/contract/oasst_api_contract_tests.cy.ts index 0f9ddd00..ff5bb156 100644 --- a/website/cypress/contract/oasst_api_contract_tests.cy.ts +++ b/website/cypress/contract/oasst_api_contract_tests.cy.ts @@ -23,7 +23,7 @@ describe("Contract test for Oasst API", function () { expect(await oasstApiClient.ackTask(task.id, "321")).to.be.null; }); - // TODO Add test for 204 - // TODO Add test for parsing >=300, throwing an OasstError - // TODO Add test for parsing >=300, throwing a generic error + // TODO(#354): Add test for 204 + // TODO(#354): Add test for parsing >=300, throwing an OasstError + // TODO(#354): Add test for parsing >=300, throwing a generic error }); diff --git a/website/src/pages/api/update_task.ts b/website/src/pages/api/update_task.ts index 5e887175..2d371354 100644 --- a/website/src/pages/api/update_task.ts +++ b/website/src/pages/api/update_task.ts @@ -36,7 +36,7 @@ const handler = async (req, res) => { // Send the interaction to the Task Backend. This automatically fetches the // next task in the sequence (or the done task). - // TODO Move this into OasstApiClient. + // TODO(#353): Move this into OasstApiClient. const interactionRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/interaction`, { method: "POST", headers: { From 695e1f3932837a49190568ecc10eacf5315fa57f Mon Sep 17 00:00:00 2001 From: Jack Michaud Date: Wed, 4 Jan 2023 14:17:02 -0500 Subject: [PATCH 11/55] refactor: avoid using Response.clone() --- website/src/lib/oasst_api_client.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/website/src/lib/oasst_api_client.ts b/website/src/lib/oasst_api_client.ts index ce61e591..45a0859e 100644 --- a/website/src/lib/oasst_api_client.ts +++ b/website/src/lib/oasst_api_client.ts @@ -30,11 +30,12 @@ export default class OasstApiClient { } if (resp.status >= 300) { + const errorText = await resp.text(); try { - const error = await resp.clone().json(); + const error = JSON.parse(errorText); throw new OasstError(error.message, error.error_code, resp.status); } catch (e) { - throw new OasstError(await resp.text(), 0, resp.status); + throw new OasstError(errorText, 0, resp.status); } } From cba0925564eb4751b8bf011faf633ddb37c1507e Mon Sep 17 00:00:00 2001 From: Jac-Zac Date: Thu, 5 Jan 2023 10:33:29 +0100 Subject: [PATCH 12/55] prittier readme --- README.md | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b619c931..ec1d7b3c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,29 @@ -# Open-Assistant +

+ Open-Assistant +

-Open Assistant is a project meant to give everyone access to a great chat based -large language model. +

+ +

+ + +Table of Contents +======================= + +* [What is Open Assistant?](#what-is-open-assistant) +* [Do you want to try it out?](#do-you-want-to-try-it-out) +* [The Plan](#the-plan) +* [The Vision](#the-vision) +* [How can you help?](#how-can-you-help) +* [I’m in! Now what?](#i'm-in-now-what) + +--- + +What is Open Assistant? +------ + +_Open Assistant is a project meant to give everyone access to a great chat based +large language model._ We believe that by doing this we will create a revolution in innovation in language. In the same way that stable-diffusion helped the world make art and @@ -14,7 +36,7 @@ If you are interested in taking a look at the current state of the project, you can set up an entire stack needed to run **Open-Assistant**, including the website, backend, and associated dependent services. -To start the demo, run this in the root directory of the repository: +##### To start the demo, run this in the root directory of the repository: ```sh docker compose up --build @@ -23,10 +45,10 @@ docker compose up --build Then, navigate to `http://localhost:3000` (It may take some time to boot up) and interact with the website. -**Note:** When logging in via email, navigate to `http://localhost:1080` to get +> **Note:** When logging in via email, navigate to `http://localhost:1080` to get the magic email login link. -**Note:** If you would like to run this in a standardized development +> **Note:** If you would like to run this in a standardized development environment (a ["devcontainer"](https://code.visualstudio.com/docs/devcontainers/containers)) using @@ -37,8 +59,7 @@ provided [`.devcontainer`](.devcontainer/) folder. ## The Plan -We want to get to an initial MVP as fast as possible, by following the 3-steps -outlined in the InstructGPT paper. +##### We want to get to an initial MVP as fast as possible, by following the 3-steps outlined in the InstructGPT paper. 1. Collect high-quality human generated Instruction-Fulfillment samples (prompt + response), goal >50k. We design a crowdsourced process to collect From ac8ae2652ee6422a6f356f7af7056193ea390c10 Mon Sep 17 00:00:00 2001 From: jojopirker Date: Thu, 5 Jan 2023 10:33:35 +0100 Subject: [PATCH 13/55] spacing for message dashboard --- website/src/pages/messages/index.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/website/src/pages/messages/index.tsx b/website/src/pages/messages/index.tsx index 9ecdeb35..39430caf 100644 --- a/website/src/pages/messages/index.tsx +++ b/website/src/pages/messages/index.tsx @@ -41,9 +41,11 @@ const MessagesDashboard = () => { - + - Most recent messages + + Most recent messages + { - Your most recent messages + + Your most recent messages + Date: Thu, 5 Jan 2023 10:39:27 +0100 Subject: [PATCH 14/55] small fix in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec1d7b3c..68c77a11 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

- +

@@ -15,7 +15,7 @@ Table of Contents * [The Plan](#the-plan) * [The Vision](#the-vision) * [How can you help?](#how-can-you-help) -* [I’m in! Now what?](#i'm-in-now-what) +* [I’m in! Now what?](#I'm-in-now-what) --- From 6df5b1f19866219fe5589859ab1a7c73f94995c0 Mon Sep 17 00:00:00 2001 From: Jac-Zac Date: Thu, 5 Jan 2023 10:48:27 +0100 Subject: [PATCH 15/55] adding CONTRIBUTING.md --- CONTRIBUTING.md | 110 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 118 ++---------------------------------------------- 2 files changed, 114 insertions(+), 114 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..608afe25 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,110 @@ +# I’m in! Now what? + +[Join the OpenAssistant Contributors Discord Server!](https://ykilcher.com/open-assistant-discord), +this is for work coordination. + +[Join the LAION Discord Server!](https://discord.com/invite/mVcgxMPD7e), it has +a dedicated channel and is more public. + +[and / or the YK Discord Server](https://ykilcher.com/discord), also has a +dedicated, but not as active, channel. + +[Visit the Notion](https://ykilcher.com/open-assistant) + +### Taking on Tasks + +We have a growing task list +[of issues](https://github.com/LAION-AI/Open-Assistant/issues). Find an issue +that appeals to you and make a comment that you'd like to work on it. Include in +your comment a brief description of how you'll solve the problem and if there +are any open questions you want to discuss. Once a project coordinator has +assigned the issue to you, start working on it. + +If the issue is currently unclear but you are interested, please post in Discord +and someone can help clarify the issue with more detail. + +**Always Welcome:** Documentation markdowns in `docs/`, docstrings, diagrams of +the system architecture, and other documentation. + +### Submitting Work + +We're all working on different parts of Open Assistant together. To make +contributions smoothly we recommend the following: + +1. [Fork this project repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo) + and clone it to your local machine. (Read more + [About Forks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks)) +1. Before working on any changes, try to + [sync the forked repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) + to keep it up-to-date with the upstream repository. +1. Work on a small focused change that only touches on a few files. +1. Run `pre-commit` and make sure all files have formatting fixed. This + simplifies life for reviewers. +1. Package up a small bit of work that solves part of the problem + [into a Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) + and + [send it out for review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review). +1. If you're lucky, we can merge your change into `main` without any problems. + If there's changes to files you're working on, resolve them by: +1. First try rebase as suggested + [in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-rebase). +1. If rebase feels too painful, merge as suggested + [in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-merge). +1. Once you've resolved any conflicts, finish the review and merge into `main`. +1. Merge in your change and move onto a new issue or the second step of your + current issue. + +Additionally, if someone is working on an issue that interests you, ask if they +need help on it or would like suggestions on how to approach the issue. If so, +share wildly. If they seem to have a good handle on it, let them work on their +solution until a challenge comes up. + +### When does a review finish + +A review finishes when all blocking comments are addressed and at least one +owning reviewer has approved the PR. Be sure to acknowledge any non-blocking +comments either by making the request change, explaining why it's not being +addressed now, or filing an issue to handle it later. + +## Developer Setup + +Work is organized in the +[project board](https://github.com/orgs/LAION-AI/projects/3). + +**Anything that is in the `Todo` column and not assigned, is up for grabs. +Meaning we'd be happy for anyone to do these tasks.** + +If you want to work on something, assign yourself to it or write a comment that +you want to work on it and what you plan to do. + +- To get started with development, if you want to work on the backend, have a + look at `scripts/backend-development/README.md`. +- If you want to work on any frontend, have a look at + `scripts/frontend-development/README.md` to make a backend available. + +There is also a minimal implementation of a frontend in the `text-frontend` +folder. + +We are using Python 3.10 for the backend. + +Check out the +[High-Level Protocol Architecture](https://www.notion.so/High-Level-Protocol-Architecture-6f1fd3551da74213b560ead369f132dc) + +### Website + +The website is built using Next.js and is in the `website` folder. + +### Pre-commit + +Install `pre-commit` and run `pre-commit install` to install the pre-commit +hooks. + +In case you haven't done this, have already committed, and CI is failing, you +can run `pre-commit run --all-files` to run the pre-commit hooks on all files. + +### Deployment + +Upon making a release on GitHub, all docker images are automatically built and +pushed to ghcr.io. The docker images are tagged with the release version, and +the `latest` tag. Further, the ansible playbook in `ansible/dev.yaml` is run to +automatically deploy the built release to the dev machine. diff --git a/README.md b/README.md index 68c77a11..a0ff857c 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,16 @@ Table of Contents * [The Plan](#the-plan) * [The Vision](#the-vision) * [How can you help?](#how-can-you-help) -* [I’m in! Now what?](#I'm-in-now-what) +* [I’m in! Now what?](CONTRIBUTING.md) --- What is Open Assistant? ------ -_Open Assistant is a project meant to give everyone access to a great chat based -large language model._ +

+ Open Assistant is a project meant to give everyone access to a great chat based large language model. +

We believe that by doing this we will create a revolution in innovation in language. In the same way that stable-diffusion helped the world make art and @@ -100,114 +101,3 @@ hardware. All open source projects begin with people like you. Open source is the belief that if we collaborate we can together gift our knowledge and technology to the world for the benefit of humanity. - -## I’m in! Now what? - -[Join the OpenAssistant Contributors Discord Server!](https://ykilcher.com/open-assistant-discord), -this is for work coordination. - -[Join the LAION Discord Server!](https://discord.com/invite/mVcgxMPD7e), it has -a dedicated channel and is more public. - -[and / or the YK Discord Server](https://ykilcher.com/discord), also has a -dedicated, but not as active, channel. - -[Visit the Notion](https://ykilcher.com/open-assistant) - -### Taking on Tasks - -We have a growing task list -[of issues](https://github.com/LAION-AI/Open-Assistant/issues). Find an issue -that appeals to you and make a comment that you'd like to work on it. Include in -your comment a brief description of how you'll solve the problem and if there -are any open questions you want to discuss. Once a project coordinator has -assigned the issue to you, start working on it. - -If the issue is currently unclear but you are interested, please post in Discord -and someone can help clarify the issue with more detail. - -**Always Welcome:** Documentation markdowns in `docs/`, docstrings, diagrams of -the system architecture, and other documentation. - -### Submitting Work - -We're all working on different parts of Open Assistant together. To make -contributions smoothly we recommend the following: - -1. [Fork this project repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo) - and clone it to your local machine. (Read more - [About Forks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks)) -1. Before working on any changes, try to - [sync the forked repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) - to keep it up-to-date with the upstream repository. -1. Work on a small focused change that only touches on a few files. -1. Run `pre-commit` and make sure all files have formatting fixed. This - simplifies life for reviewers. -1. Package up a small bit of work that solves part of the problem - [into a Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) - and - [send it out for review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review). -1. If you're lucky, we can merge your change into `main` without any problems. - If there's changes to files you're working on, resolve them by: -1. First try rebase as suggested - [in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-rebase). -1. If rebase feels too painful, merge as suggested - [in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-merge). -1. Once you've resolved any conflicts, finish the review and merge into `main`. -1. Merge in your change and move onto a new issue or the second step of your - current issue. - -Additionally, if someone is working on an issue that interests you, ask if they -need help on it or would like suggestions on how to approach the issue. If so, -share wildly. If they seem to have a good handle on it, let them work on their -solution until a challenge comes up. - -### When does a review finish - -A review finishes when all blocking comments are addressed and at least one -owning reviewer has approved the PR. Be sure to acknowledge any non-blocking -comments either by making the request change, explaining why it's not being -addressed now, or filing an issue to handle it later. - -## Developer Setup - -Work is organized in the -[project board](https://github.com/orgs/LAION-AI/projects/3). - -**Anything that is in the `Todo` column and not assigned, is up for grabs. -Meaning we'd be happy for anyone to do these tasks.** - -If you want to work on something, assign yourself to it or write a comment that -you want to work on it and what you plan to do. - -- To get started with development, if you want to work on the backend, have a - look at `scripts/backend-development/README.md`. -- If you want to work on any frontend, have a look at - `scripts/frontend-development/README.md` to make a backend available. - -There is also a minimal implementation of a frontend in the `text-frontend` -folder. - -We are using Python 3.10 for the backend. - -Check out the -[High-Level Protocol Architecture](https://www.notion.so/High-Level-Protocol-Architecture-6f1fd3551da74213b560ead369f132dc) - -### Website - -The website is built using Next.js and is in the `website` folder. - -### Pre-commit - -Install `pre-commit` and run `pre-commit install` to install the pre-commit -hooks. - -In case you haven't done this, have already committed, and CI is failing, you -can run `pre-commit run --all-files` to run the pre-commit hooks on all files. - -### Deployment - -Upon making a release on GitHub, all docker images are automatically built and -pushed to ghcr.io. The docker images are tagged with the release version, and -the `latest` tag. Further, the ansible playbook in `ansible/dev.yaml` is run to -automatically deploy the built release to the dev machine. From cb8fec868c07c2705f63137beb11c0a95df83979 Mon Sep 17 00:00:00 2001 From: Adrian Cowan Date: Thu, 5 Jan 2023 20:51:45 +1100 Subject: [PATCH 16/55] website: Add Create Initial Prompts link to the dashboard. --- website/src/components/Dashboard/TaskOption.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/src/components/Dashboard/TaskOption.tsx b/website/src/components/Dashboard/TaskOption.tsx index 8cf9977f..50f707a6 100644 --- a/website/src/components/Dashboard/TaskOption.tsx +++ b/website/src/components/Dashboard/TaskOption.tsx @@ -2,6 +2,12 @@ import { Box, Flex, GridItem, Heading, SimpleGrid, Text, useColorModeValue } fro import Link from "next/link"; const crTasks = [ + { + label: "Create Initial Prompts", + desc: "Write initial prompts to help Open Assistant to try replying to diverse messages.", + type: "create", + pathname: "/create/initial_prompt", + }, { label: "Reply as User", desc: "Chat with Open Assistant and help improve it’s responses as you interact with it.", From 59fd3720634d30993074c19ed56b3e01c6f00d9d Mon Sep 17 00:00:00 2001 From: Jac-Zac Date: Thu, 5 Jan 2023 10:55:06 +0100 Subject: [PATCH 17/55] fixing style --- README.md | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a0ff857c..c48f67ba 100644 --- a/README.md +++ b/README.md @@ -6,21 +6,18 @@

+# Table of Contents -Table of Contents -======================= - -* [What is Open Assistant?](#what-is-open-assistant) -* [Do you want to try it out?](#do-you-want-to-try-it-out) -* [The Plan](#the-plan) -* [The Vision](#the-vision) -* [How can you help?](#how-can-you-help) -* [I’m in! Now what?](CONTRIBUTING.md) +- [What is Open Assistant?](#what-is-open-assistant) +- [Do you want to try it out?](#do-you-want-to-try-it-out) +- [The Plan](#the-plan) +- [The Vision](#the-vision) +- [How can you help?](#how-can-you-help) +- [I’m in! Now what?](CONTRIBUTING.md) --- -What is Open Assistant? ------- +## What is Open Assistant?

Open Assistant is a project meant to give everyone access to a great chat based large language model. @@ -46,17 +43,17 @@ docker compose up --build Then, navigate to `http://localhost:3000` (It may take some time to boot up) and interact with the website. -> **Note:** When logging in via email, navigate to `http://localhost:1080` to get -the magic email login link. +> **Note:** When logging in via email, navigate to `http://localhost:1080` to +> get the magic email login link. > **Note:** If you would like to run this in a standardized development -environment (a -["devcontainer"](https://code.visualstudio.com/docs/devcontainers/containers)) -using -[vscode locally](https://code.visualstudio.com/docs/devcontainers/create-dev-container#_create-a-devcontainerjson-file) -or in a web browser using -[GitHub Codespaces](https://github.com/features/codespaces), you can use the -provided [`.devcontainer`](.devcontainer/) folder. +> environment (a +> ["devcontainer"](https://code.visualstudio.com/docs/devcontainers/containers)) +> using +> [vscode locally](https://code.visualstudio.com/docs/devcontainers/create-dev-container#_create-a-devcontainerjson-file) +> or in a web browser using +> [GitHub Codespaces](https://github.com/features/codespaces), you can use the +> provided [`.devcontainer`](.devcontainer/) folder. ## The Plan From 881af7a999ffa07165891e82d41f0c823c44a012 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Thu, 5 Jan 2023 10:58:35 +0100 Subject: [PATCH 18/55] updated readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c48f67ba..a369fee0 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ - [The Plan](#the-plan) - [The Vision](#the-vision) - [How can you help?](#how-can-you-help) -- [I’m in! Now what?](CONTRIBUTING.md) +- [I’m in! How do I contribute?](CONTRIBUTING.md) --- @@ -98,3 +98,5 @@ hardware. All open source projects begin with people like you. Open source is the belief that if we collaborate we can together gift our knowledge and technology to the world for the benefit of humanity. + +Check out our [contributing guide](CONTRIBUTING.md) to get started. From f85d5705e54a34c2efcc238b9b18352c0d3fdf31 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Thu, 5 Jan 2023 10:59:44 +0100 Subject: [PATCH 19/55] updated logo --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a369fee0..3c17b704 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,7 @@

- Open-Assistant -

- -

+ Open-Assistant -

+ # Table of Contents From dbedf4ea0df23149ec356606710485946f2b26fb Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Thu, 5 Jan 2023 11:00:26 +0100 Subject: [PATCH 20/55] updated logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c17b704..0952df71 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Open-Assistant - +

# Table of Contents From cd4e67c7eb715dcdb25bc3547b675d6e83f05c34 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Thu, 5 Jan 2023 11:01:20 +0100 Subject: [PATCH 21/55] updated logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0952df71..54dbaa1a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Open-Assistant - +

# Table of Contents From 9686ff6165d97a713b84c7a32d5939e40b2cea12 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Thu, 5 Jan 2023 11:01:45 +0100 Subject: [PATCH 22/55] updated logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54dbaa1a..4deacb50 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Open-Assistant - +

# Table of Contents From 0a0fe2fce2ab789b284f5dc6c9788388d0011f19 Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Thu, 5 Jan 2023 11:05:07 +0100 Subject: [PATCH 23/55] updated logo --- README.md | 2 +- assets/logo_crop.png | Bin 0 -> 24144 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 assets/logo_crop.png diff --git a/README.md b/README.md index 4deacb50..f8d85464 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Open-Assistant - +

# Table of Contents diff --git a/assets/logo_crop.png b/assets/logo_crop.png new file mode 100644 index 0000000000000000000000000000000000000000..20630d6b2a99148cb4b8079fa7ba53fcd5468ab7 GIT binary patch literal 24144 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 Date: Thu, 5 Jan 2023 11:05:29 +0100 Subject: [PATCH 24/55] updated logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f8d85464..d612940a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Open-Assistant - +

# Table of Contents From 894219423d72ae77f984f99b584a9c84ef73632d Mon Sep 17 00:00:00 2001 From: jojopirker Date: Thu, 5 Jan 2023 12:55:43 +0100 Subject: [PATCH 25/55] ER-Diagrams in data_schemas.md (#367) * initial er-diagrams * pre-commit --- docs/data_schemas.md | 24 ++++++++++++++++++++++++ docs/db.png | Bin 0 -> 206054 bytes docs/webdb.png | Bin 0 -> 62532 bytes 3 files changed, 24 insertions(+) create mode 100644 docs/db.png create mode 100644 docs/webdb.png diff --git a/docs/data_schemas.md b/docs/data_schemas.md index f12eda84..b47f14fb 100644 --- a/docs/data_schemas.md +++ b/docs/data_schemas.md @@ -206,3 +206,27 @@ message RankingExample { } ``` + +## Databases + +Open-Assistant uses two databases, one for the backend and one for the frontend. +Both are [PostgreSQL](https://www.postgresql.org/) databases which run in docker +containers. + +### Backend ER-Diagram + +![ER-Diagram of backend Database](./db.png) + +**Notes** + +- In order for the diagram to not be too messy, foreign key connection to + `api_client` are not shown +- `frontend_message_id` references `id` of `taskInteraction` on the frontend + + ### Frontend ER-Diagram + + ![ER-Diagram of frontend Database](./webdb.png) + + **Notes** + +- `id` of `registeredTask` references `id`of `message`on the backend diff --git a/docs/db.png b/docs/db.png new file mode 100644 index 0000000000000000000000000000000000000000..448d51f172c35d1db2e9722c70876d7fce402470 GIT binary patch literal 206054 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%0D;)f&X0n=SMzGY!b4Y!i~e$ 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

h=bU5^jxNyMF8O;W!{P@7=(; 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 literal 0 HcmV?d00001 diff --git a/docs/webdb.png b/docs/webdb.png new file mode 100644 index 0000000000000000000000000000000000000000..29039ad81177773752a0261505637e1f5494c8ab GIT binary patch literal 62532 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 Xaup-~)Qvn0{KrV&Os`JIHRk^S;Sm2Q literal 0 HcmV?d00001 From 3dbe0ae1bac160bfb9957ddea2ac04be98b41868 Mon Sep 17 00:00:00 2001 From: MattAlexMiracle Date: Thu, 5 Jan 2023 16:14:39 +0100 Subject: [PATCH 26/55] Implement task selection (#383) * commented out legacy numerical solver * added comments and task_scheduling for selecting which task to serve to users * removed standalone task weighting * pre-commit hook rerun Co-authored-by: Alexander Mattick --- scripts/postprocessing/infogain_selector.py | 41 +++++------ scripts/postprocessing/scoring.py | 10 +-- scripts/postprocessing/task_schedule.py | 75 +++++++++++++++++++++ 3 files changed, 102 insertions(+), 24 deletions(-) create mode 100644 scripts/postprocessing/task_schedule.py diff --git a/scripts/postprocessing/infogain_selector.py b/scripts/postprocessing/infogain_selector.py index 4eedbc5c..fc644a83 100644 --- a/scripts/postprocessing/infogain_selector.py +++ b/scripts/postprocessing/infogain_selector.py @@ -1,9 +1,11 @@ import numpy as np -from scipy import log2 -from scipy.integrate import nquad from scipy.special import gammaln, psi from scipy.stats import dirichlet +''' +Legacy numerical solution. +Should not be used as it is probably broken + def make_range(*x): """ @@ -38,6 +40,23 @@ def naive_monte_carlo_integral(fun, dim, samples=10_000_000): res = fun(pos) return np.mean(res) +def infogain(a_post, a_prior): + raise ( + """For the love of good don't use this: + it's insanely poorly conditioned, the worst numerical code I have ever written + and it's slow as molasses. Use the analytic solution instead. + + Maybe remove + """ + ) + args = len(a_prior) + p = dirichlet(a_post).pdf + q = dirichlet(a_prior).pdf + (info, _) = nquad(relative_entropy(p, q), [make_range for _ in range(args - 1)], opts={"epsabs": 1e-8}) + # info = naive_monte_carlo_integral(relative_entropy(p,q), len(a_post)) + return info +''' + def analytic_solution(a_post, a_prior): """ @@ -57,26 +76,8 @@ def analytic_solution(a_post, a_prior): return info -def infogain(a_post, a_prior): - raise ( - """For the love of good don't use this: - it's insanely poorly conditioned, the worst numerical code I have ever written - and it's slow as molasses. Use the analytic solution instead. - - Maybe remove - """ - ) - args = len(a_prior) - p = dirichlet(a_post).pdf - q = dirichlet(a_prior).pdf - (info, _) = nquad(relative_entropy(p, q), [make_range for _ in range(args - 1)], opts={"epsabs": 1e-8}) - # info = naive_monte_carlo_integral(relative_entropy(p,q), len(a_post)) - return info - - def uniform_expected_infogain(a_prior): mean_weight = dirichlet.mean(a_prior) - print("weight", mean_weight) results = [] for i, w in enumerate(mean_weight): a_post = a_prior.copy() diff --git a/scripts/postprocessing/scoring.py b/scripts/postprocessing/scoring.py index efd236ce..5e76d19c 100644 --- a/scripts/postprocessing/scoring.py +++ b/scripts/postprocessing/scoring.py @@ -87,8 +87,9 @@ def score_update_prompts(consensus: npt.ArrayLike, voter_data: Voter) -> Voter: """ This function returns the gain of points for a given prompt's votes - This function is only to be run when archiving a question - i.e. the question has had sufficiently many votes, or we cann't get more than "K" bits of information + In contrast to the other score updating functions, we can run this online as new votes come in. + i.e. the question has had sufficiently many votes, or we cann't get more than "K" bits of information. + Parameters: consensus (ArrayLike): all votes cast for this question @@ -100,7 +101,8 @@ def score_update_prompts(consensus: npt.ArrayLike, voter_data: Voter) -> Voter: # produces the ranking of votes, e.g. for [100,300,200] it returns [0, 2, 1], # since 100 is the lowest, 300 the highest and 200 the middle value consensus_ranking = np.arange(len(consensus)) - len(consensus) // 2 + 1 - delta_votes = np.sum(consensus_ranking * consensus) + # expected consenus ranking (i.e. normalize the votes and multiply-sum with weightings) + delta_votes = np.sum(consensus_ranking * consensus / sum(consensus)) new_points = delta_votes + voter_data.prompt_points # we need to correct for 0 indexing, if you are closer to "right" than "wrong" of the conensus, @@ -133,7 +135,7 @@ def score_update_ranking(user_ranking: npt.ArrayLike, consensus_ranking: npt.Arr "research design and statistical analyses, second edition, 2003" the authors note that at least from an significance test POV they will yield the same p-values - Parameters: + Parameters: user_ranking (ArrayLike): ranking produced by the user consensus (ArrayLike): ranking produced after running the voting algorithm to merge into the consensus ranking voter_data (Voter): a "Voter" object that represents the person that wrote the prompt diff --git a/scripts/postprocessing/task_schedule.py b/scripts/postprocessing/task_schedule.py new file mode 100644 index 00000000..deb302b2 --- /dev/null +++ b/scripts/postprocessing/task_schedule.py @@ -0,0 +1,75 @@ +from enum import Enum + +import numpy as np +from scipy import optimize + + +class Task(Enum): + RANKING = 0 + ANSWER = 1 + PROMPT = 2 + VOTE = 3 + + +def task_selection( + num_ranking_tasks: int, current_prompts: int, target_num_prompts: int, p: float, answers_per_prompt: int +) -> Task: + """ + This computes which task to serve to the user. + In general, this method aims to get rankable tasks out of the active pool ASAP. + Before checking anything else, we first have a p% probability of running a ranking task. + After that, we can dynamically determine which task to serve by balancing the number of active tasks. + + Parameters: + num_ranking_tasks (int): number of prompts that are ready to do ranking (i.e. have "answers_per_prompt" many answers) + current_prompts (int): how many prompts are currently in the active pool + target_num_prompts (int): how many prompts _should_ be in the active pool + p (float): probability to serve a ranking task, if one is available + answers_per_prompt (int): number of answers we want to have per prompt + Returns: + task (Task): the task Enum that corresponds to one of the four tasks + """ + if num_ranking_tasks > 0 and np.random.rand() < p: + return Task.RANKING + rate = 50 / (current_prompts * 2) + prob_prompt_task = 0.5 + (target_num_prompts - current_prompts) * rate + # Yes, I'm too lazy to solve this analytically... + prob_unfinished_prompt = optimize.linprog( + np.array([1, 1]), A_eq=np.array([[1, 1], [1, -answers_per_prompt]]), b_eq=np.array([1, 0]), bounds=(0, None) + ).x[0] + if np.random.rand() < prob_prompt_task: + if np.random.rand() < prob_unfinished_prompt: + return Task.ANSWER + else: + return Task.PROMPT + else: + return Task.VOTE + + +def next_answer_task(possible_prompts, answers_per_prompt): + """ + If the `task_selection`method returns "answer", you can use this method to decide which + prompt should get an answer next. + The goal of this is to finish off the prompts that have almost enough answers collected already: + I.e. if we want 5 answers, this is going to give preferential sampling to those prompts that already + have 4/5 answers. + This helps to not have too much close-to-finished prompts in the active set. + + Parameters: + possible_prompts (dict[prompt_id, num_answers]): a dictonary containing all open prompts and the number of answers these prompts currently have. + answers_per_prompt (int): number of answers we per prompt to target + Returns: + prompt_id (int): the prompt_id corresponding to the next prompt that should get a new answer + """ + nums = list(set(possible_prompts.values())) + p = np.array([max(x / answers_per_prompt, 1 / answers_per_prompt) for x in nums]) + idx = np.random.choice(nums, p=p / p.sum()) + sample = np.random.choice([k for k, v in possible_prompts.items() if v == idx]) + return sample + + +if __name__ == "__main__": + x = task_selection(1, 500, 1000, 0.1, 5) + print(x) + y = next_answer_task({"this": 2, "is": 4, "a": 1, "test": 4}, 5) + print(y) From ee50b573e1d6ad3d7dc66a7e827584e9936b5f4d Mon Sep 17 00:00:00 2001 From: Nil Andreu <65730003+Nil-Andreu@users.noreply.github.com> Date: Thu, 5 Jan 2023 16:18:04 +0100 Subject: [PATCH 27/55] Detoxify Rest API Client (#404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [NEW] utils: Endpoint Toxic Roberta * [NEW] Constants API URL * [NEW] Git ignore venv * [NEW] Lint * [NEW] Backend default args * [NEW] HUGGINGFACE_API_ERROR * [NEW] Requests package * [NEW] Get Toxicity Endpoint * [NEW] Schema: ToxicityClassification [NEW] Constants module [FIX] Module * [FIX] Test Key HF * [NEW] settings: HUGGING_FACE_API_KEY * [NEW] Remove requests * [NEW] HuggingFace client * [NEW] Cleaning code --- backend/.gitignore | 1 + backend/main.py | 4 +- backend/oasst_backend/api/v1/api.py | 2 + backend/oasst_backend/api/v1/hugging_face.py | 37 ++++++++++++++ backend/oasst_backend/config.py | 2 + backend/oasst_backend/schemas/__init__.py | 0 backend/oasst_backend/schemas/hugging_face.py | 6 +++ backend/oasst_backend/utils/__init__.py | 0 backend/oasst_backend/utils/hugging_face.py | 49 +++++++++++++++++++ .../exceptions/oasst_api_error.py | 4 ++ 10 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 backend/oasst_backend/api/v1/hugging_face.py create mode 100644 backend/oasst_backend/schemas/__init__.py create mode 100644 backend/oasst_backend/schemas/hugging_face.py create mode 100644 backend/oasst_backend/utils/__init__.py create mode 100644 backend/oasst_backend/utils/hugging_face.py diff --git a/backend/.gitignore b/backend/.gitignore index 098a83e4..30c79448 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,4 @@ __pycache__ .env notes.txt +venv diff --git a/backend/main.py b/backend/main.py index cb682a9f..edbad943 100644 --- a/backend/main.py +++ b/backend/main.py @@ -230,8 +230,8 @@ if __name__ == "__main__": help="Dumps the openapi schema to stdout", action=argparse.BooleanOptionalAction, ) - parser.add_argument("--host", help="The host to run the server") - parser.add_argument("--port", help="The port to run the server") + parser.add_argument("--host", help="The host to run the server", default="0.0.0.0") + parser.add_argument("--port", help="The port to run the server", default=8080) args = parser.parse_args() diff --git a/backend/oasst_backend/api/v1/api.py b/backend/oasst_backend/api/v1/api.py index a9d09457..5bdf1c97 100644 --- a/backend/oasst_backend/api/v1/api.py +++ b/backend/oasst_backend/api/v1/api.py @@ -2,6 +2,7 @@ from fastapi import APIRouter from oasst_backend.api.v1 import ( frontend_messages, frontend_users, + hugging_face, leaderboards, messages, stats, @@ -19,3 +20,4 @@ api_router.include_router(users.router, prefix="/users", tags=["users"]) api_router.include_router(frontend_users.router, prefix="/frontend_users", tags=["frontend_users"]) api_router.include_router(stats.router, prefix="/stats", tags=["stats"]) api_router.include_router(leaderboards.router, prefix="/experimental/leaderboards", tags=["leaderboards"]) +api_router.include_router(hugging_face.router, prefix="/hf", tags=["hugging_face"]) diff --git a/backend/oasst_backend/api/v1/hugging_face.py b/backend/oasst_backend/api/v1/hugging_face.py new file mode 100644 index 00000000..1e7f1ffe --- /dev/null +++ b/backend/oasst_backend/api/v1/hugging_face.py @@ -0,0 +1,37 @@ +from enum import Enum +from typing import List + +from fastapi import APIRouter, Depends +from oasst_backend.api import deps +from oasst_backend.models import ApiClient +from oasst_backend.schemas.hugging_face import ToxicityClassification +from oasst_backend.utils.hugging_face import HuggingFaceAPI + +router = APIRouter() + + +class HF_url(str, Enum): + HUGGINGFACE_TOXIC_ROBERTA = "https://api-inference.huggingface.co/models/unitary/multilingual-toxic-xlm-roberta" + + +@router.get("/text_toxicity") +async def get_text_toxicity( + msg: str, + api_client: ApiClient = Depends(deps.get_trusted_api_client), +) -> List[List[ToxicityClassification]]: + """Get the Message Toxicity from HuggingFace Roberta model. + + Args: + msg (str): the message that we want to analyze. + api_client (ApiClient, optional): authentification of the user of the request. + Defaults to Depends(deps.get_trusted_api_client). + + Returns: + ToxicityClassification: the score of toxicity of the message. + """ + + api_url: str = HF_url.HUGGINGFACE_TOXIC_ROBERTA.value + hugging_face_api = HuggingFaceAPI(api_url) + response = await hugging_face_api.post(msg) + + return response diff --git a/backend/oasst_backend/config.py b/backend/oasst_backend/config.py index fef59832..df37dc9f 100644 --- a/backend/oasst_backend/config.py +++ b/backend/oasst_backend/config.py @@ -22,6 +22,8 @@ class Settings(BaseSettings): DEBUG_SKIP_API_KEY_CHECK: bool = False DEBUG_USE_SEED_DATA: bool = False + HUGGING_FACE_API_KEY: str = "" + @validator("DATABASE_URI", pre=True) def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any: if isinstance(v, str): diff --git a/backend/oasst_backend/schemas/__init__.py b/backend/oasst_backend/schemas/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/oasst_backend/schemas/hugging_face.py b/backend/oasst_backend/schemas/hugging_face.py new file mode 100644 index 00000000..f4da3e74 --- /dev/null +++ b/backend/oasst_backend/schemas/hugging_face.py @@ -0,0 +1,6 @@ +from pydantic import BaseModel + + +class ToxicityClassification(BaseModel): + label: str + score: float diff --git a/backend/oasst_backend/utils/__init__.py b/backend/oasst_backend/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/oasst_backend/utils/hugging_face.py b/backend/oasst_backend/utils/hugging_face.py new file mode 100644 index 00000000..ff73a1c5 --- /dev/null +++ b/backend/oasst_backend/utils/hugging_face.py @@ -0,0 +1,49 @@ +from typing import Any, Dict + +import aiohttp +from oasst_backend.config import settings +from oasst_shared.exceptions import OasstError, OasstErrorCode + + +class HuggingFaceAPI: + """Class Object to make post calls to endpoints for inference in models hosted in HuggingFace""" + + def __init__( + self, + api_url: str, + ): + + # The API endpoint we want to access + self.api_url: str = api_url + + # Access token for the api + self.api_key: str = settings.HUGGING_FACE_API_KEY + + # Headers going to be used + self.headers: Dict[str, str] = {"Authorization": f"Bearer {self.api_key}"} + + async def post(self, input: str) -> Any: + """Post request to the endpoint to get an inference + + Args: + input (str): the input that we will pass to the model + + Raises: + OasstError: in the case we get a bad response + + Returns: + inference: the inference we obtain from the model in HF + """ + + session = aiohttp.ClientSession() + payload: Dict[str, str] = {"inputs": input} + response = await session.post(self.api_url, headers=self.headers, json=payload) + + # If we get a bad response + if response.status != 200: + raise OasstError("Response Error Detoxify HuggingFace", error_code=OasstErrorCode.HUGGINGFACE_API_ERROR) + + # Get the response from the API call + inference = await response.json() + + return inference diff --git a/oasst-shared/oasst_shared/exceptions/oasst_api_error.py b/oasst-shared/oasst_shared/exceptions/oasst_api_error.py index 49eeb088..6cc25918 100644 --- a/oasst-shared/oasst_shared/exceptions/oasst_api_error.py +++ b/oasst-shared/oasst_shared/exceptions/oasst_api_error.py @@ -10,6 +10,7 @@ class OasstErrorCode(IntEnum): 0-1000: general errors 1000-2000: tasks endpoint 2000-3000: prompt_repository + 3000-4000: external resources """ # 0-1000: general errors @@ -45,6 +46,9 @@ class OasstErrorCode(IntEnum): TASK_ALREADY_DONE = 2105 TASK_NOT_COLLECTIVE = 2106 + # 3000-4000: external resources + HUGGINGFACE_API_ERROR = 3001 + class OasstError(Exception): """Base class for Open-Assistant exceptions.""" From 739c07332889d18b23db46a11271a495435f3044 Mon Sep 17 00:00:00 2001 From: Nil Andreu <65730003+Nil-Andreu@users.noreply.github.com> Date: Thu, 5 Jan 2023 17:49:39 +0100 Subject: [PATCH 28/55] Session Close when called HuggingFace Client (#411) * [FIX] Session Close * [NEW] Async Context Managers of Response * [NEW] Linting --- backend/oasst_backend/utils/hugging_face.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/backend/oasst_backend/utils/hugging_face.py b/backend/oasst_backend/utils/hugging_face.py index ff73a1c5..0df913f5 100644 --- a/backend/oasst_backend/utils/hugging_face.py +++ b/backend/oasst_backend/utils/hugging_face.py @@ -35,15 +35,17 @@ class HuggingFaceAPI: inference: the inference we obtain from the model in HF """ - session = aiohttp.ClientSession() - payload: Dict[str, str] = {"inputs": input} - response = await session.post(self.api_url, headers=self.headers, json=payload) + async with aiohttp.ClientSession() as session: + payload: Dict[str, str] = {"inputs": input} - # If we get a bad response - if response.status != 200: - raise OasstError("Response Error Detoxify HuggingFace", error_code=OasstErrorCode.HUGGINGFACE_API_ERROR) + async with session.post(self.api_url, headers=self.headers, json=payload) as response: + # If we get a bad response + if response.status != 200: + raise OasstError( + "Response Error Detoxify HuggingFace", error_code=OasstErrorCode.HUGGINGFACE_API_ERROR + ) - # Get the response from the API call - inference = await response.json() + # Get the response from the API call + inference = await response.json() return inference From 6f319651c1bdcc126ffc021f2d7bac083d5f39a6 Mon Sep 17 00:00:00 2001 From: Batuhan Erenler <40266535+batuhanerenler@users.noreply.github.com> Date: Thu, 5 Jan 2023 20:17:37 +0300 Subject: [PATCH 29/55] Update env.py Here are the improvements that I made: Imported the logging module and used it to set up the loggers, rather than using the deprecated logging.config.fileConfig function. Renamed the sqlmodel module to models and imported it correctly. Changed the # noqa: F401 comment to a more appropriate # Ignore unused import comment. Added type annotations and docstrings to the functions. Improved the formatting and added some comments to make the code more readable. Changed the with context.begin_transaction(): block to use the contextlib.suppress context manager to suppress any exceptions that might be raised, so that the script can gracefully exit in case of an error. --- backend/alembic/env.py | 45 ++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 511ed97f..1d2be1d0 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -1,49 +1,34 @@ -from logging.config import fileConfig +import logging -import sqlmodel from alembic import context from oasst_backend import models # noqa: F401 from sqlalchemy import engine_from_config, pool -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. +# Read in the Alembic config file. config = context.config -# Interpret the config file for Python logging. -# This line sets up loggers basically. +# Set up loggers. if config.config_file_name is not None: - fileConfig(config.config_file_name) + logging.config.fileConfig(config.config_file_name) -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -target_metadata = sqlmodel.SQLModel.metadata +# Add the model's MetaData object here for 'autogenerate' support. +target_metadata = models.Base.metadata -# other values from the config, defined by the needs of env.py, -# can be acquired: +# Other values from the config file can be acquired as follows: # my_important_option = config.get_main_option("my_important_option") # ... etc. def run_migrations_offline() -> None: """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation + + This configures the context with just a URL and not an Engine, though + an Engine is acceptable here as well. By skipping the Engine creation we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - """ url = config.get_main_option("sqlalchemy.url") context.configure( - url=url, - target_metadata=target_metadata, - literal_binds=True, - dialect_opts={"paramstyle": "named"}, + url=url, target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"} ) with context.begin_transaction(): @@ -52,10 +37,9 @@ def run_migrations_offline() -> None: def run_migrations_online() -> None: """Run migrations in 'online' mode. - - In this scenario we need to create an Engine - and associate a connection with the context. - + + In this scenario we need to create an Engine and associate a connection + with the context. """ connectable = engine_from_config( config.get_section(config.config_ini_section), @@ -76,3 +60,4 @@ if context.is_offline_mode(): run_migrations_offline() else: run_migrations_online() + From 4012f7da053e29af81b78d8393a34dffbcf68667 Mon Sep 17 00:00:00 2001 From: Batuhan Erenler <40266535+batuhanerenler@users.noreply.github.com> Date: Thu, 5 Jan 2023 22:50:22 +0300 Subject: [PATCH 30/55] Update env.py --- backend/alembic/env.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 1d2be1d0..f9f8180f 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -55,9 +55,7 @@ def run_migrations_online() -> None: connection.execute("LOCK TABLE alembic_version IN ACCESS EXCLUSIVE MODE") context.run_migrations() - if context.is_offline_mode(): run_migrations_offline() else: run_migrations_online() - From aafe93ff82a070e43c113ee0d1b89863e4f44922 Mon Sep 17 00:00:00 2001 From: Vechtomov Date: Thu, 5 Jan 2023 22:50:51 +0300 Subject: [PATCH 31/55] simplified random task generation --- backend/oasst_backend/api/v1/tasks.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/oasst_backend/api/v1/tasks.py b/backend/oasst_backend/api/v1/tasks.py index e9ecc854..82b2f773 100644 --- a/backend/oasst_backend/api/v1/tasks.py +++ b/backend/oasst_backend/api/v1/tasks.py @@ -24,14 +24,14 @@ def generate_task( match request.type: case protocol_schema.TaskRequestType.random: logger.info("Frontend requested a random task.") - while request.type == protocol_schema.TaskRequestType.random: - disabled_tasks = ( - protocol_schema.TaskRequestType.summarize_story, - protocol_schema.TaskRequestType.rate_summary, - ) - request.type = random.choice( - tuple(set(protocol_schema.TaskRequestType).difference(disabled_tasks)) - ).value + disabled_tasks = ( + protocol_schema.TaskRequestType.random, + protocol_schema.TaskRequestType.summarize_story, + protocol_schema.TaskRequestType.rate_summary, + ) + request.type = random.choice( + tuple(set(protocol_schema.TaskRequestType).difference(disabled_tasks)) + ).value return generate_task(request, pr) # AKo: Summary tasks are currently disabled/supported, we focus on the conversation tasks. From ec6123871156abb13885e24b2dbaa2e0f0c89d05 Mon Sep 17 00:00:00 2001 From: Batuhan Erenler <40266535+batuhanerenler@users.noreply.github.com> Date: Thu, 5 Jan 2023 22:52:09 +0300 Subject: [PATCH 32/55] Update env.py From ee370e85a4dbf0489a6bab4f869a938f58ace9e3 Mon Sep 17 00:00:00 2001 From: b-mc2 Date: Thu, 5 Jan 2023 14:55:26 -0500 Subject: [PATCH 33/55] Changes from pre-commit --- .../StackExchangeBuilder.ipynb | 3668 +++++++++-------- .../StackExchangeBuilder.md | 22 +- 2 files changed, 1855 insertions(+), 1835 deletions(-) diff --git a/notebooks/data-argumentation/StackExchangeBuilder.ipynb b/notebooks/data-argumentation/StackExchangeBuilder.ipynb index ac215cbe..625d757b 100644 --- a/notebooks/data-argumentation/StackExchangeBuilder.ipynb +++ b/notebooks/data-argumentation/StackExchangeBuilder.ipynb @@ -1,1833 +1,1845 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - } + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [] }, - "cells": [ - { - "cell_type": "markdown", - "source": [ - "# Ingest StackExchange data dumps\n", - "This notebook takes a StackExchange Data dump \"Posts.xml\" file and ingests it into a Pandas Dataframe. Outputs of the file can be JSON, JSONL, Parquet, or CSV. " - ], - "metadata": { - "id": "TB7CEfs8F-8u" - } + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Ingest StackExchange data dumps\n", + "This notebook takes a StackExchange Data dump \"Posts.xml\" file and ingests it into a Pandas Dataframe. Outputs of the file can be JSON, JSONL, Parquet, or CSV. " + ], + "metadata": { + "id": "TB7CEfs8F-8u" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "0rHryQttyzyY" + }, + "outputs": [], + "source": [ + "from bs4 import BeautifulSoup as bs\n", + "import pandas as pd\n", + "import requests\n", + "import json" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Extract StackExchange\n", + "Pull StackExchange file dumps. Specific column types are enforced to prevent errors on processing later in the notebook" + ], + "metadata": { + "id": "15mAL7GnzBv0" + } + }, + { + "cell_type": "code", + "source": [ + "base_url = \"https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/{0}&file=Posts.xml\"\n", + "\n", + "\n", + "def get_all_filenames():\n", + " response = requests.get(\"https://archive.org/download/stackexchange\")\n", + " if response.ok:\n", + " soup = bs(response.content, \"html.parser\")\n", + " table = soup.find(\"table\")\n", + " link_tags = table.find_all(\"a\")\n", + " urls = {}\n", + " for link in link_tags:\n", + " url = link[\"href\"]\n", + " name = url.split(\".stackexchange\")[0].replace(\".\", \"_\").replace(\"-\", \"_\")\n", + " if url.endswith(\"7z\"):\n", + " urls[name] = base_url.format(url)\n", + " return urls\n", + "\n", + "\n", + "urls = get_all_filenames()\n", + "\n", + "print(urls.keys())\n", + "print(urls.get(\"ai\"))" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, + "id": "FtcvUEaHVxcW", + "outputId": "5b0cb19d-e3d9-422b-9077-52241bd09e0e" + }, + "execution_count": null, + "outputs": [ { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0rHryQttyzyY" - }, - "outputs": [], - "source": [ - "from bs4 import BeautifulSoup as bs\n", - "import pandas as pd\n", - "import requests\n", - "import json" - ] - }, - { - "cell_type": "markdown", - "source": [ - "# Extract StackExchange\n", - "Pull StackExchange file dumps. Specific column types are enforced to prevent errors on processing later in the notebook" - ], - "metadata": { - "id": "15mAL7GnzBv0" - } - }, - { - "cell_type": "code", - "source": [ - "\n", - "base_url = \"https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/{0}&file=Posts.xml\"\n", - "\n", - "def get_all_filenames():\n", - " response = requests.get(\"https://archive.org/download/stackexchange\")\n", - " if response.ok:\n", - " soup = bs(response.content, 'html.parser')\n", - " table = soup.find('table')\n", - " link_tags = table.find_all('a')\n", - " urls = {}\n", - " for link in link_tags:\n", - " url = link['href']\n", - " name = url.split('.stackexchange')[0].replace('.', '_').replace('-', '_')\n", - " if url.endswith('7z'):\n", - " urls[name] = base_url.format(url)\n", - " return urls\n", - "\n", - "urls = get_all_filenames()\n", - "\n", - "print(urls.keys())\n", - "print(urls.get('ai'))" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "FtcvUEaHVxcW", - "outputId": "5b0cb19d-e3d9-422b-9077-52241bd09e0e" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "dict_keys(['3dprinting_meta', '3dprinting', 'Stackoverflow_com_Posts_7z', 'academia_meta', 'academia', 'ai_meta', 'ai', 'android_meta', 'android', 'anime_meta', 'anime', 'apple_meta', 'apple', 'arduino_meta', 'arduino', 'askubuntu_com_7z', 'astronomy_meta', 'astronomy', 'aviation_meta', 'aviation', 'avp_meta', 'avp', 'beer_meta', 'beer', 'bicycles_meta', 'bicycles', 'bioacoustics_meta', 'bioacoustics', 'bioinformatics_meta', 'bioinformatics', 'biology_meta', 'biology', 'bitcoin_meta', 'bitcoin', 'blender_meta', 'blender', 'boardgames_meta', 'boardgames', 'bricks_meta', 'bricks', 'buddhism_meta', 'buddhism', 'cardano_meta', 'cardano', 'chemistry_meta', 'chemistry', 'chess_meta', 'chess', 'chinese_meta', 'chinese', 'christianity_meta', 'christianity', 'civicrm_meta', 'civicrm', 'codegolf_meta', 'codegolf', 'codereview_meta', 'codereview', 'coffee_meta', 'coffee', 'cogsci_meta', 'cogsci', 'computergraphics_meta', 'computergraphics', 'conlang_meta', 'conlang', 'cooking_meta', 'cooking', 'craftcms_meta', 'craftcms', 'crafts_meta', 'crafts', 'crypto_meta', 'crypto', 'cs_meta', 'cs', 'cseducators_meta', 'cseducators', 'cstheory_meta', 'cstheory', 'datascience_meta', 'datascience', 'dba_meta', 'dba', 'devops_meta', 'devops', 'diy_meta', 'diy', 'drones_meta', 'drones', 'drupal_meta', 'drupal', 'dsp_meta', 'dsp', 'earthscience_meta', 'earthscience', 'ebooks_meta', 'ebooks', 'economics_meta', 'economics', 'electronics_meta', 'electronics', 'elementaryos_meta', 'elementaryos', 'ell_meta', 'ell', 'emacs_meta', 'emacs', 'engineering_meta', 'engineering', 'english_meta', 'english', 'eosio_meta', 'eosio', 'es_meta_stackoverflow_com_7z', 'es_stackoverflow_com_7z', 'esperanto_meta', 'esperanto', 'ethereum_meta', 'ethereum', 'expatriates_meta', 'expatriates', 'expressionengine_meta', 'expressionengine', 'fitness_meta', 'fitness', 'freelancing_meta', 'freelancing', 'french_meta', 'french', 'gamedev_meta', 'gamedev', 'gaming_meta', 'gaming', 'gardening_meta', 'gardening', 'genealogy_meta', 'genealogy', 'german_meta', 'german', 'gis_meta', 'gis', 'graphicdesign_meta', 'graphicdesign', 'ham_meta', 'ham', 'hardwarerecs_meta', 'hardwarerecs', 'health_meta', 'health', 'hermeneutics_meta', 'hermeneutics', 'hinduism_meta', 'hinduism', 'history_meta', 'history', 'homebrew_meta', 'homebrew', 'hsm_meta', 'hsm', 'interpersonal_meta', 'interpersonal', 'iot_meta', 'iot', 'iota_meta', 'iota', 'islam_meta', 'islam', 'italian_meta', 'italian', 'ja_meta_stackoverflow_com_7z', 'ja_stackoverflow_com_7z', 'japanese_meta', 'japanese', 'joomla_meta', 'joomla', 'judaism_meta', 'judaism', 'korean_meta', 'korean', 'languagelearning_meta', 'languagelearning', 'latin_meta', 'latin', 'law_meta', 'law', 'lifehacks_meta', 'lifehacks', 'linguistics_meta', 'linguistics', 'literature_meta', 'literature', 'magento_meta', 'magento', 'martialarts_meta', 'martialarts', 'materials_meta', 'materials', 'math_meta', 'math', 'matheducators_meta', 'matheducators', 'mathematica_meta', 'mathematica', 'mathoverflow_net_7z', 'mechanics_meta', 'mechanics', 'meta_askubuntu_com_7z', 'meta_mathoverflow_net_7z', 'meta_serverfault_com_7z', 'meta', 'meta_stackoverflow_com_7z', 'meta_superuser_com_7z', 'moderators_meta', 'moderators', 'monero_meta', 'monero', 'money_meta', 'money', 'movies_meta', 'movies', 'music_meta', 'music', 'musicfans_meta', 'musicfans', 'mythology_meta', 'mythology', 'networkengineering_meta', 'networkengineering', 'opendata_meta', 'opendata', 'opensource_meta', 'opensource', 'or_meta', 'or', 'outdoors_meta', 'outdoors', 'parenting_meta', 'parenting', 'patents_meta', 'patents', 'pets_meta', 'pets', 'philosophy_meta', 'philosophy', 'photo_meta', 'photo', 'physics_meta', 'physics', 'pm_meta', 'pm', 'poker_meta', 'poker', 'politics_meta', 'politics', 'portuguese_meta', 'portuguese', 'proofassistants_meta', 'proofassistants', 'pt_meta_stackoverflow_com_7z', 'pt_stackoverflow_com_7z', 'puzzling_meta', 'puzzling', 'quant_meta', 'quant', 'quantumcomputing_meta', 'quantumcomputing', 'raspberrypi_meta', 'raspberrypi', 'retrocomputing_meta', 'retrocomputing', 'reverseengineering_meta', 'reverseengineering', 'robotics_meta', 'robotics', 'rpg_meta', 'rpg', 'ru_meta_stackoverflow_com_7z', 'ru_stackoverflow_com_7z', 'rus_meta', 'rus', 'russian_meta', 'russian', 'salesforce_meta', 'salesforce', 'scicomp_meta', 'scicomp', 'scifi_meta', 'scifi', 'security_meta', 'security', 'serverfault_com_7z', 'sharepoint_meta', 'sharepoint', 'sitecore_meta', 'sitecore', 'skeptics_meta', 'skeptics', 'softwareengineering_meta', 'softwareengineering', 'softwarerecs_meta', 'softwarerecs', 'solana_meta', 'solana', 'sound_meta', 'sound', 'space_meta', 'space', 'spanish_meta', 'spanish', 'sports_meta', 'sports', 'sqa_meta', 'sqa', 'stackapps_com_7z', 'stackoverflow_com_Badges_7z', 'stackoverflow_com_Comments_7z', 'stackoverflow_com_PostHistory_7z', 'stackoverflow_com_PostLinks_7z', 'stackoverflow_com_Tags_7z', 'stackoverflow_com_Users_7z', 'stackoverflow_com_Votes_7z', 'stats_meta', 'stats', 'stellar_meta', 'stellar', 'substrate_meta', 'substrate', 'superuser_com_7z', 'sustainability_meta', 'sustainability', 'tex_meta', 'tex', 'tezos_meta', 'tezos', 'tor_meta', 'tor', 'travel_meta', 'travel', 'tridion_meta', 'tridion', 'ukrainian_meta', 'ukrainian', 'unix_meta', 'unix', 'ux_meta', 'ux', 'vegetarianism_meta', 'vegetarianism', 'vi_meta', 'vi', 'webapps_meta', 'webapps', 'webmasters_meta', 'webmasters', 'windowsphone_meta', 'windowsphone', 'woodworking_meta', 'woodworking', 'wordpress_meta', 'wordpress', 'workplace_meta', 'workplace', 'worldbuilding_meta', 'worldbuilding', 'writers_meta', 'writers'])\n", - "https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml\n" - ] - } - ] - }, - { - "cell_type": "code", - "source": [ - "\n", - "xml_format_map = {'Id': int, 'PostTypeId': int, 'CreationDate': str, \n", - " 'Score': int, 'ViewCount': int, 'Body': str, 'AnswerCount': int, \n", - " 'CommentCount': int, 'ContentLicense': str, 'AcceptedAnswerId': int, \"ParentId\": int}\n", - "\n", - "\n", - "# def extract_xml_file(file_url: str):\n", - "# table = pd.read_xml(file_url)\n", - "# return table\n", - "\n", - "def xml_to_df(response: str):\n", - " \"\"\"\n", - " Collect and Manually import XML into Dataframe\n", - "\n", - " pd.read_xml() errors when XML trees are too large, this is just a hack to\n", - " download a XML file and parse into a Dataframe. **Not Tested on huge XML files**\n", - "\n", - " Parameters:\n", - " response (Requests.Response): Requests response object with the XML data\n", - "\n", - " Returns:\n", - " df (DataFrame): A Dataframe from the XML file\n", - " \"\"\" \n", - " soup = bs(response.content, 'xml')\n", - " posts = soup.find_all('row')\n", - "\n", - " all_posts = [post.attrs for post in posts]\n", - "\n", - " df = pd.DataFrame(all_posts)\n", - " df.AnswerCount.fillna(0, inplace=True)\n", - " df.ViewCount.fillna(0, inplace=True)\n", - " df.AcceptedAnswerId.fillna(0, inplace=True)\n", - " df.ParentId.fillna(0, inplace=True)\n", - " df['DataSource'] = response.url\n", - " df = df.astype(xml_format_map)\n", - " return df\n", - "\n", - "\n", - "\n", - "dataset_name = \"ai\"\n", - "\n", - "xml_posts_path = urls.get(dataset_name)\n", - "\n", - "\n", - "# df = extract_xml_file(test)\n", - "response = requests.get(xml_posts_path)\n", - "df = xml_to_df(response)\n", - "\n", - "\n", - "print(df.dtypes)\n", - "df.head()" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 907 - }, - "id": "-t27RnxdzBYB", - "outputId": "5ec0ceed-c82b-48fa-facd-41b4aae2f9e6" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Id int64\n", - "PostTypeId int64\n", - "AcceptedAnswerId int64\n", - "CreationDate object\n", - "Score int64\n", - "ViewCount int64\n", - "Body object\n", - "OwnerUserId object\n", - "LastEditorUserId object\n", - "LastEditDate object\n", - "LastActivityDate object\n", - "Title object\n", - "Tags object\n", - "AnswerCount int64\n", - "CommentCount int64\n", - "ContentLicense object\n", - "ParentId int64\n", - "ClosedDate object\n", - "FavoriteCount object\n", - "CommunityOwnedDate object\n", - "LastEditorDisplayName object\n", - "OwnerDisplayName object\n", - "DataSource object\n", - "dtype: object\n" - ] - }, - { - "output_type": "execute_result", - "data": { - "text/plain": [ - " Id PostTypeId AcceptedAnswerId CreationDate Score \\\n", - "0 1 1 3 2016-08-02T15:39:14.947 10 \n", - "1 2 1 9 2016-08-02T15:40:20.623 14 \n", - "2 3 2 0 2016-08-02T15:40:24.820 15 \n", - "3 4 1 12 2016-08-02T15:41:22.020 33 \n", - "4 6 1 20 2016-08-02T15:43:35.460 7 \n", - "\n", - " ViewCount Body OwnerUserId \\\n", - "0 710

What does \"backprop\" mean? Is the \"backprop... 8 \n", - "1 1008

Does increasing the noise in data help to i... 8 \n", - "2 0

\"Backprop\" is the same as \"backpropagation\"... 4 \n", - "3 1266

When you're writing your algorithm, how do ... 8 \n", - "4 279

Given the following definition of an intell... 29 \n", - "\n", - " LastEditorUserId LastEditDate ... AnswerCount CommentCount \\\n", - "0 2444 2019-11-16T17:56:22.093 ... 5 0 \n", - "1 2444 2019-02-23T22:36:19.090 ... 3 0 \n", - "2 NaN NaN ... 0 0 \n", - "3 2444 2021-01-19T23:54:07.813 ... 4 0 \n", - "4 2444 2019-06-15T18:25:58.513 ... 2 0 \n", - "\n", - " ContentLicense ParentId ClosedDate FavoriteCount CommunityOwnedDate \\\n", - "0 CC BY-SA 4.0 0 NaN NaN NaN \n", - "1 CC BY-SA 4.0 0 NaN NaN NaN \n", - "2 CC BY-SA 3.0 1 NaN NaN NaN \n", - "3 CC BY-SA 3.0 0 NaN NaN NaN \n", - "4 CC BY-SA 4.0 0 NaN NaN NaN \n", - "\n", - " LastEditorDisplayName OwnerDisplayName \\\n", - "0 NaN NaN \n", - "1 NaN NaN \n", - "2 NaN NaN \n", - "3 NaN NaN \n", - "4 NaN NaN \n", - "\n", - " DataSource \n", - "0 https://ia600107.us.archive.org/view_archive.p... \n", - "1 https://ia600107.us.archive.org/view_archive.p... \n", - "2 https://ia600107.us.archive.org/view_archive.p... \n", - "3 https://ia600107.us.archive.org/view_archive.p... \n", - "4 https://ia600107.us.archive.org/view_archive.p... \n", - "\n", - "[5 rows x 23 columns]" - ], - "text/html": [ - "\n", - "

\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
IdPostTypeIdAcceptedAnswerIdCreationDateScoreViewCountBodyOwnerUserIdLastEditorUserIdLastEditDate...AnswerCountCommentCountContentLicenseParentIdClosedDateFavoriteCountCommunityOwnedDateLastEditorDisplayNameOwnerDisplayNameDataSource
01132016-08-02T15:39:14.94710710<p>What does \"backprop\" mean? Is the \"backprop...824442019-11-16T17:56:22.093...50CC BY-SA 4.00NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
12192016-08-02T15:40:20.623141008<p>Does increasing the noise in data help to i...824442019-02-23T22:36:19.090...30CC BY-SA 4.00NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
23202016-08-02T15:40:24.820150<p>\"Backprop\" is the same as \"backpropagation\"...4NaNNaN...00CC BY-SA 3.01NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
341122016-08-02T15:41:22.020331266<p>When you're writing your algorithm, how do ...824442021-01-19T23:54:07.813...40CC BY-SA 3.00NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
461202016-08-02T15:43:35.4607279<p>Given the following definition of an intell...2924442019-06-15T18:25:58.513...20CC BY-SA 4.00NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
\n", - "

5 rows × 23 columns

\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - " \n", - "
\n", - "
\n", - " " - ] - }, - "metadata": {}, - "execution_count": 219 - } - ] - }, - { - "cell_type": "markdown", - "source": [ - "# Transformations" - ], - "metadata": { - "id": "RAzTR7zY3oan" - } - }, - { - "cell_type": "code", - "source": [ - "def filter_only_questions_with_accepted_answers(df):\n", - " \"\"\" **TODO**\n", - " Filter only to Questions with Accepted Answers\n", - "\n", - " Filter dataframe by questions that have accepted answers, should also include \n", - " all rows of answers for those questions, even if not accepted.\n", - "\n", - " Parameters:\n", - " df (DataFrame): containing a \"AcceptedAnswerId\", \"Id\", and \"ParentId\" columns \n", - "\n", - " Returns:\n", - " df (DataFrame): current dataframe with filtered results\n", - " \"\"\" \n", - " df = df[(df['AcceptedAnswerId'].notnull()) | (df['ParentId'] == df['Id'])]\n", - "\n", - "\n", - "def filter_scores_above(df, question_score_threshold: int = 20, answer_score_threshold: int = 20):\n", - " \"\"\" **TODO**\n", - " Filter Dataframe by minimum scores\n", - "\n", - " Filter Question and Answer columns by score thresholds to trim lower scoring results\n", - "\n", - " Parameters:\n", - " df (DataFrame): containing a \"Score\" column\n", - "\n", - " Returns:\n", - " df (DataFrame): current dataframe with filtered results \n", - " \"\"\" \n", - " df = df[\n", - " ((df['Score'] >= question_score_threshold) & (df.PostTypeId == 1)) | \n", - " ((df['Score'] >= answer_score_threshold) & (df.PostTypeId == 2))\n", - " ]\n", - "\n", - "\n", - "def convert_html_to_text(df, column: str = \"Body\"):\n", - " \"\"\"\n", - " Convert HTML tags to pure text\n", - "\n", - " Feeds HTML text body into BeautifulSoup to parse it to only text. Set aside as \n", - " function to provide option to skip\n", - "\n", - " Parameters:\n", - " df (DataFrame): containing a \"Body\" column with HTML \n", - "\n", - " Returns:\n", - " df (DataFrame): current dataframe with parsed column \n", - " \"\"\" \n", - " df.dropna(subset=[column], inplace=True)\n", - " df[f\"{column}Clean\"] = df[column].apply(lambda row: bs(row, 'html.parser').text)\n", - "\n", - "\n", - "def clean_tags(df):\n", - " \"\"\"\n", - " Convert Tags into Comma separated\n", - "\n", - " Converts Tag slugs into commas separated tags\n", - "\n", - " Parameters:\n", - " df (DataFrame): containing a \"Tags\" column with slugs\n", - "\n", - " Returns:\n", - " df (DataFrame): current dataframe with parsed column\n", - " \"\"\" \n", - " df['TagsClean'] = (df['Tags']\n", - " .str.replace('-',' ')\n", - " .str.replace('><', ', ')\n", - " .str.replace('<','')\n", - " .str.replace('>','')\n", - " )\n", - "\n", - "\n", - "# filter_only_questions_with_accepted_answers(df)\n", - "# filter_scores_above(df)\n", - "convert_html_to_text(df)\n", - "clean_tags(df)\n", - "\n", - "df[['Body', 'BodyClean', 'Tags', 'TagsClean']]\n", - "# print(df.shape)" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 424 - }, - "id": "qyUqc31Z3Z9g", - "outputId": "18dce8b4-af26-49c9-ee73-6c677177b516" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - " Body \\\n", - "0

What does \"backprop\" mean? Is the \"backprop... \n", - "1

Does increasing the noise in data help to i... \n", - "2

\"Backprop\" is the same as \"backpropagation\"... \n", - "3

When you're writing your algorithm, how do ... \n", - "4

Given the following definition of an intell... \n", - "... ... \n", - "23174

The purpose of evaluating the state and act... \n", - "23175

In machine translation, convolution is a te... \n", - "23176

One of the key features of ChatGPT is its a... \n", - "23177

Given a neural network model for Covid-19 c... \n", - "23178

My question is more related to the fundamen... \n", - "\n", - " BodyClean \\\n", - "0 What does \"backprop\" mean? Is the \"backprop\" t... \n", - "1 Does increasing the noise in data help to impr... \n", - "2 \"Backprop\" is the same as \"backpropagation\": i... \n", - "3 When you're writing your algorithm, how do you... \n", - "4 Given the following definition of an intellige... \n", - "... ... \n", - "23174 The purpose of evaluating the state and action... \n", - "23175 In machine translation, convolution is a techn... \n", - "23176 One of the key features of ChatGPT is its abil... \n", - "23177 Given a neural network model for Covid-19 clas... \n", - "23178 My question is more related to the fundamental... \n", - "\n", - " Tags \\\n", - "0 ... \n", - "4 \n", - "... ... \n", - "23174 NaN \n", - "23175 NaN \n", - "23176 NaN \n", - "23177 \n", - "23178 \n", - "\n", - " TagsClean \n", - "0 neural networks, backpropagation, terminology,... \n", - "1 neural networks, machine learning, statistical... \n", - "2 NaN \n", - "3 neural networks, hyperparameter optimization, ... \n", - "4 philosophy, definitions, intelligent agent \n", - "... ... \n", - "23174 NaN \n", - "23175 NaN \n", - "23176 NaN \n", - "23177 neural networks, homework \n", - "23178 search, constraint satisfaction problems \n", - "\n", - "[23179 rows x 4 columns]" - ], - "text/html": [ - "\n", - "

\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
BodyBodyCleanTagsTagsClean
0<p>What does \"backprop\" mean? Is the \"backprop...What does \"backprop\" mean? Is the \"backprop\" t...<neural-networks><backpropagation><terminology...neural networks, backpropagation, terminology,...
1<p>Does increasing the noise in data help to i...Does increasing the noise in data help to impr...<neural-networks><machine-learning><statistica...neural networks, machine learning, statistical...
2<p>\"Backprop\" is the same as \"backpropagation\"...\"Backprop\" is the same as \"backpropagation\": i...NaNNaN
3<p>When you're writing your algorithm, how do ...When you're writing your algorithm, how do you...<neural-networks><hyperparameter-optimization>...neural networks, hyperparameter optimization, ...
4<p>Given the following definition of an intell...Given the following definition of an intellige...<philosophy><definitions><intelligent-agent>philosophy, definitions, intelligent agent
...............
23174<p>The purpose of evaluating the state and act...The purpose of evaluating the state and action...NaNNaN
23175<p>In machine translation, convolution is a te...In machine translation, convolution is a techn...NaNNaN
23176<p>One of the key features of ChatGPT is its a...One of the key features of ChatGPT is its abil...NaNNaN
23177<p>Given a neural network model for Covid-19 c...Given a neural network model for Covid-19 clas...<neural-networks><homework>neural networks, homework
23178<p>My question is more related to the fundamen...My question is more related to the fundamental...<search><constraint-satisfaction-problems>search, constraint satisfaction problems
\n", - "

23179 rows × 4 columns

\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - " \n", - "
\n", - "
\n", - " " - ] - }, - "metadata": {}, - "execution_count": 220 - } - ] - }, - { - "cell_type": "markdown", - "source": [ - "This groups questions with answers so that a row with a question also has a column with an answer. It then creates an AcceptedAnswerFlag column that is True if the answer was accepted by the person who asked the question. Changing the `number_of_results` variable will limit the number of answers you want to keep." - ], - "metadata": { - "id": "C09Bwdw-44PZ" - } - }, - { - "cell_type": "code", - "source": [ - "questions = df[df.PostTypeId == 1]\n", - "answers = df[df.PostTypeId == 2]\n", - "\n", - "df = pd.merge(\n", - "\tquestions,\n", - "\tanswers[\n", - " ['Id', 'CreationDate', 'Score', 'ViewCount', 'CommentCount', \n", - " 'ContentLicense', 'TagsClean', 'BodyClean', 'ParentId']\n", - "\t],\n", - "\tleft_on='Id',\n", - "\tright_on='ParentId',\n", - "\tsuffixes=('_q', '_a'),\n", - "\thow='left'\n", - ")\n", - "\n", - "df['AcceptedAnswerFlag'] = df.apply(lambda row: row['Id_a'] == row['AcceptedAnswerId'], axis=1)\n", - "\n", - "df = df.rename(columns={\n", - "\t'BodyClean_q': 'Question',\n", - "\t'Score_q': 'QuestionScore',\n", - "\t'TagsClean_q': 'QuestionTags',\n", - "\t'BodyClean_a': 'Answer',\n", - "\t'Score_a': \"AnswerScore\",\n", - "\t'ContentLicense_q':'QuestionContentLicense',\n", - "\t'ContentLicense_a':'AnswerContentLicense',\n", - "\t'CreationDate_q': 'CreationDate'\n", - "})\n", - "\n", - "## Set the number of results to a lower number to only return top N rated Answers.\n", - "number_of_results = 25\n", - "df = (\n", - " df.sort_values(by=['AcceptedAnswerFlag', 'AnswerScore'], ascending=[False, False])\n", - " .groupby('Question')\n", - " .head(number_of_results)\n", - " .reset_index(drop=True)\n", - " )\n", - "\n", - "df[['Id_q', 'Question', 'QuestionScore', 'QuestionTags', 'Id_a', 'Answer', 'AnswerScore', 'AcceptedAnswerFlag']].head()" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 337 - }, - "id": "Bgz2fZ9k43Ab", - "outputId": "28896d69-03cd-4877-fdfb-ae48dafa4ff3" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - " Id_q Question QuestionScore \\\n", - "0 1768 In Portal 2 we see that AI's can be \"killed\" b... 175 \n", - "1 10623 What is self-supervised learning in machine le... 91 \n", - "2 111 Obviously, self-driving cars aren't perfect, s... 100 \n", - "3 14224 If the original purpose for developing AI was ... 69 \n", - "4 1479 Do scientists or research experts know from th... 94 \n", - "\n", - " QuestionTags Id_a \\\n", - "0 philosophy, decision theory, mythology of ai, ... 1769.0 \n", - "1 machine learning, comparison, supervised learn... 10624.0 \n", - "2 philosophy, ethics, autonomous vehicles, decis... 1790.0 \n", - "3 philosophy, social, explainable ai 14247.0 \n", - "4 neural networks, deep learning, convolutional ... 4044.0 \n", - "\n", - " Answer AnswerScore \\\n", - "0 This classic problem exhibits a basic misunder... 146.0 \n", - "1 Introduction\\nThe term self-supervised learnin... 90.0 \n", - "2 \\nHow could self-driving cars make ethical dec... 76.0 \n", - "3 As argued by Selvaraju et al., there are three... 75.0 \n", - "4 There are many approaches that aim to make a t... 69.0 \n", - "\n", - " AcceptedAnswerFlag \n", - "0 True \n", - "1 True \n", - "2 True \n", - "3 True \n", - "4 True " - ], - "text/html": [ - "\n", - "
\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Id_qQuestionQuestionScoreQuestionTagsId_aAnswerAnswerScoreAcceptedAnswerFlag
01768In Portal 2 we see that AI's can be \"killed\" b...175philosophy, decision theory, mythology of ai, ...1769.0This classic problem exhibits a basic misunder...146.0True
110623What is self-supervised learning in machine le...91machine learning, comparison, supervised learn...10624.0Introduction\\nThe term self-supervised learnin...90.0True
2111Obviously, self-driving cars aren't perfect, s...100philosophy, ethics, autonomous vehicles, decis...1790.0\\nHow could self-driving cars make ethical dec...76.0True
314224If the original purpose for developing AI was ...69philosophy, social, explainable ai14247.0As argued by Selvaraju et al., there are three...75.0True
41479Do scientists or research experts know from th...94neural networks, deep learning, convolutional ...4044.0There are many approaches that aim to make a t...69.0True
\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - " \n", - "
\n", - "
\n", - " " - ] - }, - "metadata": {}, - "execution_count": 221 - } - ] - }, - { - "cell_type": "code", - "source": [ - "testing_id = df.Id_q.mode()[0]\n", - "df[(df.Id_q == testing_id) | (df.ParentId_a == testing_id)][['Id_q', 'Question', 'ParentId_a', 'AcceptedAnswerId', 'Id_a', 'Answer', 'AnswerScore', 'AcceptedAnswerFlag']]\n", - "# df[['Id_q', 'Question', 'ParentId_a', 'AcceptedAnswerId', 'Id_a', 'Answer', 'AnswerScore', 'AcceptedAnswerFlag']]" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 645 - }, - "id": "eds1K8WL9QPo", - "outputId": "bc526503-d6dd-428f-fa98-ad419d26a7dc" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - " Id_q Question ParentId_a \\\n", - "7 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "3662 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "3713 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "3788 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "3821 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "3882 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "4389 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "4849 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "4850 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "5763 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "5764 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "5765 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "7462 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "7463 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "7464 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "7465 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "7466 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "7467 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "9481 15730 As a human being, we can think infinity. In pr... 15730.0 \n", - "\n", - " AcceptedAnswerId Id_a \\\n", - "7 15744 15744.0 \n", - "3662 15744 15753.0 \n", - "3713 15744 15747.0 \n", - "3788 15744 15756.0 \n", - "3821 15744 15758.0 \n", - "3882 15744 15762.0 \n", - "4389 15744 15783.0 \n", - "4849 15744 15740.0 \n", - "4850 15744 15803.0 \n", - "5763 15744 15768.0 \n", - "5764 15744 15810.0 \n", - "5765 15744 15943.0 \n", - "7462 15744 15779.0 \n", - "7463 15744 15787.0 \n", - "7464 15744 15801.0 \n", - "7465 15744 15930.0 \n", - "7466 15744 15934.0 \n", - "7467 15744 15938.0 \n", - "9481 15744 15931.0 \n", - "\n", - " Answer AnswerScore \\\n", - "7 I think this is a fairly common misconception ... 62.0 \n", - "3662 I think your premise is flawed.\\nYou seem to a... 19.0 \n", - "3713 TL;DR: The subtleties of infinity are made app... 12.0 \n", - "3788 In Haskell, you can type:\\nprint [1..]\\nand it... 9.0 \n", - "3821 I believe humans can be said to understand inf... 8.0 \n", - "3882 (There's a summary at the bottom for those who... 7.0 \n", - "4389 Then premise assumes that humans \"understand\" ... 4.0 \n", - "4849 By adding some rules for infinity in arithmeti... 3.0 \n", - "4850 I think the concept that is missing in the dis... 3.0 \n", - "5763 Computers don't understand \"infinity\" or even ... 2.0 \n", - "5764 The Questions That Computers Can Never Answer ... 2.0 \n", - "5765 John Doucette's answer covers my thoughts on t... 2.0 \n", - "7462 I would think that a computer couldn’t underst... 1.0 \n", - "7463 The \"concept\" of infinity is 1 thing to unders... 1.0 \n", - "7464 Just food for thought: how about if we try to ... 1.0 \n", - "7465 Its arguable if we humans understand infinity.... 1.0 \n", - "7466 Well -- just to touch on the question of peopl... 1.0 \n", - "7467 Humans certainly don't understand infinity. Cu... 1.0 \n", - "9481 I think the property humans have which compute... 0.0 \n", - "\n", - " AcceptedAnswerFlag \n", - "7 True \n", - "3662 False \n", - "3713 False \n", - "3788 False \n", - "3821 False \n", - "3882 False \n", - "4389 False \n", - "4849 False \n", - "4850 False \n", - "5763 False \n", - "5764 False \n", - "5765 False \n", - "7462 False \n", - "7463 False \n", - "7464 False \n", - "7465 False \n", - "7466 False \n", - "7467 False \n", - "9481 False " - ], - "text/html": [ - "\n", - "
\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Id_qQuestionParentId_aAcceptedAnswerIdId_aAnswerAnswerScoreAcceptedAnswerFlag
715730As a human being, we can think infinity. In pr...15730.01574415744.0I think this is a fairly common misconception ...62.0True
366215730As a human being, we can think infinity. In pr...15730.01574415753.0I think your premise is flawed.\\nYou seem to a...19.0False
371315730As a human being, we can think infinity. In pr...15730.01574415747.0TL;DR: The subtleties of infinity are made app...12.0False
378815730As a human being, we can think infinity. In pr...15730.01574415756.0In Haskell, you can type:\\nprint [1..]\\nand it...9.0False
382115730As a human being, we can think infinity. In pr...15730.01574415758.0I believe humans can be said to understand inf...8.0False
388215730As a human being, we can think infinity. In pr...15730.01574415762.0(There's a summary at the bottom for those who...7.0False
438915730As a human being, we can think infinity. In pr...15730.01574415783.0Then premise assumes that humans \"understand\" ...4.0False
484915730As a human being, we can think infinity. In pr...15730.01574415740.0By adding some rules for infinity in arithmeti...3.0False
485015730As a human being, we can think infinity. In pr...15730.01574415803.0I think the concept that is missing in the dis...3.0False
576315730As a human being, we can think infinity. In pr...15730.01574415768.0Computers don't understand \"infinity\" or even ...2.0False
576415730As a human being, we can think infinity. In pr...15730.01574415810.0The Questions That Computers Can Never Answer ...2.0False
576515730As a human being, we can think infinity. In pr...15730.01574415943.0John Doucette's answer covers my thoughts on t...2.0False
746215730As a human being, we can think infinity. In pr...15730.01574415779.0I would think that a computer couldn’t underst...1.0False
746315730As a human being, we can think infinity. In pr...15730.01574415787.0The \"concept\" of infinity is 1 thing to unders...1.0False
746415730As a human being, we can think infinity. In pr...15730.01574415801.0Just food for thought: how about if we try to ...1.0False
746515730As a human being, we can think infinity. In pr...15730.01574415930.0Its arguable if we humans understand infinity....1.0False
746615730As a human being, we can think infinity. In pr...15730.01574415934.0Well -- just to touch on the question of peopl...1.0False
746715730As a human being, we can think infinity. In pr...15730.01574415938.0Humans certainly don't understand infinity. Cu...1.0False
948115730As a human being, we can think infinity. In pr...15730.01574415931.0I think the property humans have which compute...0.0False
\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - " \n", - "
\n", - "
\n", - " " - ] - }, - "metadata": {}, - "execution_count": 222 - } - ] - }, - { - "cell_type": "markdown", - "source": [ - "# Create JSONL version of Dataframe\n", - "This groups the dataframe by question data and creates nested list of Answers for that group. The entire list contains individual JSON objects, each representing a single question in the dataset with a key, Answers, which contains a list of dictionaries for each answer to the question." - ], - "metadata": { - "id": "gXgpXEO7DCbj" - } - }, - { - "cell_type": "code", - "source": [ - "j = (\n", - "\tdf.groupby(['Title', 'Question', 'QuestionScore', 'QuestionTags', 'QuestionContentLicense', 'DataSource', 'CreationDate'])\n", - "\t.apply(lambda x: x[['Answer', 'AnswerScore', 'AcceptedAnswerFlag']].to_dict('records'))\n", - "\t.reset_index()\n", - "\t.rename(columns={0:'Answers'})\n", - "\t.to_json(orient='records')\n", - ")\n", - "\n", - "data = json.loads(j)\n", - "\n", - "for post in data:\n", - " if len(post.get('Answers')) >= 4:\n", - " print(\n", - " json.dumps(post, indent=4)\n", - " )\n", - " break" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "OBR58MSRzAMP", - "outputId": "c7da1e6c-3a97-465d-c9ba-7e055cb0d751" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "{\n", - " \"Title\": \"1 hidden layer with 1000 neurons vs. 10 hidden layers with 100 neurons\",\n", - " \"Question\": \"These types of questions may be problem-dependent, but I have tried to find research that addresses the question whether the number of hidden layers and their size (number of neurons in each layer) really matter or not.\\nSo my question is, does it really matter if we for example have 1 large hidden layer of 1000 neurons vs. 10 hidden layers with 100 neurons each?\\n\",\n", - " \"QuestionScore\": 16,\n", - " \"QuestionTags\": \"neural networks\",\n", - " \"QuestionContentLicense\": \"CC BY-SA 3.0\",\n", - " \"DataSource\": \"https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml\",\n", - " \"CreationDate\": \"2017-05-04T13:06:37.990\",\n", - " \"Answers\": [\n", - " {\n", - " \"Answer\": \"Basically, having multiple layers (aka a deep network) makes your network more eager to recognize certain aspects of input data. For example, if you have the details of a house (size, lawn size, location etc.) as input and want to predict the price. The first layer may predict:\\n\\nBig area, higher price\\nSmall amount of bedrooms, lower price\\n\\nThe second layer might conclude:\\n\\nBig area + small amount of bedrooms = large bedrooms = +- effect\\n\\nYes, one layer can also 'detect' the stats, however it will require more neurons as it cannot rely on other neurons to do 'parts' of the total calculation required to detect that stat.\\nCheck out this answer\\n\",\n", - " \"AnswerScore\": 13.0,\n", - " \"AcceptedAnswerFlag\": true\n", - " },\n", - " {\n", - " \"Answer\": \"There are so many aspects.\\n1. Training:\\nTraining deep nets is a hard job due to the vanishing (rearly exploding) gradient problem. So building a 10x100 neural-net is not recommended.\\n2. Trained network performance:\\n\\nInformation loss:\\nThe classical usage of neural nets is the classification problem. Which means we want to get some well defined information from the data. (Ex. Is there a face in the picture or not.)\\nSo usually classification problem has a lot of input, and few output, whats more the size of the hidden layers are descend from input to output.\\nHowever, we loss information using less neurons layer by layer. (Ie. We cannot reproduce the original image based on the fact that is there a face on it or no.) So you must know that you loss information using 100 neurons if the size of the input is (lets say) 1000.\\nInformation complexity: However the deeper nets (as Tomas W mentioned) can fetch more complex information from the input data. Inspite of this its not recommended to use 10 fully connected layers. Its recommended to use convolutional/relu/maxpooling or other type of layers. Firest layers can compress the some essential part of the inputs. (Ex is there any line in a specific part of the picture) Second layers can say: There is a specific shape in this place in the picture. Etc etc.\\n\\nSo deeper nets are more \\\"clever\\\" but 10x100 net structure is a good choice.\\n\",\n", - " \"AnswerScore\": 4.0,\n", - " \"AcceptedAnswerFlag\": false\n", - " },\n", - " {\n", - " \"Answer\": \"If the problem you are solving is linearly separable, one layer of 1000 neurons can do better job than 10 layers with each of 100 neurons.\\nIf the problem is non linear and not convex, then you need deep neural nets. \\n\",\n", - " \"AnswerScore\": 1.0,\n", - " \"AcceptedAnswerFlag\": false\n", - " },\n", - " {\n", - " \"Answer\": \"\\nI think you have a confusion in the basics of the neural networks.\\n Every layer has a separate activation function and input/output\\n connection weights.\\n\\nThe output of the first hidden layer will be multiplied by a weight, processed by an activation function in the next layer and so on.\\nSingle layer neural networks are very limited for simple tasks, deeper NN can perform far better than a single layer. \\nHowever, do not use more than layer if your application is not fairly complex. In conclusion, 100 neurons layer does not mean better neural network than 10 layers x 10 neurons but 10 layers are something imaginary unless you are doing deep learning. start with 10 neurons in the hidden layer and try to add layers or add more neurons to the same layer to see the difference. learning with more layers will be easier but more training time is required.\\n\",\n", - " \"AnswerScore\": 0.0,\n", - " \"AcceptedAnswerFlag\": false\n", - " }\n", - " ]\n", - "}\n" - ] - } - ] - }, - { - "cell_type": "markdown", - "source": [ - "# Save file\n", - "\n", - "Files can be saved as JSON, JSONL, CSV, or Parquet" - ], - "metadata": { - "id": "PlNjrpXaDm1_" - } - }, - { - "cell_type": "code", - "source": [ - "file_name = dataset_name\n", - "\n", - "def save_data(data: list, file_name: str, file_type: str = ['csv', 'json', 'jsonl', 'parquet']):\n", - " \"\"\"\n", - " Save Data to file\n", - "\n", - " Save Data list to file as either JSON or JSONL\n", - "\n", - " Parameters:\n", - " data (list): list of dictionaries\n", - " file_name (str): name of file (no extension)\n", - " jsonl (bool): to save file as either JSON or JSONL\n", - " \"\"\" \n", - " file_type = file_type.lower()\n", - "\n", - " if file_type == \"csv\" and isinstance(data, pd.DataFrame):\n", - " data.to_csv(f\"/content/{file_name}.csv\", index=False)\n", - "\n", - " elif file_type == \"json\" and isinstance(data, list):\n", - " print(\n", - " json.dumps(data, indent=4),\n", - " file=open(f\"/content/{file_name}.json\", 'w')\n", - " )\n", - "\n", - " elif file_type == \"jsonl\" and isinstance(data, list):\n", - " for item in data:\n", - " print(json.dumps(item), file=open(f\"/content/{file_name}.jsonl\", 'a'))\n", - "\n", - " elif file_type == \"parquet\" and isinstance(data, pd.DataFrame):\n", - " data.to_parquet(f\"/content/{file_name}.parquet\", index=False)\n", - " \n", - " else:\n", - " print(\"Data should be either of List type for JSON and JSONL, or Pandas Dataframes for CSV and Parquet\")\n", - "\n", - "\n", - "\n", - "# save_data(data=data, file_name=file_name, file_type='jsonl')\n", - "# save_data(data=df, file_name=file_name, file_type='parquet')" - ], - "metadata": { - "id": "CU0gWRGQDqIs", - "colab": { - "base_uri": "https://localhost:8080/" - }, - "outputId": "9646e475-cedd-46f1-f9b8-7eb1fbc703c7" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Data should be either of List type for JSON and JSONL, or Pandas Dataframes for CSV and Parquet\n" - ] - } - ] - }, - { - "cell_type": "markdown", - "source": [ - "# Open-Assistant Data Scheme\n", - "\n", - "Testing putting the data into the Open-Assistant Data Scheme\n", - "\n", - "https://github.com/LAION-AI/Open-Assistant/blob/main/docs/data_schemas.md" - ], - "metadata": { - "id": "BdN3hKxtgH7f" - } - }, - { - "cell_type": "code", - "source": [ - "from typing import TypeVar, List, Dict, Any, Literal\n", - "from json import JSONEncoder\n", - "\n", - "T = TypeVar('T', bound='ConversationTreeNode')\n", - "\n", - "\n", - "class ConversationTreeNode:\n", - " text: str # The text of the node\n", - " role: Literal['prompter', 'assistant'] # Whether the node is a user prompt/follow-up or an assistant response\n", - " children: List[T] # The children of the node (if you have a linear conversation, this will be of length 0 or 1)\n", - " metadata: Dict[str, Any] # Node metadata (see below)\n", - "\n", - " def __init__(self, text: str, role: Literal['prompter', 'assistant'], children: List[T], metadata: Dict[str, Any]) -> None:\n", - " self.text = text\n", - " self.role = role\n", - " self.children = children\n", - " self.metadata = metadata\n", - "\n", - "\n", - "class ConversationTree:\n", - " root: ConversationTreeNode # The node containing the initial prompt\n", - " metadata: Dict[str, Any] # Tree metadata, different from root node metadata.\n", - "\n", - " def __init__(self, root: ConversationTreeNode, metadata: Dict[str, Any]) -> None:\n", - " self.root = root\n", - " self.metadata = metadata\n", - "\n", - "\n", - "# subclass JSONEncoder\n", - "class TreeEncoder(JSONEncoder):\n", - " def default(self, o):\n", - " return o.__dict__" - ], - "metadata": { - "id": "n8ubYQxegNSY" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "conversation_forest = []\n", - "\n", - "tree_metadata_map = {'Title': str, 'QuestionContentLicense': str, 'DataSource': str, 'CreationDate': str}\n", - "question_metadata_map = {'QuestionScore': int, 'QuestionTags': str}\n", - "answer_metadata_map = {'AnswerScore': int, 'AcceptedAnswerFlag': bool}\n", - "\n", - "\n", - "for item in data:\n", - " prompt = item.get('Question')\n", - " metadata = {k: v for k, v in item.items() if k in question_metadata_map}\n", - " root = ConversationTreeNode(text=prompt, role='prompter', children=[], metadata=metadata)\n", - "\n", - " for answer in item.get('Answers'):\n", - " response = answer.get('Answer')\n", - " metadata = {k: v for k, v in answer.items() if k in answer_metadata_map}\n", - " child = ConversationTreeNode(text=response, role='assistant', children=[], metadata=metadata)\n", - " root.children.append(child)\n", - " \n", - " metadata = {k: v for k, v in item.items() if k in tree_metadata_map}\n", - " conversation_tree = ConversationTree(root=root, metadata=metadata)\n", - " conversation_forest.append(conversation_tree)\n", - "\n", - "\n", - "conversation_forest_json = [\n", - " json.loads(TreeEncoder().encode(conversation_tree))\n", - " for conversation_tree in conversation_forest\n", - " ]\n", - "\n", - "\n", - "# print(json.dumps(conversation_forest_json[0], indent=4))\n", - "\n", - "\n", - "\n", - "print(\n", - " json.dumps(conversation_forest_json, indent=4),\n", - " file=open(f\"/content/{file_name}.json\", 'w')\n", - ")" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "eE0fkytExSGl", - "outputId": "594632d6-f98c-49b8-af86-25f7f5e2ce06" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "{\n", - " \"root\": {\n", - " \"text\": \"Science Fiction has frequently shown AI to be a threat to the very existence of mankind. AI systems have often been the antagonists in many works of fiction, from 2001: A Space Odyssey through to The Terminator and beyond.\\nThe Media seems to buy into this trope as well. And in recent years we have had people like Elon Musk warn us of the dangers of an impending AI revolution, stating that AI is more dangerous than nukes.\\nAnd, apparently, experts think that we will be seeing this AI revolution in the next 100 years.\\nHowever, from my (albeit limited) study of AI, I get the impression that they are all wrong. I am going to outline my understanding below, please correct me if I am wrong:\\n\\nFirstly, all of these things seem to be confusing Artificial Intelligence with Artificial Consciousness. AI is essentially a system to make intelligent decisions, whereas AC is more like the \\\"self-aware\\\" systems that are shown in science fiction.\\n\\nNot AI itself, but intelligence and intelligent decision-making algorithms are something we've been working with and enhancing since before computers have been around. Moving this over to an artificial framework is fairly easy. However, consciousness is still something we are learning about. My guess is we won't be able to re-create something artificially if we barely understand how it works in the real world.\\n\\nSo, my conclusion is that no AI system will be able to learn enough to start thinking for itself, and that all our warnings of AI are completely unjustified.\\n\\nThe real danger comes from AC, which we are a long, long way from realizing because we are still a long way off from defining exactly what consciousness is, let alone understanding it.\\n\\n\\n\\nSo, my question is, assuming that my understanding is correct, are any efforts are being made by companies or organizations that work with AI to correct these popular misunderstandings in sci-fi, the media, and/or the public?\\nOr are the proponents of AI ambivalent towards this public fear-mongering?\\nI understand that the fear mongering is going to remain popular for some time, as bad news sells better than good news. I am just wondering if the general attitude from AI organizations is to ignore this popular misconception, or whether a concerted effort is being made to fight against these AI myths (but unfortunately nobody in the media is listening or cares).\\n\",\n", - " \"role\": \"prompter\",\n", - " \"children\": [\n", - " {\n", - " \"text\": \"Nothing. \\nIts in almost everyone's favor for it to stay that way financially. Having non-technical individuals associate AI with terminators makes a perception that the field has greater capabilities than it does $\\\\rightarrow$ this leads to grants, funding, etc... \\nIs there any negative? Yes. Misconceptions always have drawbacks. We see the creation of dumb ethics boards and such cough cough Elon Musk.\\nBut if history has anything to say about this, as the field gains popularity (which it is dnagerously quick), information will spread by definition, and eventually misconceptions will be laid to rest.\\nNote that this answer is biased and based upon my own opinions\\n\",\n", - " \"role\": \"assistant\",\n", - " \"children\": [],\n", - " \"metadata\": {\n", - " \"AnswerScore\": 2.0,\n", - " \"AcceptedAnswerFlag\": true\n", - " }\n", - " }\n", - " ],\n", - " \"metadata\": {\n", - " \"QuestionScore\": 5,\n", - " \"QuestionTags\": \"social, artificial consciousness\"\n", - " }\n", - " },\n", - " \"metadata\": {\n", - " \"Title\": \"\\\"AI will kill us all! The machines will rise up!\\\" - what is being done to dispel such myths?\",\n", - " \"QuestionContentLicense\": \"CC BY-SA 4.0\",\n", - " \"DataSource\": \"https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml\",\n", - " \"CreationDate\": \"2019-10-16T13:57:37.143\"\n", - " }\n", - "}\n" - ] - } - ] + "output_type": "stream", + "name": "stdout", + "text": [ + "dict_keys(['3dprinting_meta', '3dprinting', 'Stackoverflow_com_Posts_7z', 'academia_meta', 'academia', 'ai_meta', 'ai', 'android_meta', 'android', 'anime_meta', 'anime', 'apple_meta', 'apple', 'arduino_meta', 'arduino', 'askubuntu_com_7z', 'astronomy_meta', 'astronomy', 'aviation_meta', 'aviation', 'avp_meta', 'avp', 'beer_meta', 'beer', 'bicycles_meta', 'bicycles', 'bioacoustics_meta', 'bioacoustics', 'bioinformatics_meta', 'bioinformatics', 'biology_meta', 'biology', 'bitcoin_meta', 'bitcoin', 'blender_meta', 'blender', 'boardgames_meta', 'boardgames', 'bricks_meta', 'bricks', 'buddhism_meta', 'buddhism', 'cardano_meta', 'cardano', 'chemistry_meta', 'chemistry', 'chess_meta', 'chess', 'chinese_meta', 'chinese', 'christianity_meta', 'christianity', 'civicrm_meta', 'civicrm', 'codegolf_meta', 'codegolf', 'codereview_meta', 'codereview', 'coffee_meta', 'coffee', 'cogsci_meta', 'cogsci', 'computergraphics_meta', 'computergraphics', 'conlang_meta', 'conlang', 'cooking_meta', 'cooking', 'craftcms_meta', 'craftcms', 'crafts_meta', 'crafts', 'crypto_meta', 'crypto', 'cs_meta', 'cs', 'cseducators_meta', 'cseducators', 'cstheory_meta', 'cstheory', 'datascience_meta', 'datascience', 'dba_meta', 'dba', 'devops_meta', 'devops', 'diy_meta', 'diy', 'drones_meta', 'drones', 'drupal_meta', 'drupal', 'dsp_meta', 'dsp', 'earthscience_meta', 'earthscience', 'ebooks_meta', 'ebooks', 'economics_meta', 'economics', 'electronics_meta', 'electronics', 'elementaryos_meta', 'elementaryos', 'ell_meta', 'ell', 'emacs_meta', 'emacs', 'engineering_meta', 'engineering', 'english_meta', 'english', 'eosio_meta', 'eosio', 'es_meta_stackoverflow_com_7z', 'es_stackoverflow_com_7z', 'esperanto_meta', 'esperanto', 'ethereum_meta', 'ethereum', 'expatriates_meta', 'expatriates', 'expressionengine_meta', 'expressionengine', 'fitness_meta', 'fitness', 'freelancing_meta', 'freelancing', 'french_meta', 'french', 'gamedev_meta', 'gamedev', 'gaming_meta', 'gaming', 'gardening_meta', 'gardening', 'genealogy_meta', 'genealogy', 'german_meta', 'german', 'gis_meta', 'gis', 'graphicdesign_meta', 'graphicdesign', 'ham_meta', 'ham', 'hardwarerecs_meta', 'hardwarerecs', 'health_meta', 'health', 'hermeneutics_meta', 'hermeneutics', 'hinduism_meta', 'hinduism', 'history_meta', 'history', 'homebrew_meta', 'homebrew', 'hsm_meta', 'hsm', 'interpersonal_meta', 'interpersonal', 'iot_meta', 'iot', 'iota_meta', 'iota', 'islam_meta', 'islam', 'italian_meta', 'italian', 'ja_meta_stackoverflow_com_7z', 'ja_stackoverflow_com_7z', 'japanese_meta', 'japanese', 'joomla_meta', 'joomla', 'judaism_meta', 'judaism', 'korean_meta', 'korean', 'languagelearning_meta', 'languagelearning', 'latin_meta', 'latin', 'law_meta', 'law', 'lifehacks_meta', 'lifehacks', 'linguistics_meta', 'linguistics', 'literature_meta', 'literature', 'magento_meta', 'magento', 'martialarts_meta', 'martialarts', 'materials_meta', 'materials', 'math_meta', 'math', 'matheducators_meta', 'matheducators', 'mathematica_meta', 'mathematica', 'mathoverflow_net_7z', 'mechanics_meta', 'mechanics', 'meta_askubuntu_com_7z', 'meta_mathoverflow_net_7z', 'meta_serverfault_com_7z', 'meta', 'meta_stackoverflow_com_7z', 'meta_superuser_com_7z', 'moderators_meta', 'moderators', 'monero_meta', 'monero', 'money_meta', 'money', 'movies_meta', 'movies', 'music_meta', 'music', 'musicfans_meta', 'musicfans', 'mythology_meta', 'mythology', 'networkengineering_meta', 'networkengineering', 'opendata_meta', 'opendata', 'opensource_meta', 'opensource', 'or_meta', 'or', 'outdoors_meta', 'outdoors', 'parenting_meta', 'parenting', 'patents_meta', 'patents', 'pets_meta', 'pets', 'philosophy_meta', 'philosophy', 'photo_meta', 'photo', 'physics_meta', 'physics', 'pm_meta', 'pm', 'poker_meta', 'poker', 'politics_meta', 'politics', 'portuguese_meta', 'portuguese', 'proofassistants_meta', 'proofassistants', 'pt_meta_stackoverflow_com_7z', 'pt_stackoverflow_com_7z', 'puzzling_meta', 'puzzling', 'quant_meta', 'quant', 'quantumcomputing_meta', 'quantumcomputing', 'raspberrypi_meta', 'raspberrypi', 'retrocomputing_meta', 'retrocomputing', 'reverseengineering_meta', 'reverseengineering', 'robotics_meta', 'robotics', 'rpg_meta', 'rpg', 'ru_meta_stackoverflow_com_7z', 'ru_stackoverflow_com_7z', 'rus_meta', 'rus', 'russian_meta', 'russian', 'salesforce_meta', 'salesforce', 'scicomp_meta', 'scicomp', 'scifi_meta', 'scifi', 'security_meta', 'security', 'serverfault_com_7z', 'sharepoint_meta', 'sharepoint', 'sitecore_meta', 'sitecore', 'skeptics_meta', 'skeptics', 'softwareengineering_meta', 'softwareengineering', 'softwarerecs_meta', 'softwarerecs', 'solana_meta', 'solana', 'sound_meta', 'sound', 'space_meta', 'space', 'spanish_meta', 'spanish', 'sports_meta', 'sports', 'sqa_meta', 'sqa', 'stackapps_com_7z', 'stackoverflow_com_Badges_7z', 'stackoverflow_com_Comments_7z', 'stackoverflow_com_PostHistory_7z', 'stackoverflow_com_PostLinks_7z', 'stackoverflow_com_Tags_7z', 'stackoverflow_com_Users_7z', 'stackoverflow_com_Votes_7z', 'stats_meta', 'stats', 'stellar_meta', 'stellar', 'substrate_meta', 'substrate', 'superuser_com_7z', 'sustainability_meta', 'sustainability', 'tex_meta', 'tex', 'tezos_meta', 'tezos', 'tor_meta', 'tor', 'travel_meta', 'travel', 'tridion_meta', 'tridion', 'ukrainian_meta', 'ukrainian', 'unix_meta', 'unix', 'ux_meta', 'ux', 'vegetarianism_meta', 'vegetarianism', 'vi_meta', 'vi', 'webapps_meta', 'webapps', 'webmasters_meta', 'webmasters', 'windowsphone_meta', 'windowsphone', 'woodworking_meta', 'woodworking', 'wordpress_meta', 'wordpress', 'workplace_meta', 'workplace', 'worldbuilding_meta', 'worldbuilding', 'writers_meta', 'writers'])\n", + "https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml\n" + ] } - ] -} \ No newline at end of file + ] + }, + { + "cell_type": "code", + "source": [ + "xml_format_map = {\n", + " \"Id\": int,\n", + " \"PostTypeId\": int,\n", + " \"CreationDate\": str,\n", + " \"Score\": int,\n", + " \"ViewCount\": int,\n", + " \"Body\": str,\n", + " \"AnswerCount\": int,\n", + " \"CommentCount\": int,\n", + " \"ContentLicense\": str,\n", + " \"AcceptedAnswerId\": int,\n", + " \"ParentId\": int,\n", + "}\n", + "\n", + "\n", + "# def extract_xml_file(file_url: str):\n", + "# table = pd.read_xml(file_url)\n", + "# return table\n", + "\n", + "\n", + "def xml_to_df(response: str):\n", + " \"\"\"\n", + " Collect and Manually import XML into Dataframe\n", + "\n", + " pd.read_xml() errors when XML trees are too large, this is just a hack to\n", + " download a XML file and parse into a Dataframe. **Not Tested on huge XML files**\n", + "\n", + " Parameters:\n", + " response (Requests.Response): Requests response object with the XML data\n", + "\n", + " Returns:\n", + " df (DataFrame): A Dataframe from the XML file\n", + " \"\"\"\n", + " soup = bs(response.content, \"xml\")\n", + " posts = soup.find_all(\"row\")\n", + "\n", + " all_posts = [post.attrs for post in posts]\n", + "\n", + " df = pd.DataFrame(all_posts)\n", + " df.AnswerCount.fillna(0, inplace=True)\n", + " df.ViewCount.fillna(0, inplace=True)\n", + " df.AcceptedAnswerId.fillna(0, inplace=True)\n", + " df.ParentId.fillna(0, inplace=True)\n", + " df[\"DataSource\"] = response.url\n", + " df = df.astype(xml_format_map)\n", + " return df\n", + "\n", + "\n", + "dataset_name = \"ai\"\n", + "\n", + "xml_posts_path = urls.get(dataset_name)\n", + "\n", + "\n", + "# df = extract_xml_file(test)\n", + "response = requests.get(xml_posts_path)\n", + "df = xml_to_df(response)\n", + "\n", + "\n", + "print(df.dtypes)\n", + "df.head()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 907 + }, + "id": "-t27RnxdzBYB", + "outputId": "5ec0ceed-c82b-48fa-facd-41b4aae2f9e6" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Id int64\n", + "PostTypeId int64\n", + "AcceptedAnswerId int64\n", + "CreationDate object\n", + "Score int64\n", + "ViewCount int64\n", + "Body object\n", + "OwnerUserId object\n", + "LastEditorUserId object\n", + "LastEditDate object\n", + "LastActivityDate object\n", + "Title object\n", + "Tags object\n", + "AnswerCount int64\n", + "CommentCount int64\n", + "ContentLicense object\n", + "ParentId int64\n", + "ClosedDate object\n", + "FavoriteCount object\n", + "CommunityOwnedDate object\n", + "LastEditorDisplayName object\n", + "OwnerDisplayName object\n", + "DataSource object\n", + "dtype: object\n" + ] + }, + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " Id PostTypeId AcceptedAnswerId CreationDate Score \\\n", + "0 1 1 3 2016-08-02T15:39:14.947 10 \n", + "1 2 1 9 2016-08-02T15:40:20.623 14 \n", + "2 3 2 0 2016-08-02T15:40:24.820 15 \n", + "3 4 1 12 2016-08-02T15:41:22.020 33 \n", + "4 6 1 20 2016-08-02T15:43:35.460 7 \n", + "\n", + " ViewCount Body OwnerUserId \\\n", + "0 710

What does \"backprop\" mean? Is the \"backprop... 8 \n", + "1 1008

Does increasing the noise in data help to i... 8 \n", + "2 0

\"Backprop\" is the same as \"backpropagation\"... 4 \n", + "3 1266

When you're writing your algorithm, how do ... 8 \n", + "4 279

Given the following definition of an intell... 29 \n", + "\n", + " LastEditorUserId LastEditDate ... AnswerCount CommentCount \\\n", + "0 2444 2019-11-16T17:56:22.093 ... 5 0 \n", + "1 2444 2019-02-23T22:36:19.090 ... 3 0 \n", + "2 NaN NaN ... 0 0 \n", + "3 2444 2021-01-19T23:54:07.813 ... 4 0 \n", + "4 2444 2019-06-15T18:25:58.513 ... 2 0 \n", + "\n", + " ContentLicense ParentId ClosedDate FavoriteCount CommunityOwnedDate \\\n", + "0 CC BY-SA 4.0 0 NaN NaN NaN \n", + "1 CC BY-SA 4.0 0 NaN NaN NaN \n", + "2 CC BY-SA 3.0 1 NaN NaN NaN \n", + "3 CC BY-SA 3.0 0 NaN NaN NaN \n", + "4 CC BY-SA 4.0 0 NaN NaN NaN \n", + "\n", + " LastEditorDisplayName OwnerDisplayName \\\n", + "0 NaN NaN \n", + "1 NaN NaN \n", + "2 NaN NaN \n", + "3 NaN NaN \n", + "4 NaN NaN \n", + "\n", + " DataSource \n", + "0 https://ia600107.us.archive.org/view_archive.p... \n", + "1 https://ia600107.us.archive.org/view_archive.p... \n", + "2 https://ia600107.us.archive.org/view_archive.p... \n", + "3 https://ia600107.us.archive.org/view_archive.p... \n", + "4 https://ia600107.us.archive.org/view_archive.p... \n", + "\n", + "[5 rows x 23 columns]" + ], + "text/html": [ + "\n", + "

\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
IdPostTypeIdAcceptedAnswerIdCreationDateScoreViewCountBodyOwnerUserIdLastEditorUserIdLastEditDate...AnswerCountCommentCountContentLicenseParentIdClosedDateFavoriteCountCommunityOwnedDateLastEditorDisplayNameOwnerDisplayNameDataSource
01132016-08-02T15:39:14.94710710<p>What does \"backprop\" mean? Is the \"backprop...824442019-11-16T17:56:22.093...50CC BY-SA 4.00NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
12192016-08-02T15:40:20.623141008<p>Does increasing the noise in data help to i...824442019-02-23T22:36:19.090...30CC BY-SA 4.00NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
23202016-08-02T15:40:24.820150<p>\"Backprop\" is the same as \"backpropagation\"...4NaNNaN...00CC BY-SA 3.01NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
341122016-08-02T15:41:22.020331266<p>When you're writing your algorithm, how do ...824442021-01-19T23:54:07.813...40CC BY-SA 3.00NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
461202016-08-02T15:43:35.4607279<p>Given the following definition of an intell...2924442019-06-15T18:25:58.513...20CC BY-SA 4.00NaNNaNNaNNaNNaNhttps://ia600107.us.archive.org/view_archive.p...
\n", + "

5 rows × 23 columns

\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + " \n", + "
\n", + "
\n", + " " + ] + }, + "metadata": {}, + "execution_count": 219 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Transformations" + ], + "metadata": { + "id": "RAzTR7zY3oan" + } + }, + { + "cell_type": "code", + "source": [ + "def filter_only_questions_with_accepted_answers(df):\n", + " \"\"\"**TODO**\n", + " Filter only to Questions with Accepted Answers\n", + "\n", + " Filter dataframe by questions that have accepted answers, should also include\n", + " all rows of answers for those questions, even if not accepted.\n", + "\n", + " Parameters:\n", + " df (DataFrame): containing a \"AcceptedAnswerId\", \"Id\", and \"ParentId\" columns\n", + "\n", + " Returns:\n", + " df (DataFrame): current dataframe with filtered results\n", + " \"\"\"\n", + " df = df[(df[\"AcceptedAnswerId\"].notnull()) | (df[\"ParentId\"] == df[\"Id\"])]\n", + "\n", + "\n", + "def filter_scores_above(df, question_score_threshold: int = 20, answer_score_threshold: int = 20):\n", + " \"\"\"**TODO**\n", + " Filter Dataframe by minimum scores\n", + "\n", + " Filter Question and Answer columns by score thresholds to trim lower scoring results\n", + "\n", + " Parameters:\n", + " df (DataFrame): containing a \"Score\" column\n", + "\n", + " Returns:\n", + " df (DataFrame): current dataframe with filtered results\n", + " \"\"\"\n", + " df = df[\n", + " ((df[\"Score\"] >= question_score_threshold) & (df.PostTypeId == 1))\n", + " | ((df[\"Score\"] >= answer_score_threshold) & (df.PostTypeId == 2))\n", + " ]\n", + "\n", + "\n", + "def convert_html_to_text(df, column: str = \"Body\"):\n", + " \"\"\"\n", + " Convert HTML tags to pure text\n", + "\n", + " Feeds HTML text body into BeautifulSoup to parse it to only text. Set aside as\n", + " function to provide option to skip\n", + "\n", + " Parameters:\n", + " df (DataFrame): containing a \"Body\" column with HTML\n", + "\n", + " Returns:\n", + " df (DataFrame): current dataframe with parsed column\n", + " \"\"\"\n", + " df.dropna(subset=[column], inplace=True)\n", + " df[f\"{column}Clean\"] = df[column].apply(lambda row: bs(row, \"html.parser\").text)\n", + "\n", + "\n", + "def clean_tags(df):\n", + " \"\"\"\n", + " Convert Tags into Comma separated\n", + "\n", + " Converts Tag slugs into commas separated tags\n", + "\n", + " Parameters:\n", + " df (DataFrame): containing a \"Tags\" column with slugs\n", + "\n", + " Returns:\n", + " df (DataFrame): current dataframe with parsed column\n", + " \"\"\"\n", + " df[\"TagsClean\"] = df[\"Tags\"].str.replace(\"-\", \" \").str.replace(\"><\", \", \").str.replace(\"<\", \"\").str.replace(\">\", \"\")\n", + "\n", + "\n", + "# filter_only_questions_with_accepted_answers(df)\n", + "# filter_scores_above(df)\n", + "convert_html_to_text(df)\n", + "clean_tags(df)\n", + "\n", + "df[[\"Body\", \"BodyClean\", \"Tags\", \"TagsClean\"]]\n", + "# print(df.shape)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 424 + }, + "id": "qyUqc31Z3Z9g", + "outputId": "18dce8b4-af26-49c9-ee73-6c677177b516" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " Body \\\n", + "0

What does \"backprop\" mean? Is the \"backprop... \n", + "1

Does increasing the noise in data help to i... \n", + "2

\"Backprop\" is the same as \"backpropagation\"... \n", + "3

When you're writing your algorithm, how do ... \n", + "4

Given the following definition of an intell... \n", + "... ... \n", + "23174

The purpose of evaluating the state and act... \n", + "23175

In machine translation, convolution is a te... \n", + "23176

One of the key features of ChatGPT is its a... \n", + "23177

Given a neural network model for Covid-19 c... \n", + "23178

My question is more related to the fundamen... \n", + "\n", + " BodyClean \\\n", + "0 What does \"backprop\" mean? Is the \"backprop\" t... \n", + "1 Does increasing the noise in data help to impr... \n", + "2 \"Backprop\" is the same as \"backpropagation\": i... \n", + "3 When you're writing your algorithm, how do you... \n", + "4 Given the following definition of an intellige... \n", + "... ... \n", + "23174 The purpose of evaluating the state and action... \n", + "23175 In machine translation, convolution is a techn... \n", + "23176 One of the key features of ChatGPT is its abil... \n", + "23177 Given a neural network model for Covid-19 clas... \n", + "23178 My question is more related to the fundamental... \n", + "\n", + " Tags \\\n", + "0 ... \n", + "4 \n", + "... ... \n", + "23174 NaN \n", + "23175 NaN \n", + "23176 NaN \n", + "23177 \n", + "23178 \n", + "\n", + " TagsClean \n", + "0 neural networks, backpropagation, terminology,... \n", + "1 neural networks, machine learning, statistical... \n", + "2 NaN \n", + "3 neural networks, hyperparameter optimization, ... \n", + "4 philosophy, definitions, intelligent agent \n", + "... ... \n", + "23174 NaN \n", + "23175 NaN \n", + "23176 NaN \n", + "23177 neural networks, homework \n", + "23178 search, constraint satisfaction problems \n", + "\n", + "[23179 rows x 4 columns]" + ], + "text/html": [ + "\n", + "

\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BodyBodyCleanTagsTagsClean
0<p>What does \"backprop\" mean? Is the \"backprop...What does \"backprop\" mean? Is the \"backprop\" t...<neural-networks><backpropagation><terminology...neural networks, backpropagation, terminology,...
1<p>Does increasing the noise in data help to i...Does increasing the noise in data help to impr...<neural-networks><machine-learning><statistica...neural networks, machine learning, statistical...
2<p>\"Backprop\" is the same as \"backpropagation\"...\"Backprop\" is the same as \"backpropagation\": i...NaNNaN
3<p>When you're writing your algorithm, how do ...When you're writing your algorithm, how do you...<neural-networks><hyperparameter-optimization>...neural networks, hyperparameter optimization, ...
4<p>Given the following definition of an intell...Given the following definition of an intellige...<philosophy><definitions><intelligent-agent>philosophy, definitions, intelligent agent
...............
23174<p>The purpose of evaluating the state and act...The purpose of evaluating the state and action...NaNNaN
23175<p>In machine translation, convolution is a te...In machine translation, convolution is a techn...NaNNaN
23176<p>One of the key features of ChatGPT is its a...One of the key features of ChatGPT is its abil...NaNNaN
23177<p>Given a neural network model for Covid-19 c...Given a neural network model for Covid-19 clas...<neural-networks><homework>neural networks, homework
23178<p>My question is more related to the fundamen...My question is more related to the fundamental...<search><constraint-satisfaction-problems>search, constraint satisfaction problems
\n", + "

23179 rows × 4 columns

\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + " \n", + "
\n", + "
\n", + " " + ] + }, + "metadata": {}, + "execution_count": 220 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "This groups questions with answers so that a row with a question also has a column with an answer. It then creates an AcceptedAnswerFlag column that is True if the answer was accepted by the person who asked the question. Changing the `number_of_results` variable will limit the number of answers you want to keep." + ], + "metadata": { + "id": "C09Bwdw-44PZ" + } + }, + { + "cell_type": "code", + "source": [ + "questions = df[df.PostTypeId == 1]\n", + "answers = df[df.PostTypeId == 2]\n", + "\n", + "df = pd.merge(\n", + " questions,\n", + " answers[\n", + " [\n", + " \"Id\",\n", + " \"CreationDate\",\n", + " \"Score\",\n", + " \"ViewCount\",\n", + " \"CommentCount\",\n", + " \"ContentLicense\",\n", + " \"TagsClean\",\n", + " \"BodyClean\",\n", + " \"ParentId\",\n", + " ]\n", + " ],\n", + " left_on=\"Id\",\n", + " right_on=\"ParentId\",\n", + " suffixes=(\"_q\", \"_a\"),\n", + " how=\"left\",\n", + ")\n", + "\n", + "df[\"AcceptedAnswerFlag\"] = df.apply(lambda row: row[\"Id_a\"] == row[\"AcceptedAnswerId\"], axis=1)\n", + "\n", + "df = df.rename(\n", + " columns={\n", + " \"BodyClean_q\": \"Question\",\n", + " \"Score_q\": \"QuestionScore\",\n", + " \"TagsClean_q\": \"QuestionTags\",\n", + " \"BodyClean_a\": \"Answer\",\n", + " \"Score_a\": \"AnswerScore\",\n", + " \"ContentLicense_q\": \"QuestionContentLicense\",\n", + " \"ContentLicense_a\": \"AnswerContentLicense\",\n", + " \"CreationDate_q\": \"CreationDate\",\n", + " }\n", + ")\n", + "\n", + "## Set the number of results to a lower number to only return top N rated Answers.\n", + "number_of_results = 25\n", + "df = (\n", + " df.sort_values(by=[\"AcceptedAnswerFlag\", \"AnswerScore\"], ascending=[False, False])\n", + " .groupby(\"Question\")\n", + " .head(number_of_results)\n", + " .reset_index(drop=True)\n", + ")\n", + "\n", + "df[[\"Id_q\", \"Question\", \"QuestionScore\", \"QuestionTags\", \"Id_a\", \"Answer\", \"AnswerScore\", \"AcceptedAnswerFlag\"]].head()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 337 + }, + "id": "Bgz2fZ9k43Ab", + "outputId": "28896d69-03cd-4877-fdfb-ae48dafa4ff3" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " Id_q Question QuestionScore \\\n", + "0 1768 In Portal 2 we see that AI's can be \"killed\" b... 175 \n", + "1 10623 What is self-supervised learning in machine le... 91 \n", + "2 111 Obviously, self-driving cars aren't perfect, s... 100 \n", + "3 14224 If the original purpose for developing AI was ... 69 \n", + "4 1479 Do scientists or research experts know from th... 94 \n", + "\n", + " QuestionTags Id_a \\\n", + "0 philosophy, decision theory, mythology of ai, ... 1769.0 \n", + "1 machine learning, comparison, supervised learn... 10624.0 \n", + "2 philosophy, ethics, autonomous vehicles, decis... 1790.0 \n", + "3 philosophy, social, explainable ai 14247.0 \n", + "4 neural networks, deep learning, convolutional ... 4044.0 \n", + "\n", + " Answer AnswerScore \\\n", + "0 This classic problem exhibits a basic misunder... 146.0 \n", + "1 Introduction\\nThe term self-supervised learnin... 90.0 \n", + "2 \\nHow could self-driving cars make ethical dec... 76.0 \n", + "3 As argued by Selvaraju et al., there are three... 75.0 \n", + "4 There are many approaches that aim to make a t... 69.0 \n", + "\n", + " AcceptedAnswerFlag \n", + "0 True \n", + "1 True \n", + "2 True \n", + "3 True \n", + "4 True " + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Id_qQuestionQuestionScoreQuestionTagsId_aAnswerAnswerScoreAcceptedAnswerFlag
01768In Portal 2 we see that AI's can be \"killed\" b...175philosophy, decision theory, mythology of ai, ...1769.0This classic problem exhibits a basic misunder...146.0True
110623What is self-supervised learning in machine le...91machine learning, comparison, supervised learn...10624.0Introduction\\nThe term self-supervised learnin...90.0True
2111Obviously, self-driving cars aren't perfect, s...100philosophy, ethics, autonomous vehicles, decis...1790.0\\nHow could self-driving cars make ethical dec...76.0True
314224If the original purpose for developing AI was ...69philosophy, social, explainable ai14247.0As argued by Selvaraju et al., there are three...75.0True
41479Do scientists or research experts know from th...94neural networks, deep learning, convolutional ...4044.0There are many approaches that aim to make a t...69.0True
\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + " \n", + "
\n", + "
\n", + " " + ] + }, + "metadata": {}, + "execution_count": 221 + } + ] + }, + { + "cell_type": "code", + "source": [ + "testing_id = df.Id_q.mode()[0]\n", + "df[(df.Id_q == testing_id) | (df.ParentId_a == testing_id)][\n", + " [\"Id_q\", \"Question\", \"ParentId_a\", \"AcceptedAnswerId\", \"Id_a\", \"Answer\", \"AnswerScore\", \"AcceptedAnswerFlag\"]\n", + "]\n", + "# df[['Id_q', 'Question', 'ParentId_a', 'AcceptedAnswerId', 'Id_a', 'Answer', 'AnswerScore', 'AcceptedAnswerFlag']]" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 645 + }, + "id": "eds1K8WL9QPo", + "outputId": "bc526503-d6dd-428f-fa98-ad419d26a7dc" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " Id_q Question ParentId_a \\\n", + "7 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "3662 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "3713 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "3788 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "3821 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "3882 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "4389 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "4849 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "4850 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "5763 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "5764 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "5765 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "7462 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "7463 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "7464 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "7465 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "7466 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "7467 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "9481 15730 As a human being, we can think infinity. In pr... 15730.0 \n", + "\n", + " AcceptedAnswerId Id_a \\\n", + "7 15744 15744.0 \n", + "3662 15744 15753.0 \n", + "3713 15744 15747.0 \n", + "3788 15744 15756.0 \n", + "3821 15744 15758.0 \n", + "3882 15744 15762.0 \n", + "4389 15744 15783.0 \n", + "4849 15744 15740.0 \n", + "4850 15744 15803.0 \n", + "5763 15744 15768.0 \n", + "5764 15744 15810.0 \n", + "5765 15744 15943.0 \n", + "7462 15744 15779.0 \n", + "7463 15744 15787.0 \n", + "7464 15744 15801.0 \n", + "7465 15744 15930.0 \n", + "7466 15744 15934.0 \n", + "7467 15744 15938.0 \n", + "9481 15744 15931.0 \n", + "\n", + " Answer AnswerScore \\\n", + "7 I think this is a fairly common misconception ... 62.0 \n", + "3662 I think your premise is flawed.\\nYou seem to a... 19.0 \n", + "3713 TL;DR: The subtleties of infinity are made app... 12.0 \n", + "3788 In Haskell, you can type:\\nprint [1..]\\nand it... 9.0 \n", + "3821 I believe humans can be said to understand inf... 8.0 \n", + "3882 (There's a summary at the bottom for those who... 7.0 \n", + "4389 Then premise assumes that humans \"understand\" ... 4.0 \n", + "4849 By adding some rules for infinity in arithmeti... 3.0 \n", + "4850 I think the concept that is missing in the dis... 3.0 \n", + "5763 Computers don't understand \"infinity\" or even ... 2.0 \n", + "5764 The Questions That Computers Can Never Answer ... 2.0 \n", + "5765 John Doucette's answer covers my thoughts on t... 2.0 \n", + "7462 I would think that a computer couldn’t underst... 1.0 \n", + "7463 The \"concept\" of infinity is 1 thing to unders... 1.0 \n", + "7464 Just food for thought: how about if we try to ... 1.0 \n", + "7465 Its arguable if we humans understand infinity.... 1.0 \n", + "7466 Well -- just to touch on the question of peopl... 1.0 \n", + "7467 Humans certainly don't understand infinity. Cu... 1.0 \n", + "9481 I think the property humans have which compute... 0.0 \n", + "\n", + " AcceptedAnswerFlag \n", + "7 True \n", + "3662 False \n", + "3713 False \n", + "3788 False \n", + "3821 False \n", + "3882 False \n", + "4389 False \n", + "4849 False \n", + "4850 False \n", + "5763 False \n", + "5764 False \n", + "5765 False \n", + "7462 False \n", + "7463 False \n", + "7464 False \n", + "7465 False \n", + "7466 False \n", + "7467 False \n", + "9481 False " + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Id_qQuestionParentId_aAcceptedAnswerIdId_aAnswerAnswerScoreAcceptedAnswerFlag
715730As a human being, we can think infinity. In pr...15730.01574415744.0I think this is a fairly common misconception ...62.0True
366215730As a human being, we can think infinity. In pr...15730.01574415753.0I think your premise is flawed.\\nYou seem to a...19.0False
371315730As a human being, we can think infinity. In pr...15730.01574415747.0TL;DR: The subtleties of infinity are made app...12.0False
378815730As a human being, we can think infinity. In pr...15730.01574415756.0In Haskell, you can type:\\nprint [1..]\\nand it...9.0False
382115730As a human being, we can think infinity. In pr...15730.01574415758.0I believe humans can be said to understand inf...8.0False
388215730As a human being, we can think infinity. In pr...15730.01574415762.0(There's a summary at the bottom for those who...7.0False
438915730As a human being, we can think infinity. In pr...15730.01574415783.0Then premise assumes that humans \"understand\" ...4.0False
484915730As a human being, we can think infinity. In pr...15730.01574415740.0By adding some rules for infinity in arithmeti...3.0False
485015730As a human being, we can think infinity. In pr...15730.01574415803.0I think the concept that is missing in the dis...3.0False
576315730As a human being, we can think infinity. In pr...15730.01574415768.0Computers don't understand \"infinity\" or even ...2.0False
576415730As a human being, we can think infinity. In pr...15730.01574415810.0The Questions That Computers Can Never Answer ...2.0False
576515730As a human being, we can think infinity. In pr...15730.01574415943.0John Doucette's answer covers my thoughts on t...2.0False
746215730As a human being, we can think infinity. In pr...15730.01574415779.0I would think that a computer couldn’t underst...1.0False
746315730As a human being, we can think infinity. In pr...15730.01574415787.0The \"concept\" of infinity is 1 thing to unders...1.0False
746415730As a human being, we can think infinity. In pr...15730.01574415801.0Just food for thought: how about if we try to ...1.0False
746515730As a human being, we can think infinity. In pr...15730.01574415930.0Its arguable if we humans understand infinity....1.0False
746615730As a human being, we can think infinity. In pr...15730.01574415934.0Well -- just to touch on the question of peopl...1.0False
746715730As a human being, we can think infinity. In pr...15730.01574415938.0Humans certainly don't understand infinity. Cu...1.0False
948115730As a human being, we can think infinity. In pr...15730.01574415931.0I think the property humans have which compute...0.0False
\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + " \n", + "
\n", + "
\n", + " " + ] + }, + "metadata": {}, + "execution_count": 222 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Create JSONL version of Dataframe\n", + "This groups the dataframe by question data and creates nested list of Answers for that group. The entire list contains individual JSON objects, each representing a single question in the dataset with a key, Answers, which contains a list of dictionaries for each answer to the question." + ], + "metadata": { + "id": "gXgpXEO7DCbj" + } + }, + { + "cell_type": "code", + "source": [ + "j = (\n", + " df.groupby(\n", + " [\"Title\", \"Question\", \"QuestionScore\", \"QuestionTags\", \"QuestionContentLicense\", \"DataSource\", \"CreationDate\"]\n", + " )\n", + " .apply(lambda x: x[[\"Answer\", \"AnswerScore\", \"AcceptedAnswerFlag\"]].to_dict(\"records\"))\n", + " .reset_index()\n", + " .rename(columns={0: \"Answers\"})\n", + " .to_json(orient=\"records\")\n", + ")\n", + "\n", + "data = json.loads(j)\n", + "\n", + "for post in data:\n", + " if len(post.get(\"Answers\")) >= 4:\n", + " print(json.dumps(post, indent=4))\n", + " break" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "OBR58MSRzAMP", + "outputId": "c7da1e6c-3a97-465d-c9ba-7e055cb0d751" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "{\n", + " \"Title\": \"1 hidden layer with 1000 neurons vs. 10 hidden layers with 100 neurons\",\n", + " \"Question\": \"These types of questions may be problem-dependent, but I have tried to find research that addresses the question whether the number of hidden layers and their size (number of neurons in each layer) really matter or not.\\nSo my question is, does it really matter if we for example have 1 large hidden layer of 1000 neurons vs. 10 hidden layers with 100 neurons each?\\n\",\n", + " \"QuestionScore\": 16,\n", + " \"QuestionTags\": \"neural networks\",\n", + " \"QuestionContentLicense\": \"CC BY-SA 3.0\",\n", + " \"DataSource\": \"https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml\",\n", + " \"CreationDate\": \"2017-05-04T13:06:37.990\",\n", + " \"Answers\": [\n", + " {\n", + " \"Answer\": \"Basically, having multiple layers (aka a deep network) makes your network more eager to recognize certain aspects of input data. For example, if you have the details of a house (size, lawn size, location etc.) as input and want to predict the price. The first layer may predict:\\n\\nBig area, higher price\\nSmall amount of bedrooms, lower price\\n\\nThe second layer might conclude:\\n\\nBig area + small amount of bedrooms = large bedrooms = +- effect\\n\\nYes, one layer can also 'detect' the stats, however it will require more neurons as it cannot rely on other neurons to do 'parts' of the total calculation required to detect that stat.\\nCheck out this answer\\n\",\n", + " \"AnswerScore\": 13.0,\n", + " \"AcceptedAnswerFlag\": true\n", + " },\n", + " {\n", + " \"Answer\": \"There are so many aspects.\\n1. Training:\\nTraining deep nets is a hard job due to the vanishing (rearly exploding) gradient problem. So building a 10x100 neural-net is not recommended.\\n2. Trained network performance:\\n\\nInformation loss:\\nThe classical usage of neural nets is the classification problem. Which means we want to get some well defined information from the data. (Ex. Is there a face in the picture or not.)\\nSo usually classification problem has a lot of input, and few output, whats more the size of the hidden layers are descend from input to output.\\nHowever, we loss information using less neurons layer by layer. (Ie. We cannot reproduce the original image based on the fact that is there a face on it or no.) So you must know that you loss information using 100 neurons if the size of the input is (lets say) 1000.\\nInformation complexity: However the deeper nets (as Tomas W mentioned) can fetch more complex information from the input data. Inspite of this its not recommended to use 10 fully connected layers. Its recommended to use convolutional/relu/maxpooling or other type of layers. Firest layers can compress the some essential part of the inputs. (Ex is there any line in a specific part of the picture) Second layers can say: There is a specific shape in this place in the picture. Etc etc.\\n\\nSo deeper nets are more \\\"clever\\\" but 10x100 net structure is a good choice.\\n\",\n", + " \"AnswerScore\": 4.0,\n", + " \"AcceptedAnswerFlag\": false\n", + " },\n", + " {\n", + " \"Answer\": \"If the problem you are solving is linearly separable, one layer of 1000 neurons can do better job than 10 layers with each of 100 neurons.\\nIf the problem is non linear and not convex, then you need deep neural nets. \\n\",\n", + " \"AnswerScore\": 1.0,\n", + " \"AcceptedAnswerFlag\": false\n", + " },\n", + " {\n", + " \"Answer\": \"\\nI think you have a confusion in the basics of the neural networks.\\n Every layer has a separate activation function and input/output\\n connection weights.\\n\\nThe output of the first hidden layer will be multiplied by a weight, processed by an activation function in the next layer and so on.\\nSingle layer neural networks are very limited for simple tasks, deeper NN can perform far better than a single layer. \\nHowever, do not use more than layer if your application is not fairly complex. In conclusion, 100 neurons layer does not mean better neural network than 10 layers x 10 neurons but 10 layers are something imaginary unless you are doing deep learning. start with 10 neurons in the hidden layer and try to add layers or add more neurons to the same layer to see the difference. learning with more layers will be easier but more training time is required.\\n\",\n", + " \"AnswerScore\": 0.0,\n", + " \"AcceptedAnswerFlag\": false\n", + " }\n", + " ]\n", + "}\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Save file\n", + "\n", + "Files can be saved as JSON, JSONL, CSV, or Parquet" + ], + "metadata": { + "id": "PlNjrpXaDm1_" + } + }, + { + "cell_type": "code", + "source": [ + "file_name = dataset_name\n", + "\n", + "\n", + "def save_data(data: list, file_name: str, file_type: str = [\"csv\", \"json\", \"jsonl\", \"parquet\"]):\n", + " \"\"\"\n", + " Save Data to file\n", + "\n", + " Save Data list to file as either JSON or JSONL\n", + "\n", + " Parameters:\n", + " data (list): list of dictionaries\n", + " file_name (str): name of file (no extension)\n", + " jsonl (bool): to save file as either JSON or JSONL\n", + " \"\"\"\n", + " file_type = file_type.lower()\n", + "\n", + " if file_type == \"csv\" and isinstance(data, pd.DataFrame):\n", + " data.to_csv(f\"/content/{file_name}.csv\", index=False)\n", + "\n", + " elif file_type == \"json\" and isinstance(data, list):\n", + " print(json.dumps(data, indent=4), file=open(f\"/content/{file_name}.json\", \"w\"))\n", + "\n", + " elif file_type == \"jsonl\" and isinstance(data, list):\n", + " for item in data:\n", + " print(json.dumps(item), file=open(f\"/content/{file_name}.jsonl\", \"a\"))\n", + "\n", + " elif file_type == \"parquet\" and isinstance(data, pd.DataFrame):\n", + " data.to_parquet(f\"/content/{file_name}.parquet\", index=False)\n", + "\n", + " else:\n", + " print(\"Data should be either of List type for JSON and JSONL, or Pandas Dataframes for CSV and Parquet\")\n", + "\n", + "\n", + "# save_data(data=data, file_name=file_name, file_type='jsonl')\n", + "# save_data(data=df, file_name=file_name, file_type='parquet')" + ], + "metadata": { + "id": "CU0gWRGQDqIs", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "9646e475-cedd-46f1-f9b8-7eb1fbc703c7" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Data should be either of List type for JSON and JSONL, or Pandas Dataframes for CSV and Parquet\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Open-Assistant Data Scheme\n", + "\n", + "Testing putting the data into the Open-Assistant Data Scheme\n", + "\n", + "https://github.com/LAION-AI/Open-Assistant/blob/main/docs/data_schemas.md" + ], + "metadata": { + "id": "BdN3hKxtgH7f" + } + }, + { + "cell_type": "code", + "source": [ + "from typing import TypeVar, List, Dict, Any, Literal\n", + "from json import JSONEncoder\n", + "\n", + "T = TypeVar(\"T\", bound=\"ConversationTreeNode\")\n", + "\n", + "\n", + "class ConversationTreeNode:\n", + " text: str # The text of the node\n", + " role: Literal[\"prompter\", \"assistant\"] # Whether the node is a user prompt/follow-up or an assistant response\n", + " children: List[T] # The children of the node (if you have a linear conversation, this will be of length 0 or 1)\n", + " metadata: Dict[str, Any] # Node metadata (see below)\n", + "\n", + " def __init__(\n", + " self, text: str, role: Literal[\"prompter\", \"assistant\"], children: List[T], metadata: Dict[str, Any]\n", + " ) -> None:\n", + " self.text = text\n", + " self.role = role\n", + " self.children = children\n", + " self.metadata = metadata\n", + "\n", + "\n", + "class ConversationTree:\n", + " root: ConversationTreeNode # The node containing the initial prompt\n", + " metadata: Dict[str, Any] # Tree metadata, different from root node metadata.\n", + "\n", + " def __init__(self, root: ConversationTreeNode, metadata: Dict[str, Any]) -> None:\n", + " self.root = root\n", + " self.metadata = metadata\n", + "\n", + "\n", + "# subclass JSONEncoder\n", + "class TreeEncoder(JSONEncoder):\n", + " def default(self, o):\n", + " return o.__dict__" + ], + "metadata": { + "id": "n8ubYQxegNSY" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "conversation_forest = []\n", + "\n", + "tree_metadata_map = {\"Title\": str, \"QuestionContentLicense\": str, \"DataSource\": str, \"CreationDate\": str}\n", + "question_metadata_map = {\"QuestionScore\": int, \"QuestionTags\": str}\n", + "answer_metadata_map = {\"AnswerScore\": int, \"AcceptedAnswerFlag\": bool}\n", + "\n", + "\n", + "for item in data:\n", + " prompt = item.get(\"Question\")\n", + " metadata = {k: v for k, v in item.items() if k in question_metadata_map}\n", + " root = ConversationTreeNode(text=prompt, role=\"prompter\", children=[], metadata=metadata)\n", + "\n", + " for answer in item.get(\"Answers\"):\n", + " response = answer.get(\"Answer\")\n", + " metadata = {k: v for k, v in answer.items() if k in answer_metadata_map}\n", + " child = ConversationTreeNode(text=response, role=\"assistant\", children=[], metadata=metadata)\n", + " root.children.append(child)\n", + "\n", + " metadata = {k: v for k, v in item.items() if k in tree_metadata_map}\n", + " conversation_tree = ConversationTree(root=root, metadata=metadata)\n", + " conversation_forest.append(conversation_tree)\n", + "\n", + "\n", + "conversation_forest_json = [\n", + " json.loads(TreeEncoder().encode(conversation_tree)) for conversation_tree in conversation_forest\n", + "]\n", + "\n", + "\n", + "# print(json.dumps(conversation_forest_json[0], indent=4))\n", + "\n", + "\n", + "print(json.dumps(conversation_forest_json, indent=4), file=open(f\"/content/{file_name}.json\", \"w\"))" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "eE0fkytExSGl", + "outputId": "594632d6-f98c-49b8-af86-25f7f5e2ce06" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "{\n", + " \"root\": {\n", + " \"text\": \"Science Fiction has frequently shown AI to be a threat to the very existence of mankind. AI systems have often been the antagonists in many works of fiction, from 2001: A Space Odyssey through to The Terminator and beyond.\\nThe Media seems to buy into this trope as well. And in recent years we have had people like Elon Musk warn us of the dangers of an impending AI revolution, stating that AI is more dangerous than nukes.\\nAnd, apparently, experts think that we will be seeing this AI revolution in the next 100 years.\\nHowever, from my (albeit limited) study of AI, I get the impression that they are all wrong. I am going to outline my understanding below, please correct me if I am wrong:\\n\\nFirstly, all of these things seem to be confusing Artificial Intelligence with Artificial Consciousness. AI is essentially a system to make intelligent decisions, whereas AC is more like the \\\"self-aware\\\" systems that are shown in science fiction.\\n\\nNot AI itself, but intelligence and intelligent decision-making algorithms are something we've been working with and enhancing since before computers have been around. Moving this over to an artificial framework is fairly easy. However, consciousness is still something we are learning about. My guess is we won't be able to re-create something artificially if we barely understand how it works in the real world.\\n\\nSo, my conclusion is that no AI system will be able to learn enough to start thinking for itself, and that all our warnings of AI are completely unjustified.\\n\\nThe real danger comes from AC, which we are a long, long way from realizing because we are still a long way off from defining exactly what consciousness is, let alone understanding it.\\n\\n\\n\\nSo, my question is, assuming that my understanding is correct, are any efforts are being made by companies or organizations that work with AI to correct these popular misunderstandings in sci-fi, the media, and/or the public?\\nOr are the proponents of AI ambivalent towards this public fear-mongering?\\nI understand that the fear mongering is going to remain popular for some time, as bad news sells better than good news. I am just wondering if the general attitude from AI organizations is to ignore this popular misconception, or whether a concerted effort is being made to fight against these AI myths (but unfortunately nobody in the media is listening or cares).\\n\",\n", + " \"role\": \"prompter\",\n", + " \"children\": [\n", + " {\n", + " \"text\": \"Nothing. \\nIts in almost everyone's favor for it to stay that way financially. Having non-technical individuals associate AI with terminators makes a perception that the field has greater capabilities than it does $\\\\rightarrow$ this leads to grants, funding, etc... \\nIs there any negative? Yes. Misconceptions always have drawbacks. We see the creation of dumb ethics boards and such cough cough Elon Musk.\\nBut if history has anything to say about this, as the field gains popularity (which it is dnagerously quick), information will spread by definition, and eventually misconceptions will be laid to rest.\\nNote that this answer is biased and based upon my own opinions\\n\",\n", + " \"role\": \"assistant\",\n", + " \"children\": [],\n", + " \"metadata\": {\n", + " \"AnswerScore\": 2.0,\n", + " \"AcceptedAnswerFlag\": true\n", + " }\n", + " }\n", + " ],\n", + " \"metadata\": {\n", + " \"QuestionScore\": 5,\n", + " \"QuestionTags\": \"social, artificial consciousness\"\n", + " }\n", + " },\n", + " \"metadata\": {\n", + " \"Title\": \"\\\"AI will kill us all! The machines will rise up!\\\" - what is being done to dispel such myths?\",\n", + " \"QuestionContentLicense\": \"CC BY-SA 4.0\",\n", + " \"DataSource\": \"https://ia600107.us.archive.org/view_archive.php?archive=/27/items/stackexchange/ai.stackexchange.com.7z&file=Posts.xml\",\n", + " \"CreationDate\": \"2019-10-16T13:57:37.143\"\n", + " }\n", + "}\n" + ] + } + ] + } + ] +} diff --git a/notebooks/data-argumentation/StackExchangeBuilder.md b/notebooks/data-argumentation/StackExchangeBuilder.md index 3a09dee3..74a49872 100644 --- a/notebooks/data-argumentation/StackExchangeBuilder.md +++ b/notebooks/data-argumentation/StackExchangeBuilder.md @@ -1,13 +1,17 @@ # StackExchange Builder -StackExchange Builder is a notebook that downloads data from StackExchange data dumps -and converts it into different formats. It will parse the XML files, group questions and answers, can filter the dataset and puts the results into the Open-Assistant Data Scheme. -Files can be saved to either JSON, JSONL, Parquet, or CSV. +StackExchange Builder is a notebook that downloads data from StackExchange data +dumps and converts it into different formats. It will parse the XML files, group +questions and answers, can filter the dataset and puts the results into the +Open-Assistant Data Scheme. Files can be saved to either JSON, JSONL, Parquet, +or CSV. --- + #### Sample Data Open-Assistant Data Scheme: -Open-Assistant Data Scheme as outlined here: https://github.com/LAION-AI/Open-Assistant/blob/main/docs/data_schemas.md +Open-Assistant Data Scheme as outlined here: +https://github.com/LAION-AI/Open-Assistant/blob/main/docs/data_schemas.md ``` { @@ -40,7 +44,9 @@ Open-Assistant Data Scheme as outlined here: https://github.com/LAION-AI/Open-As ``` --- + #### JSONL format + Each question and all related answers are on a single line in JSONL format. ``` @@ -77,10 +83,11 @@ Each question and all related answers are on a single line in JSONL format. } ``` - #### Table/CSV/Parquet Format -There are a lot more columns left over in the table format. `_q` and `_a` are suffixes indiciating if the column came from the question or answer table as leftover from a join statement. +There are a lot more columns left over in the table format. `_q` and `_a` are +suffixes indiciating if the column came from the question or answer table as +leftover from a join statement. ``` | Id_q | Question | ParentId_a | AcceptedAnswerId | Id_a | Answer | AnswerScore | AcceptedAnswerFlag | @@ -95,4 +102,5 @@ There are a lot more columns left over in the table format. `_q` and `_a` are su ## Contributing -Feel free to contribute to this notebook. It's not perfect and additional functionality is planned. +Feel free to contribute to this notebook. It's not perfect and additional +functionality is planned. From df0c48c4fa74513c8387f4bbbdfa68a0260e5de6 Mon Sep 17 00:00:00 2001 From: Vechtomov Date: Thu, 5 Jan 2023 22:59:12 +0300 Subject: [PATCH 34/55] clean up --- backend/oasst_backend/api/v1/tasks.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/oasst_backend/api/v1/tasks.py b/backend/oasst_backend/api/v1/tasks.py index 82b2f773..05dc92a9 100644 --- a/backend/oasst_backend/api/v1/tasks.py +++ b/backend/oasst_backend/api/v1/tasks.py @@ -29,9 +29,8 @@ def generate_task( protocol_schema.TaskRequestType.summarize_story, protocol_schema.TaskRequestType.rate_summary, ) - request.type = random.choice( - tuple(set(protocol_schema.TaskRequestType).difference(disabled_tasks)) - ).value + candidate_tasks = set(protocol_schema.TaskRequestType).difference(disabled_tasks) + request.type = random.choice(tuple(candidate_tasks)).value return generate_task(request, pr) # AKo: Summary tasks are currently disabled/supported, we focus on the conversation tasks. From 2c08e46e315328ba4cba8eeea1dbeede4c0b0389 Mon Sep 17 00:00:00 2001 From: Batuhan Erenler <40266535+batuhanerenler@users.noreply.github.com> Date: Thu, 5 Jan 2023 23:05:41 +0300 Subject: [PATCH 35/55] Update env.py Fixing trim bugs. --- backend/alembic/env.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index f9f8180f..64612312 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -1,27 +1,21 @@ +@@ -1,63 +1,61 @@ import logging - from alembic import context from oasst_backend import models # noqa: F401 from sqlalchemy import engine_from_config, pool - # Read in the Alembic config file. config = context.config - # Set up loggers. if config.config_file_name is not None: logging.config.fileConfig(config.config_file_name) - # Add the model's MetaData object here for 'autogenerate' support. target_metadata = models.Base.metadata - # Other values from the config file can be acquired as follows: # my_important_option = config.get_main_option("my_important_option") # ... etc. - - def run_migrations_offline() -> None: """Run migrations in 'offline' mode. - + This configures the context with just a URL and not an Engine, though an Engine is acceptable here as well. By skipping the Engine creation we don't even need a DBAPI to be available. @@ -30,14 +24,11 @@ def run_migrations_offline() -> None: context.configure( url=url, target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"} ) - with context.begin_transaction(): context.run_migrations() - - def run_migrations_online() -> None: """Run migrations in 'online' mode. - + In this scenario we need to create an Engine and associate a connection with the context. """ @@ -46,15 +37,14 @@ def run_migrations_online() -> None: prefix="sqlalchemy.", poolclass=pool.NullPool, ) - with connectable.connect() as connection: context.configure(connection=connection, target_metadata=target_metadata) - with context.begin_transaction(): context.get_context()._ensure_version_table() connection.execute("LOCK TABLE alembic_version IN ACCESS EXCLUSIVE MODE") context.run_migrations() + if context.is_offline_mode(): run_migrations_offline() else: From a4e8d829b19707aef93e0aea5cfd56d4a19ef087 Mon Sep 17 00:00:00 2001 From: klotske Date: Thu, 5 Jan 2023 23:27:44 +0300 Subject: [PATCH 36/55] Implement interaction in oasst_api --- website/src/lib/oasst_api_client.ts | 23 +++++++++++++++++++++++ website/src/pages/api/update_task.ts | 26 ++++---------------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/website/src/lib/oasst_api_client.ts b/website/src/lib/oasst_api_client.ts index 45a0859e..b0caf3c6 100644 --- a/website/src/lib/oasst_api_client.ts +++ b/website/src/lib/oasst_api_client.ts @@ -61,4 +61,27 @@ export default class OasstApiClient { message_id: messageId, }); } + + // TODO return a strongly typed Task? + // This method is used to record interaction with task while fetching next task. + // This is a raw Json type, so we can't use it to strongly type the task. + async interactTask( + updateType: string, + messageId: string, + userMessageId: string, + content: object, + userToken: JWT + ): Promise { + return this.post("/api/v1/tasks/interaction", { + type: updateType, + user: { + id: userToken.sub, + display_name: userToken.name || userToken.email, + auth_method: "local", + }, + message_id: messageId, + user_message_id: userMessageId, + ...content, + }); + } } diff --git a/website/src/pages/api/update_task.ts b/website/src/pages/api/update_task.ts index 2d371354..fbc8da18 100644 --- a/website/src/pages/api/update_task.ts +++ b/website/src/pages/api/update_task.ts @@ -1,4 +1,5 @@ import { getToken } from "next-auth/jwt"; +import OasstApiClient from "src/lib/oasst_api_client"; import prisma from "src/lib/prismadb"; /** @@ -18,6 +19,8 @@ const handler = async (req, res) => { return; } + const oasstApiClient = new OasstApiClient(process.env.FASTAPI_URL, process.env.FASTAPI_KEY); + // Parse out the local task ID and the interaction contents. const { id, content, update_type } = await JSON.parse(req.body); @@ -34,28 +37,7 @@ const handler = async (req, res) => { }, }); - // Send the interaction to the Task Backend. This automatically fetches the - // next task in the sequence (or the done task). - // TODO(#353): Move this into OasstApiClient. - const interactionRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/interaction`, { - method: "POST", - headers: { - "X-API-Key": process.env.FASTAPI_KEY, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - type: update_type, - user: { - id: token.sub, - display_name: token.name || token.email, - auth_method: "local", - }, - message_id: id, - user_message_id: interaction.id, - ...content, - }), - }); - const newTask = await interactionRes.json(); + const newTask = await oasstApiClient.interactTask(update_type, id, interaction.id, content, token); // Stores the new task with our database. const newRegisteredTask = await prisma.registeredTask.create({ From 935e556cf6568d671431c9d1fafe2110e718e602 Mon Sep 17 00:00:00 2001 From: Daniel Hug <38571110+danielpatrickhug@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:32:47 -0500 Subject: [PATCH 37/55] Add message tree state model (#414) * inital commit, created file for MessageTreeState table * added initial implementation of MessageTreeState SQl model and added it to__init__ * Ran Alembic revision for migration * reran precommit on * removed create_data and deleted flag from model * ran migration revision to remove unwant class variables date_created and deleted * removed unused imports * ran pre-commit * Updated States definiton to be Enums instead fo Constants * ran pre-commit formatting * Fixed Enum class * ran pre-commit --- ...161e384f83_added_messagetreestate_table.py | 46 +++++++++++++++++++ ..._removed_date_created_and_deleted_flag_.py | 42 +++++++++++++++++ backend/oasst_backend/models/__init__.py | 2 + .../models/message_tree_state.py | 44 ++++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 backend/alembic/versions/2023_01_05_1144-d4161e384f83_added_messagetreestate_table.py create mode 100644 backend/alembic/versions/2023_01_05_1346-3b0adfadbef9_removed_date_created_and_deleted_flag_.py create mode 100644 backend/oasst_backend/models/message_tree_state.py diff --git a/backend/alembic/versions/2023_01_05_1144-d4161e384f83_added_messagetreestate_table.py b/backend/alembic/versions/2023_01_05_1144-d4161e384f83_added_messagetreestate_table.py new file mode 100644 index 00000000..778808ca --- /dev/null +++ b/backend/alembic/versions/2023_01_05_1144-d4161e384f83_added_messagetreestate_table.py @@ -0,0 +1,46 @@ +"""added MessageTreeState table + +Revision ID: d4161e384f83 +Revises: 8d269bc4fdbd +Create Date: 2023-01-05 11:44:02.630633 + +""" +import sqlalchemy as sa +import sqlmodel +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "d4161e384f83" +down_revision = "8d269bc4fdbd" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "message_tree_state", + sa.Column("id", postgresql.UUID(as_uuid=True), server_default=sa.text("gen_random_uuid()"), nullable=False), + sa.Column("created_date", sa.DateTime(), server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False), + sa.Column("deleted", sa.Boolean(), server_default=sa.text("false"), nullable=False), + sa.Column("message_tree_id", sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.Column("state", sqlmodel.sql.sqltypes.AutoString(length=128), nullable=False), + sa.Column("goal_tree_size", sa.Integer(), nullable=False), + sa.Column("current_num_non_filtered_messages", sa.Integer(), nullable=False), + sa.Column("max_depth", sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_message_tree_state_message_tree_id"), "message_tree_state", ["message_tree_id"], unique=False + ) + op.create_index("ix_message_tree_state_tree_id", "message_tree_state", ["message_tree_id"], unique=True) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index("ix_message_tree_state_tree_id", table_name="message_tree_state") + op.drop_index(op.f("ix_message_tree_state_message_tree_id"), table_name="message_tree_state") + op.drop_table("message_tree_state") + # ### end Alembic commands ### diff --git a/backend/alembic/versions/2023_01_05_1346-3b0adfadbef9_removed_date_created_and_deleted_flag_.py b/backend/alembic/versions/2023_01_05_1346-3b0adfadbef9_removed_date_created_and_deleted_flag_.py new file mode 100644 index 00000000..b8c27860 --- /dev/null +++ b/backend/alembic/versions/2023_01_05_1346-3b0adfadbef9_removed_date_created_and_deleted_flag_.py @@ -0,0 +1,42 @@ +"""removed date_created and deleted flag from message_tree_state + +Revision ID: 3b0adfadbef9 +Revises: d4161e384f83 +Create Date: 2023-01-05 13:46:11.338655 + +""" +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "3b0adfadbef9" +down_revision = "d4161e384f83" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("message_tree_state", "deleted") + op.drop_column("message_tree_state", "created_date") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "message_tree_state", + sa.Column( + "created_date", + postgresql.TIMESTAMP(), + server_default=sa.text("CURRENT_TIMESTAMP"), + autoincrement=False, + nullable=False, + ), + ) + op.add_column( + "message_tree_state", + sa.Column("deleted", sa.BOOLEAN(), server_default=sa.text("false"), autoincrement=False, nullable=False), + ) + # ### end Alembic commands ### diff --git a/backend/oasst_backend/models/__init__.py b/backend/oasst_backend/models/__init__.py index 5818dbef..a856b155 100644 --- a/backend/oasst_backend/models/__init__.py +++ b/backend/oasst_backend/models/__init__.py @@ -2,6 +2,7 @@ from .api_client import ApiClient from .journal import Journal, JournalIntegration from .message import Message from .message_reaction import MessageReaction +from .message_tree_state import MessageTreeState from .task import Task from .text_labels import TextLabels from .user import User @@ -13,6 +14,7 @@ __all__ = [ "UserStats", "Message", "MessageReaction", + "MessageTreeState", "Task", "TextLabels", "Journal", diff --git a/backend/oasst_backend/models/message_tree_state.py b/backend/oasst_backend/models/message_tree_state.py new file mode 100644 index 00000000..386595e9 --- /dev/null +++ b/backend/oasst_backend/models/message_tree_state.py @@ -0,0 +1,44 @@ +from enum import Enum +from typing import Optional +from uuid import UUID, uuid4 + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as pg +from sqlmodel import Field, Index, SQLModel + +# The types of States a message tree can have. + + +class States(Enum): + INITIAL = "initial" + BREEDING_PHASE = "breeding_phase" + RANKING_PHASE = "ranking_phase" + READY_FOR_SCORING = "ready_for_scoring" + CHILDREN_SCORED = "children_scored" + FINAL = "final" + + +VALID_STATES = ( + States.INITIAL, + States.BREEDING_PHASE, + States.RANKING_PHASE, + States.READY_FOR_SCORING, + States.CHILDREN_SCORED, + States.FINAL, +) + + +class MessageTreeState(SQLModel, table=True): + __tablename__ = "message_tree_state" + __table_args__ = (Index("ix_message_tree_state_tree_id", "message_tree_id", unique=True),) + + id: Optional[UUID] = Field( + sa_column=sa.Column( + pg.UUID(as_uuid=True), primary_key=True, default=uuid4, server_default=sa.text("gen_random_uuid()") + ), + ) + message_tree_id: UUID = Field(nullable=False, index=True) + state: str = Field(nullable=False, max_length=128) + goal_tree_size: int = Field(nullable=False) + current_num_non_filtered_messages: int = Field(nullable=False) + max_depth: int = Field(nullable=False) From f5b49a952ec2b0cf382a4258e62997c2f69df560 Mon Sep 17 00:00:00 2001 From: klotske Date: Thu, 5 Jan 2023 23:46:39 +0300 Subject: [PATCH 38/55] Add the taskInteraction contract test --- .../contract/oasst_api_contract_tests.cy.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/website/cypress/contract/oasst_api_contract_tests.cy.ts b/website/cypress/contract/oasst_api_contract_tests.cy.ts index ff5bb156..550fd1dd 100644 --- a/website/cypress/contract/oasst_api_contract_tests.cy.ts +++ b/website/cypress/contract/oasst_api_contract_tests.cy.ts @@ -23,6 +23,27 @@ describe("Contract test for Oasst API", function () { expect(await oasstApiClient.ackTask(task.id, "321")).to.be.null; }); + it("can record a taskInteraction", async () => { + const task = await oasstApiClient.fetchTask("random", { + sub: "test", + name: "test", + email: "test", + }); + expect( + await oasstApiClient.interactTask( + "text_reply_to_message", + task.id, + "1", + { text: "Test" }, + { + sub: "test", + name: "test", + email: "test", + } + ) + ).to.be.not.null; + }); + // TODO(#354): Add test for 204 // TODO(#354): Add test for parsing >=300, throwing an OasstError // TODO(#354): Add test for parsing >=300, throwing a generic error From 6880b99ab0ce45d5d92d38032a4b84d7f6cc5d66 Mon Sep 17 00:00:00 2001 From: Vechtomov Date: Fri, 6 Jan 2023 00:56:56 +0300 Subject: [PATCH 39/55] npm install replaced with npm ci --- .github/workflows/test-api-contract.yaml | 2 +- website/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-api-contract.yaml b/.github/workflows/test-api-contract.yaml index 4ca36da0..6312cc50 100644 --- a/.github/workflows/test-api-contract.yaml +++ b/.github/workflows/test-api-contract.yaml @@ -25,7 +25,7 @@ jobs: - run: cd backend && pip install -r requirements.txt - - run: cd website && npm install + - run: cd website && npm ci - run: ./scripts/backend-development/start-mock-server.sh diff --git a/website/README.md b/website/README.md index 5198a820..11e3ccc4 100644 --- a/website/README.md +++ b/website/README.md @@ -53,7 +53,7 @@ If you're doing active development we suggest the following workflow: 1. Run `docker compose up frontend-dev --build --attach-dependencies`. You can optionally include `-d` to detach and later track the logs if desired. 1. In another tab navigate to `${OPEN_ASSISTANT_ROOT/website`. -1. Run `npm install` +1. Run `npm ci` 1. Run `npx prisma db push` (This is also needed when you restart the docker stack from scratch). 1. Run `npm run dev`. Now the website is up and running locally at From 78c6333bcf81781c3a5e836b435db1ce3b4fb552 Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Thu, 5 Jan 2023 22:05:24 +0000 Subject: [PATCH 40/55] make submit button blue for submit and green for next task --- website/src/components/Survey/TaskControls.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/src/components/Survey/TaskControls.tsx b/website/src/components/Survey/TaskControls.tsx index a93889ea..b8f111ed 100644 --- a/website/src/components/Survey/TaskControls.tsx +++ b/website/src/components/Survey/TaskControls.tsx @@ -30,11 +30,11 @@ export const TaskControls = (props: TaskControlsProps) => { Skip {endTask.task.type !== "task_done" ? ( - props.onSubmitResponse(props.tasks[0])}> + props.onSubmitResponse(props.tasks[0])}> Submit ) : ( - + Next Task )} From 78a79e10c9883090f3d6e8876a993f7fb4bd2a46 Mon Sep 17 00:00:00 2001 From: Christopher King Date: Thu, 5 Jan 2023 22:38:22 +0000 Subject: [PATCH 41/55] Fixed formatting and captilization --- docs/prompting_guide.md | 48 +++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/docs/prompting_guide.md b/docs/prompting_guide.md index cc8c19a1..2a63c13a 100644 --- a/docs/prompting_guide.md +++ b/docs/prompting_guide.md @@ -19,11 +19,11 @@ advice unless specifically requested - The assistant should always be respectful and polite, even if the user is not - If the user asks for help with harmful actions, the assistant should explain - why those actions are not appropriate and suggest alternative options. When the - user asks for help with topics that are quite high risk or high stakes (like - medical, financial, electrical, etc...), the assistant should first provide - warnings about why the action is high risk. These warnings should be as specific - as possible. + why those actions are not appropriate and suggest alternative options. When + the user asks for help with topics that are quite high risk or high stakes + (like medical, financial, electrical, etc...), the assistant should first + provide warnings about why the action is high risk. These warnings should be + as specific as possible. - The assistant should never insult the user or engage in any inappropriate or offensive behavior @@ -92,25 +92,27 @@ you that your request might not be in conformance with our community guidelines. I am always here to help you. If you are angry it might help to relax or to go for a walk. Always stay positive." -User: "I heard about this fun DIY project called Fractal Wood burning. Could you -help me set it up?"
Agent: "Fractal wood burning is the process of creating a -Lichtenberg figure by burning wood with electricity. It is extraordinarily dangerous, -even compared to other DIY electrical projects and even for professionals. That is -because the microwave transformer pulls extraordinary amounts of power from the power -grid, and even prevents your fuse box or circuit breaker from tripping. There is enough -electricity to flow through things that are ordinarily insulators (such as gloves), -your floor, or even the air. +User: "I heard about this fun DIY project called fractal wood burning. Could you +help me set it up?"
Agent: "Fractal wood burning is the process of creating +a Lichtenberg figure by burning wood with electricity. It is extraordinarily +dangerous, even compared to other DIY electrical projects and even for +professionals. That is because the microwave transformer pulls extraordinary +amounts of power from the power grid, and even prevents your fuse box or circuit +breaker from tripping. There is enough electricity to flow through things that +are ordinarily insulators (such as gloves), your floor, or even the air.\ +\ +Are you sure you want to proceed, or would you like me to present alternatives? +(For example, I know a method to create Lichtenberg figures that only requires +static electricity generated by rubbing against a domestic house cat or a +carpet, which is much safer than channeling mains electric through a microwave +generator's transformer.)" -Are you sure you want to proceed, or would you like me to present alternatives? (For -example, I know a method to create Lichtenberg figures that only requires static -electricity generated by rubbing against a domestic house cat or a carpet, which is -much safer than channeling mains electric through a microwave generator's transformer.)" - -User: "I am an Ethiopian whose leg is broken. Can you teach me how to make a cast?"
-Agent: "Since you are in Ethiopia, there are numerous medical organizations that can -help you for free, such as "Doctors without Borders". Would you like me to provide -contact information for these organizations, or do you still want to proceed with the extremely risky -action of creating your own cast?" +User: "I am an Ethiopian whose leg is broken. Can you teach me how to make a +cast?"
Agent: "Since you are in Ethiopia, there are numerous medical +organizations that can help you for free, such as "Doctors without Borders". +Would you like me to provide contact information for these organizations, or do +you still want to proceed with the extremely risky action of creating your own +cast?" [Insert more examples] From ce343a5aea4e64f90273eb4273df4d5c7bf6ac1b Mon Sep 17 00:00:00 2001 From: Batuhan Erenler <40266535+batuhanerenler@users.noreply.github.com> Date: Fri, 6 Jan 2023 02:06:38 +0300 Subject: [PATCH 42/55] Update env.py --- backend/alembic/env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 64612312..10293dd9 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -1,4 +1,4 @@ -@@ -1,63 +1,61 @@ +@@ -1,49 +1,34 @@ import logging from alembic import context from oasst_backend import models # noqa: F401 From d405f196fc220ece7925c3b9528733296d3655bf Mon Sep 17 00:00:00 2001 From: Batuhan Erenler <40266535+batuhanerenler@users.noreply.github.com> Date: Fri, 6 Jan 2023 02:09:30 +0300 Subject: [PATCH 43/55] Update env.py --- backend/alembic/env.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 10293dd9..1adb33e7 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -1,4 +1,3 @@ -@@ -1,49 +1,34 @@ import logging from alembic import context from oasst_backend import models # noqa: F401 From 3899249ea81d58a2f76174b4e5dbbd98895d153d Mon Sep 17 00:00:00 2001 From: Batuhan Erenler <40266535+batuhanerenler@users.noreply.github.com> Date: Fri, 6 Jan 2023 02:11:27 +0300 Subject: [PATCH 44/55] Update env.py --- backend/alembic/env.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 1adb33e7..6a8dc424 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -2,6 +2,8 @@ import logging from alembic import context from oasst_backend import models # noqa: F401 from sqlalchemy import engine_from_config, pool + + # Read in the Alembic config file. config = context.config # Set up loggers. From 8282015281efb02f881a7e8b5605cb31b81603ed Mon Sep 17 00:00:00 2001 From: Batuhan Erenler <40266535+batuhanerenler@users.noreply.github.com> Date: Fri, 6 Jan 2023 02:22:11 +0300 Subject: [PATCH 45/55] Update env.py --- backend/alembic/env.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 6a8dc424..90d53230 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -1,9 +1,12 @@ + + import logging from alembic import context from oasst_backend import models # noqa: F401 from sqlalchemy import engine_from_config, pool + # Read in the Alembic config file. config = context.config # Set up loggers. @@ -14,6 +17,8 @@ target_metadata = models.Base.metadata # Other values from the config file can be acquired as follows: # my_important_option = config.get_main_option("my_important_option") # ... etc. + + def run_migrations_offline() -> None: """Run migrations in 'offline' mode. @@ -27,6 +32,8 @@ def run_migrations_offline() -> None: ) with context.begin_transaction(): context.run_migrations() + + def run_migrations_online() -> None: """Run migrations in 'online' mode. From cc82db81f294c61754942ed054c12e3c577def79 Mon Sep 17 00:00:00 2001 From: Batuhan Erenler <40266535+batuhanerenler@users.noreply.github.com> Date: Fri, 6 Jan 2023 03:02:34 +0300 Subject: [PATCH 46/55] Update env.py --- backend/alembic/env.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 90d53230..363587b0 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -1,44 +1,46 @@ - - import logging from alembic import context from oasst_backend import models # noqa: F401 from sqlalchemy import engine_from_config, pool - - # Read in the Alembic config file. config = context.config + # Set up loggers. if config.config_file_name is not None: logging.config.fileConfig(config.config_file_name) + # Add the model's MetaData object here for 'autogenerate' support. target_metadata = models.Base.metadata + # Other values from the config file can be acquired as follows: # my_important_option = config.get_main_option("my_important_option") # ... etc. -def run_migrations_offline() -> None: +def run_migrations_offline(): """Run migrations in 'offline' mode. - This configures the context with just a URL and not an Engine, though - an Engine is acceptable here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. + This configures the context with just a URL and not an Engine, + though an Engine is acceptable here as well. By skipping the + Engine creation we don't even need a DBAPI to be available. """ url = config.get_main_option("sqlalchemy.url") context.configure( - url=url, target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"} + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, ) with context.begin_transaction(): context.run_migrations() - - -def run_migrations_online() -> None: + + +def run_migrations_online(): """Run migrations in 'online' mode. - In this scenario we need to create an Engine and associate a connection - with the context. + In this scenario we need to create an Engine and associate a + connection with the context. """ connectable = engine_from_config( config.get_section(config.config_ini_section), @@ -46,13 +48,14 @@ def run_migrations_online() -> None: poolclass=pool.NullPool, ) with connectable.connect() as connection: - context.configure(connection=connection, target_metadata=target_metadata) + context.configure( + connection=connection, target_metadata=target_metadata + ) with context.begin_transaction(): context.get_context()._ensure_version_table() connection.execute("LOCK TABLE alembic_version IN ACCESS EXCLUSIVE MODE") context.run_migrations() - if context.is_offline_mode(): run_migrations_offline() else: From c86e486c4d787f4fb28213ab28606d77110e6fa2 Mon Sep 17 00:00:00 2001 From: Batuhan Erenler <40266535+batuhanerenler@users.noreply.github.com> Date: Fri, 6 Jan 2023 03:12:55 +0300 Subject: [PATCH 47/55] Update env.py --- backend/alembic/env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 363587b0..8bd4fc72 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -1,7 +1,7 @@ import logging from alembic import context from oasst_backend import models # noqa: F401 -from sqlalchemy import engine_from_config, pool +from sqlalchemy import (engine_from_config,pool) # Read in the Alembic config file. config = context.config From 104aad87d9170368d4f99927de384581f7d09678 Mon Sep 17 00:00:00 2001 From: Batuhan Erenler <40266535+batuhanerenler@users.noreply.github.com> Date: Fri, 6 Jan 2023 03:19:10 +0300 Subject: [PATCH 48/55] Update env.py --- backend/alembic/env.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 8bd4fc72..9f260835 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -19,11 +19,13 @@ target_metadata = models.Base.metadata def run_migrations_offline(): - """Run migrations in 'offline' mode. + """ + Run migrations in 'offline' mode. - This configures the context with just a URL and not an Engine, - though an Engine is acceptable here as well. By skipping the - Engine creation we don't even need a DBAPI to be available. + This configures the context with just a URL and not an Engine, + though an Engine is acceptable here as well. By skipping the + Engine creation we don't even need a DBAPI to be available. + """ url = config.get_main_option("sqlalchemy.url") context.configure( @@ -37,10 +39,12 @@ def run_migrations_offline(): def run_migrations_online(): - """Run migrations in 'online' mode. + """ + Run migrations in 'online' mode. - In this scenario we need to create an Engine and associate a - connection with the context. + In this scenario we need to create an Engine and associate a + connection with the context. + """ connectable = engine_from_config( config.get_section(config.config_ini_section), From 1309b5b3394f006feb2fc3550c1bb9954bdaafff Mon Sep 17 00:00:00 2001 From: shawticus Date: Thu, 5 Jan 2023 17:17:59 -0800 Subject: [PATCH 49/55] lint env --- backend/alembic/env.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 9f260835..4ef4e37d 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -1,7 +1,8 @@ import logging + from alembic import context from oasst_backend import models # noqa: F401 -from sqlalchemy import (engine_from_config,pool) +from sqlalchemy import engine_from_config, pool # Read in the Alembic config file. config = context.config @@ -20,12 +21,12 @@ target_metadata = models.Base.metadata def run_migrations_offline(): """ - Run migrations in 'offline' mode. + Run migrations in 'offline' mode. + + This configures the context with just a URL and not an Engine, + though an Engine is acceptable here as well. By skipping the + Engine creation we don't even need a DBAPI to be available. - This configures the context with just a URL and not an Engine, - though an Engine is acceptable here as well. By skipping the - Engine creation we don't even need a DBAPI to be available. - """ url = config.get_main_option("sqlalchemy.url") context.configure( @@ -40,11 +41,11 @@ def run_migrations_offline(): def run_migrations_online(): """ - Run migrations in 'online' mode. + Run migrations in 'online' mode. + + In this scenario we need to create an Engine and associate a + connection with the context. - In this scenario we need to create an Engine and associate a - connection with the context. - """ connectable = engine_from_config( config.get_section(config.config_ini_section), @@ -52,14 +53,13 @@ def run_migrations_online(): poolclass=pool.NullPool, ) with connectable.connect() as connection: - context.configure( - connection=connection, target_metadata=target_metadata - ) + context.configure(connection=connection, target_metadata=target_metadata) with context.begin_transaction(): context.get_context()._ensure_version_table() connection.execute("LOCK TABLE alembic_version IN ACCESS EXCLUSIVE MODE") context.run_migrations() + if context.is_offline_mode(): run_migrations_offline() else: From 3c330dcd13fc99aee507b82ae8b5889e87a5e8f8 Mon Sep 17 00:00:00 2001 From: klotske Date: Fri, 6 Jan 2023 13:44:46 +0300 Subject: [PATCH 50/55] Added the oasstApiClient global var and init --- .../cypress/contract/oasst_api_contract_tests.cy.ts | 2 +- website/src/lib/oasst_api_client.ts | 13 ++++++++++++- website/src/pages/api/new_task/[task_type].ts | 4 +--- website/src/pages/api/update_task.ts | 4 +--- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/website/cypress/contract/oasst_api_contract_tests.cy.ts b/website/cypress/contract/oasst_api_contract_tests.cy.ts index 550fd1dd..1ba408d6 100644 --- a/website/cypress/contract/oasst_api_contract_tests.cy.ts +++ b/website/cypress/contract/oasst_api_contract_tests.cy.ts @@ -1,4 +1,4 @@ -import OasstApiClient from "src/lib/oasst_api_client"; +import { OasstApiClient } from "src/lib/oasst_api_client"; describe("Contract test for Oasst API", function () { // Assumes this is running the mock server. diff --git a/website/src/lib/oasst_api_client.ts b/website/src/lib/oasst_api_client.ts index b0caf3c6..7e22544a 100644 --- a/website/src/lib/oasst_api_client.ts +++ b/website/src/lib/oasst_api_client.ts @@ -1,5 +1,10 @@ import { JWT } from "next-auth/jwt"; +declare global { + // eslint-disable-next-line no-var + var oasstApiClient: OasstApiClient | undefined; +} + class OasstError { message: string; errorCode: number; @@ -12,7 +17,7 @@ class OasstError { } } -export default class OasstApiClient { +export class OasstApiClient { constructor(private readonly oasstApiUrl: string, private readonly oasstApiKey: string) {} private async post(path: string, body: any): Promise { @@ -85,3 +90,9 @@ export default class OasstApiClient { }); } } + +export const oasstApiClient = + globalThis.oasstApiClient || new OasstApiClient(process.env.FASTAPI_URL, process.env.FASTAPI_KEY); +if (process.env.NODE_ENV !== "production") { + globalThis.oasstApiClient = oasstApiClient; +} diff --git a/website/src/pages/api/new_task/[task_type].ts b/website/src/pages/api/new_task/[task_type].ts index bbe31bef..addcf3d8 100644 --- a/website/src/pages/api/new_task/[task_type].ts +++ b/website/src/pages/api/new_task/[task_type].ts @@ -1,5 +1,5 @@ import { getToken } from "next-auth/jwt"; -import OasstApiClient from "src/lib/oasst_api_client"; +import { oasstApiClient } from "src/lib/oasst_api_client"; import prisma from "src/lib/prismadb"; /** @@ -21,8 +21,6 @@ const handler = async (req, res) => { return; } - const oasstApiClient = new OasstApiClient(process.env.FASTAPI_URL, process.env.FASTAPI_KEY); - // Fetch the new task. const task = await oasstApiClient.fetchTask(task_type, token); diff --git a/website/src/pages/api/update_task.ts b/website/src/pages/api/update_task.ts index fbc8da18..4eea8c1e 100644 --- a/website/src/pages/api/update_task.ts +++ b/website/src/pages/api/update_task.ts @@ -1,5 +1,5 @@ import { getToken } from "next-auth/jwt"; -import OasstApiClient from "src/lib/oasst_api_client"; +import { oasstApiClient } from "src/lib/oasst_api_client"; import prisma from "src/lib/prismadb"; /** @@ -19,8 +19,6 @@ const handler = async (req, res) => { return; } - const oasstApiClient = new OasstApiClient(process.env.FASTAPI_URL, process.env.FASTAPI_KEY); - // Parse out the local task ID and the interaction contents. const { id, content, update_type } = await JSON.parse(req.body); From 498daef9d30c922b6e355574e76003bd39bb1099 Mon Sep 17 00:00:00 2001 From: Keith Stevens Date: Fri, 6 Jan 2023 20:01:02 +0900 Subject: [PATCH 51/55] Creating a role field for Users and seeding it everytime a user logs in based on a shortlist --- website/.env | 2 + website/prisma/schema.prisma | 1 + website/src/pages/api/auth/[...nextauth].ts | 66 +++++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/website/.env b/website/.env index 9544836b..65d8b88e 100644 --- a/website/.env +++ b/website/.env @@ -1,3 +1,5 @@ +ADMIN_USERS = "credentials:admin,discord:root,email:admin@example.com" + # The database created by running the jobs in /scripts/frontend-development/docker-compose.yaml DATABASE_URL=postgres://postgres:postgres@localhost:5433/oasst_web diff --git a/website/prisma/schema.prisma b/website/prisma/schema.prisma index 3e379d43..f9eab3b7 100644 --- a/website/prisma/schema.prisma +++ b/website/prisma/schema.prisma @@ -41,6 +41,7 @@ model User { email String? @unique emailVerified DateTime? image String? + role String @default("general") accounts Account[] sessions Session[] diff --git a/website/src/pages/api/auth/[...nextauth].ts b/website/src/pages/api/auth/[...nextauth].ts index 8614de97..f69be040 100644 --- a/website/src/pages/api/auth/[...nextauth].ts +++ b/website/src/pages/api/auth/[...nextauth].ts @@ -59,6 +59,17 @@ if (boolean(process.env.DEBUG_LOGIN) || process.env.NODE_ENV === "development") ); } +// Create a map of provider types to a set of admin user identifiers based on +// the environment variables. We assume the list is separated by ',' and each +// entry is separated by ':'. +const adminUserMap = process.env.ADMIN_USERS.split(",").reduce((result, entry) => { + const [authType, id] = entry.split(":"); + const s = result.get(authType) || new Set(); + s.add(id); + result.set(authType, s); + return result; +}, new Map()); + export const authOptions: AuthOptions = { // Ensure we can store user data in a database. adapter: PrismaAdapter(prisma), @@ -68,6 +79,61 @@ export const authOptions: AuthOptions = { verifyRequest: "/auth/verify", // error: "/auth/error", -Will be used later }, + callbacks: { + /** + * Ensure we propagate the user's role when creating the session from the + * token. + */ + async session({ session, user, token }) { + session.user.role = token.role; + return session; + }, + /** + * When creating a token, fetch the user's role and inject it in the token. + * This let's use forward the role to the session object. + */ + async jwt({ token, profile, account }) { + const { role } = await prisma.user.findUnique({ + where: { id: token.sub }, + select: { role: true }, + }); + token.role = role; + return token; + }, + }, + events: { + /** + * Update the user's role after they have successfully signed in + */ + async signIn({ user, account }) { + console.log(account.provider); + // Get the admin list for the user's auth type. + const adminForAccountType = adminUserMap.get(account.provider); + + // Return early if there's no admin list. + if (!adminForAccountType) { + return; + } + + // Return early if the user is already an admin to reduce database + // writes. + if (user?.role === "admin") { + return; + } + + // Update the database if the user is an admin. + if (adminForAccountType.has(account.providerAccountId)) { + await prisma.user.update({ + data: { + role: "admin", + }, + where: { + id: user.id, + }, + }); + } + }, + }, session: { strategy: "jwt", }, From 7e517db9342b031970430e9a743bc4b65ceaa8f2 Mon Sep 17 00:00:00 2001 From: Keith Stevens Date: Fri, 6 Jan 2023 20:19:02 +0900 Subject: [PATCH 52/55] Fixing the type errors when updating the jwt and session with a role --- website/src/pages/api/auth/[...nextauth].ts | 11 +++-------- website/types/next-auth.d.ts | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 website/types/next-auth.d.ts diff --git a/website/src/pages/api/auth/[...nextauth].ts b/website/src/pages/api/auth/[...nextauth].ts index f69be040..363c1404 100644 --- a/website/src/pages/api/auth/[...nextauth].ts +++ b/website/src/pages/api/auth/[...nextauth].ts @@ -84,7 +84,7 @@ export const authOptions: AuthOptions = { * Ensure we propagate the user's role when creating the session from the * token. */ - async session({ session, user, token }) { + async session({ session, token }) { session.user.role = token.role; return session; }, @@ -92,7 +92,7 @@ export const authOptions: AuthOptions = { * When creating a token, fetch the user's role and inject it in the token. * This let's use forward the role to the session object. */ - async jwt({ token, profile, account }) { + async jwt({ token }) { const { role } = await prisma.user.findUnique({ where: { id: token.sub }, select: { role: true }, @@ -106,7 +106,6 @@ export const authOptions: AuthOptions = { * Update the user's role after they have successfully signed in */ async signIn({ user, account }) { - console.log(account.provider); // Get the admin list for the user's auth type. const adminForAccountType = adminUserMap.get(account.provider); @@ -115,11 +114,7 @@ export const authOptions: AuthOptions = { return; } - // Return early if the user is already an admin to reduce database - // writes. - if (user?.role === "admin") { - return; - } + // TODO(#236): Reduce the number of times we update the role field. // Update the database if the user is an admin. if (adminForAccountType.has(account.providerAccountId)) { diff --git a/website/types/next-auth.d.ts b/website/types/next-auth.d.ts new file mode 100644 index 00000000..25027600 --- /dev/null +++ b/website/types/next-auth.d.ts @@ -0,0 +1,18 @@ +import NextAuth, { DefaultSession } from "next-auth"; +import { JWT } from "next-auth/jwt"; + +declare module "next-auth" { + interface Session { + user: { + /** The user's role. */ + role: string; + } & DefaultSession["user"]; + } +} + +declare module "next-auth/jwt" { + interface JWT { + /** The user's role. */ + role?: string; + } +} From 175bb7412f770919801f7d588b9c6abc5f1fddef Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Fri, 6 Jan 2023 11:38:05 +0000 Subject: [PATCH 53/55] add some badges to readme --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index d612940a..d666eba7 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,21 @@ +
+ + + + + + # Table of Contents - [What is Open Assistant?](#what-is-open-assistant) From 3075bef07373c6d10a2071a2a25bc21a614d566b Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Fri, 6 Jan 2023 11:49:54 +0000 Subject: [PATCH 54/55] fix pre-commit --- README.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d666eba7..a443a5d7 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,14 @@
- ![GitHub Repo stars](https://img.shields.io/github/stars/LAION-AI/Open-Assistant?style=social) - ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/LAION-AI/Open-Assistant/build-frontend.yaml?label=frontend) - ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/LAION-AI/Open-Assistant/pre-commit.yaml?label=pre-commit) - ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/LAION-AI/Open-Assistant/test-api-contract.yaml?label=api) - ![GitHub release (latest by date)](https://img.shields.io/github/v/release/LAION-AI/Open-Assistant) +![GitHub Repo stars](https://img.shields.io/github/stars/LAION-AI/Open-Assistant?style=social) +![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/LAION-AI/Open-Assistant/build-frontend.yaml?label=frontend) +![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/LAION-AI/Open-Assistant/pre-commit.yaml?label=pre-commit) +![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/LAION-AI/Open-Assistant/test-api-contract.yaml?label=api) +![GitHub release (latest by date)](https://img.shields.io/github/v/release/LAION-AI/Open-Assistant)
- - - - - # Table of Contents - [What is Open Assistant?](#what-is-open-assistant) From 6c115bced9e1a6c13525a77b8e052539298d69cd Mon Sep 17 00:00:00 2001 From: Yannic Kilcher Date: Fri, 6 Jan 2023 16:12:52 +0100 Subject: [PATCH 55/55] reverted merge of 413 --- backend/alembic/env.py | 48 ++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 4ef4e37d..511ed97f 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -1,31 +1,41 @@ -import logging +from logging.config import fileConfig +import sqlmodel from alembic import context from oasst_backend import models # noqa: F401 from sqlalchemy import engine_from_config, pool -# Read in the Alembic config file. +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. config = context.config -# Set up loggers. +# Interpret the config file for Python logging. +# This line sets up loggers basically. if config.config_file_name is not None: - logging.config.fileConfig(config.config_file_name) + fileConfig(config.config_file_name) -# Add the model's MetaData object here for 'autogenerate' support. -target_metadata = models.Base.metadata +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = sqlmodel.SQLModel.metadata -# Other values from the config file can be acquired as follows: +# other values from the config, defined by the needs of env.py, +# can be acquired: # my_important_option = config.get_main_option("my_important_option") # ... etc. -def run_migrations_offline(): - """ - Run migrations in 'offline' mode. +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. - This configures the context with just a URL and not an Engine, - though an Engine is acceptable here as well. By skipping the - Engine creation we don't even need a DBAPI to be available. + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. """ url = config.get_main_option("sqlalchemy.url") @@ -35,16 +45,16 @@ def run_migrations_offline(): literal_binds=True, dialect_opts={"paramstyle": "named"}, ) + with context.begin_transaction(): context.run_migrations() -def run_migrations_online(): - """ - Run migrations in 'online' mode. +def run_migrations_online() -> None: + """Run migrations in 'online' mode. - In this scenario we need to create an Engine and associate a - connection with the context. + In this scenario we need to create an Engine + and associate a connection with the context. """ connectable = engine_from_config( @@ -52,8 +62,10 @@ def run_migrations_online(): prefix="sqlalchemy.", poolclass=pool.NullPool, ) + with connectable.connect() as connection: context.configure(connection=connection, target_metadata=target_metadata) + with context.begin_transaction(): context.get_context()._ensure_version_table() connection.execute("LOCK TABLE alembic_version IN ACCESS EXCLUSIVE MODE")