文章目录
- 数据集介绍
- 数据集常见格式读入方式
-
- [1. 文本文件(.txt)](#1. 文本文件(.txt))
- [2. CSV文件(.csv)](#2. CSV文件(.csv))
- [3. Excel文件(.xlsx或.xls)](#3. Excel文件(.xlsx或.xls))
- [4. JSON文件(.json)](#4. JSON文件(.json))
- [5. SQL数据库](#5. SQL数据库)
- DataFrame的用法
- 完整代码
kaggle比赛链接:Sentiment Analysis on Movie Reviews
数据集介绍
训练集
- PhraseId: 每条评论的唯一标识符。一条评论可以包含多个句子(Sentence)。
- SentenceId: 句子的唯一标识符。一个句子可以包含多个短语(Phrase)。
- Phrase: 具体的短语文本,表示电影评论的一部分。
- Sentiment:情感标签,表示这条短语的情感分类。情感分类共有五个等级:
0:消极情感(negative)
1:有点消极情感(somewhat negative)
2:中性情感(neutral)
3:有点积极情感(somewhat positive)
4:积极情感(positive)
训练集
- PhraseId: 每条评论的唯一标识符。
- SentenceId: 句子的唯一标识符。
- Phrase: 具体的短语文本,表示电影评论的一部分。
数据集常见格式读入方式
在处理数据科学或机器学习项目时,正确读取各种类型的数据集至关重要。不同的文件格式需要不同的处理方法。以下是一些常见文件类型的数据集读入方式,主要使用Python及其流行的库,如Pandas和NumPy。
1. 文本文件(.txt)
-
使用Python内置方法 :
pythonwith open('data.txt', 'r') as file: data = file.readlines()
-
使用NumPy :
pythonimport numpy as np data = np.loadtxt('data.txt', delimiter=',')
2. CSV文件(.csv)
-
使用Pandas :
pythonimport pandas as pd data = pd.read_csv('data.csv') #默认是','分割 data = pd.read_csv('data.csv', sep='数据集中间的分隔符')
3. Excel文件(.xlsx或.xls)
-
使用Pandas :
pythonimport pandas as pd data = pd.read_excel('data.xlsx')
4. JSON文件(.json)
-
使用Pandas :
pythonimport pandas as pd data = pd.read_json('data.json')
JSON文件的结构一般是字典形式的键值对,这些键值对在DataFrame中会被转换为列名和数据行。
过程
data.json
:
json
[
{"name": "Alice", "age": 25, "city": "New York"},
{"name": "Bob", "age": 30, "city": "San Francisco"},
{"name": "Charlie", "age": 35, "city": "Los Angeles"}
]
使用Pandas的read_json
方法来读取这个文件:
python
import pandas as pd
data = pd.read_json('data.json')
print(data)
处理复杂的JSON结构
如果JSON结构比较复杂,需要对数据进行预处理,或者使用json_normalize
来处理嵌套的数据结构:
python
from pandas import json_normalize
complex_data = [
{"name": "Alice", "age": 25, "employment": {"company": "Company A", "title": "Engineer"}},
{"name": "Bob", "age": 30, "employment": {"company": "Company B", "title": "Manager"}}
]
df = json_normalize(complex_data)
print(df)
输出:
name age employment.company employment.title
0 Alice 25 Company A Engineer
1 Bob 30 Company B Manager
5. SQL数据库
-
使用Pandas与SQLAlchemy :
pythonfrom sqlalchemy import create_engine import pandas as pd # 创建数据库引擎 engine = create_engine('sqlite:///database.db') data = pd.read_sql('SELECT * FROM table_name', con=engine)
DataFrame的用法
手动创建DataFrame
- 借助字典自动创建
- 读取某些文件后生成的(这种常用,见上面数据集读入方式)
python
import pandas as pd
data = {
'Name': ['Alice', 'Bob', 'Charlie'],
'Age': [25, 30, 35],
'City': ['New York', 'San Francisco', 'Los Angeles']
}
df = pd.DataFrame(data)
print(df)
输出:
Name Age City
0 Alice 25 New York
1 Bob 30 San Francisco
2 Charlie 35 Los Angeles
访问数据
- 访问列:
直接通过列名取:
python
print(df['Name'])
也可以借助数字索引取,但是要指定取那些行(loc
和iloc
)
-
访问行:
使用
loc
和iloc
可以访问行。loc
是基于标签的索引,而iloc
是基于整数的索引:pythonprint(df.loc[0]) # 输出第一行数据
pyprint(df.iloc[-1]) # 输出最后一行数据
Pandas会默认使用从0开始的整数索引。这样会导致loc和iloc相似,但也有区别
loc
与iloc
的区别
-
切片的含义不同:
- 使用
loc
时,切片是闭区间 (inclusive),即loc[0:2]
会选择索引为0, 1, 和2的行。 - 使用
iloc
时,切片是半开区间 (exclusive),即iloc[0:2]
只会选择索引为0和1的行,不包括2。
- 使用
-
对索引类型的解释:
loc
仍然期望得到索引的标签。即使索引是整数,loc[0]
也是尝试找到标签名为0的行,而不是位置为0的行。在默认的整数索引情况下,标签名和位置相同。iloc
始终根据行或列的位置进行访问,与索引的标签无关。
修改数据
-
添加列:
pythondf['Salary'] = [50000, 60000, 70000] print(df)
-
修改列:
pythondf['Age'] += 1 # 所有人年龄加一 print(df)
-
修改行:
pythondf.iloc[2] = [1, 2, 3, 4] # 修改第三行的数据 print(df)
筛选数据
-
条件筛选:
使用条件表达式筛选数据:
pythonprint(df[df['Age'] > 25]) # 选出年龄大于25的记录
常用方法
-
describe:
获取数据的描述性统计:
pythonprint(df.describe())
-
sort_values:
按某列排序:
pythonprint(df.sort_values(by='Age'))
-
groupby:
按某列进行分组:
pythonprint(df.groupby('City').mean()) # 按城市分组并计算平均年龄
完整代码
python
import time
import torch
from sklearn.preprocessing import LabelEncoder
from torch import nn, optim
from torch.nn.utils.rnn import pad_sequence, pack_padded_sequence
from torch.utils.data import DataLoader
import pandas as pd
from SentenceDataSet import SentimentDataset
# 1.设置超参数
TRAIN_PATH = 'D:\python\learnDL\data\sentimentAnalysis\\train.tsv'
TEST_PATH = 'D:\python\learnDL\data\sentimentAnalysis\\test.tsv'
BATCH_SIZE = 64
HIDDEN_SIZE = 100
N_LAYERS = 2
N_EPOCHS = 10
LEARNING_RATE = 0.001
# 2.预处理
# 构建单词表,获取单词的唯一索引
def build_vocab(phrases):
vocab = set() # 去重
# 取出文本里面的每个单词,添加到单词表集合里
for phrase in phrases:
for word in phrase.split():
vocab.add(word)
# 字典推导式,创建单词表
word2idx = {word: idx for idx, word in enumerate(vocab, start=1)}
# 填充位的对应索引为0
word2idx['<PAD>'] = 0
return word2idx
# 文本转换成索引列表
def phrase_to_indices(phrase, word2idx):
return [word2idx[word] for word in phrase.split() if word in word2idx]
train_df = pd.read_csv(TRAIN_PATH, sep='\t')
test_df = pd.read_csv(TEST_PATH, sep='\t')
# 数据预处理
def preprocess_data():
# 读取数据集
# 将标签转换成数值类型
le = LabelEncoder()
train_df['Sentiment'] = le.fit_transform(train_df['Sentiment'])
# fit:训练LabelEncoder,让它知道总共有多少个类别
# transform:将类别标签转换成整数
# inverse_transform:标签转换成类别
train_phrases = train_df['Phrase'].tolist()
train_sentiments = train_df['Sentiment'].tolist()
test_phrases = test_df['Phrase'].tolist()
return train_phrases, train_sentiments, test_phrases, le
train_phrases, train_sentiments, test_phrases, le = preprocess_data()
word2idx = build_vocab(train_phrases + test_phrases)
train_indices = [phrase_to_indices(phrase, word2idx) for phrase in train_phrases]
test_indices_old = [phrase_to_indices(phrase, word2idx) for phrase in test_phrases]
# 移除长度为0的样本
train_indices = [x for x in train_indices if len(x) > 0]
train_sentiments = [y for x, y in zip(train_indices, train_sentiments) if len(x) > 0]
test_indices = [x for x in test_indices_old if len(x) > 0]
# 数据加载器
def collate_fn(batch):
# *:解包 zip:打包成为元组
# 将元组列表转换成两个元组x和y,目的是为了能够后续批处理
# 如果用列表的话,只能够对(x,y)进行批处理
phrases, sentiments = zip(*batch)
# 获取文本长度tensor
lengths = torch.tensor([len(x) for x in phrases])
# 获取文本tensor
phrases = [torch.tensor(x) for x in phrases]
# 进行padding,统一序列长度
phrases_padded = pad_sequence(phrases, batch_first=True, padding_value=0)
sentiments = torch.tensor(sentiments)
return phrases_padded, sentiments, lengths
train_dataset = SentimentDataset(train_indices, train_sentiments)
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, collate_fn=collate_fn)
test_dataset = SentimentDataset(test_indices)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False,
collate_fn=lambda x: pad_sequence([torch.tensor(phrase) for phrase in x], batch_first=True,
padding_value=0))
# 模型定义
class SentimentRNN(nn.Module):
def __init__(self, vocab_size, embed_size, hidden_size, output_size, n_layers):
super(SentimentRNN, self).__init__()
self.embedding = nn.Embedding(vocab_size, embed_size, padding_idx=0)
self.lstm = nn.LSTM(embed_size, hidden_size, n_layers, batch_first=True, bidirectional=True)
self.fc = nn.Linear(hidden_size * 2, output_size)
def forward(self, x, lengths):
x = self.embedding(x)
x = pack_padded_sequence(x, lengths.cpu(), batch_first=True,
enforce_sorted=False)
# 网络的输出一定要参考官方文档怎么说的,而不是自己猜想,官网都有
_, (hidden, _) = self.lstm(x)
hidden = torch.cat((hidden[-2], hidden[-1]), dim=1)
out = self.fc(hidden)
return out
vocab_size = len(word2idx)
embed_size = 128
output_size = len(le.classes_)
model = SentimentRNN(vocab_size, embed_size, HIDDEN_SIZE, output_size, N_LAYERS)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
# 训练和测试循环
def train(model, train_loader, criterion, optimizer, n_epochs):
model.train() # 开启训练模式
# 开启了dropout层
# BatchNorm:
# 在训练时,会根据当前批次的均值和标准差进行数据的归一化处理,并更新整个数据集的均值和标准差的估计值
# 在测试时,会使用训练时估计的均值和标准差对数据进行归一化
for epoch in range(n_epochs):
total_loss = 0
for phrases, sentiments, lengths in train_loader:
optimizer.zero_grad()
output = model(phrases, lengths)
loss = criterion(output, sentiments)
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f'Epoch: {epoch + 1}, Loss: {total_loss / len(train_loader)}')
def generate_test_results(model, test_loader):
model.eval()
results = []
with torch.no_grad():
for phrases in test_loader:
lengths = torch.tensor([len(x) for x in phrases])
output = model(phrases, lengths)
preds = torch.argmax(output, dim=1)
results.extend(preds.cpu().numpy())
return results
if __name__ == '__main__':
begin = time.time()
# 模型训练
train(model, train_loader, criterion, optimizer, N_EPOCHS)
end = time.time()
print(end - begin) # 总消耗时间
test_ids = test_df['PhraseId'].tolist()
preds = generate_test_results(model, test_loader)
new_preds = []
# 有一条数据为空,被预处理清掉了,所以最后的输出相较于原始数据集少一条,这里补上
for idx in range(len(preds)):
if idx == 1390:
new_preds.append(2)
new_preds.append(preds[idx])
# df输出需要保证行数相等才能输出表格,做一个断言
assert len(test_ids) == len(new_preds), f"Lengths do not match: {len(test_ids)} vs {len(new_preds)}"
# 保存结果
output_df = pd.DataFrame({'PhraseId': test_ids, 'Sentiment': new_preds})
output_df.to_csv('sentiment_predictions.csv', index=False)