mirror of
https://github.com/QData/TextAttack.git
synced 2021-10-13 00:05:06 +03:00
make requested fixes
This commit is contained in:
@@ -117,7 +117,7 @@
|
||||
"\n",
|
||||
"# We're going to use our Banana word swap class as the attack transformation.\n",
|
||||
"transformation = BananaWordSwap() \n",
|
||||
"# We'll constrain modificaiton of already modified indices and stopwords\n",
|
||||
"# We'll constrain modification of already modified indices and stopwords\n",
|
||||
"constraints = [RepeatModification(),\n",
|
||||
" StopwordModification()]\n",
|
||||
"# We'll use the Greedy search method\n",
|
||||
|
||||
@@ -60,10 +60,10 @@
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[nltk_data] Downloading package punkt to /u/edl9cy/nltk_data...\n",
|
||||
"[nltk_data] Unzipping tokenizers/punkt.zip.\n",
|
||||
"[nltk_data] Package punkt is already up-to-date!\n",
|
||||
"[nltk_data] Downloading package maxent_ne_chunker to\n",
|
||||
"[nltk_data] /u/edl9cy/nltk_data...\n",
|
||||
"[nltk_data] Unzipping chunkers/maxent_ne_chunker.zip.\n",
|
||||
"[nltk_data] Package maxent_ne_chunker is already up-to-date!\n",
|
||||
"[nltk_data] Downloading package words to /u/edl9cy/nltk_data...\n",
|
||||
"[nltk_data] Package words is already up-to-date!\n"
|
||||
]
|
||||
@@ -323,10 +323,6 @@
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\u001b[34;1mtextattack\u001b[0m: Downloading https://textattack.s3.amazonaws.com/models/classification/lstm/yelp_polarity.\n",
|
||||
"100%|██████████| 297M/297M [00:10<00:00, 28.5MB/s] \n",
|
||||
"\u001b[34;1mtextattack\u001b[0m: Unzipping file path_to_zip_file to unzipped_folder_path.\n",
|
||||
"\u001b[34;1mtextattack\u001b[0m: Successfully saved models/classification/lstm/yelp_polarity to cache.\n",
|
||||
"\u001b[34;1mtextattack\u001b[0m: Goal function <class 'textattack.goal_functions.classification.untargeted_classification.UntargetedClassification'> matches model LSTMForYelpSentimentClassification.\n"
|
||||
]
|
||||
}
|
||||
@@ -422,12 +418,13 @@
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"metadata": {
|
||||
"scrolled": false
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from tqdm import tqdm # tqdm provides us a nice progress bar.\n",
|
||||
"from textattack.loggers import CSVLogger # tracks a dataframe for us.\n",
|
||||
"from textattack.attack_results import FailedAttackResult, SkippedAttackResult\n",
|
||||
"from textattack.attack_results import SuccessfulAttackResult\n",
|
||||
"\n",
|
||||
"results_iterable = attack.attack_dataset(YelpSentiment(), attack_n=True)\n",
|
||||
"logger = CSVLogger(color_method='html')\n",
|
||||
@@ -435,9 +432,10 @@
|
||||
"num_successes = 0\n",
|
||||
"while num_successes < 10:\n",
|
||||
" result = next(results_iterable)\n",
|
||||
" if not (isinstance(result, FailedAttackResult) or isinstance(result, SkippedAttackResult)):\n",
|
||||
" if isinstance(result, SuccessfulAttackResult):\n",
|
||||
" logger.log_attack_result(result)\n",
|
||||
" num_successes += 1"
|
||||
" num_successes += 1\n",
|
||||
" print(num_successes)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -449,82 +447,9 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 42,
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th></th>\n",
|
||||
" <th>passage_1</th>\n",
|
||||
" <th>passage_2</th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>0</th>\n",
|
||||
" <td><font color = red>Quick</font> attentive bartenders. No Convenient parking. No Patrons above the age of 24. No Hot waitresses. No. Slow Waitresses. Yes. Everlasting brodeo. Yes. Dirty Bathrooms. Yes. Douchebaggery all around. Yes. Unfresh oysters. Yes. $3 kiltlifter. Yes. Asshole cops that follow you home afterwards. Yes. I decided that I pretty much hate this place. Sorry, I just do.</td>\n",
|
||||
" <td><font color = green>Speedily</font> attentive bartenders. No Convenient parking. No Patrons above the age of 24. No Hot waitresses. No. Slow Waitresses. Yes. Everlasting brodeo. Yes. Dirty Bathrooms. Yes. Douchebaggery all around. Yes. Unfresh oysters. Yes. $3 kiltlifter. Yes. Asshole cops that follow you home afterwards. Yes. I decided that I pretty much hate this place. Sorry, I just do.</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>1</th>\n",
|
||||
" <td>On Yelp there are 5-6 Paradise restaurants in Vegas. Which one is the original? Who cares?! :) I came to this one as the only place you pass from the Monorail to the trade show conference down the hall is the Foruna coffee and wine place. The only thing fortunate was to avoid its yucky looking pastries. U-<font color = red>turn</font> back and keep right instead of heading back to the Monorail and you will hit <font color = red>Paradise</font>. I sat at the bar and stared at the hazy looking brownish restaurant. It felt like <font color = red>Canter</font>'s in L.A. (which is one of the oldest in the city!?) <font color = red>Service</font> wasn't great here. One lady gave me a menu; then she gave me the takeout menu (clearly marked with the gouging 18% gratuity added.) Then another lady came and took my order. I ordered the healthy option. <font color = red>Water</font>/ <font color = red>Joke</font>. Oatmeal with dried cherries and coffee. It came in a neat recycled cardboard holder with 2 cups (coffee and oatmeal) and 3 little containers. Milk for the oatmeal but I used it for the coffee; 2 brown sugars. Stirring the oatmeal there was no sign of the cherries so I checked the gouging menu and then asked waitress #1 where they were. It seems #2 forgot. Anyway the dried cherries transformed into tiny prunes. That was even better on the healthy quota. Overall, not thrilled with my dining experience here, but the food was ok. Of course I paid $11 for the pleasure. $3 for coffee - cheaper than the Starbucks in the Harrah's - $6 for oatmeal. $1.50 for tip.</td>\n",
|
||||
" <td>On Yelp there are 5-6 Paradise restaurants in Vegas. Which one is the original? Who cares?! :) I came to this one as the only place you pass from the Monorail to the trade show conference down the hall is the Foruna coffee and wine place. The only thing fortunate was to avoid its yucky looking pastries. U-<font color = green>transforming</font> back and keep right instead of heading back to the Monorail and you will hit <font color = green>Heavens</font>. I sat at the bar and stared at the hazy looking brownish restaurant. It felt like <font color = green>Pirouette</font>'s in L.A. (which is one of the oldest in the city!?) <font color = green>Department</font> wasn't great here. One lady gave me a menu; then she gave me the takeout menu (clearly marked with the gouging 18% gratuity added.) Then another lady came and took my order. I ordered the healthy option. <font color = green>Eau</font>/ <font color = green>Giggle</font>. Oatmeal with dried cherries and coffee. It came in a neat recycled cardboard holder with 2 cups (coffee and oatmeal) and 3 little containers. Milk for the oatmeal but I used it for the coffee; 2 brown sugars. Stirring the oatmeal there was no sign of the cherries so I checked the gouging menu and then asked waitress #1 where they were. It seems #2 forgot. Anyway the dried cherries transformed into tiny prunes. That was even better on the healthy quota. Overall, not thrilled with my dining experience here, but the food was ok. Of course I paid $11 for the pleasure. $3 for coffee - cheaper than the Starbucks in the Harrah's - $6 for oatmeal. $1.50 for tip.</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2</th>\n",
|
||||
" <td>Dining in <font color = green>Red</font> Lobster is dining in my comfort zone. Going back to 1977, I have been a Red Lobster fan. In my early forties, I continue to be a Red Lobster fan. In 35 years I never got tired of it. So, it is with little surprise that when we visited Phoenix that we would dine in a Red Lobster. For whatever reason, the Cactus Road Red Lobster (near Paradise Valley Mall) was dead on a Friday night. It was the first time in a while that when I visited a Red Lobster that a wait to be seated was nonexistent. If there was a wait, I'm sure that the usual lobster tank would have occupied me for a few minutes. As soon as I saw the \"All You Can Eat Shrimp special\" on the menu, I was decided. The shrimp special is no joke. There is a choice of four shrimps including shrimp scampi, fried shrimp, and shrimp with linguini. I'll be the first to admit that Red Lobster seafood isn't Wolfgang Puck Gang quality at the same time that I'll admit that it is far better than frozen foods seafood or a burger served in a casual restaurant chain. It wasn't just the all you can eat shrimp and bottomless soda that makes this a good deal for approximately $20. It was also the salad with dressing and house bread. Red Lobster's menu features many entrees that are affordable. I just happened to hit Red Lobster when they featured the \"All You Can Eat Shrimp.\" If it had been another time of the year, I would have ordered shrimp linguini, which comes with a salad and bread. I had zero complaints about the service. Our Waitress was prompt with the refills at the exact time that she did not rush us. Dining in Red Lobster is dining in my comfort zone. As long as I am living and have some green in my pocket, I will return to a Red Lobster throughout the year.</td>\n",
|
||||
" <td>Dining in <font color = red>Flushed</font> Lobster is dining in my comfort zone. Going back to 1977, I have been a Red Lobster fan. In my early forties, I continue to be a Red Lobster fan. In 35 years I never got tired of it. So, it is with little surprise that when we visited Phoenix that we would dine in a Red Lobster. For whatever reason, the Cactus Road Red Lobster (near Paradise Valley Mall) was dead on a Friday night. It was the first time in a while that when I visited a Red Lobster that a wait to be seated was nonexistent. If there was a wait, I'm sure that the usual lobster tank would have occupied me for a few minutes. As soon as I saw the \"All You Can Eat Shrimp special\" on the menu, I was decided. The shrimp special is no joke. There is a choice of four shrimps including shrimp scampi, fried shrimp, and shrimp with linguini. I'll be the first to admit that Red Lobster seafood isn't Wolfgang Puck Gang quality at the same time that I'll admit that it is far better than frozen foods seafood or a burger served in a casual restaurant chain. It wasn't just the all you can eat shrimp and bottomless soda that makes this a good deal for approximately $20. It was also the salad with dressing and house bread. Red Lobster's menu features many entrees that are affordable. I just happened to hit Red Lobster when they featured the \"All You Can Eat Shrimp.\" If it had been another time of the year, I would have ordered shrimp linguini, which comes with a salad and bread. I had zero complaints about the service. Our Waitress was prompt with the refills at the exact time that she did not rush us. Dining in Red Lobster is dining in my comfort zone. As long as I am living and have some green in my pocket, I will return to a Red Lobster throughout the year.</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>3</th>\n",
|
||||
" <td>Lots of great reviews for a reason. There is an undeserved 1-star on here because of lack of 'authenticity' and the fact that they gave the user a fork. <font color = green>Well</font>, guess what--I've been given forks at sushi restaurants that were fantastic. Some people don't know how to eat Ethiopian and that's fine. One thing you should definitely do is read the blurb on the front of the menu. Not only is it really interesting (the restaurant is actually named after an ancient church that was excavated) but it gives you helpful background on what you're about to get in to. Like other users note, injera (en-jee-rah) is weird. Looks like a sponge, feels like rubber, tastes like a sour sock. So, you get why Ethiopians eat it smothered in stuffs. Luckily, the stuffs are hella tasty, and vegetarian/vegan friendly. I'd recommend the spicy stuff (No. 9 was my favorite) but anything cold can get kind of odd. Lots of lentils are served here, and you can even buy the Lalibela brand at Whole Foods. What else can I say, the service was awesome, the food was unique and reasonably priced. If it's between you and one other person I'd probably only get three entrees, they are pretty big despite what the server will probably say. I didn't give five stars because I've only been once. Try it out soon.</td>\n",
|
||||
" <td>Lots of great reviews for a reason. There is an undeserved 1-star on here because of lack of 'authenticity' and the fact that they gave the user a fork. <font color = red>Alright</font>, guess what--I've been given forks at sushi restaurants that were fantastic. Some people don't know how to eat Ethiopian and that's fine. One thing you should definitely do is read the blurb on the front of the menu. Not only is it really interesting (the restaurant is actually named after an ancient church that was excavated) but it gives you helpful background on what you're about to get in to. Like other users note, injera (en-jee-rah) is weird. Looks like a sponge, feels like rubber, tastes like a sour sock. So, you get why Ethiopians eat it smothered in stuffs. Luckily, the stuffs are hella tasty, and vegetarian/vegan friendly. I'd recommend the spicy stuff (No. 9 was my favorite) but anything cold can get kind of odd. Lots of lentils are served here, and you can even buy the Lalibela brand at Whole Foods. What else can I say, the service was awesome, the food was unique and reasonably priced. If it's between you and one other person I'd probably only get three entrees, they are pretty big despite what the server will probably say. I didn't give five stars because I've only been once. Try it out soon.</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>4</th>\n",
|
||||
" <td><font color = red>Worst</font> hot dog ever.</td>\n",
|
||||
" <td><font color = green>Grandest</font> hot dog ever.</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>5</th>\n",
|
||||
" <td>For those parents who need to take kids in for their first haircut, or one of the first few haircuts, I strongly recommend a place like this. Cool <font color = green>Cuts</font> has a nice play area for kids while they wait including video game stations for the older kids. A basic cut is $16.95 which is average. You can find cheaper prices at Stupidcuts and Hateclips, but you will make up the difference by waiting at least an hour for a moron to cut your child's hair badly, and your child being scared to death in the process. Spend a few bucks more and go to a shop that specializes in kids. The kids get to sit in a regular chair or a special car or airplane chair and watch a video from a decent selection while the snippers do their work. My little boy just had his third haircut ever, and Maryam (sic) did a great job. I think she set the land speed record for cutting hair. He was seated and done within 5 minutes, and all that while having a screaming fit the way only an 18 month old boy from my family can do. They will use scissors if the child can stand it, and clippers if she has a squirmer. I recommend calling ahead for a reservation or you will have to wait (or shop with your wife while waiting!).</td>\n",
|
||||
" <td>For those parents who need to take kids in for their first haircut, or one of the first few haircuts, I strongly recommend a place like this. Cool <font color = red>Cutback</font> has a nice play area for kids while they wait including video game stations for the older kids. A basic cut is $16.95 which is average. You can find cheaper prices at Stupidcuts and Hateclips, but you will make up the difference by waiting at least an hour for a moron to cut your child's hair badly, and your child being scared to death in the process. Spend a few bucks more and go to a shop that specializes in kids. The kids get to sit in a regular chair or a special car or airplane chair and watch a video from a decent selection while the snippers do their work. My little boy just had his third haircut ever, and Maryam (sic) did a great job. I think she set the land speed record for cutting hair. He was seated and done within 5 minutes, and all that while having a screaming fit the way only an 18 month old boy from my family can do. They will use scissors if the child can stand it, and clippers if she has a squirmer. I recommend calling ahead for a reservation or you will have to wait (or shop with your wife while waiting!).</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>6</th>\n",
|
||||
" <td>I went to Ramsay's steak at the Paris for my birthday and we loved it. This time in Vegas we were looking for a light lunch and wanted to try Ramsay's pub and grill. We watch Hells Kitchen so we knew it existed. It is within the Cesar's palace. <font color = red>Nice</font> decor. I ordered a burger, nothing fancy. It took more than one hour for my girlfriend and I to get our food. THe waitress disappeared for more than 45 min, when she finally came back she was worried that we did not get our food yet, eventually the manager came to apologize as well and to offer us a round of drink assuring that food was on its way. Another 15-20 min later, we finally got our order, it looked great however my patty was not cooked... simply grilled on the outside and raw on the inside, it was funny to see that considering the show that Gordon puts on in Hells Kitchen over uncooked meat... I mentioned to the waitress and the manager who did not charge us for my dish. They offered to order a new one but at this point the quick lunch turned into a 2 hours bad experience and we wanted to get out. I think the staff handled the situation properly, some might have yelled a little more and maybe ask for more comps... This was certainly a bad experience overall, I am sure however it does not reflect the standards of the restaurant... I hope. but with so many other places in Vegas I doubt I will give it another shot.... I give two stars for the staff's reaction, even slow, at least they tried to do something... and for the beer I was drinking that I thought was great !</td>\n",
|
||||
" <td>I went to Ramsay's steak at the Paris for my birthday and we loved it. This time in Vegas we were looking for a light lunch and wanted to try Ramsay's pub and grill. We watch Hells Kitchen so we knew it existed. It is within the Cesar's palace. <font color = green>Delightful</font> decor. I ordered a burger, nothing fancy. It took more than one hour for my girlfriend and I to get our food. THe waitress disappeared for more than 45 min, when she finally came back she was worried that we did not get our food yet, eventually the manager came to apologize as well and to offer us a round of drink assuring that food was on its way. Another 15-20 min later, we finally got our order, it looked great however my patty was not cooked... simply grilled on the outside and raw on the inside, it was funny to see that considering the show that Gordon puts on in Hells Kitchen over uncooked meat... I mentioned to the waitress and the manager who did not charge us for my dish. They offered to order a new one but at this point the quick lunch turned into a 2 hours bad experience and we wanted to get out. I think the staff handled the situation properly, some might have yelled a little more and maybe ask for more comps... This was certainly a bad experience overall, I am sure however it does not reflect the standards of the restaurant... I hope. but with so many other places in Vegas I doubt I will give it another shot.... I give two stars for the staff's reaction, even slow, at least they tried to do something... and for the beer I was drinking that I thought was great !</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>7</th>\n",
|
||||
" <td><font color = red>Great</font> name, but the beer is insipid, bland, and served in plastic pitchers. Yuck. College greeksters might dig it, but anyone with an actual palate will be repelled. Costly, boring, feh.</td>\n",
|
||||
" <td><font color = green>Phenomenal</font> name, but the beer is insipid, bland, and served in plastic pitchers. Yuck. College greeksters might dig it, but anyone with an actual palate will be repelled. Costly, boring, feh.</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>8</th>\n",
|
||||
" <td><font color = green>Lauren</font> R. did a great job explaining how these bad boys are served! She's right on the money and so are these! Seriously, some of the best hot dogs in town are here.. not topped with bacon but wrapped in bacon.. the hot dogs are not cooked on site but at a local commissary where they can legally cook bacon wrapped hot dogs. It is very much illegal not only in Arizona but in California to cook hot dogs on the street in this manor. In fact there was a lady in LA who was actually arrested for cooking and serving them this way. Anyway, enough with the education.. stop by and eat one, keep this cart going, amazing hot dogs and friendly people running this one!! Oh and they have real Mexican coke, with sugar, no HFCS!</td>\n",
|
||||
" <td><font color = red>Lorraine</font> R. did a great job explaining how these bad boys are served! She's right on the money and so are these! Seriously, some of the best hot dogs in town are here.. not topped with bacon but wrapped in bacon.. the hot dogs are not cooked on site but at a local commissary where they can legally cook bacon wrapped hot dogs. It is very much illegal not only in Arizona but in California to cook hot dogs on the street in this manor. In fact there was a lady in LA who was actually arrested for cooking and serving them this way. Anyway, enough with the education.. stop by and eat one, keep this cart going, amazing hot dogs and friendly people running this one!! Oh and they have real Mexican coke, with sugar, no HFCS!</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>9</th>\n",
|
||||
" <td><font color = red>Awesome</font> atmosphere and design. Unfortunately the food doesn't keep up with the ambiance. Prices were high for pre-made food. Throw a chef in here and it could be an amazing spot.</td>\n",
|
||||
" <td><font color = green>Sublime</font> atmosphere and design. Unfortunately the food doesn't keep up with the ambiance. Prices were high for pre-made food. Throw a chef in here and it could be an amazing spot.</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.HTML object>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"pd.options.display.max_colwidth = 480 # increase column width so we can actually read the examples\n",
|
||||
@@ -550,18 +475,6 @@
|
||||
"display_name": "Python 3",
|
||||
"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.7.7"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -28,11 +28,13 @@ def TextFoolerJin2019(model):
|
||||
#
|
||||
transformation = WordSwapEmbedding(max_candidates=50)
|
||||
#
|
||||
# Don't modify the same word twice or stopwords
|
||||
# Don't modify the same word twice or the stopwords defined
|
||||
# in the TextFooler public implementation.
|
||||
#
|
||||
stopwords = set(['a', 'about', 'above', 'across', 'after', 'afterwards', 'again', 'against', 'ain', 'all', 'almost', 'alone', 'along', 'already', 'also', 'although', 'am', 'among', 'amongst', 'an', 'and', 'another', 'any', 'anyhow', 'anyone', 'anything', 'anyway', 'anywhere', 'are', 'aren', "aren't", 'around', 'as', 'at', 'back', 'been', 'before', 'beforehand', 'behind', 'being', 'below', 'beside', 'besides', 'between', 'beyond', 'both', 'but', 'by', 'can', 'cannot', 'could', 'couldn', "couldn't", 'd', 'didn', "didn't", 'doesn', "doesn't", 'don', "don't", 'down', 'due', 'during', 'either', 'else', 'elsewhere', 'empty', 'enough', 'even', 'ever', 'everyone', 'everything', 'everywhere', 'except', 'first', 'for', 'former', 'formerly', 'from', 'hadn', "hadn't", 'hasn', "hasn't", 'haven', "haven't", 'he', 'hence', 'her', 'here', 'hereafter', 'hereby', 'herein', 'hereupon', 'hers', 'herself', 'him', 'himself', 'his', 'how', 'however', 'hundred', 'i', 'if', 'in', 'indeed', 'into', 'is', 'isn', "isn't", 'it', "it's", 'its', 'itself', 'just', 'latter', 'latterly', 'least', 'll', 'may', 'me', 'meanwhile', 'mightn', "mightn't", 'mine', 'more', 'moreover', 'most', 'mostly', 'must', 'mustn', "mustn't", 'my', 'myself', 'namely', 'needn', "needn't", 'neither', 'never', 'nevertheless', 'next', 'no', 'nobody', 'none', 'noone', 'nor', 'not', 'nothing', 'now', 'nowhere', 'o', 'of', 'off', 'on', 'once', 'one', 'only', 'onto', 'or', 'other', 'others', 'otherwise', 'our', 'ours', 'ourselves', 'out', 'over', 'per', 'please','s', 'same', 'shan', "shan't", 'she', "she's", "should've", 'shouldn', "shouldn't", 'somehow', 'something', 'sometime', 'somewhere', 'such', 't', 'than', 'that', "that'll", 'the', 'their', 'theirs', 'them', 'themselves', 'then', 'thence', 'there', 'thereafter', 'thereby', 'therefore', 'therein', 'thereupon', 'these', 'they','this', 'those', 'through', 'throughout', 'thru', 'thus', 'to', 'too','toward', 'towards', 'under', 'unless', 'until', 'up', 'upon', 'used', 've', 'was', 'wasn', "wasn't", 'we', 'were', 'weren', "weren't", 'what', 'whatever', 'when', 'whence', 'whenever', 'where', 'whereafter', 'whereas', 'whereby', 'wherein', 'whereupon', 'wherever', 'whether', 'which', 'while', 'whither', 'who', 'whoever', 'whole', 'whom', 'whose', 'why', 'with', 'within', 'without', 'won', "won't", 'would', 'wouldn', "wouldn't", 'y', 'yet', 'you', "you'd", "you'll", "you're", "you've", 'your', 'yours', 'yourself', 'yourselves'])
|
||||
constraints = [
|
||||
RepeatModification(),
|
||||
StopwordModification(textfooler_stopwords=True)
|
||||
StopwordModification(stopwords=stopwords)
|
||||
]
|
||||
#
|
||||
# Minimum word embedding cosine similarity of 0.5.
|
||||
|
||||
@@ -29,7 +29,7 @@ class Constraint:
|
||||
else:
|
||||
incompatible_x_advs.append(x_adv)
|
||||
except KeyError:
|
||||
raise KeyError('x_adv must have `last_transformation` attack_attr to apply GoogLM constraint')
|
||||
raise KeyError('x_adv must have `last_transformation` attack_attr to apply constraint')
|
||||
filtered_x_advs = self._check_constraint_many(x, compatible_x_advs, original_text=original_text)
|
||||
return list(filtered_x_advs) + incompatible_x_advs
|
||||
|
||||
@@ -58,6 +58,10 @@ class Constraint:
|
||||
def check_compatibility(self, transformation):
|
||||
"""
|
||||
Checks if this constraint is compatible with the given transformation.
|
||||
For example, the WordEmbeddingDistance constraint compares the embedding of
|
||||
the word inserted with that of the word deleted. Therefore it can only be
|
||||
applied in the case of word swaps, and not for transformations which involve
|
||||
only one of insertion or deletion.
|
||||
Args:
|
||||
transformation: The transformation to check compatibility with.
|
||||
"""
|
||||
|
||||
@@ -100,7 +100,7 @@ class GoogleLanguageModel(Constraint):
|
||||
# same order they were passed in.
|
||||
max_el_indices.sort()
|
||||
|
||||
return list(np.array(x_adv_list)[max_el_indices])
|
||||
return [x_adv_list[i] for i in max_el_indices]
|
||||
|
||||
def __call__(self, x, x_adv):
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -28,7 +28,7 @@ class LanguageModelConstraint(Constraint):
|
||||
try:
|
||||
indices = x_adv.attack_attrs['newly_modified_indices']
|
||||
except KeyError:
|
||||
raise KeyError('Cannot apply part-of-speech constraint without `newly_modified_indices`')
|
||||
raise KeyError('Cannot apply language model constraint without `newly_modified_indices`')
|
||||
|
||||
for i in indices:
|
||||
probs = self.get_log_probs_at_index((x, x_adv), i)
|
||||
|
||||
@@ -3,7 +3,7 @@ import nltk
|
||||
|
||||
from textattack.constraints import Constraint
|
||||
from textattack.shared import TokenizedText
|
||||
from textattack.shared.validators import is_word_swap
|
||||
from textattack.shared.validators import consists_of_word_swaps
|
||||
|
||||
class PartOfSpeech(Constraint):
|
||||
""" Constraints word swaps to only swap words with the same part of speech.
|
||||
@@ -16,9 +16,6 @@ class PartOfSpeech(Constraint):
|
||||
self.allow_verb_noun_swap = allow_verb_noun_swap
|
||||
self._pos_tag_cache = lru.LRU(2**14)
|
||||
|
||||
def check_compatibility(self, transformation):
|
||||
return transformation.consists_of(is_word_swap)
|
||||
|
||||
def _can_replace_pos(self, pos_a, pos_b):
|
||||
return (pos_a == pos_b) or (self.allow_verb_noun_swap and set([pos_a,pos_b]) <= set(['NOUN','VERB']))
|
||||
|
||||
@@ -54,6 +51,9 @@ class PartOfSpeech(Constraint):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def check_compatibility(self, transformation):
|
||||
return consists_of_word_swaps(transformation)
|
||||
|
||||
def extra_repr_keys(self):
|
||||
return ['tagset', 'allow_verb_noun_swap']
|
||||
|
||||
@@ -6,7 +6,7 @@ from textattack.constraints import ModificationConstraint
|
||||
|
||||
class RepeatModification(ModificationConstraint):
|
||||
"""
|
||||
A constraint disallowing the modification of words which have already been modified
|
||||
A constraint disallowing the modification of words which have already been modified.
|
||||
"""
|
||||
|
||||
def _get_modifiable_indices(self, tokenized_text):
|
||||
|
||||
@@ -3,29 +3,33 @@
|
||||
|
||||
from textattack.shared.utils import default_class_repr
|
||||
from textattack.constraints import ModificationConstraint
|
||||
from textattack.shared.validators import is_word_swap
|
||||
from textattack.shared.validators import consists_of_word_swaps
|
||||
import nltk
|
||||
|
||||
class StopwordModification(ModificationConstraint):
|
||||
"""
|
||||
A constraint disallowing the modification of stopwords
|
||||
"""
|
||||
|
||||
def __init__(self, textfooler_stopwords=False):
|
||||
self.textfooler_stopwords = textfooler_stopwords
|
||||
def __init__(self, stopwords=None):
|
||||
if stopwords is not None:
|
||||
self.stopwords = set(stopwords)
|
||||
else:
|
||||
self.stopwords = set(nltk.corpus.stopwords.words('english'))
|
||||
|
||||
def _get_modifiable_indices(self, tokenized_text):
|
||||
""" Returns the word indices in x which are able to be deleted """
|
||||
try:
|
||||
tokenized_text.identify_stopwords(self.textfooler_stopwords)
|
||||
return set(range(len(tokenized_text.words))) - tokenized_text.attack_attrs['stopword_indices']
|
||||
except KeyError:
|
||||
raise KeyError('`stopword_indices` in attack_attrs required for StopwordDeletion constraint.')
|
||||
|
||||
non_stopword_indices = set()
|
||||
for i, word in enumerate(tokenized_text.words):
|
||||
if word not in self.stopwords:
|
||||
non_stopword_indices.add(i)
|
||||
return non_stopword_indices
|
||||
|
||||
def check_compatibility(self, transformation):
|
||||
"""
|
||||
Checks if this constraint is compatible with the given transformation.
|
||||
The stopword constraint only is concerned with word swaps since, paraphrasing phrases
|
||||
containing stopwords is OK.
|
||||
Args:
|
||||
transformation: The transformation to check compatibility with.
|
||||
"""
|
||||
return transformation.consists_of(is_word_swap)
|
||||
return consists_of_word_swaps(transformation)
|
||||
|
||||
@@ -6,7 +6,7 @@ import torch
|
||||
from textattack.shared import utils
|
||||
from textattack.constraints import Constraint
|
||||
from textattack.shared import TokenizedText
|
||||
from textattack.shared.validators import is_word_swap
|
||||
from textattack.shared.validators import consists_of_word_swaps
|
||||
|
||||
class WordEmbeddingDistance(Constraint):
|
||||
"""
|
||||
@@ -98,9 +98,6 @@ class WordEmbeddingDistance(Constraint):
|
||||
self.mse_dist_mat[a][b] = mse_dist
|
||||
return mse_dist
|
||||
|
||||
def check_compatibility(self, transformation):
|
||||
return transformation.consists_of(is_word_swap)
|
||||
|
||||
def _check_constraint(self, x, x_adv, original_text=None):
|
||||
""" Returns true if (x, x_adv) are closer than `self.min_cos_sim`
|
||||
and `self.max_mse_dist`. """
|
||||
@@ -139,6 +136,13 @@ class WordEmbeddingDistance(Constraint):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def check_compatibility(self, transformation):
|
||||
"""
|
||||
WordEmbeddingDistance requires a word being both deleted and inserted at the same index
|
||||
in order to compare their embeddings, therefore it's restricted to word swaps.
|
||||
"""
|
||||
return consists_of_word_swaps(transformation)
|
||||
|
||||
def extra_repr_keys(self):
|
||||
"""Set the extra representation of the constraint using these keys.
|
||||
|
||||
@@ -4,8 +4,8 @@ from textattack.search_methods import SearchMethod
|
||||
|
||||
class BeamSearch(SearchMethod):
|
||||
"""
|
||||
An attack that greedily chooses from a list of possible
|
||||
perturbations.
|
||||
An attack that maintinas a beam of the `beam_width` highest scoring TokenizedTexts, greedily
|
||||
updating the beam with the highest scoring transformations from the current beam.
|
||||
|
||||
Args:
|
||||
goal_function: A function for determining how well a perturbation is doing at achieving the attack's goal.
|
||||
@@ -16,7 +16,7 @@ class BeamSearch(SearchMethod):
|
||||
def __init__(self, beam_width=8):
|
||||
self.beam_width = beam_width
|
||||
|
||||
def __call__(self, initial_result):
|
||||
def _perform_search(self, initial_result):
|
||||
beam = [initial_result.tokenized_text]
|
||||
best_result = initial_result
|
||||
while not best_result.succeeded:
|
||||
|
||||
@@ -9,7 +9,7 @@ import torch
|
||||
from copy import deepcopy
|
||||
|
||||
from textattack.search_methods import SearchMethod
|
||||
from textattack.shared.validators import is_word_swap
|
||||
from textattack.shared.validators import consists_of_word_swaps
|
||||
|
||||
class GeneticAlgorithm(SearchMethod):
|
||||
"""
|
||||
@@ -26,9 +26,6 @@ class GeneticAlgorithm(SearchMethod):
|
||||
self.temp = temp
|
||||
self.give_up_if_no_improvement = give_up_if_no_improvement
|
||||
|
||||
def check_transformation_compatibility(self, transformation):
|
||||
return transformation.consists_of(is_word_swap)
|
||||
|
||||
def _replace_at_index(self, pop_member, idx):
|
||||
"""
|
||||
Select the best replacement for word at position (idx)
|
||||
@@ -133,7 +130,7 @@ class GeneticAlgorithm(SearchMethod):
|
||||
neighbors_len = np.array([len(x) for x in neighbors_list])
|
||||
return neighbors_len
|
||||
|
||||
def __call__(self, initial_result):
|
||||
def _perform_search(self, initial_result):
|
||||
self.original_tokenized_text = initial_result.tokenized_text
|
||||
self.correct_output = initial_result.output
|
||||
neighbors_len = self._get_neighbors_len(self.original_tokenized_text)
|
||||
@@ -173,6 +170,12 @@ class GeneticAlgorithm(SearchMethod):
|
||||
|
||||
return pop[0].result
|
||||
|
||||
def check_transformation_compatibility(self, transformation):
|
||||
"""
|
||||
The genetic algorithm is specifically designed for word substitutions.
|
||||
"""
|
||||
return consists_of_word_swaps(transformation)
|
||||
|
||||
def extra_repr_keys(self):
|
||||
return ['pop_size', 'max_iters', 'temp', 'give_up_if_no_improvement']
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import numpy as np
|
||||
|
||||
from textattack.search_methods import SearchMethod
|
||||
from textattack.shared.validators import is_word_swap
|
||||
from textattack.shared.validators import consists_of_word_swaps
|
||||
|
||||
class GreedyWordSwapWIR(SearchMethod):
|
||||
"""
|
||||
@@ -30,10 +30,7 @@ class GreedyWordSwapWIR(SearchMethod):
|
||||
except KeyError:
|
||||
raise KeyError(f'Word Importance Ranking method {wir_method} not recognized.')
|
||||
|
||||
def check_transformation_compatibility(self, transformation):
|
||||
return transformation.consists_of(is_word_swap)
|
||||
|
||||
def __call__(self, initial_result):
|
||||
def _perform_search(self, initial_result):
|
||||
tokenized_text = initial_result.tokenized_text
|
||||
cur_result = initial_result
|
||||
|
||||
@@ -90,5 +87,11 @@ class GreedyWordSwapWIR(SearchMethod):
|
||||
return results[0]
|
||||
return initial_result
|
||||
|
||||
def check_transformation_compatibility(self, transformation):
|
||||
"""
|
||||
Since it ranks words by their importance, GreedyWordSwapWIR is limited to word swaps transformations.
|
||||
"""
|
||||
return consists_of_word_swaps(transformation)
|
||||
|
||||
def extra_repr_keys(self):
|
||||
return ['wir_method']
|
||||
|
||||
@@ -3,15 +3,26 @@ from textattack.shared.utils import default_class_repr
|
||||
class SearchMethod:
|
||||
"""
|
||||
This is an abstract class that contains main helper functionality for
|
||||
search methods.
|
||||
search methods. A search method is a strategy for applying transformations
|
||||
until the goal is met or the search is exhausted.
|
||||
|
||||
"""
|
||||
def __call__(self, intial_result):
|
||||
def __call__(self, initial_result):
|
||||
"""
|
||||
Perturbs `tokenized_text` from intial_result until goal is reached
|
||||
Ensures access to necessary functions, then performs search.
|
||||
"""
|
||||
if not hasattr(self, 'get_transformations'):
|
||||
raise AttributeError('Search Method must have access to get_transformations method')
|
||||
if not hasattr(self, 'get_goal_results'):
|
||||
raise AttributeError('Search Method must have access to get_goal_results method')
|
||||
return self._perform_search(initial_result)
|
||||
|
||||
def _perform_search(self, initial_result):
|
||||
"""
|
||||
Perturbs `tokenized_text` from intial_result until goal is reached or search is exhausted.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def check_transformation_compatibility(self, transformation):
|
||||
return True
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ class Attack:
|
||||
An attack generates adversarial examples on text.
|
||||
|
||||
This is an abstract class that contains main helper functionality for
|
||||
attacks. An attack is comprised of a search method, which makes use of
|
||||
a goal function, a transformation, and a set of one or more linguistic
|
||||
constraints that successful examples must meet.
|
||||
attacks. An attack is comprised of a search method, goal function,
|
||||
a transformation, and a set of one or more linguistic constraints that
|
||||
successful examples must meet.
|
||||
|
||||
Args:
|
||||
goal_function: A function for determining how well a perturbation is doing at achieving the attack's goal.
|
||||
@@ -67,7 +67,7 @@ class Attack:
|
||||
transformation:
|
||||
text:
|
||||
original text (:obj:`type`, optional): Defaults to None.
|
||||
apply_constraints: Whether or not to apply (non-modification) constraints
|
||||
apply_constraints: Whether or not to apply non-modification constraints
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -213,7 +213,7 @@ def get_args():
|
||||
search_choices = ', '.join(SEARCH_CLASS_NAMES.keys())
|
||||
attack_group.add_argument('--search', '-s', '--search_method', type=str,
|
||||
required=False, default='greedy-word-wir',
|
||||
help=f'The search_method to use. choices: {search_choices}')
|
||||
help=f'The search method to use. choices: {search_choices}')
|
||||
|
||||
attack_group.add_argument('--recipe', '-r', type=str, required=False, default=None,
|
||||
help='full attack recipe (overrides provided goal function, transformation & constraints)',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import torch
|
||||
from copy import deepcopy
|
||||
from .utils import get_device, words_from_text
|
||||
from nltk.corpus import stopwords
|
||||
|
||||
class TokenizedText:
|
||||
|
||||
@@ -32,10 +31,7 @@ class TokenizedText:
|
||||
self.words = words_from_text(text, words_to_ignore=[TokenizedText.SPLIT_TOKEN])
|
||||
self.text = text
|
||||
self.attack_attrs = attack_attrs
|
||||
if 'modified_indices' not in attack_attrs:
|
||||
attack_attrs['modified_indices'] = set()
|
||||
if 'stopword_indices' not in attack_attrs:
|
||||
attack_attrs['stopword_indices'] = set()
|
||||
self.attack_attrs.setdefault('modified_indices', set())
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.text == other.text) and (self.attack_attrs == other.attack_attrs)
|
||||
@@ -43,16 +39,6 @@ class TokenizedText:
|
||||
def __hash__(self):
|
||||
return hash(self.text)
|
||||
|
||||
def identify_stopwords(self, textfooler_stopwords=False):
|
||||
self.stopwords = set(stopwords.words('english'))
|
||||
if textfooler_stopwords:
|
||||
self.stopwords = set(['a', 'about', 'above', 'across', 'after', 'afterwards', 'again', 'against', 'ain', 'all', 'almost', 'alone', 'along', 'already', 'also', 'although', 'am', 'among', 'amongst', 'an', 'and', 'another', 'any', 'anyhow', 'anyone', 'anything', 'anyway', 'anywhere', 'are', 'aren', "aren't", 'around', 'as', 'at', 'back', 'been', 'before', 'beforehand', 'behind', 'being', 'below', 'beside', 'besides', 'between', 'beyond', 'both', 'but', 'by', 'can', 'cannot', 'could', 'couldn', "couldn't", 'd', 'didn', "didn't", 'doesn', "doesn't", 'don', "don't", 'down', 'due', 'during', 'either', 'else', 'elsewhere', 'empty', 'enough', 'even', 'ever', 'everyone', 'everything', 'everywhere', 'except', 'first', 'for', 'former', 'formerly', 'from', 'hadn', "hadn't", 'hasn', "hasn't", 'haven', "haven't", 'he', 'hence', 'her', 'here', 'hereafter', 'hereby', 'herein', 'hereupon', 'hers', 'herself', 'him', 'himself', 'his', 'how', 'however', 'hundred', 'i', 'if', 'in', 'indeed', 'into', 'is', 'isn', "isn't", 'it', "it's", 'its', 'itself', 'just', 'latter', 'latterly', 'least', 'll', 'may', 'me', 'meanwhile', 'mightn', "mightn't", 'mine', 'more', 'moreover', 'most', 'mostly', 'must', 'mustn', "mustn't", 'my', 'myself', 'namely', 'needn', "needn't", 'neither', 'never', 'nevertheless', 'next', 'no', 'nobody', 'none', 'noone', 'nor', 'not', 'nothing', 'now', 'nowhere', 'o', 'of', 'off', 'on', 'once', 'one', 'only', 'onto', 'or', 'other', 'others', 'otherwise', 'our', 'ours', 'ourselves', 'out', 'over', 'per', 'please','s', 'same', 'shan', "shan't", 'she', "she's", "should've", 'shouldn', "shouldn't", 'somehow', 'something', 'sometime', 'somewhere', 'such', 't', 'than', 'that', "that'll", 'the', 'their', 'theirs', 'them', 'themselves', 'then', 'thence', 'there', 'thereafter', 'thereby', 'therefore', 'therein', 'thereupon', 'these', 'they','this', 'those', 'through', 'throughout', 'thru', 'thus', 'to', 'too','toward', 'towards', 'under', 'unless', 'until', 'up', 'upon', 'used', 've', 'was', 'wasn', "wasn't", 'we', 'were', 'weren', "weren't", 'what', 'whatever', 'when', 'whence', 'whenever', 'where', 'whereafter', 'whereas', 'whereby', 'wherein', 'whereupon', 'wherever', 'whether', 'which', 'while', 'whither', 'who', 'whoever', 'whole', 'whom', 'whose', 'why', 'with', 'within', 'without', 'won', "won't", 'would', 'wouldn', "wouldn't", 'y', 'yet', 'you', "you'd", "you'll", "you're", "you've", 'your', 'yours', 'yourself', 'yourselves'])
|
||||
|
||||
self.attack_attrs['stopword_indices'] = set()
|
||||
for i, word in enumerate(self.words):
|
||||
if word.lower() in self.stopwords:
|
||||
self.attack_attrs['stopword_indices'].add(i)
|
||||
|
||||
def delete_tensors(self):
|
||||
""" Delete tensors to clear up GPU space. Only should be called
|
||||
once the TokenizedText is only needed to display.
|
||||
@@ -163,7 +149,6 @@ class TokenizedText:
|
||||
final_sentence = ''
|
||||
text = self.text
|
||||
new_attack_attrs = dict()
|
||||
new_attack_attrs['stopword_indices'] = set()
|
||||
new_attack_attrs['modified_indices'] = set()
|
||||
new_attack_attrs['newly_modified_indices'] = set()
|
||||
new_i = 0
|
||||
@@ -174,8 +159,6 @@ class TokenizedText:
|
||||
final_sentence += text[:word_start]
|
||||
final_sentence += adv_word
|
||||
text = text[word_end:]
|
||||
if i in self.attack_attrs['stopword_indices']:
|
||||
new_attack_attrs['stopword_indices'].add(new_i)
|
||||
if i in self.attack_attrs['modified_indices'] or input_word != adv_word:
|
||||
new_attack_attrs['modified_indices'].add(new_i)
|
||||
if input_word != adv_word:
|
||||
|
||||
@@ -74,6 +74,25 @@ def validate_model_gradient_word_swap_compatibility(model):
|
||||
else:
|
||||
raise ValueError(f'Cannot perform GradientBasedWordSwap on model {model}.')
|
||||
|
||||
def is_word_swap(transformation):
|
||||
def consists_of(transformation, transformation_classes):
|
||||
"""
|
||||
Determines if the transofrmation is or consists only of instances of a class in `transformation_classes`
|
||||
"""
|
||||
from textattack.transformations import CompositeTransformation
|
||||
if isinstance(transformation, CompositeTransformation):
|
||||
for t in transformation.transformations:
|
||||
if not consists_of(t, transformation_classes):
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
for transformation_class in transformation_classes:
|
||||
if isinstance(transformation, transformation_class):
|
||||
return True
|
||||
return False
|
||||
|
||||
def consists_of_word_swaps(transformation):
|
||||
"""
|
||||
Determines if the transofmration is a word swap or consists of only word swaps
|
||||
"""
|
||||
from textattack.transformations import WordSwap, WordSwapGradientBased
|
||||
return isinstance(transformation, WordSwap) or isinstance(transformation, WordSwapGradientBased)
|
||||
return consists_of(transformation, [WordSwap, WordSwapGradientBased])
|
||||
|
||||
@@ -16,9 +16,3 @@ class CompositeTransformation(Transformation):
|
||||
transformation(*args, **kwargs)
|
||||
)
|
||||
return list(new_tokenized_texts)
|
||||
|
||||
def consists_of(self, validator):
|
||||
for transformation in self.transformations:
|
||||
if not transformation.consists_of(validator):
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -26,7 +26,4 @@ class Transformation:
|
||||
def extra_repr_keys(self):
|
||||
return []
|
||||
|
||||
def consists_of(self, validator):
|
||||
return validator(self)
|
||||
|
||||
__repr__ = __str__ = default_class_repr
|
||||
|
||||
Reference in New Issue
Block a user