+
diff --git a/Frontend/deguel/src/elements/ValueCard.jsx b/Frontend/deguel/src/elements/ValueCard.jsx
deleted file mode 100644
index 04e3eb3..0000000
--- a/Frontend/deguel/src/elements/ValueCard.jsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Card, Text, Metric, Flex, ProgressBar } from "@tremor/react";
-
-export default function ValueCard(){
- return
- Sales
- $ 71,465
-
- 32% of annual target
- $ 225,000
-
-
-
-}
\ No newline at end of file
diff --git a/Notebooks/Sentimental/glob.ipynb b/Notebooks/Sentimental/glob.ipynb
new file mode 100644
index 0000000..485d96b
--- /dev/null
+++ b/Notebooks/Sentimental/glob.ipynb
@@ -0,0 +1,173 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import os"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 회사명 기준으로 파일 나누기\n",
+ "preprocessed_data_로 시작하는 chatgpt 라벨링 데이터를 회사명으로 분류해서 merged_(회사명) 으로 저장"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def merge_and_move_files(directory_path):\n",
+ " file_list = os.listdir(directory_path)\n",
+ "\n",
+ " files_by_company = {}\n",
+ "\n",
+ " # 파일들을 기업 이름에 따라 그룹화\n",
+ " for file_name in file_list:\n",
+ " # if file_name[0].isdigit():\n",
+ " if file_name.startswith(\"preprocessed_data_\"):\n",
+ " file_path = os.path.join(directory_path, file_name)\n",
+ " data = pd.read_csv(file_path)\n",
+ " \n",
+ " company_name = data['company'].iloc[0]\n",
+ " \n",
+ " if company_name not in files_by_company:\n",
+ " files_by_company[company_name] = []\n",
+ " \n",
+ " files_by_company[company_name].append(file_name)\n",
+ "\n",
+ " # 각 기업에 대해 폴더를 만들고 해당 폴더로 각각 파일들을 저장\n",
+ " for company_name, files in files_by_company.items():\n",
+ " folder_path = os.path.join(directory_path, \"merged\")\n",
+ " os.makedirs(folder_path, exist_ok=True)\n",
+ "\n",
+ " combined_data = pd.concat([pd.read_csv(os.path.join(directory_path, file)) for file in files], axis=0)\n",
+ "\n",
+ " merged_file_path = os.path.join(folder_path, f\"merged_{company_name}.csv\")\n",
+ " combined_data.to_csv(merged_file_path, index=False)\n",
+ " \n",
+ "\n",
+ "if __name__ == \"__main__\":\n",
+ " target_directory = \"/opt/ml/finance_sentiment_corpus\"\n",
+ " merge_and_move_files(target_directory)\n"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 파일 합치기\n",
+ "merged_(회사명)을 학습 시키기위한 merged_all.csv 만들기"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def merge_csv_files(directory_path, output_file_name):\n",
+ " file_list = os.listdir(directory_path)\n",
+ " combined_data = pd.DataFrame()\n",
+ "\n",
+ " for file_name in file_list:\n",
+ " if file_name.endswith(\".csv\") and file_name != \"merged_all.csv\" : # merged_all.csv 제외하고 합치기\n",
+ " file_path = os.path.join(directory_path, file_name)\n",
+ "\n",
+ " data = pd.read_csv(file_path)\n",
+ " \n",
+ " # 혹여나 \"labels\"로 지정해둔 column 이름 변경\n",
+ " if \"labels\" in data.columns and \"label\" not in data.columns :\n",
+ " data[\"label\"] = data[\"labels\"]\n",
+ " \n",
+ " data = data[[\"company\", \"title\", \"date\", \"content_corpus\", \"label\"]]\n",
+ " combined_data = pd.concat([combined_data, data], axis=0, ignore_index=True)\n",
+ " \n",
+ " output_file_path = os.path.join(directory_path, output_file_name)\n",
+ "\n",
+ " # 지우고 다시 만들기\n",
+ " if os.path.exists(output_file_path):\n",
+ " os.remove(output_file_path)\n",
+ " \n",
+ " combined_data.to_csv(output_file_path, index=False)\n",
+ " return combined_data\n",
+ "\n",
+ "directory_path = \"/opt/ml/finance_sentiment_corpus/merged\"\n",
+ "output_file_name = \"merged_all.csv\"\n",
+ "output_file_path = directory_path + output_file_name\n",
+ " \n",
+ "data = merge_csv_files(directory_path , output_file_name)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## TEST 데이터셋 만들기\n",
+ "26개의 회사에서 20개씩 뽑아 test_dataset 만들기"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "csv_file_path = \"/opt/ml/finance_sentiment_corpus/label_0_to_521.csv\"\n",
+ "df = pd.read_csv(csv_file_path)\n",
+ "\n",
+ "# 새로운 DataFrame을 저장할 리스트 생성\n",
+ "new_dfs = []\n",
+ "\n",
+ "# company 컬럼의 고유한 값들을 추출하여 각 회사별로 20개씩 행을 샘플링하여 새로운 DataFrame으로 생성\n",
+ "for company_name in df['company'].unique():\n",
+ " company_subset = df[df['company'] == company_name].sample(n=10, random_state=42) # 20개씩 랜덤 샘플링 (여기서는 random_state를 고정하여 재현성을 위해 사용)\n",
+ " new_dfs.append(company_subset)\n",
+ "\n",
+ "# 새로운 DataFrame을 병합하여 하나의 DataFrame으로 합치기\n",
+ "result_df = pd.concat(new_dfs)\n",
+ "\n",
+ "# 새로운 DataFrame을 CSV 파일로 저장\n",
+ "result_csv_file_path = \"/opt/ml/finance_sentiment_corpus/26_company_half_labeled.csv\" # 저장할 파일 경로 설정 (적절하게 변경해주세요)\n",
+ "result_df.to_csv(result_csv_file_path, index=False) # index=False를 지정하여 인덱스를 저장하지 않도록 설정합니다.\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "final",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.5"
+ },
+ "orig_nbformat": 4,
+ "vscode": {
+ "interpreter": {
+ "hash": "981f108a204f421f158e0977940335d851edffa6dd3586828a3e1aec045160e4"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/Notebooks/datasets.ipynb b/Notebooks/datasets.ipynb
deleted file mode 100644
index 610a130..0000000
--- a/Notebooks/datasets.ipynb
+++ /dev/null
@@ -1,561 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {},
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/opt/conda/envs/mrc/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
- " from .autonotebook import tqdm as notebook_tqdm\n"
- ]
- }
- ],
- "source": [
- "import torch\n",
- "import pandas as pd\n",
- "import sklearn\n",
- "\n",
- "from transformers import AutoModelForSequenceClassification, AutoTokenizer\n",
- "from transformers import TrainingArguments, Trainer\n",
- "from transformers import pipeline\n",
- "\n",
- "import sys\n",
- "sys.path.append('../sentence-sentimental')\n",
- "\n",
- "from dataset.datasets import SentimentalDataset\n",
- "from metrics.metrics import compute_metrics\n",
- "\n",
- "from sklearn.datasets import load_iris # 샘플 데이터 로딩\n",
- "from sklearn.model_selection import train_test_split"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "Some weights of the model checkpoint at klue/roberta-base were not used when initializing RobertaForSequenceClassification: ['lm_head.layer_norm.bias', 'lm_head.layer_norm.weight', 'lm_head.dense.bias', 'lm_head.decoder.bias', 'lm_head.decoder.weight', 'lm_head.dense.weight', 'lm_head.bias']\n",
- "- This IS expected if you are initializing RobertaForSequenceClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).\n",
- "- This IS NOT expected if you are initializing RobertaForSequenceClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).\n",
- "Some weights of RobertaForSequenceClassification were not initialized from the model checkpoint at klue/roberta-base and are newly initialized: ['classifier.out_proj.bias', 'classifier.out_proj.weight', 'classifier.dense.weight', 'classifier.dense.bias']\n",
- "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n"
- ]
- }
- ],
- "source": [
- "device = torch.device(\"cuda\" if torch.cuda.is_available() else 'cpu')\n",
- "MODEL_NAME = 'klue/roberta-base'\n",
- "model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=3).to(device)\n",
- "tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [],
- "source": [
- "data = pd.read_csv('/opt/ml/finance_sentiment_corpus/finance_data.csv')\n",
- "data['labels'] = data['labels'].map({'negative':0, 'neutral':1, 'positive':2})\n",
- "\n",
- "train_encoding = tokenizer(\n",
- " data['kor_sentence'].tolist(),\n",
- " return_tensors='pt',\n",
- " padding=True,\n",
- " truncation=True\n",
- " )\n",
- "\n",
- "train_set = SentimentalDataset(train_encoding, data['labels'])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "pandas.core.frame.DataFrame"
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "type(data)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [],
- "source": [
- "import pandas as pd\n",
- "from sklearn.datasets import load_wine\n",
- "\n",
- "wine = load_wine()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "metadata": {},
- "outputs": [],
- "source": [
- "X = wine['data']\n",
- "Y = wine['target']"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "metadata": {},
- "outputs": [],
- "source": [
- "X_train, X_test, y_train, y_test = train_test_split(X, Y, stratify=Y, random_state=777)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "numpy.ndarray"
- ]
- },
- "execution_count": 8,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "type(X_train)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "numpy.ndarray"
- ]
- },
- "execution_count": 9,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "type(X)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": 10,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "4846 4846\n"
- ]
- }
- ],
- "source": [
- "print(len(data['kor_sentence']), len(data['labels']))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 11,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "pandas.core.series.Series"
- ]
- },
- "execution_count": 11,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "type(data['kor_sentence'])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 12,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "0 Gran에 따르면, 그 회사는 회사가 성장하고 있는 곳이지만, 모든 생산을 러시아로...\n",
- "1 테크노폴리스는 컴퓨터 기술과 통신 분야에서 일하는 회사들을 유치하기 위해 10만 평...\n",
- "2 국제 전자산업 회사인 엘코텍은 탈린 공장에서 수십 명의 직원을 해고했으며, 이전의 ...\n",
- "3 새로운 생산공장으로 인해 회사는 예상되는 수요 증가를 충족시킬 수 있는 능력을 증가...\n",
- "4 2009-2012년 회사의 업데이트된 전략에 따르면, Basware는 20% - 4...\n",
- " ... \n",
- "4841 런던 마켓워치 -- 은행주의 반등이 FTSE 100지수의 약세를 상쇄하지 못하면서 ...\n",
- "4842 린쿠스키아의 맥주 판매량은 416만 리터로 6.5% 감소했으며 카우노 알루스의 맥주...\n",
- "4843 영업이익은 2007년 68.8 mn에서 35.4 mn으로 떨어졌으며, 선박 판매 이...\n",
- "4844 페이퍼 부문 순매출은 2008년 2분기 241.1 mn에서 2009년 2분기 221...\n",
- "4845 핀란드에서의 판매는 1월에 10.5% 감소한 반면, 국외에서의 판매는 17% 감소했다.\n",
- "Name: kor_sentence, Length: 4846, dtype: object"
- ]
- },
- "execution_count": 12,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "data['kor_sentence']"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 14,
- "metadata": {},
- "outputs": [],
- "source": [
- "sentence_train, sentence_test, label_train, label_test = train_test_split(data['kor_sentence'], data['labels'],\n",
- " test_size=0.2, \n",
- " # shuffle=True, \n",
- " stratify=data['labels'], # label에 비율을 맞춰서 분리\n",
- " random_state=34)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 41,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "pandas.core.series.Series"
- ]
- },
- "execution_count": 41,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "type(data['kor_sentence'])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 39,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "pandas.core.series.Series"
- ]
- },
- "execution_count": 39,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "type(label_train)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 26,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "1828 핀란드 식품업계 기업들은 우크라이나가 제공하는 기회에 관심이 많다.\n",
- "4809 펜틱+아이넨은 미디어하우스가 제공하는 인터넷 콘텐츠 대부분이 영원히 무료일 수는 없...\n",
- "1166 또한 이러한 시장에 대한 6년간의 역사적 분석이 제공됩니다.\n",
- "277 루키 총리는 핀란드 사본리나에 위치한 키론살미 해협을 가로지르는 철제 구조물의 인도...\n",
- "4670 2009년 8월 3일 핀란드 미디어 그룹 Ilkka-Yhtyma Oyj(헬: ILK...\n",
- " ... \n",
- "3515 알스트롬 주식거래소 7.2.2.27 10시 30분에 발표 알스트롬 주식회사의 총 5...\n",
- "4503 라우트 대변인은 \"다른 인력에 대한 적응 조치는 당분간 적절하다\"고 말했다.\n",
- "434 주식수 증가는 2006년 스톡옵션제도에 따라 회사 경영에 부여된 주식선택권에 따른 ...\n",
- "3212 개발사업단도 지난해 11월 대비 개발사업단가를 3분의 1가량 낮추겠다고 밝혔다.\n",
- "3773 이 건물에는 제품 개발 및 테스트 실험실이 들어설 예정이다.\n",
- "Name: kor_sentence, Length: 3876, dtype: object"
- ]
- },
- "execution_count": 26,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "sentence_train"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 16,
- "metadata": {},
- "outputs": [],
- "source": [
- "train_encoding = tokenizer(\n",
- " sentence_train.tolist(),\n",
- " return_tensors='pt',\n",
- " padding=True,\n",
- " truncation=True\n",
- " )\n",
- "\n",
- "test_encoding = tokenizer(\n",
- " sentence_test.tolist(),\n",
- " return_tensors='pt',\n",
- " padding=True,\n",
- " truncation=True\n",
- " )"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 17,
- "metadata": {},
- "outputs": [],
- "source": [
- "train_set = SentimentalDataset(train_encoding, label_train.reset_index(drop=True))\n",
- "test_set = SentimentalDataset(test_encoding, label_test.reset_index(drop=True))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 18,
- "metadata": {},
- "outputs": [],
- "source": [
- "training_args = TrainingArguments(\n",
- " output_dir = './outputs',\n",
- " logging_steps = 50,\n",
- " num_train_epochs = 1,\n",
- " per_device_train_batch_size=32,\n",
- " per_device_eval_batch_size=32,\n",
- " fp16=True\n",
- " )\n",
- "\n",
- "trainer = Trainer(\n",
- " model=model,\n",
- " args=training_args,\n",
- " train_dataset=train_set,\n",
- " eval_dataset=test_set,\n",
- " compute_metrics=compute_metrics\n",
- " )\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 24,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
- "To disable this warning, you can either:\n",
- "\t- Avoid using `tokenizers` before the fork if possible\n",
- "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
- "/bin/bash: wandb: command not found\n"
- ]
- }
- ],
- "source": [
- "!wandb offline"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 25,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- " \n",
- " \n",
- "
\n",
- " [ 51/122 00:04 < 00:06, 11.13 it/s, Epoch 0.41/1]\n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " Step | \n",
- " Training Loss | \n",
- "
\n",
- " \n",
- " \n",
- " \n",
- "
"
- ],
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "ename": "AttributeError",
- "evalue": "module 'wandb' has no attribute 'log'",
- "output_type": "error",
- "traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
- "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
- "\u001b[1;32m/opt/ml/level3_nlp_finalproject-nlp-04/experiment/datasets.ipynb Cell 22\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m'\u001b[39m\u001b[39m---train start---\u001b[39m\u001b[39m'\u001b[39m)\n\u001b[0;32m----> 2\u001b[0m trainer\u001b[39m.\u001b[39;49mtrain()\n",
- "File \u001b[0;32m/opt/conda/envs/mrc/lib/python3.10/site-packages/transformers/trainer.py:1664\u001b[0m, in \u001b[0;36mTrainer.train\u001b[0;34m(self, resume_from_checkpoint, trial, ignore_keys_for_eval, **kwargs)\u001b[0m\n\u001b[1;32m 1659\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mmodel_wrapped \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mmodel\n\u001b[1;32m 1661\u001b[0m inner_training_loop \u001b[39m=\u001b[39m find_executable_batch_size(\n\u001b[1;32m 1662\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_inner_training_loop, \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_train_batch_size, args\u001b[39m.\u001b[39mauto_find_batch_size\n\u001b[1;32m 1663\u001b[0m )\n\u001b[0;32m-> 1664\u001b[0m \u001b[39mreturn\u001b[39;00m inner_training_loop(\n\u001b[1;32m 1665\u001b[0m args\u001b[39m=\u001b[39;49margs,\n\u001b[1;32m 1666\u001b[0m resume_from_checkpoint\u001b[39m=\u001b[39;49mresume_from_checkpoint,\n\u001b[1;32m 1667\u001b[0m trial\u001b[39m=\u001b[39;49mtrial,\n\u001b[1;32m 1668\u001b[0m ignore_keys_for_eval\u001b[39m=\u001b[39;49mignore_keys_for_eval,\n\u001b[1;32m 1669\u001b[0m )\n",
- "File \u001b[0;32m/opt/conda/envs/mrc/lib/python3.10/site-packages/transformers/trainer.py:2019\u001b[0m, in \u001b[0;36mTrainer._inner_training_loop\u001b[0;34m(self, batch_size, args, resume_from_checkpoint, trial, ignore_keys_for_eval)\u001b[0m\n\u001b[1;32m 2016\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mstate\u001b[39m.\u001b[39mepoch \u001b[39m=\u001b[39m epoch \u001b[39m+\u001b[39m (step \u001b[39m+\u001b[39m \u001b[39m1\u001b[39m \u001b[39m+\u001b[39m steps_skipped) \u001b[39m/\u001b[39m steps_in_epoch\n\u001b[1;32m 2017\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mcontrol \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mcallback_handler\u001b[39m.\u001b[39mon_step_end(args, \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mstate, \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mcontrol)\n\u001b[0;32m-> 2019\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_maybe_log_save_evaluate(tr_loss, model, trial, epoch, ignore_keys_for_eval)\n\u001b[1;32m 2020\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[1;32m 2021\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mcontrol \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mcallback_handler\u001b[39m.\u001b[39mon_substep_end(args, \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mstate, \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mcontrol)\n",
- "File \u001b[0;32m/opt/conda/envs/mrc/lib/python3.10/site-packages/transformers/trainer.py:2286\u001b[0m, in \u001b[0;36mTrainer._maybe_log_save_evaluate\u001b[0;34m(self, tr_loss, model, trial, epoch, ignore_keys_for_eval)\u001b[0m\n\u001b[1;32m 2283\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_globalstep_last_logged \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mstate\u001b[39m.\u001b[39mglobal_step\n\u001b[1;32m 2284\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mstore_flos()\n\u001b[0;32m-> 2286\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mlog(logs)\n\u001b[1;32m 2288\u001b[0m metrics \u001b[39m=\u001b[39m \u001b[39mNone\u001b[39;00m\n\u001b[1;32m 2289\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mcontrol\u001b[39m.\u001b[39mshould_evaluate:\n",
- "File \u001b[0;32m/opt/conda/envs/mrc/lib/python3.10/site-packages/transformers/trainer.py:2648\u001b[0m, in \u001b[0;36mTrainer.log\u001b[0;34m(self, logs)\u001b[0m\n\u001b[1;32m 2646\u001b[0m output \u001b[39m=\u001b[39m {\u001b[39m*\u001b[39m\u001b[39m*\u001b[39mlogs, \u001b[39m*\u001b[39m\u001b[39m*\u001b[39m{\u001b[39m\"\u001b[39m\u001b[39mstep\u001b[39m\u001b[39m\"\u001b[39m: \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mstate\u001b[39m.\u001b[39mglobal_step}}\n\u001b[1;32m 2647\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mstate\u001b[39m.\u001b[39mlog_history\u001b[39m.\u001b[39mappend(output)\n\u001b[0;32m-> 2648\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mcontrol \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mcallback_handler\u001b[39m.\u001b[39;49mon_log(\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49margs, \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mstate, \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mcontrol, logs)\n",
- "File \u001b[0;32m/opt/conda/envs/mrc/lib/python3.10/site-packages/transformers/trainer_callback.py:390\u001b[0m, in \u001b[0;36mCallbackHandler.on_log\u001b[0;34m(self, args, state, control, logs)\u001b[0m\n\u001b[1;32m 388\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mon_log\u001b[39m(\u001b[39mself\u001b[39m, args: TrainingArguments, state: TrainerState, control: TrainerControl, logs):\n\u001b[1;32m 389\u001b[0m control\u001b[39m.\u001b[39mshould_log \u001b[39m=\u001b[39m \u001b[39mFalse\u001b[39;00m\n\u001b[0;32m--> 390\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mcall_event(\u001b[39m\"\u001b[39;49m\u001b[39mon_log\u001b[39;49m\u001b[39m\"\u001b[39;49m, args, state, control, logs\u001b[39m=\u001b[39;49mlogs)\n",
- "File \u001b[0;32m/opt/conda/envs/mrc/lib/python3.10/site-packages/transformers/trainer_callback.py:397\u001b[0m, in \u001b[0;36mCallbackHandler.call_event\u001b[0;34m(self, event, args, state, control, **kwargs)\u001b[0m\n\u001b[1;32m 395\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mcall_event\u001b[39m(\u001b[39mself\u001b[39m, event, args, state, control, \u001b[39m*\u001b[39m\u001b[39m*\u001b[39mkwargs):\n\u001b[1;32m 396\u001b[0m \u001b[39mfor\u001b[39;00m callback \u001b[39min\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mcallbacks:\n\u001b[0;32m--> 397\u001b[0m result \u001b[39m=\u001b[39m \u001b[39mgetattr\u001b[39;49m(callback, event)(\n\u001b[1;32m 398\u001b[0m args,\n\u001b[1;32m 399\u001b[0m state,\n\u001b[1;32m 400\u001b[0m control,\n\u001b[1;32m 401\u001b[0m model\u001b[39m=\u001b[39;49m\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mmodel,\n\u001b[1;32m 402\u001b[0m tokenizer\u001b[39m=\u001b[39;49m\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mtokenizer,\n\u001b[1;32m 403\u001b[0m optimizer\u001b[39m=\u001b[39;49m\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49moptimizer,\n\u001b[1;32m 404\u001b[0m lr_scheduler\u001b[39m=\u001b[39;49m\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mlr_scheduler,\n\u001b[1;32m 405\u001b[0m train_dataloader\u001b[39m=\u001b[39;49m\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mtrain_dataloader,\n\u001b[1;32m 406\u001b[0m eval_dataloader\u001b[39m=\u001b[39;49m\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49meval_dataloader,\n\u001b[1;32m 407\u001b[0m \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs,\n\u001b[1;32m 408\u001b[0m )\n\u001b[1;32m 409\u001b[0m \u001b[39m# A Callback can skip the return of `control` if it doesn't change it.\u001b[39;00m\n\u001b[1;32m 410\u001b[0m \u001b[39mif\u001b[39;00m result \u001b[39mis\u001b[39;00m \u001b[39mnot\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n",
- "File \u001b[0;32m/opt/conda/envs/mrc/lib/python3.10/site-packages/transformers/integrations.py:807\u001b[0m, in \u001b[0;36mWandbCallback.on_log\u001b[0;34m(self, args, state, control, model, logs, **kwargs)\u001b[0m\n\u001b[1;32m 805\u001b[0m \u001b[39mif\u001b[39;00m state\u001b[39m.\u001b[39mis_world_process_zero:\n\u001b[1;32m 806\u001b[0m logs \u001b[39m=\u001b[39m rewrite_logs(logs)\n\u001b[0;32m--> 807\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_wandb\u001b[39m.\u001b[39;49mlog({\u001b[39m*\u001b[39m\u001b[39m*\u001b[39mlogs, \u001b[39m\"\u001b[39m\u001b[39mtrain/global_step\u001b[39m\u001b[39m\"\u001b[39m: state\u001b[39m.\u001b[39mglobal_step})\n",
- "\u001b[0;31mAttributeError\u001b[0m: module 'wandb' has no attribute 'log'"
- ]
- }
- ],
- "source": [
- "\n",
- "print('---train start---')\n",
- "trainer.train()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 26,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "1828 핀란드 식품업계 기업들은 우크라이나가 제공하는 기회에 관심이 많다.\n",
- "4809 펜틱+아이넨은 미디어하우스가 제공하는 인터넷 콘텐츠 대부분이 영원히 무료일 수는 없...\n",
- "1166 또한 이러한 시장에 대한 6년간의 역사적 분석이 제공됩니다.\n",
- "277 루키 총리는 핀란드 사본리나에 위치한 키론살미 해협을 가로지르는 철제 구조물의 인도...\n",
- "4670 2009년 8월 3일 핀란드 미디어 그룹 Ilkka-Yhtyma Oyj(헬: ILK...\n",
- " ... \n",
- "3515 알스트롬 주식거래소 7.2.2.27 10시 30분에 발표 알스트롬 주식회사의 총 5...\n",
- "4503 라우트 대변인은 \"다른 인력에 대한 적응 조치는 당분간 적절하다\"고 말했다.\n",
- "434 주식수 증가는 2006년 스톡옵션제도에 따라 회사 경영에 부여된 주식선택권에 따른 ...\n",
- "3212 개발사업단도 지난해 11월 대비 개발사업단가를 3분의 1가량 낮추겠다고 밝혔다.\n",
- "3773 이 건물에는 제품 개발 및 테스트 실험실이 들어설 예정이다.\n",
- "Name: kor_sentence, Length: 3876, dtype: object"
- ]
- },
- "execution_count": 26,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "sentence_train"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "numpy.ndarray"
- ]
- },
- "execution_count": 8,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "type(data)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "dataset.datasets.SentimentalDataset"
- ]
- },
- "execution_count": 9,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "type(train_set)"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "mrc",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.10.11"
- },
- "orig_nbformat": 4,
- "vscode": {
- "interpreter": {
- "hash": "6433bde22504cbf34326cab27df20b94e196fcf98213f776ce9807cc95ec7583"
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/Notebooks/glob.ipynb b/Notebooks/glob.ipynb
deleted file mode 100644
index 54f9977..0000000
--- a/Notebooks/glob.ipynb
+++ /dev/null
@@ -1,320 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": 12,
- "metadata": {},
- "outputs": [],
- "source": [
- "import pandas as pd\n",
- "import glob\n",
- "import re"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 공시번호 기준으로 폴더 나누기"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 21,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "data\n",
- "data\n",
- "data\n",
- "data\n",
- "data\n"
- ]
- }
- ],
- "source": [
- "import os\n",
- "import pandas as pd\n",
- "\n",
- "def merge_and_move_files(directory_path):\n",
- " file_list = os.listdir(directory_path)\n",
- "\n",
- " # 파일들을 처리하기 위해 딕셔너리를 생성합니다.\n",
- " files_by_number = {}\n",
- "\n",
- " # 파일들을 숫자에 따라 그룹화합니다.\n",
- " for file_name in file_list:\n",
- " if file_name.startswith(\"preprocessed_data_\"):\n",
- " number = file_name.split(\"_\")[2]\n",
- " if number not in files_by_number:\n",
- " files_by_number[number] = []\n",
- " files_by_number[number].append(file_name)\n",
- "\n",
- " # 각 숫자에 대해 폴더를 만들고 해당 폴더로 파일들을 이동합니다.\n",
- " for number, files in files_by_number.items():\n",
- " folder_path = os.path.join(directory_path, number)\n",
- " os.makedirs(folder_path, exist_ok=True)\n",
- "\n",
- " # 파일들을 위아래로 합쳐서 새로운 파일로 만듭니다.\n",
- " combined_data = pd.concat([pd.read_csv(os.path.join(directory_path, file)) for file in files], axis=0)\n",
- "\n",
- " # 새로운 파일을 해당 폴더로 이동합니다.\n",
- " merged_file_path = os.path.join(folder_path, f\"merged_{number}.csv\")\n",
- " combined_data.to_csv(merged_file_path, index=False)\n",
- "\n",
- " # # 이동한 파일들은 삭제합니다. (선택사항)\n",
- " # for file in files:\n",
- " # file_path = os.path.join(directory_path, file)\n",
- " # os.remove(file_path)\n",
- "\n",
- "if __name__ == \"__main__\":\n",
- " target_directory = '/opt/ml/finance_sentiment_corpus/'\n",
- " merge_and_move_files(target_directory)\n"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 회사명 기준으로 파일 나누기"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [],
- "source": [
- "import os\n",
- "import pandas as pd\n",
- "\n",
- "def merge_and_move_files(directory_path):\n",
- " # 디렉토리 내의 파일 리스트를 가져옵니다.\n",
- " file_list = os.listdir(directory_path)\n",
- "\n",
- " # 파일들을 처리하기 위해 딕셔너리를 생성합니다.\n",
- " files_by_company = {}\n",
- "\n",
- " # 파일들을 기업 이름에 따라 그룹화합니다.\n",
- " for file_name in file_list:\n",
- " # if file_name[0].isdigit():\n",
- " # if file_name.startswith(\"preprocessed_data_\"):\n",
- " file_path = os.path.join(directory_path, file_name)\n",
- " data = pd.read_csv(file_path)\n",
- " company_name = data['company'].iloc[0]\n",
- " if company_name not in files_by_company:\n",
- " files_by_company[company_name] = []\n",
- " files_by_company[company_name].append(file_name)\n",
- "\n",
- " # 각 기업에 대해 폴더를 만들고 해당 폴더로 파일들을 이동합니다.\n",
- " for company_name, files in files_by_company.items():\n",
- " folder_path = os.path.join(directory_path, \"merged\")\n",
- " os.makedirs(folder_path, exist_ok=True)\n",
- "\n",
- " # 파일들을 위아래로 합쳐서 새로운 파일로 만듭니다.\n",
- " combined_data = pd.concat([pd.read_csv(os.path.join(directory_path, file)) for file in files], axis=0)\n",
- "\n",
- " # 새로운 파일을 해당 폴더로 이동합니다.\n",
- " merged_file_path = os.path.join(folder_path, f\"merged_{company_name}.csv\")\n",
- " combined_data.to_csv(merged_file_path, index=False)\n",
- "\n",
- " # # 이동한 파일들은 삭제합니다. (선택사항)\n",
- " # for file in files:\n",
- " # file_path = os.path.join(directory_path, file)\n",
- " # os.remove(file_path)\n",
- "\n",
- "if __name__ == \"__main__\":\n",
- " # target_directory = '/opt/ml/finance_sentiment_corpus/'\n",
- " target_directory = \"/opt/ml/finance_sentiment_corpus\"\n",
- " merge_and_move_files(target_directory)\n"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 파일 합치기"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "def merge_csv_files(directory_path, output_file_name):\n",
- " file_list = os.listdir(directory_path)\n",
- " combined_data = pd.DataFrame()\n",
- "\n",
- " for file_name in file_list:\n",
- " if file_name.endswith(\".csv\") and file_name != \"merged_all.csv\":\n",
- " file_path = os.path.join(directory_path, file_name)\n",
- " print(file_path)\n",
- " data = pd.read_csv(file_path)\n",
- " \n",
- " if \"label\" in data.columns and \"labels\" not in data.columns :\n",
- " data[\"labels\"] = data[\"label\"]\n",
- " \n",
- " data = data[[\"company\", \"title\", \"date\", \"content_corpus\", \"labels\"]]\n",
- " combined_data = pd.concat([combined_data, data], axis=0, ignore_index=True)\n",
- " \n",
- " output_file_path = os.path.join(directory_path, output_file_name)\n",
- " # print(output_file_path)\n",
- " if os.path.exists(output_file_path):\n",
- " os.remove(output_file_path)\n",
- " \n",
- " combined_data.to_csv(output_file_path, index=False)\n",
- " return combined_data\n",
- "\n",
- "directory_path = \"/opt/ml/finance_sentiment_corpus/merged\"\n",
- "output_file_name = \"merged_all.csv\"\n",
- "output_file_path = directory_path + output_file_name\n",
- " \n",
- "data = merge_csv_files(directory_path , output_file_name)"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 전처리해서 chat gpt한테 넣어주기"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "/opt/ml/finance_sentiment_corpus/merged/merged_LG전자.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_KT&G.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_셀트리온.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_현대모비스.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_LG.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_SK이노베이션.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_카카오뱅크.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_HD현대중공업.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_한국전력.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_POSCO홀딩스.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_두산에너빌리티.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_삼성바이오로직스.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_LG화학.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_에코프로비엠.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_삼성물산.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_SK하이닉스.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_삼성SDI.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_삼성화재.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_카카오.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_포스코퓨처엠.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_기아.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_하이브.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_삼성생명.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_에코프로.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_신한지주.csv\n",
- "/opt/ml/finance_sentiment_corpus/merged/merged_KB금융.csv\n"
- ]
- }
- ],
- "source": [
- "def merge_csv_files(directory_path, output_file_name):\n",
- " file_list = os.listdir(directory_path)\n",
- " combined_data = pd.DataFrame()\n",
- "\n",
- " for file_name in file_list:\n",
- " if file_name.endswith(\".csv\") and file_name != \"merged_all.csv\":\n",
- " file_path = os.path.join(directory_path, file_name)\n",
- " print(file_path)\n",
- " data = pd.read_csv(file_path)\n",
- " \n",
- " # if \"label\" in data.columns and \"labels\" not in data.columns :\n",
- " # data[\"labels\"] = data[\"label\"]\n",
- " \n",
- " data = data[[\"company\", \"title\", \"date\", \"content_corpus\"]]\n",
- " combined_data = pd.concat([combined_data, data], axis=0, ignore_index=True)\n",
- " \n",
- " output_file_path = os.path.join(directory_path, output_file_name)\n",
- " # print(output_file_path)\n",
- " if os.path.exists(output_file_path):\n",
- " os.remove(output_file_path)\n",
- " \n",
- " combined_data.to_csv(output_file_path, index=False)\n",
- " return combined_data\n",
- "\n",
- "directory_path = \"/opt/ml/finance_sentiment_corpus/merged\"\n",
- "output_file_name = \"merged_all.csv\"\n",
- "output_file_path = directory_path + output_file_name\n",
- " \n",
- "data = merge_csv_files(directory_path , output_file_name)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 57,
- "metadata": {},
- "outputs": [],
- "source": [
- "import os\n",
- "import pandas as pd\n",
- "\n",
- "def merge_csv_files(directory_path, output_file_name):\n",
- " file_list = os.listdir(directory_path)\n",
- " combined_data = pd.DataFrame()\n",
- "\n",
- " for file_name in file_list:\n",
- " if file_name.endswith(\".csv\"):\n",
- " file_path = os.path.join(directory_path, file_name)\n",
- " data = pd.read_csv(file_path)\n",
- " combined_data = pd.concat([combined_data, data], axis=0, ignore_index=True)\n",
- "\n",
- " output_file_path = os.path.join(directory_path, output_file_name)\n",
- "\n",
- " combined_data.to_csv(output_file_path, index=False)\n",
- "\n",
- "directory_path = \"/opt/ml/finance_sentiment_corpus/\"\n",
- "output_file_name = \"merged_all.csv\"\n",
- "output_file_path = directory_path + output_file_name\n",
- "\n",
- "if os.path.exists(output_file_path):\n",
- " os.remove(output_file_path)\n",
- "# merge_csv_files(\"/opt/ml/finance_sentiment_corpus\", \"merged_all.csv\")"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "final",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.8.5"
- },
- "orig_nbformat": 4,
- "vscode": {
- "interpreter": {
- "hash": "981f108a204f421f158e0977940335d851edffa6dd3586828a3e1aec045160e4"
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/Notebooks/train.ipynb b/Notebooks/train.ipynb
deleted file mode 100644
index f992c70..0000000
--- a/Notebooks/train.ipynb
+++ /dev/null
@@ -1,1011 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/opt/conda/envs/final/lib/python3.8/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
- " from .autonotebook import tqdm as notebook_tqdm\n"
- ]
- }
- ],
- "source": [
- "import torch\n",
- "import pandas as pd\n",
- "import sklearn\n",
- "import random\n",
- "import numpy as np\n",
- "import wandb\n",
- "\n",
- "from transformers import AutoModel\n",
- "from transformers import AutoModelForSequenceClassification, AutoTokenizer, BertForSequenceClassification\n",
- "from transformers import TrainingArguments, Trainer\n",
- "from transformers import pipeline\n",
- "from transformers import DebertaV2ForSequenceClassification\n",
- "from transformers import RobertaTokenizer, RobertaForSequenceClassification\n",
- "\n",
- "from dataset.datasets import SentimentalDataset\n",
- "from metrics.metrics import compute_metrics\n",
- "\n",
- "from sklearn.datasets import load_iris # 샘플 데이터 로딩\n",
- "from sklearn.model_selection import train_test_split\n",
- "\n",
- "from utils.utils import config_seed\n",
- "\n",
- "import json\n",
- "import re"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### 설정"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [],
- "source": [
- "SEED = 42\n",
- "config_seed(SEED)\n",
- "device = torch.device(\"cuda\" if torch.cuda.is_available() else 'cpu')"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "#### 모델 및 토크나이저"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "Some weights of the model checkpoint at klue/roberta-large were not used when initializing RobertaForSequenceClassification: ['lm_head.layer_norm.bias', 'lm_head.dense.bias', 'lm_head.bias', 'lm_head.layer_norm.weight', 'lm_head.dense.weight']\n",
- "- This IS expected if you are initializing RobertaForSequenceClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).\n",
- "- This IS NOT expected if you are initializing RobertaForSequenceClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).\n",
- "Some weights of RobertaForSequenceClassification were not initialized from the model checkpoint at klue/roberta-large and are newly initialized: ['classifier.dense.bias', 'classifier.dense.weight', 'classifier.out_proj.weight', 'classifier.out_proj.bias']\n",
- "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "Embedding(32002, 1024)"
- ]
- },
- "execution_count": 5,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "MODEL_NAME = 'klue/roberta-large'\n",
- "model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=2).to(device)\n",
- "tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)\n",
- "\n",
- "special_tokens_dict = {'additional_special_tokens': ['[COMPANY]','[/COMPANY]']}\n",
- "num_added_toks = tokenizer.add_special_tokens(special_tokens_dict)\n",
- "model.resize_token_embeddings(len(tokenizer))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [],
- "source": [
- "# MODEL_NAME = \"team-lucid/deberta-v3-base-korean\"\n",
- "\n",
- "# model = DebertaV2ForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=2).to(device)\n",
- "# tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### 데이터셋 구성"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "#### 1) 기사 전체 학습"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "metadata": {},
- "outputs": [],
- "source": [
- "# data = pd.read_csv('/opt/ml/finance_sentiment_corpus/merged_samsung_filtered.csv')\n",
- "\n",
- "# def extract_label(json_str) :\n",
- "# data_dict = eval(json_str) # JSON 문자열을 파이썬 딕셔너리로 변환\n",
- "# return data_dict[\"label\"]\n",
- "\n",
- "# # \"label\" 값을 추출하여 새로운 Series 생성\n",
- "# data['labels'] = data[\"labels\"].apply(extract_label)\n",
- "# data['labels'] = data['labels'].map({'부정':0, '긍정':1})"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "#### 2) 기사 앞뒤 학습"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 122,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- " \n",
- " \n",
- " \n",
- " | \n",
- " company | \n",
- " title | \n",
- " date | \n",
- " content_corpus | \n",
- " labels | \n",
- " \n",
- " \n",
- " \n",
- " \n",
- " 0 | \n",
- " 삼성전자 | \n",
- " 데이터센터 전력 40% 차지하는 D램… 삼성·SK하이닉스, ‘전성비’ ... | \n",
- " 2023.07.10 15:29 | \n",
- " 챗GPT 시대 화두로 떠오른 전력효율성 문제 ”전력 먹는 하마, D램 전력효율성 개... | \n",
- " {\"label\": \"긍정\", \"reason\": \"전력효율성 개선을 위한 솔루션 개발... | \n",
- " \n",
- " \n",
- " 1 | \n",
- " 삼성전자 | \n",
- " “삼성전자가 식품도 팔았어?”…신규 가입 일단 종료한 사연 | \n",
- " 2023.07.10 15:07 | \n",
- " 삼성 가전제품 구매고객에 삼성닷컴 내 e-식품관에서 할인혜택 주며 ‘락인’ 기대 ... | \n",
- " {\"label\": \"부정\", \"reason\": \"삼성전자 멤버십 플랜 신규 고객 모... | \n",
- " \n",
- " \n",
- " 2 | \n",
- " 삼성전자 | \n",
- " SGC솔루션 '도어글라스'…삼성·LG 세탁기·건조기에 공급 | \n",
- " 2023.07.10 15:05 | \n",
- " 해외 가전 브랜드 공략…B2B 사업 확장 SGC솔루션 논산 공장. . 생활유리... | \n",
- " {\"label\": \"긍정\", \"reason\": \"해외 가전 브랜드들을 공략하며 전 ... | \n",
- " \n",
- " \n",
- " 3 | \n",
- " 삼성전자 | \n",
- " ‘페이커’ 내세운 삼성 OLED 게이밍 모니터 글로벌 3천대 돌파 | \n",
- " 2023.07.10 14:58 | \n",
- " 북미·유럽 등 예약 판매 3000대 돌파 10일 오후 6시 삼성닷컴 ‘페이커’ 출연... | \n",
- " {\"label\": \"긍정\", \"reason\": \"오디세이 OLED G9의 글로벌 예... | \n",
- " \n",
- " \n",
- " 4 | \n",
- " 삼성전자 | \n",
- " 네이처 게재 등 성과…삼성휴먼테크논문대상, 30년 맞았다 | \n",
- " 2023.07.10 14:48 | \n",
- " 29년간 3만6558편 논문 접수…수상자 5312명 9월1일부터 올해 대상 접수…상... | \n",
- " {\"label\": \"긍정\", \"reason\": \"삼성휴먼테크논문대상이 올해로 시행 ... | \n",
- " \n",
- " \n",
- " \n",
- " "
- ],
- "text/plain": [
- " company title date \\\n",
- "0 삼성전자 데이터센터 전력 40% 차지하는 D램… 삼성·SK하이닉스, ‘전성비’ ... 2023.07.10 15:29 \n",
- "1 삼성전자 “삼성전자가 식품도 팔았어?”…신규 가입 일단 종료한 사연 2023.07.10 15:07 \n",
- "2 삼성전자 SGC솔루션 '도어글라스'…삼성·LG 세탁기·건조기에 공급 2023.07.10 15:05 \n",
- "3 삼성전자 ‘페이커’ 내세운 삼성 OLED 게이밍 모니터 글로벌 3천대 돌파 2023.07.10 14:58 \n",
- "4 삼성전자 네이처 게재 등 성과…삼성휴먼테크논문대상, 30년 맞았다 2023.07.10 14:48 \n",
- "\n",
- " content_corpus \\\n",
- "0 챗GPT 시대 화두로 떠오른 전력효율성 문제 ”전력 먹는 하마, D램 전력효율성 개... \n",
- "1 삼성 가전제품 구매고객에 삼성닷컴 내 e-식품관에서 할인혜택 주며 ‘락인’ 기대 ... \n",
- "2 해외 가전 브랜드 공략…B2B 사업 확장 SGC솔루션 논산 공장. . 생활유리... \n",
- "3 북미·유럽 등 예약 판매 3000대 돌파 10일 오후 6시 삼성닷컴 ‘페이커’ 출연... \n",
- "4 29년간 3만6558편 논문 접수…수상자 5312명 9월1일부터 올해 대상 접수…상... \n",
- "\n",
- " labels \n",
- "0 {\"label\": \"긍정\", \"reason\": \"전력효율성 개선을 위한 솔루션 개발... \n",
- "1 {\"label\": \"부정\", \"reason\": \"삼성전자 멤버십 플랜 신규 고객 모... \n",
- "2 {\"label\": \"긍정\", \"reason\": \"해외 가전 브랜드들을 공략하며 전 ... \n",
- "3 {\"label\": \"긍정\", \"reason\": \"오디세이 OLED G9의 글로벌 예... \n",
- "4 {\"label\": \"긍정\", \"reason\": \"삼성휴먼테크논문대상이 올해로 시행 ... "
- ]
- },
- "execution_count": 122,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "data = pd.read_csv(\"/opt/ml/finance_sentiment_corpus/merged/merged_all.csv\")\n",
- "data.head()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "(3778, 5)\n",
- "(3777, 5)\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- " \n",
- " \n",
- " \n",
- " | \n",
- " title | \n",
- " date | \n",
- " content_corpus | \n",
- " labels | \n",
- " content_corpus_company | \n",
- " \n",
- " \n",
- " \n",
- " \n",
- " 0 | \n",
- " 데이터센터 전력 40% 차지하는 D램… 삼성·SK하이닉스, ‘전성비’ ... | \n",
- " 2023.07.10 15:29 | \n",
- " 챗GPT 시대 화두로 떠오른 전력효율성 문제 ”전력 먹는 하마, D램 전력효율성 개... | \n",
- " 1.0 | \n",
- " 이 기사는 [COMPANY]삼성전자[/COMPANY]에 대한 기사. [SEP]. 데... | \n",
- " \n",
- " \n",
- " 1 | \n",
- " “삼성전자가 식품도 팔았어?”…신규 가입 일단 종료한 사연 | \n",
- " 2023.07.10 15:07 | \n",
- " 삼성 가전제품 구매고객에 삼성닷컴 내 e-식품관에서 할인혜택 주며 ‘락인’ 기대 ... | \n",
- " 0.0 | \n",
- " 이 기사는 [COMPANY]삼성전자[/COMPANY]에 대한 기사. [SEP]. “... | \n",
- " \n",
- " \n",
- " 2 | \n",
- " SGC솔루션 '도어글라스'…삼성·LG 세탁기·건조기에 공급 | \n",
- " 2023.07.10 15:05 | \n",
- " 해외 가전 브랜드 공략…B2B 사업 확장 SGC솔루션 논산 공장. . 생활유리... | \n",
- " 1.0 | \n",
- " 이 기사는 [COMPANY]삼성전자[/COMPANY]에 대한 기사. [SEP]. S... | \n",
- " \n",
- " \n",
- " 3 | \n",
- " ‘페이커’ 내세운 삼성 OLED 게이밍 모니터 글로벌 3천대 돌파 | \n",
- " 2023.07.10 14:58 | \n",
- " 북미·유럽 등 예약 판매 3000대 돌파 10일 오후 6시 삼성닷컴 ‘페이커’ 출연... | \n",
- " 1.0 | \n",
- " 이 기사는 [COMPANY]삼성전자[/COMPANY]에 대한 기사. [SEP]. ‘... | \n",
- " \n",
- " \n",
- " 4 | \n",
- " 네이처 게재 등 성과…삼성휴먼테크논문대상, 30년 맞았다 | \n",
- " 2023.07.10 14:48 | \n",
- " 29년간 3만6558편 논문 접수…수상자 5312명 9월1일부터 올해 대상 접수…상... | \n",
- " 1.0 | \n",
- " 이 기사는 [COMPANY]삼성전자[/COMPANY]에 대한 기사. [SEP]. 네... | \n",
- " \n",
- " \n",
- " \n",
- " "
- ],
- "text/plain": [
- " title date \\\n",
- "0 데이터센터 전력 40% 차지하는 D램… 삼성·SK하이닉스, ‘전성비’ ... 2023.07.10 15:29 \n",
- "1 “삼성전자가 식품도 팔았어?”…신규 가입 일단 종료한 사연 2023.07.10 15:07 \n",
- "2 SGC솔루션 '도어글라스'…삼성·LG 세탁기·건조기에 공급 2023.07.10 15:05 \n",
- "3 ‘페이커’ 내세운 삼성 OLED 게이밍 모니터 글로벌 3천대 돌파 2023.07.10 14:58 \n",
- "4 네이처 게재 등 성과…삼성휴먼테크논문대상, 30년 맞았다 2023.07.10 14:48 \n",
- "\n",
- " content_corpus labels \\\n",
- "0 챗GPT 시대 화두로 떠오른 전력효율성 문제 ”전력 먹는 하마, D램 전력효율성 개... 1.0 \n",
- "1 삼성 가전제품 구매고객에 삼성닷컴 내 e-식품관에서 할인혜택 주며 ‘락인’ 기대 ... 0.0 \n",
- "2 해외 가전 브랜드 공략…B2B 사업 확장 SGC솔루션 논산 공장. . 생활유리... 1.0 \n",
- "3 북미·유럽 등 예약 판매 3000대 돌파 10일 오후 6시 삼성닷컴 ‘페이커’ 출연... 1.0 \n",
- "4 29년간 3만6558편 논문 접수…수상자 5312명 9월1일부터 올해 대상 접수…상... 1.0 \n",
- "\n",
- " content_corpus_company \n",
- "0 이 기사는 [COMPANY]삼성전자[/COMPANY]에 대한 기사. [SEP]. 데... \n",
- "1 이 기사는 [COMPANY]삼성전자[/COMPANY]에 대한 기사. [SEP]. “... \n",
- "2 이 기사는 [COMPANY]삼성전자[/COMPANY]에 대한 기사. [SEP]. S... \n",
- "3 이 기사는 [COMPANY]삼성전자[/COMPANY]에 대한 기사. [SEP]. ‘... \n",
- "4 이 기사는 [COMPANY]삼성전자[/COMPANY]에 대한 기사. [SEP]. 네... "
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "data = pd.read_csv(\"/opt/ml/finance_sentiment_corpus/merged/merged_all.csv\")\n",
- "# data = pd.read_csv(\"/opt/ml/finance_sentiment_corpus/merged/merged_NAVER.csv\")\n",
- "\n",
- "def remove_idx_row(data) : \n",
- " patterns = [r'idx\\s*:?\\s*.+?', r'라벨링\\s*:?\\s*.+?']\n",
- " \n",
- " for pattern in patterns :\n",
- " mask = data['labels'].str.match(pattern)\n",
- " data = data.drop(data[mask].index)\n",
- "\n",
- " return data\n",
- " \n",
- "# title과 content_corpus에서 원하는 문장 추출\n",
- "def extract_sentences(text):\n",
- " sentences = text.split('. ')\n",
- " if len(sentences) >= 5 :\n",
- " return '. '.join([sentences[0], sentences[1], sentences[-2], sentences[-1]])\n",
- " else :\n",
- " return '. '+text\n",
- " \n",
- "def extract_label(json_str) :\n",
- " json_str = json_str.replace(\"'\", \"\\\"\")\n",
- " try:\n",
- " json_data = json.loads(json_str)\n",
- "\n",
- " except json.JSONDecodeError as e:\n",
- " if json_str[-2:] == '.}' :\n",
- " json_str = json_str[:-2] + \".\\\"}\"\n",
- " elif json_str[-1] == \"\\\"\" :\n",
- " json_str = json_str + \"}\"\n",
- " else:\n",
- " json_str += \"\\\"}\"\n",
- " \n",
- " try:\n",
- " data_dict = json.loads(json_str)\n",
- " except json.JSONDecodeError as e:\n",
- " return None\n",
- "\n",
- " return data_dict[\"label\"]\n",
- " \n",
- "def preprocessing_label(json_str) :\n",
- " json_str = re.sub(r\"^.*### 출력\\s?:?\\s?\\n?\\s?\", \"\", str(json_str))\n",
- " # json_str= json_str.replace(\"\\\"\", \"'\")\n",
- " json_str = json_str.replace(\"'\", \"\\\\'\") # python interpreter에서 한번, json에서 한번\n",
- " \n",
- " return json_str\n",
- "\n",
- "data = remove_idx_row(data)\n",
- "\n",
- "# \"label\" column만 있다면 바꾸자.\n",
- "if \"label\" in data.columns and \"labels\" not in data.columns :\n",
- " data[\"labels\"] = data[\"label\"]\n",
- "\n",
- "data[\"labels\"] = data[\"labels\"].apply(preprocessing_label)\n",
- "data['labels'] = data[\"labels\"].apply(extract_label)\n",
- "data['content_corpus_company'] = data.apply(lambda row: '이 기사는 [COMPANY]'+ str(row[\"company\"]) +'[/COMPANY]에 대한 기사. [SEP]'+ extract_sentences(row['title']) + ' ' + extract_sentences(row['content_corpus']), axis=1)\n",
- "data['labels'] = data['labels'].map({'부정':0, '긍정':1})\n",
- "data = data[[\"title\", \"date\", \"content_corpus\", \"labels\", \"content_corpus_company\"]]\n",
- "print(data.shape)\n",
- "data = data[data['labels'].notna()]\n",
- "print(data.shape)\n",
- "data.head()"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Cross validation"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "metadata": {},
- "outputs": [],
- "source": [
- "# # dataset = train_test_split(data['content_corpus'], data['labels'],\n",
- "\n",
- "# # train_dataset, test_dataset = train_test_split(data['new_column'], data['labels'],\n",
- "# # test_size=0.2, shuffle=True, stratify=data['labels'], # label에 비율을 맞춰서 분리\n",
- "# # random_state=SEED)\n",
- "\n",
- "# train_dataset, test_dataset = train_test_split(data,\n",
- "# test_size=0.3, shuffle=True, stratify=data['labels'], # label에 비율을 맞춰서 분리\n",
- "# random_state=SEED)\n",
- "\n",
- "# train_dataset, val_dataset = train_test_split(train_dataset,\n",
- "# test_size=0.2, shuffle=True, stratify=train_dataset['labels'], # label에 비율을 맞춰서 분리\n",
- "# random_state=SEED)\n",
- "\n",
- "# corpus_train, label_train = train_dataset[\"new_column\"], train_dataset[\"labels\"]\n",
- "# corpus_val, label_val = val_dataset[\"new_column\"], val_dataset[\"labels\"]\n",
- "# corpus_test, label_test = test_dataset[\"new_column\"], test_dataset[\"labels\"]\n",
- "\n",
- "# # sentence_train, sentence_val, label_train, label_val = dataset\n",
- "\n",
- "\n",
- "# max_length=40\n",
- "# stride=10\n",
- "# ## TODO 임의의 값으로 차후 수정\n",
- "# train_encoding = tokenizer(corpus_train.tolist(), ## pandas.Series -> list\n",
- "# return_tensors='pt',\n",
- "# padding=True,\n",
- "# truncation=True,\n",
- "# ##\n",
- "# max_length=max_length,\n",
- "# stride=stride,\n",
- "# return_overflowing_tokens=True,\n",
- "# return_offsets_mapping=False\n",
- "# )\n",
- "\n",
- "# val_encoding = tokenizer(corpus_val.tolist(),\n",
- "# return_tensors='pt',\n",
- "# padding=True,\n",
- "# truncation=True,\n",
- "# ##\n",
- "# max_length=max_length,\n",
- "# stride=stride,\n",
- "# return_overflowing_tokens=True,\n",
- "# return_offsets_mapping=False\n",
- "# )\n",
- "\n",
- "# train_set = SentimentalDataset(train_encoding, label_train.reset_index(drop=True))\n",
- "# val_set = SentimentalDataset(val_encoding, label_val.reset_index(drop=True))\n",
- "# test_set = SentimentalDataset(val_encoding, label_test.reset_index(drop=True))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "metadata": {},
- "outputs": [],
- "source": [
- "dataset = train_test_split(data['content_corpus_company'], data['labels'],\n",
- " test_size=0.2, shuffle=True, stratify=data['labels'], # label에 비율을 맞춰서 분리\n",
- " random_state=SEED)\n",
- "\n",
- "\n",
- "sentence_train, sentence_val, label_train, label_val = dataset\n",
- "\n",
- "\n",
- "max_length=500\n",
- "# max_length = 2000\n",
- "stride=10\n",
- "## TODO 임의의 값으로 차후 수정\n",
- "train_encoding = tokenizer(sentence_train.tolist(), ## pandas.Series -> list\n",
- " return_tensors='pt',\n",
- " padding=True,\n",
- " truncation=True,\n",
- " ##\n",
- " max_length=max_length,\n",
- " stride=stride,\n",
- " # return_overflowing_tokens=True,\n",
- " return_offsets_mapping=False\n",
- " )\n",
- "\n",
- "val_encoding = tokenizer(sentence_val.tolist(),\n",
- " return_tensors='pt',\n",
- " padding=True,\n",
- " truncation=True,\n",
- " ##\n",
- " max_length=max_length,\n",
- " stride=stride,\n",
- " # return_overflowing_tokens=True,\n",
- " return_offsets_mapping=False\n",
- " )\n",
- "\n",
- "train_set = SentimentalDataset(train_encoding, label_train.reset_index(drop=True))\n",
- "val_set = SentimentalDataset(val_encoding, label_val.reset_index(drop=True))"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### 학습 (huggingface)\n",
- "#### hyperparameter\n",
- "- max_length\n",
- "- stride\n",
- "- num_train_epoch\n",
- "- learning_rate\n",
- "- per_device_train_batch_size\n",
- "- per_device_eval_batch_size"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "metadata": {},
- "outputs": [],
- "source": [
- "logging_steps = 200\n",
- "num_train_epochs = 3\n",
- "per_device_train_batch_size = 4\n",
- "per_device_eval_batch_size = 4\n",
- "learning_rate = 5e-6"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 57,
- "metadata": {},
- "outputs": [],
- "source": [
- "# !wandb online"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 131,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- " \n",
- " \n",
- " \n",
- " [2271/2271 08:54, Epoch 3/3]\n",
- " \n",
- " \n",
- " \n",
- " \n",
- " Epoch | \n",
- " Training Loss | \n",
- " Validation Loss | \n",
- " Accuracy | \n",
- " Micro F1 | \n",
- " Macro F1 | \n",
- " \n",
- " \n",
- " \n",
- " \n",
- " 1 | \n",
- " 0.356500 | \n",
- " 0.342270 | \n",
- " 0.912814 | \n",
- " 0.912814 | \n",
- " 0.893988 | \n",
- " \n",
- " \n",
- " 2 | \n",
- " 0.232600 | \n",
- " 0.395361 | \n",
- " 0.918098 | \n",
- " 0.918098 | \n",
- " 0.895991 | \n",
- " \n",
- " \n",
- " 3 | \n",
- " 0.099900 | \n",
- " 0.461685 | \n",
- " 0.919419 | \n",
- " 0.919419 | \n",
- " 0.897826 | \n",
- " \n",
- " \n",
- " "
- ],
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/plain": [
- "TrainOutput(global_step=2271, training_loss=0.2475055377948447, metrics={'train_runtime': 534.9292, 'train_samples_per_second': 16.982, 'train_steps_per_second': 4.245, 'total_flos': 8267250492648000.0, 'train_loss': 0.2475055377948447, 'epoch': 3.0})"
- ]
- },
- "execution_count": 131,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "# run = wandb.init(project=\"final_sentimental\", entity=\"nlp-10\")\n",
- "\n",
- "# run.name = f\"model: {MODEL_NAME} / batch_size: {per_device_train_batch_size} / lr: {learning_rate}\"\n",
- "\n",
- "training_args = TrainingArguments(\n",
- " output_dir = './outputs',\n",
- " logging_steps = logging_steps,\n",
- " num_train_epochs = num_train_epochs,\n",
- " per_device_train_batch_size = per_device_train_batch_size,\n",
- " per_device_eval_batch_size = per_device_eval_batch_size,\n",
- " learning_rate = learning_rate,\n",
- " evaluation_strategy=\"epoch\", \n",
- " fp16=True,\n",
- " report_to=\"wandb\",\n",
- ")\n",
- "\n",
- "trainer = Trainer(\n",
- " model=model,\n",
- " args=training_args,\n",
- " train_dataset=train_set,\n",
- " eval_dataset=val_set,\n",
- " compute_metrics=compute_metrics\n",
- ")\n",
- "\n",
- "print('---train start---')\n",
- "trainer.train()\n",
- "# wandb.finish()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 22,
- "metadata": {},
- "outputs": [],
- "source": [
- "# torch.save(model, \"/opt/ml/input/model-roberta_large-sota\")\n",
- "trainer.save_model(\"/opt/ml/input/model-roberta_large-sota_trainer_company-name\") "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 17,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
- "To disable this warning, you can either:\n",
- "\t- Avoid using `tokenizers` before the fork if possible\n",
- "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
- "Sat Jul 22 12:44:18 2023 \n",
- "+-----------------------------------------------------------------------------+\n",
- "| NVIDIA-SMI 450.80.02 Driver Version: 450.80.02 CUDA Version: 11.0 |\n",
- "|-------------------------------+----------------------+----------------------+\n",
- "| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n",
- "| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n",
- "| | | MIG M. |\n",
- "|===============================+======================+======================|\n",
- "| 0 Tesla V100-PCIE... On | 00000000:00:05.0 Off | Off |\n",
- "| N/A 41C P0 44W / 250W | 32455MiB / 32510MiB | 0% Default |\n",
- "| | | N/A |\n",
- "+-------------------------------+----------------------+----------------------+\n",
- " \n",
- "+-----------------------------------------------------------------------------+\n",
- "| Processes: |\n",
- "| GPU GI CI PID Type Process name GPU Memory |\n",
- "| ID ID Usage |\n",
- "|=============================================================================|\n",
- "+-----------------------------------------------------------------------------+\n"
- ]
- }
- ],
- "source": [
- "!nvidia-smi"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 10,
- "metadata": {},
- "outputs": [],
- "source": [
- "import torch, gc\n",
- "gc.collect()\n",
- "torch.cuda.empty_cache()"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### 평가"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# run.finish()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "---val evaulate start---\n"
- ]
- },
- {
- "ename": "NameError",
- "evalue": "name 'model' is not defined",
- "output_type": "error",
- "traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
- "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
- "\u001b[1;32m/opt/ml/level3_nlp_finalproject-nlp-04/sentence-sentimental/train.ipynb Cell 24\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m'\u001b[39m\u001b[39m---val evaulate start---\u001b[39m\u001b[39m'\u001b[39m)\n\u001b[1;32m 2\u001b[0m \u001b[39m# wandb.init()\u001b[39;00m\n\u001b[1;32m 3\u001b[0m \u001b[39m# trainer.evaluate(eval_dataset=val_set, metric_key_prefix='val1')\u001b[39;00m\n\u001b[0;32m----> 4\u001b[0m model\u001b[39m.\u001b[39mevaluate(eval_dataset\u001b[39m=\u001b[39mval_set, metric_key_prefix\u001b[39m=\u001b[39m\u001b[39m'\u001b[39m\u001b[39mval1\u001b[39m\u001b[39m'\u001b[39m)\n\u001b[1;32m 5\u001b[0m \u001b[39m# wandb.finish()\u001b[39;00m\n",
- "\u001b[0;31mNameError\u001b[0m: name 'model' is not defined"
- ]
- }
- ],
- "source": [
- "print('---val evaulate start---')\n",
- "# wandb.init()\n",
- "# trainer.evaluate(eval_dataset=val_set, metric_key_prefix='val1')\n",
- "# wandb.finish()"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### 평가"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 10,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- " \n",
- " \n",
- " \n",
- " [303/303 00:10]\n",
- " \n",
- " "
- ],
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/opt/ml/level3_nlp_finalproject-nlp-04/sentence-sentimental/metrics/metrics.py:7: FutureWarning: load_metric is deprecated and will be removed in the next major version of datasets. Use 'evaluate.load' instead, from the new library 🤗 Evaluate: https://huggingface.co/docs/evaluate\n",
- " acc = load_metric('accuracy').compute(predictions=preds, references=labels)['accuracy']\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "{'val1_loss': 0.5156943798065186,\n",
- " 'val1_accuracy': 0.8489475856376393,\n",
- " 'val1_f1': 0.8489475856376393,\n",
- " 'val1_runtime': 12.6519,\n",
- " 'val1_samples_per_second': 191.513,\n",
- " 'val1_steps_per_second': 23.949,\n",
- " 'epoch': 2.0}"
- ]
- },
- "execution_count": 10,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "trainer.evaluate(eval_dataset=val_set, metric_key_prefix='val1')"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### inference"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 43,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "---inference start---\n",
- "######################################\n",
- "tensor([[ 3.0807, -3.1782]])\n",
- "tensor([0.9981, 0.0019])\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "tensor([0.9981, 0.0019])"
- ]
- },
- "execution_count": 43,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "print('---inference start---')\n",
- "text = '이 기사는 [COMPANY]'+ '삼성전자' +'[/COMPANY]에 대한 기사. [SEP] \"\n",
- "corpus = \"삼성전자가 테슬라와 4000억 규모의 협약이 미뤄졌다.\"\n",
- "# MODEL_PATH = \"/opt/ml/input/model-roberta_large-sota_trainer\"\n",
- "MODEL_PATH = \"/opt/ml/input/model-roberta_large-sota_trainer_company-name\"\n",
- "tokenizer = AutoTokenizer.from_pretrained(\"klue/roberta-large\")\n",
- "model = AutoModelForSequenceClassification.from_pretrained(MODEL_PATH)\n",
- "\n",
- "\n",
- "# # model = torch.load(PATH)\n",
- "model.eval()\n",
- "with torch.no_grad() :\n",
- " temp = tokenizer(\n",
- " my_text,\n",
- " return_tensors='pt',\n",
- " padding=True,\n",
- " truncation=True,\n",
- " ##\n",
- " max_length=100,\n",
- " # stride=stride,\n",
- " return_overflowing_tokens=True,\n",
- " return_offsets_mapping=False\n",
- " )\n",
- "\n",
- " \n",
- " temp = {\n",
- " 'input_ids':temp['input_ids'],\n",
- " 'token_type_ids':temp['token_type_ids'],\n",
- " 'attention_mask':temp['attention_mask'],\n",
- " }\n",
- " # print(temp)\n",
- " \n",
- " print(\"######################################\")\n",
- " predicted_label = model(temp['input_ids'])\n",
- " print(predicted_label.logits)\n",
- " print(torch.nn.Softmax(dim=-1)(predicted_label.logits).mean(dim=0))\n",
- " [20 80] => [50]\n",
- " [[20, 80], [30, 70]]\n",
- " \n",
- "\n",
- "torch.nn.Softmax(dim=-1)(predicted_label.logits).mean(dim=0)\n",
- " "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "## 위에 결과에서 앞의 것이 부정 뒤에것이 긍정"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 37,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "부정\n"
- ]
- }
- ],
- "source": [
- "result = torch.nn.Softmax(dim=-1)(predicted_label.logits).mean(dim=0)\n",
- "\n",
- "if result[0] > result[1] :\n",
- " print(\"부정\")\n",
- "else :\n",
- " print(\"긍정\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "final",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.8.5"
- },
- "orig_nbformat": 4,
- "vscode": {
- "interpreter": {
- "hash": "981f108a204f421f158e0977940335d851edffa6dd3586828a3e1aec045160e4"
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/README.md b/README.md
index b4be4b4..b283871 100644
--- a/README.md
+++ b/README.md
@@ -55,26 +55,45 @@
- 기사 전체에서 단어의 가중치를 계산하고, 해당 가중치를 이용한 주요 단어 후보를 선정합니다.
- 한국어 키워드 추출의 성능을 측정하기 위한 데이터셋이 존재하지 않기 때문에 50개의 자체 평가 데이터셋을 구성하였습니다.
-## 기사 긍부정 분류
+## 기사 감성 분석
-- 추출된 키워드가 기업의 좋은 상황을 나타내는 단어인지, 나쁜 상황을 나타내는 단어인지 정보를 제공하기 위해 긍부정 분류 모델을 사용합니다. 기사 전체를 감성 분석한 뒤에 키워드의 대용 지표로 채택합니다.
-- 총 학습 데이터는 기사 긍부정 분류에는 30개의 기업의 총 9000개의 기사를 chat gpt API를 활용해 긍부정 labeling을 진행했고, train set, dev set을 8:2로 나누어서 학습을 진행했습니다.
+- 추출된 키워드가 기업의 좋은 상황을 나타내는 단어인지, 나쁜 상황을 나타내는 단어인지 정보를 제공하기 위해 감성분석 모델을 사용합니다. 기사 전체를 감성 분석한 뒤에 키워드의 대용 지표로 채택합니다.
+- 학습 데이터로 30개의 기업 기업의 기사 4개 기업(3800개)+26개 기업(520개)의 기사를 chat gpt API를 활용해 긍부정 labeling을 진행했고, train set, dev set을 8:2로 나누어서 학습을 진행했습니다.
## 기사 요약
-- IT / 경제분야 뉴스를 이용하여 학습한 모델(T5, polyglot-ko)을 이용하여 뉴스를 요약 제공합니다.
-- T5 모델을 이용하여 한줄 요약을 만들어내고, 이후 자세한 내용은 polyglot-ko 모델을 이용하여 상대적으로 긴 요약 내용을 추가해 줍니다.
+- IT / 경제분야 뉴스를 이용하여 학습한 모델(`T5`, `polyglot-ko`)을 이용하여 뉴스를 요약 제공합니다.
+- `T5` 모델을 이용하여 한줄 요약을 만들어내고, 이후 자세한 내용은 `polyglot-ko` 모델을 이용하여 상대적으로 긴 요약 내용을 추가해 줍니다.
- 모델을 이용하여 생성된 결과는 유의미한 문장만을 가져와 후처리하여 반환합니다.
----
+# 🏗️ 프로젝트 구조
+![](/assets/img/proj_structure.png)
-# 👨🔬 모델 연구
+# 👨🔬 사용 모델
## 키워드 추출
+- `TF-IDF` Vectorizer로 문서를 벡터로 표현하여, 상위 가중치를 갖는 단어를 주요 단어 후보로 선정했습니다.
- 주요 단어 후보와 기사를 한국어로 기학습된 `Sentence-Transformer`를 이용해서 Embedding을 계산한 후, 유사도를 계산하여 높은 점수를 낸 단어를 해당 기사의 주요 키워드로 선정하였습니다.
- 선정된 키워드들을 키워드의 형태(명사형 어구)로 표시하고자, 추출된 키워드에 대해 후처리를 진행하였습니다.
## 뉴스 긍부정 분류
-## 기사 요약
\ No newline at end of file
+- 최대 1900 토큰의 길이에 달하는 기사들을 첫 128 토큰과 마지막 384토큰만을 사용하여, 총 512토큰으로 요약하여 모델에 입력하였습니다.
+- 모델은 `klue/RoBERTa-large`를 사용하였습니다.
+
+## 기사 요약
+
+- 논문 요약 데이터셋과 IT / 경제분야 뉴스 요약 데이터셋을 실험한 결과 선정된 IT / 경제분야 뉴스 요약 데이터셋을 선정하여 요약 모델을 학습시켰습니다.
+- 선정된 데이터셋을 이용하여 본 서비스에서 사용될 `T5-base`, `polyglot-ko 1.3b` 모델을 학습시켰습니다.
+
+# 데모 영상
+
+![](/assets/img/demo.gif)
+
+# 🔗 링크
+
+- [랩업 리포트](/assets/docs/NLP_04_Wrap-Up_Report_FinalProj.pdf)
+- [프로젝트 소개 노션 페이지](https://www.notion.so/0375bff1dc834ead9f6a8f9ae8baa90e?pvs=21)
+- [최종 발표 영상](https://youtu.be/KgvPInfO6k8)
+- [서비스 바로가기](https://deagul.netlify.app/)
diff --git a/Sentimental/dataset/datasets.py b/Sentimental/dataset/datasets.py
index 00c401e..ae7cd5f 100644
--- a/Sentimental/dataset/datasets.py
+++ b/Sentimental/dataset/datasets.py
@@ -4,12 +4,15 @@
class SentimentalDataset(Dataset):
def __init__(self, encodings, labels):
self.encoding = encodings
- self.labels = labels[self.encoding['overflow_to_sample_mapping'].tolist()].reset_index(drop=True)
- # 여러 개로 나뉜 문장에 대해 1개의 라벨을 매핑해줍니다.
+ try:
+ self.labels = labels[self.encoding['overflow_to_sample_mapping'].tolist()].reset_index(drop=True)
+ # return_overflowing_tokens true의 경우 여러 개로 나뉜 문장에 대해 1개의 라벨을 매핑해줍니다.
+ except:
+ self.labels = labels
def __getitem__(self, idx):
data = {key: val[idx] for key, val in self.encoding.items()}
- data['labels'] = torch.tensor(self.labels[idx]).long()
+ data['label'] = torch.tensor(self.labels[idx]).long()
return data
def __len__(self):
diff --git a/Sentimental/evaluate.py b/Sentimental/evaluate.py
new file mode 100644
index 0000000..05af9aa
--- /dev/null
+++ b/Sentimental/evaluate.py
@@ -0,0 +1,102 @@
+import torch
+import pandas as pd
+import sklearn
+import random
+import numpy as np
+import wandb
+
+from transformers import AutoModel
+from transformers import AutoModelForSequenceClassification, AutoTokenizer
+from transformers import TrainingArguments, Trainer
+from transformers import pipeline
+from transformers import DebertaV2ForSequenceClassification
+
+from dataset.datasets import SentimentalDataset
+from metrics.metrics import compute_metrics
+
+from sklearn.datasets import load_iris # 샘플 데이터 로딩
+from sklearn.model_selection import train_test_split
+
+from utils.utils import config_seed, gpt_preprocessing_labels, extract_sentences_token, gpt_preprocessing_labels_token
+
+import json
+import re
+
+
+# 설정
+SEED = 42
+config_seed(SEED)
+device = torch.device("cuda" if torch.cuda.is_available() else 'cpu')
+
+
+# 모델
+name = 'name'
+MODEL_NAME = "/opt/ml/level3_nlp_finalproject-nlp-04/Sentimental/outputs/klue/roberta-large_merged-4_100-26_50"
+model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=2).to(device)
+tokenizer = AutoTokenizer.from_pretrained("klue/roberta-large")
+
+special_tokens_dict = {'additional_special_tokens': ['[COMPANY]','[/COMPANY]']}
+num_added_toks = tokenizer.add_special_tokens(special_tokens_dict)
+model.resize_token_embeddings(len(tokenizer))
+
+
+# 데이터
+data = pd.read_csv("/opt/ml/finance_sentiment_corpus/merged/merged_all.csv")
+data = gpt_preprocessing_labels_token(data) # gpt에서 출력한 오류들을 json 형식으로 맞춰주고 labels를 수정하는 것
+
+dataset = train_test_split(data['content_corpus_company'], data['labels'],
+ test_size=0.2, shuffle=True, stratify=data['labels'],
+ random_state=SEED)
+
+
+sentence_train, sentence_val, label_train, label_val = dataset
+
+
+max_length=3000
+stride=0
+
+val_encoding = tokenizer(sentence_val.tolist(),
+ return_tensors='pt',
+ padding=True,
+ truncation=True,
+ ##
+ max_length=max_length,
+ # stride=stride,
+ # return_overflowing_tokens=True,
+ # return_offsets_mapping=False
+ )
+
+val_encoding = extract_sentences_token(val_encoding, tokenizer.pad_token_id)
+# 앞 128, 뒷 384 토큰으로 센텐스를 추출합니다.
+
+val_set = SentimentalDataset(val_encoding, label_val.reset_index(drop=True))
+
+
+# evaluate
+logging_steps = 200
+num_train_epochs = 1
+per_device_train_batch_size = 8
+per_device_eval_batch_size = 8
+learning_rate = 5e-6
+
+training_args = TrainingArguments(
+ output_dir = './outputs',
+ logging_steps = logging_steps,
+ num_train_epochs = num_train_epochs,
+ per_device_train_batch_size = per_device_train_batch_size,
+ per_device_eval_batch_size = per_device_eval_batch_size,
+ learning_rate = learning_rate,
+ evaluation_strategy="epoch",
+ fp16=True,
+ report_to="wandb",
+)
+
+trainer = Trainer(
+ model=model,
+ args=training_args,
+ # train_dataset=train_set,
+ eval_dataset=val_set,
+ compute_metrics=compute_metrics
+)
+
+trainer.evaluate(eval_dataset=val_set)
\ No newline at end of file
diff --git a/Sentimental/inference_from_saving.py b/Sentimental/inference_from_saving.py
new file mode 100644
index 0000000..cd45a41
--- /dev/null
+++ b/Sentimental/inference_from_saving.py
@@ -0,0 +1,43 @@
+import torch
+from transformers import AutoTokenizer, AutoModelForSequenceClassification
+from utils.utils import extract_sentences_token
+
+
+def inference() :
+ MODEL_PATH = "" #TODO: write_your_model_path
+ tokenizer = AutoTokenizer.from_pretrained("klue/roberta-large")
+ model = AutoModelForSequenceClassification.from_pretrained(MODEL_PATH)
+ device = 'cuda' if torch.cuda.is_available() else 'cpu'
+ model = model.to(device)
+
+ arr = ["이 기사는[COMPANY] 예시 기업 1 [/COMPANY]에 대한 기사야. [SEP] news corpus 1",
+ "이 기사는[COMPANY] 예시 기업 2 [/COMPANY]에 대한 기사야. [SEP] news corpus 2"]
+ answer = []
+
+ model.eval()
+ with torch.no_grad() :
+ loader = torch.utils.data.DataLoader(dataset=arr, batch_size=32, shuffle=False)
+ for batch_text in loader:
+ temp = tokenizer(
+ batch_text,
+ return_tensors='pt',
+ padding='max_length',
+ truncation=True,
+ max_length=3000
+ )
+ temp = extract_sentences_token(temp, tokenizer.pad_token_id)
+ if torch.cuda.is_available():
+ temp = {key: value.to(device) for key, value in temp.items()}
+ predicted_label = model(**temp)
+ print(predicted_label)
+
+ results = torch.nn.Softmax(dim=-1)(predicted_label.logits)
+ for result in results :
+ if result[0]>=result[1] :
+ answer.append("부정")
+ else :
+ answer.append("긍정")
+ print(answer)
+
+if __name__== "__main__" :
+ inference()
\ No newline at end of file
diff --git a/Sentimental/main.py b/Sentimental/main.py
index af3eba2..72811d5 100644
--- a/Sentimental/main.py
+++ b/Sentimental/main.py
@@ -1,4 +1,6 @@
from train import sweep_train
+from inference_from_saving import inference
+
import wandb
import argparse
@@ -23,5 +25,4 @@
wandb.agent(sweep_id, function=sweep_train)
elif args.inference :
- # TODO: inference 코드 구현
- pass
\ No newline at end of file
+ inference()
\ No newline at end of file
diff --git a/Sentimental/train.py b/Sentimental/train.py
index fcd7175..da5930f 100644
--- a/Sentimental/train.py
+++ b/Sentimental/train.py
@@ -17,14 +17,13 @@
from sklearn.datasets import load_iris # 샘플 데이터 로딩
from sklearn.model_selection import train_test_split
-from utils.utils import config_seed, gpt_preprocessing_labels
+from utils.utils import config_seed, extract_sentences_token, gpt_preprocessing_labels_token
-import json
-import re
'''
- git clone https://github.com/ukairia777/finance_sentiment_corpus.git 먼저 실행
- readme.md 작성 예정
'''
+
def sweep_train(config=None) :
# wandb
run = wandb.init(config=wandb.config)
@@ -48,16 +47,23 @@ def sweep_train(config=None) :
# 데이터
data = pd.read_csv("/opt/ml/finance_sentiment_corpus/merged/merged_all.csv")
- data = gpt_preprocessing_labels(data) # gpt에서 출력한 오류들을 json 형식으로 맞춰주고 labels를 수정하는 것
+ data_val = pd.read_csv("/opt/ml/finance_sentiment_corpus/26_company_half_rest_labeled.csv")
+
+ data = gpt_preprocessing_labels_token(data) # gpt에서 출력한 오류들을 json 형식으로 맞춰주고 labels를 수정하는 것
+ data_val = gpt_preprocessing_labels_token(data_val)
- # "labels" 값을 추출하여 새로운 Series 생성
- dataset = train_test_split(data['content_corpus_company'], data['labels'],
- test_size=0.2, shuffle=True, stratify=data['labels'], # label에 비율을 맞춰서 분리
+ # "labels" 값을 추출하여 train/val dataset 나누기
+ dataset = train_test_split(data['content_corpus_company'], data['label'],
+ test_size=0.2, shuffle=True, stratify=data['label'], # label에 비율을 맞춰서 분리
random_state=SEED)
sentence_train, sentence_val, label_train, label_val = dataset
+ sentence_train = data["content_corpus"]
+ label_train = data["label"]
+ sentence_val = data_val["content_corpus"]
+ label_val = data_val["label"]
max_length=wandb.config.max_length #원본:500
stride=0
@@ -67,23 +73,24 @@ def sweep_train(config=None) :
return_tensors='pt',
padding=True,
truncation=True,
- ##
max_length=max_length,
- stride=stride,
- return_overflowing_tokens=True,
- return_offsets_mapping=False
+ # stride=stride,
+ # return_overflowing_tokens=True,
+ # return_offsets_mapping=False
)
val_encoding = tokenizer(sentence_val.tolist(),
return_tensors='pt',
padding=True,
truncation=True,
- ##
max_length=max_length,
- stride=stride,
- return_overflowing_tokens=True,
- return_offsets_mapping=False
+ # stride=stride,
+ # return_overflowing_tokens=True,
+ # return_offsets_mapping=False
)
+
+ train_encoding = extract_sentences_token(train_encoding, tokenizer.pad_token_id)
+ val_encoding = extract_sentences_token(val_encoding, tokenizer.pad_token_id) # 앞 128, 뒷 384 토큰으로 센텐스를 추출합니다.
train_set = SentimentalDataset(train_encoding, label_train.reset_index(drop=True))
val_set = SentimentalDataset(val_encoding, label_val.reset_index(drop=True))
@@ -92,8 +99,8 @@ def sweep_train(config=None) :
# 학습
logging_steps = 200
num_train_epochs = 3
- per_device_train_batch_size = 4
- per_device_eval_batch_size = 4
+ per_device_train_batch_size = 8
+ per_device_eval_batch_size = 8
learning_rate = 5e-6
training_args = TrainingArguments(
@@ -120,13 +127,15 @@ def sweep_train(config=None) :
trainer.train()
# wandb.finish()
+ name = f'{MODEL_NAME}_merged-4_100-26_50' # 원하는 이름으로 바꾸기
+ trainer.save_model(output_dir=f'./outputs/{name}')
if __name__ == '__main__':
wandb_config = {
'method': 'grid',
'parameters':
{
- 'max_length': {'values' : [500, 250]},
+ 'max_length': {'values' : [3000]}, # 충분히 커서 본문 전체를 토크나이징.
}
}
sweep_id = wandb.sweep(sweep=wandb_config,
diff --git a/Sentimental/utils/utils.py b/Sentimental/utils/utils.py
index 6cd25ea..304354f 100644
--- a/Sentimental/utils/utils.py
+++ b/Sentimental/utils/utils.py
@@ -16,12 +16,12 @@ def remove_idx_row(data) :
patterns = [r'idx\s*:?\s*.+?', r'라벨링\s*:?\s*.+?']
for pattern in patterns :
- mask = data['labels'].str.match(pattern)
+ mask = data["label"].str.match(pattern)
data = data.drop(data[mask].index)
return data
-# ['labels'] 전처리
+# ['label'] 전처리
def preprocessing_label(json_str) :
json_str = re.sub(r"^.*### 출력\s?:?\s?\n?\s?", "", str(json_str))
# json_str= json_str.replace("\"", "'")
@@ -39,6 +39,35 @@ def extract_sentences(text):
else :
return '. '+text
+# 512 토큰 초과인 input_ids, token_type_ids, attention_maks를
+# 앞 128, 뒤 384으로 분리합니다.
+def extract_sentences_token(input_dict, pad_token_id):
+ '''
+ 사용 방법:
+ train_encoding = extract_sentences_token(train_encoding, tokenizer.pad_token_id)
+ '''
+ new = {}
+ batch_size = len(input_dict['input_ids'])
+ new['input_ids'] = torch.ones(batch_size, 512, dtype=int)
+ new['token_type_ids'] = torch.ones(batch_size, 512, dtype=int)
+ new['attention_mask'] = torch.ones(batch_size, 512, dtype=int)
+ # batch_size, 512
+ for i in range(batch_size):
+ a = input_dict['input_ids'][i]
+ a = a[a != pad_token_id]
+ length = len(a)
+ if length > 512:
+ left, right = 1, 3
+ a = torch.cat((a[:128*left], a[-128*right:]), dim=0)
+ new['input_ids'][i] = a
+ new['token_type_ids'][i] = input_dict['token_type_ids'][i][:512]
+ new['attention_mask'][i] = input_dict['attention_mask'][i][:512]
+ else:
+ new['input_ids'][i] = input_dict['input_ids'][i][:512]
+ new['token_type_ids'][i] = input_dict['token_type_ids'][i][:512]
+ new['attention_mask'][i] = input_dict['attention_mask'][i][:512]
+ return new
+
# json 형태로 되어있지 않은 형태를 최대한 json 형태로 바꿔주고 label 추출
def extract_label(json_str) :
json_str = json_str.replace("'", "\"")
@@ -62,12 +91,30 @@ def extract_label(json_str) :
return data_dict["label"]
def gpt_preprocessing_labels(data) :
+ if "label" not in data.columns :
+ data["label"] = data["labels"]
+
+ data = remove_idx_row(data)
+ data["label"] = data["label"].apply(preprocessing_label)
+ data["label"] = data["label"].apply(extract_label)
+ data["content_corpus_company"] = data.apply(lambda row: '이 기사는 [COMPANY]'+ str(row["company"]) +'[/COMPANY]에 대한 기사. [SEP]'+ extract_sentences(row["title"]) + ' ' + extract_sentences(row["content_corpus"]), axis=1) #TODO: 학습에 들어가는 부분이기 때문에 조정 필수
+ data["label"] = data["label"].map({"부정":0, "긍정":1})
+ data = data[["title", "date", "content_corpus", "label", "content_corpus_company"]]
+ data = data[data["label"].notna()] # 최후에 처리 되지 않은 None row 제거
+
+ return data
+
+
+def gpt_preprocessing_labels_token(data) :
+ if "label" not in data.columns :
+ data["label"] = data["labels"]
+
data = remove_idx_row(data)
- data["labels"] = data["labels"].apply(preprocessing_label)
- data['labels'] = data["labels"].apply(extract_label)
- data['content_corpus_company'] = data.apply(lambda row: '이 기사는 [COMPANY]'+ str(row["company"]) +'[/COMPANY]에 대한 기사. [SEP]'+ extract_sentences(row['title']) + ' ' + extract_sentences(row['content_corpus']), axis=1) #TODO: 학습에 들어가는 부분이기 때문에 조정 필수
- data['labels'] = data['labels'].map({'부정':0, '긍정':1})
- data = data[["title", "date", "content_corpus", "labels", "content_corpus_company"]]
- data = data[data['labels'].notna()] # 최후에 처리 되지 않은 None row 제거
+ data["label"] = data["label"].apply(preprocessing_label)
+ data["label"] = data["label"].apply(extract_label)
+ data["content_corpus_company"] = data.apply(lambda row: '이 기사는 [COMPANY]'+ str(row["company"]) +'[/COMPANY]에 대한 기사. [SEP]'+ (row['content_corpus']), axis=1) #TODO: 학습에 들어가는 부분이기 때문에 조정 필수
+ data["label"] = data["label"].map({'부정':0, '긍정':1})
+ data = data[["title", "date", "content_corpus", "label", "content_corpus_company"]]
+ data = data[data["label"].notna()] # 최후에 처리 되지 않은 None row 제거
return data
\ No newline at end of file
diff --git a/airflow/dags/crawl_naver_finance_news.py b/airflow/dags/crawl_naver_finance_news.py
new file mode 100644
index 0000000..182040d
--- /dev/null
+++ b/airflow/dags/crawl_naver_finance_news.py
@@ -0,0 +1,44 @@
+import sys
+import pendulum
+
+from airflow import DAG
+from airflow.operators.python import PythonOperator
+from datetime import datetime, timedelta
+
+sys.path.append('/opt/ml/level3_nlp_finalproject-nlp-04/utils')
+from crawling_utils import crawl_finance_news
+
+tz = pendulum.timezone("Asia/Seoul")
+default_args = {
+ 'owner': 'yungi',
+ 'depends_on_past' : False,
+ 'start_date' : datetime(2023, 7, 18, 15), # UTC 시간 -> KST +9 시간 하면 7월 19일부터
+ 'retires' : 1,
+ 'retry_delay' : timedelta(minutes=5),
+ }
+def print_date(timestr):
+ print("***** Execution Time *****:",timestr)
+
+with DAG(
+ dag_id="crawl_naver_finance_news_v0.2.1",
+ user_defined_macros={'local_dt' : lambda execution_date: execution_date.in_timezone(tz).strftime("%Y-%m-%d")}, # KST시간으로 Timezone 변경(+9시간) 매크로
+ default_args=default_args,
+ schedule_interval="0 15,21,3,9 * * *", # 크롤링 어느정도 끝나고 현재로 오면, 6시간마다 크롤링하도록(UTC시간 기준시작, 15부터 해야지 KST=다음날 0시)
+ # schedule_interval="0 0 * * *", # 과거일자 크롤링할 땐 그냥 하루단위로 작업하도록
+ tags = ['crawl']
+) as dag:
+ execution_date = "{{ local_dt(execution_date) }}" # 실제 실행 시간은 KST기준(+9시간)으로 수행하도록
+ # execution_date = "{{ ds }}"
+ python_task_jinja = PythonOperator(
+ task_id="crawl_naver_finance_news",
+ python_callable=crawl_finance_news,
+ op_kwargs={'cur_time' : execution_date}
+ )
+ print_date_jinja = PythonOperator(
+ task_id="print_date",
+ python_callable=print_date,
+ op_args=[execution_date]
+ )
+
+ print_date_jinja >> python_task_jinja
+
diff --git a/airflow/dags/model_serving.py b/airflow/dags/model_serving.py
new file mode 100644
index 0000000..bcc6a10
--- /dev/null
+++ b/airflow/dags/model_serving.py
@@ -0,0 +1,186 @@
+import sys
+import pendulum
+import json
+import requests
+
+from airflow import DAG
+from airflow.operators.python import PythonOperator
+from datetime import datetime, timedelta
+from pathlib import Path
+from supabase import Client, create_client
+from collections import Counter, defaultdict
+
+
+sys.path.append('/opt/ml/level3_nlp_finalproject-nlp-04')
+from utils.secrets import Secrets
+from utils.preprocessing import *
+
+tz = pendulum.timezone("Asia/Seoul")
+default_args = {
+ 'owner': 'yungi',
+ 'depends_on_past' : False,
+ 'start_date' : datetime(2023, 7, 30, 15), # UTC 시간 -> KST +9 시간 하면 7월 19일부터
+ 'retires' : 1,
+ 'retry_delay' : timedelta(minutes=5),
+ }
+url = Secrets.url
+key = Secrets.key
+
+supabase: Client = create_client(url, key)
+
+
+def get_news(cur_time,content_length_thr):
+ print("**** Current Time **** :",cur_time)
+ cur_time = datetime.strptime(cur_time, "%Y-%m-%d %H:%M:%S")
+ stock_name = supabase.table("ticker").select("name").eq("ticker", "005930").execute().data[0]["name"]
+ res = supabase.table("news").select("*").eq("company", stock_name).filter("date", "gt",(cur_time - timedelta(hours=7)).strftime('%Y-%m-%d %H:%M:%S')
+ ).filter("date", "lt",cur_time.strftime('%Y-%m-%d %H:%M:%S')).execute().data
+ # 짧은 뉴스 제거
+ for cont in res:
+ if len(cont['content']) <= content_length_thr:
+ res.remove(cont)
+ return res
+
+def get_sentimental(text):
+ articles = eval(text)
+ res_sentimental = requests.post("http://115.85.183.242:30007/classify_sentiment",
+ data=json.dumps({
+ "corpus_list" : [all_preprocessing(news['content']) for news in articles],
+ "company_list" : [news['company'] for news in articles],
+ })).content
+ return json.loads(res_sentimental)
+
+def get_keywords(text):
+ articles = eval(text)
+ keywords = json.loads(requests.post("http://118.67.133.198:30008/keywordExtraction/5",
+ data=json.dumps({
+ "data_input" : {
+ "titles" : articles['title'] if type(articles) == dict else [all_preprocessing(news['title']) for news in articles ],
+ "contents" : articles['content'] if type(articles) == dict else [all_preprocessing(news['content']) for news in articles],
+ "dates" : articles['date'] if type(articles) == dict else [news['date'] for news in articles],
+ },
+ "stop_words" : ["삼성", "전자", "삼성전자"]}),
+ params={"vectorizer_type" : "tfidf"}
+ ).content)
+ return keywords
+
+def get_summary(articles, **config):
+ pp = requests.post(f"http://118.67.143.119:30007/api/v1/summerize/{config['model_name']}",
+ data=json.dumps({
+ "prompt": "아래의 뉴스를 사실에 입각하여 맥락의 내용을 요약해줘.",
+ "title" : articles['title'],
+ "contents" : all_preprocessing(articles['content']),
+ "num_split" : config["num_split"],
+ "options": {
+ "do_sample": True,
+ "early_stopping": "never",
+ "eos_token_id": 2,
+ "max_new_tokens": config["max_new_tokens"],
+ "no_repeat_ngram_size": config["no_repeat_ngram_size"],
+ "temperature" : config["temperature"],
+ "top_k": config["top_k"],
+ "top_p": config["top_p"],
+ }
+ }
+ )
+ ).content
+ return json.loads(pp)['arr_summerized']
+
+def filter_push_keyword_sentimental(news, keywords, sentimentals, cur_time, score_thr=0.2):
+ pos_cnt = Counter()
+ neg_cnt = Counter()
+ pos_keyword_id = defaultdict(list)
+ neg_keyword_id = defaultdict(list)
+
+ news = eval(news)
+ keywords = eval(keywords)
+ sentimentals = eval(sentimentals)
+
+ for i, sent in enumerate(sentimentals):
+ if sent == "긍정":
+ for keyword, score in keywords[0][i]:
+ if score > score_thr:
+ pos_cnt[keyword] += 1
+ pos_keyword_id[keyword].append(news[i]['id'])
+ elif sent == "부정":
+ for keyword, score in keywords[0][i]:
+ if score > score_thr:
+ neg_cnt[keyword] += 1
+ neg_keyword_id[keyword].append(news[i]['id'])
+
+ for key in set(list(pos_cnt.keys()) + list(neg_cnt.keys())):
+ pos_c = pos_cnt.get(key, 0)
+ neg_c = neg_cnt.get(key, 0)
+ pos_k = pos_keyword_id.get(key, [])
+ neg_k = neg_keyword_id.get(key, [])
+ supabase.table("keywords").insert({"keyword": key,
+ "count": pos_c + neg_c,
+ "create_time": datetime.strptime(cur_time, "%Y-%m-%d %H:%M:%S").strftime(
+ "%Y-%m-%d %H:%M:%S"),
+ "pos_cnt": pos_c,
+ "neg_cnt": neg_c,
+ "summary_id": {
+ "pos_news": pos_k,
+ "neg_news": neg_k
+ }
+ }).execute()
+ return {"pos_cnt" : pos_cnt, "neg_cnt" : neg_cnt, "pos_keyword_id" : pos_keyword_id, "neg_keyword_id" : neg_keyword_id}
+
+def push_summary(text):
+ config = {
+ "model_name" : "t5",
+ "num_split" : 9999,
+ "max_new_tokens" : 256,
+ "no_repeat_ngram_size" : 8,
+ "temperature" : 0.08,
+ "top_k" : 50,
+ "top_p" : 0.98,
+ }
+ articles = eval(text)
+ for i, news in enumerate(articles):
+ summary = get_summary(news, **config)[0]
+ supabase.table("news_summary").insert({"origin_id": articles[i]['id'], "summarization": summary}).execute()
+
+with DAG(
+ dag_id="model_serving_v0.1.0",
+ user_defined_macros={'local_dt' : lambda execution_date: execution_date.in_timezone(tz).strftime("%Y-%m-%d %H:%M:%S")}, # KST시간으로 Timezone 변경(+9시간) 매크로
+ default_args=default_args,
+ schedule_interval="0 16,22,4,10 * * *", # 크롤링 어느정도 끝나고 현재로 오면, 6시간마다 크롤링하도록(UTC: 15 = KST:다음날 0시)
+ # schedule_interval="0 0 * * *", # 과거일자 크롤링할 땐 그냥 하루단위로 작업하도록
+ catchup=True,
+ tags = ['serving']
+) as dag:
+ execution_date = "{{ local_dt(execution_date) }}" # 실제 실행 시간은 KST기준(+9시간)으로 수행하도록
+ # execution_date = "{{ ds }}"
+
+ get_news = PythonOperator(
+ task_id="get_news",
+ python_callable=get_news,
+ op_kwargs={'cur_time': execution_date,
+ "content_length_thr" : 400}
+ )
+ get_keywords = PythonOperator(
+ task_id="get_keywords",
+ python_callable=get_keywords,
+ op_args=["{{task_instance.xcom_pull(task_ids='get_news')}}"]
+ )
+ get_sentimental = PythonOperator(
+ task_id="get_sentimental",
+ python_callable=get_sentimental,
+ op_args=["{{task_instance.xcom_pull(task_ids='get_news')}}"]
+ )
+ get_push_summary = PythonOperator(
+ task_id="get_push_summary",
+ python_callable=push_summary,
+ op_args=["{{task_instance.xcom_pull(task_ids='get_news')}}"],
+ )
+ filter_push_keyword_sentimental = PythonOperator(
+ task_id="filter_keyword_sentimental",
+ python_callable=filter_push_keyword_sentimental,
+ op_kwargs={"news": "{{task_instance.xcom_pull(task_ids='get_news')}}",
+ "keywords": "{{task_instance.xcom_pull(task_ids='get_keywords')}}",
+ "sentimentals": "{{task_instance.xcom_pull(task_ids='get_sentimental')}}",
+ "cur_time" : execution_date,
+ "score_thr" : 0.2}
+ )
+ get_news >> [get_keywords, get_sentimental, get_push_summary] >> filter_push_keyword_sentimental
\ No newline at end of file
diff --git a/assets/docs/NLP_04_Wrap-Up_Report_FinalProj.pdf b/assets/docs/NLP_04_Wrap-Up_Report_FinalProj.pdf
new file mode 100644
index 0000000..e4819d2
Binary files /dev/null and b/assets/docs/NLP_04_Wrap-Up_Report_FinalProj.pdf differ
diff --git a/assets/img/demo.gif b/assets/img/demo.gif
new file mode 100644
index 0000000..ec56763
Binary files /dev/null and b/assets/img/demo.gif differ
diff --git a/assets/img/proj_structure.png b/assets/img/proj_structure.png
new file mode 100644
index 0000000..89e306f
Binary files /dev/null and b/assets/img/proj_structure.png differ
diff --git a/labeling/gpt_api.py b/labeling/gpt_api.py
index a232026..4ea7bb1 100644
--- a/labeling/gpt_api.py
+++ b/labeling/gpt_api.py
@@ -16,25 +16,27 @@ def get_completion(prompt, model="gpt-3.5-turbo"):
return response.choices[0].message["content"]
-def get_label(news: str) -> str:
+def get_label(company, news: str) -> str:
key = os.environ['OPENAPI_KEY']
openai.api_key = key
prompt = f'''### 역할:
- 너는 어느 증권사 재무팀에서 일하고 있는 애널리스트야. 너의 주 업무는 뉴스 기사를 보고 해당 기사가 "긍정" 혹은 "부정"인지 라벨링 하는 것이야. 이때 아래의 규칙을 따라야해.
-
+ 너는 어느 증권사 재무팀에서 일하고 있는 애널리스트야. 너의 주 업무는 뉴스 기사를 보고 {company} 입장에서 해당 기사가 "긍정" 혹은 "부정"인지 라벨링 하는 것이야. 이때 아래의 규칙을 따라야해.
+
+ ### 기사:
+ {news}
+
### 규칙:
1. 주어진 기사에 대해 하나의 ["긍정", "부정"]만을 선택하여 라벨링한다.
2. 기사는 "### 기사:" 부분부터 시작된다.
3. 출력은 "### 출력"와 같이 json 형식으로 출력한다.
4. 라벨링은 기사 전문에 대해서 라벨링한다.
5. 중립은 선택지에 없다.
+ 6. 출력은 형식외에 아무것도 작성하지 않는다.
### 출력
{{"label": "긍정", "reason": "새로운 투자 유치로 인해 해당 기업의 전망이 밝을것으로 예측된다. "}}
-
- ## 기사:
- {news}'''
+ '''
response = get_completion(prompt)
return response
@@ -53,7 +55,7 @@ def get_label(news: str) -> str:
for idx, row in tqdm(df_news.iterrows(), total=df_news.shape[0]):
try:
- list_result.append(get_label(row["content_corpus"]))
+ list_result.append(get_label(row["company"], row["content_corpus"]))
except Exception as e:
print(f"idx: {idx}, err: {e}")
list_result.append(f"idx: {idx}, err: {e}")
diff --git a/utils/__init__.py b/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/utils/crawling_utils.py b/utils/crawling_utils.py
new file mode 100644
index 0000000..6179ce2
--- /dev/null
+++ b/utils/crawling_utils.py
@@ -0,0 +1,152 @@
+import requests
+import re
+import pandas as pd
+import time
+import sys
+
+from pathlib import Path
+from selectolax.parser import HTMLParser, Node
+from supabase import create_client, Client
+from tqdm import tqdm, trange
+from datetime import datetime
+from typing import List, Union
+
+sys.path.append(str(Path.home().joinpath("level3_nlp_finalproject-nlp-04")))
+from utils.secrets import Secrets
+
+SECRETS = Secrets()
+
+def crawl_finance_news(cur_time: str,
+ stock_nums: int = 30,
+ delay: float = 0.0,
+ threshold_num: int = 10,) -> pd.DataFrame:
+ """
+ 네이버 금융 뉴스를 크롤링하는 함수입니다.
+
+ Params:
+ cur_time (str): 현재 시간
+ stock_nums (int): KRX300 기준 크롤링할 종목의 개수
+ delay (float): 요청 지연 시간
+ threshold_num (int): 일자가 넘어간 기사 용인 갯수
+
+ Returns:
+ pandas.DataFrame: 크롤링한 뉴스 데이터프레임
+ """
+ assert stock_nums <= 300, "stock_nums를 300개 이내로 입력해주세요."
+
+ BASE_DIR = Path.home().joinpath("level3_nlp_finalproject-nlp-04")
+ NEWS_DIR = BASE_DIR.joinpath("Data", "News")
+ STOCK_DIR = BASE_DIR.joinpath("Data", "Stock")
+
+ # 현재 날짜
+ cur_time = datetime.strptime(cur_time, "%Y-%m-%d")
+
+ stock_detail = pd.read_csv(STOCK_DIR.joinpath("stock_krx300.csv"), encoding="cp949")
+ stock_detail = stock_detail.loc[:, ["종목코드", "종목명"]]
+ stock_detail["종목코드"] = stock_detail["종목코드"].apply(lambda x: str(x).zfill(6))
+
+ pbar = tqdm(zip(stock_detail["종목코드"][:stock_nums], stock_detail["종목명"][:stock_nums]), desc="종목별 뉴스 크롤링")
+ for stock_code, company in pbar:
+ pbar.set_description(f"종목: {stock_code}|{company}")
+ threshold_cnt = 0
+ # 뉴스 페이지의 끝을 찾기 위해 크롤링 수행
+ page_max_url = f"https://finance.naver.com/item/news_news.naver?code={stock_code}"
+ page_max_html = requests.get(page_max_url).text
+ page_max_tree = HTMLParser(page_max_html)
+ nodes = page_max_tree.css("body > div > table.Nnavi > tbody > tr > td.pgRR > a")
+
+ try:
+ PAGE_MAX = int(re.findall(r"(?<=page=)\d+", nodes[-1].attributes['href'])[0])
+ except IndexError:
+ print("기사가 존재하지 않습니다.")
+ return
+ content_df = pd.DataFrame([], columns=['company', 'title', 'link', "writer", 'date', 'content']) if NEWS_DIR.joinpath(stock_code + ".csv").exists() is False else pd.read_csv(NEWS_DIR.joinpath(stock_code + ".csv"), encoding="utf-8")
+
+
+ # 전체 페이지 순환
+ for i in trange(1, PAGE_MAX + 1):
+ url = f"https://finance.naver.com/item/news_news.naver?code={stock_code}&page={i}&sm=title_entity_id.basic&clusterId="
+ html = requests.get(url).text
+
+ tree = HTMLParser(html)
+ nodes = tree.css("body > div.tb_cont > table.type5 > tbody > tr")
+
+ # 연관기사로 묶여있는 것들 제거
+ for node in nodes:
+ if len(node.attributes) != 0 and "relation_lst" in node.attributes["class"]:
+ node.decompose()
+
+ # 해당 페이지의 기사들을 content_df에 추가
+ for node in nodes:
+ content = []
+ tds = node.css("td")
+
+ # company -> title -> link -> writer -> date 순으로 추출
+ content.append(company)
+ # 연관기사로 묶여있는 리스트가 비어있기 때문에, 아닌 것들만 기사 추출
+ if len(tds) != 0:
+ for td in tds:
+ content.append(td.text(strip=True))
+ if len(td.css("a")) != 0:
+ content.append(td.css("a")[0].attributes["href"])
+
+ title = content[1]
+ link = "https://finance.naver.com" + re.findall(r"\S+(?=\&page=)", content[2])[0] # link의 page앞으로만이 실제 유효한 기사 링크
+ content[2] = link
+ writer = content[3]
+ date = content[4]
+
+ written_date = datetime.strptime(date, '%Y.%m.%d %H:%M')
+ if (written_date.date() == cur_time.date()) and not (content_df['link'] == link).any(): # 날짜가 같은 날에 작성된 기사만 크롤링
+ # 본문 추출을 위해 위에서 link를 이용해 html을 다시 요청
+ content_html = requests.get(link).text
+ content_tree = HTMLParser(content_html)
+
+ # 본문이 없는 기사(삭제된 기사)가 종종 존재 -> 예외처리
+ try:
+ res = content_tree.css(
+ "body > div#wrap > div#middle.new_totalinfo > div.content_wrap > div#content > div.section.inner_sub > table.view > tbody > tr > td > div")[0].html
+ except IndexError:
+ continue
+
+ # 본문 추출
+ # 기사본문에 해당하는 부분만 뽑기 위해 이전까지만 추출
+ try:
+ body = HTMLParser(res[:re.search(r'( )', res).start()].replace(" ", " ")).text(strip=True) # -> " "로 변경
+ except AttributeError:
+ body = HTMLParser(res.replace(" ", " ")).text(strip=True)
+
+ # 기사본문 내용 추가
+ content.append(body)
+ content = pd.DataFrame([content], columns=['company', 'title', 'link', "writer", 'date', 'content'])
+
+ # 전체 기사에 추가
+ content_df = pd.concat([content_df, content], ignore_index=True)
+ insert_into_DB(content)
+
+ # Ban되는걸 조심하도록 delay 추가
+ time.sleep(delay)
+ elif written_date.date() < cur_time.date(): # 작성된 날짜가, 현재 시간보다 이전이라면, threshold_cnt 증가
+ if threshold_cnt >= threshold_num: # threshold가 넘어가면 break => 기사 탐색 종료
+ break
+ threshold_cnt += 1
+
+ if threshold_cnt >= threshold_num: # threshold가 넘어가면 break => 종목 탐생 종료
+ break
+
+
+ if len(content_df) != 0: # content_df가 비어있지 않을 때(날짜가 같지 않은 애들 같은 경우엔 비어있을 수 있음)
+ # link 기준으로 중복 제거
+ content_df = content_df.drop_duplicates(['link'], keep='first').reset_index(drop=True)
+
+ # csv로 저장
+ content_df.to_csv(NEWS_DIR.joinpath(f"{stock_code}.csv"), encoding="utf-8", index=False)
+
+ print(f"===== Successfully Crawled {stock_code}|{company} News, Numbers = {len(content_df)} =====")
+
+def insert_into_DB(content_df: pd.DataFrame):
+ url = SECRETS.url
+ key = SECRETS.key
+ supabase = create_client(url, key)
+
+ _, _ = supabase.table("news").insert(content_df.iloc[0, :].to_dict()).execute()
diff --git a/utils/preprocessing.py b/utils/preprocessing.py
new file mode 100644
index 0000000..fda63af
--- /dev/null
+++ b/utils/preprocessing.py
@@ -0,0 +1,235 @@
+import re
+import pandas as pd
+
+
+def remove_text(texts):
+ pattern = r"\*? ?재판매 및 DB 금지"
+
+ preprocessed_text = ''
+
+ text = re.sub(pattern, " ", texts).strip()
+ if text:
+ preprocessed_text = text
+
+ return preprocessed_text
+
+
+def remove_press(texts):
+ """
+ 언론 정보를 제거
+ -> ~~ 기자 (연합뉴스)
+ -> (서울=연합뉴스) ~~ 특파원
+ """
+ re_patterns = [
+ r"\([^(]*?(뉴스|경제|일보|미디어|데일리|한겨례|타임즈|위키트리)\)",
+ r"[가-힣]{0,4}\s+(기자|선임기자|수습기자|특파원|객원기자|논설고문|통신원|연구소장)\s?=?", # 이름 + 기자
+ # r"[가-힣]{1,}(뉴스|경제|일보|미디어|데일리|한겨례|타임|위키트리)", # (... 연합뉴스) ..
+ r"(\/?뉴스|news|News)\d", # (... 연합뉴스) ..
+ r"[\(\[]\s+[\)\]]", # ( )
+ r"[\(\[]=\s+[\)\]]", # (= )
+ r"[\)\]]\s+=[\)\]]", # ( =)
+ ]
+
+ preprocessed_text = ''
+ for re_pattern in re_patterns:
+ texts = re.sub(re_pattern, " ", str(texts))
+ if texts:
+ preprocessed_text = texts
+
+ return preprocessed_text
+
+
+def remove_photo_info(texts):
+ ## 수정 필요
+ """
+ 뉴스의 이미지 설명 대한 label 제거
+ """
+ preprocessed_text = []
+
+ preprocessed_text = re.sub(r"\(출처 ?= ?.+\) |\(사진 ?= ?.+\) |\(자료 ?= ?.+\)| \(자료사진\) |사진=.+기자 ", " ", texts).strip()
+ preprocessed_text = re.sub(r"\/?사진제공(=|\:)\w+", " ", preprocessed_text)
+ preprocessed_text = re.sub(r"\/? ?(사진|그래픽) ?= ?\w+", " ", preprocessed_text)
+ preprocessed_text = re.sub(r"\/\w+\s?제공", " ", preprocessed_text)
+ # preprocessed_text = re.sub(r"\/사진제공=\S?+?\s+ | \/\s사진?=", " ", preprocessed_text)
+
+ return preprocessed_text
+
+
+def change_quotation(texts):
+ pattern = r"\""
+ replacement = "\'"
+
+ processed_text = re.sub(pattern, replacement, texts)
+
+ double_quotation = r"[\u201C\u201D\u2018\u2019]"
+
+ processed_text = re.sub(double_quotation, replacement, processed_text)
+
+
+ return processed_text
+
+
+def remove_email(texts):
+ """
+ 이메일을 제거
+ """
+ preprocessed_text = ''
+
+ text = re.sub(r"[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+", " ", texts).strip()
+ if text:
+ preprocessed_text = text
+
+ return preprocessed_text
+
+
+def remove_day(texts):
+ """
+ 날짜와 관련된 숫자 제거
+ """
+ pattern = r'\d{4}\.\d{1,2}\.\d{1,2}\.?'
+
+ text = re.sub(pattern, " ", texts)
+ return text
+
+
+def remove_triangle(texts):
+ pattern = r'▶\s?.+=?'
+
+ text = re.sub(pattern, " ", texts)
+ return text
+
+
+def remove_parentheses(texts):
+ """
+ 괄호와 그 안에 있는 내용들을 제거
+ """
+ pattern = r'[\(\[][^(^[]*[\)\]]'
+
+ processed_text = re.sub(pattern, ' ', texts)
+
+ return processed_text
+
+
+def remove_copyright(texts):
+ """
+ 뉴스 내 포함된 저작권 관련 텍스트를 제거합니다.
+ """
+ re_patterns = [
+ r"\<저작권자(\(c\)|ⓒ|©|\(Copyright\)|(\(c\))|(\(C\))).+?\>",
+ r"저작권자\(c\)|ⓒ|©|(Copyright)|(\(c\))|(\(C\))"
+ ]
+ preprocessed_text = ''
+
+ for re_pattern in re_patterns:
+ text = re.sub(re_pattern, " ", texts)
+ if text:
+ preprocessed_text = text
+
+ return preprocessed_text
+
+
+def split_sentence(texts):
+ sentence_list = texts.split(". ")
+
+ return sentence_list
+
+
+# 혹시 필요한 것들 모음
+def remove_hashtag(texts):
+ """
+ 해쉬태그(#)를 제거합니다.
+ """
+ preprocessed_text = ''
+
+ text = re.sub(r"#\S+", " ", texts).strip()
+ if text:
+ preprocessed_text = text
+
+ return preprocessed_text
+
+
+def remove_url(texts):
+ """
+ URL을 제거합니다.
+ 주소: www.naver.com`` -> 주소:
+ """
+ preprocessed_text = ''
+
+ text = re.sub(r"(http|https)?:\/\/\S+\b|www\.(\w+\.)+\S*", " ", texts).strip()
+ text = re.sub(r"pic\.(\w+\.)+\S*", " ", text).strip()
+
+ preprocessed_text = text
+
+ return preprocessed_text
+
+
+def remove_special_str(texts):
+ preprocessed_text = ''
+
+ pattern = r"[◆◇▲▼■●▶◀△▽→←⇒⇔➜➔❯❮]"
+ preprocessed_text = re.sub(pattern, " ", texts)
+ preprocessed_text = re.sub("\↑", "증가 ", texts)
+
+ return preprocessed_text
+
+def remove_space_dup(texts):
+ preprocessed_text = re.sub(r"\s+", " ", texts)
+
+ return preprocessed_text
+
+
+def all_preprocessing(texts):
+ texts = str(texts)
+ texts = remove_text(texts)
+ texts = remove_press(texts)
+ texts = remove_photo_info(texts)
+ texts = remove_email(texts)
+ texts = remove_copyright(texts)
+ texts = remove_day(texts)
+ texts = remove_triangle(texts)
+ texts = remove_parentheses(texts)
+ texts = remove_special_str(texts)
+
+ texts = change_quotation(texts)
+ texts = remove_space_dup(texts)
+
+ return texts
+
+
+def preprocess_dataframe(df):
+ df['content_corpus'] = df['content'].apply(all_preprocessing)
+
+ return df
+
+
+def preprocess_dataframe_to_sentence(df):
+ df_sentence = pd.DataFrame(columns=['title', 'date', 'content_sentence'])
+
+ for index, row in df.iterrows():
+ title = row['title']
+ date = row['date']
+ content = row['content']
+
+ content = split_sentence(content)
+
+ for sentence in content:
+ l = len(df_sentence)
+ sentence = all_preprocessing(sentence)
+ new_row = {'title': title, 'date': date, 'content_sentence': sentence}
+
+ new_row = pd.DataFrame(new_row, index=[l])
+ df_sentence = pd.concat([df_sentence, new_row], axis=0)
+
+ return df_sentence
+
+
+if __name__ == "__main__":
+ # text = "‘스마트싱스’ 중심의 지속 가능한 일상 전시 에너지 줄이는 비스포크 가전 라인업 선봬[이데일리 김응열 기자] 삼성전자가 29일부터 내달 1일까지 광주광역시 김대중컨벤션센터에서 열리는 ‘2023 국제IoT가전로봇박람회’에 참가해 각종 에너지 절감 기술을 적용한 가전제품 라인업과 솔루션을 대거 소개한다.삼성전자 모델이 29일부터 내달 1일까지 광주광역시 김대중컨벤션센터에서 진행되는 ‘2023국제IoT가전로봇박람회’에 마련된 삼성전자 부스에서 ‘스마트싱스 에너지 세이빙’ 솔루션을 소개하고 있다. 접목 디지털 제어 기술 △스마트싱스 기반 에너지 관리 솔루션 ’스마트싱스 에너지‘의 ’AI 절약모드‘ 등으로 추가적인 에너지 절감이 가능하다. 가령 비스포크 무풍에어컨 갤러리의 에너지 특화 모델은 1등급 최저 기준보다도 냉방 효율이 10% 더 뛰어나다. AI 절약 모드 기능을 활용하면 전력 사용량을 최대 20% 추가 절약할 수 있다.삼성전자 모델이 29일부터 내달 1일까지 광주광역시 김대중컨벤션센터에서 진행되는 ‘2023국제IoT가전로봇박람회’에 마련된 삼성전자 부스에서 스마트싱스 기반의 ‘넷 제로 홈’을 소개하고 있다. (사진=삼성전자)삼성전자는 이번 전시에서 스마트싱스 기반의 ’넷 제로 홈(Net Zero Home)‘으로 에너지 리더십도 강조한다. 넷 제로 홈에서는 태양광 패널로 생산한 에너지를 활용할뿐 아니라 스마트싱스를 이용해 가전제품이나 집안 전체 에너지 사용량을 모니터링하고 줄일 수 있다. 삼성전자는 에너지 절약을 위해 한국전력공사, 서울특별시, 나주시와 협력하는 ’주민 DR(Demand Response)‘ 서비스 사업도 함께 소개했다. 전력거래소나 지방자치단체가 DR 발령 시 자동으로 연동된 삼성전자 제품을 AI 절약 모드로 전환하거나 전원을 끄는 등 전력량을 최소화한다. 이 기능은 에어컨, 냉장고, 세탁기·건조기, 식기세척기, TV 등 총 9종의 삼성전자 가전제품과 파트너사의 스마트 기기까지 지원한다. ’지속 가능한 일상(Everyday Sustainability)‘을 주제로 한 전시 공간에서는 파트너십을 바탕으로 탄생한 자원순환 솔루션을 소개한다. △세탁 과정에서 발생하는 미세 플라스틱 배출 저감을 위해 글로벌 아웃도어 브랜드 파타고니아(Patagonia)와 협업 개발한 ’미세 플라스틱 저감 필터‘ △문승지 디자이너와 업사이클링 패션 브랜드 플리츠마마가 협업해 버려진 페트병, 자투리 원단 등으로 만든 ’제로 에디션(Zero Edition)‘ 의자와 러그 등을 전시하다. 박찬우 삼성전자 생활가전사업부 부사장은 “삼성전자 비스포크 가전은 제품 고유의 기술은 물론 AI와 사물인터넷(IoT)를 접목해 일상을 더욱 풍요롭게 하고 에너지를 절감하는 솔루션을 제시해 왔다”며 “앞으로는 손쉽게 실천할 수 있는 지속 가능 솔루션을 다양하게 선보이며 소비자들이 가치 있는 일상을 경험할 수 있도록 만드는 데에 주력할 것”이라고 말했다.삼성전자 모델이 29일부터 내달 1일까지 광주광역시 김대중컨벤션센터에서 진행되는 ‘2023국제 IoT가전로봇박람회’에 마련된 삼성전자 부스의 ‘지속 가능한 일상’ 주제의 전시 공간에서 자원순환 솔루션을 소개하고 있다. (사진=삼성전자)"
+ # text = "/사진제공=한화한화 건설부문은 "
+ # text = "윤선희 기자 ="
+ # text = "서울 서초구 삼성전자 서초사옥의 모습. 2023.4.7/뉴스1 News1 신웅수 기자 김정은 기자 = "
+ text = " ▶공매도 대기자금, 전체 증시 83조·삼전 11조 돌파 ‘역대 최대’=1일 헤럴드경제가 금융투자협회 종합통계포털을 분석한 결과 지난달 26일 기준 삼성전자 한 종목에 대한 일간 대차거래 잔액 규모는 11조2507억원에 달했다. 이는 공매도가 부분 재개된 2021년 5월 이후는 물론, 관련 통계가 집계되기 시작한 2009년 이후 가장 큰 액수다. 공매도 부분 재개 후 7조원대 이하에 머물던 삼성전자에 대한 대차거래 잔액 규모는 지난해 11월 8조원대를 돌파해 9조원선을 터치했다. 올해 첫 거래일(1월 2일) 6조7630억원 규모로 시작한 대차"
+ text = all_preprocessing(text)
+ # text = remove_day(text)
+ # text = remove_press(text)
+ print(text)
\ No newline at end of file
diff --git a/utils/secrets.py b/utils/secrets.py
new file mode 100644
index 0000000..c2face5
--- /dev/null
+++ b/utils/secrets.py
@@ -0,0 +1,6 @@
+from dataclasses import dataclass, field
+
+@dataclass(frozen=True)
+class Secrets:
+ url: str = ""
+ key: str = ""
\ No newline at end of file
| |