深度学习(8):Sentiment Analysis on Movie Reviews

文章目录

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内置方法 :

    python 复制代码
    with open('data.txt', 'r') as file:
        data = file.readlines()
  • 使用NumPy :

    python 复制代码
    import numpy as np
    data = np.loadtxt('data.txt', delimiter=',')

2. CSV文件(.csv)

  • 使用Pandas :

    python 复制代码
    import pandas as pd
    data = pd.read_csv('data.csv') #默认是','分割
    data = pd.read_csv('data.csv', sep='数据集中间的分隔符')

3. Excel文件(.xlsx或.xls)

  • 使用Pandas :

    python 复制代码
    import pandas as pd
    data = pd.read_excel('data.xlsx')

4. JSON文件(.json)

  • 使用Pandas :

    python 复制代码
    import 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 :

    python 复制代码
    from 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

  1. 借助字典自动创建
  2. 读取某些文件后生成的(这种常用,见上面数据集读入方式)
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'])

也可以借助数字索引取,但是要指定取那些行(lociloc

  • 访问行

    使用lociloc可以访问行。loc是基于标签的索引,而iloc是基于整数的索引:

    python 复制代码
    print(df.loc[0])  # 输出第一行数据
    py 复制代码
    print(df.iloc[-1])  # 输出最后一行数据

Pandas会默认使用从0开始的整数索引。这样会导致loc和iloc相似,但也有区别

lociloc的区别

  1. 切片的含义不同

    • 使用loc时,切片是闭区间 (inclusive),即loc[0:2]会选择索引为0, 1, 和2的行。
    • 使用iloc时,切片是半开区间 (exclusive),即iloc[0:2]只会选择索引为0和1的行,不包括2。
  2. 对索引类型的解释

    • loc仍然期望得到索引的标签。即使索引是整数,loc[0]也是尝试找到标签名为0的行,而不是位置为0的行。在默认的整数索引情况下,标签名和位置相同。
    • iloc始终根据行或列的位置进行访问,与索引的标签无关。

修改数据

  • 添加列

    python 复制代码
    df['Salary'] = [50000, 60000, 70000]
    print(df)
  • 修改列

    python 复制代码
    df['Age'] += 1  # 所有人年龄加一
    print(df)
  • 修改行

    python 复制代码
    df.iloc[2] = [1, 2, 3, 4] # 修改第三行的数据
    print(df)

筛选数据

  • 条件筛选

    使用条件表达式筛选数据:

    python 复制代码
    print(df[df['Age'] > 25])  # 选出年龄大于25的记录

常用方法

  • describe

    获取数据的描述性统计:

    python 复制代码
    print(df.describe())
  • sort_values

    按某列排序:

    python 复制代码
    print(df.sort_values(by='Age'))
  • groupby

    按某列进行分组:

    python 复制代码
    print(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)
相关推荐
B站计算机毕业设计超人1 小时前
计算机毕业设计PySpark+Hadoop中国城市交通分析与预测 Python交通预测 Python交通可视化 客流量预测 交通大数据 机器学习 深度学习
大数据·人工智能·爬虫·python·机器学习·课程设计·数据可视化
学术头条1 小时前
清华、智谱团队:探索 RLHF 的 scaling laws
人工智能·深度学习·算法·机器学习·语言模型·计算语言学
18号房客1 小时前
一个简单的机器学习实战例程,使用Scikit-Learn库来完成一个常见的分类任务——**鸢尾花数据集(Iris Dataset)**的分类
人工智能·深度学习·神经网络·机器学习·语言模型·自然语言处理·sklearn
feifeikon1 小时前
机器学习DAY3 : 线性回归与最小二乘法与sklearn实现 (线性回归完)
人工智能·机器学习·线性回归
游客5201 小时前
opencv中的常用的100个API
图像处理·人工智能·python·opencv·计算机视觉
古希腊掌管学习的神1 小时前
[机器学习]sklearn入门指南(2)
人工智能·机器学习·sklearn
Ven%1 小时前
如何在防火墙上指定ip访问服务器上任何端口呢
linux·服务器·网络·深度学习·tcp/ip
凡人的AI工具箱1 小时前
每天40分玩转Django:Django国际化
数据库·人工智能·后端·python·django·sqlite
IT猿手2 小时前
最新高性能多目标优化算法:多目标麋鹿优化算法(MOEHO)求解TP1-TP10及工程应用---盘式制动器设计,提供完整MATLAB代码
开发语言·深度学习·算法·机器学习·matlab·多目标算法
咸鱼桨2 小时前
《庐山派从入门到...》PWM板载蜂鸣器
人工智能·windows·python·k230·庐山派